Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation #59

Merged
merged 1 commit into from
Sep 24, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions src/_impl_bdd/_impl_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,81 @@

Ok(Bdd(data.to_vec()))
}

/// Check that this BDD is structurally sound.
///
/// In particular, all nodes reference existing children and there are no unreachable nodes.
///
/// This does not check that the nodes are sorted in DFS post-order, since this should not
/// be required to correctly interpret the BDD. Also note that the method can loop forever
/// if the BDD contains a loop (which it normally shouldn't).
///
/// You can use this to validate the BDD after deserialization.
pub fn validate(&self) -> Result<(), String> {
// Check constants.
if self.0.is_empty() {
return Err("No nodes".to_string());

Check warning on line 706 in src/_impl_bdd/_impl_util.rs

View check run for this annotation

Codecov / codecov/patch

src/_impl_bdd/_impl_util.rs#L706

Added line #L706 was not covered by tests
}
if self.0.len() == 1 {
return if self != &Bdd::mk_false(self.num_vars()) {
Err("Malformed false BDD.".to_string())
} else {
Ok(())

Check warning on line 712 in src/_impl_bdd/_impl_util.rs

View check run for this annotation

Codecov / codecov/patch

src/_impl_bdd/_impl_util.rs#L712

Added line #L712 was not covered by tests
};
}
if self.0.len() == 2 {
return if self != &Bdd::mk_true(self.num_vars()) {
Err("Malformed true BDD.".to_string())
} else {
Ok(())

Check warning on line 719 in src/_impl_bdd/_impl_util.rs

View check run for this annotation

Codecov / codecov/patch

src/_impl_bdd/_impl_util.rs#L719

Added line #L719 was not covered by tests
};
}

// Check that all references in nodes are valid.
let num_vars = self.num_vars();
for node_pointer in self.pointers().skip(2) {
let node = &self.0[node_pointer.to_index()];
if node.var.0 >= num_vars {
return Err(format!("Found invalid variable: {:?}.", node.var));
}
if node.low_link.0 >= (self.0.len() as u32) {
return Err(format!("Found invalid low-link: {:?}.", node.low_link));
}
if node.high_link.0 >= (self.0.len() as u32) {
return Err(format!("Found invalid high-link: {:?}.", node.high_link));
}
}

let mut visited = vec![false; self.0.len()];
// Mark terminals as visited.
visited[0] = true;
visited[1] = true;
let mut stack = vec![self.root_pointer()];

// Check reachability and sorted-ness.
while let Some(top) = stack.pop() {
if visited[top.to_index()] {
continue;
}
visited[top.to_index()] = true;
let node = &self.0[top.to_index()];
let low_child = &self.0[node.low_link.to_index()];
let high_child = &self.0[node.high_link.to_index()];

if low_child.var <= node.var || high_child.var <= node.var {
return Err(format!("Found broken child ordering in node {:?}.", top));
}

stack.push(node.low_link);
stack.push(node.high_link);
}

if !visited.into_iter().all(|it| it) {
return Err("BDD has unreachable nodes.".to_string());
}

Ok(())
}
}

#[cfg(test)]
Expand All @@ -700,6 +775,31 @@
use num_bigint::BigInt;
use std::convert::TryFrom;

#[test]
fn test_validate_bdd() {
let bdd = mk_small_test_bdd();
assert!(bdd.validate().is_ok());

let malformed_false = "|3,1,0|";
assert!(Bdd::from_string(malformed_false).validate().is_err());
let malformed_true = "|3,0,0|3,0,0|";
assert!(Bdd::from_string(malformed_true).validate().is_err());
let malformed_invalid_var = "|3,0,0|3,1,1|4,0,1|";
assert!(Bdd::from_string(malformed_invalid_var).validate().is_err());
let malformed_invalid_low = "|3,0,0|3,1,1|2,0,5|";
assert!(Bdd::from_string(malformed_invalid_low).validate().is_err());
let malformed_invalid_high = "|3,0,0|3,1,1|2,5,0|";
assert!(Bdd::from_string(malformed_invalid_high).validate().is_err());
let malformed_invalid_low = "|3,0,0|3,1,1|2,0,5|";
assert!(Bdd::from_string(malformed_invalid_low).validate().is_err());
let malformed_broken_ordering = "|3,0,0|3,1,1|1,0,1|2,2,0|";
assert!(Bdd::from_string(malformed_broken_ordering)
.validate()
.is_err());
let malformed_unreachable = "|3,0,0|3,1,1|1,0,1|1,1,0|";
assert!(Bdd::from_string(malformed_unreachable).validate().is_err());
}

#[test]
fn node_conversion() {
let bdd = mk_small_test_bdd();
Expand Down