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);
418constexpr unsigned kEnumerateMaxInputs = 20;
429std::optional<double> enumerated_moment(
const GenericCircuit &gc,
gate_t g,
430 unsigned k,
double mu,
431 std::optional<gate_t> event,
432 const std::string &what)
435 if (!worlds)
return std::nullopt;
436 if (event && worlds->empty())
437 throw CircuitException(what +
": conditioning event is infeasible");
438 double total = 0.0, mass = 0.0;
439 for (
const auto &w : *worlds) {
440 if (std::isnan(w.second))
continue;
441 total += w.first * std::pow(w.second - mu,
static_cast<double>(k));
444 if (!(mass > 0.0))
return std::numeric_limits<double>::quiet_NaN();
448double mc_raw_moment(
const GenericCircuit &gc,
gate_t g,
unsigned k,
449 const std::string &what)
451 if (
auto m = enumerated_moment(gc, g, k, 0.0, std::nullopt, what))
454 if (samples.empty())
return 0.0;
460 std::size_t finite_count = 0;
461 for (
double x : samples) {
462 if (std::isnan(x))
continue;
463 total += std::pow(x,
static_cast<double>(k));
466 if (finite_count == 0)
return std::numeric_limits<double>::quiet_NaN();
467 return total /
static_cast<double>(finite_count);
470double mc_central_moment(
const GenericCircuit &gc,
gate_t g,
unsigned k,
471 double mu,
const std::string &what)
473 if (
auto m = enumerated_moment(gc, g, k, mu, std::nullopt, what))
476 if (samples.empty())
return 0.0;
478 std::size_t finite_count = 0;
479 for (
double x : samples) {
480 if (std::isnan(x))
continue;
481 const double d = x - mu;
482 total += std::pow(d,
static_cast<double>(k));
485 if (finite_count == 0)
return std::numeric_limits<double>::quiet_NaN();
486 return total /
static_cast<double>(finite_count);
493unsigned min_accepted_floor(
unsigned attempted)
495 unsigned floor = attempted / 1000;
496 return floor < 5 ? 5 : floor;
500 const std::string &what)
502 if (cs.accepted.empty()) {
508 throw CircuitException(
509 what +
": conditioning event is infeasible (0 of " +
510 std::to_string(cs.attempted) +
511 " Monte Carlo samples satisfied it)");
513 const unsigned floor = min_accepted_floor(cs.attempted);
514 if (cs.accepted.size() < floor) {
515 throw CircuitException(
516 what +
": conditional MC accepted only " +
517 std::to_string(cs.accepted.size()) +
" out of " +
518 std::to_string(cs.attempted) +
519 " samples (need >= " + std::to_string(floor) +
520 "); raise provsql.rv_mc_samples or tighten the event.");
524double mc_conditional_raw_moment(
const GenericCircuit &gc,
gate_t g,
525 unsigned k,
gate_t event_root,
526 const std::string &what)
528 if (
auto m = enumerated_moment(gc, g, k, 0.0, event_root, what))
531 gc, g, event_root, mc_samples_or_throw(what));
532 check_acceptance_or_throw(cs, what);
537 std::size_t finite_count = 0;
538 for (
double x : cs.accepted) {
539 if (std::isnan(x))
continue;
540 total += std::pow(x,
static_cast<double>(k));
543 if (finite_count == 0)
return std::numeric_limits<double>::quiet_NaN();
544 return total /
static_cast<double>(finite_count);
547double mc_conditional_central_moment(
const GenericCircuit &gc,
gate_t g,
548 unsigned k,
double mu,
550 const std::string &what)
552 if (
auto m = enumerated_moment(gc, g, k, mu, event_root, what))
555 gc, g, event_root, mc_samples_or_throw(what));
556 check_acceptance_or_throw(cs, what);
558 std::size_t finite_count = 0;
559 for (
double x : cs.accepted) {
560 if (std::isnan(x))
continue;
561 const double d = x - mu;
562 total += std::pow(d,
static_cast<double>(k));
565 if (finite_count == 0)
return std::numeric_limits<double>::quiet_NaN();
566 return total /
static_cast<double>(finite_count);
569double rec_expectation(
const GenericCircuit &gc,
gate_t g, FootprintCache &fp);
570double rec_variance(
const GenericCircuit &gc,
gate_t g, FootprintCache &fp);
571double rec_raw_moment(
const GenericCircuit &gc,
gate_t g,
unsigned k,
590 double sw = 0.0, swx = 0.0;
591 for (
const auto &[x, w] : post.particles) {
592 if (std::isnan(x))
continue;
594 swx += w * std::pow(x,
static_cast<double>(k));
596 if (sw <= 0.0)
return std::numeric_limits<double>::quiet_NaN();
604 double sw = 0.0, swd = 0.0;
605 for (
const auto &[x, w] : post.particles) {
606 if (std::isnan(x))
continue;
608 swd += w * std::pow(x - mu,
static_cast<double>(k));
610 if (sw <= 0.0)
return std::numeric_limits<double>::quiet_NaN();
619 auto &pts = post.particles;
620 pts.erase(std::remove_if(pts.begin(), pts.end(),
621 [](
const std::pair<double, double> &pw) {
622 return std::isnan(pw.first);
625 if (pts.empty())
return std::numeric_limits<double>::quiet_NaN();
626 std::sort(pts.begin(), pts.end(),
627 [](
const auto &a,
const auto &b) { return a.first < b.first; });
629 for (
const auto &pw : pts) total += pw.second;
630 if (!(total > 0.0))
return pts.front().first;
631 const double target = p * total;
633 for (std::size_t i = 0; i < pts.size(); ++i) {
634 const double prev = cum;
635 cum += pts[i].second;
637 if (i == 0)
return pts[0].first;
640 const double frac = (pts[i].second > 0.0)
641 ? (target - prev) / pts[i].second
643 return pts[i - 1].first + frac * (pts[i].first - pts[i - 1].first);
646 return pts.back().first;
652 const std::string &what)
654 if (post.particles.empty() || post.weight_sum <= 0.0) {
655 throw CircuitException(
656 what +
": evidence is infeasible (no positive-weight draw among " +
657 std::to_string(post.attempted) +
658 " Monte Carlo samples); the observations may contradict the prior, "
659 "or raise provsql.rv_mc_samples");
661 const double ess = post.effectiveSampleSize();
662 const double nonzero =
static_cast<double>(post.particles.size());
666 "%s: posterior effective sample size low (%.1f of %u accepted); "
667 "likelihood weighting is degenerating -- raise provsql.rv_mc_samples, "
668 "or the model has many observations per latent (defer to SMC)",
669 what.c_str(), ess,
static_cast<unsigned>(post.particles.size()));
689try_truncated_closed_form(
const GenericCircuit &gc,
gate_t root,
690 gate_t event_root,
unsigned k,
bool central)
693 if (!m)
return std::nullopt;
694 const double lo = m->lo, hi = m->hi;
701 auto raw = [&](
unsigned q) -> std::optional<double> {
702 if (q == 0)
return 1.0;
703 return dist->truncatedRawMoment(lo, hi, q);
706 if (!central)
return raw(k);
714 DistributionSpec targetSpec;
715 DistributionSpec otherSpec;
723std::optional<RvVsRvCond>
724matchRvVsRvConditional(
const GenericCircuit &gc,
gate_t root,
gate_t event_root)
729 if (!specX)
return std::nullopt;
731 const auto &wires = gc.
getWires(event_root);
732 if (wires.size() != 2)
return std::nullopt;
740 if (wires[0] == root) { other = wires[1]; targetLeft =
true; }
741 else if (wires[1] == root) { other = wires[0]; targetLeft =
false; }
742 else return std::nullopt;
746 if (!specY)
return std::nullopt;
756 if (targetLeft) targetGreater = greaterOp;
757 else targetGreater = lessOp;
758 return RvVsRvCond{*specX, *specY, targetGreater};
769 bool targetGreater,
unsigned k)
776 if (!dX->integrationRange(lo, hi))
777 return std::numeric_limits<double>::quiet_NaN();
779 auto base = [&](
double x) {
780 const double fX = dX->pdf(x);
781 const double FY = dY->cdf(x);
782 if (std::isnan(fX) || std::isnan(FY))
783 return std::numeric_limits<double>::quiet_NaN();
784 const double w = targetGreater ? FY : (1.0 - FY);
788 if (std::isnan(den) || !(den > 1e-12))
789 return std::numeric_limits<double>::quiet_NaN();
792 return std::pow(x,
static_cast<double>(k)) * base(x);
795 return std::numeric_limits<double>::quiet_NaN();
803try_rvVsRv_conditional_moment(
const GenericCircuit &gc,
gate_t root,
804 gate_t event_root,
unsigned k,
bool central)
806 auto m = matchRvVsRvConditional(gc, root, event_root);
807 if (!m)
return std::nullopt;
809 auto raw = [&](
unsigned q) -> std::optional<double> {
810 if (q == 0)
return 1.0;
811 double r = rvVsRvConditionalMoment(m->targetSpec, m->otherSpec,
812 m->targetGreater, q);
813 if (std::isnan(r))
return std::nullopt;
817 if (!central)
return raw(k);
828 DistributionSpec other;
842 const std::vector<PivotFactor> &factors,
847 if (!dX->integrationRange(lo, hi))
848 return std::numeric_limits<double>::quiet_NaN();
849 for (
const auto &f : factors)
851 if (f.pivotGreater) lo = std::max(lo, f.konst);
852 else hi = std::min(hi, f.konst);
854 if (!(hi > lo))
return 0.0;
856 std::vector<std::unique_ptr<Distribution>> others;
857 std::vector<bool> greater;
858 for (
const auto &f : factors)
861 greater.push_back(f.pivotGreater);
865 const double fX = dX->pdf(x);
866 if (std::isnan(fX))
return std::numeric_limits<double>::quiet_NaN();
868 for (std::size_t j = 0; j < others.size(); ++j) {
869 const double FY = others[j]->cdf(x);
870 if (std::isnan(FY))
return std::numeric_limits<double>::quiet_NaN();
871 w *= greater[j] ? FY : (1.0 - FY);
873 return std::pow(x,
static_cast<double>(k)) * w;
879struct PivotConjunctionCond {
880 DistributionSpec targetSpec;
881 std::vector<PivotFactor> factors;
889std::optional<PivotConjunctionCond>
890matchPivotConjunctionConditional(
const GenericCircuit &gc,
gate_t root,
897 if (!specX)
return std::nullopt;
899 const auto &kids = gc.
getWires(event_root);
900 if (kids.size() < 2)
return std::nullopt;
901 std::vector<PivotFactor> factors;
902 std::set<gate_t> othersSeen;
906 if (w.size() != 2)
return std::nullopt;
911 gate_t other;
bool targetLeft;
912 if (w[0] == root) { other = w[1]; targetLeft =
true; }
913 else if (w[1] == root) { other = w[0]; targetLeft =
false; }
914 else return std::nullopt;
919 const bool pivotGreater = targetLeft ? greaterOp : lessOp;
926 if (!othersSeen.insert(other).second)
return std::nullopt;
928 if (!specY)
return std::nullopt;
929 f = {
false, *specY, 0.0, pivotGreater};
930 }
else return std::nullopt;
931 factors.push_back(std::move(f));
933 return PivotConjunctionCond{*specX, std::move(factors)};
940try_pivotConjunction_conditional_moment(
const GenericCircuit &gc,
gate_t root,
941 gate_t event_root,
unsigned k,
944 auto m = matchPivotConjunctionConditional(gc, root, event_root);
945 if (!m)
return std::nullopt;
947 const double den = pivotConjunctionIntegral(m->targetSpec, m->factors, 0);
948 if (std::isnan(den) || !(den > 1e-12))
950 auto raw = [&](
unsigned q) -> std::optional<double> {
951 if (q == 0)
return 1.0;
952 const double num = pivotConjunctionIntegral(m->targetSpec, m->factors, q);
953 if (std::isnan(num))
return std::nullopt;
957 if (!central)
return raw(k);
969std::unique_ptr<Distribution> transform_image(
const GenericCircuit &gc,
976 if (!transform)
return nullptr;
981 if (!spec)
return nullptr;
991std::unique_ptr<Distribution> product_image(
const GenericCircuit &gc,
995 if (wires.empty())
return nullptr;
996 double c_total = 1.0;
997 std::vector<std::unique_ptr<Distribution>> dists;
998 std::vector<const Distribution *> factors;
999 std::set<gate_t> seen;
1004 catch (
const CircuitException &) {
return nullptr; }
1007 if (t !=
gate_rv)
return nullptr;
1008 if (!seen.insert(w).second)
return nullptr;
1010 if (!spec)
return nullptr;
1012 factors.push_back(dists.back().get());
1014 if (factors.empty())
return nullptr;
1017 std::unique_ptr<Distribution> combined;
1018 if (factors.size() == 1)
1019 combined = std::move(dists.front());
1022 if (!combined)
return nullptr;
1023 if (c_total != 1.0)
return combined->scale(c_total);
1043struct AffineArm {
double a, b; };
1045std::optional<AffineArm>
1046affineInPivot(
const GenericCircuit &gc,
gate_t arm,
gate_t pivot)
1052 return (arm == pivot) ? std::optional<AffineArm>(AffineArm{1.0, 0.0})
1057 auto isPivot = [&](
gate_t x) {
1060 auto constVal = [&](
gate_t x,
double &out) {
1067 return AffineArm{-1.0, 0.0};
1069 if (isPivot(w[0]) && constVal(w[1], c))
return AffineArm{1.0, c};
1070 if (isPivot(w[1]) && constVal(w[0], c))
return AffineArm{1.0, c};
1073 if (isPivot(w[0]) && constVal(w[1], c))
return AffineArm{1.0, -c};
1074 if (isPivot(w[1]) && constVal(w[0], c))
return AffineArm{-1.0, c};
1077 if (isPivot(w[0]) && constVal(w[1], c))
return AffineArm{c, 0.0};
1078 if (isPivot(w[1]) && constVal(w[0], c))
return AffineArm{c, 0.0};
1081 return std::nullopt;
1084std::optional<double>
1085singlePivotCaseRawMoment(
const GenericCircuit &gc,
gate_t g,
unsigned k)
1088 const auto &wires = gc.
getWires(g);
1089 if (wires.size() < 3 || wires.size() % 2 == 0)
return std::nullopt;
1090 const std::size_t m = wires.size() / 2;
1093 gate_t pivot{};
bool havePivot =
false;
1095 std::vector<Guard> guards;
1097 for (std::size_t i = 0; i < m; ++i) {
1098 gate_t gd = wires[2 * i];
1101 if (gw.size() != 2)
return std::nullopt;
1105 return std::nullopt;
1106 gate_t rvSide, constSide;
bool pivotLeft;
1108 rvSide = gw[0]; constSide = gw[1]; pivotLeft =
true;
1111 rvSide = gw[1]; constSide = gw[0]; pivotLeft =
false;
1112 }
else return std::nullopt;
1113 if (!havePivot) { pivot = rvSide; havePivot =
true; }
1114 else if (rvSide != pivot)
return std::nullopt;
1117 if (!havePivot)
return std::nullopt;
1120 if (!spec)
return std::nullopt;
1124 std::vector<AffineArm> arms;
1125 arms.reserve(m + 1);
1126 for (std::size_t i = 0; i < m; ++i) {
1127 auto af = affineInPivot(gc, wires[2 * i + 1], pivot);
1128 if (!af)
return std::nullopt;
1129 arms.push_back(*af);
1132 auto af = affineInPivot(gc, wires.back(), pivot);
1133 if (!af)
return std::nullopt;
1134 arms.push_back(*af);
1139 auto guardTrue = [](
const Guard &gd,
double x) ->
bool {
1140 const double lhs = gd.pivotLeft ? x : gd.c;
1141 const double rhs = gd.pivotLeft ? gd.c : x;
1147 default:
return false;
1150 auto pickArm = [&](
double x) ->
const AffineArm & {
1151 for (std::size_t i = 0; i < m; ++i)
1152 if (guardTrue(guards[i], x))
return arms[i];
1157 std::vector<double> cuts;
1158 for (
const auto &gd : guards) cuts.push_back(gd.c);
1159 std::sort(cuts.begin(), cuts.end());
1160 cuts.erase(std::unique(cuts.begin(), cuts.end()), cuts.end());
1162 const double NINF = -std::numeric_limits<double>::infinity();
1163 const double PINF = std::numeric_limits<double>::infinity();
1166 const std::size_t nseg = cuts.size() + 1;
1167 for (std::size_t s = 0; s < nseg; ++s) {
1168 const double lo = (s == 0) ? NINF : cuts[s - 1];
1169 const double hi = (s == cuts.size()) ? PINF : cuts[s];
1170 if (lo == hi)
continue;
1173 if (lo == NINF && hi == PINF) t = 0.0;
1174 else if (lo == NINF) t = hi - 1.0;
1175 else if (hi == PINF) t = lo + 1.0;
1176 else t = 0.5 * (lo + hi);
1177 const AffineArm &arm = pickArm(t);
1179 const double dF = dist->cdf(hi) - dist->cdf(lo);
1180 if (std::isnan(dF))
return std::nullopt;
1181 if (dF <= 0.0)
continue;
1184 for (
unsigned j = 0; j <= k; ++j) {
1185 const double aj = std::pow(arm.a,
static_cast<double>(j));
1186 if (aj == 0.0)
continue;
1187 const double coef =
binomial(k, j) * aj
1188 * std::pow(arm.b,
static_cast<double>(k - j));
1189 if (coef == 0.0)
continue;
1194 auto trm = dist->truncatedRawMoment(lo, hi, j);
1195 if (!trm)
return std::nullopt;
1198 total += coef * intj;
1213std::optional<double>
1214twoArmCaseRawMoment(
const GenericCircuit &gc,
gate_t g,
unsigned k)
1217 const auto &wires = gc.
getWires(g);
1218 if (wires.size() != 3)
return std::nullopt;
1221 const auto &gw = gc.
getWires(guard);
1222 if (gw.size() != 2)
return std::nullopt;
1226 return std::nullopt;
1227 gate_t a = gw[0], b = gw[1];
1229 return std::nullopt;
1232 if (!specA || !specB)
return std::nullopt;
1239 auto armContribution = [&](
gate_t armGate,
bool regionAgtB)
1240 -> std::optional<double> {
1241 for (
int which = 0; which < 2; ++which) {
1242 gate_t pv = (which == 0) ? a : b;
1243 auto af = affineInPivot(gc, armGate, pv);
1249 const bool pivotGreater = (which == 0) ? regionAgtB : !regionAgtB;
1250 const PivotFactor f{
false, otSpec, 0.0, pivotGreater};
1252 for (
unsigned j = 0; j <= k; ++j) {
1253 const double aj = std::pow(af->a,
static_cast<double>(j));
1254 if (aj == 0.0)
continue;
1255 const double coef =
binomial(k, j) * aj
1256 * std::pow(af->b,
static_cast<double>(k - j));
1257 if (coef == 0.0)
continue;
1258 const double I = pivotConjunctionIntegral(pvSpec, {f}, j);
1259 if (std::isnan(I))
return std::nullopt;
1264 return std::nullopt;
1267 auto c0 = armContribution(wires[1], guard_a_gt_b);
1268 if (!c0)
return std::nullopt;
1269 auto c1 = armContribution(wires[2], !guard_a_gt_b);
1270 if (!c1)
return std::nullopt;
1279evalGuardUnderOrder(
const GenericCircuit &gc,
gate_t guard,
1280 const std::unordered_map<gate_t, int> &rank)
1284 const auto &w = gc.
getWires(guard);
1285 if (w.size() != 2)
return std::nullopt;
1288 if (!ok)
return std::nullopt;
1289 auto ia = rank.find(w[0]), ib = rank.find(w[1]);
1290 if (ia == rank.end() || ib == rank.end())
return std::nullopt;
1291 const int ra = ia->second, rb = ib->second;
1300 return std::nullopt;
1306 auto v = evalGuardUnderOrder(gc, c, rank);
1307 if (!v)
return std::nullopt;
1308 acc = isAnd ? (acc && *v) : (acc || *v);
1312 return std::nullopt;
1325std::optional<double>
1326orderStatCaseRawMoment(
const GenericCircuit &gc,
gate_t g,
unsigned k)
1329 const auto &wires = gc.
getWires(g);
1330 if (wires.size() < 3 || wires.size() % 2 == 0)
return std::nullopt;
1331 const std::size_t m = wires.size() / 2;
1334 std::vector<gate_t> armRV(m + 1);
1335 std::vector<gate_t> uniq;
1336 std::unordered_map<gate_t, DistributionSpec> specOf;
1337 auto noteRV = [&](
gate_t v) ->
bool {
1339 if (specOf.find(v) == specOf.end()) {
1341 if (!sp)
return false;
1342 specOf.emplace(v, *sp);
1347 for (std::size_t i = 0; i < m; ++i) {
1348 armRV[i] = wires[2 * i + 1];
1349 if (!noteRV(armRV[i]))
return std::nullopt;
1351 armRV[m] = wires.back();
1352 if (!noteRV(armRV[m]))
return std::nullopt;
1354 const std::size_t n = uniq.size();
1355 if (n < 2 || n > 7)
return std::nullopt;
1359 std::vector<gate_t> guards(m);
1360 for (std::size_t i = 0; i < m; ++i) guards[i] = wires[2 * i];
1363 std::vector<std::size_t> perm(n);
1364 for (std::size_t i = 0; i < n; ++i) perm[i] = i;
1365 bool alwaysMax =
true, alwaysMin =
true;
1367 std::unordered_map<gate_t, int> rank;
1368 for (std::size_t i = 0; i < n; ++i) rank[uniq[perm[i]]] = static_cast<int>(i);
1370 gate_t maxRV = uniq[perm[n - 1]], minRV = uniq[perm[0]];
1372 gate_t selected = armRV[m];
1373 for (std::size_t i = 0; i < m; ++i) {
1374 auto gv = evalGuardUnderOrder(gc, guards[i], rank);
1375 if (!gv)
return std::nullopt;
1376 if (*gv) { selected = armRV[i];
break; }
1378 if (selected != maxRV) alwaysMax =
false;
1379 if (selected != minRV) alwaysMin =
false;
1380 if (!alwaysMax && !alwaysMin)
return std::nullopt;
1381 }
while (std::next_permutation(perm.begin(), perm.end()));
1383 const bool isMax = alwaysMax;
1387 for (std::size_t i = 0; i < n; ++i) {
1388 std::vector<PivotFactor> factors;
1389 factors.reserve(n - 1);
1390 for (std::size_t j = 0; j < n; ++j) {
1391 if (j == i)
continue;
1392 factors.push_back({
false, specOf.at(uniq[j]), 0.0, isMax});
1394 const double I = pivotConjunctionIntegral(specOf.at(uniq[i]), factors, k);
1395 if (std::isnan(I))
return std::nullopt;
1402std::optional<double>
1403caseAnalyticRawMoment(
const GenericCircuit &gc,
gate_t g,
unsigned k)
1405 if (
auto v = singlePivotCaseRawMoment(gc, g, k))
return v;
1406 if (
auto v = twoArmCaseRawMoment(gc, g, k))
return v;
1407 if (
auto v = orderStatCaseRawMoment(gc, g, k))
return v;
1408 return std::nullopt;
1411double rec_expectation(
const GenericCircuit &gc,
gate_t g, FootprintCache &fp)
1426 if (rvIsParametric(gc, g)) {
1428 if (tmpl && familyMeanIsAffine(*tmpl)) {
1431 return p.wire_slot < 0 ? p.literal
1432 : rec_expectation(gc, w[p.wire_slot], fp);
1435 ->factory(param_mean(tmpl->p1), param_mean(tmpl->p2))
1438 return mc_raw_moment(gc, g, 1,
"Expectation of a latent gate_rv");
1442 throw CircuitException(
1443 "Expectation: malformed gate_rv extra: " + gc.
getExtra(g));
1448 const auto &wires = gc.
getWires(g);
1452 for (
gate_t c : wires) s += rec_expectation(gc, c, fp);
1456 if (wires.size() != 2)
1457 throw CircuitException(
"gate_arith MINUS must be binary");
1458 return rec_expectation(gc, wires[0], fp)
1459 - rec_expectation(gc, wires[1], fp);
1462 if (wires.size() != 1)
1463 throw CircuitException(
"gate_arith NEG must be unary");
1464 return -rec_expectation(gc, wires[0], fp);
1467 if (pairwise_disjoint(fp, wires)) {
1469 for (
gate_t c : wires) p *= rec_expectation(gc, c, fp);
1472 return mc_raw_moment(gc, g, 1,
1473 "Expectation of gate_arith TIMES with shared random variables");
1477 return mc_raw_moment(gc, g, 1,
1478 "Expectation of gate_arith INTDIV (integer division)");
1480 if (wires.size() != 2)
1481 throw CircuitException(
"gate_arith DIV must be binary");
1488 return std::numeric_limits<double>::quiet_NaN();
1489 return rec_expectation(gc, wires[0], fp) / divisor;
1491 return mc_raw_moment(gc, g, 1,
1492 "Expectation of gate_arith DIV with non-constant divisor");
1502 if (
auto v = iidOrderStatMean(gc, g, isMax, fp))
1504 if (
auto v = mixedOrderStatMean(gc, g, isMax, fp))
1506 return mc_raw_moment(gc, g, 1,
1507 "Expectation of gate_arith " + std::string(isMax ?
"MAX" :
"MIN"));
1512 if (wires.size() == 1)
1513 return rec_expectation(gc, wires[0], fp);
1525 return mc_raw_moment(gc, g, 1,
1526 "Expectation of a gate_arith rounding or absolute value");
1535 if (
auto image = transform_image(gc, g, op))
1536 return image->mean();
1537 return mc_raw_moment(gc, g, 1,
1538 "Expectation of a gate_arith nonlinear transform");
1542 return mc_raw_moment(gc, g, 1,
1543 "Expectation of a gate_arith PERCENTILE");
1545 throw CircuitException(
1546 "Expectation: unknown gate_arith op tag: " +
1547 std::to_string(
static_cast<unsigned>(op)));
1550 const auto &wires = gc.
getWires(g);
1555 for (std::size_t i = 1; i < wires.size(); ++i) {
1565 if (wires.size() != 3)
1566 throw CircuitException(
1567 "Expectation: gate_mixture must have exactly three children");
1568 const double pi = mixturePi(gc, wires[0]);
1569 return pi * rec_expectation(gc, wires[1], fp)
1570 + (1.0 - pi) * rec_expectation(gc, wires[2], fp);
1573 if (
auto v = caseAnalyticRawMoment(gc, g, 1))
1575 return mc_raw_moment(gc, g, 1,
"Expectation of gate type gate_case");
1577 return mc_raw_moment(gc, g, 1,
1578 "Expectation of gate type " + std::string(
gate_type_name[type]));
1582double rec_variance(
const GenericCircuit &gc,
gate_t g, FootprintCache &fp)
1592 if (rvIsParametric(gc, g)) {
1593 const std::string what =
"Variance of a latent gate_rv";
1594 const double mu = mc_raw_moment(gc, g, 1, what);
1595 return mc_central_moment(gc, g, 2, mu, what);
1599 throw CircuitException(
1600 "Variance: malformed gate_rv extra: " + gc.
getExtra(g));
1605 const auto &wires = gc.
getWires(g);
1606 auto mc_var = [&](
const std::string &what) {
1607 const double mu = mc_raw_moment(gc, g, 1, what);
1608 return mc_central_moment(gc, g, 2, mu, what);
1612 if (pairwise_disjoint(fp, wires)) {
1614 for (
gate_t c : wires) s += rec_variance(gc, c, fp);
1618 "Variance of gate_arith PLUS with shared random variables");
1621 if (wires.size() != 2)
1622 throw CircuitException(
"gate_arith MINUS must be binary");
1623 if (pairwise_disjoint(fp, wires)) {
1624 return rec_variance(gc, wires[0], fp)
1625 + rec_variance(gc, wires[1], fp);
1628 "Variance of gate_arith MINUS with shared random variables");
1631 if (wires.size() != 1)
1632 throw CircuitException(
"gate_arith NEG must be unary");
1633 return rec_variance(gc, wires[0], fp);
1636 if (pairwise_disjoint(fp, wires)) {
1639 double prod_e2 = 1.0;
1640 double prod_e1 = 1.0;
1642 const double mu_c = rec_expectation(gc, c, fp);
1643 const double v_c = rec_variance(gc, c, fp);
1644 prod_e2 *= (v_c + mu_c * mu_c);
1647 return prod_e2 - prod_e1 * prod_e1;
1650 "Variance of gate_arith TIMES with shared random variables");
1653 return mc_var(
"Variance of gate_arith INTDIV (integer division)");
1655 if (wires.size() != 2)
1656 throw CircuitException(
"gate_arith DIV must be binary");
1660 return std::numeric_limits<double>::quiet_NaN();
1661 return rec_variance(gc, wires[0], fp) / (divisor * divisor);
1664 "Variance of gate_arith DIV with non-constant divisor");
1670 "Variance of gate_arith " +
1675 if (wires.size() == 1)
1676 return rec_variance(gc, wires[0], fp);
1685 "Variance of a gate_arith rounding or absolute value");
1689 if (
auto image = transform_image(gc, g, op))
1690 return image->variance();
1691 return mc_var(
"Variance of a gate_arith nonlinear transform");
1693 return mc_var(
"Variance of a gate_arith PERCENTILE");
1695 throw CircuitException(
1696 "Variance: unknown gate_arith op tag: " +
1697 std::to_string(
static_cast<unsigned>(op)));
1700 const auto &wires = gc.
getWires(g);
1703 double e1 = 0.0, e2 = 0.0;
1704 for (std::size_t i = 1; i < wires.size(); ++i) {
1705 const double p = gc.
getProb(wires[i]);
1710 return e2 - e1 * e1;
1714 if (wires.size() != 3)
1715 throw CircuitException(
1716 "Variance: gate_mixture must have exactly three children");
1717 const double pi = mixturePi(gc, wires[0]);
1718 const double ex = rec_expectation(gc, wires[1], fp);
1719 const double ey = rec_expectation(gc, wires[2], fp);
1720 const double vx = rec_variance(gc, wires[1], fp);
1721 const double vy = rec_variance(gc, wires[2], fp);
1722 const double em = pi * ex + (1.0 - pi) * ey;
1723 return pi * (vx + ex * ex)
1724 + (1.0 - pi) * (vy + ey * ey)
1728 if (
auto m2 = caseAnalyticRawMoment(gc, g, 2))
1729 if (
auto m1 = caseAnalyticRawMoment(gc, g, 1))
1730 return *m2 - (*m1) * (*m1);
1731 const std::string what =
"Variance of gate type gate_case";
1732 const double mu = mc_raw_moment(gc, g, 1, what);
1733 return mc_central_moment(gc, g, 2, mu, what);
1736 const std::string what =
1738 const double mu = mc_raw_moment(gc, g, 1, what);
1739 return mc_central_moment(gc, g, 2, mu, what);
1744double rec_raw_moment(
const GenericCircuit &gc,
gate_t g,
unsigned k,
1747 if (k == 0)
return 1.0;
1748 if (k == 1)
return rec_expectation(gc, g, fp);
1754 static_cast<double>(k));
1757 if (rvIsParametric(gc, g))
1758 return mc_raw_moment(gc, g, k,
"Raw moment of a latent gate_rv");
1761 throw CircuitException(
1762 "Moment: malformed gate_rv extra: " + gc.
getExtra(g));
1767 const auto &wires = gc.
getWires(g);
1770 if (wires.size() != 1)
1771 throw CircuitException(
"gate_arith NEG must be unary");
1772 const double v = rec_raw_moment(gc, wires[0], k, fp);
1773 return ((k % 2 == 0) ? 1.0 : -1.0) * v;
1776 if (pairwise_disjoint(fp, wires)) {
1780 std::vector<double> m_acc(k + 1, 0.0);
1781 for (
unsigned i = 0; i <= k; ++i)
1782 m_acc[i] = rec_raw_moment(gc, wires[0], i, fp);
1783 for (
size_t w = 1; w < wires.size(); ++w) {
1784 std::vector<double> next(k + 1, 0.0);
1785 std::vector<double> moments_y(k + 1, 0.0);
1786 for (
unsigned i = 0; i <= k; ++i)
1787 moments_y[i] = rec_raw_moment(gc, wires[w], i, fp);
1788 for (
unsigned kp = 0; kp <= k; ++kp) {
1790 for (
unsigned i = 0; i <= kp; ++i) {
1791 total +=
binomial(kp, i) * m_acc[i] * moments_y[kp - i];
1795 m_acc = std::move(next);
1799 return mc_raw_moment(gc, g, k,
1800 "Raw moment of gate_arith PLUS with shared random variables");
1803 if (wires.size() != 2)
1804 throw CircuitException(
"gate_arith MINUS must be binary");
1805 if (pairwise_disjoint(fp, wires)) {
1807 for (
unsigned i = 0; i <= k; ++i) {
1808 const double sign = ((k - i) % 2 == 0) ? 1.0 : -1.0;
1810 * rec_raw_moment(gc, wires[0], i, fp)
1812 * rec_raw_moment(gc, wires[1], k - i, fp);
1816 return mc_raw_moment(gc, g, k,
1817 "Raw moment of gate_arith MINUS with shared random variables");
1820 if (pairwise_disjoint(fp, wires)) {
1823 for (
gate_t c : wires) p *= rec_raw_moment(gc, c, k, fp);
1826 return mc_raw_moment(gc, g, k,
1827 "Raw moment of gate_arith TIMES with shared random variables");
1830 return mc_raw_moment(gc, g, k,
1831 "Raw moment of gate_arith INTDIV (integer division)");
1833 if (wires.size() != 2)
1834 throw CircuitException(
"gate_arith DIV must be binary");
1838 return std::numeric_limits<double>::quiet_NaN();
1839 return rec_raw_moment(gc, wires[0], k, fp)
1840 / std::pow(divisor,
static_cast<double>(k));
1842 return mc_raw_moment(gc, g, k,
1843 "Raw moment of gate_arith DIV with non-constant divisor");
1848 return mc_raw_moment(gc, g, k,
1849 "Raw moment of gate_arith " +
1854 if (wires.size() == 1)
1855 return rec_raw_moment(gc, wires[0], k, fp);
1863 return mc_raw_moment(gc, g, k,
1864 "Raw moment of a gate_arith rounding or absolute value");
1868 if (
auto image = transform_image(gc, g, op))
1869 return image->rawMoment(k);
1870 return mc_raw_moment(gc, g, k,
1871 "Raw moment of a gate_arith nonlinear transform");
1873 return mc_raw_moment(gc, g, k,
1874 "Raw moment of a gate_arith PERCENTILE");
1876 throw CircuitException(
1877 "Moment: unknown gate_arith op tag: " +
1878 std::to_string(
static_cast<unsigned>(op)));
1881 const auto &wires = gc.
getWires(g);
1885 for (std::size_t i = 1; i < wires.size(); ++i) {
1888 * std::pow(v,
static_cast<double>(k));
1893 if (wires.size() != 3)
1894 throw CircuitException(
1895 "Moment: gate_mixture must have exactly three children");
1896 const double pi = mixturePi(gc, wires[0]);
1897 return pi * rec_raw_moment(gc, wires[1], k, fp)
1898 + (1.0 - pi) * rec_raw_moment(gc, wires[2], k, fp);
1901 if (
auto v = caseAnalyticRawMoment(gc, g, k))
1903 return mc_raw_moment(gc, g, k,
"Raw moment of gate type gate_case");
1905 return mc_raw_moment(gc, g, k,
1918[[noreturn]]
void raise_infeasible_event(
const GenericCircuit &gc,
gate_t root)
1920 (void)gc; (void)root;
1921 throw CircuitException(
1922 "conditioning event is infeasible (empty intersection with the "
1923 "random variable's support)");
1926double conditional_raw_moment(
const GenericCircuit &gc,
gate_t root,
1927 unsigned k,
gate_t event_root)
1929 if (k == 0)
return 1.0;
1947 const std::string what =
"Posterior raw moment";
1949 gc, root, event_root, mc_samples_or_throw(what));
1950 checkPosteriorOrThrow(post, what);
1951 return weightedRawMoment(post, k);
1953 if (
auto cf = try_truncated_closed_form(gc, root, event_root, k,
false))
1955 if (
auto cf = try_rvVsRv_conditional_moment(gc, root, event_root, k,
false))
1957 if (
auto cf = try_pivotConjunction_conditional_moment(gc, root, event_root,
1961 raise_infeasible_event(gc, root);
1962 return mc_conditional_raw_moment(
1963 gc, root, k, event_root,
1964 "Conditional raw moment of gate type " +
1968double conditional_central_moment(
const GenericCircuit &gc,
gate_t root,
1969 unsigned k,
gate_t event_root)
1971 if (k == 0)
return 1.0;
1972 if (k == 1)
return 0.0;
1978 if (m1 && m2)
return *m2 - (*m1) * (*m1);
1985 if (k == 2)
return dist->variance();
1986 const double mu = dist->mean();
1988 for (
unsigned i = 0; i <= k; ++i) {
1989 const double mu_pow = std::pow(-mu,
static_cast<double>(k - i));
1990 total +=
binomial(k, i) * mu_pow * dist->rawMoment(i);
1997 const std::string what =
"Posterior central moment";
1999 gc, root, event_root, mc_samples_or_throw(what));
2000 checkPosteriorOrThrow(post, what);
2001 const double mu = weightedRawMoment(post, 1);
2002 return weightedCentralMoment(post, k, mu);
2004 if (
auto cf = try_truncated_closed_form(gc, root, event_root, k,
true))
2006 if (
auto cf = try_rvVsRv_conditional_moment(gc, root, event_root, k,
true))
2008 if (
auto cf = try_pivotConjunction_conditional_moment(gc, root, event_root,
2012 raise_infeasible_event(gc, root);
2014 const double mu = conditional_raw_moment(gc, root, 1, event_root);
2015 return mc_conditional_central_moment(
2016 gc, root, k, mu, event_root,
2017 "Conditional central moment of gate type " +
2024 std::optional<gate_t> event_root)
2026 if (event_root.has_value())
2027 return conditional_raw_moment(gc, root, 1, *event_root);
2028 FootprintCache fp(gc);
2029 return rec_expectation(gc, root, fp);
2033 std::optional<gate_t> event_root)
2035 if (event_root.has_value())
2036 return conditional_raw_moment(gc, root, k, *event_root);
2037 FootprintCache fp(gc);
2038 return rec_raw_moment(gc, root, k, fp);
2042 std::optional<gate_t> event_root)
2044 if (event_root.has_value())
2045 return conditional_central_moment(gc, root, k, *event_root);
2046 if (k == 0)
return 1.0;
2047 if (k == 1)
return 0.0;
2048 FootprintCache fp(gc);
2049 if (k == 2)
return rec_variance(gc, root, fp);
2051 const double mu = rec_expectation(gc, root, fp);
2053 for (
unsigned i = 0; i <= k; ++i) {
2054 const double mu_pow = std::pow(-mu,
static_cast<double>(k - i));
2055 total +=
binomial(k, i) * mu_pow * rec_raw_moment(gc, root, i, fp);
2069double empirical_quantile(std::vector<double> xs,
double p)
2071 xs.erase(std::remove_if(xs.begin(), xs.end(),
2072 [](
double x) { return std::isnan(x); }),
2074 if (xs.empty())
return std::numeric_limits<double>::quiet_NaN();
2075 std::sort(xs.begin(), xs.end());
2076 if (p <= 0.0)
return xs.front();
2077 if (p >= 1.0)
return xs.back();
2078 const double h = p *
static_cast<double>(xs.size() - 1);
2079 const std::size_t i =
static_cast<std::size_t
>(h);
2080 if (i + 1 >= xs.size())
return xs.back();
2081 const double frac = h -
static_cast<double>(i);
2082 return xs[i] + frac * (xs[i + 1] - xs[i]);
2088std::optional<double> categorical_quantile(
const GenericCircuit &gc,
2091 const auto &wires = gc.
getWires(mix);
2092 std::vector<std::pair<double, double>> outcomes;
2093 outcomes.reserve(wires.size());
2094 for (std::size_t i = 1; i < wires.size(); ++i) {
2097 catch (
const CircuitException &) {
return std::nullopt; }
2098 outcomes.emplace_back(v, gc.
getProb(wires[i]));
2100 if (outcomes.empty())
return std::nullopt;
2101 std::sort(outcomes.begin(), outcomes.end());
2103 for (
const auto &vp : outcomes) {
2105 if (cum >= p && cum > 0.0)
return vp.first;
2107 return outcomes.back().first;
2116std::optional<double> analytic_dist_quantile(
const Distribution &dist,
2117 double p,
double lo,
double hi);
2120 double p,
double lo,
double hi)
2125std::optional<double> analytic_dist_quantile(
const Distribution &dist,
2126 double p,
double lo,
double hi)
2128 if (p <= 0.0 || p >= 1.0) {
2130 const auto sup = dist.support();
2131 return (p <= 0.0) ? std::max(sup.lo, lo) : std::min(sup.hi, hi);
2134 if (std::isfinite(lo) || std::isfinite(hi)) {
2135 const double f_lo = std::isfinite(lo) ? dist.cdf(lo) : 0.0;
2136 const double f_hi = std::isfinite(hi) ? dist.cdf(hi) : 1.0;
2137 if (std::isnan(f_lo) || std::isnan(f_hi))
return std::nullopt;
2138 const double mass = f_hi - f_lo;
2139 if (mass < 1e-12)
return std::nullopt;
2140 u = f_lo + p * mass;
2142 double q = std::numeric_limits<double>::quiet_NaN();
2143 if (
auto cf = dist.quantile(u)) q = *cf;
2145 if (std::isnan(q))
return std::nullopt;
2155 std::optional<gate_t> event_root)
2157 const double inf = std::numeric_limits<double>::infinity();
2159 if (event_root.has_value()) {
2163 if (
auto q = analytic_rv_quantile(*post, p, -inf, inf))
2167 const std::string what =
"Posterior quantile";
2169 gc, root, *event_root, mc_samples_or_throw(what));
2170 checkPosteriorOrThrow(post, what);
2171 return weightedQuantile(std::move(post), p);
2175 if (
auto q = analytic_rv_quantile(m->spec, p, m->lo, m->hi))
2179 raise_infeasible_event(gc, root);
2181 gc, root, *event_root,
2182 mc_samples_or_throw(
"Conditional quantile"));
2183 check_acceptance_or_throw(cs,
"Conditional quantile");
2184 return empirical_quantile(std::move(cs.accepted), p);
2194 if (
auto q = analytic_rv_quantile(*spec, p, -inf, inf))
2197 if (
auto q = categorical_quantile(gc, root, p))
2205 std::unique_ptr<Distribution> image =
2207 : transform_image(gc, root, op);
2209 if (
auto q = analytic_dist_quantile(*image, p, -inf, inf))
2216 return empirical_quantile(
2239 std::optional<gate_t> &event_opt)
2241 std::vector<gate_t> evidences;
2254 evidences.push_back(w[1]);
2260 std::set<gate_t> seen;
2261 std::vector<gate_t> stack{root};
2262 while (!stack.empty()) {
2265 if (!seen.insert(g).second)
continue;
2271 evidences.push_back(w[1]);
2273 stack.push_back(target);
2279 if (evidences.empty())
2281 if (event_opt.has_value())
2282 evidences.push_back(*event_opt);
2284 if (evidences.size() == 1)
2285 cond = evidences[0];
2289 for (
gate_t e : evidences)
2336 const int32 k_signed = PG_GETARG_INT32(1);
2339 provsql_error(
"agg_avg_moment_exact: k must be non-negative (got %d)",
2346 gc, root,
static_cast<unsigned>(k_signed), ok);
2349 return Float8GetDatum(r);
2350 }
catch (
const std::exception &e) {
2362 const int32 k_signed = PG_GETARG_INT32(1);
2363 const bool central = PG_GETARG_BOOL(2);
2367 provsql_error(
"rv_moment: k must be non-negative (got %d)", k_signed);
2368 const unsigned k =
static_cast<unsigned>(k_signed);
2370 gate_t root_gate, event_gate;
2374 std::optional<gate_t> event_opt;
2376 event_opt = event_gate;
2394 return Float8GetDatum(result);
2395 }
catch (
const std::exception &e) {
2418 const double p = PG_GETARG_FLOAT8(1);
2421 if (std::isnan(p) || p < 0.0 || p > 1.0)
2422 provsql_error(
"rv_quantile: p must be in [0, 1] (got %g)", p);
2424 gate_t root_gate, event_gate;
2428 std::optional<gate_t> event_opt;
2430 event_opt = event_gate;
2434 return Float8GetDatum(
2436 }
catch (
const std::exception &e) {
2463 return Float8GetDatum(std::exp(*le));
2466 "rv_evidence: provsql.rv_mc_samples is 0 (the marginal likelihood is "
2467 "estimated by Monte Carlo); set it to a positive sample budget");
2470 return Float8GetDatum(e);
2471 }
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 ...
std::optional< std::vector< std::pair< double, double > > > enumerateScalarWorlds(const GenericCircuit &gc, gate_t root, std::optional< gate_t > event, unsigned max_inputs)
The possible worlds of a circuit whose only random sources are Boolean inputs: the exact alternative ...
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_ASFLOAT8
unary, child0 as double precision
@ PROVSQL_ARITH_LN
unary, natural logarithm of child0 (a negative draw raises at evaluation)
@ PROVSQL_ARITH_ROUND
child0 rounded half away from zero, to child1 decimal digits where a second child is given (SQL round...
@ 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_ABS
unary, |child0|
@ PROVSQL_ARITH_FLOOR
unary, greatest integer <= child0
@ PROVSQL_ARITH_NEG
unary, -child0
@ PROVSQL_ARITH_INTDIV
binary, child0 / child1 truncated toward zero: SQL's division of two integers
@ PROVSQL_ARITH_MINUS
binary, child0 - child1
@ PROVSQL_ARITH_CEIL
unary, least integer >= child0
@ 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)
@ PROVSQL_ARITH_ASFLOAT4
unary, child0 as real reads it
@ 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.