use std::sync::Arc;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::cnf::{Clause, CnfFormula, Literal};
use crate::score::{BUILT_FROM_THIS_FORMULA, vtree_cost};
use crate::vtree::{VarId, Vtree, VtreeArena, VtreeIdx, VtreeNode};
use super::best::select_first_min;
use super::multilevel_bisect::multilevel_bisect;
use super::td_to_vtree::{ConversionRequest, convert_td};
use super::{
BisectDials, Bisection, BisectionSolver, EMPTY_FORMULA, TdConversion, TreeDecomposition,
local_index, run_bisection,
};
fn restrict_formula(
formula: &CnfFormula,
keep_vars: &FxHashSet<u32>,
global_to_local: &FxHashMap<u32, u32>,
) -> CnfFormula {
let num_local = global_to_local.len() as u32;
crate::decompose::meter::charge(
formula
.clauses
.iter()
.map(|c| c.literals.len() as u64 + 1)
.sum(),
);
let mut clauses = Vec::new();
for clause in &formula.clauses {
if clause.literals.iter().all(|l| keep_vars.contains(&l.var.0)) {
let lits: Vec<Literal> = clause
.literals
.iter()
.map(|l| Literal {
var: VarId(global_to_local[&l.var.0]),
positive: l.positive,
})
.collect();
clauses.push(Clause::new(lits));
}
}
CnfFormula {
num_vars: num_local,
clauses,
}
}
const GUIDED_DIRECT_MINFILL_VARS: usize = 64;
struct GuidedSolver<'a> {
td: &'a TreeDecomposition,
graph: &'a ::goatd::Graph,
dials: BisectDials,
conversion: ConversionRequest<'a>,
}
impl BisectionSolver for GuidedSolver<'_> {
fn partition(
&mut self,
vars: &[u32],
_formula: &CnfFormula,
) -> Result<Option<Bisection>, String> {
let local_graph = self
.graph
.induced_subgraph(vars)
.map_err(|error| error.to_string())?;
let parts = multilevel_bisect(&local_graph, self.dials.imbalance, self.dials.base_seed)?;
Ok(Bisection::from_side_bits(vars, &parts))
}
fn minfill_cutoff(&self) -> usize {
GUIDED_DIRECT_MINFILL_VARS
}
fn refine_subtree(
&mut self,
vars: &[u32],
formula: &CnfFormula,
nodes: &mut VtreeArena,
checkpoint: usize,
root: VtreeIdx,
) -> Option<VtreeIdx> {
let keep: FxHashSet<u32> = vars.iter().copied().collect();
let mut sorted_vars: Vec<u32> = vars.to_vec();
sorted_vars.sort_unstable();
let global_to_local = local_index(&sorted_vars);
let local_formula = restrict_formula(formula, &keep, &global_to_local);
crate::decompose::meter::charge(
self.td
.bags()
.iter()
.map(|bag| bag.vertices().len() as u64 + 1)
.sum(),
);
let proj = self.td.project(&sorted_vars).ok()?;
let td_vtree = convert_td(&local_formula, proj.decomposition(), self.conversion).vtree;
let td_score = vtree_cost(&td_vtree, &local_formula).expect(BUILT_FROM_THIS_FORMULA);
let bisected_nodes_local: Vec<VtreeNode> = nodes.nodes()[checkpoint..]
.iter()
.map(|node| match *node {
VtreeNode::Leaf { var, parent } => VtreeNode::Leaf {
var: VarId(global_to_local[&var.0]),
parent,
},
VtreeNode::Internal {
left,
right,
parent,
} => VtreeNode::Internal {
left: VtreeIdx(left.0 - checkpoint as u32),
right: VtreeIdx(right.0 - checkpoint as u32),
parent,
},
})
.collect();
let bisected_local_root = VtreeIdx((root.0 as usize - checkpoint) as u32);
let bisected_vtree = Vtree::from_nodes(
bisected_nodes_local,
bisected_local_root,
local_formula.num_vars,
);
let bisected_score =
vtree_cost(&bisected_vtree, &local_formula).expect(BUILT_FROM_THIS_FORMULA);
let keep_projection = select_first_min(
[(true, td_score), (false, bisected_score)],
|&(_, score)| score,
)
.is_some_and(|(is_projection, _)| is_projection);
keep_projection.then(|| {
nodes.truncate(checkpoint);
nodes.graft(&td_vtree, |local| {
VarId(proj.local_to_original()[local.0 as usize])
})
})
}
}
pub(super) const GUIDED_IMBALANCE: f64 = 0.40;
pub(super) fn vtree_from_guided_bisect(
formula: &CnfFormula,
td: &TreeDecomposition,
dials: BisectDials,
conversion: ConversionRequest<'_>,
) -> Result<Arc<Vtree>, String> {
if formula.num_vars == 0 {
return Err(EMPTY_FORMULA.to_string());
}
let pace = super::GraphKind::Primal.build(formula);
let mut solver = GuidedSolver {
td,
graph: pace.as_goatd(),
dials,
conversion: conversion.nested(),
};
run_bisection(formula, &mut solver)
}
pub(crate) fn guided_bisect_from_incidence_td(
formula: &CnfFormula,
td: &TreeDecomposition,
conversion: ConversionRequest<'_>,
) -> Result<TdConversion, String> {
let dials = BisectDials {
imbalance: GUIDED_IMBALANCE,
base_seed: 0,
effort_scale: conversion.effort_scale,
};
vtree_from_guided_bisect(formula, td, dials, conversion).map(TdConversion::bare)
}
#[cfg(test)]
mod tests;