use crate::{nctcdf, nctpdf, norminv, tinv};
#[allow(non_snake_case)]
pub fn nctinv(p: f64, nu: f64, delta: f64) -> f64 {
if delta == 0.0 {
let x = tinv(p, nu);
return x;
}
let ok_params = (nu > 0.0 && !delta.is_nan()) && !delta.is_infinite();
if !ok_params {
return f64::NAN;
}
match p {
0.0 => return -f64::INFINITY,
1.0 => return f64::INFINITY,
_ if p < 0.0 || p > 1.0 => return f64::NAN,
_ => (),
}
let crit = f64::sqrt(f64::EPSILON);
let pk = p;
let vk = nu;
let dk = delta;
const COUNT_LIMIT: usize = 100;
let mut count = 0;
let mut xk = norminv(pk, dk, 1.0);
let mut h = 1.0;
let mut F = nctcdf(xk, vk, dk, false);
while f64::abs(h) > crit * f64::abs(xk) && f64::abs(h) > crit && count < COUNT_LIMIT {
count += 1;
let f = nctpdf(xk, vk, dk);
h = (F - pk) / f;
let blowup = h.is_infinite() || h.is_nan();
if blowup {
h = xk / 10.0;
}
let mut xnew = f64::max(-5.0 * f64::abs(xk), f64::min(5.0 * f64::abs(xk), xk - h));
let mut Fnew = nctcdf(xnew, vk, dk, false);
loop {
let worse = (f64::abs(Fnew - pk) > f64::abs(F - pk) * (1.0 + crit))
&& (f64::abs(xk - xnew) > crit * f64::abs(xk));
if !worse {
break;
}
xnew = 0.5 * (xnew + xk);
Fnew = nctcdf(xnew, vk, dk, false);
}
xk = xnew;
F = Fnew;
}
if cfg!(debug_assertions) {
assert!(count < COUNT_LIMIT);
}
return xk;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nctinv_central() {
let nu = 10.0;
assert!((nctinv(0.5, nu, 0.0) - 0.0).abs() < 1e-14);
assert!((nctinv(0.975, nu, 0.0) - tinv(0.975, nu)).abs() < 1e-14);
}
#[test]
fn test_nctinv_known_values() {
let tol = 1e-10;
assert!((nctinv(0.5, 5.0, 1.0) - 1.052851040947396e+00).abs() < tol);
assert!((nctinv(0.9, 10.0, 2.0) - 3.746633570263350e+00).abs() < tol);
}
#[test]
fn test_nctinv_symmetry() {
let p = 0.3;
let nu = 8.0;
let delta = 1.5;
let x1 = nctinv(p, nu, delta);
let x2 = nctinv(1.0 - p, nu, -delta);
assert!((x1 + x2).abs() < 1e-12);
}
#[test]
fn test_nctinv_boundaries() {
assert_eq!(nctinv(0.0, 5.0, 1.0), f64::NEG_INFINITY);
assert_eq!(nctinv(1.0, 5.0, 1.0), f64::INFINITY);
}
#[test]
fn test_nctinv_invalid() {
assert!(nctinv(-0.1, 5.0, 1.0).is_nan());
assert!(nctinv(1.1, 5.0, 1.0).is_nan());
assert!(nctinv(0.5, 0.0, 1.0).is_nan());
assert!(nctinv(0.5, -1.0, 1.0).is_nan());
assert!(nctinv(0.5, 5.0, f64::INFINITY).is_nan());
assert!(nctinv(0.5, 5.0, f64::NAN).is_nan());
}
}