|
| 1 | +% A generic BDD parameterized over both the 'nodes and 'leafs |
| 2 | +% |
| 3 | +% hide built-in Erlang node function |
| 4 | +-compile([export_all, nowarn_export_all]). |
| 5 | +-compile({no_auto_import, [node/1]}). |
| 6 | + |
| 7 | +-ifndef(ATOM). |
| 8 | +-define(ATOM, ty_bool). |
| 9 | +-endif. |
| 10 | +-ifndef(LEAF). |
| 11 | +-define(LEAF, ty_bool). |
| 12 | +-endif. |
| 13 | + |
| 14 | +-type dnf() :: term(). % TODO |
| 15 | +-type bdd() :: |
| 16 | + {leaf, ?LEAF:type()} |
| 17 | + | {node, _Atom :: ?ATOM:type(), _PositiveEdge :: bdd(), _NegativeEdge :: bdd()}. |
| 18 | + |
| 19 | +-spec any() -> bdd(). |
| 20 | +any() -> {leaf, ?LEAF:any()}. |
| 21 | + |
| 22 | +-spec empty() -> bdd(). |
| 23 | +empty() -> {leaf, ?LEAF:empty()}. |
| 24 | + |
| 25 | +-spec singleton(?ATOM:type()) -> bdd(). |
| 26 | +singleton(Atom) -> {node, Atom, any(), empty()}. |
| 27 | + |
| 28 | +-spec negated_singleton(?ATOM:type()) -> bdd(). |
| 29 | +negated_singleton(Atom) -> {node, Atom, empty(), any()}. |
| 30 | + |
| 31 | +-spec leaf(?LEAF:type()) -> bdd(). |
| 32 | +leaf(Leaf) -> {leaf, Leaf}. |
| 33 | + |
| 34 | +-spec equal(bdd(), bdd()) -> bdd(). |
| 35 | +equal({node, A1, P1, N1}, {node, A2, P2, N2}) -> |
| 36 | + ?ATOM:equal(A1, A2) andalso equal(P1, P2) andalso equal(N1, N2); |
| 37 | +equal({leaf, T1}, {leaf, T2}) -> |
| 38 | + ?LEAF:equal(T1, T2); |
| 39 | +equal(_, _) -> |
| 40 | + false. |
| 41 | + |
| 42 | +-spec compare(bdd(), bdd()) -> lt | gt | eq. |
| 43 | +compare({leaf, T1}, {leaf, T2}) -> ?LEAF:compare(T1, T2); |
| 44 | +compare({leaf, _}, {node, _, _, _}) -> lt; |
| 45 | +compare({node, _, _, _}, {leaf, _}) -> gt; |
| 46 | +compare({node, A1, P1, N1}, {node, A2, P2, N2}) -> |
| 47 | + case ?ATOM:compare(A1, A2) of |
| 48 | + eq -> |
| 49 | + case compare(P1, P2) of |
| 50 | + eq -> compare(N1, N2); |
| 51 | + Res -> Res |
| 52 | + end; |
| 53 | + Res -> Res |
| 54 | + end. |
| 55 | + |
| 56 | +-spec negate(bdd()) -> bdd(). |
| 57 | +negate({leaf, A}) -> |
| 58 | + {leaf, ?LEAF:negate(A)}; |
| 59 | +negate({node, Atom, Pos, Neg}) -> |
| 60 | + {node, Atom, negate(Pos), negate(Neg)}. |
| 61 | + |
| 62 | + |
| 63 | +% simplification for BDDs |
| 64 | +% implements a simple form of structural subsumption |
| 65 | +% which is not apparant at first glance |
| 66 | +% TODO example here |
| 67 | +-spec normalize(bdd()) -> bdd(). |
| 68 | +normalize(Bdd = {node, _Atom, Pos, Neg}) -> |
| 69 | + case equal(Pos, Neg) of |
| 70 | + true -> Pos; |
| 71 | + false -> Bdd |
| 72 | + end; |
| 73 | +normalize(X) -> X. |
| 74 | + |
| 75 | +-spec op(fun((?LEAF:type(), ?LEAF:type()) -> ?LEAF:type()), bdd(), bdd()) -> bdd(). |
| 76 | +op(LeafOperation, Bdd1, Bdd2) -> |
| 77 | + Op = fun ROp(T1, T2) -> |
| 78 | + Res = |
| 79 | + case {T1, T2} of |
| 80 | + {{leaf, L1}, {leaf, L2}} -> {leaf, LeafOperation(L1, L2)}; |
| 81 | + {{leaf, L}, {node, A, P, N}} -> |
| 82 | + {node, A, ROp({leaf, L}, P), ROp({leaf, L}, N)}; |
| 83 | + {{node, A, P, N}, {leaf, L}} -> |
| 84 | + {node, A, ROp(P, {leaf, L}), ROp(N, {leaf, L})}; |
| 85 | + {{node, A1, P1, N1}, {node, A2, P2, N2}} -> |
| 86 | + case ?ATOM:compare(A1, A2) of |
| 87 | + lt -> |
| 88 | + {node, A1, ROp(P1, T2), ROp(N1, T2)}; |
| 89 | + gt -> |
| 90 | + {node, A2, ROp(T1, P2), ROp(T1, N2)}; |
| 91 | + eq -> |
| 92 | + {node, A1, ROp(P1, P2), ROp(N1, N2)} |
| 93 | + end |
| 94 | + end, |
| 95 | + normalize(Res) |
| 96 | + end, |
| 97 | + Op(Bdd1, Bdd2). |
| 98 | + |
| 99 | +-spec union(bdd(), bdd()) -> bdd(). |
| 100 | +union(T1, T2) -> |
| 101 | + op(fun ?LEAF:union/2, T1, T2). |
| 102 | + |
| 103 | +-spec intersect(bdd(), bdd()) -> bdd(). |
| 104 | +intersect(T1, T2) -> op(fun ?LEAF:intersect/2, T1, T2). |
| 105 | + |
| 106 | +-spec difference(bdd(), bdd()) -> bdd(). |
| 107 | +difference(T1, T2) -> op(fun ?LEAF:difference/2, T1, T2). |
| 108 | + |
| 109 | +-spec dnf(bdd()) -> dnf(). |
| 110 | +dnf(T) -> |
| 111 | + dnf_acc([], [], [], T). |
| 112 | + |
| 113 | +-spec dnf_acc(dnf(), [?ATOM:type()], [?ATOM:type()], bdd()) -> dnf(). |
| 114 | +dnf_acc(Acc, Ps, Ns, {leaf, T}) -> |
| 115 | + [{Ps, Ns, T} | Acc]; |
| 116 | +dnf_acc(Acc, Ps, Ns, {node, A, P, N}) -> |
| 117 | + % TODO small heuristic add |
| 118 | + Acc0 = dnf_acc(Acc, [A | Ps], Ns, P), |
| 119 | + dnf_acc(Acc0, Ps, [A | Ns], N). |
| 120 | + |
| 121 | + |
| 122 | +% is_empty_union(F1, F2) -> |
| 123 | +% F1() andalso F2(). |
| 124 | + |
| 125 | +% get_dnf(Bdd) -> |
| 126 | +% lists:filter( |
| 127 | +% fun({_,_,[]}) -> false; ({_, _, T}) -> |
| 128 | +% case ?TERMINAL:empty() of |
| 129 | +% T -> false; |
| 130 | +% _ -> true |
| 131 | +% end |
| 132 | +% end, |
| 133 | +% dnf(Bdd, {fun(P, N, T) -> [{P, N, T}] end, fun(C1, C2) -> C1() ++ C2() end}) |
| 134 | +% ). |
| 135 | + |
| 136 | +% dnf(Bdd, {ProcessCoclause, CombineResults}) -> |
| 137 | +% do_dnf(Bdd, {ProcessCoclause, CombineResults}, _Pos = [], _Neg = []). |
| 138 | + |
| 139 | +% do_dnf({node, Element, Left, Right}, F = {_Process, Combine}, Pos, Neg) -> |
| 140 | +% % heuristic: if Left is positive & 1, skip adding the negated Element to the right path |
| 141 | +% % TODO can use the see simplifications done in ty_rec:transform to simplify DNF before processing? |
| 142 | +% case {terminal, ?TERMINAL:any()} of |
| 143 | +% Left -> |
| 144 | +% F1 = fun() -> do_dnf(Left, F, [Element | Pos], Neg) end, |
| 145 | +% F2 = fun() -> do_dnf(Right, F, Pos, Neg) end, |
| 146 | +% Combine(F1, F2); |
| 147 | +% _ -> |
| 148 | +% F1 = fun() -> do_dnf(Left, F, [Element | Pos], Neg) end, |
| 149 | +% F2 = fun() -> do_dnf(Right, F, Pos, [Element | Neg]) end, |
| 150 | +% Combine(F1, F2) |
| 151 | +% end; |
| 152 | +% do_dnf({terminal, Terminal}, {Proc, _Comb}, Pos, Neg) -> |
| 153 | +% Proc(Pos, Neg, Terminal). |
| 154 | + |
0 commit comments