ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
poisson.cpp
Go to the documentation of this file.
1/**
2 * @file poisson.cpp
3 * @brief Poisson(λ) family implementation (discrete).
4 *
5 * A discrete, integer-valued family in the otherwise-continuous
6 * @c Distribution hierarchy. It is only ever instantiated for a
7 * @b latent (token-parameterised) leaf -- @c provsql.poisson(random_variable)
8 * -- because the literal-parameter constructor enumerates an exact
9 * @c categorical instead. A parametric leaf is declined by
10 * @c parse_distribution_spec, so no continuous-analytic path ever integrates
11 * this family's @c pdf as a density: it is reached only by the Monte Carlo
12 * @c sample(), by @c observe (whose weight is @c pdf = the pmf at the datum),
13 * and by the affine-mean fast path (@c mean = λ, @c meanIsAffine() = true).
14 *
15 * Self-contained: the class is file-local and reaches the evaluators only
16 * through the DistributionRegistry registrar at the bottom.
17 */
18#include "DistributionCommon.h"
19
20#include <cmath>
21#include <memory>
22#include <optional>
23#include <random>
24#include <string>
25#include <utility>
26#include <vector>
27
28namespace provsql {
29
30namespace {
31
32/** @brief Poisson(λ=p1). p2 unused. */
33class PoissonDistribution final : public BaseDistribution {
34public:
36 const DistributionFamily &family() const override;
37 double mean() const override { return p1_; }
38 bool meanIsAffine() const override { return true; } // mean = λ
39 bool isDiscrete() const override { return true; }
40 double variance() const override { return p1_; }
41 double rawMoment(unsigned k) const override {
42 // Touchard recurrence: m_0 = 1, m_{j} = λ Σ_{i=0}^{j-1} C(j-1,i) m_i.
43 if (k == 0) return 1.0;
44 std::vector<double> m(k + 1, 0.0);
45 m[0] = 1.0;
46 for (unsigned j = 1; j <= k; ++j) {
47 double s = 0.0;
48 for (unsigned i = 0; i < j; ++i) s += binomial_coeff(j - 1, i) * m[i];
49 m[j] = p1_ * s;
50 }
51 return m[k];
52 }
53 double pdf(double c) const override { // pmf: mass at a non-negative integer
54 if (!(p1_ > 0.0)) return kNaN;
55 const double r = std::nearbyint(c);
56 if (std::fabs(c - r) > 1e-9 || r < 0.0) return 0.0;
57 return std::exp(-p1_ + r * std::log(p1_) - std::lgamma(r + 1.0));
58 }
59 double cdf(double c) const override {
60 if (!(p1_ > 0.0)) return kNaN;
61 if (c < 0.0) return 0.0;
62 const long kmax = static_cast<long>(std::floor(c));
63 double s = 0.0;
64 for (long k = 0; k <= kmax; ++k)
65 s += std::exp(-p1_ + k * std::log(p1_) - std::lgamma(k + 1.0));
66 return s > 1.0 ? 1.0 : s;
67 }
68 DistSupport support() const override { return {0.0, kInf}; }
69 bool integrationRange(double &lo, double &hi) const override {
70 // Also the parameter-domain validity gate: λ must be > 0.
71 if (!(p1_ > 0.0)) return false;
72 lo = 0.0;
73 hi = p1_ + 12.0 * std::sqrt(p1_) + 30.0;
74 return true;
75 }
76 std::pair<double, double> plotRange(double trunc_lo,
77 double trunc_hi) const override {
78 double lo = trunc_lo, hi = trunc_hi;
79 if (!std::isfinite(lo)) lo = 0.0;
80 if (!std::isfinite(hi)) hi = p1_ + 4.0 * std::sqrt(p1_) + 5.0;
81 return {lo, hi};
82 }
83 double sample(std::mt19937_64 &rng) const override {
84 std::poisson_distribution<long> d(p1_);
85 return static_cast<double>(d(rng));
86 }
87 std::optional<double> quantile(double p) const override {
88 if (!(p1_ > 0.0)) return std::nullopt;
89 if (p <= 0.0) return 0.0;
90 double s = 0.0;
91 for (long k = 0; k < 1000000; ++k) {
92 s += std::exp(-p1_ + k * std::log(p1_) - std::lgamma(k + 1.0));
93 if (s >= p) return static_cast<double>(k);
94 }
95 return std::nullopt;
96 }
97 std::unique_ptr<Distribution> affine(double, double) const override {
98 return nullptr; // discrete: not closed under a·X + b
99 }
100 std::string serialise() const override {
101 return "poisson:" + double_to_text(p1_);
102 }
103};
104
105const DistributionFamily poisson_family = {
106 "poisson", 1, "Pois", {"λ", nullptr},
107 +[](double p1, double p2) -> std::unique_ptr<Distribution> {
108 return std::make_unique<PoissonDistribution>(p1, p2);
109 }};
110
111const DistributionFamily &PoissonDistribution::family() const
112{
113 return poisson_family;
114}
115
116/* Gamma-Poisson conjugacy: a Poisson(θ) count observation of a
117 * Gamma(k, λ) prior on the rate updates to Gamma(k+d, λ+1). The
118 * predictive is the negative-binomial pmf
119 * m(d) = Γ(k+d)/(Γ(k) d!) · λ^k / (λ+1)^{k+d}. The datum must be a
120 * non-negative integer under the same 1e-9 rounding tolerance the
121 * family pmf applies, so the closed form fires exactly where the
122 * importance weight is positive. */
123bool poissonRateConjugateUpdate(double &k, double &lambda,
124 const DistributionTemplate &, double d)
125{
126 if (!(k > 0.0) || !(lambda > 0.0)) return false;
127 const double r = std::nearbyint(d);
128 if (std::fabs(d - r) > 1e-9 || r < 0.0) return false;
129 k += r;
130 lambda += 1.0;
131 return true;
132}
133
134double poissonRateLogPredictive(double k, double lambda,
135 const DistributionTemplate &, double d)
136{
137 if (!(k > 0.0) || !(lambda > 0.0)) return kNaN;
138 const double r = std::nearbyint(d);
139 if (std::fabs(d - r) > 1e-9 || r < 0.0) return kNaN;
140 return std::lgamma(k + r) - std::lgamma(k) - std::lgamma(r + 1.0)
141 + k * std::log(lambda) - (k + r) * std::log(lambda + 1.0);
142}
143
144[[maybe_unused]] const ConjugateRuleRegistrar poisson_rate_conjugate(
145 "poisson", 0, "gamma",
146 {&poissonRateConjugateUpdate, &poissonRateLogPredictive});
147
148[[maybe_unused]] const DistributionFamilyRegistrar poisson_family_registrar(
149 poisson_family);
150
151} // namespace
152
153} // 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 binomial_coeff(unsigned n, unsigned k)
C(n, k) as a double (exact for the small moment orders used here).
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.