50#include "utils/uuid.h"
51#include "utils/array.h"
52#include "catalog/pg_type.h"
61#include <unordered_map>
62#include <unordered_set>
69constexpr double kNaN = std::numeric_limits<double>::quiet_NaN();
70constexpr int kGrid = 400;
73void collectRvLeaves(
const GenericCircuit &gc,
gate_t g,
74 std::unordered_set<gate_t> &out)
76 std::unordered_set<gate_t> seen;
77 std::vector<gate_t> stack{g};
78 while (!stack.empty()) {
79 gate_t x = stack.back(); stack.pop_back();
80 if (!seen.insert(x).second)
continue;
90double evalWithVar(
const GenericCircuit &gc,
gate_t g,
gate_t var,
double val)
94 return (g == var) ? val :
kNaN;
97 catch (
const CircuitException &) {
return kNaN; }
101 auto ev = [&](
gate_t c) {
return evalWithVar(gc, c, var, val); };
104 double s = 0.0;
for (
gate_t c : w) s += ev(c);
return s;
107 double p = 1.0;
for (
gate_t c : w) p *= ev(c);
return p;
110 return (w.size() == 2) ? ev(w[0]) - ev(w[1]) :
kNaN;
112 return (w.size() == 2) ? ev(w[0]) / ev(w[1]) :
kNaN;
114 return (w.size() == 1) ? -ev(w[0]) :
kNaN;
130std::optional<std::pair<double, double>>
131affineInVar(
const GenericCircuit &gc,
gate_t g,
gate_t var)
133 using R = std::optional<std::pair<double, double>>;
136 return (g == var) ? R({1.0, 0.0}) : std::nullopt;
139 catch (
const CircuitException &) {
return std::nullopt; }
145 double s = 0.0, c = 0.0;
147 auto a = affineInVar(gc, ch, var);
148 if (!a)
return std::nullopt;
149 s += a->first; c += a->second;
154 double s = 0.0, c = 1.0;
156 auto a = affineInVar(gc, ch, var);
157 if (!a)
return std::nullopt;
159 if (s != 0.0 && a->first != 0.0)
return std::nullopt;
160 const double ns = s * a->second + c * a->first;
167 if (w.size() != 2)
return std::nullopt;
168 auto a = affineInVar(gc, w[0], var), b = affineInVar(gc, w[1], var);
169 if (!a || !b)
return std::nullopt;
170 return R({a->first - b->first, a->second - b->second});
173 if (w.size() != 1)
return std::nullopt;
174 auto a = affineInVar(gc, w[0], var);
175 if (!a)
return std::nullopt;
176 return R({-a->first, -a->second});
179 if (w.size() != 2)
return std::nullopt;
180 auto a = affineInVar(gc, w[0], var), b = affineInVar(gc, w[1], var);
181 if (!a || !b)
return std::nullopt;
182 if (b->first != 0.0 || b->second == 0.0)
return std::nullopt;
183 return R({a->first / b->second, a->second / b->second});
196 const DistributionFamily *family;
208 std::shared_ptr<Distribution> dist;
210 double slope = 0.0, intercept = 0.0;
218 std::unique_ptr<Distribution> shared_dist;
220 std::vector<Term> terms;
225bool reduceIndicator(
const GenericCircuit &gc,
gate_t k_gate,
226 double &bern,
gate_t &cmp_out)
229 bool have_cmp =
false;
230 std::vector<gate_t> stack{k_gate};
231 while (!stack.empty()) {
232 gate_t g = stack.back(); stack.pop_back();
241 const double p = gc.
getProb(g);
242 if (std::isnan(p))
return false;
247 if (have_cmp)
return false;
270std::optional<CollapsePlan>
271buildCollapsePlan(
const GenericCircuit &gc,
gate_t agg)
278 const auto &children = gc.
getWires(agg);
279 if (children.empty())
return std::nullopt;
286 std::vector<Raw> raws;
287 raws.reserve(children.size());
288 std::unordered_map<gate_t, unsigned> leaf_rows;
290 for (
gate_t sm : children) {
293 if (smw.size() != 2)
return std::nullopt;
297 catch (
const CircuitException &) {
return std::nullopt; }
301 if (!reduceIndicator(gc, smw[0], bern, cmp))
return std::nullopt;
303 if (cw.size() != 2)
return std::nullopt;
306 if (!ok)
return std::nullopt;
308 raws.push_back({bern, value, cw[0], cw[1], op});
310 std::unordered_set<gate_t> rowleaves;
311 collectRvLeaves(gc, cmp, rowleaves);
312 for (
gate_t l : rowleaves) ++leaf_rows[l];
316 unsigned nb_shared = 0;
317 for (
const auto &[leaf, rows] : leaf_rows)
318 if (rows > 1) { shared = leaf; ++nb_shared; }
319 if (nb_shared != 1)
return std::nullopt;
322 if (!shared_spec)
return std::nullopt;
325 if (!shared_dist->integrationRange(lo, hi) || !(hi > lo))
return std::nullopt;
327 std::vector<Term> terms;
328 terms.reserve(raws.size());
329 for (
const auto &r : raws) {
330 std::unordered_set<gate_t> fa, fb;
331 collectRvLeaves(gc, r.a, fa);
332 collectRvLeaves(gc, r.b, fb);
335 shared_side =
static_cast<gate_t>(0);
337 auto is_per_row = [&](
gate_t side,
const std::unordered_set<gate_t> &f) {
340 auto is_shared_side = [&](
const std::unordered_set<gate_t> &f) {
341 return f.empty() || (f.size() == 1 && f.count(shared));
343 if (is_per_row(r.a, fa) && is_shared_side(fb)) {
344 per_row = r.a; shared_side = r.b;
345 }
else if (is_per_row(r.b, fb) && is_shared_side(fa)) {
346 per_row = r.b; shared_side = r.a; op = flipOp(op);
354 if (!spec)
return std::nullopt;
355 Term term{spec->family, spec->p1, spec->p2, shared_side, op,
357 term.dist = std::shared_ptr<Distribution>(
358 spec->family->factory(spec->p1, spec->p2));
359 if (
auto aff = affineInVar(gc, shared_side, shared)) {
361 term.slope = aff->first;
362 term.intercept = aff->second;
364 terms.push_back(std::move(term));
369 plan.shared = shared;
370 plan.shared_dist = std::move(shared_dist);
373 plan.terms = std::move(terms);
379struct Grid { std::vector<double> node, weight; };
380std::optional<Grid> pdfGrid(
const Distribution &d,
double lo,
double hi)
382 if (!(hi > lo))
return std::nullopt;
383 const double dx = (hi - lo) / (kGrid - 1);
385 grid.node.resize(kGrid);
386 grid.weight.resize(kGrid);
388 for (
int gi = 0; gi < kGrid; ++gi) {
389 grid.node[gi] = lo + gi * dx;
390 double w = d.pdf(grid.node[gi]);
391 if (std::isnan(w) || w < 0.0) w = 0.0;
392 if (gi == 0 || gi == kGrid - 1) w *= 0.5;
396 if (!(wsum > 0.0))
return std::nullopt;
397 for (
double &w : grid.weight) w /= wsum;
403bool perNodeProbs(
const GenericCircuit &gc,
const CollapsePlan &plan,
404 double b, std::vector<double> &q)
407 q.reserve(plan.terms.size());
408 for (
const auto &t : plan.terms) {
410 const double c = t.affine ? std::fma(t.slope, b, t.intercept)
411 : evalWithVar(gc, t.shared_side, plan.shared, b);
412 if (std::isnan(c))
return false;
413 double F = t.dist->cdf(c);
414 if (std::isnan(F))
return false;
415 if (F < 0.0) F = 0.0;
else if (F > 1.0) F = 1.0;
418 case ComparisonOperator::LT:
419 case ComparisonOperator::LE: qi = F; break;
420 case ComparisonOperator::GT:
421 case ComparisonOperator::GE: qi = 1.0 - F; break;
422 default: return false;
425 if (qi < 0.0) qi = 0.0;
else if (qi > 1.0) qi = 1.0;
432std::vector<double> poissonBinomialPmf(
const std::vector<double> &q)
434 std::vector<double> pmf(q.size() + 1, 0.0);
436 std::size_t filled = 0;
437 for (
double qi : q) {
440 for (std::size_t j = filled + 1; j >= 1; --j)
441 pmf[j] = pmf[j] * (1.0 - qi) + pmf[j - 1] * qi;
442 pmf[0] *= (1.0 - qi);
451std::optional<std::vector<double>>
452aggCollapsedPmf(
const GenericCircuit &gc,
gate_t agg)
454 auto plan = buildCollapsePlan(gc, agg);
455 if (!plan)
return std::nullopt;
459 for (
const auto &t : plan->terms)
460 if (t.value != 1.0)
return std::nullopt;
462 auto grid = pdfGrid(*plan->shared_dist, plan->lo, plan->hi);
463 if (!grid)
return std::nullopt;
465 const std::size_t n = plan->terms.size();
466 std::vector<double> pmf(n + 1, 0.0);
467 std::vector<double> q;
468 for (
int gi = 0; gi < kGrid; ++gi) {
469 if (!perNodeProbs(gc, *plan, grid->node[gi], q))
return std::nullopt;
470 const std::vector<double> pb = poissonBinomialPmf(q);
471 const double w = grid->weight[gi];
472 for (std::size_t j = 0; j <= n; ++j) pmf[j] += w * pb[j];
479std::optional<std::pair<double, double>>
482 auto plan = buildCollapsePlan(gc, agg);
483 if (!plan)
return std::nullopt;
485 auto grid = pdfGrid(*plan->shared_dist, plan->lo, plan->hi);
486 if (!grid)
return std::nullopt;
488 double m1 = 0.0, m2 = 0.0;
489 std::vector<double> q;
490 for (
int gi = 0; gi < kGrid; ++gi) {
491 if (!perNodeProbs(gc, *plan, grid->node[gi], q))
return std::nullopt;
492 double mean_b = 0.0, var_b = 0.0;
493 for (std::size_t i = 0; i < plan->terms.size(); ++i) {
494 const double v = plan->terms[i].value, qi = q[i];
496 var_b += v * v * qi * (1.0 - qi);
498 const double w = grid->weight[gi];
500 m2 += w * (var_b + mean_b * mean_b);
502 return std::make_pair(m1, m2);
508 if (k == 0)
return 1.0;
509 if (k > 2)
return std::nullopt;
514 if (!m)
return std::nullopt;
515 return (k == 1) ? m->first : m->second;
522 if (k == 0)
return 1.0;
523 if (k > 2)
return std::nullopt;
530 const auto &ew = gc.
getWires(event);
531 if (ew.size() != 2)
return std::nullopt;
535 for (
int i = 0; i < 2; ++i) {
536 const gate_t s = ew[i], o = ew[1 - i];
538 ygate = s; cagg = o;
break;
541 if (ygate ==
static_cast<gate_t>(0))
return std::nullopt;
545 if (!ytmpl)
return std::nullopt;
546 if (!ytmpl->family->factory(1.0, 1.0)->isDiscrete())
return std::nullopt;
549 std::unordered_set<gate_t> yfoot;
550 for (
gate_t w : gc.
getWires(ygate)) collectRvLeaves(gc, w, yfoot);
551 if (!yfoot.count(target))
return std::nullopt;
554 auto Cpmf = aggCollapsedPmf(gc, cagg);
555 if (!Cpmf)
return std::nullopt;
559 if (!rspec)
return std::nullopt;
562 if (!rdist->integrationRange(rlo, rhi))
return std::nullopt;
563 auto rgrid = pdfGrid(*rdist, rlo, rhi);
564 if (!rgrid)
return std::nullopt;
566 const auto &ywires = gc.
getWires(ygate);
569 if (
static_cast<std::size_t
>(p.
wire_slot) >= ywires.size())
return kNaN;
570 return evalWithVar(gc, ywires[p.
wire_slot], target, r);
575 const std::size_t J = Cpmf->size();
576 double Z = 0.0, N1 = 0.0, N2 = 0.0;
577 for (
int gi = 0; gi < kGrid; ++gi) {
578 const double r = rgrid->node[gi];
579 const double p1v = resolveParam(ytmpl->p1, r);
580 const double p2v = resolveParam(ytmpl->p2, r);
581 if (std::isnan(p1v))
return std::nullopt;
582 auto ydist = ytmpl->family->factory(p1v, std::isnan(p2v) ? 0.0 : p2v);
584 bool degenerate =
false;
585 for (std::size_t j = 0; j < J; ++j) {
586 const double pj = (*Cpmf)[j];
587 if (pj <= 0.0)
continue;
588 const double pmfy = ydist->pdf(
static_cast<double>(j));
589 if (std::isnan(pmfy)) { degenerate =
true;
break; }
596 if (degenerate)
continue;
597 const double w = rgrid->weight[gi];
602 if (!(Z > 0.0))
return std::nullopt;
603 return (k == 1) ? (N1 / Z) : (N2 / Z);
623 const int32 k = PG_GETARG_INT32(1);
625 provsql_error(
"agg_collapsed_moment: k must be non-negative (got %d)", k);
629 static_cast<unsigned>(k));
632 return Float8GetDatum(*r);
633 }
catch (
const std::exception &e) {
660 Datum elems[2] = { Float8GetDatum(m->first), Float8GetDatum(m->second) };
661 ArrayType *arr = construct_array(elems, 2, FLOAT8OID,
662 sizeof(float8), FLOAT8PASSBYVAL,
'd');
663 PG_RETURN_ARRAYTYPE_P(arr);
664 }
catch (
const std::exception &e) {
ComparisonOperator cmpOpFromOid(Oid op_oid, bool &ok)
Map a PostgreSQL comparison-operator OID to a ComparisonOperator.
AggregationOperator getAggregationOperator(Oid oid)
Map a PostgreSQL aggregate function OID to an AggregationOperator.
Typed aggregation value, operator, and aggregator abstractions.
AggregationOperator
SQL aggregation functions tracked by ProvSQL.
@ COUNT
COUNT(*) or COUNT(expr) → integer.
@ SUM
SUM → integer or float.
ComparisonOperator
SQL comparison operators used in gate_cmp circuit gates.
@ LE
Less than or equal (<=).
@ GE
Greater than or equal (>=).
GenericCircuit getGenericCircuit(pg_uuid_t token)
Build a GenericCircuit from the mmap store rooted at token.
Build in-memory circuits from the mmap-backed persistent store.
Generic directed-acyclic-graph circuit template and gate identifier.
gate_t
Strongly-typed gate identifier.
Datum agg_collapsed_moments(PG_FUNCTION_ARGS)
SQL: agg_collapsed_moments(token uuid) -> float8[] {E[C], E[C^2]}.
Datum agg_collapsed_moment(PG_FUNCTION_ARGS)
SQL: agg_collapsed_moment(token uuid, k integer) -> float8.
Rao-Blackwellised (collapsed) evaluation of a correlated COUNT / SUM and of a latent conditioned on s...
Per-family polymorphic view over a continuous gate_rv distribution (§F.1 class hierarchy).
Continuous random-variable helpers (distribution parsing, moments).
std::vector< gate_t > & getWires(gate_t g)
Return a mutable reference to the child-wire list of gate g.
gateType getGateType(gate_t g) const
Return the type of gate g.
gate_t getGate(const uuid &u)
Return (or create) the gate associated with UUID u.
In-memory provenance circuit with semiring-generic evaluation.
std::string getExtra(gate_t g) const
Return the string extra for gate g.
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.
Abstract per-family continuous distribution.
std::optional< double > aggCollapsedRawMoment(const GenericCircuit &gc, gate_t agg, unsigned k)
Collapsed raw moment E[C^k] of a correlated COUNT / SUM agg, or std::nullopt when the circuit does no...
double parseDoubleStrict(const std::string &s)
Strictly parse s as a double.
std::optional< std::pair< double, double > > aggCollapsedRawMoments(const GenericCircuit &gc, gate_t agg)
std::unique_ptr< Distribution > makeDistribution(const DistributionSpec &spec)
Construct the per-family Distribution for a parsed spec.
std::optional< double > collapsedConditionalMoment(const GenericCircuit &gc, gate_t target, gate_t event, unsigned k)
Collapsed exact posterior raw moment E[R^k | Y = C] for a latent target R conditioned (through the eq...
std::optional< DistributionSpec > parse_distribution_spec(const std::string &s)
Parse the on-disk text encoding of a gate_rv distribution.
std::optional< DistributionTemplate > parse_distribution_template(const std::string &s)
Parse the on-disk text encoding of a gate_rv distribution, keeping wired (token) parameters as wire r...
Uniform error-reporting macros for ProvSQL.
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
Core types, constants, and utilities shared across ProvSQL.
provsql_arith_op
Arithmetic operator tags used by gate_arith.
@ PROVSQL_ARITH_DIV
binary, child0 / child1
@ PROVSQL_ARITH_PLUS
n-ary, sum of children
@ PROVSQL_ARITH_NEG
unary, -child0
@ PROVSQL_ARITH_MINUS
binary, child0 - child1
@ PROVSQL_ARITH_TIMES
n-ary, product of children
@ gate_rv
Continuous random-variable leaf (extra encodes distribution).
@ gate_arith
n-ary arithmetic gate over scalar-valued children (info1 holds operator tag)
string uuid2string(pg_uuid_t uuid)
Format a pg_uuid_t as a std::string.
C++ utility functions for UUID manipulation.
One parameter slot of a gate_rv, either a literal or a wire.
double literal
Value when wire_slot < 0.
int wire_slot
>= 0: resolve from the gate's wires[wire_slot].