Skip to content

Commit c20e545

Browse files
refactor: remove boolean nodes, introduce flag for special cases
As boolean nodes are removed as a node type. Instead, the attribute `kind` specifies whether the d-DNNF is a normal one without boolean nodes or a special case. A special d-DNNF is either a tautology (true root node) or contradiction (false root node).
1 parent 4b3f932 commit c20e545

File tree

20 files changed

+207
-150
lines changed

20 files changed

+207
-150
lines changed

bindings/kotlin/src/test/java/de/softvare/ddnnife/JavaTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,15 @@ void cnf() {
9090
void toUIntTest() {
9191
assertEquals(1, toUInt(1));
9292
}
93+
94+
@Test
95+
fun trivial() {
96+
Ddnnf trivialDdnnf = ddnnfFromFile("../../ddnnife/tests/data/stub_true.nnf", null);
97+
assert(trivialDdnnf.isTrivial());
98+
}
99+
100+
@Test
101+
fun nonTrivial() {
102+
assert(!ddnnf.isTrivial());
103+
}
93104
}

bindings/kotlin/src/test/kotlin/de/softvare/ddnnife/DdnnfTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,15 @@ internal class DdnnfTest {
8686
assertEquals(7481, cnf.clauses().size)
8787
assertEquals(110642, serialized.length)
8888
}
89+
90+
@Test
91+
fun trivial() {
92+
val trivialDdnnf = Ddnnf.fromFile("../../ddnnife/tests/data/stub_true.nnf", null)
93+
assert(trivialDdnnf.isTrivial())
94+
}
95+
96+
@Test
97+
fun nonTrivial() {
98+
assert(!ddnnf.isTrivial())
99+
}
89100
}

ddnnife/src/cnf/into.rs

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{Ddnnf, NodeType};
1+
use crate::{Ddnnf, DdnnfKind, NodeType};
22
use ddnnife_cnf::{Clause, Cnf, Literal};
33
use std::collections::HashMap;
44

