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;
11
12use crate::stat;
13
14// spell-checker:ignore fnode,gnode
15
16mod apply_rec;
17
18// --- Edge Tag ----------------------------------------------------------------
19
20/// Edge tag in complement edge BDDs
21#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Countable)]
22#[repr(u8)]
23pub enum EdgeTag {
24    /// The edge's semantics is the semantics of the referenced node
25    #[default]
26    None,
27    /// The edge's semantics is the negation of the referenced node
28    Complemented,
29}
30
31impl std::ops::Not for EdgeTag {
32    type Output = EdgeTag;
33
34    #[inline]
35    fn not(self) -> EdgeTag {
36        match self {
37            EdgeTag::None => EdgeTag::Complemented,
38            EdgeTag::Complemented => EdgeTag::None,
39        }
40    }
41}
42
43impl std::ops::BitXor for EdgeTag {
44    type Output = EdgeTag;
45
46    #[inline]
47    fn bitxor(self, rhs: Self) -> EdgeTag {
48        use EdgeTag::*;
49        match (self, rhs) {
50            (None, None) => None,
51            (None, Complemented) => Complemented,
52            (Complemented, None) => Complemented,
53            (Complemented, Complemented) => None,
54        }
55    }
56}
57
58#[inline]
59#[must_use]
60fn not_owned<E: Edge<Tag = EdgeTag>>(e: E) -> E {
61    let tag = e.tag();
62    e.with_tag_owned(!tag)
63}
64
65#[inline]
66#[must_use]
67fn not<E: Edge<Tag = EdgeTag>>(e: &E) -> Borrowed<'_, E> {
68    let tag = e.tag();
69    e.with_tag(!tag)
70}
71
72// --- Reduction Rules ---------------------------------------------------------
73
74/// [`DiagramRules`] for complement edge binary decision diagrams
75pub struct BCDDRules;
76
77impl<E: Edge<Tag = EdgeTag>, N: InnerNode<E>> DiagramRules<E, N, BCDDTerminal> for BCDDRules {
78    type Cofactors<'a>
79        = Cofactors<'a, E, N::ChildrenIter<'a>>
80    where
81        N: 'a,
82        E: 'a;
83
84    #[inline]
85    fn reduce<M: Manager<Edge = E, InnerNode = N>>(
86        manager: &M,
87        level: LevelNo,
88        children: impl IntoIterator<Item = E>,
89    ) -> ReducedOrNew<E, N> {
90        let mut it = children.into_iter();
91        let t = it.next().unwrap();
92        let e = it.next().unwrap();
93        debug_assert!(it.next().is_none());
94
95        if t == e {
96            manager.drop_edge(e);
97            return ReducedOrNew::Reduced(t);
98        }
99
100        let tt = t.tag();
101        if tt == EdgeTag::Complemented {
102            let et = e.tag();
103            let node = N::new(
104                level,
105                [t.with_tag_owned(EdgeTag::None), e.with_tag_owned(!et)],
106            );
107            ReducedOrNew::New(node, EdgeTag::Complemented)
108        } else {
109            let node = N::new(level, [t, e]);
110            ReducedOrNew::New(node, EdgeTag::None)
111        }
112    }
113
114    #[inline]
115    fn cofactors(tag: E::Tag, node: &N) -> Self::Cofactors<'_> {
116        Cofactors {
117            it: node.children(),
118            tag,
119            phantom: PhantomData,
120        }
121    }
122
123    #[inline]
124    fn cofactor(tag: E::Tag, node: &N, n: usize) -> Borrowed<'_, E> {
125        let e = node.child(n);
126        if tag == EdgeTag::None {
127            e
128        } else {
129            let e_tag = e.tag();
130            e.edge_with_tag(!e_tag)
131        }
132    }
133}
134
135/// Iterator over the cofactors of a node in a complement edge BDD
136pub struct Cofactors<'a, E, I> {
137    it: I,
138    tag: EdgeTag,
139    phantom: PhantomData<Borrowed<'a, E>>,
140}
141
142impl<'a, E: Edge<Tag = EdgeTag> + 'a, I: Iterator<Item = Borrowed<'a, E>>> Iterator
143    for Cofactors<'a, E, I>
144{
145    type Item = Borrowed<'a, E>;
146
147    #[inline]
148    fn next(&mut self) -> Option<Self::Item> {
149        match (self.it.next(), self.tag) {
150            (Some(e), EdgeTag::None) => Some(e),
151            (Some(e), EdgeTag::Complemented) => {
152                let tag = !e.tag();
153                Some(Borrowed::edge_with_tag(e, tag))
154            }
155            (None, _) => None,
156        }
157    }
158
159    #[inline]
160    fn size_hint(&self) -> (usize, Option<usize>) {
161        self.it.size_hint()
162    }
163}
164
165impl<'a, E: Edge<Tag = EdgeTag>, I: FusedIterator<Item = Borrowed<'a, E>>> FusedIterator
166    for Cofactors<'a, E, I>
167{
168}
169
170impl<'a, E: Edge<Tag = EdgeTag>, I: ExactSizeIterator<Item = Borrowed<'a, E>>> ExactSizeIterator
171    for Cofactors<'a, E, I>
172{
173    #[inline]
174    fn len(&self) -> usize {
175        self.it.len()
176    }
177}
178
179/// Check if `edge` represents the function `⊥`
180#[inline]
181#[must_use]
182fn is_false<M: Manager<EdgeTag = EdgeTag>>(manager: &M, edge: &M::Edge) -> bool {
183    edge.tag() == EdgeTag::Complemented && manager.get_node(edge).is_any_terminal()
184}
185
186/// Collect the two cofactors `(then, else)` assuming that the incoming edge is
187/// tagged as `tag`
188#[inline]
189#[must_use]
190fn collect_cofactors<E: Edge<Tag = EdgeTag>, N: InnerNode<E>>(
191    tag: EdgeTag,
192    node: &N,
193) -> (Borrowed<'_, E>, Borrowed<'_, E>) {
194    debug_assert_eq!(N::ARITY, 2);
195    let mut it = BCDDRules::cofactors(tag, node);
196    let ft = it.next().unwrap();
197    let fe = it.next().unwrap();
198    debug_assert!(it.next().is_none());
199    (ft, fe)
200}
201
202/// Apply the reduction rules, creating a node in `manager` if necessary
203#[inline(always)]
204fn reduce<M>(
205    manager: &M,
206    level: LevelNo,
207    t: M::Edge,
208    e: M::Edge,
209    op: BCDDOp,
210) -> AllocResult<M::Edge>
211where
212    M: Manager<Terminal = BCDDTerminal, EdgeTag = EdgeTag>,
213{
214    // We do not use `DiagramRules::reduce()` here, as the iterator is
215    // apparently not fully optimized away.
216    if t == e {
217        stat!(reduced op);
218        manager.drop_edge(e);
219        return Ok(t);
220    }
221
222    let tt = t.tag();
223    let (node, tag) = if tt == EdgeTag::Complemented {
224        let et = e.tag();
225        let node = M::InnerNode::new(
226            level,
227            [t.with_tag_owned(EdgeTag::None), e.with_tag_owned(!et)],
228        );
229        (node, EdgeTag::Complemented)
230    } else {
231        (M::InnerNode::new(level, [t, e]), EdgeTag::None)
232    };
233
234    Ok(oxidd_core::LevelView::get_or_insert(&mut manager.level(level), node)?.with_tag_owned(tag))
235}
236
237// --- Terminal Type -----------------------------------------------------------
238
239/// Terminal nodes in complement edge binary decision diagrams
240#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Countable, Debug)]
241pub struct BCDDTerminal;
242
243impl oxidd_dump::ParseTagged<EdgeTag> for BCDDTerminal {
244    fn parse(s: &str) -> Option<(Self, EdgeTag)> {
245        let tag = match s {
246            "t" | "T" | "true" | "True" | "TRUE" | "⊤" | "1" => EdgeTag::None,
247            "f" | "F" | "false" | "False" | "FALSE" | "⊥" | "0" => EdgeTag::Complemented,
248            _ => return None,
249        };
250        Some((BCDDTerminal, tag))
251    }
252}
253
254impl oxidd_dump::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    Exists,
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::Exists 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/// Add a literal for the variable at `level` to the cube `sub`. The literal
440/// has positive polarity iff `positive` is true. `level` must be above (less
441/// than) the level of the node referenced by `sub`.
442#[inline]
443fn add_literal_to_cube<M>(
444    manager: &M,
445    sub: M::Edge,
446    level: LevelNo,
447    positive: bool,
448) -> AllocResult<M::Edge>
449where
450    M: Manager<EdgeTag = EdgeTag, Terminal = BCDDTerminal>,
451    M::InnerNode: HasLevel,
452{
453    let sub = EdgeDropGuard::new(manager, sub);
454    debug_assert!(!is_false(manager, &sub));
455    debug_assert!(manager.get_node(&sub).level() > level);
456
457    let t = manager.get_terminal(BCDDTerminal)?;
458    let sub = sub.into_edge();
459    let (children, tag) = if positive {
460        let tag = sub.tag();
461        if tag == EdgeTag::Complemented {
462            ([sub.with_tag_owned(EdgeTag::None), t], tag)
463        } else {
464            ([sub, t.with_tag_owned(EdgeTag::Complemented)], tag)
465        }
466    } else {
467        ([t, not_owned(sub)], EdgeTag::Complemented)
468    };
469
470    let res = oxidd_core::LevelView::get_or_insert(
471        &mut manager.level(level),
472        M::InnerNode::new(level, children),
473    )?;
474    Ok(res.with_tag_owned(tag))
475}
476
477// --- Function Interface ------------------------------------------------------
478
479#[cfg(feature = "multi-threading")]
480pub use apply_rec::mt::BCDDFunctionMT;
481pub use apply_rec::BCDDFunction;