diff --git a/bin/bottomup_cnf_to_bdd.rs b/bin/bottomup_cnf_to_bdd.rs index dc78267d..14cbb840 100644 --- a/bin/bottomup_cnf_to_bdd.rs +++ b/bin/bottomup_cnf_to_bdd.rs @@ -2,10 +2,7 @@ use std::{fs, time::Instant}; use clap::Parser; use rsdd::{ - builder::{ - bdd::{BddBuilder, RobddBuilder}, - cache::LruIteTable, - }, + builder::{bdd::RobddBuilder, cache::LruIteTable, BottomUpBuilder}, plan::BottomUpPlan, repr::{bdd::BddPtr, cnf::Cnf, dtree::DTree}, serialize::BDDSerializer, diff --git a/examples/marginal_map_experiment.rs b/examples/marginal_map_experiment.rs index 32c28ba1..7e240c8a 100644 --- a/examples/marginal_map_experiment.rs +++ b/examples/marginal_map_experiment.rs @@ -3,10 +3,7 @@ use std::{collections::HashMap, fs}; use clap::Parser; use rand::Rng; use rsdd::{ - builder::{ - bdd::{BddBuilder, RobddBuilder}, - cache::AllIteTable, - }, + builder::{bdd::RobddBuilder, cache::AllIteTable, BottomUpBuilder}, repr::{bdd::BddPtr, cnf::Cnf, var_label::VarLabel, wmc::WmcParams}, util::semirings::RealSemiring, }; diff --git a/examples/one_shot_benchmark.rs b/examples/one_shot_benchmark.rs index c30e1d77..6e5eb22a 100644 --- a/examples/one_shot_benchmark.rs +++ b/examples/one_shot_benchmark.rs @@ -1,10 +1,10 @@ use clap::Parser; -use rsdd::builder::bdd::BddBuilder; use rsdd::builder::bdd::RobddBuilder; use rsdd::builder::cache::LruIteTable; use rsdd::builder::decision_nnf::DecisionNNFBuilder; use rsdd::builder::decision_nnf::StandardDecisionNNFBuilder; use rsdd::builder::sdd::{CompressionSddBuilder, SddBuilder}; +use rsdd::builder::BottomUpBuilder; use rsdd::plan::BottomUpPlan; use rsdd::repr::bdd::BddPtr; use rsdd::repr::cnf::Cnf; diff --git a/src/builder/bdd/builder.rs b/src/builder/bdd/builder.rs index 30936939..cee00c92 100644 --- a/src/builder/bdd/builder.rs +++ b/src/builder/bdd/builder.rs @@ -1,11 +1,9 @@ use crate::{ builder::{bdd::CompiledCNF, BottomUpBuilder}, - plan::BottomUpPlan, repr::{ bdd::{BddNode, BddPtr}, cnf::Cnf, ddnnf::DDNNFPtr, - logical_expr::LogicalExpr, model::PartialModel, var_label::VarLabel, }, @@ -81,66 +79,6 @@ pub trait BddBuilder<'a>: BottomUpBuilder<'a, BddPtr<'a>> { ptr } - /// Compile a BDD from a CNF - fn compile_cnf(&'a self, cnf: &Cnf) -> BddPtr<'a> { - let mut cvec: Vec = Vec::with_capacity(cnf.clauses().len()); - if cnf.clauses().is_empty() { - return BddPtr::true_ptr(); - } - // check if there is an empty clause -- if so, UNSAT - if cnf.clauses().iter().any(|x| x.is_empty()) { - return BddPtr::false_ptr(); - } - - // sort the clauses based on a best-effort bottom-up ordering of clauses - let mut cnf_sorted = cnf.clauses().to_vec(); - cnf_sorted.sort_by(|c1, c2| { - // order the clause with the first-most variable last - let fst1 = c1 - .iter() - .max_by(|l1, l2| { - if self.less_than(l1.label(), l2.label()) { - Ordering::Less - } else { - Ordering::Equal - } - }) - .unwrap(); - let fst2 = c2 - .iter() - .max_by(|l1, l2| { - if self.less_than(l1.label(), l2.label()) { - Ordering::Less - } else { - Ordering::Equal - } - }) - .unwrap(); - if self.less_than(fst1.label(), fst2.label()) { - Ordering::Less - } else { - Ordering::Equal - } - }); - - for lit_vec in cnf_sorted.iter() { - let (vlabel, val) = (lit_vec[0].label(), lit_vec[0].polarity()); - let mut bdd = self.var(vlabel, val); - for lit in lit_vec { - let (vlabel, val) = (lit.label(), lit.polarity()); - let var = self.var(vlabel, val); - bdd = self.or(bdd, var); - } - cvec.push(bdd); - } - // now cvec has a list of all the clauses; collapse it down - let r = self.collapse_clauses(&cvec); - match r { - None => BddPtr::true_ptr(), - Some(x) => x, - } - } - fn collapse_clauses(&'a self, vec: &[BddPtr<'a>]) -> Option> { if vec.is_empty() { None @@ -157,76 +95,6 @@ pub trait BddBuilder<'a>: BottomUpBuilder<'a, BddPtr<'a>> { } } } - fn compile_boolexpr(&'a self, expr: &LogicalExpr) -> BddPtr<'a> { - match &expr { - LogicalExpr::Literal(lbl, polarity) => self.var(VarLabel::new(*lbl as u64), *polarity), - LogicalExpr::And(ref l, ref r) => { - let r1 = self.compile_boolexpr(l); - let r2 = self.compile_boolexpr(r); - self.and(r1, r2) - } - LogicalExpr::Or(ref l, ref r) => { - let r1 = self.compile_boolexpr(l); - let r2 = self.compile_boolexpr(r); - self.or(r1, r2) - } - LogicalExpr::Not(ref e) => self.compile_boolexpr(e).neg(), - LogicalExpr::Iff(ref l, ref r) => { - let r1 = self.compile_boolexpr(l); - let r2 = self.compile_boolexpr(r); - self.iff(r1, r2) - } - LogicalExpr::Xor(ref l, ref r) => { - let r1 = self.compile_boolexpr(l); - let r2 = self.compile_boolexpr(r); - self.xor(r1, r2) - } - LogicalExpr::Ite { - ref guard, - ref thn, - ref els, - } => { - let g = self.compile_boolexpr(guard); - let t = self.compile_boolexpr(thn); - let e = self.compile_boolexpr(els); - self.ite(g, t, e) - } - } - } - - /// Compiles a plan into a BDD - fn compile_plan(&'a self, expr: &BottomUpPlan) -> BddPtr<'a> { - match &expr { - BottomUpPlan::Literal(var, polarity) => self.var(*var, *polarity), - BottomUpPlan::And(ref l, ref r) => { - let r1 = self.compile_plan(l); - let r2 = self.compile_plan(r); - self.and(r1, r2) - } - BottomUpPlan::Or(ref l, ref r) => { - let r1 = self.compile_plan(l); - let r2 = self.compile_plan(r); - self.or(r1, r2) - } - BottomUpPlan::Iff(ref l, ref r) => { - let r1 = self.compile_plan(l); - let r2 = self.compile_plan(r); - self.iff(r1, r2) - } - BottomUpPlan::Ite(ref f, ref g, ref h) => { - let f = self.compile_plan(f); - let g = self.compile_plan(g); - let h = self.compile_plan(h); - self.ite(f, g, h) - } - BottomUpPlan::Not(ref f) => { - let f = self.compile_plan(f); - f.neg() - } - BottomUpPlan::ConstTrue => BddPtr::true_ptr(), - BottomUpPlan::ConstFalse => BddPtr::false_ptr(), - } - } } impl<'a, T> BottomUpBuilder<'a, BddPtr<'a>> for T @@ -317,4 +185,64 @@ where self.exists(a, lbl) } + + /// Compile a BDD from a CNF + fn compile_cnf(&'a self, cnf: &Cnf) -> BddPtr<'a> { + let mut cvec: Vec = Vec::with_capacity(cnf.clauses().len()); + if cnf.clauses().is_empty() { + return BddPtr::true_ptr(); + } + // check if there is an empty clause -- if so, UNSAT + if cnf.clauses().iter().any(|x| x.is_empty()) { + return BddPtr::false_ptr(); + } + + // sort the clauses based on a best-effort bottom-up ordering of clauses + let mut cnf_sorted = cnf.clauses().to_vec(); + cnf_sorted.sort_by(|c1, c2| { + // order the clause with the first-most variable last + let fst1 = c1 + .iter() + .max_by(|l1, l2| { + if self.less_than(l1.label(), l2.label()) { + Ordering::Less + } else { + Ordering::Equal + } + }) + .unwrap(); + let fst2 = c2 + .iter() + .max_by(|l1, l2| { + if self.less_than(l1.label(), l2.label()) { + Ordering::Less + } else { + Ordering::Equal + } + }) + .unwrap(); + if self.less_than(fst1.label(), fst2.label()) { + Ordering::Less + } else { + Ordering::Equal + } + }); + + for lit_vec in cnf_sorted.iter() { + let (vlabel, val) = (lit_vec[0].label(), lit_vec[0].polarity()); + let mut bdd = self.var(vlabel, val); + for lit in lit_vec { + let (vlabel, val) = (lit.label(), lit.polarity()); + let var = self.var(vlabel, val); + bdd = self.or(bdd, var); + } + cvec.push(bdd); + } + // now cvec has a list of all the clauses; collapse it down + let r = self.collapse_clauses(&cvec); + match r { + None => BddPtr::true_ptr(), + Some(x) => x, + } + } } diff --git a/src/builder/bdd/robdd.rs b/src/builder/bdd/robdd.rs index a9582eaf..1369124f 100644 --- a/src/builder/bdd/robdd.rs +++ b/src/builder/bdd/robdd.rs @@ -318,7 +318,6 @@ mod tests { use std::borrow::Borrow; use std::collections::HashMap; - use crate::builder::bdd::builder::BddBuilder; use crate::builder::BottomUpBuilder; use crate::repr::wmc::WmcParams; use crate::util::semirings::{FiniteField, RealSemiring}; diff --git a/src/builder/mod.rs b/src/builder/mod.rs index 7d5c37c4..8706c9f4 100644 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -7,7 +7,10 @@ pub mod bdd; pub mod decision_nnf; pub mod sdd; -use crate::repr::var_label::VarLabel; +use crate::{ + plan::BottomUpPlan, + repr::{cnf::Cnf, logical_expr::LogicalExpr, var_label::VarLabel}, +}; pub trait BottomUpBuilder<'a, Ptr> { // constants --- can elide the input lifetimes @@ -48,6 +51,83 @@ pub trait BottomUpBuilder<'a, Ptr> { /// compose g into f for variable v /// I.e., computes the logical function (exists v. (g <=> v) /\ f). fn compose(&'a self, f: Ptr, lbl: VarLabel, g: Ptr) -> Ptr; + + // compilation + + /// directly compile a CNF + fn compile_cnf(&'a self, cnf: &Cnf) -> Ptr; + + /// directly compile a logical expression + fn compile_logical_expr(&'a self, expr: &LogicalExpr) -> Ptr { + match &expr { + LogicalExpr::Literal(lbl, polarity) => self.var(VarLabel::new(*lbl as u64), *polarity), + LogicalExpr::And(ref l, ref r) => { + let r1 = self.compile_logical_expr(l); + let r2 = self.compile_logical_expr(r); + self.and(r1, r2) + } + LogicalExpr::Or(ref l, ref r) => { + let r1 = self.compile_logical_expr(l); + let r2 = self.compile_logical_expr(r); + self.or(r1, r2) + } + LogicalExpr::Not(ref e) => self.negate(self.compile_logical_expr(e)), + LogicalExpr::Iff(ref l, ref r) => { + let r1 = self.compile_logical_expr(l); + let r2 = self.compile_logical_expr(r); + self.iff(r1, r2) + } + LogicalExpr::Xor(ref l, ref r) => { + let r1 = self.compile_logical_expr(l); + let r2 = self.compile_logical_expr(r); + self.xor(r1, r2) + } + LogicalExpr::Ite { + ref guard, + ref thn, + ref els, + } => { + let g = self.compile_logical_expr(guard); + let t = self.compile_logical_expr(thn); + let e = self.compile_logical_expr(els); + self.ite(g, t, e) + } + } + } + + /// Compiles from a BottomUpPlan, which represents a deferred computation + fn compile_plan(&'a self, expr: &BottomUpPlan) -> Ptr { + match &expr { + BottomUpPlan::Literal(var, polarity) => self.var(*var, *polarity), + BottomUpPlan::And(ref l, ref r) => { + let r1 = self.compile_plan(l); + let r2 = self.compile_plan(r); + self.and(r1, r2) + } + BottomUpPlan::Or(ref l, ref r) => { + let r1 = self.compile_plan(l); + let r2 = self.compile_plan(r); + self.or(r1, r2) + } + BottomUpPlan::Iff(ref l, ref r) => { + let r1 = self.compile_plan(l); + let r2 = self.compile_plan(r); + self.iff(r1, r2) + } + BottomUpPlan::Ite(ref f, ref g, ref h) => { + let f = self.compile_plan(f); + let g = self.compile_plan(g); + let h = self.compile_plan(h); + self.ite(f, g, h) + } + BottomUpPlan::Not(ref f) => { + let f = self.compile_plan(f); + self.negate(f) + } + BottomUpPlan::ConstTrue => self.true_ptr(), + BottomUpPlan::ConstFalse => self.false_ptr(), + } + } } pub trait TopDownBuilder<'a, Ptr> { diff --git a/src/builder/sdd/builder.rs b/src/builder/sdd/builder.rs index 15dbcb52..e39f700d 100644 --- a/src/builder/sdd/builder.rs +++ b/src/builder/sdd/builder.rs @@ -6,7 +6,6 @@ use crate::{ repr::{ cnf::Cnf, ddnnf::DDNNFPtr, - logical_expr::LogicalExpr, sdd::{BinarySDD, SddAnd, SddOr, SddPtr}, var_label::VarLabel, vtree::{VTree, VTreeIndex, VTreeManager}, @@ -341,69 +340,6 @@ pub trait SddBuilder<'a>: BottomUpBuilder<'a, SddPtr<'a>> { } } - /// compile an SDD from an input CNF - fn compile_cnf(&'a self, cnf: &Cnf) -> SddPtr<'a> { - let mut cvec: Vec = Vec::with_capacity(cnf.clauses().len()); - if cnf.clauses().is_empty() { - return SddPtr::true_ptr(); - } - // check if there is an empty clause -- if so, UNSAT - if cnf.clauses().iter().any(|x| x.is_empty()) { - return SddPtr::false_ptr(); - } - - // sort the clauses based on a best-effort bottom-up ordering of clauses - let mut cnf_sorted = cnf.clauses().to_vec(); - cnf_sorted.sort_by(|c1, c2| { - // order the clause with the first-most variable last - let fst1 = c1 - .iter() - .max_by(|l1, l2| { - if self.vtree_manager().is_prime_var(l1.label(), l2.label()) { - Ordering::Less - } else { - Ordering::Equal - } - }) - .unwrap(); - let fst2 = c2 - .iter() - .max_by(|l1, l2| { - if self.vtree_manager().is_prime_var(l1.label(), l2.label()) { - Ordering::Less - } else { - Ordering::Equal - } - }) - .unwrap(); - if self - .vtree_manager() - .is_prime_var(fst1.label(), fst2.label()) - { - Ordering::Less - } else { - Ordering::Equal - } - }); - - for lit_vec in cnf_sorted.iter() { - let (vlabel, val) = (lit_vec[0].label(), lit_vec[0].polarity()); - let mut bdd = SddPtr::Var(vlabel, val); - for lit in lit_vec { - let (vlabel, val) = (lit.label(), lit.polarity()); - let var = SddPtr::Var(vlabel, val); - bdd = self.or(bdd, var); - } - cvec.push(bdd); - } - // now cvec has a list of all the clauses; collapse it down - let r = self.compile_cnf_helper(&cvec); - match r { - None => SddPtr::true_ptr(), - Some(x) => x, - } - } - fn compile_cnf_helper(&'a self, vec: &[SddPtr<'a>]) -> Option> { if vec.is_empty() { None @@ -421,47 +357,6 @@ pub trait SddBuilder<'a>: BottomUpBuilder<'a, SddPtr<'a>> { } } - // note: this is not tested - fn compile_logical_expr(&'a self, expr: &LogicalExpr) -> SddPtr { - match expr { - LogicalExpr::Literal(lbl, polarity) => self.var(VarLabel::new(*lbl as u64), *polarity), - LogicalExpr::Not(e) => { - let e = self.compile_logical_expr(e); - e.neg() - } - LogicalExpr::And(ref l, ref r) => { - let r1 = self.compile_logical_expr(l); - let r2 = self.compile_logical_expr(r); - self.and(r1, r2) - } - LogicalExpr::Or(ref l, ref r) => { - let r1 = self.compile_logical_expr(l); - let r2 = self.compile_logical_expr(r); - self.or(r1, r2) - } - LogicalExpr::Xor(ref l, ref r) => { - let r1 = self.compile_logical_expr(l); - let r2 = self.compile_logical_expr(r); - self.xor(r1, r2) - } - LogicalExpr::Iff(ref l, ref r) => { - let r1 = self.compile_logical_expr(l); - let r2 = self.compile_logical_expr(r); - self.iff(r1, r2) - } - LogicalExpr::Ite { - ref guard, - ref thn, - ref els, - } => { - let g = self.compile_logical_expr(guard); - let thn = self.compile_logical_expr(thn); - let els = self.compile_logical_expr(els); - self.ite(g, thn, els) - } - } - } - fn print_sdd(&'a self, ptr: SddPtr<'a>) -> String { use pretty::*; fn helper(ptr: SddPtr) -> Doc<'static, BoxDoc<'static>> { @@ -705,4 +600,67 @@ where let a = self.and(iff, f); self.exists(a, lbl) } + + /// compile an SDD from an input CNF + fn compile_cnf(&'a self, cnf: &Cnf) -> SddPtr<'a> { + let mut cvec: Vec = Vec::with_capacity(cnf.clauses().len()); + if cnf.clauses().is_empty() { + return SddPtr::true_ptr(); + } + // check if there is an empty clause -- if so, UNSAT + if cnf.clauses().iter().any(|x| x.is_empty()) { + return SddPtr::false_ptr(); + } + + // sort the clauses based on a best-effort bottom-up ordering of clauses + let mut cnf_sorted = cnf.clauses().to_vec(); + cnf_sorted.sort_by(|c1, c2| { + // order the clause with the first-most variable last + let fst1 = c1 + .iter() + .max_by(|l1, l2| { + if self.vtree_manager().is_prime_var(l1.label(), l2.label()) { + Ordering::Less + } else { + Ordering::Equal + } + }) + .unwrap(); + let fst2 = c2 + .iter() + .max_by(|l1, l2| { + if self.vtree_manager().is_prime_var(l1.label(), l2.label()) { + Ordering::Less + } else { + Ordering::Equal + } + }) + .unwrap(); + if self + .vtree_manager() + .is_prime_var(fst1.label(), fst2.label()) + { + Ordering::Less + } else { + Ordering::Equal + } + }); + + for lit_vec in cnf_sorted.iter() { + let (vlabel, val) = (lit_vec[0].label(), lit_vec[0].polarity()); + let mut bdd = SddPtr::Var(vlabel, val); + for lit in lit_vec { + let (vlabel, val) = (lit.label(), lit.polarity()); + let var = SddPtr::Var(vlabel, val); + bdd = self.or(bdd, var); + } + cvec.push(bdd); + } + // now cvec has a list of all the clauses; collapse it down + let r = self.compile_cnf_helper(&cvec); + match r { + None => SddPtr::true_ptr(), + Some(x) => x, + } + } } diff --git a/src/wasm/mod.rs b/src/wasm/mod.rs index 604f7ec4..eb4e0992 100644 --- a/src/wasm/mod.rs +++ b/src/wasm/mod.rs @@ -1,8 +1,9 @@ use crate::{ builder::{ - bdd::{BddBuilder, RobddBuilder}, + bdd::RobddBuilder, cache::AllIteTable, sdd::{CompressionSddBuilder, SddBuilder}, + BottomUpBuilder, }, constants::primes, repr::{ diff --git a/tests/test.rs b/tests/test.rs index 05b90be9..8aa069d0 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -4,9 +4,10 @@ extern crate rsdd; #[macro_use] extern crate quickcheck; -use rsdd::builder::bdd::{BddBuilder, RobddBuilder}; +use rsdd::builder::bdd::RobddBuilder; use rsdd::builder::cache::AllIteTable; use rsdd::builder::sdd::{CompressionSddBuilder, SddBuilder}; +use rsdd::builder::BottomUpBuilder; use rsdd::repr::bdd::BddPtr; use rsdd::repr::cnf::Cnf; use rsdd::repr::var_label::VarLabel; @@ -670,7 +671,6 @@ mod test_sdd_builder { use rand::rngs::SmallRng; use rand::seq::SliceRandom; use rand::SeedableRng; - use rsdd::builder::bdd::BddBuilder; use rsdd::builder::bdd::RobddBuilder; use rsdd::builder::cache::AllIteTable; use rsdd::builder::sdd::{CompressionSddBuilder, SddBuilder, SemanticSddBuilder};