ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
Distribution.cpp
Go to the documentation of this file.
1/**
2 * @file Distribution.cpp
3 * @brief The family-agnostic side of the @c Distribution hierarchy: the
4 * registries (family descriptors / factories, pairwise comparator
5 * and closure rules), their dispatch drivers, and the generic
6 * comparator quadrature fallback.
7 *
8 * The per-family closed forms live in one file per family next to this
9 * one (@c normal.cpp, @c uniform.cpp, @c exponential.cpp,
10 * @c erlang.cpp); each registers itself at static initialisation, so
11 * adding a family means adding a file -- nothing here changes.
12 */
13#include "DistributionCommon.h"
14
15#include <cmath>
16#include <map>
17#include <string>
18#include <tuple>
19#include <utility>
20
21namespace provsql {
22
23namespace {
24
25/* Function-local statics so registration (dynamic initialisation of the
26 * per-family registrar objects) never observes an uninitialised map,
27 * whatever the TU initialisation order. */
28using FamilyPair = std::pair<std::string, std::string>;
29
30std::map<FamilyPair, ComparatorRule> &comparatorRules()
31{
32 static std::map<FamilyPair, ComparatorRule> rules;
33 return rules;
34}
35
36std::map<FamilyPair, ClosureRule> &closureRules()
37{
38 static std::map<FamilyPair, ClosureRule> rules;
39 return rules;
40}
41
42std::map<FamilyPair, ProductRule> &productRules()
43{
44 static std::map<FamilyPair, ProductRule> rules;
45 return rules;
46}
47
48std::map<FamilyPair, TransformRule> &transformRules()
49{
50 static std::map<FamilyPair, TransformRule> rules;
51 return rules;
52}
53
54std::map<std::string, const DistributionFamily *> &familiesByName()
55{
56 static std::map<std::string, const DistributionFamily *> families;
57 return families;
58}
59
60/* (likelihood family, wired parameter position, prior family) */
61using ConjugateKey = std::tuple<std::string, int, std::string>;
62
63std::map<ConjugateKey, ConjugateRule> &conjugateRules()
64{
65 static std::map<ConjugateKey, ConjugateRule> rules;
66 return rules;
67}
68
69/* Registry-miss default: P(X < Y) by a 1-D composite-Simpson quadrature.
70 * Two equivalent identities are available --
71 * P(X<Y) = ∫ (1 - F_Y(t)) f_X(t) dt over X's integration range,
72 * P(X<Y) = ∫ F_X(t) f_Y(t) dt over Y's integration range --
73 * and the fixed panel budget resolves the narrower window better, so the
74 * side with the narrower range is the one integrated over. A heavy tail
75 * (Pareto with small α) blows its own window up to xₘ·10^{9/α}, leaving
76 * ~30-unit panels across the density peak; integrating the *other*,
77 * well-conditioned density against the heavy tail's exact CDF instead
78 * keeps the answer exact-grade. Family-agnostic -- needs only pdf /
79 * cdf / integrationRange. NaN when a density / CDF is undefined (e.g.
80 * a non-integer Erlang shape), so the caller falls back to Monte Carlo. */
81double quadraturePairLess(const Distribution &X, const Distribution &Y)
82{
83 double xlo, xhi, ylo, yhi;
84 const bool xok = X.integrationRange(xlo, xhi);
85 const bool yok = Y.integrationRange(ylo, yhi);
86 if (!xok && !yok)
87 return kNaN;
88 const bool overX = xok && (!yok || (xhi - xlo) <= (yhi - ylo));
89 const double lo = overX ? xlo : ylo;
90 const double hi = overX ? xhi : yhi;
91 const int N = 4000;
92 const double h = (hi - lo) / N;
93 double acc = 0.0;
94 for (int i = 0; i <= N; ++i) {
95 const double t = lo + i * h;
96 const double f = overX ? X.pdf(t) : Y.pdf(t);
97 const double F = overX ? Y.cdf(t) : X.cdf(t);
98 if (std::isnan(f) || std::isnan(F))
99 return kNaN;
100 const double coeff = (i == 0 || i == N) ? 1.0 : (i % 2 == 1 ? 4.0 : 2.0);
101 acc += coeff * (overX ? (1.0 - F) : F) * f;
102 }
103 return acc * h / 3.0;
104}
105
106} // namespace
107
108void registerComparatorRule(const char *x, const char *y,
109 ComparatorRule rule)
110{
111 comparatorRules()[{x, y}] = rule;
112}
113
114void registerClosureRule(const char *x, const char *y, ClosureRule rule)
115{
116 closureRules()[{x, y}] = rule;
117}
118
119void registerProductRule(const char *x, const char *y, ProductRule rule)
120{
121 productRules()[{x, y}] = rule;
122}
123
124void registerTransformRule(const char *transform, const char *family,
125 TransformRule rule)
126{
127 transformRules()[{transform, family}] = rule;
128}
129
130void registerConjugateRule(const char *likelihood_family, int wired_param,
131 const char *prior_family,
132 const ConjugateRule &rule)
133{
134 conjugateRules()[{likelihood_family, wired_param, prior_family}] = rule;
135}
136
137const ConjugateRule *lookupConjugateRule(const std::string &likelihood_family,
138 int wired_param,
139 const std::string &prior_family)
140{
141 const auto &rules = conjugateRules();
142 const auto it = rules.find({likelihood_family, wired_param, prior_family});
143 return it == rules.end() ? nullptr : &it->second;
144}
145
146std::unique_ptr<Distribution> closeProductFactors(
147 const std::vector<const Distribution *> &factors)
148{
149 const auto &rules = productRules();
150 ProductRule rule = nullptr;
151 const char *n0 = nullptr;
152 for (const Distribution *f : factors) {
153 if (!n0) {
154 n0 = f->family().name;
155 const auto it = rules.find({n0, n0});
156 if (it == rules.end()) return nullptr;
157 rule = it->second;
158 } else {
159 const auto it = rules.find({n0, f->family().name});
160 if (it == rules.end() || it->second != rule) return nullptr;
161 }
162 }
163 if (!rule) return nullptr;
164 return rule(factors);
165}
166
167std::unique_ptr<Distribution> closeTransform(const char *transform,
168 const Distribution &x)
169{
170 const auto &rules = transformRules();
171 const auto it = rules.find({transform, x.family().name});
172 return it == rules.end() ? nullptr : it->second(x);
173}
174
175std::unique_ptr<Distribution> closePlusTerms(
176 const std::vector<ClosureTerm> &terms)
177{
178 const auto &rules = closureRules();
179 ClosureRule rule = nullptr;
180 const char *n0 = nullptr;
181 for (const auto &t : terms) {
182 if (!t.dist) continue;
183 if (!n0) {
184 n0 = t.dist->family().name;
185 const auto it = rules.find({n0, n0});
186 if (it == rules.end()) return nullptr;
187 rule = it->second;
188 } else {
189 const auto it = rules.find({n0, t.dist->family().name});
190 if (it == rules.end() || it->second != rule) return nullptr;
191 }
192 }
193 if (!rule) return nullptr; /* no RV term: the constant fold's job */
194 return rule(terms);
195}
196
197double numericQuantile(const Distribution &d, double p)
198{
199 double lo, hi;
200 if (!d.integrationRange(lo, hi))
201 return kNaN;
202 const double f_lo = d.cdf(lo), f_hi = d.cdf(hi);
203 if (std::isnan(f_lo) || std::isnan(f_hi) || !(f_lo <= f_hi))
204 return kNaN;
205 if (p <= f_lo) return lo;
206 if (p >= f_hi) return hi;
207 /* The CDF is monotone, so plain bisection is unconditionally
208 * convergent; 200 halvings exhaust double precision from any
209 * starting window long before the bound. */
210 for (int i = 0; i < 200; ++i) {
211 const double mid = 0.5 * (lo + hi);
212 if (!(mid > lo && mid < hi))
213 break; /* interval narrowed to adjacent doubles */
214 const double f = d.cdf(mid);
215 if (std::isnan(f))
216 return kNaN;
217 if (f < p) lo = mid;
218 else hi = mid;
219 }
220 return 0.5 * (lo + hi);
221}
222
224{
225 const auto &rules = comparatorRules();
226 const auto it = rules.find({X.family().name, Y.family().name});
227 double pLess = kNaN;
228 if (it != rules.end())
229 pLess = it->second(X, Y);
230 /* Registry miss, or a rule that declined (parameter guard, or a shape
231 * outside its closed form): generic quadrature. */
232 if (std::isnan(pLess))
233 pLess = quadraturePairLess(X, Y);
234 return pLess;
235}
236
238{
239 familiesByName()[descriptor.name] = &descriptor;
240}
241
242std::vector<const DistributionFamily *> listDistributionFamilies()
243{
244 std::vector<const DistributionFamily *> out;
245 for (const auto &entry : familiesByName())
246 out.push_back(entry.second);
247 return out; /* std::map iteration order: sorted by name */
248}
249
250const DistributionFamily *lookupDistributionFamily(const std::string &name)
251{
252 const auto &families = familiesByName();
253 const auto it = families.find(name);
254 return it == families.end() ? nullptr : it->second;
255}
256
257std::unique_ptr<Distribution> makeDistribution(const DistributionSpec &spec)
258{
259 if (!spec.family) return nullptr;
260 return spec.family->factory(spec.p1, spec.p2);
261}
262
263} // namespace provsql
Internal helpers shared by the per-family Distribution implementations under src/distributions/.
Abstract per-family continuous distribution.
virtual bool integrationRange(double &lo, double &hi) const =0
Finite window [lo, hi] covering essentially all of X's mass, for numerical quadrature.
virtual const DistributionFamily & family() const =0
The family's interned registry descriptor.
virtual double cdf(double x) const =0
F(x); NaN if the family declines.
double comparatorPairLess(const Distribution &X, const Distribution &Y)
for two independent RVs.
std::unique_ptr< Distribution >(*)( const Distribution &x) TransformRule
A closed-form image of a monotone transform of one family (e.g.
const ConjugateRule * lookupConjugateRule(const std::string &likelihood_family, int wired_param, const std::string &prior_family)
Look up the conjugate rule for (likelihood family, wired parameter position, prior family); nullptr o...
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...
std::unique_ptr< Distribution >(*)(const std::vector< ClosureTerm > &terms) ClosureRule
A family sum-closure fold.
std::unique_ptr< Distribution > makeDistribution(const DistributionSpec &spec)
Construct the per-family Distribution for a parsed spec.
void registerProductRule(const char *x, const char *y, ProductRule rule)
Register the product-closure rule for a family pair (name-token keyed, like the sum-closure rules).
std::unique_ptr< Distribution > closePlusTerms(const std::vector< ClosureTerm > &terms)
Fold PLUS(terms) into a single distribution when a registered closure covers every family in the sum.
void registerComparatorRule(const char *x, const char *y, ComparatorRule rule)
Register the closed form for a family pair.
double(*)(const Distribution &X, const Distribution &Y) ComparatorRule
A pairwise closed form for , X and Y independent.
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...
std::vector< const DistributionFamily * > listDistributionFamilies()
Every registered family, sorted by name token.
void registerDistributionFamily(const DistributionFamily &descriptor)
Register a family; called by the registrar at static init.
std::unique_ptr< Distribution >(*)( const std::vector< const Distribution * > &factors) ProductRule
A family product-closure fold: the distribution of the product of the (independent) factors,...
double numericQuantile(const Distribution &d, double p)
Numeric inverse CDF: monotone bisection of cdf() over the family's integration window.
const DistributionFamily * lookupDistributionFamily(const std::string &name)
Look up a family by its on-disk name token.
void registerTransformRule(const char *transform, const char *family, TransformRule rule)
Register the image rule for transform ("ln" / "exp" / ...; the evaluator maps its arith opcodes to th...
void registerClosureRule(const char *x, const char *y, ClosureRule rule)
Register the sum-closure rule for a family pair (name-token keyed, like the comparator rules).
void registerConjugateRule(const char *likelihood_family, int wired_param, const char *prior_family, const ConjugateRule &rule)
Register the conjugate update for observations of likelihood_family whose parameter wired_param (0 = ...
constexpr double kNaN
The conjugate update for one observation of a given likelihood family against the running posterior (...
A registered family's descriptor: its complete identity.
DistributionFactory factory
Parsed distribution spec (family + up to two parameters).
const DistributionFamily * family
double p2
Second parameter (σ, b, or λ; unused for 1-parameter families).
double p1
First parameter (μ, a, k, or λ).