|
| 1 | +-module(inductive_no_backtrack). |
| 2 | + |
| 3 | +-compile([export_all, nowarn_unused_type, nowarn_export_all]). |
| 4 | + |
| 5 | +-type phi() :: inductive:phi(). |
| 6 | +-type variable() :: inductive:variable(). |
| 7 | + |
| 8 | +-type long_constraint() :: variable() | {phi(), long_constraint()}. |
| 9 | +-type table() :: #{variable() => 0 | 1 | [C_i :: long_constraint()]}. % with table(v(C_i)) not in {0, bottom} |
| 10 | + |
| 11 | +-spec v(long_constraint()) -> variable(). |
| 12 | +v({_, C}) -> v(C); |
| 13 | +v(Var) -> Var. |
| 14 | + |
| 15 | +-spec domain(table()) -> sets:set(variable()). |
| 16 | +domain(O) -> |
| 17 | + % maps:keys(O). |
| 18 | + sets:from_list(maps:keys(O)). |
| 19 | + |
| 20 | +-spec true_variables(table()) -> sets:set(variable()). |
| 21 | +true_variables(O) -> |
| 22 | + sets:from_list([Var || Var := 1 <- O]). |
| 23 | + |
| 24 | +-spec leq(table(), table()) -> boolean(). |
| 25 | +leq(O1, O2) -> |
| 26 | + sets:is_subset(domain(O2), domain(O1)) |
| 27 | + and |
| 28 | + sets:is_subset(true_variables(O2), true_variables(O1)). |
| 29 | + |
| 30 | +-spec extend(table(), variable(), todo) -> todo. |
| 31 | +extend(O, T, S) -> |
| 32 | + case O of |
| 33 | + #{T := _} -> O; % o(t) not bottom |
| 34 | + _ -> |
| 35 | + O0 = trigger(O#{T => []}, {maps:get(T, S), T}, S), |
| 36 | + % if the state of t is a set of constraints [C1; . . . ; Cn], |
| 37 | + % we can sometimes replaces it with 0, which will save us from extending this set later. |
| 38 | + % we have to be sure that t is indeed 0 first |
| 39 | + % that is the case if none of the other suspended constraints in the current table |
| 40 | + % is "pointing" towards t |
| 41 | + case may(O0, T, S) of |
| 42 | + true -> O0; |
| 43 | + false -> O0#{T => 0} |
| 44 | + end |
| 45 | + end. |
| 46 | + |
| 47 | +trigger(O, C, S) -> |
| 48 | + Vc = v(C), |
| 49 | + case O of |
| 50 | + % when activating the constraint C, |
| 51 | + % if we know that the truth value of Vc is 1, |
| 52 | + % we can ignore this constraint |
| 53 | + #{Vc := 1} -> O; |
| 54 | + _ -> trigger2(O, C, S) |
| 55 | + end. |
| 56 | + |
| 57 | +trigger2(O, T, S) when is_atom(T) -> |
| 58 | + #{T := L} = O, |
| 59 | + trigger3(O#{T => 1}, L, S); |
| 60 | +trigger2(O, {T, C}, S) when is_atom(T) -> |
| 61 | + case v(C) of |
| 62 | + % a constraint of the form t => ... => t can be ignored |
| 63 | + T -> O; |
| 64 | + _ -> |
| 65 | + O0 = extend(O, T, S), |
| 66 | + case O0 of |
| 67 | + #{T := 0} -> O0; |
| 68 | + #{T := 1} -> trigger(O0, C, S); |
| 69 | + #{T := L} -> |
| 70 | + O0#{T => [C | L]} |
| 71 | + end |
| 72 | + end; |
| 73 | +trigger2(O, {0, _C}, _S) -> O; |
| 74 | +trigger2(O, {1, C}, S) -> trigger2(O, C, S); |
| 75 | +trigger2(O, {{'and', F1, F2}, C}, S) -> trigger2(O, {F1, {F2, C}}, S); |
| 76 | +trigger2(O, {{'or', F1, F2}, C}, S) -> trigger(trigger2(O, {F1, C}, S), {F2, C}, S). |
| 77 | + |
| 78 | +trigger3(O, [], _S) -> O; |
| 79 | +trigger3(O, [C | L], S) -> trigger3(trigger(O, C, S), L, S). |
| 80 | + |
| 81 | + |
| 82 | +% assumption: T is in O |
| 83 | +may(O, T, _S) -> |
| 84 | + case O of |
| 85 | + #{T := 1} -> true; |
| 86 | + _ -> |
| 87 | + length([ok || _Tp := V <- O, is_list(V), lists:any(fun(C) -> case v(C) of T -> true; _ -> false end end, V) ]) > 0 |
| 88 | + end. |
| 89 | + |
| 90 | +clean(O) -> #{K => case V of 0 -> 0; 1 -> 1; _ -> 0 end || K := V <- O}. |
| 91 | + |
| 92 | + |
| 93 | +-spec 'Example 7.16_test'() -> ok. |
| 94 | +'Example 7.16_test'() -> |
| 95 | + S = #{ |
| 96 | + t1 => {'or', t2, 1}, |
| 97 | + t2 => t1 |
| 98 | + }, |
| 99 | + #{t1 := 1, t2 := 1} = extend(#{}, t1, S), |
| 100 | + ok. |
| 101 | + |
| 102 | +-spec 'Example 7.17_test'() -> ok. |
| 103 | +'Example 7.17_test'() -> |
| 104 | + S = #{ |
| 105 | + t1 => {'or', t2, 1}, |
| 106 | + t2 => 0 |
| 107 | + }, |
| 108 | + % t2 := 0 is now in the negative cache |
| 109 | + #{t1 := 1, t2 := 0} = extend(#{}, t1, S), |
| 110 | + ok. |
| 111 | + |
| 112 | +-spec 'Example 7.25_test'() -> ok. |
| 113 | +'Example 7.25_test'() -> |
| 114 | + S = #{ |
| 115 | + t1 => t2, |
| 116 | + t2 => t1 |
| 117 | + }, |
| 118 | + #{t1 := 0, t2 := 0} = clean(extend(#{}, t1, S)), |
| 119 | + ok. |
| 120 | + |
| 121 | +cache_test() -> |
| 122 | + S = #{ |
| 123 | + t1 => {'and', t2, {'and', t2, 1}}, |
| 124 | + t2 => 1 |
| 125 | + }, |
| 126 | + % cache computes t2 only once |
| 127 | + #{t1 := 1, t2 := 1} = extend(#{}, t1, S). |
| 128 | + |
| 129 | +-spec shortcut_test() -> ok. |
| 130 | +shortcut_test() -> |
| 131 | + S = #{ |
| 132 | + t1 => {'or', 1, t2}, |
| 133 | + t2 => t1 |
| 134 | + }, |
| 135 | + % t2 is not explored |
| 136 | + #{t1 := 1} = extend(#{}, t1, S), |
| 137 | + ok. |
0 commit comments