oxidd_rules_bdd/complement_edge/
apply_rec.rs

1//! Recursive apply algorithms
2
3use std::hash::BuildHasher;
4
5use bitvec::vec::BitVec;
6
7use oxidd_core::{
8    function::{
9        BooleanFunction, BooleanFunctionQuant, BooleanOperator, EdgeOfFunc, Function, FunctionSubst,
10    },
11    util::{
12        AllocResult, Borrowed, EdgeDropGuard, EdgeVecDropGuard, OptBool, SatCountCache,
13        SatCountNumber,
14    },
15    ApplyCache, Edge, HasApplyCache, HasLevel, InnerNode, LevelNo, Manager, Node, NodeID, Tag,
16};
17use oxidd_derive::Function;
18use oxidd_dump::dot::DotStyle;
19
20use crate::{complement_edge::add_literal_to_cube, stat};
21use crate::{
22    complement_edge::is_false,
23    recursor::{Recursor, SequentialRecursor},
24};
25
26use super::collect_cofactors;
27use super::get_terminal;
28use super::not;
29use super::not_owned;
30use super::reduce;
31use super::BCDDOp;
32use super::BCDDTerminal;
33use super::EdgeTag;
34use super::NodesOrDone;
35#[cfg(feature = "statistics")]
36use super::STAT_COUNTERS;
37
38// spell-checker:ignore fnode,gnode,hnode,vnode,flevel,glevel,hlevel,vlevel
39
40/// Recursively apply the binary operator `OP` to `f` and `g`
41///
42/// We use a `const` parameter `OP` to have specialized version of this function
43/// for each operator.
44///
45/// Using `Borrowed<M::Edge>` instead of `&M::Edge` means that we actually
46/// pass the edge by value, which saves a few indirections.
47fn apply_bin<M, R: Recursor<M>, const OP: u8>(
48    manager: &M,
49    rec: R,
50    f: Borrowed<M::Edge>,
51    g: Borrowed<M::Edge>,
52) -> AllocResult<M::Edge>
53where
54    M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal> + HasApplyCache<M, BCDDOp>,
55    M::InnerNode: HasLevel,
56{
57    if rec.should_switch_to_sequential() {
58        return apply_bin::<M, _, OP>(manager, SequentialRecursor, f, g);
59    }
60    stat!(call OP);
61
62    let (op, f, fnode, g, gnode) = if OP == BCDDOp::And as u8 {
63        match super::terminal_and(manager, &f, &g) {
64            NodesOrDone::Nodes(fnode, gnode) if f < g => {
65                (BCDDOp::And, f.borrowed(), fnode, g.borrowed(), gnode)
66            }
67            // `And` is commutative, hence we swap `f` and `g` in the apply
68            // cache key if `f > g` to have a unique representation of the set
69            // `{f, g}`.
70            NodesOrDone::Nodes(fnode, gnode) => {
71                (BCDDOp::And, g.borrowed(), gnode, f.borrowed(), fnode)
72            }
73            NodesOrDone::Done(h) => return Ok(h.into_edge()),
74        }
75    } else {
76        assert_eq!(OP, BCDDOp::Xor as u8);
77        match super::terminal_xor(manager, &f, &g) {
78            NodesOrDone::Nodes(fnode, gnode) if f < g => {
79                (BCDDOp::Xor, f.borrowed(), fnode, g.borrowed(), gnode)
80            }
81            NodesOrDone::Nodes(fnode, gnode) => {
82                (BCDDOp::Xor, g.borrowed(), gnode, f.borrowed(), fnode)
83            }
84            NodesOrDone::Done(h) => {
85                return Ok(h.into_edge());
86            }
87        }
88    };
89
90    // Query apply cache
91    stat!(cache_query OP);
92    if let Some(h) = manager
93        .apply_cache()
94        .get(manager, op, &[f.borrowed(), g.borrowed()])
95    {
96        stat!(cache_hit OP);
97        return Ok(h);
98    }
99
100    let flevel = fnode.level();
101    let glevel = gnode.level();
102    let level = std::cmp::min(flevel, glevel);
103
104    // Collect cofactors of all top-most nodes
105    let (ft, fe) = if flevel == level {
106        collect_cofactors(f.tag(), fnode)
107    } else {
108        (f.borrowed(), f.borrowed())
109    };
110    let (gt, ge) = if glevel == level {
111        collect_cofactors(g.tag(), gnode)
112    } else {
113        (g.borrowed(), g.borrowed())
114    };
115
116    let (t, e) = rec.binary(apply_bin::<M, R, OP>, manager, (ft, gt), (fe, ge))?;
117
118    let h = reduce(manager, level, t.into_edge(), e.into_edge(), op)?;
119
120    // Add to apply cache
121    manager
122        .apply_cache()
123        .add(manager, op, &[f, g], h.borrowed());
124
125    Ok(h)
126}
127
128/// Shorthand for `apply_bin_rec::<M, R, { BCDDOp::And as u8 }>(manager, f, g)`
129#[inline(always)]
130fn apply_and<M, R: Recursor<M>>(
131    manager: &M,
132    rec: R,
133    f: Borrowed<M::Edge>,
134    g: Borrowed<M::Edge>,
135) -> AllocResult<M::Edge>
136where
137    M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal> + HasApplyCache<M, BCDDOp>,
138    M::InnerNode: HasLevel,
139{
140    apply_bin::<M, R, { BCDDOp::And as u8 }>(manager, rec, f, g)
141}
142
143/// Recursively apply the if-then-else operator (`if f { g } else { h }`)
144fn apply_ite<M, R: Recursor<M>>(
145    manager: &M,
146    rec: R,
147    f: Borrowed<M::Edge>,
148    g: Borrowed<M::Edge>,
149    h: Borrowed<M::Edge>,
150) -> AllocResult<M::Edge>
151where
152    M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal> + HasApplyCache<M, BCDDOp>,
153    M::InnerNode: HasLevel,
154{
155    if rec.should_switch_to_sequential() {
156        return apply_ite(manager, SequentialRecursor, f, g, h);
157    }
158    stat!(call BCDDOp::Ite);
159
160    // Terminal cases
161    let gu = g.with_tag(EdgeTag::None); // untagged
162    let hu = h.with_tag(EdgeTag::None);
163    if gu == hu {
164        return Ok(if g.tag() == h.tag() {
165            manager.clone_edge(&g)
166        } else {
167            not_owned(apply_bin::<M, R, { BCDDOp::Xor as u8 }>(
168                manager, rec, f, g,
169            )?) // f ↔ g
170        });
171    }
172    let fu = f.with_tag(EdgeTag::None);
173    if fu == gu {
174        return if f.tag() == g.tag() {
175            Ok(not_owned(apply_and(manager, rec, not(&f), not(&h))?)) // f ∨ h
176        } else {
177            apply_and(manager, rec, not(&f), h) // f < h
178        };
179    }
180    if fu == hu {
181        return if f.tag() == h.tag() {
182            apply_and(manager, rec, f, g)
183        } else {
184            // f → g = ¬f ∨ g = ¬(f ∧ ¬g)
185            Ok(not_owned(apply_and(manager, rec, f, not(&g))?))
186        };
187    }
188    let fnode = match manager.get_node(&f) {
189        Node::Inner(n) => n,
190        Node::Terminal(_) => {
191            return Ok(manager.clone_edge(&*if f.tag() == EdgeTag::None { g } else { h }))
192        }
193    };
194    let (gnode, hnode) = match (manager.get_node(&g), manager.get_node(&h)) {
195        (Node::Inner(gn), Node::Inner(hn)) => (gn, hn),
196        (Node::Terminal(_), Node::Inner(_)) => {
197            return if g.tag() == EdgeTag::None {
198                // f ∨ h
199                Ok(not_owned(apply_and(manager, rec, not(&f), not(&h))?))
200            } else {
201                apply_and(manager, rec, not(&f), h) // f < h
202            };
203        }
204        (_gnode, Node::Terminal(_)) => {
205            debug_assert!(_gnode.is_inner());
206            return if h.tag() == EdgeTag::None {
207                Ok(not_owned(apply_and(manager, rec, f, not(&g))?)) // f → g
208            } else {
209                apply_and(manager, rec, f, g)
210            };
211        }
212    };
213
214    // Query apply cache
215    stat!(cache_query BCDDOp::Ite);
216    if let Some(res) = manager.apply_cache().get(
217        manager,
218        BCDDOp::Ite,
219        &[f.borrowed(), g.borrowed(), h.borrowed()],
220    ) {
221        stat!(cache_hit BCDDOp::Ite);
222        return Ok(res);
223    }
224
225    // Get the top-most level of the three
226    let flevel = fnode.level();
227    let glevel = gnode.level();
228    let hlevel = hnode.level();
229    let level = std::cmp::min(std::cmp::min(flevel, glevel), hlevel);
230
231    // Collect cofactors of all top-most nodes
232    let (ft, fe) = if flevel == level {
233        collect_cofactors(f.tag(), fnode)
234    } else {
235        (f.borrowed(), f.borrowed())
236    };
237    let (gt, ge) = if glevel == level {
238        collect_cofactors(g.tag(), gnode)
239    } else {
240        (g.borrowed(), g.borrowed())
241    };
242    let (ht, he) = if hlevel == level {
243        collect_cofactors(h.tag(), hnode)
244    } else {
245        (h.borrowed(), h.borrowed())
246    };
247
248    let (t, e) = rec.ternary(apply_ite, manager, (ft, gt, ht), (fe, ge, he))?;
249    let res = reduce(manager, level, t.into_edge(), e.into_edge(), BCDDOp::Ite)?;
250
251    manager
252        .apply_cache()
253        .add(manager, BCDDOp::Ite, &[f, g, h], res.borrowed());
254
255    Ok(res)
256}
257
258/// Prepare a substitution
259///
260/// The result is a vector that maps levels to replacement functions. The levels
261/// below the lowest variable (of `vars`) are ignored. Levels above which are
262/// not referenced from `vars` are mapped to the function representing the
263/// variable at that level. The latter is the reason why we return the owned
264/// edges.
265fn substitute_prepare<'a, M>(
266    manager: &'a M,
267    pairs: impl Iterator<Item = (Borrowed<'a, M::Edge>, Borrowed<'a, M::Edge>)>,
268) -> AllocResult<EdgeVecDropGuard<'a, M>>
269where
270    M: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag>,
271    M::Edge: 'a,
272    M::InnerNode: HasLevel,
273{
274    let mut subst = Vec::with_capacity(manager.num_levels() as usize);
275    for (v, r) in pairs {
276        let level = super::var_level(manager, v) as usize;
277        if level >= subst.len() {
278            subst.resize_with(level + 1, || None);
279        }
280        debug_assert!(
281            subst[level].is_none(),
282            "Variable at level {level} occurs twice in the substitution, but a \
283            substitution should be a mapping from variables to replacement \
284            functions"
285        );
286        subst[level] = Some(r);
287    }
288
289    let mut res = EdgeVecDropGuard::new(manager, Vec::with_capacity(subst.len()));
290    for (level, e) in subst.into_iter().enumerate() {
291        use oxidd_core::LevelView;
292
293        res.push(if let Some(e) = e {
294            manager.clone_edge(&e)
295        } else {
296            let t = EdgeDropGuard::new(manager, get_terminal(manager, true));
297            let e = EdgeDropGuard::new(manager, get_terminal(manager, false));
298            manager
299                .level(level as LevelNo)
300                .get_or_insert(InnerNode::new(
301                    level as LevelNo,
302                    [t.into_edge(), e.into_edge()],
303                ))?
304        });
305    }
306
307    Ok(res)
308}
309
310fn substitute<M, R: Recursor<M>>(
311    manager: &M,
312    rec: R,
313    f: Borrowed<M::Edge>,
314    subst: &[M::Edge],
315    cache_id: u32,
316) -> AllocResult<M::Edge>
317where
318    M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal> + HasApplyCache<M, BCDDOp>,
319    M::InnerNode: HasLevel,
320{
321    if rec.should_switch_to_sequential() {
322        return substitute(manager, SequentialRecursor, f, subst, cache_id);
323    }
324    stat!(call BCDDOp::Substitute);
325
326    let Node::Inner(node) = manager.get_node(&f) else {
327        return Ok(manager.clone_edge(&f));
328    };
329    let level = node.level();
330    if level as usize >= subst.len() {
331        return Ok(manager.clone_edge(&f));
332    }
333
334    // Query apply cache
335    stat!(cache_query BCDDOp::Substitute);
336    if let Some(h) = manager.apply_cache().get_with_numeric(
337        manager,
338        BCDDOp::Substitute,
339        &[f.borrowed()],
340        &[cache_id],
341    ) {
342        stat!(cache_hit BCDDOp::Substitute);
343        return Ok(h);
344    }
345
346    let (t, e) = collect_cofactors(f.tag(), node);
347    let (t, e) = rec.subst(
348        substitute,
349        manager,
350        (t, subst, cache_id),
351        (e, subst, cache_id),
352    )?;
353    let res = apply_ite(
354        manager,
355        rec,
356        subst[level as usize].borrowed(),
357        t.borrowed(),
358        e.borrowed(),
359    )?;
360
361    // Insert into apply cache
362    manager.apply_cache().add_with_numeric(
363        manager,
364        BCDDOp::Substitute,
365        &[f.borrowed()],
366        &[cache_id],
367        res.borrowed(),
368    );
369
370    Ok(res)
371}
372
373/// Result of [`restrict_inner()`]
374enum RestrictInnerResult<'a, M: Manager> {
375    Done(M::Edge),
376    Rec {
377        vars: Borrowed<'a, M::Edge>,
378        f: Borrowed<'a, M::Edge>,
379        f_neg: bool,
380        fnode: &'a M::InnerNode,
381    },
382}
383
384/// Tail-recursive part of [`restrict()`]. `f` is the function of which the
385/// variables should be restricted to constant values according to `vars`.
386///
387/// Invariant: `f` points to `fnode` at `flevel`, `vars` points to `vnode`
388///
389/// We expose this, because it can be reused for the multi-threaded version.
390#[inline]
391#[allow(clippy::too_many_arguments)]
392fn restrict_inner<'a, M>(
393    manager: &'a M,
394    f: Borrowed<'a, M::Edge>,
395    f_neg: bool,
396    fnode: &'a M::InnerNode,
397    flevel: LevelNo,
398    vars: Borrowed<'a, M::Edge>,
399    vars_neg: bool,
400    vnode: &'a M::InnerNode,
401) -> RestrictInnerResult<'a, M>
402where
403    M: Manager<EdgeTag = EdgeTag>,
404    M::InnerNode: HasLevel,
405{
406    debug_assert!(std::ptr::eq(manager.get_node(&f).unwrap_inner(), fnode));
407    debug_assert_eq!(fnode.level(), flevel);
408    debug_assert!(std::ptr::eq(manager.get_node(&vars).unwrap_inner(), vnode));
409
410    let vlevel = vnode.level();
411    if vlevel > flevel {
412        // f above vars
413        return RestrictInnerResult::Rec {
414            vars: vars.edge_with_tag(if vars_neg {
415                EdgeTag::Complemented
416            } else {
417                EdgeTag::None
418            }),
419            f,
420            f_neg,
421            fnode,
422        };
423    }
424
425    let (f, complement) = 'ret_f: {
426        let vt = vnode.child(0);
427        if vlevel < flevel {
428            // vars above f
429            if let Node::Inner(n) = manager.get_node(&vt) {
430                debug_assert!(
431                    manager.get_node(&vnode.child(1)).is_any_terminal(),
432                    "vars must be a conjunction of literals (but both children are non-terminals)"
433                );
434
435                debug_assert_eq!(
436                    vnode.child(1).tag(),
437                    if vars_neg {
438                        EdgeTag::None
439                    } else {
440                        EdgeTag::Complemented
441                    },
442                    "vars must be a conjunction of literals (but is of shape ¬x ∨ {}φ)",
443                    if vars_neg { "¬" } else { "" }
444                );
445                // shape: x ∧ if vars_neg { ¬φ } else { φ }
446                let vars_neg = vars_neg ^ (vt.tag() == EdgeTag::Complemented);
447                return restrict_inner(manager, f, f_neg, fnode, flevel, vt, vars_neg, n);
448            }
449            // then edge of vars edge points to ⊤
450            if vars_neg {
451                // shape ¬x ∧ φ
452                let ve = vnode.child(1);
453                if let Node::Inner(n) = manager.get_node(&ve) {
454                    // `vars` is currently negated, hence `!=`
455                    let vars_neg = ve.tag() != EdgeTag::Complemented;
456                    return restrict_inner(manager, f, f_neg, fnode, flevel, ve, vars_neg, n);
457                }
458                // shape ¬x
459            } else {
460                debug_assert!(
461                    manager.get_node(&vnode.child(1)).is_any_terminal(),
462                    "vars must be a conjunction of literals (but is of shape x ∨ φ)"
463                );
464                // shape x
465            }
466            // `vars` is a single variable above `f` ⇒ return `f`
467            break 'ret_f (f, f_neg);
468        }
469
470        debug_assert_eq!(vlevel, flevel);
471        // top var at the level of f ⇒ select accordingly
472        let (f, vars, vars_neg, vnode) = if let Node::Inner(n) = manager.get_node(&vt) {
473            debug_assert!(
474                manager.get_node(&vnode.child(1)).is_any_terminal(),
475                "vars must be a conjunction of literals (but both children are non-terminals)"
476            );
477
478            debug_assert_eq!(
479                vnode.child(1).tag(),
480                if vars_neg {
481                    EdgeTag::None
482                } else {
483                    EdgeTag::Complemented
484                },
485                "vars must be a conjunction of literals (but is of shape ¬x ∨ {}φ)",
486                if vars_neg { "¬" } else { "" }
487            );
488            // shape: x ∧ if vars_neg { ¬φ } else { φ } ⇒ select then branch
489            let vars_neg = vars_neg ^ (vt.tag() == EdgeTag::Complemented);
490            (fnode.child(0), vt, vars_neg, n)
491        } else {
492            // then edge of vars edge points to ⊤
493
494            if !vars_neg {
495                debug_assert!(
496                    manager.get_node(&vnode.child(1)).is_any_terminal(),
497                    "vars must be a conjunction of literals (but is of shape x ∨ φ)"
498                );
499
500                // shape x ⇒ select then branch
501                let f = fnode.child(0);
502                let f_neg = f_neg ^ (f.tag() == EdgeTag::Complemented);
503                break 'ret_f (f, f_neg);
504            }
505
506            // shape ¬x ∧ φ ⇒ select else branch
507            let f = fnode.child(1);
508            let ve = vnode.child(1);
509            if let Node::Inner(n) = manager.get_node(&ve) {
510                // `vars` is currently negated, hence `!=`
511                let vars_neg = ve.tag() != EdgeTag::Complemented;
512                (f, ve, vars_neg, n)
513            } else {
514                // shape `¬x` ⇒ return
515                let f_neg = f_neg ^ (f.tag() == EdgeTag::Complemented);
516                break 'ret_f (f, f_neg);
517            }
518        };
519
520        let f_neg = f_neg ^ (f.tag() == EdgeTag::Complemented);
521        if let Node::Inner(fnode) = manager.get_node(&f) {
522            let flevel = fnode.level();
523            return restrict_inner(manager, f, f_neg, fnode, flevel, vars, vars_neg, vnode);
524        }
525        (f, f_neg)
526    };
527
528    RestrictInnerResult::Done(manager.clone_edge(&f).with_tag_owned(if complement {
529        EdgeTag::Complemented
530    } else {
531        EdgeTag::None
532    }))
533}
534
535fn restrict<M, R: Recursor<M>>(
536    manager: &M,
537    rec: R,
538    f: Borrowed<M::Edge>,
539    vars: Borrowed<M::Edge>,
540) -> AllocResult<M::Edge>
541where
542    M: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag> + HasApplyCache<M, BCDDOp>,
543    M::InnerNode: HasLevel,
544{
545    if rec.should_switch_to_sequential() {
546        return restrict(manager, SequentialRecursor, f, vars);
547    }
548    stat!(call BCDDOp::Restrict);
549
550    let (Node::Inner(fnode), Node::Inner(vnode)) = (manager.get_node(&f), manager.get_node(&vars))
551    else {
552        return Ok(manager.clone_edge(&f));
553    };
554
555    let inner_res = {
556        let f_neg = f.tag() == EdgeTag::Complemented;
557        let flevel = fnode.level();
558        let vars_neg = vars.tag() == EdgeTag::Complemented;
559        restrict_inner(manager, f, f_neg, fnode, flevel, vars, vars_neg, vnode)
560    };
561    match inner_res {
562        RestrictInnerResult::Done(result) => Ok(result),
563        RestrictInnerResult::Rec {
564            vars,
565            f,
566            f_neg,
567            fnode,
568        } => {
569            // f above top-most restrict variable
570            let f_untagged = f.with_tag(EdgeTag::None);
571            let f_tag = if f_neg {
572                EdgeTag::Complemented
573            } else {
574                EdgeTag::None
575            };
576
577            // Query apply cache
578            stat!(cache_query BCDDOp::Restrict);
579            if let Some(result) = manager.apply_cache().get(
580                manager,
581                BCDDOp::Restrict,
582                &[f_untagged.borrowed(), vars.borrowed()],
583            ) {
584                stat!(cache_hit BCDDOp::Restrict);
585                let result_tag = result.tag();
586                return Ok(result.with_tag_owned(result_tag ^ f_tag));
587            }
588
589            let (t, e) = rec.binary(
590                restrict,
591                manager,
592                (fnode.child(0), vars.borrowed()),
593                (fnode.child(1), vars.borrowed()),
594            )?;
595
596            let result = reduce(
597                manager,
598                fnode.level(),
599                t.into_edge(),
600                e.into_edge(),
601                BCDDOp::Restrict,
602            )?;
603
604            manager.apply_cache().add(
605                manager,
606                BCDDOp::Restrict,
607                &[f_untagged, vars],
608                result.borrowed(),
609            );
610
611            let result_tag = result.tag();
612            Ok(result.with_tag_owned(result_tag ^ f_tag))
613        }
614    }
615}
616
617/// Compute the quantification `Q` over `vars`
618///
619/// `Q` is one of `BCDDOp::Forall`, `BCDDOp::Exist`, or `BCDDOp::Forall` as
620/// `u8`.
621fn quant<M, R: Recursor<M>, const Q: u8>(
622    manager: &M,
623    rec: R,
624    f: Borrowed<M::Edge>,
625    vars: Borrowed<M::Edge>,
626) -> AllocResult<M::Edge>
627where
628    M: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag> + HasApplyCache<M, BCDDOp>,
629    M::InnerNode: HasLevel,
630{
631    if rec.should_switch_to_sequential() {
632        return quant::<M, _, Q>(manager, SequentialRecursor, f, vars);
633    }
634    let operator = match () {
635        _ if Q == BCDDOp::Forall as u8 => BCDDOp::Forall,
636        _ if Q == BCDDOp::Exist as u8 => BCDDOp::Exist,
637        _ if Q == BCDDOp::Unique as u8 => BCDDOp::Unique,
638        _ => unreachable!("invalid quantifier"),
639    };
640
641    stat!(call operator);
642    // Terminal cases
643    let fnode = match manager.get_node(&f) {
644        Node::Inner(n) => n,
645        Node::Terminal(_) => {
646            return Ok(
647                if operator != BCDDOp::Unique || manager.get_node(&vars).is_any_terminal() {
648                    manager.clone_edge(&f)
649                } else {
650                    get_terminal(manager, false)
651                },
652            );
653        }
654    };
655    let flevel = fnode.level();
656
657    let vars = if operator != BCDDOp::Unique {
658        // We can ignore all variables above the top-most variable. Removing
659        // them before querying the apply cache should increase the hit ratio by
660        // a lot.
661        crate::set_pop(manager, vars, flevel)
662    } else {
663        // No need to pop variables here, if the variable is above `fnode`,
664        // i.e., does not occur in `f`, then the result is `f ⊕ f ≡ ⊥`. We
665        // handle this below.
666        vars
667    };
668    let vnode = match manager.get_node(&vars) {
669        Node::Inner(n) => n,
670        Node::Terminal(_) => return Ok(manager.clone_edge(&f)),
671    };
672    let vlevel = vnode.level();
673    if operator == BCDDOp::Unique && vlevel < flevel {
674        // `vnode` above `fnode`, i.e., the variable does not occur in `f` (see above)
675        return Ok(get_terminal(manager, false));
676    }
677    debug_assert!(flevel <= vlevel);
678    let vars = vars.borrowed();
679
680    // Query apply cache
681    stat!(cache_query operator);
682    if let Some(res) =
683        manager
684            .apply_cache()
685            .get(manager, operator, &[f.borrowed(), vars.borrowed()])
686    {
687        stat!(cache_hit operator);
688        return Ok(res);
689    }
690
691    let (ft, fe) = collect_cofactors(f.tag(), fnode);
692    let vt = if vlevel == flevel {
693        vnode.child(0)
694    } else {
695        vars.borrowed()
696    };
697    let (t, e) = rec.binary(
698        quant::<M, R, Q>,
699        manager,
700        (ft, vt.borrowed()),
701        (fe, vt.borrowed()),
702    )?;
703
704    let res = if flevel == vlevel {
705        match operator {
706            BCDDOp::Forall => apply_and(manager, rec, t.borrowed(), e.borrowed())?,
707            BCDDOp::Exist => not_owned(apply_and(manager, rec, not(&t), not(&e))?),
708            BCDDOp::Unique => {
709                apply_bin::<M, R, { BCDDOp::Xor as u8 }>(manager, rec, t.borrowed(), e.borrowed())?
710            }
711            _ => unreachable!(),
712        }
713    } else {
714        reduce(manager, flevel, t.into_edge(), e.into_edge(), operator)?
715    };
716
717    manager
718        .apply_cache()
719        .add(manager, operator, &[f, vars], res.borrowed());
720
721    Ok(res)
722}
723
724/// Recursively apply the binary operator `OP` to `f` and `g` while quantifying
725/// `Q` over `vars`. This is more efficient then computing then an apply
726/// operation followed by a quantification.
727///
728/// One example usage is for the relational product, i.e., computing
729/// `∃ s, t. S(s) ∧ T(s, t)`, where `S` is a boolean function representing the
730/// states and `T` a boolean function representing the transition relation.
731///
732/// `Q` is one of [`BCDDOp::Forall`], [`BCDDOp::Exist`], or [`BCDDOp::Forall`]
733/// as `u8`. We use a `const` parameter `OP` to have specialized version of this
734/// function for each operator ([`BCDDOp::And`], [`BCDDOp::Xor`], or
735/// specifically [`BCDDOp::UniqueNand`], each as `u8`).
736fn apply_quant<'a, M, R: Recursor<M>, const Q: u8, const OP: u8>(
737    manager: &'a M,
738    rec: R,
739    f: Borrowed<M::Edge>,
740    g: Borrowed<M::Edge>,
741    vars: Borrowed<M::Edge>,
742) -> AllocResult<M::Edge>
743where
744    M: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag> + HasApplyCache<M, BCDDOp>,
745    M::InnerNode: HasLevel,
746{
747    if rec.should_switch_to_sequential() {
748        return apply_quant::<M, _, Q, OP>(manager, SequentialRecursor, f, g, vars);
749    }
750
751    let operator = const { BCDDOp::from_apply_quant(Q, OP) };
752
753    stat!(call operator);
754    // Handle the terminal cases
755    let (f, fnode, g, gnode) = if OP == BCDDOp::And as u8 || OP == BCDDOp::UniqueNand as u8 {
756        match super::terminal_and(manager, &f, &g) {
757            NodesOrDone::Nodes(fnode, gnode) if f < g => (f.borrowed(), fnode, g.borrowed(), gnode),
758            // `And` is commutative, hence we swap `f` and `g` in the apply
759            // cache key if `f > g` to have a unique representation of the set
760            // `{f, g}`.
761            NodesOrDone::Nodes(fnode, gnode) => (g.borrowed(), gnode, f.borrowed(), fnode),
762            NodesOrDone::Done(h) if OP == BCDDOp::UniqueNand as u8 => {
763                return quant::<M, R, Q>(manager, rec, not(&h), vars)
764            }
765            NodesOrDone::Done(h) => return quant::<M, R, Q>(manager, rec, h.borrowed(), vars),
766        }
767    } else {
768        assert_eq!(OP, BCDDOp::Xor as u8);
769        match super::terminal_xor(manager, &f, &g) {
770            NodesOrDone::Nodes(fnode, gnode) if f < g => (f.borrowed(), fnode, g.borrowed(), gnode),
771            NodesOrDone::Nodes(fnode, gnode) => (g.borrowed(), gnode, f.borrowed(), fnode),
772            NodesOrDone::Done(h) => return quant::<M, R, Q>(manager, rec, h.borrowed(), vars),
773        }
774    };
775
776    let flevel = fnode.level();
777    let glevel = gnode.level();
778    let min_level = std::cmp::min(fnode.level(), gnode.level());
779
780    let vars = if Q != BCDDOp::Unique as u8 {
781        // We can ignore all variables above the top-most variable. Removing
782        // them before querying the apply cache should increase the hit ratio by
783        // a lot.
784        crate::set_pop(manager, vars, min_level)
785    } else {
786        // No need to pop variables here. If the variable is above `min_level`,
787        // i.e., does not occur in `f` or `g`, then the result is `f ⊕ f ≡ ⊥`. We
788        // handle this below.
789        vars
790    };
791
792    let vnode = match manager.get_node(&vars) {
793        Node::Inner(n) => n,
794        // Empty variable set: just apply operation
795        Node::Terminal(_) if OP == BCDDOp::UniqueNand as u8 => {
796            return Ok(not_owned(apply_and(manager, rec, f, g)?))
797        }
798        Node::Terminal(_) => return apply_bin::<M, R, OP>(manager, rec, f, g),
799    };
800
801    let vlevel = vnode.level();
802    if vlevel < min_level && Q == BCDDOp::Unique as u8 {
803        // `vnode` above `fnode` and `gnode`, i.e., the variable does not occur in `f`
804        // or `g` (see above)
805        return Ok(get_terminal(manager, false));
806    }
807
808    if min_level > vlevel {
809        // We are beyond the variables to be quantified, so simply apply.
810        if OP == BCDDOp::UniqueNand as u8 {
811            return Ok(not_owned(apply_and(manager, rec, f, g)?));
812        }
813        return apply_bin::<M, R, OP>(manager, rec, f, g);
814    }
815
816    // Query the cache
817    stat!(cache_query operator);
818    if let Some(res) = manager.apply_cache().get(
819        manager,
820        operator,
821        &[f.borrowed(), g.borrowed(), vars.borrowed()],
822    ) {
823        stat!(cache_hit operator);
824        return Ok(res);
825    }
826
827    let vt = if vlevel == min_level {
828        vnode.child(0)
829    } else {
830        vars.borrowed()
831    };
832
833    let (ft, fe) = if flevel <= glevel {
834        collect_cofactors(f.tag(), fnode)
835    } else {
836        (f.borrowed(), f.borrowed())
837    };
838
839    let (gt, ge) = if flevel >= glevel {
840        collect_cofactors(g.tag(), gnode)
841    } else {
842        (g.borrowed(), g.borrowed())
843    };
844
845    let (t, e) = rec.ternary(
846        apply_quant::<M, R, Q, OP>,
847        manager,
848        (ft, gt, vt.borrowed()),
849        (fe, ge, vt.borrowed()),
850    )?;
851
852    let res = if min_level == vlevel {
853        if Q == BCDDOp::Forall as u8 {
854            apply_and(manager, rec, t.borrowed(), e.borrowed())?
855        } else if Q == BCDDOp::Exist as u8 {
856            not_owned(apply_and(manager, rec, not(&t), not(&e))?)
857        } else if Q == BCDDOp::Unique as u8 {
858            apply_bin::<M, R, { BCDDOp::Xor as u8 }>(manager, rec, t.borrowed(), e.borrowed())?
859        } else {
860            unreachable!()
861        }
862    } else {
863        reduce(manager, min_level, t.into_edge(), e.into_edge(), operator)?
864    };
865
866    manager
867        .apply_cache()
868        .add(manager, operator, &[f, g, vars], res.borrowed());
869
870    Ok(res)
871}
872
873/// Dynamic dispatcher for [`apply_quant()`] and universal/existential
874/// quantification
875///
876/// In contrast to [`apply_quant()`], the operator is not a const but a runtime
877/// parameter. `QN` is the "negated" version of `Q`: If `Q` is
878/// [`BCDDOp::Forall`] (as u8) then, `QN` is [`BCDDOp::Exist`], and vice versa.
879fn apply_quant_dispatch<'a, M, R: Recursor<M>, const Q: u8, const QN: u8>(
880    manager: &'a M,
881    rec: R,
882    op: BooleanOperator,
883    f: Borrowed<M::Edge>,
884    g: Borrowed<M::Edge>,
885    vars: Borrowed<M::Edge>,
886) -> AllocResult<M::Edge>
887where
888    M: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag> + HasApplyCache<M, BCDDOp>,
889    M::InnerNode: HasLevel,
890{
891    use BooleanOperator::*;
892    const OA: u8 = BCDDOp::And as u8;
893    const OX: u8 = BCDDOp::Xor as u8;
894
895    const {
896        assert!(
897            (Q == BCDDOp::Forall as u8 && QN == BCDDOp::Exist as u8)
898                || (Q == BCDDOp::Exist as u8 && QN == BCDDOp::Forall as u8)
899        );
900    }
901
902    match op {
903        And => apply_quant::<M, R, Q, OA>(manager, rec, f, g, vars),
904        Or => {
905            let tmp = apply_quant::<M, R, QN, OA>(manager, rec, not(&f), not(&g), vars)?;
906            Ok(not_owned(tmp))
907        }
908        Xor => apply_quant::<M, R, Q, OX>(manager, rec, f, g, vars),
909        Equiv => {
910            let tmp = apply_quant::<M, R, QN, OX>(manager, rec, f, g, vars)?;
911            Ok(not_owned(tmp))
912        }
913        Nand => {
914            let tmp = apply_quant::<M, R, QN, OA>(manager, rec, f, g, vars)?;
915            Ok(not_owned(tmp))
916        }
917        Nor => apply_quant::<M, R, Q, OA>(manager, rec, not(&f), not(&g), vars),
918        Imp => {
919            let tmp = apply_quant::<M, R, QN, OA>(manager, rec, f, not(&g), vars)?;
920            Ok(not_owned(tmp))
921        }
922        ImpStrict => apply_quant::<M, R, Q, OA>(manager, rec, not(&f), g, vars),
923    }
924}
925
926/// Dynamic dispatcher for [`apply_quant()`] and unique quantification
927///
928/// In contrast to [`apply_quant()`], the operator is not a const but a runtime
929/// parameter.
930fn apply_quant_unique_dispatch<'a, M, R: Recursor<M>>(
931    manager: &'a M,
932    rec: R,
933    op: BooleanOperator,
934    f: Borrowed<M::Edge>,
935    g: Borrowed<M::Edge>,
936    vars: Borrowed<M::Edge>,
937) -> AllocResult<M::Edge>
938where
939    M: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag> + HasApplyCache<M, BCDDOp>,
940    M::InnerNode: HasLevel,
941{
942    use BooleanOperator::*;
943    const Q: u8 = BCDDOp::Unique as u8;
944    const OA: u8 = BCDDOp::And as u8;
945    const OX: u8 = BCDDOp::Xor as u8;
946    const ONA: u8 = BCDDOp::UniqueNand as u8;
947
948    match op {
949        And => apply_quant::<M, R, Q, OA>(manager, rec, f, g, vars),
950        Or => apply_quant::<M, R, Q, ONA>(manager, rec, not(&f), not(&g), vars),
951        Xor => apply_quant::<M, R, Q, OX>(manager, rec, f, g, vars),
952        Equiv => apply_quant::<M, R, Q, OX>(manager, rec, not(&f), g, vars),
953        Nand => apply_quant::<M, R, Q, ONA>(manager, rec, f, g, vars),
954        Nor => apply_quant::<M, R, Q, OA>(manager, rec, not(&f), not(&g), vars),
955        Imp => apply_quant::<M, R, Q, ONA>(manager, rec, f, not(&g), vars),
956        ImpStrict => apply_quant::<M, R, Q, OA>(manager, rec, not(&f), g, vars),
957    }
958}
959
960// --- Function Interface ------------------------------------------------------
961
962/// Workaround for https://github.com/rust-lang/rust/issues/49601
963trait HasBCDDOpApplyCache<M: Manager>: HasApplyCache<M, BCDDOp> {}
964impl<M: Manager + HasApplyCache<M, BCDDOp>> HasBCDDOpApplyCache<M> for M {}
965
966/// Boolean function backed by a complement edge binary decision diagram
967#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Function, Debug)]
968#[repr(transparent)]
969pub struct BCDDFunction<F: Function>(F);
970
971impl<F: Function> From<F> for BCDDFunction<F> {
972    #[inline(always)]
973    fn from(value: F) -> Self {
974        BCDDFunction(value)
975    }
976}
977
978impl<F: Function> BCDDFunction<F> {
979    /// Convert `self` into the underlying [`Function`]
980    #[inline(always)]
981    pub fn into_inner(self) -> F {
982        self.0
983    }
984}
985
986impl<F: Function> FunctionSubst for BCDDFunction<F>
987where
988    for<'id> F::Manager<'id>:
989        Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag> + HasBCDDOpApplyCache<F::Manager<'id>>,
990    for<'id> <F::Manager<'id> as Manager>::InnerNode: HasLevel,
991{
992    fn substitute_edge<'id, 'a>(
993        manager: &'a Self::Manager<'id>,
994        edge: &'a EdgeOfFunc<'id, Self>,
995        substitution: impl oxidd_core::util::Substitution<
996            Var = Borrowed<'a, EdgeOfFunc<'id, Self>>,
997            Replacement = Borrowed<'a, EdgeOfFunc<'id, Self>>,
998        >,
999    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1000        let rec = SequentialRecursor;
1001        let subst = substitute_prepare(manager, substitution.pairs())?;
1002        substitute(manager, rec, edge.borrowed(), &subst, substitution.id())
1003    }
1004}
1005
1006impl<F: Function> BooleanFunction for BCDDFunction<F>
1007where
1008    for<'id> F::Manager<'id>:
1009        Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag> + HasBCDDOpApplyCache<F::Manager<'id>>,
1010    for<'id> <F::Manager<'id> as Manager>::InnerNode: HasLevel,
1011{
1012    #[inline]
1013    fn new_var<'id>(manager: &mut Self::Manager<'id>) -> AllocResult<Self> {
1014        let t = get_terminal(manager, true);
1015        let e = get_terminal(manager, false);
1016        let edge = manager.add_level(|level| InnerNode::new(level, [t, e]))?;
1017        Ok(Self::from_edge(manager, edge))
1018    }
1019
1020    #[inline]
1021    fn f_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self> {
1022        get_terminal(manager, false)
1023    }
1024    #[inline]
1025    fn t_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self> {
1026        get_terminal(manager, true)
1027    }
1028
1029    #[inline]
1030    fn not_edge<'id>(
1031        manager: &Self::Manager<'id>,
1032        edge: &EdgeOfFunc<'id, Self>,
1033    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1034        Ok(not_owned(manager.clone_edge(edge)))
1035    }
1036    #[inline]
1037    fn not_edge_owned<'id>(
1038        _manager: &Self::Manager<'id>,
1039        edge: EdgeOfFunc<'id, Self>,
1040    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1041        Ok(not_owned(edge))
1042    }
1043
1044    #[inline]
1045    fn and_edge<'id>(
1046        manager: &Self::Manager<'id>,
1047        lhs: &EdgeOfFunc<'id, Self>,
1048        rhs: &EdgeOfFunc<'id, Self>,
1049    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1050        let rec = SequentialRecursor;
1051        apply_and(manager, rec, lhs.borrowed(), rhs.borrowed())
1052    }
1053    #[inline]
1054    fn or_edge<'id>(
1055        manager: &Self::Manager<'id>,
1056        lhs: &EdgeOfFunc<'id, Self>,
1057        rhs: &EdgeOfFunc<'id, Self>,
1058    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1059        Ok(not_owned(Self::nor_edge(manager, lhs, rhs)?))
1060    }
1061    #[inline]
1062    fn nand_edge<'id>(
1063        manager: &Self::Manager<'id>,
1064        lhs: &EdgeOfFunc<'id, Self>,
1065        rhs: &EdgeOfFunc<'id, Self>,
1066    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1067        Ok(not_owned(Self::and_edge(manager, lhs, rhs)?))
1068    }
1069    #[inline]
1070    fn nor_edge<'id>(
1071        manager: &Self::Manager<'id>,
1072        lhs: &EdgeOfFunc<'id, Self>,
1073        rhs: &EdgeOfFunc<'id, Self>,
1074    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1075        let rec = SequentialRecursor;
1076        apply_and(manager, rec, not(lhs), not(rhs))
1077    }
1078    #[inline]
1079    fn xor_edge<'id>(
1080        manager: &Self::Manager<'id>,
1081        lhs: &EdgeOfFunc<'id, Self>,
1082        rhs: &EdgeOfFunc<'id, Self>,
1083    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1084        let rec = SequentialRecursor;
1085        apply_bin::<_, _, { BCDDOp::Xor as u8 }>(manager, rec, lhs.borrowed(), rhs.borrowed())
1086    }
1087    #[inline]
1088    fn equiv_edge<'id>(
1089        manager: &Self::Manager<'id>,
1090        lhs: &EdgeOfFunc<'id, Self>,
1091        rhs: &EdgeOfFunc<'id, Self>,
1092    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1093        Ok(not_owned(Self::xor_edge(manager, lhs, rhs)?))
1094    }
1095    #[inline]
1096    fn imp_edge<'id>(
1097        manager: &Self::Manager<'id>,
1098        lhs: &EdgeOfFunc<'id, Self>,
1099        rhs: &EdgeOfFunc<'id, Self>,
1100    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1101        let rec = SequentialRecursor;
1102        Ok(not_owned(apply_and(
1103            manager,
1104            rec,
1105            lhs.borrowed(),
1106            not(rhs),
1107        )?))
1108    }
1109    #[inline]
1110    fn imp_strict_edge<'id>(
1111        manager: &Self::Manager<'id>,
1112        lhs: &EdgeOfFunc<'id, Self>,
1113        rhs: &EdgeOfFunc<'id, Self>,
1114    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1115        let rec = SequentialRecursor;
1116        apply_and(manager, rec, not(lhs), rhs.borrowed())
1117    }
1118
1119    #[inline]
1120    fn ite_edge<'id>(
1121        manager: &Self::Manager<'id>,
1122        if_edge: &EdgeOfFunc<'id, Self>,
1123        then_edge: &EdgeOfFunc<'id, Self>,
1124        else_edge: &EdgeOfFunc<'id, Self>,
1125    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1126        apply_ite(
1127            manager,
1128            SequentialRecursor,
1129            if_edge.borrowed(),
1130            then_edge.borrowed(),
1131            else_edge.borrowed(),
1132        )
1133    }
1134
1135    #[inline]
1136    fn sat_count_edge<'id, N: SatCountNumber, S: BuildHasher>(
1137        manager: &Self::Manager<'id>,
1138        edge: &EdgeOfFunc<'id, Self>,
1139        vars: LevelNo,
1140        cache: &mut SatCountCache<N, S>,
1141    ) -> N {
1142        fn inner<M, N: SatCountNumber, S: BuildHasher>(
1143            manager: &M,
1144            e: Borrowed<M::Edge>,
1145            terminal_val: &N,
1146            cache: &mut SatCountCache<N, S>,
1147        ) -> N
1148        where
1149            M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal>,
1150        {
1151            let node = match manager.get_node(&e) {
1152                Node::Inner(node) => node,
1153                Node::Terminal(_) => return terminal_val.clone(),
1154            };
1155
1156            // query cache
1157            let node_id = e.node_id();
1158            if let Some(n) = cache.map.get(&node_id) {
1159                return n.clone();
1160            }
1161
1162            // recursive case
1163            let mut iter = node.children().map(|c| {
1164                let n = inner(manager, c.borrowed(), terminal_val, cache);
1165                match c.tag() {
1166                    EdgeTag::None => n,
1167                    EdgeTag::Complemented => {
1168                        let mut res = terminal_val.clone();
1169                        res -= &n;
1170                        res
1171                    }
1172                }
1173            });
1174
1175            let mut n = iter.next().unwrap();
1176            n += &iter.next().unwrap();
1177            debug_assert!(iter.next().is_none());
1178            n >>= 1u32;
1179            cache.map.insert(node_id, n.clone());
1180            n
1181        }
1182
1183        // This function does not use the identity `|f| = num_vars - |f'|` to
1184        // avoid rounding issues
1185        fn inner_floating<M, N, S>(
1186            manager: &M,
1187            e: Borrowed<M::Edge>,
1188            terminal_val: &N,
1189            cache: &mut SatCountCache<N, S>,
1190        ) -> N
1191        where
1192            M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal>,
1193            N: SatCountNumber,
1194            S: BuildHasher,
1195        {
1196            let tag = e.tag();
1197            let node = match manager.get_node(&e) {
1198                Node::Inner(node) => node,
1199                Node::Terminal(_) if tag == EdgeTag::None => return terminal_val.clone(),
1200                Node::Terminal(_) => return N::from(0u32),
1201            };
1202            // MSB of NodeIDs is reserved [for us :)]
1203            let node_id = e.node_id() | ((tag as NodeID) << (NodeID::BITS - 1));
1204            if let Some(n) = cache.map.get(&node_id) {
1205                return n.clone();
1206            }
1207            let (e0, e1) = collect_cofactors(tag, node);
1208            let mut n = inner_floating(manager, e0, terminal_val, cache);
1209            n += &inner_floating(manager, e1, terminal_val, cache);
1210            n >>= 1u32;
1211            cache.map.insert(node_id, n.clone());
1212            n
1213        }
1214
1215        cache.clear_if_invalid(manager, vars);
1216
1217        if N::FLOATING_POINT {
1218            let mut terminal_val = N::from(1u32);
1219            let scale_exp = (-N::MIN_EXP) as u32;
1220            terminal_val <<= if vars >= scale_exp {
1221                // scale down to increase the precision if we have many variables
1222                vars - scale_exp
1223            } else {
1224                vars
1225            };
1226            let mut res = inner_floating(manager, edge.borrowed(), &terminal_val, cache);
1227            if vars >= scale_exp {
1228                res <<= scale_exp; // scale up again
1229            }
1230            res
1231        } else {
1232            let mut terminal_val = N::from(1u32);
1233            terminal_val <<= vars;
1234            let n = inner(manager, edge.borrowed(), &terminal_val, cache);
1235            match edge.tag() {
1236                EdgeTag::None => n,
1237                EdgeTag::Complemented => {
1238                    terminal_val -= &n;
1239                    terminal_val
1240                }
1241            }
1242        }
1243    }
1244
1245    #[inline]
1246    fn pick_cube_edge<'id, 'a, I>(
1247        manager: &'a Self::Manager<'id>,
1248        edge: &'a EdgeOfFunc<'id, Self>,
1249        order: impl IntoIterator<IntoIter = I>,
1250        choice: impl FnMut(&Self::Manager<'id>, &EdgeOfFunc<'id, Self>, LevelNo) -> bool,
1251    ) -> Option<Vec<OptBool>>
1252    where
1253        I: ExactSizeIterator<Item = &'a EdgeOfFunc<'id, Self>>,
1254    {
1255        #[inline] // this function is tail-recursive
1256        fn inner<M: Manager<EdgeTag = EdgeTag>>(
1257            manager: &M,
1258            edge: Borrowed<M::Edge>,
1259            cube: &mut [OptBool],
1260            mut choice: impl FnMut(&M, &M::Edge, LevelNo) -> bool,
1261        ) where
1262            M::InnerNode: HasLevel,
1263        {
1264            let Node::Inner(node) = manager.get_node(&edge) else {
1265                return;
1266            };
1267            let tag = edge.tag();
1268            let level = node.level();
1269            let (t, e) = collect_cofactors(tag, node);
1270            let c = if is_false(manager, &t) {
1271                false
1272            } else if is_false(manager, &e) {
1273                true
1274            } else {
1275                choice(manager, &edge, level)
1276            };
1277            cube[level as usize] = OptBool::from(c);
1278            inner(manager, if c { t } else { e }, cube, choice);
1279        }
1280
1281        let order = order.into_iter();
1282        debug_assert!(
1283            order.len() == 0 || order.len() == manager.num_levels() as usize,
1284            "order must be empty or contain all variables"
1285        );
1286
1287        if manager.get_node(edge).is_any_terminal() {
1288            return match edge.tag() {
1289                EdgeTag::None => Some(vec![OptBool::None; manager.num_levels() as usize]),
1290                EdgeTag::Complemented => None,
1291            };
1292        }
1293
1294        let mut cube = vec![OptBool::None; manager.num_levels() as usize];
1295        inner(manager, edge.borrowed(), &mut cube, choice);
1296
1297        Some(if order.len() == 0 {
1298            cube
1299        } else {
1300            order
1301                .map(|e| cube[manager.get_node(e).unwrap_inner().level() as usize])
1302                .collect()
1303        })
1304    }
1305
1306    #[inline]
1307    fn pick_cube_dd_edge<'id>(
1308        manager: &Self::Manager<'id>,
1309        edge: &EdgeOfFunc<'id, Self>,
1310        choice: impl FnMut(&Self::Manager<'id>, &EdgeOfFunc<'id, Self>, LevelNo) -> bool,
1311    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1312        fn inner<M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal>>(
1313            manager: &M,
1314            edge: Borrowed<M::Edge>,
1315            mut choice: impl FnMut(&M, &M::Edge, LevelNo) -> bool,
1316        ) -> AllocResult<M::Edge>
1317        where
1318            M::InnerNode: HasLevel,
1319        {
1320            let Node::Inner(node) = manager.get_node(&edge) else {
1321                return Ok(manager.clone_edge(&edge));
1322            };
1323
1324            let (t, e) = collect_cofactors(edge.tag(), node);
1325            let level = node.level();
1326            let c = if is_false(manager, &t) {
1327                false
1328            } else if is_false(manager, &e) {
1329                true
1330            } else {
1331                choice(manager, &edge, level)
1332            };
1333
1334            let sub = inner(manager, if c { t } else { e }, choice)?;
1335            add_literal_to_cube(manager, sub, level, c)
1336        }
1337
1338        inner(manager, edge.borrowed(), choice)
1339    }
1340
1341    fn pick_cube_dd_set_edge<'id>(
1342        manager: &Self::Manager<'id>,
1343        edge: &EdgeOfFunc<'id, Self>,
1344        literal_set: &EdgeOfFunc<'id, Self>,
1345    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1346        fn inner<M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal>>(
1347            manager: &M,
1348            edge: Borrowed<M::Edge>,
1349            literal_set: Borrowed<M::Edge>,
1350        ) -> AllocResult<M::Edge>
1351        where
1352            M::InnerNode: HasLevel,
1353        {
1354            let Node::Inner(node) = manager.get_node(&edge) else {
1355                return Ok(manager.clone_edge(&edge));
1356            };
1357            let level = node.level();
1358
1359            let literal_set = crate::set_pop(manager, literal_set, level);
1360            let (literal_set, c) = match manager.get_node(&literal_set) {
1361                Node::Inner(node) if node.level() == level => {
1362                    let (t, e) = collect_cofactors(literal_set.tag(), node);
1363                    if is_false(manager, &e) {
1364                        (e, true)
1365                    } else {
1366                        (t, false)
1367                    }
1368                }
1369                _ => (literal_set, false),
1370            };
1371
1372            let (t, e) = collect_cofactors(edge.tag(), node);
1373            let c = if is_false(manager, &t) {
1374                false
1375            } else if is_false(manager, &e) {
1376                true
1377            } else {
1378                c
1379            };
1380
1381            let sub = inner(manager, if c { t } else { e }, literal_set)?;
1382            add_literal_to_cube(manager, sub, level, c)
1383        }
1384
1385        inner(manager, edge.borrowed(), literal_set.borrowed())
1386    }
1387
1388    #[inline]
1389    fn eval_edge<'id, 'a>(
1390        manager: &'a Self::Manager<'id>,
1391        edge: &'a EdgeOfFunc<'id, Self>,
1392        args: impl IntoIterator<Item = (Borrowed<'a, EdgeOfFunc<'id, Self>>, bool)>,
1393    ) -> bool {
1394        let mut values = BitVec::new();
1395        values.resize(manager.num_levels() as usize, false);
1396        for (edge, val) in args {
1397            let node = manager
1398                .get_node(&edge)
1399                .expect_inner("edges in `args` must refer to inner nodes");
1400            values.set(node.level() as usize, val);
1401        }
1402
1403        #[inline] // this function is tail-recursive
1404        fn inner<M>(manager: &M, edge: Borrowed<M::Edge>, complement: bool, values: BitVec) -> bool
1405        where
1406            M: Manager<EdgeTag = EdgeTag>,
1407            M::InnerNode: HasLevel,
1408        {
1409            let complement = complement ^ (edge.tag() == EdgeTag::Complemented);
1410            match manager.get_node(&edge) {
1411                Node::Inner(node) => {
1412                    let edge = node.child((!values[node.level() as usize]) as usize);
1413                    inner(manager, edge, complement, values)
1414                }
1415                Node::Terminal(_) => !complement,
1416            }
1417        }
1418
1419        inner(manager, edge.borrowed(), false, values)
1420    }
1421}
1422
1423impl<F: Function> BooleanFunctionQuant for BCDDFunction<F>
1424where
1425    for<'id> F::Manager<'id>:
1426        Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag> + HasBCDDOpApplyCache<F::Manager<'id>>,
1427    for<'id> <F::Manager<'id> as Manager>::InnerNode: HasLevel,
1428{
1429    #[inline]
1430    fn restrict_edge<'id>(
1431        manager: &Self::Manager<'id>,
1432        root: &EdgeOfFunc<'id, Self>,
1433        vars: &EdgeOfFunc<'id, Self>,
1434    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1435        let rec = SequentialRecursor;
1436        restrict(manager, rec, root.borrowed(), vars.borrowed())
1437    }
1438
1439    #[inline]
1440    fn forall_edge<'id>(
1441        manager: &Self::Manager<'id>,
1442        root: &EdgeOfFunc<'id, Self>,
1443        vars: &EdgeOfFunc<'id, Self>,
1444    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1445        let rec = SequentialRecursor;
1446        quant::<_, _, { BCDDOp::Forall as u8 }>(manager, rec, root.borrowed(), vars.borrowed())
1447    }
1448    #[inline]
1449    fn exist_edge<'id>(
1450        manager: &Self::Manager<'id>,
1451        root: &EdgeOfFunc<'id, Self>,
1452        vars: &EdgeOfFunc<'id, Self>,
1453    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1454        let rec = SequentialRecursor;
1455        quant::<_, _, { BCDDOp::Exist as u8 }>(manager, rec, root.borrowed(), vars.borrowed())
1456    }
1457    #[inline]
1458    fn unique_edge<'id>(
1459        manager: &Self::Manager<'id>,
1460        root: &EdgeOfFunc<'id, Self>,
1461        vars: &EdgeOfFunc<'id, Self>,
1462    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1463        let rec = SequentialRecursor;
1464        quant::<_, _, { BCDDOp::Unique as u8 }>(manager, rec, root.borrowed(), vars.borrowed())
1465    }
1466
1467    #[inline]
1468    fn apply_forall_edge<'id>(
1469        manager: &Self::Manager<'id>,
1470        op: BooleanOperator,
1471        lhs: &EdgeOfFunc<'id, Self>,
1472        rhs: &EdgeOfFunc<'id, Self>,
1473        vars: &EdgeOfFunc<'id, Self>,
1474    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1475        apply_quant_dispatch::<_, _, { BCDDOp::Forall as u8 }, { BCDDOp::Exist as u8 }>(
1476            manager,
1477            SequentialRecursor,
1478            op,
1479            lhs.borrowed(),
1480            rhs.borrowed(),
1481            vars.borrowed(),
1482        )
1483    }
1484    #[inline]
1485    fn apply_exist_edge<'id>(
1486        manager: &Self::Manager<'id>,
1487        op: BooleanOperator,
1488        lhs: &EdgeOfFunc<'id, Self>,
1489        rhs: &EdgeOfFunc<'id, Self>,
1490        vars: &EdgeOfFunc<'id, Self>,
1491    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1492        apply_quant_dispatch::<_, _, { BCDDOp::Exist as u8 }, { BCDDOp::Forall as u8 }>(
1493            manager,
1494            SequentialRecursor,
1495            op,
1496            lhs.borrowed(),
1497            rhs.borrowed(),
1498            vars.borrowed(),
1499        )
1500    }
1501    #[inline]
1502    fn apply_unique_edge<'id>(
1503        manager: &Self::Manager<'id>,
1504        op: BooleanOperator,
1505        lhs: &EdgeOfFunc<'id, Self>,
1506        rhs: &EdgeOfFunc<'id, Self>,
1507        vars: &EdgeOfFunc<'id, Self>,
1508    ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1509        let rec = SequentialRecursor;
1510        let (lhs, rhs, vars) = (lhs.borrowed(), rhs.borrowed(), vars.borrowed());
1511        apply_quant_unique_dispatch(manager, rec, op, lhs, rhs, vars)
1512    }
1513}
1514
1515impl<F: Function, T: Tag> DotStyle<T> for BCDDFunction<F> {}
1516
1517#[cfg(feature = "multi-threading")]
1518pub mod mt {
1519    use oxidd_core::HasWorkers;
1520
1521    use crate::recursor::mt::ParallelRecursor;
1522
1523    use super::*;
1524
1525    /// Boolean function backed by a complement edge binary decision diagram
1526    #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Function, Debug)]
1527    #[repr(transparent)]
1528    pub struct BCDDFunctionMT<F: Function>(F);
1529
1530    impl<F: Function> From<F> for BCDDFunctionMT<F> {
1531        #[inline(always)]
1532        fn from(value: F) -> Self {
1533            BCDDFunctionMT(value)
1534        }
1535    }
1536
1537    impl<F: Function> BCDDFunctionMT<F>
1538    where
1539        for<'id> F::Manager<'id>: HasWorkers,
1540    {
1541        /// Convert `self` into the underlying [`Function`]
1542        #[inline(always)]
1543        pub fn into_inner(self) -> F {
1544            self.0
1545        }
1546    }
1547
1548    impl<F: Function> FunctionSubst for BCDDFunctionMT<F>
1549    where
1550        for<'id> F::Manager<'id>: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag>
1551            + HasBCDDOpApplyCache<F::Manager<'id>>
1552            + HasWorkers,
1553        for<'id> <F::Manager<'id> as Manager>::InnerNode: HasLevel,
1554        for<'id> <F::Manager<'id> as Manager>::Edge: Send + Sync,
1555    {
1556        fn substitute_edge<'id, 'a>(
1557            manager: &'a Self::Manager<'id>,
1558            edge: &'a EdgeOfFunc<'id, Self>,
1559            substitution: impl oxidd_core::util::Substitution<
1560                Var = Borrowed<'a, EdgeOfFunc<'id, Self>>,
1561                Replacement = Borrowed<'a, EdgeOfFunc<'id, Self>>,
1562            >,
1563        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1564            let subst = substitute_prepare(manager, substitution.pairs())?;
1565            let edge = edge.borrowed();
1566            let cache_id = substitution.id();
1567            let rec = ParallelRecursor::new(manager);
1568            substitute(manager, rec, edge, &subst, cache_id)
1569        }
1570    }
1571
1572    impl<F: Function> BooleanFunction for BCDDFunctionMT<F>
1573    where
1574        for<'id> F::Manager<'id>: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag>
1575            + HasBCDDOpApplyCache<F::Manager<'id>>
1576            + HasWorkers,
1577        for<'id> <F::Manager<'id> as Manager>::InnerNode: HasLevel,
1578        for<'id> <F::Manager<'id> as Manager>::Edge: Send + Sync,
1579    {
1580        #[inline]
1581        fn new_var<'id>(manager: &mut Self::Manager<'id>) -> AllocResult<Self> {
1582            let t = get_terminal(manager, true);
1583            let e = get_terminal(manager, false);
1584            let edge = manager.add_level(|level| InnerNode::new(level, [t, e]))?;
1585            Ok(Self::from_edge(manager, edge))
1586        }
1587
1588        #[inline]
1589        fn f_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self> {
1590            get_terminal(manager, false)
1591        }
1592        #[inline]
1593        fn t_edge<'id>(manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self> {
1594            get_terminal(manager, true)
1595        }
1596
1597        #[inline]
1598        fn not_edge<'id>(
1599            manager: &Self::Manager<'id>,
1600            edge: &EdgeOfFunc<'id, Self>,
1601        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1602            Ok(not_owned(manager.clone_edge(edge)))
1603        }
1604        #[inline]
1605        fn not_edge_owned<'id>(
1606            _manager: &Self::Manager<'id>,
1607            edge: EdgeOfFunc<'id, Self>,
1608        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1609            Ok(not_owned(edge))
1610        }
1611
1612        #[inline]
1613        fn and_edge<'id>(
1614            manager: &Self::Manager<'id>,
1615            lhs: &EdgeOfFunc<'id, Self>,
1616            rhs: &EdgeOfFunc<'id, Self>,
1617        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1618            let (lhs, rhs) = (lhs.borrowed(), rhs.borrowed());
1619            apply_and(manager, ParallelRecursor::new(manager), lhs, rhs)
1620        }
1621        #[inline]
1622        fn or_edge<'id>(
1623            manager: &Self::Manager<'id>,
1624            lhs: &EdgeOfFunc<'id, Self>,
1625            rhs: &EdgeOfFunc<'id, Self>,
1626        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1627            let (nl, nr) = (not(lhs), not(rhs));
1628            let nor = apply_and(manager, ParallelRecursor::new(manager), nl, nr)?;
1629            Ok(not_owned(nor))
1630        }
1631        #[inline]
1632        fn nand_edge<'id>(
1633            manager: &Self::Manager<'id>,
1634            lhs: &EdgeOfFunc<'id, Self>,
1635            rhs: &EdgeOfFunc<'id, Self>,
1636        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1637            let (lhs, rhs) = (lhs.borrowed(), rhs.borrowed());
1638            let and = apply_and(manager, ParallelRecursor::new(manager), lhs, rhs)?;
1639            Ok(not_owned(and))
1640        }
1641        #[inline]
1642        fn nor_edge<'id>(
1643            manager: &Self::Manager<'id>,
1644            lhs: &EdgeOfFunc<'id, Self>,
1645            rhs: &EdgeOfFunc<'id, Self>,
1646        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1647            let (nl, nr) = (not(lhs), not(rhs));
1648            apply_and(manager, ParallelRecursor::new(manager), nl, nr)
1649        }
1650        #[inline]
1651        fn xor_edge<'id>(
1652            manager: &Self::Manager<'id>,
1653            lhs: &EdgeOfFunc<'id, Self>,
1654            rhs: &EdgeOfFunc<'id, Self>,
1655        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1656            let (lhs, rhs) = (lhs.borrowed(), rhs.borrowed());
1657            let rec = ParallelRecursor::new(manager);
1658            apply_bin::<_, _, { BCDDOp::Xor as u8 }>(manager, rec, lhs, rhs)
1659        }
1660        #[inline]
1661        fn equiv_edge<'id>(
1662            manager: &Self::Manager<'id>,
1663            lhs: &EdgeOfFunc<'id, Self>,
1664            rhs: &EdgeOfFunc<'id, Self>,
1665        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1666            let (lhs, rhs) = (lhs.borrowed(), rhs.borrowed());
1667            let rec = ParallelRecursor::new(manager);
1668            Ok(not_owned(apply_bin::<_, _, { BCDDOp::Xor as u8 }>(
1669                manager, rec, lhs, rhs,
1670            )?))
1671        }
1672        #[inline]
1673        fn imp_edge<'id>(
1674            manager: &Self::Manager<'id>,
1675            lhs: &EdgeOfFunc<'id, Self>,
1676            rhs: &EdgeOfFunc<'id, Self>,
1677        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1678            // a → b ≡ ¬a ∨ b ≡ ¬(a ∧ ¬b)
1679            let (lhs, nr) = (lhs.borrowed(), not(rhs));
1680            let not_imp = apply_and(manager, ParallelRecursor::new(manager), lhs, nr)?;
1681            Ok(not_owned(not_imp))
1682        }
1683        #[inline]
1684        fn imp_strict_edge<'id>(
1685            manager: &Self::Manager<'id>,
1686            lhs: &EdgeOfFunc<'id, Self>,
1687            rhs: &EdgeOfFunc<'id, Self>,
1688        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1689            let (nl, rhs) = (not(lhs), rhs.borrowed());
1690            apply_and(manager, ParallelRecursor::new(manager), nl, rhs)
1691        }
1692
1693        #[inline]
1694        fn ite_edge<'id>(
1695            manager: &Self::Manager<'id>,
1696            f: &EdgeOfFunc<'id, Self>,
1697            g: &EdgeOfFunc<'id, Self>,
1698            h: &EdgeOfFunc<'id, Self>,
1699        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1700            let (f, g, h) = (f.borrowed(), g.borrowed(), h.borrowed());
1701            apply_ite(manager, ParallelRecursor::new(manager), f, g, h)
1702        }
1703
1704        #[inline]
1705        fn sat_count_edge<'id, N: SatCountNumber, S: std::hash::BuildHasher>(
1706            manager: &Self::Manager<'id>,
1707            edge: &EdgeOfFunc<'id, Self>,
1708            vars: LevelNo,
1709            cache: &mut SatCountCache<N, S>,
1710        ) -> N {
1711            BCDDFunction::<F>::sat_count_edge(manager, edge, vars, cache)
1712        }
1713
1714        #[inline]
1715        fn pick_cube_edge<'id, 'a, I>(
1716            manager: &'a Self::Manager<'id>,
1717            edge: &'a EdgeOfFunc<'id, Self>,
1718            order: impl IntoIterator<IntoIter = I>,
1719            choice: impl FnMut(&Self::Manager<'id>, &EdgeOfFunc<'id, Self>, LevelNo) -> bool,
1720        ) -> Option<Vec<OptBool>>
1721        where
1722            I: ExactSizeIterator<Item = &'a EdgeOfFunc<'id, Self>>,
1723        {
1724            BCDDFunction::<F>::pick_cube_edge(manager, edge, order, choice)
1725        }
1726        #[inline]
1727        fn pick_cube_dd_edge<'id>(
1728            manager: &Self::Manager<'id>,
1729            edge: &EdgeOfFunc<'id, Self>,
1730            choice: impl FnMut(&Self::Manager<'id>, &EdgeOfFunc<'id, Self>, LevelNo) -> bool,
1731        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1732            BCDDFunction::<F>::pick_cube_dd_edge(manager, edge, choice)
1733        }
1734        #[inline]
1735        fn pick_cube_dd_set_edge<'id>(
1736            manager: &Self::Manager<'id>,
1737            edge: &EdgeOfFunc<'id, Self>,
1738            literal_set: &EdgeOfFunc<'id, Self>,
1739        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1740            BCDDFunction::<F>::pick_cube_dd_set_edge(manager, edge, literal_set)
1741        }
1742
1743        #[inline]
1744        fn eval_edge<'id, 'a>(
1745            manager: &'a Self::Manager<'id>,
1746            edge: &'a EdgeOfFunc<'id, Self>,
1747            args: impl IntoIterator<Item = (Borrowed<'a, EdgeOfFunc<'id, Self>>, bool)>,
1748        ) -> bool {
1749            BCDDFunction::<F>::eval_edge(manager, edge, args)
1750        }
1751    }
1752
1753    impl<F: Function> BooleanFunctionQuant for BCDDFunctionMT<F>
1754    where
1755        for<'id> F::Manager<'id>: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag>
1756            + HasBCDDOpApplyCache<F::Manager<'id>>
1757            + HasWorkers,
1758        for<'id> <F::Manager<'id> as Manager>::InnerNode: HasLevel,
1759        for<'id> <F::Manager<'id> as Manager>::Edge: Send + Sync,
1760    {
1761        #[inline]
1762        fn restrict_edge<'id>(
1763            manager: &Self::Manager<'id>,
1764            root: &EdgeOfFunc<'id, Self>,
1765            vars: &EdgeOfFunc<'id, Self>,
1766        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1767            let (root, vars) = (root.borrowed(), vars.borrowed());
1768            restrict(manager, ParallelRecursor::new(manager), root, vars)
1769        }
1770
1771        #[inline]
1772        fn forall_edge<'id>(
1773            manager: &Self::Manager<'id>,
1774            root: &EdgeOfFunc<'id, Self>,
1775            vars: &EdgeOfFunc<'id, Self>,
1776        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1777            let (root, vars) = (root.borrowed(), vars.borrowed());
1778            let rec = ParallelRecursor::new(manager);
1779            quant::<_, _, { BCDDOp::Forall as u8 }>(manager, rec, root, vars)
1780        }
1781        #[inline]
1782        fn exist_edge<'id>(
1783            manager: &Self::Manager<'id>,
1784            root: &EdgeOfFunc<'id, Self>,
1785            vars: &EdgeOfFunc<'id, Self>,
1786        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1787            let (root, vars) = (root.borrowed(), vars.borrowed());
1788            let rec = ParallelRecursor::new(manager);
1789            quant::<_, _, { BCDDOp::Exist as u8 }>(manager, rec, root, vars)
1790        }
1791        #[inline]
1792        fn unique_edge<'id>(
1793            manager: &Self::Manager<'id>,
1794            root: &EdgeOfFunc<'id, Self>,
1795            vars: &EdgeOfFunc<'id, Self>,
1796        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1797            let (root, vars) = (root.borrowed(), vars.borrowed());
1798            let rec = ParallelRecursor::new(manager);
1799            quant::<_, _, { BCDDOp::Unique as u8 }>(manager, rec, root, vars)
1800        }
1801
1802        #[inline]
1803        fn apply_forall_edge<'id>(
1804            manager: &Self::Manager<'id>,
1805            op: BooleanOperator,
1806            lhs: &EdgeOfFunc<'id, Self>,
1807            rhs: &EdgeOfFunc<'id, Self>,
1808            vars: &EdgeOfFunc<'id, Self>,
1809        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1810            let (lhs, rhs, vars) = (lhs.borrowed(), rhs.borrowed(), vars.borrowed());
1811            apply_quant_dispatch::<_, _, { BCDDOp::Forall as u8 }, { BCDDOp::Exist as u8 }>(
1812                manager,
1813                ParallelRecursor::new(manager),
1814                op,
1815                lhs,
1816                rhs,
1817                vars,
1818            )
1819        }
1820        #[inline]
1821        fn apply_exist_edge<'id>(
1822            manager: &Self::Manager<'id>,
1823            op: BooleanOperator,
1824            lhs: &EdgeOfFunc<'id, Self>,
1825            rhs: &EdgeOfFunc<'id, Self>,
1826            vars: &EdgeOfFunc<'id, Self>,
1827        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1828            let (lhs, rhs, vars) = (lhs.borrowed(), rhs.borrowed(), vars.borrowed());
1829            apply_quant_dispatch::<_, _, { BCDDOp::Exist as u8 }, { BCDDOp::Forall as u8 }>(
1830                manager,
1831                ParallelRecursor::new(manager),
1832                op,
1833                lhs,
1834                rhs,
1835                vars,
1836            )
1837        }
1838        #[inline]
1839        fn apply_unique_edge<'id>(
1840            manager: &Self::Manager<'id>,
1841            op: BooleanOperator,
1842            lhs: &EdgeOfFunc<'id, Self>,
1843            rhs: &EdgeOfFunc<'id, Self>,
1844            vars: &EdgeOfFunc<'id, Self>,
1845        ) -> AllocResult<EdgeOfFunc<'id, Self>> {
1846            let (lhs, rhs, vars) = (lhs.borrowed(), rhs.borrowed(), vars.borrowed());
1847            let rec = ParallelRecursor::new(manager);
1848            apply_quant_unique_dispatch(manager, rec, op, lhs, rhs, vars)
1849        }
1850    }
1851
1852    impl<F: Function, T: Tag> DotStyle<T> for BCDDFunctionMT<F> {}
1853}