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}