ruddy/split/
bdd.rs

1use crate::bdd_node::{BddNode16, BddNode32, BddNode64, BddNodeAny, BddNodeImpl};
2use crate::conversion::{UncheckedFrom, UncheckedInto};
3use crate::node_id::{AsNodeId, NodeId, NodeId16, NodeId64};
4use crate::node_id::{NodeId32, NodeIdAny};
5use crate::variable_id::{
6    AsVarId, VarIdPacked16, VarIdPacked32, VarIdPacked64, VarIdPackedAny, VariableId,
7    variables_between,
8};
9use std::fmt::Debug;
10use std::io::{self, Write};
11
12/// A trait implemented by types that can serve as *standalone* BDDs.
13///
14/// Internally, a standalone BDD is a collection of [`BddNodeAny`] instances, such that one node is
15/// designated as the *root* of the BDD. Aside from the root, a non-trivial BDD must also contain
16/// a single [`BddNodeAny::zero`] node identified by [`NodeIdAny::zero`] and a single [`BddNodeAny::one`] node
17/// identified by [`NodeIdAny::one`]. Finally, the graph induced by the [`BddNodeAny::low`] and
18/// [`BddNodeAny::high`] edges must not contain any cycles (except for the self-loops on the
19/// two terminal nodes).
20///
21/// Note that it is not required for the nodes to (1) be sorted in any specific way; (2) be
22/// reachable from the root node; (3) adhere to any specific variable ordering or structural
23/// properties other than those outlined above. In other words, an arbitrary instance of [`BddAny`]
24/// is not required to be an OBDD (ordered BDD) or ROBDD (reduced and ordered BDD). However,
25/// these properties are typically enforced in practice by the implementations of this trait.
26///
27/// Finally, note that standalone BDDs are typically assumed to be immutable. There are certain
28/// situations where mutability makes sense (for example, to allow chaining of multiple smaller
29/// structural changes without unnecessary copying), but this is mostly an exception to the rule.
30/// In particular, no method in the actual trait allows mutability.
31pub(crate) trait BddAny: Debug + Clone {
32    /// The type of node ID used by [`BddAny::Node`].
33    type Id: NodeIdAny;
34    /// The type of variable ID used by [`BddAny::Node`].
35    type VarId: VarIdPackedAny;
36    /// The type of node used in this [`BddAny`].
37    type Node: BddNodeAny<Id = Self::Id, VarId = Self::VarId>;
38
39    /// Create a new BDD representing the constant boolean function `true`.
40    fn new_true() -> Self;
41    /// Create a new BDD representing the constant boolean function `false`.
42    fn new_false() -> Self;
43    /// Create a new BDD representing the boolean function `var=value`.
44    fn new_literal(var: Self::VarId, value: bool) -> Self;
45
46    /// Returns `true` if the BDD represents the constant boolean function `true`.
47    fn is_true(&self) -> bool;
48
49    /// Returns `true` if the BDD represents the constant boolean function `false`.
50    fn is_false(&self) -> bool;
51
52    /// Create a new instance of [`BddAny`] using a raw list of [`BddNodeAny`] items and a single
53    /// [`NodeIdAny`] root.
54    ///
55    /// ## Safety
56    ///
57    /// This function is unsafe because it can be used to create a BDD object that does not
58    /// respect the prescribed invariants of [`BddAny`]. For example, it can be used to create
59    /// a BDD that is not acyclic or a BDD that is not reduced.
60    unsafe fn new_unchecked(root: Self::Id, nodes: Vec<Self::Node>) -> Self;
61
62    /// ID of the BDD root node.
63    fn root(&self) -> Self::Id;
64    /// Get a (checked) reference to a node, or `None` if such a node does not exist.
65    #[allow(dead_code)]
66    fn get(&self, id: Self::Id) -> Option<&Self::Node>;
67
68    /// An unchecked variant of [`BddAny::get`].
69    ///
70    /// # Safety
71    ///
72    /// Calling this method with an `id` that is not in the bdd is undefined behavior.
73    unsafe fn get_node_unchecked(&self, id: Self::Id) -> &Self::Node;
74}
75
76/// A generic implementation of [`BddAny`] using [`BddNodeImpl`]. In addition to
77/// the requirements of the [`BddAny`] trait, this struct also expects the BDD to
78/// be ordered and reduced.
79#[derive(Debug, Clone)]
80pub(crate) struct BddImpl<TNodeId: NodeIdAny, TVarId: VarIdPackedAny> {
81    pub(crate) root: TNodeId,
82    pub(crate) nodes: Vec<BddNodeImpl<TNodeId, TVarId>>,
83}
84
85impl<TNodeId: NodeIdAny, TVarId: VarIdPackedAny> BddImpl<TNodeId, TVarId> {
86    /// Returns the number of nodes in the BDD, including the terminal nodes.
87    pub fn node_count(&self) -> usize {
88        self.nodes.len()
89    }
90
91    /// Calculate a BDD representing the boolean formula `!self` (negation).
92    fn not(&self) -> Self {
93        if self.is_true() {
94            return Self::new_false();
95        }
96        if self.is_false() {
97            return Self::new_true();
98        }
99
100        let mut nodes = self.nodes.clone();
101        for node in nodes.iter_mut().skip(2) {
102            node.low = node.low.flipped_if_terminal();
103            node.high = node.high.flipped_if_terminal();
104        }
105
106        Self {
107            root: self.root,
108            nodes,
109        }
110    }
111
112    /// Compares the two BDDs structurally, i.e., by comparing their roots and the
113    /// underlying lists of nodes.
114    ///
115    /// Note that this does not guarantee that the two BDDs represent the same boolean function,
116    /// unless their nodes are also ordered the same way (which they are, assuming the BDDs were
117    /// created using the same `apply` algorithm).
118    pub fn structural_eq(&self, other: &Self) -> bool {
119        self.root == other.root && self.nodes == other.nodes
120    }
121
122    /// Get the largest [`VariableId`] in the BDD, assuming it does not represent
123    /// a constant function.
124    pub(crate) fn get_largest_variable(&self) -> VariableId {
125        self.nodes
126            .iter()
127            .map(|node| node.variable())
128            .reduce(TVarId::max_defined)
129            .expect("BDD is not constant")
130            .unchecked_into()
131    }
132
133    /// Approximately counts the number of satisfying paths in the BDD.
134    fn count_satisfying_paths(&self) -> f64 {
135        if self.is_false() {
136            return 0.0;
137        }
138
139        // Use a negative value to indicate that the count is not yet computed.
140        let mut counts = vec![-1.0f64; self.node_count()];
141        counts[0] = 0.0;
142        counts[1] = 1.0;
143
144        let root = self.root;
145
146        let mut stack = vec![root];
147
148        while let Some(id) = stack.pop() {
149            if counts[id.as_usize()] >= 0.0 {
150                continue;
151            }
152
153            let node = unsafe { self.get_node_unchecked(id) };
154            let low = node.low;
155            let high = node.high;
156            let low_count = counts[low.as_usize()];
157            let high_count = counts[high.as_usize()];
158            let low_is_done = low_count >= 0.0;
159            let high_is_done = high_count >= 0.0;
160
161            if low_is_done && high_is_done {
162                counts[id.as_usize()] = low_count + high_count;
163                continue;
164            }
165
166            stack.push(id);
167
168            if !low_is_done {
169                stack.push(low);
170            }
171
172            if !high_is_done {
173                stack.push(high);
174            }
175        }
176        let result: f64 = counts[root.as_usize()];
177        debug_assert!(result >= 0.0);
178        debug_assert!(!result.is_nan());
179        result
180    }
181
182    /// Approximately counts the number of satisfying valuations in the BDD. If
183    /// `largest_variable` is [`Some`], then it is assumed to be the largest
184    /// variable. Otherwise, the largest variable in the BDD is used.
185    ///
186    /// Assumes that the given variable is greater than or equal to any
187    /// variable in the BDD. Otherwise, the function may give unexpected results
188    /// in release mode or panic in debug mode.
189    fn count_satisfying_valuations(&self, largest_variable: Option<VariableId>) -> f64 {
190        if self.is_false() {
191            return 0.0;
192        }
193
194        if self.is_true() {
195            if let Some(largest_variable) = largest_variable {
196                let exponent = (Into::<u64>::into(largest_variable) + 1)
197                    .try_into()
198                    .unwrap_or(f64::MAX_EXP);
199                return 2.0f64.powi(exponent);
200            }
201            return 1.0f64;
202        }
203
204        let largest_variable = largest_variable.unwrap_or_else(|| self.get_largest_variable());
205
206        // Use a negative value to indicate that the count is not yet computed.
207        let mut counts = vec![-1.0f64; self.node_count()];
208        counts[0] = 0.0;
209        counts[1] = 1.0;
210
211        let root = self.root;
212
213        let mut stack = vec![root];
214
215        while let Some(id) = stack.pop() {
216            if counts[id.as_usize()] >= 0.0 {
217                continue;
218            }
219
220            let node = unsafe { self.get_node_unchecked(id) };
221            let low = node.low();
222            let high = node.high();
223            let low_count = counts[low.as_usize()];
224            let high_count = counts[high.as_usize()];
225            let low_is_done = low_count >= 0.0;
226            let high_is_done = high_count >= 0.0;
227
228            let low_node = unsafe { self.get_node_unchecked(low) };
229            let high_node = unsafe { self.get_node_unchecked(high) };
230
231            let variable = node.variable();
232            let low_variable = low_node.variable();
233            let high_variable = high_node.variable();
234
235            if low_is_done && high_is_done {
236                let skipped = variables_between(low_variable, variable, largest_variable)
237                    .try_into()
238                    .unwrap_or(f64::MAX_EXP);
239                let low_count = low_count * 2.0f64.powi(skipped);
240
241                let skipped = variables_between(high_variable, variable, largest_variable)
242                    .try_into()
243                    .unwrap_or(f64::MAX_EXP);
244                let high_count = high_count * 2.0f64.powi(skipped);
245
246                counts[id.as_usize()] = low_count + high_count;
247
248                continue;
249            }
250
251            stack.push(id);
252
253            if !low_is_done {
254                stack.push(low);
255            }
256
257            if !high_is_done {
258                stack.push(high);
259            }
260        }
261
262        debug_assert!(counts[root.as_usize()] >= 0.0);
263
264        let root_variable = unsafe { self.get_node_unchecked(root) }.variable();
265        let result = counts[root.as_usize()]
266            * 2.0f64.powi(
267                root_variable
268                    .unpack_u64()
269                    .try_into()
270                    .unwrap_or(f64::MAX_EXP),
271            );
272
273        if result.is_nan() {
274            f64::INFINITY
275        } else {
276            result
277        }
278    }
279}
280
281impl<TNodeId: NodeIdAny, TVarId: VarIdPackedAny> BddAny for BddImpl<TNodeId, TVarId> {
282    type Id = TNodeId;
283    type VarId = TVarId;
284    type Node = BddNodeImpl<TNodeId, TVarId>;
285
286    fn new_true() -> Self {
287        Self {
288            root: TNodeId::one(),
289            nodes: vec![BddNodeImpl::zero(), BddNodeImpl::one()],
290        }
291    }
292
293    fn new_false() -> Self {
294        Self {
295            root: TNodeId::zero(),
296            nodes: vec![BddNodeImpl::zero()],
297        }
298    }
299
300    fn new_literal(var: Self::VarId, value: bool) -> Self {
301        let node = if value {
302            Self::Node::new(var, TNodeId::zero(), TNodeId::one())
303        } else {
304            Self::Node::new(var, TNodeId::one(), TNodeId::zero())
305        };
306        Self {
307            root: 2usize.unchecked_into(),
308            nodes: vec![Self::Node::zero(), Self::Node::one(), node],
309        }
310    }
311
312    fn is_true(&self) -> bool {
313        debug_assert!(self.root.is_one() == (self.nodes.len() == 2));
314        self.root.is_one()
315    }
316
317    fn is_false(&self) -> bool {
318        debug_assert!(self.root.is_zero() == (self.nodes.len() == 1));
319        self.root.is_zero()
320    }
321
322    unsafe fn new_unchecked(root: Self::Id, nodes: Vec<Self::Node>) -> Self {
323        Self { root, nodes }
324    }
325
326    fn root(&self) -> Self::Id {
327        self.root
328    }
329
330    fn get(&self, id: Self::Id) -> Option<&Self::Node> {
331        self.nodes.get(id.as_usize())
332    }
333
334    unsafe fn get_node_unchecked(&self, id: Self::Id) -> &Self::Node {
335        unsafe { self.nodes.get_unchecked(id.as_usize()) }
336    }
337}
338
339/// An implementation of [`BddAny`] using [`BddNode16`]. In addition to the requirements of the
340/// [`BddAny`] trait, this type also expects the BDD to be ordered and reduced.
341pub(crate) type Bdd16 = BddImpl<NodeId16, VarIdPacked16>;
342
343/// An implementation of [`BddAny`] using [`BddNode32`]. In addition to the requirements of the
344/// [`BddAny`] trait, this type also expects the BDD to be ordered and reduced.
345pub(crate) type Bdd32 = BddImpl<NodeId32, VarIdPacked32>;
346
347/// An implementation of [`BddAny`] using [`BddNode64`]. In addition to the requirements of the
348/// [`BddAny`] trait, this type also expects the BDD to be ordered and reduced.
349pub(crate) type Bdd64 = BddImpl<NodeId64, VarIdPacked64>;
350
351/// A trait indicating that the node and variable IDs of the BDD can be upcast
352/// to the node and variable IDs of the BDD specified by the generic type.
353pub(crate) trait AsBdd<TBdd: BddAny>:
354    BddAny<Id: AsNodeId<TBdd::Id>, VarId: AsVarId<TBdd::VarId>>
355{
356}
357
358impl AsBdd<Bdd16> for Bdd16 {}
359impl AsBdd<Bdd32> for Bdd16 {}
360impl AsBdd<Bdd64> for Bdd16 {}
361impl AsBdd<Bdd32> for Bdd32 {}
362impl AsBdd<Bdd64> for Bdd32 {}
363impl AsBdd<Bdd64> for Bdd64 {}
364
365macro_rules! impl_unchecked_from {
366    ($Large:ident => $Small:ident) => {
367        impl UncheckedFrom<$Large> for $Small {
368            fn unchecked_from(large: $Large) -> Self {
369                Self {
370                    root: large.root.unchecked_into(),
371                    nodes: large
372                        .nodes
373                        .into_iter()
374                        .map(UncheckedInto::unchecked_into)
375                        .collect(),
376                }
377            }
378        }
379    };
380}
381
382impl_unchecked_from!(Bdd32 => Bdd16);
383impl_unchecked_from!(Bdd64 => Bdd16);
384impl_unchecked_from!(Bdd64 => Bdd32);
385
386impl<TNodeId: NodeIdAny, TVarId: VarIdPackedAny> BddImpl<TNodeId, TVarId> {
387    /// Write this BDD as a DOT graph to the given `output` stream.
388    fn write_as_dot(&self, output: &mut dyn Write) -> io::Result<()> {
389        writeln!(output, "digraph BDD {{")?;
390        writeln!(
391            output,
392            "  __ruddy_root [label=\"\", style=invis, height=0, width=0];"
393        )?;
394
395        writeln!(output, "  __ruddy_root -> {};", self.root)?;
396        writeln!(output)?;
397        writeln!(output, "  edge [dir=none];")?;
398        writeln!(output)?;
399
400        writeln!(
401            output,
402            "  0 [label=\"0\", shape=box, width=0.3, height=0.3];"
403        )?;
404        writeln!(
405            output,
406            "  1 [label=\"1\", shape=box, width=0.3, height=0.3];"
407        )?;
408
409        for (id, node) in self.nodes.iter().enumerate().skip(2) {
410            let low = node.low();
411            let high = node.high();
412            writeln!(
413                output,
414                "  {} [label=\"{}\", shape=box, width=0.3, height=0.3];",
415                id,
416                node.variable()
417            )?;
418            writeln!(output, "  {id} -> {low} [style=dashed];")?;
419            writeln!(output, "  {id} -> {high};")?;
420        }
421
422        writeln!(output, "}}")?;
423
424        Ok(())
425    }
426}
427
428#[derive(Clone, Debug)]
429pub(crate) enum BddInner {
430    Size16(Bdd16),
431    Size32(Bdd32),
432    Size64(Bdd64),
433}
434
435/// A type representing a binary decision diagram, which is split, i.e., owns all
436/// of its nodes in a vector and is immutable.
437///
438/// Consequently, each operation produces a new BDD, which is independent of its
439/// operands.
440///
441/// This allows the BDD to be easily copied and even shared between threads. Moreover,
442/// there is no need to perform explicit garbage collection --- once the `Bdd` is dropped,
443/// so are all of its nodes.
444///
445/// However, being self-contained, if multiple BDDs share the same subgraph,
446/// each needs to store a copy of it. Furthermore, supporting data structures like the
447/// unique node table and computed cache are rebuilt for each operation, not shared or reused.
448#[derive(Clone, Debug)]
449pub struct Bdd(pub(crate) BddInner);
450
451impl From<Bdd16> for Bdd {
452    fn from(bdd: Bdd16) -> Self {
453        Self(BddInner::Size16(bdd))
454    }
455}
456
457impl From<Bdd32> for Bdd {
458    fn from(bdd: Bdd32) -> Self {
459        Self(BddInner::Size32(bdd))
460    }
461}
462
463impl From<Bdd64> for Bdd {
464    fn from(bdd: Bdd64) -> Self {
465        Self(BddInner::Size64(bdd))
466    }
467}
468
469impl Bdd {
470    /// Creates a new BDD representing the constant boolean function `true`.
471    pub fn new_true() -> Self {
472        Bdd16::new_true().into()
473    }
474
475    /// Creates a new BDD representing the constant boolean function `false`.
476    pub fn new_false() -> Self {
477        Bdd16::new_false().into()
478    }
479
480    /// Creates a new BDD representing the boolean function `var=value`.
481    pub fn new_literal(var: VariableId, value: bool) -> Self {
482        if var.fits_in_packed16() {
483            Bdd16::new_literal(var.unchecked_into(), value).into()
484        } else if var.fits_in_packed32() {
485            Bdd32::new_literal(var.unchecked_into(), value).into()
486        } else if var.fits_in_packed64() {
487            Bdd64::new_literal(var.unchecked_into(), value).into()
488        } else {
489            unreachable!("Maximum representable variable identifier exceeded.");
490        }
491    }
492
493    /// Returns the number of nodes in the `Bdd`, including the terminal nodes.
494    pub fn node_count(&self) -> usize {
495        match &self.0 {
496            BddInner::Size16(bdd) => bdd.node_count(),
497            BddInner::Size32(bdd) => bdd.node_count(),
498            BddInner::Size64(bdd) => bdd.node_count(),
499        }
500    }
501
502    /// Calculate a `Bdd` representing the boolean formula `!self` (negation).
503    pub fn not(&self) -> Self {
504        match &self.0 {
505            BddInner::Size16(bdd) => bdd.not().into(),
506            BddInner::Size32(bdd) => bdd.not().into(),
507            BddInner::Size64(bdd) => bdd.not().into(),
508        }
509    }
510
511    /// Returns `true` if the BDD represents the constant boolean function `true`.
512    pub fn is_true(&self) -> bool {
513        match &self.0 {
514            BddInner::Size16(bdd) => bdd.is_true(),
515            BddInner::Size32(bdd) => bdd.is_true(),
516            BddInner::Size64(bdd) => bdd.is_true(),
517        }
518    }
519
520    /// Returns `true` if the BDD represents the constant boolean function `false`.
521    pub fn is_false(&self) -> bool {
522        match &self.0 {
523            BddInner::Size16(bdd) => bdd.is_false(),
524            BddInner::Size32(bdd) => bdd.is_false(),
525            BddInner::Size64(bdd) => bdd.is_false(),
526        }
527    }
528
529    /// Shrink the BDD to the smallest possible bit-width.
530    ///
531    /// * If the BDD has less than `2**16` nodes and all variables fit in 16 bits, it will be
532    ///   shrunk to a 16-bit BDD.
533    /// * If the BDD has less than `2**32` nodes and all variables fit in 32 bits, it will be
534    ///   shrunk to a 32-bit BDD.
535    /// * Otherwise, the BDD will remain the same bit-width.
536    pub(crate) fn shrink(self) -> Self {
537        match self.0 {
538            BddInner::Size64(bdd) if bdd.node_count() < 1 << 32 => {
539                let (mut vars_fit_in_16, mut vars_fit_in_32) = (true, true);
540                for node in bdd.nodes.iter() {
541                    vars_fit_in_16 &= node.variable().fits_in_packed16();
542                    vars_fit_in_32 &= node.variable().fits_in_packed32();
543
544                    if !vars_fit_in_16 && !vars_fit_in_32 {
545                        break;
546                    }
547                }
548
549                if (bdd.node_count() < 1 << 16) && vars_fit_in_16 {
550                    let bdd16: Bdd16 = bdd.unchecked_into();
551                    return bdd16.into();
552                }
553
554                if vars_fit_in_32 {
555                    let bdd32: Bdd32 = bdd.unchecked_into();
556                    return bdd32.into();
557                }
558                bdd.into()
559            }
560            BddInner::Size32(bdd)
561                if (bdd.node_count() < 1 << 16)
562                    && bdd
563                        .nodes
564                        .iter()
565                        .all(|node| node.variable().fits_in_packed16()) =>
566            {
567                let bdd16: Bdd16 = bdd.unchecked_into();
568                bdd16.into()
569            }
570            _ => self,
571        }
572    }
573
574    /// Compares the two `Bdd`-s structurally, i.e., by comparing their roots and the
575    /// underlying lists of nodes.
576    ///
577    /// Note that this does not guarantee that the two BDDs represent the same boolean function,
578    /// unless their nodes are also ordered the same way (which they are, assuming the BDDs were
579    /// created using the same `apply` algorithm).
580    pub fn structural_eq(&self, other: &Self) -> bool {
581        match (&self.0, &other.0) {
582            (BddInner::Size16(a), BddInner::Size16(b)) => a.structural_eq(b),
583            (BddInner::Size32(a), BddInner::Size32(b)) => a.structural_eq(b),
584            (BddInner::Size64(a), BddInner::Size64(b)) => a.structural_eq(b),
585            _ => false,
586        }
587    }
588
589    /// Returns the identifier of the `Bdd` root node.
590    pub fn root(&self) -> NodeId {
591        match &self.0 {
592            BddInner::Size16(bdd) => bdd.root().unchecked_into(),
593            BddInner::Size32(bdd) => bdd.root().unchecked_into(),
594            BddInner::Size64(bdd) => bdd.root().unchecked_into(),
595        }
596    }
597
598    /// Returns the variable identifier of the given `node`.
599    pub fn get_variable(&self, node: NodeId) -> VariableId {
600        let index: usize = node.unchecked_into();
601        match &self.0 {
602            BddInner::Size16(bdd) => bdd.nodes[index].variable.unpack().into(),
603            BddInner::Size32(bdd) => bdd.nodes[index].variable.unpack().into(),
604            BddInner::Size64(bdd) => VariableId::new_long(bdd.nodes[index].variable.unpack())
605                .unwrap_or_else(|| {
606                    unreachable!("Variable stored in BDD table does not fit into standard range.")
607                }),
608        }
609    }
610
611    /// Returns the `low` and `high` children of the given `node`.
612    pub fn get_links(&self, node: NodeId) -> (NodeId, NodeId) {
613        // The unchecked casts are necessary to ensure we are not using any undefined values.
614        let index: usize = node.unchecked_into();
615        match &self.0 {
616            BddInner::Size16(bdd) => {
617                let BddNode16 { low, high, .. } = bdd.nodes[index];
618                (low.unchecked_into(), high.unchecked_into())
619            }
620            BddInner::Size32(bdd) => {
621                let BddNode32 { low, high, .. } = bdd.nodes[index];
622                (low.unchecked_into(), high.unchecked_into())
623            }
624            BddInner::Size64(bdd) => {
625                let BddNode64 { low, high, .. } = bdd.nodes[index];
626                (low.unchecked_into(), high.unchecked_into())
627            }
628        }
629    }
630
631    /// Approximately counts the number of `Bdd` satisfying valuations. If
632    /// `largest_variable` is [`Some`], then it is assumed to be the largest
633    /// variable. Otherwise, the largest variable in the BDD is used.
634    ///
635    /// # Panics
636    ///
637    /// Assumes that the given variable is greater than or equal to than any
638    /// variable in the BDD. Otherwise, the function may give unexpected results
639    /// in release mode or panic in debug mode.
640    pub fn count_satisfying_valuations(&self, largest_variable: Option<VariableId>) -> f64 {
641        match &self.0 {
642            BddInner::Size16(bdd) => bdd.count_satisfying_valuations(largest_variable),
643            BddInner::Size32(bdd) => bdd.count_satisfying_valuations(largest_variable),
644            BddInner::Size64(bdd) => bdd.count_satisfying_valuations(largest_variable),
645        }
646    }
647
648    /// Approximately counts the number of satisfying paths in the `Bdd`.
649    pub fn count_satisfying_paths(&self) -> f64 {
650        match &self.0 {
651            BddInner::Size16(bdd) => bdd.count_satisfying_paths(),
652            BddInner::Size32(bdd) => bdd.count_satisfying_paths(),
653            BddInner::Size64(bdd) => bdd.count_satisfying_paths(),
654        }
655    }
656
657    /// Writes this `Bdd` as a DOT graph to the given `output` stream.
658    pub fn write_bdd_as_dot(&self, output: &mut dyn Write) -> io::Result<()> {
659        match &self.0 {
660            BddInner::Size16(bdd) => bdd.write_as_dot(output),
661            BddInner::Size32(bdd) => bdd.write_as_dot(output),
662            BddInner::Size64(bdd) => bdd.write_as_dot(output),
663        }
664    }
665
666    /// Converts this `Bdd` into a DOT graph string.
667    pub fn to_dot_string(&self) -> String {
668        let mut output = Vec::new();
669        self.write_bdd_as_dot(&mut output).unwrap();
670        String::from_utf8(output).unwrap()
671    }
672}
673
674#[cfg(test)]
675pub(crate) mod tests {
676    use crate::bdd_node::BddNodeAny;
677    use crate::conversion::UncheckedInto;
678    use crate::node_id::{NodeId, NodeId16, NodeId32, NodeId64, NodeIdAny};
679    use crate::split::bdd::{Bdd, Bdd16, Bdd32, Bdd64, BddAny};
680    use crate::variable_id::{VarIdPacked16, VarIdPacked32, VarIdPacked64, VariableId};
681
682    use super::BddInner;
683
684    macro_rules! test_bdd_not_invariants {
685        ($func:ident, $Bdd:ident, $VarId:ident) => {
686            #[test]
687            pub fn $func() {
688                assert!($Bdd::new_true().not().structural_eq(&$Bdd::new_false()));
689                assert!($Bdd::new_false().not().structural_eq(&$Bdd::new_true()));
690
691                let v = $VarId::new(1);
692                let bdd = $Bdd::new_literal(v, true);
693                assert!(bdd.not().not().structural_eq(&bdd));
694            }
695        };
696    }
697
698    test_bdd_not_invariants!(bdd16_not_invariants, Bdd16, VarIdPacked16);
699    test_bdd_not_invariants!(bdd32_not_invariants, Bdd32, VarIdPacked32);
700    test_bdd_not_invariants!(bdd64_not_invariants, Bdd64, VarIdPacked64);
701
702    macro_rules! test_bdd_invariants {
703        ($func:ident, $Bdd:ident, $VarId:ident, $NodeId:ident) => {
704            #[test]
705            pub fn $func() {
706                assert!($Bdd::new_true().root().is_one());
707                assert!($Bdd::new_true().is_true());
708                assert!($Bdd::new_false().root().is_zero());
709                assert!($Bdd::new_false().is_false());
710
711                let v = $VarId::new(1);
712                let x = $Bdd::new_literal(v, true);
713                assert!(!x.root().is_terminal());
714                assert!(x.get($NodeId::new(3)).is_none());
715                assert_eq!(v, x.get(x.root()).unwrap().variable());
716                assert_eq!(x.node_count(), 3);
717                unsafe {
718                    assert_eq!(v, x.get_node_unchecked(x.root()).variable());
719                    let x_p = $Bdd::new_unchecked(x.root(), x.nodes.clone());
720                    assert!(x.structural_eq(&x_p));
721                }
722            }
723        };
724    }
725
726    test_bdd_invariants!(bdd16_invariants, Bdd16, VarIdPacked16, NodeId16);
727    test_bdd_invariants!(bdd32_invariants, Bdd32, VarIdPacked32, NodeId32);
728    test_bdd_invariants!(bdd64_invariants, Bdd64, VarIdPacked64, NodeId64);
729
730    #[test]
731    fn bdd_expands_to_32_and_shrinks_to_16() {
732        let n: u16 = 16;
733        // Create a BDD with 2n-2 variables v_1, ..., v_{2n-2} for the function
734        // f(v_1, ..., v_{2n-2}) = v_1 * v_2 + v_3 * v_4 + ... + v_{2n-3} * v_{2n-2}.
735        // With the variable ordering v_1 < v_3 < ... < v_{2n-3} < v_2 < v_4 < ... < v_{2n-2}.
736        // The BDD will have 2^n nodes, hence it should grow to a 32-bit BDD.
737        let low_vars: Vec<_> = (1..n).map(VariableId::from).collect();
738        let high_vars: Vec<_> = (n + 1..2 * n).map(VariableId::from).collect();
739
740        let mut bdd = Bdd::new_false();
741        let mut bdd32 = Bdd32::new_false();
742
743        for i in 0..high_vars.len() {
744            let prod =
745                Bdd::new_literal(low_vars[i], true).and(&Bdd::new_literal(high_vars[i], true));
746
747            let prod32 = Bdd32::new_literal(low_vars[i].unchecked_into(), true)
748                .and(&Bdd32::new_literal(high_vars[i].unchecked_into(), true))
749                .unwrap();
750
751            bdd = bdd.or(&prod);
752            bdd32 = bdd32.or(&prod32).unwrap();
753        }
754
755        assert_eq!(bdd.node_count(), 1 << n);
756        assert_eq!(bdd32.node_count(), 1 << n);
757
758        // Check that the BDD grew correctly.
759        match &bdd.0 {
760            BddInner::Size32(b) => {
761                // both checks should not be necessary, but they are here as a sanity check
762                assert!(b.iff(&bdd32).unwrap().is_true());
763                assert!(b.structural_eq(&bdd32));
764            }
765            _ => panic!("expected 32-bit BDD"),
766        }
767
768        // Now, transform the BDD into the function
769        // f(v_1, ..., v_{2n-2}) = v_1 * v_2 * v_3 * v_4 * ... * v_{2n-3} * v_{2n-2}.
770        // The BDD will have 32 nodes and hence should shrink to a 16-bit BDD.
771
772        let mut bdd16_ands = Bdd16::new_true();
773
774        for i in 0..high_vars.len() {
775            let prod =
776                Bdd::new_literal(low_vars[i], true).and(&Bdd::new_literal(high_vars[i], true));
777
778            let prod16 = Bdd16::new_literal(low_vars[i].unchecked_into(), true)
779                .and(&Bdd16::new_literal(high_vars[i].unchecked_into(), true))
780                .unwrap();
781
782            bdd = bdd.and(&prod);
783            bdd16_ands = bdd16_ands.and(&prod16).unwrap();
784        }
785
786        assert_eq!(bdd.node_count(), usize::from(2 * n));
787
788        // Check that the BDD shrank correctly.
789        match &bdd.0 {
790            BddInner::Size16(b) => {
791                assert!(b.iff(&bdd16_ands).unwrap().is_true());
792                assert!(b.structural_eq(&bdd16_ands));
793            }
794            _ => panic!("expected 16-bit BDD"),
795        }
796    }
797
798    /// Since creating a BDD with more than 2^32 nodes is impractical, we
799    /// have to create a 64-bit BDD manually and then test if it shrinks correctly.
800    ///
801    /// See [`bdd_expands_to_32_and_shrinks_to_16`] for details on the BDD structure.
802    #[test]
803    fn bdd_64_shrink_to_32() {
804        let n = 17;
805        let low_vars: Vec<_> = (1..n).map(VarIdPacked64::new).collect();
806        let high_vars: Vec<_> = (n + 1..2 * n).map(VarIdPacked64::new).collect();
807
808        let mut bdd64 = Bdd64::new_false();
809        let mut bdd32 = Bdd32::new_false();
810
811        for i in 0..high_vars.len() {
812            let prod = Bdd64::new_literal(low_vars[i], true)
813                .and(&Bdd64::new_literal(high_vars[i], true))
814                .unwrap();
815
816            let prod32 = Bdd32::new_literal(low_vars[i].try_into().unwrap(), true)
817                .and(&Bdd32::new_literal(high_vars[i].try_into().unwrap(), true))
818                .unwrap();
819
820            bdd64 = bdd64.or(&prod).unwrap();
821            bdd32 = bdd32.or(&prod32).unwrap();
822        }
823
824        assert_eq!(bdd64.node_count(), 1 << n);
825
826        let bdd = Into::<Bdd>::into(bdd64).shrink();
827
828        assert_eq!(bdd.node_count(), 1 << n);
829
830        match &bdd.0 {
831            BddInner::Size32(b) => {
832                assert!(b.iff(&bdd32).unwrap().is_true());
833                assert!(b.structural_eq(&bdd32));
834            }
835            _ => panic!("expected 32-bit BDD"),
836        }
837    }
838
839    /// Similar to [`bdd_64_shrink_to_32`], but shrinks to a 16-bit BDD.
840    ///
841    /// See [`bdd_expands_to_32_and_shrinks_to_16`] for details on the BDD structure.
842    #[test]
843    fn bdd_64_shrink_to_16() {
844        let n = 4;
845        let low_vars: Vec<_> = (1..n).map(VarIdPacked64::new).collect();
846        let high_vars: Vec<_> = (n + 1..2 * n).map(VarIdPacked64::new).collect();
847
848        let mut bdd16 = Bdd16::new_false();
849        let mut bdd64 = Bdd64::new_false();
850
851        for i in 0..high_vars.len() {
852            let prod = Bdd64::new_literal(low_vars[i], true)
853                .and(&Bdd64::new_literal(high_vars[i], true))
854                .unwrap();
855
856            let prod16 = Bdd16::new_literal(low_vars[i].try_into().unwrap(), true)
857                .and(&Bdd16::new_literal(high_vars[i].try_into().unwrap(), true))
858                .unwrap();
859
860            bdd64 = bdd64.or(&prod).unwrap();
861            bdd16 = bdd16.or(&prod16).unwrap();
862        }
863
864        assert_eq!(bdd64.node_count(), 1 << n);
865
866        let bdd = Into::<Bdd>::into(bdd64).shrink();
867
868        assert_eq!(bdd.node_count(), 1 << n);
869
870        match &bdd.0 {
871            BddInner::Size16(b) => {
872                assert!(b.iff(&bdd16).unwrap().is_true());
873                assert!(b.structural_eq(&bdd16));
874            }
875            _ => panic!("expected 16-bit BDD"),
876        }
877    }
878
879    #[test]
880    fn new_bdd_literal_16() {
881        let var = VariableId::from(1u32);
882        let bdd = Bdd::new_literal(var, true);
883        let bdd16 = Bdd16::new_literal(var.unchecked_into(), true);
884
885        match &bdd.0 {
886            BddInner::Size16(b) => {
887                assert!(b.iff(&bdd16).unwrap().is_true());
888                assert!(b.structural_eq(&bdd16));
889            }
890            _ => panic!("expected 16-bit BDD"),
891        }
892    }
893
894    #[test]
895    fn new_bdd_literal_32() {
896        let max_id: u32 = VariableId::MAX_16_BIT_ID.try_into().unwrap();
897        let var = VariableId::from(max_id + 1);
898        let bdd = Bdd::new_literal(var, true);
899        let bdd32 = Bdd32::new_literal(var.unchecked_into(), true);
900
901        match &bdd.0 {
902            BddInner::Size32(b) => {
903                assert!(b.iff(&bdd32).unwrap().is_true());
904                assert!(b.structural_eq(&bdd32));
905            }
906            _ => panic!("expected 32-bit BDD"),
907        }
908    }
909
910    #[test]
911    fn new_bdd_literal_64() {
912        let var = VariableId::new_long(VariableId::MAX_32_BIT_ID + 1).unwrap();
913        let bdd = Bdd::new_literal(var, true);
914        let bdd64 = Bdd64::new_literal(var.unchecked_into(), true);
915
916        match &bdd.0 {
917            BddInner::Size64(b) => {
918                assert!(b.iff(&bdd64).unwrap().is_true());
919                assert!(b.structural_eq(&bdd64));
920            }
921            _ => panic!("expected 64-bit BDD"),
922        }
923    }
924
925    #[test]
926    fn new_bdd_literal_64_but_should_be_16() {
927        let var = VariableId::from(1u32);
928        let bdd = Bdd::new_literal(var, true);
929        let bdd16 = Bdd16::new_literal(var.unchecked_into(), true);
930
931        match &bdd.0 {
932            BddInner::Size16(b) => {
933                assert!(b.iff(&bdd16).unwrap().is_true());
934                assert!(b.structural_eq(&bdd16));
935            }
936            _ => panic!("expected 16-bit BDD"),
937        }
938    }
939
940    #[test]
941    fn new_bdd_literal_32_but_should_be_16() {
942        let var = VariableId::from(1u32);
943        let bdd = Bdd::new_literal(var, true);
944        let bdd16 = Bdd16::new_literal(var.unchecked_into(), true);
945
946        match &bdd.0 {
947            BddInner::Size16(b) => {
948                assert!(b.iff(&bdd16).unwrap().is_true());
949                assert!(b.structural_eq(&bdd16));
950            }
951            _ => panic!("expected 16-bit BDD"),
952        }
953    }
954
955    #[test]
956    fn new_bdd_literal_64_but_should_be_32() {
957        let var = VariableId::new_long(VariableId::MAX_16_BIT_ID + 1).unwrap();
958        let bdd = Bdd::new_literal(var, true);
959        let bdd32 = Bdd32::new_literal(var.unchecked_into(), true);
960
961        match &bdd.0 {
962            BddInner::Size32(b) => {
963                assert!(b.iff(&bdd32).unwrap().is_true());
964                assert!(b.structural_eq(&bdd32));
965            }
966            _ => panic!("expected 32-bit BDD"),
967        }
968    }
969
970    #[test]
971    fn bdd_node_count() {
972        let bdd16 = Bdd::new_literal(VariableId::new(1u32 << 8), true);
973        let bdd32 = Bdd::new_literal(VariableId::new(1u32 << 24), true);
974        let bdd64 = Bdd::new_literal(VariableId::new_long(1u64 << 48).unwrap(), true);
975
976        assert_eq!(bdd16.node_count(), 3);
977        assert_eq!(bdd32.node_count(), 3);
978        assert_eq!(bdd64.node_count(), 3);
979    }
980
981    #[test]
982    fn bdd_simple_not() {
983        let bdd16_1 = Bdd::new_literal(VariableId::new(1u32 << 8), true);
984        let bdd32_1 = Bdd::new_literal(VariableId::new(1u32 << 24), true);
985        let bdd64_1 = Bdd::new_literal(VariableId::new_long(1u64 << 48).unwrap(), true);
986
987        let bdd16_0 = Bdd::new_literal(VariableId::new(1u32 << 8), false);
988        let bdd32_0 = Bdd::new_literal(VariableId::new(1u32 << 24), false);
989        let bdd64_0 = Bdd::new_literal(VariableId::new_long(1u64 << 48).unwrap(), false);
990
991        assert!(bdd16_1.not().structural_eq(&bdd16_0));
992        assert!(bdd32_1.not().structural_eq(&bdd32_0));
993        assert!(bdd64_1.not().structural_eq(&bdd64_0));
994    }
995
996    #[test]
997    fn bdd_structural_eq() {
998        // Test that BDDs of different sizes are not equal even if they have "the same" nodes.
999        let bdd16: Bdd = Bdd16::new_literal(VarIdPacked16::new(1234), true).into();
1000        let bdd32: Bdd = Bdd32::new_literal(VarIdPacked32::new(1234), true).into();
1001        assert!(!bdd16.structural_eq(&bdd32));
1002    }
1003
1004    #[test]
1005    fn bdd_cannot_shrink() {
1006        let bdd64 = Bdd::new_literal(VariableId::new_long(1u64 << 48).unwrap(), true);
1007        assert!(bdd64.clone().shrink().structural_eq(&bdd64));
1008    }
1009
1010    #[test]
1011    fn bdd_constants() {
1012        let bdd_true = Bdd::new_true();
1013        let bdd_false = Bdd::new_false();
1014        assert!(bdd_true.is_true() && !bdd_true.is_false());
1015        assert!(bdd_false.is_false() && !bdd_false.is_true());
1016
1017        // There is no "normal" way to build 32-bit and 64-bit constant BDD,
1018        // but in theory they are valid BDDs, they are just not "reduced" properly.
1019
1020        let true_32: Bdd = Bdd32::new_true().into();
1021        let false_32: Bdd = Bdd32::new_false().into();
1022        assert!(true_32.is_true() && !true_32.is_false());
1023        assert!(false_32.is_false() && !false_32.is_true());
1024
1025        let true_64: Bdd = Bdd64::new_true().into();
1026        let false_64: Bdd = Bdd64::new_false().into();
1027        assert!(true_64.is_true() && !true_64.is_false());
1028        assert!(false_64.is_false() && !false_64.is_true());
1029    }
1030
1031    #[test]
1032    fn bdd_getters() {
1033        let var16 = VariableId::new(1u32 << 8);
1034        let var32 = VariableId::new(1u32 << 24);
1035        let var64 = VariableId::new_long(1u64 << 48).unwrap();
1036        let bdd16 = Bdd::new_literal(var16, true);
1037        let bdd32 = Bdd::new_literal(var32, true);
1038        let bdd64 = Bdd::new_literal(var64, true);
1039        let zero = NodeId::zero();
1040        let one = NodeId::one();
1041
1042        assert_eq!(bdd16.get_variable(bdd16.root()), var16);
1043        assert_eq!(bdd32.get_variable(bdd32.root()), var32);
1044        assert_eq!(bdd64.get_variable(bdd64.root()), var64);
1045
1046        assert_eq!(bdd16.get_links(bdd16.root()), (zero, one));
1047        assert_eq!(bdd32.get_links(bdd32.root()), (zero, one));
1048        assert_eq!(bdd64.get_links(bdd64.root()), (zero, one));
1049    }
1050
1051    #[allow(clippy::cast_possible_truncation)]
1052    pub(crate) fn queens(n: usize) -> Bdd {
1053        fn mk_negative_literals(n: usize) -> Vec<Bdd> {
1054            let mut bdd_literals = Vec::with_capacity(n * n);
1055            for i in 0..n {
1056                for j in 0..n {
1057                    let literal = Bdd::new_literal(((i * n + j) as u32).into(), false);
1058                    bdd_literals.push(literal);
1059                }
1060            }
1061            bdd_literals
1062        }
1063
1064        fn one_queen(n: usize, i: usize, j: usize, negative: &[Bdd]) -> Bdd {
1065            let mut s = Bdd::new_literal(((i * n + j) as u32).into(), true);
1066
1067            // no queens in the same row
1068            for k in 0..n {
1069                if k != j {
1070                    s = s.and(&negative[i * n + k]);
1071                }
1072            }
1073
1074            // no queens in the same column
1075            for k in 0..n {
1076                if k != i {
1077                    s = s.and(&negative[k * n + j]);
1078                }
1079            }
1080
1081            // no queens in the main diagonal (top-left to bot-right)
1082            // r - c = i - j  =>  c = (r + j) - i
1083            for row in 0..n {
1084                if let Some(col) = (row + j).checked_sub(i) {
1085                    if col < n && row != i {
1086                        s = s.and(&negative[row * n + col]);
1087                    }
1088                }
1089            }
1090
1091            // no queens in the anti diagonal (top-right to bot-left)
1092            // r + c = i + j  =>  c = (i + j) - r
1093            for row in 0..n {
1094                if let Some(col) = (i + j).checked_sub(row) {
1095                    if col < n && row != i {
1096                        s = s.and(&negative[row * n + col]);
1097                    }
1098                }
1099            }
1100
1101            s
1102        }
1103
1104        fn queen_in_row(n: usize, row: usize, negative: &[Bdd]) -> Bdd {
1105            let mut r = Bdd::new_false();
1106            for col in 0..n {
1107                let one_queen = one_queen(n, row, col, negative);
1108                r = r.or(&one_queen);
1109            }
1110            r
1111        }
1112
1113        let negative = mk_negative_literals(n);
1114        let mut result = Bdd::new_true();
1115        for row in 0..n {
1116            let in_row = queen_in_row(n, row, &negative);
1117            result = result.and(&in_row);
1118        }
1119        result
1120    }
1121
1122    #[test]
1123    fn count_sat_valuations() {
1124        assert_eq!(Bdd::new_false().count_satisfying_valuations(None), 0.0,);
1125
1126        assert_eq!(Bdd::new_true().count_satisfying_valuations(None), 1.0,);
1127
1128        assert_eq!(
1129            Bdd::new_true().count_satisfying_valuations(Some(VariableId::new(0))),
1130            2.0,
1131        );
1132
1133        assert_eq!(
1134            Bdd::new_literal(VariableId::new(0u32), true)
1135                .count_satisfying_valuations(Some(VariableId::new(0u32))),
1136            1.0,
1137        );
1138
1139        assert_eq!(
1140            Bdd::new_literal(VariableId::new(0u32), true).count_satisfying_valuations(None),
1141            1.0,
1142        );
1143
1144        assert_eq!(
1145            Bdd::new_literal(VariableId::new(0u32), false).count_satisfying_valuations(None),
1146            1.0,
1147        );
1148
1149        assert_eq!(
1150            Bdd::new_literal(VariableId::new(0u32), false)
1151                .count_satisfying_valuations(Some(VariableId::new(2u32))),
1152            4.0,
1153        );
1154
1155        let bdd6 = queens(6);
1156        let bdd8 = queens(9);
1157
1158        assert_eq!(bdd6.count_satisfying_valuations(None), 4.0);
1159        assert_eq!(bdd8.count_satisfying_valuations(None), 352.0);
1160
1161        let or3 = Bdd::new_literal(VariableId::new(0u32), true)
1162            .or(&Bdd::new_literal(VariableId::new(1u32), true))
1163            .or(&Bdd::new_literal(VariableId::new(3u32), true));
1164
1165        let and3 = Bdd::new_literal(VariableId::new(0u32), true)
1166            .and(&Bdd::new_literal(VariableId::new(1u32), true))
1167            .and(&Bdd::new_literal(VariableId::new(3u32), true));
1168
1169        assert_eq!(or3.count_satisfying_valuations(None), 14.0);
1170        assert_eq!(and3.count_satisfying_valuations(None), 2.0);
1171    }
1172
1173    #[test]
1174    fn count_sat_paths() {
1175        assert_eq!(Bdd::new_false().count_satisfying_paths(), 0.0,);
1176        assert_eq!(Bdd::new_true().count_satisfying_paths(), 1.0,);
1177
1178        assert_eq!(
1179            Bdd::new_literal(VariableId::new(0u32), true).count_satisfying_paths(),
1180            1.0,
1181        );
1182
1183        assert_eq!(
1184            Bdd::new_literal(VariableId::new(0u32), false).count_satisfying_paths(),
1185            1.0,
1186        );
1187
1188        let bdd4 = queens(6);
1189        let bdd8 = queens(9);
1190
1191        assert_eq!(bdd4.count_satisfying_paths(), 4.0);
1192        assert_eq!(bdd8.count_satisfying_paths(), 352.0);
1193
1194        let or3 = Bdd::new_literal(VariableId::new(0u32), true)
1195            .or(&Bdd::new_literal(VariableId::new(1u32), true))
1196            .or(&Bdd::new_literal(VariableId::new(3u32), true));
1197
1198        let and3 = Bdd::new_literal(VariableId::new(0u32), true)
1199            .and(&Bdd::new_literal(VariableId::new(1u32), true))
1200            .and(&Bdd::new_literal(VariableId::new(3u32), true));
1201
1202        assert_eq!(or3.count_satisfying_paths(), 3.0);
1203        assert_eq!(and3.count_satisfying_paths(), 1.0);
1204    }
1205
1206    #[test]
1207    fn bdd_to_dot() {
1208        let v_1 = VariableId::new(1);
1209        let v_4 = VariableId::new(4);
1210
1211        let bdd = Bdd::new_literal(v_1, true).xor(&Bdd::new_literal(v_4, true));
1212
1213        let result = bdd.to_dot_string();
1214
1215        let expected = r#"digraph BDD {
1216  __ruddy_root [label="", style=invis, height=0, width=0];
1217  __ruddy_root -> 4;
1218
1219  edge [dir=none];
1220
1221  0 [label="0", shape=box, width=0.3, height=0.3];
1222  1 [label="1", shape=box, width=0.3, height=0.3];
1223  2 [label="4", shape=box, width=0.3, height=0.3];
1224  2 -> 0 [style=dashed];
1225  2 -> 1;
1226  3 [label="4", shape=box, width=0.3, height=0.3];
1227  3 -> 1 [style=dashed];
1228  3 -> 0;
1229  4 [label="1", shape=box, width=0.3, height=0.3];
1230  4 -> 2 [style=dashed];
1231  4 -> 3;
1232}
1233"#;
1234
1235        assert_eq!(result, expected);
1236    }
1237}