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 exists(&self, vars: &Self) -> AllocResult<Self> {
939 self.with_manager_shared(|manager, root| {
940 let e = Self::exists_edge(manager, root, vars.as_edge(manager))?;
941 Ok(Self::from_edge(manager, e))
942 })
943 }
944 /// Deprecated alias for [`Self::exists()`]
945 #[deprecated]
946 fn exist(&self, vars: &Self) -> AllocResult<Self> {
947 self.exists(vars)
948 }
949
950 /// Compute the unique quantification over `vars`
951 ///
952 /// `vars` is a set of variables, which in turn is just the conjunction of
953 /// the variables. This operation removes all occurrences of the variables
954 /// by unique quantification. Unique quantification `∃!x. f(…, x, …)` of a
955 /// Boolean function `f(…, x, …)` over a single variable `x` is
956 /// `f(…, 0, …) ⊕ f(…, 1, …)`.
957 ///
958 /// Unique quantification is also known as the
959 /// [Boolean difference](https://en.wikipedia.org/wiki/Boole%27s_expansion_theorem#Operations_with_cofactors)
960 /// or
961 /// [Boolean derivative](https://en.wikipedia.org/wiki/Boolean_differential_calculus).
962 ///
963 /// Locking behavior: acquires the manager's lock for shared access.
964 ///
965 /// Panics if `self` and `vars` don't belong to the same manager.
966 fn unique(&self, vars: &Self) -> AllocResult<Self> {
967 self.with_manager_shared(|manager, root| {
968 let e = Self::unique_edge(manager, root, vars.as_edge(manager))?;
969 Ok(Self::from_edge(manager, e))
970 })
971 }
972
973 /// Combined application of `op` and quantification `∀x. self <op> rhs`,
974 /// where `<op>` is any of the operations from [`BooleanOperator`]
975 ///
976 /// See also [`Self::forall()`] and the trait [`BooleanFunction`] for more
977 /// details.
978 ///
979 /// Locking behavior: acquires the manager's lock for shared access.
980 ///
981 /// Panics if `self` and `rhs` and `vars` don't belong to the same manager.
982 fn apply_forall(&self, op: BooleanOperator, rhs: &Self, vars: &Self) -> AllocResult<Self> {
983 self.with_manager_shared(|manager, root| {
984 let e = Self::apply_forall_edge(
985 manager,
986 op,
987 root,
988 rhs.as_edge(manager),
989 vars.as_edge(manager),
990 )?;
991 Ok(Self::from_edge(manager, e))
992 })
993 }
994
995 /// Combined application of `op` and quantification `∃x. self <op> rhs`,
996 /// where `<op>` is any of the operations from [`BooleanOperator`]
997 ///
998 /// See also [`Self::exist()`] and the trait [`BooleanFunction`] for more
999 /// details.
1000 ///
1001 /// Panics if `self` and `rhs` and `vars` don't belong to the same manager.
1002 fn apply_exists(&self, op: BooleanOperator, rhs: &Self, vars: &Self) -> AllocResult<Self> {
1003 self.with_manager_shared(|manager, root| {
1004 let e = Self::apply_exists_edge(
1005 manager,
1006 op,
1007 root,
1008 rhs.as_edge(manager),
1009 vars.as_edge(manager),
1010 )?;
1011 Ok(Self::from_edge(manager, e))
1012 })
1013 }
1014 /// Deprecated alias for [`Self::apply_exists()`]
1015 #[deprecated]
1016 #[must_use]
1017 fn apply_exist(&self, op: BooleanOperator, rhs: &Self, vars: &Self) -> AllocResult<Self> {
1018 self.apply_exists(op, rhs, vars)
1019 }
1020
1021 /// Combined application of `op` and quantification `∃!x. self <op> rhs`,
1022 /// where `<op>` is any of the operations from [`BooleanOperator`]
1023 ///
1024 /// See also [`Self::unique()`] and the trait [`BooleanFunction`] for more
1025 /// details.
1026 ///
1027 /// Panics if `self` and `rhs` and `vars` don't belong to the same manager.
1028 fn apply_unique(&self, op: BooleanOperator, rhs: &Self, vars: &Self) -> AllocResult<Self> {
1029 self.with_manager_shared(|manager, root| {
1030 let e = Self::apply_unique_edge(
1031 manager,
1032 op,
1033 root,
1034 rhs.as_edge(manager),
1035 vars.as_edge(manager),
1036 )?;
1037 Ok(Self::from_edge(manager, e))
1038 })
1039 }
1040
1041 /// Restrict a set of `vars` to constant values, edge version
1042 ///
1043 /// See [`Self::restrict()`] for more details.
1044 #[must_use]
1045 fn restrict_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 universal quantification of `root` over `vars`, edge
1052 /// version
1053 ///
1054 /// See [`Self::forall()`] for more details.
1055 #[must_use]
1056 fn forall_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 existential quantification of `root` over `vars`, edge
1063 /// version
1064 ///
1065 /// See [`Self::exists()`] for more details.
1066 #[must_use]
1067 fn exists_edge<'id>(
1068 manager: &Self::Manager<'id>,
1069 root: &EdgeOfFunc<'id, Self>,
1070 vars: &EdgeOfFunc<'id, Self>,
1071 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1072 /// Deprecated alias for [`Self::exists_edge()`]
1073 #[must_use]
1074 #[deprecated]
1075 fn exist_edge<'id>(
1076 manager: &Self::Manager<'id>,
1077 root: &EdgeOfFunc<'id, Self>,
1078 vars: &EdgeOfFunc<'id, Self>,
1079 ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1080 Self::exists_edge(manager, root, vars)
1081 }
1082
1083 /// Compute the unique quantification of `root` over `vars`, edge version
1084 ///
1085 /// See [`Self::unique()`] for more details.
1086 #[must_use]
1087 fn unique_edge<'id>(
1088 manager: &Self::Manager<'id>,
1089 root: &EdgeOfFunc<'id, Self>,
1090 vars: &EdgeOfFunc<'id, Self>,
1091 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1092
1093 /// Combined application of `op` and forall quantification, edge version
1094 ///
1095 /// See [`Self::apply_forall()`] for more details.
1096 #[must_use]
1097 fn apply_forall_edge<'id>(
1098 manager: &Self::Manager<'id>,
1099 op: BooleanOperator,
1100 lhs: &EdgeOfFunc<'id, Self>,
1101 rhs: &EdgeOfFunc<'id, Self>,
1102 vars: &EdgeOfFunc<'id, Self>,
1103 ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1104 // Naive default implementation
1105 use BooleanOperator::*;
1106 let inner = EdgeDropGuard::new(
1107 manager,
1108 match op {
1109 And => Self::and_edge(manager, lhs, rhs),
1110 Or => Self::or_edge(manager, lhs, rhs),
1111 Xor => Self::xor_edge(manager, lhs, rhs),
1112 Equiv => Self::equiv_edge(manager, lhs, rhs),
1113 Nand => Self::nand_edge(manager, lhs, rhs),
1114 Nor => Self::nor_edge(manager, lhs, rhs),
1115 Imp => Self::imp_edge(manager, lhs, rhs),
1116 ImpStrict => Self::imp_strict_edge(manager, lhs, rhs),
1117 }?,
1118 );
1119
1120 Self::forall_edge(manager, &inner, vars)
1121 }
1122
1123 /// Combined application of `op` and existential quantification, edge
1124 /// version
1125 ///
1126 /// See [`Self::apply_exist()`] for more details.
1127 #[must_use]
1128 fn apply_exists_edge<'id>(
1129 manager: &Self::Manager<'id>,
1130 op: BooleanOperator,
1131 lhs: &EdgeOfFunc<'id, Self>,
1132 rhs: &EdgeOfFunc<'id, Self>,
1133 vars: &EdgeOfFunc<'id, Self>,
1134 ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1135 // Naive default implementation
1136 use BooleanOperator::*;
1137 let inner = EdgeDropGuard::new(
1138 manager,
1139 match op {
1140 And => Self::and_edge(manager, lhs, rhs),
1141 Or => Self::or_edge(manager, lhs, rhs),
1142 Xor => Self::xor_edge(manager, lhs, rhs),
1143 Equiv => Self::equiv_edge(manager, lhs, rhs),
1144 Nand => Self::nand_edge(manager, lhs, rhs),
1145 Nor => Self::nor_edge(manager, lhs, rhs),
1146 Imp => Self::imp_edge(manager, lhs, rhs),
1147 ImpStrict => Self::imp_strict_edge(manager, lhs, rhs),
1148 }?,
1149 );
1150
1151 Self::exists_edge(manager, &inner, vars)
1152 }
1153 /// Deprecated alias for [`Self::apply_exists_edge()`]
1154 #[deprecated]
1155 #[must_use]
1156 fn apply_exist_edge<'id>(
1157 manager: &Self::Manager<'id>,
1158 op: BooleanOperator,
1159 lhs: &EdgeOfFunc<'id, Self>,
1160 rhs: &EdgeOfFunc<'id, Self>,
1161 vars: &EdgeOfFunc<'id, Self>,
1162 ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1163 Self::apply_exists_edge(manager, op, lhs, rhs, vars)
1164 }
1165
1166 /// Combined application of `op` and unique quantification, edge version
1167 ///
1168 /// See [`Self::apply_unique()`] for more details.
1169 #[must_use]
1170 fn apply_unique_edge<'id>(
1171 manager: &Self::Manager<'id>,
1172 op: BooleanOperator,
1173 lhs: &EdgeOfFunc<'id, Self>,
1174 rhs: &EdgeOfFunc<'id, Self>,
1175 vars: &EdgeOfFunc<'id, Self>,
1176 ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1177 // Naive default implementation
1178 use BooleanOperator::*;
1179 let inner = EdgeDropGuard::new(
1180 manager,
1181 match op {
1182 And => Self::and_edge(manager, lhs, rhs),
1183 Or => Self::or_edge(manager, lhs, rhs),
1184 Xor => Self::xor_edge(manager, lhs, rhs),
1185 Equiv => Self::equiv_edge(manager, lhs, rhs),
1186 Nand => Self::nand_edge(manager, lhs, rhs),
1187 Nor => Self::nor_edge(manager, lhs, rhs),
1188 Imp => Self::imp_edge(manager, lhs, rhs),
1189 ImpStrict => Self::imp_strict_edge(manager, lhs, rhs),
1190 }?,
1191 );
1192
1193 Self::unique_edge(manager, &inner, vars)
1194 }
1195}
1196
1197/// Set of Boolean vectors
1198///
1199/// A Boolean function f: 𝔹ⁿ → 𝔹 may also be regarded as a set S ∈ 𝒫(𝔹ⁿ), where
1200/// S = {v ∈ 𝔹ⁿ | f(v) = 1}. f is also called the characteristic function of S.
1201/// We can even view a Boolean vector as a subset of some "Universe" U, so we
1202/// also have S ∈ 𝒫(𝒫(U)). For example, let U = {a, b, c}. The function a is
1203/// the set of all sets containing a, {a, ab, abc, ac} (for the sake of
1204/// readability, we write ab for the set {a, b}). Conversely, the set {a} is the
1205/// function a ∧ ¬b ∧ ¬c.
1206///
1207/// Counting the number of elements in a `BoolVecSet` is equivalent to counting
1208/// the number of satisfying assignments of its characteristic function. Hence,
1209/// you may use [`BooleanFunction::sat_count()`] for this task.
1210///
1211/// The functions of this trait can be implemented efficiently for ZBDDs.
1212///
1213/// As a user of this trait, you are probably most interested in methods like
1214/// [`Self::union()`], [`Self::intsec()`], and [`Self::diff()`]. As an
1215/// implementor, it suffices to implement the functions operating on edges.
1216pub trait BooleanVecSet: Function {
1217 /// Add a new variable to the manager and get the corresponding singleton
1218 /// set
1219 ///
1220 /// This adds a new level to the decision diagram.
1221 fn new_singleton<'id>(manager: &mut Self::Manager<'id>) -> AllocResult<Self>;
1222
1223 /// Get the empty set ∅
1224 ///
1225 /// This corresponds to the Boolean function ⊥.
1226 fn empty<'id>(manager: &Self::Manager<'id>) -> Self {
1227 Self::from_edge(manager, Self::empty_edge(manager))
1228 }
1229
1230 /// Get the set {∅}
1231 ///
1232 /// This corresponds to the Boolean function ¬x₁ ∧ … ∧ ¬xₙ
1233 fn base<'id>(manager: &Self::Manager<'id>) -> Self {
1234 Self::from_edge(manager, Self::base_edge(manager))
1235 }
1236
1237 /// Get the subset of `self` not containing `var`, formally
1238 /// `{s ∈ self | var ∉ s}`
1239 ///
1240 /// `var` must be a singleton set, otherwise the result is unspecified.
1241 /// Ideally, the implementation panics.
1242 ///
1243 /// Locking behavior: acquires a shared manager lock
1244 ///
1245 /// Panics if `self` and `var` do not belong to the same manager.
1246 fn subset0(&self, var: &Self) -> AllocResult<Self> {
1247 self.with_manager_shared(|manager, set| {
1248 let e = Self::subset0_edge(manager, set, var.as_edge(manager))?;
1249 Ok(Self::from_edge(manager, e))
1250 })
1251 }
1252
1253 /// Get the subset of `self` containing `var` with `var` removed afterwards,
1254 /// formally `{s ∖ {var} | s ∈ self ∧ var ∈ s}`
1255 ///
1256 /// `var` must be a singleton set, otherwise the result is unspecified.
1257 /// Ideally, the implementation panics.
1258 ///
1259 /// Locking behavior: acquires a shared manager lock
1260 ///
1261 /// Panics if `self` and `var` do not belong to the same manager.
1262 fn subset1(&self, var: &Self) -> AllocResult<Self> {
1263 self.with_manager_shared(|manager, set| {
1264 let e = Self::subset1_edge(manager, set, var.as_edge(manager))?;
1265 Ok(Self::from_edge(manager, e))
1266 })
1267 }
1268
1269 /// Swap [`subset0`][Self::subset0] and [`subset1`][Self::subset1] with
1270 /// respect to `var`, formally
1271 /// `{s ∪ {var} | s ∈ self ∧ var ∉ s} ∪ {s ∖ {var} | s ∈ self ∧ var ∈ s}`
1272 ///
1273 /// `var` must be a singleton set, otherwise the result is unspecified.
1274 /// Ideally, the implementation panics.
1275 ///
1276 /// Locking behavior: acquires a shared manager lock
1277 ///
1278 /// Panics if `self` and `var` do not belong to the same manager.
1279 fn change(&self, var: &Self) -> AllocResult<Self> {
1280 self.with_manager_shared(|manager, set| {
1281 let e = Self::change_edge(manager, set, var.as_edge(manager))?;
1282 Ok(Self::from_edge(manager, e))
1283 })
1284 }
1285
1286 /// Compute the union `self ∪ rhs`
1287 ///
1288 /// Locking behavior: acquires a shared manager lock
1289 ///
1290 /// Panics if `self` and `rhs` do not belong to the same manager.
1291 fn union(&self, rhs: &Self) -> AllocResult<Self> {
1292 self.with_manager_shared(|manager, lhs| {
1293 let e = Self::union_edge(manager, lhs, rhs.as_edge(manager))?;
1294 Ok(Self::from_edge(manager, e))
1295 })
1296 }
1297
1298 /// Compute the intersection `self ∩ rhs`
1299 ///
1300 /// Locking behavior: acquires a shared manager lock
1301 ///
1302 /// Panics if `self` and `rhs` do not belong to the same manager.
1303 fn intsec(&self, rhs: &Self) -> AllocResult<Self> {
1304 self.with_manager_shared(|manager, lhs| {
1305 let e = Self::intsec_edge(manager, lhs, rhs.as_edge(manager))?;
1306 Ok(Self::from_edge(manager, e))
1307 })
1308 }
1309
1310 /// Compute the set difference `self ∖ rhs`
1311 ///
1312 /// Locking behavior: acquires a shared manager lock
1313 ///
1314 /// Panics if `self` and `rhs` do not belong to the same manager.
1315 fn diff(&self, rhs: &Self) -> AllocResult<Self> {
1316 self.with_manager_shared(|manager, lhs| {
1317 let e = Self::diff_edge(manager, lhs, rhs.as_edge(manager))?;
1318 Ok(Self::from_edge(manager, e))
1319 })
1320 }
1321
1322 /// Edge version of [`Self::empty()`]
1323 fn empty_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
1324
1325 /// Edge version of [`Self::base()`]
1326 fn base_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
1327
1328 /// Edge version of [`Self::subset0()`]
1329 fn subset0_edge<'id>(
1330 manager: &Self::Manager<'id>,
1331 set: &EdgeOfFunc<'id, Self>,
1332 var: &EdgeOfFunc<'id, Self>,
1333 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1334
1335 /// Edge version of [`Self::subset1()`]
1336 fn subset1_edge<'id>(
1337 manager: &Self::Manager<'id>,
1338 set: &EdgeOfFunc<'id, Self>,
1339 var: &EdgeOfFunc<'id, Self>,
1340 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1341
1342 /// Edge version of [`Self::change()`]
1343 fn change_edge<'id>(
1344 manager: &Self::Manager<'id>,
1345 set: &EdgeOfFunc<'id, Self>,
1346 var: &EdgeOfFunc<'id, Self>,
1347 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1348
1349 /// Compute the union `lhs ∪ rhs`, edge version
1350 fn union_edge<'id>(
1351 manager: &Self::Manager<'id>,
1352 lhs: &EdgeOfFunc<'id, Self>,
1353 rhs: &EdgeOfFunc<'id, Self>,
1354 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1355
1356 /// Compute the intersection `lhs ∩ rhs`, edge version
1357 fn intsec_edge<'id>(
1358 manager: &Self::Manager<'id>,
1359 lhs: &EdgeOfFunc<'id, Self>,
1360 rhs: &EdgeOfFunc<'id, Self>,
1361 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1362
1363 /// Compute the set difference `lhs ∖ rhs`, edge version
1364 fn diff_edge<'id>(
1365 manager: &Self::Manager<'id>,
1366 lhs: &EdgeOfFunc<'id, Self>,
1367 rhs: &EdgeOfFunc<'id, Self>,
1368 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1369}
1370
1371/// Basic trait for numbers
1372///
1373/// [`zero()`][Self::zero], [`one()`][Self::one], and [`nan()`][Self::nan] are
1374/// implemented as functions because depending on the number underlying type,
1375/// it can be hard/impossible to obtain a `const` for these values.
1376/// This trait also includes basic arithmetic methods. This is to avoid cloning
1377/// of big integers. We could also require `&Self: Add<&Self, Output = Self>`
1378/// etc., but this would lead to ugly trait bounds.
1379///
1380/// Used by [`PseudoBooleanFunction::Number`]
1381pub trait NumberBase: Clone + Eq + Hash + PartialOrd {
1382 /// Get the value 0
1383 fn zero() -> Self;
1384 /// Get the value 1
1385 fn one() -> Self;
1386
1387 /// Get the value "not a number," e.g. the result of a division 0/0.
1388 ///
1389 /// `Self::nan() == Self::nan()` should evaluate to `true`.
1390 fn nan() -> Self;
1391
1392 /// Returns `true` iff `self == Self::zero()`
1393 fn is_zero(&self) -> bool {
1394 self == &Self::zero()
1395 }
1396 /// Returns `true` iff `self == Self::one()`
1397 fn is_one(&self) -> bool {
1398 self == &Self::one()
1399 }
1400 /// Returns `true` iff `self == Self::nan()`
1401 fn is_nan(&self) -> bool {
1402 self == &Self::nan()
1403 }
1404
1405 /// Compute `self + rhs`
1406 fn add(&self, rhs: &Self) -> Self;
1407 /// Compute `self - rhs`
1408 fn sub(&self, rhs: &Self) -> Self;
1409 /// Compute `self * rhs`
1410 fn mul(&self, rhs: &Self) -> Self;
1411 /// Compute `self / rhs`
1412 fn div(&self, rhs: &Self) -> Self;
1413}
1414
1415/// Pseudo-Boolean function 𝔹ⁿ → ℝ
1416pub trait PseudoBooleanFunction: Function {
1417 /// The number type used for the functions' target set.
1418 type Number: NumberBase;
1419
1420 /// Get the constant `value`
1421 fn constant<'id>(manager: &Self::Manager<'id>, value: Self::Number) -> AllocResult<Self> {
1422 Ok(Self::from_edge(
1423 manager,
1424 Self::constant_edge(manager, value)?,
1425 ))
1426 }
1427
1428 /// Get a fresh variable, i.e. a function that is 1 if the variable is true
1429 /// and 0 otherwise. This adds a new level to a decision diagram.
1430 fn new_var<'id>(manager: &mut Self::Manager<'id>) -> AllocResult<Self>;
1431
1432 /// Point-wise addition `self + rhs`
1433 ///
1434 /// Locking behavior: acquires a shared manager lock
1435 ///
1436 /// Panics if `self` and `rhs` do not belong to the same manager.
1437 fn add(&self, rhs: &Self) -> AllocResult<Self> {
1438 self.with_manager_shared(|manager, lhs| {
1439 let e = Self::add_edge(manager, lhs, rhs.as_edge(manager))?;
1440 Ok(Self::from_edge(manager, e))
1441 })
1442 }
1443
1444 /// Point-wise subtraction `self - rhs`
1445 ///
1446 /// Locking behavior: acquires a shared manager lock
1447 ///
1448 /// Panics if `self` and `rhs` do not belong to the same manager.
1449 fn sub(&self, rhs: &Self) -> AllocResult<Self> {
1450 self.with_manager_shared(|manager, lhs| {
1451 let e = Self::sub_edge(manager, lhs, rhs.as_edge(manager))?;
1452 Ok(Self::from_edge(manager, e))
1453 })
1454 }
1455
1456 /// Point-wise multiplication `self * rhs`
1457 ///
1458 /// Locking behavior: acquires a shared manager lock
1459 ///
1460 /// Panics if `self` and `rhs` do not belong to the same manager.
1461 fn mul(&self, rhs: &Self) -> AllocResult<Self> {
1462 self.with_manager_shared(|manager, lhs| {
1463 let e = Self::mul_edge(manager, lhs, rhs.as_edge(manager))?;
1464 Ok(Self::from_edge(manager, e))
1465 })
1466 }
1467
1468 /// Point-wise division `self / rhs`
1469 ///
1470 /// Locking behavior: acquires a shared manager lock
1471 ///
1472 /// Panics if `self` and `rhs` do not belong to the same manager.
1473 fn div(&self, rhs: &Self) -> AllocResult<Self> {
1474 self.with_manager_shared(|manager, lhs| {
1475 let e = Self::div_edge(manager, lhs, rhs.as_edge(manager))?;
1476 Ok(Self::from_edge(manager, e))
1477 })
1478 }
1479
1480 /// Point-wise minimum `min(self, rhs)`
1481 ///
1482 /// Locking behavior: acquires a shared manager lock
1483 ///
1484 /// Panics if `self` and `rhs` do not belong to the same manager.
1485 fn min(&self, rhs: &Self) -> AllocResult<Self> {
1486 self.with_manager_shared(|manager, lhs| {
1487 let e = Self::min_edge(manager, lhs, rhs.as_edge(manager))?;
1488 Ok(Self::from_edge(manager, e))
1489 })
1490 }
1491
1492 /// Point-wise maximum `max(self, rhs)`
1493 ///
1494 /// Locking behavior: acquires a shared manager lock
1495 ///
1496 /// Panics if `self` and `rhs` do not belong to the same manager.
1497 fn max(&self, rhs: &Self) -> AllocResult<Self> {
1498 self.with_manager_shared(|manager, lhs| {
1499 let e = Self::max_edge(manager, lhs, rhs.as_edge(manager))?;
1500 Ok(Self::from_edge(manager, e))
1501 })
1502 }
1503
1504 /// Get the constant `value`, edge version
1505 fn constant_edge<'id>(
1506 manager: &Self::Manager<'id>,
1507 value: Self::Number,
1508 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1509
1510 /// Point-wise addition `self + rhs`, edge version
1511 fn add_edge<'id>(
1512 manager: &Self::Manager<'id>,
1513 lhs: &EdgeOfFunc<'id, Self>,
1514 rhs: &EdgeOfFunc<'id, Self>,
1515 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1516
1517 /// Point-wise subtraction `self - rhs`, edge version
1518 fn sub_edge<'id>(
1519 manager: &Self::Manager<'id>,
1520 lhs: &EdgeOfFunc<'id, Self>,
1521 rhs: &EdgeOfFunc<'id, Self>,
1522 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1523
1524 /// Point-wise multiplication `self * rhs`, edge version
1525 fn mul_edge<'id>(
1526 manager: &Self::Manager<'id>,
1527 lhs: &EdgeOfFunc<'id, Self>,
1528 rhs: &EdgeOfFunc<'id, Self>,
1529 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1530
1531 /// Point-wise division `self / rhs`, edge version
1532 fn div_edge<'id>(
1533 manager: &Self::Manager<'id>,
1534 lhs: &EdgeOfFunc<'id, Self>,
1535 rhs: &EdgeOfFunc<'id, Self>,
1536 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1537
1538 /// Point-wise minimum `min(self, rhs)`, edge version
1539 fn min_edge<'id>(
1540 manager: &Self::Manager<'id>,
1541 lhs: &EdgeOfFunc<'id, Self>,
1542 rhs: &EdgeOfFunc<'id, Self>,
1543 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1544
1545 /// Point-wise maximum `max(self, rhs)`, edge version
1546 fn max_edge<'id>(
1547 manager: &Self::Manager<'id>,
1548 lhs: &EdgeOfFunc<'id, Self>,
1549 rhs: &EdgeOfFunc<'id, Self>,
1550 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1551}
1552
1553/// Function of three valued logic
1554pub trait TVLFunction: Function {
1555 /// Get the always false function `⊥`
1556 fn f<'id>(manager: &Self::Manager<'id>) -> Self {
1557 Self::from_edge(manager, Self::f_edge(manager))
1558 }
1559 /// Get the always true function `⊤`
1560 fn t<'id>(manager: &Self::Manager<'id>) -> Self {
1561 Self::from_edge(manager, Self::t_edge(manager))
1562 }
1563 /// Get the "unknown" function `U`
1564 fn u<'id>(manager: &Self::Manager<'id>) -> Self {
1565 Self::from_edge(manager, Self::t_edge(manager))
1566 }
1567
1568 /// Get the cofactors `(f_true, f_unknown, f_false)` of `self`
1569 ///
1570 /// Let f(x₀, …, xₙ) be represented by `self`, where x₀ is (currently) the
1571 /// top-most variable. Then f<sub>true</sub>(x₁, …, xₙ) = f(⊤, x₁, …, xₙ)
1572 /// and f<sub>false</sub>(x₁, …, xₙ) = f(⊥, x₁, …, xₙ).
1573 ///
1574 /// Note that the domain of f is 𝔹ⁿ⁺¹ while the domain of f<sub>true</sub>
1575 /// and f<sub>false</sub> is 𝔹ⁿ.
1576 ///
1577 /// Returns `None` iff `self` references a terminal node. If you only need
1578 /// `f_true`, `f_unknown`, or `f_false`, [`Self::cofactor_true`],
1579 /// [`Self::cofactor_unknown`], or [`Self::cofactor_false`] are slightly
1580 /// more efficient.
1581 ///
1582 /// Locking behavior: acquires the manager's lock for shared access.
1583 fn cofactors(&self) -> Option<(Self, Self, Self)> {
1584 self.with_manager_shared(|manager, f| {
1585 let (ft, fu, ff) = Self::cofactors_edge(manager, f)?;
1586 Some((
1587 Self::from_edge_ref(manager, &ft),
1588 Self::from_edge_ref(manager, &fu),
1589 Self::from_edge_ref(manager, &ff),
1590 ))
1591 })
1592 }
1593
1594 /// Get the cofactor `f_true` of `self`
1595 ///
1596 /// This method is slightly more efficient than [`Self::cofactors`] in case
1597 /// `f_unknown` and `f_false` 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_true(&self) -> Option<Self> {
1605 self.with_manager_shared(|manager, f| {
1606 let (ft, _, _) = Self::cofactors_edge(manager, f)?;
1607 Some(Self::from_edge_ref(manager, &ft))
1608 })
1609 }
1610 /// Get the cofactor `f_unknown` of `self`
1611 ///
1612 /// This method is slightly more efficient than [`Self::cofactors`] in case
1613 /// `f_true` and `f_false` are not needed.
1614 ///
1615 /// For a more detailed description, see [`Self::cofactors`].
1616 ///
1617 /// Returns `None` iff `self` references a terminal node.
1618 ///
1619 /// Locking behavior: acquires the manager's lock for shared access.
1620 fn cofactor_unknown(&self) -> Option<Self> {
1621 self.with_manager_shared(|manager, f| {
1622 let (_, fu, _) = Self::cofactors_edge(manager, f)?;
1623 Some(Self::from_edge_ref(manager, &fu))
1624 })
1625 }
1626 /// Get the cofactor `f_false` of `self`
1627 ///
1628 /// This method is slightly more efficient than [`Self::cofactors`] in case
1629 /// `f_true` and `f_unknown` are not needed.
1630 ///
1631 /// For a more detailed description, see [`Self::cofactors`].
1632 ///
1633 /// Returns `None` iff `self` references a terminal node.
1634 ///
1635 /// Locking behavior: acquires the manager's lock for shared access.
1636 fn cofactor_false(&self) -> Option<Self> {
1637 self.with_manager_shared(|manager, f| {
1638 let (_, _, ff) = Self::cofactors_edge(manager, f)?;
1639 Some(Self::from_edge_ref(manager, &ff))
1640 })
1641 }
1642
1643 /// Get a fresh variable, i.e. a function that is true if the variable is
1644 /// true, false if the variable is false, and undefined otherwise. This adds
1645 /// a new level to a decision diagram.
1646 fn new_var<'id>(manager: &mut Self::Manager<'id>) -> AllocResult<Self>;
1647
1648 /// Compute the negation `¬self`
1649 ///
1650 /// Locking behavior: acquires the manager's lock for shared access.
1651 fn not(&self) -> AllocResult<Self> {
1652 self.with_manager_shared(|manager, edge| {
1653 Ok(Self::from_edge(manager, Self::not_edge(manager, edge)?))
1654 })
1655 }
1656 /// Compute the negation `¬self`, owned version
1657 ///
1658 /// Compared to [`Self::not()`], this method does not need to clone the
1659 /// function, so when the implementation is using (e.g.) complemented edges,
1660 /// this might be a little bit faster than [`Self::not()`].
1661 ///
1662 /// Locking behavior: acquires the manager's lock for shared access.
1663 fn not_owned(self) -> AllocResult<Self> {
1664 self.not()
1665 }
1666 /// Compute the conjunction `self ∧ rhs`
1667 ///
1668 /// Locking behavior: acquires the manager's lock for shared access.
1669 ///
1670 /// Panics if `self` and `rhs` don't belong to the same manager.
1671 fn and(&self, rhs: &Self) -> AllocResult<Self> {
1672 self.with_manager_shared(|manager, lhs| {
1673 let e = Self::and_edge(manager, lhs, rhs.as_edge(manager))?;
1674 Ok(Self::from_edge(manager, e))
1675 })
1676 }
1677 /// Compute the disjunction `self ∨ rhs`
1678 ///
1679 /// Locking behavior: acquires the manager's lock for shared access.
1680 ///
1681 /// Panics if `self` and `rhs` don't belong to the same manager.
1682 fn or(&self, rhs: &Self) -> AllocResult<Self> {
1683 self.with_manager_shared(|manager, lhs| {
1684 let e = Self::or_edge(manager, lhs, rhs.as_edge(manager))?;
1685 Ok(Self::from_edge(manager, e))
1686 })
1687 }
1688 /// Compute the negated conjunction `self ⊼ rhs`
1689 ///
1690 /// Locking behavior: acquires the manager's lock for shared access.
1691 ///
1692 /// Panics if `self` and `rhs` don't belong to the same manager.
1693 fn nand(&self, rhs: &Self) -> AllocResult<Self> {
1694 self.with_manager_shared(|manager, lhs| {
1695 let e = Self::nand_edge(manager, lhs, rhs.as_edge(manager))?;
1696 Ok(Self::from_edge(manager, e))
1697 })
1698 }
1699 /// Compute the negated disjunction `self ⊽ rhs`
1700 ///
1701 /// Locking behavior: acquires the manager's lock for shared access.
1702 ///
1703 /// Panics if `self` and `rhs` don't belong to the same manager.
1704 fn nor(&self, rhs: &Self) -> AllocResult<Self> {
1705 self.with_manager_shared(|manager, lhs| {
1706 let e = Self::nor_edge(manager, lhs, rhs.as_edge(manager))?;
1707 Ok(Self::from_edge(manager, e))
1708 })
1709 }
1710 /// Compute the exclusive disjunction `self ⊕ rhs`
1711 ///
1712 /// Locking behavior: acquires the manager's lock for shared access.
1713 ///
1714 /// Panics if `self` and `rhs` don't belong to the same manager.
1715 fn xor(&self, rhs: &Self) -> AllocResult<Self> {
1716 self.with_manager_shared(|manager, lhs| {
1717 let e = Self::xor_edge(manager, lhs, rhs.as_edge(manager))?;
1718 Ok(Self::from_edge(manager, e))
1719 })
1720 }
1721 /// Compute the equivalence `self ↔ rhs`
1722 ///
1723 /// Locking behavior: acquires the manager's lock for shared access.
1724 ///
1725 /// Panics if `self` and `rhs` don't belong to the same manager.
1726 fn equiv(&self, rhs: &Self) -> AllocResult<Self> {
1727 self.with_manager_shared(|manager, lhs| {
1728 let e = Self::equiv_edge(manager, lhs, rhs.as_edge(manager))?;
1729 Ok(Self::from_edge(manager, e))
1730 })
1731 }
1732 /// Compute the implication `self → rhs` (or `self ≤ rhs`)
1733 ///
1734 /// Locking behavior: acquires the manager's lock for shared access.
1735 ///
1736 /// Panics if `self` and `rhs` don't belong to the same manager.
1737 fn imp(&self, rhs: &Self) -> AllocResult<Self> {
1738 self.with_manager_shared(|manager, lhs| {
1739 let e = Self::imp_edge(manager, lhs, rhs.as_edge(manager))?;
1740 Ok(Self::from_edge(manager, e))
1741 })
1742 }
1743 /// Compute the strict implication `self < rhs`
1744 ///
1745 /// Locking behavior: acquires the manager's lock for shared access.
1746 ///
1747 /// Panics if `self` and `rhs` don't belong to the same manager.
1748 fn imp_strict(&self, rhs: &Self) -> AllocResult<Self> {
1749 self.with_manager_shared(|manager, lhs| {
1750 let e = Self::imp_strict_edge(manager, lhs, rhs.as_edge(manager))?;
1751 Ok(Self::from_edge(manager, e))
1752 })
1753 }
1754
1755 /// Get the always false function `⊥` as edge
1756 fn f_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
1757 /// Get the always true function `⊤` as edge
1758 fn t_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
1759 /// Get the "unknown" function `U` as edge
1760 fn u_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self>;
1761
1762 /// Get the cofactors `(f_true, f_unknown, f_false)` of `f`, edge version
1763 ///
1764 /// Returns `None` iff `f` references a terminal node. For more details on
1765 /// the semantics of `f_true` and `f_false`, see [`Self::cofactors`].
1766 #[inline]
1767 #[allow(clippy::type_complexity)]
1768 fn cofactors_edge<'a, 'id>(
1769 manager: &'a Self::Manager<'id>,
1770 f: &'a EdgeOfFunc<'id, Self>,
1771 ) -> Option<(
1772 Borrowed<'a, EdgeOfFunc<'id, Self>>,
1773 Borrowed<'a, EdgeOfFunc<'id, Self>>,
1774 Borrowed<'a, EdgeOfFunc<'id, Self>>,
1775 )> {
1776 if let Node::Inner(node) = manager.get_node(f) {
1777 Some(Self::cofactors_node(f.tag(), node))
1778 } else {
1779 None
1780 }
1781 }
1782
1783 /// Get the cofactors `(f_true, f_unknown, f_false)` of `node`, assuming an
1784 /// incoming edge with `EdgeTag`
1785 ///
1786 /// Returns `None` iff `f` references a terminal node. For more details on
1787 /// the semantics of `f_true` and `f_false`, see [`Self::cofactors`].
1788 ///
1789 /// Implementation note: The default implementation assumes that
1790 /// [cofactor 0][DiagramRules::cofactor] corresponds to `f_true`,
1791 /// [cofactor 1][DiagramRules::cofactor] corresponds to `f_unknown`, and
1792 /// [cofactor 2][DiagramRules::cofactor] corresponds to `f_false`.
1793 #[inline]
1794 #[allow(clippy::type_complexity)]
1795 fn cofactors_node<'a, 'id>(
1796 tag: ETagOfFunc<'id, Self>,
1797 node: &'a INodeOfFunc<'id, Self>,
1798 ) -> (
1799 Borrowed<'a, EdgeOfFunc<'id, Self>>,
1800 Borrowed<'a, EdgeOfFunc<'id, Self>>,
1801 Borrowed<'a, EdgeOfFunc<'id, Self>>,
1802 ) {
1803 let cofactor = <<Self::Manager<'id> as Manager>::Rules as DiagramRules<_, _, _>>::cofactor;
1804 (
1805 cofactor(tag, node, 0),
1806 cofactor(tag, node, 1),
1807 cofactor(tag, node, 2),
1808 )
1809 }
1810
1811 /// Compute the negation `¬edge`, edge version
1812 #[must_use]
1813 fn not_edge<'id>(
1814 manager: &Self::Manager<'id>,
1815 edge: &EdgeOfFunc<'id, Self>,
1816 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1817
1818 /// Compute the negation `¬edge`, owned edge version
1819 ///
1820 /// Compared to [`Self::not_edge()`], this method does not need to clone the
1821 /// edge, so when the implementation is using (e.g.) complemented edges,
1822 /// this might be a little bit faster than [`Self::not_edge()`].
1823 #[must_use]
1824 fn not_edge_owned<'id>(
1825 manager: &Self::Manager<'id>,
1826 edge: EdgeOfFunc<'id, Self>,
1827 ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1828 Self::not_edge(manager, &edge)
1829 }
1830
1831 /// Compute the conjunction `lhs ∧ rhs`, edge version
1832 #[must_use]
1833 fn and_edge<'id>(
1834 manager: &Self::Manager<'id>,
1835 lhs: &EdgeOfFunc<'id, Self>,
1836 rhs: &EdgeOfFunc<'id, Self>,
1837 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1838 /// Compute the disjunction `lhs ∨ rhs`, edge version
1839 #[must_use]
1840 fn or_edge<'id>(
1841 manager: &Self::Manager<'id>,
1842 lhs: &EdgeOfFunc<'id, Self>,
1843 rhs: &EdgeOfFunc<'id, Self>,
1844 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1845 /// Compute the negated conjunction `lhs ⊼ rhs`, edge version
1846 #[must_use]
1847 fn nand_edge<'id>(
1848 manager: &Self::Manager<'id>,
1849 lhs: &EdgeOfFunc<'id, Self>,
1850 rhs: &EdgeOfFunc<'id, Self>,
1851 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1852 /// Compute the negated disjunction `lhs ⊽ rhs`, edge version
1853 #[must_use]
1854 fn nor_edge<'id>(
1855 manager: &Self::Manager<'id>,
1856 lhs: &EdgeOfFunc<'id, Self>,
1857 rhs: &EdgeOfFunc<'id, Self>,
1858 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1859 /// Compute the exclusive disjunction `lhs ⊕ rhs`, edge version
1860 #[must_use]
1861 fn xor_edge<'id>(
1862 manager: &Self::Manager<'id>,
1863 lhs: &EdgeOfFunc<'id, Self>,
1864 rhs: &EdgeOfFunc<'id, Self>,
1865 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1866 /// Compute the equivalence `lhs ↔ rhs`, edge version
1867 #[must_use]
1868 fn equiv_edge<'id>(
1869 manager: &Self::Manager<'id>,
1870 lhs: &EdgeOfFunc<'id, Self>,
1871 rhs: &EdgeOfFunc<'id, Self>,
1872 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1873 /// Compute the implication `lhs → rhs`, edge version
1874 #[must_use]
1875 fn imp_edge<'id>(
1876 manager: &Self::Manager<'id>,
1877 lhs: &EdgeOfFunc<'id, Self>,
1878 rhs: &EdgeOfFunc<'id, Self>,
1879 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1880 /// Compute the strict implication `lhs < rhs`, edge version
1881 #[must_use]
1882 fn imp_strict_edge<'id>(
1883 manager: &Self::Manager<'id>,
1884 lhs: &EdgeOfFunc<'id, Self>,
1885 rhs: &EdgeOfFunc<'id, Self>,
1886 ) -> AllocResult<EdgeOfFunc<'id, Self>>;
1887
1888 /// Compute `if self { then_case } else { else_case }`
1889 ///
1890 /// This is equivalent to `(self ∧ then_case) ∨ (¬self ∧ else_case)` but
1891 /// possibly more efficient than computing all the
1892 /// conjunctions/disjunctions.
1893 ///
1894 /// Locking behavior: acquires the manager's lock for shared access.
1895 ///
1896 /// Panics if `self`, `then_case`, and `else_case` don't belong to the same
1897 /// manager.
1898 fn ite(&self, then_case: &Self, else_case: &Self) -> AllocResult<Self> {
1899 self.with_manager_shared(|manager, if_edge| {
1900 let then_edge = then_case.as_edge(manager);
1901 let else_edge = else_case.as_edge(manager);
1902 let res = Self::ite_edge(manager, if_edge, then_edge, else_edge)?;
1903 Ok(Self::from_edge(manager, res))
1904 })
1905 }
1906
1907 /// Compute `if if_edge { then_edge } else { else_edge }` (edge version)
1908 ///
1909 /// This is equivalent to `(self ∧ then_case) ∨ (¬self ∧ else_case)` but
1910 /// possibly more efficient than computing all the
1911 /// conjunctions/disjunctions.
1912 #[must_use]
1913 fn ite_edge<'id>(
1914 manager: &Self::Manager<'id>,
1915 if_edge: &EdgeOfFunc<'id, Self>,
1916 then_edge: &EdgeOfFunc<'id, Self>,
1917 else_edge: &EdgeOfFunc<'id, Self>,
1918 ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1919 let f = EdgeDropGuard::new(manager, Self::and_edge(manager, if_edge, then_edge)?);
1920 let g = EdgeDropGuard::new(manager, Self::imp_strict_edge(manager, if_edge, else_edge)?);
1921 Self::or_edge(manager, &*f, &*g)
1922 }
1923}