ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
DistributionCommon.h
Go to the documentation of this file.
1/**
2 * @file DistributionCommon.h
3 * @brief Internal helpers shared by the per-family @c Distribution
4 * implementations under @c src/distributions/.
5 *
6 * Not part of the public @c Distribution interface -- include only from
7 * the family implementation files.
8 */
9#ifndef PROVSQL_DISTRIBUTION_COMMON_H
10#define PROVSQL_DISTRIBUTION_COMMON_H
11
12#include <cmath>
13#include <limits>
14
15#include "Distribution.h"
16
17namespace provsql {
18
19inline constexpr double kNaN = std::numeric_limits<double>::quiet_NaN();
20inline constexpr double kInf = std::numeric_limits<double>::infinity();
21
22/// C(n, k) as a double (exact for the small moment orders used here).
23inline double binomial_coeff(unsigned n, unsigned k)
24{
25 if (k > n) return 0.0;
26 if (k > n - k) k = n - k;
27 double r = 1.0;
28 for (unsigned i = 1; i <= k; ++i) {
29 r *= static_cast<double>(n - i + 1);
30 r /= static_cast<double>(i);
31 }
32 return r;
33}
34
35/// ln B(a, b) = lnΓ(a) + lnΓ(b) − lnΓ(a+b), for a, b > 0.
36inline double lbeta(double a, double b)
37{
38 return std::lgamma(a) + std::lgamma(b) - std::lgamma(a + b);
39}
40
41/// Standard normal pdf φ(z) = exp(-z²/2)/√(2π).
42inline double phi(double z)
43{
44 static const double INV_SQRT_2PI = 1.0 / std::sqrt(2.0 * M_PI);
45 return INV_SQRT_2PI * std::exp(-0.5 * z * z);
46}
47
48/// Standard normal CDF Φ(z) = ½(1 + erf(z/√2)). Mirrors the
49/// NormalDistribution::cdf convention so the truncation formulas
50/// here use the same numerics.
51inline double Phi(double z)
52{
53 static const double SQRT2 = std::sqrt(2.0);
54 return 0.5 * (1.0 + std::erf(z / SQRT2));
55}
56
57/**
58 * @brief Inverse standard-normal CDF, Beasley-Springer-Moro (1995).
59 *
60 * Returns @c z such that @f$\Phi(z) = p@f$. Accurate to about
61 * @c 1e-7 over @c p ∈ [0.02425, 1 - 0.02425], with a tail
62 * rational fallback for the rest of @c (0, 1). Callers must clamp
63 * @c p strictly inside @c (0, 1) since the function diverges at the
64 * endpoints; the truncated-normal sampler clamps to
65 * @c [1e-15, 1 - 1e-15] before each call.
66 *
67 * The Beasley-Springer-Moro routine is in widespread library use
68 * (NumPy/SciPy 'norminv', etc.) and its accuracy is several orders of
69 * magnitude tighter than the sampling noise the tests can detect at
70 * 10k draws, so it's a comfortable margin.
71 */
72inline double inv_phi(double p)
73{
74 static const double a[] = {
75 -3.969683028665376e+01, 2.209460984245205e+02,
76 -2.759285104469687e+02, 1.383577518672690e+02,
77 -3.066479806614716e+01, 2.506628277459239e+00
78 };
79 static const double b[] = {
80 -5.447609879822406e+01, 1.615858368580409e+02,
81 -1.556989798598866e+02, 6.680131188771972e+01,
82 -1.328068155288572e+01
83 };
84 static const double c_arr[] = {
85 -7.784894002430293e-03, -3.223964580411365e-01,
86 -2.400758277161838e+00, -2.549732539343734e+00,
87 4.374664141464968e+00, 2.938163982698783e+00
88 };
89 static const double d[] = {
90 7.784695709041462e-03, 3.224671290700398e-01,
91 2.445134137142996e+00, 3.754408661907416e+00
92 };
93 static const double p_low = 0.02425;
94 static const double p_high = 1.0 - p_low;
95
96 if (p < p_low) {
97 const double q = std::sqrt(-2.0 * std::log(p));
98 return (((((c_arr[0]*q + c_arr[1])*q + c_arr[2])*q
99 + c_arr[3])*q + c_arr[4])*q + c_arr[5])
100 / ((((d[0]*q + d[1])*q + d[2])*q + d[3])*q + 1.0);
101 }
102 if (p <= p_high) {
103 const double q = p - 0.5;
104 const double r = q * q;
105 return (((((a[0]*r + a[1])*r + a[2])*r + a[3])*r + a[4])*r + a[5]) * q
106 / (((((b[0]*r + b[1])*r + b[2])*r + b[3])*r + b[4])*r + 1.0);
107 }
108 const double q = std::sqrt(-2.0 * std::log(1.0 - p));
109 return -(((((c_arr[0]*q + c_arr[1])*q + c_arr[2])*q
110 + c_arr[3])*q + c_arr[4])*q + c_arr[5])
111 / ((((d[0]*q + d[1])*q + d[2])*q + d[3])*q + 1.0);
112}
113
114/**
115 * @brief Regularised lower incomplete gamma @f$P(a, x) = \gamma(a, x) /
116 * \Gamma(a)@f$ for @c a > 0, @c x >= 0.
117 *
118 * Series expansion of @f$\gamma(a, x)@f$ for @c x < @c a + 1, modified
119 * Lentz continued fraction for the complement @f$Q(a, x)@f$ otherwise --
120 * each converges fast in its region (Numerical Recipes §6.2). NaN on
121 * invalid @p a or non-convergence, so callers fall through to Monte
122 * Carlo.
123 */
124inline double gammaP(double a, double x)
125{
126 if (!(a > 0.0) || std::isnan(x)) return kNaN;
127 if (x <= 0.0) return 0.0;
128 const double lg = std::lgamma(a);
129 if (x < a + 1.0) {
130 /* γ(a,x) = e^{-x} x^a Σ_{n≥0} x^n Γ(a)/Γ(a+1+n). */
131 double ap = a, sum = 1.0 / a, del = sum;
132 for (int n = 0; n < 500; ++n) {
133 ap += 1.0;
134 del *= x / ap;
135 sum += del;
136 if (std::fabs(del) < std::fabs(sum) * 1e-15)
137 return sum * std::exp(-x + a * std::log(x) - lg);
138 }
139 return kNaN;
140 }
141 /* Q(a,x) = e^{-x} x^a / Γ(a) · 1/(x+1-a- 1(1-a)/(x+3-a- ...)). */
142 const double FPMIN = 1e-300;
143 double b = x + 1.0 - a, c = 1.0 / FPMIN, d = 1.0 / b, h = d;
144 for (int i = 1; i <= 500; ++i) {
145 const double an = -static_cast<double>(i) * (static_cast<double>(i) - a);
146 b += 2.0;
147 d = an * d + b;
148 if (std::fabs(d) < FPMIN) d = FPMIN;
149 c = b + an / c;
150 if (std::fabs(c) < FPMIN) c = FPMIN;
151 d = 1.0 / d;
152 const double del = d * c;
153 h *= del;
154 if (std::fabs(del - 1.0) < 1e-15) {
155 const double q = std::exp(-x + a * std::log(x) - lg) * h;
156 return 1.0 - q;
157 }
158 }
159 return kNaN;
160}
161
162/** @brief Base holding the two parameters; subclasses add closed forms. */
164public:
165 BaseDistribution(double p1, double p2) : p1_(p1), p2_(p2) {}
166 double p1() const override { return p1_; }
167 double p2() const override { return p2_; }
168protected:
169 double p1_, p2_;
170};
171
172} // namespace provsql
173
174#endif // PROVSQL_DISTRIBUTION_COMMON_H
Per-family polymorphic view over a continuous gate_rv distribution (§F.1 class hierarchy).
double p1() const override
double p2() const override
BaseDistribution(double p1, double p2)
Abstract per-family continuous distribution.
double phi(double z)
Standard normal pdf φ(z) = exp(-z²/2)/√(2π).
double lbeta(double a, double b)
ln B(a, b) = lnΓ(a) + lnΓ(b) − lnΓ(a+b), for a, b > 0.
double Phi(double z)
Standard normal CDF Φ(z) = ½(1 + erf(z/√2)).
constexpr double kInf
double gammaP(double a, double x)
Regularised lower incomplete gamma for a > 0, x >= 0.
double binomial_coeff(unsigned n, unsigned k)
C(n, k) as a double (exact for the small moment orders used here).
double inv_phi(double p)
Inverse standard-normal CDF, Beasley-Springer-Moro (1995).
constexpr double kNaN