1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
use crate::{Bdd, BddNode, BddPartialValuation, BddPointer, BddVariable};
use fxhash::FxBuildHasher;
use std::collections::HashMap;

impl Bdd {
    /// **(internal)** A specialized algorithm for constructing BDDs from DNFs. It builds the BDD
    /// directly by recursively "splitting" the clauses. The advantage is that we avoid a lot of
    /// memory copying. The disadvantage is that when the number of variables is high and the
    /// number of clauses low, this could be slightly slower due to all the recursion. However,
    /// it definitely needs to be tested at some point.
    pub(crate) fn mk_dnf(num_vars: u16, dnf: &[BddPartialValuation]) -> Bdd {
        if dnf.is_empty() {
            return Bdd::mk_false(num_vars);
        }

        // TODO:
        //  Can we turn the algorithm into a normal loop to prevent stack overflow in
        //  extreme cases?
        fn build_recursive(
            num_vars: u16,
            mut variable: u16,
            dnf: &[&BddPartialValuation],
            result: &mut Bdd,
            node_cache: &mut HashMap<BddNode, BddPointer, FxBuildHasher>,
        ) -> BddPointer {
            // The loop will automatically skip variables that are not relevant for the validity
            // of the provided DNF. This should significantly decrease the risk of stack overflow,
            // since we only run recursion when it is reasonably likely that we actually need to
            // condition on the specific variable. Otherwise, the variable is just skipped, since
            // we would get `low == high` anyway.
            loop {
                if variable == num_vars {
                    return BddPointer::from_bool(!dnf.is_empty());
                }
                if dnf.is_empty() {
                    return BddPointer::zero();
                }

                let var = BddVariable(variable);
                let should_branch = dnf.iter().any(|val| val.get_value(var).is_some());
                if !should_branch {
                    variable += 1;
                    continue;
                }

                let mut var_true = Vec::new();
                let mut var_false = Vec::new();
                for clause in dnf {
                    match clause.get_value(var) {
                        Some(true) => var_true.push(*clause),
                        Some(false) => var_false.push(*clause),
                        _ => {
                            var_true.push(*clause);
                            var_false.push(*clause);
                        }
                    }
                }

                let high = build_recursive(num_vars, variable + 1, &var_true, result, node_cache);
                let low = build_recursive(num_vars, variable + 1, &var_false, result, node_cache);

                if high == low {
                    return high;
                }

                let node = BddNode::mk_node(var, low, high);
                return if let Some(id) = node_cache.get(&node) {
                    *id
                } else {
                    result.push_node(node);
                    node_cache.insert(node, result.root_pointer());
                    result.root_pointer()
                };
            }
        }

        let mut result = Bdd::mk_true(num_vars);
        let mut node_cache = HashMap::with_capacity_and_hasher(dnf.len(), FxBuildHasher::default());
        node_cache.insert(BddNode::mk_zero(num_vars), BddPointer::zero());
        node_cache.insert(BddNode::mk_one(num_vars), BddPointer::one());

        let dnf = Vec::from_iter(dnf.iter());
        build_recursive(num_vars, 0, &dnf, &mut result, &mut node_cache);

        result
    }

    /// Construct a DNF representation of this BDD. This is equivalent to collecting all results
    /// of the `Bdd::sat_clauses` iterator. However, it uses a different algorithm that should be
    /// slightly faster for enumerating all results at the same time (the `sat_clauses` iterator
    /// is optimized for visiting the results one-by-one).
    pub fn to_dnf(&self) -> Vec<BddPartialValuation> {
        let mut results: Vec<BddPartialValuation> = Vec::new();
        let mut path = BddPartialValuation::empty();

        let mut stack: Vec<(BddPointer, Option<bool>)> = vec![(self.root_pointer(), Some(true))];
        while let Some((node, go_low)) = stack.pop() {
            if node.is_zero() {
                // An unsatisfied clause.
                continue;
            }
            if node.is_one() {
                // A satisfied clause.
                results.push(path.clone());
                continue;
            }

            let node_var = self.var_of(node);

            if let Some(go_low) = go_low {
                if go_low {
                    // First, we go into the low child. But even before that,
                    // we add a new item that indicates we should go to the high child
                    // once the low child is done.
                    stack.push((node, Some(false)));

                    // Update `path` to indicate that we are in the low child.
                    path[node_var] = Some(false);
                    stack.push((self.low_link_of(node), Some(true)));
                } else {
                    // Here, we visit the high child. But we still have to retain the current
                    // node to remove it from the `path` once the subgraph is done.
                    stack.push((node, None));

                    path[node_var] = Some(true);
                    stack.push((self.high_link_of(node), Some(true)));
                }
            } else {
                // Finally, both children are processed. We can remove the variable
                // from the current path.
                path.unset_value(node_var);
            }
        }

        results
    }

    /// Similar to [Bdd::to_dnf], but uses a quadratic optimization algorithm to greedily
    /// minimize the number of clauses in the final normal form.
    ///
    /// For very large BDDs, this can require a lot of computation time. However, in such cases,
    /// [Bdd::to_dnf] is likely not going to yield good results either.
    ///
    /// Currently, there is no "iterator" variant of this algorithm that would generate the DNF
    /// on-the-fly. But it should be relatively straightforward, so if you need it, please get
    /// in touch.
    pub fn to_optimized_dnf(&self) -> Vec<BddPartialValuation> {
        self._to_optimized_dnf(&|| Ok::<(), ()>(())).unwrap()
    }

    /// A cancellable variant of [Bdd::to_optimized_dnf].
    pub fn _to_optimized_dnf<E, I: Fn() -> Result<(), E>>(
        &self,
        interrupt: &I,
    ) -> Result<Vec<BddPartialValuation>, E> {
        if self.is_false() {
            return Ok(Vec::new());
        }
        if self.is_true() {
            return Ok(vec![BddPartialValuation::empty()]);
        }

        fn _rec<E, I: Fn() -> Result<(), E>>(
            bdd: &Bdd,
            clause: &mut BddPartialValuation,
            results: &mut Vec<BddPartialValuation>,
            interrupt: &I,
        ) -> Result<(), E> {
            if bdd.is_false() {
                return Ok(());
            }

            if bdd.is_true() {
                results.push(clause.clone());
                return Ok(());
            }

            let mut support = Vec::from_iter(bdd.support_set());
            support.sort();

            assert!(!support.is_empty());

            let mut best = (support[0], usize::MAX);

            for var in support {
                interrupt()?;

                let bdd_t = bdd.var_restrict(var, true);
                let bdd_f = bdd.var_restrict(var, false);
                let size = bdd_t.size() + bdd_f.size();
                if size < best.1 {
                    best = (var, size);
                }
            }

            let (var, _) = best;

            clause[var] = Some(true);
            _rec(&bdd.var_restrict(var, true), clause, results, interrupt)?;
            clause[var] = Some(false);
            _rec(&bdd.var_restrict(var, false), clause, results, interrupt)?;
            clause[var] = None;

            Ok(())
        }

        let mut buffer = BddPartialValuation::empty();
        let mut results = Vec::new();
        _rec(self, &mut buffer, &mut results, interrupt)?;

        Ok(results)
    }
}