19#include "utils/uuid.h"
24PG_FUNCTION_INFO_V1(
rv_kl);
41constexpr double kInf = std::numeric_limits<double>::infinity();
43unsigned mc_budget_or_throw(
const std::string &what)
47 throw CircuitException(
48 what +
" has no closed form for this shape and "
49 "provsql.rv_mc_samples = 0 disables the Monte Carlo fallback");
50 return static_cast<unsigned>(n);
67 std::map<double, double> pmf;
68 std::function<double(
double)> pdf;
69 double lo = 0.0, hi = 0.0;
76void collectStochasticLeaves(
const GenericCircuit &gc,
gate_t g,
77 std::set<gate_t> &out, std::set<gate_t> &seen)
79 if (!seen.insert(g).second)
return;
84 collectStochasticLeaves(gc, c, out, seen);
87std::set<gate_t> stochasticLeaves(
const GenericCircuit &gc,
gate_t g)
89 std::set<gate_t> out, seen;
90 collectStochasticLeaves(gc, g, out, seen);
94std::optional<DensityView>
95resolveDensity(
const GenericCircuit &gc,
gate_t g)
102 catch (
const CircuitException &) {
return std::nullopt; }
104 view.discrete =
true;
111 if (!spec)
return std::nullopt;
114 view.discrete =
false;
115 if (!dist->integrationRange(view.lo, view.hi))
return std::nullopt;
116 view.pdf = [dist](
double x) {
return dist->pdf(x); };
124 view.discrete =
true;
125 for (std::size_t i = 1; i < wires.size(); ++i) {
128 catch (
const CircuitException &) {
return std::nullopt; }
129 view.pmf[v] += gc.
getProb(wires[i]);
137 if (wires.size() != 3)
return std::nullopt;
139 const double pi = gc.
getProb(wires[0]);
140 if (std::isnan(pi))
return std::nullopt;
141 auto ax = stochasticLeaves(gc, wires[1]);
142 auto ay = stochasticLeaves(gc, wires[2]);
144 if (ay.count(l))
return std::nullopt;
145 auto vx = resolveDensity(gc, wires[1]);
146 auto vy = resolveDensity(gc, wires[2]);
147 if (!vx || !vy)
return std::nullopt;
148 if (vx->discrete && vy->discrete) {
150 view.discrete =
true;
151 for (
const auto &[v, p] : vx->pmf) view.pmf[v] += pi * p;
152 for (
const auto &[v, p] : vy->pmf) view.pmf[v] += (1.0 - pi) * p;
155 if (!vx->discrete && !vy->discrete) {
157 view.discrete =
false;
158 view.lo = std::min(vx->lo, vy->lo);
159 view.hi = std::max(vx->hi, vy->hi);
160 auto fx = vx->pdf, fy = vy->pdf;
161 view.pdf = [pi, fx, fy](
double x) {
162 const double a = fx(x), b = fy(x);
163 if (std::isnan(a) || std::isnan(b))
164 return std::numeric_limits<double>::quiet_NaN();
165 return pi * a + (1.0 - pi) * b;
177double entropyOfView(
const DensityView &view)
181 for (
const auto &[v, p] : view.pmf)
182 if (p > 0.0) h -= p * std::log(p);
187 const double f = view.pdf(x);
188 if (std::isnan(f)) return std::numeric_limits<double>::quiet_NaN();
189 if (f <= 0.0) return 0.0;
190 return -f * std::log(f);
197double entropyFromSamples(std::vector<double> xs,
const std::string &what)
199 xs.erase(std::remove_if(xs.begin(), xs.end(),
200 [](
double v) { return std::isnan(v); }),
202 const std::size_t m = xs.size();
204 throw CircuitException(what +
": no defined Monte Carlo draws");
206 for (
double v : xs) mean += v;
207 mean /=
static_cast<double>(m);
209 for (
double v : xs) var += (v - mean) * (v - mean);
210 var /=
static_cast<double>(m);
211 const double sd = std::sqrt(var);
212 if (!(sd > 0.0))
return 0.0;
214 3.49 * sd / std::cbrt(
static_cast<double>(m));
215 const auto [mn_it, mx_it] = std::minmax_element(xs.begin(), xs.end());
216 const double lo = *mn_it, hi = *mx_it;
217 const std::size_t nbins =
218 std::max<std::size_t>(1,
static_cast<std::size_t
>((hi - lo) / h) + 1);
219 std::vector<std::size_t> counts(nbins, 0);
220 for (
double v : xs) {
221 std::size_t b =
static_cast<std::size_t
>((v - lo) / h);
222 if (b >= nbins) b = nbins - 1;
226 for (std::size_t c : counts) {
227 if (c == 0)
continue;
228 const double p =
static_cast<double>(c) /
static_cast<double>(m);
229 H -= p * std::log(p / h);
237 std::optional<gate_t> event)
240 if (
auto view = resolveDensity(gc, root)) {
241 const double h = entropyOfView(*view);
242 if (!std::isnan(h))
return h;
244 return entropyFromSamples(
254 view.discrete =
false;
255 if (dist->integrationRange(view.lo, view.hi)) {
256 view.pdf = [dist](
double x) {
return dist->pdf(x); };
257 const double h = entropyOfView(view);
258 if (!std::isnan(h))
return h;
265 gc, root, *event, mc_budget_or_throw(
"conditional entropy"));
266 if (cs.accepted.empty())
268 "conditional entropy: the conditioning event was never satisfied "
269 "in the Monte Carlo pass (probability ~ 0?)");
270 return entropyFromSamples(std::move(cs.accepted),
"conditional entropy");
275 auto vp = resolveDensity(gc, p_root);
276 auto vq = resolveDensity(gc, q_root);
279 "kl: both arguments must resolve to a closed-form density (a bare "
280 "distribution constructor, a constant, a categorical, or a mixture "
281 "of those with independent arms); arithmetic composites and "
282 "conditioned variables are not supported");
284 if (vp->discrete && vq->discrete) {
286 for (
const auto &[v, p] : vp->pmf) {
287 if (p <= 0.0)
continue;
288 auto it = vq->pmf.find(v);
289 const double q = (it == vq->pmf.end()) ? 0.0 : it->second;
290 if (q <= 0.0)
return kInf;
291 kl += p * std::log(p / q);
295 if (vp->discrete != vq->discrete)
303 const double fp = vp->pdf(x);
304 if (std::isnan(fp)) return std::numeric_limits<double>::quiet_NaN();
305 if (fp <= 0.0) return 0.0;
306 const double fq = vq->pdf(x);
307 if (std::isnan(fq)) return std::numeric_limits<double>::quiet_NaN();
308 if (fq <= 0.0) return kInf;
309 return fp * std::log(fp / fq);
316 if (x_root == y_root) {
319 if (
auto view = resolveDensity(gc, x_root))
320 if (view->discrete)
return entropyOfView(*view);
326 auto lx = stochasticLeaves(gc, x_root);
327 auto ly = stochasticLeaves(gc, y_root);
330 if (ly.count(l)) { shared =
true;
break; }
331 if (!shared)
return 0.0;
337 const unsigned m0 = mc_budget_or_throw(
"mutual_information");
339 std::vector<std::pair<double, double>> pairs;
340 pairs.reserve(xs.size());
341 for (std::size_t i = 0; i < xs.size(); ++i)
342 if (!std::isnan(xs[i]) && !std::isnan(ys[i]))
343 pairs.emplace_back(xs[i], ys[i]);
344 const std::size_t m = pairs.size();
348 double xlo = pairs[0].first, xhi = xlo, ylo = pairs[0].second, yhi = ylo;
349 for (
const auto &[a, b] : pairs) {
350 xlo = std::min(xlo, a); xhi = std::max(xhi, a);
351 ylo = std::min(ylo, b); yhi = std::max(yhi, b);
353 if (!(xhi > xlo) || !(yhi > ylo))
356 const std::size_t B = std::max<std::size_t>(
357 4,
static_cast<std::size_t
>(std::cbrt(
static_cast<double>(m))));
358 auto binOf = [B](
double v,
double lo,
double hi) {
359 auto b =
static_cast<std::size_t
>((v - lo) / (hi - lo)
360 *
static_cast<double>(B));
361 return (b >= B) ? B - 1 : b;
363 std::vector<std::size_t> joint(B * B, 0), margx(B, 0), margy(B, 0);
364 for (
const auto &[a, b] : pairs) {
365 const std::size_t bx = binOf(a, xlo, xhi), by = binOf(b, ylo, yhi);
366 ++joint[bx * B + by];
370 const double dm =
static_cast<double>(m);
372 for (std::size_t bx = 0; bx < B; ++bx)
373 for (std::size_t by = 0; by < B; ++by) {
374 const std::size_t c = joint[bx * B + by];
375 if (c == 0)
continue;
376 const double pij =
static_cast<double>(c) / dm;
377 const double px =
static_cast<double>(margx[bx]) / dm;
378 const double py =
static_cast<double>(margy[by]) / dm;
379 mi += pij * std::log(pij / (px * py));
381 return std::max(mi, 0.0);
399 gate_t root_gate, event_gate;
402 std::optional<gate_t> event_opt;
404 event_opt = event_gate;
407 return Float8GetDatum(
409 }
catch (
const std::exception &e) {
430 }
catch (
const std::exception &e) {
450 return Float8GetDatum(
452 }
catch (
const std::exception &e) {
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.
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.
Monte Carlo sampling over a GenericCircuit, RV-aware.
Shared 1-D quadrature core for the pivot-conjunction and order-statistic closed forms.
Continuous random-variable helpers (distribution parsing, moments).
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.
double computeKL(const GenericCircuit &gc, gate_t p_root, gate_t q_root)
Kullback-Leibler divergence KL(P || Q) in nats.
gate_t lift_conditioning(GenericCircuit &gc, gate_t root, std::optional< gate_t > &event_opt)
Lift conditioning out of a scalar arithmetic expression.
std::pair< std::vector< double >, std::vector< double > > monteCarloScalarPairSamples(const GenericCircuit &gc, gate_t root_a, gate_t root_b, unsigned samples)
Coupled per-iteration draws of two scalar roots.
double parseDoubleStrict(const std::string &s)
Strictly parse s as a double.
std::unique_ptr< Distribution > makeDistribution(const DistributionSpec &spec)
Construct the per-family Distribution for a parsed spec.
double computeEntropy(const GenericCircuit &gc, gate_t root, std::optional< gate_t > event)
Entropy of root in nats: Shannon for a discrete view, differential for a continuous one.
double simpsonIntegrate(double lo, double hi, int N, F &&f)
Composite-Simpson with N panels.
double computeMutualInformation(const GenericCircuit &gc, gate_t x_root, gate_t y_root)
Mutual information I(X; Y) in nats: exactly 0 for structurally independent roots, H(X) / Infinity for...
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< DistributionSpec > parse_distribution_spec(const std::string &s)
Parse the on-disk text encoding of a gate_rv distribution.
constexpr int kSimpsonPanels
Panel count shared by every composite-Simpson quadrature over a distribution's integration range: exa...
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.
Core types, constants, and utilities shared across ProvSQL.
@ gate_rv
Continuous random-variable leaf (extra encodes distribution).
@ gate_mixture
Probabilistic mixture: three wires [p_token (gate_input Bernoulli), x_token, y_token]; samples x when...
C++ utility functions for UUID manipulation.