// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // distributions.hpp // Additional probability distributions (5-3f) // // Continuous distributions: // ExponentialDistribution, GammaDistribution, BetaDistribution, // ChiSquaredDistribution, FDistribution, StudentTDistribution, // CauchyDistribution, RayleighDistribution, WeibullDistribution, // LognormalDistribution, LaplaceDistribution, ParetoDistribution, // LogisticDistribution, GumbelDistribution // // Discrete distributions: // BernoulliDistribution, PoissonDistribution, BinomialDistribution, // GeometricDistribution, NegativeBinomialDistribution #ifndef SANGI_DISTRIBUTIONS_HPP #define SANGI_DISTRIBUTIONS_HPP #include #include // FloatingPointType + Float sampler foundation #include #include #include #include #include #include #include namespace sangi { // ================================================================ // Common utilities // ================================================================ namespace detail { template inline constexpr T pi_v = std::numbers::pi_v; // Factorial (for small integers) inline double factorial(int n) { double r = 1.0; for (int i = 2; i <= n; ++i) r *= i; return r; } // Binomial coefficient inline double binomCoeff(int n, int k) { if (k < 0 || k > n) return 0.0; if (k == 0 || k == n) return 1.0; if (k > n - k) k = n - k; double r = 1.0; for (int i = 0; i < k; ++i) r = r * (n - i) / (i + 1); return r; } } // namespace detail // ================================================================ // ExponentialDistribution — exponential distribution Exp(λ) // ================================================================ template class ExponentialDistribution { public: explicit ExponentialDistribution(T lambda = T(1)) : lambda_(lambda) { if (lambda <= T(0)) throw std::invalid_argument("ExponentialDistribution: lambda must be > 0"); } [[nodiscard]] T pdf(T x) const { if (x < T(0)) return T(0); return lambda_ * std::exp(-lambda_ * x); } [[nodiscard]] T cdf(T x) const { if (x < T(0)) return T(0); return T(1) - std::exp(-lambda_ * x); } [[nodiscard]] T quantile(T p) const { if (p < T(0) || p > T(1)) throw std::invalid_argument("quantile: p must be in [0,1]"); if (p == T(1)) return std::numeric_limits::infinity(); return -std::log(T(1) - p) / lambda_; } [[nodiscard]] T mean() const { return T(1) / lambda_; } [[nodiscard]] T variance() const { return T(1) / (lambda_ * lambda_); } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::exponential_distribution d(lambda_); return d(gen); } else { return detail::sampleExponentialStd(gen) / lambda_; } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T lambda_; }; // ================================================================ // GammaDistribution — gamma distribution Γ(α, β) // ================================================================ template class GammaDistribution { public: // α = shape, β = rate (1/scale) GammaDistribution(T alpha = T(1), T beta = T(1)) : alpha_(alpha), beta_(beta) { if (alpha <= T(0) || beta <= T(0)) throw std::invalid_argument("GammaDistribution: alpha, beta must be > 0"); } [[nodiscard]] T pdf(T x) const { if (x <= T(0)) return T(0); return std::pow(beta_, alpha_) / detail::generic_tgamma(alpha_) * std::pow(x, alpha_ - T(1)) * std::exp(-beta_ * x); } [[nodiscard]] T cdf(T x) const { if (x <= T(0)) return T(0); return special::gammaP(alpha_, beta_ * x); } [[nodiscard]] T mean() const { return alpha_ / beta_; } [[nodiscard]] T variance() const { return alpha_ / (beta_ * beta_); } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::gamma_distribution d(alpha_, T(1) / beta_); return d(gen); } else { // scale-1 gamma / rate beta = gamma with scale 1/beta return detail::sampleGammaStd(gen, alpha_) / beta_; } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T alpha_, beta_; }; // ================================================================ // BetaDistribution — beta distribution Beta(α, β) // ================================================================ template class BetaDistribution { public: BetaDistribution(T alpha = T(1), T beta = T(1)) : alpha_(alpha), beta_(beta) { if (alpha <= T(0) || beta <= T(0)) throw std::invalid_argument("BetaDistribution: alpha, beta must be > 0"); } [[nodiscard]] T pdf(T x) const { if (x < T(0) || x > T(1)) return T(0); T B = detail::generic_tgamma(alpha_) * detail::generic_tgamma(beta_) / detail::generic_tgamma(alpha_ + beta_); return std::pow(x, alpha_ - T(1)) * std::pow(T(1) - x, beta_ - T(1)) / B; } [[nodiscard]] T cdf(T x) const { if (x <= T(0)) return T(0); if (x >= T(1)) return T(1); return special::betaRegularized(x, alpha_, beta_); } [[nodiscard]] T mean() const { return alpha_ / (alpha_ + beta_); } [[nodiscard]] T variance() const { T ab = alpha_ + beta_; return (alpha_ * beta_) / (ab * ab * (ab + T(1))); } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::gamma_distribution ga(alpha_, T(1)); std::gamma_distribution gb(beta_, T(1)); T x = ga(gen), y = gb(gen); return x / (x + y); } else { T x = detail::sampleGammaStd(gen, alpha_); T y = detail::sampleGammaStd(gen, beta_); return x / (x + y); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T alpha_, beta_; }; // ================================================================ // ChiSquaredDistribution — chi-squared distribution χ²(k) // ================================================================ template class ChiSquaredDistribution { public: explicit ChiSquaredDistribution(T k = T(1)) : k_(k) { if (k <= T(0)) throw std::invalid_argument("ChiSquaredDistribution: k must be > 0"); } [[nodiscard]] T pdf(T x) const { if (x <= T(0)) return T(0); T half_k = k_ / T(2); return std::pow(x, half_k - T(1)) * std::exp(-x / T(2)) / (std::pow(T(2), half_k) * detail::generic_tgamma(half_k)); } [[nodiscard]] T cdf(T x) const { if (x <= T(0)) return T(0); return special::gammaP(k_ / T(2), x / T(2)); } [[nodiscard]] T mean() const { return k_; } [[nodiscard]] T variance() const { return T(2) * k_; } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::chi_squared_distribution d(k_); return d(gen); } else { // χ²(k) = Gamma(shape k/2, scale 2) return detail::sampleGammaStd(gen, k_ / T(2)) * T(2); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T k_; }; // ================================================================ // FDistribution — F distribution F(d1, d2) // ================================================================ template class FDistribution { public: FDistribution(T d1 = T(1), T d2 = T(1)) : d1_(d1), d2_(d2) { if (d1 <= T(0) || d2 <= T(0)) throw std::invalid_argument("FDistribution: d1, d2 must be > 0"); } [[nodiscard]] T pdf(T x) const { if (x <= T(0)) return T(0); T a = d1_ / T(2), b = d2_ / T(2); T num = std::pow(d1_ * x, a) * std::pow(d2_, b); T den = std::pow(d1_ * x + d2_, a + b); T B = detail::generic_tgamma(a) * detail::generic_tgamma(b) / detail::generic_tgamma(a + b); return num / (den * x * B); } [[nodiscard]] T cdf(T x) const { if (x <= T(0)) return T(0); T t = d1_ * x / (d1_ * x + d2_); return special::betaRegularized(t, d1_ / T(2), d2_ / T(2)); } [[nodiscard]] T mean() const { if (d2_ <= T(2)) return std::numeric_limits::infinity(); return d2_ / (d2_ - T(2)); } [[nodiscard]] T variance() const { if (d2_ <= T(4)) return std::numeric_limits::infinity(); return T(2) * d2_ * d2_ * (d1_ + d2_ - T(2)) / (d1_ * (d2_ - T(2)) * (d2_ - T(2)) * (d2_ - T(4))); } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::fisher_f_distribution d(d1_, d2_); return d(gen); } else { // F = (χ²(d1)/d1) / (χ²(d2)/d2), χ²(d) = 2·Gamma(d/2) T g1 = detail::sampleGammaStd(gen, d1_ / T(2)); T g2 = detail::sampleGammaStd(gen, d2_ / T(2)); return (g1 * d2_) / (g2 * d1_); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T d1_, d2_; }; // ================================================================ // StudentTDistribution — Student's t distribution t(ν) // ================================================================ template class StudentTDistribution { public: explicit StudentTDistribution(T nu = T(1)) : nu_(nu) { if (nu <= T(0)) throw std::invalid_argument("StudentTDistribution: nu must be > 0"); } [[nodiscard]] T pdf(T x) const { T a = (nu_ + T(1)) / T(2); T B = detail::generic_tgamma(a) / (std::sqrt(nu_ * detail::generic_pi()) * detail::generic_tgamma(nu_ / T(2))); return B * std::pow(T(1) + x * x / nu_, -a); } [[nodiscard]] T cdf(T x) const { T t = nu_ / (nu_ + x * x); T Ix = special::betaRegularized(t, nu_ / T(2), T(0.5)); return (x >= T(0)) ? T(1) - Ix / T(2) : Ix / T(2); } [[nodiscard]] T mean() const { if (nu_ <= T(1)) return std::numeric_limits::quiet_NaN(); return T(0); } [[nodiscard]] T variance() const { if (nu_ <= T(1)) return std::numeric_limits::quiet_NaN(); if (nu_ <= T(2)) return std::numeric_limits::infinity(); return nu_ / (nu_ - T(2)); } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::student_t_distribution d(nu_); return d(gen); } else { // t(ν) = Z / sqrt(χ²(ν)/ν), Z~N(0,1), χ²(ν)=2·Gamma(ν/2) T z = detail::sampleNormalStd(gen); T v = detail::sampleGammaStd(gen, nu_ / T(2)) * T(2); return z / detail::generic_sqrt(v / nu_); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T nu_; }; // ================================================================ // CauchyDistribution — Cauchy distribution Cauchy(x0, γ) // ================================================================ template class CauchyDistribution { public: CauchyDistribution(T x0 = T(0), T gamma = T(1)) : x0_(x0), gamma_(gamma) { if (gamma <= T(0)) throw std::invalid_argument("CauchyDistribution: gamma must be > 0"); } [[nodiscard]] T pdf(T x) const { T z = (x - x0_) / gamma_; return T(1) / (detail::generic_pi() * gamma_ * (T(1) + z * z)); } [[nodiscard]] T cdf(T x) const { return T(0.5) + std::atan((x - x0_) / gamma_) / detail::generic_pi(); } [[nodiscard]] T quantile(T p) const { if (p < T(0) || p > T(1)) throw std::invalid_argument("quantile: p must be in [0,1]"); return x0_ + gamma_ * std::tan(detail::generic_pi() * (p - T(0.5))); } // Cauchy: mean/variance are undefined [[nodiscard]] T median() const { return x0_; } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::cauchy_distribution d(x0_, gamma_); return d(gen); } else { return quantile(detail::uniform01(gen)); // inverse CDF } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T x0_, gamma_; }; // ================================================================ // RayleighDistribution — Rayleigh distribution Rayleigh(σ) // ================================================================ template class RayleighDistribution { public: explicit RayleighDistribution(T sigma = T(1)) : sigma_(sigma) { if (sigma <= T(0)) throw std::invalid_argument("RayleighDistribution: sigma must be > 0"); } [[nodiscard]] T pdf(T x) const { if (x < T(0)) return T(0); return (x / (sigma_ * sigma_)) * std::exp(-x * x / (T(2) * sigma_ * sigma_)); } [[nodiscard]] T cdf(T x) const { if (x < T(0)) return T(0); return T(1) - std::exp(-x * x / (T(2) * sigma_ * sigma_)); } [[nodiscard]] T mean() const { return sigma_ * std::sqrt(detail::generic_pi() / T(2)); } [[nodiscard]] T variance() const { return (T(4) - detail::generic_pi()) / T(2) * sigma_ * sigma_; } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::uniform_real_distribution u(T(0), T(1)); return sigma_ * std::sqrt(-T(2) * std::log(u(gen))); } else { return sigma_ * detail::generic_sqrt(-T(2) * detail::generic_log(detail::uniform01_pos(gen))); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T sigma_; }; // ================================================================ // WeibullDistribution — Weibull distribution Weibull(k, λ) // ================================================================ template class WeibullDistribution { public: WeibullDistribution(T k = T(1), T lambda = T(1)) : k_(k), lambda_(lambda) { if (k <= T(0) || lambda <= T(0)) throw std::invalid_argument("WeibullDistribution: k, lambda must be > 0"); } [[nodiscard]] T pdf(T x) const { if (x < T(0)) return T(0); return (k_ / lambda_) * std::pow(x / lambda_, k_ - T(1)) * std::exp(-std::pow(x / lambda_, k_)); } [[nodiscard]] T cdf(T x) const { if (x < T(0)) return T(0); return T(1) - std::exp(-std::pow(x / lambda_, k_)); } [[nodiscard]] T mean() const { return lambda_ * detail::generic_tgamma(T(1) + T(1) / k_); } [[nodiscard]] T variance() const { T g1 = detail::generic_tgamma(T(1) + T(1) / k_); T g2 = detail::generic_tgamma(T(1) + T(2) / k_); return lambda_ * lambda_ * (g2 - g1 * g1); } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::weibull_distribution d(k_, lambda_); return d(gen); } else { // inverse CDF: λ·(-ln(1-u))^{1/k} return lambda_ * detail::generic_pow( -detail::generic_log(detail::uniform01_pos(gen)), T(1) / k_); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T k_, lambda_; }; // ================================================================ // LognormalDistribution — log-normal distribution LogNormal(μ, σ) // ================================================================ template class LognormalDistribution { public: LognormalDistribution(T mu = T(0), T sigma = T(1)) : mu_(mu), sigma_(sigma) { if (sigma <= T(0)) throw std::invalid_argument("LognormalDistribution: sigma must be > 0"); } [[nodiscard]] T pdf(T x) const { if (x <= T(0)) return T(0); T z = (std::log(x) - mu_) / sigma_; return std::exp(-T(0.5) * z * z) / (x * sigma_ * std::sqrt(T(2) * detail::generic_pi())); } [[nodiscard]] T cdf(T x) const { if (x <= T(0)) return T(0); return T(0.5) * detail::generic_erfc(-(std::log(x) - mu_) / (sigma_ * std::sqrt(T(2)))); } [[nodiscard]] T mean() const { return std::exp(mu_ + sigma_ * sigma_ / T(2)); } [[nodiscard]] T variance() const { T s2 = sigma_ * sigma_; return (std::exp(s2) - T(1)) * std::exp(T(2) * mu_ + s2); } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::lognormal_distribution d(mu_, sigma_); return d(gen); } else { // exp(μ + σ·N(0,1)) return detail::generic_exp(mu_ + sigma_ * detail::sampleNormalStd(gen)); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T mu_, sigma_; }; // ================================================================ // LaplaceDistribution — Laplace distribution Laplace(μ, b) // ================================================================ template class LaplaceDistribution { public: LaplaceDistribution(T mu = T(0), T b = T(1)) : mu_(mu), b_(b) { if (b <= T(0)) throw std::invalid_argument("LaplaceDistribution: b must be > 0"); } [[nodiscard]] T pdf(T x) const { return std::exp(-std::abs(x - mu_) / b_) / (T(2) * b_); } [[nodiscard]] T cdf(T x) const { if (x < mu_) return T(0.5) * std::exp((x - mu_) / b_); else return T(1) - T(0.5) * std::exp(-(x - mu_) / b_); } [[nodiscard]] T quantile(T p) const { if (p < T(0) || p > T(1)) throw std::invalid_argument("quantile: p must be in [0,1]"); if (p < T(0.5)) return mu_ + b_ * std::log(T(2) * p); else return mu_ - b_ * std::log(T(2) * (T(1) - p)); } [[nodiscard]] T mean() const { return mu_; } [[nodiscard]] T variance() const { return T(2) * b_ * b_; } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::uniform_real_distribution u(T(0), T(1)); T p = u(gen) - T(0.5); return mu_ - b_ * std::copysign(std::log(T(1) - T(2) * std::abs(p)), p); } else { return quantile(detail::uniform01(gen)); // inverse CDF } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T mu_, b_; }; // ================================================================ // ParetoDistribution — Pareto distribution Pareto(α, xm) // ================================================================ template class ParetoDistribution { public: ParetoDistribution(T alpha = T(1), T xm = T(1)) : alpha_(alpha), xm_(xm) { if (alpha <= T(0) || xm <= T(0)) throw std::invalid_argument("ParetoDistribution: alpha, xm must be > 0"); } [[nodiscard]] T pdf(T x) const { if (x < xm_) return T(0); return alpha_ * std::pow(xm_, alpha_) / std::pow(x, alpha_ + T(1)); } [[nodiscard]] T cdf(T x) const { if (x < xm_) return T(0); return T(1) - std::pow(xm_ / x, alpha_); } [[nodiscard]] T mean() const { if (alpha_ <= T(1)) return std::numeric_limits::infinity(); return alpha_ * xm_ / (alpha_ - T(1)); } [[nodiscard]] T variance() const { if (alpha_ <= T(2)) return std::numeric_limits::infinity(); return xm_ * xm_ * alpha_ / ((alpha_ - T(1)) * (alpha_ - T(1)) * (alpha_ - T(2))); } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::uniform_real_distribution u(T(0), T(1)); return xm_ / std::pow(u(gen), T(1) / alpha_); } else { return xm_ / detail::generic_pow(detail::uniform01_pos(gen), T(1) / alpha_); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T alpha_, xm_; }; // ================================================================ // LogisticDistribution — logistic distribution Logistic(μ, s) // ================================================================ template class LogisticDistribution { public: LogisticDistribution(T mu = T(0), T s = T(1)) : mu_(mu), s_(s) { if (s <= T(0)) throw std::invalid_argument("LogisticDistribution: s must be > 0"); } [[nodiscard]] T pdf(T x) const { T z = std::exp(-(x - mu_) / s_); return z / (s_ * (T(1) + z) * (T(1) + z)); } [[nodiscard]] T cdf(T x) const { return T(1) / (T(1) + std::exp(-(x - mu_) / s_)); } [[nodiscard]] T quantile(T p) const { if (p <= T(0) || p >= T(1)) throw std::invalid_argument("quantile: p must be in (0,1)"); return mu_ + s_ * std::log(p / (T(1) - p)); } [[nodiscard]] T mean() const { return mu_; } [[nodiscard]] T variance() const { return s_ * s_ * detail::generic_pi() * detail::generic_pi() / T(3); } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::uniform_real_distribution u(T(0), T(1)); T p = u(gen); return mu_ + s_ * std::log(p / (T(1) - p)); } else { T p = detail::uniform01_pos(gen); // (0,1] return mu_ + s_ * detail::generic_log(p / (T(1) - p)); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T mu_, s_; }; // ================================================================ // GumbelDistribution — Gumbel distribution (Type-1 extreme-value distribution) // ================================================================ template class GumbelDistribution { public: GumbelDistribution(T mu = T(0), T beta = T(1)) : mu_(mu), beta_(beta) { if (beta <= T(0)) throw std::invalid_argument("GumbelDistribution: beta must be > 0"); } [[nodiscard]] T pdf(T x) const { T z = (x - mu_) / beta_; return std::exp(-(z + std::exp(-z))) / beta_; } [[nodiscard]] T cdf(T x) const { T z = (x - mu_) / beta_; return std::exp(-std::exp(-z)); } [[nodiscard]] T quantile(T p) const { if (p <= T(0) || p >= T(1)) throw std::invalid_argument("quantile: p must be in (0,1)"); return mu_ - beta_ * std::log(-std::log(p)); } // Euler-Mascheroni constant γ ≈ 0.5772 [[nodiscard]] T mean() const { return mu_ + beta_ * T(0.5772156649015329); } [[nodiscard]] T variance() const { return detail::generic_pi() * detail::generic_pi() * beta_ * beta_ / T(6); } [[nodiscard]] T sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::extreme_value_distribution d(mu_, beta_); return d(gen); } else { // inverse CDF: μ - β·ln(-ln(u)), u∈(0,1] T u = detail::uniform01_pos(gen); return mu_ - beta_ * detail::generic_log(-detail::generic_log(u)); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T mu_, beta_; }; // ================================================================ // Discrete distributions // ================================================================ // ================================================================ // BernoulliDistribution — Bernoulli distribution Bernoulli(p) // ================================================================ template class BernoulliDistribution { public: explicit BernoulliDistribution(T p = T(0.5)) : p_(p) { if (p < T(0) || p > T(1)) throw std::invalid_argument("BernoulliDistribution: p must be in [0,1]"); } [[nodiscard]] T pmf(int k) const { if (k == 0) return T(1) - p_; if (k == 1) return p_; return T(0); } [[nodiscard]] T cdf(int k) const { if (k < 0) return T(0); if (k < 1) return T(1) - p_; return T(1); } [[nodiscard]] T mean() const { return p_; } [[nodiscard]] T variance() const { return p_ * (T(1) - p_); } [[nodiscard]] int sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::bernoulli_distribution d(static_cast(p_)); return d(gen) ? 1 : 0; } else { return (detail::uniform01(gen) < p_) ? 1 : 0; } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T p_; }; // ================================================================ // PoissonDistribution — Poisson distribution Poisson(λ) // ================================================================ template class PoissonDistribution { public: explicit PoissonDistribution(T lambda = T(1)) : lambda_(lambda) { if (lambda <= T(0)) throw std::invalid_argument("PoissonDistribution: lambda must be > 0"); } [[nodiscard]] T pmf(int k) const { if (k < 0) return T(0); return std::exp(-lambda_ + k * std::log(lambda_) - detail::generic_lgamma(T(k + 1))); } [[nodiscard]] T cdf(int k) const { if (k < 0) return T(0); // P(X ≤ k) = Q(k+1, λ) = 1 - P(k+1, λ) (upper regularized incomplete gamma) return T(1) - special::gammaP(T(k + 1), lambda_); } [[nodiscard]] T mean() const { return lambda_; } [[nodiscard]] T variance() const { return lambda_; } [[nodiscard]] int sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::poisson_distribution d(static_cast(lambda_)); return d(gen); } else { return detail::samplePoissonKnuth(gen, lambda_); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T lambda_; }; // ================================================================ // BinomialDistribution — binomial distribution Binomial(n, p) // ================================================================ template class BinomialDistribution { public: BinomialDistribution(int n = 1, T p = T(0.5)) : n_(n), p_(p) { if (n < 0) throw std::invalid_argument("BinomialDistribution: n must be >= 0"); if (p < T(0) || p > T(1)) throw std::invalid_argument("BinomialDistribution: p must be in [0,1]"); } [[nodiscard]] T pmf(int k) const { if (k < 0 || k > n_) return T(0); return T(detail::binomCoeff(n_, k)) * std::pow(p_, T(k)) * std::pow(T(1) - p_, T(n_ - k)); } [[nodiscard]] T cdf(int k) const { if (k < 0) return T(0); if (k >= n_) return T(1); // I_{1-p}(n-k, k+1) return special::betaRegularized(T(1) - p_, T(n_ - k), T(k + 1)); } [[nodiscard]] T mean() const { return T(n_) * p_; } [[nodiscard]] T variance() const { return T(n_) * p_ * (T(1) - p_); } [[nodiscard]] int sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::binomial_distribution d(n_, static_cast(p_)); return d(gen); } else { // sum of n Bernoulli trials (O(n)) int count = 0; for (int i = 0; i < n_; ++i) if (detail::uniform01(gen) < p_) ++count; return count; } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: int n_; T p_; }; // ================================================================ // GeometricDistribution — geometric distribution Geometric(p) // ================================================================ // P(X=k) = (1-p)^k * p, k = 0, 1, 2, ... (number of failures) template class GeometricDistribution { public: explicit GeometricDistribution(T p = T(0.5)) : p_(p) { if (p <= T(0) || p > T(1)) throw std::invalid_argument("GeometricDistribution: p must be in (0,1]"); } [[nodiscard]] T pmf(int k) const { if (k < 0) return T(0); return std::pow(T(1) - p_, T(k)) * p_; } [[nodiscard]] T cdf(int k) const { if (k < 0) return T(0); return T(1) - std::pow(T(1) - p_, T(k + 1)); } [[nodiscard]] T mean() const { return (T(1) - p_) / p_; } [[nodiscard]] T variance() const { return (T(1) - p_) / (p_ * p_); } [[nodiscard]] int sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::geometric_distribution d(static_cast(p_)); return d(gen); } else { if (p_ >= T(1)) return 0; // inverse CDF: k = floor( ln(u) / ln(1-p) ), u∈(0,1] T k = detail::generic_floor( detail::generic_log(detail::uniform01_pos(gen)) / detail::generic_log(T(1) - p_)); return static_cast(detail::to_int64(k)); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T p_; }; // ================================================================ // NegativeBinomialDistribution — negative binomial distribution NB(r, p) // ================================================================ // P(X=k) = C(k+r-1, k) * p^r * (1-p)^k, k = 0, 1, 2, ... template class NegativeBinomialDistribution { public: NegativeBinomialDistribution(T r = T(1), T p = T(0.5)) : r_(r), p_(p) { if (r <= T(0)) throw std::invalid_argument("NegativeBinomialDistribution: r must be > 0"); if (p <= T(0) || p > T(1)) throw std::invalid_argument("NegativeBinomialDistribution: p must be in (0,1]"); } [[nodiscard]] T pmf(int k) const { if (k < 0) return T(0); return std::exp(detail::generic_lgamma(T(k) + r_) - detail::generic_lgamma(T(k + 1)) - detail::generic_lgamma(r_) + r_ * std::log(p_) + T(k) * std::log(T(1) - p_)); } [[nodiscard]] T cdf(int k) const { if (k < 0) return T(0); return special::betaRegularized(p_, r_, T(k + 1)); } [[nodiscard]] T mean() const { return r_ * (T(1) - p_) / p_; } [[nodiscard]] T variance() const { return r_ * (T(1) - p_) / (p_ * p_); } [[nodiscard]] int sample(std::mt19937& gen) const { if constexpr (std::is_floating_point_v) { std::negative_binomial_distribution d( static_cast(std::round(static_cast(r_))), static_cast(p_)); return d(gen); } else { // gamma-Poisson mixture: λ ~ Gamma(r, (1-p)/p), then Poisson(λ). // (correct even for real r, and more exact than STL's integer-r rounding) T lambda = detail::sampleGammaStd(gen, r_) * (T(1) - p_) / p_; return detail::samplePoissonKnuth(gen, lambda); } } [[nodiscard]] std::vector sample(std::mt19937& gen, size_t n) const { std::vector r(n); for (auto& v : r) v = sample(gen); return r; } private: T r_, p_; }; } // namespace sangi #endif // SANGI_DISTRIBUTIONS_HPP