ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
GenericCircuit.cpp
Go to the documentation of this file.
1/**
2 * @file GenericCircuit.cpp
3 * @brief GenericCircuit method implementations.
4 *
5 * Implements the virtual methods of @c GenericCircuit that override the
6 * @c Circuit<gate_type> base class:
7 * - @c addGate(): allocates a new gate and extends the @c prob vector.
8 * - @c setGate(gate_type): creates a new gate, registering it as an
9 * input gate when the type is @c gate_input or @c gate_update.
10 * - @c setGate(const uuid&, gate_type): same with UUID binding.
11 *
12 * The template method @c evaluate() is defined in @c GenericCircuit.hpp.
13 */
14#include "GenericCircuit.h"
15
16#include <unordered_set>
17
19{
20 auto id = Circuit::setGate(type);
21 if(type == gate_input || type==gate_update) {
22 inputs.insert(id);
23 }
24 return id;
25}
26
28{
29 auto id = Circuit::setGate(u, type);
30 if(type == gate_input || type==gate_update) {
31 inputs.insert(id);
32 }
33 return id;
34}
35
37{
38 auto id=Circuit::addGate();
39 prob.push_back(1);
40 return id;
41}
42
43#include <unordered_map>
44
46{
47 GenericCircuit &gc = *this;
48 const std::size_t n = gc.getNbGates();
49
50 /* Wrap the three phases in an outer fixpoint loop. Phase 3's
51 * substitutions can mutate a gate into @c gate_zero / @c gate_one
52 * (multiplicative absorber, single-wire collapse), which is a fresh
53 * identity-wire opportunity for any parent that referenced the
54 * mutated gate. @c createGenericCircuit loads gates in BFS-from-
55 * root order (parents before children), so a single pass through
56 * Phase 1 would propagate identity-collapse in the wrong direction
57 * anyway -- the outer loop handles both effects with the same
58 * machinery. Terminates after at most one iteration per DAG
59 * level. */
60 bool changed = true;
61 while (changed) {
62 changed = false;
63
64 /* Phase 1: drop identity wires in place. Multiplicative identity
65 * is @c gate_one (drop from @c gate_times); additive identity is
66 * @c gate_zero (drop from @c gate_plus). If every wire of a
67 * @c gate_plus / @c gate_times was the identity, the gate is left
68 * with an empty wire list: collapse it to the identity (empty sum
69 * is @c gate_zero, empty product is @c gate_one) by mutating the
70 * gate type in place. */
71 for (std::size_t i = 0; i < n; ++i) {
72 auto g = static_cast<gate_t>(i);
73 auto t = gc.getGateType(g);
74 if (t != gate_times && t != gate_plus) continue;
75 gate_type identity = (t == gate_times) ? gate_one : gate_zero;
76 auto &wires = gc.getWires(g);
77 std::vector<gate_t> kept;
78 kept.reserve(wires.size());
79 for (gate_t c : wires) {
80 if (gc.getGateType(c) != identity) kept.push_back(c);
81 }
82 if (kept.size() != wires.size()) {
83 wires = std::move(kept);
84 changed = true;
85 }
86 if (wires.empty()) {
87 gc.setGateType(g, identity);
88 changed = true;
89 }
90 }
91
92 /* Phase 2: build a substitution map for gates that should be
93 * collapsed to a single descendant after phase 1:
94 * - @c gate_times that still has a @c gate_zero wire ->
95 * substitute to that absorber (multiplicative zero is the
96 * universal absorber across semirings);
97 * - any @c gate_times / @c gate_plus left with exactly one wire
98 * -> substitute to that wire (identity collapse).
99 * The plus-with-one absorber rewrite is intentionally omitted:
100 * @c gate_one is the additive absorber only in idempotent
101 * semirings (Boolean, MinMax) and would silently change the
102 * semantics for @c Counting / @c Formula / etc. */
103 std::unordered_map<gate_t, gate_t> subst;
104 for (std::size_t i = 0; i < n; ++i) {
105 auto g = static_cast<gate_t>(i);
106 auto t = gc.getGateType(g);
107 if (t != gate_times && t != gate_plus) continue;
108 const auto &wires = gc.getWires(g);
109 if (t == gate_times) {
110 bool absorbed = false;
111 for (gate_t c : wires) {
112 if (gc.getGateType(c) == gate_zero) {
113 subst[g] = c;
114 absorbed = true;
115 break;
116 }
117 }
118 if (absorbed) continue;
119 }
120 if (wires.size() == 1) subst[g] = wires[0];
121 }
122 /* Resolve transitively so a chain of singleton wrappers maps to
123 * its bottom endpoint in one lookup. */
124 for (auto &kv : subst) {
125 gate_t cur = kv.second;
126 while (subst.count(cur)) cur = subst[cur];
127 kv.second = cur;
128 }
129
130 /* Phase 3: mutate every substituted gate to carry its target's
131 * type / wires / info / extra / prob / input-set membership.
132 * In-place transformation keeps the original gate's UUID (so
133 * downstream consumers that key on the caller-supplied root UUID
134 * still find it) while every walk from here sees the target's
135 * content. Mutually-substituted gates all resolve to the same
136 * endpoint (phase 2 above), so the order in which we apply them
137 * within the loop doesn't matter: copying from the endpoint
138 * always yields the correct content. */
139 for (const auto &kv : subst) {
140 gate_t g = kv.first;
141 gate_t target = kv.second;
142 if (g == target) continue;
143 const auto target_type = gc.getGateType(target);
144 gc.setGateType(g, target_type);
145 gc.getWires(g) = gc.getWires(target);
146 auto [ti1, ti2] = gc.getInfos(target);
147 const unsigned NO_INFO = static_cast<unsigned>(-1);
148 if (ti1 != NO_INFO || ti2 != NO_INFO) {
149 gc.setInfos(g, ti1, ti2);
150 }
151 try {
152 const std::string e = gc.getExtra(target);
153 gc.setExtra(g, e);
154 } catch (const CircuitException &) {
155 /* target has no extra; leave g's extra as-is (it was already
156 * cleared if it was an internal gate; values_t entry persisted
157 * for value gates we don't reach here). */
158 }
159 /* Copy the target's probability. Without this, a substituted
160 * @c gate_plus(x, @c gate_zero) → x where x is a
161 * Bernoulli @c gate_input would lose x's probability:
162 * @c addGate seeds @c prob = 1 for non-input gates, so the
163 * substituted gate would read as an input with @c prob = 1
164 * instead of x's actual probability. Mirror the @c inputs
165 * set membership too: leaf-typed targets register their
166 * substituted-gate id so probability evaluators see it. */
167 gc.setProb(g, gc.getProb(target));
168 if (target_type == gate_input || target_type == gate_update) {
169 gc.inputs.insert(g);
170 }
171 changed = true;
172 }
173 }
174}
175
177{
178 GenericCircuit &gc = *this;
179
180 bool changed = true;
181 while (changed) {
182 changed = false;
183 const std::size_t n = gc.getNbGates();
184 for (std::size_t i = 0; i < n; ++i) {
185 auto g = static_cast<gate_t>(i);
186 auto t = gc.getGateType(g);
187 if (t != gate_plus && t != gate_times) continue;
188
189 bool any_rule_fired = false;
190
191 /* Rule B1 : idempotence (Boolean-only, unsound in Counting,
192 * Tropical, Viterbi, ...). Drop repeated child wires
193 * preserving order of first occurrence. */
194 {
195 auto &wires = gc.getWires(g);
196 std::unordered_set<gate_t> seen;
197 std::vector<gate_t> deduped;
198 deduped.reserve(wires.size());
199 for (gate_t c : wires) {
200 if (seen.insert(c).second) deduped.push_back(c);
201 }
202 if (deduped.size() != wires.size()) {
203 wires = std::move(deduped);
204 any_rule_fired = true;
205 }
206 }
207
208 /* Rule B2 : plus-with-one absorber (Boolean-only). Replace
209 * @c gate_plus(..., @c gate_one, ...) with an empty
210 * @c gate_plus, which the trailing
211 * @c foldSemiringIdentities collapses back to a single
212 * @c gate_one wire ; that gate is then Phase-2-substituted
213 * into @c gate_one in place, preserving @p g's UUID. */
214 if (t == gate_plus) {
215 bool has_one = false;
216 for (gate_t c : gc.getWires(g)) {
217 if (gc.getGateType(c) == gate_one) { has_one = true; break; }
218 }
219 if (has_one) {
220 gc.setGateType(g, gate_one);
221 gc.setWires(g, std::vector<gate_t>{});
222 infos.erase(g);
223 extra.erase(g);
224 any_rule_fired = true;
225 }
226 }
227
228 /* Rule B3 : absorption (Boolean-only).
229 * gate_plus (x, gate_times(x, y, ...), ...) -> gate_plus (x, ...)
230 * gate_times(x, gate_plus (x, y, ...), ...) -> gate_times(x, ...)
231 * The absorbed times / plus child is dominated by its sibling
232 * @c x present in the parent's children set. Implemented as a
233 * single pass : build a set view of the parent's children, then
234 * drop every opposite-type child whose wires intersect that set.
235 * The set view captures the wires snapshot ; in case B2 already
236 * mutated @p g into @c gate_one above, @c t still holds the
237 * pre-mutation type so absorption skips correctly via the
238 * type-mismatch check at the top. */
239 if (t == gate_plus || t == gate_times) {
240 const gate_type opposite = (t == gate_plus) ? gate_times : gate_plus;
241 const auto &wires_now = gc.getWires(g);
242 if (wires_now.size() >= 2) {
243 std::unordered_set<gate_t> sibling_set(wires_now.begin(),
244 wires_now.end());
245 std::vector<gate_t> kept;
246 kept.reserve(wires_now.size());
247 bool dropped = false;
248 for (gate_t c : wires_now) {
249 if (gc.getGateType(c) == opposite) {
250 bool absorb = false;
251 for (gate_t w : gc.getWires(c)) {
252 if (w != c && sibling_set.count(w)) {
253 absorb = true;
254 break;
255 }
256 }
257 if (absorb) { dropped = true; continue; }
258 }
259 kept.push_back(c);
260 }
261 if (dropped) {
262 gc.setWires(g, std::move(kept));
263 any_rule_fired = true;
264 }
265 }
266 }
267
268 if (any_rule_fired) {
270 changed = true;
271 }
272 }
273 }
274
275 /* The dedup may leave single-wire plus / times around. Re-run the
276 * semiring-safe pass to collapse them ; that pass mutates gates in
277 * place (Phase 3) and preserves their UUID and -- crucially for
278 * us -- their identity in @c boolean_assumed_gates. */
280}
gate_t
Strongly-typed gate identifier.
Definition Circuit.h:49
Semiring-agnostic in-memory provenance circuit.
Exception type thrown by circuit operations on invalid input.
Definition Circuit.h:206
std::string uuid
Definition Circuit.h:65
std::vector< gate_t > & getWires(gate_t g)
Return a mutable reference to the child-wire list of gate g.
Definition Circuit.h:140
gateType getGateType(gate_t g) const
Return the type of gate g.
Definition Circuit.h:130
virtual gate_t setGate(const uuid &u, gateType type)
Create or update the gate associated with UUID u.
Definition Circuit.hpp:73
void setGateType(gate_t g, gateType t)
Update the type of an existing gate.
Definition Circuit.h:79
std::vector< std::vector< gate_t > > wires
Definition Circuit.h:72
std::vector< gate_t >::size_type getNbGates() const
Return the total number of gates in the circuit.
Definition Circuit.h:103
virtual gate_t addGate()
Allocate a new gate with a default-initialised type.
Definition Circuit.hpp:56
In-memory provenance circuit with semiring-generic evaluation.
void setWires(gate_t g, std::vector< gate_t > w)
Replace the wires of g with w.
std::map< gate_t, std::pair< unsigned, unsigned > > infos
Per-gate (info1, info2) annotations.
std::map< gate_t, std::string > extra
Per-gate string extras.
gate_t addGate() override
Allocate a new gate with a default-initialised type.
std::set< gate_t > inputs
Set of input (leaf) gate IDs.
void foldSemiringIdentities()
Drop semiring identity wires and collapse single-wire gate_times / gate_plus to their lone non-identi...
std::vector< double > prob
Per-gate probability values.
void setInfos(gate_t g, unsigned info1, unsigned info2)
Set the integer annotation pair for gate g.
std::string getExtra(gate_t g) const
Return the string extra for gate g.
gate_t setGate(gate_type type) override
Allocate a new gate with type type and no UUID.
void markBooleanAssumed(gate_t g)
Mark gate g as Boolean-assumed (in-memory side band).
double getProb(gate_t g) const
Return the probability for gate g.
std::pair< unsigned, unsigned > getInfos(gate_t g) const
Return the integer annotation pair for gate g.
void setExtra(gate_t g, const std::string &ex)
Attach a string extra to gate g.
void foldBooleanIdentities()
Apply Boolean-only simplification rules to gate_plus and gate_times.
void setProb(gate_t g, double p)
Set the probability for gate g.