ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
inverse_gamma.cpp
Go to the documentation of this file.
1/**
2 * @file inverse_gamma.cpp
3 * @brief InvGamma(α, β) family implementation: shape @c α > 0, scale
4 * @c β > 0.
5 *
6 * The distribution of @c 1/Y for @c Y ~ Gamma(shape α, rate β): the
7 * conjugate prior for a Gaussian variance and a common heavy-tailed
8 * model for positive quantities. The CDF is the regularised @b upper
9 * incomplete gamma @f$Q(α, β/x)@f$, so it reuses the same @c gammaP
10 * kernel as Gamma; raw moments @c E[X^k] @c = @c β^k Γ(α-k)/Γ(α) are
11 * @b infinite for @c α <= @c k and reported honestly as @c +Infinity
12 * (the mean for @c α <= @c 1, the variance for @c α <= @c 2) rather than
13 * estimated. Positive scalings rescale @c β (scale family); the
14 * quantile / truncated-moment paths decline (no elementary inverse CDF)
15 * and fall through to the numeric bisection / Monte Carlo, exactly as
16 * Gamma does.
17 *
18 * Self-contained family implementation: the class is file-local and
19 * reaches the evaluators only through the registrars at the bottom
20 * (DistributionRegistry factory). No evaluator or parser code changes.
21 */
22#include "DistributionCommon.h"
23
24#include <algorithm>
25#include <cmath>
26#include <memory>
27#include <optional>
28#include <random>
29#include <string>
30#include <utility>
31#include <vector>
32
33namespace provsql {
34
35namespace {
36
37/** @brief InvGamma(α=p1 shape > 0, β=p2 scale > 0). */
38class InverseGammaDistribution final : public BaseDistribution {
39public:
41 const DistributionFamily &family() const override;
42 double mean() const override {
43 if (!(p1_ > 1.0)) return kInf; /* diverges for α <= 1 */
44 return p2_ / (p1_ - 1.0);
45 }
46 double variance() const override {
47 if (!(p1_ > 2.0)) return kInf; /* diverges for α <= 2 */
48 const double am1 = p1_ - 1.0;
49 return p2_ * p2_ / (am1 * am1 * (p1_ - 2.0));
50 }
51 double rawMoment(unsigned k) const override {
52 if (k == 0) return 1.0;
53 const double alpha = p1_;
54 const double kd = static_cast<double>(k);
55 if (!(alpha > kd)) return kInf; /* E[X^k] diverges for α <= k */
56 /* β^k Γ(α-k)/Γ(α) = β^k / [(α-1)(α-2)...(α-k)], every factor > 0. */
57 double denom = 1.0;
58 for (unsigned i = 1; i <= k; ++i) denom *= (alpha - static_cast<double>(i));
59 return std::pow(p2_, kd) / denom;
60 }
61 double pdf(double c) const override {
62 const double alpha = p1_, beta = p2_;
63 if (!(alpha > 0.0) || !(beta > 0.0)) return kNaN;
64 if (c <= 0.0) return 0.0;
65 /* β^α/Γ(α) · x^{-α-1} · e^{-β/x}, computed in log space. */
66 return std::exp(alpha * std::log(beta) - std::lgamma(alpha)
67 - (alpha + 1.0) * std::log(c) - beta / c);
68 }
69 double cdf(double c) const override {
70 const double alpha = p1_, beta = p2_;
71 if (!(alpha > 0.0) || !(beta > 0.0)) return kNaN;
72 if (c <= 0.0) return 0.0;
73 /* F(x) = P(1/X <= 1/x)ᶜ = Q(α, β/x) = 1 - P(α, β/x). */
74 const double p = gammaP(alpha, beta / c);
75 if (std::isnan(p)) return kNaN;
76 return 1.0 - p;
77 }
78 DistSupport support() const override { return {0.0, kInf}; }
79 bool integrationRange(double &lo, double &hi) const override {
80 const double alpha = p1_, beta = p2_;
81 if (!(alpha > 0.0 && beta > 0.0)) return false;
82 lo = 0.0;
83 if (alpha > 2.0) {
84 /* Finite variance: the Gamma-style mean + 12σ window. */
85 const double m = beta / (alpha - 1.0);
86 const double sd =
87 std::sqrt(beta * beta / ((alpha - 1.0) * (alpha - 1.0) * (alpha - 2.0)));
88 hi = m + 12.0 * sd;
89 } else {
90 /* Heavy right tail (Pareto-like exponent α): the tail
91 * approximation q(1 - 1e-9) ≈ β·(1e9)^{1/α}, generously wide. */
92 hi = beta * std::pow(1e9, 1.0 / alpha);
93 }
94 return true;
95 }
96 std::pair<double, double> plotRange(double trunc_lo, double trunc_hi) const override {
97 double lo = trunc_lo, hi = trunc_hi;
98 if (!std::isfinite(lo)) lo = 0.0;
99 if (!std::isfinite(hi)) {
100 if (p1_ > 2.0) {
101 const double m = p2_ / (p1_ - 1.0);
102 const double sd = std::sqrt(
103 p2_ * p2_ / ((p1_ - 1.0) * (p1_ - 1.0) * (p1_ - 2.0)));
104 hi = m + 4.0 * sd;
105 } else {
106 hi = p2_ * std::pow(1e3, 1.0 / p1_); /* q(0.999) */
107 }
108 }
109 return {lo, hi};
110 }
111 double sample(std::mt19937_64 &rng) const override {
112 /* 1/Y for Y ~ Gamma(shape α, scale 1/β) = Gamma(shape α, rate β). */
113 std::gamma_distribution<double> d(p1_, 1.0 / p2_);
114 const double y = d(rng);
115 return 1.0 / y;
116 }
117 std::unique_ptr<Distribution> affine(double a, double b) const override {
118 /* c · InvGamma(α, β) = InvGamma(α, c·β) for c > 0; a negative
119 * scaling flips the support and an offset shifts it, neither of
120 * which is inverse-gamma. */
121 if (!(a > 0.0) || b != 0.0) return nullptr;
122 if (!(p1_ > 0.0)) return nullptr;
123 return std::make_unique<InverseGammaDistribution>(p1_, a * p2_);
124 }
125 std::string serialise() const override {
126 return "inverse_gamma:" + double_to_text(p1_) + "," + double_to_text(p2_);
127 }
128};
129
130const DistributionFamily inverse_gamma_family = {
131 "inverse_gamma", 2, "IΓ", {"α", "β"},
132 +[](double p1, double p2) -> std::unique_ptr<Distribution> {
133 return std::make_unique<InverseGammaDistribution>(p1, p2);
134 }};
135
136const DistributionFamily &InverseGammaDistribution::family() const
137{
138 return inverse_gamma_family;
139}
140
141[[maybe_unused]] const DistributionFamilyRegistrar inverse_gamma_family_registrar(
142 inverse_gamma_family);
143
144} // namespace
145
146} // namespace provsql
Internal helpers shared by the per-family Distribution implementations under src/distributions/.
Base holding the two parameters; subclasses add closed forms.
BaseDistribution(double p1, double p2)
constexpr double kInf
double gammaP(double a, double x)
Regularised lower incomplete gamma for a > 0, x >= 0.
std::string double_to_text(double v)
Format a double back into the canonical text form used by gate_value extras and gate_rv distribution ...
constexpr double kNaN
A registered family's descriptor: its complete identity.