1
//! Iterator over cubes in a BDD.
2

            
3
use std::marker::PhantomData;
4

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

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

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

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

            
43
    _marker: PhantomData<&'a ()>,
44
}
45

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

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

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

            
66
6240
        self.bdd = match minus(&self.bdd, &cube) {
67
6240
            Ok(bdd) => bdd,
68
            Err(e) => return Some(Err(e)),
69
        };
70

            
71
6240
        cube.pick_cube(|_, _, _| true).map(|bits| Ok((bits, cube)))
72
6240
    }
73
}
74

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

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

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

            
99
    /// Optional set of variable indices to project the output onto.
100
    variable_indices: Option<&'a Vec<VarNo>>,
101
}
102

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

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

            
124
    /// Initialize the `current` cube by replacing don't cares with false.
125
1878
    fn initialize_cube(&mut self) {
126
1878
        if let Some(cube) = &mut self.cube {
127
1488
            self.current_cube = cube
128
1488
                .iter()
129
6912
                .map(|element| {
130
6912
                    if *element == OptBool::None {
131
990
                        OptBool::False
132
                    } else {
133
5922
                        *element
134
                    }
135
6912
                })
136
1488
                .collect();
137

            
138
            // Project to selected variable indices if requested.
139
1488
            if let Some(indices) = &self.variable_indices {                    
140
                *cube = project_bits(cube, indices);       
141
                self.current_cube = cube.clone();
142
1488
            }
143
390
        }
144
1878
    }
145
}
146

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

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

            
159
390
            self.initialize_cube();
160
2766
        }
161

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

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

            
175
1488
            self.initialize_cube();
176
1668
        }
177

            
178
3156
        Some(Ok(result))
179
3556
    }
180
}
181

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

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

            
206
1490
        Ok(result)
207
1490
    })
208
1490
}
209

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

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

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

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

            
252
    use itertools::Itertools;
253

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

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

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

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

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

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

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

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

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

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

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

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

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