1
use std::marker::PhantomData;
2

            
3
use oxidd::BooleanFunction;
4
use oxidd::Function;
5
use oxidd::Manager;
6
use oxidd::ManagerRef;
7
use oxidd::VarNo;
8
use oxidd::bdd::BDDFunction;
9
use oxidd::bdd::BDDManagerRef;
10
use oxidd::util::AllocResult;
11
use oxidd::util::OptBool;
12
use oxidd::util::OutOfMemory;
13
use oxidd_core::function::EdgeOfFunc;
14

            
15
/// Returns the boolean set difference of two BDD functions: lhs \ rhs.
16
/// Implemented as lhs AND (NOT rhs).
17
1112313
pub fn minus(lhs: &BDDFunction, rhs: &BDDFunction) -> AllocResult<BDDFunction> {
18
1112313
    rhs.imp_strict(lhs)
19
1112313
}
20

            
21
/// Variant of [minus] that works on edges.
22
353424
pub fn minus_edge<'id>(
23
353424
    manager: &<BDDFunction as Function>::Manager<'id>,
24
353424
    lhs: &EdgeOfFunc<'id, BDDFunction>,
25
353424
    rhs: &EdgeOfFunc<'id, BDDFunction>,
26
353424
) -> AllocResult<<<BDDFunction as Function>::Manager<'id> as Manager>::Edge> {
27
353424
    BDDFunction::imp_strict_edge(manager, rhs, lhs)
28
353424
}
29

            
30
/// Iterator over all cubes (satisfying assignments) in a BDD.
31
///
32
/// The returned cubes contain don't care values (OptBool::None) for variables
33
/// that can be either true or false without affecting the satisfaction of the
34
/// BDD.
35
///
36
/// Returns Result to handle out of memory errors.
37
pub struct CubeIter<'a> {
38
    /// The BDD to iterate over.
39
    bdd: BDDFunction,
40

            
41
    _marker: PhantomData<&'a ()>,
42
}
43

            
44
impl<'a> CubeIter<'a> {
45
    /// Creates a new cube iterator for the given BDD.
46
58463
    pub fn new(bdd: &'a BDDFunction) -> Self {
47
58463
        Self {
48
58463
            bdd: bdd.clone(),
49
58463
            _marker: PhantomData,
50
58463
        }
51
58463
    }
52
}
53

            
54
impl Iterator for CubeIter<'_> {
55
    type Item = Result<(Vec<OptBool>, BDDFunction), OutOfMemory>;
56

            
57
1070085
    fn next(&mut self) -> Option<Self::Item> {
58
        // We need to turn Err into Some(Err) to satisfy the Iterator trait.
59
1070085
        let cube = match self.bdd.pick_cube_dd(|_, _, _| true) {
60
1070085
            Ok(cube) => cube,
61
            Err(e) => return Some(Err(e)),
62
        };
63

            
64
1070085
        self.bdd = match minus(&self.bdd, &cube) {
65
1070085
            Ok(bdd) => bdd,
66
            Err(e) => return Some(Err(e)),
67
        };
68

            
69
1070085
        cube.pick_cube(|_, _, _| true).map(|bits| Ok((bits, cube)))
70
1070085
    }
71
}
72

            
73
/// The same as [CubeIter], but iterates over all satisfying assignments without
74
/// considering don't care values.
75
///
76
/// # Details
77
///
78
/// For the universe BDD, the [CubeIter] yields only one cube with all don't
79
/// cares, while this iterator yields all possible cubes. When
80
/// `variable_indices` is provided, returned assignments are projected to only
81
/// those variables.
82
///
83
/// Note that with `variable_indices` set, there can be multiple (identical)
84
/// cubes returned if the original cube has relevant outside of the projected
85
/// variables. This can be avoided by first restricting the input BDD to only
86
/// the desired `variable_indices`.
87
pub struct CubeIterAll<'a> {
88
    /// Iterator over the cubes with don't cares.
89
    iter: CubeIter<'a>,
90

            
91
    /// The current cube returned from CubeIter.
92
    cube: Option<Vec<OptBool>>,
93

            
94
    /// The cube that is currently being iterated over.
95
    current_cube: Vec<OptBool>,
96

            
97
    /// Optional set of variable indices to project the output onto.
98
    variable_indices: Option<&'a [VarNo]>,
