#include "datascience/beta_dist.h" #include namespace fn::ds { // Lanczos approx with g=7, n=9 — precision ~1e-15 sobre x > 0.5. Para // x <= 0.5 usa la reflexion lgamma(x) = log(pi/sin(pi*x)) - lgamma(1-x). double lgamma_lanczos(double x) { static const double p[] = { 676.5203681218851, -1259.1392167224028, 771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7 }; if (x < 0.5) { return std::log(3.14159265358979323846 / std::sin(3.14159265358979323846 * x)) - lgamma_lanczos(1.0 - x); } x -= 1.0; double a = 0.99999999999980993; double t = x + 7.5; for (int i = 0; i < 8; ++i) { a += p[i] / (x + static_cast(i + 1)); } return 0.5 * std::log(6.28318530717958647692) + (x + 0.5) * std::log(t) - t + std::log(a); } double log_beta(double a, double b) { return lgamma_lanczos(a) + lgamma_lanczos(b) - lgamma_lanczos(a + b); } double beta_pdf(double x, double a, double b) { if (a <= 0.0 || b <= 0.0) return 0.0; if (x <= 0.0 || x >= 1.0) return 0.0; double lp = (a - 1.0) * std::log(x) + (b - 1.0) * std::log(1.0 - x) - log_beta(a, b); return std::exp(lp); } // Continued fraction expansion de I_x(a, b). Numerical Recipes 6.4. static double betacf(double x, double a, double b) { constexpr int MAXIT = 200; constexpr double EPS = 3.0e-15; constexpr double FPMIN = 1.0e-300; double qab = a + b; double qap = a + 1.0; double qam = a - 1.0; double c = 1.0; double d = 1.0 - qab * x / qap; if (std::fabs(d) < FPMIN) d = FPMIN; d = 1.0 / d; double h = d; for (int m = 1; m <= MAXIT; ++m) { int m2 = 2 * m; double aa = static_cast(m) * (b - m) * x / ((qam + m2) * (a + m2)); d = 1.0 + aa * d; if (std::fabs(d) < FPMIN) d = FPMIN; c = 1.0 + aa / c; if (std::fabs(c) < FPMIN) c = FPMIN; d = 1.0 / d; h *= d * c; aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2)); d = 1.0 + aa * d; if (std::fabs(d) < FPMIN) d = FPMIN; c = 1.0 + aa / c; if (std::fabs(c) < FPMIN) c = FPMIN; d = 1.0 / d; double del = d * c; h *= del; if (std::fabs(del - 1.0) < EPS) break; } return h; } double beta_cdf(double x, double a, double b) { if (a <= 0.0 || b <= 0.0) return 0.0; if (x <= 0.0) return 0.0; if (x >= 1.0) return 1.0; double bt = std::exp(lgamma_lanczos(a + b) - lgamma_lanczos(a) - lgamma_lanczos(b) + a * std::log(x) + b * std::log(1.0 - x)); if (x < (a + 1.0) / (a + b + 2.0)) { return bt * betacf(x, a, b) / a; } return 1.0 - bt * betacf(1.0 - x, b, a) / b; } double beta_quantile(double p, double a, double b) { if (a <= 0.0 || b <= 0.0) return 0.0; if (p <= 0.0) return 0.0; if (p >= 1.0) return 1.0; // Bisection robusto. Newton tendria mejor convergencia pero requiere // beta_pdf(x) que cerca de 0 o 1 puede oscilar; bisection en [0,1] es // siempre estable. ~50 iter para 1e-6. double lo = 0.0, hi = 1.0; for (int i = 0; i < 60; ++i) { double mid = 0.5 * (lo + hi); double cdf = beta_cdf(mid, a, b); if (cdf < p) lo = mid; else hi = mid; if (hi - lo < 1e-7) break; } return 0.5 * (lo + hi); } double beta_mean(double a, double b) { if (a + b <= 0.0) return 0.5; return a / (a + b); } double beta_variance(double a, double b) { double s = a + b; if (s <= 0.0 || (s + 1.0) <= 0.0) return 0.0; return (a * b) / (s * s * (s + 1.0)); } double beta_std(double a, double b) { return std::sqrt(beta_variance(a, b)); } } // namespace fn::ds