use crate::{gammainc, erfcinv};
pub fn poissinv(p: f64, lambda: f64) -> f64 {
if p.is_nan() || lambda.is_nan() {
return f64::NAN;
}
if lambda < 0.0 || p < 0.0 || p > 1.0 {
return f64::NAN;
}
if p == 0.0 {
return 0.0;
}
if lambda > 0.0 && p == 1.0 {
return f64::INFINITY;
}
if lambda == 0.0 {
return 0.0;
}
if lambda > 10.0 {
let x1 = -2.0f64.sqrt() * erfcinv(2.0 * p);
let mut count = (lambda.sqrt() * x1 + lambda).ceil().max(0.0);
let y = gammainc(lambda, count + 1.0, false, false);
if y < p {
loop {
let y_new = gammainc(lambda, count + 2.0, false, false);
count += 1.0;
if y_new >= p {
break;
}
}
} else {
loop {
if count <= 0.0 {
break;
}
let y_new = gammainc(lambda, count, false, false);
if y_new < p {
break;
}
count -= 1.0;
}
}
return count;
}
let mut count = 0.0;
loop {
let cum_dist = gammainc(lambda, count + 1.0, false, false);
if cum_dist >= p {
break;
}
count += 1.0;
}
count
}
#[cfg(test)]
mod tests {
use super::*;
use crate::poisscdf;
#[test]
fn test_poissinv_basic_small_lambda() {
assert_eq!(poissinv(0.3, 1.0), 0.0);
assert_eq!(poissinv(0.367879, 1.0), 0.0);
assert_eq!(poissinv(0.367880, 1.0), 1.0);
assert_eq!(poissinv(0.7, 1.0), 1.0);
assert_eq!(poissinv(0.5, 5.0), 5.0);
assert_eq!(poissinv(0.4, 5.0), 4.0);
assert_eq!(poissinv(0.65, 5.0), 6.0);
}
#[test]
fn test_poissinv_basic_large_lambda() {
assert_eq!(poissinv(0.5, 20.0), 20.0);
assert_eq!(poissinv(0.4, 20.0), 19.0);
assert_eq!(poissinv(0.6, 20.0), 21.0);
assert_eq!(poissinv(0.5, 100.0), 100.0);
assert_eq!(poissinv(0.1, 100.0), 87.0);
assert_eq!(poissinv(0.9, 100.0), 113.0);
}
#[test]
fn test_poissinv_boundary_p() {
assert_eq!(poissinv(0.0, 1.0), 0.0);
assert_eq!(poissinv(0.0, 100.0), 0.0);
assert_eq!(poissinv(0.0, 0.0), 0.0);
assert_eq!(poissinv(1.0, 1.0), f64::INFINITY);
assert_eq!(poissinv(1.0, 100.0), f64::INFINITY);
assert_eq!(poissinv(1.0, 0.0), 0.0); }
#[test]
fn test_poissinv_boundary_lambda() {
assert_eq!(poissinv(0.0, 0.0), 0.0);
assert_eq!(poissinv(0.5, 0.0), 0.0);
assert_eq!(poissinv(1.0, 0.0), 0.0);
}
#[test]
fn test_poissinv_invalid_inputs() {
assert!(poissinv(f64::NAN, 5.0).is_nan());
assert!(poissinv(0.5, f64::NAN).is_nan());
assert!(poissinv(-0.1, 5.0).is_nan());
assert!(poissinv(1.1, 5.0).is_nan());
assert!(poissinv(0.5, -1.0).is_nan());
}
#[test]
fn test_poissinv_roundtrip_small_lambda() {
let lambda = 5.0;
for x in 0..=15 { let p = poisscdf(x as f64, lambda, false);
let inv_x = poissinv(p, lambda);
assert_eq!(inv_x, x as f64, "Roundtrip failed for x={}, lambda={}", x, lambda);
}
}
#[test]
fn test_poissinv_roundtrip_large_lambda() {
let lambda = 50.0;
for x_offset in -20..=20 {
let x = lambda + x_offset as f64;
if x < 0.0 { continue; }
let p = poisscdf(x, lambda, false);
let inv_x = poissinv(p, lambda);
assert_eq!(inv_x, x, "Roundtrip failed for x={}, lambda={}", x, lambda);
}
}
}