use crate::cdcl::Lit;
use std::collections::{BTreeSet, HashMap, HashSet};
pub type Mono = u64;
pub type Poly = BTreeSet<Mono>;
fn toggle(p: &mut Poly, m: Mono) {
if !p.remove(&m) {
p.insert(m);
}
}
pub(crate) fn poly_mul_mono(p: &Poly, m: Mono) -> Poly {
let mut r = Poly::new();
for &t in p {
toggle(&mut r, t | m); }
r
}
fn poly_mul(a: &Poly, b: &Poly) -> Poly {
let mut r = Poly::new();
for &s in a {
for &t in b {
toggle(&mut r, s | t);
}
}
r
}
pub fn clause_polynomial(clause: &[Lit]) -> Poly {
let mut p: Poly = [0u64].into_iter().collect(); for l in clause {
let bit = 1u64 << l.var();
let indicator: Poly = if l.is_positive() {
[0u64, bit].into_iter().collect() } else {
[bit].into_iter().collect() };
p = poly_mul(&p, &indicator);
}
p
}
fn in_gf2_span(mut rows: Vec<Vec<u64>>, target: &[u64]) -> bool {
let words = target.len();
let high_bit = |r: &[u64]| -> Option<usize> {
for w in (0..words).rev() {
if r[w] != 0 {
return Some(w * 64 + (63 - r[w].leading_zeros() as usize));
}
}
None
};
let xor_into = |dst: &mut [u64], src: &[u64]| {
for w in 0..words {
dst[w] ^= src[w];
}
};
let mut basis: HashMap<usize, Vec<u64>> = HashMap::new();
for mut r in rows.drain(..) {
while let Some(p) = high_bit(&r) {
match basis.get(&p) {
Some(b) => xor_into(&mut r, b),
None => break,
}
}
if let Some(p) = high_bit(&r) {
basis.insert(p, r);
}
}
let mut t = target.to_vec();
while let Some(p) = high_bit(&t) {
match basis.get(&p) {
Some(b) => xor_into(&mut t, b),
None => break,
}
}
t.iter().all(|&w| w == 0)
}
pub fn nullstellensatz_refutes(num_vars: usize, clauses: &[Vec<Lit>], degree: usize) -> bool {
if num_vars > 20 {
return false;
}
let mut index: HashMap<Mono, usize> = HashMap::new();
for m in 0u64..(1u64 << num_vars) {
if m.count_ones() as usize <= degree {
let n = index.len();
index.insert(m, n);
}
}
let nb = index.len();
let words = nb.div_ceil(64).max(1);
let to_bits = |p: &Poly| -> Vec<u64> {
let mut b = vec![0u64; words];
for &m in p {
if let Some(&i) = index.get(&m) {
b[i / 64] |= 1 << (i % 64);
}
}
b
};
let monos: Vec<Mono> = index.keys().copied().collect();
let mut rows: Vec<Vec<u64>> = Vec::new();
for c in clauses {
if c.is_empty() {
return true; }
let width = c.len();
if width > degree {
continue;
}
let pc = clause_polynomial(c);
for &m in &monos {
if m.count_ones() as usize <= degree - width {
rows.push(to_bits(&poly_mul_mono(&pc, m)));
}
}
}
let mut target = vec![0u64; words];
let t0 = index[&0u64];
target[t0 / 64] |= 1 << (t0 % 64);
in_gf2_span(rows, &target)
}
fn pc_reduce(basis: &HashMap<Mono, Poly>, mut p: Poly) -> Poly {
while let Some(&lm) = p.iter().next_back() {
match basis.get(&lm) {
Some(b) => {
for &m in b {
toggle(&mut p, m);
}
}
None => break,
}
}
p
}
pub fn polynomial_calculus_refutes(num_vars: usize, clauses: &[Vec<Lit>], degree: usize) -> bool {
if num_vars > 20 {
return false;
}
let poly_deg = |p: &Poly| p.iter().map(|&m| m.count_ones() as usize).max().unwrap_or(0);
let mut basis: HashMap<Mono, Poly> = HashMap::new();
let one: Poly = [0u64].into_iter().collect();
let mut worklist: Vec<Poly> = Vec::new();
for c in clauses {
if c.is_empty() {
return true; }
if c.len() <= degree {
worklist.push(clause_polynomial(c));
}
}
while let Some(p) = worklist.pop() {
let r = pc_reduce(&basis, p);
let Some(&lm) = r.iter().next_back() else { continue }; for i in 0..num_vars as u64 {
let q = poly_mul_mono(&r, 1u64 << i);
if !q.is_empty() && poly_deg(&q) <= degree {
worklist.push(q);
}
}
basis.insert(lm, r);
if pc_reduce(&basis, one.clone()).is_empty() {
return true; }
}
false
}
#[derive(Clone, Debug)]
pub struct NsCertificate {
num_vars: usize,
coeffs: Vec<Poly>,
}
impl NsCertificate {
pub fn num_vars(&self) -> usize {
self.num_vars
}
pub fn degree(&self) -> usize {
self.coeffs.iter().flatten().map(|m| m.count_ones() as usize).max().unwrap_or(0)
}
pub fn verify(&self, clauses: &[Vec<Lit>]) -> bool {
if self.coeffs.len() != clauses.len() {
return false;
}
let mut sum = Poly::new();
for (c, g) in clauses.iter().zip(&self.coeffs) {
if g.is_empty() {
continue;
}
for m in poly_mul(&clause_polynomial(c), g) {
toggle(&mut sum, m);
}
}
sum.len() == 1 && sum.contains(&0u64)
}
}
fn point_indicator(a: u64, num_vars: usize) -> Poly {
let mask = (1u64 << num_vars).wrapping_sub(1);
let ones = a & mask;
let zeros = !a & mask;
let mut p = Poly::new();
let mut sub = zeros;
loop {
p.insert(ones | sub); if sub == 0 {
break;
}
sub = (sub - 1) & zeros;
}
p
}
pub fn build_ns_certificate(num_vars: usize, clauses: &[Vec<Lit>]) -> Result<NsCertificate, Vec<bool>> {
assert!(num_vars <= 20, "the explicit-corner construction is bounded to num_vars ≤ 20");
let mut coeffs: Vec<Poly> = vec![Poly::new(); clauses.len()];
for a in 0u64..(1u64 << num_vars) {
let sel = clauses
.iter()
.position(|c| !c.iter().any(|l| ((a >> l.var()) & 1 == 1) == l.is_positive()));
match sel {
None => return Err((0..num_vars).map(|i| (a >> i) & 1 == 1).collect()),
Some(ci) => {
for m in point_indicator(a, num_vars) {
toggle(&mut coeffs[ci], m);
}
}
}
}
Ok(NsCertificate { num_vars, coeffs })
}
pub(crate) fn close_perm_group(gens: &[crate::proof::Perm], num_vars: usize) -> Vec<crate::proof::Perm> {
use crate::proof::Perm;
let key = |p: &Perm| -> Vec<u32> { (0..num_vars).map(|v| p.apply(Lit::pos(v as u32)).var()).collect() };
let id = Perm::identity(num_vars);
let mut seen: std::collections::BTreeSet<Vec<u32>> = [key(&id)].into_iter().collect();
let mut group = vec![id.clone()];
let mut frontier = vec![id];
while let Some(p) = frontier.pop() {
for g in gens {
let q = p.compose(g);
if seen.insert(key(&q)) {
group.push(q.clone());
frontier.push(q);
}
}
}
group
}
fn symmetrize(l: &[Mono], group: &[crate::proof::Perm]) -> BTreeSet<Mono> {
let mut sym: BTreeSet<Mono> = BTreeSet::new();
for &m in l {
for g in group {
let img = apply_perm_to_mono(g, m);
if !sym.remove(&img) {
sym.insert(img);
}
}
}
sym
}
fn binom(n: usize, k: usize) -> u128 {
if k > n {
return 0;
}
let k = k.min(n - k);
let mut c = 1u128;
for i in 0..k {
c = c * (n - i) as u128 / (i + 1) as u128;
}
c
}
pub fn nullstellensatz_basis_size(n: usize, d: usize) -> u128 {
(0..=d.min(n)).map(|k| binom(n, k)).sum()
}
pub fn monomials_up_to_degree(num_vars: usize, degree: usize) -> Vec<Mono> {
assert!(num_vars <= 63, "the u64 monomial mask carries ≤ 63 variables");
let mut out: Vec<Mono> = vec![0];
for k in 1..=degree.min(num_vars) {
let limit: Mono = 1u64 << num_vars;
let mut m: Mono = (1u64 << k) - 1; while m < limit {
out.push(m);
let c = m & m.wrapping_neg(); let r = m + c;
m = (((r ^ m) >> 2) / c) | r;
}
}
out.sort_unstable();
out
}
pub fn poly_degree(p: &Poly) -> usize {
p.iter().map(|&m| m.count_ones() as usize).max().unwrap_or(0)
}
pub fn ns_refutes_polys(num_vars: usize, gens: &[Poly], degree: usize) -> bool {
let basis = monomials_up_to_degree(num_vars, degree);
let index: HashMap<Mono, usize> = basis.iter().enumerate().map(|(i, &m)| (m, i)).collect();
let words = basis.len().div_ceil(64).max(1);
let to_bits = |p: &Poly| -> Vec<u64> {
let mut b = vec![0u64; words];
for &m in p {
if let Some(&i) = index.get(&m) {
b[i / 64] |= 1 << (i % 64);
}
}
b
};
let mut rows: Vec<Vec<u64>> = Vec::new();
for g in gens {
if g.is_empty() {
continue; }
for &m in &basis {
let prod = poly_mul_mono(g, m);
if !prod.is_empty() && poly_degree(&prod) <= degree {
rows.push(to_bits(&prod));
}
}
}
let t0 = index[&0u64];
let mut target = vec![0u64; words];
target[t0 / 64] |= 1 << (t0 % 64);
in_gf2_span(rows, &target)
}
pub fn ns_lower_bound_witness_polys(num_vars: usize, gens: &[Poly], degree: usize) -> Option<Vec<Mono>> {
let basis = monomials_up_to_degree(num_vars, degree);
let index: HashMap<Mono, usize> = basis.iter().enumerate().map(|(i, &m)| (m, i)).collect();
let nb = basis.len();
let words = nb.div_ceil(64).max(1);
let mask_of = |p: &Poly| -> Vec<u64> {
let mut mask = vec![0u64; words];
for &m in p {
if let Some(&i) = index.get(&m) {
mask[i / 64] |= 1u64 << (i % 64);
}
}
mask
};
let mut eqs: Vec<(Vec<u64>, bool)> = Vec::new();
for g in gens {
if g.is_empty() {
continue;
}
for &m in &basis {
let prod = poly_mul_mono(g, m);
if !prod.is_empty() && poly_degree(&prod) <= degree {
eqs.push((mask_of(&prod), false)); }
}
}
let t0 = index[&0u64];
let mut target = vec![0u64; words];
target[t0 / 64] |= 1u64 << (t0 % 64);
eqs.push((target, true)); let l = gf2_solve(&eqs, nb)?;
Some((0..nb).filter(|&i| (l[i / 64] >> (i % 64)) & 1 == 1).map(|i| basis[i]).collect())
}
pub fn ns_lower_bound_witness_polys_on_basis(
num_vars: usize,
gens: &[Poly],
degree: usize,
in_basis: &dyn Fn(Mono) -> bool,
) -> Option<Vec<Mono>> {
let all = monomials_up_to_degree(num_vars, degree);
let basis: Vec<Mono> = all.iter().copied().filter(|&m| in_basis(m)).collect();
let index: HashMap<Mono, usize> = basis.iter().enumerate().map(|(i, &m)| (m, i)).collect();
index.get(&0u64)?; let nb = basis.len();
let words = nb.div_ceil(64).max(1);
let mask_of = |p: &Poly| -> Vec<u64> {
let mut mask = vec![0u64; words];
for &m in p {
if let Some(&i) = index.get(&m) {
mask[i / 64] |= 1u64 << (i % 64);
}
}
mask
};
let mut eqs: Vec<(Vec<u64>, bool)> = Vec::new();
for g in gens {
if g.is_empty() {
continue;
}
for &m in &all {
let prod = poly_mul_mono(g, m);
if !prod.is_empty() && poly_degree(&prod) <= degree {
eqs.push((mask_of(&prod), false));
}
}
}
let t0 = index[&0u64];
let mut target = vec![0u64; words];
target[t0 / 64] |= 1u64 << (t0 % 64);
eqs.push((target, true));
let l = gf2_solve(&eqs, nb)?;
Some((0..nb).filter(|&i| (l[i / 64] >> (i % 64)) & 1 == 1).map(|i| basis[i]).collect())
}
pub fn check_ns_lower_bound_polys(num_vars: usize, gens: &[Poly], degree: usize, witness: &[Mono]) -> bool {
let l: BTreeSet<Mono> = witness.iter().copied().collect();
if !l.contains(&0u64) {
return false; }
let basis = monomials_up_to_degree(num_vars, degree);
for g in gens {
if g.is_empty() {
continue;
}
for &m in &basis {
let prod = poly_mul_mono(g, m);
if !prod.is_empty()
&& poly_degree(&prod) <= degree
&& prod.iter().filter(|t| l.contains(t)).count() % 2 == 1
{
return false; }
}
}
true
}
pub fn exactly_one_linear_generators(groups: &[Vec<u32>]) -> Vec<Poly> {
let mut gens: Vec<Poly> = Vec::new();
for g in groups {
let mut lin: Poly = [0u64].into_iter().collect();
for &v in g {
assert!(v < 63, "the u64 monomial mask carries ≤ 63 variables");
toggle(&mut lin, 1u64 << v);
}
gens.push(lin);
}
let mut pairs: BTreeSet<Mono> = BTreeSet::new();
for g in groups {
for (i, &u) in g.iter().enumerate() {
for &v in &g[i + 1..] {
pairs.insert((1u64 << u) | (1u64 << v));
}
}
}
gens.extend(pairs.into_iter().map(|m| [m].into_iter().collect::<Poly>()));
gens
}
pub(crate) fn gf2_solve(equations: &[(Vec<u64>, bool)], nvars: usize) -> Option<Vec<u64>> {
let words = nvars.div_ceil(64).max(1);
let bit = |v: &[u64], i: usize| (v[i / 64] >> (i % 64)) & 1 == 1;
let mut rows: Vec<(Vec<u64>, bool)> = equations.to_vec();
let mut pivots: Vec<usize> = Vec::new();
let mut r = 0usize;
for col in 0..nvars {
let Some(sel) = (r..rows.len()).find(|&i| bit(&rows[i].0, col)) else {
continue;
};
rows.swap(r, sel);
let (pmask, prhs) = (rows[r].0.clone(), rows[r].1);
for i in 0..rows.len() {
if i != r && bit(&rows[i].0, col) {
for w in 0..words {
rows[i].0[w] ^= pmask[w];
}
rows[i].1 ^= prhs;
}
}
pivots.push(col);
r += 1;
}
if rows.iter().any(|(m, b)| *b && m.iter().all(|&w| w == 0)) {
return None; }
let mut x = vec![0u64; words];
for (i, &col) in pivots.iter().enumerate() {
if rows[i].1 {
x[col / 64] |= 1u64 << (col % 64);
}
}
Some(x)
}
pub fn ns_lower_bound_witness(num_vars: usize, clauses: &[Vec<Lit>], degree: usize) -> Option<Vec<u64>> {
if num_vars > 20 {
return None;
}
let mut index: HashMap<Mono, usize> = HashMap::new();
let mut monos: Vec<Mono> = Vec::new();
for m in 0u64..(1u64 << num_vars) {
if m.count_ones() as usize <= degree {
index.insert(m, monos.len());
monos.push(m);
}
}
let nb = monos.len();
let words = nb.div_ceil(64).max(1);
let mask_of = |p: &Poly| -> Vec<u64> {
let mut mask = vec![0u64; words];
for &m in p {
if let Some(&i) = index.get(&m) {
mask[i / 64] |= 1u64 << (i % 64);
}
}
mask
};
let mut eqs: Vec<(Vec<u64>, bool)> = Vec::new();
for c in clauses {
let width = c.len();
if width == 0 {
return None; }
if width > degree {
continue;
}
let pc = clause_polynomial(c);
for &m in &monos {
if m.count_ones() as usize <= degree - width {
eqs.push((mask_of(&poly_mul_mono(&pc, m)), false)); }
}
}
let t0 = index[&0u64];
let mut target = vec![0u64; words];
target[t0 / 64] |= 1u64 << (t0 % 64);
eqs.push((target, true)); let l = gf2_solve(&eqs, nb)?;
Some((0..nb).filter(|&i| (l[i / 64] >> (i % 64)) & 1 == 1).map(|i| monos[i]).collect())
}
pub fn ns_lower_bound_witness_on_basis(
num_vars: usize,
clauses: &[Vec<Lit>],
degree: usize,
in_basis: &dyn Fn(Mono) -> bool,
) -> Option<Vec<u64>> {
if num_vars > 20 {
return None;
}
let mut index: HashMap<Mono, usize> = HashMap::new();
let mut basis: Vec<Mono> = Vec::new();
for m in 0u64..(1u64 << num_vars) {
if m.count_ones() as usize <= degree && in_basis(m) {
index.insert(m, basis.len());
basis.push(m);
}
}
index.get(&0u64)?; let nb = basis.len();
let words = nb.div_ceil(64).max(1);
let mask_of = |p: &Poly| -> Vec<u64> {
let mut mask = vec![0u64; words];
for &m in p {
if let Some(&i) = index.get(&m) {
mask[i / 64] |= 1u64 << (i % 64);
}
}
mask
};
let mults: Vec<Mono> =
(0u64..(1u64 << num_vars)).filter(|m| m.count_ones() as usize <= degree).collect();
let mut eqs: Vec<(Vec<u64>, bool)> = Vec::new();
for c in clauses {
let width = c.len();
if width == 0 {
return None;
}
if width > degree {
continue;
}
let pc = clause_polynomial(c);
for &m in &mults {
if m.count_ones() as usize <= degree - width {
eqs.push((mask_of(&poly_mul_mono(&pc, m)), false));
}
}
}
let t0 = index[&0u64];
let mut target = vec![0u64; words];
target[t0 / 64] |= 1u64 << (t0 % 64);
eqs.push((target, true));
let l = gf2_solve(&eqs, nb)?;
Some((0..nb).filter(|&i| (l[i / 64] >> (i % 64)) & 1 == 1).map(|i| basis[i]).collect())
}
pub fn php_is_partial_matching(mono: Mono, holes: usize) -> bool {
let (mut pigeons, mut used_holes) = (0u64, 0u64);
let mut bits = mono;
while bits != 0 {
let v = bits.trailing_zeros() as usize;
let (p, h) = (v / holes, v % holes);
if (pigeons >> p) & 1 == 1 || (used_holes >> h) & 1 == 1 {
return false;
}
pigeons |= 1u64 << p;
used_holes |= 1u64 << h;
bits &= bits - 1;
}
true
}
pub fn php_is_hole_injective(mono: Mono, holes: usize) -> bool {
let mut used_holes = 0u64;
let mut bits = mono;
while bits != 0 {
let h = (bits.trailing_zeros() as usize) % holes;
if (used_holes >> h) & 1 == 1 {
return false;
}
used_holes |= 1u64 << h;
bits &= bits - 1;
}
true
}
pub fn check_ns_lower_bound(num_vars: usize, clauses: &[Vec<Lit>], degree: usize, witness: &[u64]) -> bool {
let l: BTreeSet<Mono> = witness.iter().copied().collect();
let pairs = |p: &Poly| -> bool { p.iter().filter(|m| l.contains(m)).count() % 2 == 1 }; if !l.contains(&0u64) {
return false; }
for c in clauses {
let width = c.len();
if width == 0 {
return false;
}
if width > degree {
continue;
}
let pc = clause_polynomial(c);
for m in 0u64..(1u64 << num_vars) {
if m.count_ones() as usize <= degree.saturating_sub(width) {
if pairs(&poly_mul_mono(&pc, m)) {
return false; }
}
}
}
true
}
pub fn pou_atom(v: usize) -> Poly {
let x: Poly = [1u64 << v].into_iter().collect();
let one_plus_x: Poly = [0u64, 1u64 << v].into_iter().collect();
let mut atom = one_plus_x;
for m in x {
toggle(&mut atom, m);
}
atom
}
pub fn partition_of_unity(n: usize) -> Poly {
let mut sum = Poly::new();
for a in 0..(1u64 << n) {
for m in point_indicator(a, n) {
toggle(&mut sum, m);
}
}
sum
}
pub fn pou_as_product(n: usize) -> Poly {
let mut product: Poly = [0u64].into_iter().collect(); for v in 0..n {
product = poly_mul(&product, &pou_atom(v));
}
product
}
pub(crate) fn apply_perm_to_mono(perm: &crate::proof::Perm, m: Mono) -> Mono {
let mut out = 0u64;
for v in 0..perm.num_vars() {
if m & (1 << v) != 0 {
out |= 1 << perm.apply(Lit::pos(v as u32)).var();
}
}
out
}
pub fn monomial_orbits(num_vars: usize, degree: usize, generators: &[crate::proof::Perm]) -> Vec<Vec<Mono>> {
let basis: BTreeSet<Mono> =
(0u64..(1u64 << num_vars)).filter(|m| m.count_ones() as usize <= degree).collect();
let mut seen: BTreeSet<Mono> = BTreeSet::new();
let mut orbits = Vec::new();
for &m in &basis {
if seen.contains(&m) {
continue;
}
let mut orbit = BTreeSet::new();
orbit.insert(m);
let mut stack = vec![m];
while let Some(x) = stack.pop() {
for g in generators {
let y = apply_perm_to_mono(g, x);
if basis.contains(&y) && orbit.insert(y) {
stack.push(y);
}
}
}
for &x in &orbit {
seen.insert(x);
}
orbits.push(orbit.into_iter().collect());
}
orbits
}
pub fn symmetric_group_generators(n: usize) -> Vec<crate::proof::Perm> {
(0..n.saturating_sub(1))
.map(|i| {
let images: Vec<Lit> = (0..n)
.map(|v| {
if v == i {
Lit::pos((i + 1) as u32)
} else if v == i + 1 {
Lit::pos(i as u32)
} else {
Lit::pos(v as u32)
}
})
.collect();
crate::proof::Perm::from_images(images)
})
.collect()
}
pub fn nullstellensatz_refutes_symmetric(
num_vars: usize,
clauses: &[Vec<Lit>],
degree: usize,
generators: &[crate::proof::Perm],
) -> bool {
if num_vars > 20 {
return false;
}
let mono_orbits = monomial_orbits(num_vars, degree, generators);
let n_orbits = mono_orbits.len();
let words = n_orbits.div_ceil(64).max(1);
let orbit_index: HashMap<Mono, usize> = (0u64..(1u64 << num_vars))
.filter(|m| m.count_ones() as usize <= degree)
.map(|m| (m, mono_orbits.iter().position(|o| o.contains(&m)).unwrap()))
.collect();
let to_orbit_bits = |p: &Poly| -> Vec<u64> {
let mut b = vec![0u64; words];
for (oi, orbit) in mono_orbits.iter().enumerate() {
if p.contains(&orbit[0]) {
b[oi / 64] |= 1 << (oi % 64);
}
}
b
};
let apply_sigma = |sigma: &crate::proof::Perm, p: &Poly| -> Poly {
let mut out = Poly::new();
for &m in p {
toggle(&mut out, apply_perm_to_mono(sigma, m));
}
out
};
let canon = |p: &Poly| -> Vec<Mono> { p.iter().copied().collect() };
let monos: Vec<Mono> = orbit_index.keys().copied().collect();
let mut gens: Vec<Poly> = Vec::new();
for c in clauses {
if c.is_empty() {
return true; }
let width = c.len();
if width > degree {
continue;
}
let pc = clause_polynomial(c);
for &m in &monos {
if m.count_ones() as usize <= degree - width {
gens.push(poly_mul_mono(&pc, m));
}
}
}
let gen_set: HashSet<Vec<Mono>> = gens.iter().map(|p| canon(p)).collect();
let mut seen: HashSet<Vec<Mono>> = HashSet::new();
let mut rows: Vec<Vec<u64>> = Vec::new();
for g in &gens {
if seen.contains(&canon(g)) {
continue;
}
let mut orbit_sum = Poly::new();
let mut local: HashSet<Vec<Mono>> = HashSet::from([canon(g)]);
let mut stack = vec![g.clone()];
while let Some(x) = stack.pop() {
seen.insert(canon(&x));
for &m in &x {
toggle(&mut orbit_sum, m);
}
for sigma in generators {
let y = apply_sigma(sigma, &x);
let yk = canon(&y);
if !gen_set.contains(&yk) {
return false; }
if local.insert(yk) {
stack.push(y);
}
}
}
rows.push(to_orbit_bits(&orbit_sum));
}
let mut target = vec![0u64; words];
if let Some(&oi) = orbit_index.get(&0u64) {
target[oi / 64] |= 1 << (oi % 64);
}
in_gf2_span(rows, &target)
}
#[cfg(test)]
mod tests {
use super::*;
fn sat(num_vars: usize, clauses: &[Vec<Lit>]) -> bool {
(0u64..(1u64 << num_vars)).any(|x| {
clauses.iter().all(|c| c.iter().any(|l| ((x >> l.var()) & 1 != 0) == l.is_positive()))
})
}
#[test]
fn ns_certificate_is_a_constructive_completeness_proof() {
let mut unity = Poly::new();
for a in 0u64..8 {
for m in point_indicator(a, 3) {
toggle(&mut unity, m);
}
}
assert!(unity.len() == 1 && unity.contains(&0u64), "Σ_a δ_a must be the constant 1");
let p = |v: u32| Lit::pos(v);
let q = |v: u32| Lit::neg(v);
let core = vec![
vec![q(0), p(1)], vec![p(0), q(1)],
vec![q(1), p(2)], vec![p(1), q(2)],
vec![p(0), p(2)], vec![q(0), q(2)],
];
let cert = build_ns_certificate(3, &core).expect("the UNSAT core has a constructive certificate");
assert!(cert.verify(&core), "the constructive certificate re-checks against the original clauses");
assert!(cert.degree() <= cert.num_vars(), "the certificate degree is ≤ n by construction");
assert!(!cert.verify(&core[..core.len() - 1]), "a certificate must not verify a different clause set");
let satisfiable = vec![vec![p(0), p(1)], vec![q(0), p(2)]];
match build_ns_certificate(3, &satisfiable) {
Err(model) => assert!(
satisfiable.iter().all(|c| c.iter().any(|l| model[l.var() as usize] == l.is_positive())),
"the returned SAT witness must satisfy every clause"
),
Ok(_) => panic!("a satisfiable formula must not yield a refutation certificate"),
}
}
#[test]
fn partition_of_unity_is_one_for_all_n_by_the_atom_factorization() {
let one: Poly = [0u64].into_iter().collect();
for v in 0..8 {
assert_eq!(pou_atom(v), one, "the atom (1+x{v})+x{v} reduces to 1");
}
assert_eq!(partition_of_unity(0), one, "PoU(0) = 1 (base case)");
for n in 0..=12 {
assert_eq!(partition_of_unity(n), pou_as_product(n), "PoU(n) = Π atoms (sum-of-products = product-of-sums)");
assert_eq!(partition_of_unity(n), one, "PoU(n) = 1");
}
for n in 0..12 {
assert_eq!(
partition_of_unity(n + 1),
poly_mul(&partition_of_unity(n), &pou_atom(n)),
"PoU(n+1) = PoU(n)·atom — the n-uniform inductive step"
);
}
}
#[test]
#[test]
fn pigeonhole_has_certified_growing_non_width_ns_degree() {
let measured = [(3usize, 4usize), (4, 6)]; let mut degrees = Vec::new();
for (m, deg) in measured {
let (php, _) = crate::families::php(m);
let w = ns_lower_bound_witness(php.num_vars, &php.clauses, deg - 1)
.unwrap_or_else(|| panic!("PHP({m}): a degree-{} lower-bound witness must exist", deg - 1));
assert!(check_ns_lower_bound(php.num_vars, &php.clauses, deg - 1, &w), "PHP({m}): NS-degree > {} re-checks", deg - 1);
assert!(nullstellensatz_refutes(php.num_vars, &php.clauses, deg), "PHP({m}): a degree-{deg} refutation exists");
assert!(!nullstellensatz_refutes(php.num_vars, &php.clauses, deg - 1), "PHP({m}): NS-degree = {deg} exactly");
assert!(deg > m - 1, "PHP({m}): NS-degree {deg} > max clause width {} — not a width bound", m - 1);
degrees.push(deg);
}
assert!(degrees.windows(2).all(|w| w[1] > w[0]), "the certified NS degree grows with n: {degrees:?}");
}
#[test]
fn php_symmetric_ns_width_is_constant_in_m_at_fixed_degree() {
for (d, ms) in [(1usize, vec![2, 3, 4, 5]), (2, vec![3, 4, 5]), (3, vec![4, 5])] {
let counts: Vec<usize> = ms
.iter()
.map(|&m| {
let (php, _) = crate::families::php(m);
monomial_orbits(php.num_vars, d, &crate::hypercube::php_perm_symmetries(m)).len()
})
.collect();
eprintln!("degree {d}: symmetric-NS orbit-type counts across m = {counts:?}");
assert!(counts.windows(2).all(|w| w[0] == w[1]), "degree {d}: orbit-type count constant in m: {counts:?}");
}
}
#[test]
fn the_uniform_php_witness_is_the_symmetric_pseudo_expectation() {
for m in [3usize, 4] {
let (php, _) = crate::families::php(m);
let holes = m - 1;
let d = 2 * holes - 1;
let l: BTreeSet<Mono> = (0u64..(1u64 << php.num_vars))
.filter(|&mo| mo.count_ones() as usize <= d && php_is_hole_injective(mo, holes))
.collect();
let gens = crate::hypercube::php_perm_symmetries(m);
for g in &gens {
for &mo in &l {
assert!(l.contains(&apply_perm_to_mono(g, mo)), "PHP({m}): the uniform witness is symmetry-invariant");
}
}
let orbits = monomial_orbits(php.num_vars, d, &gens);
let witness_orbits = orbits.iter().filter(|o| l.contains(&o[0])).count();
eprintln!(
"PHP({m}): symmetric witness = {} monomials → {} orbit-types (of {} total), compression ×{:.1}",
l.len(), witness_orbits, orbits.len(), l.len() as f64 / witness_orbits as f64
);
assert!(witness_orbits < l.len(), "PHP({m}): symmetry compresses the witness to fewer orbit-types");
}
}
#[test]
fn uniform_parity_aware_witness_proves_php_degree_bound_for_all_m() {
for m in [3usize, 4] {
let (php, _) = crate::families::php(m);
let holes = m - 1;
let d = 2 * holes - 1; let l: Vec<u64> = (0u64..(1u64 << php.num_vars))
.filter(|&mo| mo.count_ones() as usize <= d && php_is_hole_injective(mo, holes))
.collect();
assert!(
check_ns_lower_bound(php.num_vars, &php.clauses, d, &l),
"PHP({m}): the hole-injective indicator is a valid degree-{d} pseudo-expectation ⟹ NS-degree ≥ {}",
2 * holes
);
assert!(nullstellensatz_refutes(php.num_vars, &php.clauses, 2 * holes), "PHP({m}): refuted at 2(m−1)");
}
}
#[test]
fn the_symmetric_certificate_depth_is_exactly_one_below_ns_degree() {
for m in [3usize, 4] {
let (php, _) = crate::families::php(m);
let holes = m - 1;
let nv = php.num_vars;
let ns_degree = (1..=nv)
.find(|&d| nullstellensatz_refutes(nv, &php.clauses, d))
.expect("PHP is UNSAT so some refutation degree exists");
let symmetric_depth = (0..=nv)
.rev()
.find(|&d| {
let w: Vec<u64> = (0u64..(1u64 << nv))
.filter(|&mo| mo.count_ones() as usize <= d && php_is_hole_injective(mo, holes))
.collect();
check_ns_lower_bound(nv, &php.clauses, d, &w)
})
.expect("the invariant witness is valid at some degree");
assert_eq!(ns_degree, 2 * (m - 1), "PHP({m}): NS degree is 2(m−1)");
assert_eq!(symmetric_depth, 2 * m - 3, "PHP({m}): symmetric certificate depth is 2m−3");
assert_eq!(
ns_degree,
symmetric_depth + 1,
"PHP({m}): NS-degree = symmetric-depth + 1 — the witness↔refutation duality unit"
);
eprintln!("PHP({m}): symmetric-depth={symmetric_depth}, NS-degree={ns_degree} = depth+1 ✓");
}
}
#[test]
fn the_symmetric_group_arity_grades_the_certificate_depth() {
let mut points = Vec::new();
for m in [3usize, 4] {
let (php, _) = crate::families::php(m);
let holes = m - 1;
let nv = php.num_vars;
let cert_depth = (0..=nv)
.rev()
.find(|&d| {
let w: Vec<u64> = (0u64..(1u64 << nv))
.filter(|&mo| mo.count_ones() as usize <= d && php_is_hole_injective(mo, holes))
.collect();
check_ns_lower_bound(nv, &php.clauses, d, &w)
})
.expect("the invariant witness is valid at some degree");
assert_eq!(cert_depth, 2 * m - 3, "arity {m}: certificate depth = 2·arity − 3");
points.push((m, cert_depth));
}
let (m0, d0) = points[0];
let (m1, d1) = points[1];
assert_eq!(
(d1 - d0) / (m1 - m0),
2,
"each unit of symmetric-group arity buys exactly two units of certificate depth"
);
for (m, d) in points {
eprintln!("arity m={m} → certificate depth {d} = 2m−3");
}
}
#[test]
fn the_ultimate_symmetry_to_hardness_chain_is_certified() {
use crate::res_width::{
check_res_width_lower_bound, min_res_width_clauses, resolution_width_closure, WidthConvention,
};
let mut chain = Vec::new();
for m in [3usize, 4] {
let (php, _) = crate::families::php(m);
let holes = m - 1;
let nv = php.num_vars;
let group_order = (1..=m as u128).product::<u128>() * (1..=holes as u128).product::<u128>();
let cert_depth = (0..=nv)
.rev()
.find(|&d| {
let w: Vec<u64> = (0u64..(1u64 << nv))
.filter(|&mo| mo.count_ones() as usize <= d && php_is_hole_injective(mo, holes))
.collect();
check_ns_lower_bound(nv, &php.clauses, d, &w)
})
.unwrap();
let ns_degree = (1..=nv).find(|&d| nullstellensatz_refutes(nv, &php.clauses, d)).unwrap();
let res_width = min_res_width_clauses(nv, &php.clauses, WidthConvention::WideAxioms).unwrap();
let closed = resolution_width_closure(&php.clauses, res_width - 1, WidthConvention::WideAxioms);
assert!(
check_res_width_lower_bound(&php.clauses, res_width - 1, WidthConvention::WideAxioms, &closed),
"PHP({m}): certified resolution width > {}",
res_width - 1
);
assert_eq!(cert_depth, 2 * m - 3, "arity {m}: certificate depth = 2m−3");
assert_eq!(ns_degree, 2 * m - 2, "arity {m}: NS degree = 2m−2");
assert_eq!(ns_degree, cert_depth + 1, "the witness↔refutation duality unit");
assert_eq!(res_width, m - 1, "arity {m}: resolution width = m−1");
chain.push((m, group_order, cert_depth, ns_degree, res_width));
}
let (a, b) = (chain[0], chain[1]);
assert!(b.0 > a.0, "arity increases");
assert!(b.2 > a.2, "certificate depth grows with arity");
assert!(b.3 > a.3, "NS degree grows with arity");
assert!(b.4 > a.4, "resolution width grows with arity");
for (m, order, d, g, w) in chain {
eprintln!(
"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"
);
}
}
#[test]
fn affine_shear_symmetry_and_high_ns_degree_are_mutually_exclusive() {
use crate::census::affine_composite_shear_generators;
for m in [3usize, 4] {
let (php, _) = crate::families::php(m);
let nv = php.num_vars;
for depth in 1..=nv.min(5) {
let shears = affine_composite_shear_generators(nv, &php.clauses, depth);
assert!(
shears.iter().all(|(s, _)| s.len() != depth),
"PHP({m}) is affine-shear-rigid — no genuine depth-{depth} shear"
);
}
assert!(nullstellensatz_refutes(nv, &php.clauses, 2 * (m - 1)), "PHP({m}) NS degree grows to 2(m−1)");
}
for w in [3usize, 4, 5] {
let vars: Vec<u32> = (0..w as u32).collect();
let clauses: Vec<Vec<Lit>> = (0u32..(1 << w))
.filter(|p| p.count_ones() % 2 == 1)
.map(|p| (0..w).map(|i| Lit::new(vars[i], (p >> i) & 1 == 0)).collect())
.collect();
assert!(affine_composite_shear_generators(w, &clauses, 1).is_empty(), "parity({w}): no depth-1 shear");
assert!(
affine_composite_shear_generators(w, &clauses, 2).iter().any(|(s, _)| s.len() == 2),
"parity({w}): a genuine depth-2 shear exists — constant, does not grow with w"
);
}
}
#[test]
fn over_gf2_symmetrizing_a_proof_annihilates_when_the_group_is_even() {
for m in [3usize, 4] {
let (php, _) = crate::families::php(m);
let holes = m - 1;
let d = 2 * holes - 1;
let group = close_perm_group(&crate::hypercube::php_perm_symmetries(m), php.num_vars);
let expected_order: usize =
(1..=m).product::<usize>() * (1..=holes).product::<usize>(); assert_eq!(group.len(), expected_order, "PHP({m}): |Sₘ × Sₘ₋₁| = m!·(m−1)!");
assert_eq!(group.len() % 2, 0, "PHP({m}): the pigeonhole group is even");
let witness = ns_lower_bound_witness(php.num_vars, &php.clauses, d).expect("witness exists");
assert!(check_ns_lower_bound(php.num_vars, &php.clauses, d, &witness), "the witness re-checks");
assert!(witness.contains(&0u64), "a valid pseudo-expectation carries L(1)=1 (the empty monomial)");
let averaged = symmetrize(&witness, &group);
assert!(
!averaged.contains(&0u64),
"PHP({m}): symmetrizing over an even group kills L(1)=1 — the averaged witness is degenerate"
);
let native: Vec<u64> = (0u64..(1u64 << php.num_vars))
.filter(|&mo| mo.count_ones() as usize <= d && php_is_hole_injective(mo, holes))
.collect();
assert!(check_ns_lower_bound(php.num_vars, &php.clauses, d, &native), "PHP({m}): native witness valid");
let native_set: BTreeSet<Mono> = native.iter().copied().collect();
for g in &group {
for &mo in &native {
assert!(native_set.contains(&apply_perm_to_mono(g, mo)), "PHP({m}): native witness is invariant");
}
}
eprintln!(
"PHP({m}): |G|={} (even) ⟹ averaged witness annihilated; native symmetric witness = {} monomials survives",
group.len(), native.len()
);
}
}
#[test]
fn pigeonhole_witness_structure_differs_over_gf2() {
for (m, deg) in [(3usize, 4usize), (4, 6)] {
let (php, _) = crate::families::php(m);
let holes = m - 1;
let full = ns_lower_bound_witness_on_basis(php.num_vars, &php.clauses, deg - 1, &|_| true)
.expect("full-basis witness exists");
assert!(check_ns_lower_bound(php.num_vars, &php.clauses, deg - 1, &full), "full-basis witness re-checks");
let pm = move |mono: Mono| php_is_partial_matching(mono, holes);
let on_pm = ns_lower_bound_witness_on_basis(php.num_vars, &php.clauses, deg - 1, &pm);
let hi = move |mono: Mono| php_is_hole_injective(mono, holes);
let on_hi = ns_lower_bound_witness_on_basis(php.num_vars, &php.clauses, deg - 1, &hi);
eprintln!(
"PHP({m}) over GF(2): partial-matching carries witness? {} ; hole-injective? {}",
on_pm.is_some(),
on_hi.is_some()
);
assert!(on_pm.is_none(), "PHP({m}): partial-matching sub-basis fails (too strict for GF(2))");
let w = on_hi.expect("hole-injective sub-basis must carry the GF(2) witness");
assert!(check_ns_lower_bound(php.num_vars, &php.clauses, deg - 1, &w), "PHP({m}): hole-injective witness re-checks");
assert!(w.iter().all(|&mo| php_is_hole_injective(mo, holes)), "PHP({m}): witness supported on hole-injective monomials");
}
}
fn parametric_family_has_machine_checked_degree_growth() {
for n in 2..=5usize {
let f_n: Vec<Vec<Lit>> = (0u64..(1u64 << n))
.map(|a| (0..n as u32).map(|v| Lit::new(v, (a >> v) & 1 == 0)).collect())
.collect();
let w = ns_lower_bound_witness(n, &f_n, n - 1)
.unwrap_or_else(|| panic!("F_{n}: a degree-(n-1) lower-bound witness must exist"));
assert!(check_ns_lower_bound(n, &f_n, n - 1, &w), "F_{n}: the degree-{} lower bound re-checks", n - 1);
assert!(nullstellensatz_refutes(n, &f_n, n), "F_{n}: a degree-{n} refutation exists (NS-degree = n)");
assert!(!nullstellensatz_refutes(n, &f_n, n - 1), "F_{n}: no degree-(n-1) refutation (NS-degree > n-1)");
}
let probe = 3usize;
for pigeons in [3usize, 4] {
let (php, _) = crate::families::php(pigeons);
let min_deg = (1..=probe).find(|&d| nullstellensatz_refutes(php.num_vars, &php.clauses, d));
eprintln!("PHP({pigeons}): {} vars, min GF(2) NS degree (probed ≤{probe}) = {min_deg:?}", php.num_vars);
let (d, msg) = match min_deg {
Some(d) if d >= 2 => (d - 1, "min-1"),
None => (probe, "> probe (counting is NS-hard)"),
_ => continue,
};
let w = ns_lower_bound_witness(php.num_vars, &php.clauses, d)
.unwrap_or_else(|| panic!("PHP({pigeons}): a degree-{d} lower-bound witness must exist"));
assert!(check_ns_lower_bound(php.num_vars, &php.clauses, d, &w), "PHP({pigeons}): degree-{d} lower bound ({msg}) re-checks");
}
}
#[test]
fn ns_degree_lower_bounds_are_certifiable_and_dual_to_refutation() {
let mut s = 0xCAFE_F00D_1234_9999u64;
let mut rng = || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
for _ in 0..120 {
let n = 3 + (rng() % 3) as usize; let m = 2 + (rng() % 8) as usize;
let clauses: Vec<Vec<Lit>> = (0..m)
.map(|_| {
let mut c = Vec::new();
for v in 0..n {
if rng() % 2 == 0 {
c.push(Lit::new(v as u32, rng() % 2 == 0));
}
}
if c.is_empty() {
c.push(Lit::new((rng() % n as u64) as u32, rng() % 2 == 0));
}
c
})
.collect();
for d in 1..=n {
let refutes = nullstellensatz_refutes(n, &clauses, d);
match ns_lower_bound_witness(n, &clauses, d) {
Some(w) => {
assert!(!refutes, "a lower-bound witness exists only when there is NO degree-{d} refutation");
assert!(check_ns_lower_bound(n, &clauses, d, &w), "the lower-bound witness must re-check");
}
None => assert!(refutes, "no witness ⟹ a degree-{d} refutation exists"),
}
}
}
for n in 3..=4usize {
let all_corners: Vec<Vec<Lit>> = (0u64..(1u64 << n))
.map(|a| (0..n as u32).map(|v| Lit::new(v, (a >> v) & 1 == 0)).collect())
.collect();
let w = ns_lower_bound_witness(n, &all_corners, n - 1)
.expect("all-corners has no degree-(n−1) refutation — a lower bound witness exists");
assert!(check_ns_lower_bound(n, &all_corners, n - 1, &w), "the degree-(n−1) lower bound re-checks");
assert!(ns_lower_bound_witness(n, &all_corners, n).is_none(), "at full degree n a refutation exists");
}
}
#[test]
fn no_finite_randomness_but_the_certificate_is_exponentially_large() {
for n in 5..=6usize {
let all_corners: Vec<Vec<Lit>> = (0u64..(1u64 << n))
.map(|a| (0..n as u32).map(|v| Lit::new(v, (a >> v) & 1 == 0)).collect())
.collect();
assert!(build_ns_certificate(n, &all_corners).is_ok(), "n={n}: no structureless formula — a certificate exists");
}
for n in 1..=16usize {
assert_eq!(nullstellensatz_basis_size(n, n), 1u128 << n, "the degree-n NS basis is exactly 2ⁿ");
assert!(nullstellensatz_basis_size(n, n / 2) <= nullstellensatz_basis_size(n, n), "basis grows with degree");
}
assert!(nullstellensatz_basis_size(40, 40) > 40u128.pow(6), "the certificate space is exponential, not polynomial");
}
#[test]
fn ns_construction_is_total_and_sound_past_the_census_wall() {
let mut state = 0x1234_5678_9abc_def0u64;
let mut rng = || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for &n in &[5usize, 6, 7] {
for _ in 0..150 {
let num_clauses = n + (rng() % (3 * n as u64)) as usize;
let clauses: Vec<Vec<Lit>> = (0..num_clauses)
.map(|_| {
let width = 2 + (rng() % 2) as usize; let mut seen = std::collections::HashSet::new();
let mut c = Vec::new();
while c.len() < width {
let v = (rng() % n as u64) as u32;
if seen.insert(v) {
c.push(Lit::new(v, rng() & 1 == 0));
}
}
c
})
.collect();
match build_ns_certificate(n, &clauses) {
Ok(cert) => {
assert!(cert.verify(&clauses), "n={n}: the constructive certificate must re-check");
assert!(cert.degree() <= n, "n={n}: the certificate degree must be ≤ n");
assert!(!sat(n, &clauses), "n={n}: a certificate is issued only for a genuinely UNSAT formula");
}
Err(model) => {
assert!(sat(n, &clauses), "n={n}: SAT witness ⟹ the formula is genuinely satisfiable");
assert!(
clauses.iter().all(|c| c.iter().any(|l| model[l.var() as usize] == l.is_positive())),
"n={n}: the returned model must satisfy every clause"
);
}
}
}
}
}
#[test]
fn truly_random_cannot_live_in_a_finite_hypercube() {
for nv in 3..=5 {
let mut cl: Vec<Vec<Lit>> = Vec::new();
for a in 0..(1u32 << nv) {
cl.push((0..nv as u32).map(|v| Lit::new(v, (a >> v) & 1 == 0)).collect());
}
assert!(!sat(nv, &cl), "all assignments forbidden ⟹ UNSAT");
assert!(
nullstellensatz_refutes(nv, &cl, nv),
"the hardest n={nv} formula is refuted at degree n — complexity capped at the dimension"
);
}
}
#[test]
fn symmetry_collapses_the_pc_basis_to_a_counting_problem() {
let mut orbit_counts = Vec::new();
for n in 3..=5 {
let (cnf, _) = crate::families::php(n);
let nv = cnf.num_vars;
let generators = crate::hypercube::php_perm_symmetries(n);
let orbits = monomial_orbits(nv, 2, &generators);
let full = 1 + nv + nv * (nv - 1) / 2;
assert!(
orbits.len() * 3 < full,
"PHP({n}): {} orbit-types ≪ {full} monomials — the counting collapse",
orbits.len()
);
assert_eq!(
orbits.iter().map(|o| o.len()).sum::<usize>(),
full,
"the orbits partition the monomial basis"
);
for orbit in &orbits {
let set: BTreeSet<Mono> = orbit.iter().copied().collect();
for &m in orbit {
for g in &generators {
assert!(set.contains(&apply_perm_to_mono(g, m)), "orbit closed under the group");
}
}
}
orbit_counts.push(orbits.len());
}
let max = *orbit_counts.iter().max().unwrap();
let min = *orbit_counts.iter().min().unwrap();
assert!(max - min <= 2, "orbit-type count is ~constant in n: {orbit_counts:?}");
}
#[test]
fn symmetry_cuts_the_full_ns_basis_from_exponential_to_linear() {
for n in 2..=8usize {
let gens = symmetric_group_generators(n);
let orbits = monomial_orbits(n, n, &gens);
assert_eq!(orbits.len(), n + 1, "Sₙ collapses the 2ⁿ basis to n+1 degree-orbits (n={n})");
assert_eq!(nullstellensatz_basis_size(n, n), 1u128 << n, "the full basis is 2ⁿ");
let mut sizes: Vec<u128> = orbits.iter().map(|o| o.len() as u128).collect();
sizes.sort_unstable();
let mut binoms: Vec<u128> = (0..=n).map(|k| binom(n, k)).collect();
binoms.sort_unstable();
assert_eq!(sizes, binoms, "each degree-orbit k has C(n,k) monomials");
}
let cut = |n: u32| (1u128 << n) / (n as u128 + 1);
assert!(cut(8) > cut(4) && cut(4) > cut(2), "the symmetry cut 2ⁿ/(n+1) grows with n");
for n in 2..=5usize {
let all_corners: Vec<Vec<Lit>> = (0u64..(1u64 << n))
.map(|a| (0..n as u32).map(|v| Lit::new(v, (a >> v) & 1 == 0)).collect())
.collect();
let gens = symmetric_group_generators(n);
assert!(nullstellensatz_refutes(n, &all_corners, n), "full NS refutes all-corners at degree n");
assert!(
nullstellensatz_refutes_symmetric(n, &all_corners, n, &gens),
"the symmetry-reduced NS (n+1 columns) still refutes — the 2ⁿ→n+1 cut is sound (n={n})"
);
}
}
#[test]
fn nullstellensatz_full_degree_matches_brute_force() {
fn sm(s: &mut u64) -> u64 {
*s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *s;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z ^ (z >> 31)
}
let mut state = 0x9015_0001u64;
for _ in 0..60 {
let nv = 3 + (sm(&mut state) % 3) as usize; let m = 3 + (sm(&mut state) % 8) as usize;
let mut cl: Vec<Vec<Lit>> = Vec::new();
for _ in 0..m {
let mut c = Vec::new();
for v in 0..nv {
if sm(&mut state) % 2 == 0 {
c.push(Lit::new(v as u32, sm(&mut state) % 2 == 0));
}
}
if !c.is_empty() {
cl.push(c);
}
}
if cl.is_empty() {
continue;
}
let unsat = !sat(nv, &cl);
assert_eq!(
nullstellensatz_refutes(nv, &cl, nv),
unsat,
"NS at full degree must decide exactly: {cl:?}"
);
}
}
#[test]
fn nullstellensatz_refutes_parity_at_low_degree() {
let edges = [(0u32, 1u32), (1, 2), (2, 3), (3, 4), (4, 0)];
let mut cl = Vec::new();
for (u, v) in edges {
cl.push(vec![Lit::new(u, true), Lit::new(v, true)]);
cl.push(vec![Lit::new(u, false), Lit::new(v, false)]);
}
assert!(!sat(5, &cl), "odd-cycle 2-colouring is UNSAT");
assert!(nullstellensatz_refutes(5, &cl, 2), "NS refutes the parity obstruction at degree 2");
}
#[test]
fn the_degree_is_a_genuine_power_dial() {
let mut cl = Vec::new();
for a in 0u32..8 {
cl.push(
(0..3u32)
.map(|v| Lit::new(v, (a >> v) & 1 == 0))
.collect::<Vec<Lit>>(),
);
}
assert!(!sat(3, &cl), "all 8 assignments forbidden ⟹ UNSAT");
assert!(!nullstellensatz_refutes(3, &cl, 2), "no degree-2 certificate — width-3 clauses unusable");
assert!(nullstellensatz_refutes(3, &cl, 3), "degree 3 refutes it");
}
#[test]
fn satisfiable_formulas_are_never_refuted() {
let cl = vec![vec![Lit::new(0, true), Lit::new(1, true)], vec![Lit::new(0, false), Lit::new(2, true)]];
assert!(sat(3, &cl));
for d in 0..=3 {
assert!(!nullstellensatz_refutes(3, &cl, d), "a SAT formula is refuted at no degree (d={d})");
}
}
#[test]
fn symmetry_reduced_nullstellensatz_collapses_and_is_sound() {
for nv in 3..=5usize {
let mut cl: Vec<Vec<Lit>> = Vec::new();
for a in 0..(1u32 << nv) {
cl.push((0..nv as u32).map(|v| Lit::new(v, (a >> v) & 1 == 0)).collect());
}
assert!(!sat(nv, &cl), "all assignments forbidden ⟹ UNSAT");
let gens = crate::symmetry_detect::find_generators(nv, &cl);
assert!(
nullstellensatz_refutes_symmetric(nv, &cl, nv, &gens),
"symmetry-reduced NS refutes the all-forbidden instance (n={nv})"
);
let cols = monomial_orbits(nv, nv, &gens).len();
assert!(cols < (1usize << nv), "n={nv}: {cols} orbit columns ≪ {} monomials", 1usize << nv);
}
let sat_cl = vec![vec![Lit::new(0, true), Lit::new(1, true), Lit::new(2, true)]];
let gens = crate::symmetry_detect::find_generators(3, &sat_cl);
for d in 0..=3 {
assert!(
!nullstellensatz_refutes_symmetric(3, &sat_cl, d, &gens),
"a satisfiable formula has no symmetry-reduced refutation (d={d})"
);
}
}
fn lcg(state: &mut u64) -> u64 {
*state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
*state >> 33
}
#[test]
fn ns_over_polynomial_generators_agrees_with_the_clause_encoding_on_cnfs() {
let mut corpus: Vec<(usize, Vec<Vec<Lit>>)> = Vec::new();
let (php3, _) = crate::families::php(3);
corpus.push((php3.num_vars, php3.clauses));
let (cnt32, _) = crate::families::mod_counting(3, 2);
corpus.push((cnt32.num_vars, cnt32.clauses));
let (cnt42, _) = crate::families::mod_counting(4, 2);
corpus.push((cnt42.num_vars, cnt42.clauses));
let p = |v: u32| Lit::pos(v);
let q = |v: u32| Lit::neg(v);
corpus.push((3, vec![
vec![q(0), p(1)], vec![p(0), q(1)],
vec![q(1), p(2)], vec![p(1), q(2)],
vec![p(0), p(2)], vec![q(0), q(2)],
]));
let mut seed = 0x5EED_CAFE_u64;
for _ in 0..24 {
let nv = 4 + (lcg(&mut seed) % 3) as usize; let nc = 6 + (lcg(&mut seed) % 12) as usize;
let mut cl = Vec::new();
for _ in 0..nc {
let mut vars: Vec<u32> = Vec::new();
while vars.len() < 3 {
let v = (lcg(&mut seed) % nv as u64) as u32;
if !vars.contains(&v) {
vars.push(v);
}
}
cl.push(vars.iter().map(|&v| Lit::new(v, lcg(&mut seed) & 1 == 1)).collect());
}
corpus.push((nv, cl));
}
for (nv, clauses) in &corpus {
let gens: Vec<Poly> = clauses.iter().map(|c| clause_polynomial(c)).collect();
for d in 1..=(*nv).min(5) {
let clause_verdict = nullstellensatz_refutes(*nv, clauses, d);
assert_eq!(
ns_refutes_polys(*nv, &gens, d),
clause_verdict,
"n={nv} d={d}: the polynomial-generator engine matches the clause engine"
);
let w_clause = ns_lower_bound_witness(*nv, clauses, d);
let w_polys = ns_lower_bound_witness_polys(*nv, &gens, d);
assert_eq!(
w_clause.is_some(),
w_polys.is_some(),
"n={nv} d={d}: witness existence agrees across the two engines"
);
assert_eq!(
w_polys.is_none(),
clause_verdict,
"n={nv} d={d}: duality — a refutation at d exists iff no degree-d pseudo-expectation"
);
if let (Some(wc), Some(wp)) = (w_clause, w_polys) {
assert!(
check_ns_lower_bound_polys(*nv, &gens, d, &wc),
"n={nv} d={d}: the clause-engine witness passes the polynomial checker"
);
assert!(
check_ns_lower_bound(*nv, clauses, d, &wp),
"n={nv} d={d}: the polynomial-engine witness passes the clause checker"
);
}
}
}
}
#[test]
fn degree_bounded_monomial_enumeration_scales_past_twenty_variables() {
for n in 0..=12usize {
for d in 0..=n {
let bounded: BTreeSet<Mono> = monomials_up_to_degree(n, d).into_iter().collect();
let filtered: BTreeSet<Mono> =
(0u64..(1u64 << n)).filter(|m| m.count_ones() as usize <= d).collect();
assert_eq!(bounded, filtered, "n={n} d={d}: bounded enumeration = filtered cube");
assert_eq!(
bounded.len() as u128,
nullstellensatz_basis_size(n, d),
"n={n} d={d}: the count is Σ C(n,k)"
);
}
}
let ms = monomials_up_to_degree(10, 3);
assert!(ms.windows(2).all(|w| w[0] < w[1]), "monomials come sorted ascending");
assert_eq!(monomials_up_to_degree(40, 2).len(), 821); assert_eq!(nullstellensatz_basis_size(40, 2), 821);
let (php3, _) = crate::families::php(3);
let gens: Vec<Poly> = php3.clauses.iter().map(|c| clause_polynomial(c)).collect();
let nv = 22usize;
let w = ns_lower_bound_witness_polys(nv, &gens, 2)
.expect("a degree-2 witness exists for padded PHP(3) — the degree is 4");
assert!(
check_ns_lower_bound_polys(nv, &gens, 2, &w),
"the 22-variable witness re-checks on the bounded basis"
);
assert!(!ns_refutes_polys(nv, &gens, 3), "padded PHP(3) is not refuted at degree 3");
assert!(ns_refutes_polys(nv, &gens, 4), "padded PHP(3) is refuted at degree 4");
}
#[test]
fn the_linear_and_clause_encodings_interreduce_at_bounded_degree() {
for k in 2..=6usize {
let group: Vec<u32> = (0..k as u32).collect();
let alo: Vec<Lit> = group.iter().map(|&v| Lit::pos(v)).collect();
let clause_poly = clause_polynomial(&alo);
let gens = exactly_one_linear_generators(&[group.clone()]);
let linear: &Poly = &gens[0]; assert_eq!(linear.len(), k + 1, "the linear generator is 1 + Σ x (k+1 monomials)");
assert_eq!(gens.len(), 1 + k * (k - 1) / 2, "one linear generator plus C(k,2) pairs");
let mut diff = clause_poly.clone();
for &m in linear {
toggle(&mut diff, m);
}
let mut index: HashMap<Mono, usize> = HashMap::new();
for m in 0u64..(1u64 << k) {
let i = index.len();
index.insert(m, i);
}
let words = index.len().div_ceil(64).max(1);
let to_bits = |p: &Poly| -> Vec<u64> {
let mut b = vec![0u64; words];
for &m in p {
b[index[&m] / 64] |= 1 << (index[&m] % 64);
}
b
};
let mut rows = Vec::new();
for pair in &gens[1..] {
for m in monomials_up_to_degree(k, k) {
let prod = poly_mul_mono(pair, m);
if !prod.is_empty() && poly_degree(&prod) <= k {
rows.push(to_bits(&prod));
}
}
}
assert!(
in_gf2_span(rows, &to_bits(&diff)),
"k={k}: Π(1+x) + (1+Σx) is a sum of pair-generator multiples at degree k"
);
}
for (n, q) in [(3usize, 2usize), (5, 2)] {
let (cnf, _) = crate::families::mod_counting(n, q);
let nv = cnf.num_vars;
let groups: Vec<Vec<u32>> = cnf
.clauses
.iter()
.filter(|c| c.iter().all(|l| l.is_positive()))
.map(|c| c.iter().map(|l| l.var()).collect())
.collect();
assert_eq!(groups.len(), n, "one exactly-one group per point");
let k = groups.iter().map(|g| g.len()).max().unwrap();
let linear_gens = exactly_one_linear_generators(&groups);
let clause_gens: Vec<Poly> = cnf.clauses.iter().map(|c| clause_polynomial(c)).collect();
let dmax = nv.min(5);
for d in 1..=dmax {
if ns_refutes_polys(nv, &clause_gens, d) {
assert!(
ns_refutes_polys(nv, &linear_gens, d),
"Count_{q}({n}) d={d}: clause-refutable ⟹ linear-refutable at the SAME degree"
);
}
if ns_refutes_polys(nv, &linear_gens, d) {
assert!(
ns_refutes_polys(nv, &clause_gens, (d + k - 1).min(nv)),
"Count_{q}({n}) d={d}: linear-refutable ⟹ clause-refutable at d + k − 1"
);
}
}
assert!(
(1..=dmax).any(|d| ns_refutes_polys(nv, &linear_gens, d)),
"Count_{q}({n}): the linear encoding refutes within the probed degrees"
);
}
}
#[test]
fn count_two_is_char_matched_and_falls_to_low_degree_gf2_ns() {
for n in [3usize, 5, 7] {
let (cnf, _) = crate::families::mod_counting(n, 2);
let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 2));
assert!(
ns_refutes_polys(cnf.num_vars, &gens, 1),
"Count_2({n}), n odd: linear-encoded NS degree 1 — the char-matched collapse"
);
}
for n in [4usize, 6] {
let (cnf, _) = crate::families::mod_counting(n, 2);
let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 2));
for d in 1..=3 {
assert!(
!ns_refutes_polys(cnf.num_vars, &gens, d),
"Count_2({n}), n even: SAT (a perfect matching) ⟹ no refutation at degree {d}"
);
}
}
}
#[test]
fn count_three_has_certified_growing_non_width_ns_degree_over_gf2() {
for n in [4usize, 5] {
let (cnf, _) = crate::families::mod_counting(n, 3);
let nv = cnf.num_vars;
let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 3));
assert!(!ns_refutes_polys(nv, &gens, 1), "Count_3({n}): no degree-1 refutation");
let w1 = ns_lower_bound_witness_polys(nv, &gens, 1).expect("dual witness at degree 1");
assert!(check_ns_lower_bound_polys(nv, &gens, 1, &w1), "Count_3({n}): NS-degree > 1 re-checks");
assert!(
ns_refutes_polys(nv, &gens, 2),
"Count_3({n}), n < 2q: every two blocks overlap ⟹ the degree-2 collapse"
);
eprintln!("Count_3({n}): certified exact linear-encoded GF(2) NS degree = 2 (dense regime)");
}
for n in [7usize, 8] {
let (cnf, _) = crate::families::mod_counting(n, 3);
let nv = cnf.num_vars;
assert!(nv > 20, "the genuine regime lives past the clause engine's cap ({nv} vars)");
let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 3));
let w2 = ns_lower_bound_witness_polys(nv, &gens, 2)
.expect("a degree-2 pseudo-expectation exists — the degree exceeds 2");
assert!(
check_ns_lower_bound_polys(nv, &gens, 2, &w2),
"Count_3({n}): NS-degree ≥ 3 re-checks with zero trust"
);
eprintln!("Count_3({n}) [{nv} vars]: certified NS-degree ≥ 3 (exact = 3 in the scale probe)");
}
}
#[test]
#[ignore = "scale measurement — minutes of Gaussian elimination; run explicitly or via the fast suite"]
fn count_three_scale_probe_measures_the_degree_growth() {
for n in [7usize, 8] {
let (cnf, _) = crate::families::mod_counting(n, 3);
let nv = cnf.num_vars;
let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 3));
assert!(!ns_refutes_polys(nv, &gens, 2), "Count_3({n}): no degree-2 refutation");
assert!(ns_refutes_polys(nv, &gens, 3), "Count_3({n}): refuted at degree 3 — exact");
eprintln!("Count_3({n}) [{nv} vars]: exact linear-encoded GF(2) NS degree = 3");
}
}
fn count_is_disjoint(mono: Mono, edges: &[Vec<usize>]) -> bool {
let mut used = 0u64;
let mut bits = mono;
while bits != 0 {
let e = bits.trailing_zeros() as usize;
let mask: u64 = edges[e].iter().fold(0, |m, &v| m | (1u64 << v));
if used & mask != 0 {
return false;
}
used |= mask;
bits &= bits - 1;
}
true
}
#[test]
fn count_three_witness_support_structure_is_probed_on_sub_bases() {
for n in [4usize, 5] {
let (cnf, _) = crate::families::mod_counting(n, 3);
let nv = cnf.num_vars;
let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 3));
let full = ns_lower_bound_witness_polys_on_basis(nv, &gens, 1, &|_| true)
.expect("the unrestricted sub-basis search reproduces the witness");
assert!(check_ns_lower_bound_polys(nv, &gens, 1, &full), "the control witness re-checks");
let edges = crate::families::mod_counting_edges(n, 3);
let probe =
ns_lower_bound_witness_polys_on_basis(nv, &gens, 1, &|m| count_is_disjoint(m, &edges));
let w = probe.expect("dense regime, degree 1: the disjoint support carries the witness");
assert!(check_ns_lower_bound_polys(nv, &gens, 1, &w), "the sub-basis witness re-checks");
}
for n in [7usize, 8] {
let on_schedule = n % 4 == 3;
let (cnf, _) = crate::families::mod_counting(n, 3);
let nv = cnf.num_vars;
let gens = exactly_one_linear_generators(&crate::families::mod_counting_groups(n, 3));
let edges = crate::families::mod_counting_edges(n, 3);
let disjoint: Vec<Mono> = monomials_up_to_degree(nv, 2)
.into_iter()
.filter(|&m| count_is_disjoint(m, &edges))
.collect();
for (a, b0) in [(false, false), (false, true), (true, false), (true, true)] {
let candidate: Vec<Mono> = disjoint
.iter()
.copied()
.filter(|&m| match m.count_ones() {
0 => true,
1 => a,
_ => b0,
})
.collect();
let is_indicator = a && b0;
let expect = on_schedule && is_indicator;
assert_eq!(
check_ns_lower_bound_polys(nv, &gens, 2, &candidate),
expect,
"Count_3({n}): invariant candidate (a={a}, b0={b0}) valid iff on the Lucas \
schedule and the full indicator"
);
}
let probe =
ns_lower_bound_witness_polys_on_basis(nv, &gens, 2, &|m| count_is_disjoint(m, &edges));
let w = probe.expect("the disjoint support carries a (possibly asymmetric) witness");
assert!(check_ns_lower_bound_polys(nv, &gens, 2, &w), "the sub-basis witness re-checks");
eprintln!(
"Count_3({n}) at degree 2 (n mod 4 = {}): invariant closed form {}; support witness found",
n % 4,
if on_schedule { "VALID (the partial-partition indicator)" } else { "IMPOSSIBLE — witness is necessarily asymmetric" },
);
}
}
}