use crate::cnf::Clause;
use crate::cnf::CnfFormula;
use crate::cnf::Literal;
use crate::cnf::VarId;
use crate::preprocess::renumber::Renumber;
use crate::preprocess::simplify::*;
use crate::tests::pmc_oracle::brute_force_mc;
use num_bigint::BigUint;
fn reconstruct_count(stripped: &CnfFormula, red: &VariableStripping) -> BigUint {
brute_force_mc(stripped) * BigUint::from(1u64 << red.dead.len())
}
#[test]
fn strip_recovers_via_cleanup_when_forced_var_survives() {
let formula = CnfFormula {
num_vars: 2,
clauses: vec![
Clause::new(vec![Literal::new(VarId(0), true)]),
Clause::new(vec![
Literal::new(VarId(0), true),
Literal::new(VarId(1), false),
]),
],
};
let stripped = strip_backbone_vars(&formula);
assert!(
stripped.is_some(),
"cleanup UP should let stripping proceed, not panic or bail",
);
let (f, red) = stripped.unwrap();
assert_eq!(
reconstruct_count(&f, &red),
brute_force_mc(&formula),
"stripped+cleanup count must equal the original model count",
);
}
#[test]
fn strip_cleanup_is_count_exact_on_chained_units() {
let formula = CnfFormula {
num_vars: 4,
clauses: vec![
Clause::new(vec![Literal::new(VarId(0), true)]),
Clause::new(vec![Literal::new(VarId(1), true)]),
Clause::new(vec![
Literal::new(VarId(0), true),
Literal::new(VarId(2), true),
]),
Clause::new(vec![
Literal::new(VarId(1), true),
Literal::new(VarId(2), false),
Literal::new(VarId(3), true),
]),
],
};
let (f, red) = strip_backbone_vars(&formula).expect("cleanup should strip");
assert_eq!(
reconstruct_count(&f, &red),
brute_force_mc(&formula),
"chained-units cleanup count must match brute force",
);
}
#[test]
fn strip_proceeds_when_forced_var_fully_eliminated() {
let formula = CnfFormula {
num_vars: 3,
clauses: vec![
Clause::new(vec![Literal::new(VarId(0), true)]),
Clause::new(vec![
Literal::new(VarId(1), true),
Literal::new(VarId(2), false),
]),
],
};
let stripped = strip_backbone_vars(&formula);
assert!(stripped.is_some(), "should strip when the invariant holds");
let (f, red) = stripped.unwrap();
assert_eq!(f.num_vars, 2, "v0 stripped, v1/v2 renumbered");
assert_eq!(red.backbone.len(), 1, "v0 recorded as backbone");
assert_eq!(
reconstruct_count(&f, &red),
brute_force_mc(&formula),
"fast-path strip count must match brute force",
);
}
#[test]
fn a_cleanup_that_derives_unsat_declines_to_strip() {
let formula = CnfFormula {
num_vars: 2,
clauses: vec![
Clause::new(vec![Literal::new(VarId(0), true)]),
Clause::new(vec![Literal::new(VarId(0), false)]),
Clause::new(vec![
Literal::new(VarId(0), true),
Literal::new(VarId(1), false),
]),
],
};
assert_eq!(
brute_force_mc(&formula),
BigUint::ZERO,
"the fixture must be the refuted case",
);
assert!(
strip_backbone_vars(&formula).is_none(),
"a formula the cleanup refuted must not come back with a stripping",
);
}
#[test]
fn a_forced_original_variable_reports_its_polarity_and_a_dead_one_reports_unconstrained() {
let original = CnfFormula {
num_vars: 5,
clauses: vec![
Clause::new(vec![Literal::new(VarId(0), true)]),
Clause::new(vec![Literal::new(VarId(1), false)]),
Clause::new(vec![
Literal::new(VarId(2), true),
Literal::new(VarId(3), false),
]),
],
};
let record = SimplifiedFormula {
original,
equiv_reduced: None,
dve_reduced: None,
preprocessed: None,
stripped: Some(Stripped {
formula: CnfFormula {
num_vars: 2,
clauses: vec![Clause::new(vec![
Literal::new(VarId(0), true),
Literal::new(VarId(1), false),
])],
},
removed: VariableStripping {
backbone: vec![(VarId(0), true), (VarId(1), false)],
dead: vec![VarId(4)],
renumbering: Renumber::of_kept(5, [VarId(2), VarId(3)]),
},
}),
telemetry: SimplifyTelemetry::default(),
decision_trace: None,
};
assert_eq!(
record.original_fates(),
vec![
OriginalFate::Forced(true),
OriginalFate::Forced(false),
OriginalFate::Variable {
index: 0,
same_polarity: true,
},
OriginalFate::Variable {
index: 1,
same_polarity: true,
},
OriginalFate::Unconstrained,
],
"every original variable must report the fate its stripping gave it",
);
}