#![cfg_attr(miri, allow(unused))]
mod boolean_prop;
mod util;
use std::fmt;
use rustc_hash::FxHashMap;
use oxidd::bcdd::BCDDFunction;
use oxidd::bdd::BDDFunction;
use oxidd::util::OptBool;
use oxidd::zbdd::ZBDDFunction;
use oxidd::{
BooleanFunction, BooleanFunctionQuant, BooleanOperator, BooleanVecSet, Function, FunctionSubst,
HasWorkers, InnerNode, Manager, ManagerRef, VarNo, WorkerPool,
};
use boolean_prop::Prop;
use util::progress::Progress;
#[test]
fn bdd_node_count() {
let mref = oxidd::bdd::new_manager(1024, 128, 2);
let (x0, x1, ff, tt) = mref.with_manager_exclusive(|manager| {
manager.add_named_vars(["x0", "x1"]).unwrap();
(
BDDFunction::var(manager, 0).unwrap(),
BDDFunction::var(manager, 1).unwrap(),
BDDFunction::f(manager),
BDDFunction::t(manager),
)
});
assert_eq!(ff.node_count(), 1);
assert_eq!(tt.node_count(), 1);
assert_eq!(x0.node_count(), 3);
assert_eq!(x1.node_count(), 3);
let g = x0.and(&x1).unwrap().not().unwrap();
assert_eq!(g.node_count(), 4);
}
#[test]
fn bcdd_node_count() {
let mref = oxidd::bcdd::new_manager(1024, 128, 2);
let (x0, x1, ff, tt) = mref.with_manager_exclusive(|manager| {
manager.add_named_vars(["x0", "x1"]).unwrap();
(
BCDDFunction::var(manager, 0).unwrap(),
BCDDFunction::var(manager, 1).unwrap(),
BCDDFunction::f(manager),
BCDDFunction::t(manager),
)
});
assert_eq!(ff.node_count(), 1);
assert_eq!(tt.node_count(), 1);
assert_eq!(x0.node_count(), 2);
assert_eq!(x1.node_count(), 2);
let g = x0.and(&x1).unwrap().not().unwrap();
assert_eq!(g.node_count(), 3);
}
#[test]
fn zbdd_node_count() {
let mref = oxidd::zbdd::new_manager(1024, 128, 2);
let (x0, x1, ee, bb) = mref.with_manager_exclusive(|manager| {
manager.add_named_vars(["x0", "x1"]).unwrap();
(
ZBDDFunction::singleton(manager, 0).unwrap(),
ZBDDFunction::singleton(manager, 1).unwrap(),
ZBDDFunction::empty(manager),
ZBDDFunction::base(manager),
)
});
assert_eq!(ee.node_count(), 1);
assert_eq!(bb.node_count(), 1);
assert_eq!(x0.node_count(), 3);
assert_eq!(x1.node_count(), 3);
let p = x0.union(&x1).unwrap();
assert_eq!(p.node_count(), 4);
}
#[test]
fn bdd_cofactors() {
let mref = oxidd::bdd::new_manager(1024, 128, 2);
let (x0, x1, ff, tt) = mref.with_manager_exclusive(|manager| {
manager.add_named_vars(["x0", "x1"]).unwrap();
(
BDDFunction::var(manager, 0).unwrap(),
BDDFunction::var(manager, 1).unwrap(),
BDDFunction::f(manager),
BDDFunction::t(manager),
)
});
let g = x0.and(&x1).unwrap().not().unwrap();
assert!(ff.cofactors().is_none());
assert!(ff.cofactor_true().is_none());
assert!(ff.cofactor_false().is_none());
assert!(tt.cofactors().is_none());
assert!(tt.cofactor_true().is_none());
assert!(tt.cofactor_false().is_none());
for v in [&x0, &x1] {
assert_eq!(v.node_count(), 3);
let (vt, vf) = v.cofactors().unwrap();
assert!(vt == v.cofactor_true().unwrap());
assert!(vf == v.cofactor_false().unwrap());
assert!(vt == tt);
assert!(vf == ff);
}
let (gt, gf) = g.cofactors().unwrap();
assert!(gt == g.cofactor_true().unwrap());
assert!(gf == g.cofactor_false().unwrap());
assert!(gt == x1.not().unwrap());
assert!(gf == tt);
}
#[test]
fn bcdd_cofactors() {
let mref = oxidd::bcdd::new_manager(1024, 128, 2);
let (x0, x1, ff, tt) = mref.with_manager_exclusive(|manager| {
manager.add_named_vars(["x0", "x1"]).unwrap();
(
BCDDFunction::var(manager, 0).unwrap(),
BCDDFunction::var(manager, 1).unwrap(),
BCDDFunction::f(manager),
BCDDFunction::t(manager),
)
});
let g = x0.and(&x1).unwrap().not().unwrap();
assert!(ff.cofactors().is_none());
assert!(ff.cofactor_true().is_none());
assert!(ff.cofactor_false().is_none());
assert!(tt.cofactors().is_none());
assert!(tt.cofactor_true().is_none());
assert!(tt.cofactor_false().is_none());
for v in [&x0, &x1] {
let (vt, vf) = v.cofactors().unwrap();
assert!(vt == v.cofactor_true().unwrap());
assert!(vf == v.cofactor_false().unwrap());
assert!(vt == tt);
assert!(vf == ff);
}
let (gt, gf) = g.cofactors().unwrap();
assert!(gt == g.cofactor_true().unwrap());
assert!(gf == g.cofactor_false().unwrap());
assert!(gt == x1.not().unwrap());
assert!(gf == tt);
}
#[test]
fn zbdd_cofactors() {
let mref = oxidd::zbdd::new_manager(1024, 128, 2);
let (x0, x1, ee, bb) = mref.with_manager_exclusive(|manager| {
manager.add_named_vars(["x0", "x1"]).unwrap();
(
ZBDDFunction::singleton(manager, 0).unwrap(),
ZBDDFunction::singleton(manager, 1).unwrap(),
ZBDDFunction::empty(manager),
ZBDDFunction::base(manager),
)
});
let g = x0.union(&x1).unwrap();
assert!(ee.cofactors().is_none());
assert!(ee.cofactor_true().is_none());
assert!(ee.cofactor_false().is_none());
assert!(bb.cofactors().is_none());
assert!(bb.cofactor_true().is_none());
assert!(bb.cofactor_false().is_none());
for v in [&x0, &x1] {
let (vt, vf) = v.cofactors().unwrap();
assert!(vt == v.cofactor_true().unwrap());
assert!(vf == v.cofactor_false().unwrap());
assert!(vt == bb);
assert!(vf == ee);
}
let (gt, gf) = g.cofactors().unwrap();
assert!(gt == g.cofactor_true().unwrap());
assert!(gf == g.cofactor_false().unwrap());
assert!(gt == bb);
assert!(gf == x1);
}
fn test_simple_formulas<B: BooleanFunction>(manager: &B::ManagerRef) {
use Prop::*;
for op1 in [false, true] {
Prop::from(op1).build_and_check::<B>(manager, &[]).unwrap();
Not(Box::new(op1.into()))
.build_and_check::<B>(manager, &[])
.unwrap();
for op2 in [false, true] {
for binop in [And, Or, Xor, Equiv, Nand, Nor, Imp, ImpStrict] {
binop(Box::new(op1.into()), Box::new(op2.into()))
.build_and_check::<B>(manager, &[])
.unwrap();
}
for op3 in [false, true] {
Ite(
Box::new(op1.into()),
Box::new(op2.into()),
Box::new(op3.into()),
)
.build_and_check::<B>(manager, &[])
.unwrap();
}
}
}
}
#[test]
fn bdd_simple_formulas_t1() {
let mref = oxidd::bdd::new_manager(65536, 1024, 1);
test_simple_formulas::<BDDFunction>(&mref);
}
#[test]
fn bdd_simple_formulas_t2() {
let mref = oxidd::bdd::new_manager(65536, 1024, 2);
test_simple_formulas::<BDDFunction>(&mref);
}
#[test]
fn bcdd_simple_formulas_t1() {
let mref = oxidd::bcdd::new_manager(65536, 1024, 1);
test_simple_formulas::<BCDDFunction>(&mref);
}
#[test]
fn bcdd_simple_formulas_t2() {
let mref = oxidd::bcdd::new_manager(65536, 1024, 2);
test_simple_formulas::<BCDDFunction>(&mref);
}
#[test]
fn zbdd_simple_formulas_t1() {
let mref = oxidd::zbdd::new_manager(65536, 1024, 1);
test_simple_formulas::<ZBDDFunction>(&mref);
}
#[test]
fn zbdd_simple_formulas_t2() {
let mref = oxidd::zbdd::new_manager(65536, 1024, 2);
test_simple_formulas::<ZBDDFunction>(&mref);
}
fn make_vars<B: BooleanFunction>(mref: &B::ManagerRef, n: VarNo) -> Vec<B> {
mref.with_manager_exclusive(|manager| {
manager.add_vars(n);
(0..n).map(|i| B::var(manager, i).unwrap()).collect()
})
}
#[test]
fn bcdd_restrict() {
let mref = oxidd::bcdd::new_manager(1024, 1024, 2);
let vars = make_vars::<BCDDFunction>(&mref, 3);
use Prop::*;
let formulas = [
Restrict(
0b010,
0b001,
Box::new(Or(Box::new(Var(0)), Box::new(Var(1)))),
),
Restrict(0b110, 0, Box::new(Var(0))),
Restrict(0b110, 0, Box::new(Not(Box::new(Var(0))))),
Restrict(0b0, 0b10, Box::new(And(Box::new(Var(0)), Box::new(Var(1))))),
Restrict(
0b100,
0b001,
Box::new(And(Box::new(Var(1)), Box::new(Var(2)))),
),
];
for formula in formulas {
formula.build_and_check_quant(&mref, &vars).unwrap();
}
}
fn test_prop_depth2<B: BooleanFunction>(manager: &B::ManagerRef, vars: &[B]) {
assert!(vars.len() < 32);
let mut f = Prop::False;
let mut progress = Progress::new(38_493_515);
loop {
f.build_and_check(manager, vars).unwrap();
progress.step();
if !f.next::<false>(2, vars.len() as u32) {
break;
}
}
progress.done();
}
fn test_prop_depth2_quant<B: BooleanFunctionQuant>(manager: &B::ManagerRef, vars: &[B]) {
assert!(vars.len() < 32);
let mut f = Prop::False;
let mut progress = Progress::new(208_194_485);
loop {
f.build_and_check_quant(manager, vars).unwrap();
progress.step();
if !f.next::<true>(2, vars.len() as u32) {
break;
}
}
progress.done();
}
#[test]
#[ignore]
fn bdd_prop_depth2_3vars_t1() {
let mref = oxidd::bdd::new_manager(65536, 1024, 1);
let vars = make_vars::<BDDFunction>(&mref, 3);
test_prop_depth2_quant(&mref, &vars);
}
#[test]
#[ignore]
fn bdd_prop_depth2_3vars_t2() {
let mref = oxidd::bdd::new_manager(65536, 1024, 2);
let vars = make_vars::<BDDFunction>(&mref, 3);
test_prop_depth2_quant(&mref, &vars);
}
#[test]
#[ignore]
fn bcdd_prop_depth2_3vars_t1() {
let mref = oxidd::bcdd::new_manager(65536, 1024, 1);
let vars = make_vars::<BCDDFunction>(&mref, 3);
test_prop_depth2_quant(&mref, &vars);
}
#[test]
#[ignore]
fn bcdd_prop_depth2_3vars_t2() {
let mref = oxidd::bcdd::new_manager(65536, 1024, 2);
let vars = make_vars::<BCDDFunction>(&mref, 3);
test_prop_depth2_quant(&mref, &vars);
}
#[test]
#[ignore]
fn zbdd_prop_depth2_3vars_t1() {
let mref = oxidd::zbdd::new_manager(65536, 1024, 1);
let vars = make_vars::<ZBDDFunction>(&mref, 3);
test_prop_depth2(&mref, &vars);
}
#[test]
#[ignore]
fn zbdd_prop_depth2_3vars_t2() {
let mref = oxidd::zbdd::new_manager(65536, 1024, 2);
let vars = make_vars::<ZBDDFunction>(&mref, 3);
test_prop_depth2(&mref, &vars);
}
type ExplicitBFunc = u32;
struct TestAllBooleanFunctions<'a, B: BooleanFunction> {
mref: &'a B::ManagerRef,
nvars: u32,
boolean_functions: Vec<B>,
dd_to_boolean_func: FxHashMap<B, ExplicitBFunc>,
var_functions: Vec<ExplicitBFunc>,
}
impl<'a, B: BooleanFunction> TestAllBooleanFunctions<'a, B> {
#[track_caller]
fn check(
&self,
desc: impl fmt::Display,
actual: ExplicitBFunc,
expected: ExplicitBFunc,
operands: &[ExplicitBFunc],
sets: &[u32],
) {
if actual != expected {
let vars = self.nvars;
let mut columns = Vec::with_capacity(operands.len() + 2);
let op_it = operands.iter().copied();
if operands.len() <= 3 {
columns.extend(["f", "g", "h"].iter().map(|n| n.to_string()).zip(op_it))
} else {
columns.extend((0..operands.len()).map(|i| format!("f{i}")).zip(op_it))
};
columns.push(("expected".to_string(), expected));
columns.push(("actual".to_string(), actual));
let table = util::debug::TruthTable {
vars,
columns: &columns,
};
if sets.is_empty() {
panic!("Operation {desc} failed\n\n{table}");
} else {
panic!(
"Operation {desc} failed\n\n{table}\nwith {:?}",
util::debug::SetList { vars, sets }
);
}
}
}
#[track_caller]
fn panic(&self, desc: impl fmt::Display, columns: &[(impl AsRef<str>, ExplicitBFunc)]) -> ! {
panic!(
"{desc}\n\n{}",
util::debug::TruthTable {
vars: self.nvars,
columns,
}
)
}
#[track_caller]
fn check_cond(
&self,
cond: bool,
desc: impl fmt::Display,
columns: &[(impl AsRef<str>, ExplicitBFunc)],
) {
if !cond {
self.panic(desc, columns)
}
}
pub fn init(mref: &'a B::ManagerRef) -> Self {
let nvars = mref.with_manager_shared(|manager| manager.num_vars());
assert!(
ExplicitBFunc::BITS.ilog2() >= nvars,
"too many variables, only {} are possible",
ExplicitBFunc::BITS.ilog2()
);
let num_assignments = 1u32 << nvars;
let num_functions: ExplicitBFunc = 1 << num_assignments;
let mut boolean_functions = Vec::with_capacity(num_functions as usize);
let mut dd_to_boolean_func =
FxHashMap::with_capacity_and_hasher(num_functions as usize, Default::default());
mref.with_manager_shared(|manager| {
for explicit_f in 0..num_functions {
let mut f = B::f(manager);
for assignment in 0..num_assignments {
if explicit_f & (1 << assignment) == 0 {
continue; }
let mut cube = B::t(manager);
for var in 0..nvars {
if assignment & (1 << var) != 0 {
cube = cube.and(&B::var(manager, var).unwrap()).unwrap();
} else {
cube = cube.and(&B::not_var(manager, var).unwrap()).unwrap();
}
}
f = f.or(&cube).unwrap();
}
for assignment in 0..(1u32 << nvars) {
let expected = explicit_f & (1 << assignment) != 0;
let actual = f.eval((0..nvars).map(|i| (i, assignment & (1 << i) != 0)));
assert_eq!(actual, expected);
}
boolean_functions.push(f.clone());
let res = dd_to_boolean_func.insert(f, explicit_f);
assert!(
res.is_none(),
"two different Boolean functions have the same representation"
);
}
});
let var_functions: Vec<ExplicitBFunc> = (0..nvars)
.map(|i| {
let mut f = 0;
for assignment in 0..num_assignments {
f |= (((assignment >> i) & 1) as ExplicitBFunc) << assignment;
}
f
})
.collect();
Self {
mref,
nvars,
boolean_functions,
dd_to_boolean_func,
var_functions,
}
}
fn make_var_set(&self, vars: u32) -> ExplicitBFunc {
let mut conj = (1 << (1 << self.nvars)) - 1;
for (i, &var) in self.var_functions.iter().enumerate() {
if vars & (1 << i) != 0 {
conj &= var;
}
}
conj
}
fn make_cube(&self, positive: u32, negative: u32) -> ExplicitBFunc {
assert_eq!(positive & negative, 0);
let mut cube = (1 << (1 << self.nvars)) - 1; for (i, &var) in self.var_functions.iter().enumerate() {
if (positive >> i) & 1 != 0 {
cube &= var;
} else if (negative >> i) & 1 != 0 {
cube &= !var;
}
}
cube
}
pub fn basic(&self) {
let nvars = self.nvars;
let num_assignments = 1u32 << nvars;
let num_functions: ExplicitBFunc = 1 << num_assignments;
let func_mask = num_functions - 1;
self.mref.with_manager_shared(|manager| {
assert!(B::f(manager) == self.boolean_functions[0]);
assert!(&B::t(manager) == self.boolean_functions.last().unwrap());
for (i, &expected) in self.var_functions.iter().enumerate() {
let i = i as VarNo;
let actual = self.dd_to_boolean_func[&B::var(manager, i).unwrap()];
self.check("var", actual, expected, &[], &[]);
let actual = self.dd_to_boolean_func[&B::not_var(manager, i).unwrap()];
self.check("not var", actual, expected ^ func_mask, &[], &[]);
}
});
for (f_explicit, f) in self.boolean_functions.iter().enumerate() {
let f_explicit = f_explicit as ExplicitBFunc;
let expected = !f_explicit & func_mask;
let actual = self.dd_to_boolean_func[&f.not().unwrap()];
self.check("¬f", actual, expected, &[f_explicit], &[]);
for (g_explicit, g) in self.boolean_functions.iter().enumerate() {
let g_explicit = g_explicit as ExplicitBFunc;
let expected = f_explicit & g_explicit;
let actual = self.dd_to_boolean_func[&f.and(g).unwrap()];
self.check("f ∧ g", actual, expected, &[f_explicit, g_explicit], &[]);
let expected = f_explicit | g_explicit;
let actual = self.dd_to_boolean_func[&f.or(g).unwrap()];
self.check("f ∨ g", actual, expected, &[f_explicit, g_explicit], &[]);
let expected = f_explicit ^ g_explicit;
let actual = self.dd_to_boolean_func[&f.xor(g).unwrap()];
self.check("f ⊕ g", actual, expected, &[f_explicit, g_explicit], &[]);
let expected = !(f_explicit ^ g_explicit) & func_mask;
let actual = self.dd_to_boolean_func[&f.equiv(g).unwrap()];
self.check("f ↔ g", actual, expected, &[f_explicit, g_explicit], &[]);
let expected = !(f_explicit & g_explicit) & func_mask;
let actual = self.dd_to_boolean_func[&f.nand(g).unwrap()];
self.check("f ⊼ g", actual, expected, &[f_explicit, g_explicit], &[]);
let expected = !(f_explicit | g_explicit) & func_mask;
let actual = self.dd_to_boolean_func[&f.nor(g).unwrap()];
self.check("f ⊽ g", actual, expected, &[f_explicit, g_explicit], &[]);
let expected = (!f_explicit | g_explicit) & func_mask;
let actual = self.dd_to_boolean_func[&f.imp(g).unwrap()];
self.check("f → g", actual, expected, &[f_explicit, g_explicit], &[]);
let expected = !f_explicit & g_explicit;
let actual = self.dd_to_boolean_func[&f.imp_strict(g).unwrap()];
self.check("f < g", actual, expected, &[f_explicit, g_explicit], &[]);
for (h_explicit, h) in self.boolean_functions.iter().enumerate() {
let h_explicit = h_explicit as ExplicitBFunc;
self.check(
"if f { g } else { h }",
self.dd_to_boolean_func[&f.ite(g, h).unwrap()],
(f_explicit & g_explicit) | (!f_explicit & h_explicit),
&[f_explicit, g_explicit, h_explicit],
&[],
);
}
}
for assignment in 0..num_assignments {
let mut choice_requested = 0u32;
let cube = f.pick_cube(|manager, edge, level| {
manager
.get_node(edge)
.unwrap_inner()
.assert_level_matches(level);
if choice_requested & (1 << level) != 0 {
panic!("choice requested twice for x{level}");
} else {
choice_requested |= 1 << level;
}
assignment & (1u32 << level) != 0
});
let mut choice_requested_sym = 0u32;
let dd_cube = f
.pick_cube_dd(|manager, edge, level| {
manager
.get_node(edge)
.unwrap_inner()
.assert_level_matches(level);
if choice_requested_sym & (1 << level) != 0 {
panic!("choice requested twice for x{level}");
} else {
choice_requested_sym |= 1 << level;
}
assignment & (1u32 << level) != 0
})
.unwrap();
let actual = self.dd_to_boolean_func[&dd_cube];
if f_explicit == 0 {
self.check("f.pick_cube_dd(..)", actual, 0, &[f_explicit], &[]);
assert_eq!(cube, None);
assert_eq!(choice_requested, 0);
} else {
let cube =
cube.expect("f.pick_cube(..) returned None for a satisfiable function");
assert_eq!(cube.len(), nvars as usize);
self.check_cond(
actual & !f_explicit == 0,
"f.pick_cube_dd(..) does not imply f",
&[("f", f_explicit), ("f.pick_cube_dd(..)", actual)],
);
let mut cube_func = func_mask;
for (var, (&literal, &var_func)) in
cube.iter().zip(&self.var_functions).enumerate()
{
if choice_requested & (1 << var) != 0 {
assert_eq!(
literal,
OptBool::from(assignment & (1 << var) != 0),
"If a choice was requested, the cube should reflect the choice"
);
} else if literal != OptBool::None {
let flipped = if literal == OptBool::True {
actual >> (1 << var)
} else {
actual << (1 << var)
};
self.check_cond(
flipped & !f_explicit != 0,
format_args!("f.pick_cube_dd(..) should call the choice function or leave a don't care for x{var}"),
&[
("f", f_explicit),
("f.pick_cube_dd(..)", actual),
("cube with flipped literal", flipped),
],
);
}
match literal {
OptBool::False => cube_func &= !var_func,
OptBool::True => cube_func &= var_func,
_ => {}
}
}
self.check_cond(
cube_func & !f_explicit == 0,
"f.pick_cube(..) does not imply f",
&[("f", f_explicit), ("f.pick_cube(..)", actual)],
);
self.check(
"f.pick_cube_dd(choice_fn) does not agree with f.pick_cube([], choice_fn)",
actual,
cube_func,
&[f_explicit],
&[assignment],
);
}
assert_eq!(
choice_requested, choice_requested_sym,
"pick_cube should request a choice iff pick_cube_dd requests a choice"
);
}
for pos in 0..num_assignments {
for neg in 0..num_assignments {
if pos & neg != 0 {
continue;
}
let literal_set_explicit = self.make_cube(pos, neg);
let literal_set = &self.boolean_functions[literal_set_explicit as usize];
let actual = self.dd_to_boolean_func[&f.pick_cube_dd_set(literal_set).unwrap()];
if f_explicit == 0 {
self.check(
"f.pick_cube_dd_set(g)",
actual,
0,
&[f_explicit, literal_set_explicit],
&[],
);
} else {
self.check_cond(
actual & !f_explicit == 0,
"f.pick_cube_dd_set(literal_set) does not imply f",
&[
("f", f_explicit),
("literal_set", literal_set_explicit),
("f.pick_cube_dd_set(literal_set)", actual),
],
);
for (var, var_func) in self.var_functions.iter().enumerate() {
if (actual & var_func) >> (1 << var) == actual & !var_func {
continue; }
let selected = if actual & var_func == 0 {
if pos & (1 << var) == 0 {
continue; }
false
} else if actual & !var_func == 0 {
if neg & (1 << var) == 0 {
continue; }
true
} else {
self.panic(
format_args!("f.pick_cube_dd_set(literal_set) is not a cube (checking x{var})"),
&[
("f", f_explicit),
("literal_set", literal_set_explicit),
("f.pick_cube_dd_set(literal_set)", actual),
],
)
};
let flipped = if selected {
actual >> (1 << var)
} else {
actual << (1 << var)
};
self.check_cond(
flipped & !f_explicit != 0,
format_args!("f.pick_cube_dd_set(literal_set) does not follow the requirements from literal_set (selecting {selected} for x{var})"),
&[
("f", f_explicit),
("literal_set", literal_set_explicit),
("f.pick_cube_dd_set(literal_set)", actual),
("flipped", flipped),
],
);
}
}
}
}
}
}
}
impl<B: BooleanFunction + BooleanVecSet> TestAllBooleanFunctions<'_, B> {
fn subset_and_change(&self) {
for (f_explicit, f) in self.boolean_functions.iter().enumerate() {
let f_explicit = f_explicit as ExplicitBFunc;
for (v, &v_func) in self.var_functions.iter().enumerate() {
let v = v as VarNo;
self.check(
"f.subset0(v)",
self.dd_to_boolean_func[&f.subset0(v).unwrap()],
f_explicit & !v_func,
&[f_explicit],
&[1 << v],
);
self.check(
"f.subset1(v)",
self.dd_to_boolean_func[&f.subset1(v).unwrap()],
(f_explicit & v_func) >> (1 << v),
&[f_explicit],
&[1 << v],
);
self.check(
"f.change(v)",
self.dd_to_boolean_func[&f.change(v).unwrap()],
((f_explicit & !v_func) << (1 << v)) | ((f_explicit & v_func) >> (1 << v)),
&[f_explicit],
&[1 << v],
);
}
}
}
}
impl<B: BooleanFunction + FunctionSubst> TestAllBooleanFunctions<'_, B> {
pub fn subst(&self) {
self.subst_rec(&mut vec![None; self.nvars as usize], 0);
}
fn subst_rec(&self, replacements: &mut [Option<ExplicitBFunc>], current_var: u32) {
let nvars = self.nvars;
debug_assert_eq!(replacements.len(), nvars as usize);
if current_var < nvars {
replacements[current_var as usize] = None;
self.subst_rec(replacements, current_var + 1);
for f in 0..self.boolean_functions.len() as ExplicitBFunc {
replacements[current_var as usize] = Some(f);
self.subst_rec(replacements, current_var + 1);
}
} else {
let num_assignments = 1u32 << nvars;
let mut subst_vars = Vec::with_capacity(nvars as usize);
let mut subst_repl = Vec::with_capacity(nvars as usize);
for (i, &repl) in replacements.iter().enumerate() {
if let Some(repl) = repl {
subst_vars.push(i as VarNo);
subst_repl.push(self.boolean_functions[repl as usize].clone());
}
}
let subst = oxidd_core::util::Subst::new(&subst_vars, &subst_repl);
for (f_explicit, f) in self.boolean_functions.iter().enumerate() {
let f_explicit = f_explicit as ExplicitBFunc;
let mut expected = 0;
for assignment in 0..num_assignments {
let mut mapped_assignment = 0;
for (var, repl) in replacements.iter().enumerate() {
let val = if let Some(repl) = repl {
repl >> assignment
} else {
assignment >> var
} & 1;
mapped_assignment |= val << var;
}
expected |= ((f_explicit >> mapped_assignment) & 1) << assignment;
}
let actual = self.dd_to_boolean_func[&f.substitute(&subst).unwrap()];
assert_eq!(actual, expected);
}
}
}
}
impl<B: BooleanFunctionQuant> TestAllBooleanFunctions<'_, B> {
pub fn quant(&self) {
let nvars = self.nvars;
let num_assignments = 1u32 << nvars;
let num_functions: ExplicitBFunc = 1 << num_assignments;
let func_mask = num_functions - 1;
for pos in 0..num_assignments {
for neg in 0..num_assignments {
if pos & neg != 0 {
continue; }
let dd_literal_set = &self.boolean_functions[self.make_cube(pos, neg) as usize];
for (f_explicit, f) in self.boolean_functions.iter().enumerate() {
let f_explicit = f_explicit as ExplicitBFunc;
let mut expected = 0;
for assignment in 0..num_assignments {
let assignment_restricted = (assignment | pos) & !neg;
expected |= ((f_explicit >> assignment_restricted) & 1) << assignment;
}
let actual = self.dd_to_boolean_func[&f.restrict(dd_literal_set).unwrap()];
assert_eq!(actual, expected);
}
}
}
let mut assignment_to_mask: Vec<ExplicitBFunc> = vec![0; num_assignments as usize];
for var_set in 0..num_assignments {
let dd_var_set = &self.boolean_functions[self.make_var_set(var_set) as usize];
for (assignment, mask) in assignment_to_mask.iter_mut().enumerate() {
let mut tmp: ExplicitBFunc = func_mask;
for (i, func) in self.var_functions.iter().copied().enumerate() {
if (var_set >> i) & 1 == 0 {
tmp &= if (assignment >> i) & 1 == 0 {
!func
} else {
func
};
}
}
*mask = tmp;
}
for (f_explicit, f) in self.boolean_functions.iter().enumerate() {
let f_explicit = f_explicit as ExplicitBFunc;
let mut exists_expected: ExplicitBFunc = 0;
let mut forall_expected: ExplicitBFunc = 0;
let mut unique_expected: ExplicitBFunc = 0;
for (assignment, mask) in assignment_to_mask.iter().copied().enumerate() {
let exists_bit = f_explicit & mask != 0; let forall_bit = f_explicit & mask == mask; let unique_bit = (f_explicit & mask).count_ones() & 1; exists_expected |= (exists_bit as ExplicitBFunc) << assignment;
forall_expected |= (forall_bit as ExplicitBFunc) << assignment;
unique_expected |= (unique_bit as ExplicitBFunc) << assignment;
}
self.check(
"∃v. f",
self.dd_to_boolean_func[&f.exists(dd_var_set).unwrap()],
exists_expected,
&[f_explicit],
&[var_set],
);
self.check(
"∀v. f",
self.dd_to_boolean_func[&f.forall(dd_var_set).unwrap()],
forall_expected,
&[f_explicit],
&[var_set],
);
self.check(
"∃!v. f",
self.dd_to_boolean_func[&f.unique(dd_var_set).unwrap()],
unique_expected,
&[f_explicit],
&[var_set],
);
let f_explicit = f_explicit as ExplicitBFunc;
for (g_explicit, g) in self.boolean_functions.iter().enumerate() {
let g_explicit = g_explicit as ExplicitBFunc;
use BooleanOperator::*;
for op in [And, Or, Xor, Equiv, Nand, Nor, Imp, ImpStrict] {
let (inner, inner_symbol) = match op {
And => (f.and(g).unwrap(), "∧"),
Or => (f.or(g).unwrap(), "∨"),
Xor => (f.xor(g).unwrap(), "⊕"),
Equiv => (f.equiv(g).unwrap(), "↔"),
Nand => (f.nand(g).unwrap(), "⊼"),
Nor => (f.nor(g).unwrap(), "⊽"),
Imp => (f.imp(g).unwrap(), "→"),
ImpStrict => (f.imp_strict(g).unwrap(), "<"),
};
self.check(
format_args!("∃v. f {inner_symbol} g"),
self.dd_to_boolean_func[&f.apply_exists(op, g, dd_var_set).unwrap()],
self.dd_to_boolean_func[&inner.exists(dd_var_set).unwrap()],
&[f_explicit, g_explicit],
&[var_set],
);
self.check(
format_args!("∀v. f {inner_symbol} g"),
self.dd_to_boolean_func[&f.apply_forall(op, g, dd_var_set).unwrap()],
self.dd_to_boolean_func[&inner.forall(dd_var_set).unwrap()],
&[f_explicit, g_explicit],
&[var_set],
);
self.check(
format_args!("∃!v. f {inner_symbol} g"),
self.dd_to_boolean_func[&f.apply_unique(op, g, dd_var_set).unwrap()],
self.dd_to_boolean_func[&inner.unique(dd_var_set).unwrap()],
&[f_explicit, g_explicit],
&[var_set],
);
}
}
}
}
}
}
fn setup_manager<MR: ManagerRef>(
new_manager: fn(usize, usize, u32) -> MR,
threads: u32,
nvars: VarNo,
) -> MR
where
for<'id> MR::Manager<'id>: HasWorkers,
{
let mref = new_manager(65536, 1024, threads);
let r = mref.with_manager_exclusive(|manager| {
if threads > 1 {
manager.workers().set_split_depth(Some(u32::MAX));
}
manager.add_vars(nvars)
});
assert_eq!(r, (0..nvars));
mref
}
#[test]
#[cfg_attr(miri, ignore)]
fn bdd_all_boolean_functions_2vars_t1() {
let mref = setup_manager(oxidd::bdd::new_manager, 1, 2);
let test = TestAllBooleanFunctions::<'_, BDDFunction>::init(&mref);
test.basic();
test.subst();
test.quant();
}
#[test]
#[cfg_attr(miri, ignore)]
fn bdd_all_boolean_functions_2vars_t2() {
let mref = setup_manager(oxidd::bdd::new_manager, 2, 2);
let test = TestAllBooleanFunctions::<'_, BDDFunction>::init(&mref);
test.basic();
test.subst();
test.quant();
}
#[test]
#[cfg_attr(miri, ignore)]
fn bcdd_all_boolean_functions_2vars_t1() {
let mref = setup_manager(oxidd::bcdd::new_manager, 1, 2);
let test = TestAllBooleanFunctions::<'_, BCDDFunction>::init(&mref);
test.basic();
test.subst();
test.quant();
}
#[test]
#[cfg_attr(miri, ignore)]
fn bcdd_all_boolean_functions_2vars_t2() {
let mref = setup_manager(oxidd::bcdd::new_manager, 2, 2);
let test = TestAllBooleanFunctions::<'_, BCDDFunction>::init(&mref);
test.basic();
test.subst();
test.quant();
}
#[test]
#[cfg_attr(miri, ignore)]
fn zbdd_all_boolean_functions_2vars_t1() {
let mref = setup_manager(oxidd::zbdd::new_manager, 1, 2);
let test = TestAllBooleanFunctions::<'_, ZBDDFunction>::init(&mref);
test.basic();
test.subset_and_change();
}
#[test]
#[cfg_attr(miri, ignore)]
fn zbdd_all_boolean_functions_2vars_t2() {
let mref = setup_manager(oxidd::zbdd::new_manager, 2, 2);
let test = TestAllBooleanFunctions::<'_, ZBDDFunction>::init(&mref);
test.basic();
test.subset_and_change();
}