99
}
100

            
101
impl<'a> CubeIterAll<'a> {
102
    /// Creates a new cube iterator that defers initialization to the first `next` call.
103
500
    pub fn new(bdd: &'a BDDFunction) -> Self {
104
500
        Self {
105
500
            iter: CubeIter::new(bdd),
106
500
            cube: None,
107
500
            current_cube: Vec::new(),
108
500
            variable_indices: None,
109
500
        }
110
500
    }
111

            
112
    /// Creates a new cube iterator that returns assignments only for the given variable indices.
113
57613
    pub fn with_variables(bdd: &'a BDDFunction, variable_indices: &'a [VarNo]) -> Self {
114
57613
        Self {
115
57613
            iter: CubeIter::new(bdd),
116
57613
            cube: None,
117
57613
            current_cube: Vec::new(),
118
57613
            variable_indices: Some(variable_indices),
119
57613
        }
120
57613
    }
121

            
122
    /// Initialize the `current` cube: project onto the selected variables (if requested)
123
    /// and replace the remaining don't cares with false to form the first assignment.
124
1007927
    fn initialize_cube(&mut self) {
125
1007927
        if let Some(cube) = &mut self.cube {
126
            // Project to the selected variable indices first, so the concretized assignment
127
            // below is over the projected space and never contains a don't care.
128
951004
            if let Some(indices) = &self.variable_indices {
129
949261
                *cube = project_bits(cube, indices);
130
949882
            }
131

            
132
951004
            self.current_cube = cube
133
951004
                .iter()
134
23739762
                .map(|element| {
135
23739762
                    if *element == OptBool::None {
136
40720
                        OptBool::False
137
                    } else {
138
23699042
                        *element
139
                    }
140
23739762
                })
141
951004
                .collect();
142
56923
        }
143
1007927
    }
144
}
145

            
146
impl Iterator for CubeIterAll<'_> {
147
    type Item = Result<Vec<OptBool>, OutOfMemory>;
148

            
149
1054031
    fn next(&mut self) -> Option<Self::Item> {
150
        // Lazy initialization on first call.
151
1054031
        if self.cube.is_none() {
152
114835
            self.cube = match self.iter.next() {
153
56923
                Some(Ok((bits, _))) => Some(bits),
154
                Some(Err(e)) => return Some(Err(e)),
155
57912
                None => return None,
156
            };
157

            
158
56923
            self.initialize_cube();
159
939196
        }
160

            
161
        // At this point, cube must be Some; otherwise we would have returned None above.
162
996119
        let cube = self.cube.as_ref().unwrap();
163
996119
        let result = self.current_cube.clone();
164

            
165
        // Advance to the next assignment for this cube. If overflow,
166
        // move to the next cube and initialize its current assignment.
167
996119
        if !increment(&mut self.current_cube, cube) {
168
951004
            self.cube = match self.iter.next() {
169
894081
                Some(Ok((bits, _))) => Some(bits), // We cannot use the cube since we are going to fill in the don't cares.
170
                Some(Err(e)) => return Some(Err(e)),
171
56923
                None => None,
172
            };
173

            
174
951004
            self.initialize_cube();
175
45115
        }
176

            
177
996119
        Some(Ok(result))
178
1054031
    }
