1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
use crate::biodivine_std::traits::Set;
use biodivine_lib_bdd::Bdd;
use num_bigint::BigInt;
use num_traits::ToPrimitive;
use std::ops::Shr;

/// A simple trait that is implemented by symbolic sets represented using `Bdd` objects.
///
/// Historically, many of these methods were implemented directly by the actual objects
/// (and are still implemented for compatibility reasons). However, they can also be implemented
/// by this "blanket" implementation which might be easier to use, as we don't need to
/// reimplement each method for every type.
pub trait BddSet {
    /// A reference to the underlying `Bdd`.
    fn as_bdd(&self) -> &Bdd;
    /// Make a copy of this set but use a completely new BDD instead. This is very much
    /// unsafe, so make sure to only use it with the "right" BDDs.
    fn copy(&self, bdd: Bdd) -> Self;

    /// The number of BDD variables that are actually used by this encoding (they don't have
    /// to appear in the actual BDD, they just theoretically appear in the encoding).
    fn active_variables(&self) -> u16;

    /// Compute the number of BDD nodes required to represent this set.
    fn symbolic_size(&self) -> usize {
        self.as_bdd().size()
    }

    /// Compute the exact cardinality of this symbolic set.
    fn exact_cardinality(&self) -> BigInt {
        let unused_variables = self.as_bdd().num_vars() - self.active_variables();
        self.as_bdd().exact_cardinality().shr(unused_variables)
    }

    /// Compute an "approximate" cardinality of this symbolic set.
    ///
    /// The value is approximate because the limitations of `f64` can apply during computation.
    fn approx_cardinality(&self) -> f64 {
        let cardinality = self.as_bdd().cardinality();
        let unused_variables = self.as_bdd().num_vars() - self.active_variables();
        let cardinality = cardinality / 2.0f64.powi(i32::from(unused_variables));
        if cardinality.is_nan() || cardinality.is_infinite() {
            // If the result is invalid, try to recompute it with
            // better precision.
            self.exact_cardinality().to_f64().unwrap_or(f64::INFINITY)
        } else {
            cardinality
        }
    }
}

impl<T: BddSet + Clone> Set for T {
    fn union(&self, other: &Self) -> Self {
        self.copy(self.as_bdd().or(other.as_bdd()))
    }

    fn intersect(&self, other: &Self) -> Self {
        self.copy(self.as_bdd().and(other.as_bdd()))
    }

    fn minus(&self, other: &Self) -> Self {
        self.copy(self.as_bdd().and_not(other.as_bdd()))
    }

    fn is_empty(&self) -> bool {
        self.as_bdd().is_false()
    }

    fn is_subset(&self, other: &Self) -> bool {
        /*
           If A is a subset of B, then (A and not B) is `false` (everything that is in A is in B).
           The following operation can only return a `false` BDD (due to the limit argument).
           Any other result will terminate early and be returned as `None`.
        */
        Bdd::binary_op_with_limit(
            1,
            self.as_bdd(),
            other.as_bdd(),
            biodivine_lib_bdd::op_function::and_not,
        )
        .is_some()
    }
}