oxidd_core/
function.rs

1//! Function traits
2
3use std::fmt::Display;
4use std::hash::{BuildHasher, Hash};
5
6use nanorand::Rng;
7
8use crate::util::num::F64;
9use crate::util::{
10    AllocResult, Borrowed, EdgeDropGuard, NodeSet, OptBool, SatCountCache, SatCountNumber,
11    Substitution,
12};
13use crate::{DiagramRules, Edge, InnerNode, LevelNo, Manager, ManagerRef, Node};
14
15/// Shorthand to get the [`Edge`] type associated with a [`Function`]
16pub type EdgeOfFunc<'id, F> = <<F as Function>::Manager<'id> as Manager>::Edge;
17/// Shorthand to get the edge tag type associated with a [`Function`]
18pub type ETagOfFunc<'id, F> = <<F as Function>::Manager<'id> as Manager>::EdgeTag;
19/// Shorthand to get the [`InnerNode`] type associated with a [`Function`]
20pub type INodeOfFunc<'id, F> = <<F as Function>::Manager<'id> as Manager>::InnerNode;
21
22/// Function in a decision diagram
23///
24/// A function is some kind of external reference to a node as opposed to
25/// [`Edge`]s, which are diagram internal. A function also includes a reference
26/// to the diagram's manager. So one may view a function as an [`Edge`] plus a
27/// [`ManagerRef`].
28///
29/// Functions are what the library's user mostly works with. There may be
30/// subtraits such as `BooleanFunction` in the `oxidd-rules-bdd` crate which
31/// provide more functionality, in this case applying connectives of boolean
32/// logic to other functions.
33///
34/// For some methods of this trait, there are notes on locking behavior. In a
35/// concurrent setting, a manager has some kind of read/write lock, and
36/// [`Self::with_manager_shared()`] / [`Self::with_manager_exclusive()`] acquire
37/// this lock accordingly. In a sequential implementation, a
38/// [`RefCell`][std::cell::RefCell] or the like may be used instead of lock.
39///
40/// # Safety
41///
42/// An implementation must ensure that the "[`Edge`] part" of the function
43/// points to a node that is stored in the manager referenced  by the
44/// "[`ManagerRef`] part" of the function. All functions of this trait must
45/// maintain this link accordingly. In particular, [`Self::as_edge()`] and
46/// [`Self::into_edge()`] must panic as specified there.
47pub unsafe trait Function: Clone + Ord + Hash {
48    /// Type of the associated manager
49    ///
50    /// This type is generic over a lifetime `'id` to permit the "lifetime
51    /// trick" used, e.g., in [`GhostCell`][GhostCell]: The idea is to make the
52    /// [`Manager`], [`Edge`] and [`InnerNode`] types [invariant][variance] over
53    /// `'id`. Any call to one of the
54    /// [`with_manager_shared()`][Function::with_manager_shared] /
55    /// [`with_manager_exclusive()`][Function::with_manager_exclusive] functions
56    /// of the [`Function`] or [`ManagerRef`] trait, which "generate" a fresh
57    /// lifetime `'id`. Now the type system ensures that every edge or node with
58    /// `'id` comes belongs to the manager from the `with_manager_*()` call.
59    /// This means that we can reduce the amount of runtime checks needed to
60    /// uphold the invariant that the children of a node stored in [`Manager`] M
61    /// are stored in M as well.
62    ///
63    /// Note that [`Function`] and [`ManagerRef`] are (typically) outside the
64    /// scope of this lifetime trick to make the library more flexible.
65    ///
66    /// [GhostCell]: https://plv.mpi-sws.org/rustbelt/ghostcell/
67    /// [variance]: https://doc.rust-lang.org/reference/subtyping.html
68    type Manager<'id>: Manager;
69
70    /// [Manager references][ManagerRef] for [`Self::Manager`]
71    type ManagerRef: for<'id> ManagerRef<Manager<'id> = Self::Manager<'id>>;
72
73    /// Create a new function from a manager reference and an edge
74    fn from_edge<'id>(manager: &Self::Manager<'id>, edge: EdgeOfFunc<'id, Self>) -> Self;
75
76    /// Create a new function from a manager reference and an edge reference
77    #[inline(always)]
78    fn from_edge_ref<'id>(manager: &Self::Manager<'id>, edge: &EdgeOfFunc<'id, Self>) -> Self {
79        Self::from_edge(manager, manager.clone_edge(edge))
80    }
81
82    /// Converts this function into the underlying edge (as reference), checking
83    /// that it belongs to the given `manager`
84    ///
85    /// Panics if the function does not belong to `manager`.
86    fn as_edge<'id>(&self, manager: &Self::Manager<'id>) -> &EdgeOfFunc<'id, Self>;
87
88    /// Converts this function into the underlying edge, checking that it
89    /// belongs to the given `manager`
90    ///
91    /// Panics if the function does not belong to `manager`.
92    fn into_edge<'id>(self, manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
93
94    /// Clone the [`ManagerRef`] part
95    fn manager_ref(&self) -> Self::ManagerRef;
96
97    /// Obtain a shared manager reference as well as the underlying edge
98    ///
99    /// Locking behavior: acquires the manager's lock for shared access.
100    ///
101    /// # Example
102    ///
103    /// ```
104    /// # use oxidd_core::function::Function;
105    /// fn my_eq<F: Function>(f: &F, g: &F) -> bool {
106    ///     f.with_manager_shared(|manager, f_edge| {
107    ///         // Do something meaningful with `manager` and `edge` (the following
108    ///         // is better done using `f == g` without `with_manager_shared()`)
109    ///         let g_edge = g.as_edge(manager);
110    ///         f_edge == g_edge
111    ///     })
112    /// }
113    /// ```
114    fn with_manager_shared<F, T>(&self, f: F) -> T
115    where
116        F: for<'id> FnOnce(&Self::Manager<'id>, &EdgeOfFunc<'id, Self>) -> T;
117
118    /// Obtain an exclusive manager reference as well as the underlying edge
119    ///
120    /// Locking behavior: acquires the manager's lock for exclusive access.
121    ///
122    /// # Example
123    ///
124    /// ```
125    /// # use oxidd_core::{*, function::Function, util::AllocResult};
126    /// /// Adds a binary node on a new level with children `f` and `g`
127    /// fn foo<F: Function>(f: &F, g: &F) -> AllocResult<F> {
128    ///     f.with_manager_exclusive(|manager, f_edge| {
129    ///         let fe = manager.clone_edge(f_edge);
130    ///         let ge = manager.clone_edge(g.as_edge(manager));
131    ///         let he = manager.add_level(|level| InnerNode::new(level, [fe, ge]))?;
132    ///         Ok(F::from_edge(manager, he))
133    ///     })
134    /// }
135    /// ```
136    fn with_manager_exclusive<F, T>(&self, f: F) -> T
137    where
138        F: for<'id> FnOnce(&mut Self::Manager<'id>, &EdgeOfFunc<'id, Self>) -> T;
139
140    /// Count the number of nodes in this function, including terminal nodes
141    ///
142    /// Locking behavior: acquires the manager's lock for shared access.
143    fn node_count(&self) -> usize {
144        fn inner<M: Manager>(manager: &M, e: &M::Edge, set: &mut M::NodeSet) {
145            if set.insert(e) {
146                if let Node::Inner(node) = manager.get_node(e) {
147                    for e in node.children() {
148                        inner(manager, &*e, set)
149                    }
150                }
151            }
152        }
153
154        self.with_manager_shared(|manager, edge| {
155            let mut set = Default::default();
156            inner(manager, edge, &mut set);
157            set.len()
158        })
159    }
160}
161
162/// Substitution extension for [`Function`]
163pub trait FunctionSubst: Function {
164    /// Substitute variables in `self` according to `substitution`
165    ///
166    /// The substitution is performed in a parallel fashion, e.g.:
167    /// `(¬x ∧ ¬y)[x ↦ ¬x ∧ ¬y, y ↦ ⊥] = ¬(¬x ∧ ¬y) ∧ ¬⊥ = x ∨ y`
168    ///
169    /// Locking behavior: acquires the manager's lock for shared access.
170    ///
171    /// Panics if `self` and the function in `substitution` don't belong to the
172    /// same manager.
173    fn substitute<'a>(
174        &'a self,
175        substitution: impl Substitution<Var = &'a Self, Replacement = &'a Self>,
176    ) -> AllocResult<Self> {
177        if substitution.pairs().len() == 0 {
178            return Ok(self.clone());
179        }
180        self.with_manager_shared(|manager, edge| {
181            Ok(Self::from_edge(
182                manager,
183                Self::substitute_edge(
184                    manager,
185                    edge,
186                    substitution.map(|(v, r)| {
187                        (v.as_edge(manager).borrowed(), r.as_edge(manager).borrowed())
188                    }),
189                )?,
190            ))
191        })
192    }
193
194    /// `Edge` version of [`Self::substitute()`]
195    #[must_use]
196    fn substitute_edge<'id, 'a>(
197        manager: &'a Self::Manager<'id>,
198        edge: &'a EdgeOfFunc<'id, Self>,
199        substitution: impl Substitution<
200            Var = Borrowed<'a, EdgeOfFunc<'id, Self>>,
201            Replacement = Borrowed<'a, EdgeOfFunc<'id, Self>>,
202        >,
203    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
204}
205
206/// Boolean functions 𝔹ⁿ → 𝔹
207///
208/// As a user of this trait, you are probably most interested in methods like
209/// [`Self::not()`], [`Self::and()`], and [`Self::or()`]. As an implementor, it
210/// suffices to implement the functions operating on edges.
211pub trait BooleanFunction: Function {
212    /// Get the always false function `⊥`
213    fn f<'id>(manager: &Self::Manager<'id>) -> Self {
214        Self::from_edge(manager, Self::f_edge(manager))
215    }
216    /// Get the always true function `⊤`
217    fn t<'id>(manager: &Self::Manager<'id>) -> Self {
218        Self::from_edge(manager, Self::t_edge(manager))
219    }
220
221    /// Get a fresh variable, i.e., a function that is true if and only if the
222    /// variable is true. This adds a new level to a decision diagram.
223    fn new_var<'id>(manager: &mut Self::Manager<'id>) -> AllocResult<Self>;
224
225    /// Get the cofactors `(f_true, f_false)` of `self`
226    ///
227    /// Let f(x₀, …, xₙ) be represented by `self`, where x₀ is (currently) the
228    /// top-most variable. Then f<sub>true</sub>(x₁, …, xₙ) = f(⊤, x₁, …, xₙ)
229    /// and f<sub>false</sub>(x₁, …, xₙ) = f(⊥, x₁, …, xₙ).
230    ///
231    /// Note that the domain of f is 𝔹ⁿ⁺¹ while the domain of f<sub>true</sub>
232    /// and f<sub>false</sub> is 𝔹ⁿ. This is irrelevant in case of BDDs and
233    /// BCDDs, but not for ZBDDs: For instance, g(x₀) = x₀ and g'(x₀, x₁) = x₀
234    /// have the same representation as BDDs or BCDDs, but different
235    /// representations as ZBDDs.
236    ///
237    /// Structurally, the cofactors are simply the children in case of BDDs and
238    /// ZBDDs. (For BCDDs, the edge tags are adjusted accordingly.) On these
239    /// representations, runtime is thus in O(1).
240    ///
241    /// Returns `None` iff `self` references a terminal node. If you only need
242    /// `f_true` or `f_false`, [`Self::cofactor_true`] or
243    /// [`Self::cofactor_false`] are slightly more efficient.
244    ///
245    /// Locking behavior: acquires the manager's lock for shared access.
246    fn cofactors(&self) -> Option<(Self, Self)> {
247        self.with_manager_shared(|manager, f| {
248            let (ft, ff) = Self::cofactors_edge(manager, f)?;
249            Some((
250                Self::from_edge_ref(manager, &ft),
251                Self::from_edge_ref(manager, &ff),
252            ))
253        })
254    }
255
256    /// Get the cofactor `f_true` of `self`
257    ///
258    /// This method is slightly more efficient than [`Self::cofactors`] in case
259    /// `f_false` is not needed.
260    ///
261    /// For a more detailed description, see [`Self::cofactors`].
262    ///
263    /// Returns `None` iff `self` references a terminal node.
264    ///
265    /// Locking behavior: acquires the manager's lock for shared access.
266    fn cofactor_true(&self) -> Option<Self> {
267        self.with_manager_shared(|manager, f| {
268            let (ft, _) = Self::cofactors_edge(manager, f)?;
269            Some(Self::from_edge_ref(manager, &ft))
270        })
271    }
272
273    /// Get the cofactor `f_false` of `self`
274    ///
275    /// This method is slightly more efficient than [`Self::cofactors`] in case
276    /// `f_true` is not needed.
277    ///
278    /// For a more detailed description, see [`Self::cofactors`].
279    ///
280    /// Returns `None` iff `self` references a terminal node.
281    ///
282    /// Locking behavior: acquires the manager's lock for shared access.
283    fn cofactor_false(&self) -> Option<Self> {
284        self.with_manager_shared(|manager, f| {
285            let (_, ff) = Self::cofactors_edge(manager, f)?;
286            Some(Self::from_edge_ref(manager, &ff))
287        })
288    }
289
290    /// Compute the negation `¬self`
291    ///
292    /// Locking behavior: acquires the manager's lock for shared access.
293    fn not(&self) -> AllocResult<Self> {
294        self.with_manager_shared(|manager, edge| {
295            Ok(Self::from_edge(manager, Self::not_edge(manager, edge)?))
296        })
297    }
298    /// Compute the negation `¬self`, owned version
299    ///
300    /// Compared to [`Self::not()`], this method does not need to clone the
301    /// function, so when the implementation is using (e.g.) complemented edges,
302    /// this might be a little bit faster than [`Self::not()`].
303    ///
304    /// Locking behavior: acquires the manager's lock for shared access.
305    fn not_owned(self) -> AllocResult<Self> {
306        self.not()
307    }
308    /// Compute the conjunction `self ∧ rhs`
309    ///
310    /// Locking behavior: acquires the manager's lock for shared access.
311    ///
312    /// Panics if `self` and `rhs` don't belong to the same manager.
313    fn and(&self, rhs: &Self) -> AllocResult<Self> {
314        self.with_manager_shared(|manager, lhs| {
315            let e = Self::and_edge(manager, lhs, rhs.as_edge(manager))?;
316            Ok(Self::from_edge(manager, e))
317        })
318    }
319    /// Compute the disjunction `self ∨ rhs`
320    ///
321    /// Locking behavior: acquires the manager's lock for shared access.
322    ///
323    /// Panics if `self` and `rhs` don't belong to the same manager.
324    fn or(&self, rhs: &Self) -> AllocResult<Self> {
325        self.with_manager_shared(|manager, lhs| {
326            let e = Self::or_edge(manager, lhs, rhs.as_edge(manager))?;
327            Ok(Self::from_edge(manager, e))
328        })
329    }
330    /// Compute the negated conjunction `self ⊼ rhs`
331    ///
332    /// Locking behavior: acquires the manager's lock for shared access.
333    ///
334    /// Panics if `self` and `rhs` don't belong to the same manager.
335    fn nand(&self, rhs: &Self) -> AllocResult<Self> {
336        self.with_manager_shared(|manager, lhs| {
337            let e = Self::nand_edge(manager, lhs, rhs.as_edge(manager))?;
338            Ok(Self::from_edge(manager, e))
339        })
340    }
341    /// Compute the negated disjunction `self ⊽ rhs`
342    ///
343    /// Locking behavior: acquires the manager's lock for shared access.
344    ///
345    /// Panics if `self` and `rhs` don't belong to the same manager.
346    fn nor(&self, rhs: &Self) -> AllocResult<Self> {
347        self.with_manager_shared(|manager, lhs| {
348            let e = Self::nor_edge(manager, lhs, rhs.as_edge(manager))?;
349            Ok(Self::from_edge(manager, e))
350        })
351    }
352    /// Compute the exclusive disjunction `self ⊕ rhs`
353    ///
354    /// Locking behavior: acquires the manager's lock for shared access.
355    ///
356    /// Panics if `self` and `rhs` don't belong to the same manager.
357    fn xor(&self, rhs: &Self) -> AllocResult<Self> {
358        self.with_manager_shared(|manager, lhs| {
359            let e = Self::xor_edge(manager, lhs, rhs.as_edge(manager))?;
360            Ok(Self::from_edge(manager, e))
361        })
362    }
363    /// Compute the equivalence `self ↔ rhs`
364    ///
365    /// Locking behavior: acquires the manager's lock for shared access.
366    ///
367    /// Panics if `self` and `rhs` don't belong to the same manager.
368    fn equiv(&self, rhs: &Self) -> AllocResult<Self> {
369        self.with_manager_shared(|manager, lhs| {
370            let e = Self::equiv_edge(manager, lhs, rhs.as_edge(manager))?;
371            Ok(Self::from_edge(manager, e))
372        })
373    }
374    /// Compute the implication `self → rhs` (or `self ≤ rhs`)
375    ///
376    /// Locking behavior: acquires the manager's lock for shared access.
377    ///
378    /// Panics if `self` and `rhs` don't belong to the same manager.
379    fn imp(&self, rhs: &Self) -> AllocResult<Self> {
380        self.with_manager_shared(|manager, lhs| {
381            let e = Self::imp_edge(manager, lhs, rhs.as_edge(manager))?;
382            Ok(Self::from_edge(manager, e))
383        })
384    }
385    /// Compute the strict implication `self < rhs`
386    ///
387    /// Locking behavior: acquires the manager's lock for shared access.
388    ///
389    /// Panics if `self` and `rhs` don't belong to the same manager.
390    fn imp_strict(&self, rhs: &Self) -> AllocResult<Self> {
391        self.with_manager_shared(|manager, lhs| {
392            let e = Self::imp_strict_edge(manager, lhs, rhs.as_edge(manager))?;
393            Ok(Self::from_edge(manager, e))
394        })
395    }
396
397    /// Get the always false function `⊥` as edge
398    fn f_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
399    /// Get the always true function `⊤` as edge
400    fn t_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
401
402    /// Get the cofactors `(f_true, f_false)` of `f`, edge version
403    ///
404    /// Returns `None` iff `f` references a terminal node. For more details on
405    /// the semantics of `f_true` and `f_false`, see [`Self::cofactors`].
406    #[inline]
407    #[allow(clippy::type_complexity)]
408    fn cofactors_edge<'a, 'id>(
409        manager: &'a Self::Manager<'id>,
410        f: &'a EdgeOfFunc<'id, Self>,
411    ) -> Option<(
412        Borrowed<'a, EdgeOfFunc<'id, Self>>,
413        Borrowed<'a, EdgeOfFunc<'id, Self>>,
414    )> {
415        if let Node::Inner(node) = manager.get_node(f) {
416            Some(Self::cofactors_node(f.tag(), node))
417        } else {
418            None
419        }
420    }
421
422    /// Get the cofactors `(f_true, f_false)` of `node`, assuming an incoming
423    /// edge with `EdgeTag`
424    ///
425    /// Returns `None` iff `f` references a terminal node. For more details on
426    /// the semantics of `f_true` and `f_false`, see [`Self::cofactors`].
427    ///
428    /// Implementation note: The default implementation assumes that
429    /// [cofactor 0][DiagramRules::cofactor] corresponds to `f_true` and
430    /// [cofactor 1][DiagramRules::cofactor] corresponds to `f_false`.
431    #[inline]
432    fn cofactors_node<'a, 'id>(
433        tag: ETagOfFunc<'id, Self>,
434        node: &'a INodeOfFunc<'id, Self>,
435    ) -> (
436        Borrowed<'a, EdgeOfFunc<'id, Self>>,
437        Borrowed<'a, EdgeOfFunc<'id, Self>>,
438    ) {
439        let cofactor = <<Self::Manager<'id> as Manager>::Rules as DiagramRules<_, _, _>>::cofactor;
440        (cofactor(tag, node, 0), cofactor(tag, node, 1))
441    }
442
443    /// Compute the negation `¬edge`, edge version
444    #[must_use]
445    fn not_edge<'id>(
446        manager: &Self::Manager<'id>,
447        edge: &EdgeOfFunc<'id, Self>,
448    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
449
450    /// Compute the negation `¬edge`, owned edge version
451    ///
452    /// Compared to [`Self::not_edge()`], this method does not need to clone the
453    /// edge, so when the implementation is using (e.g.) complemented edges,
454    /// this might be a little bit faster than [`Self::not_edge()`].
455    #[must_use]
456    fn not_edge_owned<'id>(
457        manager: &Self::Manager<'id>,
458        edge: EdgeOfFunc<'id, Self>,
459    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
460        Self::not_edge(manager, &edge)
461    }
462
463    /// Compute the conjunction `lhs ∧ rhs`, edge version
464    #[must_use]
465    fn and_edge<'id>(
466        manager: &Self::Manager<'id>,
467        lhs: &EdgeOfFunc<'id, Self>,
468        rhs: &EdgeOfFunc<'id, Self>,
469    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
470    /// Compute the disjunction `lhs ∨ rhs`, edge version
471    #[must_use]
472    fn or_edge<'id>(
473        manager: &Self::Manager<'id>,
474        lhs: &EdgeOfFunc<'id, Self>,
475        rhs: &EdgeOfFunc<'id, Self>,
476    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
477    /// Compute the negated conjunction `lhs ⊼ rhs`, edge version
478    #[must_use]
479    fn nand_edge<'id>(
480        manager: &Self::Manager<'id>,
481        lhs: &EdgeOfFunc<'id, Self>,
482        rhs: &EdgeOfFunc<'id, Self>,
483    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
484    /// Compute the negated disjunction `lhs ⊽ rhs`, edge version
485    #[must_use]
486    fn nor_edge<'id>(
487        manager: &Self::Manager<'id>,
488        lhs: &EdgeOfFunc<'id, Self>,
489        rhs: &EdgeOfFunc<'id, Self>,
490    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
491    /// Compute the exclusive disjunction `lhs ⊕ rhs`, edge version
492    #[must_use]
493    fn xor_edge<'id>(
494        manager: &Self::Manager<'id>,
495        lhs: &EdgeOfFunc<'id, Self>,
496        rhs: &EdgeOfFunc<'id, Self>,
497    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
498    /// Compute the equivalence `lhs ↔ rhs`, edge version
499    #[must_use]
500    fn equiv_edge<'id>(
501        manager: &Self::Manager<'id>,
502        lhs: &EdgeOfFunc<'id, Self>,
503        rhs: &EdgeOfFunc<'id, Self>,
504    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
505    /// Compute the implication `lhs → rhs`, edge version
506    #[must_use]
507    fn imp_edge<'id>(
508        manager: &Self::Manager<'id>,
509        lhs: &EdgeOfFunc<'id, Self>,
510        rhs: &EdgeOfFunc<'id, Self>,
511    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
512    /// Compute the strict implication `lhs < rhs`, edge version
513    #[must_use]
514    fn imp_strict_edge<'id>(
515        manager: &Self::Manager<'id>,
516        lhs: &EdgeOfFunc<'id, Self>,
517        rhs: &EdgeOfFunc<'id, Self>,
518    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
519
520    /// Returns `true` iff `self` is satisfiable, i.e. is not `⊥`
521    ///
522    /// Locking behavior: acquires the manager's lock for shared access.
523    fn satisfiable(&self) -> bool {
524        self.with_manager_shared(|manager, edge| {
525            let f = EdgeDropGuard::new(manager, Self::f_edge(manager));
526            edge != &*f
527        })
528    }
529
530    /// Returns `true` iff `self` is valid, i.e. is `⊤`
531    ///
532    /// Locking behavior: acquires the manager's lock for shared access.
533    fn valid(&self) -> bool {
534        self.with_manager_shared(|manager, edge| {
535            let t = EdgeDropGuard::new(manager, Self::t_edge(manager));
536            edge == &*t
537        })
538    }
539
540    /// Compute `if self { then_case } else { else_case }`
541    ///
542    /// This is equivalent to `(self ∧ then_case) ∨ (¬self ∧ else_case)` but
543    /// possibly more efficient than computing all the
544    /// conjunctions/disjunctions.
545    ///
546    /// Locking behavior: acquires the manager's lock for shared access.
547    ///
548    /// Panics if `self`, `then_case`, and `else_case` don't belong to the same
549    /// manager.
550    fn ite(&self, then_case: &Self, else_case: &Self) -> AllocResult<Self> {
551        self.with_manager_shared(|manager, if_edge| {
552            let then_edge = then_case.as_edge(manager);
553            let else_edge = else_case.as_edge(manager);
554            let res = Self::ite_edge(manager, if_edge, then_edge, else_edge)?;
555            Ok(Self::from_edge(manager, res))
556        })
557    }
558
559    /// Compute `if if_edge { then_edge } else { else_edge }` (edge version)
560    ///
561    /// This is equivalent to `(self ∧ then_case) ∨ (¬self ∧ else_case)` but
562    /// possibly more efficient than computing all the
563    /// conjunctions/disjunctions.
564    #[must_use]
565    fn ite_edge<'id>(
566        manager: &Self::Manager<'id>,
567        if_edge: &EdgeOfFunc<'id, Self>,
568        then_edge: &EdgeOfFunc<'id, Self>,
569        else_edge: &EdgeOfFunc<'id, Self>,
570    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
571        let f = EdgeDropGuard::new(manager, Self::and_edge(manager, if_edge, then_edge)?);
572        let g = EdgeDropGuard::new(manager, Self::imp_strict_edge(manager, if_edge, else_edge)?);
573        Self::or_edge(manager, &*f, &*g)
574    }
575
576    /// Count the number of satisfying assignments, assuming `vars` input
577    /// variables
578    ///
579    /// The `cache` can be used to speed up multiple model counting operations
580    /// for functions in the same decision diagram. If the model counts of just
581    /// one function are of interest, one may simply pass an empty
582    /// [`SatCountCache`] (using `&mut SatCountCache::default()`). The cache
583    /// will automatically be invalidated in case there have been reordering
584    /// operations or `vars` changed since the last query (see
585    /// [`SatCountCache::clear_if_invalid()`]). Still, it is the caller's
586    /// responsibility to not use the cache for different managers.
587    ///
588    /// Locking behavior: acquires the manager's lock for shared access.
589    fn sat_count<N: SatCountNumber, S: std::hash::BuildHasher>(
590        &self,
591        vars: LevelNo,
592        cache: &mut SatCountCache<N, S>,
593    ) -> N {
594        self.with_manager_shared(|manager, edge| Self::sat_count_edge(manager, edge, vars, cache))
595    }
596
597    /// `Edge` version of [`Self::sat_count()`]
598    fn sat_count_edge<'id, N: SatCountNumber, S: std::hash::BuildHasher>(
599        manager: &Self::Manager<'id>,
600        edge: &EdgeOfFunc<'id, Self>,
601        vars: LevelNo,
602        cache: &mut SatCountCache<N, S>,
603    ) -> N;
604
605    /// Pick a cube of this function
606    ///
607    /// A cube `c` of a function `f` is a satisfying assignment, i.e., `c → f`
608    /// holds, and can be represented as a conjunction of literals. It does
609    /// not necessarily define all variables in the function's domain (it is
610    /// not necessarily a canonical minterm). For most (if not all) kinds of
611    /// decision diagrams, cubes have at most one node per level.
612    ///
613    /// `order` is a list of variables. If it is non-empty, it must contain as
614    /// many variables as there are levels.
615    ///
616    /// Returns `None` if the function is false. Otherwise, this method returns
617    /// a vector where the i-th entry indicates if the i-th variable of `order`
618    /// (or the variable currently at the i-th level in case `order` is empty)
619    /// is true, false, or "don't care".
620    ///
621    /// Whenever a value for a variable needs to be chosen (i.e., it cannot be
622    /// left as a don't care), `choice` is called to determine the valuation for
623    /// this variable. The argument of type [`LevelNo`] is the level
624    /// corresponding to that variable. It is guaranteed that `choice` will
625    /// only be called at most once for each level. The [`Edge`] argument is
626    /// guaranteed to point to an inner node at the respective level. (We
627    /// pass an [`Edge`] and not an [`InnerNode`] reference since [`Edge`]s
628    /// provide more information, e.g., the [`NodeID`][Edge::node_id()].)
629    ///
630    /// Locking behavior: acquires the manager's lock for shared access.
631    fn pick_cube<'a, I: ExactSizeIterator<Item = &'a Self>>(
632        &'a self,
633        order: impl IntoIterator<IntoIter = I>,
634        choice: impl for<'id> FnMut(&Self::Manager<'id>, &EdgeOfFunc<'id, Self>, LevelNo) -> bool,
635    ) -> Option<Vec<OptBool>> {
636        self.with_manager_shared(|manager, edge| {
637            Self::pick_cube_edge(
638                manager,
639                edge,
640                order.into_iter().map(|f| f.as_edge(manager)),
641                choice,
642            )
643        })
644    }
645
646    /// Pick a symbolic cube of this function, i.e., as decision diagram
647    ///
648    /// A cube `c` of a function `f` is a satisfying assignment, i.e., `c → f`
649    /// holds, and can be represented as a conjunction of literals. It does
650    /// not necessarily define all variables in the function's domain (it is
651    /// not necessarily a canonical minterm). For most (if not all) kinds of
652    /// decision diagrams, cubes have at most one node per level.
653    ///
654    /// Whenever a value for a variable needs to be chosen (i.e., it cannot be
655    /// left as a don't care), `choice` is called to determine the valuation for
656    /// this variable. The argument of type [`LevelNo`] is the level
657    /// corresponding to that variable. It is guaranteed that `choice` will
658    /// only be called at most once for each level. The [`Edge`] argument is
659    /// guaranteed to point to an inner node at the respective level. (We
660    /// pass an [`Edge`] and not an [`InnerNode`] reference since [`Edge`]s
661    /// provide more information, e.g., the [`NodeID`][Edge::node_id()].)
662    ///
663    /// If `self` is `⊥`, then the return value will be `⊥`.
664    ///
665    /// Locking behavior: acquires the manager's lock for shared access.
666    fn pick_cube_dd(
667        &self,
668        choice: impl for<'id> FnMut(&Self::Manager<'id>, &EdgeOfFunc<'id, Self>, LevelNo) -> bool,
669    ) -> AllocResult<Self> {
670        self.with_manager_shared(|manager, edge| {
671            let res = Self::pick_cube_dd_edge(manager, edge, choice)?;
672            Ok(Self::from_edge(manager, res))
673        })
674    }
675
676    /// Pick a symbolic cube of this function, i.e., as decision diagram, using
677    /// the literals in `literal_set` if there is a choice
678    ///
679    /// A cube `c` of a function `f` is a satisfying assignment, i.e., `c → f`
680    /// holds, and can be represented as a conjunction of literals. It does
681    /// not necessarily define all variables in the function's domain (it is
682    /// not necessarily a canonical minterm). For most (if not all) kinds of
683    /// decision diagrams, cubes have at most one node per level.
684    ///
685    /// `literal_set` is represented as a conjunction of literals. Whenever
686    /// there is a choice for a variable, it will be set to true if the variable
687    /// has a positive occurrence in `literal_set`, and set to false if it
688    /// occurs negated in `literal_set`. If the variable does not occur in
689    /// `literal_set`, then it will be left as don't care if possible, otherwise
690    /// an arbitrary (not necessarily random) choice will be performed.
691    ///
692    /// If `self` is `⊥`, then the return value will be `⊥`.
693    ///
694    /// Locking behavior: acquires the manager's lock for shared access.
695    fn pick_cube_dd_set(&self, literal_set: &Self) -> AllocResult<Self> {
696        self.with_manager_shared(|manager, edge| {
697            let res = Self::pick_cube_dd_set_edge(manager, edge, literal_set.as_edge(manager))?;
698            Ok(Self::from_edge(manager, res))
699        })
700    }
701
702    /// `Edge` version of [`Self::pick_cube()`]
703    fn pick_cube_edge<'id, 'a, I>(
704        manager: &'a Self::Manager<'id>,
705        edge: &'a EdgeOfFunc<'id, Self>,
706        order: impl IntoIterator<IntoIter = I>,
707        choice: impl FnMut(&Self::Manager<'id>, &EdgeOfFunc<'id, Self>, LevelNo) -> bool,
708    ) -> Option<Vec<OptBool>>
709    where
710        I: ExactSizeIterator<Item = &'a EdgeOfFunc<'id, Self>>;
711
712    /// `Edge` version of [`Self::pick_cube_dd()`]
713    fn pick_cube_dd_edge<'id>(
714        manager: &Self::Manager<'id>,
715        edge: &EdgeOfFunc<'id, Self>,
716        choice: impl FnMut(&Self::Manager<'id>, &EdgeOfFunc<'id, Self>, LevelNo) -> bool,
717    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
718
719    /// `Edge` version of [`Self::pick_cube_dd_set()`]
720    fn pick_cube_dd_set_edge<'id>(
721        manager: &Self::Manager<'id>,
722        edge: &EdgeOfFunc<'id, Self>,
723        literal_set: &EdgeOfFunc<'id, Self>,
724    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
725
726    /// Pick a random cube of this function, where each cube has the same
727    /// probability of being chosen
728    ///
729    /// `order` is a list of variables. If it is non-empty, it must contain as
730    /// many variables as there are levels.
731    ///
732    /// Returns `None` if the function is false. Otherwise, this method returns
733    /// a vector where the i-th entry indicates if the i-th variable of `order`
734    /// (or the variable currently at the i-th level in case `order` is empty)
735    /// is true, false, or "don't care". To obtain a total valuation from this
736    /// partial valuation, it suffices to pick true or false with probability ½.
737    /// (Note that this function returns a partial valuation with n "don't
738    /// cares" with a probability that is 2<sup>n</sup> as high as the
739    /// probability of any total valuation.)
740    ///
741    /// Locking behavior: acquires the manager's lock for shared access.
742    fn pick_cube_uniform<'a, I: ExactSizeIterator<Item = &'a Self>, S: BuildHasher>(
743        &'a self,
744        order: impl IntoIterator<IntoIter = I>,
745        cache: &mut SatCountCache<F64, S>,
746        rng: &mut crate::util::Rng,
747    ) -> Option<Vec<OptBool>> {
748        self.with_manager_shared(|manager, edge| {
749            Self::pick_cube_uniform_edge(
750                manager,
751                edge,
752                order.into_iter().map(|f| f.as_edge(manager)),
753                cache,
754                rng,
755            )
756        })
757    }
758
759    /// `Edge` version of [`Self::pick_cube_uniform()`]
760    fn pick_cube_uniform_edge<'id, 'a, I, S>(
761        manager: &'a Self::Manager<'id>,
762        edge: &'a EdgeOfFunc<'id, Self>,
763        order: impl IntoIterator<IntoIter = I>,
764        cache: &mut SatCountCache<F64, S>,
765        rng: &mut crate::util::Rng,
766    ) -> Option<Vec<OptBool>>
767    where
768        I: ExactSizeIterator<Item = &'a EdgeOfFunc<'id, Self>>,
769        S: BuildHasher,
770    {
771        let vars = manager.num_levels();
772        Self::pick_cube_edge(manager, edge, order, |manager, edge, _| {
773            let tag = edge.tag();
774            // `edge` is guaranteed to point to an inner node
775            let node = manager.get_node(edge).unwrap_inner();
776            let (t, e) = Self::cofactors_node(tag, node);
777            let t_count = Self::sat_count_edge(manager, &*t, vars, cache).0;
778            let e_count = Self::sat_count_edge(manager, &*e, vars, cache).0;
779            rng.generate::<f64>() < t_count / (t_count + e_count)
780        })
781    }
782
783    /// Evaluate this Boolean function
784    ///
785    /// `args` consists of pairs `(variable, value)` and determines the
786    /// valuation for all variables. Missing values are assumed to be false.
787    /// However, note that the arguments may also determine the domain,
788    /// e.g., in case of ZBDDs. If values are specified multiple times, the
789    /// last one counts.
790    ///
791    /// Note that all variables in `args` must be handles for the respective
792    /// decision diagram levels, i.e., the Boolean function representing the
793    /// variable in case of B(C)DDs, and a singleton set for ZBDDs.
794    ///
795    /// Locking behavior: acquires the manager's lock for shared access.
796    ///
797    /// Panics if any function in `args` refers to a terminal node.
798    fn eval<'a>(&'a self, args: impl IntoIterator<Item = (&'a Self, bool)>) -> bool {
799        self.with_manager_shared(|manager, edge| {
800            Self::eval_edge(
801                manager,
802                edge,
803                args.into_iter()
804                    .map(|(f, b)| (f.as_edge(manager).borrowed(), b)),
805            )
806        })
807    }
808
809    /// `Edge` version of [`Self::eval()`]
810    fn eval_edge<'id, 'a>(
811        manager: &'a Self::Manager<'id>,
812        edge: &'a EdgeOfFunc<'id, Self>,
813        args: impl IntoIterator<Item = (Borrowed<'a, EdgeOfFunc<'id, Self>>, bool)>,
814    ) -> bool;
815}
816
817// The `cfg_attr` below is used such that cbindgen does not output the
818// Rust-specific documentation.
819
820/// Binary operators on Boolean functions
821#[cfg_attr(
822    all(),
823    doc = "
824
825The operators are used by the combined apply and quantification operations of
826the [`BooleanFunctionQuant`] trait. The operators themselves correspond to the
827ones defined in [`BooleanFunction`]."
828)]
829#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
830#[repr(u8)]
831pub enum BooleanOperator {
832    /// Conjunction `lhs ∧ rhs`
833    And,
834    /// Disjunction `lhs ∨ rhs`
835    Or,
836    /// Exclusive disjunction `lhs ⊕ rhs`
837    Xor,
838    /// Equivalence `lhs ↔ rhs`
839    Equiv,
840    /// Negated conjunction `lhs ⊼ rhs`
841    Nand,
842    /// Negated disjunction `lhs ⊽ rhs`
843    Nor,
844    /// Implication `lhs → rhs`
845    Imp,
846    /// Strict implication `lhs < rhs`
847    ImpStrict,
848}
849
850/// cbindgen:ignore
851unsafe impl crate::Countable for BooleanOperator {
852    const MAX_VALUE: usize = BooleanOperator::ImpStrict as usize;
853
854    fn as_usize(self) -> usize {
855        self as usize
856    }
857
858    fn from_usize(value: usize) -> Self {
859        use BooleanOperator::*;
860        match () {
861            _ if value == And as usize => And,
862            _ if value == Or as usize => Or,
863            _ if value == Xor as usize => Xor,
864            _ if value == Equiv as usize => Equiv,
865            _ if value == Nand as usize => Nand,
866            _ if value == Nor as usize => Nor,
867            _ if value == Imp as usize => Imp,
868            _ if value == ImpStrict as usize => ImpStrict,
869            _ => panic!("{value} does not correspond to a Boolean operator"),
870        }
871    }
872}
873
874/// cbindgen:ignore
875impl Display for BooleanOperator {
876    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
877        use BooleanOperator::*;
878        match self {
879            And => write!(f, "∧"),
880            Or => write!(f, "∨"),
881            Xor => write!(f, "⊕"),
882            Equiv => write!(f, "↔"),
883            Nand => write!(f, "⊼"),
884            Nor => write!(f, "⊽"),
885            Imp => write!(f, "→"),
886            ImpStrict => write!(f, "<"),
887        }
888    }
889}
890
891/// Quantification extension for [`BooleanFunction`]
892pub trait BooleanFunctionQuant: BooleanFunction {
893    /// Restrict a set of `vars` to constant values
894    ///
895    /// `vars` conceptually is a partial assignment, represented as the
896    /// conjunction of positive or negative literals, depending on whether the
897    /// variable should be mapped to true or false.
898    ///
899    /// Locking behavior: acquires the manager's lock for shared access.
900    ///
901    /// Panics if `self` and `vars` don't belong to the same manager.
902    fn restrict(&self, vars: &Self) -> AllocResult<Self> {
903        self.with_manager_shared(|manager, root| {
904            let e = Self::restrict_edge(manager, root, vars.as_edge(manager))?;
905            Ok(Self::from_edge(manager, e))
906        })
907    }
908
909    /// Compute the universal quantification over `vars`
910    ///
911    /// `vars` is a set of variables, which in turn is just the conjunction of
912    /// the variables. This operation removes all occurrences of the variables
913    /// by universal quantification. Universal quantification `∀x. f(…, x, …)`
914    /// of a Boolean function `f(…, x, …)` over a single variable `x` is
915    /// `f(…, 0, …) ∧ f(…, 1, …)`.
916    ///
917    /// Locking behavior: acquires the manager's lock for shared access.
918    ///
919    /// Panics if `self` and `vars` don't belong to the same manager.
920    fn forall(&self, vars: &Self) -> AllocResult<Self> {
921        self.with_manager_shared(|manager, root| {
922            let e = Self::forall_edge(manager, root, vars.as_edge(manager))?;
923            Ok(Self::from_edge(manager, e))
924        })
925    }
926
927    /// Compute the existential quantification over `vars`
928    ///
929    /// `vars` is a set of variables, which in turn is just the conjunction of
930    /// the variables. This operation removes all occurrences of the variables
931    /// by existential quantification. Existential quantification
932    /// `∃x. f(…, x, …)` of a Boolean function `f(…, x, …)` over a single
933    /// variable `x` is `f(…, 0, …) ∨ f(…, 1, …)`.
934    ///
935    /// Locking behavior: acquires the manager's lock for shared access.
936    ///
937    /// Panics if `self` and `vars` don't belong to the same manager.
938    fn exist(&self, vars: &Self) -> AllocResult<Self> {
939        self.with_manager_shared(|manager, root| {
940            let e = Self::exist_edge(manager, root, vars.as_edge(manager))?;
941            Ok(Self::from_edge(manager, e))
942        })
943    }
944
945    /// Compute the unique quantification over `vars`
946    ///
947    /// `vars` is a set of variables, which in turn is just the conjunction of
948    /// the variables. This operation removes all occurrences of the variables
949    /// by unique quantification. Unique quantification `∃!x. f(…, x, …)` of a
950    /// Boolean function `f(…, x, …)` over a single variable `x` is
951    /// `f(…, 0, …) ⊕ f(…, 1, …)`.
952    ///
953    /// Unique quantification is also known as the
954    /// [Boolean difference](https://en.wikipedia.org/wiki/Boole%27s_expansion_theorem#Operations_with_cofactors)
955    /// or
956    /// [Boolean derivative](https://en.wikipedia.org/wiki/Boolean_differential_calculus).
957    ///
958    /// Locking behavior: acquires the manager's lock for shared access.
959    ///
960    /// Panics if `self` and `vars` don't belong to the same manager.
961    fn unique(&self, vars: &Self) -> AllocResult<Self> {
962        self.with_manager_shared(|manager, root| {
963            let e = Self::unique_edge(manager, root, vars.as_edge(manager))?;
964            Ok(Self::from_edge(manager, e))
965        })
966    }
967
968    /// Combined application of `op` and quantification `∀x. self <op> rhs`,
969    /// where `<op>` is any of the operations from [`BooleanOperator`]
970    ///
971    /// See also [`Self::forall()`] and the trait [`BooleanFunction`] for more
972    /// details.
973    ///
974    /// Locking behavior: acquires the manager's lock for shared access.
975    ///
976    /// Panics if `self` and `rhs` and `vars` don't belong to the same manager.
977    fn apply_forall(&self, op: BooleanOperator, rhs: &Self, vars: &Self) -> AllocResult<Self> {
978        self.with_manager_shared(|manager, root| {
979            let e = Self::apply_forall_edge(
980                manager,
981                op,
982                root,
983                rhs.as_edge(manager),
984                vars.as_edge(manager),
985            )?;
986            Ok(Self::from_edge(manager, e))
987        })
988    }
989
990    /// Combined application of `op` and quantification `∃x. self <op> rhs`,
991    /// where `<op>` is any of the operations from [`BooleanOperator`]
992    ///
993    /// See also [`Self::exist()`] and the trait [`BooleanFunction`] for more
994    /// details.
995    ///
996    /// Panics if `self` and `rhs` and `vars` don't belong to the same manager.
997    fn apply_exist(&self, op: BooleanOperator, rhs: &Self, vars: &Self) -> AllocResult<Self> {
998        self.with_manager_shared(|manager, root| {
999            let e = Self::apply_exist_edge(
1000                manager,
1001                op,
1002                root,
1003                rhs.as_edge(manager),
1004                vars.as_edge(manager),
1005            )?;
1006            Ok(Self::from_edge(manager, e))
1007        })
1008    }
1009
1010    /// Combined application of `op` and quantification `∃!x. self <op> rhs`,
1011    /// where `<op>` is any of the operations from [`BooleanOperator`]
1012    ///
1013    /// See also [`Self::unique()`] and the trait [`BooleanFunction`] for more
1014    /// details.
1015    ///
1016    /// Panics if `self` and `rhs` and `vars` don't belong to the same manager.
1017    fn apply_unique(&self, op: BooleanOperator, rhs: &Self, vars: &Self) -> AllocResult<Self> {
1018        self.with_manager_shared(|manager, root| {
1019            let e = Self::apply_unique_edge(
1020                manager,
1021                op,
1022                root,
1023                rhs.as_edge(manager),
1024                vars.as_edge(manager),
1025            )?;
1026            Ok(Self::from_edge(manager, e))
1027        })
1028    }
1029
1030    /// Restrict a set of `vars` to constant values, edge version
1031    ///
1032    /// See [`Self::restrict()`] for more details.
1033    #[must_use]
1034    fn restrict_edge<'id>(
1035        manager: &Self::Manager<'id>,
1036        root: &EdgeOfFunc<'id, Self>,
1037        vars: &EdgeOfFunc<'id, Self>,
1038    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1039
1040    /// Compute the universal quantification of `root` over `vars`, edge
1041    /// version
1042    ///
1043    /// See [`Self::forall()`] for more details.
1044    #[must_use]
1045    fn forall_edge<'id>(
1046        manager: &Self::Manager<'id>,
1047        root: &EdgeOfFunc<'id, Self>,
1048        vars: &EdgeOfFunc<'id, Self>,
1049    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1050
1051    /// Compute the existential quantification of `root` over `vars`, edge
1052    /// version
1053    ///
1054    /// See [`Self::exist()`] for more details.
1055    #[must_use]
1056    fn exist_edge<'id>(
1057        manager: &Self::Manager<'id>,
1058        root: &EdgeOfFunc<'id, Self>,
1059        vars: &EdgeOfFunc<'id, Self>,
1060    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1061
1062    /// Compute the unique quantification of `root` over `vars`, edge version
1063    ///
1064    /// See [`Self::unique()`] for more details.
1065    #[must_use]
1066    fn unique_edge<'id>(
1067        manager: &Self::Manager<'id>,
1068        root: &EdgeOfFunc<'id, Self>,
1069        vars: &EdgeOfFunc<'id, Self>,
1070    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1071
1072    /// Combined application of `op` and forall quantification, edge version
1073    ///
1074    /// See [`Self::apply_forall()`] for more details.
1075    #[must_use]
1076    fn apply_forall_edge<'id>(
1077        manager: &Self::Manager<'id>,
1078        op: BooleanOperator,
1079        lhs: &EdgeOfFunc<'id, Self>,
1080        rhs: &EdgeOfFunc<'id, Self>,
1081        vars: &EdgeOfFunc<'id, Self>,
1082    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1083        // Naive default implementation
1084        use BooleanOperator::*;
1085        let inner = EdgeDropGuard::new(
1086            manager,
1087            match op {
1088                And => Self::and_edge(manager, lhs, rhs),
1089                Or => Self::or_edge(manager, lhs, rhs),
1090                Xor => Self::xor_edge(manager, lhs, rhs),
1091                Equiv => Self::equiv_edge(manager, lhs, rhs),
1092                Nand => Self::nand_edge(manager, lhs, rhs),
1093                Nor => Self::nor_edge(manager, lhs, rhs),
1094                Imp => Self::imp_edge(manager, lhs, rhs),
1095                ImpStrict => Self::imp_strict_edge(manager, lhs, rhs),
1096            }?,
1097        );
1098
1099        Self::forall_edge(manager, &inner, vars)
1100    }
1101
1102    /// Combined application of `op` and existential quantification, edge
1103    /// version
1104    ///
1105    /// See [`Self::apply_exist()`] for more details.
1106    #[must_use]
1107    fn apply_exist_edge<'id>(
1108        manager: &Self::Manager<'id>,
1109        op: BooleanOperator,
1110        lhs: &EdgeOfFunc<'id, Self>,
1111        rhs: &EdgeOfFunc<'id, Self>,
1112        vars: &EdgeOfFunc<'id, Self>,
1113    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1114        // Naive default implementation
1115        use BooleanOperator::*;
1116        let inner = EdgeDropGuard::new(
1117            manager,
1118            match op {
1119                And => Self::and_edge(manager, lhs, rhs),
1120                Or => Self::or_edge(manager, lhs, rhs),
1121                Xor => Self::xor_edge(manager, lhs, rhs),
1122                Equiv => Self::equiv_edge(manager, lhs, rhs),
1123                Nand => Self::nand_edge(manager, lhs, rhs),
1124                Nor => Self::nor_edge(manager, lhs, rhs),
1125                Imp => Self::imp_edge(manager, lhs, rhs),
1126                ImpStrict => Self::imp_strict_edge(manager, lhs, rhs),
1127            }?,
1128        );
1129
1130        Self::exist_edge(manager, &inner, vars)
1131    }
1132
1133    /// Combined application of `op` and unique quantification, edge version
1134    ///
1135    /// See [`Self::apply_unique()`] for more details.
1136    #[must_use]
1137    fn apply_unique_edge<'id>(
1138        manager: &Self::Manager<'id>,
1139        op: BooleanOperator,
1140        lhs: &EdgeOfFunc<'id, Self>,
1141        rhs: &EdgeOfFunc<'id, Self>,
1142        vars: &EdgeOfFunc<'id, Self>,
1143    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1144        // Naive default implementation
1145        use BooleanOperator::*;
1146        let inner = EdgeDropGuard::new(
1147            manager,
1148            match op {
1149                And => Self::and_edge(manager, lhs, rhs),
1150                Or => Self::or_edge(manager, lhs, rhs),
1151                Xor => Self::xor_edge(manager, lhs, rhs),
1152                Equiv => Self::equiv_edge(manager, lhs, rhs),
1153                Nand => Self::nand_edge(manager, lhs, rhs),
1154                Nor => Self::nor_edge(manager, lhs, rhs),
1155                Imp => Self::imp_edge(manager, lhs, rhs),
1156                ImpStrict => Self::imp_strict_edge(manager, lhs, rhs),
1157            }?,
1158        );
1159
1160        Self::unique_edge(manager, &inner, vars)
1161    }
1162}
1163
1164/// Set of Boolean vectors
1165///
1166/// A Boolean function f: 𝔹ⁿ → 𝔹 may also be regarded as a set S ∈ 𝒫(𝔹ⁿ), where
1167/// S = {v ∈ 𝔹ⁿ | f(v) = 1}. f is also called the characteristic function of S.
1168/// We can even view a Boolean vector as a subset of some "Universe" U, so we
1169/// also have S ∈ 𝒫(𝒫(U)). For example, let U = {a, b, c}. The function a is
1170/// the set of all sets containing a, {a, ab, abc, ac} (for the sake of
1171/// readability, we write ab for the set {a, b}). Conversely, the set {a} is the
1172/// function a ∧ ¬b ∧ ¬c.
1173///
1174/// Counting the number of elements in a `BoolVecSet` is equivalent to counting
1175/// the number of satisfying assignments of its characteristic function. Hence,
1176/// you may use [`BooleanFunction::sat_count()`] for this task.
1177///
1178/// The functions of this trait can be implemented efficiently for ZBDDs.
1179///
1180/// As a user of this trait, you are probably most interested in methods like
1181/// [`Self::union()`], [`Self::intsec()`], and [`Self::diff()`]. As an
1182/// implementor, it suffices to implement the functions operating on edges.
1183pub trait BooleanVecSet: Function {
1184    /// Add a new variable to the manager and get the corresponding singleton
1185    /// set
1186    ///
1187    /// This adds a new level to the decision diagram.
1188    fn new_singleton<'id>(manager: &mut Self::Manager<'id>) -> AllocResult<Self>;
1189
1190    /// Get the empty set ∅
1191    ///
1192    /// This corresponds to the Boolean function ⊥.
1193    fn empty<'id>(manager: &Self::Manager<'id>) -> Self {
1194        Self::from_edge(manager, Self::empty_edge(manager))
1195    }
1196
1197    /// Get the set {∅}
1198    ///
1199    /// This corresponds to the Boolean function ¬x₁ ∧ … ∧ ¬xₙ
1200    fn base<'id>(manager: &Self::Manager<'id>) -> Self {
1201        Self::from_edge(manager, Self::base_edge(manager))
1202    }
1203
1204    /// Get the set of subsets of `self` not containing `var`, formally
1205    /// `{s ∈ self | var ∉ s}`
1206    ///
1207    /// `var` must be a singleton set, otherwise the result is unspecified.
1208    /// Ideally, the implementation panics.
1209    ///
1210    /// Locking behavior: acquires a shared manager lock
1211    ///
1212    /// Panics if `self` and `var` do not belong to the same manager.
1213    fn subset0(&self, var: &Self) -> AllocResult<Self> {
1214        self.with_manager_shared(|manager, set| {
1215            let e = Self::subset0_edge(manager, set, var.as_edge(manager))?;
1216            Ok(Self::from_edge(manager, e))
1217        })
1218    }
1219
1220    /// Get the set of subsets of `self` containing `var`, formally
1221    /// `{s ∈ self | var ∈ s}`
1222    ///
1223    /// `var` must be a singleton set, otherwise the result is unspecified.
1224    /// Ideally, the implementation panics.
1225    ///
1226    /// Locking behavior: acquires a shared manager lock
1227    ///
1228    /// Panics if `self` and `var` do not belong to the same manager.
1229    fn subset1(&self, var: &Self) -> AllocResult<Self> {
1230        self.with_manager_shared(|manager, set| {
1231            let e = Self::subset1_edge(manager, set, var.as_edge(manager))?;
1232            Ok(Self::from_edge(manager, e))
1233        })
1234    }
1235
1236    /// Get the set of subsets derived from `self` by adding `var` to the
1237    /// subsets that do not contain `var`, and removing `var` from the subsets
1238    /// that contain `var`, formally
1239    /// `{s ∪ {var} | s ∈ self ∧ var ∉ s} ∪ {s ∖ {var} | s ∈ self ∧ var ∈ s}`
1240    ///
1241    /// `var` must be a singleton set, otherwise the result is unspecified.
1242    /// Ideally, the implementation panics.
1243    ///
1244    /// Locking behavior: acquires a shared manager lock
1245    ///
1246    /// Panics if `self` and `var` do not belong to the same manager.
1247    fn change(&self, var: &Self) -> AllocResult<Self> {
1248        self.with_manager_shared(|manager, set| {
1249            let e = Self::change_edge(manager, set, var.as_edge(manager))?;
1250            Ok(Self::from_edge(manager, e))
1251        })
1252    }
1253
1254    /// Compute the union `self ∪ rhs`
1255    ///
1256    /// Locking behavior: acquires a shared manager lock
1257    ///
1258    /// Panics if `self` and `rhs` do not belong to the same manager.
1259    fn union(&self, rhs: &Self) -> AllocResult<Self> {
1260        self.with_manager_shared(|manager, lhs| {
1261            let e = Self::union_edge(manager, lhs, rhs.as_edge(manager))?;
1262            Ok(Self::from_edge(manager, e))
1263        })
1264    }
1265
1266    /// Compute the intersection `self ∩ rhs`
1267    ///
1268    /// Locking behavior: acquires a shared manager lock
1269    ///
1270    /// Panics if `self` and `rhs` do not belong to the same manager.
1271    fn intsec(&self, rhs: &Self) -> AllocResult<Self> {
1272        self.with_manager_shared(|manager, lhs| {
1273            let e = Self::intsec_edge(manager, lhs, rhs.as_edge(manager))?;
1274            Ok(Self::from_edge(manager, e))
1275        })
1276    }
1277
1278    /// Compute the set difference `self ∖ rhs`
1279    ///
1280    /// Locking behavior: acquires a shared manager lock
1281    ///
1282    /// Panics if `self` and `rhs` do not belong to the same manager.
1283    fn diff(&self, rhs: &Self) -> AllocResult<Self> {
1284        self.with_manager_shared(|manager, lhs| {
1285            let e = Self::diff_edge(manager, lhs, rhs.as_edge(manager))?;
1286            Ok(Self::from_edge(manager, e))
1287        })
1288    }
1289
1290    /// Edge version of [`Self::empty()`]
1291    fn empty_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
1292
1293    /// Edge version of [`Self::base()`]
1294    fn base_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
1295
1296    /// Edge version of [`Self::subset0()`]
1297    fn subset0_edge<'id>(
1298        manager: &Self::Manager<'id>,
1299        set: &EdgeOfFunc<'id, Self>,
1300        var: &EdgeOfFunc<'id, Self>,
1301    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1302
1303    /// Edge version of [`Self::subset1()`]
1304    fn subset1_edge<'id>(
1305        manager: &Self::Manager<'id>,
1306        set: &EdgeOfFunc<'id, Self>,
1307        var: &EdgeOfFunc<'id, Self>,
1308    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1309
1310    /// Edge version of [`Self::change()`]
1311    fn change_edge<'id>(
1312        manager: &Self::Manager<'id>,
1313        set: &EdgeOfFunc<'id, Self>,
1314        var: &EdgeOfFunc<'id, Self>,
1315    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1316
1317    /// Compute the union `lhs ∪ rhs`, edge version
1318    fn union_edge<'id>(
1319        manager: &Self::Manager<'id>,
1320        lhs: &EdgeOfFunc<'id, Self>,
1321        rhs: &EdgeOfFunc<'id, Self>,
1322    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1323
1324    /// Compute the intersection `lhs ∩ rhs`, edge version
1325    fn intsec_edge<'id>(
1326        manager: &Self::Manager<'id>,
1327        lhs: &EdgeOfFunc<'id, Self>,
1328        rhs: &EdgeOfFunc<'id, Self>,
1329    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1330
1331    /// Compute the set difference `lhs ∖ rhs`, edge version
1332    fn diff_edge<'id>(
1333        manager: &Self::Manager<'id>,
1334        lhs: &EdgeOfFunc<'id, Self>,
1335        rhs: &EdgeOfFunc<'id, Self>,
1336    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1337}
1338
1339/// Basic trait for numbers
1340///
1341/// [`zero()`][Self::zero], [`one()`][Self::one], and [`nan()`][Self::nan] are
1342/// implemented as functions because depending on the number underlying type,
1343/// it can be hard/impossible to obtain a `const` for these values.
1344/// This trait also includes basic arithmetic methods. This is to avoid cloning
1345/// of big integers. We could also require `&Self: Add<&Self, Output = Self>`
1346/// etc., but this would lead to ugly trait bounds.
1347///
1348/// Used by [`PseudoBooleanFunction::Number`]
1349pub trait NumberBase: Clone + Eq + Hash + PartialOrd {
1350    /// Get the value 0
1351    fn zero() -> Self;
1352    /// Get the value 1
1353    fn one() -> Self;
1354
1355    /// Get the value "not a number," e.g. the result of a division 0/0.
1356    ///
1357    /// `Self::nan() == Self::nan()` should evaluate to `true`.
1358    fn nan() -> Self;
1359
1360    /// Returns `true` iff `self == Self::zero()`
1361    fn is_zero(&self) -> bool {
1362        self == &Self::zero()
1363    }
1364    /// Returns `true` iff `self == Self::one()`
1365    fn is_one(&self) -> bool {
1366        self == &Self::one()
1367    }
1368    /// Returns `true` iff `self == Self::nan()`
1369    fn is_nan(&self) -> bool {
1370        self == &Self::nan()
1371    }
1372
1373    /// Compute `self + rhs`
1374    fn add(&self, rhs: &Self) -> Self;
1375    /// Compute `self - rhs`
1376    fn sub(&self, rhs: &Self) -> Self;
1377    /// Compute `self * rhs`
1378    fn mul(&self, rhs: &Self) -> Self;
1379    /// Compute `self / rhs`
1380    fn div(&self, rhs: &Self) -> Self;
1381}
1382
1383/// Pseudo-Boolean function 𝔹ⁿ → ℝ
1384pub trait PseudoBooleanFunction: Function {
1385    /// The number type used for the functions' target set.
1386    type Number: NumberBase;
1387
1388    /// Get the constant `value`
1389    fn constant<'id>(manager: &Self::Manager<'id>, value: Self::Number) -> AllocResult<Self> {
1390        Ok(Self::from_edge(
1391            manager,
1392            Self::constant_edge(manager, value)?,
1393        ))
1394    }
1395
1396    /// Get a fresh variable, i.e. a function that is 1 if the variable is true
1397    /// and 0 otherwise. This adds a new level to a decision diagram.
1398    fn new_var<'id>(manager: &mut Self::Manager<'id>) -> AllocResult<Self>;
1399
1400    /// Point-wise addition `self + rhs`
1401    ///
1402    /// Locking behavior: acquires a shared manager lock
1403    ///
1404    /// Panics if `self` and `rhs` do not belong to the same manager.
1405    fn add(&self, rhs: &Self) -> AllocResult<Self> {
1406        self.with_manager_shared(|manager, lhs| {
1407            let e = Self::add_edge(manager, lhs, rhs.as_edge(manager))?;
1408            Ok(Self::from_edge(manager, e))
1409        })
1410    }
1411
1412    /// Point-wise subtraction `self - rhs`
1413    ///
1414    /// Locking behavior: acquires a shared manager lock
1415    ///
1416    /// Panics if `self` and `rhs` do not belong to the same manager.
1417    fn sub(&self, rhs: &Self) -> AllocResult<Self> {
1418        self.with_manager_shared(|manager, lhs| {
1419            let e = Self::sub_edge(manager, lhs, rhs.as_edge(manager))?;
1420            Ok(Self::from_edge(manager, e))
1421        })
1422    }
1423
1424    /// Point-wise multiplication `self * rhs`
1425    ///
1426    /// Locking behavior: acquires a shared manager lock
1427    ///
1428    /// Panics if `self` and `rhs` do not belong to the same manager.
1429    fn mul(&self, rhs: &Self) -> AllocResult<Self> {
1430        self.with_manager_shared(|manager, lhs| {
1431            let e = Self::mul_edge(manager, lhs, rhs.as_edge(manager))?;
1432            Ok(Self::from_edge(manager, e))
1433        })
1434    }
1435
1436    /// Point-wise division `self / rhs`
1437    ///
1438    /// Locking behavior: acquires a shared manager lock
1439    ///
1440    /// Panics if `self` and `rhs` do not belong to the same manager.
1441    fn div(&self, rhs: &Self) -> AllocResult<Self> {
1442        self.with_manager_shared(|manager, lhs| {
1443            let e = Self::div_edge(manager, lhs, rhs.as_edge(manager))?;
1444            Ok(Self::from_edge(manager, e))
1445        })
1446    }
1447
1448    /// Point-wise minimum `min(self, rhs)`
1449    ///
1450    /// Locking behavior: acquires a shared manager lock
1451    ///
1452    /// Panics if `self` and `rhs` do not belong to the same manager.
1453    fn min(&self, rhs: &Self) -> AllocResult<Self> {
1454        self.with_manager_shared(|manager, lhs| {
1455            let e = Self::min_edge(manager, lhs, rhs.as_edge(manager))?;
1456            Ok(Self::from_edge(manager, e))
1457        })
1458    }
1459
1460    /// Point-wise maximum `max(self, rhs)`
1461    ///
1462    /// Locking behavior: acquires a shared manager lock
1463    ///
1464    /// Panics if `self` and `rhs` do not belong to the same manager.
1465    fn max(&self, rhs: &Self) -> AllocResult<Self> {
1466        self.with_manager_shared(|manager, lhs| {
1467            let e = Self::max_edge(manager, lhs, rhs.as_edge(manager))?;
1468            Ok(Self::from_edge(manager, e))
1469        })
1470    }
1471
1472    /// Get the constant `value`, edge version
1473    fn constant_edge<'id>(
1474        manager: &Self::Manager<'id>,
1475        value: Self::Number,
1476    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1477
1478    /// Point-wise addition `self + rhs`, edge version
1479    fn add_edge<'id>(
1480        manager: &Self::Manager<'id>,
1481        lhs: &EdgeOfFunc<'id, Self>,
1482        rhs: &EdgeOfFunc<'id, Self>,
1483    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1484
1485    /// Point-wise subtraction `self - rhs`, edge version
1486    fn sub_edge<'id>(
1487        manager: &Self::Manager<'id>,
1488        lhs: &EdgeOfFunc<'id, Self>,
1489        rhs: &EdgeOfFunc<'id, Self>,
1490    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1491
1492    /// Point-wise multiplication `self * rhs`, edge version
1493    fn mul_edge<'id>(
1494        manager: &Self::Manager<'id>,
1495        lhs: &EdgeOfFunc<'id, Self>,
1496        rhs: &EdgeOfFunc<'id, Self>,
1497    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1498
1499    /// Point-wise division `self / rhs`, edge version
1500    fn div_edge<'id>(
1501        manager: &Self::Manager<'id>,
1502        lhs: &EdgeOfFunc<'id, Self>,
1503        rhs: &EdgeOfFunc<'id, Self>,
1504    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1505
1506    /// Point-wise minimum `min(self, rhs)`, edge version
1507    fn min_edge<'id>(
1508        manager: &Self::Manager<'id>,
1509        lhs: &EdgeOfFunc<'id, Self>,
1510        rhs: &EdgeOfFunc<'id, Self>,
1511    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1512
1513    /// Point-wise maximum `max(self, rhs)`, edge version
1514    fn max_edge<'id>(
1515        manager: &Self::Manager<'id>,
1516        lhs: &EdgeOfFunc<'id, Self>,
1517        rhs: &EdgeOfFunc<'id, Self>,
1518    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1519}
1520
1521/// Function of three valued logic
1522pub trait TVLFunction: Function {
1523    /// Get the always false function `⊥`
1524    fn f<'id>(manager: &Self::Manager<'id>) -> Self {
1525        Self::from_edge(manager, Self::f_edge(manager))
1526    }
1527    /// Get the always true function `⊤`
1528    fn t<'id>(manager: &Self::Manager<'id>) -> Self {
1529        Self::from_edge(manager, Self::t_edge(manager))
1530    }
1531    /// Get the "unknown" function `U`
1532    fn u<'id>(manager: &Self::Manager<'id>) -> Self {
1533        Self::from_edge(manager, Self::t_edge(manager))
1534    }
1535
1536    /// Get the cofactors `(f_true, f_unknown, f_false)` of `self`
1537    ///
1538    /// Let f(x₀, …, xₙ) be represented by `self`, where x₀ is (currently) the
1539    /// top-most variable. Then f<sub>true</sub>(x₁, …, xₙ) = f(⊤, x₁, …, xₙ)
1540    /// and f<sub>false</sub>(x₁, …, xₙ) = f(⊥, x₁, …, xₙ).
1541    ///
1542    /// Note that the domain of f is 𝔹ⁿ⁺¹ while the domain of f<sub>true</sub>
1543    /// and f<sub>false</sub> is 𝔹ⁿ.
1544    ///
1545    /// Returns `None` iff `self` references a terminal node. If you only need
1546    /// `f_true`, `f_unknown`, or `f_false`, [`Self::cofactor_true`],
1547    /// [`Self::cofactor_unknown`], or [`Self::cofactor_false`] are slightly
1548    /// more efficient.
1549    ///
1550    /// Locking behavior: acquires the manager's lock for shared access.
1551    fn cofactors(&self) -> Option<(Self, Self, Self)> {
1552        self.with_manager_shared(|manager, f| {
1553            let (ft, fu, ff) = Self::cofactors_edge(manager, f)?;
1554            Some((
1555                Self::from_edge_ref(manager, &ft),
1556                Self::from_edge_ref(manager, &fu),
1557                Self::from_edge_ref(manager, &ff),
1558            ))
1559        })
1560    }
1561
1562    /// Get the cofactor `f_true` of `self`
1563    ///
1564    /// This method is slightly more efficient than [`Self::cofactors`] in case
1565    /// `f_unknown` and `f_false` are not needed.
1566    ///
1567    /// For a more detailed description, see [`Self::cofactors`].
1568    ///
1569    /// Returns `None` iff `self` references a terminal node.
1570    ///
1571    /// Locking behavior: acquires the manager's lock for shared access.
1572    fn cofactor_true(&self) -> Option<Self> {
1573        self.with_manager_shared(|manager, f| {
1574            let (ft, _, _) = Self::cofactors_edge(manager, f)?;
1575            Some(Self::from_edge_ref(manager, &ft))
1576        })
1577    }
1578    /// Get the cofactor `f_unknown` of `self`
1579    ///
1580    /// This method is slightly more efficient than [`Self::cofactors`] in case
1581    /// `f_true` and `f_false` are not needed.
1582    ///
1583    /// For a more detailed description, see [`Self::cofactors`].
1584    ///
1585    /// Returns `None` iff `self` references a terminal node.
1586    ///
1587    /// Locking behavior: acquires the manager's lock for shared access.
1588    fn cofactor_unknown(&self) -> Option<Self> {
1589        self.with_manager_shared(|manager, f| {
1590            let (_, fu, _) = Self::cofactors_edge(manager, f)?;
1591            Some(Self::from_edge_ref(manager, &fu))
1592        })
1593    }
1594    /// Get the cofactor `f_false` of `self`
1595    ///
1596    /// This method is slightly more efficient than [`Self::cofactors`] in case
1597    /// `f_true` and `f_unknown` are not needed.
1598    ///
1599    /// For a more detailed description, see [`Self::cofactors`].
1600    ///
1601    /// Returns `None` iff `self` references a terminal node.
1602    ///
1603    /// Locking behavior: acquires the manager's lock for shared access.
1604    fn cofactor_false(&self) -> Option<Self> {
1605        self.with_manager_shared(|manager, f| {
1606            let (_, _, ff) = Self::cofactors_edge(manager, f)?;
1607            Some(Self::from_edge_ref(manager, &ff))
1608        })
1609    }
1610
1611    /// Get a fresh variable, i.e. a function that is true if the variable is
1612    /// true, false if the variable is false, and undefined otherwise. This adds
1613    /// a new level to a decision diagram.
1614    fn new_var<'id>(manager: &mut Self::Manager<'id>) -> AllocResult<Self>;
1615
1616    /// Compute the negation `¬self`
1617    ///
1618    /// Locking behavior: acquires the manager's lock for shared access.
1619    fn not(&self) -> AllocResult<Self> {
1620        self.with_manager_shared(|manager, edge| {
1621            Ok(Self::from_edge(manager, Self::not_edge(manager, edge)?))
1622        })
1623    }
1624    /// Compute the negation `¬self`, owned version
1625    ///
1626    /// Compared to [`Self::not()`], this method does not need to clone the
1627    /// function, so when the implementation is using (e.g.) complemented edges,
1628    /// this might be a little bit faster than [`Self::not()`].
1629    ///
1630    /// Locking behavior: acquires the manager's lock for shared access.
1631    fn not_owned(self) -> AllocResult<Self> {
1632        self.not()
1633    }
1634    /// Compute the conjunction `self ∧ rhs`
1635    ///
1636    /// Locking behavior: acquires the manager's lock for shared access.
1637    ///
1638    /// Panics if `self` and `rhs` don't belong to the same manager.
1639    fn and(&self, rhs: &Self) -> AllocResult<Self> {
1640        self.with_manager_shared(|manager, lhs| {
1641            let e = Self::and_edge(manager, lhs, rhs.as_edge(manager))?;
1642            Ok(Self::from_edge(manager, e))
1643        })
1644    }
1645    /// Compute the disjunction `self ∨ rhs`
1646    ///
1647    /// Locking behavior: acquires the manager's lock for shared access.
1648    ///
1649    /// Panics if `self` and `rhs` don't belong to the same manager.
1650    fn or(&self, rhs: &Self) -> AllocResult<Self> {
1651        self.with_manager_shared(|manager, lhs| {
1652            let e = Self::or_edge(manager, lhs, rhs.as_edge(manager))?;
1653            Ok(Self::from_edge(manager, e))
1654        })
1655    }
1656    /// Compute the negated conjunction `self ⊼ rhs`
1657    ///
1658    /// Locking behavior: acquires the manager's lock for shared access.
1659    ///
1660    /// Panics if `self` and `rhs` don't belong to the same manager.
1661    fn nand(&self, rhs: &Self) -> AllocResult<Self> {
1662        self.with_manager_shared(|manager, lhs| {
1663            let e = Self::nand_edge(manager, lhs, rhs.as_edge(manager))?;
1664            Ok(Self::from_edge(manager, e))
1665        })
1666    }
1667    /// Compute the negated disjunction `self ⊽ rhs`
1668    ///
1669    /// Locking behavior: acquires the manager's lock for shared access.
1670    ///
1671    /// Panics if `self` and `rhs` don't belong to the same manager.
1672    fn nor(&self, rhs: &Self) -> AllocResult<Self> {
1673        self.with_manager_shared(|manager, lhs| {
1674            let e = Self::nor_edge(manager, lhs, rhs.as_edge(manager))?;
1675            Ok(Self::from_edge(manager, e))
1676        })
1677    }
1678    /// Compute the exclusive disjunction `self ⊕ rhs`
1679    ///
1680    /// Locking behavior: acquires the manager's lock for shared access.
1681    ///
1682    /// Panics if `self` and `rhs` don't belong to the same manager.
1683    fn xor(&self, rhs: &Self) -> AllocResult<Self> {
1684        self.with_manager_shared(|manager, lhs| {
1685            let e = Self::xor_edge(manager, lhs, rhs.as_edge(manager))?;
1686            Ok(Self::from_edge(manager, e))
1687        })
1688    }
1689    /// Compute the equivalence `self ↔ rhs`
1690    ///
1691    /// Locking behavior: acquires the manager's lock for shared access.
1692    ///
1693    /// Panics if `self` and `rhs` don't belong to the same manager.
1694    fn equiv(&self, rhs: &Self) -> AllocResult<Self> {
1695        self.with_manager_shared(|manager, lhs| {
1696            let e = Self::equiv_edge(manager, lhs, rhs.as_edge(manager))?;
1697            Ok(Self::from_edge(manager, e))
1698        })
1699    }
1700    /// Compute the implication `self → rhs` (or `self ≤ rhs`)
1701    ///
1702    /// Locking behavior: acquires the manager's lock for shared access.
1703    ///
1704    /// Panics if `self` and `rhs` don't belong to the same manager.
1705    fn imp(&self, rhs: &Self) -> AllocResult<Self> {
1706        self.with_manager_shared(|manager, lhs| {
1707            let e = Self::imp_edge(manager, lhs, rhs.as_edge(manager))?;
1708            Ok(Self::from_edge(manager, e))
1709        })
1710    }
1711    /// Compute the strict implication `self < rhs`
1712    ///
1713    /// Locking behavior: acquires the manager's lock for shared access.
1714    ///
1715    /// Panics if `self` and `rhs` don't belong to the same manager.
1716    fn imp_strict(&self, rhs: &Self) -> AllocResult<Self> {
1717        self.with_manager_shared(|manager, lhs| {
1718            let e = Self::imp_strict_edge(manager, lhs, rhs.as_edge(manager))?;
1719            Ok(Self::from_edge(manager, e))
1720        })
1721    }
1722
1723    /// Get the always false function `⊥` as edge
1724    fn f_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
1725    /// Get the always true function `⊤` as edge
1726    fn t_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
1727    /// Get the "unknown" function `U` as edge
1728    fn u_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
1729
1730    /// Get the cofactors `(f_true, f_unknown, f_false)` of `f`, edge version
1731    ///
1732    /// Returns `None` iff `f` references a terminal node. For more details on
1733    /// the semantics of `f_true` and `f_false`, see [`Self::cofactors`].
1734    #[inline]
1735    #[allow(clippy::type_complexity)]
1736    fn cofactors_edge<'a, 'id>(
1737        manager: &'a Self::Manager<'id>,
1738        f: &'a EdgeOfFunc<'id, Self>,
1739    ) -> Option<(
1740        Borrowed<'a, EdgeOfFunc<'id, Self>>,
1741        Borrowed<'a, EdgeOfFunc<'id, Self>>,
1742        Borrowed<'a, EdgeOfFunc<'id, Self>>,
1743    )> {
1744        if let Node::Inner(node) = manager.get_node(f) {
1745            Some(Self::cofactors_node(f.tag(), node))
1746        } else {
1747            None
1748        }
1749    }
1750
1751    /// Get the cofactors `(f_true, f_unknown, f_false)` of `node`, assuming an
1752    /// incoming edge with `EdgeTag`
1753    ///
1754    /// Returns `None` iff `f` references a terminal node. For more details on
1755    /// the semantics of `f_true` and `f_false`, see [`Self::cofactors`].
1756    ///
1757    /// Implementation note: The default implementation assumes that
1758    /// [cofactor 0][DiagramRules::cofactor] corresponds to `f_true`,
1759    /// [cofactor 1][DiagramRules::cofactor] corresponds to `f_unknown`, and
1760    /// [cofactor 2][DiagramRules::cofactor] corresponds to `f_false`.
1761    #[inline]
1762    #[allow(clippy::type_complexity)]
1763    fn cofactors_node<'a, 'id>(
1764        tag: ETagOfFunc<'id, Self>,
1765        node: &'a INodeOfFunc<'id, Self>,
1766    ) -> (
1767        Borrowed<'a, EdgeOfFunc<'id, Self>>,
1768        Borrowed<'a, EdgeOfFunc<'id, Self>>,
1769        Borrowed<'a, EdgeOfFunc<'id, Self>>,
1770    ) {
1771        let cofactor = <<Self::Manager<'id> as Manager>::Rules as DiagramRules<_, _, _>>::cofactor;
1772        (
1773            cofactor(tag, node, 0),
1774            cofactor(tag, node, 1),
1775            cofactor(tag, node, 2),
1776        )
1777    }
1778
1779    /// Compute the negation `¬edge`, edge version
1780    #[must_use]
1781    fn not_edge<'id>(
1782        manager: &Self::Manager<'id>,
1783        edge: &EdgeOfFunc<'id, Self>,
1784    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1785
1786    /// Compute the negation `¬edge`, owned edge version
1787    ///
1788    /// Compared to [`Self::not_edge()`], this method does not need to clone the
1789    /// edge, so when the implementation is using (e.g.) complemented edges,
1790    /// this might be a little bit faster than [`Self::not_edge()`].
1791    #[must_use]
1792    fn not_edge_owned<'id>(
1793        manager: &Self::Manager<'id>,
1794        edge: EdgeOfFunc<'id, Self>,
1795    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1796        Self::not_edge(manager, &edge)
1797    }
1798
1799    /// Compute the conjunction `lhs ∧ rhs`, edge version
1800    #[must_use]
1801    fn and_edge<'id>(
1802        manager: &Self::Manager<'id>,
1803        lhs: &EdgeOfFunc<'id, Self>,
1804        rhs: &EdgeOfFunc<'id, Self>,
1805    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1806    /// Compute the disjunction `lhs ∨ rhs`, edge version
1807    #[must_use]
1808    fn or_edge<'id>(
1809        manager: &Self::Manager<'id>,
1810        lhs: &EdgeOfFunc<'id, Self>,
1811        rhs: &EdgeOfFunc<'id, Self>,
1812    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1813    /// Compute the negated conjunction `lhs ⊼ rhs`, edge version
1814    #[must_use]
1815    fn nand_edge<'id>(
1816        manager: &Self::Manager<'id>,
1817        lhs: &EdgeOfFunc<'id, Self>,
1818        rhs: &EdgeOfFunc<'id, Self>,
1819    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1820    /// Compute the negated disjunction `lhs ⊽ rhs`, edge version
1821    #[must_use]
1822    fn nor_edge<'id>(
1823        manager: &Self::Manager<'id>,
1824        lhs: &EdgeOfFunc<'id, Self>,
1825        rhs: &EdgeOfFunc<'id, Self>,
1826    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1827    /// Compute the exclusive disjunction `lhs ⊕ rhs`, edge version
1828    #[must_use]
1829    fn xor_edge<'id>(
1830        manager: &Self::Manager<'id>,
1831        lhs: &EdgeOfFunc<'id, Self>,
1832        rhs: &EdgeOfFunc<'id, Self>,
1833    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1834    /// Compute the equivalence `lhs ↔ rhs`, edge version
1835    #[must_use]
1836    fn equiv_edge<'id>(
1837        manager: &Self::Manager<'id>,
1838        lhs: &EdgeOfFunc<'id, Self>,
1839        rhs: &EdgeOfFunc<'id, Self>,
1840    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1841    /// Compute the implication `lhs → rhs`, edge version
1842    #[must_use]
1843    fn imp_edge<'id>(
1844        manager: &Self::Manager<'id>,
1845        lhs: &EdgeOfFunc<'id, Self>,
1846        rhs: &EdgeOfFunc<'id, Self>,
1847    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1848    /// Compute the strict implication `lhs < rhs`, edge version
1849    #[must_use]
1850    fn imp_strict_edge<'id>(
1851        manager: &Self::Manager<'id>,
1852        lhs: &EdgeOfFunc<'id, Self>,
1853        rhs: &EdgeOfFunc<'id, Self>,
1854    ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1855
1856    /// Compute `if self { then_case } else { else_case }`
1857    ///
1858    /// This is equivalent to `(self ∧ then_case) ∨ (¬self ∧ else_case)` but
1859    /// possibly more efficient than computing all the
1860    /// conjunctions/disjunctions.
1861    ///
1862    /// Locking behavior: acquires the manager's lock for shared access.
1863    ///
1864    /// Panics if `self`, `then_case`, and `else_case` don't belong to the same
1865    /// manager.
1866    fn ite(&self, then_case: &Self, else_case: &Self) -> AllocResult<Self> {
1867        self.with_manager_shared(|manager, if_edge| {
1868            let then_edge = then_case.as_edge(manager);
1869            let else_edge = else_case.as_edge(manager);
1870            let res = Self::ite_edge(manager, if_edge, then_edge, else_edge)?;
1871            Ok(Self::from_edge(manager, res))
1872        })
1873    }
1874
1875    /// Compute `if if_edge { then_edge } else { else_edge }` (edge version)
1876    ///
1877    /// This is equivalent to `(self ∧ then_case) ∨ (¬self ∧ else_case)` but
1878    /// possibly more efficient than computing all the
1879    /// conjunctions/disjunctions.
1880    #[must_use]
1881    fn ite_edge<'id>(
1882        manager: &Self::Manager<'id>,
1883        if_edge: &EdgeOfFunc<'id, Self>,
1884        then_edge: &EdgeOfFunc<'id, Self>,
1885        else_edge: &EdgeOfFunc<'id, Self>,
1886    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1887        let f = EdgeDropGuard::new(manager, Self::and_edge(manager, if_edge, then_edge)?);
1888        let g = EdgeDropGuard::new(manager, Self::imp_strict_edge(manager, if_edge, else_edge)?);
1889        Self::or_edge(manager, &*f, &*g)
1890    }
1891}