ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
Distribution.h
Go to the documentation of this file.
1/**
2 * @file Distribution.h
3 * @brief Per-family polymorphic view over a continuous @c gate_rv
4 * distribution (§F.1 class hierarchy).
5 *
6 * Every RV evaluator dispatches through this interface (one virtual call
7 * per family) and the registries below; family identity is the interned
8 * @c DistributionFamily descriptor each implementation file registers --
9 * there is no family enum anywhere. A @c Distribution is a transient
10 * view constructed from a parsed @c DistributionSpec via
11 * @ref provsql::makeDistribution; the on-disk @c extra text encoding stores the
12 * family's name token.
13 *
14 * Adding a family is one new self-registering implementation file under
15 * @c src/distributions/ (plus its SQL constructor). Methods a family
16 * cannot answer follow the NaN-as-undecided contract so the analytic
17 * paths fall back to Monte Carlo unchanged.
18 */
19#ifndef PROVSQL_DISTRIBUTION_H
20#define PROVSQL_DISTRIBUTION_H
21
22#include <cstddef>
23#include <memory>
24#include <optional>
25#include <random>
26#include <string>
27#include <utility>
28#include <vector>
29
30#include "../RandomVariable.h" // DistributionSpec
31
32namespace provsql {
33
34/** @brief A closed support interval [lo, hi] (±infinity for unbounded). */
36 double lo;
37 double hi;
38};
39
40/**
41 * @brief Abstract per-family continuous distribution.
42 *
43 * Concrete subclasses (Normal / Uniform / Exponential / Erlang / Gamma)
44 * hold the two parameters and implement the family-specific closed forms.
45 * The methods below are the family-local ones (pairwise closure /
46 * comparison rules live in separate registries, not here).
47 */
49public:
50 virtual ~Distribution() = default;
51
52 /** @name Identity (parameters, for the i.i.d. equality test) */
53 ///@{
54 /**
55 * @brief The family's interned registry descriptor.
56 *
57 * One instance per family, so @c &family() (or comparing
58 * @c family().name) is family identity.
59 */
60 virtual const DistributionFamily &family() const = 0;
61 virtual double p1() const = 0;
62 virtual double p2() const = 0;
63 ///@}
64
65 /** @name Closed-form moments */
66 ///@{
67 virtual double mean() const = 0; ///< E[X]
68 virtual double variance() const = 0; ///< Var(X)
69 virtual double rawMoment(unsigned k) const = 0; ///< E[X^k]
70 ///@}
71
72 /**
73 * @brief Whether @c mean() is an affine function of the raw parameters
74 * @c (p1, p2) -- i.e. @c mean = c0 + c1·p1 + c2·p2.
75 *
76 * Default @c false. A family whose mean is a linear combination of its
77 * parameters (Normal @c μ, Uniform @c (a+b)/2, inverse-Gaussian @c μ)
78 * overrides to @c true. This is the authoritative signal the latent-
79 * variable evaluator uses to keep the expectation of a compound (token-
80 * parameterised) leaf EXACT: when the mean is affine,
81 * @f$E[X] = E[\mathrm{mean}(\theta)] = \mathrm{mean}(E[\theta])@f$ by
82 * linearity of expectation (no independence assumption), so the evaluator
83 * recurses into the parameter wires instead of falling back to Monte
84 * Carlo. Families with a nonlinear mean (Exponential @c 1/λ, Gamma
85 * @c k/λ, ...) keep the default and decline.
86 */
87 virtual bool meanIsAffine() const { return false; }
88
89 /**
90 * @brief Whether the family is discrete (integer-valued), so a point
91 * event @c X @c = @c c carries positive mass.
92 *
93 * Default @c false (continuous). The discrete families (Poisson,
94 * Binomial) override to @c true. It is the authoritative, per-family
95 * signal -- not inferred -- that a point observation / conditioning
96 * event on a leaf of this family is @b feasible (a continuous point
97 * event is measure-zero, so it is only meaningful as a likelihood
98 * weight, never as a rejection interval). @c pdf() already returns the
99 * pmf for a discrete family, so the density-weight path needs no special
100 * case beyond this classification.
101 */
102 virtual bool isDiscrete() const { return false; }
103
104 /** @name Density / distribution */
105 ///@{
106 virtual double pdf(double x) const = 0; ///< f(x); NaN if the family declines
107 virtual double cdf(double x) const = 0; ///< F(x); NaN if the family declines
108 ///@}
109
110 /** @brief Natural support interval of X. */
111 virtual DistSupport support() const = 0;
112
113 /**
114 * @brief Finite window [lo, hi] covering essentially all of X's mass, for
115 * numerical quadrature. Returns false (leaving @p lo / @p hi
116 * untouched) when the parameters are degenerate.
117 */
118 virtual bool integrationRange(double &lo, double &hi) const = 0;
119
120 /**
121 * @brief Plot x-window given optional truncation bounds (±infinity means
122 * "unbounded on that side"); used by the SVG curve renderer.
123 */
124 virtual std::pair<double, double> plotRange(double trunc_lo,
125 double trunc_hi) const = 0;
126
127 /** @brief Draw one sample using the shared MC generator. */
128 virtual double sample(std::mt19937_64 &rng) const = 0;
129
130 /**
131 * @brief Inverse CDF @f$Q(p) = F^{-1}(p)@f$ for @f$p \in (0, 1)@f$.
132 *
133 * @c std::nullopt when the family has no elementary inverse CDF
134 * (Erlang), so "unsupported" cannot be silently consumed -- callers
135 * branch on availability explicitly. Normal uses the
136 * Beasley-Springer-Moro rational approximation (~1e-7 accuracy, ample
137 * for sampling); Uniform / Exponential invert exactly. Behaviour at
138 * the endpoints is the family's (BSM diverges; clamp @p p strictly
139 * inside @c (0, 1) before calling).
140 */
141 virtual std::optional<double> quantile(double p) const {
142 return std::nullopt;
143 }
144
145 /**
146 * @brief Closed-form raw moment @f$E[X^k \mid lo < X < hi]@f$ of the
147 * distribution truncated to @c [lo, hi] (±infinity for a
148 * semi-infinite side).
149 *
150 * @c std::nullopt when the family has no closed form (Erlang: needs
151 * the regularised lower incomplete gamma) or the truncation is
152 * degenerate (mass below a numerical floor), so the caller falls
153 * through to MC rejection. @p k is at least 1 (the caller
154 * short-circuits @c k @c = @c 0).
155 */
156 virtual std::optional<double> truncatedRawMoment(double lo, double hi,
157 unsigned k) const {
158 (void) lo; (void) hi; (void) k;
159 return std::nullopt;
160 }
161
162 /**
163 * @brief Draw @p n rejection-free samples of X conditioned on
164 * @c lo < X < hi (±infinity for a semi-infinite side).
165 *
166 * Each family uses its own exact scheme (interval intersection for
167 * Uniform, memorylessness / stable inverse-CDF for Exponential,
168 * inverse-CDF transform for Normal). @c std::nullopt when the family
169 * has no scheme (Erlang: needs the inverse regularised incomplete
170 * gamma) or the truncation is degenerate / parameters invalid, so the
171 * caller's MC-rejection fallback can emit its usual diagnostics.
172 */
173 virtual std::optional<std::vector<double>> sampleTruncated(
174 std::mt19937_64 &rng, double lo, double hi, unsigned n) const {
175 (void) rng; (void) lo; (void) hi; (void) n;
176 return std::nullopt;
177 }
178
179 /**
180 * @brief Closed-form mean of the max / min of @p n i.i.d. copies of X.
181 *
182 * - Uniform(a, b): @c E[max] @c = @c a + (b-a)·n/(n+1),
183 * @c E[min] @c = @c a + (b-a)/(n+1).
184 * - Exponential(λ): @c E[min] @c = @c 1/(nλ), @c E[max] @c = @c H_n/λ
185 * (harmonic number), for @c λ > 0.
186 *
187 * @c std::nullopt when the family has no elementary order-statistic
188 * mean (Normal, Erlang -- those need the 1-D layer-cake quadrature) or
189 * the parameters are degenerate, so the caller falls back to
190 * quadrature / Monte Carlo.
191 */
192 virtual std::optional<double> iidOrderStatMean(std::size_t n,
193 bool isMax) const {
194 (void) n; (void) isMax;
195 return std::nullopt;
196 }
197
198 /**
199 * @brief Closed-form location-scale transform @c a·X @c + @c b within
200 * the family.
201 *
202 * Returns the transformed distribution when the family is closed under
203 * these coefficients, @c nullptr when it is not: Exponential / Erlang
204 * decline @c a @c <= @c 0 (the support flips) and any non-zero offset
205 * (a shifted Erlang leaves the family); every family declines
206 * @c a @c == @c 0 (a Dirac is not in-family -- the constant-fold path
207 * is responsible for pure constants).
208 */
209 virtual std::unique_ptr<Distribution> affine(double a, double b) const = 0;
210
211 /** @brief Closed-form @c c·X (affine with no offset). */
212 std::unique_ptr<Distribution> scale(double c) const {
213 return affine(c, 0.0);
214 }
215
216 /** @brief Closed-form @c -X (affine with coefficient -1). */
217 std::unique_ptr<Distribution> negate() const {
218 return affine(-1.0, 0.0);
219 }
220
221 /**
222 * @brief The on-disk @c gate_rv @c extra text encoding
223 * (e.g. <tt>"normal:2.5,0.5"</tt>), inverse of
224 * @c parse_distribution_spec.
225 */
226 virtual std::string serialise() const = 0;
227
228 /**
229 * @brief The point-mass value when the parameters make the
230 * distribution degenerate (a Dirac), @c std::nullopt for a
231 * proper distribution.
232 *
233 * Only Normal reports one (σ == 0, e.g. after an underflowing scale
234 * fold); the simplifier collapses such a result to a plain constant.
235 * Families whose degenerate forms are still sampled as-is
236 * (Uniform(a, a)) do not report.
237 */
238 virtual std::optional<double> asDirac() const { return std::nullopt; }
239};
240
241/**
242 * @brief Construct the per-family @c Distribution for a parsed spec.
243 *
244 * Returns @c nullptr only for a spec with a null family pointer (which
245 * @c parse_distribution_spec never produces). Parameter-validity guards
246 * live in the family methods (e.g. @c pdf returns NaN for a
247 * non-positive σ).
248 */
249std::unique_ptr<Distribution> makeDistribution(const DistributionSpec &spec);
250
251/**
252 * @name DistributionRegistry – family descriptor table
253 *
254 * Family implementations self-register their descriptor at static
255 * initialisation: the on-disk name token (the part before the colon in a
256 * @c gate_rv @c extra), the parameter count, the display metadata, and
257 * the constructor. @c makeDistribution and @c parse_distribution_spec
258 * dispatch through this table, so adding a family means one new
259 * implementation file with one registrar -- no existing factory, parser,
260 * or header is touched.
261 */
262///@{
263
264/** @brief Construct a family instance from its (up to) two parameters. */
266 std::unique_ptr<Distribution> (*)(double p1, double p2);
267
268/**
269 * @brief A registered family's descriptor: its complete identity.
270 *
271 * @c name is the on-disk token (the part before the colon in a
272 * @c gate_rv @c extra); @c factory constructs an instance from the
273 * parsed parameters; @c label / @c param_names are the display metadata
274 * UI clients (ProvSQL Studio) read through @c provsql.rv_families() --
275 * a short glance-recognisable glyph for a node circle (e.g. "N", "Exp",
276 * "Γ") and the conventional parameter symbols in @c extra order
277 * (e.g. {"μ", "σ"}; the second entry is @c nullptr for a 1-parameter
278 * family). Purely presentational choices beyond these (colours,
279 * geometry) stay client-side.
280 *
281 * Each family implementation file defines exactly ONE descriptor with
282 * static storage duration and registers its address, so the pointer is
283 * an interned family identity (this is what @c DistributionSpec and
284 * @c Distribution::family() carry; there is no family enum to extend).
285 */
287 const char *name;
288 unsigned nparams; ///< 1 or 2 (a 1-parameter family leaves p2 = 0)
289 const char *label;
290 const char *param_names[2];
292};
293
294/** @brief Register a family; called by the registrar at static init. */
295void registerDistributionFamily(const DistributionFamily &descriptor);
296
297/** @brief Static-initialisation helper: one per family implementation. */
300 registerDistributionFamily(descriptor);
301 }
302};
303
304/**
305 * @brief Look up a family by its on-disk name token.
306 *
307 * Used by @c parse_distribution_spec to resolve the interned
308 * descriptor and expected parameter count; @c nullptr for an unknown
309 * name.
310 */
311const DistributionFamily *lookupDistributionFamily(const std::string &name);
312
313/**
314 * @brief Every registered family, sorted by name token.
315 *
316 * Backs the @c provsql.rv_families() catalog function.
317 */
318std::vector<const DistributionFamily *> listDistributionFamilies();
319
320///@}
321
322/**
323 * @brief Numeric inverse CDF: monotone bisection of @c cdf() over the
324 * family's integration window.
325 *
326 * Family-agnostic fallback for families whose @c quantile() declines
327 * (no elementary inverse CDF -- Erlang, Gamma): needs only @c cdf and
328 * @c integrationRange, converges to ~1 ulp, and is deterministic, so
329 * the quantile readout stays analytic instead of dropping to Monte
330 * Carlo. @p p outside the window's mass clamps to the window edge
331 * (the window covers all but a vanishing tail). NaN when the CDF or
332 * the window is unavailable, matching the NaN-as-undecided contract of
333 * the other analytic paths.
334 */
335double numericQuantile(const Distribution &d, double p);
336
337/**
338 * @name ComparatorRuleRegistry – pairwise §B.2 closed forms
339 *
340 * Closed-form @f$P(X < Y)@f$ for an ordered pair of independent RV
341 * families. On continuous distributions every ordered comparator reduces
342 * to @f$P(X < Y)@f$ or its complement, so rules are keyed on the family
343 * pair alone (no operator in the key). Pairwise behaviour deliberately
344 * stays out of the @c Distribution interface: family files self-register
345 * their rules at static initialisation through
346 * @ref provsql::ComparatorRuleRegistrar, so adding a family touches no existing
347 * file, and a missing rule is not an error -- the driver falls back to a
348 * family-agnostic quadrature.
349 */
350///@{
351
352/**
353 * @brief A pairwise closed form for @f$P(X < Y)@f$, X and Y independent.
354 *
355 * Returns NaN when its parameter guards fail (e.g. a non-positive rate);
356 * the driver then tries the generic quadrature.
357 */
358using ComparatorRule = double (*)(const Distribution &X,
359 const Distribution &Y);
360
361/**
362 * @brief Register the @f$P(X < Y)@f$ closed form for a family pair.
363 *
364 * Keyed by the families' name tokens rather than descriptor pointers so
365 * a family file can register a cross-family rule without depending on
366 * another file's static-initialisation order.
367 */
368void registerComparatorRule(const char *x, const char *y,
369 ComparatorRule rule);
370
371/** @brief Static-initialisation helper: one per registered family pair. */
373 ComparatorRuleRegistrar(const char *x, const char *y,
374 ComparatorRule rule) {
375 registerComparatorRule(x, y, rule);
376 }
377};
378
379/**
380 * @brief @f$P(X < Y)@f$ for two independent RVs.
381 *
382 * Applies the registered closed form for the family pair when there is
383 * one; on a registry miss (or a rule declining with NaN) falls back to the
384 * 1-D composite-Simpson quadrature
385 * @f$P(X<Y) = \int (1 - F_Y(t))\, f_X(t)\, dt@f$ over X's integration
386 * range. NaN when neither decides (a density / CDF is undefined, e.g. a
387 * non-integer Erlang shape), so the caller falls back to Monte Carlo.
388 */
389double comparatorPairLess(const Distribution &X, const Distribution &Y);
390
391///@}
392
393/**
394 * @name ClosureRuleRegistry – pairwise family-closure folds on PLUS
395 *
396 * Closed-form folds of a sum of independent scalar terms into a single
397 * distribution (Normal + Normal, same-rate Exponential / Erlang chains).
398 * Like the comparator rules, pairwise behaviour stays out of the
399 * @c Distribution interface: family files self-register at static
400 * initialisation via @ref provsql::ClosureRuleRegistrar, keyed on the (ordered)
401 * pair of families that may meet in the sum, and a registry miss simply
402 * means the sum stays unfolded (Monte Carlo handles it).
403 *
404 * A rule receives the whole term list rather than reducing pairwise so
405 * its accumulation arithmetic (e.g. one variance sum with a single final
406 * square root) is not perturbed by intermediate re-serialisation.
407 */
408///@{
409
410/**
411 * @brief One wire of a PLUS under closure: @c a·Z @c + @c b for a base
412 * RV @c Z (@c dist non-null), or a pure additive constant @c b
413 * (@c dist null, @c a @c == @c 0).
414 *
415 * Structural concerns (base-RV identity, pairwise independence of the
416 * @c Z's) are the caller's responsibility; rules only see distributions
417 * and coefficients.
418 */
421 double a;
422 double b;
423};
424
425/**
426 * @brief A family sum-closure fold. Returns the closed-form
427 * distribution of the summed terms, or @c nullptr when the shape
428 * is outside the closure (mixed rates, scaled / shifted terms a
429 * family cannot absorb, degenerate variance...).
430 */
432 std::unique_ptr<Distribution> (*)(const std::vector<ClosureTerm> &terms);
433
434/**
435 * @brief Register the sum-closure rule for a family pair (name-token
436 * keyed, like the comparator rules).
437 */
438void registerClosureRule(const char *x, const char *y, ClosureRule rule);
439
440/** @brief Static-initialisation helper: one per registered family pair. */
442 ClosureRuleRegistrar(const char *x, const char *y, ClosureRule rule) {
443 registerClosureRule(x, y, rule);
444 }
445};
446
447/**
448 * @brief A family product-closure fold: the distribution of the product
449 * of the (independent) @p factors, or @c nullptr when the shape
450 * is outside the closure. Scalar factors are the caller's job
451 * (applied afterwards via @c affine).
452 */
453using ProductRule = std::unique_ptr<Distribution> (*)(
454 const std::vector<const Distribution *> &factors);
455
456/** @brief Register the product-closure rule for a family pair
457 * (name-token keyed, like the sum-closure rules). */
458void registerProductRule(const char *x, const char *y, ProductRule rule);
459
460/** @brief Static-initialisation helper: one per registered family pair. */
462 ProductRuleRegistrar(const char *x, const char *y, ProductRule rule) {
463 registerProductRule(x, y, rule);
464 }
465};
466
467/**
468 * @brief Fold a product of independent factors into a single
469 * distribution when a registered closure covers every family
470 * present (same first-vs-each dispatch as @c closePlusTerms);
471 * @c nullptr on any miss.
472 */
473std::unique_ptr<Distribution> closeProductFactors(
474 const std::vector<const Distribution *> &factors);
475
476/**
477 * @brief A closed-form image of a monotone transform of one family
478 * (e.g. exp of a normal is a lognormal), or @c nullptr when the
479 * family has none.
480 */
481using TransformRule = std::unique_ptr<Distribution> (*)(
482 const Distribution &x);
483
484/**
485 * @brief Register the image rule for @p transform ("ln" / "exp" / ...;
486 * the evaluator maps its arith opcodes to these names, keeping
487 * this registry opcode-free) applied to @p family.
488 */
489void registerTransformRule(const char *transform, const char *family,
490 TransformRule rule);
491
492/** @brief Static-initialisation helper: one per registered transform. */
494 TransformRuleRegistrar(const char *transform, const char *family,
495 TransformRule rule) {
496 registerTransformRule(transform, family, rule);
497 }
498};
499
500/**
501 * @brief The image distribution of @p transform applied to @p x, when
502 * a registered rule covers @p x's family; @c nullptr otherwise.
503 */
504std::unique_ptr<Distribution> closeTransform(const char *transform,
505 const Distribution &x);
506
507/**
508 * @brief Fold @c PLUS(terms) into a single distribution when a
509 * registered closure covers every family in the sum.
510 *
511 * Dispatch: the first RV term's family is looked up against itself and
512 * against every other RV term's family; all lookups must resolve to the
513 * same rule (this is how the Exponential / Erlang pairs share one
514 * Erlang-sum rule). Returns @c nullptr on any miss, on an
515 * inconsistent pair, when no RV term is present (the constant-fold
516 * path's job), or when the rule itself declines.
517 */
518std::unique_ptr<Distribution> closePlusTerms(
519 const std::vector<ClosureTerm> &terms);
520
521///@}
522
523/**
524 * @name ConjugateRuleRegistry – exact conjugate-prior posterior updates
525 *
526 * One-observation Bayesian updates for conjugate prior/likelihood pairs:
527 * the exact posterior of a latent, wired into one parameter slot of an
528 * observed leaf, stays in the prior's family with updated parameters.
529 * The conjugate-posterior recogniser (@c ConjugatePosterior.cpp) folds
530 * these rules over an @c and_agg evidence conjunction of @c gate_observe
531 * atoms, replacing likelihood-weighting importance sampling with the
532 * closed form where a rule chain covers every observation.
533 *
534 * Like the comparator / closure registries, the rules stay out of the
535 * @c Distribution interface: the likelihood family's implementation file
536 * self-registers its rule(s) at static initialisation, keyed by name
537 * tokens (no cross-file static-init order dependence), and a registry
538 * miss is a silent decline -- the caller falls back to importance
539 * sampling unchanged.
540 */
541///@{
542
543/**
544 * @brief The conjugate update for one observation of a given likelihood
545 * family against the running posterior (= prior) family.
546 *
547 * Both callbacks receive the observed leaf's parsed template @c lik (its
548 * non-wired slots carry the resolved literal parameters, e.g. the known
549 * @c σ of a @c normal(θ,σ) likelihood) and the observed datum.
550 */
552 /**
553 * @brief Update the running posterior parameters @p q1 / @p q2 (a
554 * distribution of the prior's family) by one observation.
555 * Returns @c false to decline (guard failure: an invalid
556 * literal slot, a datum outside the likelihood's support...),
557 * which declines the whole recognition.
558 */
559 bool (*update)(double &q1, double &q2,
560 const DistributionTemplate &lik, double datum);
561 /**
562 * @brief Log predictive density @f$\ln m(d \mid q_1, q_2)@f$ of the
563 * datum under the running posterior BEFORE the update; summed
564 * over the fold it is the exact log marginal likelihood behind
565 * @c provsql.evidence. NaN (or a null pointer) = unavailable,
566 * so @c evidence() keeps its Monte Carlo path.
567 */
568 double (*log_predictive)(double q1, double q2,
569 const DistributionTemplate &lik, double datum);
570};
571
572/**
573 * @brief Register the conjugate update for observations of
574 * @p likelihood_family whose parameter @p wired_param (0 = p1,
575 * 1 = p2) is the latent, against a prior of @p prior_family.
576 */
577void registerConjugateRule(const char *likelihood_family, int wired_param,
578 const char *prior_family,
579 const ConjugateRule &rule);
580
581/**
582 * @brief Look up the conjugate rule for (likelihood family, wired
583 * parameter position, prior family); @c nullptr on a miss.
584 */
585const ConjugateRule *lookupConjugateRule(const std::string &likelihood_family,
586 int wired_param,
587 const std::string &prior_family);
588
589/** @brief Static-initialisation helper: one per registered pair. */
591 ConjugateRuleRegistrar(const char *likelihood_family, int wired_param,
592 const char *prior_family, const ConjugateRule &rule) {
593 registerConjugateRule(likelihood_family, wired_param, prior_family, rule);
594 }
595};
596
597///@}
598
599} // namespace provsql
600
601#endif // PROVSQL_DISTRIBUTION_H
Continuous random-variable helpers (distribution parsing, moments).
Abstract per-family continuous distribution.
virtual double p2() const =0
virtual DistSupport support() const =0
Natural support interval of X.
virtual std::optional< double > iidOrderStatMean(std::size_t n, bool isMax) const
Closed-form mean of the max / min of n i.i.d.
virtual std::optional< double > asDirac() const
The point-mass value when the parameters make the distribution degenerate (a Dirac),...
virtual double rawMoment(unsigned k) const =0
E[X^k].
virtual std::optional< std::vector< double > > sampleTruncated(std::mt19937_64 &rng, double lo, double hi, unsigned n) const
Draw n rejection-free samples of X conditioned on lo < X < hi (±infinity for a semi-infinite side).
virtual double sample(std::mt19937_64 &rng) const =0
Draw one sample using the shared MC generator.
virtual double variance() const =0
Var(X).
virtual std::unique_ptr< Distribution > affine(double a, double b) const =0
Closed-form location-scale transform a·X + b within the family.
virtual std::optional< double > quantile(double p) const
Inverse CDF for .
virtual double mean() const =0
E[X].
virtual bool isDiscrete() const
Whether the family is discrete (integer-valued), so a point event X = c carries positive mass.
virtual bool integrationRange(double &lo, double &hi) const =0
Finite window [lo, hi] covering essentially all of X's mass, for numerical quadrature.
virtual double pdf(double x) const =0
f(x); NaN if the family declines
virtual const DistributionFamily & family() const =0
The family's interned registry descriptor.
virtual std::string serialise() const =0
The on-disk gate_rv extra text encoding (e.g.
std::unique_ptr< Distribution > negate() const
Closed-form -X (affine with coefficient -1).
virtual double p1() const =0
virtual double cdf(double x) const =0
F(x); NaN if the family declines.
virtual std::pair< double, double > plotRange(double trunc_lo, double trunc_hi) const =0
Plot x-window given optional truncation bounds (±infinity means "unbounded on that side"); used by th...
virtual bool meanIsAffine() const
Whether mean() is an affine function of the raw parameters (p1, p2) – i.e.
std::unique_ptr< Distribution > scale(double c) const
Closed-form c·X (affine with no offset).
virtual std::optional< double > truncatedRawMoment(double lo, double hi, unsigned k) const
Closed-form raw moment of the distribution truncated to [lo, hi] (±infinity for a semi-infinite side...
virtual ~Distribution()=default
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.
std::unique_ptr< Distribution >(*)(double p1, double p2) DistributionFactory
Construct a family instance from its (up to) two parameters.
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 = ...
ClosureRuleRegistrar(const char *x, const char *y, ClosureRule rule)
One wire of a PLUS under closure: a·Z + b for a base RV Z (dist non-null), or a pure additive constan...
const Distribution * dist
ComparatorRuleRegistrar(const char *x, const char *y, ComparatorRule rule)
ConjugateRuleRegistrar(const char *likelihood_family, int wired_param, const char *prior_family, const ConjugateRule &rule)
The conjugate update for one observation of a given likelihood family against the running posterior (...
double(* log_predictive)(double q1, double q2, const DistributionTemplate &lik, double datum)
Log predictive density of the datum under the running posterior BEFORE the update; summed over the f...
bool(* update)(double &q1, double &q2, const DistributionTemplate &lik, double datum)
Update the running posterior parameters q1 / q2 (a distribution of the prior's family) by one observa...
A closed support interval [lo, hi] (±infinity for unbounded).
DistributionFamilyRegistrar(const DistributionFamily &descriptor)
A registered family's descriptor: its complete identity.
DistributionFactory factory
unsigned nparams
1 or 2 (a 1-parameter family leaves p2 = 0)
A gate_rv distribution spec that may carry wired (token) parameters – the parse-time counterpart of D...
ProductRuleRegistrar(const char *x, const char *y, ProductRule rule)
TransformRuleRegistrar(const char *transform, const char *family, TransformRule rule)