use std::collections::HashSet;
use crate::cnf::ShowMask;
use crate::cnf::VarId;
use crate::cnf::occ;
use crate::cnf::{Clause, CnfFormula, Literal};
pub(crate) fn bve_project(formula: &CnfFormula, show: &ShowMask) -> CnfFormula {
bve_project_bounded(formula, show, 1.0)
}
pub(crate) fn bve_project_bounded(
formula: &CnfFormula,
show: &ShowMask,
grow_ratio: f64,
) -> CnfFormula {
let num_vars = formula.num_vars;
let eliminable = |v: u32| !show.is_show(VarId(v));
let mut clauses: Vec<Vec<Literal>> = formula
.clauses
.iter()
.map(|c| {
let mut lits = c.literals.clone();
lits.sort_by_key(|l| (l.var.0, !l.positive));
lits.dedup();
lits
})
.collect();
let mut live: Vec<bool> = vec![true; clauses.len()];
let (mut occ_pos, mut occ_neg) =
occ::occurrence_lists_of(clauses.iter().map(|c| c.as_slice()), num_vars as usize);
fn purge_dead(occ: &mut Vec<usize>, live: &[bool]) {
occ.retain(|&i| live[i]);
}
fn resolve_on(cp: &[Literal], cn: &[Literal], v: VarId) -> Option<Vec<Literal>> {
let mut out: Vec<Literal> = Vec::with_capacity(cp.len() + cn.len());
for &l in cp.iter().chain(cn.iter()) {
if l.var == v {
continue;
}
out.push(l);
}
out.sort_by_key(|l| (l.var.0, !l.positive));
out.dedup();
for w in out.windows(2) {
if w[0].var == w[1].var {
return None;
}
}
Some(out)
}
let mut queued = vec![false; num_vars as usize];
let mut queue: std::collections::VecDeque<u32> = std::collections::VecDeque::new();
for v in 0..num_vars {
if eliminable(v) && (!occ_pos[v as usize].is_empty() || !occ_neg[v as usize].is_empty()) {
queue.push_back(v);
queued[v as usize] = true;
}
}
while let Some(v) = queue.pop_front() {
let vi = v as usize;
queued[vi] = false;
if !eliminable(v) {
continue;
}
let vid = VarId(v);
purge_dead(&mut occ_pos[vi], &live);
purge_dead(&mut occ_neg[vi], &live);
if occ_pos[vi].is_empty() && occ_neg[vi].is_empty() {
continue;
}
if occ_pos[vi].is_empty() || occ_neg[vi].is_empty() {
let to_kill: Vec<usize> = occ_pos[vi]
.iter()
.chain(occ_neg[vi].iter())
.copied()
.collect();
occ_pos[vi].clear();
occ_neg[vi].clear();
for i in to_kill {
if !live[i] {
continue;
}
live[i] = false;
for l in &clauses[i] {
let w = l.var.0;
if w != v && eliminable(w) && !queued[w as usize] {
queue.push_back(w);
queued[w as usize] = true;
}
}
}
continue;
}
let pos: Vec<usize> = occ_pos[vi].clone();
let neg: Vec<usize> = occ_neg[vi].clone();
let k = pos.len() + neg.len();
let budget = (k as f64) * grow_ratio;
let mut seen: HashSet<Vec<Literal>> = HashSet::new();
let mut resolvents: Vec<Vec<Literal>> = Vec::new();
let mut over_budget = false;
'enumerate: for &ip in &pos {
for &in_ in &neg {
if let Some(r) = resolve_on(&clauses[ip], &clauses[in_], vid) {
if seen.insert(r.clone()) {
resolvents.push(r);
if (resolvents.len() as f64) > budget {
over_budget = true;
break 'enumerate;
}
}
}
}
}
if over_budget {
continue; }
occ_pos[vi].clear();
occ_neg[vi].clear();
for i in pos.iter().chain(neg.iter()).copied() {
if !live[i] {
continue;
}
live[i] = false;
for l in &clauses[i] {
let w = l.var.0;
if w != v && eliminable(w) && !queued[w as usize] {
queue.push_back(w);
queued[w as usize] = true;
}
}
}
for lits in resolvents {
let idx = clauses.len();
for l in &lits {
if l.positive {
occ_pos[l.var.idx()].push(idx);
} else {
occ_neg[l.var.idx()].push(idx);
}
let w = l.var.0;
if eliminable(w) && !queued[w as usize] {
queue.push_back(w);
queued[w as usize] = true;
}
}
clauses.push(lits);
live.push(true);
}
}
let out_clauses: Vec<Clause> = clauses
.into_iter()
.zip(live)
.filter_map(|(lits, alive)| if alive { Some(Clause::new(lits)) } else { None })
.collect();
CnfFormula {
num_vars,
clauses: out_clauses,
}
}