Skip to main content

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}