use crate::cnf::CnfFormula;
use crate::cnf::{Reduced, ShowMask, ShowSet};
use crate::preprocess::bve_project::*;
use crate::tests::common::clause;
use crate::tests::pmc_oracle::brute_force_pmc;
use std::collections::HashSet;
fn hiding(num_vars: u32, projected: &[u32]) -> ShowMask {
ShowSet::<Reduced>::from_zero_based((0..num_vars).filter(|v| !projected.contains(v)))
.mask(num_vars)
}
fn occurs(f: &CnfFormula, var: u32) -> bool {
f.clauses
.iter()
.any(|c| c.literals.iter().any(|l| l.var.0 == var))
}
fn has_clause(f: &CnfFormula, lits: &[(u32, bool)]) -> bool {
let want: HashSet<(u32, bool)> = lits.iter().map(|&(v, p)| (v, p)).collect();
f.clauses.iter().any(|c| {
let got: HashSet<(u32, bool)> = c.literals.iter().map(|l| (l.var.0, l.positive)).collect();
got == want
})
}
#[test]
fn bve_project_pure_literal() {
let f = CnfFormula {
num_vars: 3,
clauses: vec![
clause(&[(0, true), (1, true)]),
clause(&[(2, true), (1, true)]),
],
};
let out = bve_project(&f, &hiding(f.num_vars, &[1]));
assert!(!occurs(&out, 1), "pure projected var must be gone");
assert!(out.clauses.is_empty(), "all x-clauses should be deleted");
}
#[test]
fn bve_project_basic_resolution() {
let f = CnfFormula {
num_vars: 3,
clauses: vec![
clause(&[(0, true), (2, true)]),
clause(&[(1, true), (2, false)]),
],
};
let out = bve_project(&f, &hiding(f.num_vars, &[2]));
assert!(!occurs(&out, 2), "x must be eliminated");
assert!(
has_clause(&out, &[(0, true), (1, true)]),
"resolvent (a ∨ b) present"
);
assert_eq!(out.clauses.len(), 1);
}
#[test]
fn bve_project_taut_dropped() {
let f = CnfFormula {
num_vars: 2,
clauses: vec![
clause(&[(0, true), (1, true)]),
clause(&[(0, true), (1, false)]),
],
};
let out = bve_project(&f, &hiding(f.num_vars, &[1]));
assert!(!occurs(&out, 1), "x must be eliminated");
assert!(has_clause(&out, &[(0, true)]), "resolvent collapses to (a)");
assert_eq!(out.clauses.len(), 1);
}
#[test]
fn bve_project_growth_ratio_gates_elimination() {
let f = CnfFormula {
num_vars: 6,
clauses: vec![
clause(&[(1, true), (0, true)]),
clause(&[(2, true), (0, true)]),
clause(&[(3, true), (0, true)]),
clause(&[(4, false), (0, false)]),
clause(&[(5, false), (0, false)]),
],
};
assert!(
occurs(&bve_project_bounded(&f, &hiding(f.num_vars, &[0]), 1.0), 0),
"grow=1.0 must skip x (R=6 > K=5)"
);
assert!(
!occurs(&bve_project_bounded(&f, &hiding(f.num_vars, &[0]), 2.0), 0),
"grow=2.0 must eliminate x (R=6 ≤ 10)"
);
}
fn check_pmc(f: &CnfFormula, show: &[u32]) {
let n = f.num_vars;
let expected = brute_force_pmc(f, show);
let reduced = bve_project(
f,
&ShowSet::<Reduced>::from_zero_based(show.iter().copied()).mask(n),
);
let got = brute_force_pmc(&reduced, show);
assert_eq!(
expected, got,
"PMC mismatch: original={expected} reduced={got}; show={show:?}"
);
for &s in show {
let in_orig = occurs(f, s);
if in_orig {
assert!(occurs(&reduced, s), "show var {s} must not be eliminated");
}
}
}
#[test]
fn bve_project_preserves_pmc() {
check_pmc(
&CnfFormula {
num_vars: 3,
clauses: vec![
clause(&[(0, true), (2, true)]),
clause(&[(1, true), (2, false)]),
],
},
&[0, 1],
);
check_pmc(
&CnfFormula {
num_vars: 4,
clauses: vec![
clause(&[(0, true), (2, false)]),
clause(&[(2, true), (3, true)]),
clause(&[(1, false), (3, false)]),
clause(&[(0, false), (1, true)]),
],
},
&[0, 1],
);
check_pmc(
&CnfFormula {
num_vars: 5,
clauses: vec![
clause(&[(0, true), (1, true), (2, false)]),
clause(&[(1, false), (3, true)]),
clause(&[(2, true), (3, false), (4, true)]),
clause(&[(0, false), (4, false)]),
clause(&[(2, true), (4, true)]),
],
},
&[0, 4],
);
check_pmc(
&CnfFormula {
num_vars: 2,
clauses: vec![clause(&[(1, true)]), clause(&[(1, false)])],
},
&[0],
);
check_pmc(
&CnfFormula {
num_vars: 2,
clauses: vec![
clause(&[(0, true), (1, true)]),
clause(&[(1, false)]),
clause(&[(0, false)]),
],
},
&[0],
);
}