Skip to main content

logicaffeine_proof/
orbit_stability.rs

1//! **Orbit stability: deciding symmetric Nullstellensatz questions at every scale by one finite
2//! computation.** The machinery behind the ∀m lower-bound program.
3//!
4//! A symmetric family (pigeonhole, modular counting) is one formula *shape* instantiated at every
5//! scale `m`, with the scale-`m` symmetric group acting on its variables. At a **fixed degree `d`**,
6//! the `GF(2)` NS dual question — does a degree-`d` pseudo-expectation exist? — restricted to
7//! **invariant** functionals collapses onto *orbit types*: an invariant `L` is constant on monomial
8//! orbits, an orbit is named scale-independently by its **canonical structure** (the bipartite
9//! pigeon/hole graph; the block-intersection hypergraph), and one constraint per joint
10//! (multiplier-orbit × generator) representative captures the whole system
11//! ([`collapsed_dual_system`], soundness = the one-line invariance lemma tested in
12//! `an_invariant_functional_checked_on_orbit_representatives_is_checked_on_all_generators`).
13//!
14//! The stability phenomenon: at fixed `d` the type set stabilizes once every `≤ d`-edge structure is
15//! realizable, and the constraint rows' entries are parities of *counting polynomials in `m`*
16//! (binomials — e.g. `Count_3`'s off-point constraint `a + C(n−4, 2)·b₀`), hence eventually periodic
17//! in `m` with period a power of two (Lucas). So the invariant-witness verdict is decidable **for
18//! every `m`** from finitely many evaluations — each in-window verdict differentially validated
19//! against the direct orbit-collapsed solver and, where a witness exists, lifted and re-checked by
20//! `check_ns_lower_bound_polys` with zero trust.
21//!
22//! The honest boundary, forced by our own char-2 theorem
23//! (`over_gf2_symmetrizing_a_proof_annihilates_when_the_group_is_even`): over `GF(2)` there is no
24//! Reynolds averaging, so "no invariant witness" does **not** imply "no witness" — `Count_3(8)`
25//! realizes the gap (an asymmetric witness on the partial-partition support survives where every
26//! invariant candidate dies). Verdicts here are about *invariant* certificates; a positive verdict
27//! is a sound `NS-degree > d` lower bound, a negative one is measured against the gap.
28
29use crate::cdcl::Lit;
30use crate::polycalc::{
31    apply_perm_to_mono, check_ns_lower_bound_polys, clause_polynomial, gf2_solve,
32    monomials_up_to_degree, ns_lower_bound_witness_polys, poly_degree, poly_mul_mono, Mono, Poly,
33};
34use crate::proof::Perm;
35use std::collections::{BTreeMap, BTreeSet, HashMap};
36
37/// A monomial's **structure**: one atom list per variable it contains, each atom a `(sort, point)`
38/// pair — pigeonhole variables are `[(0, pigeon), (1, hole)]`, counting variables are the block's
39/// points `[(0, a), (0, b), (0, c)]`. Two monomials at any two scales have the same orbit type iff
40/// their structures agree up to a sort-respecting relabeling of points ([`canonical_structure`]).
41pub type Structure = Vec<Vec<(u8, u32)>>;
42
43/// The **canonical form** of a structure: the lexicographically least relabeling, minimized over all
44/// sort-respecting point bijections (brute force over the touched points of each sort — bounded by
45/// the degree, so a handful of points). This is the scale-independent orbit-type name.
46pub fn canonical_structure(structure: &Structure) -> Structure {
47    // Touched points per sort, in first-appearance order.
48    let mut sorts: BTreeMap<u8, Vec<u32>> = BTreeMap::new();
49    for var in structure {
50        for &(s, p) in var {
51            let pts = sorts.entry(s).or_default();
52            if !pts.contains(&p) {
53                pts.push(p);
54            }
55        }
56    }
57    let sort_keys: Vec<u8> = sorts.keys().copied().collect();
58    // All relabelings = product of per-sort permutations, enumerated recursively.
59    let mut best: Option<Structure> = None;
60    let mut perms: Vec<Vec<usize>> = Vec::new();
61    fn rec(
62        level: usize,
63        sort_keys: &[u8],
64        sorts: &BTreeMap<u8, Vec<u32>>,
65        perms: &mut Vec<Vec<usize>>,
66        structure: &Structure,
67        best: &mut Option<Structure>,
68    ) {
69        if level == sort_keys.len() {
70            // Apply: point `pts[i]` of sort `s` ↦ `perm[i]`.
71            let mut maps: BTreeMap<u8, HashMap<u32, u32>> = BTreeMap::new();
72            for (li, &s) in sort_keys.iter().enumerate() {
73                let pts = &sorts[&s];
74                let map = maps.entry(s).or_default();
75                for (i, &p) in pts.iter().enumerate() {
76                    map.insert(p, perms[li][i] as u32);
77                }
78            }
79            let mut relabeled: Structure = structure
80                .iter()
81                .map(|var| {
82                    let mut v: Vec<(u8, u32)> =
83                        var.iter().map(|&(s, p)| (s, maps[&s][&p])).collect();
84                    v.sort_unstable();
85                    v
86                })
87                .collect();
88            relabeled.sort_unstable();
89            if best.as_ref().is_none_or(|b| relabeled < *b) {
90                *best = Some(relabeled);
91            }
92            return;
93        }
94        let k = sorts[&sort_keys[level]].len();
95        let mut idx: Vec<usize> = (0..k).collect();
96        permute(&mut idx, 0, &mut |perm| {
97            perms.push(perm.to_vec());
98            rec(level + 1, sort_keys, sorts, perms, structure, best);
99            perms.pop();
100        });
101    }
102    fn permute(idx: &mut Vec<usize>, at: usize, f: &mut dyn FnMut(&[usize])) {
103        if at == idx.len() {
104            f(idx);
105            return;
106        }
107        for i in at..idx.len() {
108            idx.swap(at, i);
109            permute(idx, at + 1, f);
110            idx.swap(at, i);
111        }
112    }
113    rec(0, &sort_keys, &sorts, &mut perms, structure, &mut best);
114    best.unwrap_or_default()
115}
116
117/// **Degree-bounded monomial orbits**: the quotient of the degree-`≤ d` basis under the group, walked
118/// on the bounded basis (`C(n, ≤d)` monomials, never `2ⁿ`) so it scales to `num_vars ≤ 63` — the
119/// scale window `polycalc::monomial_orbits`'s cube filter cannot reach.
120pub fn monomial_orbits_bounded(num_vars: usize, degree: usize, generators: &[Perm]) -> Vec<Vec<Mono>> {
121    let basis: BTreeSet<Mono> = monomials_up_to_degree(num_vars, degree).into_iter().collect();
122    let mut seen: BTreeSet<Mono> = BTreeSet::new();
123    let mut orbits = Vec::new();
124    for &m in &basis {
125        if seen.contains(&m) {
126            continue;
127        }
128        let mut orbit = BTreeSet::new();
129        orbit.insert(m);
130        let mut stack = vec![m];
131        while let Some(x) = stack.pop() {
132            for g in generators {
133                let y = apply_perm_to_mono(g, x);
134                if basis.contains(&y) && orbit.insert(y) {
135                    stack.push(y);
136                }
137            }
138        }
139        for &x in &orbit {
140            seen.insert(x);
141        }
142        orbits.push(orbit.into_iter().collect());
143    }
144    orbits
145}
146
147/// A symmetric family instantiated at one scale: the variable count, the `GF(2)` generator
148/// polynomials, the scale's symmetry generators, the variable → atom-list map that names orbit
149/// types scale-independently, and one **anchor** per generator — the pseudo-structure that pins the
150/// generator's identity for cross-scale row labeling (a pigeon row is anchored by its pigeon, a
151/// point generator by its point, a fixed-shape generator by its own monomial structure; anchor
152/// atom-lists carry the marker sort `255` so they never collide with multiplier variables, while
153/// their point atoms share the real sorts and relabel *consistently* with the multiplier's).
154pub struct SymmetricInstance {
155    pub num_vars: usize,
156    pub gens: Vec<Poly>,
157    pub sym: Vec<Perm>,
158    pub atoms: Box<dyn Fn(u32) -> Vec<(u8, u32)>>,
159    pub anchors: Vec<Structure>,
160}
161
162impl SymmetricInstance {
163    /// The structure of a monomial under this instance's atom map.
164    pub fn structure(&self, mono: Mono) -> Structure {
165        let mut vars = Vec::new();
166        let mut bits = mono;
167        while bits != 0 {
168            let v = bits.trailing_zeros();
169            vars.push((self.atoms)(v));
170            bits &= bits - 1;
171        }
172        vars
173    }
174
175    /// The canonical orbit-type of a monomial — [`canonical_structure`] of its structure.
176    pub fn type_of(&self, mono: Mono) -> Structure {
177        canonical_structure(&self.structure(mono))
178    }
179}
180
181/// The anchor of a **fixed-shape generator**: its own variables' atom lists, each carrying the
182/// marker sort `255` — so the row label distinguishes "multiplier variable" from "generator
183/// variable" while the point atoms relabel consistently across both.
184fn fixed_shape_anchor(vars: &[Vec<(u8, u32)>]) -> Structure {
185    vars.iter()
186        .map(|atoms| {
187            let mut a = atoms.clone();
188            a.push((255, 0));
189            a
190        })
191        .collect()
192}
193
194/// **Pigeonhole at scale `m`, clause encoding** (the [`crate::families::php`] layout, `Sₘ × Sₘ₋₁`
195/// symmetry): variable `p·holes + h` is the atom pair `(pigeon p, hole h)` — two sorts. A pigeon
196/// clause (all-positive row) is anchored by its pigeon; a hole pair by its own structure.
197pub fn php_instance_clause(m: usize) -> SymmetricInstance {
198    let (cnf, _) = crate::families::php(m);
199    let holes = m - 1;
200    let atom = move |v: u32| vec![(0u8, v / holes as u32), (1u8, v % holes as u32)];
201    let mut gens = Vec::new();
202    let mut anchors = Vec::new();
203    for c in &cnf.clauses {
204        gens.push(clause_polynomial(c));
205        if c.iter().all(|l| l.is_positive()) {
206            let pigeon = c[0].var() / holes as u32;
207            anchors.push(vec![vec![(0u8, pigeon), (255, 0)]]);
208        } else {
209            anchors.push(fixed_shape_anchor(
210                &c.iter().map(|l| atom(l.var())).collect::<Vec<_>>(),
211            ));
212        }
213    }
214    SymmetricInstance {
215        num_vars: cnf.num_vars,
216        gens,
217        sym: crate::hypercube::php_perm_symmetries(m),
218        atoms: Box::new(atom),
219        anchors,
220    }
221}
222
223/// **Pigeonhole at scale `m`, linear encoding**: each pigeon row as the degree-1 generator
224/// `1 + Σ_h x_{p,h}` (never dropped by a degree budget — the fixed-degree questions stay real at
225/// every `m`), plus the hole at-most-one pairs. Same symmetry and atoms as the clause encoding;
226/// pigeon-row generators anchored by their pigeon.
227pub fn php_instance_linear(m: usize) -> SymmetricInstance {
228    let (cnf, _) = crate::families::php(m);
229    let holes = m - 1;
230    let atom = move |v: u32| vec![(0u8, v / holes as u32), (1u8, v % holes as u32)];
231    let mut gens: Vec<Poly> = Vec::new();
232    let mut anchors: Vec<Structure> = Vec::new();
233    for p in 0..m {
234        let mut lin: Poly = [0u64].into_iter().collect();
235        for h in 0..holes {
236            lin.insert(1u64 << (p * holes + h));
237        }
238        gens.push(lin);
239        anchors.push(vec![vec![(0u8, p as u32), (255, 0)]]);
240    }
241    for c in &cnf.clauses {
242        if c.iter().all(|l| !l.is_positive()) {
243            gens.push(clause_polynomial(c)); // the hole AMO pairs x_{p,h}·x_{q,h}
244            anchors.push(fixed_shape_anchor(
245                &c.iter().map(|l| atom(l.var())).collect::<Vec<_>>(),
246            ));
247        }
248    }
249    SymmetricInstance {
250        num_vars: cnf.num_vars,
251        gens,
252        sym: crate::hypercube::php_perm_symmetries(m),
253        atoms: Box::new(atom),
254        anchors,
255    }
256}
257
258/// **Modular counting `Count_q` at scale `n`, linear encoding** (`Sₙ` symmetry acting on the
259/// `q`-subsets): variable `e` is its block's points — one sort. The symmetry generators are the
260/// adjacent point transpositions, induced onto the edge variables.
261pub fn count_instance_linear(n: usize, q: usize) -> SymmetricInstance {
262    let (cnf, _) = crate::families::mod_counting(n, q);
263    let groups = crate::families::mod_counting_groups(n, q);
264    let gens = crate::polycalc::exactly_one_linear_generators(&groups);
265    let edges = crate::families::mod_counting_edges(n, q);
266    let edge_index: HashMap<Vec<usize>, usize> =
267        edges.iter().enumerate().map(|(i, e)| (e.clone(), i)).collect();
268    // Adjacent transpositions i ↔ i+1 of the point set, induced on edge variables.
269    let mut sym = Vec::new();
270    for i in 0..n - 1 {
271        let images: Vec<Lit> = (0..cnf.num_vars as u32)
272            .map(|e| {
273                let mut swapped: Vec<usize> = edges[e as usize]
274                    .iter()
275                    .map(|&p| if p == i { i + 1 } else if p == i + 1 { i } else { p })
276                    .collect();
277                swapped.sort_unstable();
278                Lit::pos(edge_index[&swapped] as u32)
279            })
280            .collect();
281        sym.push(Perm::from_images(images));
282    }
283    // Anchors: the first n generators are the point rows (anchored by their point, in group order);
284    // the rest are the overlap pairs (fixed shapes).
285    let atom = {
286        let edges = edges.clone();
287        move |v: u32| -> Vec<(u8, u32)> {
288            edges[v as usize].iter().map(|&p| (0u8, p as u32)).collect()
289        }
290    };
291    let mut anchors: Vec<Structure> = (0..n).map(|i| vec![vec![(0u8, i as u32), (255, 0)]]).collect();
292    for g in gens.iter().skip(n) {
293        let &pair_mono = g.iter().next().expect("a pair generator is a single monomial");
294        let mut vars = Vec::new();
295        let mut bits = pair_mono;
296        while bits != 0 {
297            vars.push(atom(bits.trailing_zeros()));
298            bits &= bits - 1;
299        }
300        anchors.push(fixed_shape_anchor(&vars));
301    }
302    let atoms_edges = edges.clone();
303    SymmetricInstance {
304        num_vars: cnf.num_vars,
305        gens,
306        sym,
307        atoms: Box::new(move |v| atoms_edges[v as usize].iter().map(|&p| (0, p as u32)).collect()),
308        anchors,
309    }
310}
311
312/// The **collapsed dual system** at one scale: columns = canonical orbit types of the degree-`≤ d`
313/// basis, rows = the distinct type-vectors of the admitted NS generators `m·g`, taken over one
314/// multiplier per orbit × every generator (complete by the invariance lemma: the row of
315/// `σ(m)·g` equals the row of `m·σ⁻¹(g)`, and the group permutes the generator set). An invariant
316/// functional `L` (one bit per type, `L(∅-type) = 1`) is a valid degree-`d` pseudo-expectation iff
317/// it annihilates every row.
318pub struct CollapsedDual {
319    /// Canonical types, the empty monomial's type first; the column order of `rows`.
320    pub types: Vec<Structure>,
321    /// Distinct constraint rows, each a bitmask over `types` (types ≤ 64 at the degrees this runs).
322    pub rows: BTreeSet<u64>,
323}
324
325impl CollapsedDual {
326    /// Does an invariant degree-`d` pseudo-expectation exist — is the system `L ⊥ rows, L(∅) = 1`
327    /// solvable? Returns the type-bitmask of a solution.
328    pub fn solve(&self) -> Option<u64> {
329        let nt = self.types.len();
330        let mut eqs: Vec<(Vec<u64>, bool)> = self.rows.iter().map(|&r| (vec![r], false)).collect();
331        eqs.push((vec![1u64], true)); // column 0 is the empty type: L(1) = 1
332        gf2_solve(&eqs, nt).map(|x| x[0])
333    }
334
335    /// Lift a type-solution back to the full monomial witness at a concrete scale.
336    pub fn lift(&self, inst: &SymmetricInstance, degree: usize, solution: u64) -> Vec<Mono> {
337        let on: BTreeSet<&Structure> = self
338            .types
339            .iter()
340            .enumerate()
341            .filter(|(i, _)| (solution >> i) & 1 == 1)
342            .map(|(_, t)| t)
343            .collect();
344        monomials_up_to_degree(inst.num_vars, degree)
345            .into_iter()
346            .filter(|&m| on.contains(&inst.type_of(m)))
347            .collect()
348    }
349}
350
351/// Build the [`CollapsedDual`] of an instance at degree `d`. The type column set comes from the
352/// bounded orbit quotient (one canonization per orbit); the rows from one multiplier representative
353/// per orbit against every generator, each product's monomials bucketed by cached type.
354pub fn collapsed_dual_system(inst: &SymmetricInstance, degree: usize) -> CollapsedDual {
355    let orbits = monomial_orbits_bounded(inst.num_vars, degree, &inst.sym);
356    let mut types: Vec<Structure> = Vec::new();
357    let mut type_index: BTreeMap<Structure, usize> = BTreeMap::new();
358    let mut mono_type: HashMap<Mono, usize> = HashMap::new();
359    // The empty monomial's orbit is {0}; force its type to column 0.
360    let empty_type = inst.type_of(0);
361    types.push(empty_type.clone());
362    type_index.insert(empty_type, 0);
363    for orbit in &orbits {
364        let t = inst.type_of(orbit[0]);
365        let idx = *type_index.entry(t.clone()).or_insert_with(|| {
366            types.push(t.clone());
367            types.len() - 1
368        });
369        for &m in orbit {
370            mono_type.insert(m, idx);
371        }
372    }
373    assert!(types.len() <= 64, "the type bitmask carries ≤ 64 orbit types (degree too high?)");
374
375    let mut rows: BTreeSet<u64> = BTreeSet::new();
376    for orbit in &orbits {
377        let mult = orbit[0]; // one multiplier per orbit — complete by the invariance lemma
378        for g in &inst.gens {
379            let prod = poly_mul_mono(g, mult);
380            if prod.is_empty() || poly_degree(&prod) > degree {
381                continue;
382            }
383            let mut row = 0u64;
384            for &t in &prod {
385                row ^= 1u64 << mono_type[&t]; // parity of the type count
386            }
387            if row != 0 {
388                rows.insert(row);
389            }
390        }
391    }
392    CollapsedDual { types, rows }
393}
394
395/// **The direct orbit-collapsed solver** — the independent validation path. No canonical types, no
396/// representative tricks: every admitted generator `m·g` (all multipliers) becomes a constraint, the
397/// unknowns are one bit per *orbit* (constancy imposed directly), plus `L(1) = 1`. Same mathematical
398/// question as [`collapsed_dual_system`] + [`CollapsedDual::solve`], different code path — the two
399/// must agree at every scale, which is the machine check that representative completeness and type
400/// canonization are right.
401pub fn invariant_witness_exists_direct(inst: &SymmetricInstance, degree: usize) -> Option<Vec<Mono>> {
402    let orbits = monomial_orbits_bounded(inst.num_vars, degree, &inst.sym);
403    let orbit_of: HashMap<Mono, usize> = orbits
404        .iter()
405        .enumerate()
406        .flat_map(|(i, o)| o.iter().map(move |&m| (m, i)))
407        .collect();
408    let no = orbits.len();
409    let words = no.div_ceil(64).max(1);
410    let mut eqs: Vec<(Vec<u64>, bool)> = Vec::new();
411    for g in &inst.gens {
412        if g.is_empty() {
413            continue;
414        }
415        for &m in &monomials_up_to_degree(inst.num_vars, degree) {
416            let prod = poly_mul_mono(g, m);
417            if prod.is_empty() || poly_degree(&prod) > degree {
418                continue;
419            }
420            let mut mask = vec![0u64; words];
421            for &t in &prod {
422                let oi = orbit_of[&t];
423                mask[oi / 64] ^= 1u64 << (oi % 64); // parity per orbit column
424            }
425            if mask.iter().any(|&w| w != 0) {
426                eqs.push((mask, false));
427            }
428        }
429    }
430    let empty_orbit = orbit_of[&0u64];
431    let mut target = vec![0u64; words];
432    target[empty_orbit / 64] |= 1u64 << (empty_orbit % 64);
433    eqs.push((target, true));
434    let sol = gf2_solve(&eqs, no)?;
435    let mut witness = Vec::new();
436    for (i, orbit) in orbits.iter().enumerate() {
437        if (sol[i / 64] >> (i % 64)) & 1 == 1 {
438            witness.extend(orbit.iter().copied());
439        }
440    }
441    Some(witness)
442}
443
444/// **Modular counting with one marked point** — the point-stabilizer instrument. Point `0` gets its
445/// own sort, so the symmetry drops from `Sₙ` to `Stab(0) ≅ Sₙ₋₁` (adjacent transpositions of the
446/// unmarked points only) and the canonical types automatically distinguish "touches the marked
447/// point." The collapsed dual then decides existence of a *marked-invariant* witness — a strictly
448/// larger search space than the fully-invariant one (every `Sₙ`-invariant functional is
449/// `Stab(0)`-invariant), the first refinement rung between "symmetric" and "arbitrary" on the
450/// char-2 gap.
451pub fn count_instance_linear_marked(n: usize, q: usize) -> SymmetricInstance {
452    let base = count_instance_linear(n, q);
453    let edges = crate::families::mod_counting_edges(n, q);
454    let edge_index: HashMap<Vec<usize>, usize> =
455        edges.iter().enumerate().map(|(i, e)| (e.clone(), i)).collect();
456    // Adjacent transpositions of the UNMARKED points 1..n−1 only.
457    let mut sym = Vec::new();
458    for i in 1..n - 1 {
459        let images: Vec<Lit> = (0..base.num_vars as u32)
460            .map(|e| {
461                let mut swapped: Vec<usize> = edges[e as usize]
462                    .iter()
463                    .map(|&p| if p == i { i + 1 } else if p == i + 1 { i } else { p })
464                    .collect();
465                swapped.sort_unstable();
466                Lit::pos(edge_index[&swapped] as u32)
467            })
468            .collect();
469        sym.push(Perm::from_images(images));
470    }
471    let mark = |p: usize| -> (u8, u32) { if p == 0 { (1, 0) } else { (0, p as u32) } };
472    // Anchors re-marked to match: point generators by their (marked-aware) point atom; pair
473    // generators by their variables' marked atom lists.
474    let mut anchors: Vec<Structure> = (0..n).map(|i| vec![vec![mark(i), (255, 0)]]).collect();
475    for g in base.gens.iter().skip(n) {
476        let &pair_mono = g.iter().next().expect("a pair generator is a single monomial");
477        let mut vars = Vec::new();
478        let mut bits = pair_mono;
479        while bits != 0 {
480            let e = bits.trailing_zeros() as usize;
481            let mut atoms: Vec<(u8, u32)> = edges[e].iter().map(|&p| mark(p)).collect();
482            atoms.push((255, 0));
483            atoms.sort_unstable();
484            vars.push(atoms);
485            bits &= bits - 1;
486        }
487        anchors.push(vars);
488    }
489    let atoms_edges = edges;
490    SymmetricInstance {
491        num_vars: base.num_vars,
492        gens: base.gens,
493        sym,
494        atoms: Box::new(move |v| atoms_edges[v as usize].iter().map(|&p| mark(p)).collect()),
495        anchors,
496    }
497}
498
499/// **Linear-encoded pigeonhole with one marked hole** — the hole-stabilizer instrument
500/// (`Sₘ × Stab(h₀) ≅ Sₘ × Sₘ₋₂`). Hole `0` gets its own sort; pigeon symmetry is untouched.
501pub fn php_instance_linear_marked_hole(m: usize) -> SymmetricInstance {
502    let base = php_instance_linear(m);
503    let holes = m - 1;
504    let num_vars = base.num_vars;
505    let var = |p: usize, h: usize| p * holes + h;
506    let mark = move |v: u32| -> Vec<(u8, u32)> {
507        let (p, h) = (v / holes as u32, v % holes as u32);
508        if h == 0 {
509            vec![(0, p), (2, 0)]
510        } else {
511            vec![(0, p), (1, h)]
512        }
513    };
514    // Pigeon transpositions (all) + hole transpositions among the UNMARKED holes 1..holes−1.
515    let mut sym = Vec::new();
516    for p in 0..m - 1 {
517        let mut images: Vec<Lit> = (0..num_vars as u32).map(Lit::pos).collect();
518        for h in 0..holes {
519            images.swap(var(p, h), var(p + 1, h));
520        }
521        sym.push(Perm::from_images(images));
522    }
523    for h in 1..holes.saturating_sub(1) {
524        let mut images: Vec<Lit> = (0..num_vars as u32).map(Lit::pos).collect();
525        for p in 0..m {
526            images.swap(var(p, h), var(p, h + 1));
527        }
528        sym.push(Perm::from_images(images));
529    }
530    // Anchors: pigeon rows keep their pigeon anchor; AMO pairs re-marked.
531    let mut anchors: Vec<Structure> = (0..m).map(|p| vec![vec![(0u8, p as u32), (255, 0)]]).collect();
532    for g in base.gens.iter().skip(m) {
533        let &pair = g.iter().next().expect("an AMO generator is a single monomial");
534        let mut vars = Vec::new();
535        let mut bits = pair;
536        while bits != 0 {
537            let mut atoms = mark(bits.trailing_zeros());
538            atoms.push((255, 0));
539            atoms.sort_unstable();
540            vars.push(atoms);
541            bits &= bits - 1;
542        }
543        anchors.push(vars);
544    }
545    SymmetricInstance { num_vars, gens: base.gens, sym, atoms: Box::new(mark), anchors }
546}
547
548/// **Linear-encoded pigeonhole with one marked pigeon** — the pigeon-stabilizer instrument
549/// (`Stab(p₀) × Sₘ₋₁`). Pigeon `0` gets its own sort; hole symmetry is untouched.
550pub fn php_instance_linear_marked_pigeon(m: usize) -> SymmetricInstance {
551    let base = php_instance_linear(m);
552    let holes = m - 1;
553    let num_vars = base.num_vars;
554    let var = |p: usize, h: usize| p * holes + h;
555    let mark = move |v: u32| -> Vec<(u8, u32)> {
556        let (p, h) = (v / holes as u32, v % holes as u32);
557        if p == 0 {
558            vec![(3, 0), (1, h)]
559        } else {
560            vec![(0, p), (1, h)]
561        }
562    };
563    let mut sym = Vec::new();
564    for p in 1..m - 1 {
565        let mut images: Vec<Lit> = (0..num_vars as u32).map(Lit::pos).collect();
566        for h in 0..holes {
567            images.swap(var(p, h), var(p + 1, h));
568        }
569        sym.push(Perm::from_images(images));
570    }
571    for h in 0..holes.saturating_sub(1) {
572        let mut images: Vec<Lit> = (0..num_vars as u32).map(Lit::pos).collect();
573        for p in 0..m {
574            images.swap(var(p, h), var(p, h + 1));
575        }
576        sym.push(Perm::from_images(images));
577    }
578    let mut anchors: Vec<Structure> = (0..m)
579        .map(|p| {
580            let sort = if p == 0 { 3u8 } else { 0u8 };
581            let id = if p == 0 { 0u32 } else { p as u32 };
582            vec![vec![(sort, id), (255, 0)]]
583        })
584        .collect();
585    for g in base.gens.iter().skip(m) {
586        let &pair = g.iter().next().expect("an AMO generator is a single monomial");
587        let mut vars = Vec::new();
588        let mut bits = pair;
589        while bits != 0 {
590            let mut atoms = mark(bits.trailing_zeros());
591            atoms.push((255, 0));
592            atoms.sort_unstable();
593            vars.push(atoms);
594            bits &= bits - 1;
595        }
596        anchors.push(vars);
597    }
598    SymmetricInstance { num_vars, gens: base.gens, sym, atoms: Box::new(mark), anchors }
599}
600
601/// A cross-scale row label: the canonical joint structure of `multiplier ⊕ generator anchor`. Two
602/// (multiplier, generator) pairs with the same label lie in the same joint orbit, so their entry
603/// counts must be identical — enforced fail-closed during collection.
604pub type RowLabel = Structure;
605
606/// The labeled dual entries at one scale: `label → (column type → monomial count)` — the raw
607/// integer counts before the mod-2 reduction, the objects that are polynomial in the scale.
608pub fn labeled_dual_counts(
609    inst: &SymmetricInstance,
610    degree: usize,
611) -> BTreeMap<RowLabel, BTreeMap<Structure, u64>> {
612    let orbits = monomial_orbits_bounded(inst.num_vars, degree, &inst.sym);
613    let mut type_cache: HashMap<Mono, Structure> = HashMap::new();
614    let mut out: BTreeMap<RowLabel, BTreeMap<Structure, u64>> = BTreeMap::new();
615    for orbit in &orbits {
616        let mult = orbit[0];
617        let mult_structure = inst.structure(mult);
618        for (gi, g) in inst.gens.iter().enumerate() {
619            let prod = poly_mul_mono(g, mult);
620            if prod.is_empty() || poly_degree(&prod) > degree {
621                continue;
622            }
623            let mut joint = mult_structure.clone();
624            joint.extend(inst.anchors[gi].iter().cloned());
625            let label = canonical_structure(&joint);
626            let mut counts: BTreeMap<Structure, u64> = BTreeMap::new();
627            for &t in &prod {
628                let ty = type_cache
629                    .entry(t)
630                    .or_insert_with(|| inst.type_of(t))
631                    .clone();
632                *counts.entry(ty).or_insert(0) += 1;
633            }
634            match out.entry(label) {
635                std::collections::btree_map::Entry::Vacant(e) => {
636                    e.insert(counts);
637                }
638                std::collections::btree_map::Entry::Occupied(e) => {
639                    assert_eq!(
640                        e.get(),
641                        &counts,
642                        "same joint label ⟹ same entry counts (joint-orbit invariance, fail-closed)"
643                    );
644                }
645            }
646        }
647    }
648    out
649}
650
651/// An integer-valued polynomial in the scale, in the **finite-difference (binomial) basis**:
652/// `value(m) = Σ_k diffs[k] · C(m − base, k)`. Fitted from consecutive window evaluations; the
653/// interpolation certificate is that the difference table vanishes beyond the allowed degree — every
654/// window point past the fitting prefix is an exact verification.
655#[derive(Clone, Debug, PartialEq)]
656pub struct FittedCount {
657    pub base: usize,
658    pub diffs: Vec<i128>,
659}
660
661impl FittedCount {
662    /// Fit `values` observed at consecutive scales `base, base+1, …`. Returns `None` (fail-closed)
663    /// unless the finite-difference table vanishes above `max_degree` — which, given
664    /// `values.len() > max_degree + 1`, is exactly the statement that the fitted polynomial
665    /// *reproduces every extra window point*.
666    pub fn fit(base: usize, values: &[i128], max_degree: usize) -> Option<FittedCount> {
667        let mut table: Vec<i128> = values.to_vec();
668        let mut diffs: Vec<i128> = Vec::new();
669        let mut level = 0usize;
670        while !table.is_empty() {
671            diffs.push(table[0]);
672            if level > max_degree && table.iter().any(|&v| v != 0) {
673                return None; // needs degree beyond the bound — no certificate
674            }
675            table = table.windows(2).map(|w| w[1] - w[0]).collect();
676            level += 1;
677        }
678        diffs.truncate(max_degree + 1);
679        Some(FittedCount { base, diffs })
680    }
681
682    /// Evaluate at any scale `m ≥ base` (exact `i128` binomials).
683    pub fn eval(&self, m: usize) -> i128 {
684        let a = (m - self.base) as u128;
685        let mut c: u128 = 1; // C(a, k), built incrementally
686        let mut total: i128 = 0;
687        for (k, &d) in self.diffs.iter().enumerate() {
688            if k > 0 {
689                if a < k as u128 {
690                    break;
691                }
692                c = c * (a - (k as u128 - 1)) / k as u128;
693            }
694            total += d * c as i128;
695        }
696        total
697    }
698
699    /// The parity at any scale, via Lucas: `C(a, k)` is odd iff `k & a == k` (every base-2 digit of
700    /// `k` is ≤ the digit of `a`). No big integers, valid at every `m ≥ base`.
701    pub fn parity(&self, m: usize) -> bool {
702        let a = (m - self.base) as u64;
703        self.diffs
704            .iter()
705            .enumerate()
706            .filter(|(k, &d)| d % 2 != 0 && (*k as u64) & a == *k as u64)
707            .count()
708            % 2
709            == 1
710    }
711
712    /// The period of [`FittedCount::parity`] in `m`: `C(·, k) mod 2` has period `2^⌈log₂(k+1)⌉`, so
713    /// the parity's period is the next power of two above the highest odd-coefficient index.
714    pub fn parity_period(&self) -> usize {
715        let top = self
716            .diffs
717            .iter()
718            .enumerate()
719            .filter(|(_, &d)| d % 2 != 0)
720            .map(|(k, _)| k)
721            .max();
722        match top {
723            None => 1,
724            Some(k) => (k + 1).next_power_of_two(),
725        }
726    }
727}
728
729/// The **stabilized symbolic system**: the window-fitted entry polynomials, evaluable (mod 2) at
730/// every scale — the finite object that decides the invariant-witness question for all `m`.
731pub struct StabilizedSystem {
732    pub degree: usize,
733    /// The stabilized column types (union over the window), the empty type first.
734    pub cols: Vec<Structure>,
735    /// Per row label, one fitted counting polynomial per column.
736    pub entries: BTreeMap<RowLabel, Vec<FittedCount>>,
737    /// First scale of the fitting window — verdicts apply to `m ≥ onset`.
738    pub onset: usize,
739    /// The lcm of all entry parity periods (a power of two).
740    pub period: usize,
741}
742
743impl StabilizedSystem {
744    /// The mod-2 collapsed dual at scale `m` (from the fitted entries), solved: does an invariant
745    /// degree-`d` pseudo-expectation exist at scale `m`? By construction this is periodic in `m`
746    /// with period [`StabilizedSystem::period`] for `m ≥ onset`.
747    pub fn invariant_witness_exists_at(&self, m: usize) -> bool {
748        let nc = self.cols.len();
749        let mut eqs: Vec<(Vec<u64>, bool)> = Vec::new();
750        for fits in self.entries.values() {
751            let mut row = 0u64;
752            for (ci, f) in fits.iter().enumerate() {
753                if f.parity(m) {
754                    row |= 1u64 << ci;
755                }
756            }
757            if row != 0 {
758                eqs.push((vec![row], false));
759            }
760        }
761        eqs.push((vec![1u64], true)); // L(empty type) = 1
762        gf2_solve(&eqs, nc).is_some()
763    }
764}
765
766/// The ∀-scales verdict: the stabilized system plus its per-residue answers and the in-window
767/// differential validation record.
768pub struct ForAllVerdict {
769    pub system: StabilizedSystem,
770    /// For each residue `r` mod `period`: does an invariant witness exist at scales `≡ r`,
771    /// `≥ onset`?
772    pub by_residue: Vec<bool>,
773    /// Window validation: per scale, the fitted-system verdict — each asserted equal to the direct
774    /// orbit-collapsed solver's, with every positive lifted and re-checked.
775    pub validated: Vec<(usize, bool)>,
776}
777
778/// **Decide the invariant-witness question at every scale by one finite computation.** Build the
779/// labeled entry counts at each window scale (consecutive scales, all post-onset so every label is
780/// realizable throughout), fit each entry as a degree-`≤ max_count_degree` integer polynomial (the
781/// finite-difference certificate — window points beyond the fitting prefix are exact
782/// verifications), read off the Lucas period, and solve the fitted system once per residue class.
783/// Every window scale is differentially validated: the fitted verdict must equal
784/// [`invariant_witness_exists_direct`]'s, and positive verdicts lift to
785/// `check_ns_lower_bound_polys`-passing witnesses. A `true` residue is a machine-decided
786/// `NS-degree > degree` lower bound for **every** scale in that class from `onset` on — including
787/// scales no explicit basis can represent.
788pub fn decide_invariant_witness_for_all_scales(
789    make: &dyn Fn(usize) -> SymmetricInstance,
790    window: &[usize],
791    degree: usize,
792    max_count_degree: usize,
793) -> ForAllVerdict {
794    assert!(window.windows(2).all(|w| w[1] == w[0] + 1), "the window is consecutive scales");
795    assert!(window.len() > max_count_degree, "enough window points to pin the polynomials");
796    let onset = window[0];
797
798    // Collect labeled counts and the stabilized column set.
799    let mut per_scale: Vec<BTreeMap<RowLabel, BTreeMap<Structure, u64>>> = Vec::new();
800    let mut col_set: BTreeSet<Structure> = BTreeSet::new();
801    let mut instances: Vec<SymmetricInstance> = Vec::new();
802    for &m in window {
803        let inst = make(m);
804        let counts = labeled_dual_counts(&inst, degree);
805        for cols in counts.values() {
806            col_set.extend(cols.keys().cloned());
807        }
808        per_scale.push(counts);
809        instances.push(inst);
810    }
811    let empty_type = instances[0].type_of(0);
812    let mut cols: Vec<Structure> = vec![empty_type.clone()];
813    cols.extend(col_set.into_iter().filter(|t| *t != empty_type));
814    assert!(cols.len() <= 64, "the type bitmask carries ≤ 64 columns");
815
816    // Every label must be realized at every window scale (post-onset window).
817    let labels: BTreeSet<RowLabel> = per_scale.iter().flat_map(|s| s.keys().cloned()).collect();
818    for (i, s) in per_scale.iter().enumerate() {
819        for l in &labels {
820            assert!(
821                s.contains_key(l),
822                "scale {}: a row label is unrealized — the window starts before the onset",
823                window[i]
824            );
825        }
826    }
827
828    // Fit every entry, fail-closed on the certificate.
829    let mut entries: BTreeMap<RowLabel, Vec<FittedCount>> = BTreeMap::new();
830    let mut period = 1usize;
831    for l in &labels {
832        let mut fits = Vec::with_capacity(cols.len());
833        for c in &cols {
834            let values: Vec<i128> = per_scale
835                .iter()
836                .map(|s| *s[l].get(c).unwrap_or(&0) as i128)
837                .collect();
838            let f = FittedCount::fit(onset, &values, max_count_degree)
839                .expect("every entry count is a bounded-degree integer polynomial in the scale");
840            period = period.max(f.parity_period()); // powers of two: max = lcm
841            fits.push(f);
842        }
843        entries.insert(l.clone(), fits);
844    }
845    let system = StabilizedSystem { degree, cols, entries, onset, period };
846
847    // Differential validation across the window + witness lifting.
848    let mut validated = Vec::new();
849    for (i, &m) in window.iter().enumerate() {
850        let fitted = system.invariant_witness_exists_at(m);
851        let direct = invariant_witness_exists_direct(&instances[i], degree);
852        assert_eq!(
853            fitted,
854            direct.is_some(),
855            "scale {m}: the fitted system agrees with the direct orbit-collapsed solver"
856        );
857        if let Some(w) = direct {
858            assert!(
859                check_ns_lower_bound_polys(instances[i].num_vars, &instances[i].gens, degree, &w),
860                "scale {m}: the direct invariant witness re-checks"
861            );
862        }
863        validated.push((m, fitted));
864    }
865
866    // One verdict per residue class, evaluated past the window (periodicity makes the choice moot).
867    let beyond = window.last().unwrap() + 1;
868    let by_residue: Vec<bool> = (0..system.period)
869        .map(|r| {
870            let mut m = beyond;
871            while m % system.period != r {
872                m += 1;
873            }
874            system.invariant_witness_exists_at(m)
875        })
876        .collect();
877    ForAllVerdict { system, by_residue, validated }
878}
879
880#[cfg(test)]
881mod tests {
882    use super::*;
883
884    /// **Orbit types align across scales.** The canonical structure names an orbit
885    /// scale-independently: at each scale the map orbit → type is a bijection (same orbit ⟹ same
886    /// type by group-invariance of the structure; distinct orbits ⟹ distinct types by completeness
887    /// of the canonization), and at each fixed degree the type SET stabilizes across scales once
888    /// every structure is realizable — the cross-`m` alignment the stability theorem stands on.
889    /// Reproduces the fixed-degree orbit-count stabilization of
890    /// `php_symmetric_ns_width_is_constant_in_m_at_fixed_degree` through the naming layer, for both
891    /// pigeonhole (two sorts) and modular counting (one sort).
892    #[test]
893    fn php_monomial_orbit_types_align_across_m_by_bipartite_graph_type() {
894        for d in [1usize, 2, 3] {
895            let mut prev: Option<BTreeSet<Structure>> = None;
896            for m in [d + 1, d + 2, d + 3] {
897                let inst = php_instance_clause(m);
898                let orbits = monomial_orbits_bounded(inst.num_vars, d, &inst.sym);
899                let type_set: BTreeSet<Structure> =
900                    orbits.iter().map(|o| inst.type_of(o[0])).collect();
901                // Bijection: one type per orbit, every orbit one type.
902                assert_eq!(
903                    type_set.len(),
904                    orbits.len(),
905                    "PHP({m}) d={d}: distinct orbits get distinct canonical types"
906                );
907                for orbit in &orbits {
908                    let t = inst.type_of(orbit[0]);
909                    for &mono in orbit.iter().take(8) {
910                        assert_eq!(
911                            inst.type_of(mono),
912                            t,
913                            "PHP({m}) d={d}: the canonical type is constant on the orbit"
914                        );
915                    }
916                }
917                if let Some(p) = &prev {
918                    assert_eq!(
919                        p, &type_set,
920                        "PHP d={d}: the type set is IDENTICAL across scales m={m}−1, {m}"
921                    );
922                }
923                prev = Some(type_set);
924            }
925        }
926        // The one-sort family: Count_3 types stabilize across its scale window too.
927        for d in [1usize, 2] {
928            let mut prev: Option<BTreeSet<Structure>> = None;
929            for n in [7usize, 8] {
930                let inst = count_instance_linear(n, 3);
931                let orbits = monomial_orbits_bounded(inst.num_vars, d, &inst.sym);
932                let type_set: BTreeSet<Structure> =
933                    orbits.iter().map(|o| inst.type_of(o[0])).collect();
934                assert_eq!(type_set.len(), orbits.len(), "Count_3({n}) d={d}: orbit↔type bijection");
935                if let Some(p) = &prev {
936                    assert_eq!(p, &type_set, "Count_3 d={d}: type set identical at n=7, 8");
937                }
938                prev = Some(type_set);
939            }
940        }
941    }
942
943    /// **The invariance lemma, machine-checked.** For an invariant functional `L` (constant on
944    /// monomial orbits) and any group element `σ`: `⟨L, σ(p)⟩ = ⟨L, p⟩`. Hence a constraint checked
945    /// on one representative per joint (multiplier-orbit × generator) is checked on all — the
946    /// soundness of the collapsed system's representative rows. Verified on pigeonhole with random
947    /// invariant functionals against every generator polynomial and symmetry generator.
948    #[test]
949    fn an_invariant_functional_checked_on_orbit_representatives_is_checked_on_all_generators() {
950        let mut seed = 0xA11C_E5EEDu64;
951        let mut lcg = move || {
952            seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
953            seed >> 33
954        };
955        for m in [3usize, 4] {
956            let inst = php_instance_clause(m);
957            let d = 3;
958            let orbits = monomial_orbits_bounded(inst.num_vars, d, &inst.sym);
959            for _trial in 0..8 {
960                // A random invariant L: each orbit all-in or all-out.
961                let l: BTreeSet<Mono> = orbits
962                    .iter()
963                    .filter(|_| lcg() & 1 == 1)
964                    .flat_map(|o| o.iter().copied())
965                    .collect();
966                let pair = |p: &Poly| p.iter().filter(|t| l.contains(t)).count() % 2;
967                for g in &inst.gens {
968                    for sigma in &inst.sym {
969                        let image: Poly =
970                            g.iter().map(|&t| apply_perm_to_mono(sigma, t)).collect();
971                        assert_eq!(
972                            pair(g),
973                            pair(&image),
974                            "PHP({m}): ⟨L, σ(p)⟩ = ⟨L, p⟩ for invariant L"
975                        );
976                    }
977                }
978            }
979        }
980    }
981
982    /// **The collapsed dual agrees with the direct orbit-collapsed solver, and its lifted witnesses
983    /// re-check.** Two independent implementations of "does an invariant degree-`d`
984    /// pseudo-expectation exist": the type-named representative system ([`collapsed_dual_system`])
985    /// and the everything-explicit direct solver ([`invariant_witness_exists_direct`]). They must
986    /// agree at every scale and degree — and every positive verdict lifts to a full witness passing
987    /// `check_ns_lower_bound_polys` with zero trust. This pins representative completeness and type
988    /// canonization at once. The `Count_3` degree-2 row reproduces today's Lucas-schedule discovery:
989    /// invariant witness at `n = 7` (`n ≡ 3 mod 4`), NO invariant witness at `n = 8` — machine-decided
990    /// through both paths.
991    #[test]
992    fn collapsed_dual_system_agrees_with_full_symmetric_ns_on_php_small_m() {
993        // PHP, clause and linear encodings, over the window.
994        for m in [3usize, 4, 5] {
995            for (label, inst) in
996                [("clause", php_instance_clause(m)), ("linear", php_instance_linear(m))]
997            {
998                for d in 1..=3usize {
999                    let sys = collapsed_dual_system(&inst, d);
1000                    let collapsed = sys.solve();
1001                    let direct = invariant_witness_exists_direct(&inst, d);
1002                    assert_eq!(
1003                        collapsed.is_some(),
1004                        direct.is_some(),
1005                        "PHP({m}) {label} d={d}: type-system and direct solver agree"
1006                    );
1007                    if let Some(sol) = collapsed {
1008                        let witness = sys.lift(&inst, d, sol);
1009                        assert!(
1010                            check_ns_lower_bound_polys(inst.num_vars, &inst.gens, d, &witness),
1011                            "PHP({m}) {label} d={d}: the lifted invariant witness re-checks"
1012                        );
1013                    }
1014                    if let Some(w) = direct {
1015                        assert!(
1016                            check_ns_lower_bound_polys(inst.num_vars, &inst.gens, d, &w),
1017                            "PHP({m}) {label} d={d}: the direct invariant witness re-checks"
1018                        );
1019                    }
1020                }
1021            }
1022        }
1023        // Count_3 at degree 2: the Lucas schedule, decided by the collapsed machinery.
1024        for (n, invariant_exists) in [(7usize, true), (8, false)] {
1025            let inst = count_instance_linear(n, 3);
1026            let sys = collapsed_dual_system(&inst, 2);
1027            let collapsed = sys.solve();
1028            let direct = invariant_witness_exists_direct(&inst, 2);
1029            assert_eq!(collapsed.is_some(), direct.is_some(), "Count_3({n}): paths agree");
1030            assert_eq!(
1031                collapsed.is_some(),
1032                invariant_exists,
1033                "Count_3({n}) d=2: invariant witness iff n ≡ 3 (mod 4) — the Lucas schedule"
1034            );
1035            if let Some(sol) = collapsed {
1036                let witness = sys.lift(&inst, 2, sol);
1037                assert!(
1038                    check_ns_lower_bound_polys(inst.num_vars, &inst.gens, 2, &witness),
1039                    "Count_3({n}): the lifted witness re-checks"
1040                );
1041            } else {
1042                // The char-2 gap, live: no invariant witness, yet a general witness exists.
1043                let general = ns_lower_bound_witness_polys(inst.num_vars, &inst.gens, 2);
1044                assert!(
1045                    general.is_some(),
1046                    "Count_3({n}): the general witness survives where every invariant one dies"
1047                );
1048            }
1049        }
1050    }
1051
1052    /// **Entry counts are bounded-degree integer polynomials in the scale, with an interpolation
1053    /// certificate.** Every labeled entry of the collapsed dual, observed across a consecutive
1054    /// window, fits a degree-`≤ 2` polynomial in the finite-difference basis — and for PHP the
1055    /// window (5 points, 3 fitted) leaves two exact verification points per entry, which is the
1056    /// certificate that the fit is the truth and not an artifact. For `Count_3` the machinery
1057    /// locates the hand-derived quadratic: some entry's difference table is exactly `[1, 2, 1]` —
1058    /// the `C(n−4, 2)` count behind the mod-4 witness schedule, machine-found.
1059    #[test]
1060    fn collapsed_entry_counts_are_integer_polynomials_in_m_with_an_interpolation_certificate() {
1061        // PHP linear encoding, degree 2, window m = 4..8 — every entry certified with 2 spare points.
1062        let window: Vec<usize> = (4..=8).collect();
1063        let mut per_scale = Vec::new();
1064        let mut labels: BTreeSet<RowLabel> = BTreeSet::new();
1065        let mut cols: BTreeSet<Structure> = BTreeSet::new();
1066        for &m in &window {
1067            let counts = labeled_dual_counts(&php_instance_linear(m), 2);
1068            labels.extend(counts.keys().cloned());
1069            for c in counts.values() {
1070                cols.extend(c.keys().cloned());
1071            }
1072            per_scale.push(counts);
1073        }
1074        let mut fitted = 0usize;
1075        for l in &labels {
1076            for c in &cols {
1077                let values: Vec<i128> = per_scale
1078                    .iter()
1079                    .enumerate()
1080                    .map(|(i, s)| {
1081                        *s.get(l)
1082                            .unwrap_or_else(|| panic!("label unrealized at m={}", window[i]))
1083                            .get(c)
1084                            .unwrap_or(&0) as i128
1085                    })
1086                    .collect();
1087                let f = FittedCount::fit(window[0], &values, 2)
1088                    .expect("every PHP entry is a degree-≤2 polynomial, verified on 2 spare points");
1089                for (i, &m) in window.iter().enumerate() {
1090                    assert_eq!(f.eval(m), values[i], "the fit reproduces every window point");
1091                }
1092                fitted += 1;
1093            }
1094        }
1095        assert!(fitted > 20, "a non-trivial system was fitted ({fitted} entries)");
1096
1097        // Count_3, degree 2, window n = 6..8: the machinery finds the hand-derived C(n−4,2) entry.
1098        let cwindow: Vec<usize> = (6..=8).collect();
1099        let mut cscale = Vec::new();
1100        for &n in &cwindow {
1101            cscale.push(labeled_dual_counts(&count_instance_linear(n, 3), 2));
1102        }
1103        let clabels: BTreeSet<RowLabel> = cscale.iter().flat_map(|s| s.keys().cloned()).collect();
1104        let ccols: BTreeSet<Structure> = cscale
1105            .iter()
1106            .flat_map(|s| s.values().flat_map(|c| c.keys().cloned()))
1107            .collect();
1108        let mut found_quadratic = false;
1109        for l in &clabels {
1110            for c in &ccols {
1111                let values: Vec<i128> = cscale
1112                    .iter()
1113                    .map(|s| s.get(l).map(|m| *m.get(c).unwrap_or(&0)).unwrap_or(0) as i128)
1114                    .collect();
1115                let f = FittedCount::fit(cwindow[0], &values, 2)
1116                    .expect("every Count_3 entry fits at degree ≤ 2");
1117                if f.diffs == vec![1, 2, 1] {
1118                    found_quadratic = true; // C(n−4, 2) at n = 6, 7, 8 is 1, 3, 6 — diffs [1, 2, 1]
1119                    assert_eq!(f.parity_period(), 4, "the quadratic entry has Lucas period 4");
1120                }
1121            }
1122        }
1123        assert!(
1124            found_quadratic,
1125            "the machinery locates the hand-derived C(n−4,2) quadratic behind the mod-4 schedule"
1126        );
1127    }
1128
1129    /// **Parity of binomial-basis polynomials is periodic, exactly as Lucas says.** The bit-trick
1130    /// `C(a, k) odd ⟺ k & a == k` is pinned against exact binomials; fitted counts round-trip
1131    /// through evaluation; and every [`FittedCount`]'s parity repeats with its declared period —
1132    /// swept far past the fitting window. This is the finite arithmetic fact that turns a window of
1133    /// evaluations into a verdict for every scale.
1134    #[test]
1135    fn parity_of_binomial_entries_is_eventually_periodic_by_lucas() {
1136        // Lucas bit-trick vs exact binomials.
1137        fn binom_exact(a: u64, k: u64) -> u128 {
1138            if k > a {
1139                return 0;
1140            }
1141            let mut c: u128 = 1;
1142            for i in 0..k {
1143                c = c * (a - i) as u128 / (i + 1) as u128;
1144            }
1145            c
1146        }
1147        for a in 0u64..=40 {
1148            for k in 0u64..=12 {
1149                assert_eq!(
1150                    binom_exact(a, k) % 2 == 1,
1151                    k & a == k,
1152                    "Lucas: C({a},{k}) parity by the digit condition"
1153                );
1154            }
1155        }
1156        // Fit round-trips and declared periods hold, far past the window.
1157        let mut seed = 0xB1_A5EDu64;
1158        let mut lcg = move || {
1159            seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
1160            (seed >> 33) as i128
1161        };
1162        for _ in 0..50 {
1163            let base = 4 + (lcg() % 5) as usize;
1164            let coeffs: Vec<i128> = (0..=3).map(|_| lcg() % 7 - 3).collect();
1165            let poly = |m: usize| -> i128 {
1166                let a = (m - base) as u64;
1167                coeffs
1168                    .iter()
1169                    .enumerate()
1170                    .map(|(k, &c)| c * binom_exact(a, k as u64) as i128)
1171                    .sum()
1172            };
1173            let values: Vec<i128> = (base..base + 6).map(&poly).collect();
1174            let f = FittedCount::fit(base, &values, 3).expect("a degree-3 polynomial fits");
1175            let period = f.parity_period();
1176            assert!(period.is_power_of_two() && period <= 4, "period is a small power of two");
1177            for m in base..base + 96 {
1178                assert_eq!(f.eval(m), poly(m), "evaluation reproduces the polynomial everywhere");
1179                assert_eq!(
1180                    f.parity(m),
1181                    f.parity(m + period),
1182                    "parity repeats with the declared period (m={m})"
1183                );
1184            }
1185        }
1186    }
1187
1188    /// **THE CROWN: fixed-degree invariant-witness verdicts decided for EVERY scale by one finite
1189    /// computation.** For each family and degree the machinery fits the collapsed dual across a
1190    /// window, certifies the entry polynomials, and solves once per residue class — validated at
1191    /// every window scale against the direct solver, with every positive lifted to a re-checked
1192    /// full-basis witness. A `true` residue is a machine-decided `NS-degree > d` lower bound for
1193    /// every scale in the class — including scales whose explicit monomial basis (`C(n,3)` variables
1194    /// at `n ≥ 10`) exceeds anything the per-scale engines can represent. Verdicts locked from the
1195    /// machinery's own first run; the `Count_3` ones agree with (and extend beyond) the
1196    /// independently-derived mod-4 schedule.
1197    #[test]
1198    fn fixed_degree_symmetric_ns_verdict_for_php_is_decided_for_all_m_by_finite_computation() {
1199        // Pigeonhole, linear encoding, degree 2 — window m = 4..8, entries certified with cushion.
1200        let php = decide_invariant_witness_for_all_scales(
1201            &|m| php_instance_linear(m),
1202            &(4..=8).collect::<Vec<_>>(),
1203            2,
1204            2,
1205        );
1206        eprintln!(
1207            "PHP-linear d=2: onset={} period={} by_residue={:?} validated={:?}",
1208            php.system.onset, php.system.period, php.by_residue, php.validated
1209        );
1210        // LOCKED (machine-decided ∀m ≥ 4): the linear-encoded pigeonhole dual has NO invariant
1211        // witness at degree 2, at ANY scale — while general witnesses exist (the gap test) — so for
1212        // this family the char-2 gap is the RULE, not the exception: every degree-2 witness of
1213        // linear PHP is necessarily asymmetric, at every m.
1214        assert_eq!(php.system.period, 2, "PHP-linear entries are degree-≤1 in m — Lucas period 2");
1215        assert_eq!(
1216            php.by_residue,
1217            vec![false, false],
1218            "PHP-linear d=2: the invariant dual is EMPTY at every scale m ≥ 4"
1219        );
1220
1221        // Pigeonhole, clause encoding, degree 2 — the contrast: past m = d + 1 the wide pigeon
1222        // clauses leave the degree budget and the hole-injective indicator survives invariantly.
1223        let php_clause = decide_invariant_witness_for_all_scales(
1224            &|m| php_instance_clause(m),
1225            &(4..=8).collect::<Vec<_>>(),
1226            2,
1227            2,
1228        );
1229        eprintln!(
1230            "PHP-clause d=2: onset={} period={} by_residue={:?} validated={:?}",
1231            php_clause.system.onset,
1232            php_clause.system.period,
1233            php_clause.by_residue,
1234            php_clause.validated
1235        );
1236        assert!(
1237            php_clause.by_residue.iter().all(|&v| v),
1238            "PHP-clause d=2: the invariant witness exists at EVERY scale m ≥ 4 — the encoding \
1239             chooses whether symmetry can see the bound"
1240        );
1241
1242        // Modular counting, linear encoding, degree 2 — window n = 6..8 (the post-onset scales the
1243        // u64 basis can represent; the entry certificate rests on the located closed forms and the
1244        // per-scale differential validation).
1245        let count = decide_invariant_witness_for_all_scales(
1246            &|n| count_instance_linear(n, 3),
1247            &(6..=8).collect::<Vec<_>>(),
1248            2,
1249            2,
1250        );
1251        eprintln!(
1252            "Count_3 d=2: onset={} period={} by_residue={:?} validated={:?}",
1253            count.system.onset, count.system.period, count.by_residue, count.validated
1254        );
1255        // The window validations already hard-assert agreement + witness re-checks inside decide().
1256        assert_eq!(count.system.period, 4, "Count_3's schedule is mod 4 — the located quadratic");
1257        // LOCKED (machine-decided ∀n ≥ 6): invariant witnesses exist EXACTLY on the class
1258        // n ≡ 3 (mod 4) — so NS-degree(Count_3(n)) ≥ 3 for every n ≡ 3 (mod 4), including scales
1259        // (n = 11: 165 variables; n = 15: 455) far beyond any representable monomial basis.
1260        assert_eq!(
1261            count.by_residue,
1262            vec![false, false, false, true],
1263            "Count_3 d=2: the invariant witness lives exactly on n ≡ 3 (mod 4)"
1264        );
1265    }
1266
1267    /// **Refining the gap: the asymmetric witnesses are NOT one-marking-symmetric — the
1268    /// symmetry-breaking depth is ≥ 2.** The first rung between "fully invariant" and "arbitrary":
1269    /// restrict the symmetry to a point/hole/pigeon stabilizer (one marked object, its own sort)
1270    /// and re-run the collapsed dual. Predicted by hand before measurement and CONFIRMED at all
1271    /// seven cells: one marking extends nothing — `Count_3` stays exactly on its `n ≡ 3 (mod 4)`
1272    /// schedule, and all four marked PHP-linear cells stay empty. The parity mechanics say why:
1273    /// marking one object shifts the counting-polynomial arguments by one, and the dead constraint
1274    /// rows are dead by an *evenness* a single mark cannot split (e.g. the marked-point row
1275    /// `1 + C(n−2, 2)-type` counts stay even off schedule; PHP's cross rows keep their even
1276    /// hole-sums). So the char-2 gap witnesses carry **symmetry-breaking depth ≥ 2** — the witness-
1277    /// level analog of the census's composite-shear depth measure. Every marked search space
1278    /// contains the fully-invariant one, so on-schedule scales stay positive (a fortiori); both
1279    /// engine paths agree and every positive lifts to a re-checked witness, at every cell.
1280    #[test]
1281    fn the_off_schedule_witnesses_are_probed_for_stabilizer_invariance() {
1282        // Count_3 at degree 2: one marked point does NOT extend the mod-4 schedule (locked).
1283        for n in [6usize, 7, 8] {
1284            let marked = count_instance_linear_marked(n, 3);
1285            let sys = collapsed_dual_system(&marked, 2);
1286            let collapsed = sys.solve();
1287            let direct = invariant_witness_exists_direct(&marked, 2);
1288            assert_eq!(collapsed.is_some(), direct.is_some(), "Count_3({n}) marked: paths agree");
1289            assert_eq!(
1290                collapsed.is_some(),
1291                n % 4 == 3,
1292                "Count_3({n}): one marked point does not extend the mod-4 schedule (locked)"
1293            );
1294            if let Some(sol) = collapsed {
1295                let w = sys.lift(&marked, 2, sol);
1296                assert!(
1297                    check_ns_lower_bound_polys(marked.num_vars, &marked.gens, 2, &w),
1298                    "Count_3({n}) marked: the lifted witness re-checks"
1299                );
1300            }
1301            eprintln!(
1302                "MARKED | Count_3({n}) d=2 (n mod 4 = {}): Stab(point)-invariant witness = {}",
1303                n % 4,
1304                collapsed.is_some()
1305            );
1306        }
1307        // PHP-linear at degree 2: fully-invariant is empty at EVERY scale; is one marked hole or
1308        // one marked pigeon enough to see the bound?
1309        for m in [4usize, 5] {
1310            for (which, inst) in [
1311                ("hole", php_instance_linear_marked_hole(m)),
1312                ("pigeon", php_instance_linear_marked_pigeon(m)),
1313            ] {
1314                let sys = collapsed_dual_system(&inst, 2);
1315                let collapsed = sys.solve();
1316                let direct = invariant_witness_exists_direct(&inst, 2);
1317                assert_eq!(
1318                    collapsed.is_some(),
1319                    direct.is_some(),
1320                    "PHP-linear({m}) marked-{which}: paths agree"
1321                );
1322                assert!(
1323                    collapsed.is_none(),
1324                    "PHP-linear({m}) marked-{which}: one marking sees nothing — the witnesses are \
1325                     ≥ 2-deep (locked)"
1326                );
1327                eprintln!(
1328                    "MARKED | PHP-linear({m}) d=2 marked-{which}: stabilizer-invariant witness = {}",
1329                    collapsed.is_some()
1330                );
1331            }
1332        }
1333    }
1334
1335    /// **The symmetric primal–dual gap over `GF(2)`, measured.** Forced by the char-2 Reynolds
1336    /// annihilation, "no invariant witness" need not mean "no witness". The map of
1337    /// `(invariant, general)` witness existence across families, scales and degrees: soundness
1338    /// demands `invariant ⟹ general` everywhere (no `(true, false)` cell), and the gap is real —
1339    /// `Count_3(8)` at degree 2 is `(false, true)`, an asymmetric witness surviving where every
1340    /// invariant candidate dies.
1341    #[test]
1342    fn the_symmetric_primal_dual_gap_over_gf2_is_measured() {
1343        let mut gap_cells = Vec::new();
1344        let mut cases: Vec<(String, SymmetricInstance, usize)> = Vec::new();
1345        for m in [3usize, 4, 5] {
1346            for d in 1..=3usize {
1347                cases.push((format!("PHP-linear({m}) d={d}"), php_instance_linear(m), d));
1348            }
1349        }
1350        for n in [7usize, 8] {
1351            cases.push((format!("Count_3({n}) d=2"), count_instance_linear(n, 3), 2));
1352        }
1353        for (label, inst, d) in &cases {
1354            let invariant = invariant_witness_exists_direct(inst, *d).is_some();
1355            let general = ns_lower_bound_witness_polys(inst.num_vars, &inst.gens, *d).is_some();
1356            assert!(
1357                !invariant || general,
1358                "{label}: an invariant witness IS a witness — (true, false) is impossible"
1359            );
1360            if !invariant && general {
1361                gap_cells.push(label.clone());
1362            }
1363            eprintln!("{label}: invariant={invariant} general={general}");
1364        }
1365        assert!(
1366            gap_cells.contains(&"Count_3(8) d=2".to_string()),
1367            "the char-2 gap is real: Count_3(8) at degree 2 has only asymmetric witnesses"
1368        );
1369        // The systematic face of the gap: linear-encoded pigeonhole's degree-2 witnesses are
1370        // asymmetric at every measured scale where they exist at all.
1371        for cell in ["PHP-linear(4) d=2", "PHP-linear(5) d=2", "PHP-linear(5) d=3"] {
1372            assert!(
1373                gap_cells.contains(&cell.to_string()),
1374                "{cell}: a gap cell — the witness exists and is necessarily asymmetric"
1375            );
1376        }
1377    }
1378}