1
0
mirror of https://github.com/ellmau/adf-obdd.git synced 2025-12-20 09:39:38 +01:00
adf-obdd/lib/src/adfbiodivine.rs
Lukas Gerlach 26e978ca47
Update Cargo.lock (#148)
* Update Cargo.lock

* Update biodivine-lib-bdd

---------

Co-authored-by: monsterkrampe <monsterkrampe@users.noreply.github.com>
Co-authored-by: Stefan Ellmauthaler <stefan.ellmauthaler@tu-dresden.de>
2023-05-05 15:37:34 +02:00

648 lines
23 KiB
Rust

//! This module describes the abstract dialectical framework
//! utilising the biodivine-lib-bdd (see <https://github.com/sybila/biodivine-lib-bdd>) BDD implementation to compute various semantics.
//!
//! These are currently the
//! - grounded
//! - stable
//! - complete
//! semantics of ADFs.
use crate::{
datatypes::{
adf::{
PrintDictionary, PrintableInterpretation, ThreeValuedInterpretationsIterator,
TwoValuedInterpretationsIterator, VarContainer,
},
Term,
},
parser::AdfParser,
};
use biodivine_lib_bdd::{boolean_expression::BooleanExpression, Bdd, BddVariableSet};
use derivative::Derivative;
#[derive(Derivative)]
#[derivative(Debug)]
/// Representation of an ADF, with an ordering and dictionary which relates statements to numbers, a binary decision diagram, and a list of acceptance functions in biodivine representation together with a variable-list (needed by biodivine).
///
/// To be compatible with results from the own implementation of the Bdd-based [`Adf`][crate::adf::Adf], we use the [`Term`][crate::datatypes::Term]-based representation for the various computed models.
pub struct Adf {
ordering: VarContainer,
ac: Vec<Bdd>,
vars: Vec<biodivine_lib_bdd::BddVariable>,
#[derivative(Debug = "ignore")]
varset: BddVariableSet,
rewrite: Option<Bdd>, // stable rewrite
}
impl Adf {
/// Instantiates a new ADF, based on the parser-data.
pub fn from_parser(parser: &AdfParser) -> Self {
log::info!("[Start] instantiating BDD");
let mut bdd_var_builder = biodivine_lib_bdd::BddVariableSetBuilder::new();
let namelist = parser
.namelist()
.read()
.expect("ReadLock on namelist failed")
.clone();
let slice_vec: Vec<&str> = namelist.iter().map(<_>::as_ref).collect();
bdd_var_builder.make_variables(&slice_vec);
let bdd_variables = bdd_var_builder.build();
let mut result = Self {
ordering: parser.var_container(),
ac: vec![bdd_variables.mk_false(); parser.dict_size()],
vars: bdd_variables.variables(),
varset: bdd_variables,
rewrite: None,
};
log::trace!("variable order: {:?}", result.vars);
log::debug!("[Start] adding acs");
parser
.formula_order()
.iter()
.enumerate()
.for_each(|(insert_order, new_order)| {
log::trace!(
"Pos {}/{} formula {}, {:?}",
insert_order + 1,
parser.formula_count(),
new_order,
parser.ac_at(insert_order)
);
result.ac[*new_order] = result
.varset
.eval_expression(&parser.ac_at(insert_order).expect("Insert order needs to exist, as all the data originates from the same parser object").to_boolean_expr());
log::trace!("instantiated {}", result.ac[*new_order]);
});
log::info!("[Success] instantiated");
result
}
/// Instantiates a new ADF and prepares a rewriting for the stable model computation based on the parser-data.
pub fn from_parser_with_stm_rewrite(parser: &AdfParser) -> Self {
let mut result = Self::from_parser(parser);
log::debug!("[Start] rewriting");
result.stm_rewriting(parser);
log::debug!("[Done] rewriting");
result
}
pub(crate) fn stm_rewriting(&mut self, parser: &AdfParser) {
let expr = parser.formula_order().iter().enumerate().fold(
biodivine_lib_bdd::boolean_expression::BooleanExpression::Const(true),
|acc, (insert_order, new_order)| {
BooleanExpression::And(
Box::new(acc),
Box::new(BooleanExpression::Iff(
Box::new(BooleanExpression::Variable(
self.ordering
.name(crate::datatypes::Var(*new_order))
.expect("Variable should exist"),
)),
Box::new(parser.ac_at(insert_order).expect("Insert order needs to exist, as all the data originates from the same parser object").to_boolean_expr()),
)),
)
},
);
log::trace!("{:?}", expr);
self.rewrite = Some(self.varset.eval_expression(&expr));
}
/// returns `true` if the stable rewriting for this ADF exists.
pub fn has_stm_rewriting(&self) -> bool {
self.rewrite.is_some()
}
pub(crate) fn var_container(&self) -> &VarContainer {
&self.ordering
}
pub(crate) fn ac(&self) -> &[Bdd] {
&self.ac
}
/// Computes the grounded extension and returns it as a list.
pub fn grounded(&self) -> Vec<Term> {
log::info!("[Start] grounded");
let ac = &self.ac.clone();
let result = self
.grounded_internal(ac)
.iter()
.map(|elem| elem.into())
.collect();
log::info!("[Done] grounded");
result
}
fn var_list(&self, interpretation: &[Bdd]) -> Vec<(biodivine_lib_bdd::BddVariable, bool)> {
self.vars
.iter()
.enumerate()
.filter(|(idx, _elem)| interpretation[*idx].is_truth_value())
.map(|(idx, elem)| (*elem, interpretation[idx].is_true()))
.collect::<Vec<(biodivine_lib_bdd::BddVariable, bool)>>()
}
fn var_list_from_term(
&self,
interpretation: &[Term],
) -> Vec<(biodivine_lib_bdd::BddVariable, bool)> {
self.vars
.iter()
.enumerate()
.filter(|(idx, _elem)| interpretation[*idx].is_truth_value())
.map(|(idx, elem)| (*elem, interpretation[idx].is_true()))
.collect::<Vec<(biodivine_lib_bdd::BddVariable, bool)>>()
}
pub(crate) fn grounded_internal(&self, interpretation: &[Bdd]) -> Vec<Bdd> {
let mut new_interpretation: Vec<Bdd> = interpretation.into();
loop {
let mut truth_extention: bool = false;
// let var_list: Vec<(biodivine_lib_bdd::BddVariable, bool)> = self
// .vars
// .iter()
// .enumerate()
// .filter(|(idx, _elem)| new_interpretation[*idx].is_truth_value())
// .map(|(idx, elem)| (*elem, new_interpretation[idx].is_true()))
// .collect();
let var_list = self.var_list(&new_interpretation);
log::trace!("var-list: {:?}", var_list);
for ac in new_interpretation
.iter_mut()
.filter(|elem| !elem.is_truth_value())
{
log::trace!("checking ac: {}", ac);
*ac = ac.restrict(&var_list);
if ac.is_truth_value() {
truth_extention = true;
}
}
if !truth_extention {
break;
}
}
new_interpretation
}
/// Computes the complete models.
/// Returns an [Iterator][std::iter::Iterator] which contains all the complete models.
pub fn complete<'a, 'b>(&'a self) -> impl Iterator<Item = Vec<Term>> + 'b
where
'a: 'b,
{
ThreeValuedInterpretationsIterator::from_bdd(&self.grounded_internal(&self.ac)).filter(
|terms| {
let var_list = self.var_list_from_term(terms);
self.ac
.iter()
.enumerate()
.all(|(idx, ac)| terms[idx].cmp_information(&ac.restrict(&var_list)))
},
)
}
/// Shifts the representation and allows to use the naive approach.
///
/// The grounded interpretation is computed by the [biodivine library](https://github.com/sybila/biodivine-lib-bdd) first.
pub fn hybrid_step(&self) -> crate::adf::Adf {
crate::adf::Adf::from_biodivine_vector(
self.var_container(),
&self.grounded_internal(self.ac()),
)
}
/// Shifts the representation and allows to use the naive approach.
///
/// `bio_grounded` will compute the grounded, based on [biodivine](https://github.com/sybila/biodivine-lib-bdd), first.
pub fn hybrid_step_opt(&self, bio_grounded: bool) -> crate::adf::Adf {
if bio_grounded {
self.hybrid_step()
} else {
crate::adf::Adf::from_biodivine_vector(self.var_container(), self.ac())
}
}
/// Computes the stable models.
/// Returns an [Iterator][std::iter::Iterator] which contains all the stable models.
pub fn stable<'a, 'b>(&'a self) -> impl Iterator<Item = Vec<Term>> + 'b
where
'a: 'b,
{
TwoValuedInterpretationsIterator::from_bdd(&self.grounded_internal(&self.ac)).filter(
|terms| {
let reduction_list = self
.vars
.iter()
.enumerate()
.filter_map(|(idx, elem)| {
if terms[idx].is_truth_value() && !terms[idx].is_true() {
Some((*elem, false))
} else {
None
}
})
.collect::<Vec<(biodivine_lib_bdd::BddVariable, bool)>>();
let reduct = self
.ac
.iter()
.map(|ac| ac.restrict(&reduction_list))
.collect::<Vec<_>>();
let grounded = self.grounded_internal(&reduct);
terms
.iter()
.zip(grounded.iter())
.all(|(left, right)| left.cmp_information(right))
},
)
}
/// Computes the stable models.
/// This variant returns all stable models and utilises a rewrite of the ADF as one big conjunction of equalities (`if and only if`).
pub fn stable_bdd_representation(&self) -> Vec<Vec<Term>> {
let smc = self.stable_model_candidates();
log::debug!("[Start] checking for stability");
smc.into_iter()
.filter(|terms| {
let reduction_list = self
.vars
.iter()
.enumerate()
.filter_map(|(idx, elem)| {
if terms[idx].is_truth_value() && !terms[idx].is_true() {
Some((*elem, false))
} else {
None
}
})
.collect::<Vec<(biodivine_lib_bdd::BddVariable, bool)>>();
let reduct = self
.ac
.iter()
.map(|ac| ac.restrict(&reduction_list))
.collect::<Vec<_>>();
let grounded = self.grounded_internal(&reduct);
terms
.iter()
.zip(grounded.iter())
.all(|(left, right)| left.cmp_information(right))
})
.collect::<Vec<Vec<Term>>>()
}
pub(crate) fn stable_model_candidates(&self) -> Vec<Vec<Term>> {
let internal_rewriting: Bdd;
let sr: &Bdd;
if self.has_stm_rewriting() {
sr = self.rewrite.as_ref().expect("self.rewrite has to exist, because it has been checked just before calling this reference");
} else {
internal_rewriting = self.stable_representation();
sr = &internal_rewriting;
}
log::debug!("[Start] construct stable model candidates");
sr.sat_valuations()
.map(|valuation| {
self.vars
.iter()
.map(|var| {
if valuation.value(*var) {
Term::TOP
} else {
Term::BOT
}
})
.collect::<Vec<Term>>()
})
.collect::<Vec<Vec<Term>>>()
}
/// compute the stable representation
fn stable_representation(&self) -> Bdd {
log::debug!("[Start] stable representation rewriting");
self.ac.iter().enumerate().fold(
self.varset.eval_expression(
&biodivine_lib_bdd::boolean_expression::BooleanExpression::Const(true),
),
|acc, (idx, formula)| {
acc.and(
&formula.iff(
&self.varset.eval_expression(
&biodivine_lib_bdd::boolean_expression::BooleanExpression::Variable(
self.ordering
.name(crate::datatypes::Var(idx))
.expect("Variable should exist"),
),
),
),
)
},
)
}
/// Creates a [PrintableInterpretation] for output purposes.
pub fn print_interpretation<'a, 'b>(
&'a self,
interpretation: &'b [Term],
) -> PrintableInterpretation<'b>
where
'a: 'b,
{
PrintableInterpretation::new(interpretation, &self.ordering)
}
/// Creates a [PrintDictionary] for output purposes.
pub fn print_dictionary(&self) -> PrintDictionary {
PrintDictionary::new(&self.ordering)
}
}
/// Provides ADF-Specific operations on truth valuations.
pub trait AdfOperations {
/// Returns `true` if the roBDD is either valid or unsatisfiable.
fn is_truth_value(&self) -> bool;
/// Compares whether the information between two given roBDDs are the same.
fn cmp_information(&self, other: &Self) -> bool;
}
impl AdfOperations for Bdd {
fn is_truth_value(&self) -> bool {
self.is_false() || self.is_true()
}
fn cmp_information(&self, other: &Self) -> bool {
self.is_truth_value() == other.is_truth_value() && self.is_true() == other.is_true()
}
}
/// Implementations of the restrict-operations on roBDDs.
pub trait BddRestrict {
/// Provides an implementation of the restrict-operation on roBDDs for one variable.
fn var_restrict(&self, variable: biodivine_lib_bdd::BddVariable, value: bool) -> Self;
/// Provides an implementation of the restrict-operation on a set of variables.
fn restrict(&self, variables: &[(biodivine_lib_bdd::BddVariable, bool)]) -> Self;
}
impl BddRestrict for Bdd {
fn var_restrict(&self, variable: biodivine_lib_bdd::BddVariable, value: bool) -> Bdd {
self.var_select(variable, value).var_exists(variable)
}
fn restrict(&self, variables: &[(biodivine_lib_bdd::BddVariable, bool)]) -> Bdd {
let mut variablelist: Vec<biodivine_lib_bdd::BddVariable> = Vec::new();
variables
.iter()
.for_each(|(var, _val)| variablelist.push(*var));
self.select(variables).exists(&variablelist)
}
}
impl ThreeValuedInterpretationsIterator {
fn from_bdd(bdd: &[Bdd]) -> Self {
let terms = bdd.iter().map(|value| value.into()).collect::<Vec<_>>();
Self::new(&terms)
}
}
impl TwoValuedInterpretationsIterator {
fn from_bdd(bdd: &[Bdd]) -> Self {
let terms = bdd.iter().map(|value| value.into()).collect::<Vec<_>>();
Self::new(&terms)
}
}
#[cfg(test)]
mod test {
use super::*;
use test_log::test;
use biodivine_lib_bdd::*;
#[test]
fn biodivine_internals() {
let mut builder = BddVariableSetBuilder::new();
let [_a, _b, _c] = builder.make(&["a", "b", "c"]);
let variables: BddVariableSet = builder.build();
let a = variables.eval_expression_string("a");
let b = variables.eval_expression_string("b");
let c = variables.eval_expression_string("c");
let d = variables.eval_expression_string("a & b & c");
let e = variables.eval_expression_string("a ^ b");
let t = variables.eval_expression(&boolean_expression::BooleanExpression::Const(true));
let f = variables.eval_expression(&boolean_expression::BooleanExpression::Const(false));
println!("{:?}", a.to_string());
println!("{:?}", a.to_bytes());
println!("{:?}", b.to_string());
println!("{:?}", c.to_string());
println!("{:?}", d.to_string());
println!("{:?}", e.to_string());
println!("{:?}", e.to_bytes());
println!("{:?}", t.to_string());
println!("{:?}", f.to_string());
}
#[test]
fn grounded() {
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)).\ns(e).ac(e,and(b,or(neg(b),c(f)))).s(f).\n\nac(f,xor(a,e)).")
.unwrap();
let adf = Adf::from_parser(&parser);
let xor = adf.ac[5].clone();
assert!(xor
.var_restrict(adf.vars[4], false)
.var_restrict(adf.vars[0], true)
.var_restrict(adf.vars[1], true)
.var_restrict(adf.vars[2], false)
.is_true());
let result = adf.grounded();
assert_eq!(
result,
vec![
Term::TOP,
Term::UND,
Term::UND,
Term::UND,
Term::BOT,
Term::TOP
]
);
assert_eq!(
format!("{}", adf.print_interpretation(&result)),
"T(a) u(b) u(c) u(d) F(e) T(f) \n"
);
let parser = AdfParser::default();
parser.parse()(
"s(a).s(b).s(c).s(d).s(e).ac(a,c(v)).ac(b,a).ac(c,b).ac(d,neg(c)).ac(e,and(a,d)).",
)
.unwrap();
let adf = Adf::from_parser(&parser);
let result = adf.grounded();
assert_eq!(
result,
vec![Term::TOP, Term::TOP, Term::TOP, Term::BOT, Term::BOT]
);
}
#[test]
fn grounded_eq_naive() {
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)).\ns(e).ac(e,and(b,or(neg(b),c(f)))).s(f).\n\nac(f,xor(a,e)).")
.unwrap();
let adf = Adf::from_parser(&parser);
let adf_grd = adf.grounded();
let adf_hybrid_true = adf.hybrid_step_opt(true).grounded();
let adf_hybrid_false = adf.hybrid_step_opt(false).grounded();
adf_grd
.iter()
.zip(adf_hybrid_false.iter())
.zip(adf_hybrid_true.iter())
.for_each(|((a, b), c)| {
assert!(a.compare_inf(b));
assert!(b.compare_inf(c));
});
}
#[test]
fn complete() {
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)).\ns(e).ac(e,and(b,or(neg(b),c(f)))).s(f).\n\nac(f,xor(a,e)).")
.unwrap();
let adf = Adf::from_parser(&parser);
assert_eq!(
adf.complete().next(),
Some(vec![
Term::TOP,
Term::UND,
Term::UND,
Term::UND,
Term::BOT,
Term::TOP
])
);
assert_eq!(
adf.complete().collect::<Vec<_>>(),
[
[
Term::TOP,
Term::UND,
Term::UND,
Term::UND,
Term::BOT,
Term::TOP
],
[
Term::TOP,
Term::TOP,
Term::TOP,
Term::BOT,
Term::BOT,
Term::TOP
],
[
Term::TOP,
Term::BOT,
Term::BOT,
Term::TOP,
Term::BOT,
Term::TOP
]
]
);
}
#[test]
fn complete2() {
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.complete().collect::<Vec<_>>(),
[
[Term::TOP, Term::UND, Term::UND, Term::UND],
[Term::TOP, Term::TOP, Term::TOP, Term::BOT],
[Term::TOP, Term::BOT, Term::BOT, Term::TOP]
]
);
}
#[test]
fn stable() {
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)).\ns(e).ac(e,and(b,or(neg(b),c(f)))).s(f).\n\nac(f,xor(a,e)).")
.unwrap();
let adf = Adf::from_parser(&parser);
let mut stable = adf.stable();
assert_eq!(
stable.next(),
Some(vec![
Term::TOP,
Term::BOT,
Term::BOT,
Term::TOP,
Term::BOT,
Term::TOP
])
);
assert_eq!(stable.next(), None);
let parser = AdfParser::default();
parser.parse()("s(a).s(b).ac(a,neg(b)).ac(b,neg(a)).").unwrap();
let adf = Adf::from_parser(&parser);
let mut stable = adf.stable();
assert_eq!(stable.next(), Some(vec![Term::BOT, Term::TOP]));
assert_eq!(stable.next(), Some(vec![Term::TOP, Term::BOT]));
assert_eq!(stable.next(), None);
let parser = AdfParser::default();
parser.parse()("s(a).s(b).ac(a,b).ac(b,a).").unwrap();
let adf = Adf::from_parser(&parser);
assert_eq!(adf.stable().next(), Some(vec![Term::BOT, Term::BOT]));
for model in adf.stable() {
let printer = adf.print_dictionary();
assert_eq!(
format!("{}", adf.print_interpretation(&model)),
format!("{}", printer.print_interpretation(&model))
);
}
let parser = AdfParser::default();
parser.parse()("s(a).s(b).ac(a,neg(a)).ac(b,a).").unwrap();
let adf = Adf::from_parser(&parser);
assert_eq!(adf.stable().next(), None);
}
#[test]
fn stable_version2() {
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)).\ns(e).ac(e,and(b,or(neg(b),c(f)))).s(f).\n\nac(f,xor(a,e)).")
.unwrap();
let adf = Adf::from_parser(&parser);
let mut stable_naive: Vec<Vec<Term>> = adf.stable().collect();
let mut stable_v2 = adf.stable_bdd_representation();
let adf2 = Adf::from_parser_with_stm_rewrite(&parser);
let mut stable_v3 = adf2.stable_bdd_representation();
stable_naive.sort();
stable_v2.sort();
stable_v3.sort();
assert_eq!(stable_naive, stable_v2);
assert_eq!(stable_v2, stable_v3);
}
}