ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
DTree.cpp
Go to the documentation of this file.
1/**
2 * @file DTree.cpp
3 * @brief Implementation of the d-tree anytime interval-bounds engine.
4 */
5#include <algorithm>
6#include <functional>
7#include <map>
8#include <set>
9#include <string>
10#include <type_traits>
11#include <unordered_map>
12#include <utility>
13#include <vector>
14
15// Keep the Boost-including headers ahead of the PostgreSQL block: port.h
16// #defines snprintf to pg_snprintf, which breaks Boost headers (>= 1.79)
17// whose inline code calls std::snprintf (boost/assert/source_location.hpp).
18#include "BooleanCircuit.h"
19#include "DTree.h"
20
21extern "C" {
22#include "provsql_utils.h" // provsql_interrupted
23#include "miscadmin.h" // check_stack_depth
24}
25
26namespace provsql {
27
28namespace {
29
30using Clauses = std::vector<std::set<gate_t> >;
31
32/**
33 * @brief Order-sensitive hash of a @e canonical clause set.
34 *
35 * The memo only ever sees clause sets that @c recurse has already
36 * subsumption-reduced and sorted, so two logically equal residual DNFs have an
37 * identical @c Clauses representation (same clause order, each clause a sorted
38 * @c std::set) and therefore hash identically. Collisions fall back to the
39 * vector/set @c operator== the @c unordered_map applies, so the hash needs only
40 * to be well-distributed, not perfect -- the lookup stays exact. This avoids
41 * a @c std::map whose @c O(log n) lookups would each run a lexicographic
42 * compare over @c vector<set<gate_t>> (a costly per-node key operation);
43 * the hash makes lookups average @c O(clause-set size).
44 */
45struct ClausesHash {
46 std::size_t operator()(const Clauses &cls) const
47 {
48 std::size_t h = 1469598103934665603ull; // FNV-1a offset basis
49 std::hash<gate_t> gh;
50 for(const auto &cl : cls) {
51 std::size_t ch = 1099511628211ull; // per-clause FNV prime seed
52 for(gate_t v : cl)
53 ch = ch * 31u + gh(v);
54 // boost::hash_combine-style mix so clause order matters and the
55 // per-clause hashes do not simply XOR-cancel.
56 h ^= ch + 0x9e3779b97f4a7c15ull + (h << 6) + (h >> 2);
57 }
58 return h;
59 }
60};
61
62/**
63 * @brief Shared recursion state: the circuit and the subproblem memo.
64 *
65 * The memo turns the Shannon/independence recursion from a tree into a shared
66 * DAG: distinct paths that reach the same residual DNF (very common on path- and
67 * cycle-shaped lineage) are computed once. Without it the recursion is
68 * exponential even on bounded treewidth; with it, the number of distinct
69 * residual clause sets is polynomial there, matching the paper's tractable-query
70 * behaviour.
71 *
72 * The key is the @e canonical clause set (subsumption-reduced, then sorted), and
73 * the value is the subproblem's @b exact probability. An entry is therefore
74 * sound to reuse for @e any request (an exact value satisfies any width target),
75 * but it is only ever WRITTEN on an exact request (@c max_width == 0), where the
76 * whole recursion is exact -- an early-stopped interval is budget-dependent and
77 * must not be cached. Hashed (@c ClausesHash) for average-constant lookup.
78 */
79struct DTreeContext {
80 const BooleanCircuit &c;
81 std::unordered_map<Clauses, double, ClausesHash> memo;
82 // Speculative-execution budget: count subproblems (recursion entries) and bail
83 // when they exceed @c budget, so the chooser drops the d-tree and escalates to
84 // the next method. @c budget == 0 means unbounded. Deterministic (a function
85 // of the circuit), so the chosen method is reproducible.
86 unsigned long steps = 0;
87 unsigned long budget = 0;
88};
89
90/**
91 * @brief Drop subsumed clauses from a monotone DNF.
92 *
93 * For a monotone DNF a clause is the conjunction of its (positive) literals, so
94 * if clause @c A is a subset of clause @c B then @c B implies @c A and
95 * @c A∨B ≡ A: the superset @c B is redundant. Keeping only the minimal clauses
96 * (and a single copy of duplicates) preserves the function and shrinks the work
97 * Shannon expansion generates. @c O(m^2) set-inclusion tests.
98 */
99void removeSubsumed(Clauses &clauses)
100{
101 // Ascending size, so a subsumer (subset) is always considered before any
102 // clause it could subsume.
103 std::sort(clauses.begin(), clauses.end(),
104 [](const std::set<gate_t> &a, const std::set<gate_t> &b) {
105 return a.size() < b.size();
106 });
107 Clauses kept;
108 kept.reserve(clauses.size());
109 for(auto &c : clauses) {
110 bool subsumed = false;
111 for(const auto &k : kept)
112 // k ⊆ c (k has size <= c by the sort), so c is subsumed by k.
113 if(std::includes(c.begin(), c.end(), k.begin(), k.end())) {
114 subsumed = true;
115 break;
116 }
117 if(!subsumed)
118 kept.push_back(std::move(c));
119 }
120 clauses = std::move(kept);
121}
122
123/**
124 * @brief Partition a DNF into independent-or components.
125 *
126 * Two clauses are connected when their supports share an input leaf; the
127 * connected components are sub-DNFs over disjoint variable sets, so the whole
128 * DNF is their independent disjunction. A single component means the
129 * disjunction does not split here. Union-find over the clause/variable
130 * incidence, @c O(m·avg-clause·α).
131 */
132std::vector<Clauses> components(const Clauses &clauses)
133{
134 const size_t m = clauses.size();
135 std::vector<size_t> parent(m);
136 for(size_t i = 0; i < m; ++i)
137 parent[i] = i;
138 std::function<size_t(size_t)> find = [&](size_t x) {
139 while(parent[x] != x) {
140 parent[x] = parent[parent[x]];
141 x = parent[x];
142 }
143 return x;
144 };
145 std::unordered_map<gate_t, size_t> owner; // first clause seen carrying a leaf
146 for(size_t i = 0; i < m; ++i)
147 for(gate_t v : clauses[i]) {
148 auto it = owner.find(v);
149 if(it == owner.end())
150 owner.emplace(v, i);
151 else
152 parent[find(i)] = find(it->second);
153 }
154 // Group clauses by component root, keeping a deterministic order.
155 std::map<size_t, Clauses> groups;
156 for(size_t i = 0; i < m; ++i)
157 groups[find(i)].push_back(clauses[i]);
158 std::vector<Clauses> out;
159 out.reserve(groups.size());
160 for(auto &kv : groups)
161 out.push_back(std::move(kv.second));
162 return out;
163}
164
165/// The variable appearing in the most clauses (ties broken by smallest gate id
166/// for reproducibility) -- the Shannon-expansion pivot.
167gate_t mostFrequentVar(const Clauses &clauses)
168{
169 std::map<gate_t, size_t> freq; // ordered for a deterministic tie-break
170 for(const auto &c : clauses)
171 for(gate_t v : c)
172 ++freq[v];
173 gate_t best{};
174 size_t best_count = 0;
175 bool found = false;
176 for(const auto &kv : freq)
177 if(!found || kv.second > best_count) { // strict '>' keeps the smallest id
178 best = kv.first;
179 best_count = kv.second;
180 found = true;
181 }
182 return best;
183}
184
185DTreeInterval recurse(DTreeContext &ctx, Clauses clauses, double max_width)
186{
187 check_stack_depth();
189 throw CircuitException("Interrupted");
190 ++ctx.steps;
191 if(ctx.budget && ctx.steps > ctx.budget)
192 throw CircuitException("d-tree: cost budget exceeded");
193
194 if(clauses.empty())
195 return {0., 0.}; // empty disjunction is false
196 for(const auto &cl : clauses)
197 if(cl.empty())
198 return {1., 1.}; // a clause with no literals is true, so the DNF is true
199
200 // Canonicalise (subsumption-reduce, then sort) so equivalent residual DNFs
201 // share a memo key.
202 removeSubsumed(clauses);
203 std::sort(clauses.begin(), clauses.end());
204
205 // An exact request keeps max_width == 0 through every decomposition (the ⊗
206 // split passes 0/k = 0, ⊕ passes it unchanged), so the whole recursion is
207 // exact and every result is cacheable; an approximate request never writes.
208 const bool exact = (max_width <= 0.);
209 if(exact) {
210 const auto it = ctx.memo.find(clauses);
211 if(it != ctx.memo.end())
212 return {it->second, it->second};
213 }
214
215 // Cheap certified interval; stop as soon as it is narrow enough.
216 double L, U;
217 ctx.c.dnfBounds(clauses, L, U);
218 if(U - L <= max_width) {
219 if(exact)
220 ctx.memo.emplace(clauses, L); // independent leaf: L == U, exact
221 return {L, U};
222 }
223
224 DTreeInterval res;
225 // Independent-or: recurse on each component with a 1/k share of the budget
226 // (the OR width is at most the sum of component widths) and combine.
227 std::vector<Clauses> comps = components(clauses);
228 if(comps.size() > 1) {
229 double prod_lower = 1., prod_upper = 1.;
230 const double sub_width = max_width / static_cast<double>(comps.size());
231 for(auto &comp : comps) {
232 DTreeInterval r = recurse(ctx, std::move(comp), sub_width);
233 prod_lower *= (1. - r.lower);
234 prod_upper *= (1. - r.upper);
235 }
236 res = {1. - prod_lower, 1. - prod_upper};
237 } else {
238 // Shannon expansion on the most frequent variable x: Pr = Pr[x]·Pr[Phi|x=1]
239 // + (1-Pr[x])·Pr[Phi|x=0]. For a monotone DNF the cofactors are, on x=1,
240 // every clause with x stripped of x (the literal is satisfied) and every
241 // clause without x unchanged; on x=0, only the clauses not containing x
242 // survive. The same width budget passes to both branches (the mixture
243 // width is at most the larger branch's).
244 const gate_t x = mostFrequentVar(clauses);
245 const double px = ctx.c.getProb(x);
246 Clauses pos, neg;
247 pos.reserve(clauses.size());
248 neg.reserve(clauses.size());
249 for(const auto &cl : clauses) {
250 if(cl.count(x)) {
251 std::set<gate_t> reduced = cl;
252 reduced.erase(x);
253 pos.push_back(std::move(reduced));
254 } else {
255 pos.push_back(cl);
256 neg.push_back(cl);
257 }
258 }
259 const DTreeInterval rp = recurse(ctx, std::move(pos), max_width);
260 const DTreeInterval rn = recurse(ctx, std::move(neg), max_width);
261 res = {px * rp.lower + (1. - px) * rn.lower,
262 px * rp.upper + (1. - px) * rn.upper};
263 }
264
265 if(exact)
266 ctx.memo.emplace(clauses, res.lower); // exact (res.lower == res.upper)
267 return res;
268}
269
270} // namespace
271
272DTreeInterval dtreeBounds(const BooleanCircuit &c, Clauses clauses,
273 double max_width, unsigned long budget,
274 unsigned long *steps_out)
275{
276 DTreeContext ctx{c, {}};
277 ctx.budget = budget;
278 DTreeInterval r = recurse(ctx, std::move(clauses), max_width);
279 if(steps_out)
280 *steps_out = ctx.steps;
281 return r;
282}
283
284// ===========================================================================
285// General-circuit d-tree: the same anytime engine on the BooleanCircuit DAG
286// (AND / OR / NOT / IN), so it drops the monotone-DNF shape restriction.
287// ===========================================================================
288
289namespace {
290
291using Assignment = std::unordered_map<gate_t, bool>;
292
293/// Static cone of genuine variable inputs (probability strictly in (0,1)); the
294/// memo + the recursion state for the general-circuit d-tree.
295struct GenContext {
296 const BooleanCircuit &c;
297 std::unordered_map<gate_t, std::set<gate_t> > footprint;
298 std::unordered_map<std::string, double> exactMemo; // exact subproblem values
299 // Speculative-execution budget (see DTreeContext): subproblem count + cap.
300 unsigned long steps = 0;
301 unsigned long budget = 0;
302};
303
304inline unsigned long gid(gate_t g)
305{
306 return static_cast<unsigned long>(
307 static_cast<std::underlying_type<gate_t>::type>(g));
308}
309
310/// Variable-input cone of @p g (inputs with probability in (0,1)). Constant
311/// inputs (probability 0 / 1) carry no variable and are dropped. A multivalued
312/// (BID) block is rewritten to independent Booleans by the dispatcher before
313/// this method runs (d-tree's handlesMultivalued() is false), so a MULIN gate
314/// should never reach here; the throw is a defensive net rather than the
315/// applicability signal it once was.
316/// unordered_map references survive rehash, so holding a child's reference while
317/// inserting the parent is safe.
318const std::set<gate_t> &footprintOf(GenContext &ctx, gate_t g)
319{
320 auto it = ctx.footprint.find(g);
321 if(it != ctx.footprint.end())
322 return it->second;
323 std::set<gate_t> s;
324 switch(ctx.c.getGateType(g)) {
325 case BooleanGate::IN: {
326 const double p = ctx.c.getProb(g);
327 if(p > 0.0 && p < 1.0)
328 s.insert(g);
329 break;
330 }
331 case BooleanGate::AND:
332 case BooleanGate::OR:
333 case BooleanGate::NOT:
334 for(gate_t ch : ctx.c.getWires(g)) {
335 const auto &cs = footprintOf(ctx, ch);
336 s.insert(cs.begin(), cs.end());
337 }
338 break;
339 default: // MULIN / MULVAR / UNDETERMINED
340 throw CircuitException(
341 "d-tree: multivalued / undetermined gate not supported on the general "
342 "circuit path");
343 }
344 return ctx.footprint.emplace(g, std::move(s)).first->second;
345}
346
347/// Whether every variable in @p g's cone is assigned by @p A (so @p g has a
348/// definite truth value).
349bool determined(GenContext &ctx, gate_t g, const Assignment &A)
350{
351 for(gate_t v : footprintOf(ctx, g))
352 if(A.find(v) == A.end())
353 return false;
354 return true;
355}
356
357/// Truth value of a fully-determined gate under @p A.
358bool evalDet(GenContext &ctx, gate_t g, const Assignment &A)
359{
360 switch(ctx.c.getGateType(g)) {
361 case BooleanGate::IN: {
362 auto it = A.find(g);
363 if(it != A.end())
364 return it->second;
365 return ctx.c.getProb(g) >= 1.0; // a constant input (prob 0 or 1)
366 }
367 case BooleanGate::NOT:
368 return !evalDet(ctx, ctx.c.getWires(g)[0], A);
369 case BooleanGate::AND:
370 for(gate_t ch : ctx.c.getWires(g))
371 if(!evalDet(ctx, ch, A))
372 return false;
373 return true;
374 case BooleanGate::OR:
375 for(gate_t ch : ctx.c.getWires(g))
376 if(evalDet(ctx, ch, A))
377 return true;
378 return false;
379 default:
380 throw CircuitException("d-tree: unsupported gate in evalDet");
381 }
382}
383
384/// Partition @p live into groups whose free-variable footprints are pairwise
385/// disjoint (independent sub-formulas), preserving a deterministic order.
386std::vector<std::vector<gate_t> > genComponents(
387 GenContext &ctx, const std::vector<gate_t> &live, const Assignment &A)
388{
389 const size_t m = live.size();
390 std::vector<size_t> parent(m);
391 for(size_t i = 0; i < m; ++i)
392 parent[i] = i;
393 std::function<size_t(size_t)> find = [&](size_t x) {
394 while(parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
395 return x;
396 };
397 std::unordered_map<gate_t, size_t> owner; // first live index carrying a free var
398 for(size_t i = 0; i < m; ++i)
399 for(gate_t v : footprintOf(ctx, live[i])) {
400 if(A.find(v) != A.end())
401 continue; // assigned: not a shared free variable
402 auto it = owner.find(v);
403 if(it == owner.end())
404 owner.emplace(v, i);
405 else
406 parent[find(i)] = find(it->second);
407 }
408 std::map<size_t, std::vector<gate_t> > groups;
409 for(size_t i = 0; i < m; ++i)
410 groups[find(i)].push_back(live[i]);
411 std::vector<std::vector<gate_t> > out;
412 out.reserve(groups.size());
413 for(auto &kv : groups)
414 out.push_back(std::move(kv.second));
415 return out;
416}
417
418/// The free variable shared by the most members of @p live (ties -> smallest
419/// gate id), the Shannon-expansion pivot.
420gate_t genPivot(GenContext &ctx, const std::vector<gate_t> &live,
421 const Assignment &A)
422{
423 std::map<gate_t, size_t> freq; // ordered for a deterministic tie-break
424 for(gate_t c : live)
425 for(gate_t v : footprintOf(ctx, c))
426 if(A.find(v) == A.end())
427 ++freq[v];
428 gate_t best{};
429 size_t best_count = 0;
430 bool found = false;
431 for(const auto &kv : freq)
432 if(!found || kv.second > best_count) {
433 best = kv.first;
434 best_count = kv.second;
435 found = true;
436 }
437 return best;
438}
439
440DTreeInterval genBound(GenContext &ctx, gate_t g, const Assignment &A);
441
442/// Cheap sound interval of @c op over @p children under @p A: independent
443/// components compose exactly; within a component AND uses a Bonferroni lower /
444/// min upper and OR a max lower / union upper. Generalises @c dnfBounds.
445DTreeInterval genBoundGroup(GenContext &ctx, BooleanGate op,
446 const std::vector<gate_t> &children,
447 const Assignment &A)
448{
449 auto comps = genComponents(ctx, children, A);
450 double L = (op == BooleanGate::AND) ? 1.0 : 1.0; // AND: prod L; OR: prod (1-L)
451 double U = 1.0;
452 for(const auto &comp : comps) {
453 double gL, gU;
454 if(comp.size() == 1) {
455 DTreeInterval b = genBound(ctx, comp[0], A);
456 gL = b.lower;
457 gU = b.upper;
458 } else if(op == BooleanGate::AND) {
459 double sumL = 0.0;
460 gU = 1.0;
461 for(gate_t c : comp) {
462 DTreeInterval b = genBound(ctx, c, A);
463 sumL += b.lower;
464 gU = std::min(gU, b.upper);
465 }
466 gL = sumL - (static_cast<double>(comp.size()) - 1.0); // Bonferroni
467 if(gL < 0.0) gL = 0.0;
468 } else { // OR
469 double sumU = 0.0;
470 gL = 0.0;
471 for(gate_t c : comp) {
472 DTreeInterval b = genBound(ctx, c, A);
473 sumU += b.upper;
474 gL = std::max(gL, b.lower);
475 }
476 gU = (sumU > 1.0) ? 1.0 : sumU; // union bound
477 }
478 if(op == BooleanGate::AND) { L *= gL; U *= gU; }
479 else { L *= (1.0 - gL); U *= (1.0 - gU); }
480 }
481 if(op == BooleanGate::AND)
482 return {L, U};
483 return {1.0 - L, 1.0 - U};
484}
485
486DTreeInterval genBound(GenContext &ctx, gate_t g, const Assignment &A)
487{
488 switch(ctx.c.getGateType(g)) {
489 case BooleanGate::IN: {
490 auto it = A.find(g);
491 if(it != A.end())
492 return {it->second ? 1.0 : 0.0, it->second ? 1.0 : 0.0};
493 double p = ctx.c.getProb(g);
494 if(p < 0.0) p = 0.0;
495 if(p > 1.0) p = 1.0;
496 return {p, p};
497 }
498 case BooleanGate::NOT: {
499 DTreeInterval b = genBound(ctx, ctx.c.getWires(g)[0], A);
500 return {1.0 - b.upper, 1.0 - b.lower};
501 }
502 case BooleanGate::AND:
503 case BooleanGate::OR:
504 return genBoundGroup(ctx, ctx.c.getGateType(g), ctx.c.getWires(g), A);
505 default:
506 throw CircuitException("d-tree: unsupported gate in genBound");
507 }
508}
509
510/// Key identifying an exact subproblem: the (op, child set, assignment over the
511/// children's footprint) it depends on. @p single distinguishes a one-gate
512/// problem from a group with the same id.
513std::string exactKey(GenContext &ctx, char tag, BooleanGate op,
514 const std::vector<gate_t> &gates, const Assignment &A)
515{
516 std::string k(1, tag);
517 k += (op == BooleanGate::AND) ? 'A' : 'O';
518 std::set<gate_t> footunion;
519 for(gate_t g : gates) {
520 k += ':';
521 k += std::to_string(gid(g));
522 const auto &fp = footprintOf(ctx, g);
523 footunion.insert(fp.begin(), fp.end());
524 }
525 k += '|';
526 for(gate_t v : footunion) { // std::set: ascending, canonical
527 auto it = A.find(v);
528 if(it != A.end()) {
529 k += std::to_string(gid(v));
530 k += it->second ? '=' : '#';
531 }
532 }
533 return k;
534}
535
536DTreeInterval genRefineGroup(GenContext &ctx, BooleanGate op,
537 const std::vector<gate_t> &children,
538 Assignment &A, double w);
539
540DTreeInterval genRefine(GenContext &ctx, gate_t g, Assignment &A, double w)
541{
542 check_stack_depth();
544 throw CircuitException("Interrupted");
545 ++ctx.steps;
546 if(ctx.budget && ctx.steps > ctx.budget)
547 throw CircuitException("d-tree: cost budget exceeded");
548
549 if(determined(ctx, g, A)) {
550 double v = evalDet(ctx, g, A) ? 1.0 : 0.0;
551 return {v, v};
552 }
553 if(w > 0.0) {
554 DTreeInterval b = genBound(ctx, g, A);
555 if(b.upper - b.lower <= w)
556 return b;
557 }
558 switch(ctx.c.getGateType(g)) {
559 case BooleanGate::NOT: {
560 DTreeInterval r = genRefine(ctx, ctx.c.getWires(g)[0], A, w);
561 return {1.0 - r.upper, 1.0 - r.lower};
562 }
563 case BooleanGate::IN: {
564 const double p = ctx.c.getProb(g); // a single free variable
565 return {p, p};
566 }
567 case BooleanGate::AND:
568 case BooleanGate::OR:
569 return genRefineGroup(ctx, ctx.c.getGateType(g), ctx.c.getWires(g), A, w);
570 default:
571 throw CircuitException("d-tree: unsupported gate in genRefine");
572 }
573}
574
575DTreeInterval genRefineGroup(GenContext &ctx, BooleanGate op,
576 const std::vector<gate_t> &children,
577 Assignment &A, double w)
578{
579 check_stack_depth();
581 throw CircuitException("Interrupted");
582 ++ctx.steps;
583 if(ctx.budget && ctx.steps > ctx.budget)
584 throw CircuitException("d-tree: cost budget exceeded");
585
586 // Drop children fixed by A (and short-circuit on an absorbing one).
587 std::vector<gate_t> live;
588 live.reserve(children.size());
589 for(gate_t c : children) {
590 if(determined(ctx, c, A)) {
591 bool v = evalDet(ctx, c, A);
592 if(op == BooleanGate::AND && !v) return {0.0, 0.0};
593 if(op == BooleanGate::OR && v) return {1.0, 1.0};
594 // AND-true / OR-false: the identity, drop it
595 } else {
596 live.push_back(c);
597 }
598 }
599 if(live.empty())
600 return (op == BooleanGate::AND) ? DTreeInterval{1.0, 1.0}
601 : DTreeInterval{0.0, 0.0};
602
603 const bool exact = (w <= 0.0);
604 std::sort(live.begin(), live.end());
605 std::string key;
606 if(exact) {
607 key = exactKey(ctx, 'G', op, live, A);
608 auto it = ctx.exactMemo.find(key);
609 if(it != ctx.exactMemo.end())
610 return {it->second, it->second};
611 }
612
613 DTreeInterval res;
614 auto comps = genComponents(ctx, live, A);
615 if(comps.size() > 1) {
616 const double w_sub = w / static_cast<double>(comps.size());
617 double L = 1.0, U = 1.0; // AND: prod; OR: prod of (1-.)
618 for(auto &comp : comps) {
619 DTreeInterval r = genRefineGroup(ctx, op, comp, A, w_sub);
620 if(op == BooleanGate::AND) { L *= r.lower; U *= r.upper; }
621 else { L *= (1.0 - r.lower); U *= (1.0 - r.upper); }
622 }
623 res = (op == BooleanGate::AND) ? DTreeInterval{L, U}
624 : DTreeInterval{1.0 - L, 1.0 - U};
625 } else if(live.size() == 1) {
626 res = genRefine(ctx, live[0], A, w);
627 } else {
628 if(w > 0.0) {
629 DTreeInterval b = genBoundGroup(ctx, op, live, A);
630 if(b.upper - b.lower <= w)
631 return b; // approximate: do not memoise an early-stopped interval
632 }
633 const gate_t x = genPivot(ctx, live, A);
634 const double px = ctx.c.getProb(x);
635 A[x] = true;
636 DTreeInterval r1 = genRefineGroup(ctx, op, live, A, w);
637 A[x] = false;
638 DTreeInterval r0 = genRefineGroup(ctx, op, live, A, w);
639 A.erase(x);
640 res = {px * r1.lower + (1.0 - px) * r0.lower,
641 px * r1.upper + (1.0 - px) * r0.upper};
642 }
643
644 if(exact)
645 ctx.exactMemo.emplace(key, res.lower); // exact: lower == upper
646 return res;
647}
648
649} // namespace
650
652 double max_width, unsigned long budget,
653 unsigned long *steps_out)
654{
655 GenContext ctx{c, {}, {}};
656 ctx.budget = budget;
657 Assignment A;
658 DTreeInterval r = genRefine(ctx, root, A, max_width);
659 if(steps_out)
660 *steps_out = ctx.steps;
661 return r;
662}
663
664} // namespace provsql
Boolean provenance circuit with support for knowledge compilation.
BooleanGate
Gate types for a Boolean provenance circuit.
@ NOT
Logical negation of a single child gate.
@ OR
Logical disjunction of child gates.
@ AND
Logical conjunction of child gates.
@ IN
Input (variable) gate representing a base tuple.
gate_t
Strongly-typed gate identifier.
Definition Circuit.h:49
Anytime interval-bounds probability for monotone DNFs (d-trees).
Boolean circuit for provenance formula evaluation.
DTreeInterval dtreeBounds(const BooleanCircuit &c, Clauses clauses, double max_width, unsigned long budget, unsigned long *steps_out)
Definition DTree.cpp:272
DTreeInterval dtreeBoundsCircuit(const BooleanCircuit &c, gate_t root, double max_width, unsigned long budget, unsigned long *steps_out)
Certified probability interval of an arbitrary Boolean circuit, refined to a target width (the d-tree...
Definition DTree.cpp:651
bool provsql_interrupted
Global variable that becomes true if this particular backend received an interrupt signal.
Definition provsql.c:85
Core types, constants, and utilities shared across ProvSQL.
A certified probability interval lower <= Pr <= upper.
Definition DTree.h:30