use crate::cdcl::Lit;
use std::collections::BTreeSet;
pub type MaskClause = (u64, u64);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WidthConvention {
Strict,
WideAxioms,
}
pub fn mask_clause(clause: &[Lit]) -> MaskClause {
let (mut pos, mut neg) = (0u64, 0u64);
for l in clause {
assert!(l.var() < 63, "the u64 clause mask carries ≤ 63 variables");
if l.is_positive() {
pos |= 1u64 << l.var();
} else {
neg |= 1u64 << l.var();
}
}
(pos, neg)
}
pub fn mask_width(c: MaskClause) -> usize {
(c.0.count_ones() + c.1.count_ones()) as usize
}
pub fn resolve_masks(a: MaskClause, b: MaskClause) -> Option<MaskClause> {
let clash = (a.0 & b.1) | (a.1 & b.0);
if clash.count_ones() != 1 {
return None;
}
Some(((a.0 | b.0) & !clash, (a.1 | b.1) & !clash))
}
fn seed(clauses: &[Vec<Lit>], w: usize, convention: WidthConvention) -> BTreeSet<MaskClause> {
clauses
.iter()
.map(|c| mask_clause(c))
.filter(|&m| m.0 & m.1 == 0)
.filter(|&m| convention == WidthConvention::WideAxioms || mask_width(m) <= w)
.collect()
}
pub fn resolution_width_closure(
clauses: &[Vec<Lit>],
w: usize,
convention: WidthConvention,
) -> BTreeSet<MaskClause> {
let mut set = seed(clauses, w, convention);
let mut worklist: Vec<MaskClause> = set.iter().copied().collect();
while let Some(a) = worklist.pop() {
let snapshot: Vec<MaskClause> = set.iter().copied().collect();
for b in snapshot {
if let Some(r) = resolve_masks(a, b) {
if mask_width(r) <= w && set.insert(r) {
worklist.push(r);
}
}
}
}
set
}
pub fn width_refutes(clauses: &[Vec<Lit>], w: usize, convention: WidthConvention) -> bool {
resolution_width_closure(clauses, w, convention).contains(&(0, 0))
}
pub fn min_res_width_clauses(
num_vars: usize,
clauses: &[Vec<Lit>],
convention: WidthConvention,
) -> Option<usize> {
(0..=num_vars).find(|&w| width_refutes(clauses, w, convention))
}
pub fn check_res_width_lower_bound(
clauses: &[Vec<Lit>],
w: usize,
convention: WidthConvention,
closed: &BTreeSet<MaskClause>,
) -> bool {
if closed.contains(&(0, 0)) {
return false; }
if closed.iter().any(|&(p, n)| p & n != 0) {
return false; }
if !seed(clauses, w, convention).iter().all(|m| closed.contains(m)) {
return false; }
let all: Vec<MaskClause> = closed.iter().copied().collect();
for (i, &a) in all.iter().enumerate() {
for &b in &all[i..] {
if let Some(r) = resolve_masks(a, b) {
if mask_width(r) <= w && !closed.contains(&r) {
return false; }
}
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hypercube::Subcube;
#[test]
fn width_closure_matches_the_subcube_resolution_width_on_the_census() {
for n in 1..=3usize {
for rec in crate::census::census(n) {
let clauses: Vec<Vec<Lit>> = rec
.rep
.blockers
.iter()
.map(|b: &Subcube| {
b.clause_literals()
.into_iter()
.map(|(v, positive)| Lit::new(v as u32, positive))
.collect()
})
.collect();
let ours = min_res_width_clauses(n, &clauses, WidthConvention::Strict);
assert_eq!(
ours,
Some(rec.min_res_width),
"n={n}: mask-clause closure width = census width on the orbit representative"
);
}
}
let sat = vec![vec![Lit::pos(0), Lit::pos(1)], vec![Lit::neg(0), Lit::pos(2)]];
for conv in [WidthConvention::Strict, WidthConvention::WideAxioms] {
assert_eq!(min_res_width_clauses(3, &sat, conv), None, "a satisfiable formula has no width");
}
}
#[test]
fn resolution_width_lower_bounds_are_certified_by_the_closed_clause_set() {
let p = |v: u32| Lit::pos(v);
let q = |v: u32| Lit::neg(v);
let xor_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 (php3, _) = crate::families::php(3);
for (nv, clauses) in [(3usize, xor_core), (php3.num_vars, php3.clauses)] {
for conv in [WidthConvention::Strict, WidthConvention::WideAxioms] {
let wstar = min_res_width_clauses(nv, &clauses, conv)
.expect("UNSAT ⟹ some width refutes");
for w in 0..wstar {
let closed = resolution_width_closure(&clauses, w, conv);
assert!(
!closed.contains(&(0, 0)),
"below the minimum width the closure must not refute (w={w})"
);
assert!(
check_res_width_lower_bound(&clauses, w, conv, &closed),
"the closure certifies res-width > {w}"
);
let mut with_empty = closed.clone();
with_empty.insert((0, 0));
assert!(
!check_res_width_lower_bound(&clauses, w, conv, &with_empty),
"a set containing ⊥ is no lower bound"
);
if let Some(&first) = closed.iter().next() {
let mut missing = closed.clone();
missing.remove(&first);
assert!(
!check_res_width_lower_bound(&clauses, w, conv, &missing),
"a set with a hole (missing axiom or closure gap) must be rejected"
);
}
let mut with_taut = closed.clone();
with_taut.insert((1, 1));
assert!(
!check_res_width_lower_bound(&clauses, w, conv, &with_taut),
"a tautology-smuggling set must be rejected"
);
}
assert!(width_refutes(&clauses, wstar, conv), "the closure refutes at w*");
}
}
}
#[test]
fn tseitin_expander_has_certified_resolution_width_lower_bounds() {
for n in [6usize, 8] {
let (_eqs, cnf, verdict) = crate::families::tseitin_expander(n, 0xC0FFEE + n as u64);
assert!(matches!(verdict, crate::families::ExpectedVerdict::Unsat));
let ws = min_res_width_clauses(cnf.num_vars, &cnf.clauses, WidthConvention::Strict)
.expect("Tseitin expanders are UNSAT");
let ww = min_res_width_clauses(cnf.num_vars, &cnf.clauses, WidthConvention::WideAxioms)
.expect("Tseitin expanders are UNSAT");
assert_eq!(ws, ww, "n={n}: width-3 axioms ⟹ the conventions coincide");
assert!(ws > 3, "n={n}: the width exceeds the axiom width — a genuine, non-axiom bound");
for w in [ws - 1] {
let closed = resolution_width_closure(&cnf.clauses, w, WidthConvention::Strict);
assert!(
check_res_width_lower_bound(&cnf.clauses, w, WidthConvention::Strict, &closed),
"n={n}: certified res-width > {w}"
);
}
eprintln!("tseitin({n}): {} vars, certified min res-width = {ws}", cnf.num_vars);
}
}
#[test]
fn php_resolution_width_certificate_completes_the_three_system_row() {
let mut widths = Vec::new();
for m in [3usize, 4] {
let (php, _) = crate::families::php(m);
let w = min_res_width_clauses(php.num_vars, &php.clauses, WidthConvention::WideAxioms)
.expect("PHP is UNSAT");
let closed = resolution_width_closure(&php.clauses, w - 1, WidthConvention::WideAxioms);
assert!(
check_res_width_lower_bound(&php.clauses, w - 1, WidthConvention::WideAxioms, &closed),
"PHP({m}): certified res-width > {}",
w - 1
);
eprintln!("PHP({m}): certified min res-width (wide axioms) = {w}");
widths.push(w);
}
assert!(widths[1] > widths[0], "the pigeonhole width grows with m: {widths:?}");
}
}