use crate::cnf::Clause;
use crate::cnf::CnfFormula;
use crate::cnf::Literal;
use crate::cnf::VarId;
use crate::preprocess::equivalence::*;
use crate::tests::common::clause;
use crate::tests::pmc_oracle::brute_force_mc;
fn two_equivalence_classes() -> CnfFormula {
CnfFormula {
num_vars: 6,
clauses: vec![
clause(&[(0, true), (1, false)]),
clause(&[(0, false), (1, true)]),
clause(&[(2, true), (3, false)]),
clause(&[(2, false), (3, true)]),
clause(&[(0, true), (2, true), (4, true)]),
clause(&[(0, false), (2, false), (5, true)]),
],
}
}
#[test]
fn equivalence_substitution_preserves_the_model_count() {
let formula = two_equivalence_classes();
let result = extract_equivalences_with_mapping(&formula).0;
assert!(
result.num_equivalences >= 1,
"the fixture must give the substitution a class to work on",
);
assert_eq!(
result.formula.num_vars, formula.num_vars,
"the substitution keeps every variable of its input",
);
assert_eq!(
brute_force_mc(&result.formula),
brute_force_mc(&formula),
"substituting onto representatives changed the model count",
);
}
#[test]
fn a_dropped_equivalence_partner_leaves_the_model_count_unchanged() {
let formula = two_equivalence_classes();
let mapping = extract_equivalences_with_mapping(&formula)
.1
.expect("the fixture has equivalences");
let (reduced, renumbering) = mapping.reduce_formula(&formula);
assert_eq!(
reduced.num_vars,
formula.num_vars - 2,
"one partner per class must be dropped",
);
assert_eq!(renumbering.num_new_vars(), reduced.num_vars);
assert_eq!(
brute_force_mc(&reduced),
brute_force_mc(&formula),
"dropping a determined partner must not change the model count",
);
}
#[test]
fn a_formula_with_no_binary_clauses_yields_no_equivalences() {
let formula = CnfFormula {
num_vars: 3,
clauses: vec![clause(&[(0, true), (1, false), (2, true)])],
};
let result = extract_equivalences_with_mapping(&formula).0;
assert_eq!(result.num_equivalences, 0);
assert!(!result.is_unsat);
assert_eq!(result.formula.clauses.len(), 1);
}
#[test]
fn simple_equivalence() {
let formula = CnfFormula {
num_vars: 3,
clauses: vec![
clause(&[(0, false), (1, true)]),
clause(&[(0, true), (1, false)]),
clause(&[(0, true), (2, true)]), ],
};
let result = extract_equivalences_with_mapping(&formula).0;
assert!(result.num_equivalences >= 1);
assert!(!result.is_unsat);
assert!(result.formula.clauses.len() <= 3);
}
#[test]
fn unsat_detection() {
let formula = CnfFormula {
num_vars: 2,
clauses: vec![
clause(&[(0, false), (1, true)]),
clause(&[(0, true), (1, false)]),
clause(&[(0, false), (1, false)]),
clause(&[(0, true), (1, true)]),
],
};
let result = extract_equivalences_with_mapping(&formula).0;
assert!(result.is_unsat);
assert!(result.formula.clauses.iter().any(|c| c.literals.is_empty()));
}
#[test]
fn tautology_removal() {
let formula = CnfFormula {
num_vars: 3,
clauses: vec![
clause(&[(0, false), (1, true)]),
clause(&[(0, true), (1, false)]),
clause(&[(0, true), (1, false), (2, true)]), ],
};
let result = extract_equivalences_with_mapping(&formula).0;
assert!(!result.is_unsat);
}
#[test]
fn preserves_num_vars() {
let formula = CnfFormula {
num_vars: 10,
clauses: vec![
clause(&[(0, false), (1, true)]),
clause(&[(0, true), (1, false)]),
],
};
let result = extract_equivalences_with_mapping(&formula).0;
assert_eq!(result.formula.num_vars, 10);
}
#[test]
fn empty_formula() {
let formula = CnfFormula {
num_vars: 0,
clauses: vec![],
};
let result = extract_equivalences_with_mapping(&formula).0;
assert_eq!(result.num_equivalences, 0);
assert!(!result.is_unsat);
}
#[test]
fn the_mapping_covers_every_variable_and_records_the_inverse_of_each_merge() {
let formula = CnfFormula {
num_vars: 3,
clauses: vec![
clause(&[(0, false), (1, true)]),
clause(&[(0, true), (1, false)]),
clause(&[(0, true), (2, true)]),
],
};
let (result, mapping) = extract_equivalences_with_mapping(&formula);
assert!(!result.is_unsat);
assert!(result.num_equivalences >= 1);
let mapping = mapping.unwrap();
assert_eq!(mapping.var_to_rep[0], Literal::pos(VarId(0))); assert_eq!(mapping.var_to_rep[1].var, VarId(0)); assert!(mapping.var_to_rep[1].positive); assert_eq!(mapping.var_to_rep[2], Literal::pos(VarId(2)));
assert_eq!(mapping.representatives.len(), 2);
assert!(mapping.representatives.contains(&VarId(0)));
assert!(mapping.representatives.contains(&VarId(2)));
let equivs = &mapping.rep_to_equivs[&VarId(0)];
assert_eq!(equivs.len(), 1);
assert_eq!(equivs[0], Literal::pos(VarId(1)));
}
#[test]
fn no_mapping_is_produced_when_nothing_is_equivalent() {
let formula = CnfFormula {
num_vars: 3,
clauses: vec![clause(&[(0, true), (1, false), (2, true)])],
};
let (_, mapping) = extract_equivalences_with_mapping(&formula);
assert!(mapping.is_none());
}
#[test]
fn reduce_formula_simple() {
let formula = CnfFormula {
num_vars: 3,
clauses: vec![
clause(&[(0, false), (1, true)]),
clause(&[(0, true), (1, false)]),
clause(&[(0, true), (2, true)]),
],
};
let (_, mapping) = extract_equivalences_with_mapping(&formula);
let mapping = mapping.unwrap();
let (reduced, renumbering) = mapping.reduce_formula(&formula);
assert_eq!(reduced.num_vars, 2);
assert_eq!(renumbering.num_new_vars(), 2);
assert_eq!(reduced.clauses.len(), 1);
assert_eq!(reduced.clauses[0].literals.len(), 2);
}
#[test]
fn reduce_formula_preserves_polarity_flip() {
let formula = CnfFormula {
num_vars: 3,
clauses: vec![
clause(&[(0, true), (1, true)]),
clause(&[(0, false), (1, false)]),
clause(&[(1, true), (2, true)]),
],
};
let (_, mapping) = extract_equivalences_with_mapping(&formula);
let mapping = mapping.unwrap();
let rep0 = mapping.var_to_rep[0];
let rep1 = mapping.var_to_rep[1];
assert_eq!(rep0.var, rep1.var);
assert_ne!(rep0.positive, rep1.positive);
let (reduced, _) = mapping.reduce_formula(&formula);
assert_eq!(reduced.num_vars, 2);
}
#[test]
fn reduce_formula_preserves_empty_clause() {
let formula = CnfFormula {
num_vars: 3,
clauses: vec![
clause(&[(0, false), (1, true)]), clause(&[(0, true), (1, false)]),
Clause::new(vec![]), ],
};
let (_, mapping) = extract_equivalences_with_mapping(&formula);
let mapping = mapping.unwrap();
let (reduced, _) = mapping.reduce_formula(&formula);
assert!(
reduced.clauses.iter().any(|c| c.literals.is_empty()),
"empty clause (UNSAT) was dropped by reduce_formula"
);
}