@@ -91,22 +91,12 @@ impl From<Biconditional> for Clauses {
9191

9292
impl From<&Ddnnf> for Cnf {
9393
/// Generates an equi-countable CNF representation via Tseitin transformation.
94-
///
95-
/// **Note:** The d-DNNF is not allowed to contain boolean nodes in positions other than the root
96-
/// node.
9794
fn from(ddnnf: &Ddnnf) -> Self {
9895
// Check for special case of boolean root nodes.
99-
let root_index = ddnnf.nodes.len() - 1;
100-
let root = &ddnnf.nodes[root_index];
101-
102-
// A `true` root node implicates a model count of `1`.
103-
if root.ntype == NodeType::True {
104-
return Cnf::with_count_1();
105-
}
106-
107-
// A `false` root node implicates a model count of `0`.
108-
if root.ntype == NodeType::False {
109-
return Cnf::with_count_0();
96+
match ddnnf.kind {
97+
DdnnfKind::Tautology => return Cnf::with_count_1(),
98+
DdnnfKind::Contradiction => return Cnf::with_count_0(),
99+
_ => {}
110100
}
111101

112102
// Index to keep track of the last variable introduced to reference a Tseitin transformation.
@@ -143,8 +133,6 @@ impl From<&Ddnnf> for Cnf {
143133
&mut tseitin_index,
144134
),
145135
NodeType::Literal { literal } => *literal as Literal,
146-
NodeType::True => unreachable!("Boolean nodes are only allowed as a root node."),
147-
NodeType::False => unreachable!("Boolean nodes are only allowed as a root node."),
148136
};
149137

150138
node_literals[index] = literal;

ddnnife/src/ddnnf.rs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,35 @@ pub mod node;
66
pub mod statistics;
77

88
use self::node::Node;
9+
use crate::NodeType;
910
use crate::parser::graph::{DdnnfGraph, rebuild_graph};
1011
use num::BigInt;
1112
use petgraph::stable_graph::NodeIndex;
1213
use std::collections::{HashMap, HashSet};
1314
use std::fmt::{Display, Formatter};
1415
use std::path::Path;
1516

17+
/// Value for indicating whether the d-DNNF is a special case: tautology or contradiction.
18+
/// If it is neither a tautology nor a contradiction, it is non-trivial.
19+
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
20+
pub enum DdnnfKind {
21+
#[default]
22+
NonTrivial,
23+
Tautology,
24+
Contradiction,
25+
}
26+
1627
/// A Ddnnf holds all the nodes as a vector, also includes meta data and further information that is used for optimations
1728
#[derive(Clone, Debug)]
1829
pub struct Ddnnf {
30+
/// Flag indicating whether the d-DNNF is a special case: tautology or contradiction.
31+
///
32+
/// **Note:** A d-DNNF representing a tautology or contradiction contains no nodes.
33+
pub kind: DdnnfKind,
1934
/// The actual nodes of the d-DNNF in postorder
2035
pub nodes: Vec<Node>,
2136
/// Literals for upwards propagation
2237
pub literals: HashMap<i32, usize>, // <var_number of the Literal, and the corresponding indize>
23-
pub true_nodes: Vec<usize>, // Indices of true nodes. In some cases those nodes needed to have special treatment
2438
/// The core/dead features of the model corresponding with this ddnnf
2539
pub core: HashSet<i32>,
2640
/// An interim save for the marking algorithm
@@ -33,9 +47,9 @@ pub struct Ddnnf {
3347
impl Default for Ddnnf {
3448
fn default() -> Self {
3549
Ddnnf {
50+
kind: DdnnfKind::default(),
3651
nodes: Vec::new(),
3752
literals: HashMap::new(),
38-
true_nodes: Vec::new(),
3953
core: HashSet::new(),
4054
md: Vec::new(),
4155
number_of_variables: 0,
@@ -47,12 +61,12 @@ impl Default for Ddnnf {
4761
impl Ddnnf {
4862
/// Creates a new ddnnf including dead and core features
4963
pub fn new(graph: DdnnfGraph, root: NodeIndex, number_of_variables: u32) -> Ddnnf {
50-
let dfs_ig = rebuild_graph(graph, root);
64+
let (kind, nodes, literals) = rebuild_graph(graph, root);
5165

5266
let mut ddnnf = Ddnnf {
53-
nodes: dfs_ig.0,
54-
literals: dfs_ig.1,
55-
true_nodes: dfs_ig.2,
67+
kind,
68+
nodes,
69+
literals,
5670
core: HashSet::new(),
5771
md: Vec::new(),
5872
number_of_variables,
@@ -64,6 +78,15 @@ impl Ddnnf {
6478
ddnnf
6579
}
6680

81+
/// Checks whether this d-DNNF is trivial. A trivial d-DNNF is either a tautology or a
82+
/// contradiction.
83+
pub fn is_trivial(&self) -> bool {
84+
match self.kind {
85+
DdnnfKind::Tautology | DdnnfKind::Contradiction => true,
86+
DdnnfKind::NonTrivial => false,
87+
}
88+
}
89+
6790
/// Loads a d-DNNF from file.
6891
pub fn from_file(path: &Path, features: Option<u32>) -> Self {
6992
crate::parser::build_ddnnf(path, features)
@@ -73,7 +96,11 @@ impl Ddnnf {
7396
///
7497
/// This value is the same during all computations.
7598
pub fn rc(&self) -> BigInt {
76-
self.nodes[self.nodes.len() - 1].count.clone()
99+
match self.kind {
100+
DdnnfKind::NonTrivial => self.nodes[self.nodes.len() - 1].count.clone(),
101+
DdnnfKind::Tautology => BigInt::from(2).pow(self.number_of_variables),
102+
DdnnfKind::Contradiction => BigInt::ZERO,
103+
}
77104
}
78105

79106
/// Returns the core features of this d-DNNF.
@@ -96,12 +123,11 @@ impl Ddnnf {
96123

97124
/// Computes the total number of edges in the dDNNF
98125
pub fn edge_count(&self) -> usize {
99-
use crate::NodeType::*;
100126
let mut total_edges = 0;
101127

102128
for node in self.nodes.iter() {
103129
match &node.ntype {
104-
And { children } | Or { children } => {
130+
NodeType::And { children } | NodeType::Or { children } => {
105131
total_edges += children.len();
106132
}
107133
_ => (),

ddnnife/src/ddnnf/anomalies/config_creation.rs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@ static ENUMERATION_CACHE: Lazy<Arc<Mutex<HashMap<Vec<i32>, usize>>>> =
2020
Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
2121

2222
impl Ddnnf {
23-
/// Creates satisfiable complete configurations for a ddnnf and given assumptions
24-
/// If the ddnnf on itself or in combination with the assumption is unsatisfiable,
25-
/// then we can not create any satisfiable configuration and simply return None.
23+
/// Creates satisfiable complete configurations for a d-DNNF and given assumptions.
24+
///
25+
/// Returns `None` if the d-DNNF itself or with the assumptions represents a tautology or
26+
/// contradiction.
2627
pub fn enumerate(
2728
&mut self,
2829
assumptions: &mut Vec<i32>,
2930
amount: usize,
3031
) -> Option<Vec<Vec<i32>>> {
32+
if self.is_trivial() {
33+
return None;
34+
}
35+
3136
if amount == 0 {
3237
return Some(Vec::new());
3338
}
@@ -66,14 +71,21 @@ impl Ddnnf {
6671
}
6772

6873
/// Generates amount many uniform random samples under a given set of assumptions and a seed.
69-
/// Each sample is sorted by the number of the features. Each sample is a complete configuration with #SAT of 1.
70-
/// If the ddnnf itself or in combination with the assumptions is unsatisfiable, None is returned.
74+
/// Each sample is sorted by the number of the features. Each sample is a complete configuration
75+
/// with #SAT of 1.
76+
///
77+
/// Returns `None` if the d-DNNF itself or with the assumptions represents a tautology or
78+
/// contradiction.
7179
pub fn uniform_random_sampling(
7280
&mut self,
7381
assumptions: &[i32],
7482
amount: usize,
7583
seed: u64,
7684
) -> Option<Vec<Vec<i32>>> {
85+
if self.is_trivial() {
86+
return None;
87+
}
88+
7789
if !self.preprocess_config_creation(assumptions) {
7890
return None;
7991
}
@@ -114,11 +126,6 @@ impl Ddnnf {
114126
}
115127
}
116128

117-
// We can't create a config that contains a true node.
118-
// Hence, we have to hide the true node by changing its count to 0
119-
for &index in self.true_nodes.iter() {
120-
self.nodes[index].temp.set_zero();
121-
}
122129
true
123130
}
124131

@@ -146,11 +153,6 @@ impl Ddnnf {
146153
let mut enumeration_child_lists = Vec::new();
147154

148155
for &child in children {
149-
// skip the true nodes
150-
if self.true_nodes.contains(&child) {
151-
continue;
152-
}
153-
154156
if &acc_amount < range.1 {
155157
let change = (&BigInt::ZERO, min(range.1, &self.nodes[child].temp));
156158
enumeration_child_lists.push(self.enumerate_node(change, child));
@@ -215,7 +217,6 @@ impl Ddnnf {
215217
Literal { literal } => {
216218
enumeration_list.push(vec![*literal]);
217219
}
218-
_ => (),
219220
}
220221
enumeration_list
221222
}
@@ -304,7 +305,6 @@ impl Ddnnf {
304305
sample_list.push(vec![*literal]);
305306
}
306307
}
307-
_ => (),
308308
}
309309
sample_list
310310
}

ddnnife/src/ddnnf/anomalies/sat.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{Ddnnf, NodeType::*};
1+
use crate::{Ddnnf, DdnnfKind, NodeType::*};
22
use num::Zero;
33

44
impl Ddnnf {
@@ -33,6 +33,12 @@ impl Ddnnf {
3333
mark: &mut Vec<bool>,
3434
root_index: Option<usize>,
3535
) -> bool {
36+
match self.kind {
37+
DdnnfKind::Tautology => return true,
38+
DdnnfKind::Contradiction => return false,
39+
_ => {}
40+
}
41+
3642
let root_index = root_index.unwrap_or(self.nodes.len() - 1);
3743

3844
if features.iter().any(|f| self.makes_query_unsat(f)) {

ddnnife/src/ddnnf/anomalies/t_wise_sampling.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ mod sat_wrapper;
77
mod t_iterator;
88
mod t_wise_sampler;
99

10-
use crate::Ddnnf;
1110
use crate::ddnnf::extended_ddnnf::ExtendedDdnnf;
1211
use crate::ddnnf::extended_ddnnf::objective_function::FloatOrd;
12+
use crate::{Ddnnf, DdnnfKind};
1313
use SamplingResult::ResultWithSample;
1414
pub use config::Config;
1515
use covering_strategies::cover_with_caching_sorted;
@@ -45,6 +45,12 @@ impl Ddnnf {
4545

4646
impl ExtendedDdnnf {
4747
pub fn sample_t_wise(&self, t: usize) -> SamplingResult {
48+
match self.ddnnf.kind {
49+
DdnnfKind::Tautology => return SamplingResult::Empty,
50+
DdnnfKind::Contradiction => return SamplingResult::Void,
51+
_ => {}
52+
}
53+
4854
let sat_solver = SatWrapper::new(&self.ddnnf);
4955
let and_merger = AttributeZippingMerger {
5056
t,

ddnnife/src/ddnnf/anomalies/t_wise_sampling/t_wise_sampler.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use super::covering_strategies::cover_with_caching;
22
use super::sample_merger::{AndMerger, OrMerger, SampleMerger};
33
use super::t_iterator::TInteractionIter;
44
use super::{Sample, SamplingResult, SatWrapper};
5-
use crate::Ddnnf;
65
use crate::NodeType;
76
use crate::ddnnf::extended_ddnnf::ExtendedDdnnf;
87
use crate::util::rng;
8+
use crate::{Ddnnf, DdnnfKind};
99
use itertools::Itertools;
1010
use rand::prelude::SliceRandom;
1111
use std::cmp::min;
@@ -35,6 +35,12 @@ impl<'a, A: AndMerger, O: OrMerger> TWiseSampler<'a, A, O> {
3535
}
3636

3737
pub fn sample(&mut self, t: usize) -> SamplingResult {
38+
match self.ddnnf.kind {
39+
DdnnfKind::Tautology => return SamplingResult::Empty,
40+
DdnnfKind::Contradiction => return SamplingResult::Void,
41+
_ => {}
42+
}
43+
3844
let sat_solver = SatWrapper::new(self.ddnnf);
3945

4046
// Sample each node and keep the result as a partial sample.
@@ -81,6 +87,12 @@ impl<'a, A: AndMerger, O: OrMerger> TWiseSampler<'a, A, O> {
8187
/// # Panics
8288
/// Panics if one child does not have a [SamplingResult] in [TWiseSampler::partial_samples].
8389
pub(crate) fn partial_sample(&mut self, node_id: usize) -> SamplingResult {
90+
match self.ddnnf.kind {
91+
DdnnfKind::Tautology => return SamplingResult::Empty,
92+
DdnnfKind::Contradiction => return SamplingResult::Void,
93+
_ => {}
94+
}
95+
8496
let node = self.ddnnf.nodes.get(node_id).expect("Node does not exist!");
8597

8698
match &node.ntype {
@@ -97,8 +109,6 @@ impl<'a, A: AndMerger, O: OrMerger> TWiseSampler<'a, A, O> {
97109
self.remove_unneeded(node_id, children);
98110
sample
99111
}
100-
NodeType::True => SamplingResult::Empty,
101-
NodeType::False => SamplingResult::Void,
102112
}
103113
}
104114

ddnnife/src/ddnnf/counting/default_count.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ impl Ddnnf {
1919
.map(|&indice| &self.nodes[indice].temp)
2020
.sum()
2121
}
22-
False => self.nodes[i].temp.set_zero(),
2322
_ => self.nodes[i].temp.set_one(), // True and Literal
2423
}
2524
}

0 commit comments

Comments
 (0)