oxidd_rules_bdd/complement_edge/
mod.rs

1//! Binary decision diagrams with complemented edges
2
3use std::fmt;
4use std::hash::Hash;
5use std::iter::FusedIterator;
6use std::marker::PhantomData;
7
8use oxidd_core::util::{AllocResult, Borrowed, EdgeDropGuard};
9use oxidd_core::{DiagramRules, Edge, HasLevel, InnerNode, LevelNo, Manager, Node, ReducedOrNew};
10use oxidd_derive::Countable;
11use oxidd_dump::dddmp::AsciiDisplay;
12
13use crate::stat;
14
15// spell-checker:ignore fnode,gnode
16
17mod apply_rec;
18
19// --- Edge Tag ----------------------------------------------------------------
20
21/// Edge tag in complement edge BDDs
22#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Countable)]
23#[repr(u8)]
24pub enum EdgeTag {
25    /// The edge's semantics is the semantics of the referenced node
26    #[default]
27    None,
28    /// The edge's semantics is the negation of the referenced node
29    Complemented,
30}
31
32impl std::ops::Not for EdgeTag {
33    type Output = EdgeTag;
34
35    #[inline]
36    fn not(self) -> EdgeTag {
37        match self {
38            EdgeTag::None => EdgeTag::Complemented,
39            EdgeTag::Complemented => EdgeTag::None,
40        }
41    }
42}
43
44impl std::ops::BitXor for EdgeTag {
45    type Output = EdgeTag;
46
47    #[inline]
48    fn bitxor(self, rhs: Self) -> EdgeTag {
49        use EdgeTag::*;
50        match (self, rhs) {
51            (None, None) => None,
52            (None, Complemented) => Complemented,
53            (Complemented, None) => Complemented,
54            (Complemented, Complemented) => None,
55        }
56    }
57}
58
59#[inline]
60#[must_use]
61fn not_owned<E: Edge<Tag = EdgeTag>>(e: E) -> E {
62    let tag = e.tag();
63    e.with_tag_owned(!tag)
64}
65
66#[inline]
67#[must_use]
68fn not<E: Edge<Tag = EdgeTag>>(e: &E) -> Borrowed<E> {
69    let tag = e.tag();
70    e.with_tag(!tag)
71}
72
73// --- Reduction Rules ---------------------------------------------------------
74
75/// [`DiagramRules`] for complement edge binary decision diagrams
76pub struct BCDDRules;
77
78impl<E: Edge<Tag = EdgeTag>, N: InnerNode<E>> DiagramRules<E, N, BCDDTerminal> for BCDDRules {
79    type Cofactors<'a>
80        = Cofactors<'a, E, N::ChildrenIter<'a>>
81    where
82        N: 'a,
83        E: 'a;
84
85    #[inline]
86    #[must_use]
87    fn reduce<M: Manager<Edge = E, InnerNode = N>>(
88        manager: &M,
89        level: LevelNo,
90        children: impl IntoIterator<Item = E>,
91    ) -> ReducedOrNew<E, N> {
92        let mut it = children.into_iter();
93        let t = it.next().unwrap();
94        let e = it.next().unwrap();
95        debug_assert!(it.next().is_none());
96
97        if t == e {
98            manager.drop_edge(e);
99            return ReducedOrNew::Reduced(t);
100        }
101
102        let tt = t.tag();
103        if tt == EdgeTag::Complemented {
104            let et = e.tag();
105            let node = N::new(
106                level,
107                [t.with_tag_owned(EdgeTag::None), e.with_tag_owned(!et)],
108            );
109            ReducedOrNew::New(node, EdgeTag::Complemented)
110        } else {
111            let node = N::new(level, [t, e]);
112            ReducedOrNew::New(node, EdgeTag::None)
113        }
114    }
115
116    #[inline]
117    #[must_use]
118    fn cofactors(tag: E::Tag, node: &N) -> Self::Cofactors<'_> {
119        Cofactors {
120            it: node.children(),
121            tag,
122            phantom: PhantomData,
123        }
124    }
125
126    #[inline]
127    fn cofactor(tag: E::Tag, node: &N, n: usize) -> Borrowed<E> {
128        let e = node.child(n);
129        if tag == EdgeTag::None {
130            e
131        } else {
132            let e_tag = e.tag();
133            e.edge_with_tag(!e_tag)
134        }
135    }
136}
137
138/// Iterator over the cofactors of a node in a complement edge BDD
139pub struct Cofactors<'a, E, I> {
140    it: I,
141    tag: EdgeTag,
142    phantom: PhantomData<Borrowed<'a, E>>,
143}
144
145impl<'a, E: Edge<Tag = EdgeTag> + 'a, I: Iterator<Item = Borrowed<'a, E>>> Iterator
146    for Cofactors<'a, E, I>
147{
148    type Item = Borrowed<'a, E>;
149
150    #[inline]
151    fn next(&mut self) -> Option<Self::Item> {
152        match (self.it.next(), self.tag) {
153            (Some(e), EdgeTag::None) => Some(e),
154            (Some(e), EdgeTag::Complemented) => {
155                let tag = !e.tag();
156                Some(Borrowed::edge_with_tag(e, tag))
157            }
158            (None, _) => None,
159        }
160    }
161
162    #[inline]
163    fn size_hint(&self) -> (usize, Option<usize>) {
164        self.it.size_hint()
165    }
166}
167
168impl<'a, E: Edge<Tag = EdgeTag>, I: FusedIterator<Item = Borrowed<'a, E>>> FusedIterator
169    for Cofactors<'a, E, I>
170{
171}
172
173impl<'a, E: Edge<Tag = EdgeTag>, I: ExactSizeIterator<Item = Borrowed<'a, E>>> ExactSizeIterator
174    for Cofactors<'a, E, I>
175{
176    #[inline]
177    fn len(&self) -> usize {
178        self.it.len()
179    }
180}
181
182/// Check if `edge` represents the function `⊥`
183#[inline]
184#[must_use]
185fn is_false<M: Manager<EdgeTag = EdgeTag>>(manager: &M, edge: &M::Edge) -> bool {
186    edge.tag() == EdgeTag::Complemented && manager.get_node(edge).is_any_terminal()
187}
188
189/// Collect the two cofactors `(then, else)` assuming that the incoming edge is
190/// tagged as `tag`
191#[inline]
192#[must_use]
193fn collect_cofactors<E: Edge<Tag = EdgeTag>, N: InnerNode<E>>(
194    tag: EdgeTag,
195    node: &N,
196) -> (Borrowed<E>, Borrowed<E>) {
197    debug_assert_eq!(N::ARITY, 2);
198    let mut it = BCDDRules::cofactors(tag, node);
199    let ft = it.next().unwrap();
200    let fe = it.next().unwrap();
201    debug_assert!(it.next().is_none());
202    (ft, fe)
203}
204
205/// Apply the reduction rules, creating a node in `manager` if necessary
206#[inline(always)]
207fn reduce<M>(
208    manager: &M,
209    level: LevelNo,
210    t: M::Edge,
211    e: M::Edge,
212    op: BCDDOp,
213) -> AllocResult<M::Edge>
214where
215    M: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag>,
216{
217    // We do not use `DiagramRules::reduce()` here, as the iterator is
218    // apparently not fully optimized away.
219    if t == e {
220        stat!(reduced op);
221        manager.drop_edge(e);
222        return Ok(t);
223    }
224
225    let tt = t.tag();
226    let (node, tag) = if tt == EdgeTag::Complemented {
227        let et = e.tag();
228        let node = M::InnerNode::new(
229            level,
230            [t.with_tag_owned(EdgeTag::None), e.with_tag_owned(!et)],
231        );
232        (node, EdgeTag::Complemented)
233    } else {
234        (M::InnerNode::new(level, [t, e]), EdgeTag::None)
235    };
236
237    Ok(oxidd_core::LevelView::get_or_insert(&mut manager.level(level), node)?.with_tag_owned(tag))
238}
239
240// --- Terminal Type -----------------------------------------------------------
241
242/// Terminal nodes in complement edge binary decision diagrams
243#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Countable, Debug)]
244pub struct BCDDTerminal;
245
246impl std::str::FromStr for BCDDTerminal {
247    type Err = std::convert::Infallible;
248
249    fn from_str(_s: &str) -> Result<Self, Self::Err> {
250        Ok(BCDDTerminal)
251    }
252}
253
254impl AsciiDisplay for BCDDTerminal {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
256        f.write_str("T")
257    }
258}
259
260impl fmt::Display for BCDDTerminal {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        f.write_str("⊤")
263    }
264}
265
266#[inline]
267#[must_use]
268fn get_terminal<M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal>>(
269    manager: &M,
270    val: bool,
271) -> M::Edge {
272    let t = manager.get_terminal(BCDDTerminal).unwrap();
273    if val {
274        t
275    } else {
276        t.with_tag_owned(EdgeTag::Complemented)
277    }
278}
279
280/// Terminal case for 'and'
281#[inline]
282#[must_use]
283fn terminal_and<'a, M>(
284    manager: &'a M,
285    f: &'a M::Edge,
286    g: &'a M::Edge,
287) -> NodesOrDone<'a, EdgeDropGuard<'a, M>, M::InnerNode>
288where
289    M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal>,
290{
291    use EdgeTag::*;
292    use Node::*;
293    use NodesOrDone::*;
294
295    let ft = f.tag();
296    let gt = g.tag();
297    let fu = f.with_tag(None);
298    let gu = g.with_tag(None);
299    if *fu == *gu {
300        if ft == gt {
301            return Done(EdgeDropGuard::new(manager, manager.clone_edge(g)));
302        }
303        return Done(EdgeDropGuard::new(manager, get_terminal(manager, false)));
304    }
305
306    let (h, tag) = match (manager.get_node(f), manager.get_node(g)) {
307        (Inner(fnode), Inner(gnode)) => return Nodes(fnode, gnode),
308        (Inner(_), Terminal(_)) => (f, gt),
309        (Terminal(_), Inner(_)) => (g, ft),
310        (Terminal(_), Terminal(_)) => {
311            let res = get_terminal(manager, ft == None && gt == None);
312            return Done(EdgeDropGuard::new(manager, res));
313        }
314    };
315    let res = if tag == Complemented {
316        get_terminal(manager, false)
317    } else {
318        manager.clone_edge(h)
319    };
320    Done(EdgeDropGuard::new(manager, res))
321}
322
323/// Terminal case for 'xor'
324#[inline]
325#[must_use]
326fn terminal_xor<'a, M>(
327    manager: &'a M,
328    f: &'a M::Edge,
329    g: &'a M::Edge,
330) -> NodesOrDone<'a, EdgeDropGuard<'a, M>, M::InnerNode>
331where
332    M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal>,
333{
334    use EdgeTag::*;
335    use Node::*;
336    use NodesOrDone::*;
337
338    let ft = f.tag();
339    let gt = g.tag();
340    let fu = f.with_tag(None);
341    let gu = g.with_tag(None);
342    if *fu == *gu {
343        return Done(EdgeDropGuard::new(manager, get_terminal(manager, ft != gt)));
344    }
345    let (h, tag) = match (manager.get_node(f), manager.get_node(g)) {
346        (Inner(fnode), Inner(gnode)) => return Nodes(fnode, gnode),
347        (Inner(_), Terminal(_)) => (f, gt),
348        (Terminal(_), Inner(_)) => (g, ft),
349        (Terminal(_), Terminal(_)) => {
350            return Done(EdgeDropGuard::new(manager, get_terminal(manager, ft != gt)))
351        }
352    };
353    let h = manager.clone_edge(h);
354    let h = if tag == Complemented { h } else { not_owned(h) };
355    Done(EdgeDropGuard::new(manager, h))
356}
357
358// --- Operations & Apply Implementation ---------------------------------------
359
360/// Native operators of this BDD implementation
361#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Countable, Debug)]
362#[repr(u8)]
363#[allow(missing_docs)]
364pub enum BCDDOp {
365    And,
366    Xor,
367
368    /// If-then-else
369    Ite,
370
371    Substitute,
372
373    Restrict,
374    /// Forall quantification
375    Forall,
376    /// Existential quantification
377    Exist,
378    /// Unique quantification
379    Unique,
380
381    ForallAnd,
382    ForallXor,
383    ExistAnd,
384    ExistXor,
385    UniqueAnd,
386    /// Usually, we don't need `⊼` since we have `∧` and a cheap `¬` operation.
387    /// However, `∃! v. ¬φ` is not equivalent to `¬∃! v. φ`, so we use
388    /// `∃! v. φ ⊼ ψ` (i.e., a special `⊼` operator) for the combined
389    /// apply-quantify algorithm.
390    UniqueNand,
391    UniqueXor,
392}
393
394impl BCDDOp {
395    const fn from_apply_quant(q: u8, op: u8) -> Self {
396        if q == BCDDOp::Forall as u8 {
397            match () {
398                _ if op == BCDDOp::And as u8 => BCDDOp::ForallAnd,
399                _ if op == BCDDOp::Xor as u8 => BCDDOp::ForallXor,
400                _ => panic!("invalid OP"),
401            }
402        } else if q == BCDDOp::Exist as u8 {
403            match () {
404                _ if op == BCDDOp::And as u8 => BCDDOp::ExistAnd,
405                _ if op == BCDDOp::Xor as u8 => BCDDOp::ExistXor,
406                _ => panic!("invalid OP"),
407            }
408        } else if q == BCDDOp::Unique as u8 {
409            match () {
410                _ if op == BCDDOp::And as u8 => BCDDOp::UniqueAnd,
411                _ if op == BCDDOp::UniqueNand as u8 => BCDDOp::UniqueNand,
412                _ if op == BCDDOp::Xor as u8 => BCDDOp::UniqueXor,
413                _ => panic!("invalid OP"),
414            }
415        } else {
416            panic!("invalid quantifier");
417        }
418    }
419}
420
421enum NodesOrDone<'a, E, N> {
422    Nodes(&'a N, &'a N),
423    Done(E),
424}
425
426#[cfg(feature = "statistics")]
427static STAT_COUNTERS: [crate::StatCounters; <BCDDOp as oxidd_core::Countable>::MAX_VALUE + 1] =
428    [crate::StatCounters::INIT; <BCDDOp as oxidd_core::Countable>::MAX_VALUE + 1];
429
430#[cfg(feature = "statistics")]
431/// Print statistics to stderr
432pub fn print_stats() {
433    eprintln!("[oxidd_rules_bdd::complement_edge]");
434    crate::StatCounters::print::<BCDDOp>(&STAT_COUNTERS);
435}
436
437// --- Utility Functions -------------------------------------------------------
438
439#[inline]
440fn is_var<M>(manager: &M, node: &M::InnerNode) -> bool
441where
442    M: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag>,
443{
444    let t = node.child(0);
445    let e = node.child(1);
446    t.tag() == EdgeTag::None
447        && e.tag() == EdgeTag::Complemented
448        && manager.get_node(&t).is_any_terminal()
449        && manager.get_node(&e).is_any_terminal()
450}
451
452#[inline]
453#[track_caller]
454fn var_level<M>(manager: &M, e: Borrowed<M::Edge>) -> LevelNo
455where
456    M: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag>,
457    M::InnerNode: HasLevel,
458{
459    let node = manager
460        .get_node(&e)
461        .expect_inner("Expected a variable but got a terminal node");
462    debug_assert!(is_var(manager, node));
463    node.level()
464}
465
466/// Add a literal for the variable at `level` to the cube `sub`. The literal
467/// has positive polarity iff `positive` is true. `level` must be above (less
468/// than) the level of the node referenced by `sub`.
469#[inline]
470fn add_literal_to_cube<M>(
471    manager: &M,
472    sub: M::Edge,
473    level: LevelNo,
474    positive: bool,
475) -> AllocResult<M::Edge>
476where
477    M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal>,
478    M::InnerNode: HasLevel,
479{
480    let sub = EdgeDropGuard::new(manager, sub);
481    debug_assert!(!is_false(manager, &sub));
482    debug_assert!(manager.get_node(&sub).level() > level);
483
484    let t = manager.get_terminal(BCDDTerminal)?;
485    let sub = sub.into_edge();
486    let (children, tag) = if positive {
487        let tag = sub.tag();
488        if tag == EdgeTag::Complemented {
489            ([sub.with_tag_owned(EdgeTag::None), t], tag)
490        } else {
491            ([sub, t.with_tag_owned(EdgeTag::Complemented)], tag)
492        }
493    } else {
494        ([t, not_owned(sub)], EdgeTag::Complemented)
495    };
496
497    let res = oxidd_core::LevelView::get_or_insert(
498        &mut manager.level(level),
499        M::InnerNode::new(level, children),
500    )?;
501    Ok(res.with_tag_owned(tag))
502}
503
504// --- Function Interface ------------------------------------------------------
505
506#[cfg(feature = "multi-threading")]
507pub use apply_rec::mt::BCDDFunctionMT;
508pub use apply_rec::BCDDFunction;