179
}
180

            
181
/// Constructs a BDD representing the given vector of (optional) bits.
182
996
pub fn bits_to_bdd(
183
996
    manager_ref: &BDDManagerRef,
184
996
    variables: &[BDDFunction],
185
996
    cube: &[OptBool],
186
996
) -> AllocResult<BDDFunction> {
187
996
    manager_ref.with_manager_shared(|manager| {
188
996
        let mut result = BDDFunction::t(manager);
189

            
190
2988
        for (var, value) in variables.iter().zip(cube.iter()) {
191
2988
            match value {
192
                OptBool::True => {
193
1515
                    result = result.and(var)?;
194
                }
195
                OptBool::False => {
196
1473
                    let not_var = var.not()?;
197
1473
                    result = result.and(&not_var)?;
198
                }
199
                OptBool::None => {
200
                    // Don't care, skip
201
                }
202
            }
203
        }
204

            
205
996
        Ok(result)
206
996
    })
207
996
}
208

            
209
/// Perform the binary increment, returns false if overflow occurs.
210
///
211
/// Only considers bits for which the `cube` has don't care values, since these
212
/// are the only ones that can be changed.
213
996119
fn increment(current_cube: &mut [OptBool], cube: &[OptBool]) -> bool {
214
    // Propagate carry across don't-care positions, resetting carried bits to false.
215
996119
    let mut carry = true;
216
24246001
    for i in 0..current_cube.len() {
217
24246001
        if cube[i] != OptBool::None {
218
24155771
            continue;
219
90230
        }
220
90230
        match current_cube[i] {
221
            // Treat None as False for robustness.
222
            OptBool::None | OptBool::False => {
223
                // Found a bit to flip; set it to True and stop carrying.
224
45115
                current_cube[i] = OptBool::True;
225
45115
                carry = false;
226
45115
                break;
227
            }
228
45115
            OptBool::True => {
229
45115
                // Reset this don't-care bit and continue carrying to the next.
230
45115
                current_cube[i] = OptBool::False;
231
45115
            }
232
        }
233
    }
234

            
235
    // All relevant variables were true (or there were none), overflow.
236
996119
    !carry
237
996119
}
238

            
239
/// Project bits onto the given variable indices.
240
949261
fn project_bits(bits: &[OptBool], variable_indices: &[VarNo]) -> Vec<OptBool> {
241
949261
    variable_indices
242
949261
        .iter()
243
23732289
        .map(|&i| bits.get(i as usize).copied().expect("Projecting out of bounds"))
244
949261
        .collect()
245
949261
}
246

            
247
#[cfg(test)]
248
mod tests {
249
    use std::collections::HashSet;
250

            
251
    use itertools::Itertools;
252

            
253
    use merc_utilities::MercError;
254
    use merc_utilities::random_test;
255
    use oxidd::BooleanFunction;
256
    use oxidd::Manager;
257
    use oxidd::ManagerRef;
258
    use oxidd::bdd::BDDFunction;
259
    use oxidd::util::OptBool;
260

            
261
    use crate::CubeIter;
262
    use crate::CubeIterAll;
263
    use crate::FormatConfig;
264
    use crate::bdd_from_iter;
265
    use crate::random_bitvectors;
266

            
267
    #[test]
268
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
269
1
    fn test_random_cube_iter_all() {
270
100
        random_test(100, |rng| {
271
100
            let manager_ref = oxidd::bdd::new_manager(2048, 1024, 1);
272
100
            let set = random_bitvectors(rng, 5, 20);
273
936
            println!("Set: {:?}", set.iter().format_with(", ", |v, f| f(&FormatConfig(v))));
274

            
275
100
            let variables = manager_ref
276
100
                .with_manager_exclusive(|manager| -> Result<Vec<BDDFunction>, MercError> {
277
100
                    Ok(manager
278
100
                        .add_vars(5)
279
500
                        .map(|i| BDDFunction::var(manager, i))
280
100
                        .collect::<Result<Vec<BDDFunction>, _>>()?)
281
100
                })
282
100
                .expect("Failed to create variables");
283

            
284
100
            let bdd = bdd_from_iter(&manager_ref, &variables, set.iter()).unwrap();
285

            
286
            // Check that the cube iterator yields all the expected cubes
287
100
            let mut seen = HashSet::new();
288
791
            for bits in CubeIterAll::new(&bdd) {
289
791
                let bits = bits.expect("Failed to iterate cubes");
290

            
291
791
                println!("Cube: {}", FormatConfig(&bits));
292
791
                assert!(!bits.contains(&OptBool::None), "Cube has no don't cares:");
293
791
                assert!(set.contains(&bits), "Cube {} not in expected set", FormatConfig(&bits));
294
791
                assert!(
295
791
                    seen.insert(bits.clone()),
296
                    "Duplicate cube found: {}",
297
                    FormatConfig(&bits)
298
                );
299
            }
300

            
301
100
            let cubes: Vec<Vec<OptBool>> = CubeIterAll::new(&bdd)
302
100
                .collect::<Result<Vec<_>, _>>()
303
100
                .expect("Failed to iterate cubes");
304
936
            for cube in &set {
305
5479
                let found = cubes.iter().find(|bits| *bits == cube);
306
936
                assert!(found.is_some(), "Expected cube {} not found", FormatConfig(cube));
307
            }
308
100
        })
309
1
    }
310

            
311
    #[test]
312
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
313
1
    fn test_random_cube_iter() {
314
100
        random_test(100, |rng| {
315
100
            let manager_ref = oxidd::bdd::new_manager(2048, 1024, 1);
316
100
            let set = random_bitvectors(rng, 5, 20);
317
939
            println!("Set: {:?}", set.iter().format_with(", ", |v, f| f(&FormatConfig(v))));
318

            
319
100
            let variables = manager_ref
320
100
                .with_manager_exclusive(|manager| -> Result<Vec<BDDFunction>, MercError> {
321
100
                    Ok(manager
322
100
                        .add_vars(5)
323
500
                        .map(|i| BDDFunction::var(manager, i))
324
100
                        .collect::<Result<Vec<BDDFunction>, _>>()?)
325
100
                })
326
100
                .expect("Failed to create variables");
327

            
328
100
            let bdd = bdd_from_iter(&manager_ref, &variables, set.iter()).unwrap();
329

            
330
            // Check that it does not yield duplicates.
331
100
            let mut seen = HashSet::new();
332
558
            for cube in CubeIter::new(&bdd) {
333
558
                let (cube, _) = cube.expect("Failed to iterate cubes");
334
558
                println!("Cube: {}", FormatConfig(&cube));
335

            
336
558
                assert!(
337
558
                    seen.insert(cube.clone()),
338
                    "Duplicate cube found: {}",
339
                    FormatConfig(&cube)
340
                );
341
            }
342
100
        })
343
1
    }
344
}