use crate::{betainc, betaln, beta};
pub fn betaincinv(y: f64, z: f64, w: f64, lower: bool) -> f64 {
if y.is_nan() || z.is_nan() || w.is_nan() || z <= 0.0 || w <= 0.0 || !(0.0..=1.0).contains(&y) {
return f64::NAN;
}
if y == 0.0 {
return if lower { 0.0 } else { 1.0 };
}
if y == 1.0 {
return if lower { 1.0 } else { 0.0 };
}
let target = if lower { y } else { 1.0 - y };
let mut lo = 0.0;
let mut hi = 1.0;
let mut x = z / (z + w);
if target < 0.1 {
x = (target * z * beta(z, w)).powf(1.0 / z).min(x);
} else if target > 0.9 {
let t_upper = 1.0 - target;
let x_upper = (t_upper * w * beta(z, w)).powf(1.0 / w).min(1.0 - x);
x = 1.0 - x_upper;
}
x = x.clamp(1e-15, 1.0 - 1e-15);
let ln_beta = betaln(z, w);
for _ in 0..100 {
if x <= lo || x >= hi {
x = 0.5 * (lo + hi);
}
let val = betainc(x, z, w, true);
let f = val - target;
if f.abs() < 1e-14 * target.max(1e-12) {
break;
}
if f > 0.0 {
hi = x;
} else {
lo = x;
}
let log_f_prime = (z - 1.0) * x.ln() + (w - 1.0) * (1.0 - x).ln() - ln_beta;
let f_prime = log_f_prime.exp();
let deriv_ratio = (z - 1.0) / x - (w - 1.0) / (1.0 - x);
let mut delta = f / (f_prime - (f * deriv_ratio / 2.0));
if !delta.is_finite() || (x - delta) <= lo || (x - delta) >= hi {
delta = f / f_prime;
}
let x_new = x - delta;
if !x_new.is_finite() || x_new <= lo || x_new >= hi {
x = 0.5 * (lo + hi);
} else {
if (x_new - x).abs() < f64::EPSILON * x.max(1e-12) {
x = x_new;
break;
}
x = x_new;
}
}
x.clamp(0.0, 1.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_betaincinv_roundtrip() {
let z = 2.5;
let w = 1.5;
let x_vals = [0.1, 0.3, 0.5, 0.7, 0.9];
for &x in &x_vals {
let y = betainc(x, z, w, true);
let x_inv = betaincinv(y, z, w, true);
assert!((x - x_inv).abs() < 1e-12, "Roundtrip failed for x={}", x);
}
}
#[test]
fn test_betaincinv_identities() {
assert!((betaincinv(0.42, 1.0, 1.0, true) - 0.42).abs() < 1e-15);
}
}