ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
RvSample.cpp
Go to the documentation of this file.
1/**
2 * @file RvSample.cpp
3 * @brief SQL function `provsql.rv_sample(token, n, prov)`.
4 *
5 * Returns up to @c n samples from the (possibly conditional) scalar
6 * distribution rooted at @c token. When @c prov resolves to
7 * @c gate_one the samples come from the unconditional distribution
8 * (one draw per call to @c monteCarloScalarSamples); when @c prov is
9 * a non-trivial gate the path switches to MC rejection via
10 * @c monteCarloConditionalScalarSamples, with a budget large enough
11 * to deliver @c n accepted draws under the @c acceptance_floor
12 * heuristic.
13 *
14 * Result: @c SETOF @c float8 emitted through the Materialize SRF
15 * pattern (same shape as @c shapley_all_vars). The unconditional
16 * path always returns exactly @c n rows; the conditional path may
17 * return fewer, in which case a @c NOTICE is emitted so the caller
18 * can choose to widen the budget by raising
19 * @c provsql.rv_mc_samples.
20 */
21extern "C" {
22#include "postgres.h"
23#include "fmgr.h"
24#include "funcapi.h"
25#include "miscadmin.h"
26#include "utils/builtins.h"
27#include "utils/tuplestore.h"
28#include "utils/uuid.h"
29#include "provsql_utils.h"
30#include "provsql_error.h"
31
32PG_FUNCTION_INFO_V1(rv_sample);
33}
34
35#include "CircuitFromMMap.h"
36#include "ConjugatePosterior.h"
37#include "Expectation.h"
38#include "GenericCircuit.h"
39#include "MonteCarloSampler.h"
41#include "provsql_utils_cpp.h"
42
43#include <algorithm>
44#include <optional>
45#include <vector>
46
47extern "C" Datum
48rv_sample(PG_FUNCTION_ARGS)
49{
50 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
51
52 MemoryContext per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
53 MemoryContext oldcontext = MemoryContextSwitchTo(per_query_ctx);
54
55 TupleDesc tupdesc = rsinfo->expectedDesc;
56 Tuplestorestate *tupstore = tuplestore_begin_heap(
57 rsinfo->allowedModes & SFRM_Materialize_Random, false, work_mem);
58
59 rsinfo->returnMode = SFRM_Materialize;
60 rsinfo->setResult = tupstore;
61
62 try {
63 pg_uuid_t *token = (pg_uuid_t *) PG_GETARG_POINTER(0);
64 const int32 n_signed = PG_GETARG_INT32(1);
65 pg_uuid_t *prov = (pg_uuid_t *) PG_GETARG_POINTER(2);
66
67 if (n_signed <= 0)
68 provsql_error("rv_sample: n must be positive (got %d)", n_signed);
69 const unsigned n = static_cast<unsigned>(n_signed);
70
71 gate_t root_gate, event_gate;
72 auto gc = getJointCircuit(*token, *prov, root_gate, event_gate);
73
74 /* A stored "X | C" arrives as a conditioned root: peel it to the bare
75 * scalar target and fold the condition into the event so the rest of the
76 * function samples the conditional (truncated) distribution. */
77 std::optional<gate_t> event_opt;
78 if (gc.getGateType(event_gate) != gate_one) event_opt = event_gate;
79 root_gate = provsql::lift_conditioning(gc, root_gate, event_opt);
80 const bool conditional = event_opt.has_value();
81
82 std::vector<double> samples;
83 if (conditional) {
84 const gate_t event = *event_opt;
85 /* Conjugate shape: the posterior is a first-class distribution, so
86 * draw n i.i.d. samples from it directly -- exact (no weighted-
87 * particle resampling), reproducible under a pinned seed, and
88 * available at rv_mc_samples = 0. */
89 if (auto post = provsql::conjugatePosterior(gc, root_gate, event)) {
90 auto dist = provsql::makeDistribution(*post);
91 auto rng = provsql::seedRng();
92 samples.reserve(n);
93 for (unsigned i = 0; i < n; ++i)
94 samples.push_back(dist->sample(rng));
95 } else
96 /* Continuous-density evidence (latent-variable posterior): draw
97 * latents from the prior, weight by the observations' densities,
98 * and resample n posterior draws (SIR). Posterior predictive is
99 * rv_sample on a fresh leaf that reuses the same latent. */
100 if (provsql::circuitHasObserve(gc, event)) {
101 const unsigned budget =
103 ? static_cast<unsigned>(provsql_rv_mc_samples) : 1000u * n;
104 auto post =
105 provsql::importanceSampleConditional(gc, root_gate, event, budget);
106 if (post.particles.empty() || post.weight_sum <= 0.0)
108 "rv_sample: evidence is infeasible (no positive-weight draw "
109 "among %u Monte Carlo samples); the observations may contradict "
110 "the prior, or raise provsql.rv_mc_samples", budget);
111 samples = provsql::posteriorResample(post, n);
112 } else {
113 /* Closed-form truncation fast path: when the root is a bare
114 * gate_rv of a supported family (Uniform / Normal / Exponential)
115 * and the event reduces to a single interval on it, we draw
116 * exactly @c n samples directly from the truncated distribution.
117 * 100% acceptance, no NOTICE on tight events like X > 9.5 over
118 * U(0, 10) that the MC rejection path degrades on. Falls
119 * through to the MC rejection path for un-extractable shapes
120 * (Erlang, gate_arith composites, gate_mixture roots…). */
122 gc, root_gate, event, n);
123 if (direct) {
124 samples = std::move(*direct);
125 } else {
126 /* Budget: n / acceptance_floor candidate draws, capped at the
127 * GUC ceiling. acceptance_floor = 0.001 means a 0.1% acceptance
128 * rate still delivers n samples; rates below that yield fewer
129 * samples + a NOTICE. */
130 const unsigned budget = std::min(
131 static_cast<unsigned>(1000u) * n,
133 ? static_cast<unsigned>(provsql_rv_mc_samples) : 1000u * n);
135 gc, root_gate, event, budget);
136 if (cs.accepted.size() > n) cs.accepted.resize(n);
137 if (cs.accepted.size() < n) {
138 ereport(NOTICE,
139 (errmsg("rv_sample: requested %u, returning %zu "
140 "(acceptance rate %zu/%u)",
141 n, cs.accepted.size(),
142 cs.accepted.size(), cs.attempted)));
143 }
144 samples = std::move(cs.accepted);
145 }
146 }
147 } else {
148 samples = provsql::monteCarloScalarSamples(gc, root_gate, n);
149 }
150
151 for (double x : samples) {
152 Datum values[1] = { Float8GetDatum(x) };
153 bool nulls[1] = { false };
154 tuplestore_putvalues(tupstore, tupdesc, values, nulls);
155 }
156 } catch (const std::exception &e) {
157 MemoryContextSwitchTo(oldcontext);
158 provsql_error("rv_sample: %s", e.what());
159 } catch (...) {
160 MemoryContextSwitchTo(oldcontext);
161 provsql_error("rv_sample: unknown exception");
162 }
163
164 MemoryContextSwitchTo(oldcontext);
165 PG_RETURN_NULL();
166}
GenericCircuit getJointCircuit(const std::vector< pg_uuid_t > &tokens, std::vector< gate_t > &gates)
Multi-root variant of getJointCircuit.
Build in-memory circuits from the mmap-backed persistent store.
gate_t
Strongly-typed gate identifier.
Definition Circuit.h:49
Exact conjugate-prior posteriors for observe-evidence circuits.
Per-family polymorphic view over a continuous gate_rv distribution (§F.1 class hierarchy).
Analytical expectation / variance / moment evaluator over RV circuits.
Semiring-agnostic in-memory provenance circuit.
Monte Carlo sampling over a GenericCircuit, RV-aware.
Datum rv_sample(PG_FUNCTION_ARGS)
Definition RvSample.cpp:48
gate_t lift_conditioning(GenericCircuit &gc, gate_t root, std::optional< gate_t > &event_opt)
Lift conditioning out of a scalar arithmetic expression.
std::vector< double > posteriorResample(const WeightedPosterior &post, unsigned n)
Sampling-importance-resampling: draw n posterior samples from a weighted particle set (proportional t...
std::unique_ptr< Distribution > makeDistribution(const DistributionSpec &spec)
Construct the per-family Distribution for a parsed spec.
std::mt19937_64 seedRng()
The shared Monte Carlo generator, seeded from the provsql.monte_carlo_seed GUC (-1 = non-deterministi...
ConditionalScalarSamples monteCarloConditionalScalarSamples(const GenericCircuit &gc, gate_t root, gate_t event_root, unsigned samples)
Rejection-sample root conditioned on event_root.
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< 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...
WeightedPosterior importanceSampleConditional(const GenericCircuit &gc, gate_t root, gate_t evidence, unsigned samples)
Self-normalised importance sampling of root given evidence.
bool circuitHasObserve(const GenericCircuit &gc, gate_t root)
Whether the circuit reachable from root contains a gate_observe – the signal that a conditioning even...
int provsql_rv_mc_samples
Default sample count for analytical-evaluator MC fallbacks; 0 disables fallback (callers raise instea...
Definition provsql.c:100
Uniform error-reporting macros for ProvSQL.
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
Core types, constants, and utilities shared across ProvSQL.
C++ utility functions for UUID manipulation.
UUID structure.