use crate::{gammainc, gammaincinv};
pub fn gaminv(p: f64, a: f64, b: f64) -> f64 {
let ok_ab = (0.0 < a && a < f64::INFINITY) && 0.0 < b;
let ok_p = 0.0 < p && p < 1.0;
if !ok_ab || !ok_p {
let mut x = f64::NAN;
if ok_ab && p == 0.0 {
x = 0.0;
}
if ok_ab && p == 1.0 {
x = f64::INFINITY;
}
if a == 0.0 && ok_p {
x = 0.0;
}
return x;
}
let q = gammaincinv(p, a, true);
if cfg!(debug_assertions) {
let tolerance = f64::sqrt(f64::EPSILON);
let badcdf = (f64::abs(gammainc(q, a, true, false) - p) / p) > tolerance;
assert!(!badcdf, "cdf is too far off");
}
q * b
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gamcdf;
#[test]
fn test_gaminv_exponential_identity() {
let a = 1.0;
let b = 2.5;
let p = 0.75;
let x = gaminv(p, a, b);
let expected = -b * (1.0 - p).ln();
assert!((x - expected).abs() < 1e-14);
}
#[test]
fn test_gaminv_roundtrip() {
let a = 3.2;
let b = 0.5;
let probabilities = [0.01, 0.1, 0.5, 0.9, 0.99];
for &p in &probabilities {
let x = gaminv(p, a, b);
let p_back = gamcdf(x, a, b, false);
assert!((p - p_back).abs() < 1e-12);
}
}
#[test]
fn test_gaminv_scaling() {
let p = 0.4;
let a = 2.0;
let x1 = gaminv(p, a, 1.0);
let x2 = gaminv(p, a, 5.0);
assert!((x2 - 5.0 * x1).abs() < 1e-14);
}
#[test]
fn test_gaminv_boundaries() {
let a = 2.0;
let b = 1.0;
assert_eq!(gaminv(0.0, a, b), 0.0);
assert_eq!(gaminv(1.0, a, b), f64::INFINITY);
assert_eq!(gaminv(0.5, 0.0, b), 0.0);
}
#[test]
fn test_gaminv_invalid_params() {
assert!(gaminv(0.5, -1.0, 1.0).is_nan());
assert!(gaminv(0.5, 1.0, 0.0).is_nan());
assert!(gaminv(1.1, 1.0, 1.0).is_nan());
assert!(gaminv(-0.1, 1.0, 1.0).is_nan());
}
}