use std::collections::{BTreeSet, HashSet};
use crate::cdcl::Lit;
use crate::xorsat::XorEquation;
pub fn gadget_clauses(eq: &XorEquation) -> Vec<Vec<Lit>> {
let k = eq.vars.len();
let mut out = Vec::new();
for mask in 0u32..(1u32 << k) {
if (mask.count_ones() % 2 == 1) != eq.rhs {
out.push((0..k).map(|i| Lit::new(eq.vars[i] as u32, (mask >> i) & 1 == 0)).collect());
}
}
out
}
fn key(c: &[Lit]) -> Vec<u32> {
let mut k: Vec<u32> = c.iter().map(|l| l.var() * 2 + u32::from(!l.is_positive())).collect();
k.sort_unstable();
k.dedup();
k
}
fn resolve(a: &[Lit], b: &[Lit], v: u32) -> Option<Vec<Lit>> {
let mut seen: std::collections::HashMap<u32, bool> = std::collections::HashMap::new();
for l in a.iter().chain(b.iter()) {
if l.var() == v {
continue;
}
if let Some(&prev) = seen.get(&l.var()) {
if prev != l.is_positive() {
return None; }
} else {
seen.insert(l.var(), l.is_positive());
}
}
let mut out: Vec<Lit> = seen.into_iter().map(|(var, pos)| Lit::new(var, pos)).collect();
out.sort_by_key(|l| l.var() * 2 + u32::from(!l.is_positive()));
Some(out)
}
const RESOLUTION_BUDGET: usize = 40_000;
const RESOLUTION_WORK_BUDGET: u64 = 4_000_000;
fn resolution_refutation(support: &[Vec<Lit>]) -> Option<Vec<Vec<Lit>>> {
let mut active: Vec<Vec<Lit>> = Vec::new();
let mut present: HashSet<Vec<u32>> = HashSet::new();
for c in support {
let k = key(c);
if present.insert(k) {
active.push({
let mut c = c.clone();
c.sort_by_key(|l| l.var() * 2 + u32::from(!l.is_positive()));
c.dedup();
c
});
}
}
let mut emitted: Vec<Vec<Lit>> = Vec::new();
let mut work: u64 = 0;
if active.iter().any(|c| c.is_empty()) {
emitted.push(Vec::new());
return Some(emitted);
}
loop {
let vars: BTreeSet<u32> = active.iter().flat_map(|c| c.iter().map(|l| l.var())).collect();
if vars.is_empty() {
return None; }
let v = *vars
.iter()
.min_by_key(|&&v| {
let (mut p, mut n) = (0usize, 0usize);
for c in &active {
match c.iter().find(|l| l.var() == v) {
Some(l) if l.is_positive() => p += 1,
Some(_) => n += 1,
None => {}
}
}
p * n
})
.unwrap();
let mut pos: Vec<Vec<Lit>> = Vec::new();
let mut neg: Vec<Vec<Lit>> = Vec::new();
let mut rest: Vec<Vec<Lit>> = Vec::new();
for c in active.drain(..) {
match c.iter().find(|l| l.var() == v) {
Some(l) if l.is_positive() => pos.push(c),
Some(_) => neg.push(c),
None => rest.push(c),
}
}
let mut next = rest;
for cp in &pos {
for cn in &neg {
work += 1;
if work > RESOLUTION_WORK_BUDGET {
return None; }
let Some(r) = resolve(cp, cn, v) else { continue };
if r.is_empty() {
emitted.push(Vec::new());
return Some(emitted);
}
let k = key(&r);
if present.insert(k) {
emitted.push(r.clone());
next.push(r);
if emitted.len() > RESOLUTION_BUDGET {
return None; }
}
}
}
active = next;
}
}
pub fn emit_xor_drat(equations: &[XorEquation], refutation: &[usize]) -> Option<Vec<Vec<Lit>>> {
let mut support: Vec<Vec<Lit>> = Vec::new();
for &i in refutation {
support.extend(gadget_clauses(&equations[i]));
}
resolution_refutation(&support)
}
pub fn emit_drat_over_support(clauses: &[Vec<Lit>], support: &BTreeSet<u32>) -> Option<Vec<Vec<Lit>>> {
let sub: Vec<Vec<Lit>> = clauses
.iter()
.filter(|c| c.iter().all(|l| support.contains(&l.var())))
.cloned()
.collect();
resolution_refutation(&sub)
}
pub fn emit_modp_drat(num_vars: usize, clauses: &[Vec<Lit>]) -> Option<Vec<Vec<Lit>>> {
let rec = crate::modp::recover_from_cnf(num_vars, clauses)?;
if !crate::modp::is_prime(rec.modulus) {
return None; }
let combo = match crate::modp::solve(&rec.equations, rec.num_vars, rec.modulus) {
crate::modp::ModpOutcome::Unsat(combo) => combo,
crate::modp::ModpOutcome::Sat(_) => return None,
};
let mut groups_involved: BTreeSet<usize> = BTreeSet::new();
for &(eq_idx, mult) in &combo {
if mult % rec.modulus == 0 {
continue;
}
for &(g, _) in &rec.equations[eq_idx].coeffs {
groups_involved.insert(g);
}
}
let support: BTreeSet<u32> =
groups_involved.iter().flat_map(|&g| rec.groups[g].iter().copied()).collect();
emit_drat_over_support(clauses, &support)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::xorsat::{self, XorOutcome};
#[test]
fn xor_linear_dependency_compiles_to_a_drat_refutation_checked_by_rup() {
let eqs = vec![
XorEquation::new(vec![0, 1], false),
XorEquation::new(vec![1, 2], false),
XorEquation::new(vec![0, 2], true),
];
let num_vars = 3;
let clauses: Vec<Vec<Lit>> = eqs.iter().flat_map(gadget_clauses).collect();
let refutation = match xorsat::solve(&eqs, num_vars) {
XorOutcome::Unsat(s) => s,
XorOutcome::Sat(_) => panic!("the system is UNSAT"),
};
let drat = emit_xor_drat(&eqs, &refutation).expect("a linear dependency must compile to DRAT");
assert!(drat.last().is_some_and(|c| c.is_empty()), "the proof must end in the empty clause");
assert!(
crate::rup::check_refutation(num_vars, &clauses, &drat),
"every DRAT line must be RUP and the proof must refute the CNF"
);
}
}