Skip to main content

logicaffeine_proof/
polycalc_zm.rs

1//! Nullstellensatz over the **rings `ℤ/m`** — composite moduli, zero divisors and all, at arbitrary
2//! degree. The characteristic axis ([`crate::polycalc_gfp`]) covered every *field*; this module covers
3//! what is left of "GF(N) for any N": the moduli where no field exists (`ℤ/6`) and the prime powers
4//! where the ring is not the field (`ℤ/4`, with its nilpotent `2`).
5//!
6//! Three facts shape the module:
7//!
8//! - **Completeness needs no division.** The partition-of-unity construction — the engine of "no
9//!   finite formula is structureless" — only ever adds, subtracts, and multiplies: the atom
10//!   `(1 − x) + x = 1` is a ring identity, the signed point indicators cancel by additive inverses,
11//!   and multilinear representations of cube functions are unique over any commutative ring (the
12//!   Möbius transform is unipotent). So **every UNSAT formula has a degree-`≤ n` certificate over
13//!   every `ℤ/m`, `m ≥ 2`** — hardness-as-structurelessness has *no witness at any finite `n` over
14//!   any modulus*. The honest boundary is unchanged: the certificate lives in the `2ⁿ` basis, and the
15//!   asymptotic cost-growth statements (the certified degree lower bounds) are exactly the results
16//!   pointing the other way.
17//! - **Refutation needs real ring linear algebra.** Gaussian elimination dies without inverses, so
18//!   span membership over `ℤ/m` is decided by a Howell-style echelon: gcd pivoting (every pivot
19//!   normalized, by a unit, to a divisor of `m`) plus **annihilator completion** (each pivot row
20//!   spawns its `(m/d)`-multiple, which lives strictly below it) — validated against an exhaustive
21//!   all-combinations oracle on small systems and against the field engine at prime `m`.
22//! - **Composite moduli intersect, they do not add.** For squarefree `m`, CRT splits the coefficient
23//!   ring, so a `ℤ/6` refutation is exactly a `GF(2)` refutation *and* a `GF(3)` refutation at the
24//!   same degree — the composite ring is the *conjunction* of its prime parts, weaker than either.
25//!   Dual witnesses generalize with the normalization `L(1) ≠ 0` (a zero divisor is fine): a prime
26//!   witness lifts to a ring witness by scaling with `m/p`.
27
28use crate::cdcl::Lit;
29pub use crate::polycalc::Mono;
30use std::collections::BTreeMap;
31
32/// A multilinear polynomial over `ℤ/m`: monomial → nonzero coefficient in `1..m` (absent = 0).
33pub type ZmPoly = BTreeMap<Mono, u64>;
34
35#[inline]
36fn zm_add(m: u64, a: u64, b: u64) -> u64 {
37    (a % m + b % m) % m
38}
39#[inline]
40fn zm_neg(m: u64, a: u64) -> u64 {
41    (m - a % m) % m
42}
43#[inline]
44fn zm_mul(m: u64, a: u64, b: u64) -> u64 {
45    (a % m) * (b % m) % m
46}
47
48fn gcd(a: u64, b: u64) -> u64 {
49    let (mut a, mut b) = (a, b);
50    while b != 0 {
51        let t = a % b;
52        a = b;
53        b = t;
54    }
55    a
56}
57
58/// Extended gcd over the integers: `(g, x, y)` with `x·a + y·b = g = gcd(a, b)`.
59fn egcd(a: i128, b: i128) -> (i128, i128, i128) {
60    if b == 0 {
61        (a, 1, 0)
62    } else {
63        let (g, x, y) = egcd(b, a % b);
64        (g, y, x - (a / b) * y)
65    }
66}
67
68/// A **unit** `u` of `ℤ/m` with `u·a ≡ gcd(a, m) (mod m)` — the scaling that normalizes a pivot to a
69/// divisor of `m` without leaving the ring's unit group (the standard Howell "stabilization"). For
70/// nonzero `a mod m`: write `d = gcd(a, m)`; then `a/d` is invertible mod `m/d`, and some lift
71/// `u ≡ (a/d)⁻¹ (mod m/d)` coprime to `m` exists among `u₀ + k·(m/d)`, `k < d`.
72fn unit_scaling(a: u64, m: u64) -> u64 {
73    let a = a % m;
74    debug_assert!(a != 0, "the zero pivot has no scaling");
75    let d = gcd(a, m);
76    let (ap, mp) = (a / d, m / d);
77    if mp == 1 {
78        return 1; // a ≡ 0 case is excluded; mp = 1 cannot occur for nonzero a < m
79    }
80    let (_, x, _) = egcd(ap as i128, mp as i128);
81    let u0 = ((x % mp as i128 + mp as i128) % mp as i128) as u64;
82    for k in 0..d {
83        let u = u0 + k * mp;
84        if u != 0 && gcd(u, m) == 1 {
85            return u;
86        }
87    }
88    unreachable!("a unit lift of (a/d)⁻¹ mod m/d always exists below m")
89}
90
91fn add_term(m: u64, p: &mut ZmPoly, mono: Mono, c: u64) {
92    let c = c % m;
93    if c == 0 {
94        return;
95    }
96    let e = p.entry(mono).or_insert(0);
97    *e = zm_add(m, *e, c);
98    if *e == 0 {
99        p.remove(&mono);
100    }
101}
102
103fn poly_mul(m: u64, a: &ZmPoly, b: &ZmPoly) -> ZmPoly {
104    let mut r = ZmPoly::new();
105    for (&ma, &ca) in a {
106        for (&mb, &cb) in b {
107            add_term(m, &mut r, ma | mb, zm_mul(m, ca, cb));
108        }
109    }
110    r
111}
112
113fn poly_mul_mono(m: u64, p: &ZmPoly, mono: Mono) -> ZmPoly {
114    let mut r = ZmPoly::new();
115    for (&t, &c) in p {
116        add_term(m, &mut r, t | mono, c);
117    }
118    r
119}
120
121/// The degree of a multilinear `ℤ/m` polynomial (0 for the zero polynomial).
122pub fn zm_poly_degree(p: &ZmPoly) -> usize {
123    p.keys().map(|mo| mo.count_ones() as usize).max().unwrap_or(0)
124}
125
126/// The clause polynomial over `ℤ/m` — the signed false-indicator, exactly as over the fields: a
127/// positive literal contributes `1 − x` (over `ℤ/6`: `1 + 5x`), a negative one contributes `x`; the
128/// product is `1` on falsifying corners and `0` elsewhere over every commutative ring.
129pub fn clause_polynomial_zm(m: u64, clause: &[Lit]) -> ZmPoly {
130    let mut p: ZmPoly = [(0u64, 1u64)].into_iter().collect();
131    for l in clause {
132        let bit = 1u64 << l.var();
133        let indicator: ZmPoly = if l.is_positive() {
134            [(0u64, 1u64), (bit, zm_neg(m, 1))].into_iter().collect()
135        } else {
136            [(bit, 1u64)].into_iter().collect()
137        };
138        p = poly_mul(m, &p, &indicator);
139    }
140    p
141}
142
143/// The signed point indicator `δ_a` over `ℤ/m` — only addition and negation, so it is ring-generic.
144fn point_indicator_zm(m: u64, a: u64, num_vars: usize) -> ZmPoly {
145    let mask = (1u64 << num_vars).wrapping_sub(1);
146    let ones = a & mask;
147    let zeros = !a & mask;
148    let mut p = ZmPoly::new();
149    let mut sub = zeros;
150    loop {
151        let sign = if sub.count_ones() % 2 == 0 { 1 } else { zm_neg(m, 1) };
152        p.insert(ones | sub, sign);
153        if sub == 0 {
154            break;
155        }
156        sub = (sub - 1) & zeros;
157    }
158    p
159}
160
161/// The partition-of-unity atom `(1 − x_v) + x_v` — the constant `1` in **every commutative ring**,
162/// zero divisors notwithstanding: the identity needs only additive inverses.
163pub fn pou_atom_zm(m: u64, v: usize) -> ZmPoly {
164    let mut atom: ZmPoly = [(0u64, 1u64), (1u64 << v, zm_neg(m, 1))].into_iter().collect();
165    add_term(m, &mut atom, 1u64 << v, 1);
166    atom
167}
168
169/// The partition of unity `Σ_a δ_a` over `ℤ/m` — the constant `1` at every `n` over every modulus.
170pub fn partition_of_unity_zm(m: u64, n: usize) -> ZmPoly {
171    let mut sum = ZmPoly::new();
172    for a in 0..(1u64 << n) {
173        for (mo, c) in point_indicator_zm(m, a, n) {
174            add_term(m, &mut sum, mo, c);
175        }
176    }
177    sum
178}
179
180/// A constructive Nullstellensatz certificate over `ℤ/m`: `Σ_C p_C · g_C = 1` in the multilinear ring.
181#[derive(Clone, Debug)]
182pub struct NsCertificateZm {
183    modulus: u64,
184    num_vars: usize,
185    coeffs: Vec<ZmPoly>,
186}
187
188impl NsCertificateZm {
189    pub fn modulus(&self) -> u64 {
190        self.modulus
191    }
192
193    pub fn num_vars(&self) -> usize {
194        self.num_vars
195    }
196
197    /// The maximum monomial degree among the coefficient polynomials.
198    pub fn degree(&self) -> usize {
199        self.coeffs.iter().map(zm_poly_degree).max().unwrap_or(0)
200    }
201
202    /// The certificate's SIZE: the total nonzero-monomial count across the coefficient
203    /// polynomials — the honest measure of the `2ⁿ`-basis cost the existence pole pays.
204    pub fn coeff_monomial_count(&self) -> usize {
205        self.coeffs.iter().map(|g| g.len()).sum()
206    }
207
208    /// **Re-check against the original clauses** (zero trust), twice: `Σ_C p_C · g_C = 1` recomputed in
209    /// the multilinear ring over `ℤ/m`, then the engine-independent corner evaluation (`≤ 20` vars).
210    /// Fails closed on a clause-count mismatch.
211    pub fn verify(&self, clauses: &[Vec<Lit>]) -> bool {
212        if self.coeffs.len() != clauses.len() {
213            return false;
214        }
215        let m = self.modulus;
216        let mut sum = ZmPoly::new();
217        for (c, g) in clauses.iter().zip(&self.coeffs) {
218            if g.is_empty() {
219                continue;
220            }
221            for (mo, co) in poly_mul(m, &clause_polynomial_zm(m, c), g) {
222                add_term(m, &mut sum, mo, co);
223            }
224        }
225        if !(sum.len() == 1 && sum.get(&0u64) == Some(&1)) {
226            return false;
227        }
228        if self.num_vars <= 20 {
229            let eval = |p: &ZmPoly, a: u64| -> u64 {
230                p.iter()
231                    .fold(0u64, |acc, (&mo, &c)| if mo & !a == 0 { zm_add(m, acc, c) } else { acc })
232            };
233            for a in 0u64..(1u64 << self.num_vars) {
234                let total = clauses.iter().zip(&self.coeffs).fold(0u64, |acc, (c, g)| {
235                    zm_add(m, acc, zm_mul(m, eval(&clause_polynomial_zm(m, c), a), eval(g, a)))
236                });
237                if total != 1 {
238                    return false;
239                }
240            }
241        }
242        true
243    }
244}
245
246/// **The uniform completeness construction over `ℤ/m`** — partition-of-unity charging, valid over any
247/// commutative ring because it never divides. Returns a constructive certificate proving UNSAT or a
248/// satisfying assignment proving SAT: a *total, certifying* decision over every modulus `m ≥ 2` — so
249/// "structureless" (no certificate at any degree `≤ n`) has **no witness among finite formulas over
250/// any `ℤ/m`**. The honest cost is unchanged: the certificate lives in the `2ⁿ` monomial basis
251/// (existence, not efficiency), and `num_vars ≤ 20` bounds the explicit-corner construction.
252pub fn build_ns_certificate_zm(
253    m: u64,
254    num_vars: usize,
255    clauses: &[Vec<Lit>],
256) -> Result<NsCertificateZm, Vec<bool>> {
257    assert!(m >= 2, "a modulus needs at least two residues");
258    assert!(num_vars <= 20, "the explicit-corner construction is bounded to num_vars ≤ 20");
259    let mut coeffs: Vec<ZmPoly> = vec![ZmPoly::new(); clauses.len()];
260    for a in 0u64..(1u64 << num_vars) {
261        let sel = clauses
262            .iter()
263            .position(|c| !c.iter().any(|l| ((a >> l.var()) & 1 == 1) == l.is_positive()));
264        match sel {
265            None => return Err((0..num_vars).map(|i| (a >> i) & 1 == 1).collect()),
266            Some(ci) => {
267                for (mo, c) in point_indicator_zm(m, a, num_vars) {
268                    add_term(m, &mut coeffs[ci], mo, c);
269                }
270            }
271        }
272    }
273    Ok(NsCertificateZm { modulus: m, num_vars, coeffs })
274}
275
276/// A **Howell-style echelon over `ℤ/m`** — the ring replacement for Gaussian elimination. Invariants:
277/// every stored pivot row is unit-normalized so its leading entry is a **divisor of `m`**, and every
278/// pivot's **annihilator multiple** `(m/d)·row` (which vanishes at the pivot column) is recursively
279/// inserted, so successive leading-column reduction decides span membership exactly — including the
280/// zero-divisor moves Gaussian elimination cannot make.
281struct ZmEchelon {
282    m: u64,
283    pivots: std::collections::HashMap<usize, Vec<u64>>,
284}
285
286fn row_lead(row: &[u64]) -> Option<usize> {
287    row.iter().rposition(|&c| c != 0)
288}
289
290impl ZmEchelon {
291    fn new(m: u64) -> Self {
292        ZmEchelon { m, pivots: std::collections::HashMap::new() }
293    }
294
295    fn insert(&mut self, mut row: Vec<u64>) {
296        let m = self.m;
297        loop {
298            let Some(c) = row_lead(&row) else { return };
299            let Some(pivot) = self.pivots.get(&c) else {
300                // Fresh pivot: unit-normalize the leading entry to gcd(row[c], m), a divisor of m.
301                let u = unit_scaling(row[c], m);
302                for v in 0..=c {
303                    row[v] = zm_mul(m, row[v], u);
304                }
305                let d = row[c];
306                debug_assert!(m % d == 0, "a normalized pivot divides the modulus");
307                let ann: Vec<u64> = row.iter().map(|&x| zm_mul(m, x, m / d)).collect();
308                self.pivots.insert(c, row);
309                if ann.iter().any(|&x| x != 0) {
310                    self.insert(ann); // the annihilator lives strictly below column c
311                }
312                return;
313            };
314            let d = pivot[c];
315            let a = row[c];
316            if a % d == 0 {
317                // The pivot's ideal absorbs the entry: reduce and continue below.
318                let f = a / d;
319                let pivot = pivot.clone();
320                for v in 0..=c {
321                    row[v] = zm_add(m, row[v], zm_neg(m, zm_mul(m, pivot[v], f)));
322                }
323            } else {
324                // Zero-divisor combine: replace the pivot by the gcd row, reinsert both remainders.
325                let (g0, x, y) = egcd(d as i128, a as i128);
326                let g = g0 as u64; // g = gcd(d, a) divides d, hence divides m
327                let (s, t) = (
328                    ((x % m as i128 + m as i128) % m as i128) as u64,
329                    ((y % m as i128 + m as i128) % m as i128) as u64,
330                );
331                let pivot = pivot.clone();
332                let comb: Vec<u64> = (0..=c)
333                    .map(|v| zm_add(m, zm_mul(m, s, pivot[v]), zm_mul(m, t, row[v])))
334                    .collect();
335                debug_assert_eq!(comb[c], g % m, "the combined pivot leads with the gcd");
336                let rem_p: Vec<u64> = (0..=c)
337                    .map(|v| zm_add(m, pivot[v], zm_neg(m, zm_mul(m, comb[v], d / g))))
338                    .collect();
339                let rem_r: Vec<u64> = (0..=c)
340                    .map(|v| zm_add(m, row[v], zm_neg(m, zm_mul(m, comb[v], a / g))))
341                    .collect();
342                let ann: Vec<u64> = comb.iter().map(|&v| zm_mul(m, v, m / g)).collect();
343                self.pivots.insert(c, comb);
344                if rem_p.iter().any(|&v| v != 0) {
345                    self.insert(rem_p);
346                }
347                if ann.iter().any(|&v| v != 0) {
348                    self.insert(ann);
349                }
350                row = rem_r;
351            }
352        }
353    }
354
355    fn contains(&self, mut target: Vec<u64>) -> bool {
356        let m = self.m;
357        while let Some(c) = row_lead(&target) {
358            let Some(pivot) = self.pivots.get(&c) else { return false };
359            let d = pivot[c];
360            if target[c] % d != 0 {
361                return false; // the pivot's ideal (d) ∌ target[c]
362            }
363            let f = target[c] / d;
364            for v in 0..=c {
365                target[v] = zm_add(m, target[v], zm_neg(m, zm_mul(m, pivot[v], f)));
366            }
367        }
368        true
369    }
370}
371
372/// Does a **degree-`d` Nullstellensatz refutation over the ring `ℤ/m`** exist for a polynomial
373/// generator system — is `1` in the `ℤ/m`-span of `{ mono·g : deg ≤ d }`? Decided by the Howell
374/// echelon (validated against the exhaustive all-combinations oracle and against the field engine at
375/// prime `m`). Degree-bounded enumeration — `num_vars ≤ 63`.
376pub fn ns_refutes_polys_zm(m: u64, num_vars: usize, gens: &[ZmPoly], degree: usize) -> bool {
377    let basis = crate::polycalc::monomials_up_to_degree(num_vars, degree);
378    let index: std::collections::HashMap<Mono, usize> =
379        basis.iter().enumerate().map(|(i, &mo)| (mo, i)).collect();
380    let nb = basis.len();
381    let mut ech = ZmEchelon::new(m);
382    for g in gens {
383        if g.is_empty() {
384            continue;
385        }
386        for &mo in &basis {
387            let prod = poly_mul_mono(m, g, mo);
388            if !prod.is_empty() && zm_poly_degree(&prod) <= degree {
389                let mut row = vec![0u64; nb];
390                for (t, c) in prod {
391                    row[index[&t]] = c;
392                }
393                ech.insert(row);
394            }
395        }
396    }
397    let mut target = vec![0u64; nb];
398    target[index[&0u64]] = 1;
399    ech.contains(target)
400}
401
402/// [`ns_refutes_polys_zm`] for a CNF (signed clause false-indicators as generators). An empty clause
403/// is `1 = 0` outright.
404pub fn ns_refutes_zm(m: u64, num_vars: usize, clauses: &[Vec<Lit>], degree: usize) -> bool {
405    if clauses.iter().any(|c| c.is_empty()) {
406        return true;
407    }
408    let gens: Vec<ZmPoly> = clauses.iter().map(|c| clause_polynomial_zm(m, c)).collect();
409    ns_refutes_polys_zm(m, num_vars, &gens, degree)
410}
411
412/// Re-check a **ring pseudo-expectation** (zero trust): `L(1) ≢ 0 (mod m)` — over a ring the
413/// normalization cannot demand a unit, and a zero-divisor value like `m/p` is honest — and
414/// `⟨L, mono·g⟩ ≡ 0` for every admitted generator product. Soundness is one line and field-free: a
415/// degree-`d` refutation `1 = Σ λ·prod` would force `L(1) = Σ λ·L(prod) = 0`. So `true` certifies
416/// that no degree-`d` refutation over `ℤ/m` exists. Degree-bounded enumeration — `num_vars ≤ 63`.
417pub fn check_ns_lower_bound_polys_zm(
418    m: u64,
419    num_vars: usize,
420    gens: &[ZmPoly],
421    degree: usize,
422    witness: &[(Mono, u64)],
423) -> bool {
424    let mut l: BTreeMap<Mono, u64> = BTreeMap::new();
425    for &(mo, v) in witness {
426        add_term(m, &mut l, mo, v);
427    }
428    if l.get(&0u64).copied().unwrap_or(0) == 0 {
429        return false; // L(1) must be nonzero (a unit is not required — zero divisors are honest)
430    }
431    let value = |mo: &Mono| l.get(mo).copied().unwrap_or(0);
432    for g in gens {
433        if g.is_empty() {
434            continue;
435        }
436        for &mo in &crate::polycalc::monomials_up_to_degree(num_vars, degree) {
437            let prod = poly_mul_mono(m, g, mo);
438            if !prod.is_empty() && zm_poly_degree(&prod) <= degree {
439                let pairing =
440                    prod.iter().fold(0u64, |acc, (t, &c)| zm_add(m, acc, zm_mul(m, c, value(t))));
441                if pairing != 0 {
442                    return false;
443                }
444            }
445        }
446    }
447    true
448}
449
450/// [`check_ns_lower_bound_polys_zm`] for a CNF. An empty clause admits no lower bound at any degree.
451pub fn check_ns_lower_bound_zm(
452    m: u64,
453    num_vars: usize,
454    clauses: &[Vec<Lit>],
455    degree: usize,
456    witness: &[(Mono, u64)],
457) -> bool {
458    if clauses.iter().any(|c| c.is_empty()) {
459        return false;
460    }
461    let gens: Vec<ZmPoly> = clauses.iter().map(|c| clause_polynomial_zm(m, c)).collect();
462    check_ns_lower_bound_polys_zm(m, num_vars, &gens, degree, witness)
463}
464
465/// **Lift a prime-field pseudo-expectation to the ring**: for `p | m`, the functional
466/// `L(x) = (m/p) · L_p(x mod p)` satisfies every `ℤ/m` constraint — `(m/p)·c·v` depends on `c` only
467/// mod `p`, so each pairing is `(m/p) · ⟨L_p, prod mod p⟩ = 0` — and carries the zero-divisor
468/// normalization `L(1) = m/p ≠ 0`. One prime witness therefore certifies the ring lower bound: the
469/// composite modulus inherits the hardness of **each** of its prime parts (the witness face of the
470/// CRT conjunction).
471pub fn lift_prime_witness_to_zm(m: u64, p: u64, witness_p: &[(Mono, u64)]) -> Vec<(Mono, u64)> {
472    assert!(p >= 2 && m % p == 0, "the lift needs a prime divisor of the modulus");
473    let scale = m / p;
474    witness_p
475        .iter()
476        .filter_map(|&(mo, v)| {
477            let lifted = zm_mul(m, scale, v % p);
478            (lifted != 0).then_some((mo, lifted))
479        })
480        .collect()
481}
482
483#[cfg(test)]
484mod tests {
485    use super::*;
486
487    /// A deterministic LCG for reproducible fuzz corpora inside tests (no `rand`, no wall clock).
488    fn lcg(state: &mut u64) -> u64 {
489        *state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
490        *state >> 33
491    }
492
493    fn eval_at(m: u64, p: &ZmPoly, a: u64) -> u64 {
494        p.iter().fold(0u64, |acc, (&mo, &c)| if mo & !a == 0 { zm_add(m, acc, c) } else { acc })
495    }
496
497    fn falsifies(clause: &[Lit], a: u64) -> bool {
498        !clause.iter().any(|l| ((a >> l.var()) & 1 == 1) == l.is_positive())
499    }
500
501    fn sat(num_vars: usize, clauses: &[Vec<Lit>]) -> bool {
502        (0u64..(1u64 << num_vars)).any(|x| {
503            clauses.iter().all(|c| c.iter().any(|l| ((x >> l.var()) & 1 != 0) == l.is_positive()))
504        })
505    }
506
507    /// An explicit finite abelian group: element ids `0..n` with an addition table; `0` is the zero.
508    struct AddGroup {
509        name: &'static str,
510        n: usize,
511        add: Vec<Vec<usize>>,
512    }
513
514    fn cyclic_group(n: usize, name: &'static str) -> AddGroup {
515        let add = (0..n).map(|a| (0..n).map(|b| (a + b) % n).collect()).collect();
516        AddGroup { name, n, add }
517    }
518
519    /// `ℤ/p × ℤ/q` with the pair `(x₁, x₂)` encoded as `x₁·q + x₂`.
520    fn product_group(p: usize, q: usize, name: &'static str) -> AddGroup {
521        let n = p * q;
522        let add = (0..n)
523            .map(|a| {
524                (0..n)
525                    .map(|b| ((a / q + b / q) % p) * q + ((a % q + b % q) % q))
526                    .collect()
527            })
528            .collect();
529        AddGroup { name, n, add }
530    }
531
532    fn scalar_mul(g: &AddGroup, k: usize, x: usize) -> usize {
533        (0..k).fold(0, |acc, _| g.add[acc][x])
534    }
535
536    fn add_order(g: &AddGroup, x: usize) -> usize {
537        let mut acc = x;
538        let mut k = 1;
539        while acc != 0 {
540            acc = g.add[acc][x];
541            k += 1;
542        }
543        k
544    }
545
546    /// The first field axiom a candidate `(add, mul)` violates, or `None` if it is a field. The
547    /// checks re-verify even what the construction guarantees (distributivity) — zero trust in the
548    /// composer itself.
549    fn field_axiom_failure(g: &AddGroup, mul: &[Vec<usize>]) -> Option<&'static str> {
550        let n = g.n;
551        for a in 0..n {
552            for b in 0..n {
553                for c in 0..n {
554                    if mul[a][g.add[b][c]] != g.add[mul[a][b]][mul[a][c]]
555                        || mul[g.add[a][b]][c] != g.add[mul[a][c]][mul[b][c]]
556                    {
557                        return Some("distributivity");
558                    }
559                }
560            }
561        }
562        for a in 0..n {
563            for b in 0..n {
564                if mul[a][b] != mul[b][a] {
565                    return Some("commutativity");
566                }
567            }
568        }
569        for a in 0..n {
570            for b in 0..n {
571                for c in 0..n {
572                    if mul[mul[a][b]][c] != mul[a][mul[b][c]] {
573                        return Some("associativity");
574                    }
575                }
576            }
577        }
578        let Some(e) = (0..n).find(|&e| (0..n).all(|x| mul[e][x] == x && mul[x][e] == x)) else {
579            return Some("no multiplicative identity");
580        };
581        for a in 1..n {
582            for b in 1..n {
583                if mul[a][b] == 0 {
584                    return Some("a zero divisor");
585                }
586            }
587        }
588        if (1..n).any(|a| !(1..n).any(|b| mul[a][b] == e)) {
589            return Some("a nonzero element without an inverse");
590        }
591        None
592    }
593
594    /// **Fields COMPOSE exactly at prime-power orders — shown by building them, and by exhausting
595    /// the alternatives.** The composer: take every abelian group of order `N` (the additive
596    /// pieces), and every multiplication that distributes over it — for a cyclic group,
597    /// bilinearity forces `a·b = (ab)·u` for a single seed `u = 1·1`; for a product group it is
598    /// determined by the four generator products, each constrained by `ord(eᵢ·x) | gcd(ord(eᵢ),
599    /// ord(x))`. Every candidate table is then judged against the full field axioms (including
600    /// re-verifying distributivity — zero trust in the composer). The verdicts:
601    ///
602    /// - **Order 4**: zero fields on `ℤ/4` (the nilpotent kills it), and the search over
603    ///   `ℤ/2 × ℤ/2` *builds* fields — one is checked isomorphic to the engine's `NsField::Gf4`
604    ///   table. Order **9**: zero on `ℤ/9`, built on `ℤ/3 × ℤ/3`. Composition WORKS: this is
605    ///   exactly how `GF(4)` and `GF(9)` come from their pieces.
606    /// - **Orders 6 and 10**: zero fields, exhaustively — and the obstruction is *composed into
607    ///   every candidate*, not found by luck: on `ℤ/2 × ℤ/3` (resp. `ℤ/2 × ℤ/5`) the cross
608    ///   products `e₁·e₂`, `e₂·e₁` have additive order dividing `gcd(2, 3) = 1`, so bilinearity
609    ///   **forces `e₁·e₂ = 0`** — two nonzero pieces whose product is zero, in every distributive
610    ///   multiplication whatsoever. Composition at coprime-order pieces *is* the CRT ring with its
611    ///   idempotent zero divisors; a field of order 6 or 10 is not un-found, it is impossible, and
612    ///   the census of candidate deaths is printed per order.
613    ///
614    /// This is the classification theorem "finite fields exist exactly at prime-power orders" made
615    /// executable at the orders in question — the same lens that composes `GF(4)` certifies that no
616    /// lens composes `GF(6)`.
617    #[test]
618    fn finite_fields_compose_exactly_at_prime_power_orders() {
619        let mut field_counts: std::collections::BTreeMap<(usize, &'static str), usize> =
620            std::collections::BTreeMap::new();
621        let mut census: std::collections::BTreeMap<(usize, &'static str), std::collections::BTreeMap<&'static str, usize>> =
622            std::collections::BTreeMap::new();
623        let mut gf4_iso_found = false;
624
625        // Cyclic candidates: a·b = (ab)·u, one seed u per candidate.
626        for (order, g) in [
627            (4usize, cyclic_group(4, "ℤ/4")),
628            (6, cyclic_group(6, "ℤ/6")),
629            (9, cyclic_group(9, "ℤ/9")),
630            (10, cyclic_group(10, "ℤ/10")),
631        ] {
632            for u in 0..g.n {
633                let mul: Vec<Vec<usize>> =
634                    (0..g.n).map(|a| (0..g.n).map(|b| scalar_mul(&g, a * b, u)).collect()).collect();
635                match field_axiom_failure(&g, &mul) {
636                    None => *field_counts.entry((order, g.name)).or_insert(0) += 1,
637                    Some(why) => {
638                        *census.entry((order, g.name)).or_default().entry(why).or_insert(0) += 1
639                    }
640                }
641            }
642            field_counts.entry((order, g.name)).or_insert(0);
643        }
644
645        // Product-group candidates: choose the four generator products, each respecting the
646        // bilinear order constraint ord(eᵢ·eⱼ) | gcd(ord(eᵢ), ord(eⱼ)).
647        for (order, p, q, name) in [
648            (4usize, 2usize, 2usize, "ℤ/2×ℤ/2"),
649            (6, 2, 3, "ℤ/2×ℤ/3"),
650            (9, 3, 3, "ℤ/3×ℤ/3"),
651            (10, 2, 5, "ℤ/2×ℤ/5"),
652        ] {
653            let g = product_group(p, q, name);
654            let (e1, e2) = (q, 1); // (1,0) and (0,1) in the pair encoding
655            let allowed = |bound: usize| -> Vec<usize> {
656                (0..g.n).filter(|&x| x == 0 || bound % add_order(&g, x) == 0).collect()
657            };
658            let gpq = gcd(p as u64, q as u64) as usize;
659            let (c11, c12, c21, c22) = (allowed(p), allowed(gpq), allowed(gpq), allowed(q));
660            // The coprime-pieces forcing: when gcd(p, q) = 1, the cross products CANNOT be nonzero.
661            if gpq == 1 {
662                assert_eq!(c12, vec![0], "{name}: bilinearity forces e1·e2 = 0 — the composed zero divisor");
663                assert_eq!(c21, vec![0], "{name}: bilinearity forces e2·e1 = 0");
664            }
665            for &m11 in &c11 {
666                for &m12 in &c12 {
667                    for &m21 in &c21 {
668                        for &m22 in &c22 {
669                            let mul: Vec<Vec<usize>> = (0..g.n)
670                                .map(|a| {
671                                    let (a1, a2) = (a / q, a % q);
672                                    (0..g.n)
673                                        .map(|b| {
674                                            let (b1, b2) = (b / q, b % q);
675                                            let mut acc = scalar_mul(&g, a1 * b1, m11);
676                                            acc = g.add[acc][scalar_mul(&g, a1 * b2, m12)];
677                                            acc = g.add[acc][scalar_mul(&g, a2 * b1, m21)];
678                                            g.add[acc][scalar_mul(&g, a2 * b2, m22)]
679                                        })
680                                        .collect()
681                                })
682                                .collect();
683                            match field_axiom_failure(&g, &mul) {
684                                None => {
685                                    *field_counts.entry((order, name)).or_insert(0) += 1;
686                                    // Cross-anchor: a composed order-4 field is the engine's Gf4.
687                                    if order == 4 && !gf4_iso_found {
688                                        let f = crate::polycalc_gfp::NsField::Gf4;
689                                        let perms: Vec<[usize; 4]> = vec![
690                                            [0, 1, 2, 3], [0, 1, 3, 2], [0, 2, 1, 3],
691                                            [0, 2, 3, 1], [0, 3, 1, 2], [0, 3, 2, 1],
692                                        ];
693                                        gf4_iso_found = perms.iter().any(|phi| {
694                                            (0..4).all(|a| {
695                                                (0..4).all(|b| {
696                                                    phi[g.add[a][b]] as u64
697                                                        == (phi[a] as u64 ^ phi[b] as u64)
698                                                        && phi[mul[a][b]] as u64
699                                                            == f.mul(phi[a] as u64, phi[b] as u64)
700                                                })
701                                            })
702                                        });
703                                    }
704                                }
705                                Some(why) => {
706                                    *census.entry((order, name)).or_default().entry(why).or_insert(0) += 1
707                                }
708                            }
709                        }
710                    }
711                }
712            }
713            field_counts.entry((order, name)).or_insert(0);
714        }
715
716        for ((order, name), reasons) in &census {
717            eprintln!("order {order} on {name}: candidate deaths {reasons:?}");
718        }
719        eprintln!("field structures found: {field_counts:?}");
720
721        // Prime powers: composition BUILDS the fields — on the elementary-abelian pieces only.
722        assert_eq!(field_counts[&(4, "ℤ/4")], 0, "no field on the nilpotent additive group ℤ/4");
723        assert!(field_counts[&(4, "ℤ/2×ℤ/2")] > 0, "GF(4) composes from ℤ/2 × ℤ/2");
724        assert!(gf4_iso_found, "a composed order-4 field is isomorphic to the engine's NsField::Gf4");
725        assert_eq!(field_counts[&(9, "ℤ/9")], 0, "no field on ℤ/9");
726        assert!(field_counts[&(9, "ℤ/3×ℤ/3")] > 0, "GF(9) composes from ℤ/3 × ℤ/3");
727        // Composite non-prime-power: EVERY candidate over EVERY abelian group of the order dies.
728        for (order, presentations) in
729            [(6usize, ["ℤ/6", "ℤ/2×ℤ/3"]), (10, ["ℤ/10", "ℤ/2×ℤ/5"])]
730        {
731            for name in presentations {
732                assert_eq!(
733                    field_counts[&(order, name)],
734                    0,
735                    "GF({order}) is impossible: zero survivors on {name}, exhaustively"
736                );
737            }
738        }
739    }
740
741    /// An odd XOR triangle (`x0 ≠ x1`, `x1 ≠ x2`, `x2 ≠ x0`) — UNSAT by parity; `GF(2)`-easy,
742    /// `GF(3)`-hard: the family that separates the ring from its residue field.
743    fn parity_triangle() -> Vec<Vec<Lit>> {
744        let p = |v: u32| Lit::pos(v);
745        let q = |v: u32| Lit::neg(v);
746        vec![
747            vec![p(0), p(1)], vec![q(0), q(1)],
748            vec![p(1), p(2)], vec![q(1), q(2)],
749            vec![p(2), p(0)], vec![q(2), q(0)],
750        ]
751    }
752
753    /// The shared measurement corpus: the named separating families plus a deterministic random sweep.
754    fn ring_corpus() -> Vec<(&'static str, usize, Vec<Vec<Lit>>)> {
755        let (php3, _) = crate::families::php(3);
756        let (cnt34, _) = crate::families::mod_counting(4, 3);
757        let (cnt23, _) = crate::families::mod_counting(3, 2);
758        let mut corpus: Vec<(&'static str, usize, Vec<Vec<Lit>>)> = vec![
759            ("parity", 3, parity_triangle()),
760            ("php3", php3.num_vars, php3.clauses),
761            ("cnt34", cnt34.num_vars, cnt34.clauses),
762            ("cnt23", cnt23.num_vars, cnt23.clauses),
763        ];
764        let mut seed = 0x0CA7_C047u64;
765        for _ in 0..8 {
766            let nv = 3 + (lcg(&mut seed) % 2) as usize;
767            let nc = 2 * nv + (lcg(&mut seed) % 6) as usize;
768            let clauses: Vec<Vec<Lit>> = (0..nc)
769                .map(|_| {
770                    let width = 2 + (lcg(&mut seed) % 2) as usize;
771                    let mut vars: Vec<u32> = Vec::new();
772                    while vars.len() < width {
773                        let v = (lcg(&mut seed) % nv as u64) as u32;
774                        if !vars.contains(&v) {
775                            vars.push(v);
776                        }
777                    }
778                    vars.iter().map(|&v| Lit::new(v, lcg(&mut seed) & 1 == 1)).collect()
779                })
780                .collect();
781            corpus.push(("rand", nv, clauses));
782        }
783        corpus
784    }
785
786    /// **Composite moduli intersect — they do not add: `ℤ/m`-NS is the CONJUNCTION of its coprime
787    /// parts.** CRT splits the coefficient ring, and a certificate's coefficients split with it (any
788    /// component pair of coefficient choices is realized by one `ℤ/m` choice), so `1` is in the
789    /// `ℤ/6`-span iff it is in the `GF(2)`-span AND the `GF(3)`-span — measured to hold at every
790    /// degree across the whole corpus, and likewise `ℤ/12 = ℤ/4 ∧ ℤ/3` (coprime prime-power
791    /// components, not just squarefree). The consequence, exhibited on named families: the composite
792    /// ring is *weaker* than each of its parts — parity is `GF(2)`-degree-2 but NOT `ℤ/6`-degree-2
793    /// (the `GF(3)` component blocks it), and `NS-degree over ℤ/6 = max` of the component degrees
794    /// (PHP(3): 4, both components 4). So "mod 6 reasoning" buys nothing for Nullstellensatz
795    /// refutation — the Barrington–Beigel–Rudich mod-6 power lives in polynomial *representation* of
796    /// Boolean functions, not in bounded-degree ideal membership, where coefficient freedom makes CRT
797    /// split cleanly.
798    #[test]
799    fn zm_ns_at_coprime_composite_moduli_is_the_conjunction_of_its_parts() {
800        use crate::polycalc_gfp::{ns_refutes_gfp, NsField};
801        for (name, nv, clauses) in &ring_corpus() {
802            for d in 1..=(*nv).min(4) {
803                let g2 = ns_refutes_gfp(NsField::Prime(2), *nv, clauses, d);
804                let g3 = ns_refutes_gfp(NsField::Prime(3), *nv, clauses, d);
805                assert_eq!(
806                    ns_refutes_zm(6, *nv, clauses, d),
807                    g2 && g3,
808                    "{name} n={nv} d={d}: ℤ/6-NS = GF(2)-NS ∧ GF(3)-NS"
809                );
810                assert_eq!(
811                    ns_refutes_zm(12, *nv, clauses, d),
812                    ns_refutes_zm(4, *nv, clauses, d) && ns_refutes_zm(3, *nv, clauses, d),
813                    "{name} n={nv} d={d}: ℤ/12-NS = ℤ/4-NS ∧ ℤ/3-NS"
814                );
815            }
816        }
817        // The named separation: parity is GF(2)-degree-2, but ℤ/6 cannot refute it at 2 — the
818        // composite ring inherits the WEAKNESS of its GF(3) component.
819        let parity = parity_triangle();
820        assert!(ns_refutes_gfp(crate::polycalc_gfp::NsField::Prime(2), 3, &parity, 2));
821        assert!(!ns_refutes_zm(6, 3, &parity, 2), "ℤ/6 is blocked by its GF(3) part on parity");
822        // And the degree law on PHP(3): ℤ/6 degree = max(GF(2) degree, GF(3) degree) = 4.
823        let (php3, _) = crate::families::php(3);
824        assert!(!ns_refutes_zm(6, php3.num_vars, &php3.clauses, 3), "PHP(3): ℤ/6 degree > 3");
825        assert!(ns_refutes_zm(6, php3.num_vars, &php3.clauses, 4), "PHP(3): ℤ/6 degree = 4 = max(4, 4)");
826    }
827
828    /// **A prime witness lifts to a ring witness — with a zero-divisor normalization.** Over a ring
829    /// the pseudo-expectation normalization is `L(1) ≠ 0` (demanding a unit would be dishonest:
830    /// `Hom(M, ℤ/m)` separates points because `ℤ/m` is self-injective, but the value at `1` may be a
831    /// zero divisor). The lift `L = (m/p)·L_p` turns a `GF(p)` witness into a `ℤ/m` one — every
832    /// pairing picks up the factor `m/p` and dies mod `m`, and `L(1) = m/p ≠ 0` — so the composite
833    /// ring inherits the LOWER BOUND of each prime part (the witness face of the CRT conjunction).
834    /// Checked concretely: the `GF(2)` PHP(3) witness lifts to `ℤ/6` with `L(1) = 3`; the `GF(3)`
835    /// parity witness lifts with `L(1) = 2` and certifies `ℤ/6`-degree > 2 for a family the
836    /// `GF(2)` component refutes at 2. The checker's soundness is one field-free line — a refutation
837    /// would force `L(1) = 0` — and it rejects the adversarial corruptions (dropped normalization,
838    /// annihilated scaling, perturbed constrained monomial).
839    #[test]
840    fn a_prime_witness_lifts_to_a_ring_witness_with_zero_divisor_normalization() {
841        use crate::polycalc_gfp::{ns_lower_bound_witness_gfp, NsField};
842        // GF(2) PHP(3) witness at degree 3 → ℤ/6 witness with L(1) = 3.
843        let (php3, _) = crate::families::php(3);
844        let w2 = ns_lower_bound_witness_gfp(NsField::Prime(2), php3.num_vars, &php3.clauses, 3)
845            .expect("PHP(3) has a GF(2) witness at degree 3");
846        let lifted2 = lift_prime_witness_to_zm(6, 2, &w2);
847        assert_eq!(
848            lifted2.iter().find(|&&(mo, _)| mo == 0).map(|&(_, v)| v),
849            Some(3),
850            "the GF(2) lift is normalized at the zero divisor L(1) = 6/2 = 3"
851        );
852        assert!(
853            check_ns_lower_bound_zm(6, php3.num_vars, &php3.clauses, 3, &lifted2),
854            "the lifted witness certifies ℤ/6-NS-degree(PHP(3)) > 3 with zero trust"
855        );
856        // GF(3) parity witness at degree 2 → ℤ/6 witness with L(1) = 2: a ring lower bound for a
857        // family the OTHER component (GF(2)) refutes at that very degree.
858        let parity = parity_triangle();
859        let w3 = ns_lower_bound_witness_gfp(NsField::Prime(3), 3, &parity, 2)
860            .expect("parity has a GF(3) witness at degree 2");
861        let lifted3 = lift_prime_witness_to_zm(6, 3, &w3);
862        assert_eq!(
863            lifted3.iter().find(|&&(mo, _)| mo == 0).map(|&(_, v)| v),
864            Some(2),
865            "the GF(3) lift is normalized at L(1) = 6/3 = 2"
866        );
867        assert!(
868            check_ns_lower_bound_zm(6, 3, &parity, 2, &lifted3),
869            "one prime part's witness certifies the ring lower bound"
870        );
871        // Corruptions are rejected: no normalization; scaling that annihilates L(1); a perturbed
872        // constrained monomial (bump L on a monomial of an admitted generator product).
873        let no_one: Vec<(Mono, u64)> = lifted2.iter().copied().filter(|&(mo, _)| mo != 0).collect();
874        assert!(!check_ns_lower_bound_zm(6, php3.num_vars, &php3.clauses, 3, &no_one));
875        let annihilated: Vec<(Mono, u64)> =
876            lifted2.iter().map(|&(mo, v)| (mo, zm_mul(6, v, 2))).collect(); // 3·2 = 0 (mod 6)
877        assert!(!check_ns_lower_bound_zm(6, php3.num_vars, &php3.clauses, 3, &annihilated));
878        let gens: Vec<ZmPoly> =
879            php3.clauses.iter().map(|c| clause_polynomial_zm(6, c)).collect();
880        let target = gens
881            .iter()
882            .find_map(|g| {
883                crate::polycalc::monomials_up_to_degree(php3.num_vars, 3).into_iter().find_map(
884                    |mo| {
885                        let prod = poly_mul_mono(6, g, mo);
886                        (!prod.is_empty() && zm_poly_degree(&prod) <= 3)
887                            .then(|| *prod.keys().next_back().unwrap())
888                    },
889                )
890            })
891            .expect("an admitted generator product exists");
892        let mut perturbed: Vec<(Mono, u64)> =
893            lifted2.iter().copied().filter(|&(mo, _)| mo != target).collect();
894        let old = lifted2.iter().find(|&&(mo, _)| mo == target).map_or(0, |&(_, v)| v);
895        perturbed.push((target, zm_add(6, old, 1)));
896        assert!(!check_ns_lower_bound_zm(6, php3.num_vars, &php3.clauses, 3, &perturbed));
897    }
898
899    /// **The nilpotent ring is STRICTLY weaker than its residue field at fixed degree.** `ℤ/4` maps
900    /// onto `GF(2)` (a ring homomorphism), so a `ℤ/4` refutation projects to a `GF(2)` refutation at
901    /// the same degree — soundness, swept across the corpus. The converse FAILS, and the measured
902    /// separation is parity: `GF(2)` refutes the odd XOR triangle at degree 2, `ℤ/4` cannot (no
903    /// mismatch of the other direction anywhere), refuting it only at degree 3. The explanation is
904    /// the Hensel tax: lifting `Σ g·p ≡ 1 (mod 2)` to `mod 4` multiplies by `(2 − s)` — a genuine
905    /// certificate, but of degree up to `2d`. Nilpotents make the ring *harder to refute in*, never
906    /// easier: exactly opposite to the naive "more structure, more power" guess, and the reason the
907    /// prime-power components in the CRT conjunction are `ℤ/pᵏ`, not `GF(p)`.
908    #[test]
909    fn z4_is_strictly_weaker_than_its_residue_field_at_fixed_degree() {
910        use crate::polycalc_gfp::{ns_refutes_gfp, NsField};
911        // Projection soundness: ℤ/4 refutes at d ⟹ GF(2) refutes at d, everywhere on the corpus.
912        for (name, nv, clauses) in &ring_corpus() {
913            for d in 1..=(*nv).min(4) {
914                if ns_refutes_zm(4, *nv, clauses, d) {
915                    assert!(
916                        ns_refutes_gfp(NsField::Prime(2), *nv, clauses, d),
917                        "{name} n={nv} d={d}: a ℤ/4 refutation projects to GF(2)"
918                    );
919                }
920            }
921        }
922        // The strict separation, both named instances: GF(2) degree 2, ℤ/4 degree 3.
923        for (name, nv, clauses) in
924            [("parity", 3usize, parity_triangle()), ("cnt23", 3, crate::families::mod_counting(3, 2).0.clauses)]
925        {
926            assert!(ns_refutes_gfp(NsField::Prime(2), nv, &clauses, 2), "{name}: GF(2) refutes at 2");
927            assert!(!ns_refutes_zm(4, nv, &clauses, 2), "{name}: ℤ/4 cannot refute at 2 — the nilpotent tax");
928            assert!(ns_refutes_zm(4, nv, &clauses, 3), "{name}: ℤ/4 refutes at 3 (within the Hensel 2d bound)");
929        }
930    }
931
932    /// **The partition-of-unity atom survives zero divisors.** `(1 − x) + x = 1` is a ring identity —
933    /// no inverses anywhere — so the atom and the full `2ⁿ`-corner cancellation hold over `ℤ/4`
934    /// (nilpotent 2), `ℤ/6` (idempotents 3, 4), `ℤ/9`, and `ℤ/12` exactly as over the fields. This is
935    /// the load-bearing fact behind ring completeness.
936    #[test]
937    fn the_partition_of_unity_atom_is_one_over_zero_divisor_moduli() {
938        for &m in &[4u64, 6, 9, 12] {
939            let one: ZmPoly = [(0u64, 1u64)].into_iter().collect();
940            for v in 0..8 {
941                assert_eq!(pou_atom_zm(m, v), one, "m={m}: the atom (1−x{v})+x{v} reduces to 1");
942            }
943            for n in 0..=8 {
944                assert_eq!(partition_of_unity_zm(m, n), one, "m={m}: Σ_a δ_a = 1 over the {n}-cube");
945            }
946        }
947    }
948
949    /// **The signed clause indicator works over every modulus, pinned corner-by-corner.** Over `ℤ/6`
950    /// the positive-literal factor is `1 + 5x` (`5 = −1`); the product is `1` exactly on falsifying
951    /// corners and `0` elsewhere for `m ∈ {4, 6, 9, 12}` on a deterministic random-clause corpus —
952    /// the 0/1-valued factor argument is ring-generic.
953    #[test]
954    fn z6_clause_polynomial_is_the_signed_false_indicator_on_every_corner() {
955        let pos = clause_polynomial_zm(6, &[Lit::pos(0)]);
956        let expected: ZmPoly = [(0u64, 1u64), (1u64, 5u64)].into_iter().collect();
957        assert_eq!(pos, expected, "positive literal → 1 − x = 1 + 5x over ℤ/6");
958        let mut seed = 0x0006_C0DEu64;
959        for &m in &[4u64, 6, 9, 12] {
960            for _ in 0..30 {
961                let n = 2 + (lcg(&mut seed) % 4) as usize; // 2..=5 variables
962                let width = 1 + (lcg(&mut seed) % n as u64) as usize;
963                let mut vars: Vec<u32> = Vec::new();
964                while vars.len() < width {
965                    let v = (lcg(&mut seed) % n as u64) as u32;
966                    if !vars.contains(&v) {
967                        vars.push(v);
968                    }
969                }
970                let clause: Vec<Lit> =
971                    vars.iter().map(|&v| Lit::new(v, lcg(&mut seed) & 1 == 1)).collect();
972                let poly = clause_polynomial_zm(m, &clause);
973                for a in 0u64..(1u64 << n) {
974                    let want = if falsifies(&clause, a) { 1 } else { 0 };
975                    assert_eq!(eval_at(m, &poly, a), want, "m={m} clause={clause:?} corner={a:b}");
976                }
977            }
978        }
979    }
980
981    /// **The Howell echelon decides `ℤ/m` span membership EXACTLY — proven against the exhaustive
982    /// oracle.** For small systems the ground truth is enumerable: every one of the `m^rows`
983    /// coefficient combinations is generated, giving the true span as a set; the echelon's
984    /// `contains` must then agree on **every** vector of `(ℤ/m)^n` — all `m^n` of them. Swept over
985    /// the zero-divisor moduli `{4, 6, 9, 12}` (nilpotents, idempotents, both) on a deterministic
986    /// random corpus. This is the module's trust anchor: no Gaussian intuition survives zero
987    /// divisors unchecked.
988    #[test]
989    fn zm_span_membership_matches_the_exhaustive_oracle() {
990        let mut seed = 0x40E1_1000u64;
991        for &m in &[4u64, 6, 9, 12] {
992            for _ in 0..25 {
993                let n = 2 + (lcg(&mut seed) % 3) as usize; // 2..=4 columns
994                let nrows = 1 + (lcg(&mut seed) % 3) as usize; // 1..=3 generators
995                let rows: Vec<Vec<u64>> = (0..nrows)
996                    .map(|_| (0..n).map(|_| lcg(&mut seed) % m).collect())
997                    .collect();
998                // Ground truth: every ℤ/m combination of the generators.
999                let mut span: std::collections::BTreeSet<Vec<u64>> = std::collections::BTreeSet::new();
1000                let mut combo = vec![0u64; nrows];
1001                loop {
1002                    let v: Vec<u64> = (0..n)
1003                        .map(|j| {
1004                            rows.iter()
1005                                .zip(&combo)
1006                                .fold(0u64, |acc, (r, &c)| zm_add(m, acc, zm_mul(m, c, r[j])))
1007                        })
1008                        .collect();
1009                    span.insert(v);
1010                    let mut i = 0;
1011                    while i < nrows {
1012                        combo[i] += 1;
1013                        if combo[i] < m {
1014                            break;
1015                        }
1016                        combo[i] = 0;
1017                        i += 1;
1018                    }
1019                    if i == nrows {
1020                        break;
1021                    }
1022                }
1023                // The echelon, fed the same generators, must agree on EVERY vector of (ℤ/m)^n.
1024                let mut ech = ZmEchelon::new(m);
1025                for r in &rows {
1026                    ech.insert(r.clone());
1027                }
1028                let mut target = vec![0u64; n];
1029                loop {
1030                    assert_eq!(
1031                        ech.contains(target.clone()),
1032                        span.contains(&target),
1033                        "m={m} rows={rows:?} target={target:?}"
1034                    );
1035                    let mut i = 0;
1036                    while i < n {
1037                        target[i] += 1;
1038                        if target[i] < m {
1039                            break;
1040                        }
1041                        target[i] = 0;
1042                        i += 1;
1043                    }
1044                    if i == n {
1045                        break;
1046                    }
1047                }
1048            }
1049        }
1050    }
1051
1052    /// **At prime `m` the ring engine IS the field engine.** `ℤ/p = GF(p)`, so on random CNFs the
1053    /// Howell-based decision must coincide with the Gaussian-based one at every degree — the
1054    /// differential anchor tying the ring machinery to the already-anchored characteristic axis.
1055    #[test]
1056    fn the_ring_engine_at_prime_m_agrees_with_the_field_engine() {
1057        let mut seed = 0x9219_0E11u64;
1058        for _ in 0..25 {
1059            let nv = 3 + (lcg(&mut seed) % 3) as usize; // 3..=5
1060            let nc = 4 + (lcg(&mut seed) % 8) as usize;
1061            let clauses: Vec<Vec<Lit>> = (0..nc)
1062                .map(|_| {
1063                    let width = 1 + (lcg(&mut seed) % 3) as usize;
1064                    let mut vars: Vec<u32> = Vec::new();
1065                    while vars.len() < width {
1066                        let v = (lcg(&mut seed) % nv as u64) as u32;
1067                        if !vars.contains(&v) {
1068                            vars.push(v);
1069                        }
1070                    }
1071                    vars.iter().map(|&v| Lit::new(v, lcg(&mut seed) & 1 == 1)).collect()
1072                })
1073                .collect();
1074            for &p in &[2u64, 3, 5] {
1075                for d in 1..=nv.min(4) {
1076                    assert_eq!(
1077                        ns_refutes_zm(p, nv, &clauses, d),
1078                        crate::polycalc_gfp::ns_refutes_gfp(
1079                            crate::polycalc_gfp::NsField::Prime(p),
1080                            nv,
1081                            &clauses,
1082                            d
1083                        ),
1084                        "p={p} n={nv} d={d}: ℤ/p ring engine = GF(p) field engine"
1085                    );
1086                }
1087            }
1088        }
1089    }
1090
1091    /// **Ring completeness is total, sound, and fail-closed over `ℤ/6` AND `ℤ/4`.** The
1092    /// partition-of-unity charging never divides, so it survives idempotents (`ℤ/6`) and nilpotents
1093    /// (`ℤ/4`) alike: every UNSAT formula gets a certificate that re-checks (ring and corner-wise),
1094    /// every SAT formula gets a model that satisfies it, and a certificate refuses a clause set it
1095    /// was not built for. Cross-checked against brute-force satisfiability throughout.
1096    #[test]
1097    fn build_ns_certificate_zm_is_total_sound_and_fail_closed_over_z6_and_z4() {
1098        let mut seed = 0x0BAD_0604u64;
1099        for &m in &[6u64, 4] {
1100            let mut unsat_seen = 0usize;
1101            for _ in 0..60 {
1102                let nv = 4 + (lcg(&mut seed) % 3) as usize; // 4..=6
1103                let nc = 2 * nv + (lcg(&mut seed) % (3 * nv as u64)) as usize; // dense: UNSAT-rich
1104                let clauses: Vec<Vec<Lit>> = (0..nc)
1105                    .map(|_| {
1106                        let width = 2 + (lcg(&mut seed) % 2) as usize;
1107                        let mut vars: Vec<u32> = Vec::new();
1108                        while vars.len() < width {
1109                            let v = (lcg(&mut seed) % nv as u64) as u32;
1110                            if !vars.contains(&v) {
1111                                vars.push(v);
1112                            }
1113                        }
1114                        vars.iter().map(|&v| Lit::new(v, lcg(&mut seed) & 1 == 1)).collect()
1115                    })
1116                    .collect();
1117                match build_ns_certificate_zm(m, nv, &clauses) {
1118                    Ok(cert) => {
1119                        unsat_seen += 1;
1120                        assert_eq!(cert.modulus(), m);
1121                        assert!(cert.verify(&clauses), "m={m} n={nv}: the ring certificate re-checks");
1122                        assert!(cert.degree() <= nv, "m={m} n={nv}: certificate degree ≤ n");
1123                        assert!(!sat(nv, &clauses), "m={m}: certificates only for genuine UNSAT");
1124                        assert!(
1125                            !cert.verify(&clauses[..clauses.len() - 1]),
1126                            "m={m}: a certificate must not verify a different clause set"
1127                        );
1128                    }
1129                    Err(model) => {
1130                        assert!(sat(nv, &clauses), "m={m}: SAT verdicts only for satisfiable formulas");
1131                        assert!(
1132                            clauses
1133                                .iter()
1134                                .all(|c| c.iter().any(|l| model[l.var() as usize] == l.is_positive())),
1135                            "m={m}: the returned model satisfies every clause"
1136                        );
1137                    }
1138                }
1139            }
1140            assert!(unsat_seen >= 5, "m={m}: the corpus exercises the UNSAT branch ({unsat_seen})");
1141        }
1142    }
1143
1144    /// **"Structureless" has no witness over ANY modulus — the two-poles theorem, ring-completed.**
1145    /// Define hardness-as-structurelessness: *no certificate at any degree `≤ n` exists*. This test
1146    /// certifies that **no finite formula fulfills that definition over any `ℤ/m`, `m = 2..12`** —
1147    /// every UNSAT instance in the corpus (pigeonhole, an odd-parity block, modular counting, and a
1148    /// deterministic random sweep) receives a degree-`≤ n` certificate that re-checks with zero
1149    /// trust, and every SAT instance a model. Structure always exists, over every coefficient ring.
1150    /// The honest boundary, stated with the theorem: the certificate lives in the `2ⁿ` basis —
1151    /// existence, not efficiency — and the *cost* pole is real and certified elsewhere (the degree
1152    /// lower bounds grow; the incompressibility theorems are kernel facts). What is refuted here is
1153    /// precisely the existence-form of randomness at finite `n`, not the asymptotic cost-form of
1154    /// NP-hardness, which no algebraic instrument can decide (algebrization).
1155    #[test]
1156    fn no_finite_formula_is_structureless_over_any_modulus() {
1157        let mut corpus: Vec<(usize, Vec<Vec<Lit>>)> = Vec::new();
1158        let (php3, _) = crate::families::php(3);
1159        corpus.push((php3.num_vars, php3.clauses));
1160        let (cnt34, _) = crate::families::mod_counting(4, 3);
1161        corpus.push((cnt34.num_vars, cnt34.clauses));
1162        // An odd XOR cycle (x0≠x1, x1≠x2, x2≠x0) — UNSAT by parity.
1163        let p = |v: u32| Lit::pos(v);
1164        let q = |v: u32| Lit::neg(v);
1165        corpus.push((3, vec![
1166            vec![p(0), p(1)], vec![q(0), q(1)],
1167            vec![p(1), p(2)], vec![q(1), q(2)],
1168            vec![p(2), p(0)], vec![q(2), q(0)],
1169        ]));
1170        let mut seed = 0xA11_0DD5u64;
1171        for _ in 0..10 {
1172            let nv = 3 + (lcg(&mut seed) % 3) as usize;
1173            let nc = nv + (lcg(&mut seed) % (2 * nv as u64)) as usize;
1174            let clauses: Vec<Vec<Lit>> = (0..nc)
1175                .map(|_| {
1176                    let width = 2 + (lcg(&mut seed) % 2) as usize;
1177                    let mut vars: Vec<u32> = Vec::new();
1178                    while vars.len() < width {
1179                        let v = (lcg(&mut seed) % nv as u64) as u32;
1180                        if !vars.contains(&v) {
1181                            vars.push(v);
1182                        }
1183                    }
1184                    vars.iter().map(|&v| Lit::new(v, lcg(&mut seed) & 1 == 1)).collect()
1185                })
1186                .collect();
1187            corpus.push((nv, clauses));
1188        }
1189        let mut certified = 0usize;
1190        for m in 2u64..=12 {
1191            for (nv, clauses) in &corpus {
1192                match build_ns_certificate_zm(m, *nv, clauses) {
1193                    Ok(cert) => {
1194                        assert!(cert.verify(clauses), "m={m} n={nv}: the certificate re-checks");
1195                        assert!(cert.degree() <= *nv, "m={m}: degree ≤ n — structure within the cube");
1196                        certified += 1;
1197                    }
1198                    Err(model) => {
1199                        assert!(
1200                            clauses
1201                                .iter()
1202                                .all(|c| c.iter().any(|l| model[l.var() as usize] == l.is_positive())),
1203                            "m={m}: SAT half — the model satisfies every clause"
1204                        );
1205                    }
1206                }
1207            }
1208        }
1209        assert!(certified >= 3 * 11, "the UNSAT half is exercised across all 11 moduli ({certified})");
1210        eprintln!(
1211            "structureless-witness count over m = 2..12: 0 of {} (UNSAT instances certified: {certified})",
1212            corpus.len() * 11
1213        );
1214    }
1215}