21#include <unordered_map>
22#include <unordered_set>
31std::mt19937_64 seedRng()
37 std::random_device rd;
38 rng.seed((
static_cast<uint64_t
>(rd()) << 32) | rd());
61 Sampler(
const GenericCircuit &gc, std::mt19937_64 &rng)
62 : gc_(gc), rng_(rng) {}
65 void resetIteration() {
67 scalar_cache_.clear();
71 double evalScalar(
gate_t g);
74 const GenericCircuit &gc_;
75 std::mt19937_64 &rng_;
76 std::unordered_map<gate_t, bool> bool_cache_;
77 std::unordered_map<gate_t, double> scalar_cache_;
80bool Sampler::evalBool(
gate_t g)
82 auto it = bool_cache_.find(g);
83 if(it != bool_cache_.end())
return it->second;
93 std::uniform_real_distribution<double> u(0.0, 1.0);
94 result = u(rng_) < gc_.
getProb(g);
100 if(evalBool(c)) { result =
true;
break; }
106 if(!evalBool(c)) { result =
false;
break; }
110 if(wires.size() != 2)
111 throw CircuitException(
"gate_monus must have exactly two children");
112 result = evalBool(wires[0]) && !evalBool(wires[1]);
122 if(wires.size() != 2)
123 throw CircuitException(
"gate_cmp must have exactly two children");
127 throw CircuitException(
128 "gate_cmp: unsupported operator OID " +
129 std::to_string(gc_.
getInfos(g).first));
130 double l = evalScalar(wires[0]);
131 double r = evalScalar(wires[1]);
132 result = applyCmp(l, op, r);
136 throw CircuitException(
137 "Monte Carlo over circuits containing gate_mulinput "
138 "is not yet supported on the RV path");
145 if(wires.size() != 1)
146 throw CircuitException(
"gate_delta must have exactly one child");
147 result = evalBool(wires[0]);
154 if(wires.size() != 1)
155 throw CircuitException(
156 "gate_assumed must have exactly one child");
157 result = evalBool(wires[0]);
162 if(wires.size() != 1)
163 throw CircuitException(
"gate_annotation must have exactly one child");
164 result = evalBool(wires[0]);
167 throw CircuitException(
168 "Unsupported gate type in Boolean evaluation: " +
172 bool_cache_[g] = result;
176double Sampler::evalScalar(
gate_t g)
178 auto it = scalar_cache_.find(g);
179 if(it != scalar_cache_.end())
return it->second;
183 const auto &wires = gc_.
getWires(g);
193 throw CircuitException(
194 "Malformed gate_rv extra: " + gc_.
getExtra(g));
196 case DistKind::Normal: {
197 std::normal_distribution<double> d(spec->p1, spec->p2);
201 case DistKind::Uniform: {
202 std::uniform_real_distribution<double> d(spec->p1, spec->p2);
206 case DistKind::Exponential: {
207 std::exponential_distribution<double> d(spec->p1);
211 case DistKind::Erlang: {
215 std::gamma_distribution<double> d(spec->p1, 1.0 / spec->p2);
225 throw CircuitException(
"gate_arith must have at least one child");
230 for(
gate_t c : wires) result += evalScalar(c);
234 for(
gate_t c : wires) result *= evalScalar(c);
237 if(wires.size() != 2)
238 throw CircuitException(
"gate_arith MINUS must be binary");
239 result = evalScalar(wires[0]) - evalScalar(wires[1]);
242 if(wires.size() != 2)
243 throw CircuitException(
"gate_arith DIV must be binary");
244 result = evalScalar(wires[0]) / evalScalar(wires[1]);
247 if(wires.size() != 1)
248 throw CircuitException(
"gate_arith NEG must be unary");
249 result = -evalScalar(wires[0]);
252 throw CircuitException(
253 "Unknown gate_arith operator tag: " +
254 std::to_string(
static_cast<unsigned>(op)));
281 std::unique_ptr<Aggregator> agg =
286 throw CircuitException(
287 "gate_agg: makeAggregator returned null for op " +
288 std::to_string(
static_cast<int>(op)));
289 for(
gate_t child : wires) {
291 const auto &sm = gc_.
getWires(child);
292 if(sm.size() != 2)
continue;
293 if(!evalBool(sm[0]))
continue;
295 agg->add(AggValue(
static_cast<long>(evalScalar(sm[1]))));
297 agg->add(AggValue(evalScalar(sm[1])));
300 AggValue r = agg->finalize();
303 result =
static_cast<double>(std::get<long>(r.
v));
306 result = std::get<double>(r.
v);
324 : std::numeric_limits<double>::quiet_NaN();
327 throw CircuitException(
328 "gate_agg: unsupported aggregate result ValueType in MC");
342 const auto &wires = gc_.
getWires(g);
343 if(wires.size() != 2)
344 throw CircuitException(
345 "gate_semimod must have exactly two children "
346 "[k_gate, value_gate]");
347 result = evalBool(wires[0]) ? evalScalar(wires[1]) : 0.0;
371 std::uniform_real_distribution<double> u(0.0, 1.0);
372 const double r = u(rng_);
376 std::size_t chosen = wires.size() - 1;
377 for(std::size_t i = 1; i < wires.size(); ++i) {
379 if(r < cum) { chosen = i;
break; }
381 for(std::size_t i = 1; i < wires.size(); ++i) {
382 bool_cache_[wires[i]] = (i == chosen);
387 if(wires.size() != 3)
388 throw CircuitException(
389 "gate_mixture must have exactly three children "
390 "[p_token, x_token, y_token]");
391 result = evalBool(wires[0]) ? evalScalar(wires[1])
392 : evalScalar(wires[2]);
396 throw CircuitException(
397 "Unsupported gate type in scalar evaluation: " +
401 scalar_cache_[g] = result;
409 std::mt19937_64 rng = seedRng();
410 Sampler sampler(gc, rng);
412 unsigned success = 0;
413 for(
unsigned i = 0; i < samples; ++i) {
414 sampler.resetIteration();
415 if(sampler.evalBool(root))
420 "Interrupted after " + std::to_string(i + 1) +
" samples");
422 return success * 1.0 / samples;
426 double eps,
double delta,
427 unsigned long max_samples,
428 unsigned long &samples_used,
429 bool &reached_target)
432 reached_target =
false;
440 const double e = std::exp(1.0);
441 const double Y = 4.0 * (e - 2.0) * std::log(2.0 / delta) / (eps * eps);
442 const double Y1 = 1.0 + (1.0 + eps) * Y;
444 std::mt19937_64 rng = seedRng();
445 Sampler sampler(gc, rng);
447 unsigned long success = 0;
448 for(
unsigned long s = 0; s < max_samples; ++s) {
449 sampler.resetIteration();
450 if(sampler.evalBool(root)) {
452 if(
static_cast<double>(success) >= Y1) {
453 samples_used = s + 1;
454 reached_target =
true;
455 return Y1 /
static_cast<double>(samples_used);
460 "Interrupted after " + std::to_string(s + 1) +
" samples");
465 samples_used = max_samples;
466 return static_cast<double>(success) /
static_cast<double>(max_samples);
471 const std::vector<gate_t> &cmps,
474 const unsigned k = cmps.size();
477 "monteCarloJointDistribution: empty cmps list");
480 "monteCarloJointDistribution: too many cmps in island ("
481 + std::to_string(k) +
" > 30)");
483 std::mt19937_64 rng = seedRng();
484 Sampler sampler(gc, rng);
486 const std::size_t nb_outcomes = std::size_t{1} << k;
487 std::vector<unsigned> counts(nb_outcomes, 0);
489 for (
unsigned i = 0; i < samples; ++i) {
490 sampler.resetIteration();
492 for (
unsigned j = 0; j < k; ++j) {
493 if (sampler.evalBool(cmps[j])) w |= (std::size_t{1} << j);
498 "Interrupted after " + std::to_string(i + 1) +
" samples");
501 std::vector<double> probs(nb_outcomes);
502 for (std::size_t w = 0; w < nb_outcomes; ++w)
503 probs[w] = counts[w] * 1.0 / samples;
510 std::mt19937_64 rng = seedRng();
511 Sampler sampler(gc, rng);
513 std::vector<double> out;
514 out.reserve(samples);
515 for(
unsigned i = 0; i < samples; ++i) {
516 sampler.resetIteration();
517 out.push_back(sampler.evalScalar(root));
521 "Interrupted after " + std::to_string(i + 1) +
" samples");
529 std::mt19937_64 rng = seedRng();
530 Sampler sampler(gc, rng);
536 for(
unsigned i = 0; i < samples; ++i) {
537 sampler.resetIteration();
544 if(sampler.evalBool(event_root)) {
545 out.
accepted.push_back(sampler.evalScalar(root));
551 "Interrupted after " + std::to_string(i + 1) +
" samples");
575double inv_phi(
double p)
577 static const double a[] = {
578 -3.969683028665376e+01, 2.209460984245205e+02,
579 -2.759285104469687e+02, 1.383577518672690e+02,
580 -3.066479806614716e+01, 2.506628277459239e+00
582 static const double b[] = {
583 -5.447609879822406e+01, 1.615858368580409e+02,
584 -1.556989798598866e+02, 6.680131188771972e+01,
585 -1.328068155288572e+01
587 static const double c_arr[] = {
588 -7.784894002430293e-03, -3.223964580411365e-01,
589 -2.400758277161838e+00, -2.549732539343734e+00,
590 4.374664141464968e+00, 2.938163982698783e+00
592 static const double d[] = {
593 7.784695709041462e-03, 3.224671290700398e-01,
594 2.445134137142996e+00, 3.754408661907416e+00
596 static const double p_low = 0.02425;
597 static const double p_high = 1.0 - p_low;
600 const double q = std::sqrt(-2.0 * std::log(p));
601 return (((((c_arr[0]*q + c_arr[1])*q + c_arr[2])*q
602 + c_arr[3])*q + c_arr[4])*q + c_arr[5])
603 / ((((d[0]*q + d[1])*q + d[2])*q + d[3])*q + 1.0);
606 const double q = p - 0.5;
607 const double r = q * q;
608 return (((((a[0]*r + a[1])*r + a[2])*r + a[3])*r + a[4])*r + a[5]) * q
609 / (((((b[0]*r + b[1])*r + b[2])*r + b[3])*r + b[4])*r + 1.0);
611 const double q = std::sqrt(-2.0 * std::log(1.0 - p));
612 return -(((((c_arr[0]*q + c_arr[1])*q + c_arr[2])*q
613 + c_arr[3])*q + c_arr[4])*q + c_arr[5])
614 / ((((d[0]*q + d[1])*q + d[2])*q + d[3])*q + 1.0);
619std::optional<std::vector<double>>
621 gate_t event_root,
unsigned n)
624 if (!m)
return std::nullopt;
626 const double lo = m->lo, hi = m->hi;
628 std::mt19937_64 rng = seedRng();
629 std::uniform_real_distribution<double> U01(0.0, 1.0);
631 std::vector<double> out;
639 std::uniform_real_distribution<double> U(lo, hi);
640 for (
unsigned i = 0; i < n; ++i) out.push_back(U(rng));
644 const double lambda = spec.
p1;
645 if (!(lambda > 0.0))
return std::nullopt;
646 if (std::isinf(hi)) {
650 std::exponential_distribution<double> E(lambda);
651 for (
unsigned i = 0; i < n; ++i) out.push_back(lo + E(rng));
657 const double F_lo = -std::expm1(-lambda * lo);
658 const double F_hi = -std::expm1(-lambda * hi);
659 if (!(F_lo < F_hi))
return std::nullopt;
660 for (
unsigned i = 0; i < n; ++i) {
661 const double u = F_lo + U01(rng) * (F_hi - F_lo);
662 out.push_back(-std::log1p(-u) / lambda);
667 const double mu = spec.
p1;
668 const double sigma = spec.
p2;
669 if (!(sigma > 0.0))
return std::nullopt;
670 const double sqrt2 = std::sqrt(2.0);
671 const double alpha = (lo - mu) / sigma;
672 const double beta = (hi - mu) / sigma;
673 const double Phi_a = std::isfinite(alpha)
674 ? 0.5 * (1.0 + std::erf(alpha / sqrt2))
675 : (alpha < 0 ? 0.0 : 1.0);
676 const double Phi_b = std::isfinite(beta)
677 ? 0.5 * (1.0 + std::erf(beta / sqrt2))
678 : (beta < 0 ? 0.0 : 1.0);
679 if (!(Phi_a < Phi_b))
return std::nullopt;
684 static constexpr double EPS = 1e-15;
685 for (
unsigned i = 0; i < n; ++i) {
686 double u = Phi_a + U01(rng) * (Phi_b - Phi_a);
687 if (u < EPS) u = EPS;
688 if (u > 1.0 - EPS) u = 1.0 - EPS;
689 const double z = inv_phi(u);
690 out.push_back(mu + sigma * z);
704 std::unordered_set<gate_t> seen;
705 std::stack<gate_t> stack;
707 while(!stack.empty()) {
710 if(!seen.insert(g).second)
continue;
741 std::unordered_set<gate_t> seen;
742 std::stack<gate_t> stack;
745 while(!stack.empty()) {
748 if(!seen.insert(g).second)
continue;
ComparisonOperator cmpOpFromOid(Oid op_oid, bool &ok)
Map a PostgreSQL comparison-operator OID to a ComparisonOperator.
AggregationOperator getAggregationOperator(Oid oid)
Map a PostgreSQL aggregate function OID to an AggregationOperator.
std::unique_ptr< Aggregator > makeAggregator(AggregationOperator op, ValueType t)
Create a concrete Aggregator for the given operator and value type.
Typed aggregation value, operator, and aggregator abstractions.
AggregationOperator
SQL aggregation functions tracked by ProvSQL.
@ COUNT
COUNT(*) or COUNT(expr) → integer.
@ SUM
SUM → integer or float.
ComparisonOperator
SQL comparison operators used in gate_cmp circuit gates.
@ LE
Less than or equal (<=).
@ GE
Greater than or equal (>=).
@ INT
Signed 64-bit integer.
@ FLOAT
Double-precision float.
Generic directed-acyclic-graph circuit template and gate identifier.
gate_t
Strongly-typed gate identifier.
Monte Carlo sampling over a GenericCircuit, RV-aware.
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.
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).
std::string getExtra(gate_t g) const
Return the string extra for gate g.
double getProb(gate_t g) const
Return the probability for gate g.
std::pair< unsigned, unsigned > getInfos(gate_t g) const
Return the integer annotation pair for gate g.
@ Normal
Normal (Gaussian): p1=μ, p2=σ
@ Exponential
Exponential: p1=λ, p2 unused.
@ Uniform
Uniform on [a,b]: p1=a, p2=b.
@ Erlang
Erlang: p1=k (positive integer), p2=λ.
double parseDoubleStrict(const std::string &s)
Strictly parse s as a double.
std::vector< double > monteCarloJointDistribution(const GenericCircuit &gc, const std::vector< gate_t > &cmps, unsigned samples)
Estimate the joint distribution of cmps via Monte Carlo.
double monteCarloRVStopping(const GenericCircuit &gc, gate_t root, double eps, double delta, unsigned long max_samples, unsigned long &samples_used, bool &reached_target)
Whole-circuit (eps,delta)-relative probability via the Dagum-Karp-Luby-Ross stopping rule.
bool circuitHasUnresolvedSampleableAgg(const GenericCircuit &gc, gate_t root)
Whether a surviving gate_agg exists and every one is sample-faithful (SUM / AVG / MIN / MAX / COUNT –...
ConditionalScalarSamples monteCarloConditionalScalarSamples(const GenericCircuit &gc, gate_t root, gate_t event_root, unsigned samples)
Rejection-sample root conditioned on event_root.
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.
double monteCarloRV(const GenericCircuit &gc, gate_t root, unsigned samples)
Run Monte Carlo on a circuit that may contain gate_rv leaves.
std::optional< std::vector< double > > try_truncated_closed_form_sample(const GenericCircuit &gc, gate_t root, gate_t event_root, unsigned n)
Try to draw n exact samples from the conditional distribution of root given event_root via closed-for...
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.
bool circuitHasRV(const GenericCircuit &gc, gate_t root)
Walk the circuit reachable from root looking for any gate_rv.
int provsql_monte_carlo_seed
Seed for the Monte Carlo sampler; -1 means non-deterministic (std::random_device); controlled by the ...
bool provsql_interrupted
Global variable that becomes true if this particular backend received an interrupt signal.
const char * gate_type_name[]
Names of gate types.
provsql_arith_op
Arithmetic operator tags used by gate_arith.
@ PROVSQL_ARITH_DIV
binary, child0 / child1
@ PROVSQL_ARITH_PLUS
n-ary, sum of children
@ PROVSQL_ARITH_NEG
unary, -child0
@ PROVSQL_ARITH_MINUS
binary, child0 - child1
@ PROVSQL_ARITH_TIMES
n-ary, product of children
@ gate_rv
Continuous random-variable leaf (extra encodes distribution).
@ gate_annotation
Transparent single-child wrapper carrying a query-level annotation in extra (inversion-free certifica...
@ 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...
ValueType getType() const
Return the runtime type tag of this value.
std::variant< long, double, bool, std::string, std::vector< long >, std::vector< double >, std::vector< bool >, std::vector< std::string > > v
The variant holding the actual value.
Outcome of a conditional Monte Carlo sampling pass.
std::vector< double > accepted
Parsed distribution spec (kind + up to two parameters).
double p2
Second parameter (σ or b; unused for Exponential).
double p1
First parameter (μ, a, or λ).