Skip to main content

logicaffeine_proof/
polycalc.rs

1//! Polynomial Calculus / Nullstellensatz over `GF(2)` — the algebraic proof system that **subsumes the
2//! linear cuts** (parity is its degree-1 fragment) and, at higher degree, refutes strictly more.
3//!
4//! A CNF is unsatisfiable iff the constant polynomial `1` lies in the ideal generated by the clause
5//! polynomials over the *multilinear* `GF(2)` ring (where `x² = x`, so every monomial is squarefree).
6//! At a **fixed degree `d`** this is a finite linear-algebra question — is `1` in the `GF(2)`-span of
7//! `{ m · p_C : clause C, monomial m, deg(m·p_C) ≤ d }`? — answered by Gaussian elimination. So degree-`d`
8//! Nullstellensatz is polynomial, and the **degree is the power dial**: `d = 1` recovers parity, larger
9//! `d` reaches counting-style and beyond. The genuinely hard residue (random) needs degree `Θ(n)`, which
10//! is exactly why it stays hard — there is no low-degree algebraic certificate, just as there is no small
11//! symmetry quotient. The two are the same wall seen from two sides.
12//!
13//! Symmetry meets this engine at the monomial basis: an automorphism of the formula permutes monomials,
14//! so the Nullstellensatz system has the family's symmetry and its solution can be sought on the orbit
15//! quotient of the basis — the algebraic form of "decide on the quotient."
16
17use crate::cdcl::Lit;
18use std::collections::{BTreeSet, HashMap, HashSet};
19
20/// A multilinear monomial: the bitmask of the variables it contains (`x² = x` ⟹ squarefree). The `u64`
21/// mask carries up to 63 variables; the *clause* engine's explicit cube enumeration stops at 20, the
22/// degree-bounded polynomial engine ([`monomials_up_to_degree`]) uses the full range.
23pub type Mono = u64;
24/// A multilinear polynomial over `GF(2)`: the set of monomials with coefficient 1 (XOR/symmetric-
25/// difference semantics).
26pub type Poly = BTreeSet<Mono>;
27
28fn toggle(p: &mut Poly, m: Mono) {
29    if !p.remove(&m) {
30        p.insert(m);
31    }
32}
33
34pub(crate) fn poly_mul_mono(p: &Poly, m: Mono) -> Poly {
35    let mut r = Poly::new();
36    for &t in p {
37        toggle(&mut r, t | m); // multilinear product: x·x = x ⟹ OR the masks
38    }
39    r
40}
41
42fn poly_mul(a: &Poly, b: &Poly) -> Poly {
43    let mut r = Poly::new();
44    for &s in a {
45        for &t in b {
46            toggle(&mut r, s | t);
47        }
48    }
49    r
50}
51
52/// The clause polynomial: `1` exactly on the clause's falsifying assignment. The false-indicator of a
53/// positive literal `x` is `1 + x` (`{∅, {x}}`), of a negative literal `¬x` is `x` (`{{x}}`); the clause
54/// polynomial is their product. Degree = clause width.
55pub fn clause_polynomial(clause: &[Lit]) -> Poly {
56    let mut p: Poly = [0u64].into_iter().collect(); // the polynomial "1" (the empty monomial)
57    for l in clause {
58        let bit = 1u64 << l.var();
59        let indicator: Poly = if l.is_positive() {
60            [0u64, bit].into_iter().collect() // 1 + x
61        } else {
62            [bit].into_iter().collect() // x
63        };
64        p = poly_mul(&p, &indicator);
65    }
66    p
67}
68
69/// Is `target` in the `GF(2)`-span of `rows`? Gaussian elimination over packed bit-rows: reduce the rows
70/// to an echelon basis (one pivot bit each), then reduce `target` by it and check it vanishes.
71fn in_gf2_span(mut rows: Vec<Vec<u64>>, target: &[u64]) -> bool {
72    let words = target.len();
73    let high_bit = |r: &[u64]| -> Option<usize> {
74        for w in (0..words).rev() {
75            if r[w] != 0 {
76                return Some(w * 64 + (63 - r[w].leading_zeros() as usize));
77            }
78        }
79        None
80    };
81    let xor_into = |dst: &mut [u64], src: &[u64]| {
82        for w in 0..words {
83            dst[w] ^= src[w];
84        }
85    };
86    // Build an echelon basis keyed by pivot bit.
87    let mut basis: HashMap<usize, Vec<u64>> = HashMap::new();
88    for mut r in rows.drain(..) {
89        while let Some(p) = high_bit(&r) {
90            match basis.get(&p) {
91                Some(b) => xor_into(&mut r, b),
92                None => break,
93            }
94        }
95        if let Some(p) = high_bit(&r) {
96            basis.insert(p, r);
97        }
98    }
99    // Reduce the target.
100    let mut t = target.to_vec();
101    while let Some(p) = high_bit(&t) {
102        match basis.get(&p) {
103            Some(b) => xor_into(&mut t, b),
104            None => break,
105        }
106    }
107    t.iter().all(|&w| w == 0)
108}
109
110/// Does a **degree-`d` Nullstellensatz refutation** exist over `GF(2)`? Sound: such a certificate exists
111/// only when the formula is unsatisfiable. Complete at `d = num_vars` (full degree decides any instance).
112/// Bounded to `num_vars ≤ 20` (the explicit monomial basis).
113pub fn nullstellensatz_refutes(num_vars: usize, clauses: &[Vec<Lit>], degree: usize) -> bool {
114    if num_vars > 20 {
115        return false;
116    }
117    // Monomial basis: every squarefree monomial of degree ≤ `degree`.
118    let mut index: HashMap<Mono, usize> = HashMap::new();
119    for m in 0u64..(1u64 << num_vars) {
120        if m.count_ones() as usize <= degree {
121            let n = index.len();
122            index.insert(m, n);
123        }
124    }
125    let nb = index.len();
126    let words = nb.div_ceil(64).max(1);
127    let to_bits = |p: &Poly| -> Vec<u64> {
128        let mut b = vec![0u64; words];
129        for &m in p {
130            if let Some(&i) = index.get(&m) {
131                b[i / 64] |= 1 << (i % 64);
132            }
133        }
134        b
135    };
136    let monos: Vec<Mono> = index.keys().copied().collect();
137
138    // Generators: m · p_C for every clause C and monomial m with deg(m · p_C) ≤ degree.
139    let mut rows: Vec<Vec<u64>> = Vec::new();
140    for c in clauses {
141        if c.is_empty() {
142            return true; // an empty clause is `1 = 0` outright
143        }
144        let width = c.len();
145        if width > degree {
146            continue;
147        }
148        let pc = clause_polynomial(c);
149        for &m in &monos {
150            if m.count_ones() as usize <= degree - width {
151                rows.push(to_bits(&poly_mul_mono(&pc, m)));
152            }
153        }
154    }
155    // Target: the constant polynomial `1` — the empty monomial.
156    let mut target = vec![0u64; words];
157    let t0 = index[&0u64];
158    target[t0 / 64] |= 1 << (t0 % 64);
159    in_gf2_span(rows, &target)
160}
161
162/// Reduce a polynomial against an echelon basis keyed by leading (largest) monomial: while its leading
163/// monomial is a pivot, XOR (symmetric-difference) that basis row in. The leading monomial strictly
164/// decreases each step, so this terminates; the result is `p` modulo the span.
165fn pc_reduce(basis: &HashMap<Mono, Poly>, mut p: Poly) -> Poly {
166    while let Some(&lm) = p.iter().next_back() {
167        match basis.get(&lm) {
168            Some(b) => {
169                for &m in b {
170                    toggle(&mut p, m);
171                }
172            }
173            None => break,
174        }
175    }
176    p
177}
178
179/// Does a **degree-`d` Polynomial Calculus refutation** exist over `GF(2)`? PC is the *dynamic*
180/// strengthening of Nullstellensatz: start from the clause polynomials and close under (i) `GF(2)` linear
181/// combination and (ii) multiplication by a single variable, keeping every *derived* polynomial
182/// multilinear of degree ≤ `d`; the system is refuted iff the constant `1` is derived. Because an
183/// intermediate linear combination can cancel high-degree terms *before* the next multiply, degree-`d` PC
184/// certifies a superset of what degree-`d` Nullstellensatz can (which must hit each axiom with a single
185/// monomial in one shot). Sound — `1` is derivable only from an unsatisfiable system — and `PC ⊇ NS`, so
186/// it never refutes fewer. Complete at `d = num_vars`. Bounded to `num_vars ≤ 20`.
187pub fn polynomial_calculus_refutes(num_vars: usize, clauses: &[Vec<Lit>], degree: usize) -> bool {
188    if num_vars > 20 {
189        return false;
190    }
191    let poly_deg = |p: &Poly| p.iter().map(|&m| m.count_ones() as usize).max().unwrap_or(0);
192    let mut basis: HashMap<Mono, Poly> = HashMap::new();
193    let one: Poly = [0u64].into_iter().collect();
194
195    // Seed the worklist with the clause polynomials usable at this degree (width ≤ d).
196    let mut worklist: Vec<Poly> = Vec::new();
197    for c in clauses {
198        if c.is_empty() {
199            return true; // an empty clause is `1 = 0` outright
200        }
201        if c.len() <= degree {
202            worklist.push(clause_polynomial(c));
203        }
204    }
205    // Saturate: each newly-independent polynomial joins the basis and is multiplied by every variable
206    // (staying within degree), feeding the closure back into the worklist.
207    while let Some(p) = worklist.pop() {
208        let r = pc_reduce(&basis, p);
209        let Some(&lm) = r.iter().next_back() else { continue }; // reduced to 0 — no new information
210        for i in 0..num_vars as u64 {
211            let q = poly_mul_mono(&r, 1u64 << i);
212            if !q.is_empty() && poly_deg(&q) <= degree {
213                worklist.push(q);
214            }
215        }
216        basis.insert(lm, r);
217        if pc_reduce(&basis, one.clone()).is_empty() {
218            return true; // the constant 1 is in the span — refuted
219        }
220    }
221    false
222}
223
224/// A **constructive Nullstellensatz certificate** over the multilinear `GF(2)` ring: one coefficient
225/// polynomial `g_C` per input clause such that `Σ_C p_C · g_C = 1`, where `p_C = clause_polynomial(C)` is the
226/// clause's false-indicator. The identity is a re-checkable proof of UNSAT — evaluate it at any assignment
227/// `a`: the right side is `1`, so some `p_C(a) = 1`, i.e. *every* assignment falsifies some clause. Where
228/// [`nullstellensatz_refutes`] only *decides* whether such a certificate exists, this one carries the witness.
229#[derive(Clone, Debug)]
230pub struct NsCertificate {
231    num_vars: usize,
232    /// `coeffs[i]` is `g_{C_i}` for the `i`-th input clause (parallel indexing to the clause list).
233    coeffs: Vec<Poly>,
234}
235
236impl NsCertificate {
237    /// The variable count the certificate lives over.
238    pub fn num_vars(&self) -> usize {
239        self.num_vars
240    }
241
242    /// The maximum monomial degree among the coefficient polynomials. Multilinear over `num_vars`
243    /// variables, so `≤ num_vars` unconditionally — the content is not this (trivial for a multilinear
244    /// certificate) but that the certificate *exists and is built by one uniform construction at every `n`*.
245    pub fn degree(&self) -> usize {
246        self.coeffs.iter().flatten().map(|m| m.count_ones() as usize).max().unwrap_or(0)
247    }
248
249    /// **Re-check against the original clauses** (zero trust in the producer): recompute
250    /// `Σ_C clause_polynomial(C) · g_C` and confirm it is the constant `1` (the empty monomial). A `true`
251    /// verdict is an independent proof that `clauses` is unsatisfiable; it fails closed if the certificate's
252    /// clause count does not match the formula it is checked against.
253    pub fn verify(&self, clauses: &[Vec<Lit>]) -> bool {
254        if self.coeffs.len() != clauses.len() {
255            return false;
256        }
257        let mut sum = Poly::new();
258        for (c, g) in clauses.iter().zip(&self.coeffs) {
259            if g.is_empty() {
260                continue;
261            }
262            for m in poly_mul(&clause_polynomial(c), g) {
263                toggle(&mut sum, m);
264            }
265        }
266        sum.len() == 1 && sum.contains(&0u64)
267    }
268}
269
270/// The single-point indicator `δ_a` — the multilinear function that is `1` at assignment `a` and `0` at every
271/// other corner: `Π_{a_i=1} x_i · Π_{a_i=0} (1 + x_i)`. Its monomials are `ones(a) ∪ T` over every subset `T`
272/// of the zero-coordinates (top degree `n`); the `Σ_a δ_a = 1` identity over all corners is the partition of
273/// unity the certificate construction rests on.
274fn point_indicator(a: u64, num_vars: usize) -> Poly {
275    let mask = (1u64 << num_vars).wrapping_sub(1);
276    let ones = a & mask;
277    let zeros = !a & mask;
278    let mut p = Poly::new();
279    let mut sub = zeros;
280    loop {
281        p.insert(ones | sub); // masks are distinct (T ⊆ zeros, disjoint from ones) — no cancellation
282        if sub == 0 {
283            break;
284        }
285        sub = (sub - 1) & zeros;
286    }
287    p
288}
289
290/// **The uniform Nullstellensatz completeness construction.** For any CNF over `num_vars ≤ 20` variables,
291/// return either a constructive [`NsCertificate`] proving UNSAT, or a satisfying assignment proving SAT — a
292/// *total, certifying* decision. The construction is the partition of unity `Σ_a δ_a = 1`: every corner `a`
293/// is charged to one clause it falsifies (a corner that falsifies none *is* a model — SAT), and the
294/// coefficient of clause `C` is `g_C = Σ_{a charged to C} δ_a`. Then `Σ_C p_C · g_C = Σ_a p_{sel(a)} · δ_a =
295/// Σ_a δ_a = 1`, because `p_{sel(a)}(a) = 1` collapses `p·δ_a` to `δ_a` on the cube (multilinear
296/// representations are unique). Because this succeeds *identically at every `n`*, it does not merely measure
297/// but **proves** that every unsatisfiable formula has a degree-`≤ n` `GF(2)` Nullstellensatz refutation — no
298/// minimal-UNSAT family is structureless, at any `n`. This is the census's `max_ns_degree = n` ceiling as a
299/// construction, settling `n = 5, 6, …` where the orbit census is infeasible.
300pub fn build_ns_certificate(num_vars: usize, clauses: &[Vec<Lit>]) -> Result<NsCertificate, Vec<bool>> {
301    assert!(num_vars <= 20, "the explicit-corner construction is bounded to num_vars ≤ 20");
302    let mut coeffs: Vec<Poly> = vec![Poly::new(); clauses.len()];
303    for a in 0u64..(1u64 << num_vars) {
304        let sel = clauses
305            .iter()
306            .position(|c| !c.iter().any(|l| ((a >> l.var()) & 1 == 1) == l.is_positive()));
307        match sel {
308            None => return Err((0..num_vars).map(|i| (a >> i) & 1 == 1).collect()),
309            Some(ci) => {
310                for m in point_indicator(a, num_vars) {
311                    toggle(&mut coeffs[ci], m);
312                }
313            }
314        }
315    }
316    Ok(NsCertificate { num_vars, coeffs })
317}
318
319/// The closure of a permutation group under composition (BFS), for small groups. Elements keyed by their
320/// image vector so the group is deduplicated.
321pub(crate) fn close_perm_group(gens: &[crate::proof::Perm], num_vars: usize) -> Vec<crate::proof::Perm> {
322    use crate::proof::Perm;
323    let key = |p: &Perm| -> Vec<u32> { (0..num_vars).map(|v| p.apply(Lit::pos(v as u32)).var()).collect() };
324    let id = Perm::identity(num_vars);
325    let mut seen: std::collections::BTreeSet<Vec<u32>> = [key(&id)].into_iter().collect();
326    let mut group = vec![id.clone()];
327    let mut frontier = vec![id];
328    while let Some(p) = frontier.pop() {
329        for g in gens {
330            let q = p.compose(g);
331            if seen.insert(key(&q)) {
332                group.push(q.clone());
333                frontier.push(q);
334            }
335        }
336    }
337    group
338}
339
340/// The **symmetrization** of a `GF(2)` functional `L` (given as the monomials where it is `1`) over a group:
341/// `Σ_{g∈G} g·L` (mod 2). The Reynolds/averaging operator of characteristic 0 — but *without the `1/|G|`*,
342/// because that division is unavailable over `GF(2)`.
343fn symmetrize(l: &[Mono], group: &[crate::proof::Perm]) -> BTreeSet<Mono> {
344    let mut sym: BTreeSet<Mono> = BTreeSet::new();
345    for &m in l {
346        for g in group {
347            let img = apply_perm_to_mono(g, m);
348            if !sym.remove(&img) {
349                sym.insert(img);
350            }
351        }
352    }
353    sym
354}
355
356/// Exact binomial coefficient `C(n, k)` in `u128` (small `n`; the running-product form stays integral).
357fn binom(n: usize, k: usize) -> u128 {
358    if k > n {
359        return 0;
360    }
361    let k = k.min(n - k);
362    let mut c = 1u128;
363    for i in 0..k {
364        c = c * (n - i) as u128 / (i + 1) as u128;
365    }
366    c
367}
368
369/// The width of the degree-`d` Nullstellensatz system over `n` variables: the count of multilinear monomials
370/// of degree `≤ d`, `Σ_{k≤d} C(n,k)`. At `d = n` this is exactly `2ⁿ` — so the degree-`n` certificate that
371/// [`build_ns_certificate`] always produces (completeness: no unsatisfiable formula over `n` variables is
372/// "structureless") lives in an **exponentially large** space. The certificate's *existence* is an
373/// information-theoretic fact about the finite cube; it is not an efficient algorithm, and it says nothing
374/// about P vs NP — which is a statement about the asymptotic growth of a *family* of instances, not any fixed
375/// finite `n` (a fixed finite problem is decidable by table lookup, vacuously).
376pub fn nullstellensatz_basis_size(n: usize, d: usize) -> u128 {
377    (0..=d.min(n)).map(|k| binom(n, k)).sum()
378}
379
380/// All squarefree monomials of degree `≤ degree` over `num_vars ≤ 63` variables, ascending as `u64`s —
381/// enumerated directly (Gosper's next-`k`-subset walk per degree class), never touching the `2ⁿ` cube.
382/// This is the basis walk that lifts fixed-degree Nullstellensatz work past the clause engine's
383/// 20-variable cap: the count is `Σ_{k≤d} C(n,k)` ([`nullstellensatz_basis_size`]), not `2ⁿ`.
384pub fn monomials_up_to_degree(num_vars: usize, degree: usize) -> Vec<Mono> {
385    assert!(num_vars <= 63, "the u64 monomial mask carries ≤ 63 variables");
386    let mut out: Vec<Mono> = vec![0];
387    for k in 1..=degree.min(num_vars) {
388        let limit: Mono = 1u64 << num_vars;
389        let mut m: Mono = (1u64 << k) - 1; // the least k-subset
390        while m < limit {
391            out.push(m);
392            let c = m & m.wrapping_neg(); // Gosper's hack: next integer with the same popcount
393            let r = m + c;
394            m = (((r ^ m) >> 2) / c) | r;
395        }
396    }
397    out.sort_unstable();
398    out
399}
400
401/// The degree of a multilinear `GF(2)` polynomial: its largest monomial's popcount (`0` for the zero
402/// polynomial and for the constant `1`).
403pub fn poly_degree(p: &Poly) -> usize {
404    p.iter().map(|&m| m.count_ones() as usize).max().unwrap_or(0)
405}
406
407/// Does a **degree-`d` Nullstellensatz refutation** exist over `GF(2)` for an arbitrary polynomial
408/// generator system — is `1` in the `GF(2)`-span of `{ m·g : deg(m·g) ≤ d }`? The clause engine's
409/// question asked of *any* generators, not just clause polynomials: the substrate for the linear
410/// encoding ([`exactly_one_linear_generators`]) and the symmetric-family machinery. The multiplier rule
411/// is exact — a product is admitted by its degree *after* multilinear collapse. (For clause polynomials
412/// this span equals the clause engine's: a multiplier overlapping a positive literal kills the product,
413/// one overlapping a negative literal absorbs into a smaller multiplier — pinned by the differential
414/// test.) Degree-bounded enumeration, so it scales to `num_vars ≤ 63`.
415pub fn ns_refutes_polys(num_vars: usize, gens: &[Poly], degree: usize) -> bool {
416    let basis = monomials_up_to_degree(num_vars, degree);
417    let index: HashMap<Mono, usize> = basis.iter().enumerate().map(|(i, &m)| (m, i)).collect();
418    let words = basis.len().div_ceil(64).max(1);
419    let to_bits = |p: &Poly| -> Vec<u64> {
420        let mut b = vec![0u64; words];
421        for &m in p {
422            if let Some(&i) = index.get(&m) {
423                b[i / 64] |= 1 << (i % 64);
424            }
425        }
426        b
427    };
428    let mut rows: Vec<Vec<u64>> = Vec::new();
429    for g in gens {
430        if g.is_empty() {
431            continue; // the zero polynomial generates nothing
432        }
433        for &m in &basis {
434            let prod = poly_mul_mono(g, m);
435            if !prod.is_empty() && poly_degree(&prod) <= degree {
436                rows.push(to_bits(&prod));
437            }
438        }
439    }
440    let t0 = index[&0u64];
441    let mut target = vec![0u64; words];
442    target[t0 / 64] |= 1 << (t0 % 64);
443    in_gf2_span(rows, &target)
444}
445
446/// [`ns_lower_bound_witness`] for an arbitrary polynomial generator system: a degree-`d`
447/// pseudo-expectation `L` with `L(1) = 1` and `L(m·g) = 0` for every admitted generator, returned as the
448/// monomials where `L = 1`. `Some(L)` certifies `NS-degree > d` for the system (re-checkable by
449/// [`check_ns_lower_bound_polys`], zero trust in the solver); `None` means a degree-`d` refutation
450/// exists. Degree-bounded enumeration — `num_vars ≤ 63`.
451pub fn ns_lower_bound_witness_polys(num_vars: usize, gens: &[Poly], degree: usize) -> Option<Vec<Mono>> {
452    let basis = monomials_up_to_degree(num_vars, degree);
453    let index: HashMap<Mono, usize> = basis.iter().enumerate().map(|(i, &m)| (m, i)).collect();
454    let nb = basis.len();
455    let words = nb.div_ceil(64).max(1);
456    let mask_of = |p: &Poly| -> Vec<u64> {
457        let mut mask = vec![0u64; words];
458        for &m in p {
459            if let Some(&i) = index.get(&m) {
460                mask[i / 64] |= 1u64 << (i % 64);
461            }
462        }
463        mask
464    };
465    let mut eqs: Vec<(Vec<u64>, bool)> = Vec::new();
466    for g in gens {
467        if g.is_empty() {
468            continue;
469        }
470        for &m in &basis {
471            let prod = poly_mul_mono(g, m);
472            if !prod.is_empty() && poly_degree(&prod) <= degree {
473                eqs.push((mask_of(&prod), false)); // ⟨L, m·g⟩ = 0
474            }
475        }
476    }
477    let t0 = index[&0u64];
478    let mut target = vec![0u64; words];
479    target[t0 / 64] |= 1u64 << (t0 % 64);
480    eqs.push((target, true)); // L(1) = 1
481    let l = gf2_solve(&eqs, nb)?;
482    Some((0..nb).filter(|&i| (l[i / 64] >> (i % 64)) & 1 == 1).map(|i| basis[i]).collect())
483}
484
485/// [`ns_lower_bound_witness_polys`] restricted to a **sub-basis**: the functional `L` is sought only on
486/// monomials passing `in_basis` (`L = 0` elsewhere), while the constraints `⟨L, m·g⟩ = 0` still range
487/// over *all* admitted generators — so any `Some` is a fully valid,
488/// [`check_ns_lower_bound_polys`]-verifiable witness, and `None` means only "no witness on this
489/// sub-basis". The structure probe: which candidate supports carry a family's lower bound (for
490/// pigeonhole this is how the hole-injective support was found and the classical partial-matching
491/// support was ruled out over `GF(2)`). Degree-bounded enumeration — `num_vars ≤ 63`.
492pub fn ns_lower_bound_witness_polys_on_basis(
493    num_vars: usize,
494    gens: &[Poly],
495    degree: usize,
496    in_basis: &dyn Fn(Mono) -> bool,
497) -> Option<Vec<Mono>> {
498    let all = monomials_up_to_degree(num_vars, degree);
499    let basis: Vec<Mono> = all.iter().copied().filter(|&m| in_basis(m)).collect();
500    let index: HashMap<Mono, usize> = basis.iter().enumerate().map(|(i, &m)| (m, i)).collect();
501    index.get(&0u64)?; // the empty monomial must be in the sub-basis for L(1) = 1
502    let nb = basis.len();
503    let words = nb.div_ceil(64).max(1);
504    let mask_of = |p: &Poly| -> Vec<u64> {
505        let mut mask = vec![0u64; words];
506        for &m in p {
507            if let Some(&i) = index.get(&m) {
508                mask[i / 64] |= 1u64 << (i % 64);
509            }
510        }
511        mask
512    };
513    let mut eqs: Vec<(Vec<u64>, bool)> = Vec::new();
514    for g in gens {
515        if g.is_empty() {
516            continue;
517        }
518        for &m in &all {
519            let prod = poly_mul_mono(g, m);
520            if !prod.is_empty() && poly_degree(&prod) <= degree {
521                eqs.push((mask_of(&prod), false));
522            }
523        }
524    }
525    let t0 = index[&0u64];
526    let mut target = vec![0u64; words];
527    target[t0 / 64] |= 1u64 << (t0 % 64);
528    eqs.push((target, true));
529    let l = gf2_solve(&eqs, nb)?;
530    Some((0..nb).filter(|&i| (l[i / 64] >> (i % 64)) & 1 == 1).map(|i| basis[i]).collect())
531}
532
533/// Re-check a [`ns_lower_bound_witness_polys`] certificate (zero trust in the producer): `L(1) = 1` and
534/// `L(m·g) = 0` for every admitted generator of the system. `true` ⟹ the generator system genuinely has
535/// no degree-`d` `GF(2)` Nullstellensatz refutation. Degree-bounded enumeration — `num_vars ≤ 63`.
536pub fn check_ns_lower_bound_polys(num_vars: usize, gens: &[Poly], degree: usize, witness: &[Mono]) -> bool {
537    let l: BTreeSet<Mono> = witness.iter().copied().collect();
538    if !l.contains(&0u64) {
539        return false; // L(1) must be 1
540    }
541    let basis = monomials_up_to_degree(num_vars, degree);
542    for g in gens {
543        if g.is_empty() {
544            continue;
545        }
546        for &m in &basis {
547            let prod = poly_mul_mono(g, m);
548            if !prod.is_empty()
549                && poly_degree(&prod) <= degree
550                && prod.iter().filter(|t| l.contains(t)).count() % 2 == 1
551            {
552                return false; // ⟨L, m·g⟩ must be 0
553            }
554        }
555    }
556    true
557}
558
559/// The **linear encoding** of exactly-one constraints: for each group `G` the degree-1 generator
560/// `1 + Σ_{v∈G} x_v` (the `GF(2)` form of `Σ x = 1`) plus the pairwise products `x_u·x_v` over each
561/// group, deduplicated. This is the encoding under which the modular-counting and pigeonhole degree
562/// lower bounds are stated in the literature — the wide at-least-one clause is recovered from it modulo
563/// the pairs (the interreduction test pins the identity, and the clause→linear direction is
564/// degree-preserving, so bounds against this encoding are the stronger statements). The linear
565/// generators come first (one per group, in group order), then the pairs.
566pub fn exactly_one_linear_generators(groups: &[Vec<u32>]) -> Vec<Poly> {
567    let mut gens: Vec<Poly> = Vec::new();
568    for g in groups {
569        let mut lin: Poly = [0u64].into_iter().collect();
570        for &v in g {
571            assert!(v < 63, "the u64 monomial mask carries ≤ 63 variables");
572            toggle(&mut lin, 1u64 << v);
573        }
574        gens.push(lin);
575    }
576    let mut pairs: BTreeSet<Mono> = BTreeSet::new();
577    for g in groups {
578        for (i, &u) in g.iter().enumerate() {
579            for &v in &g[i + 1..] {
580                pairs.insert((1u64 << u) | (1u64 << v));
581            }
582        }
583    }
584    gens.extend(pairs.into_iter().map(|m| [m].into_iter().collect::<Poly>()));
585    gens
586}
587
588/// Solve a `GF(2)` linear system `⟨x, coeffᵢ⟩ = rhsᵢ` (each `coeff` a multi-word bit-vector over `nvars`
589/// variables) by Gaussian elimination to reduced row echelon form. Returns any solution `x` (bit-packed into
590/// `⌈nvars/64⌉` words) or `None` if the system is inconsistent. Multi-word, so it scales past 63 variables.
591pub(crate) fn gf2_solve(equations: &[(Vec<u64>, bool)], nvars: usize) -> Option<Vec<u64>> {
592    let words = nvars.div_ceil(64).max(1);
593    let bit = |v: &[u64], i: usize| (v[i / 64] >> (i % 64)) & 1 == 1;
594    let mut rows: Vec<(Vec<u64>, bool)> = equations.to_vec();
595    let mut pivots: Vec<usize> = Vec::new();
596    let mut r = 0usize;
597    for col in 0..nvars {
598        let Some(sel) = (r..rows.len()).find(|&i| bit(&rows[i].0, col)) else {
599            continue;
600        };
601        rows.swap(r, sel);
602        let (pmask, prhs) = (rows[r].0.clone(), rows[r].1);
603        for i in 0..rows.len() {
604            if i != r && bit(&rows[i].0, col) {
605                for w in 0..words {
606                    rows[i].0[w] ^= pmask[w];
607                }
608                rows[i].1 ^= prhs;
609            }
610        }
611        pivots.push(col);
612        r += 1;
613    }
614    if rows.iter().any(|(m, b)| *b && m.iter().all(|&w| w == 0)) {
615        return None; // 0 = 1 — inconsistent
616    }
617    // RREF: each pivot row's only set bits are its pivot column plus free columns; free vars = 0 ⟹ x_col = rhs.
618    let mut x = vec![0u64; words];
619    for (i, &col) in pivots.iter().enumerate() {
620        if rows[i].1 {
621            x[col / 64] |= 1u64 << (col % 64);
622        }
623    }
624    Some(x)
625}
626
627/// **A re-checkable degree-`d` Nullstellensatz LOWER-bound certificate.** Where [`nullstellensatz_refutes`]
628/// *decides* whether a degree-`d` refutation exists, this witnesses its NON-existence: a `GF(2)` linear
629/// functional `L` on the degree-`≤ d` monomials (a *degree-`d` pseudo-expectation`) with `L(1) = 1` and
630/// `L(m · p_C) = 0` for every generator. Such an `L` exists iff `1` is **not** in the `GF(2)`-span of the
631/// generators, i.e. iff there is no degree-`d` refutation — so `Some(L)` certifies the lower bound
632/// `NS-degree(F) > d`, independently re-checkable by [`check_ns_lower_bound`] (no trust in the solver). The
633/// certificate is returned as the list of monomials on which `L` is `1`. `None` means a degree-`d` refutation
634/// exists (no lower bound at `d`). Multi-word linear algebra, so the monomial basis is not size-capped —
635/// only `num_vars ≤ 20` for the explicit basis enumeration.
636pub fn ns_lower_bound_witness(num_vars: usize, clauses: &[Vec<Lit>], degree: usize) -> Option<Vec<u64>> {
637    if num_vars > 20 {
638        return None;
639    }
640    let mut index: HashMap<Mono, usize> = HashMap::new();
641    let mut monos: Vec<Mono> = Vec::new();
642    for m in 0u64..(1u64 << num_vars) {
643        if m.count_ones() as usize <= degree {
644            index.insert(m, monos.len());
645            monos.push(m);
646        }
647    }
648    let nb = monos.len();
649    let words = nb.div_ceil(64).max(1);
650    let mask_of = |p: &Poly| -> Vec<u64> {
651        let mut mask = vec![0u64; words];
652        for &m in p {
653            if let Some(&i) = index.get(&m) {
654                mask[i / 64] |= 1u64 << (i % 64);
655            }
656        }
657        mask
658    };
659    let mut eqs: Vec<(Vec<u64>, bool)> = Vec::new();
660    for c in clauses {
661        let width = c.len();
662        if width == 0 {
663            return None; // an empty clause is an immediate refutation — no lower bound
664        }
665        if width > degree {
666            continue;
667        }
668        let pc = clause_polynomial(c);
669        for &m in &monos {
670            if m.count_ones() as usize <= degree - width {
671                eqs.push((mask_of(&poly_mul_mono(&pc, m)), false)); // ⟨L, m·p_C⟩ = 0
672            }
673        }
674    }
675    let t0 = index[&0u64];
676    let mut target = vec![0u64; words];
677    target[t0 / 64] |= 1u64 << (t0 % 64);
678    eqs.push((target, true)); // L(1) = 1
679    let l = gf2_solve(&eqs, nb)?;
680    Some((0..nb).filter(|&i| (l[i / 64] >> (i % 64)) & 1 == 1).map(|i| monos[i]).collect())
681}
682
683/// [`ns_lower_bound_witness`] restricted to a **sub-basis** of monomials: the functional `L` is sought only
684/// on monomials `m` with `in_basis(m)` (and `L = 0` elsewhere), while the constraints `⟨L, m·p_C⟩ = 0` still
685/// range over *all* generators. `Some(L)` is a fully valid, `check_ns_lower_bound`-verifiable witness (the
686/// restriction only limits the *search*, not the check); `None` here means "no witness on this sub-basis" —
687/// which, unlike the full-basis version, does **not** imply a refutation exists. This exposes the *structure*
688/// of a lower bound: e.g. for pigeonhole, the partial-matching sub-basis already carries the certificate.
689pub fn ns_lower_bound_witness_on_basis(
690    num_vars: usize,
691    clauses: &[Vec<Lit>],
692    degree: usize,
693    in_basis: &dyn Fn(Mono) -> bool,
694) -> Option<Vec<u64>> {
695    if num_vars > 20 {
696        return None;
697    }
698    let mut index: HashMap<Mono, usize> = HashMap::new();
699    let mut basis: Vec<Mono> = Vec::new();
700    for m in 0u64..(1u64 << num_vars) {
701        if m.count_ones() as usize <= degree && in_basis(m) {
702            index.insert(m, basis.len());
703            basis.push(m);
704        }
705    }
706    index.get(&0u64)?; // the empty monomial must be in the basis for L(1) = 1
707    let nb = basis.len();
708    let words = nb.div_ceil(64).max(1);
709    let mask_of = |p: &Poly| -> Vec<u64> {
710        let mut mask = vec![0u64; words];
711        for &m in p {
712            if let Some(&i) = index.get(&m) {
713                mask[i / 64] |= 1u64 << (i % 64);
714            }
715        }
716        mask
717    };
718    let mults: Vec<Mono> =
719        (0u64..(1u64 << num_vars)).filter(|m| m.count_ones() as usize <= degree).collect();
720    let mut eqs: Vec<(Vec<u64>, bool)> = Vec::new();
721    for c in clauses {
722        let width = c.len();
723        if width == 0 {
724            return None;
725        }
726        if width > degree {
727            continue;
728        }
729        let pc = clause_polynomial(c);
730        for &m in &mults {
731            if m.count_ones() as usize <= degree - width {
732                eqs.push((mask_of(&poly_mul_mono(&pc, m)), false));
733            }
734        }
735    }
736    let t0 = index[&0u64];
737    let mut target = vec![0u64; words];
738    target[t0 / 64] |= 1u64 << (t0 % 64);
739    eqs.push((target, true));
740    let l = gf2_solve(&eqs, nb)?;
741    Some((0..nb).filter(|&i| (l[i / 64] >> (i % 64)) & 1 == 1).map(|i| basis[i]).collect())
742}
743
744/// Is a `PHP` monomial a **partial matching** — an injective partial pigeon→hole map? PHP's variable
745/// `x_{p,h}` sits at index `p·holes + h` ([`crate::families::php`]), so a monomial (a set of variables) is a
746/// partial matching iff no two of its variables share a pigeon or a hole. This is the support of the
747/// Razborov degree lower bound's pseudo-expectation.
748pub fn php_is_partial_matching(mono: Mono, holes: usize) -> bool {
749    let (mut pigeons, mut used_holes) = (0u64, 0u64);
750    let mut bits = mono;
751    while bits != 0 {
752        let v = bits.trailing_zeros() as usize;
753        let (p, h) = (v / holes, v % holes);
754        if (pigeons >> p) & 1 == 1 || (used_holes >> h) & 1 == 1 {
755            return false;
756        }
757        pigeons |= 1u64 << p;
758        used_holes |= 1u64 << h;
759        bits &= bits - 1;
760    }
761    true
762}
763
764/// Is a `PHP` monomial **hole-injective** — no two of its edges share a hole? Standard PHP forbids two
765/// pigeons in one hole but allows a pigeon in several holes, so the at-most-one clauses force the `GF(2)`
766/// pseudo-expectation to vanish exactly on hole-*collision* monomials — i.e. its support lies in the
767/// hole-injective monomials (partial functions hole→pigeon). This is looser than a partial matching (which
768/// also forbids pigeon repeats) and is the correct `GF(2)` support.
769pub fn php_is_hole_injective(mono: Mono, holes: usize) -> bool {
770    let mut used_holes = 0u64;
771    let mut bits = mono;
772    while bits != 0 {
773        let h = (bits.trailing_zeros() as usize) % holes;
774        if (used_holes >> h) & 1 == 1 {
775            return false;
776        }
777        used_holes |= 1u64 << h;
778        bits &= bits - 1;
779    }
780    true
781}
782
783/// Re-check a [`ns_lower_bound_witness`] certificate (zero trust in the producer): the functional `L` (given
784/// as the monomials on which it is `1`) must satisfy `L(1) = 1` and `L(m · p_C) = 0` for every degree-`≤ d`
785/// generator. `true` ⟹ `F` genuinely has no degree-`d` `GF(2)` Nullstellensatz refutation.
786pub fn check_ns_lower_bound(num_vars: usize, clauses: &[Vec<Lit>], degree: usize, witness: &[u64]) -> bool {
787    let l: BTreeSet<Mono> = witness.iter().copied().collect();
788    let pairs = |p: &Poly| -> bool { p.iter().filter(|m| l.contains(m)).count() % 2 == 1 }; // ⟨L, p⟩
789    if !l.contains(&0u64) {
790        return false; // L(1) must be 1
791    }
792    for c in clauses {
793        let width = c.len();
794        if width == 0 {
795            return false;
796        }
797        if width > degree {
798            continue;
799        }
800        let pc = clause_polynomial(c);
801        for m in 0u64..(1u64 << num_vars) {
802            if m.count_ones() as usize <= degree.saturating_sub(width) {
803                if pairs(&poly_mul_mono(&pc, m)) {
804                    return false; // ⟨L, m·p_C⟩ must be 0
805                }
806            }
807        }
808    }
809    true
810}
811
812/// The **atom** of the partition-of-unity recurrence on variable `v`: `(1 + x_v) + x_v`, which reduces to
813/// the constant `1` in the multilinear `GF(2)` ring (`x_v + x_v = 0`). This single identity is the engine of
814/// the whole `n = ∞` ratchet: it is *independent of `n`*, so a product of `n` copies of it is `1` at every
815/// scale.
816pub fn pou_atom(v: usize) -> Poly {
817    let x: Poly = [1u64 << v].into_iter().collect();
818    let one_plus_x: Poly = [0u64, 1u64 << v].into_iter().collect();
819    let mut atom = one_plus_x;
820    for m in x {
821        toggle(&mut atom, m);
822    }
823    atom
824}
825
826/// The **partition of unity** over the `n`-cube: `Σ_{a ∈ {0,1}ⁿ} δ_a`, the sum of every corner's
827/// point-indicator. It is the constant `1` for all `n` — the identity the constructive Nullstellensatz
828/// certificate ([`build_ns_certificate`]) rests on. Computed here by direct summation (for finite checks);
829/// [`pou_as_product`] gives the closed form that proves it `∀n`.
830pub fn partition_of_unity(n: usize) -> Poly {
831    let mut sum = Poly::new();
832    for a in 0..(1u64 << n) {
833        for m in point_indicator(a, n) {
834            toggle(&mut sum, m);
835        }
836    }
837    sum
838}
839
840/// The **closed form** of the partition of unity: the product `Π_{v<n} ((1+x_v) + x_v)` of the per-coordinate
841/// atoms. By distributivity `Σ_a Π_i f_{i,a_i} = Π_i (f_{i,0} + f_{i,1})`, this equals
842/// [`partition_of_unity`] — a *sum of `2ⁿ` products* rewritten as a *product of `n` sums*. Since every atom
843/// is `1` ([`pou_atom`]), the product is `1` for **every** `n`. This is the ratchet to `n = ∞`: not `2ⁿ`
844/// terms checked one cube at a time, but `n` identical unit factors.
845pub fn pou_as_product(n: usize) -> Poly {
846    let mut product: Poly = [0u64].into_iter().collect(); // the polynomial 1
847    for v in 0..n {
848        product = poly_mul(&product, &pou_atom(v));
849    }
850    product
851}
852
853/// Apply a formula automorphism to a monomial: a monomial is a *set of variables*, so the permutation
854/// part of `σ` relabels them (phase flips do not act on monomials). The bridge from the symmetry group
855/// to the polynomial basis.
856pub(crate) fn apply_perm_to_mono(perm: &crate::proof::Perm, m: Mono) -> Mono {
857    let mut out = 0u64;
858    for v in 0..perm.num_vars() {
859        if m & (1 << v) != 0 {
860            out |= 1 << perm.apply(Lit::pos(v as u32)).var();
861        }
862    }
863    out
864}
865
866/// **Symmetry-break Polynomial Calculus at the basis.** Partition the degree-≤`degree` monomials into
867/// orbits under the formula's automorphisms. The orbit count is the width of the *symmetry-reduced*
868/// Nullstellensatz system — a symmetric certificate is constant on each orbit, so the Gaussian runs over
869/// the quotient instead of all `C(n,d)` monomials. The same collapse that made the field cuts O(1),
870/// inherited by the algebraic engine. (Bounded to `num_vars ≤ 20`.)
871pub fn monomial_orbits(num_vars: usize, degree: usize, generators: &[crate::proof::Perm]) -> Vec<Vec<Mono>> {
872    let basis: BTreeSet<Mono> =
873        (0u64..(1u64 << num_vars)).filter(|m| m.count_ones() as usize <= degree).collect();
874    let mut seen: BTreeSet<Mono> = BTreeSet::new();
875    let mut orbits = Vec::new();
876    for &m in &basis {
877        if seen.contains(&m) {
878            continue;
879        }
880        let mut orbit = BTreeSet::new();
881        orbit.insert(m);
882        let mut stack = vec![m];
883        while let Some(x) = stack.pop() {
884            for g in generators {
885                let y = apply_perm_to_mono(g, x);
886                if basis.contains(&y) && orbit.insert(y) {
887                    stack.push(y);
888                }
889            }
890        }
891        for &x in &orbit {
892            seen.insert(x);
893        }
894        orbits.push(orbit.into_iter().collect());
895    }
896    orbits
897}
898
899/// The generators of the full symmetric group `Sₙ` on `n` variables: the adjacent transpositions
900/// `(i, i+1)`. `Sₙ` is the symmetry of a *fully-symmetric* formula (every variable interchangeable), and
901/// under it the monomial basis collapses to one orbit per degree — the sharpest instance of
902/// [`monomial_orbits`]' compression.
903pub fn symmetric_group_generators(n: usize) -> Vec<crate::proof::Perm> {
904    (0..n.saturating_sub(1))
905        .map(|i| {
906            let images: Vec<Lit> = (0..n)
907                .map(|v| {
908                    if v == i {
909                        Lit::pos((i + 1) as u32)
910                    } else if v == i + 1 {
911                        Lit::pos(i as u32)
912                    } else {
913                        Lit::pos(v as u32)
914                    }
915                })
916                .collect();
917            crate::proof::Perm::from_images(images)
918        })
919        .collect()
920}
921
922/// **Symmetry-reduced Nullstellensatz** — the algebraic refutation collapsed by the formula's symmetry.
923/// Full degree-`d` NS asks whether `1` lies in the `GF(2)`-span of the generators `m·p_C`, a Gaussian over
924/// up to `C(n,≤d)` monomial columns. When the formula has a symmetry group `G`, summing each generator's
925/// `G`-orbit gives an *invariant* generator, and every invariant polynomial is constant on the monomial
926/// orbits ([`monomial_orbits`]) — so the same span check runs over just `#orbits` columns and
927/// `#generator-orbits` rows. For a symmetric family that is the difference between `2^Θ(n)` and `O(1)`.
928///
929/// **Sound**: each row is an honest `GF(2)`-sum of NS generators, so `1` in the reduced span is `1` in the
930/// full span — a real refutation, hence UNSAT. Incomplete (a refutation need not be `G`-invariant), and
931/// fail-closed: if a passed generator is not a genuine symmetry (an orbit step leaves the generator set)
932/// it declines rather than reduce unsoundly. `generators` must be automorphisms of `clauses`.
933pub fn nullstellensatz_refutes_symmetric(
934    num_vars: usize,
935    clauses: &[Vec<Lit>],
936    degree: usize,
937    generators: &[crate::proof::Perm],
938) -> bool {
939    if num_vars > 20 {
940        return false;
941    }
942    // Monomial orbits give the reduced column basis; an invariant polynomial is all-or-nothing per orbit.
943    let mono_orbits = monomial_orbits(num_vars, degree, generators);
944    let n_orbits = mono_orbits.len();
945    let words = n_orbits.div_ceil(64).max(1);
946    let orbit_index: HashMap<Mono, usize> = (0u64..(1u64 << num_vars))
947        .filter(|m| m.count_ones() as usize <= degree)
948        .map(|m| (m, mono_orbits.iter().position(|o| o.contains(&m)).unwrap()))
949        .collect();
950    let to_orbit_bits = |p: &Poly| -> Vec<u64> {
951        let mut b = vec![0u64; words];
952        for (oi, orbit) in mono_orbits.iter().enumerate() {
953            if p.contains(&orbit[0]) {
954                b[oi / 64] |= 1 << (oi % 64);
955            }
956        }
957        b
958    };
959    let apply_sigma = |sigma: &crate::proof::Perm, p: &Poly| -> Poly {
960        let mut out = Poly::new();
961        for &m in p {
962            toggle(&mut out, apply_perm_to_mono(sigma, m));
963        }
964        out
965    };
966    let canon = |p: &Poly| -> Vec<Mono> { p.iter().copied().collect() };
967
968    // The NS generators m·p_C with deg(m·p_C) ≤ degree.
969    let monos: Vec<Mono> = orbit_index.keys().copied().collect();
970    let mut gens: Vec<Poly> = Vec::new();
971    for c in clauses {
972        if c.is_empty() {
973            return true; // an empty clause is `1 = 0`
974        }
975        let width = c.len();
976        if width > degree {
977            continue;
978        }
979        let pc = clause_polynomial(c);
980        for &m in &monos {
981            if m.count_ones() as usize <= degree - width {
982                gens.push(poly_mul_mono(&pc, m));
983            }
984        }
985    }
986    let gen_set: HashSet<Vec<Mono>> = gens.iter().map(|p| canon(p)).collect();
987
988    // Sum each generator-orbit into an invariant generator; express it in the orbit basis.
989    let mut seen: HashSet<Vec<Mono>> = HashSet::new();
990    let mut rows: Vec<Vec<u64>> = Vec::new();
991    for g in &gens {
992        if seen.contains(&canon(g)) {
993            continue;
994        }
995        let mut orbit_sum = Poly::new();
996        let mut local: HashSet<Vec<Mono>> = HashSet::from([canon(g)]);
997        let mut stack = vec![g.clone()];
998        while let Some(x) = stack.pop() {
999            seen.insert(canon(&x));
1000            for &m in &x {
1001                toggle(&mut orbit_sum, m);
1002            }
1003            for sigma in generators {
1004                let y = apply_sigma(sigma, &x);
1005                let yk = canon(&y);
1006                if !gen_set.contains(&yk) {
1007                    return false; // not a genuine symmetry of the NS system — fail closed
1008                }
1009                if local.insert(yk) {
1010                    stack.push(y);
1011                }
1012            }
1013        }
1014        rows.push(to_orbit_bits(&orbit_sum));
1015    }
1016
1017    // Target: the constant `1` (the empty monomial, its own orbit).
1018    let mut target = vec![0u64; words];
1019    if let Some(&oi) = orbit_index.get(&0u64) {
1020        target[oi / 64] |= 1 << (oi % 64);
1021    }
1022    in_gf2_span(rows, &target)
1023}
1024
1025#[cfg(test)]
1026mod tests {
1027    use super::*;
1028
1029    fn sat(num_vars: usize, clauses: &[Vec<Lit>]) -> bool {
1030        (0u64..(1u64 << num_vars)).any(|x| {
1031            clauses.iter().all(|c| c.iter().any(|l| ((x >> l.var()) & 1 != 0) == l.is_positive()))
1032        })
1033    }
1034
1035    /// **The constructive completeness certificate carries a re-checkable witness.** `nullstellensatz_refutes`
1036    /// only *decides* that a degree-`n` certificate exists; [`build_ns_certificate`] *produces* it — the
1037    /// explicit `g_C` with `Σ_C p_C·g_C = 1` — and it re-checks against the original clauses. The construction
1038    /// rests on the partition of unity `Σ_a δ_a = 1`; a SAT formula yields a re-checked model, never a false
1039    /// refutation; and the certificate fails closed against a clause set it was not built for.
1040    #[test]
1041    fn ns_certificate_is_a_constructive_completeness_proof() {
1042        // Partition of unity `Σ_a δ_a = 1` — the identity the whole construction rests on.
1043        let mut unity = Poly::new();
1044        for a in 0u64..8 {
1045            for m in point_indicator(a, 3) {
1046                toggle(&mut unity, m);
1047            }
1048        }
1049        assert!(unity.len() == 1 && unity.contains(&0u64), "Σ_a δ_a must be the constant 1");
1050
1051        // A transitive-XOR contradiction: x0=x1, x1=x2, x0≠x2 — UNSAT with no two clauses in direct conflict.
1052        let p = |v: u32| Lit::pos(v);
1053        let q = |v: u32| Lit::neg(v);
1054        let core = vec![
1055            vec![q(0), p(1)], vec![p(0), q(1)],
1056            vec![q(1), p(2)], vec![p(1), q(2)],
1057            vec![p(0), p(2)], vec![q(0), q(2)],
1058        ];
1059        let cert = build_ns_certificate(3, &core).expect("the UNSAT core has a constructive certificate");
1060        assert!(cert.verify(&core), "the constructive certificate re-checks against the original clauses");
1061        assert!(cert.degree() <= cert.num_vars(), "the certificate degree is ≤ n by construction");
1062        // Fail-closed: the certificate must not verify against a clause set it was not built for.
1063        assert!(!cert.verify(&core[..core.len() - 1]), "a certificate must not verify a different clause set");
1064
1065        // SAT ⇒ a re-checked satisfying assignment, never a spurious refutation.
1066        let satisfiable = vec![vec![p(0), p(1)], vec![q(0), p(2)]];
1067        match build_ns_certificate(3, &satisfiable) {
1068            Err(model) => assert!(
1069                satisfiable.iter().all(|c| c.iter().any(|l| model[l.var() as usize] == l.is_positive())),
1070                "the returned SAT witness must satisfy every clause"
1071            ),
1072            Ok(_) => panic!("a satisfiable formula must not yield a refutation certificate"),
1073        }
1074    }
1075
1076    /// **The pattern that ratchets to `n = ∞` (discrete-math induction).** The partition of unity — the
1077    /// identity `Σ_a δ_a = 1` behind constructive Nullstellensatz completeness — is not checked cube-by-cube
1078    /// (that dies at `2ⁿ`). It FACTORS: `Σ_a Π_i f_{i,a_i} = Π_i ((1+x_i) + x_i)`, a product of `n`
1079    /// per-coordinate atoms, each equal to the constant `1`. Base `PoU(0) = 1`; step `PoU(n+1) = PoU(n)·atom
1080    /// = PoU(n)·1 = PoU(n)`. The step is `n`-INDEPENDENT (one fixed identity `(1+x)+x = 1`), so induction
1081    /// closes it for ALL `n`. Here: the atom is `1`, the base holds, the recurrence holds, and `PoU(n)`
1082    /// equals both the direct sum and the product — the finite checks that pin the `∀n` factorization.
1083    #[test]
1084    fn partition_of_unity_is_one_for_all_n_by_the_atom_factorization() {
1085        let one: Poly = [0u64].into_iter().collect();
1086        // The atom is the constant 1 — the n-independent engine of the ratchet.
1087        for v in 0..8 {
1088            assert_eq!(pou_atom(v), one, "the atom (1+x{v})+x{v} reduces to 1");
1089        }
1090        // Base case: the 0-cube's partition of unity is 1.
1091        assert_eq!(partition_of_unity(0), one, "PoU(0) = 1 (base case)");
1092        for n in 0..=12 {
1093            // The factorization (distributivity): the 2ⁿ-term sum equals the n-factor product.
1094            assert_eq!(partition_of_unity(n), pou_as_product(n), "PoU(n) = Π atoms (sum-of-products = product-of-sums)");
1095            // And the product of n ones is one — the conclusion, at every n.
1096            assert_eq!(partition_of_unity(n), one, "PoU(n) = 1");
1097        }
1098        // The inductive STEP, explicit: PoU(n+1) = PoU(n) · atom_n (the recurrence the induction climbs).
1099        for n in 0..12 {
1100            assert_eq!(
1101                partition_of_unity(n + 1),
1102                poly_mul(&partition_of_unity(n), &pou_atom(n)),
1103                "PoU(n+1) = PoU(n)·atom — the n-uniform inductive step"
1104            );
1105        }
1106    }
1107
1108    /// **A parametric family with machine-checked degree growth.** The all-corners cube `F_n` (every one of
1109    /// the `2ⁿ` assignments forbidden by a full-width clause) has minimum `GF(2)` Nullstellensatz degree
1110    /// *exactly* `n` — certified at each `n` by a re-checkable dual witness that no degree-`(n-1)` refutation
1111    /// exists, plus a degree-`n` refutation that does. That is machine-checked, re-verifiable **linear degree
1112    /// growth**. Honest caveat: this particular bound is *width-driven* — a width-`n` family admits no
1113    /// generator below degree `n` — so it is a clean but "easy" lower bound. A *bounded-width* family with
1114    /// super-constant degree is the deeper object; PHP(3) is measured as an honest data point.
1115    #[test]
1116    /// **The genuine Ω(n) degree lower bound: pigeonhole is NS-hard, and its degree GROWS.** PHP(m) (m
1117    /// pigeons, m−1 holes) has clause width `≤ m−1` but its `GF(2)` Nullstellensatz degree is `2(m−1) =
1118    /// Θ(√vars)` — the classical pigeonhole degree bound. We certify the EXACT degree for m = 3 (degree 4) and
1119    /// m = 4 (degree 6): a re-checkable dual witness that there is no degree-`(2m−3)` refutation, and a
1120    /// refutation at `2(m−1)`. The certified degree strictly *exceeds the clause width* (so it is not the
1121    /// trivial width bound) and strictly *grows* with `m` — a genuine, non-width, parametric degree lower
1122    /// bound with certified growth. (The uniform `∀m` proof that it is exactly `2(m−1)` is the classical
1123    /// Razborov-style theorem; here it is certified per-`m` with re-checkable witnesses.)
1124    #[test]
1125    fn pigeonhole_has_certified_growing_non_width_ns_degree() {
1126        let measured = [(3usize, 4usize), (4, 6)]; // (pigeons, exact GF(2) NS degree)
1127        let mut degrees = Vec::new();
1128        for (m, deg) in measured {
1129            let (php, _) = crate::families::php(m);
1130            // Re-checkable dual witness: no degree-(deg−1) refutation ⟹ NS-degree > deg−1.
1131            let w = ns_lower_bound_witness(php.num_vars, &php.clauses, deg - 1)
1132                .unwrap_or_else(|| panic!("PHP({m}): a degree-{} lower-bound witness must exist", deg - 1));
1133            assert!(check_ns_lower_bound(php.num_vars, &php.clauses, deg - 1, &w), "PHP({m}): NS-degree > {} re-checks", deg - 1);
1134            // Refuted at `deg` ⟹ NS-degree(PHP(m)) = deg exactly.
1135            assert!(nullstellensatz_refutes(php.num_vars, &php.clauses, deg), "PHP({m}): a degree-{deg} refutation exists");
1136            assert!(!nullstellensatz_refutes(php.num_vars, &php.clauses, deg - 1), "PHP({m}): NS-degree = {deg} exactly");
1137            // Genuine (non-width): the degree exceeds the max clause width `m−1`.
1138            assert!(deg > m - 1, "PHP({m}): NS-degree {deg} > max clause width {} — not a width bound", m - 1);
1139            degrees.push(deg);
1140        }
1141        // Certified GROWTH: the NS degree strictly climbs with the family size (Θ(√vars)).
1142        assert!(degrees.windows(2).all(|w| w[1] > w[0]), "the certified NS degree grows with n: {degrees:?}");
1143    }
1144
1145    /// **The symmetric NS width is CONSTANT in `m` at each fixed degree.** An orbit-type of a degree-`≤d` PHP
1146    /// monomial under `Sₘ × Sₘ₋₁` is the isomorphism type of its bipartite pigeon/hole graph; there are only
1147    /// finitely many such types with `≤ d` edges, and every one is realizable once `m ≥ d+1`. So for each fixed
1148    /// `d` the orbit-type count is **independent of `m`** — the symmetric Nullstellensatz basis at degree `d` is
1149    /// `O(1)`-wide at every scale. (The *witness* of §5.3 has degree `2m−3` that grows with `m`, so its own
1150    /// orbit count grows as `Σ_{k≤m−1} p(k)`; this is the complementary fixed-degree statement.) We verify the
1151    /// count stabilizes across `m` for `d = 1, 2, 3`.
1152    #[test]
1153    fn php_symmetric_ns_width_is_constant_in_m_at_fixed_degree() {
1154        // `monomial_orbits` enumerates the 2^vars monomial cube, so keep PHP small (m ≤ 5 ⟹ ≤ 20 vars). The
1155        // orbit-type count stabilizes once m ≥ d+1 (enough pigeons/holes to realize every ≤d-edge graph type).
1156        for (d, ms) in [(1usize, vec![2, 3, 4, 5]), (2, vec![3, 4, 5]), (3, vec![4, 5])] {
1157            let counts: Vec<usize> = ms
1158                .iter()
1159                .map(|&m| {
1160                    let (php, _) = crate::families::php(m);
1161                    monomial_orbits(php.num_vars, d, &crate::hypercube::php_perm_symmetries(m)).len()
1162                })
1163                .collect();
1164            eprintln!("degree {d}: symmetric-NS orbit-type counts across m = {counts:?}");
1165            assert!(counts.windows(2).all(|w| w[0] == w[1]), "degree {d}: orbit-type count constant in m: {counts:?}");
1166        }
1167    }
1168
1169    /// **The uniform lower bound is FORCED BY SYMMETRY.** The witness `L = [hole-injective]` is invariant
1170    /// under PHP's automorphism group (permute pigeons × permute holes) — it is the *symmetric*
1171    /// pseudo-expectation. So the whole degree lower bound is a symmetry fact: the invariant witness collapses
1172    /// the exponential monomial basis to a handful of orbit-types (symmetry = compression = the certificate).
1173    /// We verify invariance and report the orbit compression.
1174    #[test]
1175    fn the_uniform_php_witness_is_the_symmetric_pseudo_expectation() {
1176        for m in [3usize, 4] {
1177            let (php, _) = crate::families::php(m);
1178            let holes = m - 1;
1179            let d = 2 * holes - 1;
1180            let l: BTreeSet<Mono> = (0u64..(1u64 << php.num_vars))
1181                .filter(|&mo| mo.count_ones() as usize <= d && php_is_hole_injective(mo, holes))
1182                .collect();
1183            let gens = crate::hypercube::php_perm_symmetries(m);
1184            // Invariance: every pigeon/hole permutation maps the witness onto itself.
1185            for g in &gens {
1186                for &mo in &l {
1187                    assert!(l.contains(&apply_perm_to_mono(g, mo)), "PHP({m}): the uniform witness is symmetry-invariant");
1188                }
1189            }
1190            // Compression: the witness (|L| monomials) is a union of few symmetry orbit-types.
1191            let orbits = monomial_orbits(php.num_vars, d, &gens);
1192            let witness_orbits = orbits.iter().filter(|o| l.contains(&o[0])).count();
1193            eprintln!(
1194                "PHP({m}): symmetric witness = {} monomials → {} orbit-types (of {} total), compression ×{:.1}",
1195                l.len(), witness_orbits, orbits.len(), l.len() as f64 / witness_orbits as f64
1196            );
1197            assert!(witness_orbits < l.len(), "PHP({m}): symmetry compresses the witness to fewer orbit-types");
1198        }
1199    }
1200
1201    /// **A UNIFORM `∀m` degree lower bound, via a closed-form parity-aware witness.** The explicit functional
1202    /// `L(M) = [M is hole-injective]` (1 on every hole-injective monomial, 0 elsewhere) is a valid
1203    /// degree-`(2m−3)` `GF(2)` pseudo-expectation for PHP(m) — proving `NS-degree(PHP(m)) ≥ 2(m−1)` for **all
1204    /// m**, by a single argument, not a per-`m` Gaussian search:
1205    ///   - the at-most-one clauses vanish because their generators are hole collisions (`L = 0`);
1206    ///   - each pigeon clause gives `⟨L, m·p_C⟩ = Σ_{S⊆U} 1 = 2^{|U|} ≡ 0 (mod 2)`, and the multiplier `m` has
1207    ///     degree `≤ (2m−3) − (m−1) = m−2 < holes`, so it misses ≥1 hole (`|U| ≥ 1`) — the parity that holds
1208    ///     over `GF(2)` and **fails** over characteristic 0.
1209    /// Here the *explicit* `L` is re-checked at `m = 3, 4` (the argument gives every `m`). This is a genuine,
1210    /// uniform proof-complexity lower bound — a hardness result (the P ≠ NP direction), not an algorithm.
1211    #[test]
1212    fn uniform_parity_aware_witness_proves_php_degree_bound_for_all_m() {
1213        for m in [3usize, 4] {
1214            let (php, _) = crate::families::php(m);
1215            let holes = m - 1;
1216            let d = 2 * holes - 1; // 2m − 3
1217            let l: Vec<u64> = (0u64..(1u64 << php.num_vars))
1218                .filter(|&mo| mo.count_ones() as usize <= d && php_is_hole_injective(mo, holes))
1219                .collect();
1220            assert!(
1221                check_ns_lower_bound(php.num_vars, &php.clauses, d, &l),
1222                "PHP({m}): the hole-injective indicator is a valid degree-{d} pseudo-expectation ⟹ NS-degree ≥ {}",
1223                2 * holes
1224            );
1225            // Tight: it matches the measured exact degree 2(m−1).
1226            assert!(nullstellensatz_refutes(php.num_vars, &php.clauses, 2 * holes), "PHP({m}): refuted at 2(m−1)");
1227        }
1228    }
1229
1230    /// **THE BRIDGE: the symmetric certificate's *depth* equals the NS degree, exactly, `∀m`.** The naive
1231    /// guess — that the affine-symmetry *group* grows with hardness — is false and the tools prove it: PHP is
1232    /// affine-shear-*rigid* (no shears at any depth), and parity carries only a constant depth-2 shear. So the
1233    /// symmetry that governs degree is not read off the group's *width* but off the *depth of the invariant
1234    /// certificate it supports*. Define the **symmetric depth** of a family as the greatest degree `d` at which
1235    /// its symmetry-invariant pseudo-expectation is still valid (the deepest the symmetric compression reaches
1236    /// before a refutation forces it to zero). For PHP(m) that certificate is the hole-injective indicator
1237    /// (§5.3), invariant under `Sₘ × Sₘ₋₁`, and its symmetric depth is `2m−3`, while the NS degree is `2m−2` —
1238    /// so **`NS-degree(PHP(m)) = symmetric-depth(PHP(m)) + 1` for every `m`**. The `+1` is exactly the
1239    /// witness↔refutation duality unit: a degree-`d` pseudo-expectation certifies "no degree-`d` refutation," so
1240    /// the deepest surviving symmetric certificate sits precisely one below the refutation degree. This is the
1241    /// honest form of "depth tracks NS degree" — not a loose correlation but an exact identity, certified at
1242    /// `m = 3, 4` here and proven for all `m` by the uniform parity-aware witness. Symmetry-depth and
1243    /// proof-degree are one number, read twice.
1244    #[test]
1245    fn the_symmetric_certificate_depth_is_exactly_one_below_ns_degree() {
1246        for m in [3usize, 4] {
1247            let (php, _) = crate::families::php(m);
1248            let holes = m - 1;
1249            let nv = php.num_vars;
1250
1251            // NS degree = least d with a degree-d refutation (scan up).
1252            let ns_degree = (1..=nv)
1253                .find(|&d| nullstellensatz_refutes(nv, &php.clauses, d))
1254                .expect("PHP is UNSAT so some refutation degree exists");
1255
1256            // Symmetric depth = greatest d at which the invariant (hole-injective) pseudo-expectation is valid.
1257            let symmetric_depth = (0..=nv)
1258                .rev()
1259                .find(|&d| {
1260                    let w: Vec<u64> = (0u64..(1u64 << nv))
1261                        .filter(|&mo| mo.count_ones() as usize <= d && php_is_hole_injective(mo, holes))
1262                        .collect();
1263                    check_ns_lower_bound(nv, &php.clauses, d, &w)
1264                })
1265                .expect("the invariant witness is valid at some degree");
1266
1267            // The exact bridge, both sides computed independently.
1268            assert_eq!(ns_degree, 2 * (m - 1), "PHP({m}): NS degree is 2(m−1)");
1269            assert_eq!(symmetric_depth, 2 * m - 3, "PHP({m}): symmetric certificate depth is 2m−3");
1270            assert_eq!(
1271                ns_degree,
1272                symmetric_depth + 1,
1273                "PHP({m}): NS-degree = symmetric-depth + 1 — the witness↔refutation duality unit"
1274            );
1275            eprintln!("PHP({m}): symmetric-depth={symmetric_depth}, NS-degree={ns_degree} = depth+1 ✓");
1276        }
1277    }
1278
1279    /// **The symmetric-group arity grades the certificate depth (`∀m`).** The dichotomy (§4) says PHP's
1280    /// hardness is protected by *permutation* symmetry — the group `Sₘ × Sₘ₋₁`, of arity `m` (the pigeon
1281    /// count). This is the tracking that the dichotomy predicts *should* exist on the permutation side, and it
1282    /// does, exactly: the depth of the symmetry-invariant certificate (the hole-injective indicator, §5.3) is
1283    /// `2m − 3`, an exact strictly-increasing linear function of the arity — **each unit of arity buys exactly
1284    /// two units of certificate depth**. Not a correlation; a closed form, certified at `m = 3, 4` and proven
1285    /// for all `m` by the uniform witness. This is the graded law the failed shear/symplectic chases were
1286    /// groping for — on the correct axis (arity), not the wrong one (linear-symmetry weight, structurally
1287    /// pinned at 2 by the weight-2 generation of the classical groups).
1288    #[test]
1289    fn the_symmetric_group_arity_grades_the_certificate_depth() {
1290        let mut points = Vec::new();
1291        for m in [3usize, 4] {
1292            let (php, _) = crate::families::php(m);
1293            let holes = m - 1;
1294            let nv = php.num_vars;
1295            let cert_depth = (0..=nv)
1296                .rev()
1297                .find(|&d| {
1298                    let w: Vec<u64> = (0u64..(1u64 << nv))
1299                        .filter(|&mo| mo.count_ones() as usize <= d && php_is_hole_injective(mo, holes))
1300                        .collect();
1301                    check_ns_lower_bound(nv, &php.clauses, d, &w)
1302                })
1303                .expect("the invariant witness is valid at some degree");
1304            assert_eq!(cert_depth, 2 * m - 3, "arity {m}: certificate depth = 2·arity − 3");
1305            points.push((m, cert_depth));
1306        }
1307        let (m0, d0) = points[0];
1308        let (m1, d1) = points[1];
1309        assert_eq!(
1310            (d1 - d0) / (m1 - m0),
1311            2,
1312            "each unit of symmetric-group arity buys exactly two units of certificate depth"
1313        );
1314        for (m, d) in points {
1315            eprintln!("arity m={m} → certificate depth {d} = 2m−3");
1316        }
1317    }
1318
1319    /// **THE CAPSTONE — one group-theoretic number grades a machine-certified lower bound across three proof
1320    /// systems.** For `PHP_m`, a single quantity, the *arity* `m` of the protecting symmetric group `Sₘ ×
1321    /// Sₘ₋₁` (order `m!·(m−1)!`), drives — through certified chains, no trust — three independent
1322    /// proof-complexity coordinates, each computed from a *different* proof system and each strictly increasing
1323    /// with `m`:
1324    ///   arity `m`  →  symmetric-certificate depth `2m−3`  →  Nullstellensatz degree `2m−2`  →  resolution
1325    ///   width `m−1` (lower bound re-checked by a closed set).
1326    /// This is the thesis "symmetry = compression = complexity" made an *exact, executable, cross-system law*:
1327    /// the amount of symmetry (arity) determines the amount of hardness (degree, width), end to end, in the
1328    /// kernel's own currency of re-checkable certificates. By Ben-Sasson–Wigderson the growing width forces
1329    /// super-polynomial resolution *size* — the classical exponential lower bound this chain terminates in.
1330    ///
1331    /// **What this is not** — the honest boundary, stated in the theorem itself. This lives entirely in the
1332    /// *structured / symmetric* regime. As `work/PROOF_SKETCH.md` records: *"a fast algorithm for structured or
1333    /// symmetric instances says nothing; NP-hardness lives in the worst case."* The chain measures how a
1334    /// symmetry number places a *symmetric family* between the two kernel poles (trivial structure at degree 1;
1335    /// completeness at degree `n`); it says nothing about worst-case instances and is **not** a step toward P
1336    /// vs NP. The ultimate here is an exact law of the measurement science, not a resolution of the open
1337    /// problem — and it is stronger for being honest about which it is.
1338    #[test]
1339    fn the_ultimate_symmetry_to_hardness_chain_is_certified() {
1340        use crate::res_width::{
1341            check_res_width_lower_bound, min_res_width_clauses, resolution_width_closure, WidthConvention,
1342        };
1343        let mut chain = Vec::new();
1344        for m in [3usize, 4] {
1345            let (php, _) = crate::families::php(m);
1346            let holes = m - 1;
1347            let nv = php.num_vars;
1348            let group_order = (1..=m as u128).product::<u128>() * (1..=holes as u128).product::<u128>();
1349
1350            // (1) symmetric-certificate depth
1351            let cert_depth = (0..=nv)
1352                .rev()
1353                .find(|&d| {
1354                    let w: Vec<u64> = (0u64..(1u64 << nv))
1355                        .filter(|&mo| mo.count_ones() as usize <= d && php_is_hole_injective(mo, holes))
1356                        .collect();
1357                    check_ns_lower_bound(nv, &php.clauses, d, &w)
1358                })
1359                .unwrap();
1360            // (2) Nullstellensatz degree
1361            let ns_degree = (1..=nv).find(|&d| nullstellensatz_refutes(nv, &php.clauses, d)).unwrap();
1362            // (3) resolution width, with a re-checked lower-bound certificate (zero trust)
1363            let res_width = min_res_width_clauses(nv, &php.clauses, WidthConvention::WideAxioms).unwrap();
1364            let closed = resolution_width_closure(&php.clauses, res_width - 1, WidthConvention::WideAxioms);
1365            assert!(
1366                check_res_width_lower_bound(&php.clauses, res_width - 1, WidthConvention::WideAxioms, &closed),
1367                "PHP({m}): certified resolution width > {}",
1368                res_width - 1
1369            );
1370
1371            // Each coordinate is the arity, transformed by a certified chain.
1372            assert_eq!(cert_depth, 2 * m - 3, "arity {m}: certificate depth = 2m−3");
1373            assert_eq!(ns_degree, 2 * m - 2, "arity {m}: NS degree = 2m−2");
1374            assert_eq!(ns_degree, cert_depth + 1, "the witness↔refutation duality unit");
1375            assert_eq!(res_width, m - 1, "arity {m}: resolution width = m−1");
1376            chain.push((m, group_order, cert_depth, ns_degree, res_width));
1377        }
1378
1379        // One symmetry number, three proof systems, all graded strictly by the arity.
1380        let (a, b) = (chain[0], chain[1]);
1381        assert!(b.0 > a.0, "arity increases");
1382        assert!(b.2 > a.2, "certificate depth grows with arity");
1383        assert!(b.3 > a.3, "NS degree grows with arity");
1384        assert!(b.4 > a.4, "resolution width grows with arity");
1385        for (m, order, d, g, w) in chain {
1386            eprintln!(
1387                "arity m={m} (|Sₘ×Sₘ₋₁|={order}): cert-depth={d}=2m−3  NS-degree={g}=2m−2  res-width={w}=m−1 — all certified, all graded by m"
1388            );
1389        }
1390    }
1391
1392    /// **The dichotomy the exploration forced into the open, as asserted facts.** Over `GF(2)` a formula's
1393    /// symmetry is one of two *types*, and the type predicts the complexity regime: PHP carries growing NS
1394    /// degree yet is affine-shear-**rigid** (no shear automorphism at any depth up to the variable count — its
1395    /// hardness is permutation-protected), whereas a single parity block carries a nontrivial affine shear at
1396    /// constant depth 2 and no NS degree at all (it is satisfiable — Gaussian-trivial). Affine-shear symmetry
1397    /// and high NS degree are mutually exclusive here: the symmetry *type* is the complexity *type*. This is
1398    /// why "shear depth tracks degree" is the wrong bridge and [`the_symmetric_certificate_depth_is_exactly_one_below_ns_degree`]
1399    /// is the right one.
1400    #[test]
1401    fn affine_shear_symmetry_and_high_ns_degree_are_mutually_exclusive() {
1402        use crate::census::affine_composite_shear_generators;
1403        // PHP: growing NS degree, but rigid under affine shears at every depth.
1404        for m in [3usize, 4] {
1405            let (php, _) = crate::families::php(m);
1406            let nv = php.num_vars;
1407            for depth in 1..=nv.min(5) {
1408                let shears = affine_composite_shear_generators(nv, &php.clauses, depth);
1409                assert!(
1410                    shears.iter().all(|(s, _)| s.len() != depth),
1411                    "PHP({m}) is affine-shear-rigid — no genuine depth-{depth} shear"
1412                );
1413            }
1414            assert!(nullstellensatz_refutes(nv, &php.clauses, 2 * (m - 1)), "PHP({m}) NS degree grows to 2(m−1)");
1415        }
1416        // Parity block: a nontrivial shear appears at depth 2 (never depth 1), and it is satisfiable.
1417        for w in [3usize, 4, 5] {
1418            let vars: Vec<u32> = (0..w as u32).collect();
1419            let clauses: Vec<Vec<Lit>> = (0u32..(1 << w))
1420                .filter(|p| p.count_ones() % 2 == 1)
1421                .map(|p| (0..w).map(|i| Lit::new(vars[i], (p >> i) & 1 == 0)).collect())
1422                .collect();
1423            assert!(affine_composite_shear_generators(w, &clauses, 1).is_empty(), "parity({w}): no depth-1 shear");
1424            assert!(
1425                affine_composite_shear_generators(w, &clauses, 2).iter().any(|(s, _)| s.len() == 2),
1426                "parity({w}): a genuine depth-2 shear exists — constant, does not grow with w"
1427            );
1428        }
1429    }
1430
1431    /// **The characteristic-2 obstruction to symmetrizing a proof — why the witness must be found *natively*.**
1432    /// Over a field of characteristic 0 the Reynolds operator `L ↦ (1/|G|) Σ_{g∈G} g·L` averages *any* valid
1433    /// witness into a symmetric one, so "the extremal witness is symmetric" is free (Razborov's symmetric
1434    /// pseudo-expectation is obtained this way). Over `GF(2)` there is no `1/|G|`, and the un-normalized sum
1435    /// `Σ_{g∈G} g·L` evaluates on the constant monomial to `Σ_{g} L(g⁻¹·1) = |G|·L(1) = |G| (mod 2)` — so when
1436    /// `|G|` is **even** it *annihilates* the normalization `L(1)=1`, collapsing the witness to the zero
1437    /// functional. The pigeonhole group `Sₘ × Sₘ₋₁` has order `m!·(m−1)!`, even for every `m ≥ 2`. So symmetry
1438    /// is **not** free here: averaging destroys the witness, and the symmetric pseudo-expectation
1439    /// (the hole-injective indicator) must be exhibited *natively* — which is exactly what the parity-aware
1440    /// construction does. This is the GF(2)-specific sauce: the degree bound lives precisely where a native
1441    /// symmetric proof survives an obstruction that kills the averaged one.
1442    #[test]
1443    fn over_gf2_symmetrizing_a_proof_annihilates_when_the_group_is_even() {
1444        for m in [3usize, 4] {
1445            let (php, _) = crate::families::php(m);
1446            let holes = m - 1;
1447            let d = 2 * holes - 1;
1448            let group = close_perm_group(&crate::hypercube::php_perm_symmetries(m), php.num_vars);
1449            let expected_order: usize =
1450                (1..=m).product::<usize>() * (1..=holes).product::<usize>(); // m! · (m−1)!
1451            assert_eq!(group.len(), expected_order, "PHP({m}): |Sₘ × Sₘ₋₁| = m!·(m−1)!");
1452            assert_eq!(group.len() % 2, 0, "PHP({m}): the pigeonhole group is even");
1453
1454            // A genuine, re-checkable degree-d witness exists.
1455            let witness = ns_lower_bound_witness(php.num_vars, &php.clauses, d).expect("witness exists");
1456            assert!(check_ns_lower_bound(php.num_vars, &php.clauses, d, &witness), "the witness re-checks");
1457            assert!(witness.contains(&0u64), "a valid pseudo-expectation carries L(1)=1 (the empty monomial)");
1458
1459            // Reynolds over GF(2) annihilates it: the constant monomial is toggled |G| times ⟹ gone.
1460            let averaged = symmetrize(&witness, &group);
1461            assert!(
1462                !averaged.contains(&0u64),
1463                "PHP({m}): symmetrizing over an even group kills L(1)=1 — the averaged witness is degenerate"
1464            );
1465
1466            // Yet the NATIVE symmetric witness (hole-injective indicator) is both invariant AND valid.
1467            let native: Vec<u64> = (0u64..(1u64 << php.num_vars))
1468                .filter(|&mo| mo.count_ones() as usize <= d && php_is_hole_injective(mo, holes))
1469                .collect();
1470            assert!(check_ns_lower_bound(php.num_vars, &php.clauses, d, &native), "PHP({m}): native witness valid");
1471            let native_set: BTreeSet<Mono> = native.iter().copied().collect();
1472            for g in &group {
1473                for &mo in &native {
1474                    assert!(native_set.contains(&apply_perm_to_mono(g, mo)), "PHP({m}): native witness is invariant");
1475                }
1476            }
1477            eprintln!(
1478                "PHP({m}): |G|={} (even) ⟹ averaged witness annihilated; native symmetric witness = {} monomials survives",
1479                group.len(), native.len()
1480            );
1481        }
1482    }
1483
1484    /// **A structural finding: over GF(2), the pigeonhole witness is NOT the classical matching one.** The
1485    /// restricted-basis tool is sound — with the full (all-monomial) sub-basis it reproduces a valid witness.
1486    /// We then probe the classical char-0 structure (Razborov's pseudo-expectation, supported on partial
1487    /// matchings) over GF(2): it does **not** carry the witness. The reason is a parity obstruction — the
1488    /// pigeon-clause constraint on the matching indicator reduces to `1 + (holes − |m|) ≡ 0 (mod 2)`, which
1489    /// fails for half the monomials. So the GF(2) degree lower bound needs a genuinely different (parity-aware)
1490    /// witness structure than the field-of-characteristic-0 case — an honest, non-obvious observation the tool
1491    /// surfaces, and a caution for anyone porting classical lower bounds to `GF(2)`.
1492    #[test]
1493    fn pigeonhole_witness_structure_differs_over_gf2() {
1494        for (m, deg) in [(3usize, 4usize), (4, 6)] {
1495            let (php, _) = crate::families::php(m);
1496            let holes = m - 1;
1497            // Soundness of the restricted-basis tool: the full sub-basis reproduces a valid witness.
1498            let full = ns_lower_bound_witness_on_basis(php.num_vars, &php.clauses, deg - 1, &|_| true)
1499                .expect("full-basis witness exists");
1500            assert!(check_ns_lower_bound(php.num_vars, &php.clauses, deg - 1, &full), "full-basis witness re-checks");
1501            // The classical partial-matching sub-basis does NOT carry the GF(2) witness (parity obstruction:
1502            // it also forbids pigeon repeats, which GF(2) does not require).
1503            let pm = move |mono: Mono| php_is_partial_matching(mono, holes);
1504            let on_pm = ns_lower_bound_witness_on_basis(php.num_vars, &php.clauses, deg - 1, &pm);
1505            // The correct GF(2) support is HOLE-INJECTIVE monomials — the at-most-one clauses vanish exactly
1506            // on hole collisions. This looser sub-basis DOES carry a valid, re-checkable witness.
1507            let hi = move |mono: Mono| php_is_hole_injective(mono, holes);
1508            let on_hi = ns_lower_bound_witness_on_basis(php.num_vars, &php.clauses, deg - 1, &hi);
1509            eprintln!(
1510                "PHP({m}) over GF(2): partial-matching carries witness? {} ; hole-injective? {}",
1511                on_pm.is_some(),
1512                on_hi.is_some()
1513            );
1514            assert!(on_pm.is_none(), "PHP({m}): partial-matching sub-basis fails (too strict for GF(2))");
1515            let w = on_hi.expect("hole-injective sub-basis must carry the GF(2) witness");
1516            assert!(check_ns_lower_bound(php.num_vars, &php.clauses, deg - 1, &w), "PHP({m}): hole-injective witness re-checks");
1517            assert!(w.iter().all(|&mo| php_is_hole_injective(mo, holes)), "PHP({m}): witness supported on hole-injective monomials");
1518        }
1519    }
1520
1521    fn parametric_family_has_machine_checked_degree_growth() {
1522        for n in 2..=5usize {
1523            let f_n: Vec<Vec<Lit>> = (0u64..(1u64 << n))
1524                .map(|a| (0..n as u32).map(|v| Lit::new(v, (a >> v) & 1 == 0)).collect())
1525                .collect();
1526            let w = ns_lower_bound_witness(n, &f_n, n - 1)
1527                .unwrap_or_else(|| panic!("F_{n}: a degree-(n-1) lower-bound witness must exist"));
1528            assert!(check_ns_lower_bound(n, &f_n, n - 1, &w), "F_{n}: the degree-{} lower bound re-checks", n - 1);
1529            assert!(nullstellensatz_refutes(n, &f_n, n), "F_{n}: a degree-{n} refutation exists (NS-degree = n)");
1530            assert!(!nullstellensatz_refutes(n, &f_n, n - 1), "F_{n}: no degree-(n-1) refutation (NS-degree > n-1)");
1531        }
1532        // Bounded-width data points: PHP is a COUNTING principle — incomparable to GF(2) algebra — so it is
1533        // algebraically hard for Nullstellensatz. We certify `NS-degree(PHP(m)) > 3` for m = 3 (6 vars) AND
1534        // m = 4 (12 vars), the latter with a monomial basis of 299 ≫ 63 (exercising the multi-word solver).
1535        // These are genuine, non-width-driven degree lower bounds, each a re-checkable dual witness.
1536        let probe = 3usize;
1537        for pigeons in [3usize, 4] {
1538            let (php, _) = crate::families::php(pigeons);
1539            let min_deg = (1..=probe).find(|&d| nullstellensatz_refutes(php.num_vars, &php.clauses, d));
1540            eprintln!("PHP({pigeons}): {} vars, min GF(2) NS degree (probed ≤{probe}) = {min_deg:?}", php.num_vars);
1541            let (d, msg) = match min_deg {
1542                Some(d) if d >= 2 => (d - 1, "min-1"),
1543                None => (probe, "> probe (counting is NS-hard)"),
1544                _ => continue,
1545            };
1546            let w = ns_lower_bound_witness(php.num_vars, &php.clauses, d)
1547                .unwrap_or_else(|| panic!("PHP({pigeons}): a degree-{d} lower-bound witness must exist"));
1548            assert!(check_ns_lower_bound(php.num_vars, &php.clauses, d, &w), "PHP({pigeons}): degree-{d} lower bound ({msg}) re-checks");
1549        }
1550    }
1551
1552    /// **Degree lower bounds are re-checkable certificates, dual to refutation existence.** The witness `L`
1553    /// exists *exactly* when no degree-`d` refutation does (`ns_lower_bound_witness` ⟺ `¬nullstellensatz_refutes`),
1554    /// and when present it re-checks — turning "our solver found no proof" into an independently verifiable
1555    /// lower bound `NS-degree(F) > d`. Checked on random formulas and on a family with minimum degree exactly
1556    /// `n` (the all-corners cube: a witness at `d = n−1`, none at `d = n`).
1557    #[test]
1558    fn ns_degree_lower_bounds_are_certifiable_and_dual_to_refutation() {
1559        // Consistency + re-checkability on random formulas across degrees.
1560        let mut s = 0xCAFE_F00D_1234_9999u64;
1561        let mut rng = || {
1562            s ^= s << 13;
1563            s ^= s >> 7;
1564            s ^= s << 17;
1565            s
1566        };
1567        for _ in 0..120 {
1568            let n = 3 + (rng() % 3) as usize; // 3..=5
1569            let m = 2 + (rng() % 8) as usize;
1570            let clauses: Vec<Vec<Lit>> = (0..m)
1571                .map(|_| {
1572                    let mut c = Vec::new();
1573                    for v in 0..n {
1574                        if rng() % 2 == 0 {
1575                            c.push(Lit::new(v as u32, rng() % 2 == 0));
1576                        }
1577                    }
1578                    if c.is_empty() {
1579                        c.push(Lit::new((rng() % n as u64) as u32, rng() % 2 == 0));
1580                    }
1581                    c
1582                })
1583                .collect();
1584            for d in 1..=n {
1585                let refutes = nullstellensatz_refutes(n, &clauses, d);
1586                match ns_lower_bound_witness(n, &clauses, d) {
1587                    Some(w) => {
1588                        assert!(!refutes, "a lower-bound witness exists only when there is NO degree-{d} refutation");
1589                        assert!(check_ns_lower_bound(n, &clauses, d, &w), "the lower-bound witness must re-check");
1590                    }
1591                    None => assert!(refutes, "no witness ⟹ a degree-{d} refutation exists"),
1592                }
1593            }
1594        }
1595        // A family with minimum degree exactly n: the all-corners cube. Certified lower bound at n−1.
1596        for n in 3..=4usize {
1597            let all_corners: Vec<Vec<Lit>> = (0u64..(1u64 << n))
1598                .map(|a| (0..n as u32).map(|v| Lit::new(v, (a >> v) & 1 == 0)).collect())
1599                .collect();
1600            let w = ns_lower_bound_witness(n, &all_corners, n - 1)
1601                .expect("all-corners has no degree-(n−1) refutation — a lower bound witness exists");
1602            assert!(check_ns_lower_bound(n, &all_corners, n - 1, &w), "the degree-(n−1) lower bound re-checks");
1603            assert!(ns_lower_bound_witness(n, &all_corners, n).is_none(), "at full degree n a refutation exists");
1604        }
1605    }
1606
1607    /// **No finite randomness — but at exponential cost, which is why it is NOT P = NP.** "For finite `n`,
1608    /// random does not exist" is a *theorem*: every unsatisfiable formula over `n` variables has a degree-`≤n`
1609    /// GF(2) Nullstellensatz certificate (completeness), so nothing is structureless — verified constructively
1610    /// on the hardest object (all `2ⁿ` corners forbidden) at `n = 5, 6`. What this does NOT give is efficiency:
1611    /// the degree-`n` certificate lives in the full multilinear basis of size `2ⁿ`, so its *existence* is an
1612    /// information-theoretic fact about the finite cube, not a fast proof. P vs NP is asymptotic (about a
1613    /// family as `n → ∞`); "P = NP for finite `n`" is vacuous (a fixed finite problem is `O(1)` by table
1614    /// lookup). The honest content is exactly this gap: structure always exists, and it always costs `2ⁿ`.
1615    #[test]
1616    fn no_finite_randomness_but_the_certificate_is_exponentially_large() {
1617        // No structureless finite formula: the hardest object (every corner forbidden) is refuted at n.
1618        for n in 5..=6usize {
1619            let all_corners: Vec<Vec<Lit>> = (0u64..(1u64 << n))
1620                .map(|a| (0..n as u32).map(|v| Lit::new(v, (a >> v) & 1 == 0)).collect())
1621                .collect();
1622            assert!(build_ns_certificate(n, &all_corners).is_ok(), "n={n}: no structureless formula — a certificate exists");
1623        }
1624        // But the full-degree NS system is exactly 2ⁿ wide — the certificate exists in EXPONENTIAL space.
1625        for n in 1..=16usize {
1626            assert_eq!(nullstellensatz_basis_size(n, n), 1u128 << n, "the degree-n NS basis is exactly 2ⁿ");
1627            // Monotone in the degree budget; the cheap (bounded-degree) fragment is a vanishing slice at large n.
1628            assert!(nullstellensatz_basis_size(n, n / 2) <= nullstellensatz_basis_size(n, n), "basis grows with degree");
1629        }
1630        // Exponential, not polynomial: by n=40 the certificate space (2⁴⁰ ≈ 10¹²) dwarfs any fixed poly like n⁶.
1631        assert!(nullstellensatz_basis_size(40, 40) > 40u128.pow(6), "the certificate space is exponential, not polynomial");
1632    }
1633
1634    /// **We PROVE, we do not iterate: structureless = 0 past the census wall.** The `Bₙ`-orbit census is
1635    /// infeasible at `n = 5` (~10⁷ orbits); it can only *measure* `structureless = 0` up to `n = 4`. The
1636    /// uniform construction settles it by *proof*: at `n = 5, 6, 7` it is a total, certifying decision on
1637    /// hundreds of random formulas — every UNSAT instance gets a certificate that re-checks (degree ≤ n) and
1638    /// is genuinely modelless by brute force; every SAT instance gets a model that satisfies it. One
1639    /// construction, correct at every `n`: no minimal-UNSAT family is ever structureless.
1640    #[test]
1641    fn ns_construction_is_total_and_sound_past_the_census_wall() {
1642        let mut state = 0x1234_5678_9abc_def0u64;
1643        let mut rng = || {
1644            state ^= state << 13;
1645            state ^= state >> 7;
1646            state ^= state << 17;
1647            state
1648        };
1649        for &n in &[5usize, 6, 7] {
1650            for _ in 0..150 {
1651                let num_clauses = n + (rng() % (3 * n as u64)) as usize;
1652                let clauses: Vec<Vec<Lit>> = (0..num_clauses)
1653                    .map(|_| {
1654                        let width = 2 + (rng() % 2) as usize; // 2- or 3-clauses
1655                        let mut seen = std::collections::HashSet::new();
1656                        let mut c = Vec::new();
1657                        while c.len() < width {
1658                            let v = (rng() % n as u64) as u32;
1659                            if seen.insert(v) {
1660                                c.push(Lit::new(v, rng() & 1 == 0));
1661                            }
1662                        }
1663                        c
1664                    })
1665                    .collect();
1666                match build_ns_certificate(n, &clauses) {
1667                    Ok(cert) => {
1668                        assert!(cert.verify(&clauses), "n={n}: the constructive certificate must re-check");
1669                        assert!(cert.degree() <= n, "n={n}: the certificate degree must be ≤ n");
1670                        assert!(!sat(n, &clauses), "n={n}: a certificate is issued only for a genuinely UNSAT formula");
1671                    }
1672                    Err(model) => {
1673                        assert!(sat(n, &clauses), "n={n}: SAT witness ⟹ the formula is genuinely satisfiable");
1674                        assert!(
1675                            clauses.iter().all(|c| c.iter().any(|l| model[l.var() as usize] == l.is_positive())),
1676                            "n={n}: the returned model must satisfy every clause"
1677                        );
1678                    }
1679                }
1680            }
1681        }
1682    }
1683
1684    /// **Truly random can't live in a finite hypercube — the complexity is *capped by the dimension*.**
1685    /// True (Martin-Löf) randomness needs *unbounded* Kolmogorov complexity. But the `n`-cube is finite,
1686    /// and Nullstellensatz is **complete at degree `n`** — every unsatisfiable formula over `n` variables
1687    /// has its `1`-in-the-ideal certificate by degree `n`, no higher. So the proof-complexity (the
1688    /// "randomness" measure) is **bounded by `n`**, never unbounded. The cube holds only the *finite
1689    /// shadow* of randomness — incompressible-relative-to-size, capped at the dimension. Truly random,
1690    /// being unbounded, has no room. We verify the cap on the maximally-constrained ("most random") UNSAT
1691    /// formula: it is decided exactly at degree `n`.
1692    #[test]
1693    fn truly_random_cannot_live_in_a_finite_hypercube() {
1694        for nv in 3..=5 {
1695            // The hardest object: every one of the 2ⁿ assignments forbidden by a full-width clause.
1696            let mut cl: Vec<Vec<Lit>> = Vec::new();
1697            for a in 0..(1u32 << nv) {
1698                cl.push((0..nv as u32).map(|v| Lit::new(v, (a >> v) & 1 == 0)).collect());
1699            }
1700            assert!(!sat(nv, &cl), "all assignments forbidden ⟹ UNSAT");
1701            // It is decided at the dimension-bound degree n — the cap. No formula over n vars needs more.
1702            assert!(
1703                nullstellensatz_refutes(nv, &cl, nv),
1704                "the hardest n={nv} formula is refuted at degree n — complexity capped at the dimension"
1705            );
1706        }
1707    }
1708
1709    /// **Symmetry collapses Polynomial Calculus to a counting problem — and for pigeonhole it *is* the
1710    /// pigeonhole count.** The degree-2 monomial basis of PHP(n), `Θ(n⁴)` monomials, collapses under the
1711    /// grid group `Sₙ × Sₙ₋₁` to a handful of orbit-types — and that handful is *constant in n*. The
1712    /// symmetric Nullstellensatz system has one column per orbit, so it is `O(1)` wide regardless of
1713    /// scale; counting those orbit-types is the whole cut. Everything came down to counting.
1714    #[test]
1715    fn symmetry_collapses_the_pc_basis_to_a_counting_problem() {
1716        let mut orbit_counts = Vec::new();
1717        for n in 3..=5 {
1718            let (cnf, _) = crate::families::php(n);
1719            let nv = cnf.num_vars;
1720            let generators = crate::hypercube::php_perm_symmetries(n);
1721            let orbits = monomial_orbits(nv, 2, &generators);
1722            let full = 1 + nv + nv * (nv - 1) / 2; // C(nv,0)+C(nv,1)+C(nv,2)
1723
1724            // The basis collapses hard — orbit-types are a tiny fraction of the monomials.
1725            assert!(
1726                orbits.len() * 3 < full,
1727                "PHP({n}): {} orbit-types ≪ {full} monomials — the counting collapse",
1728                orbits.len()
1729            );
1730            // Burnside check: the orbits partition the basis exactly (counting is consistent).
1731            assert_eq!(
1732                orbits.iter().map(|o| o.len()).sum::<usize>(),
1733                full,
1734                "the orbits partition the monomial basis"
1735            );
1736            // Each orbit is closed under the symmetry.
1737            for orbit in &orbits {
1738                let set: BTreeSet<Mono> = orbit.iter().copied().collect();
1739                for &m in orbit {
1740                    for g in &generators {
1741                        assert!(set.contains(&apply_perm_to_mono(g, m)), "orbit closed under the group");
1742                    }
1743                }
1744            }
1745            orbit_counts.push(orbits.len());
1746        }
1747        // The orbit-type count is essentially constant in n — O(1) symmetric NS width, at every scale.
1748        let max = *orbit_counts.iter().max().unwrap();
1749        let min = *orbit_counts.iter().min().unwrap();
1750        assert!(max - min <= 2, "orbit-type count is ~constant in n: {orbit_counts:?}");
1751    }
1752
1753    /// **THE SYMMETRY COST-CUT: `2ⁿ → n+1`, exponential to linear.** The full-degree Nullstellensatz basis is
1754    /// `2ⁿ` monomials — the brute-force cost of finding structure. But when a formula is *fully symmetric*
1755    /// (the symmetric group `Sₙ` permutes its variables), an invariant certificate is constant on the monomial
1756    /// orbits, and under `Sₙ` two monomials are equivalent iff they have the same degree — so the `2ⁿ` basis
1757    /// collapses to exactly **`n+1` orbit-columns, one per degree**. The Gaussian runs over `n+1` columns, not
1758    /// `2ⁿ`. That is symmetry = compression, made exact: the exponential search becomes linear. The reduced
1759    /// certificate is *sound* (an invariant `1`-in-the-span is a genuine refutation), checked here on a
1760    /// fully-`Sₙ`-symmetric UNSAT family.
1761    #[test]
1762    fn symmetry_cuts_the_full_ns_basis_from_exponential_to_linear() {
1763        for n in 2..=8usize {
1764            let gens = symmetric_group_generators(n);
1765            let orbits = monomial_orbits(n, n, &gens);
1766            // Under Sₙ, the 2ⁿ full-degree basis collapses to exactly n+1 orbits — one per monomial degree.
1767            assert_eq!(orbits.len(), n + 1, "Sₙ collapses the 2ⁿ basis to n+1 degree-orbits (n={n})");
1768            assert_eq!(nullstellensatz_basis_size(n, n), 1u128 << n, "the full basis is 2ⁿ");
1769            // Each orbit is *all* monomials of one degree, so the orbit sizes are exactly {C(n,k)}.
1770            let mut sizes: Vec<u128> = orbits.iter().map(|o| o.len() as u128).collect();
1771            sizes.sort_unstable();
1772            let mut binoms: Vec<u128> = (0..=n).map(|k| binom(n, k)).collect();
1773            binoms.sort_unstable();
1774            assert_eq!(sizes, binoms, "each degree-orbit k has C(n,k) monomials");
1775        }
1776        // The cut deepens without bound: 2ⁿ / (n+1) → ∞. Symmetry turns the exponential basis linear.
1777        let cut = |n: u32| (1u128 << n) / (n as u128 + 1);
1778        assert!(cut(8) > cut(4) && cut(4) > cut(2), "the symmetry cut 2ⁿ/(n+1) grows with n");
1779
1780        // SOUNDNESS of the collapsed certificate: on a fully-Sₙ-symmetric UNSAT family (every corner
1781        // forbidden), the symmetry-reduced NS — over the n+1 orbit columns, not 2ⁿ — still refutes, and agrees
1782        // with full NS. The cheap certificate is a real one.
1783        for n in 2..=5usize {
1784            let all_corners: Vec<Vec<Lit>> = (0u64..(1u64 << n))
1785                .map(|a| (0..n as u32).map(|v| Lit::new(v, (a >> v) & 1 == 0)).collect())
1786                .collect();
1787            let gens = symmetric_group_generators(n);
1788            assert!(nullstellensatz_refutes(n, &all_corners, n), "full NS refutes all-corners at degree n");
1789            assert!(
1790                nullstellensatz_refutes_symmetric(n, &all_corners, n, &gens),
1791                "the symmetry-reduced NS (n+1 columns) still refutes — the 2ⁿ→n+1 cut is sound (n={n})"
1792            );
1793        }
1794    }
1795
1796    /// **Nullstellensatz at full degree is a complete, sound decision** — verified against brute force on
1797    /// a fuzz: a degree-`num_vars` refutation exists iff the formula is unsatisfiable.
1798    #[test]
1799    fn nullstellensatz_full_degree_matches_brute_force() {
1800        fn sm(s: &mut u64) -> u64 {
1801            *s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
1802            let mut z = *s;
1803            z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
1804            z ^ (z >> 31)
1805        }
1806        let mut state = 0x9015_0001u64;
1807        for _ in 0..60 {
1808            let nv = 3 + (sm(&mut state) % 3) as usize; // 3..5 vars
1809            let m = 3 + (sm(&mut state) % 8) as usize;
1810            let mut cl: Vec<Vec<Lit>> = Vec::new();
1811            for _ in 0..m {
1812                let mut c = Vec::new();
1813                for v in 0..nv {
1814                    if sm(&mut state) % 2 == 0 {
1815                        c.push(Lit::new(v as u32, sm(&mut state) % 2 == 0));
1816                    }
1817                }
1818                if !c.is_empty() {
1819                    cl.push(c);
1820                }
1821            }
1822            if cl.is_empty() {
1823                continue;
1824            }
1825            let unsat = !sat(nv, &cl);
1826            assert_eq!(
1827                nullstellensatz_refutes(nv, &cl, nv),
1828                unsat,
1829                "NS at full degree must decide exactly: {cl:?}"
1830            );
1831        }
1832    }
1833
1834    /// **Parity is the degree-1 fragment.** An odd XOR cycle (`x_i ≠ x_{i+1}` around a 5-cycle) is
1835    /// unsatisfiable, and Nullstellensatz refutes it at the low degree its width permits — the algebraic
1836    /// form of the parity cut, now inside the one general engine.
1837    #[test]
1838    fn nullstellensatz_refutes_parity_at_low_degree() {
1839        // 2-colouring an odd cycle: (x_u ∨ x_v) ∧ (¬x_u ∨ ¬x_v) per edge — UNSAT for an odd cycle.
1840        let edges = [(0u32, 1u32), (1, 2), (2, 3), (3, 4), (4, 0)];
1841        let mut cl = Vec::new();
1842        for (u, v) in edges {
1843            cl.push(vec![Lit::new(u, true), Lit::new(v, true)]);
1844            cl.push(vec![Lit::new(u, false), Lit::new(v, false)]);
1845        }
1846        assert!(!sat(5, &cl), "odd-cycle 2-colouring is UNSAT");
1847        assert!(nullstellensatz_refutes(5, &cl, 2), "NS refutes the parity obstruction at degree 2");
1848    }
1849
1850    /// **The degree dial has real teeth: a refutation can need more than the minimum.** A formula that is
1851    /// UNSAT but whose only Nullstellensatz certificate needs the full degree is *not* refuted below it —
1852    /// the engine honestly reports "no low-degree algebraic proof," which is the hardness signal.
1853    #[test]
1854    fn the_degree_is_a_genuine_power_dial() {
1855        // All eight 3-clauses over x0,x1,x2 forbidding each assignment ⟹ UNSAT; needs degree 3 (the width).
1856        let mut cl = Vec::new();
1857        for a in 0u32..8 {
1858            cl.push(
1859                (0..3u32)
1860                    .map(|v| Lit::new(v, (a >> v) & 1 == 0))
1861                    .collect::<Vec<Lit>>(),
1862            );
1863        }
1864        assert!(!sat(3, &cl), "all 8 assignments forbidden ⟹ UNSAT");
1865        assert!(!nullstellensatz_refutes(3, &cl, 2), "no degree-2 certificate — width-3 clauses unusable");
1866        assert!(nullstellensatz_refutes(3, &cl, 3), "degree 3 refutes it");
1867    }
1868
1869    /// Soundness, isolated: a satisfiable formula has **no** Nullstellensatz refutation at any degree.
1870    #[test]
1871    fn satisfiable_formulas_are_never_refuted() {
1872        let cl = vec![vec![Lit::new(0, true), Lit::new(1, true)], vec![Lit::new(0, false), Lit::new(2, true)]];
1873        assert!(sat(3, &cl));
1874        for d in 0..=3 {
1875            assert!(!nullstellensatz_refutes(3, &cl, d), "a SAT formula is refuted at no degree (d={d})");
1876        }
1877    }
1878
1879    /// **Symmetry-reduced Nullstellensatz collapses the basis and stays sound.** The "all assignments
1880    /// forbidden" instance is fully symmetric and UNSAT; full NS spans `2ⁿ` monomials, but the
1881    /// symmetry-reduced refutation runs over the `n+1` weight-class orbits and still refutes — an
1882    /// exponential column collapse. Soundness: a satisfiable symmetric formula is refuted at no degree.
1883    #[test]
1884    fn symmetry_reduced_nullstellensatz_collapses_and_is_sound() {
1885        for nv in 3..=5usize {
1886            let mut cl: Vec<Vec<Lit>> = Vec::new();
1887            for a in 0..(1u32 << nv) {
1888                cl.push((0..nv as u32).map(|v| Lit::new(v, (a >> v) & 1 == 0)).collect());
1889            }
1890            assert!(!sat(nv, &cl), "all assignments forbidden ⟹ UNSAT");
1891            let gens = crate::symmetry_detect::find_generators(nv, &cl);
1892            assert!(
1893                nullstellensatz_refutes_symmetric(nv, &cl, nv, &gens),
1894                "symmetry-reduced NS refutes the all-forbidden instance (n={nv})"
1895            );
1896            let cols = monomial_orbits(nv, nv, &gens).len();
1897            assert!(cols < (1usize << nv), "n={nv}: {cols} orbit columns ≪ {} monomials", 1usize << nv);
1898        }
1899        // Soundness: a satisfiable symmetric formula is refuted at no degree.
1900        let sat_cl = vec![vec![Lit::new(0, true), Lit::new(1, true), Lit::new(2, true)]];
1901        let gens = crate::symmetry_detect::find_generators(3, &sat_cl);
1902        for d in 0..=3 {
1903            assert!(
1904                !nullstellensatz_refutes_symmetric(3, &sat_cl, d, &gens),
1905                "a satisfiable formula has no symmetry-reduced refutation (d={d})"
1906            );
1907        }
1908    }
1909
1910    /// A deterministic LCG for reproducible fuzz corpora inside tests (no `rand`, no wall clock).
1911    fn lcg(state: &mut u64) -> u64 {
1912        *state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
1913        *state >> 33
1914    }
1915
1916    /// **The polynomial-generator NS engine agrees with the clause engine on CNFs.** `ns_refutes_polys`
1917    /// / `ns_lower_bound_witness_polys` / `check_ns_lower_bound_polys` generalize the clause API to
1918    /// arbitrary `GF(2)` generator systems (the substrate for the linear encoding and the symmetric-family
1919    /// machinery). Fed the clause polynomials of a CNF they must reproduce the clause engine exactly —
1920    /// refutability at every degree, witness existence (the duality `refutes(d) ⟺ no witness at d`), and
1921    /// cross-checker validation both ways. Corpus: pigeonhole, modular counting, a transitive-XOR core,
1922    /// and a deterministic random-3-CNF fuzz sweep.
1923    #[test]
1924    fn ns_over_polynomial_generators_agrees_with_the_clause_encoding_on_cnfs() {
1925        let mut corpus: Vec<(usize, Vec<Vec<Lit>>)> = Vec::new();
1926        let (php3, _) = crate::families::php(3);
1927        corpus.push((php3.num_vars, php3.clauses));
1928        let (cnt32, _) = crate::families::mod_counting(3, 2);
1929        corpus.push((cnt32.num_vars, cnt32.clauses));
1930        let (cnt42, _) = crate::families::mod_counting(4, 2);
1931        corpus.push((cnt42.num_vars, cnt42.clauses));
1932        let p = |v: u32| Lit::pos(v);
1933        let q = |v: u32| Lit::neg(v);
1934        corpus.push((3, vec![
1935            vec![q(0), p(1)], vec![p(0), q(1)],
1936            vec![q(1), p(2)], vec![p(1), q(2)],
1937            vec![p(0), p(2)], vec![q(0), q(2)],
1938        ]));
1939        let mut seed = 0x5EED_CAFE_u64;
1940        for _ in 0..24 {
1941            let nv = 4 + (lcg(&mut seed) % 3) as usize; // 4..=6 variables
1942            let nc = 6 + (lcg(&mut seed) % 12) as usize;
1943            let mut cl = Vec::new();
1944            for _ in 0..nc {
1945                let mut vars: Vec<u32> = Vec::new();
1946                while vars.len() < 3 {
1947                    let v = (lcg(&mut seed) % nv as u64) as u32;
1948                    if !vars.contains(&v) {
1949                        vars.push(v);
1950                    }
1951                }
1952                cl.push(vars.iter().map(|&v| Lit::new(v, lcg(&mut seed) & 1 == 1)).collect());
1953            }
1954            corpus.push((nv, cl));
1955        }
1956
1957        for (nv, clauses) in &corpus {
1958            let gens: Vec<Poly> = clauses.iter().map(|c| clause_polynomial(c)).collect();
1959            for d in 1..=(*nv).min(5) {
1960                let clause_verdict = nullstellensatz_refutes(*nv, clauses, d);
1961                assert_eq!(
1962                    ns_refutes_polys(*nv, &gens, d),
1963                    clause_verdict,
1964                    "n={nv} d={d}: the polynomial-generator engine matches the clause engine"
1965                );
1966                let w_clause = ns_lower_bound_witness(*nv, clauses, d);
1967                let w_polys = ns_lower_bound_witness_polys(*nv, &gens, d);
1968                assert_eq!(
1969                    w_clause.is_some(),
1970                    w_polys.is_some(),
1971                    "n={nv} d={d}: witness existence agrees across the two engines"
1972                );
1973                assert_eq!(
1974                    w_polys.is_none(),
1975                    clause_verdict,
1976                    "n={nv} d={d}: duality — a refutation at d exists iff no degree-d pseudo-expectation"
1977                );
1978                if let (Some(wc), Some(wp)) = (w_clause, w_polys) {
1979                    assert!(
1980                        check_ns_lower_bound_polys(*nv, &gens, d, &wc),
1981                        "n={nv} d={d}: the clause-engine witness passes the polynomial checker"
1982                    );
1983                    assert!(
1984                        check_ns_lower_bound(*nv, clauses, d, &wp),
1985                        "n={nv} d={d}: the polynomial-engine witness passes the clause checker"
1986                    );
1987                }
1988            }
1989        }
1990    }
1991
1992    /// **Degree-bounded monomial enumeration lifts the 20-variable cap.** The clause engine enumerates
1993    /// `0..2ⁿ` and filters — dead at `n > 20`. `monomials_up_to_degree` walks only the `C(n, ≤d)`
1994    /// monomials, so fixed-degree work scales to the full `Mono = u64` range. Differential against the
1995    /// filter on every `n ≤ 12`; counts pinned to `nullstellensatz_basis_size`; and the real payoff
1996    /// exercised end-to-end: a re-checked degree-2 lower-bound witness on a 22-variable instance (PHP(3)
1997    /// padded with sixteen spectator variables — the ideal, hence the NS degree, is unchanged), where the
1998    /// bounded basis has 254 monomials against the 4-million-corner cube.
1999    #[test]
2000    fn degree_bounded_monomial_enumeration_scales_past_twenty_variables() {
2001        for n in 0..=12usize {
2002            for d in 0..=n {
2003                let bounded: BTreeSet<Mono> = monomials_up_to_degree(n, d).into_iter().collect();
2004                let filtered: BTreeSet<Mono> =
2005                    (0u64..(1u64 << n)).filter(|m| m.count_ones() as usize <= d).collect();
2006                assert_eq!(bounded, filtered, "n={n} d={d}: bounded enumeration = filtered cube");
2007                assert_eq!(
2008                    bounded.len() as u128,
2009                    nullstellensatz_basis_size(n, d),
2010                    "n={n} d={d}: the count is Σ C(n,k)"
2011                );
2012            }
2013        }
2014        // Sorted ascending — the stable index order the witness machinery relies on.
2015        let ms = monomials_up_to_degree(10, 3);
2016        assert!(ms.windows(2).all(|w| w[0] < w[1]), "monomials come sorted ascending");
2017        // Far past the cube: C(40, ≤2) monomials without touching 2^40.
2018        assert_eq!(monomials_up_to_degree(40, 2).len(), 821); // 1 + 40 + C(40,2)
2019        assert_eq!(nullstellensatz_basis_size(40, 2), 821);
2020
2021        // End-to-end at 22 variables: PHP(3) with spectator variables. NS-degree(PHP(3)) = 4, so a
2022        // degree-2 pseudo-expectation exists — found and re-checked entirely on the bounded basis.
2023        let (php3, _) = crate::families::php(3);
2024        let gens: Vec<Poly> = php3.clauses.iter().map(|c| clause_polynomial(c)).collect();
2025        let nv = 22usize;
2026        let w = ns_lower_bound_witness_polys(nv, &gens, 2)
2027            .expect("a degree-2 witness exists for padded PHP(3) — the degree is 4");
2028        assert!(
2029            check_ns_lower_bound_polys(nv, &gens, 2, &w),
2030            "the 22-variable witness re-checks on the bounded basis"
2031        );
2032        // Spectators change nothing: the same verdicts as the unpadded instance.
2033        assert!(!ns_refutes_polys(nv, &gens, 3), "padded PHP(3) is not refuted at degree 3");
2034        assert!(ns_refutes_polys(nv, &gens, 4), "padded PHP(3) is refuted at degree 4");
2035    }
2036
2037    /// **The clause and linear encodings interreduce at bounded degree — with an asymmetric, degree-exact
2038    /// direction.** For an exactly-one group `G` (at-least-one clause + at-most-one pairs), the wide ALO
2039    /// clause polynomial `Π_{v∈G}(1+x_v)` and the linear generator `1 + Σ_{v∈G} x_v` differ by a sum of
2040    /// pair-generator multiples: every `≥2`-subset monomial is a multiple of some pair `x_u x_v`. So:
2041    /// (i) the syntactic identity `Π(1+x) + (1+Σx) ∈ span{m·(x_u x_v)}` at degree `|G|`, exactly;
2042    /// (ii) **clause-refutable at `d` ⟹ linear-refutable at `d`** (degree-preserving — every clause
2043    ///     generator rewrites into linear generators of no larger degree), which is why lower bounds
2044    ///     proven against the linear encoding are the stronger statements (the literature-standard form);
2045    /// (iii) linear-refutable at `d` ⟹ clause-refutable at `d + k−1` (`k` = the widest group) — the
2046    ///     honest reverse transfer with its explicit degree tax.
2047    #[test]
2048    fn the_linear_and_clause_encodings_interreduce_at_bounded_degree() {
2049        // (i) The syntactic identity, exactly, for group sizes 2..=6.
2050        for k in 2..=6usize {
2051            let group: Vec<u32> = (0..k as u32).collect();
2052            let alo: Vec<Lit> = group.iter().map(|&v| Lit::pos(v)).collect();
2053            let clause_poly = clause_polynomial(&alo);
2054            let gens = exactly_one_linear_generators(&[group.clone()]);
2055            let linear: &Poly = &gens[0]; // the 1 + Σ x_v generator leads; pairs follow
2056            assert_eq!(linear.len(), k + 1, "the linear generator is 1 + Σ x (k+1 monomials)");
2057            assert_eq!(gens.len(), 1 + k * (k - 1) / 2, "one linear generator plus C(k,2) pairs");
2058            let mut diff = clause_poly.clone();
2059            for &m in linear {
2060                toggle(&mut diff, m);
2061            }
2062            // diff = Σ_{|S|≥2} x_S must lie in span{ m · pair : deg ≤ k }.
2063            let mut index: HashMap<Mono, usize> = HashMap::new();
2064            for m in 0u64..(1u64 << k) {
2065                let i = index.len();
2066                index.insert(m, i);
2067            }
2068            let words = index.len().div_ceil(64).max(1);
2069            let to_bits = |p: &Poly| -> Vec<u64> {
2070                let mut b = vec![0u64; words];
2071                for &m in p {
2072                    b[index[&m] / 64] |= 1 << (index[&m] % 64);
2073                }
2074                b
2075            };
2076            let mut rows = Vec::new();
2077            for pair in &gens[1..] {
2078                for m in monomials_up_to_degree(k, k) {
2079                    let prod = poly_mul_mono(pair, m);
2080                    if !prod.is_empty() && poly_degree(&prod) <= k {
2081                        rows.push(to_bits(&prod));
2082                    }
2083                }
2084            }
2085            assert!(
2086                in_gf2_span(rows, &to_bits(&diff)),
2087                "k={k}: Π(1+x) + (1+Σx) is a sum of pair-generator multiples at degree k"
2088            );
2089        }
2090
2091        // (ii)+(iii) The semantic transfer, measured on modular counting (the W2 substrate).
2092        for (n, q) in [(3usize, 2usize), (5, 2)] {
2093            let (cnf, _) = crate::families::mod_counting(n, q);
2094            let nv = cnf.num_vars;
2095            // The exactly-one groups are the all-positive covering clauses; AMO pairs are the rest.
2096            let groups: Vec<Vec<u32>> = cnf
2097                .clauses
2098                .iter()
2099                .filter(|c| c.iter().all(|l| l.is_positive()))
2100                .map(|c| c.iter().map(|l| l.var()).collect())
2101                .collect();
2102            assert_eq!(groups.len(), n, "one exactly-one group per point");
2103            let k = groups.iter().map(|g| g.len()).max().unwrap();
2104            let linear_gens = exactly_one_linear_generators(&groups);
2105            let clause_gens: Vec<Poly> = cnf.clauses.iter().map(|c| clause_polynomial(c)).collect();
2106            let dmax = nv.min(5);
2107            for d in 1..=dmax {
2108                if ns_refutes_polys(nv, &clause_gens, d) {
2109                    assert!(
2110                        ns_refutes_polys(nv, &linear_gens, d),
2111                        "Count_{q}({n}) d={d}: clause-refutable ⟹ linear-refutable at the SAME degree"
2112                    );
2113                }
2114                if ns_refutes_polys(nv, &linear_gens, d) {
2115                    assert!(
2116                        ns_refutes_polys(nv, &clause_gens, (d + k - 1).min(nv)),
2117                        "Count_{q}({n}) d={d}: linear-refutable ⟹ clause-refutable at d + k − 1"
2118                    );
2119                }
2120            }
2121            // The linear encoding refutes an UNSAT counting instance at low degree somewhere ≤ dmax.
2122            assert!(
2123                (1..=dmax).any(|d| ns_refutes_polys(nv, &linear_gens, d)),
2124                "Count_{q}({n}): the linear encoding refutes within the probed degrees"
2125            );
2126        }
2127    }
2128
2129    /// **The char-matched control: `Count_2` collapses to degree 1 over `GF(2)`.** The linear encoding's
2130    /// point generators are `GF(2)` linear equations `1 + Σ_{e∋i} x_e`; summing all `n` of them counts
2131    /// each edge `q = 2` times, so the edge terms cancel and the sum is `n·1 = 1` for odd `n` — a
2132    /// degree-**1** Nullstellensatz refutation, pure linear algebra. This is the characteristic-matched
2133    /// foil for the mismatch row: the same family that is resolution-hard (Ajtai; Beame–Pitassi) is
2134    /// trivial for the algebra whose characteristic divides the count. Even `n` is SAT (a perfect
2135    /// matching exists), and soundness holds: no refutation at any probed degree.
2136    #[test]
2137    fn count_two_is_char_matched_and_falls_to_low_degree_gf2_ns() {
2138        for n in [3usize, 5, 7] {
2139            let (cnf, _) = crate::families::mod_counting(n, 2);
2140            let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 2));
2141            assert!(
2142                ns_refutes_polys(cnf.num_vars, &gens, 1),
2143                "Count_2({n}), n odd: linear-encoded NS degree 1 — the char-matched collapse"
2144            );
2145        }
2146        for n in [4usize, 6] {
2147            let (cnf, _) = crate::families::mod_counting(n, 2);
2148            let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 2));
2149            for d in 1..=3 {
2150                assert!(
2151                    !ns_refutes_polys(cnf.num_vars, &gens, d),
2152                    "Count_2({n}), n even: SAT (a perfect matching) ⟹ no refutation at degree {d}"
2153                );
2154            }
2155        }
2156    }
2157
2158    /// **The char-mismatch row: `Count_3` has certified, growing, non-width `GF(2)` NS degree — in two
2159    /// provably distinct regimes.** The modular counting principle with `3 ∤ n` is UNSAT by a mod-3
2160    /// argument `GF(2)` algebra cannot make at low degree; on the **linear encoding** (degree-1 point
2161    /// generators `P_i = 1 + Σ_{e∋i} x_e` + degree-2 overlap pairs — the encoding the literature states
2162    /// bounds against, and the *stronger* side of the interreduction):
2163    ///
2164    /// - **The dense degenerate regime `n < 2q` (`n = 4, 5`): exact degree 2.** Below `n = 6` every two
2165    ///   triples of `[n]` intersect, so for any `f` and `i ∉ f`, `x_f·P_i ≡ x_f` mod the pairs — every
2166    ///   variable enters the degree-2 span, and with `Σ_i P_i = n + Σ_e x_e (mod 2)` (each edge counted
2167    ///   `q = 3 ≡ 1` times) the constant `1` follows. Degree 1 is impossible: a sum `Σ_{i∈S} P_i = 1`
2168    ///   needs `|e ∩ S|` even for every triple `e` with `|S|` odd, and no such `S ⊆ [n]` exists (a
2169    ///   singleton meets some triple once; a triple meets itself thrice). Both halves certified.
2170    /// - **The genuine regime (`n = 7, 8` — 35 and 56 variables, reachable only through the
2171    ///   degree-bounded basis): NS-degree ≥ 3, certified.** The re-checked dual witness at degree 2
2172    ///   exceeds every generator's degree (non-width) and the bound grows from the degenerate regime's
2173    ///   2. The exact upper half (refutation at degree 3) is release-scale Gaussian elimination and
2174    ///   lives in the `#[ignore]` scale probe, measured `= 3` at both `n`.
2175    ///
2176    /// The `GF(3)` route refutes the same family in microseconds — together the marquee two-sided
2177    /// characteristic-mismatch row of the separations atlas.
2178    #[test]
2179    fn count_three_has_certified_growing_non_width_ns_degree_over_gf2() {
2180        // Dense degenerate regime: exact degree 2, both halves certified.
2181        for n in [4usize, 5] {
2182            let (cnf, _) = crate::families::mod_counting(n, 3);
2183            let nv = cnf.num_vars;
2184            let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 3));
2185            assert!(!ns_refutes_polys(nv, &gens, 1), "Count_3({n}): no degree-1 refutation");
2186            let w1 = ns_lower_bound_witness_polys(nv, &gens, 1).expect("dual witness at degree 1");
2187            assert!(check_ns_lower_bound_polys(nv, &gens, 1, &w1), "Count_3({n}): NS-degree > 1 re-checks");
2188            assert!(
2189                ns_refutes_polys(nv, &gens, 2),
2190                "Count_3({n}), n < 2q: every two blocks overlap ⟹ the degree-2 collapse"
2191            );
2192            eprintln!("Count_3({n}): certified exact linear-encoded GF(2) NS degree = 2 (dense regime)");
2193        }
2194        // Genuine regime: certified NS-degree ≥ 3 — non-width, and growth past the dense regime.
2195        for n in [7usize, 8] {
2196            let (cnf, _) = crate::families::mod_counting(n, 3);
2197            let nv = cnf.num_vars;
2198            assert!(nv > 20, "the genuine regime lives past the clause engine's cap ({nv} vars)");
2199            let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 3));
2200            let w2 = ns_lower_bound_witness_polys(nv, &gens, 2)
2201                .expect("a degree-2 pseudo-expectation exists — the degree exceeds 2");
2202            assert!(
2203                check_ns_lower_bound_polys(nv, &gens, 2, &w2),
2204                "Count_3({n}): NS-degree ≥ 3 re-checks with zero trust"
2205            );
2206            eprintln!("Count_3({n}) [{nv} vars]: certified NS-degree ≥ 3 (exact = 3 in the scale probe)");
2207        }
2208    }
2209
2210    /// **The exact upper half at scale: `Count_3(7)` and `Count_3(8)` refute at degree 3, exactly.**
2211    /// The certified test carries the lower half (re-checked degree-2 dual witnesses); this probe pins
2212    /// the refutations at degree 3 — 35- and 56-variable Gaussian eliminations over the degree-bounded
2213    /// basis, minutes at test-profile optimization — locking the exact linear-encoded degree
2214    /// `NS-degree(Count_3(7)) = NS-degree(Count_3(8)) = 3`.
2215    #[test]
2216    #[ignore = "scale measurement — minutes of Gaussian elimination; run explicitly or via the fast suite"]
2217    fn count_three_scale_probe_measures_the_degree_growth() {
2218        for n in [7usize, 8] {
2219            let (cnf, _) = crate::families::mod_counting(n, 3);
2220            let nv = cnf.num_vars;
2221            let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 3));
2222            assert!(!ns_refutes_polys(nv, &gens, 2), "Count_3({n}): no degree-2 refutation");
2223            assert!(ns_refutes_polys(nv, &gens, 3), "Count_3({n}): refuted at degree 3 — exact");
2224            eprintln!("Count_3({n}) [{nv} vars]: exact linear-encoded GF(2) NS degree = 3");
2225        }
2226    }
2227
2228    /// Is a `Count_q` monomial a **partial partition** — a set of pairwise-disjoint blocks? The
2229    /// counting-family analog of pigeonhole's partial matchings.
2230    fn count_is_disjoint(mono: Mono, edges: &[Vec<usize>]) -> bool {
2231        let mut used = 0u64;
2232        let mut bits = mono;
2233        while bits != 0 {
2234            let e = bits.trailing_zeros() as usize;
2235            let mask: u64 = edges[e].iter().fold(0, |m, &v| m | (1u64 << v));
2236            if used & mask != 0 {
2237                return false;
2238            }
2239            used |= mask;
2240            bits &= bits - 1;
2241        }
2242        true
2243    }
2244
2245    /// **The `Count_3` witness support: the invariant closed form lives on a Lucas schedule in `n`, and
2246    /// off-schedule symmetry and validity part ways.** The `Sₙ`-invariant degree-2 pseudo-expectation
2247    /// on the **partial-partition support** (pairwise-disjoint blocks — the counting analog of partial
2248    /// matchings) is forced onto type values `(a, b₀)` (singles, disjoint pairs); solving the type
2249    /// constraints by hand: the pair generators vanish outright, `⟨L, P_i⟩ = 1 + C(n−1,2)·a` pins `a`,
2250    /// and `⟨L, x_f·P_i⟩` with `i ∉ f` contributes `a + C(n−4, 2)·b₀` — so an **invariant** witness on
2251    /// this support exists iff those binomials are odd: a parity-of-binomials condition, `n ≡ 3 (mod 4)`
2252    /// (Lucas). Machine-locked, both faces:
2253    ///
2254    /// - **`n = 7` (on schedule):** the explicit indicator `L(M) = [M pairwise disjoint]` is valid — a
2255    ///   closed-form, symmetry-invariant witness, the analog of pigeonhole's hole-injective one.
2256    /// - **`n = 8` (off schedule):** *all four* invariant candidates on the disjoint support (the whole
2257    ///   `(a, b₀)` cube) are invalid — machine-enumerated — yet the sub-basis search still finds a
2258    ///   valid witness on the very same support, necessarily **non-invariant**. The Reynolds
2259    ///   obstruction (`over_gf2_symmetrizing_a_proof_annihilates_when_the_group_is_even`) live at a
2260    ///   concrete scale: over `GF(2)`, off the Lucas schedule, no symmetric witness survives where
2261    ///   asymmetric ones do. This is the phenomenon the symmetric-collapse machinery must respect (its
2262    ///   verdicts are about *invariant* certificates), and the first concrete face of the
2263    ///   periodicity-in-`n` it is built to decide.
2264    #[test]
2265    fn count_three_witness_support_structure_is_probed_on_sub_bases() {
2266        // Dense regime, degree 1: the disjoint support carries (singles are always disjoint).
2267        for n in [4usize, 5] {
2268            let (cnf, _) = crate::families::mod_counting(n, 3);
2269            let nv = cnf.num_vars;
2270            let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 3));
2271            let full = ns_lower_bound_witness_polys_on_basis(nv, &gens, 1, &|_| true)
2272                .expect("the unrestricted sub-basis search reproduces the witness");
2273            assert!(check_ns_lower_bound_polys(nv, &gens, 1, &full), "the control witness re-checks");
2274            let edges = crate::families::mod_counting_edges(n, 3);
2275            let probe =
2276                ns_lower_bound_witness_polys_on_basis(nv, &gens, 1, &|m| count_is_disjoint(m, &edges));
2277            let w = probe.expect("dense regime, degree 1: the disjoint support carries the witness");
2278            assert!(check_ns_lower_bound_polys(nv, &gens, 1, &w), "the sub-basis witness re-checks");
2279        }
2280
2281        // Genuine regime, degree 2: the invariant closed form on its n mod 4 schedule.
2282        for n in [7usize, 8] {
2283            let on_schedule = n % 4 == 3;
2284            let (cnf, _) = crate::families::mod_counting(n, 3);
2285            let nv = cnf.num_vars;
2286            let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 3));
2287            let edges = crate::families::mod_counting_edges(n, 3);
2288            let disjoint: Vec<Mono> = monomials_up_to_degree(nv, 2)
2289                .into_iter()
2290                .filter(|&m| count_is_disjoint(m, &edges))
2291                .collect();
2292            // Every invariant candidate on the disjoint support: L(1)=1, singles ∈ {0,1}, pairs ∈ {0,1}.
2293            for (a, b0) in [(false, false), (false, true), (true, false), (true, true)] {
2294                let candidate: Vec<Mono> = disjoint
2295                    .iter()
2296                    .copied()
2297                    .filter(|&m| match m.count_ones() {
2298                        0 => true,
2299                        1 => a,
2300                        _ => b0,
2301                    })
2302                    .collect();
2303                let is_indicator = a && b0;
2304                let expect = on_schedule && is_indicator;
2305                assert_eq!(
2306                    check_ns_lower_bound_polys(nv, &gens, 2, &candidate),
2307                    expect,
2308                    "Count_3({n}): invariant candidate (a={a}, b0={b0}) valid iff on the Lucas \
2309                     schedule and the full indicator"
2310                );
2311            }
2312            // The support still carries a witness at every n — off schedule it must be non-invariant.
2313            let probe =
2314                ns_lower_bound_witness_polys_on_basis(nv, &gens, 2, &|m| count_is_disjoint(m, &edges));
2315            let w = probe.expect("the disjoint support carries a (possibly asymmetric) witness");
2316            assert!(check_ns_lower_bound_polys(nv, &gens, 2, &w), "the sub-basis witness re-checks");
2317            eprintln!(
2318                "Count_3({n}) at degree 2 (n mod 4 = {}): invariant closed form {}; support witness found",
2319                n % 4,
2320                if on_schedule { "VALID (the partial-partition indicator)" } else { "IMPOSSIBLE — witness is necessarily asymmetric" },
2321            );
2322        }
2323    }
2324}