oxidd_rules_bdd/complement_edge/
apply_rec.rs

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