Skip to main content

oxiz_math/
bdd.rs

1//! Binary Decision Diagrams (BDDs) for efficient boolean function representation.
2//!
3//! This module implements Reduced Ordered Binary Decision Diagrams (ROBDDs),
4//! which provide a canonical representation for boolean functions.
5//!
6//! Reference: Bryant, "Graph-Based Algorithms for Boolean Function Manipulation" (1986)
7
8#[allow(unused_imports)]
9use crate::prelude::*;
10use core::fmt;
11
12/// Variable identifier in a BDD.
13pub type VarId = u32;
14
15/// Node identifier in the BDD.
16/// Special values: 0 = FALSE, 1 = TRUE
17pub type NodeId = usize;
18
19/// The constant FALSE BDD node (node id 0).
20pub const BDD_FALSE: NodeId = 0;
21/// The constant TRUE BDD node (node id 1).
22pub const BDD_TRUE: NodeId = 1;
23
24/// A BDD node representing: if var then high else low
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26struct BddNode {
27    var: VarId,
28    low: NodeId,
29    high: NodeId,
30}
31
32/// A Binary Decision Diagram manager.
33/// Maintains a unique table to ensure node sharing and canonical representation.
34#[derive(Debug, Clone)]
35pub struct BddManager {
36    /// All nodes in the BDD. Index 0 is FALSE, index 1 is TRUE.
37    nodes: Vec<BddNode>,
38    /// Unique table mapping (var, low, high) to node ID.
39    unique_table: FxHashMap<(VarId, NodeId, NodeId), NodeId>,
40    /// Cache for binary operations: (op, left, right) -> result
41    op_cache: FxHashMap<(BddOp, NodeId, NodeId), NodeId>,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45enum BddOp {
46    And,
47    Or,
48    Xor,
49}
50
51impl BddManager {
52    /// Creates a new BDD manager.
53    pub fn new() -> Self {
54        let mut manager = Self {
55            nodes: Vec::new(),
56            unique_table: FxHashMap::default(),
57            op_cache: FxHashMap::default(),
58        };
59
60        // Initialize constant nodes
61        // FALSE node (id = 0): dummy node, never actually used for var/low/high
62        manager.nodes.push(BddNode {
63            var: u32::MAX,
64            low: 0,
65            high: 0,
66        });
67
68        // TRUE node (id = 1): dummy node, never actually used for var/low/high
69        manager.nodes.push(BddNode {
70            var: u32::MAX,
71            low: 1,
72            high: 1,
73        });
74
75        manager
76    }
77
78    /// Returns the FALSE constant.
79    pub fn constant_false(&self) -> NodeId {
80        BDD_FALSE
81    }
82
83    /// Returns the TRUE constant.
84    pub fn constant_true(&self) -> NodeId {
85        BDD_TRUE
86    }
87
88    /// Creates a BDD for a single variable.
89    pub fn variable(&mut self, var: VarId) -> NodeId {
90        self.make_node(var, BDD_FALSE, BDD_TRUE)
91    }
92
93    /// Creates or retrieves a BDD node.
94    /// Implements the reduction rules:
95    /// - If low == high, return low (redundancy elimination)
96    /// - Otherwise, check unique table for existing node
97    fn make_node(&mut self, var: VarId, low: NodeId, high: NodeId) -> NodeId {
98        // Reduction rule: if both branches are the same, return that branch
99        if low == high {
100            return low;
101        }
102
103        // Check if this node already exists
104        let key = (var, low, high);
105        if let Some(&node_id) = self.unique_table.get(&key) {
106            return node_id;
107        }
108
109        // Create new node
110        let node_id = self.nodes.len();
111        self.nodes.push(BddNode { var, low, high });
112        self.unique_table.insert(key, node_id);
113        node_id
114    }
115
116    /// Negates a BDD node.
117    pub fn not(&mut self, node: NodeId) -> NodeId {
118        self.apply_not_rec(node)
119    }
120
121    fn apply_not_rec(&mut self, node: NodeId) -> NodeId {
122        if node == BDD_FALSE {
123            return BDD_TRUE;
124        }
125        if node == BDD_TRUE {
126            return BDD_FALSE;
127        }
128
129        let n = self.nodes[node];
130        let low = self.apply_not_rec(n.low);
131        let high = self.apply_not_rec(n.high);
132        self.make_node(n.var, low, high)
133    }
134
135    /// Computes the AND of two BDD nodes.
136    pub fn and(&mut self, left: NodeId, right: NodeId) -> NodeId {
137        self.apply(BddOp::And, left, right)
138    }
139
140    /// Computes the OR of two BDD nodes.
141    pub fn or(&mut self, left: NodeId, right: NodeId) -> NodeId {
142        self.apply(BddOp::Or, left, right)
143    }
144
145    /// Computes the XOR of two BDD nodes.
146    pub fn xor(&mut self, left: NodeId, right: NodeId) -> NodeId {
147        self.apply(BddOp::Xor, left, right)
148    }
149
150    /// Computes the implication: left => right (equivalent to !left | right)
151    pub fn implies(&mut self, left: NodeId, right: NodeId) -> NodeId {
152        let not_left = self.not(left);
153        self.or(not_left, right)
154    }
155
156    /// Computes the equivalence: left <=> right
157    pub fn iff(&mut self, left: NodeId, right: NodeId) -> NodeId {
158        let not_xor = self.xor(left, right);
159        self.not(not_xor)
160    }
161
162    /// Apply a binary operation with memoization.
163    fn apply(&mut self, op: BddOp, left: NodeId, right: NodeId) -> NodeId {
164        // Terminal cases
165        match op {
166            BddOp::And => {
167                if left == BDD_FALSE || right == BDD_FALSE {
168                    return BDD_FALSE;
169                }
170                if left == BDD_TRUE {
171                    return right;
172                }
173                if right == BDD_TRUE {
174                    return left;
175                }
176                if left == right {
177                    return left;
178                }
179            }
180            BddOp::Or => {
181                if left == BDD_TRUE || right == BDD_TRUE {
182                    return BDD_TRUE;
183                }
184                if left == BDD_FALSE {
185                    return right;
186                }
187                if right == BDD_FALSE {
188                    return left;
189                }
190                if left == right {
191                    return left;
192                }
193            }
194            BddOp::Xor => {
195                if left == BDD_FALSE {
196                    return right;
197                }
198                if right == BDD_FALSE {
199                    return left;
200                }
201                if left == BDD_TRUE {
202                    return self.apply_not_rec(right);
203                }
204                if right == BDD_TRUE {
205                    return self.apply_not_rec(left);
206                }
207                if left == right {
208                    return BDD_FALSE;
209                }
210            }
211        }
212
213        // Check cache
214        let cache_key = (op, left, right);
215        if let Some(&result) = self.op_cache.get(&cache_key) {
216            return result;
217        }
218
219        // Recursive case: Shannon expansion
220        let left_node = self.nodes[left];
221        let right_node = self.nodes[right];
222
223        let result = if left_node.var == right_node.var {
224            // Same variable
225            let low = self.apply(op, left_node.low, right_node.low);
226            let high = self.apply(op, left_node.high, right_node.high);
227            self.make_node(left_node.var, low, high)
228        } else if left_node.var < right_node.var {
229            // Left has smaller variable (higher in ordering)
230            let low = self.apply(op, left_node.low, right);
231            let high = self.apply(op, left_node.high, right);
232            self.make_node(left_node.var, low, high)
233        } else {
234            // Right has smaller variable
235            let low = self.apply(op, left, right_node.low);
236            let high = self.apply(op, left, right_node.high);
237            self.make_node(right_node.var, low, high)
238        };
239
240        self.op_cache.insert(cache_key, result);
241        result
242    }
243
244    /// If-then-else operation: if cond then then_node else else_node
245    pub fn ite(&mut self, cond: NodeId, then_node: NodeId, else_node: NodeId) -> NodeId {
246        // Terminal cases
247        if cond == BDD_TRUE {
248            return then_node;
249        }
250        if cond == BDD_FALSE {
251            return else_node;
252        }
253        if then_node == else_node {
254            return then_node;
255        }
256        if then_node == BDD_TRUE && else_node == BDD_FALSE {
257            return cond;
258        }
259
260        let cond_node = self.nodes[cond];
261        let then_n = if then_node <= BDD_TRUE {
262            None
263        } else {
264            Some(self.nodes[then_node])
265        };
266        let else_n = if else_node <= BDD_TRUE {
267            None
268        } else {
269            Some(self.nodes[else_node])
270        };
271
272        // Find the top variable
273        let mut top_var = cond_node.var;
274        if let Some(n) = then_n
275            && n.var < top_var
276        {
277            top_var = n.var;
278        }
279        if let Some(n) = else_n
280            && n.var < top_var
281        {
282            top_var = n.var;
283        }
284
285        // Cofactors
286        let cond_low = if cond_node.var == top_var {
287            cond_node.low
288        } else {
289            cond
290        };
291        let cond_high = if cond_node.var == top_var {
292            cond_node.high
293        } else {
294            cond
295        };
296
297        let then_low = if let Some(n) = then_n {
298            if n.var == top_var { n.low } else { then_node }
299        } else {
300            then_node
301        };
302        let then_high = if let Some(n) = then_n {
303            if n.var == top_var { n.high } else { then_node }
304        } else {
305            then_node
306        };
307
308        let else_low = if let Some(n) = else_n {
309            if n.var == top_var { n.low } else { else_node }
310        } else {
311            else_node
312        };
313        let else_high = if let Some(n) = else_n {
314            if n.var == top_var { n.high } else { else_node }
315        } else {
316            else_node
317        };
318
319        let low = self.ite(cond_low, then_low, else_low);
320        let high = self.ite(cond_high, then_high, else_high);
321        self.make_node(top_var, low, high)
322    }
323
324    /// Evaluates a BDD with a given variable assignment.
325    pub fn eval(&self, node: NodeId, assignment: &FxHashMap<VarId, bool>) -> bool {
326        if node == BDD_FALSE {
327            return false;
328        }
329        if node == BDD_TRUE {
330            return true;
331        }
332
333        let n = self.nodes[node];
334        let var_value = assignment.get(&n.var).copied().unwrap_or(false);
335
336        if var_value {
337            self.eval(n.high, assignment)
338        } else {
339            self.eval(n.low, assignment)
340        }
341    }
342
343    /// Counts the number of satisfying assignments for a BDD.
344    pub fn sat_count(&self, node: NodeId, num_vars: u32) -> u64 {
345        let mut cache = FxHashMap::default();
346        self.sat_count_rec(node, &mut cache)
347            * (1u64 << num_vars.saturating_sub(self.max_var(node).map_or(0, |v| v + 1)))
348    }
349
350    fn sat_count_rec(&self, node: NodeId, cache: &mut FxHashMap<NodeId, u64>) -> u64 {
351        if node == BDD_FALSE {
352            return 0;
353        }
354        if node == BDD_TRUE {
355            return 1;
356        }
357
358        if let Some(&count) = cache.get(&node) {
359            return count;
360        }
361
362        let n = self.nodes[node];
363        let low_count = self.sat_count_rec(n.low, cache);
364        let high_count = self.sat_count_rec(n.high, cache);
365
366        // Account for the difference in variable levels
367        let low_mult = if n.low <= BDD_TRUE {
368            1
369        } else {
370            let low_var = self.nodes[n.low].var;
371            1u64 << (low_var - n.var - 1)
372        };
373        let high_mult = if n.high <= BDD_TRUE {
374            1
375        } else {
376            let high_var = self.nodes[n.high].var;
377            1u64 << (high_var - n.var - 1)
378        };
379
380        let count = low_count * low_mult + high_count * high_mult;
381        cache.insert(node, count);
382        count
383    }
384
385    /// Returns the maximum variable ID in a BDD.
386    fn max_var(&self, node: NodeId) -> Option<VarId> {
387        if node <= BDD_TRUE {
388            return None;
389        }
390        Some(self.nodes[node].var)
391    }
392
393    /// Returns the number of nodes in the manager.
394    pub fn node_count(&self) -> usize {
395        self.nodes.len()
396    }
397
398    /// Clears the operation cache (useful for memory management).
399    pub fn clear_cache(&mut self) {
400        self.op_cache.clear();
401    }
402}
403
404impl Default for BddManager {
405    fn default() -> Self {
406        Self::new()
407    }
408}
409
410impl fmt::Display for BddManager {
411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412        writeln!(f, "BDD Manager: {} nodes", self.nodes.len())?;
413        writeln!(f, "  Cache size: {}", self.op_cache.len())?;
414        Ok(())
415    }
416}
417
418/// Zero-suppressed Binary Decision Diagram (ZDD) manager.
419/// ZDDs use a different reduction rule optimized for sparse sets:
420/// - Eliminate node if high == 0 (instead of low == high for BDDs)
421///
422/// ZDDs are particularly efficient for representing:
423/// - Sparse sets and families of sets
424/// - Combinatorial structures
425/// - Paths in graphs
426#[derive(Debug, Clone)]
427pub struct ZddManager {
428    nodes: Vec<BddNode>,
429    unique_table: FxHashMap<(VarId, NodeId, NodeId), NodeId>,
430    op_cache: FxHashMap<(ZddOp, NodeId, NodeId), NodeId>,
431}
432
433#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
434enum ZddOp {
435    Union,
436    Intersection,
437    Difference,
438}
439
440/// The empty set in ZDD.
441pub const ZDD_EMPTY: NodeId = 0;
442/// The set containing only the empty set in ZDD.
443pub const ZDD_BASE: NodeId = 1;
444
445impl ZddManager {
446    /// Creates a new ZDD manager.
447    pub fn new() -> Self {
448        let mut manager = Self {
449            nodes: Vec::new(),
450            unique_table: FxHashMap::default(),
451            op_cache: FxHashMap::default(),
452        };
453
454        // EMPTY set (0)
455        manager.nodes.push(BddNode {
456            var: u32::MAX,
457            low: 0,
458            high: 0,
459        });
460
461        // BASE set containing empty set (1)
462        manager.nodes.push(BddNode {
463            var: u32::MAX,
464            low: 1,
465            high: 1,
466        });
467
468        manager
469    }
470
471    /// Returns the empty set.
472    pub fn empty(&self) -> NodeId {
473        ZDD_EMPTY
474    }
475
476    /// Returns the base set (set containing only the empty set).
477    pub fn base(&self) -> NodeId {
478        ZDD_BASE
479    }
480
481    /// Creates a ZDD for a singleton set containing just one variable.
482    pub fn singleton(&mut self, var: VarId) -> NodeId {
483        self.make_node(var, ZDD_EMPTY, ZDD_BASE)
484    }
485
486    /// Creates or retrieves a ZDD node with ZDD reduction rule.
487    /// ZDD reduction: If high == 0 (empty), return low
488    fn make_node(&mut self, var: VarId, low: NodeId, high: NodeId) -> NodeId {
489        // ZDD reduction rule: if high branch is empty, eliminate node
490        if high == ZDD_EMPTY {
491            return low;
492        }
493
494        // Check if node exists
495        let key = (var, low, high);
496        if let Some(&node_id) = self.unique_table.get(&key) {
497            return node_id;
498        }
499
500        // Create new node
501        let node_id = self.nodes.len();
502        self.nodes.push(BddNode { var, low, high });
503        self.unique_table.insert(key, node_id);
504        node_id
505    }
506
507    /// Computes the union of two ZDD sets.
508    pub fn union(&mut self, left: NodeId, right: NodeId) -> NodeId {
509        self.apply(ZddOp::Union, left, right)
510    }
511
512    /// Computes the intersection of two ZDD sets.
513    pub fn intersection(&mut self, left: NodeId, right: NodeId) -> NodeId {
514        self.apply(ZddOp::Intersection, left, right)
515    }
516
517    /// Computes the set difference: left \ right
518    pub fn difference(&mut self, left: NodeId, right: NodeId) -> NodeId {
519        self.apply(ZddOp::Difference, left, right)
520    }
521
522    fn apply(&mut self, op: ZddOp, left: NodeId, right: NodeId) -> NodeId {
523        // Terminal cases
524        match op {
525            ZddOp::Union => {
526                if left == ZDD_EMPTY {
527                    return right;
528                }
529                if right == ZDD_EMPTY {
530                    return left;
531                }
532                if left == right {
533                    return left;
534                }
535            }
536            ZddOp::Intersection => {
537                if left == ZDD_EMPTY || right == ZDD_EMPTY {
538                    return ZDD_EMPTY;
539                }
540                if left == right {
541                    return left;
542                }
543                // BASE only intersects with itself
544                if left == ZDD_BASE || right == ZDD_BASE {
545                    return ZDD_EMPTY;
546                }
547            }
548            ZddOp::Difference => {
549                if left == ZDD_EMPTY {
550                    return ZDD_EMPTY;
551                }
552                if right == ZDD_EMPTY {
553                    return left;
554                }
555                if left == right {
556                    return ZDD_EMPTY;
557                }
558            }
559        }
560
561        // Check cache
562        let cache_key = (op, left, right);
563        if let Some(&result) = self.op_cache.get(&cache_key) {
564            return result;
565        }
566
567        // Get top variable
568        let left_node = self.nodes[left];
569        let right_node = self.nodes[right];
570
571        let result = if left_node.var == right_node.var {
572            let low = self.apply(op, left_node.low, right_node.low);
573            let high = self.apply(op, left_node.high, right_node.high);
574            self.make_node(left_node.var, low, high)
575        } else if left_node.var < right_node.var {
576            let low = self.apply(op, left_node.low, right);
577            let high = match op {
578                ZddOp::Union => left_node.high,
579                ZddOp::Intersection => self.apply(op, left_node.high, right),
580                ZddOp::Difference => left_node.high,
581            };
582            self.make_node(left_node.var, low, high)
583        } else {
584            let low = self.apply(op, left, right_node.low);
585            let high = match op {
586                ZddOp::Union => right_node.high,
587                ZddOp::Intersection => self.apply(op, left, right_node.high),
588                ZddOp::Difference => ZDD_EMPTY,
589            };
590            self.make_node(right_node.var, low, high)
591        };
592
593        self.op_cache.insert(cache_key, result);
594        result
595    }
596
597    /// Returns the number of sets in the family represented by this ZDD.
598    pub fn count(&self, node: NodeId) -> u64 {
599        let mut cache = FxHashMap::default();
600        self.count_rec(node, &mut cache)
601    }
602
603    fn count_rec(&self, node: NodeId, cache: &mut FxHashMap<NodeId, u64>) -> u64 {
604        if node == ZDD_EMPTY {
605            return 0;
606        }
607        if node == ZDD_BASE {
608            return 1;
609        }
610
611        if let Some(&count) = cache.get(&node) {
612            return count;
613        }
614
615        let n = self.nodes[node];
616        let count = self.count_rec(n.low, cache) + self.count_rec(n.high, cache);
617        cache.insert(node, count);
618        count
619    }
620
621    /// Returns the number of nodes in the manager.
622    pub fn node_count(&self) -> usize {
623        self.nodes.len()
624    }
625
626    /// Clears the operation cache.
627    pub fn clear_cache(&mut self) {
628        self.op_cache.clear();
629    }
630}
631
632impl Default for ZddManager {
633    fn default() -> Self {
634        Self::new()
635    }
636}
637
638impl fmt::Display for ZddManager {
639    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
640        writeln!(f, "ZDD Manager: {} nodes", self.nodes.len())?;
641        writeln!(f, "  Cache size: {}", self.op_cache.len())?;
642        Ok(())
643    }
644}
645
646/// Algebraic Decision Diagram (ADD) for arithmetic functions.
647/// ADDs generalize BDDs by allowing arbitrary terminal values (not just 0/1).
648/// They can represent functions from boolean vectors to any algebraic domain.
649///
650/// Reference: Bahar et al., "Algebraic Decision Diagrams and Their Applications" (1993)
651use num_rational::BigRational;
652use num_traits::Zero;
653
654/// An ADD node with rational number terminals.
655#[derive(Debug, Clone, PartialEq, Eq, Hash)]
656enum AddNode {
657    /// Terminal node with a rational value.
658    Terminal(BigRational),
659    /// Internal node: if var then high else low
660    Internal {
661        var: VarId,
662        low: NodeId,
663        high: NodeId,
664    },
665}
666
667/// ADD manager for rational-valued functions.
668#[derive(Debug, Clone)]
669pub struct AddManager {
670    nodes: Vec<AddNode>,
671    unique_table: FxHashMap<AddNode, NodeId>,
672    op_cache: FxHashMap<(AddOp, NodeId, NodeId), NodeId>,
673}
674
675#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
676enum AddOp {
677    Add,
678    Mul,
679    Max,
680    Min,
681}
682
683impl AddManager {
684    /// Creates a new ADD manager.
685    pub fn new() -> Self {
686        let mut manager = Self {
687            nodes: Vec::new(),
688            unique_table: FxHashMap::default(),
689            op_cache: FxHashMap::default(),
690        };
691
692        // Create zero terminal
693        let zero = AddNode::Terminal(BigRational::zero());
694        manager.nodes.push(zero.clone());
695        manager.unique_table.insert(zero, 0);
696
697        manager
698    }
699
700    /// Creates a constant ADD.
701    pub fn constant(&mut self, value: BigRational) -> NodeId {
702        let node = AddNode::Terminal(value);
703
704        // Check if this terminal already exists
705        if let Some(&id) = self.unique_table.get(&node) {
706            return id;
707        }
708
709        let id = self.nodes.len();
710        self.nodes.push(node.clone());
711        self.unique_table.insert(node, id);
712        id
713    }
714
715    /// Creates an ADD for a single variable (returns 1 if true, 0 if false).
716    pub fn variable(&mut self, var: VarId) -> NodeId {
717        let zero = self.constant(BigRational::zero());
718        let one = self.constant(BigRational::from_integer(1.into()));
719        self.make_node(var, zero, one)
720    }
721
722    /// Creates or retrieves an ADD node.
723    fn make_node(&mut self, var: VarId, low: NodeId, high: NodeId) -> NodeId {
724        // Reduction rule: if both branches are the same, return that branch
725        if low == high {
726            return low;
727        }
728
729        let node = AddNode::Internal { var, low, high };
730
731        // Check if this node already exists
732        if let Some(&id) = self.unique_table.get(&node) {
733            return id;
734        }
735
736        // Create new node
737        let id = self.nodes.len();
738        self.nodes.push(node.clone());
739        self.unique_table.insert(node, id);
740        id
741    }
742
743    /// Evaluates the ADD given a variable assignment.
744    pub fn eval(&self, node: NodeId, assignment: &FxHashMap<VarId, bool>) -> BigRational {
745        match &self.nodes[node] {
746            AddNode::Terminal(value) => value.clone(),
747            AddNode::Internal { var, low, high } => {
748                if *assignment.get(var).unwrap_or(&false) {
749                    self.eval(*high, assignment)
750                } else {
751                    self.eval(*low, assignment)
752                }
753            }
754        }
755    }
756
757    /// Computes the sum of two ADDs.
758    pub fn add(&mut self, left: NodeId, right: NodeId) -> NodeId {
759        self.apply(AddOp::Add, left, right)
760    }
761
762    /// Computes the product of two ADDs.
763    pub fn mul(&mut self, left: NodeId, right: NodeId) -> NodeId {
764        self.apply(AddOp::Mul, left, right)
765    }
766
767    /// Computes the maximum of two ADDs.
768    pub fn max(&mut self, left: NodeId, right: NodeId) -> NodeId {
769        self.apply(AddOp::Max, left, right)
770    }
771
772    /// Computes the minimum of two ADDs.
773    pub fn min(&mut self, left: NodeId, right: NodeId) -> NodeId {
774        self.apply(AddOp::Min, left, right)
775    }
776
777    /// Multiplies an ADD by a scalar.
778    pub fn scale(&mut self, node: NodeId, scalar: &BigRational) -> NodeId {
779        let scalar_node = self.constant(scalar.clone());
780        self.mul(node, scalar_node)
781    }
782
783    /// Apply a binary operation with memoization.
784    fn apply(&mut self, op: AddOp, left: NodeId, right: NodeId) -> NodeId {
785        // Terminal cases
786        if let (AddNode::Terminal(l), AddNode::Terminal(r)) =
787            (&self.nodes[left], &self.nodes[right])
788        {
789            let result_value = match op {
790                AddOp::Add => l + r,
791                AddOp::Mul => l * r,
792                AddOp::Max => {
793                    if l > r {
794                        l.clone()
795                    } else {
796                        r.clone()
797                    }
798                }
799                AddOp::Min => {
800                    if l < r {
801                        l.clone()
802                    } else {
803                        r.clone()
804                    }
805                }
806            };
807            return self.constant(result_value);
808        }
809
810        // Check cache
811        let cache_key = (op, left, right);
812        if let Some(&result) = self.op_cache.get(&cache_key) {
813            return result;
814        }
815
816        // Recursive case
817        let (var, left_low, left_high, right_low, right_high) = self.split_nodes(left, right);
818
819        let low = self.apply(op, left_low, right_low);
820        let high = self.apply(op, left_high, right_high);
821
822        let result = self.make_node(var, low, high);
823        self.op_cache.insert(cache_key, result);
824        result
825    }
826
827    /// Helper function to split nodes for apply operation.
828    fn split_nodes(&self, left: NodeId, right: NodeId) -> (VarId, NodeId, NodeId, NodeId, NodeId) {
829        let (left_var, left_low, left_high) = match &self.nodes[left] {
830            AddNode::Terminal(_) => (u32::MAX, left, left),
831            AddNode::Internal { var, low, high } => (*var, *low, *high),
832        };
833
834        let (right_var, right_low, right_high) = match &self.nodes[right] {
835            AddNode::Terminal(_) => (u32::MAX, right, right),
836            AddNode::Internal { var, low, high } => (*var, *low, *high),
837        };
838
839        if left_var == u32::MAX && right_var == u32::MAX {
840            panic!("Both nodes are terminals - should have been handled earlier");
841        }
842
843        if left_var == right_var {
844            (left_var, left_low, left_high, right_low, right_high)
845        } else if left_var < right_var {
846            (left_var, left_low, left_high, right, right)
847        } else {
848            (right_var, left, left, right_low, right_high)
849        }
850    }
851
852    /// If-then-else operation for ADDs.
853    pub fn ite(&mut self, cond: NodeId, then_add: NodeId, else_add: NodeId) -> NodeId {
854        // Convert boolean condition to ADD if needed
855        // ite(c, t, e) = c * t + (1 - c) * e
856        let one = self.constant(BigRational::from_integer(1.into()));
857        let neg_one = BigRational::from_integer((-1).into());
858        let scaled_cond = self.scale(cond, &neg_one);
859        let not_cond = self.apply(AddOp::Add, one, scaled_cond);
860
861        let then_part = self.mul(cond, then_add);
862        let else_part = self.mul(not_cond, else_add);
863        self.add(then_part, else_part)
864    }
865
866    /// Returns the number of nodes in the manager.
867    pub fn node_count(&self) -> usize {
868        self.nodes.len()
869    }
870
871    /// Clears the operation cache.
872    pub fn clear_cache(&mut self) {
873        self.op_cache.clear();
874    }
875
876    /// Gets the variable of a node, if it's internal.
877    pub fn get_var(&self, node: NodeId) -> Option<VarId> {
878        match &self.nodes[node] {
879            AddNode::Terminal(_) => None,
880            AddNode::Internal { var, .. } => Some(*var),
881        }
882    }
883
884    /// Gets the terminal value of a node, if it's a terminal.
885    pub fn get_value(&self, node: NodeId) -> Option<&BigRational> {
886        match &self.nodes[node] {
887            AddNode::Terminal(value) => Some(value),
888            AddNode::Internal { .. } => None,
889        }
890    }
891}
892
893impl Default for AddManager {
894    fn default() -> Self {
895        Self::new()
896    }
897}
898
899impl fmt::Display for AddManager {
900    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
901        writeln!(f, "ADD Manager: {} nodes", self.nodes.len())?;
902        writeln!(f, "  Cache size: {}", self.op_cache.len())?;
903        Ok(())
904    }
905}
906
907#[cfg(test)]
908mod tests {
909    use super::*;
910
911    #[test]
912    fn test_bdd_constants() {
913        let manager = BddManager::new();
914        assert_eq!(manager.constant_false(), BDD_FALSE);
915        assert_eq!(manager.constant_true(), BDD_TRUE);
916    }
917
918    #[test]
919    fn test_bdd_variable() {
920        let mut manager = BddManager::new();
921        let x = manager.variable(0);
922
923        let mut assignment = FxHashMap::default();
924        assignment.insert(0, true);
925        assert!(manager.eval(x, &assignment));
926
927        assignment.insert(0, false);
928        assert!(!manager.eval(x, &assignment));
929    }
930
931    #[test]
932    fn test_bdd_not() {
933        let mut manager = BddManager::new();
934        let x = manager.variable(0);
935        let not_x = manager.not(x);
936
937        let mut assignment = FxHashMap::default();
938        assignment.insert(0, true);
939        assert!(!manager.eval(not_x, &assignment));
940
941        assignment.insert(0, false);
942        assert!(manager.eval(not_x, &assignment));
943    }
944
945    #[test]
946    fn test_bdd_and() {
947        let mut manager = BddManager::new();
948        let x = manager.variable(0);
949        let y = manager.variable(1);
950        let x_and_y = manager.and(x, y);
951
952        let mut assignment = FxHashMap::default();
953        assignment.insert(0, true);
954        assignment.insert(1, true);
955        assert!(manager.eval(x_and_y, &assignment));
956
957        assignment.insert(0, false);
958        assert!(!manager.eval(x_and_y, &assignment));
959    }
960
961    #[test]
962    fn test_bdd_or() {
963        let mut manager = BddManager::new();
964        let x = manager.variable(0);
965        let y = manager.variable(1);
966        let x_or_y = manager.or(x, y);
967
968        let mut assignment = FxHashMap::default();
969        assignment.insert(0, false);
970        assignment.insert(1, false);
971        assert!(!manager.eval(x_or_y, &assignment));
972
973        assignment.insert(0, true);
974        assert!(manager.eval(x_or_y, &assignment));
975    }
976
977    #[test]
978    fn test_bdd_xor() {
979        let mut manager = BddManager::new();
980        let x = manager.variable(0);
981        let y = manager.variable(1);
982        let x_xor_y = manager.xor(x, y);
983
984        let mut assignment = FxHashMap::default();
985        assignment.insert(0, true);
986        assignment.insert(1, true);
987        assert!(!manager.eval(x_xor_y, &assignment));
988
989        assignment.insert(0, true);
990        assignment.insert(1, false);
991        assert!(manager.eval(x_xor_y, &assignment));
992    }
993
994    #[test]
995    fn test_bdd_ite() {
996        let mut manager = BddManager::new();
997        let x = manager.variable(0);
998        let y = manager.variable(1);
999        let z = manager.variable(2);
1000
1001        // if x then y else z
1002        let result = manager.ite(x, y, z);
1003
1004        let mut assignment = FxHashMap::default();
1005        assignment.insert(0, true);
1006        assignment.insert(1, true);
1007        assignment.insert(2, false);
1008        assert!(manager.eval(result, &assignment)); // x=T, y=T => T
1009
1010        assignment.insert(0, false);
1011        assignment.insert(1, true);
1012        assignment.insert(2, false);
1013        assert!(!manager.eval(result, &assignment)); // x=F, z=F => F
1014    }
1015
1016    #[test]
1017    fn test_bdd_implies() {
1018        let mut manager = BddManager::new();
1019        let x = manager.variable(0);
1020        let y = manager.variable(1);
1021        let x_implies_y = manager.implies(x, y);
1022
1023        let mut assignment = FxHashMap::default();
1024        assignment.insert(0, true);
1025        assignment.insert(1, false);
1026        assert!(!manager.eval(x_implies_y, &assignment));
1027
1028        assignment.insert(0, false);
1029        assignment.insert(1, false);
1030        assert!(manager.eval(x_implies_y, &assignment));
1031    }
1032
1033    #[test]
1034    fn test_bdd_sharing() {
1035        let mut manager = BddManager::new();
1036        let x = manager.variable(0);
1037        let y = manager.variable(0); // Same variable
1038        assert_eq!(x, y); // Should return the same node
1039    }
1040
1041    // ZDD tests
1042    #[test]
1043    fn test_zdd_empty_and_base() {
1044        let manager = ZddManager::new();
1045        assert_eq!(manager.empty(), ZDD_EMPTY);
1046        assert_eq!(manager.base(), ZDD_BASE);
1047        assert_eq!(manager.count(ZDD_EMPTY), 0);
1048        assert_eq!(manager.count(ZDD_BASE), 1);
1049    }
1050
1051    #[test]
1052    fn test_zdd_singleton() {
1053        let mut manager = ZddManager::new();
1054        let x = manager.singleton(0);
1055        assert_eq!(manager.count(x), 1); // One set: {0}
1056        assert_ne!(x, ZDD_EMPTY);
1057        assert_ne!(x, ZDD_BASE);
1058    }
1059
1060    #[test]
1061    fn test_zdd_union() {
1062        let mut manager = ZddManager::new();
1063        let x = manager.singleton(0);
1064        let y = manager.singleton(1);
1065        let union = manager.union(x, y);
1066
1067        // Union of {0} and {1} should give us 2 sets: {0} and {1}
1068        assert_eq!(manager.count(union), 2);
1069    }
1070
1071    #[test]
1072    fn test_zdd_intersection() {
1073        let mut manager = ZddManager::new();
1074        let x = manager.singleton(0);
1075        let y = manager.singleton(1);
1076        let intersection = manager.intersection(x, y);
1077
1078        // Intersection of {0} and {1} should be empty
1079        assert_eq!(intersection, ZDD_EMPTY);
1080        assert_eq!(manager.count(intersection), 0);
1081    }
1082
1083    #[test]
1084    fn test_zdd_difference() {
1085        let mut manager = ZddManager::new();
1086        let x = manager.singleton(0);
1087        let y = manager.singleton(1);
1088
1089        let diff1 = manager.difference(x, y);
1090        assert_eq!(manager.count(diff1), 1); // {0} - {1} = {0}
1091
1092        let diff2 = manager.difference(x, x);
1093        assert_eq!(diff2, ZDD_EMPTY); // {0} - {0} = empty
1094    }
1095
1096    #[test]
1097    fn test_zdd_union_associative() {
1098        let mut manager = ZddManager::new();
1099        let x = manager.singleton(0);
1100        let y = manager.singleton(1);
1101        let z = manager.singleton(2);
1102
1103        let xy = manager.union(x, y);
1104        let xyz1 = manager.union(xy, z);
1105
1106        let yz = manager.union(y, z);
1107        let xyz2 = manager.union(x, yz);
1108
1109        assert_eq!(manager.count(xyz1), manager.count(xyz2));
1110        assert_eq!(manager.count(xyz1), 3); // Three sets
1111    }
1112
1113    // ADD tests
1114    #[test]
1115    fn test_add_constant() {
1116        use num_bigint::BigInt;
1117        let mut manager = AddManager::new();
1118        let five = manager.constant(BigRational::from_integer(BigInt::from(5)));
1119
1120        let assignment = FxHashMap::default();
1121        let result = manager.eval(five, &assignment);
1122        assert_eq!(result, BigRational::from_integer(BigInt::from(5)));
1123    }
1124
1125    #[test]
1126    fn test_add_variable() {
1127        use num_bigint::BigInt;
1128        let mut manager = AddManager::new();
1129        let x = manager.variable(0);
1130
1131        let mut assignment = FxHashMap::default();
1132        assignment.insert(0, true);
1133        let result = manager.eval(x, &assignment);
1134        assert_eq!(result, BigRational::from_integer(BigInt::from(1)));
1135
1136        assignment.insert(0, false);
1137        let result = manager.eval(x, &assignment);
1138        assert_eq!(result, BigRational::from_integer(BigInt::from(0)));
1139    }
1140
1141    #[test]
1142    fn test_add_addition() {
1143        use num_bigint::BigInt;
1144        let mut manager = AddManager::new();
1145        let three = manager.constant(BigRational::from_integer(BigInt::from(3)));
1146        let five = manager.constant(BigRational::from_integer(BigInt::from(5)));
1147        let sum = manager.add(three, five);
1148
1149        let assignment = FxHashMap::default();
1150        let result = manager.eval(sum, &assignment);
1151        assert_eq!(result, BigRational::from_integer(BigInt::from(8)));
1152    }
1153
1154    #[test]
1155    fn test_add_multiplication() {
1156        use num_bigint::BigInt;
1157        let mut manager = AddManager::new();
1158        let three = manager.constant(BigRational::from_integer(BigInt::from(3)));
1159        let five = manager.constant(BigRational::from_integer(BigInt::from(5)));
1160        let product = manager.mul(three, five);
1161
1162        let assignment = FxHashMap::default();
1163        let result = manager.eval(product, &assignment);
1164        assert_eq!(result, BigRational::from_integer(BigInt::from(15)));
1165    }
1166
1167    #[test]
1168    fn test_add_max_min() {
1169        use num_bigint::BigInt;
1170        let mut manager = AddManager::new();
1171        let three = manager.constant(BigRational::from_integer(BigInt::from(3)));
1172        let five = manager.constant(BigRational::from_integer(BigInt::from(5)));
1173
1174        let max_val = manager.max(three, five);
1175        let min_val = manager.min(three, five);
1176
1177        let assignment = FxHashMap::default();
1178        assert_eq!(
1179            manager.eval(max_val, &assignment),
1180            BigRational::from_integer(BigInt::from(5))
1181        );
1182        assert_eq!(
1183            manager.eval(min_val, &assignment),
1184            BigRational::from_integer(BigInt::from(3))
1185        );
1186    }
1187
1188    #[test]
1189    fn test_add_scale() {
1190        use num_bigint::BigInt;
1191        let mut manager = AddManager::new();
1192        let x = manager.variable(0);
1193        let scaled = manager.scale(x, &BigRational::from_integer(BigInt::from(5)));
1194
1195        let mut assignment = FxHashMap::default();
1196        assignment.insert(0, true);
1197        let result = manager.eval(scaled, &assignment);
1198        assert_eq!(result, BigRational::from_integer(BigInt::from(5)));
1199
1200        assignment.insert(0, false);
1201        let result = manager.eval(scaled, &assignment);
1202        assert_eq!(result, BigRational::from_integer(BigInt::from(0)));
1203    }
1204
1205    #[test]
1206    fn test_add_sharing() {
1207        use num_bigint::BigInt;
1208        let mut manager = AddManager::new();
1209        let x = manager.constant(BigRational::from_integer(BigInt::from(5)));
1210        let y = manager.constant(BigRational::from_integer(BigInt::from(5)));
1211        assert_eq!(x, y); // Should return the same node
1212    }
1213}