26double double_factorial_minus_one(
unsigned j)
28 if (j == 0)
return 1.0;
30 for (
unsigned i = 1; i < j; i += 2) r *= static_cast<double>(i);
49double truncated_normal_raw_moment(
double mu,
double sigma,
double a,
double b,
52 const double alpha = std::isfinite(a) ? (a - mu) / sigma : -
kInf;
53 const double beta = std::isfinite(b) ? (b - mu) / sigma : +
kInf;
54 const double Phi_alpha = std::isfinite(alpha) ?
Phi(alpha) : 0.0;
55 const double Phi_beta = std::isfinite(beta) ?
Phi(beta) : 1.0;
56 const double Z = Phi_beta - Phi_alpha;
57 if (Z < 1e-12)
return kNaN;
59 const double phi_alpha = std::isfinite(alpha) ?
phi(alpha) : 0.0;
60 const double phi_beta = std::isfinite(beta) ?
phi(beta) : 0.0;
63 std::vector<double> M(k + 1, 0.0);
65 if (k >= 1) M[1] = (phi_alpha - phi_beta) / Z;
66 for (
unsigned m = 2; m <= k; ++m) {
69 double end_term = 0.0;
70 if (std::isfinite(alpha))
71 end_term += std::pow(alpha,
static_cast<double>(m - 1)) * phi_alpha;
72 if (std::isfinite(beta))
73 end_term -= std::pow(beta,
static_cast<double>(m - 1)) * phi_beta;
74 M[m] = (m - 1) * M[m - 2] + end_term / Z;
79 for (
unsigned i = 0; i <= k; ++i) {
81 * std::pow(mu,
static_cast<double>(k - i))
82 * std::pow(sigma,
static_cast<double>(i))
92 const DistributionFamily &family()
const override;
93 double mean()
const override {
return p1_; }
94 bool meanIsAffine()
const override {
return true; }
95 double variance()
const override {
return p2_ * p2_; }
96 double rawMoment(
unsigned k)
const override {
97 if (k == 0)
return 1.0;
98 if (k == 1)
return mean();
100 const double mu = p1_, sigma = p2_;
102 for (
unsigned j = 0; j <= k; j += 2) {
104 * std::pow(mu,
static_cast<double>(k - j))
105 * std::pow(sigma,
static_cast<double>(j))
106 * double_factorial_minus_one(j);
110 double pdf(
double c)
const override {
111 if (!(p2_ > 0.0))
return kNaN;
112 const double SQRT_2PI = std::sqrt(2.0 * M_PI);
113 const double z = (c - p1_) / p2_;
114 return std::exp(-0.5 * z * z) / (p2_ * SQRT_2PI);
116 double cdf(
double c)
const override {
117 const double SQRT2 = std::sqrt(2.0);
118 const double z = (c - p1_) / p2_;
119 return 0.5 * (1.0 + std::erf(z / SQRT2));
121 DistSupport support()
const override {
return {-
kInf,
kInf}; }
122 bool integrationRange(
double &lo,
double &hi)
const override {
123 if (!(p2_ > 0.0))
return false;
124 lo = p1_ - 12.0 * p2_;
125 hi = p1_ + 12.0 * p2_;
128 std::pair<double, double> plotRange(
double trunc_lo,
double trunc_hi)
const override {
129 double lo = trunc_lo, hi = trunc_hi;
130 if (!std::isfinite(lo)) lo = p1_ - 4.0 * p2_;
131 if (!std::isfinite(hi)) hi = p1_ + 4.0 * p2_;
134 double sample(std::mt19937_64 &rng)
const override {
135 std::normal_distribution<double> d(p1_, p2_);
138 std::optional<double> quantile(
double p)
const override {
139 if (!(p2_ > 0.0))
return std::nullopt;
145 for (
int i = 0; i < 2; ++i) {
146 const double d =
phi(z);
147 if (!(d > 0.0))
break;
148 z -= (
Phi(z) - p) / d;
150 return p1_ + p2_ * z;
152 std::optional<double> truncatedRawMoment(
double lo,
double hi,
153 unsigned k)
const override {
154 const double r = truncated_normal_raw_moment(p1_, p2_, lo, hi, k);
155 if (std::isnan(r))
return std::nullopt;
158 std::optional<std::vector<double>> sampleTruncated(
159 std::mt19937_64 &rng,
double lo,
double hi,
unsigned n)
const override {
160 const double mu = p1_, sigma = p2_;
161 if (!(sigma > 0.0))
return std::nullopt;
162 const double sqrt2 = std::sqrt(2.0);
163 const double alpha = (lo - mu) / sigma;
164 const double beta = (hi - mu) / sigma;
165 const double Phi_a = std::isfinite(alpha)
166 ? 0.5 * (1.0 + std::erf(alpha / sqrt2))
167 : (alpha < 0 ? 0.0 : 1.0);
168 const double Phi_b = std::isfinite(beta)
169 ? 0.5 * (1.0 + std::erf(beta / sqrt2))
170 : (beta < 0 ? 0.0 : 1.0);
171 if (!(Phi_a < Phi_b))
return std::nullopt;
176 static constexpr double EPS = 1e-15;
177 std::uniform_real_distribution<double> U01(0.0, 1.0);
178 std::vector<double> out;
180 for (
unsigned i = 0; i < n; ++i) {
181 double u = Phi_a + U01(rng) * (Phi_b - Phi_a);
182 if (u < EPS) u = EPS;
183 if (u > 1.0 - EPS) u = 1.0 - EPS;
185 out.push_back(mu + sigma * z);
189 std::unique_ptr<Distribution> affine(
double a,
double b)
const override {
190 if (a == 0.0)
return nullptr;
192 if (b != 0.0) mu += b;
193 return std::make_unique<NormalDistribution>(mu, std::fabs(a) * p2_);
195 std::string serialise()
const override {
198 std::optional<double> asDirac()
const override {
199 if (p2_ == 0.0)
return p1_;
205 "normal", 2,
"N", {
"μ",
"σ"},
206 +[](
double p1,
double p2) -> std::unique_ptr<Distribution> {
207 return std::make_unique<NormalDistribution>(p1, p2);
212 return normal_family;
221std::unique_ptr<Distribution>
222normalSumRule(
const std::vector<ClosureTerm> &terms)
224 double total_mean = 0.0;
225 double total_var = 0.0;
226 for (
const auto &t : terms) {
228 if (!t.dist)
continue;
229 const double mu = t.dist->p1();
230 const double sigma = t.dist->p2();
231 total_mean += t.a * mu;
232 total_var += t.a * t.a * sigma * sigma;
234 if (total_var <= 0.0)
return nullptr;
235 return std::make_unique<NormalDistribution>(total_mean,
236 std::sqrt(total_var));
239[[maybe_unused]]
const ClosureRuleRegistrar normal_sum_rule(
240 "normal",
"normal", &normalSumRule);
244double normalPairLess(
const Distribution &X,
const Distribution &Y)
246 return NormalDistribution(
248 std::sqrt(X.p2() * X.p2() + Y.p2() * Y.p2())).cdf(0.0);
251[[maybe_unused]]
const ComparatorRuleRegistrar normal_less_rule(
252 "normal",
"normal", &normalPairLess);
259bool normalMeanConjugateUpdate(
double &mu,
double &sigma,
260 const DistributionTemplate &lik,
double d)
262 const double s = lik.p2.literal;
263 if (!(s > 0.0) || !(sigma > 0.0))
return false;
264 const double tau0 = 1.0 / (sigma * sigma);
265 const double taul = 1.0 / (s * s);
266 mu = (tau0 * mu + taul * d) / (tau0 + taul);
267 sigma = std::sqrt(1.0 / (tau0 + taul));
271double normalMeanLogPredictive(
double mu,
double sigma,
272 const DistributionTemplate &lik,
double d)
274 const double s = lik.p2.literal;
275 if (!(s > 0.0) || !(sigma > 0.0))
return kNaN;
276 const double v = sigma * sigma + s * s;
277 return -0.5 * ((d - mu) * (d - mu) / v + std::log(2.0 * M_PI * v));
280[[maybe_unused]]
const ConjugateRuleRegistrar normal_mean_conjugate(
281 "normal", 0,
"normal",
282 {&normalMeanConjugateUpdate, &normalMeanLogPredictive});
284[[maybe_unused]]
const DistributionFamilyRegistrar normal_family_registrar(
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 phi(double z)
Standard normal pdf φ(z) = exp(-z²/2)/√(2π).
double Phi(double z)
Standard normal CDF Φ(z) = ½(1 + erf(z/√2)).
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).
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 ...
A registered family's descriptor: its complete identity.