use crate::special::gamma::lgamma;
const MAX_ITER: usize = 500;
const EPS: f64 = 1e-15;
const FPMIN: f64 = 1e-300;
#[must_use]
pub fn beta(a: f64, b: f64) -> f64 {
assert!(a > 0.0 && b > 0.0, "beta requires a > 0 and b > 0");
(lgamma(a) + lgamma(b) - lgamma(a + b)).exp()
}
fn beta_cf(a: f64, b: f64, x: f64) -> f64 {
let qab = a + b;
let qap = a + 1.0;
let qam = a - 1.0;
let mut c = 1.0;
let mut d = 1.0 - qab * x / qap;
if d.abs() < FPMIN {
d = FPMIN;
}
d = 1.0 / d;
let mut h = d;
for m in 1..=MAX_ITER {
let m = m as f64;
let m2 = 2.0 * m;
let aa = m * (b - m) * x / ((qam + m2) * (a + m2));
d = 1.0 + aa * d;
if d.abs() < FPMIN {
d = FPMIN;
}
c = 1.0 + aa / c;
if c.abs() < FPMIN {
c = FPMIN;
}
d = 1.0 / d;
h *= d * c;
let aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
d = 1.0 + aa * d;
if d.abs() < FPMIN {
d = FPMIN;
}
c = 1.0 + aa / c;
if c.abs() < FPMIN {
c = FPMIN;
}
d = 1.0 / d;
let del = d * c;
h *= del;
if (del - 1.0).abs() < EPS {
break;
}
}
h
}
#[must_use]
pub fn beta_inc(a: f64, b: f64, x: f64) -> f64 {
assert!(a > 0.0 && b > 0.0, "beta_inc requires a > 0 and b > 0");
assert!((0.0..=1.0).contains(&x), "beta_inc requires x in [0, 1]");
if x == 0.0 {
return 0.0;
}
if x == 1.0 {
return 1.0;
}
let ln_front =
lgamma(a + b) - lgamma(a) - lgamma(b) + a * x.ln() + b * (1.0 - x).ln();
let front = ln_front.exp();
if x < (a + 1.0) / (a + b + 2.0) {
front * beta_cf(a, b, x) / a
} else {
1.0 - front * beta_cf(b, a, 1.0 - x) / b
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::constants::PI;
fn approx(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn test_beta_known_values() {
assert!(approx(beta(1.0, 1.0), 1.0, 1e-12));
assert!(approx(beta(2.0, 3.0), 1.0 / 12.0, 1e-13));
assert!(approx(beta(0.5, 0.5), PI, 1e-10));
}
#[test]
fn test_beta_inc_uniform_case() {
for &x in &[0.0, 0.25, 0.5, 0.75, 1.0] {
assert!(approx(beta_inc(1.0, 1.0, x), x, 1e-13));
}
}
#[test]
fn test_beta_inc_known_value() {
assert!(approx(beta_inc(2.0, 2.0, 0.5), 0.5, 1e-13));
assert!(approx(beta_inc(2.0, 2.0, 0.25), 0.15625, 1e-13));
}
#[test]
fn test_beta_inc_symmetry() {
for &(a, b) in &[(2.0, 5.0), (0.5, 0.5), (3.5, 1.2)] {
for &x in &[0.1, 0.4, 0.9] {
let lhs = beta_inc(a, b, x);
let rhs = 1.0 - beta_inc(b, a, 1.0 - x);
assert!(approx(lhs, rhs, 1e-12), "a={a} b={b} x={x}");
}
}
}
#[test]
#[should_panic(expected = "x in [0, 1]")]
fn test_beta_inc_rejects_out_of_range() {
let _ = beta_inc(1.0, 1.0, 1.5);
}
}