use std::collections::{HashMap, HashSet};
use crate::cnf::VarId;
use crate::cnf::{Clause, CnfFormula, Literal};
use super::renumber::Renumber;
#[inline]
fn lit_to_node(lit: Literal) -> usize {
crate::cnf::occ::literal_index(lit.var.idx(), lit.positive)
}
#[inline]
fn node_to_lit(node: usize) -> Literal {
let var = VarId((node / 2) as u32);
let positive = node.is_multiple_of(2);
Literal::new(var, positive)
}
#[inline]
fn rep_of(representative: &[usize], v: usize) -> Literal {
node_to_lit(representative[v * 2])
}
fn var_to_rep_of(representative: &[usize], num_vars: u32) -> Vec<Literal> {
(0..num_vars as usize)
.map(|v| rep_of(representative, v))
.collect()
}
#[inline]
fn neg_node(node: usize) -> usize {
node ^ 1
}
fn tarjan_scc(num_nodes: usize, adj: &[Vec<usize>]) -> Vec<usize> {
let groups = super::tarjan::tarjan_scc_groups(num_nodes, adj);
let mut representative = vec![0usize; num_nodes];
for group in &groups {
let rep = *group.iter().min().unwrap_or(&0);
for &node in group {
representative[node] = rep;
}
}
representative
}
pub(super) struct EquivalenceResult {
pub formula: CnfFormula,
pub num_equivalences: usize,
pub is_unsat: bool,
}
pub(crate) struct EquivMapping {
pub var_to_rep: Vec<Literal>,
pub rep_to_equivs: HashMap<VarId, Vec<Literal>>,
pub representatives: Vec<VarId>,
}
impl EquivMapping {
fn from_var_to_rep(var_to_rep: Vec<Literal>) -> Self {
let mut rep_to_equivs: HashMap<VarId, Vec<Literal>> = HashMap::new();
let mut rep_set = HashSet::new();
for (v, &rep) in var_to_rep.iter().enumerate() {
rep_set.insert(rep.var);
if rep.var.0 != v as u32 {
rep_to_equivs
.entry(rep.var)
.or_default()
.push(Literal::new(VarId(v as u32), rep.positive));
}
}
let mut representatives: Vec<VarId> = rep_set.into_iter().collect();
representatives.sort_by_key(|v| v.0);
EquivMapping {
var_to_rep,
rep_to_equivs,
representatives,
}
}
pub(crate) fn remap_for_stripped(
&self,
bb: &super::simplify::VariableStripping,
) -> Option<Self> {
let stripped_num_vars = bb.renumbering.num_new_vars();
let mut new_var_to_rep = Vec::with_capacity(stripped_num_vars as usize);
for &orig_var in bb.renumbering.kept() {
let rep = self.var_to_rep[orig_var.idx()];
if let Some(stripped_rep) = bb.renumbering.new_id(rep.var) {
new_var_to_rep.push(Literal::new(stripped_rep, rep.positive));
} else {
debug_assert!(
false,
"non-backbone var {:?} has backbone representative {:?}",
orig_var, rep.var
);
let stripped_self = bb.renumbering.new_id(orig_var).unwrap();
new_var_to_rep.push(Literal::pos(stripped_self));
}
}
let remapped = EquivMapping::from_var_to_rep(new_var_to_rep);
if remapped.rep_to_equivs.is_empty() {
return None;
}
Some(remapped)
}
pub(crate) fn reduce_formula(&self, formula: &CnfFormula) -> (CnfFormula, Renumber) {
let renumbering = Renumber::of_kept(
formula.num_vars as usize,
self.representatives.iter().copied(),
);
let num_reduced = self.representatives.len() as u32;
let mut new_clauses: Vec<Vec<Literal>> = Vec::with_capacity(formula.clauses.len());
let mut clause_set: HashSet<Vec<Literal>> = HashSet::new();
for clause in &formula.clauses {
let substituted = substitute_clause(clause, &self.var_to_rep, Some(&renumbering));
if let Some(lits) = substituted
&& clause_set.insert(lits.clone())
{
new_clauses.push(lits);
}
}
let clauses = new_clauses.into_iter().map(Clause::new).collect();
let reduced = CnfFormula {
num_vars: num_reduced,
clauses,
};
debug_assert_eq!(
renumbering.num_new_vars(),
reduced.num_vars,
"the renumbering must keep exactly the reduced formula's variables",
);
(reduced, renumbering)
}
}
fn substitute_clause(
clause: &Clause,
var_to_rep: &[Literal],
renumber: Option<&Renumber>,
) -> Option<Vec<Literal>> {
let mut new_lits: Vec<Literal> = Vec::with_capacity(clause.literals.len());
let mut lit_set: HashSet<usize> = HashSet::new();
for &lit in &clause.literals {
let rep = var_to_rep[lit.var.idx()];
let sub = if lit.positive { rep } else { rep.negated() };
let final_var = renumber.map_or(sub.var, |r| {
r.new_id(sub.var)
.expect("an equivalence representative must survive into the reduced formula")
});
let rep_node = final_var.0 as usize * 2 + if sub.positive { 0 } else { 1 };
if lit_set.contains(&(rep_node ^ 1)) {
return None;
}
if lit_set.insert(rep_node) {
new_lits.push(Literal::new(final_var, sub.positive));
}
}
new_lits.sort_by_key(|l| (l.var.0, !l.positive));
Some(new_lits)
}
fn build_implication_graph(formula: &CnfFormula) -> Vec<Vec<usize>> {
let num_nodes = formula.num_vars as usize * 2;
let mut adj = vec![Vec::new(); num_nodes];
for clause in &formula.clauses {
if clause.literals.len() == 2 {
let a = lit_to_node(clause.literals[0]);
let b = lit_to_node(clause.literals[1]);
adj[neg_node(a)].push(b);
adj[neg_node(b)].push(a);
}
}
adj
}
fn has_equiv_contradiction(representative: &[usize], num_vars: usize) -> bool {
(0..num_vars).any(|v| representative[v * 2] == representative[v * 2 + 1])
}
enum EquivSccResult {
Unsat,
NoEquivs,
Found {
representative: Vec<usize>,
equiv_count: usize,
},
}
fn find_equivalences(formula: &CnfFormula) -> EquivSccResult {
let num_nodes = formula.num_vars as usize * 2;
if num_nodes == 0 {
return EquivSccResult::NoEquivs;
}
let adj = build_implication_graph(formula);
let representative = tarjan_scc(num_nodes, &adj);
if has_equiv_contradiction(&representative, formula.num_vars as usize) {
return EquivSccResult::Unsat;
}
let mut equiv_count = 0;
let mut seen_reps = vec![false; num_nodes];
for (node, &rep) in representative.iter().enumerate() {
if rep != node && !seen_reps[rep] {
seen_reps[rep] = true;
equiv_count += 1;
}
}
if equiv_count == 0 {
EquivSccResult::NoEquivs
} else {
EquivSccResult::Found {
representative,
equiv_count,
}
}
}
fn build_substituted_formula(
formula: &CnfFormula,
representative: &[usize],
equiv_count: usize,
) -> EquivalenceResult {
let n = formula.num_vars as usize;
let var_to_rep = var_to_rep_of(representative, formula.num_vars);
let mut new_clauses: Vec<Vec<Literal>> = Vec::with_capacity(formula.clauses.len());
let mut clause_set: HashSet<Vec<Literal>> = HashSet::new();
for clause in &formula.clauses {
if let Some(lits) = substitute_clause(clause, &var_to_rep, None)
&& clause_set.insert(lits.clone())
{
new_clauses.push(lits);
}
}
for v in 0..n {
let pos_node = v * 2;
let rep_node = representative[pos_node];
if rep_node == pos_node {
continue;
}
let v_pos = Literal::new(VarId(v as u32), true);
let v_neg = Literal::new(VarId(v as u32), false);
let rep_lit = node_to_lit(rep_node);
let rep_neg = Literal::new(rep_lit.var, !rep_lit.positive);
new_clauses.push(vec![v_neg, rep_lit]);
new_clauses.push(vec![v_pos, rep_neg]);
}
let clauses = new_clauses.into_iter().map(Clause::new).collect();
EquivalenceResult {
formula: CnfFormula {
num_vars: formula.num_vars,
clauses,
},
num_equivalences: equiv_count,
is_unsat: false,
}
}
pub(super) fn extract_equivalences_with_mapping(
formula: &CnfFormula,
) -> (EquivalenceResult, Option<EquivMapping>) {
match find_equivalences(formula) {
EquivSccResult::Unsat => (
EquivalenceResult {
formula: CnfFormula {
num_vars: formula.num_vars,
clauses: vec![Clause::new(vec![])],
},
num_equivalences: 0,
is_unsat: true,
},
None,
),
EquivSccResult::NoEquivs => (
EquivalenceResult {
formula: formula.clone(),
num_equivalences: 0,
is_unsat: false,
},
None,
),
EquivSccResult::Found {
representative,
equiv_count,
} => {
let mapping =
EquivMapping::from_var_to_rep(var_to_rep_of(&representative, formula.num_vars));
let result = build_substituted_formula(formula, &representative, equiv_count);
(result, Some(mapping))
}
}
}