logicaffeine_proof/res_width.rs
1//! **Certified resolution-width lower bounds** — the third proof system of the separations atlas,
2//! with the same zero-trust certificate discipline as the Nullstellensatz dual witnesses.
3//!
4//! Width-`w` resolution keeps every clause in the derivation to `≤ w` literals; Ben-Sasson–Wigderson
5//! made width the master resolution complexity measure (short proofs imply narrow proofs). At a fixed
6//! budget `w` the derivable set is a **finite least fixpoint**: saturate pairwise resolution, keeping
7//! resolvents of width `≤ w`. That fixpoint *is* the lower-bound certificate: a clause set `S` that
8//! (i) contains the admissible inputs, (ii) is closed under width-`≤ w` resolution, and (iii) lacks
9//! the empty clause proves — by induction over any purported derivation — that **no width-`w`
10//! resolution refutation exists**. The certificate is re-checked by [`check_res_width_lower_bound`]
11//! with zero trust in the producer, mirroring `polycalc::check_ns_lower_bound`.
12//!
13//! Two width conventions live in the literature, and they genuinely differ on wide-axiom families
14//! like pigeonhole (pigeon clauses have width `m−1`):
15//! [`WidthConvention::Strict`] counts axioms against the budget (the census's
16//! `hypercube::min_resolution_width` convention); [`WidthConvention::WideAxioms`] admits every axiom
17//! and bounds only *derived* clauses — the convention under which PHP's width question is non-trivial.
18//! Tautologies are dropped throughout: a resolvent with a second clashing variable is tautological
19//! (mirrored from `Subcube::resolve`, which requires a single clean pivot), and tautological axioms
20//! only ever produce subsumed or tautological resolvents.
21
22use crate::cdcl::Lit;
23use std::collections::BTreeSet;
24
25/// A clause as a signed bitmask pair `(pos, neg)` over ≤ 63 variables — `pos` the positive literals'
26/// variable set, `neg` the negative ones'. The empty clause is `(0, 0)`; a tautology has
27/// `pos & neg ≠ 0`. This is the `Subcube` blocker seen from the clause side (`care = pos|neg`,
28/// `value = neg`).
29pub type MaskClause = (u64, u64);
30
31/// Which clauses the width budget `w` applies to.
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum WidthConvention {
34 /// Every clause in the derivation — axioms included — must have width `≤ w` (the
35 /// Ben-Sasson–Wigderson refutation-width measure, and the census's convention).
36 Strict,
37 /// Axioms enter at any width; only *derived* clauses must have width `≤ w`. The convention under
38 /// which wide-axiom families (pigeonhole) have a non-trivial width question.
39 WideAxioms,
40}
41
42/// The bitmask form of a clause (duplicates collapse; ≤ 63 variables).
43pub fn mask_clause(clause: &[Lit]) -> MaskClause {
44 let (mut pos, mut neg) = (0u64, 0u64);
45 for l in clause {
46 assert!(l.var() < 63, "the u64 clause mask carries ≤ 63 variables");
47 if l.is_positive() {
48 pos |= 1u64 << l.var();
49 } else {
50 neg |= 1u64 << l.var();
51 }
52 }
53 (pos, neg)
54}
55
56/// The width of a mask clause — its literal count.
57pub fn mask_width(c: MaskClause) -> usize {
58 (c.0.count_ones() + c.1.count_ones()) as usize
59}
60
61/// Resolve two mask clauses on their single clashing variable. `Some(resolvent)` iff exactly one
62/// variable appears positively in one and negatively in the other (a second clash would make every
63/// resolvent a tautology — the same single-pivot rule as `Subcube::resolve`). The resolvent is
64/// automatically tautology-free.
65pub fn resolve_masks(a: MaskClause, b: MaskClause) -> Option<MaskClause> {
66 let clash = (a.0 & b.1) | (a.1 & b.0);
67 if clash.count_ones() != 1 {
68 return None;
69 }
70 Some(((a.0 | b.0) & !clash, (a.1 | b.1) & !clash))
71}
72
73/// The admissible axioms under a convention: tautologies dropped, and under [`WidthConvention::Strict`]
74/// only clauses of width `≤ w`.
75fn seed(clauses: &[Vec<Lit>], w: usize, convention: WidthConvention) -> BTreeSet<MaskClause> {
76 clauses
77 .iter()
78 .map(|c| mask_clause(c))
79 .filter(|&m| m.0 & m.1 == 0)
80 .filter(|&m| convention == WidthConvention::WideAxioms || mask_width(m) <= w)
81 .collect()
82}
83
84/// **The width-`w` resolution closure** — the least fixpoint of pairwise resolution over the admissible
85/// axioms, keeping every resolvent of width `≤ w`. This set contains every clause any width-`w`
86/// resolution derivation (under the convention) can produce, so it *decides* width-`w` refutability
87/// (`(0,0)` present ⟺ refutable) and, when the empty clause is absent, the set itself is the
88/// re-checkable lower-bound certificate ([`check_res_width_lower_bound`]).
89pub fn resolution_width_closure(
90 clauses: &[Vec<Lit>],
91 w: usize,
92 convention: WidthConvention,
93) -> BTreeSet<MaskClause> {
94 let mut set = seed(clauses, w, convention);
95 let mut worklist: Vec<MaskClause> = set.iter().copied().collect();
96 while let Some(a) = worklist.pop() {
97 let snapshot: Vec<MaskClause> = set.iter().copied().collect();
98 for b in snapshot {
99 if let Some(r) = resolve_masks(a, b) {
100 if mask_width(r) <= w && set.insert(r) {
101 worklist.push(r);
102 }
103 }
104 }
105 }
106 set
107}
108
109/// Does a width-`w` resolution refutation exist under the convention? Decided by the closure.
110pub fn width_refutes(clauses: &[Vec<Lit>], w: usize, convention: WidthConvention) -> bool {
111 resolution_width_closure(clauses, w, convention).contains(&(0, 0))
112}
113
114/// **The minimum resolution-refutation width** under a convention: the least `w` whose closure derives
115/// the empty clause. `None` iff the formula is satisfiable (resolution is complete at `w = num_vars`).
116pub fn min_res_width_clauses(
117 num_vars: usize,
118 clauses: &[Vec<Lit>],
119 convention: WidthConvention,
120) -> Option<usize> {
121 (0..=num_vars).find(|&w| width_refutes(clauses, w, convention))
122}
123
124/// **Re-check a resolution-width lower-bound certificate** (zero trust in the producer). `closed`
125/// certifies "no width-`w` resolution refutation of `clauses` under `convention`" iff:
126/// (i) it contains every admissible axiom, (ii) it is tautology-free, (iii) it is closed under
127/// width-`≤ w` resolution, and (iv) it lacks the empty clause. Soundness is an induction over any
128/// purported width-`w` derivation: its every clause lies in `closed`, and `⊥ ∉ closed`. A padded
129/// (superset) certificate still certifies — extra clauses only weaken the producer, never the bound.
130pub fn check_res_width_lower_bound(
131 clauses: &[Vec<Lit>],
132 w: usize,
133 convention: WidthConvention,
134 closed: &BTreeSet<MaskClause>,
135) -> bool {
136 if closed.contains(&(0, 0)) {
137 return false; // the empty clause is a refutation, not a lower bound
138 }
139 if closed.iter().any(|&(p, n)| p & n != 0) {
140 return false; // tautologies are outside the derivation calculus
141 }
142 if !seed(clauses, w, convention).iter().all(|m| closed.contains(m)) {
143 return false; // an admissible axiom is missing — derivations could escape the set
144 }
145 let all: Vec<MaskClause> = closed.iter().copied().collect();
146 for (i, &a) in all.iter().enumerate() {
147 for &b in &all[i..] {
148 if let Some(r) = resolve_masks(a, b) {
149 if mask_width(r) <= w && !closed.contains(&r) {
150 return false; // not closed — a width-w step escapes
151 }
152 }
153 }
154 }
155 true
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::hypercube::Subcube;
162
163 /// **The clause-mask closure agrees with the census's geometric oracle.** The census computes
164 /// `min_resolution_width` on subcube covers (`Subcube::resolve` — the same single-pivot rule seen
165 /// from the blocker side). On every minimal-UNSAT orbit representative at `n ≤ 3`, the mask-clause
166 /// closure under [`WidthConvention::Strict`] must reproduce the recorded width exactly; and on the
167 /// SAT side, no width ever refutes.
168 #[test]
169 fn width_closure_matches_the_subcube_resolution_width_on_the_census() {
170 for n in 1..=3usize {
171 for rec in crate::census::census(n) {
172 let clauses: Vec<Vec<Lit>> = rec
173 .rep
174 .blockers
175 .iter()
176 .map(|b: &Subcube| {
177 b.clause_literals()
178 .into_iter()
179 .map(|(v, positive)| Lit::new(v as u32, positive))
180 .collect()
181 })
182 .collect();
183 let ours = min_res_width_clauses(n, &clauses, WidthConvention::Strict);
184 assert_eq!(
185 ours,
186 Some(rec.min_res_width),
187 "n={n}: mask-clause closure width = census width on the orbit representative"
188 );
189 }
190 }
191 // SAT ⟹ no refutation at any width, under either convention.
192 let sat = vec![vec![Lit::pos(0), Lit::pos(1)], vec![Lit::neg(0), Lit::pos(2)]];
193 for conv in [WidthConvention::Strict, WidthConvention::WideAxioms] {
194 assert_eq!(min_res_width_clauses(3, &sat, conv), None, "a satisfiable formula has no width");
195 }
196 }
197
198 /// **The closed clause set is a re-checkable lower-bound certificate.** For UNSAT formulas with
199 /// measured minimum width `w*`: at every `w < w*` the closure (without `⊥`) passes the independent
200 /// checker — certifying `res-width > w` with zero trust in the producer — and the checker fails
201 /// closed on every mutilation: a set containing `⊥`, a set missing an axiom, a set with a closure
202 /// hole, and a set smuggling a tautology.
203 #[test]
204 fn resolution_width_lower_bounds_are_certified_by_the_closed_clause_set() {
205 let p = |v: u32| Lit::pos(v);
206 let q = |v: u32| Lit::neg(v);
207 // The transitive-XOR core (min width 3 among Strict derivations) and PHP(3).
208 let xor_core = vec![
209 vec![q(0), p(1)], vec![p(0), q(1)],
210 vec![q(1), p(2)], vec![p(1), q(2)],
211 vec![p(0), p(2)], vec![q(0), q(2)],
212 ];
213 let (php3, _) = crate::families::php(3);
214 for (nv, clauses) in [(3usize, xor_core), (php3.num_vars, php3.clauses)] {
215 for conv in [WidthConvention::Strict, WidthConvention::WideAxioms] {
216 let wstar = min_res_width_clauses(nv, &clauses, conv)
217 .expect("UNSAT ⟹ some width refutes");
218 for w in 0..wstar {
219 let closed = resolution_width_closure(&clauses, w, conv);
220 assert!(
221 !closed.contains(&(0, 0)),
222 "below the minimum width the closure must not refute (w={w})"
223 );
224 assert!(
225 check_res_width_lower_bound(&clauses, w, conv, &closed),
226 "the closure certifies res-width > {w}"
227 );
228 // Fail-closed on mutilations.
229 let mut with_empty = closed.clone();
230 with_empty.insert((0, 0));
231 assert!(
232 !check_res_width_lower_bound(&clauses, w, conv, &with_empty),
233 "a set containing ⊥ is no lower bound"
234 );
235 if let Some(&first) = closed.iter().next() {
236 let mut missing = closed.clone();
237 missing.remove(&first);
238 assert!(
239 !check_res_width_lower_bound(&clauses, w, conv, &missing),
240 "a set with a hole (missing axiom or closure gap) must be rejected"
241 );
242 }
243 let mut with_taut = closed.clone();
244 with_taut.insert((1, 1));
245 assert!(
246 !check_res_width_lower_bound(&clauses, w, conv, &with_taut),
247 "a tautology-smuggling set must be rejected"
248 );
249 }
250 // At w*, the closure genuinely refutes — the two-sidedness of the measure.
251 assert!(width_refutes(&clauses, wstar, conv), "the closure refutes at w*");
252 }
253 }
254 }
255
256 /// **Tseitin expanders carry certified resolution-width lower bounds.** Parity on a 3-regular
257 /// expander is the classical width-hard family (Ben-Sasson–Wigderson: width `Ω(expansion)`). We pin
258 /// the exact minimum width on small expanders under both conventions (they agree — Tseitin axioms
259 /// are already narrow, width 3) and certify each `w < w*` with the re-checked closed-set
260 /// certificate. This is the resolution half of the Tseitin atlas row (the GF(2)-rank refutation is
261 /// its polynomial upper half).
262 #[test]
263 fn tseitin_expander_has_certified_resolution_width_lower_bounds() {
264 for n in [6usize, 8] {
265 let (_eqs, cnf, verdict) = crate::families::tseitin_expander(n, 0xC0FFEE + n as u64);
266 assert!(matches!(verdict, crate::families::ExpectedVerdict::Unsat));
267 let ws = min_res_width_clauses(cnf.num_vars, &cnf.clauses, WidthConvention::Strict)
268 .expect("Tseitin expanders are UNSAT");
269 let ww = min_res_width_clauses(cnf.num_vars, &cnf.clauses, WidthConvention::WideAxioms)
270 .expect("Tseitin expanders are UNSAT");
271 assert_eq!(ws, ww, "n={n}: width-3 axioms ⟹ the conventions coincide");
272 assert!(ws > 3, "n={n}: the width exceeds the axiom width — a genuine, non-axiom bound");
273 for w in [ws - 1] {
274 let closed = resolution_width_closure(&cnf.clauses, w, WidthConvention::Strict);
275 assert!(
276 check_res_width_lower_bound(&cnf.clauses, w, WidthConvention::Strict, &closed),
277 "n={n}: certified res-width > {w}"
278 );
279 }
280 eprintln!("tseitin({n}): {} vars, certified min res-width = {ws}", cnf.num_vars);
281 }
282 }
283
284 /// **Pigeonhole's certified width completes its three-system atlas row.** Under
285 /// [`WidthConvention::WideAxioms`] (the convention that makes wide-axiom width non-trivial) PHP(m)'s
286 /// minimum refutation width is measured exactly and its lower half certified by the closed set —
287 /// alongside the certified NS degree `2(m−1)` (dual witness) and the `m(m−1)/2`-step SR upper bound,
288 /// this is the third machine-certified coordinate of the same family. The width grows with `m`.
289 #[test]
290 fn php_resolution_width_certificate_completes_the_three_system_row() {
291 let mut widths = Vec::new();
292 for m in [3usize, 4] {
293 let (php, _) = crate::families::php(m);
294 let w = min_res_width_clauses(php.num_vars, &php.clauses, WidthConvention::WideAxioms)
295 .expect("PHP is UNSAT");
296 let closed = resolution_width_closure(&php.clauses, w - 1, WidthConvention::WideAxioms);
297 assert!(
298 check_res_width_lower_bound(&php.clauses, w - 1, WidthConvention::WideAxioms, &closed),
299 "PHP({m}): certified res-width > {}",
300 w - 1
301 );
302 eprintln!("PHP({m}): certified min res-width (wide axioms) = {w}");
303 widths.push(w);
304 }
305 assert!(widths[1] > widths[0], "the pigeonhole width grows with m: {widths:?}");
306 }
307}