mirror of
https://github.com/ellmau/adf-obdd.git
synced 2025-12-19 09:29:36 +01:00
1009 lines
36 KiB
Rust
1009 lines
36 KiB
Rust
//! Module which represents obdds.
|
||
//!
|
||
#[cfg(feature = "frontend")]
|
||
pub mod frontend;
|
||
pub mod vectorize;
|
||
use crate::datatypes::*;
|
||
use serde::{Deserialize, Serialize};
|
||
use std::collections::HashSet;
|
||
use std::{cell::RefCell, cmp::min, collections::HashMap, fmt::Display};
|
||
|
||
#[cfg(test)]
|
||
use mimicry::mock;
|
||
|
||
/// Contains the data of (possibly) multiple roBDDs, managed over one collection of nodes.
|
||
/// It has a couple of methods to instantiate, update, and query properties on a given roBDD.
|
||
/// Each roBDD is identified by its corresponding [`Term`], which implicitly identifies the root node of a roBDD.
|
||
#[derive(Debug, Serialize, Deserialize)]
|
||
pub struct Bdd {
|
||
pub(crate) nodes: Vec<BddNode>,
|
||
#[cfg(feature = "variablelist")]
|
||
#[serde(skip)]
|
||
var_deps: Vec<HashSet<Var>>,
|
||
#[serde(with = "vectorize")]
|
||
cache: HashMap<BddNode, Term>,
|
||
#[serde(skip, default = "Bdd::default_count_cache")]
|
||
count_cache: RefCell<HashMap<Term, CountNode>>,
|
||
#[cfg(feature = "frontend")]
|
||
#[serde(skip)]
|
||
sender: Option<crossbeam_channel::Sender<BddNode>>,
|
||
#[cfg(feature = "frontend")]
|
||
#[serde(skip)]
|
||
receiver: Option<crossbeam_channel::Receiver<BddNode>>,
|
||
#[serde(skip)]
|
||
ite_cache: HashMap<(Term, Term, Term), Term>,
|
||
#[serde(skip)]
|
||
restrict_cache: HashMap<(Term, Var, bool), Term>,
|
||
#[serde(skip)]
|
||
restrict_cache2: HashMap<(Term, Vec<(Var, bool)>), Term>,
|
||
}
|
||
|
||
impl Display for Bdd {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
writeln!(f, " ")?;
|
||
for (idx, elem) in self.nodes.iter().enumerate() {
|
||
writeln!(f, "{} {}", idx, *elem)?;
|
||
}
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
impl Default for Bdd {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
impl Bdd {
|
||
/// Instantiate a new roBDD structure.
|
||
/// Constants for the [`⊤`][crate::datatypes::Term::TOP] and [`⊥`][crate::datatypes::Term::BOT] concepts are prepared in that step too.
|
||
pub fn new() -> Self {
|
||
#[cfg(not(feature = "adhoccounting"))]
|
||
{
|
||
Self {
|
||
nodes: vec![BddNode::bot_node(), BddNode::top_node()],
|
||
#[cfg(feature = "variablelist")]
|
||
var_deps: vec![HashSet::new(), HashSet::new()],
|
||
cache: HashMap::new(),
|
||
count_cache: RefCell::new(HashMap::new()),
|
||
#[cfg(feature = "frontend")]
|
||
sender: None,
|
||
#[cfg(feature = "frontend")]
|
||
receiver: None,
|
||
ite_cache: HashMap::new(),
|
||
restrict_cache: HashMap::new(),
|
||
restrict_cache2: HashMap::new(),
|
||
}
|
||
}
|
||
#[cfg(feature = "adhoccounting")]
|
||
{
|
||
let result = Self {
|
||
nodes: vec![BddNode::bot_node(), BddNode::top_node()],
|
||
#[cfg(feature = "variablelist")]
|
||
var_deps: vec![HashSet::new(), HashSet::new()],
|
||
cache: HashMap::new(),
|
||
count_cache: RefCell::new(HashMap::new()),
|
||
#[cfg(feature = "frontend")]
|
||
sender: None,
|
||
#[cfg(feature = "frontend")]
|
||
receiver: None,
|
||
ite_cache: HashMap::new(),
|
||
restrict_cache: HashMap::new(),
|
||
restrict_cache2: HashMap::new(),
|
||
};
|
||
result
|
||
.count_cache
|
||
.borrow_mut()
|
||
.insert(Term::TOP, (ModelCounts::top(), ModelCounts::top(), 0));
|
||
result
|
||
.count_cache
|
||
.borrow_mut()
|
||
.insert(Term::BOT, (ModelCounts::bot(), ModelCounts::bot(), 0));
|
||
result
|
||
}
|
||
}
|
||
|
||
fn default_count_cache() -> RefCell<HashMap<Term, CountNode>> {
|
||
RefCell::new(HashMap::new())
|
||
}
|
||
|
||
/// Instantiates a [variable][crate::datatypes::Var] and returns the representing roBDD as a [`Term`][crate::datatypes::Term].
|
||
pub fn variable(&mut self, var: Var) -> Term {
|
||
self.node(var, Term::BOT, Term::TOP)
|
||
}
|
||
|
||
/// Instantiates a constant, which is either [true] or [false].
|
||
pub fn constant(val: bool) -> Term {
|
||
if val {
|
||
Term::TOP
|
||
} else {
|
||
Term::BOT
|
||
}
|
||
}
|
||
|
||
/// Returns an roBDD, which represents the negation of the given roBDD.
|
||
pub fn not(&mut self, term: Term) -> Term {
|
||
self.if_then_else(term, Term::BOT, Term::TOP)
|
||
}
|
||
|
||
/// Returns an roBDD, which represents the conjunction of the two given roBDDs.
|
||
pub fn and(&mut self, term_a: Term, term_b: Term) -> Term {
|
||
self.if_then_else(term_a, term_b, Term::BOT)
|
||
}
|
||
|
||
/// Returns an roBDD, which represents the disjunction of the two given roBDDs.
|
||
pub fn or(&mut self, term_a: Term, term_b: Term) -> Term {
|
||
self.if_then_else(term_a, Term::TOP, term_b)
|
||
}
|
||
|
||
/// Returns an roBDD, which represents the implication of the two given roBDDs.
|
||
pub fn imp(&mut self, term_a: Term, term_b: Term) -> Term {
|
||
self.if_then_else(term_a, term_b, Term::TOP)
|
||
}
|
||
|
||
/// Returns an roBDD, which represents the if and only if relation of the two given roBDDs.
|
||
pub fn iff(&mut self, term_a: Term, term_b: Term) -> Term {
|
||
let not_b = self.not(term_b);
|
||
self.if_then_else(term_a, term_b, not_b)
|
||
}
|
||
|
||
/// Returns an roBDD, which represents the exclusive disjunction of the two given roBDDs.
|
||
pub fn xor(&mut self, term_a: Term, term_b: Term) -> Term {
|
||
let not_b = self.not(term_b);
|
||
self.if_then_else(term_a, not_b, term_b)
|
||
}
|
||
|
||
/// Computes the interpretations represented in the roBDD, which are either models or counter-models.
|
||
/// **goal_var** is the [variable][Var] to which the roBDD is related to and it is ensured that the goal is consistent with the respective interpretation.
|
||
/// **goal** is a boolean [variable][Var], which defines whether the models or counter-models are of interest.
|
||
pub fn interpretations(
|
||
&self,
|
||
tree: Term,
|
||
goal: bool,
|
||
goal_var: Var,
|
||
negative: &[Var],
|
||
positive: &[Var],
|
||
) -> Vec<(Vec<Var>, Vec<Var>)> {
|
||
let mut result = Vec::new();
|
||
let node = self.nodes[tree.value()];
|
||
let var = node.var();
|
||
if tree.is_truth_value() {
|
||
return Vec::new();
|
||
}
|
||
// if the current var is the goal var, then only work with the hi-node if the goal is true
|
||
if (goal_var != var) || goal {
|
||
if node.hi().is_truth_value() {
|
||
if node.hi().is_true() == goal {
|
||
result.push((negative.to_vec(), [positive, &[var]].concat()));
|
||
}
|
||
} else {
|
||
result.append(&mut self.interpretations(
|
||
node.hi(),
|
||
goal,
|
||
goal_var,
|
||
negative,
|
||
&[positive, &[var]].concat(),
|
||
));
|
||
}
|
||
}
|
||
// if the current var is the goal var, then only work with the lo-node if the goal is false
|
||
if (goal_var != var) || !goal {
|
||
if node.lo().is_truth_value() {
|
||
if node.lo().is_true() == goal {
|
||
result.push(([negative, &[var]].concat(), positive.to_vec()));
|
||
}
|
||
} else {
|
||
result.append(&mut self.interpretations(
|
||
node.lo(),
|
||
goal,
|
||
goal_var,
|
||
&[negative, &[var]].concat(),
|
||
positive,
|
||
));
|
||
}
|
||
}
|
||
result
|
||
}
|
||
|
||
/// Restrict the bdd to the given list of [variables][crate::datatypes::Var] and [values][bool].
|
||
///
|
||
/// # Caution
|
||
/// `restrict_list` only works as described if the `list` follows the variable ordering.
|
||
pub fn restrict_list(&mut self, tree: Term, list: &[(Var, bool)]) -> Term {
|
||
if let Some(&(var, val)) = list.first() {
|
||
if let Some(result) = self.restrict_cache2.get(&(tree, list.to_vec())) {
|
||
*result
|
||
} else {
|
||
let node = self.nodes[tree.value()];
|
||
#[cfg(feature = "variablelist")]
|
||
{
|
||
if !self.var_deps[tree.value()].contains(&var) {
|
||
return self.restrict_list(tree, &list[1..]);
|
||
}
|
||
}
|
||
if node.var() >= Var::BOT {
|
||
tree
|
||
} else if node.var() > var {
|
||
self.restrict_list(tree, &list[1..])
|
||
} else if node.var() < var {
|
||
let lonode = self.restrict_list(node.lo(), list);
|
||
let hinode = self.restrict_list(node.hi(), list);
|
||
let mut result = self.node(node.var(), lonode, hinode);
|
||
result = self.restrict_list(result, &list[1..]);
|
||
self.restrict_cache2.insert((tree, list.into()), result);
|
||
result
|
||
} else {
|
||
let result =
|
||
self.restrict_list(if val { node.hi() } else { node.lo() }, &list[1..]);
|
||
self.restrict_cache2.insert((tree, list.into()), result);
|
||
result
|
||
}
|
||
}
|
||
} else {
|
||
// No elements in list
|
||
tree
|
||
}
|
||
}
|
||
|
||
/// Creates an roBDD, based on the relation of three roBDDs, which are in an `if-then-else` relation.
|
||
fn if_then_else(&mut self, i: Term, t: Term, e: Term) -> Term {
|
||
if i == Term::TOP {
|
||
t
|
||
} else if i == Term::BOT {
|
||
e
|
||
} else if t == e {
|
||
t
|
||
} else if t == Term::TOP && e == Term::BOT {
|
||
i
|
||
} else if let Some(result) = self.ite_cache.get(&(i, t, e)) {
|
||
*result
|
||
} else {
|
||
log::trace!("if_then_else: i {i} t {t} e {e} not found");
|
||
let minvar = Var(min(
|
||
self.nodes[i.value()].var().value(),
|
||
min(
|
||
self.nodes[t.value()].var().value(),
|
||
self.nodes[e.value()].var().value(),
|
||
),
|
||
));
|
||
let itop = self.restrict(i, minvar, true);
|
||
let ttop = self.restrict(t, minvar, true);
|
||
let etop = self.restrict(e, minvar, true);
|
||
let ibot = self.restrict(i, minvar, false);
|
||
let tbot = self.restrict(t, minvar, false);
|
||
let ebot = self.restrict(e, minvar, false);
|
||
|
||
let top_ite = self.if_then_else(itop, ttop, etop);
|
||
let bot_ite = self.if_then_else(ibot, tbot, ebot);
|
||
let result = self.node(minvar, bot_ite, top_ite);
|
||
self.ite_cache.insert((i, t, e), result);
|
||
result
|
||
}
|
||
}
|
||
|
||
/// Creates a new node in the roBDD.
|
||
/// It will not create duplicate nodes and uses already existing nodes, if applicable.
|
||
pub fn node(&mut self, var: Var, lo: Term, hi: Term) -> Term {
|
||
if lo == hi {
|
||
lo
|
||
} else {
|
||
let node = BddNode::new(var, lo, hi);
|
||
match self.cache.get(&node) {
|
||
Some(t) => *t,
|
||
None => {
|
||
let new_term = Term(self.nodes.len());
|
||
self.nodes.push(node);
|
||
self.cache.insert(node, new_term);
|
||
#[cfg(feature = "frontend")]
|
||
if let Some(send) = &self.sender {
|
||
match send.send(node) {
|
||
Ok(_) => log::trace!("Sent {node} to the channel."),
|
||
Err(e) => {
|
||
log::error!("Error {e} occurred when sending {node} to {:?}", send)
|
||
}
|
||
}
|
||
}
|
||
#[cfg(feature = "variablelist")]
|
||
{
|
||
let mut var_set: HashSet<Var> = self.var_deps[lo.value()]
|
||
.union(&self.var_deps[hi.value()])
|
||
.copied()
|
||
.collect();
|
||
var_set.insert(var);
|
||
self.var_deps.push(var_set);
|
||
}
|
||
log::trace!("newterm: {} as {:?}", new_term, node);
|
||
#[cfg(feature = "adhoccounting")]
|
||
{
|
||
let mut count_cache = self.count_cache.borrow_mut();
|
||
let (lo_counts, lo_paths, lodepth) =
|
||
*count_cache.get(&lo).expect("Cache corrupted");
|
||
let (hi_counts, hi_paths, hidepth) =
|
||
*count_cache.get(&hi).expect("Cache corrupted");
|
||
log::debug!(
|
||
"lo (cm: {}, mo: {}, p-: {}, p+: {}, dp: {})",
|
||
lo_counts.cmodels,
|
||
lo_counts.models,
|
||
lo_paths.cmodels,
|
||
lo_paths.models,
|
||
lodepth
|
||
);
|
||
log::debug!(
|
||
"hi (cm: {}, mo: {}, p-: {}, p+: {}, dp: {})",
|
||
hi_counts.cmodels,
|
||
hi_counts.models,
|
||
hi_paths.cmodels,
|
||
hi_paths.models,
|
||
hidepth
|
||
);
|
||
#[cfg(feature = "adhoccountmodels")]
|
||
let (lo_exp, hi_exp) = if lodepth > hidepth {
|
||
(1, 2usize.pow((lodepth - hidepth) as u32))
|
||
} else {
|
||
(2usize.pow((hidepth - lodepth) as u32), 1)
|
||
};
|
||
#[cfg(not(feature = "adhoccountmodels"))]
|
||
let (lo_exp, hi_exp) = (0, 0);
|
||
log::debug!("lo_exp {}, hi_exp {}", lo_exp, hi_exp);
|
||
count_cache.insert(
|
||
new_term,
|
||
(
|
||
(
|
||
lo_counts.cmodels * lo_exp + hi_counts.cmodels * hi_exp,
|
||
lo_counts.models * lo_exp + hi_counts.models * hi_exp,
|
||
)
|
||
.into(),
|
||
(
|
||
lo_paths.cmodels + hi_paths.cmodels,
|
||
lo_paths.models + hi_paths.models,
|
||
)
|
||
.into(),
|
||
std::cmp::max(lodepth, hidepth) + 1,
|
||
),
|
||
);
|
||
}
|
||
new_term
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Computes the number of counter-models and models for a given roBDD.
|
||
///
|
||
/// Use the flag `_memoization` to choose between using the memoization approach or not. (This flag does nothing, if the feature `adhoccounting` is used)
|
||
pub fn models(&self, term: Term, _memoization: bool) -> ModelCounts {
|
||
#[cfg(feature = "adhoccountmodels")]
|
||
{
|
||
return self.count_cache.borrow().get(&term).expect("The term should be originating from this bdd, otherwise the result would be inconsistent anyways").0;
|
||
}
|
||
#[cfg(not(feature = "adhoccountmodels"))]
|
||
if _memoization {
|
||
self.modelcount_memoization(term).0
|
||
} else {
|
||
self.modelcount_naive(term).0
|
||
}
|
||
}
|
||
|
||
/// Computes the number of paths, which lead to ⊥ respectively ⊤.
|
||
///
|
||
/// Use the flag `_memoization` to choose between using the memoization approach or not. (This flag does nothing, if the feature `adhoccounting` is used)
|
||
pub fn paths(&self, term: Term, _memoization: bool) -> ModelCounts {
|
||
#[cfg(feature = "adhoccounting")]
|
||
{
|
||
return self.count_cache.borrow().get(&term).expect("The term should be originating from this bdd, otherwise the result would be inconsistent anyways").1;
|
||
}
|
||
#[cfg(not(feature = "adhoccounting"))]
|
||
if _memoization {
|
||
self.modelcount_memoization(term).1
|
||
} else {
|
||
self.modelcount_naive(term).1
|
||
}
|
||
}
|
||
|
||
/// Computes the maximal depth of the given sub-diagram.
|
||
///
|
||
/// Intuitively this will compute the longest possible path from **term** to a leaf-node (i.e., ⊥ or ⊤).
|
||
#[allow(dead_code)] // max depth may be used in future heuristics
|
||
pub fn max_depth(&self, term: Term) -> usize {
|
||
#[cfg(feature = "adhoccounting")]
|
||
{
|
||
return self.count_cache.borrow().get(&term).expect("The term should be originating from this bdd, otherwise the result would be inconsistent anyways").2;
|
||
}
|
||
#[cfg(not(feature = "adhoccounting"))]
|
||
match self.count_cache.borrow().get(&term) {
|
||
Some((_mc, _pc, depth)) => *depth,
|
||
None => {
|
||
if term.is_truth_value() {
|
||
0
|
||
} else {
|
||
self.max_depth(self.nodes[term.0].hi())
|
||
.max(self.max_depth(self.nodes[term.0].lo()))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#[allow(dead_code)] // dead code due to more efficient ad-hoc building, still used for a couple of tests
|
||
/// Computes the number of counter-models, models, and variables for a given roBDD
|
||
fn modelcount_naive(&self, term: Term) -> CountNode {
|
||
if term == Term::TOP {
|
||
(ModelCounts::top(), ModelCounts::top(), 0)
|
||
} else if term == Term::BOT {
|
||
(ModelCounts::bot(), ModelCounts::bot(), 0)
|
||
} else {
|
||
let node = &self.nodes[term.0];
|
||
let mut lo_exp = 0u32;
|
||
let mut hi_exp = 0u32;
|
||
let (lo_counts, lo_paths, lodepth) = self.modelcount_naive(node.lo());
|
||
let (hi_counts, hi_paths, hidepth) = self.modelcount_naive(node.hi());
|
||
if lodepth > hidepth {
|
||
hi_exp = (lodepth - hidepth) as u32;
|
||
} else {
|
||
lo_exp = (hidepth - lodepth) as u32;
|
||
}
|
||
(
|
||
(
|
||
lo_counts.cmodels * 2usize.pow(lo_exp) + hi_counts.cmodels * 2usize.pow(hi_exp),
|
||
lo_counts.models * 2usize.pow(lo_exp) + hi_counts.models * 2usize.pow(hi_exp),
|
||
)
|
||
.into(),
|
||
(
|
||
lo_paths.cmodels + hi_paths.cmodels,
|
||
lo_paths.models + hi_paths.models,
|
||
)
|
||
.into(),
|
||
std::cmp::max(lodepth, hidepth) + 1,
|
||
)
|
||
}
|
||
}
|
||
|
||
fn modelcount_memoization(&self, term: Term) -> CountNode {
|
||
if term == Term::TOP {
|
||
(ModelCounts::top(), ModelCounts::top(), 0)
|
||
} else if term == Term::BOT {
|
||
(ModelCounts::bot(), ModelCounts::bot(), 0)
|
||
} else {
|
||
if let Some(result) = self.count_cache.borrow().get(&term) {
|
||
return *result;
|
||
}
|
||
let result = {
|
||
let node = &self.nodes[term.0];
|
||
let mut lo_exp = 0u32;
|
||
let mut hi_exp = 0u32;
|
||
let (lo_counts, lo_paths, lodepth) = self.modelcount_memoization(node.lo());
|
||
let (hi_counts, hi_paths, hidepth) = self.modelcount_memoization(node.hi());
|
||
if lodepth > hidepth {
|
||
hi_exp = (lodepth - hidepth) as u32;
|
||
} else {
|
||
lo_exp = (hidepth - lodepth) as u32;
|
||
}
|
||
(
|
||
(
|
||
lo_counts.cmodels * 2usize.pow(lo_exp)
|
||
+ hi_counts.cmodels * 2usize.pow(hi_exp),
|
||
lo_counts.models * 2usize.pow(lo_exp)
|
||
+ hi_counts.models * 2usize.pow(hi_exp),
|
||
)
|
||
.into(),
|
||
(
|
||
lo_paths.cmodels + hi_paths.cmodels,
|
||
lo_paths.models + hi_paths.models,
|
||
)
|
||
.into(),
|
||
std::cmp::max(lodepth, hidepth) + 1,
|
||
)
|
||
};
|
||
self.count_cache.borrow_mut().insert(term, result);
|
||
result
|
||
}
|
||
}
|
||
|
||
/// Repairs the internal structures after an import.
|
||
pub fn fix_import(&mut self) {
|
||
self.generate_var_dependencies();
|
||
#[cfg(feature = "adhoccounting")]
|
||
{
|
||
self.count_cache
|
||
.borrow_mut()
|
||
.insert(Term::TOP, (ModelCounts::top(), ModelCounts::top(), 0));
|
||
self.count_cache
|
||
.borrow_mut()
|
||
.insert(Term::BOT, (ModelCounts::bot(), ModelCounts::bot(), 0));
|
||
for i in 0..self.nodes.len() {
|
||
log::debug!("fixing Term({})", i);
|
||
self.modelcount_memoization(Term(i));
|
||
}
|
||
}
|
||
}
|
||
|
||
fn generate_var_dependencies(&mut self) {
|
||
#[cfg(feature = "variablelist")]
|
||
self.nodes.iter().for_each(|node| {
|
||
if node.var() >= Var::BOT {
|
||
self.var_deps.push(HashSet::new());
|
||
} else {
|
||
let mut var_set: HashSet<Var> = self.var_deps[node.lo().value()]
|
||
.union(&self.var_deps[node.hi().value()])
|
||
.copied()
|
||
.collect();
|
||
var_set.insert(node.var());
|
||
self.var_deps.push(var_set);
|
||
}
|
||
});
|
||
}
|
||
|
||
/// Returns a [HashSet] of [variables][crate::datatypes::Var], which occur in a given roBDD.
|
||
pub fn var_dependencies(&self, tree: Term) -> HashSet<Var> {
|
||
#[cfg(feature = "variablelist")]
|
||
{
|
||
self.var_deps[tree.value()].clone()
|
||
}
|
||
#[cfg(not(feature = "variablelist"))]
|
||
{
|
||
let node = self.nodes[tree.value()];
|
||
if node.var().is_constant() {
|
||
return HashSet::new();
|
||
}
|
||
let mut var_set = self
|
||
.var_dependencies(node.lo())
|
||
.union(&self.var_dependencies(node.hi()))
|
||
.copied()
|
||
.collect::<HashSet<Var>>();
|
||
var_set.insert(node.var());
|
||
var_set
|
||
}
|
||
}
|
||
|
||
/// Returns the variable impact of a [variable][crate::datatypes::Var] with respect to a given set of roBDDs.
|
||
pub fn passive_var_impact(&self, var: Var, termlist: &[Term]) -> usize {
|
||
termlist.iter().fold(0usize, |acc, val| {
|
||
if self.var_dependencies(*val).contains(&var) {
|
||
acc + 1
|
||
} else {
|
||
acc
|
||
}
|
||
})
|
||
}
|
||
|
||
/// Counts how often another roBDD uses a [variable][crate::datatypes::Var], which occurs in this roBDD.
|
||
pub fn active_var_impact(&self, var: Var, termlist: &[Term]) -> usize {
|
||
(0..termlist.len()).into_iter().fold(0usize, |acc, idx| {
|
||
if self
|
||
.var_dependencies(termlist[var.value()])
|
||
.contains(&Var(idx))
|
||
{
|
||
acc + 1
|
||
} else {
|
||
acc
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
#[cfg_attr(test, mock(using = "test::TestBdd"))]
|
||
impl Bdd {
|
||
/// Restrict the value of a given [variable][crate::datatypes::Var] to **val**.
|
||
pub fn restrict(&mut self, tree: Term, var: Var, val: bool) -> Term {
|
||
if let Some(result) = self.restrict_cache.get(&(tree, var, val)) {
|
||
*result
|
||
} else {
|
||
let node = self.nodes[tree.0];
|
||
#[cfg(feature = "variablelist")]
|
||
{
|
||
if !self.var_deps[tree.value()].contains(&var) {
|
||
return tree;
|
||
}
|
||
}
|
||
#[allow(clippy::collapsible_else_if)]
|
||
// Readability of algorithm > code-elegance
|
||
if node.var() > var || node.var() >= Var::BOT {
|
||
tree
|
||
} else if node.var() < var {
|
||
let lonode = self.restrict(node.lo(), var, val);
|
||
let hinode = self.restrict(node.hi(), var, val);
|
||
let result = self.node(node.var(), lonode, hinode);
|
||
self.restrict_cache.insert((tree, var, val), result);
|
||
result
|
||
} else {
|
||
if val {
|
||
let result = self.restrict(node.hi(), var, val);
|
||
self.restrict_cache.insert((tree, var, val), result);
|
||
result
|
||
} else {
|
||
let result = self.restrict(node.lo(), var, val);
|
||
self.restrict_cache.insert((tree, var, val), result);
|
||
result
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod test {
|
||
use super::*;
|
||
use mimicry::Mock;
|
||
|
||
#[derive(Default, Mock)]
|
||
pub struct TestBdd {}
|
||
impl mimicry::CheckRealCall for TestBdd {}
|
||
impl TestBdd {
|
||
pub fn restrict<'s>(&self, recv: &'s mut Bdd, tree: Term, var: Var, val: bool) -> Term {
|
||
recv.restrict_list(tree, &[(var, val)])
|
||
}
|
||
}
|
||
|
||
macro_rules! genmockguard {
|
||
($func: ident) => {
|
||
let _guard = TestBdd::default().set_as_mock();
|
||
$func();
|
||
};
|
||
}
|
||
|
||
macro_rules! test {
|
||
($func: ident, $func2: ident) => {
|
||
#[test]
|
||
fn $func2() {
|
||
genmockguard!($func);
|
||
}
|
||
};
|
||
}
|
||
|
||
test!(newbdd, mock_newbdd);
|
||
test!(addconst, mock_addconst);
|
||
test!(addvar, mock_addvar);
|
||
test!(use_negation, mock_use_negation);
|
||
test!(use_and, mock_use_and);
|
||
test!(use_or, mock_use_or);
|
||
test!(
|
||
produce_different_conversions,
|
||
mock_produce_different_conversions
|
||
);
|
||
test!(display, mock_display);
|
||
test!(counting, mock_counting);
|
||
|
||
#[test]
|
||
fn newbdd() {
|
||
let bdd = Bdd::new();
|
||
assert_eq!(bdd.nodes.len(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn addconst() {
|
||
let bdd = Bdd::new();
|
||
|
||
assert_eq!(Bdd::constant(true), Term::TOP);
|
||
assert_eq!(Bdd::constant(false), Term::BOT);
|
||
assert_eq!(bdd.nodes.len(), 2);
|
||
}
|
||
|
||
test!(addconst, addconst_list);
|
||
|
||
#[test]
|
||
fn addvar() {
|
||
let mut bdd = Bdd::new();
|
||
assert_eq!(bdd.variable(Var(0)), Term(2));
|
||
assert_eq!(bdd.variable(Var(1)), Term(3));
|
||
assert_eq!(Var(1), Var(1));
|
||
bdd.variable(Var(0));
|
||
assert_eq!(bdd.variable(Var(0)), Term(2));
|
||
}
|
||
|
||
#[test]
|
||
fn use_negation() {
|
||
let mut bdd = Bdd::new();
|
||
let v1 = bdd.variable(Var(0));
|
||
assert_eq!(v1, 2.into());
|
||
assert_eq!(bdd.not(v1), Term(3));
|
||
}
|
||
|
||
#[test]
|
||
fn use_and() {
|
||
let mut bdd = Bdd::new();
|
||
let v1 = bdd.variable(Var(0));
|
||
let v2 = bdd.variable(Var(1));
|
||
let v3 = bdd.variable(Var(2));
|
||
|
||
let a1 = bdd.and(v1, v2);
|
||
let a2 = bdd.and(a1, v3);
|
||
assert_eq!(a2, Term(7));
|
||
}
|
||
|
||
#[test]
|
||
fn use_or() {
|
||
let mut bdd = Bdd::new();
|
||
|
||
let v1 = bdd.variable(Var(0));
|
||
let v2 = bdd.variable(Var(1));
|
||
let v3 = bdd.variable(Var(2));
|
||
|
||
let a1 = bdd.and(v1, v2);
|
||
let a2 = bdd.or(a1, v3);
|
||
assert_eq!(a2, Term(7));
|
||
}
|
||
|
||
#[test]
|
||
fn produce_different_conversions() {
|
||
let mut bdd = Bdd::new();
|
||
|
||
let v1 = bdd.variable(Var(0));
|
||
let v2 = bdd.variable(Var(1));
|
||
|
||
assert_eq!(v1, Term(2));
|
||
assert_eq!(v2, Term(3));
|
||
|
||
let t1 = bdd.and(v1, v2);
|
||
let nt1 = bdd.not(t1);
|
||
let ft = bdd.or(v1, nt1);
|
||
|
||
assert_eq!(ft, Term::TOP);
|
||
|
||
let v3 = bdd.variable(Var(2));
|
||
let nv3 = bdd.not(v3);
|
||
assert_eq!(bdd.and(v3, nv3), Term::BOT);
|
||
|
||
let conj = bdd.and(v1, v2);
|
||
assert_eq!(bdd.restrict_list(conj, &[(Var(0), true)]), v2);
|
||
assert_eq!(bdd.restrict(conj, Var(0), false), Term::BOT);
|
||
assert_eq!(bdd.restrict(conj, Var(0), true), v2);
|
||
assert_eq!(bdd.restrict_list(conj, &[(Var(0), false)]), Term::BOT);
|
||
|
||
let a = bdd.and(v3, v2);
|
||
let b = bdd.or(v2, v1);
|
||
|
||
let con1 = bdd.and(a, conj);
|
||
|
||
let end = bdd.or(con1, b);
|
||
log::debug!("Restrict test: restrict({},{},false)", end, Var(1));
|
||
let x = bdd.restrict(end, Var(1), false);
|
||
assert_eq!(x, Term(2));
|
||
}
|
||
|
||
#[test]
|
||
fn display() {
|
||
let mut bdd = Bdd::new();
|
||
|
||
let v1 = bdd.variable(Var(0));
|
||
let v2 = bdd.variable(Var(1));
|
||
let v3 = bdd.variable(Var(2));
|
||
|
||
let a1 = bdd.and(v1, v2);
|
||
let _a2 = bdd.or(a1, v3);
|
||
|
||
assert_eq!(format!("{}", bdd), " \n0 BddNode: Var(18446744073709551614), lo: Term(0), hi: Term(0)\n1 BddNode: Var(18446744073709551615), lo: Term(1), hi: Term(1)\n2 BddNode: Var(0), lo: Term(0), hi: Term(1)\n3 BddNode: Var(1), lo: Term(0), hi: Term(1)\n4 BddNode: Var(2), lo: Term(0), hi: Term(1)\n5 BddNode: Var(0), lo: Term(0), hi: Term(3)\n6 BddNode: Var(1), lo: Term(4), hi: Term(1)\n7 BddNode: Var(0), lo: Term(4), hi: Term(6)\n");
|
||
}
|
||
|
||
#[test]
|
||
fn counting() {
|
||
let mut bdd = Bdd::new();
|
||
|
||
let v1 = bdd.variable(Var(0));
|
||
let v2 = bdd.variable(Var(1));
|
||
let v3 = bdd.variable(Var(2));
|
||
|
||
let formula1 = bdd.and(v1, v2);
|
||
let formula2 = bdd.or(v1, v2);
|
||
let formula3 = bdd.xor(v1, v2);
|
||
let formula4 = bdd.and(v3, formula2);
|
||
|
||
#[cfg(feature = "adhoccountmodels")]
|
||
assert_eq!(bdd.models(v1, false), (1, 1).into());
|
||
let mut x = bdd.count_cache.get_mut().iter().collect::<Vec<_>>();
|
||
x.sort();
|
||
log::debug!("{:?}", formula1);
|
||
for x in bdd.nodes.iter().enumerate() {
|
||
log::debug!("{:?}", x);
|
||
}
|
||
log::debug!("{:?}", x);
|
||
#[cfg(feature = "adhoccountmodels")]
|
||
{
|
||
assert_eq!(bdd.models(formula1, false), (3, 1).into());
|
||
assert_eq!(bdd.models(formula2, false), (1, 3).into());
|
||
assert_eq!(bdd.models(formula3, false), (2, 2).into());
|
||
assert_eq!(bdd.models(formula4, false), (5, 3).into());
|
||
assert_eq!(bdd.models(Term::TOP, false), (0, 1).into());
|
||
assert_eq!(bdd.models(Term::BOT, false), (1, 0).into());
|
||
|
||
assert_eq!(bdd.models(v1, true), (1, 1).into());
|
||
assert_eq!(bdd.models(formula1, true), (3, 1).into());
|
||
assert_eq!(bdd.models(formula2, true), (1, 3).into());
|
||
assert_eq!(bdd.models(formula3, true), (2, 2).into());
|
||
assert_eq!(bdd.models(formula4, true), (5, 3).into());
|
||
assert_eq!(bdd.models(Term::TOP, true), (0, 1).into());
|
||
assert_eq!(bdd.models(Term::BOT, true), (1, 0).into());
|
||
}
|
||
|
||
assert_eq!(bdd.paths(formula1, false), (2, 1).into());
|
||
assert_eq!(bdd.paths(formula2, false), (1, 2).into());
|
||
assert_eq!(bdd.paths(formula3, false), (2, 2).into());
|
||
assert_eq!(bdd.paths(formula4, false), (3, 2).into());
|
||
assert_eq!(bdd.paths(Term::TOP, false), (0, 1).into());
|
||
assert_eq!(bdd.paths(Term::BOT, false), (1, 0).into());
|
||
|
||
assert_eq!(bdd.paths(v1, true), (1, 1).into());
|
||
assert_eq!(bdd.paths(formula1, true), (2, 1).into());
|
||
assert_eq!(bdd.paths(formula2, true), (1, 2).into());
|
||
assert_eq!(bdd.paths(formula3, true), (2, 2).into());
|
||
assert_eq!(bdd.paths(formula4, true), (3, 2).into());
|
||
assert_eq!(bdd.paths(Term::TOP, true), (0, 1).into());
|
||
assert_eq!(bdd.paths(Term::BOT, true), (1, 0).into());
|
||
|
||
assert_eq!(bdd.modelcount_naive(v1), ((1, 1).into(), (1, 1).into(), 1));
|
||
assert_eq!(
|
||
bdd.modelcount_naive(formula1),
|
||
((3, 1).into(), (2, 1).into(), 2)
|
||
);
|
||
assert_eq!(
|
||
bdd.modelcount_naive(formula2),
|
||
((1, 3).into(), (1, 2).into(), 2)
|
||
);
|
||
assert_eq!(
|
||
bdd.modelcount_naive(formula3),
|
||
((2, 2).into(), (2, 2).into(), 2)
|
||
);
|
||
assert_eq!(
|
||
bdd.modelcount_naive(formula4),
|
||
((5, 3).into(), (3, 2).into(), 3)
|
||
);
|
||
assert_eq!(
|
||
bdd.modelcount_naive(Term::TOP),
|
||
((0, 1).into(), (0, 1).into(), 0)
|
||
);
|
||
assert_eq!(
|
||
bdd.modelcount_naive(Term::BOT),
|
||
((1, 0).into(), (1, 0).into(), 0)
|
||
);
|
||
|
||
#[cfg(feature = "adhoccountmodels")]
|
||
{
|
||
assert_eq!(
|
||
bdd.modelcount_naive(formula4),
|
||
bdd.modelcount_memoization(formula4)
|
||
);
|
||
|
||
assert_eq!(bdd.modelcount_naive(v1), bdd.modelcount_memoization(v1));
|
||
assert_eq!(
|
||
bdd.modelcount_naive(formula1),
|
||
bdd.modelcount_memoization(formula1)
|
||
);
|
||
assert_eq!(
|
||
bdd.modelcount_naive(formula2),
|
||
bdd.modelcount_memoization(formula2)
|
||
);
|
||
assert_eq!(
|
||
bdd.modelcount_naive(formula3),
|
||
bdd.modelcount_memoization(formula3)
|
||
);
|
||
assert_eq!(
|
||
bdd.modelcount_naive(Term::TOP),
|
||
bdd.modelcount_memoization(Term::TOP)
|
||
);
|
||
assert_eq!(
|
||
bdd.modelcount_naive(Term::BOT),
|
||
bdd.modelcount_memoization(Term::BOT)
|
||
);
|
||
}
|
||
|
||
assert_eq!(bdd.max_depth(Term::BOT), 0);
|
||
assert_eq!(bdd.max_depth(v1), 1);
|
||
assert_eq!(bdd.max_depth(formula3), 2);
|
||
assert_eq!(bdd.max_depth(formula4), 3);
|
||
}
|
||
|
||
#[cfg(feature = "variablelist")]
|
||
#[test]
|
||
fn generate_var_dependencies() {
|
||
let mut bdd = Bdd::new();
|
||
|
||
let v1 = bdd.variable(Var(0));
|
||
let v2 = bdd.variable(Var(1));
|
||
let v3 = bdd.variable(Var(2));
|
||
|
||
let formula1 = bdd.and(v1, v2);
|
||
let formula2 = bdd.or(v1, v2);
|
||
let formula3 = bdd.xor(v1, v2);
|
||
let formula4 = bdd.and(v3, formula2);
|
||
|
||
bdd.iff(formula1, formula3);
|
||
bdd.not(formula4);
|
||
|
||
let constructed = bdd.var_deps.clone();
|
||
bdd.var_deps = Vec::new();
|
||
bdd.generate_var_dependencies();
|
||
|
||
constructed
|
||
.iter()
|
||
.zip(bdd.var_deps.iter())
|
||
.for_each(|(left, right)| {
|
||
assert!(left == right);
|
||
});
|
||
|
||
assert_eq!(
|
||
bdd.passive_var_impact(Var(0), &[formula1, formula2, formula3, formula4]),
|
||
4
|
||
);
|
||
assert_eq!(
|
||
bdd.passive_var_impact(Var(2), &[formula1, formula2, formula3, formula4]),
|
||
1
|
||
);
|
||
assert_eq!(
|
||
bdd.passive_var_impact(Var(2), &[formula1, formula2, formula3]),
|
||
0
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn var_impact() {
|
||
let mut bdd = Bdd::new();
|
||
let v1 = bdd.variable(Var(0));
|
||
let v2 = bdd.variable(Var(1));
|
||
let v3 = bdd.variable(Var(2));
|
||
|
||
let formula1 = bdd.and(v1, v2);
|
||
let formula2 = bdd.or(v1, v2);
|
||
|
||
let ac: Vec<Term> = vec![formula1, formula2, v3];
|
||
|
||
assert_eq!(bdd.passive_var_impact(Var(0), &ac), 2);
|
||
assert_eq!(bdd.passive_var_impact(Var(1), &ac), 2);
|
||
assert_eq!(bdd.passive_var_impact(Var(2), &ac), 1);
|
||
|
||
assert_eq!(bdd.active_var_impact(Var(0), &ac), 2);
|
||
assert_eq!(bdd.active_var_impact(Var(2), &ac), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn interpretations() {
|
||
let mut bdd = Bdd::new();
|
||
|
||
let v1 = bdd.variable(Var(0));
|
||
let v2 = bdd.variable(Var(1));
|
||
|
||
let formula1 = bdd.and(v1, v2);
|
||
let formula2 = bdd.xor(v1, v2);
|
||
|
||
assert_eq!(
|
||
bdd.interpretations(formula1, true, Var(2), &[], &[]),
|
||
vec![(vec![], vec![Var(0), Var(1)])]
|
||
);
|
||
|
||
assert_eq!(
|
||
bdd.interpretations(formula1, true, Var(0), &[], &[]),
|
||
vec![(vec![], vec![Var(0), Var(1)])]
|
||
);
|
||
|
||
assert_eq!(
|
||
bdd.interpretations(formula1, false, Var(2), &[], &[]),
|
||
vec![(vec![Var(1)], vec![Var(0)]), (vec![Var(0)], vec![])]
|
||
);
|
||
|
||
assert_eq!(
|
||
bdd.interpretations(formula1, false, Var(0), &[], &[]),
|
||
vec![(vec![Var(0)], vec![])]
|
||
);
|
||
|
||
assert_eq!(
|
||
bdd.interpretations(formula2, false, Var(2), &[], &[]),
|
||
vec![
|
||
(vec![], vec![Var(0), Var(1)]),
|
||
(vec![Var(0), Var(1)], vec![])
|
||
]
|
||
);
|
||
|
||
assert_eq!(
|
||
bdd.interpretations(formula2, true, Var(2), &[], &[]),
|
||
vec![(vec![Var(1)], vec![Var(0)]), (vec![Var(0)], vec![Var(1)])]
|
||
);
|
||
|
||
assert_eq!(
|
||
bdd.interpretations(formula2, true, Var(0), &[], &[]),
|
||
vec![(vec![Var(1)], vec![Var(0)])]
|
||
);
|
||
|
||
assert_eq!(
|
||
bdd.interpretations(Term::TOP, true, Var(0), &[], &[]),
|
||
vec![]
|
||
);
|
||
}
|
||
}
|