1
0
mirror of https://github.com/ellmau/adf-obdd.git synced 2025-12-19 09:29:36 +01:00

ADD counting of (counter) models and variables of one BDD-Tree (#21)

* ADD counting of (counter) models and variables of one BDD-Tree
* ADD counting with memoization
set ahoc-counting as default feature
This commit is contained in:
Stefan Ellmauthaler 2022-02-21 10:49:27 +01:00 committed by GitHub
parent e110b72115
commit 565fef99d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 315 additions and 11 deletions

View File

@ -43,4 +43,9 @@ quickcheck = "1"
quickcheck_macros = "1"
assert_cmd = "2.0"
predicates = "2.1"
assert_fs = "1.0"
assert_fs = "1.0"
[features]
default = ["adhoccounting"]
adhoccounting = [] # count models ad-hoc - disable if counting is not needed
importexport = []

View File

@ -11,7 +11,7 @@ use crate::{
PrintDictionary, PrintableInterpretation, ThreeValuedInterpretationsIterator,
TwoValuedInterpretationsIterator, VarContainer,
},
Term, Var,
ModelCounts, Term, Var,
},
obdd::Bdd,
parser::{AdfParser, Formula},
@ -368,6 +368,16 @@ impl Adf {
})
}
/// Returns a [Vector][std::vec::Vec] of [ModelCounts][crate::datatypes::ModelCounts] for each acceptance condition.
///
/// `memoization` controls whether memoization is utilised or not.
pub fn formulacounts(&self, memoization: bool) -> Vec<ModelCounts> {
self.ac
.iter()
.map(|ac| self.bdd.models(*ac, memoization))
.collect()
}
/// creates a [PrintableInterpretation] for output purposes
pub fn print_interpretation<'a, 'b>(
&'a self,
@ -383,6 +393,12 @@ impl Adf {
pub fn print_dictionary(&self) -> PrintDictionary {
PrintDictionary::new(&self.ordering)
}
#[cfg(feature = "adhoccounting")]
/// If the feature for adhoccounting is enabled, this fixes the bdd after an import with serde
pub fn fix_import(&self) {
self.bdd.fix_import();
}
}
#[cfg(test)]
@ -433,7 +449,7 @@ mod test {
let serialized = serde_json::to_string(&adf).unwrap();
log::debug!("Serialized to {}", serialized);
let result = r#"{"ordering":{"names":["a","c","b","e","d"],"mapping":{"b":2,"a":0,"e":3,"c":1,"d":4}},"bdd":{"nodes":[{"var":18446744073709551614,"lo":0,"hi":0},{"var":18446744073709551615,"lo":1,"hi":1},{"var":0,"lo":0,"hi":1},{"var":1,"lo":0,"hi":1},{"var":2,"lo":0,"hi":1},{"var":3,"lo":0,"hi":1},{"var":4,"lo":0,"hi":1},{"var":0,"lo":1,"hi":0},{"var":0,"lo":1,"hi":4},{"var":1,"lo":1,"hi":0},{"var":2,"lo":1,"hi":0},{"var":1,"lo":10,"hi":4},{"var":0,"lo":3,"hi":11},{"var":3,"lo":1,"hi":0},{"var":4,"lo":1,"hi":0},{"var":3,"lo":6,"hi":14}],"cache":[[{"var":3,"lo":1,"hi":0},13],[{"var":3,"lo":6,"hi":14},15],[{"var":4,"lo":0,"hi":1},6],[{"var":0,"lo":0,"hi":1},2],[{"var":4,"lo":1,"hi":0},14],[{"var":2,"lo":0,"hi":1},4],[{"var":1,"lo":0,"hi":1},3],[{"var":0,"lo":1,"hi":4},8],[{"var":3,"lo":0,"hi":1},5],[{"var":0,"lo":1,"hi":0},7],[{"var":2,"lo":1,"hi":0},10],[{"var":0,"lo":3,"hi":11},12],[{"var":1,"lo":1,"hi":0},9],[{"var":1,"lo":10,"hi":4},11]]},"ac":[4,2,7,15,12]}"#;
let result = r#"{"ordering":{"names":["a","c","b","e","d"],"mapping":{"b":2,"a":0,"c":1,"e":3,"d":4}},"bdd":{"nodes":[{"var":18446744073709551614,"lo":0,"hi":0},{"var":18446744073709551615,"lo":1,"hi":1},{"var":0,"lo":0,"hi":1},{"var":1,"lo":0,"hi":1},{"var":2,"lo":0,"hi":1},{"var":3,"lo":0,"hi":1},{"var":4,"lo":0,"hi":1},{"var":0,"lo":1,"hi":0},{"var":0,"lo":1,"hi":4},{"var":1,"lo":1,"hi":0},{"var":2,"lo":1,"hi":0},{"var":1,"lo":10,"hi":4},{"var":0,"lo":3,"hi":11},{"var":3,"lo":1,"hi":0},{"var":4,"lo":1,"hi":0},{"var":3,"lo":6,"hi":14}],"cache":[[{"var":1,"lo":0,"hi":1},3],[{"var":3,"lo":6,"hi":14},15],[{"var":2,"lo":0,"hi":1},4],[{"var":0,"lo":1,"hi":0},7],[{"var":0,"lo":3,"hi":11},12],[{"var":3,"lo":1,"hi":0},13],[{"var":4,"lo":1,"hi":0},14],[{"var":0,"lo":0,"hi":1},2],[{"var":3,"lo":0,"hi":1},5],[{"var":0,"lo":1,"hi":4},8],[{"var":4,"lo":0,"hi":1},6],[{"var":1,"lo":1,"hi":0},9],[{"var":2,"lo":1,"hi":0},10],[{"var":1,"lo":10,"hi":4},11]],"count_cache":{}},"ac":[4,2,7,15,12]}"#;
let mut deserialized: Adf = serde_json::from_str(&result).unwrap();
assert_eq!(adf.ac, deserialized.ac);
let grounded_import = deserialized.grounded();
@ -558,6 +574,17 @@ mod test {
println!("{}", printer.print_interpretation(&model));
}
}
#[test]
fn formulacounts() {
let parser = AdfParser::default();
parser.parse()("s(a).s(b).s(c).s(d).ac(a,c(v)).ac(b,b).ac(c,and(a,b)).ac(d,neg(b)).")
.unwrap();
let adf = Adf::from_parser(&parser);
assert_eq!(adf.formulacounts(false), adf.formulacounts(true));
}
#[test]
fn adf_default() {
let _adf = Adf::default();

View File

@ -56,6 +56,9 @@ struct App {
/// Export the adf-bdd state after parsing and BDD instantiation to the given filename
#[structopt(long)]
export: Option<PathBuf>,
/// Set if the (counter-)models shall be computed and printed, possible values are 'nai' and 'mem' for naive and memoization repectively (only works in hybrid and naive mode)
#[structopt(long)]
counter: Option<String>,
}
impl App {
@ -99,12 +102,33 @@ impl App {
} else {
BdAdf::from_parser_with_stm_rewrite(&parser)
};
match self.counter.as_deref() {
Some("nai") => {
let naive_adf = adf.hybrid_step_opt(false);
for ac_counts in naive_adf.formulacounts(false) {
print!("{:?} ", ac_counts);
}
println!();
}
Some("mem") => {
let naive_adf = adf.hybrid_step_opt(false);
for ac_counts in naive_adf.formulacounts(true) {
print!("{:?}", ac_counts);
}
println!();
}
Some(_) => {}
None => {}
}
log::info!("[Start] translate into naive representation");
let mut naive_adf = adf.hybrid_step();
log::info!("[Done] translate into naive representation");
if self.grounded {
let grounded = adf.hybrid_step_opt(false).grounded();
print!("{}", adf.print_interpretation(&grounded));
let grounded = naive_adf.grounded();
print!("{}", naive_adf.print_interpretation(&grounded));
}
let mut naive_adf = adf.hybrid_step();
let printer = naive_adf.print_dictionary();
if self.complete {
for model in naive_adf.complete() {
@ -131,6 +155,9 @@ impl App {
}
}
"biodivine" => {
if self.counter.is_some() {
log::error!("Modelcounting not supported in biodivine mode");
}
let parser = adf_bdd::parser::AdfParser::default();
parser.parse()(&input).unwrap();
log::info!("[Done] parsing");
@ -145,6 +172,7 @@ impl App {
} else {
BdAdf::from_parser_with_stm_rewrite(&parser)
};
if self.grounded {
let grounded = adf.grounded();
print!("{}", adf.print_interpretation(&grounded));
@ -170,7 +198,17 @@ impl App {
}
_ => {
let mut adf = if self.import {
serde_json::from_str(&input).unwrap()
#[cfg(not(feature = "adhoccounting"))]
{
serde_json::from_str(&input).unwrap()
}
#[cfg(feature = "adhoccounting")]
{
let result: Adf = serde_json::from_str(&input).unwrap();
log::debug!("test");
result.fix_import();
result
}
} else {
let parser = AdfParser::default();
parser.parse()(&input).unwrap();
@ -201,6 +239,24 @@ impl App {
});
}
}
match self.counter.as_deref() {
Some("nai") => {
for ac_counts in adf.formulacounts(false) {
print!("{:?} ", ac_counts);
}
println!();
}
Some("mem") => {
for ac_counts in adf.formulacounts(true) {
print!("{:?}", ac_counts);
}
println!();
}
Some(_) => {}
None => {}
}
if self.grounded {
let grounded = adf.grounded();
print!("{}", adf.print_interpretation(&grounded));

View File

@ -172,6 +172,11 @@ impl BddNode {
}
}
/// Type alias for the pair of counter-models and models
pub type ModelCounts = (usize, usize);
/// Type alias for the Modelcounts and the depth of a given Node in a BDD
pub type CountNode = (ModelCounts, usize);
#[cfg(test)]
mod test {
use super::*;

View File

@ -2,13 +2,15 @@
pub mod vectorize;
use crate::datatypes::*;
use serde::{Deserialize, Serialize};
use std::{cmp::min, collections::HashMap, fmt::Display};
use std::{cell::RefCell, cmp::min, collections::HashMap, fmt::Display};
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct Bdd {
pub(crate) nodes: Vec<BddNode>,
#[serde(with = "vectorize")]
cache: HashMap<BddNode, Term>,
#[serde(skip, default = "Bdd::default_count_cache")]
count_cache: RefCell<HashMap<Term, CountNode>>,
}
impl Display for Bdd {
@ -23,10 +25,35 @@ impl Display for Bdd {
impl Bdd {
pub fn new() -> Self {
Self {
nodes: vec![BddNode::bot_node(), BddNode::top_node()],
cache: HashMap::new(),
#[cfg(not(feature = "adhoccounting"))]
{
Self {
nodes: vec![BddNode::bot_node(), BddNode::top_node()],
cache: HashMap::new(),
count_cache: RefCell::new(HashMap::new()),
}
}
#[cfg(feature = "adhoccounting")]
{
let result = Self {
nodes: vec![BddNode::bot_node(), BddNode::top_node()],
cache: HashMap::new(),
count_cache: RefCell::new(HashMap::new()),
};
result
.count_cache
.borrow_mut()
.insert(Term::TOP, ((0, 1), 0));
result
.count_cache
.borrow_mut()
.insert(Term::BOT, ((1, 0), 0));
result
}
}
fn default_count_cache() -> RefCell<HashMap<Term, CountNode>> {
RefCell::new(HashMap::new())
}
pub fn variable(&mut self, var: Var) -> Term {
@ -126,11 +153,123 @@ impl Bdd {
let new_term = Term(self.nodes.len());
self.nodes.push(node);
self.cache.insert(node, new_term);
#[cfg(feature = "adhoccounting")]
{
log::debug!("newterm: {} as {:?}", new_term, node);
let mut count_cache = self.count_cache.borrow_mut();
let ((lo_cmodel, lo_model), lodepth) =
*count_cache.get(&lo).expect("Cache corrupted");
let ((hi_cmodel, hi_model), hidepth) =
*count_cache.get(&hi).expect("Cache corrupted");
log::debug!("lo (cm: {}, mo: {}, dp: {})", lo_cmodel, lo_model, lodepth);
log::debug!("hi (cm: {}, mo: {}, dp: {})", hi_cmodel, hi_model, hidepth);
let (lo_exp, hi_exp) = if lodepth > hidepth {
(1, 2usize.pow((lodepth - hidepth) as u32))
} else {
(2usize.pow((hidepth - lodepth) as u32), 1)
};
log::debug!("lo_exp {}, hi_exp {}", lo_exp, hi_exp);
count_cache.insert(
new_term,
(
(
lo_cmodel * lo_exp + hi_cmodel * hi_exp,
lo_model * lo_exp + hi_model * hi_exp,
),
std::cmp::max(lodepth, hidepth) + 1,
),
);
}
new_term
}
}
}
}
/// Computes the number of counter-models and models for a given BDD-tree
pub fn models(&self, term: Term, _memoization: bool) -> ModelCounts {
#[cfg(feature = "adhoccounting")]
{
return self.count_cache.borrow().get(&term).unwrap().0;
}
#[cfg(not(feature = "adhoccounting"))]
if _memoization {
self.modelcount_memoization(term).0
} else {
self.modelcount_naive(term).0
}
}
#[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 BDD-tree
fn modelcount_naive(&self, term: Term) -> CountNode {
if term == Term::TOP {
((0, 1), 0)
} else if term == Term::BOT {
((1, 0), 0)
} else {
let node = &self.nodes[term.0];
let mut lo_exp = 0u32;
let mut hi_exp = 0u32;
let ((lo_counter, lo_model), lodepth) = self.modelcount_naive(node.lo());
let ((hi_counter, hi_model), hidepth) = self.modelcount_naive(node.hi());
if lodepth > hidepth {
hi_exp = (lodepth - hidepth) as u32;
} else {
lo_exp = (hidepth - lodepth) as u32;
}
(
(
lo_counter * 2usize.pow(lo_exp) + hi_counter * 2usize.pow(hi_exp),
lo_model * 2usize.pow(lo_exp) + hi_model * 2usize.pow(hi_exp),
),
std::cmp::max(lodepth, hidepth) + 1,
)
}
}
fn modelcount_memoization(&self, term: Term) -> CountNode {
if term == Term::TOP {
((0, 1), 0)
} else if term == Term::BOT {
((1, 0), 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_counter, lo_model), lodepth) = self.modelcount_memoization(node.lo());
let ((hi_counter, hi_model), hidepth) = self.modelcount_memoization(node.hi());
if lodepth > hidepth {
hi_exp = (lodepth - hidepth) as u32;
} else {
lo_exp = (hidepth - lodepth) as u32;
}
(
(
lo_counter * 2usize.pow(lo_exp) + hi_counter * 2usize.pow(hi_exp),
lo_model * 2usize.pow(lo_exp) + hi_model * 2usize.pow(hi_exp),
),
std::cmp::max(lodepth, hidepth) + 1,
)
};
self.count_cache.borrow_mut().insert(term, result);
result
}
}
#[cfg(feature = "adhoccounting")]
pub fn fix_import(&self) {
self.count_cache.borrow_mut().insert(Term::TOP, ((0, 1), 0));
self.count_cache.borrow_mut().insert(Term::BOT, ((1, 0), 0));
for i in 0..self.nodes.len() {
log::debug!("fixing Term({})", i);
self.modelcount_memoization(Term(i));
}
}
}
#[cfg(test)]
@ -242,4 +381,76 @@ mod test {
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);
assert_eq!(bdd.models(v1, false), (1, 1));
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);
assert_eq!(bdd.models(formula1, false), (3, 1));
assert_eq!(bdd.models(formula2, false), (1, 3));
assert_eq!(bdd.models(formula3, false), (2, 2));
assert_eq!(bdd.models(formula4, false), (5, 3));
assert_eq!(bdd.models(Term::TOP, false), (0, 1));
assert_eq!(bdd.models(Term::BOT, false), (1, 0));
assert_eq!(bdd.models(v1, true), (1, 1));
assert_eq!(bdd.models(formula1, true), (3, 1));
assert_eq!(bdd.models(formula2, true), (1, 3));
assert_eq!(bdd.models(formula3, true), (2, 2));
assert_eq!(bdd.models(formula4, true), (5, 3));
assert_eq!(bdd.models(Term::TOP, true), (0, 1));
assert_eq!(bdd.models(Term::BOT, true), (1, 0));
assert_eq!(bdd.modelcount_naive(v1), ((1, 1), 1));
assert_eq!(bdd.modelcount_naive(formula1), ((3, 1), 2));
assert_eq!(bdd.modelcount_naive(formula2), ((1, 3), 2));
assert_eq!(bdd.modelcount_naive(formula3), ((2, 2), 2));
assert_eq!(bdd.modelcount_naive(formula4), ((5, 3), 3));
assert_eq!(bdd.modelcount_naive(Term::TOP), ((0, 1), 0));
assert_eq!(bdd.modelcount_naive(Term::BOT), ((1, 0), 0));
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)
);
}
}