use rustc_hash::FxHashMap;
use crate::cnf::CnfFormula;
use crate::error::VitriError;
pub use goatd::{TdBag, TreeDecomposition};
pub(super) const COOC_CLAUSE_LEN_CAP: usize = 50;
fn for_each_cooccurring_pair(formula: &CnfFormula, num_vars: u32, mut f: impl FnMut(u32, u32)) {
let nv = num_vars as usize;
for clause in &formula.clauses {
if clause.literals.len() > COOC_CLAUSE_LEN_CAP {
continue;
}
let vars: Vec<u32> = clause
.literals
.iter()
.map(|l| l.var.0)
.filter(|&v| (v as usize) < nv)
.collect();
for i in 0..vars.len() {
for j in (i + 1)..vars.len() {
f(vars[i], vars[j]);
}
}
}
}
pub(crate) fn primal_adjacency(formula: &CnfFormula, num_vars: u32) -> Vec<Vec<u32>> {
let mut primal_adj: Vec<Vec<u32>> = vec![Vec::new(); num_vars as usize];
for_each_cooccurring_pair(formula, num_vars, |u, v| {
primal_adj[u as usize].push(v);
primal_adj[v as usize].push(u);
});
for nbrs in &mut primal_adj {
nbrs.sort_unstable();
nbrs.dedup();
}
primal_adj
}
fn push_clique(vars: &[u32], out: &mut Vec<(u32, u32)>) {
for i in 0..vars.len() {
for j in (i + 1)..vars.len() {
out.push((vars[i].min(vars[j]), vars[i].max(vars[j])));
}
}
}
pub(crate) fn build_primal_edges(formula: &CnfFormula) -> Vec<(u32, u32)> {
let mut edges = Vec::new();
for clause in &formula.clauses {
let vars: Vec<u32> = clause.literals.iter().map(|l| l.var.0).collect();
push_clique(&vars, &mut edges);
}
goatd::Graph::new(formula.num_vars, edges).edges().to_vec()
}
pub(crate) fn primal_edges_on_subset(formula: &CnfFormula, subset: &[u32]) -> Vec<(u32, u32)> {
let local = local_index(subset);
let mut edges: Vec<(u32, u32)> = Vec::new();
for clause in &formula.clauses {
let local_vars: Vec<u32> = clause
.literals
.iter()
.filter_map(|l| local.get(&l.var.0).copied())
.collect();
push_clique(&local_vars, &mut edges);
}
goatd::Graph::new(subset.len() as u32, edges)
.edges()
.to_vec()
}
pub(crate) fn local_index(subset: &[u32]) -> FxHashMap<u32, u32> {
subset
.iter()
.enumerate()
.map(|(i, &v)| (v, i as u32))
.collect()
}
pub(crate) fn build_incidence_edges(formula: &CnfFormula) -> Vec<(u32, u32)> {
let mut edges = Vec::new();
for (ci, clause) in formula.clauses.iter().enumerate() {
let clause_vertex = formula.num_vars + ci as u32;
for lit in &clause.literals {
let var_vertex = lit.var.0;
let (u, v) = (var_vertex.min(clause_vertex), var_vertex.max(clause_vertex));
edges.push((u, v));
}
}
goatd::Graph::new(formula.num_vars + formula.clauses.len() as u32, edges)
.edges()
.to_vec()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GraphKind {
Primal,
Incidence,
}
impl GraphKind {
pub fn build(self, formula: &CnfFormula) -> PaceGraph {
let (num_vertices, edges) = match self {
GraphKind::Primal => (formula.num_vars, build_primal_edges(formula)),
GraphKind::Incidence => (
formula.num_vars + formula.clauses.len() as u32,
build_incidence_edges(formula),
),
};
PaceGraph {
kind: self,
graph: goatd::Graph::new(num_vertices, edges),
}
}
fn name(self) -> &'static str {
match self {
GraphKind::Primal => "primal",
GraphKind::Incidence => "incidence",
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PaceGraph {
kind: GraphKind,
graph: goatd::Graph,
}
impl PaceGraph {
pub fn kind(&self) -> GraphKind {
self.kind
}
pub fn num_vertices(&self) -> u32 {
self.graph.num_vertices()
}
pub fn edges(&self) -> &[(u32, u32)] {
self.graph.edges()
}
pub(crate) fn as_goatd(&self) -> &goatd::Graph {
&self.graph
}
pub fn to_gr(&self) -> String {
format!("c vitri {} graph\n{}", self.kind.name(), self.graph.to_gr())
}
pub fn parse_td(&self, td_output: &str) -> Result<TreeDecomposition, VitriError> {
let decomposition = TreeDecomposition::from_td(td_output)
.map_err(|error| VitriError::input(error.to_string()))?;
decomposition
.validate(&self.graph)
.map_err(|error| VitriError::input(error.to_string()))?;
Ok(decomposition)
}
}
pub fn parse_pace_td(td_output: &str, graph: &PaceGraph) -> Result<TreeDecomposition, VitriError> {
graph.parse_td(td_output)
}