use num_rational::BigRational;
use num_traits::One;
use crate::cnf::{Literal, Original, VarId, Weights};
use crate::preprocess::dve::types::DveFate;
use crate::preprocess::simplify::SimplifiedFormula;
pub(crate) fn folded_weights(
simplified: &SimplifiedFormula,
orig_w: &Weights<Original>,
) -> Weights<Original> {
let mut w = orig_w.clone();
if let Some(eq) = simplified.equiv_reduced.as_ref() {
for (rep_s, equivs) in &eq.mapping.rep_to_equivs {
let rep_o = simplified.stripped_var_to_original(*rep_s);
for &eq_s in equivs {
let pair = orig_w[var_of(simplified.stripped_var_to_original(eq_s.var))].clone();
w.fold_into(pair, Literal::new(VarId(rep_o as u32), eq_s.positive));
}
}
}
fold_dve_equivs(&mut w, simplified);
w
}
fn var_of(index: usize) -> VarId {
VarId(index as u32)
}
pub(crate) fn dve_equiv_survivor(fates: &[DveFate], v: usize) -> Option<Literal> {
let mut cur = v;
let mut same = true;
loop {
let rep = fates.get(cur).copied()?.as_equiv()?;
same = same == rep.positive;
cur = rep.var.idx();
if fates.get(cur).copied()?.as_equiv().is_none() {
break;
}
}
if fates.get(cur).copied().is_none_or(DveFate::eliminated) {
None
} else {
Some(Literal::new(VarId(cur as u32), same))
}
}
fn fold_dve_equivs(w: &mut Weights<Original>, simplified: &SimplifiedFormula) {
let Some(dve) = simplified.dve_reduced.as_ref() else {
return;
};
for v in 0..dve.fates.len() {
if dve.fates[v].as_equiv().is_none() {
continue;
}
let Some(surv) = dve_equiv_survivor(&dve.fates, v) else {
continue;
};
let v_o = simplified.pre_dve_var_to_original(v);
let surv_o = simplified.pre_dve_var_to_original(surv.var.idx());
let pair = w[var_of(v_o)].clone();
w.fold_into(pair, Literal::new(VarId(surv_o as u32), surv.positive));
}
}
pub(crate) fn stripped_correction(
simplified: &SimplifiedFormula,
orig_w: &Weights<Original>,
) -> BigRational {
let mut correction = BigRational::one();
let Some(stripped) = simplified.stripped.as_ref() else {
return correction;
};
for &(var, pos) in &stripped.removed.backbone {
let (wn, wp) = &orig_w[var];
correction *= if pos { wp.clone() } else { wn.clone() };
}
for &var in &stripped.removed.dead {
let (wn, wp) = &orig_w[var];
correction *= wn.clone() + wp.clone();
}
correction
}
pub(crate) fn dve_correction(
simplified: &SimplifiedFormula,
folded_w: &Weights<Original>,
) -> BigRational {
classify_dve(simplified, folded_w).0
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DveEligibility {
Supported {
defined: usize,
free: usize,
},
Unsupported,
}
pub(crate) fn dve_eligibility(
simplified: &SimplifiedFormula,
folded_w: &Weights<Original>,
) -> DveEligibility {
classify_dve(simplified, folded_w).1
}
fn classify_dve(
simplified: &SimplifiedFormula,
folded_w: &Weights<Original>,
) -> (BigRational, DveEligibility) {
let mut correction = BigRational::one();
let Some(dve) = simplified.dve_reduced.as_ref() else {
return (
correction,
DveEligibility::Supported {
defined: 0,
free: 0,
},
);
};
let (mut defined, mut free) = (0usize, 0usize);
let mut supported = true;
for j in 0..dve.fates.len() {
match dve.fates[j] {
DveFate::Kept => continue,
DveFate::Equiv { .. } => {
if dve_equiv_survivor(&dve.fates, j).is_none() {
supported = false;
}
}
DveFate::Free => {
let (wn, wp) = &folded_w[var_of(simplified.pre_dve_var_to_original(j))];
correction *= wn.clone() + wp.clone();
free += 1;
}
DveFate::Defined => {
let (wn, wp) = &folded_w[var_of(simplified.pre_dve_var_to_original(j))];
if wn != wp {
supported = false;
}
correction *= wp.clone();
defined += 1;
}
}
}
let eligibility = if supported {
DveEligibility::Supported { defined, free }
} else {
DveEligibility::Unsupported
};
(correction, eligibility)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DveVerdict {
Keep {
defined: usize,
free: usize,
residual: u32,
},
Revert(&'static str),
}
pub(crate) fn dve_verdict(
simplified: &SimplifiedFormula,
folded_w: &Weights<Original>,
freeze: bool,
) -> DveVerdict {
let residual = match dve_residual_vars(simplified) {
Some(n) => n,
None => {
return DveVerdict::Keep {
defined: 0,
free: 0,
residual: 0,
};
}
};
match dve_eligibility(simplified, folded_w) {
DveEligibility::Unsupported => DveVerdict::Revert("unsupported elimination"),
DveEligibility::Supported { defined, free } => {
if residual == 0 || freeze {
DveVerdict::Keep {
defined,
free,
residual,
}
} else {
DveVerdict::Revert("residual vars remain — only full elimination is supported")
}
}
}
}
fn dve_residual_vars(simplified: &SimplifiedFormula) -> Option<u32> {
simplified.dve_reduced.as_ref().map(|d| d.formula.num_vars)
}
pub(crate) fn weighted_lift(
simplified: &SimplifiedFormula,
orig_w: &Weights<Original>,
folded_w: &Weights<Original>,
) -> BigRational {
stripped_correction(simplified, orig_w) * dve_correction(simplified, folded_w)
}