29#include "utils/uuid.h"
44#include <unordered_map>
45#include <unordered_set>
57void extractSubcircuit(
const GenericCircuit &gc,
gate_t root,
58 GenericCircuit &sub,
gate_t &sub_root)
60 std::unordered_set<gate_t> seen;
61 std::stack<gate_t> stk;
63 while (!stk.empty()) {
64 gate_t g = stk.top(); stk.pop();
65 if (!seen.insert(g).second)
continue;
66 const std::string u = gc.
getUUID(g);
69 const double pr = gc.
getProb(g);
70 if (!std::isnan(pr)) sub.
setProb(
id, pr);
74 sub.
setInfos(
id, infos.first, infos.second);
77 if (infos.first != 0 || infos.second != 0)
78 sub.
setInfos(
id, infos.first, infos.second);
104 extractSubcircuit(gc, boolRoot, sub, sub_root);
111using RvSet = std::set<gate_t>;
127bool rvIsParametric(
const GenericCircuit &gc,
gate_t g)
131 return tmpl && tmpl->parametric();
139 return tmpl.family->factory(0.0, 0.0)->meanIsAffine();
147class FootprintCache {
149 explicit FootprintCache(
const GenericCircuit &gc) : gc_(gc) {}
151 const RvSet &of(
gate_t g) {
152 auto it = cache_.find(g);
153 if (it != cache_.end())
return it->second;
155 auto type = gc_.getGateType(g);
166 for (
gate_t c : gc_.getWires(g)) {
167 const auto &cs = of(c);
168 s.insert(cs.begin(), cs.end());
178 for (
gate_t c : gc_.getWires(g)) {
179 const auto &cs = of(c);
180 s.insert(cs.begin(), cs.end());
183 const auto &wires = gc_.getWires(g);
184 if (gc_.isCategoricalMixture(g)) {
189 for (std::size_t i = 1; i < wires.size(); ++i) {
190 const auto &fm = of(wires[i]);
191 s.insert(fm.begin(), fm.end());
193 }
else if (wires.size() == 3) {
202 const auto &fp = of(wires[0]);
203 s.insert(fp.begin(), fp.end());
204 const auto &fx = of(wires[1]);
205 s.insert(fx.begin(), fx.end());
206 const auto &fy = of(wires[2]);
207 s.insert(fy.begin(), fy.end());
217 const auto &wires = gc_.getWires(g);
218 if (!wires.empty()) {
219 const auto &fk = of(wires[0]);
220 s.insert(fk.begin(), fk.end());
234 for (
gate_t c : gc_.getWires(g)) {
235 const auto &cs = of(c);
236 s.insert(cs.begin(), cs.end());
247 return cache_.emplace(g, std::move(s)).first->second;
251 const GenericCircuit &gc_;
252 std::unordered_map<gate_t, RvSet> cache_;
255bool pairwise_disjoint(FootprintCache &fp,
const std::vector<gate_t> &children)
258 for (
gate_t c : children) {
259 const auto &fpc = fp.of(c);
261 if (!seen.insert(r).second)
return false;
280iidOrderStatMean(
const GenericCircuit &gc,
gate_t g,
bool isMax,
283 const auto &raw_wires = gc.
getWires(g);
284 if (raw_wires.empty())
290 std::vector<gate_t> wires;
291 wires.reserve(raw_wires.size());
293 std::set<gate_t> seen;
294 for (
gate_t c : raw_wires)
295 if (seen.insert(c).second)
298 if (!pairwise_disjoint(fp, wires))
301 std::vector<DistributionSpec> specs;
302 specs.reserve(wires.size());
315 for (std::size_t i = 1; i < specs.size(); ++i)
316 if (specs[i].family != specs[0].family ||
317 specs[i].p1 != specs[0].p1 || specs[i].p2 != specs[0].p2)
332mixedOrderStatMean(
const GenericCircuit &gc,
gate_t g,
bool isMax,
335 const auto &raw_wires = gc.
getWires(g);
336 if (raw_wires.empty())
338 std::vector<gate_t> wires;
340 std::set<gate_t> seen;
341 for (
gate_t c : raw_wires)
342 if (seen.insert(c).second)
345 if (!pairwise_disjoint(fp, wires))
350 std::vector<std::unique_ptr<Distribution>> dists;
351 double lo = 0.0, hi = 0.0;
361 if (!d->integrationRange(clo, chi))
363 if (first) { lo = clo; hi = chi; first =
false; }
364 else { lo = std::min(lo, clo); hi = std::max(hi, chi); }
365 dists.push_back(std::move(d));
374 for (
const auto &d : dists) {
375 const double F = d->cdf(t);
376 if (std::isnan(F))
return std::numeric_limits<double>::quiet_NaN();
381 double prod1mF = 1.0;
382 for (
const auto &d : dists) {
383 const double F = d->cdf(t);
384 if (std::isnan(F))
return std::numeric_limits<double>::quiet_NaN();
385 prod1mF *= (1.0 - F);
389 if (std::isnan(integral))
391 return lo + integral;
394unsigned mc_samples_or_throw(
const std::string &what)
398 throw CircuitException(
399 what +
" could not be decomposed analytically and "
400 "provsql.rv_mc_samples = 0 disables the Monte Carlo fallback");
410 "%s: no closed form found; estimating by Monte Carlo over %d samples "
411 "(an approximation, not an exact moment) -- set provsql.rv_mc_samples = 0 "
412 "to require an exact result instead", what.c_str(), n);
413 return static_cast<unsigned>(n);
416double mc_raw_moment(
const GenericCircuit &gc,
gate_t g,
unsigned k,
417 const std::string &what)
420 if (samples.empty())
return 0.0;
426 std::size_t finite_count = 0;
427 for (
double x : samples) {
428 if (std::isnan(x))
continue;
429 total += std::pow(x,
static_cast<double>(k));
432 if (finite_count == 0)
return std::numeric_limits<double>::quiet_NaN();
433 return total /
static_cast<double>(finite_count);
436double mc_central_moment(
const GenericCircuit &gc,
gate_t g,
unsigned k,
437 double mu,
const std::string &what)
440 if (samples.empty())
return 0.0;
442 std::size_t finite_count = 0;
443 for (
double x : samples) {
444 if (std::isnan(x))
continue;
445 const double d = x - mu;
446 total += std::pow(d,
static_cast<double>(k));
449 if (finite_count == 0)
return std::numeric_limits<double>::quiet_NaN();
450 return total /
static_cast<double>(finite_count);
457unsigned min_accepted_floor(
unsigned attempted)
459 unsigned floor = attempted / 1000;
460 return floor < 5 ? 5 : floor;
464 const std::string &what)
466 if (cs.accepted.empty()) {
472 throw CircuitException(
473 what +
": conditioning event is infeasible (0 of " +
474 std::to_string(cs.attempted) +
475 " Monte Carlo samples satisfied it)");
477 const unsigned floor = min_accepted_floor(cs.attempted);
478 if (cs.accepted.size() < floor) {
479 throw CircuitException(
480 what +
": conditional MC accepted only " +
481 std::to_string(cs.accepted.size()) +
" out of " +
482 std::to_string(cs.attempted) +
483 " samples (need >= " + std::to_string(floor) +
484 "); raise provsql.rv_mc_samples or tighten the event.");
488double mc_conditional_raw_moment(
const GenericCircuit &gc,
gate_t g,
489 unsigned k,
gate_t event_root,
490 const std::string &what)
493 gc, g, event_root, mc_samples_or_throw(what));
494 check_acceptance_or_throw(cs, what);
499 std::size_t finite_count = 0;
500 for (
double x : cs.accepted) {
501 if (std::isnan(x))
continue;
502 total += std::pow(x,
static_cast<double>(k));
505 if (finite_count == 0)
return std::numeric_limits<double>::quiet_NaN();
506 return total /
static_cast<double>(finite_count);
509double mc_conditional_central_moment(
const GenericCircuit &gc,
gate_t g,
510 unsigned k,
double mu,
512 const std::string &what)
515 gc, g, event_root, mc_samples_or_throw(what));
516 check_acceptance_or_throw(cs, what);
518 std::size_t finite_count = 0;
519 for (
double x : cs.accepted) {
520 if (std::isnan(x))
continue;
521 const double d = x - mu;
522 total += std::pow(d,
static_cast<double>(k));
525 if (finite_count == 0)
return std::numeric_limits<double>::quiet_NaN();
526 return total /
static_cast<double>(finite_count);
529double rec_expectation(
const GenericCircuit &gc,
gate_t g, FootprintCache &fp);
530double rec_variance(
const GenericCircuit &gc,
gate_t g, FootprintCache &fp);
531double rec_raw_moment(
const GenericCircuit &gc,
gate_t g,
unsigned k,
550 double sw = 0.0, swx = 0.0;
551 for (
const auto &[x, w] : post.particles) {
552 if (std::isnan(x))
continue;
554 swx += w * std::pow(x,
static_cast<double>(k));
556 if (sw <= 0.0)
return std::numeric_limits<double>::quiet_NaN();
564 double sw = 0.0, swd = 0.0;
565 for (
const auto &[x, w] : post.particles) {
566 if (std::isnan(x))
continue;
568 swd += w * std::pow(x - mu,
static_cast<double>(k));
570 if (sw <= 0.0)
return std::numeric_limits<double>::quiet_NaN();
579 auto &pts = post.particles;
580 pts.erase(std::remove_if(pts.begin(), pts.end(),
581 [](
const std::pair<double, double> &pw) {
582 return std::isnan(pw.first);
585 if (pts.empty())
return std::numeric_limits<double>::quiet_NaN();
586 std::sort(pts.begin(), pts.end(),
587 [](
const auto &a,
const auto &b) { return a.first < b.first; });
589 for (
const auto &pw : pts) total += pw.second;
590 if (!(total > 0.0))
return pts.front().first;
591 const double target = p * total;
593 for (std::size_t i = 0; i < pts.size(); ++i) {
594 const double prev = cum;
595 cum += pts[i].second;
597 if (i == 0)
return pts[0].first;
600 const double frac = (pts[i].second > 0.0)
601 ? (target - prev) / pts[i].second
603 return pts[i - 1].first + frac * (pts[i].first - pts[i - 1].first);
606 return pts.back().first;
612 const std::string &what)
614 if (post.particles.empty() || post.weight_sum <= 0.0) {
615 throw CircuitException(
616 what +
": evidence is infeasible (no positive-weight draw among " +
617 std::to_string(post.attempted) +
618 " Monte Carlo samples); the observations may contradict the prior, "
619 "or raise provsql.rv_mc_samples");
621 const double ess = post.effectiveSampleSize();
622 const double nonzero =
static_cast<double>(post.particles.size());
626 "%s: posterior effective sample size low (%.1f of %u accepted); "
627 "likelihood weighting is degenerating -- raise provsql.rv_mc_samples, "
628 "or the model has many observations per latent (defer to SMC)",
629 what.c_str(), ess,
static_cast<unsigned>(post.particles.size()));
649try_truncated_closed_form(
const GenericCircuit &gc,
gate_t root,
650 gate_t event_root,
unsigned k,
bool central)
653 if (!m)
return std::nullopt;
654 const double lo = m->lo, hi = m->hi;
661 auto raw = [&](
unsigned q) -> std::optional<double> {
662 if (q == 0)
return 1.0;
663 return dist->truncatedRawMoment(lo, hi, q);
666 if (!central)
return raw(k);
674 DistributionSpec targetSpec;
675 DistributionSpec otherSpec;
683std::optional<RvVsRvCond>
684matchRvVsRvConditional(
const GenericCircuit &gc,
gate_t root,
gate_t event_root)
689 if (!specX)
return std::nullopt;
691 const auto &wires = gc.
getWires(event_root);
692 if (wires.size() != 2)
return std::nullopt;
700 if (wires[0] == root) { other = wires[1]; targetLeft =
true; }
701 else if (wires[1] == root) { other = wires[0]; targetLeft =
false; }
702 else return std::nullopt;
706 if (!specY)
return std::nullopt;
716 if (targetLeft) targetGreater = greaterOp;
717 else targetGreater = lessOp;
718 return RvVsRvCond{*specX, *specY, targetGreater};
729 bool targetGreater,
unsigned k)
736 if (!dX->integrationRange(lo, hi))
737 return std::numeric_limits<double>::quiet_NaN();
739 auto base = [&](
double x) {
740 const double fX = dX->pdf(x);
741 const double FY = dY->cdf(x);
742 if (std::isnan(fX) || std::isnan(FY))
743 return std::numeric_limits<double>::quiet_NaN();
744 const double w = targetGreater ? FY : (1.0 - FY);
748 if (std::isnan(den) || !(den > 1e-12))
749 return std::numeric_limits<double>::quiet_NaN();
752 return std::pow(x,
static_cast<double>(k)) * base(x);
755 return std::numeric_limits<double>::quiet_NaN();
763try_rvVsRv_conditional_moment(
const GenericCircuit &gc,
gate_t root,
764 gate_t event_root,
unsigned k,
bool central)
766 auto m = matchRvVsRvConditional(gc, root, event_root);
767 if (!m)
return std::nullopt;
769 auto raw = [&](
unsigned q) -> std::optional<double> {
770 if (q == 0)
return 1.0;
771 double r = rvVsRvConditionalMoment(m->targetSpec, m->otherSpec,
772 m->targetGreater, q);
773 if (std::isnan(r))
return std::nullopt;
777 if (!central)
return raw(k);
788 DistributionSpec other;
802 const std::vector<PivotFactor> &factors,
807 if (!dX->integrationRange(lo, hi))
808 return std::numeric_limits<double>::quiet_NaN();
809 for (
const auto &f : factors)
811 if (f.pivotGreater) lo = std::max(lo, f.konst);
812 else hi = std::min(hi, f.konst);
814 if (!(hi > lo))
return 0.0;
816 std::vector<std::unique_ptr<Distribution>> others;
817 std::vector<bool> greater;
818 for (
const auto &f : factors)
821 greater.push_back(f.pivotGreater);
825 const double fX = dX->pdf(x);
826 if (std::isnan(fX))
return std::numeric_limits<double>::quiet_NaN();
828 for (std::size_t j = 0; j < others.size(); ++j) {
829 const double FY = others[j]->cdf(x);
830 if (std::isnan(FY))
return std::numeric_limits<double>::quiet_NaN();
831 w *= greater[j] ? FY : (1.0 - FY);
833 return std::pow(x,
static_cast<double>(k)) * w;
839struct PivotConjunctionCond {
840 DistributionSpec targetSpec;
841 std::vector<PivotFactor> factors;
849std::optional<PivotConjunctionCond>
850matchPivotConjunctionConditional(
const GenericCircuit &gc,
gate_t root,
857 if (!specX)
return std::nullopt;
859 const auto &kids = gc.
getWires(event_root);
860 if (kids.size() < 2)
return std::nullopt;
861 std::vector<PivotFactor> factors;
862 std::set<gate_t> othersSeen;
866 if (w.size() != 2)
return std::nullopt;
871 gate_t other;
bool targetLeft;
872 if (w[0] == root) { other = w[1]; targetLeft =
true; }
873 else if (w[1] == root) { other = w[0]; targetLeft =
false; }
874 else return std::nullopt;
879 const bool pivotGreater = targetLeft ? greaterOp : lessOp;
886 if (!othersSeen.insert(other).second)
return std::nullopt;
888 if (!specY)
return std::nullopt;
889 f = {
false, *specY, 0.0, pivotGreater};
890 }
else return std::nullopt;
891 factors.push_back(std::move(f));
893 return PivotConjunctionCond{*specX, std::move(factors)};
900try_pivotConjunction_conditional_moment(
const GenericCircuit &gc,
gate_t root,
901 gate_t event_root,
unsigned k,
904 auto m = matchPivotConjunctionConditional(gc, root, event_root);
905 if (!m)
return std::nullopt;
907 const double den = pivotConjunctionIntegral(m->targetSpec, m->factors, 0);
908 if (std::isnan(den) || !(den > 1e-12))
910 auto raw = [&](
unsigned q) -> std::optional<double> {
911 if (q == 0)
return 1.0;
912 const double num = pivotConjunctionIntegral(m->targetSpec, m->factors, q);
913 if (std::isnan(num))
return std::nullopt;
917 if (!central)
return raw(k);
929std::unique_ptr<Distribution> transform_image(
const GenericCircuit &gc,
936 if (!transform)
return nullptr;
941 if (!spec)
return nullptr;
951std::unique_ptr<Distribution> product_image(
const GenericCircuit &gc,
955 if (wires.empty())
return nullptr;
956 double c_total = 1.0;
957 std::vector<std::unique_ptr<Distribution>> dists;
958 std::vector<const Distribution *> factors;
959 std::set<gate_t> seen;
964 catch (
const CircuitException &) {
return nullptr; }
967 if (t !=
gate_rv)
return nullptr;
968 if (!seen.insert(w).second)
return nullptr;
970 if (!spec)
return nullptr;
972 factors.push_back(dists.back().get());
974 if (factors.empty())
return nullptr;
977 std::unique_ptr<Distribution> combined;
978 if (factors.size() == 1)
979 combined = std::move(dists.front());
982 if (!combined)
return nullptr;
983 if (c_total != 1.0)
return combined->scale(c_total);
1003struct AffineArm {
double a, b; };
1005std::optional<AffineArm>
1006affineInPivot(
const GenericCircuit &gc,
gate_t arm,
gate_t pivot)
1012 return (arm == pivot) ? std::optional<AffineArm>(AffineArm{1.0, 0.0})
1017 auto isPivot = [&](
gate_t x) {
1020 auto constVal = [&](
gate_t x,
double &out) {
1027 return AffineArm{-1.0, 0.0};
1029 if (isPivot(w[0]) && constVal(w[1], c))
return AffineArm{1.0, c};
1030 if (isPivot(w[1]) && constVal(w[0], c))
return AffineArm{1.0, c};
1033 if (isPivot(w[0]) && constVal(w[1], c))
return AffineArm{1.0, -c};
1034 if (isPivot(w[1]) && constVal(w[0], c))
return AffineArm{-1.0, c};
1037 if (isPivot(w[0]) && constVal(w[1], c))
return AffineArm{c, 0.0};
1038 if (isPivot(w[1]) && constVal(w[0], c))
return AffineArm{c, 0.0};
1041 return std::nullopt;
1044std::optional<double>
1045singlePivotCaseRawMoment(
const GenericCircuit &gc,
gate_t g,
unsigned k)
1048 const auto &wires = gc.
getWires(g);
1049 if (wires.size() < 3 || wires.size() % 2 == 0)
return std::nullopt;
1050 const std::size_t m = wires.size() / 2;
1053 gate_t pivot{};
bool havePivot =
false;
1055 std::vector<Guard> guards;
1057 for (std::size_t i = 0; i < m; ++i) {
1058 gate_t gd = wires[2 * i];
1061 if (gw.size() != 2)
return std::nullopt;
1065 return std::nullopt;
1066 gate_t rvSide, constSide;
bool pivotLeft;
1068 rvSide = gw[0]; constSide = gw[1]; pivotLeft =
true;
1071 rvSide = gw[1]; constSide = gw[0]; pivotLeft =
false;
1072 }
else return std::nullopt;
1073 if (!havePivot) { pivot = rvSide; havePivot =
true; }
1074 else if (rvSide != pivot)
return std::nullopt;
1077 if (!havePivot)
return std::nullopt;
1080 if (!spec)
return std::nullopt;
1084 std::vector<AffineArm> arms;
1085 arms.reserve(m + 1);
1086 for (std::size_t i = 0; i < m; ++i) {
1087 auto af = affineInPivot(gc, wires[2 * i + 1], pivot);
1088 if (!af)
return std::nullopt;
1089 arms.push_back(*af);
1092 auto af = affineInPivot(gc, wires.back(), pivot);
1093 if (!af)
return std::nullopt;
1094 arms.push_back(*af);
1099 auto guardTrue = [](
const Guard &gd,
double x) ->
bool {
1100 const double lhs = gd.pivotLeft ? x : gd.c;
1101 const double rhs = gd.pivotLeft ? gd.c : x;
1107 default:
return false;
1110 auto pickArm = [&](
double x) ->
const AffineArm & {
1111 for (std::size_t i = 0; i < m; ++i)
1112 if (guardTrue(guards[i], x))
return arms[i];
1117 std::vector<double> cuts;
1118 for (
const auto &gd : guards) cuts.push_back(gd.c);
1119 std::sort(cuts.begin(), cuts.end());
1120 cuts.erase(std::unique(cuts.begin(), cuts.end()), cuts.end());
1122 const double NINF = -std::numeric_limits<double>::infinity();
1123 const double PINF = std::numeric_limits<double>::infinity();
1126 const std::size_t nseg = cuts.size() + 1;
1127 for (std::size_t s = 0; s < nseg; ++s) {
1128 const double lo = (s == 0) ? NINF : cuts[s - 1];
1129 const double hi = (s == cuts.size()) ? PINF : cuts[s];
1130 if (lo == hi)
continue;
1133 if (lo == NINF && hi == PINF) t = 0.0;
1134 else if (lo == NINF) t = hi - 1.0;
1135 else if (hi == PINF) t = lo + 1.0;
1136 else t = 0.5 * (lo + hi);
1137 const AffineArm &arm = pickArm(t);
1139 const double dF = dist->cdf(hi) - dist->cdf(lo);
1140 if (std::isnan(dF))
return std::nullopt;
1141 if (dF <= 0.0)
continue;
1144 for (
unsigned j = 0; j <= k; ++j) {
1145 const double aj = std::pow(arm.a,
static_cast<double>(j));
1146 if (aj == 0.0)
continue;
1147 const double coef =
binomial(k, j) * aj
1148 * std::pow(arm.b,
static_cast<double>(k - j));
1149 if (coef == 0.0)
continue;
1154 auto trm = dist->truncatedRawMoment(lo, hi, j);
1155 if (!trm)
return std::nullopt;
1158 total += coef * intj;
1173std::optional<double>
1174twoArmCaseRawMoment(
const GenericCircuit &gc,
gate_t g,
unsigned k)
1177 const auto &wires = gc.
getWires(g);
1178 if (wires.size() != 3)
return std::nullopt;
1181 const auto &gw = gc.
getWires(guard);
1182 if (gw.size() != 2)
return std::nullopt;
1186 return std::nullopt;
1187 gate_t a = gw[0], b = gw[1];
1189 return std::nullopt;
1192 if (!specA || !specB)
return std::nullopt;
1199 auto armContribution = [&](
gate_t armGate,
bool regionAgtB)
1200 -> std::optional<double> {
1201 for (
int which = 0; which < 2; ++which) {
1202 gate_t pv = (which == 0) ? a : b;
1203 auto af = affineInPivot(gc, armGate, pv);
1209 const bool pivotGreater = (which == 0) ? regionAgtB : !regionAgtB;
1210 const PivotFactor f{
false, otSpec, 0.0, pivotGreater};
1212 for (
unsigned j = 0; j <= k; ++j) {
1213 const double aj = std::pow(af->a,
static_cast<double>(j));
1214 if (aj == 0.0)
continue;
1215 const double coef =
binomial(k, j) * aj
1216 * std::pow(af->b,
static_cast<double>(k - j));
1217 if (coef == 0.0)
continue;
1218 const double I = pivotConjunctionIntegral(pvSpec, {f}, j);
1219 if (std::isnan(I))
return std::nullopt;
1224 return std::nullopt;
1227 auto c0 = armContribution(wires[1], guard_a_gt_b);
1228 if (!c0)
return std::nullopt;
1229 auto c1 = armContribution(wires[2], !guard_a_gt_b);
1230 if (!c1)
return std::nullopt;
1239evalGuardUnderOrder(
const GenericCircuit &gc,
gate_t guard,
1240 const std::unordered_map<gate_t, int> &rank)
1244 const auto &w = gc.
getWires(guard);
1245 if (w.size() != 2)
return std::nullopt;
1248 if (!ok)
return std::nullopt;
1249 auto ia = rank.find(w[0]), ib = rank.find(w[1]);
1250 if (ia == rank.end() || ib == rank.end())
return std::nullopt;
1251 const int ra = ia->second, rb = ib->second;
1260 return std::nullopt;
1266 auto v = evalGuardUnderOrder(gc, c, rank);
1267 if (!v)
return std::nullopt;
1268 acc = isAnd ? (acc && *v) : (acc || *v);
1272 return std::nullopt;
1285std::optional<double>
1286orderStatCaseRawMoment(
const GenericCircuit &gc,
gate_t g,
unsigned k)
1289 const auto &wires = gc.
getWires(g);
1290 if (wires.size() < 3 || wires.size() % 2 == 0)
return std::nullopt;
1291 const std::size_t m = wires.size() / 2;
1294 std::vector<gate_t> armRV(m + 1);
1295 std::vector<gate_t> uniq;
1296 std::unordered_map<gate_t, DistributionSpec> specOf;
1297 auto noteRV = [&](
gate_t v) ->
bool {
1299 if (specOf.find(v) == specOf.end()) {
1301 if (!sp)
return false;
1302 specOf.emplace(v, *sp);
1307 for (std::size_t i = 0; i < m; ++i) {
1308 armRV[i] = wires[2 * i + 1];
1309 if (!noteRV(armRV[i]))
return std::nullopt;
1311 armRV[m] = wires.back();
1312 if (!noteRV(armRV[m]))
return std::nullopt;
1314 const std::size_t n = uniq.size();
1315 if (n < 2 || n > 7)
return std::nullopt;
1319 std::vector<gate_t> guards(m);
1320 for (std::size_t i = 0; i < m; ++i) guards[i] = wires[2 * i];
1323 std::vector<std::size_t> perm(n);
1324 for (std::size_t i = 0; i < n; ++i) perm[i] = i;
1325 bool alwaysMax =
true, alwaysMin =
true;
1327 std::unordered_map<gate_t, int> rank;
1328 for (std::size_t i = 0; i < n; ++i) rank[uniq[perm[i]]] = static_cast<int>(i);
1330 gate_t maxRV = uniq[perm[n - 1]], minRV = uniq[perm[0]];
1332 gate_t selected = armRV[m];
1333 for (std::size_t i = 0; i < m; ++i) {
1334 auto gv = evalGuardUnderOrder(gc, guards[i], rank);
1335 if (!gv)
return std::nullopt;
1336 if (*gv) { selected = armRV[i];
break; }
1338 if (selected != maxRV) alwaysMax =
false;
1339 if (selected != minRV) alwaysMin =
false;
1340 if (!alwaysMax && !alwaysMin)
return std::nullopt;
1341 }
while (std::next_permutation(perm.begin(), perm.end()));
1343 const bool isMax = alwaysMax;
1347 for (std::size_t i = 0; i < n; ++i) {
1348 std::vector<PivotFactor> factors;
1349 factors.reserve(n - 1);
1350 for (std::size_t j = 0; j < n; ++j) {
1351 if (j == i)
continue;
1352 factors.push_back({
false, specOf.at(uniq[j]), 0.0, isMax});
1354 const double I = pivotConjunctionIntegral(specOf.at(uniq[i]), factors, k);
1355 if (std::isnan(I))
return std::nullopt;
1362std::optional<double>
1363caseAnalyticRawMoment(
const GenericCircuit &gc,
gate_t g,
unsigned k)
1365 if (
auto v = singlePivotCaseRawMoment(gc, g, k))
return v;
1366 if (
auto v = twoArmCaseRawMoment(gc, g, k))
return v;
1367 if (
auto v = orderStatCaseRawMoment(gc, g, k))
return v;
1368 return std::nullopt;
1371double rec_expectation(
const GenericCircuit &gc,
gate_t g, FootprintCache &fp)
1386 if (rvIsParametric(gc, g)) {
1388 if (tmpl && familyMeanIsAffine(*tmpl)) {
1391 return p.wire_slot < 0 ? p.literal
1392 : rec_expectation(gc, w[p.wire_slot], fp);
1395 ->factory(param_mean(tmpl->p1), param_mean(tmpl->p2))
1398 return mc_raw_moment(gc, g, 1,
"Expectation of a latent gate_rv");
1402 throw CircuitException(
1403 "Expectation: malformed gate_rv extra: " + gc.
getExtra(g));
1408 const auto &wires = gc.
getWires(g);
1412 for (
gate_t c : wires) s += rec_expectation(gc, c, fp);
1416 if (wires.size() != 2)
1417 throw CircuitException(
"gate_arith MINUS must be binary");
1418 return rec_expectation(gc, wires[0], fp)
1419 - rec_expectation(gc, wires[1], fp);
1422 if (wires.size() != 1)
1423 throw CircuitException(
"gate_arith NEG must be unary");
1424 return -rec_expectation(gc, wires[0], fp);
1427 if (pairwise_disjoint(fp, wires)) {
1429 for (
gate_t c : wires) p *= rec_expectation(gc, c, fp);
1432 return mc_raw_moment(gc, g, 1,
1433 "Expectation of gate_arith TIMES with shared random variables");
1436 if (wires.size() != 2)
1437 throw CircuitException(
"gate_arith DIV must be binary");
1440 return rec_expectation(gc, wires[0], fp) / divisor;
1442 return mc_raw_moment(gc, g, 1,
1443 "Expectation of gate_arith DIV with non-constant divisor");
1453 if (
auto v = iidOrderStatMean(gc, g, isMax, fp))
1455 if (
auto v = mixedOrderStatMean(gc, g, isMax, fp))
1457 return mc_raw_moment(gc, g, 1,
1458 "Expectation of gate_arith " + std::string(isMax ?
"MAX" :
"MIN"));
1468 if (
auto image = transform_image(gc, g, op))
1469 return image->mean();
1470 return mc_raw_moment(gc, g, 1,
1471 "Expectation of a gate_arith nonlinear transform");
1475 return mc_raw_moment(gc, g, 1,
1476 "Expectation of a gate_arith PERCENTILE");
1478 throw CircuitException(
1479 "Expectation: unknown gate_arith op tag: " +
1480 std::to_string(
static_cast<unsigned>(op)));
1483 const auto &wires = gc.
getWires(g);
1488 for (std::size_t i = 1; i < wires.size(); ++i) {
1498 if (wires.size() != 3)
1499 throw CircuitException(
1500 "Expectation: gate_mixture must have exactly three children");
1501 const double pi = mixturePi(gc, wires[0]);
1502 return pi * rec_expectation(gc, wires[1], fp)
1503 + (1.0 - pi) * rec_expectation(gc, wires[2], fp);
1506 if (
auto v = caseAnalyticRawMoment(gc, g, 1))
1508 return mc_raw_moment(gc, g, 1,
"Expectation of gate type gate_case");
1510 return mc_raw_moment(gc, g, 1,
1511 "Expectation of gate type " + std::string(
gate_type_name[type]));
1515double rec_variance(
const GenericCircuit &gc,
gate_t g, FootprintCache &fp)
1525 if (rvIsParametric(gc, g)) {
1526 const std::string what =
"Variance of a latent gate_rv";
1527 const double mu = mc_raw_moment(gc, g, 1, what);
1528 return mc_central_moment(gc, g, 2, mu, what);
1532 throw CircuitException(
1533 "Variance: malformed gate_rv extra: " + gc.
getExtra(g));
1538 const auto &wires = gc.
getWires(g);
1539 auto mc_var = [&](
const std::string &what) {
1540 const double mu = mc_raw_moment(gc, g, 1, what);
1541 return mc_central_moment(gc, g, 2, mu, what);
1545 if (pairwise_disjoint(fp, wires)) {
1547 for (
gate_t c : wires) s += rec_variance(gc, c, fp);
1551 "Variance of gate_arith PLUS with shared random variables");
1554 if (wires.size() != 2)
1555 throw CircuitException(
"gate_arith MINUS must be binary");
1556 if (pairwise_disjoint(fp, wires)) {
1557 return rec_variance(gc, wires[0], fp)
1558 + rec_variance(gc, wires[1], fp);
1561 "Variance of gate_arith MINUS with shared random variables");
1564 if (wires.size() != 1)
1565 throw CircuitException(
"gate_arith NEG must be unary");
1566 return rec_variance(gc, wires[0], fp);
1569 if (pairwise_disjoint(fp, wires)) {
1572 double prod_e2 = 1.0;
1573 double prod_e1 = 1.0;
1575 const double mu_c = rec_expectation(gc, c, fp);
1576 const double v_c = rec_variance(gc, c, fp);
1577 prod_e2 *= (v_c + mu_c * mu_c);
1580 return prod_e2 - prod_e1 * prod_e1;
1583 "Variance of gate_arith TIMES with shared random variables");
1586 if (wires.size() != 2)
1587 throw CircuitException(
"gate_arith DIV must be binary");
1590 return rec_variance(gc, wires[0], fp) / (divisor * divisor);
1593 "Variance of gate_arith DIV with non-constant divisor");
1599 "Variance of gate_arith " +
1604 if (
auto image = transform_image(gc, g, op))
1605 return image->variance();
1606 return mc_var(
"Variance of a gate_arith nonlinear transform");
1608 return mc_var(
"Variance of a gate_arith PERCENTILE");
1610 throw CircuitException(
1611 "Variance: unknown gate_arith op tag: " +
1612 std::to_string(
static_cast<unsigned>(op)));
1615 const auto &wires = gc.
getWires(g);
1618 double e1 = 0.0, e2 = 0.0;
1619 for (std::size_t i = 1; i < wires.size(); ++i) {
1620 const double p = gc.
getProb(wires[i]);
1625 return e2 - e1 * e1;
1629 if (wires.size() != 3)
1630 throw CircuitException(
1631 "Variance: gate_mixture must have exactly three children");
1632 const double pi = mixturePi(gc, wires[0]);
1633 const double ex = rec_expectation(gc, wires[1], fp);
1634 const double ey = rec_expectation(gc, wires[2], fp);
1635 const double vx = rec_variance(gc, wires[1], fp);
1636 const double vy = rec_variance(gc, wires[2], fp);
1637 const double em = pi * ex + (1.0 - pi) * ey;
1638 return pi * (vx + ex * ex)
1639 + (1.0 - pi) * (vy + ey * ey)
1643 if (
auto m2 = caseAnalyticRawMoment(gc, g, 2))
1644 if (
auto m1 = caseAnalyticRawMoment(gc, g, 1))
1645 return *m2 - (*m1) * (*m1);
1646 const std::string what =
"Variance of gate type gate_case";
1647 const double mu = mc_raw_moment(gc, g, 1, what);
1648 return mc_central_moment(gc, g, 2, mu, what);
1651 const std::string what =
1653 const double mu = mc_raw_moment(gc, g, 1, what);
1654 return mc_central_moment(gc, g, 2, mu, what);
1659double rec_raw_moment(
const GenericCircuit &gc,
gate_t g,
unsigned k,
1662 if (k == 0)
return 1.0;
1663 if (k == 1)
return rec_expectation(gc, g, fp);
1669 static_cast<double>(k));
1672 if (rvIsParametric(gc, g))
1673 return mc_raw_moment(gc, g, k,
"Raw moment of a latent gate_rv");
1676 throw CircuitException(
1677 "Moment: malformed gate_rv extra: " + gc.
getExtra(g));
1682 const auto &wires = gc.
getWires(g);
1685 if (wires.size() != 1)
1686 throw CircuitException(
"gate_arith NEG must be unary");
1687 const double v = rec_raw_moment(gc, wires[0], k, fp);
1688 return ((k % 2 == 0) ? 1.0 : -1.0) * v;
1691 if (pairwise_disjoint(fp, wires)) {
1695 std::vector<double> m_acc(k + 1, 0.0);
1696 for (
unsigned i = 0; i <= k; ++i)
1697 m_acc[i] = rec_raw_moment(gc, wires[0], i, fp);
1698 for (
size_t w = 1; w < wires.size(); ++w) {
1699 std::vector<double> next(k + 1, 0.0);
1700 std::vector<double> moments_y(k + 1, 0.0);
1701 for (
unsigned i = 0; i <= k; ++i)
1702 moments_y[i] = rec_raw_moment(gc, wires[w], i, fp);
1703 for (
unsigned kp = 0; kp <= k; ++kp) {
1705 for (
unsigned i = 0; i <= kp; ++i) {
1706 total +=
binomial(kp, i) * m_acc[i] * moments_y[kp - i];
1710 m_acc = std::move(next);
1714 return mc_raw_moment(gc, g, k,
1715 "Raw moment of gate_arith PLUS with shared random variables");
1718 if (wires.size() != 2)
1719 throw CircuitException(
"gate_arith MINUS must be binary");
1720 if (pairwise_disjoint(fp, wires)) {
1722 for (
unsigned i = 0; i <= k; ++i) {
1723 const double sign = ((k - i) % 2 == 0) ? 1.0 : -1.0;
1725 * rec_raw_moment(gc, wires[0], i, fp)
1727 * rec_raw_moment(gc, wires[1], k - i, fp);
1731 return mc_raw_moment(gc, g, k,
1732 "Raw moment of gate_arith MINUS with shared random variables");
1735 if (pairwise_disjoint(fp, wires)) {
1738 for (
gate_t c : wires) p *= rec_raw_moment(gc, c, k, fp);
1741 return mc_raw_moment(gc, g, k,
1742 "Raw moment of gate_arith TIMES with shared random variables");
1745 if (wires.size() != 2)
1746 throw CircuitException(
"gate_arith DIV must be binary");
1749 return rec_raw_moment(gc, wires[0], k, fp)
1750 / std::pow(divisor,
static_cast<double>(k));
1752 return mc_raw_moment(gc, g, k,
1753 "Raw moment of gate_arith DIV with non-constant divisor");
1758 return mc_raw_moment(gc, g, k,
1759 "Raw moment of gate_arith " +
1764 if (
auto image = transform_image(gc, g, op))
1765 return image->rawMoment(k);
1766 return mc_raw_moment(gc, g, k,
1767 "Raw moment of a gate_arith nonlinear transform");
1769 return mc_raw_moment(gc, g, k,
1770 "Raw moment of a gate_arith PERCENTILE");
1772 throw CircuitException(
1773 "Moment: unknown gate_arith op tag: " +
1774 std::to_string(
static_cast<unsigned>(op)));
1777 const auto &wires = gc.
getWires(g);
1781 for (std::size_t i = 1; i < wires.size(); ++i) {
1784 * std::pow(v,
static_cast<double>(k));
1789 if (wires.size() != 3)
1790 throw CircuitException(
1791 "Moment: gate_mixture must have exactly three children");
1792 const double pi = mixturePi(gc, wires[0]);
1793 return pi * rec_raw_moment(gc, wires[1], k, fp)
1794 + (1.0 - pi) * rec_raw_moment(gc, wires[2], k, fp);
1797 if (
auto v = caseAnalyticRawMoment(gc, g, k))
1799 return mc_raw_moment(gc, g, k,
"Raw moment of gate type gate_case");
1801 return mc_raw_moment(gc, g, k,
1814[[noreturn]]
void raise_infeasible_event(
const GenericCircuit &gc,
gate_t root)
1816 (void)gc; (void)root;
1817 throw CircuitException(
1818 "conditioning event is infeasible (empty intersection with the "
1819 "random variable's support)");
1822double conditional_raw_moment(
const GenericCircuit &gc,
gate_t root,
1823 unsigned k,
gate_t event_root)
1825 if (k == 0)
return 1.0;
1843 const std::string what =
"Posterior raw moment";
1845 gc, root, event_root, mc_samples_or_throw(what));
1846 checkPosteriorOrThrow(post, what);
1847 return weightedRawMoment(post, k);
1849 if (
auto cf = try_truncated_closed_form(gc, root, event_root, k,
false))
1851 if (
auto cf = try_rvVsRv_conditional_moment(gc, root, event_root, k,
false))
1853 if (
auto cf = try_pivotConjunction_conditional_moment(gc, root, event_root,
1857 raise_infeasible_event(gc, root);
1858 return mc_conditional_raw_moment(
1859 gc, root, k, event_root,
1860 "Conditional raw moment of gate type " +
1864double conditional_central_moment(
const GenericCircuit &gc,
gate_t root,
1865 unsigned k,
gate_t event_root)
1867 if (k == 0)
return 1.0;
1868 if (k == 1)
return 0.0;
1874 if (m1 && m2)
return *m2 - (*m1) * (*m1);
1881 if (k == 2)
return dist->variance();
1882 const double mu = dist->mean();
1884 for (
unsigned i = 0; i <= k; ++i) {
1885 const double mu_pow = std::pow(-mu,
static_cast<double>(k - i));
1886 total +=
binomial(k, i) * mu_pow * dist->rawMoment(i);
1893 const std::string what =
"Posterior central moment";
1895 gc, root, event_root, mc_samples_or_throw(what));
1896 checkPosteriorOrThrow(post, what);
1897 const double mu = weightedRawMoment(post, 1);
1898 return weightedCentralMoment(post, k, mu);
1900 if (
auto cf = try_truncated_closed_form(gc, root, event_root, k,
true))
1902 if (
auto cf = try_rvVsRv_conditional_moment(gc, root, event_root, k,
true))
1904 if (
auto cf = try_pivotConjunction_conditional_moment(gc, root, event_root,
1908 raise_infeasible_event(gc, root);
1910 const double mu = conditional_raw_moment(gc, root, 1, event_root);
1911 return mc_conditional_central_moment(
1912 gc, root, k, mu, event_root,
1913 "Conditional central moment of gate type " +
1920 std::optional<gate_t> event_root)
1922 if (event_root.has_value())
1923 return conditional_raw_moment(gc, root, 1, *event_root);
1924 FootprintCache fp(gc);
1925 return rec_expectation(gc, root, fp);
1929 std::optional<gate_t> event_root)
1931 if (event_root.has_value())
1932 return conditional_raw_moment(gc, root, k, *event_root);
1933 FootprintCache fp(gc);
1934 return rec_raw_moment(gc, root, k, fp);
1938 std::optional<gate_t> event_root)
1940 if (event_root.has_value())
1941 return conditional_central_moment(gc, root, k, *event_root);
1942 if (k == 0)
return 1.0;
1943 if (k == 1)
return 0.0;
1944 FootprintCache fp(gc);
1945 if (k == 2)
return rec_variance(gc, root, fp);
1947 const double mu = rec_expectation(gc, root, fp);
1949 for (
unsigned i = 0; i <= k; ++i) {
1950 const double mu_pow = std::pow(-mu,
static_cast<double>(k - i));
1951 total +=
binomial(k, i) * mu_pow * rec_raw_moment(gc, root, i, fp);
1965double empirical_quantile(std::vector<double> xs,
double p)
1967 xs.erase(std::remove_if(xs.begin(), xs.end(),
1968 [](
double x) { return std::isnan(x); }),
1970 if (xs.empty())
return std::numeric_limits<double>::quiet_NaN();
1971 std::sort(xs.begin(), xs.end());
1972 if (p <= 0.0)
return xs.front();
1973 if (p >= 1.0)
return xs.back();
1974 const double h = p *
static_cast<double>(xs.size() - 1);
1975 const std::size_t i =
static_cast<std::size_t
>(h);
1976 if (i + 1 >= xs.size())
return xs.back();
1977 const double frac = h -
static_cast<double>(i);
1978 return xs[i] + frac * (xs[i + 1] - xs[i]);
1984std::optional<double> categorical_quantile(
const GenericCircuit &gc,
1987 const auto &wires = gc.
getWires(mix);
1988 std::vector<std::pair<double, double>> outcomes;
1989 outcomes.reserve(wires.size());
1990 for (std::size_t i = 1; i < wires.size(); ++i) {
1993 catch (
const CircuitException &) {
return std::nullopt; }
1994 outcomes.emplace_back(v, gc.
getProb(wires[i]));
1996 if (outcomes.empty())
return std::nullopt;
1997 std::sort(outcomes.begin(), outcomes.end());
1999 for (
const auto &vp : outcomes) {
2001 if (cum >= p && cum > 0.0)
return vp.first;
2003 return outcomes.back().first;
2012std::optional<double> analytic_dist_quantile(
const Distribution &dist,
2013 double p,
double lo,
double hi);
2016 double p,
double lo,
double hi)
2021std::optional<double> analytic_dist_quantile(
const Distribution &dist,
2022 double p,
double lo,
double hi)
2024 if (p <= 0.0 || p >= 1.0) {
2026 const auto sup = dist.support();
2027 return (p <= 0.0) ? std::max(sup.lo, lo) : std::min(sup.hi, hi);
2030 if (std::isfinite(lo) || std::isfinite(hi)) {
2031 const double f_lo = std::isfinite(lo) ? dist.cdf(lo) : 0.0;
2032 const double f_hi = std::isfinite(hi) ? dist.cdf(hi) : 1.0;
2033 if (std::isnan(f_lo) || std::isnan(f_hi))
return std::nullopt;
2034 const double mass = f_hi - f_lo;
2035 if (mass < 1e-12)
return std::nullopt;
2036 u = f_lo + p * mass;
2038 double q = std::numeric_limits<double>::quiet_NaN();
2039 if (
auto cf = dist.quantile(u)) q = *cf;
2041 if (std::isnan(q))
return std::nullopt;
2051 std::optional<gate_t> event_root)
2053 const double inf = std::numeric_limits<double>::infinity();
2055 if (event_root.has_value()) {
2059 if (
auto q = analytic_rv_quantile(*post, p, -inf, inf))
2063 const std::string what =
"Posterior quantile";
2065 gc, root, *event_root, mc_samples_or_throw(what));
2066 checkPosteriorOrThrow(post, what);
2067 return weightedQuantile(std::move(post), p);
2071 if (
auto q = analytic_rv_quantile(m->spec, p, m->lo, m->hi))
2075 raise_infeasible_event(gc, root);
2077 gc, root, *event_root,
2078 mc_samples_or_throw(
"Conditional quantile"));
2079 check_acceptance_or_throw(cs,
"Conditional quantile");
2080 return empirical_quantile(std::move(cs.accepted), p);
2090 if (
auto q = analytic_rv_quantile(*spec, p, -inf, inf))
2093 if (
auto q = categorical_quantile(gc, root, p))
2101 std::unique_ptr<Distribution> image =
2103 : transform_image(gc, root, op);
2105 if (
auto q = analytic_dist_quantile(*image, p, -inf, inf))
2112 return empirical_quantile(
2135 std::optional<gate_t> &event_opt)
2137 std::vector<gate_t> evidences;
2150 evidences.push_back(w[1]);
2156 std::set<gate_t> seen;
2157 std::vector<gate_t> stack{root};
2158 while (!stack.empty()) {
2161 if (!seen.insert(g).second)
continue;
2167 evidences.push_back(w[1]);
2169 stack.push_back(target);
2175 if (evidences.empty())
2177 if (event_opt.has_value())
2178 evidences.push_back(*event_opt);
2180 if (evidences.size() == 1)
2181 cond = evidences[0];
2185 for (
gate_t e : evidences)
2232 const int32 k_signed = PG_GETARG_INT32(1);
2235 provsql_error(
"agg_avg_moment_exact: k must be non-negative (got %d)",
2242 gc, root,
static_cast<unsigned>(k_signed), ok);
2245 return Float8GetDatum(r);
2246 }
catch (
const std::exception &e) {
2258 const int32 k_signed = PG_GETARG_INT32(1);
2259 const bool central = PG_GETARG_BOOL(2);
2263 provsql_error(
"rv_moment: k must be non-negative (got %d)", k_signed);
2264 const unsigned k =
static_cast<unsigned>(k_signed);
2266 gate_t root_gate, event_gate;
2270 std::optional<gate_t> event_opt;
2272 event_opt = event_gate;
2290 return Float8GetDatum(result);
2291 }
catch (
const std::exception &e) {
2314 const double p = PG_GETARG_FLOAT8(1);
2317 if (std::isnan(p) || p < 0.0 || p > 1.0)
2318 provsql_error(
"rv_quantile: p must be in [0, 1] (got %g)", p);
2320 gate_t root_gate, event_gate;
2324 std::optional<gate_t> event_opt;
2326 event_opt = event_gate;
2330 return Float8GetDatum(
2332 }
catch (
const std::exception &e) {
2359 return Float8GetDatum(std::exp(*le));
2362 "rv_evidence: provsql.rv_mc_samples is 0 (the marginal likelihood is "
2363 "estimated by Monte Carlo); set it to a positive sample budget");
2366 return Float8GetDatum(e);
2367 }
catch (
const std::exception &ex) {
Exact closed-form HAVING COUNT(*) op C probability over safe-join lineage – the recursive marginal-ve...
ComparisonOperator cmpOpFromOid(Oid op_oid, bool &ok)
Map a PostgreSQL comparison-operator OID to a ComparisonOperator.
Typed aggregation value, operator, and aggregator abstractions.
ComparisonOperator
SQL comparison operators used in gate_cmp circuit gates.
@ LE
Less than or equal (<=).
@ GE
Greater than or equal (>=).
Closed-form CDF resolution for trivial gate_cmp shapes.
Boolean-expression (lineage formula) semiring.
Boolean provenance circuit with support for knowledge compilation.
GenericCircuit getJointCircuit(const std::vector< pg_uuid_t > &tokens, std::vector< gate_t > &gates)
Multi-root variant of getJointCircuit.
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.
Rao-Blackwellised (collapsed) evaluation of a correlated COUNT / SUM and of a latent conditioned on s...
The single comparator-resolution pipeline and the single Boolean-subcircuit probability entry point,...
Exact conjugate-prior posteriors for observe-evidence circuits.
Per-family polymorphic view over a continuous gate_rv distribution (§F.1 class hierarchy).
Datum rv_quantile(PG_FUNCTION_ARGS)
SQL: rv_quantile(token uuid, p float8, prov uuid DEFAULT gate_one()) -> float8...
Datum rv_moment(PG_FUNCTION_ARGS)
Datum agg_avg_moment_exact(PG_FUNCTION_ARGS)
SQL: rv_moment(token uuid, k integer, central boolean, prov uuid DEFAULT gate_on...
Datum rv_evidence(PG_FUNCTION_ARGS)
SQL: rv_evidence(evidence uuid) -> float8.
Analytical expectation / variance / moment evaluator over RV circuits.
Monte Carlo sampling over a GenericCircuit, RV-aware.
Shared 1-D quadrature core for the pivot-conjunction and order-statistic closed forms.
Catalog of probability-evaluation methods (Strategy + registry).
Continuous random-variable helpers (distribution parsing, moments).
Support-based bound check for continuous-RV comparators.
Exception type thrown by circuit operations on invalid input.
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.
void addWire(gate_t f, gate_t t)
Add a directed wire from gate f (parent) to gate t (child).
uuid getUUID(gate_t g) const
Return the UUID string associated with 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.
bool isCategoricalMixture(gate_t g) const
Test whether g is a categorical-form gate_mixture (the explicit provsql.categorical output).
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.
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 liftConditionedToTarget(gate_t g, gate_t target)
Replace a gate_conditioned g by a transparent passthrough to its target child (a single-child gate_ar...
void setExtra(gate_t g, const std::string &ex)
Attach a string extra to gate g.
void setProb(gate_t g, double p)
Set the probability for gate g.
Abstract per-family continuous distribution.
double compute_raw_moment(const GenericCircuit &gc, gate_t root, unsigned k, std::optional< gate_t > event_root)
Compute the raw moment (or if event_root is set) for k >= 0.
std::optional< double > conjugateLogEvidence(const GenericCircuit &gc, gate_t evidence)
The exact log marginal likelihood of a conjugate-shaped evidence circuit; std::nullopt on any shape ...
double compute_quantile(const GenericCircuit &gc, gate_t root, double p, std::optional< gate_t > event_root)
Compute the p-quantile of the scalar rooted at root (of the truncated distribution if event_root is ...
double aggAvgRawMomentExact(GenericCircuit &gc, gate_t g, unsigned k, bool &ok)
Exact k-th raw moment of AVG = SUM/COUNT over independent rows, conditional on COUNT >= 1.
gate_t lift_conditioning(GenericCircuit &gc, gate_t root, std::optional< gate_t > &event_opt)
Lift conditioning out of a scalar arithmetic expression.
double analytical_variance(const DistributionSpec &d)
Closed-form variance Var(X) for a basic distribution.
double importanceEvidence(const GenericCircuit &gc, gate_t evidence, unsigned samples)
Marginal likelihood P(data) of evidence: the mean raw importance weight over samples prior draws.
double booleanSubcircuitProbability(GenericCircuit &gc, gate_t root, const std::string &method, const std::string &args, bool inv_free_cert, const Tolerance &tol, bool mc_fallback, std::string *actual_method_out)
Probability of the Boolean function rooted at root in gc – THE single entry point over the method por...
std::unique_ptr< Distribution > closeTransform(const char *transform, const Distribution &x)
The image distribution of transform applied to x, when a registered rule covers x's family; nullptr o...
double parseDoubleStrict(const std::string &s)
Strictly parse s as a double.
std::optional< double > centralFromRaw(unsigned k, Raw &&raw)
Central moment of order k from a raw-moment closure: .
bool eventIsProvablyInfeasible(const GenericCircuit &gc, gate_t root, std::optional< gate_t > event_root)
True iff the conditioning event is provably infeasible for a bare gate_rv root.
std::unique_ptr< Distribution > makeDistribution(const DistributionSpec &spec)
Construct the per-family Distribution for a parsed spec.
double compute_central_moment(const GenericCircuit &gc, gate_t root, unsigned k, std::optional< gate_t > event_root)
Compute the central moment (or if event_root is set).
double simpsonIntegrate(double lo, double hi, int N, F &&f)
Composite-Simpson with N panels.
ConditionalScalarSamples monteCarloConditionalScalarSamples(const GenericCircuit &gc, gate_t root, gate_t event_root, unsigned samples)
Rejection-sample root conditioned on event_root.
double binomial(unsigned n, unsigned k)
Binomial coefficient as a double (exact for the small orders the moment expansions use).
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::unique_ptr< Distribution > closeProductFactors(const std::vector< const Distribution * > &factors)
Fold a product of independent factors into a single distribution when a registered closure covers eve...
double evaluateBooleanProbability(const GenericCircuit &gc, gate_t boolRoot)
Probability that the Boolean subcircuit rooted at boolRoot evaluates to true under the tuple-independ...
std::optional< DistributionSpec > conjugatePosterior(const GenericCircuit &gc, gate_t target, gate_t evidence)
The exact posterior of target given evidence, as a resolved distribution spec, when the circuit match...
std::vector< double > monteCarloScalarSamples(const GenericCircuit &gc, gate_t root, unsigned samples)
Sample a scalar sub-circuit samples times and return the draws.
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...
double numericQuantile(const Distribution &d, double p)
Numeric inverse CDF: monotone bisection of cdf() over the family's integration window.
double analytical_mean(const DistributionSpec &d)
Closed-form expectation E[X] for a basic distribution.
constexpr int kSimpsonPanels
Panel count shared by every composite-Simpson quadrature over a distribution's integration range: exa...
WeightedPosterior importanceSampleConditional(const GenericCircuit &gc, gate_t root, gate_t evidence, unsigned samples)
Self-normalised importance sampling of root given evidence.
std::optional< TruncatedSingleRv > matchTruncatedSingleRv(const GenericCircuit &gc, gate_t root, std::optional< gate_t > event_root)
Detect a closed-form, optionally-truncated single-RV shape.
double compute_expectation(const GenericCircuit &gc, gate_t root, std::optional< gate_t > event_root)
Compute (or if event_root is set) over the scalar sub-circuit rooted at root.
double analytical_raw_moment(const DistributionSpec &d, unsigned k)
Closed-form raw moment for a basic distribution.
bool circuitHasObserve(const GenericCircuit &gc, gate_t root)
Whether the circuit reachable from root contains a gate_observe – the signal that a conditioning even...
void resolveComparators(GenericCircuit &gc, gate_t root, bool simplify, bool decompose)
Run the comparator-resolution pipeline on gc, rewriting every gate_cmp (RV comparison,...
int provsql_verbose
Verbosity level; controlled by the provsql.verbose_level GUC.
double provsql_ess_warn_fraction
Effective-sample-size warning threshold for likelihood weighting: warn when the posterior ESS falls b...
int provsql_rv_mc_samples
Default sample count for analytical-evaluator MC fallbacks; 0 disables fallback (callers raise instea...
Uniform error-reporting macros for ProvSQL.
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
#define provsql_warning(fmt,...)
Emit a ProvSQL warning message (execution continues).
#define provsql_notice(fmt,...)
Emit a ProvSQL informational notice (execution continues).
const char * gate_type_name[]
Names of gate types.
Core types, constants, and utilities shared across ProvSQL.
provsql_arith_op
Arithmetic operator tags used by gate_arith.
@ PROVSQL_ARITH_PERCENTILE
continuous percentile (order-statistic aggregate): wires are interleaved [ind_1, x_1,...
@ PROVSQL_ARITH_DIV
binary, child0 / child1
@ PROVSQL_ARITH_LN
unary, natural logarithm of child0 (a negative draw raises at evaluation)
@ PROVSQL_ARITH_PLUS
n-ary, sum of children
@ PROVSQL_ARITH_POW
binary, child0 ^ child1 (real branch only: a negative base drawn with a non-integer exponent raises a...
@ PROVSQL_ARITH_NEG
unary, -child0
@ PROVSQL_ARITH_MINUS
binary, child0 - child1
@ PROVSQL_ARITH_EXP
unary, e^child0
@ PROVSQL_ARITH_TIMES
n-ary, product of children
@ PROVSQL_ARITH_MIN
n-ary, min of children (order statistic; least / min aggregate)
@ PROVSQL_ARITH_MAX
n-ary, max of children (order statistic; greatest / max aggregate)
@ gate_observe
Latent-variable observation (likelihood-weighting evidence): one wire → an observed bare gate_rv leaf...
@ gate_rv
Continuous random-variable leaf (extra encodes distribution).
@ gate_case
N-ary guarded selection over scalar (RV) children: wires are [guard_1, value_1, .....
@ gate_annotation
Transparent single-child wrapper carrying a query-level annotation in extra (inversion-free certifica...
@ gate_mobius
Signed Möbius combination: a MEASURE-only gate carrying one integer coefficient per child (in extra,...
@ gate_conditioned
Conditioning marker with two children [target, evidence]: measure-only, probability_evaluate returns ...
@ gate_mixture
Probabilistic mixture: three wires [p_token (gate_input Bernoulli), x_token, y_token]; samples x when...
@ gate_arith
n-ary arithmetic gate over scalar-valued children (info1 holds operator tag)
@ gate_assumed
Structural marker over a single child whose sub-circuit was computed under a Boolean-provenance assum...
string uuid2string(pg_uuid_t uuid)
Format a pg_uuid_t as a std::string.
C++ utility functions for UUID manipulation.
Outcome of a conditional Monte Carlo sampling pass.
One parameter slot of a gate_rv, either a literal or a wire.
Parsed distribution spec (family + up to two parameters).
A gate_rv distribution spec that may carry wired (token) parameters – the parse-time counterpart of D...
Outcome of a likelihood-weighting (importance-sampling) pass.