ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
SumCmpEvaluator.cpp
Go to the documentation of this file.
1/**
2 * @file SumCmpEvaluator.cpp
3 * @brief Implementation of the weighted-sum DP pre-pass.
4 * See @c SumCmpEvaluator.h for the DP, the soundness contract,
5 * and the pseudo-polynomial caveat.
6 */
7#include "SumCmpEvaluator.h"
8
9#include <cstdint>
10#include <vector>
11
12#include "Aggregation.h" // AggregationOperator + ComparisonOperator
13#include "CmpEvaluatorCommon.h" // matchAggCmp, computeRefCounts, contributorProb
14
15namespace provsql {
16
17namespace {
18
19/* Reachable-sum range cap. The DP is O(N x R) in time and O(R) in
20 * memory with R the reachable-sum span ; R is linear in the weight /
21 * threshold magnitude, hence exponential in their bit-length (Remark 3
22 * of the HAVING-complexity theory). Above this span the pass declines
23 * and the cmp falls back to the general enumeration path. ~16M doubles
24 * = 128 MB is the ceiling we accept for the closed form. */
25constexpr long kMaxSumRange = 1L << 24;
26
27/* Does the integer sum @p s satisfy @p s op C ? */
28static bool sumSatisfies(long s, ComparisonOperator op, long C)
29{
30 switch (op) {
31 case ComparisonOperator::EQ: return s == C;
32 case ComparisonOperator::NE: return s != C;
33 case ComparisonOperator::LE: return s <= C;
34 case ComparisonOperator::LT: return s < C;
35 case ComparisonOperator::GE: return s >= C;
36 case ComparisonOperator::GT: return s > C;
37 }
38 return false;
39}
40
41} // namespace
42
44{
45 unsigned resolved = 0;
46 const auto nb = gc.getNbGates();
47
48 /* Snapshot the cmp-gate ids so in-place rewrites don't affect the
49 * iteration : same pattern as runCountCmpEvaluator. */
50 std::vector<gate_t> cmps;
51 for (std::size_t i = 0; i < nb; ++i) {
52 auto g = static_cast<gate_t>(i);
53 if (gc.getGateType(g) == gate_cmp)
54 cmps.push_back(g);
55 }
56 if (cmps.empty()) return 0;
57
58 /* Reference counts computed once; resolveCmpToBernoulli only clears
59 * the cmp's own wires, leaving every other gate's ref count intact. */
60 auto ref = computeRefCounts(gc);
61
62 for (gate_t cmp : cmps) {
63 if (gc.getGateType(cmp) != gate_cmp) continue; /* defensive */
64
65 AggCmpMatch match;
66 if (!matchAggCmp(gc, cmp, match))
67 continue;
68
69 /* Genuine SUM only. matchAggCmp remaps SUM-of-1s (COUNT(*)) to
70 * COUNT, so a match that is still SUM here carries real weights;
71 * MIN / MAX / AVG are out of this pass's scope. */
73 continue;
74
75 /* Reachable-sum range [lo, hi] from the weights, and the cap check
76 * (done before the independence walk : a too-wide range is rejected
77 * regardless of soundness). */
78 long lo = 0, hi = 0;
79 for (long m : match.ms) {
80 if (m < 0) lo += m; else hi += m;
81 }
82 const long range = hi - lo + 1;
83 if (range <= 0 || range > kMaxSumRange) continue;
84
85 /* Independence certification, identical to runCountCmpEvaluator. */
86 if (ref[static_cast<std::size_t>(match.agg)] != 1) continue;
87 bool sound = true;
88 std::vector<double> p;
89 p.reserve(match.ks.size());
90 for (std::size_t i = 0; i < match.ks.size(); ++i) {
91 if (ref[static_cast<std::size_t>(match.semimods[i])] != 1) { sound = false; break; }
92 double pi = contributorProb(gc, match.ks[i], ref, sound);
93 if (!sound) break;
94 p.push_back(pi);
95 }
96 if (!sound) continue;
97
98 /* Subset-sum DP : dp[s - lo] = Pr(sum of present weights = s).
99 * Start from the empty world (sum 0, mass 1), fold in each child. */
100 std::vector<double> dp(static_cast<std::size_t>(range), 0.0);
101 dp[static_cast<std::size_t>(-lo)] = 1.0; /* sum 0 */
102 for (std::size_t i = 0; i < p.size(); ++i) {
103 const long m = match.ms[i];
104 const double pi = p[i];
105 const double qi = 1.0 - pi;
106 if (m == 0) {
107 /* Present or absent leaves the sum unchanged ; the row only
108 * affects group (non-)emptiness, handled by the empty-world
109 * subtraction below. Mass is conserved, so dp is untouched. */
110 continue;
111 }
112 std::vector<double> ndp(static_cast<std::size_t>(range), 0.0);
113 for (long s = lo; s <= hi; ++s) {
114 const double cur = dp[static_cast<std::size_t>(s - lo)];
115 if (cur == 0.0) continue;
116 /* Child absent : sum stays s. */
117 ndp[static_cast<std::size_t>(s - lo)] += cur * qi;
118 /* Child present : sum becomes s + m (always in range by
119 * construction of [lo, hi]). */
120 ndp[static_cast<std::size_t>(s + m - lo)] += cur * pi;
121 }
122 dp.swap(ndp);
123 }
124
125 /* Naive Pr(S op C) over all worlds, then exclude the empty group. */
126 double pr = 0.0;
127 for (long s = lo; s <= hi; ++s) {
128 if (sumSatisfies(s, match.op, match.C))
129 pr += dp[static_cast<std::size_t>(s - lo)];
130 }
131 if (sumSatisfies(0, match.op, match.C)) {
132 double prEmpty = 1.0;
133 for (double pi : p) prEmpty *= (1.0 - pi);
134 pr -= prEmpty;
135 }
136
137 /* Defensive clamp against floating-point roundoff. */
138 if (pr < 0.0) pr = 0.0;
139 if (pr > 1.0) pr = 1.0;
140
141 gc.resolveCmpToBernoulli(cmp, pr);
142 ++resolved;
143 }
144
145 return resolved;
146}
147
148} // namespace provsql
Typed aggregation value, operator, and aggregator abstractions.
@ SUM
SUM → integer or float.
Definition Aggregation.h:53
ComparisonOperator
SQL comparison operators used in gate_cmp circuit gates.
Definition Aggregation.h:39
@ LT
Less than (<).
Definition Aggregation.h:43
@ GT
Greater than (>).
Definition Aggregation.h:45
@ LE
Less than or equal (<=).
Definition Aggregation.h:42
@ NE
Not equal (<>).
Definition Aggregation.h:41
@ GE
Greater than or equal (>=).
Definition Aggregation.h:44
gate_t
Strongly-typed gate identifier.
Definition Circuit.h:49
Shared machinery for the closed-form HAVING gate_cmp probability evaluators (Poisson-binomial COUNT,...
Closed-form probability resolution for HAVING SUM(a) op C gate_cmps via a weighted-sum DP.
gateType getGateType(gate_t g) const
Return the type of gate g.
Definition Circuit.h:130
std::vector< gate_t >::size_type getNbGates() const
Return the total number of gates in the circuit.
Definition Circuit.h:103
In-memory provenance circuit with semiring-generic evaluation.
void resolveCmpToBernoulli(gate_t g, double p)
Replace a gate_cmp by a constant Boolean leaf (gate_one for p == 1, gate_zero for p == 0) or by a Ber...
std::vector< unsigned > computeRefCounts(const GenericCircuit &gc)
Reference count of every gate as a wire-target across the whole circuit.
bool matchAggCmp(GenericCircuit &gc, gate_t cmp, AggCmpMatch &out)
Try to match cmp against gate_cmp(gate_agg(α, semimod_i(K_i, m_i)*), gate_value(C)).
double contributorProb(const GenericCircuit &gc, gate_t g, const std::vector< unsigned > &ref, bool &ok)
Read-once marginal probability of a count/aggregate contributor (the K side of a semimod).
unsigned runSumCmpEvaluator(GenericCircuit &gc)
Run the weighted-sum DP pre-pass over gc.
Result of matching a gate_cmp against the canonical HAVING aggregate-comparison shape.
gate_t agg
the gate_agg operand of the cmp
long C
the constant threshold, on the same integer grid as ms
std::vector< gate_t > ks
the K side of each semimod (contributor root)
std::vector< gate_t > semimods
the per-child gate_semimod parents
std::vector< long > ms
the M side of each semimod (per-row value), scaled to a common integer grid (numeric / decimal-float ...
AggregationOperator agg_kind
effective aggregate (SUM-of-1s remapped to COUNT)
ComparisonOperator op
comparator, flipped if the agg sits on the right