extern crate alloc;
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DnnfGate {
Literal(u32, bool),
True,
False,
And(Vec<u32>),
Or(Vec<u32>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DnnfDag {
pub gates: Vec<DnnfGate>,
pub num_vars: u32,
}
impl DnnfDag {
#[must_use]
pub fn root(&self) -> u32 {
(self.gates.len() - 1) as u32
}
}
#[must_use]
pub fn compile_dnnf(clauses: &[Vec<(u32, bool)>], num_vars: u32, max_depth: u32) -> DnnfDag {
let mut dag = DnnfDag {
gates: Vec::new(),
num_vars,
};
compile_recursive(&mut dag, clauses, num_vars, 0, max_depth);
dag
}
fn smoothed_true(dag: &mut DnnfDag, num_vars: u32, var: u32) -> u32 {
if var >= num_vars {
let id = dag.gates.len() as u32;
dag.gates.push(DnnfGate::True);
return id;
}
let mut taut_ids = Vec::with_capacity((num_vars - var) as usize);
for v in var..num_vars {
let pos = dag.gates.len() as u32;
dag.gates.push(DnnfGate::Literal(v, true));
let neg = dag.gates.len() as u32;
dag.gates.push(DnnfGate::Literal(v, false));
let or_id = dag.gates.len() as u32;
dag.gates.push(DnnfGate::Or(alloc::vec![pos, neg]));
taut_ids.push(or_id);
}
if taut_ids.len() == 1 {
return taut_ids[0];
}
let id = dag.gates.len() as u32;
dag.gates.push(DnnfGate::And(taut_ids));
id
}
fn compile_recursive(
dag: &mut DnnfDag,
clauses: &[Vec<(u32, bool)>],
num_vars: u32,
var: u32,
remaining_depth: u32,
) -> u32 {
if clauses.is_empty() {
return smoothed_true(dag, num_vars, var);
}
if clauses.iter().any(|c| c.is_empty()) {
let id = dag.gates.len() as u32;
dag.gates.push(DnnfGate::False);
return id;
}
if var >= num_vars || remaining_depth == 0 {
let mut clause_ids = Vec::with_capacity(clauses.len());
for clause in clauses {
let mut lits = Vec::with_capacity(clause.len());
for &(v, p) in clause {
let id = dag.gates.len() as u32;
dag.gates.push(DnnfGate::Literal(v, p));
lits.push(id);
}
let id = dag.gates.len() as u32;
dag.gates.push(DnnfGate::Or(lits));
clause_ids.push(id);
}
if clause_ids.len() == 1 {
return clause_ids[0];
}
let id = dag.gates.len() as u32;
dag.gates.push(DnnfGate::And(clause_ids));
return id;
}
let positive_branch = simplify_clauses(clauses, var, true);
let negative_branch = simplify_clauses(clauses, var, false);
let left = compile_recursive(
dag,
&positive_branch,
num_vars,
var + 1,
remaining_depth - 1,
);
let right = compile_recursive(
dag,
&negative_branch,
num_vars,
var + 1,
remaining_depth - 1,
);
let pos_lit = dag.gates.len() as u32;
dag.gates.push(DnnfGate::Literal(var, true));
let pos_and = dag.gates.len() as u32;
dag.gates.push(DnnfGate::And(alloc::vec![pos_lit, left]));
let neg_lit = dag.gates.len() as u32;
dag.gates.push(DnnfGate::Literal(var, false));
let neg_and = dag.gates.len() as u32;
dag.gates.push(DnnfGate::And(alloc::vec![neg_lit, right]));
let or_id = dag.gates.len() as u32;
dag.gates.push(DnnfGate::Or(alloc::vec![pos_and, neg_and]));
or_id
}
fn simplify_clauses(
clauses: &[Vec<(u32, bool)>],
var: u32,
assignment: bool,
) -> Vec<Vec<(u32, bool)>> {
let mut out = Vec::with_capacity(clauses.len());
for clause in clauses {
let mut satisfied = false;
let mut residual = Vec::with_capacity(clause.len());
for &(v, p) in clause {
if v == var {
if p == assignment {
satisfied = true;
break;
}
} else {
residual.push((v, p));
}
}
if !satisfied {
out.push(residual);
}
}
out
}
#[must_use]
pub fn model_count(dag: &DnnfDag) -> u64 {
let mut counts: Vec<u64> = Vec::with_capacity(dag.gates.len());
for gate in &dag.gates {
let c = match gate {
DnnfGate::True => 1u64,
DnnfGate::False => 0u64,
DnnfGate::Literal(_, _) => 1u64,
DnnfGate::And(children) => {
let mut prod = 1u64;
for &c_id in children {
prod = prod.saturating_mul(counts[c_id as usize]);
}
prod
}
DnnfGate::Or(children) => {
let mut sum = 0u64;
for &c_id in children {
sum = sum.saturating_add(counts[c_id as usize]);
}
sum
}
};
counts.push(c);
}
counts[dag.gates.len() - 1]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compile_empty_formula_is_true() {
let dag = compile_dnnf(&[], 0, 4);
assert_eq!(dag.gates.last(), Some(&DnnfGate::True));
assert_eq!(model_count(&dag), 1);
}
#[test]
fn compile_single_literal() {
let dag = compile_dnnf(&[alloc::vec![(0u32, true)]], 1, 4);
assert_eq!(model_count(&dag), 1);
}
#[test]
fn compile_contradiction_yields_zero_models() {
let dag = compile_dnnf(&[alloc::vec![(0u32, true)], alloc::vec![(0, false)]], 1, 4);
assert_eq!(model_count(&dag), 0);
}
#[test]
fn compile_disjunction_of_two_lits() {
let dag = compile_dnnf(&[alloc::vec![(0u32, true), (1, true)]], 2, 4);
assert_eq!(model_count(&dag), 3);
}
#[test]
fn matches_brute_force_on_small_formulas() {
let clauses = alloc::vec![
alloc::vec![(0u32, true), (1, false)],
alloc::vec![(1, true), (2, true)],
];
let dag = compile_dnnf(&clauses, 3, 8);
let dag_count = model_count(&dag);
let mut bf = 0u64;
for assignment in 0u8..8 {
let x = [
(assignment & 1) != 0,
(assignment & 2) != 0,
(assignment & 4) != 0,
];
let c1 = x[0] || !x[1];
let c2 = x[1] || x[2];
if c1 && c2 {
bf += 1;
}
}
assert_eq!(dag_count, bf, "d-DNNF count must match brute force");
}
#[test]
fn depth_budget_terminates() {
let clauses = alloc::vec![
alloc::vec![(0u32, true), (1, true)],
alloc::vec![(2, true), (3, true)],
alloc::vec![(4, true), (5, true)],
];
let dag = compile_dnnf(&clauses, 6, 2);
assert!(!dag.gates.is_empty());
}
#[test]
fn model_count_smooths_over_free_vars() {
let dag = compile_dnnf(&[], 0, 4);
assert_eq!(model_count(&dag), 1);
let dag = compile_dnnf(&[], 5, 4);
assert_eq!(model_count(&dag), 32);
let dag = compile_dnnf(&[], 1, 4);
assert_eq!(model_count(&dag), 2);
}
#[test]
fn model_count_saturates_at_u64_max() {
let dag = compile_dnnf(&[], 64, 4);
assert!(model_count(&dag) > 0);
}
#[test]
fn root_is_last_gate() {
let dag = compile_dnnf(&[alloc::vec![(0u32, true)]], 1, 4);
assert_eq!(dag.root(), (dag.gates.len() - 1) as u32);
}
}