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

Implement Stable Models based on lazy evaluated iterators (#13)

Implements #12 

* Implement Stable Models based on lazy evaluated iterators
* Adjustment of the internal computation of the grounded interpretation
* Update build.rs to replace "@" in test-instance names with "at"
* Implement de-/serialization of the adf (in OBDD representation) in library
* Adjust tests
* Add more style-restrictions to compiler
This commit is contained in:
Stefan Ellmauthaler 2022-01-11 14:01:15 +01:00 committed by GitHub
parent a95f88dee4
commit 07d15167fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 399 additions and 35 deletions

45
Cargo.lock generated
View File

@ -16,6 +16,8 @@ dependencies = [
"predicates",
"quickcheck",
"quickcheck_macros",
"serde",
"serde_json",
"test-log",
]
@ -269,6 +271,12 @@ dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -500,6 +508,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "ryu"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "same-file"
version = "1.0.6"
@ -509,6 +523,37 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "serde"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "strsim"
version = "0.8.0"

View File

@ -1,6 +1,6 @@
[package]
name = "adf_bdd"
version = "0.1.2"
version = "0.1.3"
authors = ["Stefan Ellmauthaler <stefan.ellmauthaler@tu-dresden.de>"]
edition = "2021"
repository = "https://github.com/ellmau/adf-obdd/"
@ -19,6 +19,8 @@ log = { version = "0.4", features = [ "max_level_trace", "release_max_level_info
env_logger = "0.9"
nom = "7.1.0"
lexical-sort = "0.3.1"
serde = { version = "1.0", features = ["derive","rc"] }
serde_json = "1.0"
[dev-dependencies]
test-log = "0.2.*"

View File

@ -33,6 +33,7 @@ fn write_test(test_file: &mut File, file: &DirEntry) {
let test_name = format!("{}", file.file_name().unwrap().to_string_lossy())
.replace(".", "_")
.replace("-", "_")
.replace("@", "at")
.to_lowercase();
let grounded_name = format!(
"{}-grounded.txt",

@ -1 +1 @@
Subproject commit 322d7d44662450f25caa09c569b5f8362547907b
Subproject commit a5548233eca5ecd15e0a2113ab6759f6a4751b3f

View File

@ -5,15 +5,18 @@
//! - computing interpretations
//! - computing fixpoints
//! - computing the least fixpoint by using a shortcut
use serde::{Deserialize, Serialize};
use crate::{
datatypes::{
adf::{PrintableInterpretation, VarContainer},
adf::{PrintableInterpretation, TwoValuedInterpretationsIterator, VarContainer},
Term, Var,
},
obdd::Bdd,
parser::{AdfParser, Formula},
};
#[derive(Serialize, Deserialize, Debug)]
/// Representation of an ADF, with an ordering and dictionary of statement <-> number relations, a binary decision diagram, and a list of acceptance functions in Term representation
pub struct Adf {
ordering: VarContainer,
@ -101,36 +104,44 @@ impl Adf {
/// Computes the grounded extension and returns it as a list
pub fn grounded(&mut self) -> Vec<Term> {
let ac = &self.ac.clone();
self.grounded_internal(ac)
}
fn grounded_internal(&mut self, interpretation: &[Term]) -> Vec<Term> {
log::info!("[Start] grounded");
let mut t_vals: usize =
self.ac.iter().fold(
0,
|acc, elem| {
if elem.is_truth_value() {
acc + 1
} else {
acc
}
},
);
let mut new_interpretation = self.ac.clone();
let mut t_vals: usize = interpretation
.iter()
.filter(|elem| elem.is_truth_value())
.count();
let mut new_interpretation: Vec<Term> = interpretation.into();
loop {
let curr_interpretation = new_interpretation.clone();
let old_t_vals = t_vals;
for ac in new_interpretation
.iter_mut()
.filter(|term| !term.is_truth_value())
{
*ac = self.ac.iter().enumerate().fold(*ac, |acc, (var, term)| {
if term.is_truth_value() {
self.bdd.restrict(acc, Var(var), term.is_true())
} else {
acc
}
});
*ac = curr_interpretation
.iter()
.enumerate()
.fold(*ac, |acc, (var, term)| {
if term.is_truth_value() {
self.bdd.restrict(acc, Var(var), term.is_true())
} else {
acc
}
});
if ac.is_truth_value() {
t_vals += 1;
}
}
log::debug!(
"old-int: {:?}, {} constants",
curr_interpretation,
old_t_vals
);
log::debug!("new-int: {:?}, {} constants", new_interpretation, t_vals);
if t_vals == old_t_vals {
break;
}
@ -139,6 +150,62 @@ impl Adf {
new_interpretation
}
/// Computes the first `max_values` stable models
/// if max_values is 0, then all will be computed
pub fn stable(&mut self, max_values: usize) -> Vec<Vec<Term>> {
let grounded = self.grounded();
if max_values == 0 {
self.stable_iter(&grounded).collect()
} else {
self.stable_iter(&grounded)
.enumerate()
.take_while(|(idx, _elem)| *idx < max_values)
.map(|(_, elem)| elem)
.collect()
}
}
/// Computes the stable models
/// Returns an Iterator which contains all stable models
fn stable_iter<'a, 'b, 'c>(
&'a mut self,
grounded: &'b [Term],
) -> impl Iterator<Item = Vec<Term>> + 'c
where
'a: 'c,
'b: 'c,
{
TwoValuedInterpretationsIterator::new(grounded)
.map(|interpretation| {
let mut interpr = self.ac.clone();
for ac in interpr.iter_mut() {
*ac = interpretation
.iter()
.enumerate()
.fold(*ac, |acc, (var, term)| {
if term.is_truth_value() && !term.is_true() {
self.bdd.restrict(acc, Var(var), false)
} else {
acc
}
});
}
let grounded_check = self.grounded_internal(&interpr);
log::debug!(
"grounded candidate\n{:?}\n{:?}",
interpretation,
grounded_check
);
(interpretation, grounded_check)
})
.filter(|(int, grd)| {
int.iter()
.zip(grd.iter())
.all(|(it, gr)| it.compare_inf(gr))
})
.map(|(int, _grd)| int)
}
/// creates a [PrintableInterpretation] for output purposes
pub fn print_interpretation<'a, 'b>(
&'a self,
@ -188,7 +255,31 @@ mod test {
}
#[test]
fn complete() {
fn serialize() {
let parser = AdfParser::default();
let input = "s(a).s(c).ac(a,b).ac(b,neg(a)).s(b).ac(c,and(c(v),or(c(f),a))).s(e).s(d).ac(d,iff(imp(a,b),c)).ac(e,xor(d,e)).";
parser.parse()(input).unwrap();
let mut adf = Adf::from_parser(&parser);
let grounded = adf.grounded();
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 mut deserialized: Adf = serde_json::from_str(&result).unwrap();
assert_eq!(adf.ac, deserialized.ac);
let grounded_import = deserialized.grounded();
assert_eq!(grounded, grounded_import);
assert_eq!(
format!("{}", adf.print_interpretation(&grounded)),
format!("{}", deserialized.print_interpretation(&grounded_import))
);
}
#[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();
@ -203,5 +294,70 @@ mod test {
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 mut adf = Adf::from_parser(&parser);
let result = adf.grounded();
assert_eq!(result, vec![Term(1), Term(1), Term(1), Term(0), Term(0)]);
}
#[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 mut adf = Adf::from_parser(&parser);
assert_eq!(
adf.stable(0),
vec![vec![
Term::TOP,
Term::BOT,
Term::BOT,
Term::TOP,
Term::BOT,
Term::TOP
]]
);
assert_eq!(
adf.stable(10),
vec![vec![
Term::TOP,
Term::BOT,
Term::BOT,
Term::TOP,
Term::BOT,
Term::TOP
]]
);
let parser = AdfParser::default();
parser.parse()("s(a).s(b).ac(a,neg(b)).ac(b,neg(a)).").unwrap();
let mut adf = Adf::from_parser(&parser);
assert_eq!(adf.stable(1), vec![vec![Term::BOT, Term::TOP]]);
assert_eq!(adf.stable(2), adf.stable(0));
assert_eq!(
adf.stable(0),
vec![vec![Term::BOT, Term::TOP], vec![Term::TOP, Term::BOT]]
);
let parser = AdfParser::default();
parser.parse()("s(a).s(b).ac(a,b).ac(b,a).").unwrap();
let mut adf = Adf::from_parser(&parser);
assert_eq!(adf.stable(0), vec![vec![Term::BOT, Term::BOT]]);
let parser = AdfParser::default();
parser.parse()("s(a).s(b).ac(a,neg(a)).ac(b,a).").unwrap();
let mut adf = Adf::from_parser(&parser);
let empty: Vec<Vec<Term>> = Vec::new();
assert_eq!(adf.stable(0), empty);
assert_eq!(adf.stable(99999), empty);
}
}

View File

@ -1,9 +1,11 @@
//! Repesentation of all needed ADF based datatypes
use serde::{Deserialize, Serialize};
use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};
use super::{Term, Var};
#[derive(Serialize, Deserialize,Debug)]
pub(crate) struct VarContainer {
names: Rc<RefCell<Vec<String>>>,
mapping: Rc<RefCell<HashMap<String, usize>>>,
@ -40,6 +42,7 @@ impl VarContainer {
}
/// A struct to print a representation, it will be instantiated by [Adf] by calling the method [`Adf::print_interpretation`].
#[derive(Debug)]
pub struct PrintableInterpretation<'a> {
interpretation: &'a [Term],
ordering: &'a VarContainer,
@ -76,6 +79,69 @@ impl Display for PrintableInterpretation<'_> {
}
}
/// Provides an Iterator, which contains all two valued Interpretations, with respect to the given
/// 3-valued interpretation.
#[derive(Debug)]
pub struct TwoValuedInterpretationsIterator {
indexes: Vec<usize>,
current: Option<Vec<Term>>,
started: bool,
}
impl TwoValuedInterpretationsIterator {
/// Creates a new iterable structure, which represents all two-valued interpretations wrt. the given 3-valued interpretation
pub fn new(term: &[Term]) -> Self {
let indexes = term
.iter()
.enumerate()
.filter_map(|(idx, &v)| (!v.is_truth_value()).then(|| idx))
.rev()
.collect::<Vec<_>>();
let current = term
.iter()
.map(|&v| if !v.is_truth_value() { Term::BOT } else { v })
.collect::<Vec<_>>();
Self {
indexes,
started: false,
current: Some(current),
}
}
}
impl Iterator for TwoValuedInterpretationsIterator {
type Item = Vec<Term>;
fn next(&mut self) -> Option<Self::Item> {
if self.started {
if let Some(current) = &self.current {
if let Some((idx, &at)) = self
.indexes
.iter()
.enumerate()
.find(|(_, &idx)| current[idx] == Term::BOT)
{
let mut result = current.clone();
result[at] = Term::TOP;
for &at in self.indexes[0..idx].iter() {
result[at] = Term::BOT;
}
log::trace!("{:?} -> {:?}", current, result);
self.current = Some(result);
} else {
self.current = None;
}
};
} else {
self.started = true;
}
self.current.clone()
}
}
#[cfg(test)]
mod test {
use super::*;
@ -84,4 +150,35 @@ mod test {
let vc = VarContainer::default();
assert_eq!(vc.variable("foo"), None);
}
#[test]
fn two_valued_interpretations() {
let testinterpretation = vec![Term::TOP, Term(22), Term::BOT, Term(12), Term::TOP];
let mut iter = TwoValuedInterpretationsIterator::new(&testinterpretation);
assert_eq!(
iter.next(),
Some(vec![Term::TOP, Term::BOT, Term::BOT, Term::BOT, Term::TOP])
);
assert_eq!(
iter.next(),
Some(vec![Term::TOP, Term::BOT, Term::BOT, Term::TOP, Term::TOP])
);
assert_eq!(
iter.next(),
Some(vec![Term::TOP, Term::TOP, Term::BOT, Term::BOT, Term::TOP])
);
assert_eq!(
iter.next(),
Some(vec![Term::TOP, Term::TOP, Term::BOT, Term::TOP, Term::TOP])
);
assert_eq!(iter.next(), None);
let testinterpretation = vec![Term(22), Term(12)];
let mut iter = TwoValuedInterpretationsIterator::new(&testinterpretation);
assert_eq!(iter.next(), Some(vec![Term::BOT, Term::BOT]));
assert_eq!(iter.next(), Some(vec![Term::BOT, Term::TOP]));
assert_eq!(iter.next(), Some(vec![Term::TOP, Term::BOT]));
assert_eq!(iter.next(), Some(vec![Term::TOP, Term::TOP]));
assert_eq!(iter.next(), None);
}
}

View File

@ -1,12 +1,13 @@
//! To represent a BDD, a couple of datatypes is needed.
//! This module consists of all internally and externally used datatypes, such as
//! [Term], [Var], and [BddNode]
use serde::{Deserialize, Serialize};
use std::{fmt::Display, ops::Deref};
/// Representation of a Term
/// Each Term is represented in a number ([usize]) and relates to a
/// Node in the decision diagram
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Copy, Clone)]
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Copy, Clone, Serialize, Deserialize)]
pub struct Term(pub usize);
impl Deref for Term {
@ -49,12 +50,17 @@ impl Term {
pub fn is_true(&self) -> bool {
*self == Self::TOP
}
/// Returns true, if the Terms have the same information-value
pub fn compare_inf(&self, other: &Self) -> bool {
self.is_truth_value() == other.is_truth_value() && self.is_true() == other.is_true()
}
}
/// Representation of Variables
/// Note that the algorithm only uses [usize] values to identify variables.
/// The order of these values will be defining for the Variable order of the decision diagram.
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Clone, Copy)]
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
pub struct Var(pub usize);
impl Deref for Var {
@ -92,7 +98,7 @@ impl Var {
///
/// Intuitively this is a binary tree structure, where the diagram is allowed to
/// pool same values to the same Node.
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Clone, Copy)]
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
pub(crate) struct BddNode {
var: Var,
lo: Term,
@ -151,6 +157,18 @@ mod test {
use quickcheck_macros::quickcheck;
use test_log::test;
#[test]
fn cmp() {
assert!(!Term::BOT.compare_inf(&Term::TOP));
assert!(!Term::TOP.compare_inf(&Term::BOT));
assert!(!Term::TOP.compare_inf(&Term(22)));
assert!(!Term(22).compare_inf(&Term::BOT));
assert!(Term(22).compare_inf(&Term(12323)));
assert!(Term::TOP.compare_inf(&Term::TOP));
assert!(Term::BOT.compare_inf(&Term::BOT));
assert!(Term(22).compare_inf(&Term(22)));
}
#[quickcheck]
fn deref_display_from(value: usize) -> bool {
// from

View File

@ -32,6 +32,8 @@
//! ac(d,d).
//! ```
#![deny(
missing_debug_implementations,
missing_copy_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,

View File

@ -23,6 +23,8 @@ fn main() {
(@arg sort_lex: --lx "Sorts variables in a lexicographic manner")
(@arg sort_alphan: --an "Sorts variables in an alphanumeric manner")
)
(@arg grounded: --grd "Compute the grounded model")
(@arg stable: --stm "Compute the stable models")
)
.get_matches_safe()
.unwrap_or_else(|e| match e.kind {
@ -75,7 +77,14 @@ fn main() {
}
let mut adf = Adf::from_parser(&parser);
let grounded = adf.grounded();
println!("{}", adf.print_interpretation(&grounded));
if matches.is_present("grounded") {
let grounded = adf.grounded();
println!("{}", adf.print_interpretation(&grounded));
}
if matches.is_present("stable") {
let stable = adf.stable(1);
for model in stable {
println!("{}", adf.print_interpretation(&model));
}
}
}

View File

@ -1,10 +1,12 @@
//! Represents an obdd
use crate::datatypes::*;
use serde::{Deserialize, Serialize};
use std::{cmp::min, collections::HashMap, fmt::Display};
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct Bdd {
pub(crate) nodes: Vec<BddNode>,
#[serde(with = "vectorize")]
cache: HashMap<BddNode, Term>,
}
@ -226,3 +228,33 @@ mod test {
assert_eq!(x, Term(2));
}
}
/// vectorize maps with non-standard keys
pub mod vectorize {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::iter::FromIterator;
/// Serialise into a Vector from a Map
pub fn serialize<'a, T, K, V, S>(target: T, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: IntoIterator<Item = (&'a K, &'a V)>,
K: Serialize + 'a,
V: Serialize + 'a,
{
let container: Vec<_> = target.into_iter().collect();
serde::Serialize::serialize(&container, ser)
}
/// Deserialize from a Vector to a Map
pub fn deserialize<'de, T, K, V, D>(des: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: FromIterator<(K, V)>,
K: Deserialize<'de>,
V: Deserialize<'de>,
{
let container: Vec<_> = serde::Deserialize::deserialize(des)?;
Ok(T::from_iter(container.into_iter()))
}
}

View File

@ -75,6 +75,7 @@ impl std::fmt::Debug for Formula<'_> {
/// handed over to other structures without further storage needs.
///
/// Note that the parser can be utilised by an [ADF][`crate::datatypes::adf::Adf`] to initialise it with minimal overhead.
#[derive(Debug)]
pub struct AdfParser<'a> {
namelist: Rc<RefCell<Vec<String>>>,
dict: Rc<RefCell<HashMap<String, usize>>>,
@ -97,6 +98,7 @@ impl<'a, 'b> AdfParser<'b>
where
'a: 'b,
{
#[allow(dead_code)]
fn parse_statements(&'a self) -> impl FnMut(&'a str) -> IResult<&'a str, ()> {
move |input| {
let (rem, _) = many1(self.parse_statement())(input)?;

View File

@ -46,21 +46,21 @@ fn runs() -> Result<(), Box<dyn std::error::Error>> {
.stderr(predicate::str::contains("code: Eof"));
cmd = Command::cargo_bin("adf_bdd")?;
cmd.arg(file.path()).arg("-vv");
cmd.arg(file.path()).arg("-vv").arg("--grd");
cmd.assert().success().stdout(predicate::str::contains(
"u(7) F(4) u(8) u(3) F(5) u(9) u(10) u(1) u(6) u(2)",
));
cmd = Command::cargo_bin("adf_bdd")?;
cmd.arg(file.path()).arg("--lx").arg("-v");
cmd.arg(file.path()).arg("--lx").arg("-v").arg("--grd");
cmd.assert().success().stdout(predicate::str::contains(
"u(1) u(10) u(2) u(3) F(4) F(5) u(6) u(7) u(8) u(9)",
));
cmd = Command::cargo_bin("adf_bdd")?;
cmd.arg(file.path()).arg("--an");
cmd.arg(file.path()).arg("--an").arg("--grd").arg("--stm");
cmd.assert().success().stdout(predicate::str::contains(
"u(1) u(2) u(3) F(4) F(5) u(6) u(7) u(8) u(9) u(10)",
"u(1) u(2) u(3) F(4) F(5) u(6) u(7) u(8) u(9) u(10) \n\n",
));
Ok(())
}

View File

@ -14,7 +14,7 @@ fn {name}() {{
let grounded = adf.grounded();
assert_eq!(
format!("{{}}", adf.print_interpretation(&grounded)),
expected_result.unwrap()
format!("{{}}\n",expected_result.unwrap())
);
}}