sddrs/manager/
manager.rs

1use crate::{
2    btreeset,
3    dot_writer::{Dot, DotWriter},
4    literal::{Literal, LiteralManager, Polarity, Variable, VariableIdx},
5    manager::{
6        dimacs,
7        model::Models,
8        options::{GarbageCollection, SddOptions},
9    },
10    sdd::{Decision, Element, Sdd, SddId, SddRef, SddType},
11    util::set_bits_indices,
12    vtree::{
13        rotate_partition_left, rotate_partition_right, split_nodes_for_left_rotate,
14        split_nodes_for_right_rotate, split_nodes_for_swap, swap_partition, Direction, Fragment,
15        LeftRotateSplit, RightRotateSplit, VTreeIdx, VTreeManager, VTreeOrder, VTreeRef,
16    },
17};
18use bitvec::prelude::*;
19use rustc_hash::{FxHashMap, FxHashSet};
20use std::{cell::RefCell, cmp::Ordering, collections::BTreeSet, ops::BitOr};
21
22use anyhow::{anyhow, bail, Context, Result};
23use tracing::instrument;
24
25use super::options::{FragmentHeuristic, MinimizationCutoff};
26
27/// Binary operation
28#[derive(Clone, Eq, PartialEq, Hash, Debug, Copy)]
29pub(crate) enum Operation {
30    Conjoin,
31    Disjoin,
32}
33
34impl Operation {
35    /// Get the absorbing element with respect to the Boolean operation.
36    fn zero(self) -> SddId {
37        match self {
38            Operation::Conjoin => FALSE_SDD_IDX,
39            Operation::Disjoin => TRUE_SDD_IDX,
40        }
41    }
42}
43
44/// Wrapper around different types of operations to be cached.
45pub(crate) enum CachedOperation {
46    BinOp(SddId, Operation, SddId),
47    Neg(SddId),
48}
49
50#[derive(Eq, PartialEq, Hash, Debug, Clone)]
51struct Entry {
52    fst: SddId,
53    snd: SddId,
54    op: Operation,
55}
56
57/// Key into the `op_cache`.
58impl Entry {
59    fn contains_id(&self, id: SddId) -> bool {
60        self.fst == id || self.snd == id
61    }
62}
63
64/// Statistics related to garbage collection.
65#[derive(Debug, Clone)]
66pub struct GCStatistics {
67    pub nodes_collected: usize,
68    pub gc_triggered: usize,
69}
70
71impl GCStatistics {
72    fn collected(&mut self, nodes_collected: usize) {
73        self.gc_triggered += 1;
74        self.nodes_collected += nodes_collected;
75    }
76}
77
78/// Structure responsible for maintaing the state of the compilation.
79/// It is the central piece when compiling Boolean functions --- it is responsible
80/// combining SDDs, querying the knowledge, caching operations, collecting garbage
81/// (if configured), dynamically minimizing compiled knowledge (if configured),
82/// and much more. See [`SddOptions`] on how [`SddManager`] can be configured.
83///
84/// ```
85/// use sddrs::literal::literal::Polarity;
86/// use sddrs::manager::{SddManager, options::SddOptions};
87/// use bon::arr;
88/// let opts = SddOptions::builder().variables(arr!["A", "B"]).build();
89/// let manager = SddManager::new(&opts);
90/// let a = manager.literal("A", Polarity::Positive).unwrap();
91/// let n_b = manager.literal("B", Polarity::Negative).unwrap();
92/// // Compiles SDD for function 'A ∨ ¬B':
93/// let disjunction = manager.disjoin(&a, &n_b);
94/// assert_eq!(manager.model_count(&disjunction), 3);
95/// ```
96#[allow(clippy::module_name_repetitions)]
97#[derive(Debug)]
98pub struct SddManager {
99    options: SddOptions,
100
101    vtree_manager: RefCell<VTreeManager>,
102    literal_manager: RefCell<LiteralManager>,
103
104    // Unique table holding all the decision nodes.
105    // More details can be found in [Algorithms and Data Structures in VLSI Design](https://link.springer.com/book/10.1007/978-3-642-58940-9).
106    unique_table: RefCell<FxHashMap<SddId, SddRef>>,
107
108    // Caches all the computations.
109    op_cache: RefCell<FxHashMap<Entry, SddId>>,
110    neg_cache: RefCell<FxHashMap<SddId, SddId>>,
111
112    next_idx: RefCell<SddId>,
113
114    // Flag denoting whether we are doing rotations. If so, garbage collection
115    // is turned off.
116    rotating: RefCell<bool>,
117
118    gc_stats: RefCell<GCStatistics>,
119
120    apply_level: RefCell<u32>,
121}
122
123// True and false SDDs have indicies 0 and 1 throughout the whole computation.
124pub(crate) const FALSE_SDD_IDX: SddId = SddId(0);
125pub(crate) const TRUE_SDD_IDX: SddId = SddId(1);
126
127impl SddManager {
128    /// Construct a new SDD manager based on [`SddOptions`].
129    ///
130    /// # Panics
131    ///
132    /// This function panics if the manager is built with no variables.
133    /// This is user's responsibility and should never happen since it
134    /// does not make sense to compile a Boolean function that cannot
135    /// contain any variables.
136    #[must_use]
137    pub fn new(options: &SddOptions) -> SddManager {
138        assert!(
139            !options.variables.is_empty(),
140            "SddManager must be initialized with at least one variable!"
141        );
142
143        let mut unique_table = RefCell::new(FxHashMap::default());
144        let ff = SddRef::new(Sdd::new_false());
145        let tt = SddRef::new(Sdd::new_true());
146
147        // Sanity checks.
148        assert_eq!(tt.id(), TRUE_SDD_IDX);
149        assert_eq!(ff.id(), FALSE_SDD_IDX);
150
151        unique_table.get_mut().insert(tt.id(), tt);
152        unique_table.get_mut().insert(ff.id(), ff);
153
154        let variables: Vec<_> = options
155            .variables
156            .iter()
157            .enumerate()
158            .map(|(idx, variable)| Variable::new(variable, u32::try_from(idx).unwrap()))
159            .collect();
160
161        let manager = SddManager {
162            options: options.clone(),
163            op_cache: RefCell::new(FxHashMap::default()),
164            neg_cache: RefCell::new(FxHashMap::default()),
165            next_idx: RefCell::new(SddId(2)), // Account for ff and tt created earlier which have indices 0 and 1.
166            vtree_manager: RefCell::new(VTreeManager::new(options.vtree_strategy, &variables)),
167            literal_manager: RefCell::new(LiteralManager::new()),
168            rotating: RefCell::new(false),
169            apply_level: RefCell::new(0),
170            gc_stats: RefCell::new(GCStatistics {
171                nodes_collected: 0,
172                gc_triggered: 0,
173            }),
174            unique_table,
175        };
176
177        for variable in variables {
178            let vtree = manager.vtree_manager.borrow().get_variable_vtree(&variable);
179
180            let positive_literal = manager.new_sdd_from_type(
181                SddType::Literal(Literal::new_with_label(
182                    Polarity::Positive,
183                    variable.clone(),
184                )),
185                vtree.clone(),
186                None,
187            );
188
189            let negative_literal = manager.new_sdd_from_type(
190                SddType::Literal(Literal::new_with_label(
191                    Polarity::Negative,
192                    variable.clone(),
193                )),
194                vtree.clone(),
195                None,
196            );
197
198            manager.literal_manager.borrow().add_variable(
199                &variable,
200                positive_literal,
201                negative_literal,
202            );
203        }
204
205        manager
206    }
207
208    /// Parse a CNF in [DIMACS] format and construct an SDD. Function expects there is a
209    /// sufficient number of variables already defined in the manager and tries to map
210    /// variable indices in DIMACS to their string representations.
211    ///
212    /// # Errors
213    ///
214    /// Returns an error if:
215    /// * insufficient number of variables has been created during [`SddManager`] initialization
216    /// * DIMACS cannot be parsed.
217    ///
218    /// [DIMACS]: https://www21.in.tum.de/~lammich/2015_SS_Seminar_SAT/resources/dimacs-cnf.pdf
219    pub fn from_dimacs(&self, reader: &mut dyn std::io::Read) -> Result<SddRef> {
220        let mut reader = std::io::BufReader::new(reader);
221        let mut dimacs = dimacs::DimacsParser::new(&mut reader);
222
223        let preamble = dimacs.parse_preamble()?;
224        let num_variables = self.literal_manager.borrow().len();
225        if preamble.variables > num_variables {
226            bail!("preamble specifies more variables than those present in the manager",);
227        }
228
229        let mut sdd = self.tautology();
230        let mut i = 0;
231        loop {
232            match dimacs.parse_next_clause()? {
233                None => return Ok(sdd),
234                Some(clause) => {
235                    sdd = self.conjoin(&sdd, &clause.to_sdd(self));
236                    if sdd.is_false() {
237                        return Ok(sdd);
238                    }
239
240                    if i != 0
241                        && self.options.minimize_after != 0
242                        && i % self.options.minimize_after == 0
243                    {
244                        tracing::info!(sdd_id = sdd.id().0, size = sdd.size(), "before minimizing");
245                        self.minimize(
246                            self.options.minimization_cutoff,
247                            &self.options.fragment_heuristic,
248                            &sdd,
249                        )?;
250                        tracing::info!(sdd_id = sdd.id().0, size = sdd.size(), "after minimizing");
251                    }
252                    i += 1;
253                }
254            }
255        }
256    }
257
258    pub(crate) fn try_get_node(&self, id: SddId) -> Option<SddRef> {
259        self.unique_table.borrow().get(&id).cloned()
260    }
261
262    /// Retrieve an SDD from the unique table.
263    ///
264    /// # Panics
265    ///
266    /// Function panics if there is no such node with the corresponding id in the unique table.
267    #[must_use]
268    pub(crate) fn get_node(&self, id: SddId) -> SddRef {
269        self.unique_table
270            .borrow()
271            .get(&id)
272            .unwrap_or_else(|| panic!("SDD {id} is not in the unique_table"))
273            .clone()
274    }
275
276    pub(crate) fn get_nodes_normalized_for(&self, vtree_idx: VTreeIdx) -> Vec<(SddId, SddRef)> {
277        self.unique_table
278            .borrow()
279            .iter()
280            .filter(|(_, sdd)| !sdd.is_constant() && sdd.vtree().unwrap().index() == vtree_idx)
281            .map(|(id, sdd)| (*id, sdd.clone()))
282            .collect()
283    }
284
285    /// Remove a node from unique table. Result denotes whether the node
286    /// was present and therefore successfully removed.
287    /// TODO: This should be superseded by `remove_from_op_cache`.
288    pub(crate) fn remove_node(&self, id: SddId) -> Result<(), ()> {
289        tracing::debug!(id = id.0, "removing node from cache");
290        let entries: Vec<_> = {
291            self.op_cache
292                .borrow()
293                .iter()
294                .filter(|(entry, res)| entry.contains_id(id) || **res == id)
295                .map(|(entry, res)| (entry.clone(), *res))
296                .collect()
297        };
298
299        for (entry, _) in entries {
300            _ = self.op_cache.borrow_mut().remove(&entry).unwrap();
301        }
302
303        match self.unique_table.borrow_mut().remove(&id) {
304            Some(..) => Ok(()),
305            None => Err(()),
306        }
307    }
308
309    /// Remove a nodes from unique table and caches.
310    fn remove_from_op_cache(&self, ids: &FxHashSet<SddId>) {
311        let mut entries_to_remove = Vec::new();
312        for (fst, snd) in self.neg_cache.borrow().iter() {
313            if ids.contains(fst) || ids.contains(snd) {
314                entries_to_remove.push(*fst);
315                entries_to_remove.push(*snd);
316            }
317        }
318
319        let mut cache = self.neg_cache.borrow_mut();
320        for id in &entries_to_remove {
321            cache.remove(id);
322        }
323
324        let mut entries_to_remove = Vec::new();
325        for (entry @ Entry { fst, snd, .. }, res) in self.op_cache.borrow().iter() {
326            if ids.contains(fst) || ids.contains(snd) || ids.contains(res) {
327                entries_to_remove.push(entry.clone());
328            }
329        }
330
331        let mut cache = self.op_cache.borrow_mut();
332        for entry in &entries_to_remove {
333            cache.remove(entry);
334        }
335
336        let mut unique_table = self.unique_table.borrow_mut();
337        for id in ids {
338            unique_table.remove(id);
339        }
340    }
341
342    /// Retrieve the SDD representing a literal with given label and [`Polarity`].
343    /// Returns [`None`] if such variable does not exist.
344    pub fn literal(&self, label: &str, polarity: Polarity) -> Option<SddRef> {
345        let (_, variants) = self.literal_manager.borrow().find_by_label(label)?;
346        Some(variants.get(polarity))
347    }
348
349    /// Retrieve [`SddRef`] representing [`Literal`] with given label and [`Polarity`].
350    ///
351    /// # Panics
352    ///
353    /// Panics if the literal is not found. Since this function is only
354    /// called from within the crate, it should not happen and is considered a bug.
355    pub(crate) fn literal_from_idx(&self, idx: VariableIdx, polarity: Polarity) -> SddRef {
356        let Some((_, variants)) = self.literal_manager.borrow().find_by_index(idx) else {
357            panic!("literal with index {idx:?} has not been created!");
358        };
359
360        variants.get(polarity)
361    }
362
363    /// Retrieve [`SddRef`] representing constant true.
364    ///
365    /// # Panics
366    ///
367    /// Panics if constant true is not in the unique table, which should never happen.
368    pub fn tautology(&self) -> SddRef {
369        self.try_get_node(TRUE_SDD_IDX)
370            .expect("True SDD node must be present in the unique table at all times")
371    }
372
373    /// Retrieve [`SddRef`] representing constant false.
374    ///
375    /// # Panics
376    ///
377    /// Panics if constant false is not in the unique table, which should never happen.
378    pub fn contradiction(&self) -> SddRef {
379        self.try_get_node(FALSE_SDD_IDX)
380            .expect("False SDD node must be present in the unique table at all times")
381    }
382
383    /// Conjoin two SDDs.
384    ///
385    /// ```
386    /// # use sddrs::manager::{SddManager, options::SddOptions};
387    /// # use sddrs::literal::literal::Polarity;
388    /// # use bon::arr;
389    /// # let opts = SddOptions::builder().variables(arr!["A", "B"]).build();
390    /// # let manager = SddManager::new(&opts);
391    /// let a = manager.literal("A", Polarity::Positive).unwrap();
392    /// let n_b = manager.literal("B", Polarity::Negative).unwrap();
393    /// // Compiles SDD for function 'A ∧ ¬B':
394    /// let conjunction = manager.conjoin(&a, &n_b);
395    /// assert_eq!(manager.model_count(&conjunction), 1);
396    /// ```
397    #[must_use]
398    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
399    pub fn conjoin(&self, fst: &SddRef, snd: &SddRef) -> SddRef {
400        tracing::debug!(fst_id = fst.id().0, snd_id = snd.id().0);
401        if fst == snd {
402            return fst.clone();
403        }
404
405        if fst.is_false() {
406            return fst.clone();
407        }
408
409        if snd.is_false() {
410            return snd.clone();
411        }
412
413        if fst.is_true() {
414            return snd.clone();
415        }
416
417        if snd.is_true() {
418            return fst.clone();
419        }
420
421        if fst.eq_negated(snd, self) {
422            return self.contradiction();
423        }
424
425        self.apply(fst, snd, Operation::Conjoin)
426    }
427
428    /// Disjoin two SDDs.
429    ///
430    /// ```
431    /// # use sddrs::manager::{SddManager, options::SddOptions};
432    /// # use sddrs::literal::literal::Polarity;
433    /// # use bon::arr;
434    /// # let opts = SddOptions::builder().variables(arr!["A", "B"]).build();
435    /// # let manager = SddManager::new(&opts);
436    /// let a = manager.literal("A", Polarity::Positive).unwrap();
437    /// let n_b = manager.literal("B", Polarity::Negative).unwrap();
438    /// // Compiles SDD for function 'A ∨ ¬B':
439    /// let disjunction = manager.disjoin(&a, &n_b);
440    /// assert_eq!(manager.model_count(&disjunction), 3);
441    /// ```
442    #[must_use]
443    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
444    pub fn disjoin(&self, fst: &SddRef, snd: &SddRef) -> SddRef {
445        tracing::debug!(fst_id = fst.id().0, snd_id = snd.id().0);
446        if fst == snd {
447            return fst.clone();
448        }
449
450        if fst.is_true() {
451            return fst.clone();
452        }
453
454        if snd.is_true() {
455            return snd.clone();
456        }
457
458        if fst.is_false() {
459            return snd.clone();
460        }
461
462        if snd.is_false() {
463            return fst.clone();
464        }
465
466        if fst.eq_negated(snd, self) {
467            return self.tautology();
468        }
469
470        self.apply(fst, snd, Operation::Disjoin)
471    }
472
473    /// Negate an SDD.
474    ///
475    /// ```
476    /// # use sddrs::manager::{SddManager, options::SddOptions};
477    /// # use sddrs::literal::literal::Polarity;
478    /// # use bon::arr;
479    /// # let opts = SddOptions::builder().variables(arr!["A", "B"]).build();
480    /// # let manager = SddManager::new(&opts);
481    /// let a = manager.literal("A", Polarity::Positive).unwrap();
482    /// let n_b = manager.literal("B", Polarity::Negative).unwrap();
483    /// // Compiles SDD for function 'A ∨ ¬B'.
484    /// let disjunction = manager.disjoin(&a, &n_b);
485    /// // Negate 'A ∨ ¬B' to  '¬A ∧ B'.
486    /// let negation = manager.negate(&disjunction);
487    /// assert_eq!(manager.model_count(&negation), 1);
488    /// ```
489    #[must_use]
490    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
491    pub fn negate(&self, fst: &SddRef) -> SddRef {
492        if fst.is_true() {
493            return self.contradiction();
494        }
495        if fst.is_false() {
496            return self.tautology();
497        }
498
499        tracing::debug!(fst_id = fst.id().0);
500        fst.clone().negate(self)
501    }
502
503    /// Imply one SDD by another.
504    ///
505    /// ```
506    /// # use sddrs::manager::{SddManager, options::SddOptions};
507    /// # use sddrs::literal::literal::Polarity;
508    /// # use bon::arr;
509    /// # let opts = SddOptions::builder().variables(arr!["A", "B"]).build();
510    /// # let manager = SddManager::new(&opts);
511    /// let a = manager.literal("A", Polarity::Positive).unwrap();
512    /// let n_b = manager.literal("B", Polarity::Negative).unwrap();
513    /// // Compiles SDD for function 'A → ¬B'.
514    /// let implication = manager.imply(&a, &n_b);
515    /// assert_eq!(manager.model_count(&implication), 3);
516    /// ```
517    #[must_use]
518    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
519    pub fn imply(&self, fst: &SddRef, snd: &SddRef) -> SddRef {
520        tracing::debug!(fst_id = fst.id().0, snd_id = snd.id().0);
521        if fst == snd && fst.is_true() {
522            return snd.clone();
523        }
524
525        if fst.is_false() {
526            return self.tautology();
527        }
528
529        if fst.is_true() {
530            return snd.clone();
531        }
532
533        if fst.eq_negated(snd, self) {
534            return snd.clone();
535        }
536
537        // Relies on the fact that A => B is equivalent to !A || B.
538        self.apply(&fst.negate(self), snd, Operation::Disjoin)
539    }
540
541    /// Compute equivalence of two SDDs.
542    ///
543    /// ```
544    /// # use sddrs::manager::{SddManager, options::SddOptions};
545    /// # use sddrs::literal::literal::Polarity;
546    /// # use bon::arr;
547    /// # let opts = SddOptions::builder().variables(arr!["A", "B"]).build();
548    /// # let manager = SddManager::new(&opts);
549    /// let a = manager.literal("A", Polarity::Positive).unwrap();
550    /// let n_b = manager.literal("B", Polarity::Negative).unwrap();
551    /// // Compiles SDD for function 'A iff ¬B'.
552    /// let implication = manager.equiv(&a, &n_b);
553    /// assert_eq!(manager.model_count(&implication), 2);
554    /// ```
555    #[must_use]
556    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
557    pub fn equiv(&self, fst: &SddRef, snd: &SddRef) -> SddRef {
558        tracing::debug!(fst_id = fst.id().0, snd_id = snd.id().0);
559        if fst == snd {
560            return self.tautology();
561        }
562
563        if fst.eq_negated(snd, self) {
564            return self.contradiction();
565        }
566
567        // Relies on the fact that A <=> B is equivalent (!A && !B) || (A && B).
568        let fst_con = self.conjoin(&fst.negate(self), &snd.negate(self));
569        let snd_con = self.conjoin(fst, snd);
570        self.disjoin(&fst_con, &snd_con)
571    }
572
573    /// Enumerate models of the SDD.
574    pub fn model_enumeration(&self, sdd: &SddRef) -> Models {
575        let all_variables: BTreeSet<_> = self.literal_manager.borrow().all_variables();
576        let mut models: Vec<BitVec> = Vec::new();
577
578        if sdd.is_true() {
579            let all_variables: Vec<_> = all_variables.iter().cloned().collect();
580            SddManager::expand_models(&mut models, &all_variables);
581            return Models::new(&models, all_variables.clone());
582        } else if !sdd.is_false() {
583            // In the case of False, we do not add any models.
584            self.model_enumeration_rec(sdd, &mut models);
585
586            let all_variables: BTreeSet<_> = self.literal_manager.borrow().all_variables();
587            let unbound_variables: Vec<_> = all_variables
588                .difference(&self.get_variables(sdd))
589                .cloned()
590                .collect();
591
592            SddManager::expand_models(&mut models, &unbound_variables);
593        }
594
595        Models::new(&models, all_variables.iter().cloned().collect())
596    }
597
598    /// Count models of the SDD.
599    ///
600    /// # Panics
601    ///
602    /// Function panics if the number of variables cannot fit into u32.
603    pub fn model_count(&self, sdd: &SddRef) -> u64 {
604        if sdd.is_true() {
605            let all_variables = self.literal_manager.borrow().len();
606            return 2_u64.pow(u32::try_from(all_variables).unwrap());
607        }
608
609        if sdd.is_false() {
610            return 0;
611        }
612
613        let models = self.model_count_rec(sdd);
614
615        if self.root().index() == sdd.vtree().unwrap().index() {
616            return models;
617        }
618
619        let sdd_variables = self
620            .vtree_manager
621            .borrow()
622            .get_vtree(sdd.vtree().unwrap().index())
623            .unwrap()
624            .0
625            .borrow()
626            .get_variables()
627            .len();
628        let unbound = self.literal_manager.borrow().len() - sdd_variables;
629
630        models * 2_u64.pow(u32::try_from(unbound).unwrap())
631    }
632
633    /// Create a fragment given a heuristic [`FragmentHeuristic`].
634    fn create_fragment(&self, fragment_strategy: &FragmentHeuristic) -> Result<Fragment> {
635        let variables = self.literal_manager.borrow().len();
636        if variables <= 2 {
637            bail!("cannot construct a fragment: SddManager has only {variables} variables");
638        }
639
640        match fragment_strategy {
641            FragmentHeuristic::Custom(fragment) => Ok(fragment.clone()),
642            FragmentHeuristic::Root => {
643                let root = self.root();
644                if root.right_child().unwrap().is_internal() {
645                    Ok(Fragment::new(&root, &root.right_child().unwrap()))
646                } else if root.left_child().unwrap().is_internal() {
647                    Ok(Fragment::new(&root, &root.left_child().unwrap()))
648                } else {
649                    Err(anyhow!("cannot construct fragment from root since neither children are internal nodes"))
650                }
651            }
652            FragmentHeuristic::MostNormalized => {
653                fn find_root(manager: &SddManager, frequencies: &[i32]) -> VTreeRef {
654                    let root = frequencies
655                        .iter()
656                        .enumerate()
657                        .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal))
658                        .map(|(index, _)| index)
659                        .unwrap();
660
661                    let root = manager
662                        .vtree_manager
663                        .borrow()
664                        .get_vtree(VTreeIdx(u32::try_from(root).unwrap()))
665                        .unwrap();
666
667                    assert!(root.is_internal());
668
669                    root
670                }
671
672                // There are 2n-1 nodes in the vtree where n is the number
673                // of variables.
674                let nodes = 2 * self.options.variables.len() - 1;
675                let mut frequencies = vec![0; nodes];
676                for sdd in self.unique_table.borrow().values() {
677                    if sdd.is_constant_or_literal() {
678                        continue;
679                    }
680                    frequencies[sdd.vtree().unwrap().index().0 as usize] += 1;
681                }
682
683                let mut fragment = None;
684                loop {
685                    let root = find_root(self, &frequencies);
686                    let lc = root.left_child().unwrap();
687                    let rc = root.right_child().unwrap();
688
689                    if frequencies.iter().all(|freq| *freq == -1) {
690                        // We have explored all the nodes and none were suitable.
691                        break;
692                    }
693
694                    if frequencies[lc.index().0 as usize] > frequencies[rc.index().0 as usize]
695                        && lc.is_internal()
696                    {
697                        fragment = Some((root.clone(), lc.clone()));
698                        break;
699                    } else if rc.is_internal() {
700                        fragment = Some((root.clone(), rc.clone()));
701                        break;
702                    }
703
704                    // This root cannot be fragment's root since its children are not internal nodes.
705                    // Mark them as explored.
706                    frequencies[lc.index().0 as usize] = -1;
707                    frequencies[rc.index().0 as usize] = -1;
708                    frequencies[root.index().0 as usize] = 1;
709                }
710
711                match fragment {
712                    Some((root, child)) => Ok(Fragment::new(&root, &child)),
713                    None => Err(anyhow!("no suitable fragment found")),
714                }
715            }
716        }
717    }
718
719    /// Minimize SDDs via searching vtree fragments. [`MinimizationCutoff`] dictates
720    /// when to bail, [`FragmentHeuristic`] describes how to pick appropriate
721    /// vtree fragment to manipulate, and referential [`SddRef`] is the SDD
722    /// against which the minimization success is measured.
723    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
724    pub fn minimize(
725        &self,
726        cut_off: MinimizationCutoff,
727        fragment_strategy: &FragmentHeuristic,
728        reference_sdd: &SddRef,
729    ) -> Result<()> {
730        let mut fragment = match self.create_fragment(fragment_strategy) {
731            Ok(fragment) => fragment,
732            Err(err) => bail!(err.context("could not minize")),
733        };
734
735        tracing::debug!(sdd_id = reference_sdd.id().0, size = reference_sdd.size());
736
737        let init_size = reference_sdd.size();
738        let mut best_i: usize = 0;
739        let mut best_size = init_size;
740        let mut curr_size = init_size;
741        for (i, _) in (0..12).enumerate() {
742            fragment
743                .next(&Direction::Forward, self)
744                .with_context(|| format!("couild not move to {i}th fragment state"))?;
745
746            tracing::debug!(
747                iteration = i,
748                sdd_id = reference_sdd.id().0,
749                size = reference_sdd.size()
750            );
751
752            debug_assert!(reference_sdd.is_trimmed(self));
753            debug_assert!(reference_sdd.is_compressed(self));
754
755            curr_size = reference_sdd.size();
756            if curr_size <= best_size {
757                // We have found better (or equal) fragment state, mark the state we found it in
758                // so we can come back to it once we go through all fragment configurations.
759                best_size = curr_size;
760                best_i = i;
761            }
762
763            if SddManager::should_stop_minimizing(cut_off, init_size, curr_size, i) {
764                if best_i == i || curr_size == best_size {
765                    // We have fulfilled the searching criteria and the current vtree configuration
766                    // makes the reference sdd sufficiently small.
767                    return Ok(());
768                }
769                // We have fulfilled the searching criteria but we have already iterated over
770                // the best vtree configuration. We have to break out and rewind to it.
771                break;
772            }
773        }
774
775        // The last iteration is when we found the best fragment.
776        if curr_size == best_size {
777            return Ok(());
778        }
779
780        fragment
781            .rewind(best_i, self)
782            .with_context(|| format!("could not rewind to {best_i}th state"))?;
783        Ok(())
784    }
785
786    fn should_stop_minimizing(
787        cut_off: MinimizationCutoff,
788        init_size: u64,
789        curr_size: u64,
790        curr_iter: usize,
791    ) -> bool {
792        match cut_off {
793            MinimizationCutoff::Iteration(after_iter) => curr_iter >= after_iter,
794            MinimizationCutoff::BreakAfterFirstImprovement => curr_size <= init_size,
795            MinimizationCutoff::Decrease(decrease) => {
796                #[allow(clippy::cast_precision_loss)]
797                let ratio = curr_size as f64 / init_size as f64;
798                ratio - decrease >= f64::EPSILON
799            }
800            MinimizationCutoff::None => false,
801        }
802    }
803
804    /// Collect dead SDD nodes.
805    ///
806    /// # Panics
807    ///
808    /// Function panics if trying to collect nodes which are terminals, which
809    /// would mean bug in GC.
810    pub fn collect_garbage(&self) {
811        let mut removed = FxHashSet::default();
812
813        let roots: FxHashSet<_> = self
814            .unique_table
815            .borrow()
816            .values()
817            // An SDD is root if there is a single reference to it -- one
818            // from the unique_table. It therefore has no parents
819            // and the user does not point to it.
820            .filter(|sdd| {
821                sdd.0.try_borrow().is_ok() && !sdd.is_constant_or_literal() && sdd.strong_count() == 1
822            })
823            .map(SddRef::id)
824            .collect();
825
826        // Mark the roots as removed.
827        removed.extend(roots.iter());
828
829        for root in &roots {
830            let root = self.get_node(*root);
831
832            let mut queue: Vec<_> = root
833                .elements()
834                .unwrap()
835                .into_iter()
836                .flat_map(|Element { prime, sub }| [prime, sub])
837                .collect();
838            while let Some(sdd) = queue.pop() {
839                // 3 references means orphaned: 1 from unique_table, 1 from parent not present
840                // in the unique_table anymore and 1 from here.
841                if sdd.strong_count() == 3 && !sdd.is_constant_or_literal() {
842                    // Mark the node as removed.
843                    removed.insert(sdd.id());
844
845                    queue.extend(
846                        sdd.elements()
847                            .unwrap()
848                            .into_iter()
849                            .flat_map(|Element { prime, sub }| [prime, sub])
850                            .filter(|sdd| !sdd.is_constant_or_literal()),
851                    );
852                }
853            }
854        }
855
856        // Remove the removed SDDs from computational caches
857        // and the unique_table.
858        self.remove_from_op_cache(&removed);
859        self.gc_stats.borrow_mut().collected(removed.len());
860    }
861
862    fn should_collect_garbage(&self) -> bool {
863        matches!(
864            self.options.garbage_collection,
865            GarbageCollection::Automatic
866        ) && *self.apply_level.borrow() == 0
867    }
868
869    #[instrument(skip_all, level = tracing::Level::DEBUG)]
870    fn model_enumeration_rec(&self, sdd: &SddRef, bitvecs: &mut Vec<BitVec>) {
871        tracing::debug!(sdd_id = sdd.id().0);
872        // Return the cached value if it already exists.
873        if let Some(ref mut models) = sdd.models() {
874            tracing::debug!("has {} cached models", models.len());
875            bitvecs.append(models);
876            return;
877        }
878
879        if sdd.is_constant() {
880            return;
881        }
882
883        let mut all_models = Vec::new();
884        {
885            // Create a new scope to borrow sdd_type since we will mutate it later on due to caching.
886            let sdd_type = &sdd.0.borrow().sdd_type;
887
888            if let SddType::Literal(ref literal) = sdd_type {
889                let mut model = bitvec![usize, LocalBits; 0; self.literal_manager.borrow().len()];
890                model.set(
891                    literal.var_label().index().0 as usize,
892                    literal.polarity() == Polarity::Positive,
893                );
894                bitvecs.push(model);
895                return;
896            }
897
898            let SddType::Decision(decision) = sdd_type else {
899                panic!("every other sddType should've been handled ({sdd_type:?})",);
900            };
901
902            let all_variables = self.get_variables(sdd);
903            for Element { prime, sub } in &decision.elements {
904                let mut models = Vec::new();
905
906                if prime.is_false() || sub.is_false() {
907                    continue;
908                }
909
910                if prime.is_true() || sub.is_true() {
911                    if prime.is_true() {
912                        self.model_enumeration_rec(sub, &mut models);
913                    } else {
914                        self.model_enumeration_rec(prime, &mut models);
915                    }
916                } else {
917                    let mut fst = Vec::new();
918                    let mut snd = Vec::new();
919
920                    self.model_enumeration_rec(prime, &mut fst);
921                    self.model_enumeration_rec(sub, &mut snd);
922
923                    for fst_bv in &fst {
924                        for snd_bv in &snd {
925                            models.push(fst_bv.clone().bitor(snd_bv));
926                        }
927                    }
928                }
929
930                let all_reachable_variables = self
931                    .get_variables(prime)
932                    .union(&self.get_variables(sub))
933                    .cloned()
934                    .collect();
935                let unbound_variables: Vec<_> = all_variables
936                    .difference(&all_reachable_variables)
937                    .cloned()
938                    .collect();
939
940                SddManager::expand_models(&mut models, &unbound_variables);
941                all_models.append(&mut models);
942            }
943        }
944        bitvecs.append(&mut all_models);
945
946        sdd.cache_models(bitvecs);
947    }
948
949    /// Count number of models for this SDD.
950    fn model_count_rec(&self, sdd: &SddRef) -> u64 {
951        // Return the cached value if it already exists.
952        if let Some(model_count) = sdd.model_count() {
953            return model_count;
954        }
955
956        let mut total_models = 0;
957        {
958            // Create a new scope to borrow sdd_type since we will mutate it later on due to caching.
959            let SddType::Decision(ref decision) = sdd.0.borrow().sdd_type else {
960                panic!("every other sddType should've been handled");
961            };
962
963            let get_models_count = |sdd: &SddRef| {
964                if sdd.is_literal() || sdd.is_true() {
965                    1
966                } else if sdd.is_false() {
967                    0
968                } else {
969                    self.model_count_rec(sdd)
970                }
971            };
972
973            let all_variables = self.get_variables(sdd).len();
974
975            for Element { prime, sub } in &decision.elements {
976                let model_count = get_models_count(prime) * get_models_count(sub);
977
978                // Account for variables that do not appear in neither prime or sub.
979                let all_reachable = self
980                    .get_variables(prime)
981                    .union(&self.get_variables(sub))
982                    .count();
983                let unbound_variables = all_variables - all_reachable;
984
985                total_models += model_count * 2_u64.pow(u32::try_from(unbound_variables).unwrap());
986            }
987        }
988
989        sdd.cache_model_count(total_models);
990        total_models
991    }
992
993    /// Draw all SDDs present in the unique table to the .DOT Graphviz format.
994    ///
995    /// # Errors
996    ///
997    /// Function returns an error if the writing to a file or flushing fails.
998    pub fn draw_all_sdds(&self, writer: &mut dyn std::io::Write) -> Result<()> {
999        let mut dot_writer = DotWriter::new(String::from("sdd"), true);
1000        for sdd in self.unique_table.borrow().values() {
1001            sdd.draw(&mut dot_writer);
1002        }
1003        dot_writer.write(writer)
1004    }
1005
1006    /// Draw SDD to the .DOT Graphviz format.
1007    ///
1008    /// # Errors
1009    ///
1010    /// Function returns an error if the writing to a file or flushing fails.
1011    pub fn draw_sdd(&self, writer: &mut dyn std::io::Write, sdd: &SddRef) -> Result<()> {
1012        let mut dot_writer = DotWriter::new(String::from("sdd"), false);
1013        let mut seen = FxHashSet::default();
1014
1015        let mut sdds = vec![sdd.clone()];
1016        while let Some(sdd) = sdds.pop() {
1017            if seen.contains(&sdd.id()) {
1018                continue;
1019            }
1020
1021            sdd.draw(&mut dot_writer);
1022            seen.insert(sdd.id());
1023
1024            if let SddType::Decision(Decision { ref elements }) = sdd.0.borrow().sdd_type {
1025                elements
1026                    .iter()
1027                    .map(Element::get_prime_sub)
1028                    .for_each(|(prime, sub)| {
1029                        sdds.push(prime);
1030                        sdds.push(sub);
1031                    });
1032            };
1033        }
1034
1035        dot_writer.write(writer)
1036    }
1037
1038    /// Draw vtree to the .DOT Grapgiz format.
1039    ///
1040    /// # Errors
1041    ///
1042    /// TODO: Fix error types.
1043    pub fn draw_vtree(&self, writer: &mut dyn std::io::Write) -> Result<()> {
1044        let mut dot_writer = DotWriter::new(String::from("vtree"), false);
1045        self.vtree_manager.borrow().draw(&mut dot_writer);
1046        dot_writer.write(writer)
1047    }
1048
1049    /// Apply [`Operation`] on the two Sdds.
1050    #[must_use]
1051    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
1052    fn apply(&self, fst: &SddRef, snd: &SddRef, op: Operation) -> SddRef {
1053        tracing::debug!(fst_id = fst.id().0, snd_id = snd.id().0, ?op, "apply");
1054        let (fst, snd) = if fst.vtree().unwrap().index() < snd.vtree().unwrap().index() {
1055            (fst, snd)
1056        } else {
1057            (snd, fst)
1058        };
1059
1060        if let Some(result) =
1061            self.get_cached_operation(&CachedOperation::BinOp(fst.id(), op, snd.id()))
1062        {
1063            tracing::debug!(fst_id = fst.id().0, snd_id = snd.id().0, ?op, "cached");
1064            return self.get_node(result);
1065        }
1066
1067        let (lca, order) = self
1068            .vtree_manager
1069            .borrow()
1070            .least_common_ancestor(fst.vtree().unwrap().index(), snd.vtree().unwrap().index());
1071
1072        *self.apply_level.borrow_mut() += 1;
1073
1074        let elements = match order {
1075            VTreeOrder::Equal => self._apply_eq(fst, snd, op),
1076            VTreeOrder::Inequal => self._apply_ineq(fst, snd, op),
1077            VTreeOrder::LeftSubOfRight => self._apply_left_sub_of_right(fst, snd, op),
1078            VTreeOrder::RightSubOfLeft => self._apply_right_sub_of_left(fst, snd, op),
1079        };
1080
1081        let sdd = Sdd::unique_d(&elements, &lca, self).canonicalize(self);
1082
1083        debug_assert!(sdd.is_trimmed(self));
1084        debug_assert!(sdd.is_compressed(self));
1085
1086        self.insert_node(&sdd);
1087        self.cache_operation(&CachedOperation::BinOp(fst.id(), op, snd.id()), sdd.id());
1088
1089        *self.apply_level.borrow_mut() -= 1;
1090
1091        if self.should_collect_garbage() {
1092            self.collect_garbage();
1093        }
1094
1095        sdd
1096    }
1097
1098    /// Return garbage-collection-related statistics.
1099    pub fn gc_statistics(&self) -> GCStatistics {
1100        self.gc_stats.borrow().clone()
1101    }
1102
1103    /// Return the total number of distinct SDDs present in the unique table.
1104    pub fn total_sdds(&self) -> u64 {
1105        self.unique_table.borrow().len() as u64
1106    }
1107
1108    /// Apply [`Operation`] on SDDs normalized w.r.t. the same vtree. Returns
1109    /// X-partition yet to be canonicalized.
1110    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
1111    fn _apply_eq(&self, fst: &SddRef, snd: &SddRef, op: Operation) -> BTreeSet<Element> {
1112        tracing::debug!(fst_id = fst.id().0, snd_id = snd.id().0, ?op, "apply_eq");
1113        assert_eq!(fst.vtree().unwrap().index(), snd.vtree().unwrap().index());
1114        assert!(!fst.is_constant());
1115        assert!(!snd.is_constant());
1116
1117        let mut elements = BTreeSet::new();
1118        for Element {
1119            prime: fst_prime,
1120            sub: fst_sub,
1121        } in &fst.0.borrow().expand(self).elements
1122        {
1123            for Element {
1124                prime: snd_prime,
1125                sub: snd_sub,
1126            } in &snd.0.borrow().expand(self).elements
1127            {
1128                let res_prime = self.conjoin(fst_prime, snd_prime);
1129
1130                if !res_prime.is_false() {
1131                    let res_sub = match op {
1132                        Operation::Conjoin => self.conjoin(fst_sub, snd_sub),
1133                        Operation::Disjoin => self.disjoin(fst_sub, snd_sub),
1134                    };
1135
1136                    if res_sub.is_true() && res_prime.is_true() {
1137                        println!(
1138                            "_apply_eq: we can optimize since res_sub and res_prime are both true"
1139                        );
1140                    }
1141
1142                    elements.insert(Element {
1143                        prime: res_prime,
1144                        sub: res_sub,
1145                    });
1146                }
1147            }
1148        }
1149
1150        elements
1151    }
1152
1153    /// Apply [`Operation`] on incomparable SDDs. Returns X-partition yet to be canonicalized.
1154    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
1155    fn _apply_ineq(&self, fst: &SddRef, snd: &SddRef, op: Operation) -> BTreeSet<Element> {
1156        tracing::debug!(fst_id = fst.id().0, snd_id = snd.id().0, ?op, "apply_ineq");
1157        assert!(fst.vtree().unwrap().index() < snd.vtree().unwrap().index());
1158        assert!(!fst.is_constant());
1159        assert!(!snd.is_constant());
1160
1161        let fst_negated = fst.negate(self);
1162
1163        let apply = |fst: &SddRef, snd: &SddRef| {
1164            if op == Operation::Conjoin {
1165                self.conjoin(fst, snd)
1166            } else {
1167                self.disjoin(fst, snd)
1168            }
1169        };
1170
1171        let tt = self.tautology();
1172        let ff = self.contradiction();
1173
1174        btreeset!(
1175            Element {
1176                prime: fst.clone(),
1177                sub: apply(snd, &tt),
1178            },
1179            Element {
1180                prime: fst_negated,
1181                sub: apply(snd, &ff),
1182            }
1183        )
1184    }
1185
1186    /// Apply [`Operation`] on SDDs where vtree of `snd` is ancestor of `fst`'s .
1187    /// Returns X-partition yet to be canonicalized.
1188    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
1189    fn _apply_left_sub_of_right(
1190        &self,
1191        fst: &SddRef,
1192        snd: &SddRef,
1193        op: Operation,
1194    ) -> BTreeSet<Element> {
1195        tracing::debug!(
1196            fst_id = fst.id().0,
1197            snd_id = snd.id().0,
1198            ?op,
1199            "apply_left_sub_of_right"
1200        );
1201        assert!(fst.vtree().unwrap().index() < snd.vtree().unwrap().index());
1202        assert!(!fst.is_constant());
1203        assert!(!snd.is_constant());
1204
1205        let new_node = if op == Operation::Conjoin {
1206            fst
1207        } else {
1208            &fst.negate(self)
1209        };
1210
1211        let snd_elements = snd.0.borrow().sdd_type.elements().unwrap_or_else(||
1212            panic!(
1213                "snd of _apply_left_sub_of_right must be a decision node but was instead {} (id {})",
1214                snd.0.borrow().sdd_type.name(),
1215                snd.id(),
1216            )
1217        );
1218
1219        let mut elements = btreeset!(Element {
1220            prime: self.negate(new_node),
1221            sub: self.get_node(op.zero()),
1222        });
1223
1224        for Element {
1225            prime: snd_prime,
1226            sub: snd_sub,
1227        } in snd_elements
1228        {
1229            let new_prime = self.conjoin(&snd_prime, new_node);
1230            if !new_prime.is_false() {
1231                elements.insert(Element {
1232                    prime: new_prime,
1233                    sub: snd_sub,
1234                });
1235            }
1236        }
1237
1238        elements
1239    }
1240    /// Apply [`Operation`] on SDDs where vtree of `fst` is ancestor of `snd`'s .
1241    /// Returns X-partition yet to be canonicalized.
1242    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
1243    fn _apply_right_sub_of_left(
1244        &self,
1245        fst: &SddRef,
1246        snd: &SddRef,
1247        op: Operation,
1248    ) -> BTreeSet<Element> {
1249        tracing::debug!(
1250            fst_id = fst.id().0,
1251            snd_id = snd.id().0,
1252            ?op,
1253            "apply_right_sub_of_left"
1254        );
1255        assert!(fst.vtree().unwrap().index() < snd.vtree().unwrap().index());
1256        assert!(!fst.is_constant());
1257        assert!(!snd.is_constant());
1258
1259        let fst_elements = fst.0.borrow().sdd_type.elements().unwrap_or_else(||
1260            panic!(
1261                "fst of _apply_right_sub_of_left must be a decision node but was instead {} (id {})",
1262                fst.0.borrow().sdd_type.name(),
1263                fst.id(),
1264            )
1265        );
1266
1267        let mut elements = BTreeSet::new();
1268
1269        for Element {
1270            prime: fst_prime,
1271            sub: fst_sub,
1272        } in fst_elements
1273        {
1274            let new_sub = match op {
1275                Operation::Conjoin => self.conjoin(&fst_sub, snd),
1276                Operation::Disjoin => self.disjoin(&fst_sub, snd),
1277            };
1278
1279            elements.insert(Element {
1280                prime: fst_prime,
1281                sub: new_sub,
1282            });
1283        }
1284
1285        elements
1286    }
1287
1288    /// Insert a new [`SddRef`] into the unique table.
1289    pub(crate) fn insert_node(&self, sdd: &SddRef) {
1290        debug_assert!(sdd.is_trimmed(self));
1291        debug_assert!(sdd.is_compressed(self));
1292
1293        self.unique_table.borrow_mut().insert(sdd.id(), sdd.clone());
1294    }
1295
1296    /// Cache the result of a conjunction, disjunction, or negation.
1297    pub(crate) fn cache_operation(&self, op: &CachedOperation, result: SddId) {
1298        match op {
1299            CachedOperation::BinOp(fst, op, snd) => self.op_cache.borrow_mut().insert(
1300                Entry {
1301                    fst: *fst,
1302                    snd: *snd,
1303                    op: *op,
1304                },
1305                result,
1306            ),
1307            CachedOperation::Neg(fst) => self.neg_cache.borrow_mut().insert(*fst, result),
1308        };
1309    }
1310
1311    /// Retrieve the result of an operation from the cache, if it is already cached.
1312    pub(crate) fn get_cached_operation(&self, op: &CachedOperation) -> Option<SddId> {
1313        match op {
1314            CachedOperation::BinOp(fst, op, snd) => self
1315                .op_cache
1316                .borrow()
1317                .get(&Entry {
1318                    fst: *fst,
1319                    snd: *snd,
1320                    op: *op,
1321                })
1322                .copied(),
1323            CachedOperation::Neg(fst) => self.neg_cache.borrow().get(fst).copied(),
1324        }
1325    }
1326
1327    /// Get variables 'covered' by the vtree for which the SDD is normalized.
1328    fn get_variables(&self, sdd: &SddRef) -> BTreeSet<Variable> {
1329        if sdd.is_constant() {
1330            return BTreeSet::new();
1331        }
1332
1333        self.vtree_manager
1334            .borrow()
1335            .get_vtree(sdd.vtree().unwrap().index())
1336            .unwrap()
1337            .0
1338            .borrow()
1339            .get_variables()
1340    }
1341
1342    /// Expand [`models`] with all the possible instantiations of [`unbound _variables`].
1343    fn expand_models(models: &mut Vec<BitVec>, unbound_variables: &[Variable]) {
1344        if unbound_variables.is_empty() {
1345            return;
1346        }
1347
1348        let num_models = models.len();
1349        if unbound_variables.len() == 1 {
1350            let unbound_variable = unbound_variables.first().unwrap();
1351            for i in 0..num_models {
1352                let mut new_model = models.get(i).unwrap().clone();
1353                new_model.set(unbound_variable.index().0 as usize, true);
1354                models.push(new_model);
1355            }
1356
1357            return;
1358        }
1359
1360        for mask in 1..=unbound_variables.len() + 1 {
1361            let variables_to_set: Vec<_> = set_bits_indices(mask)
1362                .iter()
1363                .map(|&idx| unbound_variables.get(idx).unwrap())
1364                .collect();
1365
1366            for i in 0..num_models {
1367                let mut new_model = models.get(i).unwrap().clone();
1368                for variable_to_set in &variables_to_set {
1369                    new_model.set(variable_to_set.index().0 as usize, true);
1370                }
1371                models.push(new_model);
1372            }
1373        }
1374    }
1375
1376    /// Get the root of the vtree.
1377    pub fn root(&self) -> VTreeRef {
1378        self.vtree_manager.borrow().root()
1379    }
1380
1381    /// Rotate the vtree `x` to the left and adjust SDDs accordingly.
1382    /// The user must make sure that `x` is 'rotatable', i.e., `x`
1383    /// is an internal node and has a parent.
1384    ///
1385    /// ```text
1386    ///      w                x
1387    ///     / \              / \
1388    ///    a   x     ~>     w   c
1389    ///       / \          / \
1390    ///      b   c        a   b
1391    /// ```
1392    ///
1393    /// This is a low-level operation working directly on a vtree. See
1394    /// [`SddManager::minimize`] for a more sophisticated way of finding better vtrees.
1395    ///
1396    /// Children hanged at `w` must be split accordingly, depending on the vtrees
1397    /// they are normalized for:
1398    /// * `w(a, bc)` must be rotated and moved to `x` (~> `x(ab, c)`)
1399    /// * `w(a, c)` must be moved to `x` (~> `x(a, c)`)
1400    /// * `w(a, b)` stay at `w`
1401    ///
1402    /// # Errors
1403    ///
1404    /// The function returns an error if the node is not rotatable to the left.
1405    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
1406    pub fn rotate_left(&self, x: &VTreeRef) -> Result<()> {
1407        self.rotating.replace(true);
1408
1409        let w =
1410            x.0.borrow()
1411                .get_parent()
1412                .expect("invalid fragment: `x` does not have a parent");
1413
1414        let LeftRotateSplit { bc_vec, c_vec } = split_nodes_for_left_rotate(&w, x, self);
1415
1416        self.vtree_manager.borrow_mut().rotate_left(x)?;
1417
1418        for bc in &bc_vec {
1419            bc.replace_contents(SddType::Decision(Decision {
1420                elements: rotate_partition_left(bc, x, self).elements,
1421            }));
1422            bc.replace_contents(bc.canonicalize(self).0.borrow().sdd_type.clone());
1423            bc.set_vtree(x.clone());
1424
1425            debug_assert!(bc.is_compressed(self));
1426            debug_assert!(bc.is_trimmed(self));
1427        }
1428
1429        self.finalize_vtree_op(&bc_vec, &c_vec, x);
1430        self.invalidate_cached_models();
1431
1432        self.rotating.replace(false);
1433        Ok(())
1434    }
1435
1436    fn finalize_vtree_op(&self, replaced: &[SddRef], moved: &[SddRef], vtree: &VTreeRef) {
1437        for sdd in replaced {
1438            self.insert_node(sdd);
1439        }
1440
1441        for sdd in moved {
1442            sdd.set_vtree(vtree.clone());
1443            self.insert_node(sdd);
1444        }
1445    }
1446
1447    /// Rotate the vtree `x` to the right and adjust SDDs accordingly.
1448    /// The user must make sure that `x` is 'rotatable', i.e., `x`
1449    /// is an internal node an its left child `w` is an internal node as well.
1450    ///
1451    /// ```text
1452    ///       x                w
1453    ///      / \              / \
1454    ///     w   c     ~>     a   x
1455    ///    / \                  / \
1456    ///   a   b                b   c
1457    /// ```
1458    ///
1459    /// This is a low-level operation working directly on a vtree. See
1460    /// [`SddManager::minimize`] for a more sophisticated way of finding better vtrees.
1461    ///
1462    /// Children hanged at `w` must be split accordingly, depending on the vtrees
1463    /// they are normalized for:
1464    /// * `x(ab, c)` must be rotated and moved to `w` (~> `w(a, bc)`)
1465    /// * `x(a, c)` must be moved to `w` (~> `w(a, c)`)
1466    /// * `x(a, b)` stay at `x`
1467    ///
1468    /// # Errors
1469    ///
1470    /// The function returns an error if the node is not rotatable to the right.
1471    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
1472    pub fn rotate_right(&self, x: &VTreeRef) -> Result<()> {
1473        self.rotating.replace(true);
1474
1475        let w = x.left_child().unwrap();
1476        let RightRotateSplit { ab_vec, a_vec } = split_nodes_for_right_rotate(x, &w, self);
1477        self.vtree_manager.borrow_mut().rotate_right(x)?;
1478
1479        for ab in &ab_vec {
1480            ab.replace_contents(SddType::Decision(Decision {
1481                elements: rotate_partition_right(ab, &w, self).elements,
1482            }));
1483            ab.replace_contents(ab.canonicalize(self).0.borrow().sdd_type.clone());
1484            ab.set_vtree(w.clone());
1485
1486            debug_assert!(ab.is_compressed(self));
1487            debug_assert!(ab.is_trimmed(self));
1488        }
1489
1490        self.finalize_vtree_op(&ab_vec, &a_vec, &w);
1491        self.invalidate_cached_models();
1492
1493        self.rotating.replace(false);
1494        Ok(())
1495    }
1496
1497    /// Swap children of the given vtree `x` and adjust SDDs accordingly.
1498    /// The user must make sure that `x` is 'swappable', i.e., it is
1499    /// an internal node.
1500    ///
1501    /// ```text
1502    ///     x          x
1503    ///    / \   ~>   / \
1504    ///   a   b      b   a
1505    /// ```
1506    ///
1507    /// This is a low-level operation working directly on a vtree. See
1508    /// [`SddManager::minimize`] for a more sophisticated way of finding better vtrees.
1509    ///
1510    /// # Errors
1511    ///
1512    /// The function retursn an error if the node's children cannot be swapped.
1513    #[instrument(skip_all, ret, level = tracing::Level::DEBUG)]
1514    pub fn swap(&self, x: &VTreeRef) -> Result<()> {
1515        self.rotating.replace(true);
1516
1517        let split = split_nodes_for_swap(x, self);
1518        self.vtree_manager.borrow_mut().swap(x)?;
1519
1520        for sdd in &split {
1521            let dec = Decision {
1522                elements: swap_partition(sdd, self).elements,
1523            };
1524
1525            if let Some(trimmed) = dec.trim(self) {
1526                sdd.replace_contents(trimmed.0.borrow().sdd_type.clone());
1527                sdd.set_id(trimmed.id());
1528                if !sdd.is_constant() {
1529                    sdd.set_vtree(trimmed.vtree().unwrap());
1530                }
1531            } else {
1532                sdd.replace_contents(SddType::Decision(dec));
1533                sdd.set_vtree(x.clone());
1534            }
1535
1536            debug_assert!(sdd.is_compressed(self));
1537            debug_assert!(sdd.is_trimmed(self));
1538        }
1539
1540        self.finalize_vtree_op(&split, &[], x);
1541        self.invalidate_cached_models();
1542
1543        self.rotating.replace(false);
1544
1545        Ok(())
1546    }
1547
1548    fn invalidate_cached_models(&self) {
1549        // TODO: Verify that this can be deleted.
1550        for (_, sdd) in self.unique_table.borrow().iter() {
1551            sdd.0.borrow_mut().invalidate_cache();
1552        }
1553    }
1554
1555    /// Helper function to construct as new SDD.
1556    pub(crate) fn new_sdd_from_type(
1557        &self,
1558        sdd_type: SddType,
1559        vtree: VTreeRef,
1560        negation: Option<SddId>,
1561    ) -> SddRef {
1562        let sdd = SddRef::new(Sdd::new(sdd_type, *self.next_idx.borrow(), vtree));
1563        self.move_idx();
1564        if let Some(negation) = negation {
1565            self.cache_operation(&CachedOperation::Neg(sdd.id()), negation);
1566        }
1567
1568        self.insert_node(&sdd);
1569        sdd
1570    }
1571
1572    pub(crate) fn idx(&self) -> SddId {
1573        *self.next_idx.borrow()
1574    }
1575
1576    /// Move ID of the next SDD.
1577    pub(crate) fn move_idx(&self) {
1578        let mut idx = self.next_idx.borrow_mut();
1579        *idx += SddId(1);
1580    }
1581}
1582
1583#[cfg(test)]
1584mod test {
1585    #![allow(non_snake_case)]
1586
1587    use super::{SddManager, SddOptions};
1588    use crate::{
1589        literal::{Literal, Polarity, VariableIdx},
1590        manager::{
1591            model::Model,
1592            options::{GarbageCollection, VTreeStrategy},
1593        },
1594        util::quick_draw,
1595    };
1596    use bon::arr;
1597    use pretty_assertions::assert_eq;
1598
1599    #[test]
1600    fn simple_conjoin() {
1601        let options = SddOptions::builder().variables(arr!["a", "b"]).build();
1602        let manager = SddManager::new(&options);
1603
1604        let tt = manager.tautology();
1605        let ff = manager.contradiction();
1606
1607        assert_eq!(tt, manager.conjoin(&tt, &tt));
1608        assert_eq!(ff, manager.conjoin(&tt, &ff));
1609        assert_eq!(ff, manager.conjoin(&ff, &tt));
1610        assert_eq!(ff, manager.conjoin(&ff, &ff));
1611
1612        let lit_a = manager.literal("a", Polarity::Positive).unwrap();
1613        let lit_not_a = manager.literal("a", Polarity::Negative).unwrap();
1614
1615        assert_eq!(ff, manager.conjoin(&lit_a, &lit_not_a));
1616        assert_eq!(ff, manager.conjoin(&lit_not_a, &lit_a));
1617        assert_eq!(lit_a, manager.conjoin(&lit_a, &lit_a));
1618        assert_eq!(lit_not_a, manager.conjoin(&lit_not_a, &lit_not_a));
1619    }
1620
1621    #[test]
1622    fn simple_disjoin() {
1623        let options = SddOptions::builder().variables(arr!["a", "b"]).build();
1624        let manager = SddManager::new(&options);
1625
1626        let tt = manager.tautology();
1627        let ff = manager.contradiction();
1628
1629        assert_eq!(tt, manager.disjoin(&tt, &tt));
1630        assert_eq!(tt, manager.disjoin(&tt, &ff));
1631        assert_eq!(tt, manager.disjoin(&ff, &tt));
1632        assert_eq!(ff, manager.disjoin(&ff, &ff));
1633
1634        let lit_a = manager.literal("a", Polarity::Positive).unwrap();
1635        let lit_not_a = manager.literal("a", Polarity::Negative).unwrap();
1636
1637        assert_eq!(tt, manager.disjoin(&lit_a, &lit_not_a));
1638        assert_eq!(tt, manager.disjoin(&lit_not_a, &lit_a));
1639        assert_eq!(lit_a, manager.disjoin(&lit_a, &lit_a));
1640        assert_eq!(lit_not_a, manager.disjoin(&lit_not_a, &lit_not_a));
1641    }
1642
1643    #[test]
1644    fn simple_negate() {
1645        let options = SddOptions::builder().variables(arr!["a", "b"]).build();
1646        let manager = SddManager::new(&options);
1647
1648        let tt = manager.tautology();
1649        let ff = manager.contradiction();
1650
1651        assert_eq!(ff, manager.negate(&tt));
1652        assert_eq!(tt, manager.negate(&ff));
1653
1654        let lit_a = manager.literal("a", Polarity::Positive).unwrap();
1655        let lit_not_a = manager.literal("a", Polarity::Negative).unwrap();
1656
1657        assert_eq!(lit_a, manager.negate(&lit_not_a));
1658        assert_eq!(lit_not_a, manager.negate(&lit_a));
1659    }
1660
1661    #[test]
1662    fn simple_imply() {
1663        let options = SddOptions::builder().variables(arr!["a", "b"]).build();
1664        let manager = SddManager::new(&options);
1665
1666        let tt = manager.tautology();
1667        let ff = manager.contradiction();
1668
1669        assert_eq!(ff, manager.imply(&tt, &ff));
1670        assert_eq!(tt, manager.imply(&ff, &ff));
1671
1672        let lit_a = manager.literal("a", Polarity::Positive).unwrap();
1673        let lit_not_a = manager.literal("a", Polarity::Negative).unwrap();
1674
1675        // A => !A <=> !A && !A <=> !A
1676        assert_eq!(lit_not_a, manager.imply(&lit_a, &lit_not_a));
1677        // !A => A <=> !!A && A <=> A
1678        assert_eq!(lit_a, manager.imply(&lit_not_a, &lit_a));
1679    }
1680
1681    #[test]
1682    fn simple_equiv() {
1683        let options = SddOptions::builder().variables(arr!["a", "b"]).build();
1684        let manager = SddManager::new(&options);
1685
1686        let tt = manager.tautology();
1687        let ff = manager.contradiction();
1688
1689        assert_eq!(ff, manager.equiv(&tt, &ff));
1690        assert_eq!(tt, manager.equiv(&ff, &ff));
1691
1692        let lit_a = manager.literal("a", Polarity::Positive).unwrap();
1693        let lit_not_a = manager.literal("a", Polarity::Negative).unwrap();
1694
1695        assert_eq!(tt, manager.equiv(&lit_a, &lit_a));
1696        assert_eq!(ff, manager.equiv(&lit_a, &lit_not_a));
1697    }
1698
1699    #[test]
1700    fn apply() {
1701        let options = SddOptions::builder()
1702            .variables(arr!["a", "b", "c", "d"])
1703            .garbage_collection(GarbageCollection::Automatic)
1704            .build();
1705        let manager = SddManager::new(&options);
1706
1707        let lit_a = manager.literal("a", Polarity::Positive).unwrap();
1708        let lit_b = manager.literal("b", Polarity::Positive).unwrap();
1709        let lit_d = manager.literal("d", Polarity::Positive).unwrap();
1710        //           3
1711        //         /   \
1712        //        1     5
1713        //      / |     | \
1714        //     0  2     4  6
1715        //     A  B     C  D
1716
1717        // Resulting SDD must be normalized w.r.t. vtree with index 3.
1718        let a_and_d = manager.conjoin(&lit_a, &lit_d);
1719        assert_eq!(a_and_d.vtree().unwrap().index().0, 3);
1720
1721        // Resulting SDD must be normalized w.r.t. vtree with index 3.
1722        let a_and_d__and_b = manager.conjoin(&a_and_d, &lit_b);
1723        assert_eq!(a_and_d__and_b.vtree().unwrap().index().0, 3);
1724    }
1725
1726    #[test]
1727    fn model_counting() {
1728        let options = SddOptions::builder()
1729            .variables(arr!["a", "b", "c", "d"])
1730            .build();
1731        let manager = SddManager::new(&options);
1732
1733        let lit_a = manager.literal("a", Polarity::Positive).unwrap();
1734        let lit_b = manager.literal("b", Polarity::Positive).unwrap();
1735        let lit_c = manager.literal("c", Polarity::Positive).unwrap();
1736        let lit_d = manager.literal("d", Polarity::Positive).unwrap();
1737
1738        let a_and_d = manager.conjoin(&lit_a, &lit_d);
1739        assert_eq!(manager.model_count(&a_and_d), 4);
1740
1741        let a_or_d = manager.disjoin(&a_and_d, &lit_a);
1742        assert_eq!(manager.model_count(&a_or_d), manager.model_count(&lit_a));
1743
1744        let a_and_b = manager.conjoin(&lit_a, &lit_b);
1745        assert_eq!(manager.model_count(&a_and_b), 4);
1746
1747        // A && B && B == A && B
1748        let a_and_b_and_b = manager.conjoin(&a_and_b, &lit_b);
1749        assert_eq!(
1750            manager.model_count(&a_and_b_and_b),
1751            manager.model_count(&a_and_b)
1752        );
1753
1754        let a_and_b_and_c = manager.conjoin(&a_and_b, &lit_c);
1755        assert_eq!(manager.model_count(&a_and_b_and_c), 2);
1756
1757        let a_and_b_and_c_or_d = manager.disjoin(&a_and_b_and_c, &lit_d);
1758        assert_eq!(manager.model_count(&a_and_b_and_c_or_d), 9);
1759
1760        assert_eq!(manager.model_count(&manager.tautology()), 2_u64.pow(4));
1761        assert_eq!(manager.model_count(&manager.contradiction()), 0);
1762    }
1763
1764    #[test]
1765    fn model_enumeration() {
1766        // This test is broken down into two parts since the first
1767        // part uses only a single literal 'a'.
1768        {
1769            let options = SddOptions::builder().variables(arr!["a"]).build();
1770            let manager = SddManager::new(&options);
1771            let lit_a = manager.literal("a", Polarity::Positive).unwrap();
1772
1773            assert_eq!(
1774                manager.model_enumeration(&lit_a).all_models(),
1775                vec![Model::new_from_literals(&[Literal::new(
1776                    Polarity::Positive,
1777                    "a",
1778                    VariableIdx(0)
1779                )])]
1780            );
1781        }
1782
1783        {
1784            let options = SddOptions::builder()
1785                .variables(arr!["a", "b", "c", "d"])
1786                .build();
1787            let manager = SddManager::new(&options);
1788            let lit_a = manager.literal("a", Polarity::Positive).unwrap();
1789            let lit_b = manager.literal("b", Polarity::Positive).unwrap();
1790            let lit_c = manager.literal("c", Polarity::Positive).unwrap();
1791            let lit_d = manager.literal("d", Polarity::Positive).unwrap();
1792
1793            let a_and_b = manager.conjoin(&lit_a, &lit_b);
1794            let models = &[
1795                Model::new_from_literals(&[
1796                    Literal::new(Polarity::Positive, "a", VariableIdx(0)),
1797                    Literal::new(Polarity::Positive, "b", VariableIdx(1)),
1798                    Literal::new(Polarity::Negative, "c", VariableIdx(2)),
1799                    Literal::new(Polarity::Negative, "d", VariableIdx(3)),
1800                ]),
1801                Model::new_from_literals(&[
1802                    Literal::new(Polarity::Positive, "a", VariableIdx(0)),
1803                    Literal::new(Polarity::Positive, "b", VariableIdx(1)),
1804                    Literal::new(Polarity::Negative, "c", VariableIdx(2)),
1805                    Literal::new(Polarity::Positive, "d", VariableIdx(3)),
1806                ]),
1807                Model::new_from_literals(&[
1808                    Literal::new(Polarity::Positive, "a", VariableIdx(0)),
1809                    Literal::new(Polarity::Positive, "b", VariableIdx(1)),
1810                    Literal::new(Polarity::Positive, "c", VariableIdx(2)),
1811                    Literal::new(Polarity::Negative, "d", VariableIdx(3)),
1812                ]),
1813                Model::new_from_literals(&[
1814                    Literal::new(Polarity::Positive, "a", VariableIdx(0)),
1815                    Literal::new(Polarity::Positive, "b", VariableIdx(1)),
1816                    Literal::new(Polarity::Positive, "c", VariableIdx(2)),
1817                    Literal::new(Polarity::Positive, "d", VariableIdx(3)),
1818                ]),
1819            ];
1820
1821            assert_eq!(manager.model_enumeration(&a_and_b).all_models(), models);
1822
1823            let a_and_b_and_c = manager.conjoin(&a_and_b, &lit_c);
1824
1825            let models = &[
1826                Model::new_from_literals(&[
1827                    Literal::new(Polarity::Positive, "a", VariableIdx(0)),
1828                    Literal::new(Polarity::Positive, "b", VariableIdx(1)),
1829                    Literal::new(Polarity::Positive, "c", VariableIdx(2)),
1830                    Literal::new(Polarity::Negative, "d", VariableIdx(3)),
1831                ]),
1832                Model::new_from_literals(&[
1833                    Literal::new(Polarity::Positive, "a", VariableIdx(0)),
1834                    Literal::new(Polarity::Positive, "b", VariableIdx(1)),
1835                    Literal::new(Polarity::Positive, "c", VariableIdx(2)),
1836                    Literal::new(Polarity::Positive, "d", VariableIdx(3)),
1837                ]),
1838            ];
1839
1840            assert_eq!(
1841                manager.model_enumeration(&a_and_b_and_c).all_models(),
1842                models
1843            );
1844
1845            let a_and_b_and_c_and_d = manager.conjoin(&a_and_b_and_c, &lit_d);
1846            let models = &[Model::new_from_literals(&[
1847                Literal::new(Polarity::Positive, "a", VariableIdx(0)),
1848                Literal::new(Polarity::Positive, "b", VariableIdx(1)),
1849                Literal::new(Polarity::Positive, "c", VariableIdx(2)),
1850                Literal::new(Polarity::Positive, "d", VariableIdx(3)),
1851            ])];
1852            assert_eq!(
1853                manager.model_enumeration(&a_and_b_and_c_and_d).all_models(),
1854                models,
1855            );
1856
1857            let not_a = manager.literal("a", Polarity::Negative).unwrap();
1858            let ff = manager.conjoin(&not_a, &a_and_b_and_c_and_d);
1859            assert_eq!(manager.model_enumeration(&ff).all_models(), vec![]);
1860        }
1861    }
1862
1863    #[test]
1864    fn left_rotation() {
1865        let options = SddOptions::builder()
1866            .vtree_strategy(VTreeStrategy::RightLinear)
1867            .garbage_collection(GarbageCollection::Automatic)
1868            .variables(arr!["a", "b", "c", "d"])
1869            .build();
1870        let manager = SddManager::new(&options);
1871
1872        let lit_a = manager.literal("a", Polarity::Positive).unwrap();
1873        let lit_b = manager.literal("b", Polarity::Positive).unwrap();
1874        let lit_c = manager.literal("c", Polarity::Positive).unwrap();
1875        let lit_d = manager.literal("d", Polarity::Positive).unwrap();
1876
1877        let a_and_d = manager.conjoin(&lit_a, &lit_d);
1878        let a_and_d_and_b = manager.conjoin(&a_and_d, &lit_b);
1879        let a_and_d_and_b_and_c = manager.conjoin(&a_and_d_and_b, &lit_c);
1880        let models_before = manager.model_enumeration(&a_and_d_and_b_and_c);
1881
1882        // Rotating right child of the root to the left makes the vtree balanced.
1883        manager
1884            .rotate_left(&manager.root().right_child().unwrap())
1885            .unwrap();
1886
1887        let models_after = manager.model_enumeration(&a_and_d_and_b_and_c);
1888        assert_eq!(models_before, models_after);
1889    }
1890
1891    #[test]
1892    fn right_rotation() {
1893        let options = SddOptions::builder()
1894            .vtree_strategy(VTreeStrategy::LeftLinear)
1895            .garbage_collection(GarbageCollection::Automatic)
1896            .variables(arr!["a", "b", "c", "d"])
1897            .build();
1898        let manager = SddManager::new(&options);
1899
1900        let lit_a = manager.literal("a", Polarity::Positive).unwrap();
1901        let lit_b = manager.literal("b", Polarity::Positive).unwrap();
1902        let lit_c = manager.literal("c", Polarity::Positive).unwrap();
1903        let lit_d = manager.literal("d", Polarity::Positive).unwrap();
1904
1905        let a_and_d = manager.conjoin(&lit_a, &lit_d);
1906        let a_and_d_and_b = manager.conjoin(&a_and_d, &lit_b);
1907        let a_and_d_and_b_and_c = manager.conjoin(&a_and_d_and_b, &lit_c);
1908        let models_before = manager.model_enumeration(&a_and_d_and_b_and_c);
1909
1910        // Rotating the root to the right makes the vtree balanced.
1911        manager.rotate_right(&manager.root()).unwrap();
1912
1913        let models_after = manager.model_enumeration(&a_and_d_and_b_and_c);
1914        assert_eq!(models_before, models_after);
1915    }
1916
1917    #[test]
1918    fn swap() {
1919        let options = SddOptions::builder()
1920            .vtree_strategy(VTreeStrategy::Balanced)
1921            .garbage_collection(GarbageCollection::Automatic)
1922            .variables(arr!["a", "b", "c", "d"])
1923            .build();
1924        let manager = SddManager::new(&options);
1925
1926        let lit_a = manager.literal("a", Polarity::Positive).unwrap();
1927        let lit_b = manager.literal("b", Polarity::Positive).unwrap();
1928        let lit_c = manager.literal("c", Polarity::Positive).unwrap();
1929        let lit_d = manager.literal("d", Polarity::Positive).unwrap();
1930
1931        let a_and_d = manager.conjoin(&lit_a, &lit_d);
1932        let a_and_d_and_b = manager.conjoin(&a_and_d, &lit_b);
1933        let a_and_d_and_b_and_c = manager.conjoin(&a_and_d_and_b, &lit_c);
1934        quick_draw(&manager, &a_and_d_and_b_and_c, "easy");
1935        assert_eq!(a_and_d_and_b_and_c.size(), 8);
1936
1937        let models_before = manager.model_enumeration(&a_and_d_and_b_and_c);
1938
1939        manager.swap(&manager.root()).unwrap();
1940
1941        let models_after = manager.model_enumeration(&a_and_d_and_b_and_c);
1942        assert_eq!(models_before, models_after);
1943    }
1944}