ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
binomial.cpp
Go to the documentation of this file.
1/**
2 * @file binomial.cpp
3 * @brief Binomial(n, p) family implementation (discrete).
4 *
5 * A discrete, integer-valued family in the otherwise-continuous
6 * @c Distribution hierarchy, instantiated only for a @b latent
7 * (token-parameterised) leaf -- @c provsql.binomial(integer, random_variable)
8 * -- the literal constructor enumerates an exact @c categorical instead.
9 * As for @c poisson, a parametric leaf is declined by
10 * @c parse_distribution_spec, so no continuous-analytic path integrates its
11 * @c pdf as a density: it is reached only by the Monte Carlo @c sample(), by
12 * @c observe (weight @c pdf = pmf at the datum), and by the moment path.
13 *
14 * @c p1 = n (number of trials), @c p2 = p (success probability).
15 */
16#include "DistributionCommon.h"
17
18#include <cmath>
19#include <memory>
20#include <optional>
21#include <random>
22#include <string>
23#include <utility>
24#include <vector>
25
26namespace provsql {
27
28namespace {
29
30/** @brief Binomial(n=p1, p=p2). */
31class BinomialDistribution final : public BaseDistribution {
32public:
34 const DistributionFamily &family() const override;
35
36 long n() const { return static_cast<long>(std::llround(p1_)); }
37
38 double mean() const override { return p1_ * p2_; }
39 bool isDiscrete() const override { return true; }
40 double variance() const override { return p1_ * p2_ * (1.0 - p2_); }
41 double rawMoment(unsigned k) const override {
42 if (k == 0) return 1.0;
43 // Exact finite sum Σ_{j=0}^{n} j^k · pmf(j).
44 double total = 0.0;
45 const long nn = n();
46 for (long j = 0; j <= nn; ++j)
47 total += std::pow(static_cast<double>(j), static_cast<double>(k)) * pmf(j);
48 return total;
49 }
50 double pdf(double c) const override { // pmf at a valid integer count
51 if (!valid()) return kNaN;
52 const double r = std::nearbyint(c);
53 if (std::fabs(c - r) > 1e-9) return 0.0;
54 const long k = static_cast<long>(r);
55 if (k < 0 || k > n()) return 0.0;
56 return pmf(k);
57 }
58 double cdf(double c) const override {
59 if (!valid()) return kNaN;
60 if (c < 0.0) return 0.0;
61 const long nn = n();
62 const long kmax = std::min(nn, static_cast<long>(std::floor(c)));
63 double s = 0.0;
64 for (long k = 0; k <= kmax; ++k) s += pmf(k);
65 return s > 1.0 ? 1.0 : s;
66 }
67 DistSupport support() const override {
68 return {0.0, static_cast<double>(n())};
69 }
70 bool integrationRange(double &lo, double &hi) const override {
71 // Also the parameter-domain validity gate: n >= 0 and 0 <= p <= 1.
72 if (!valid()) return false;
73 lo = 0.0;
74 hi = static_cast<double>(n());
75 return true;
76 }
77 std::pair<double, double> plotRange(double trunc_lo,
78 double trunc_hi) const override {
79 double lo = trunc_lo, hi = trunc_hi;
80 if (!std::isfinite(lo)) lo = 0.0;
81 if (!std::isfinite(hi)) hi = static_cast<double>(n());
82 return {lo, hi};
83 }
84 double sample(std::mt19937_64 &rng) const override {
85 std::binomial_distribution<long> d(n(), p2_);
86 return static_cast<double>(d(rng));
87 }
88 std::optional<double> quantile(double p) const override {
89 if (!valid()) return std::nullopt;
90 if (p <= 0.0) return 0.0;
91 const long nn = n();
92 double s = 0.0;
93 for (long k = 0; k <= nn; ++k) {
94 s += pmf(k);
95 if (s >= p) return static_cast<double>(k);
96 }
97 return static_cast<double>(nn);
98 }
99 std::unique_ptr<Distribution> affine(double, double) const override {
100 return nullptr; // discrete: not closed under a·X + b
101 }
102 std::string serialise() const override {
103 return "binomial:" + double_to_text(p1_) + "," + double_to_text(p2_);
104 }
105
106private:
107 bool valid() const {
108 return std::isfinite(p1_) && p1_ >= 0.0 &&
109 p2_ >= 0.0 && p2_ <= 1.0;
110 }
111 double pmf(long k) const {
112 const long nn = n();
113 if (k < 0 || k > nn) return 0.0;
114 // Edge cases where std::log(p) / log(1-p) would be -inf.
115 if (p2_ <= 0.0) return (k == 0) ? 1.0 : 0.0;
116 if (p2_ >= 1.0) return (k == nn) ? 1.0 : 0.0;
117 const double logC = std::lgamma(nn + 1.0) - std::lgamma(k + 1.0)
118 - std::lgamma(nn - k + 1.0);
119 return std::exp(logC + k * std::log(p2_)
120 + (nn - k) * std::log1p(-p2_));
121 }
122};
123
124const DistributionFamily binomial_family = {
125 "binomial", 2, "Bin", {"n", "p"},
126 +[](double p1, double p2) -> std::unique_ptr<Distribution> {
127 return std::make_unique<BinomialDistribution>(p1, p2);
128 }};
129
130const DistributionFamily &BinomialDistribution::family() const
131{
132 return binomial_family;
133}
134
135/* Beta-Binomial conjugacy: a Binomial(n, θ) observation with the latent
136 * in the success-probability slot (n a known literal) updates a
137 * Beta(α, β) prior to Beta(α+d, β+n−d). The predictive is the
138 * beta-binomial pmf m(d) = C(n,d) B(α+d, β+n−d) / B(α, β). The count
139 * must be an integer in [0, n] under the same 1e-9 rounding tolerance
140 * as the family pmf. */
141bool binomialPConjugateUpdate(double &alpha, double &beta,
142 const DistributionTemplate &lik, double d)
143{
144 const double n = lik.p1.literal;
145 if (!(alpha > 0.0) || !(beta > 0.0)) return false;
146 if (n < 1.0 || n != std::floor(n)) return false;
147 const double r = std::nearbyint(d);
148 if (std::fabs(d - r) > 1e-9 || r < 0.0 || r > n) return false;
149 alpha += r;
150 beta += n - r;
151 return true;
152}
153
154double binomialPLogPredictive(double alpha, double beta,
155 const DistributionTemplate &lik, double d)
156{
157 const double n = lik.p1.literal;
158 if (!(alpha > 0.0) || !(beta > 0.0)) return kNaN;
159 if (n < 1.0 || n != std::floor(n)) return kNaN;
160 const double r = std::nearbyint(d);
161 if (std::fabs(d - r) > 1e-9 || r < 0.0 || r > n) return kNaN;
162 return std::lgamma(n + 1.0) - std::lgamma(r + 1.0)
163 - std::lgamma(n - r + 1.0)
164 + lbeta(alpha + r, beta + n - r) - lbeta(alpha, beta);
165}
166
167[[maybe_unused]] const ConjugateRuleRegistrar binomial_p_conjugate(
168 "binomial", 1, "beta",
169 {&binomialPConjugateUpdate, &binomialPLogPredictive});
170
171[[maybe_unused]] const DistributionFamilyRegistrar binomial_family_registrar(
172 binomial_family);
173
174} // namespace
175
176} // 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)
double lbeta(double a, double b)
ln B(a, b) = lnΓ(a) + lnΓ(b) − lnΓ(a+b), for a, b > 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.