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