use crate::error::{SpecialError, SpecialResult};
use crate::gamma::{betainc_regularized, gamma};
use crate::incomplete_gamma::{gammainc, gammaincc};
use crate::validation::{check_finite, check_probability};
use scirs2_core::ndarray::{Array1, ArrayView1};
use scirs2_core::numeric::{Float, FromPrimitive};
use std::f64::consts::PI;
use std::fmt::{Debug, Display};
use std::ops::{AddAssign, MulAssign, SubAssign};
#[inline(always)]
fn const_f64<T: Float + FromPrimitive>(value: f64) -> T {
T::from_f64(value).expect("Failed to convert constant to target float type - this indicates an incompatible numeric type")
}
#[allow(dead_code)]
pub fn ndtr<T: Float + FromPrimitive>(x: T) -> T {
let sqrt2 = const_f64::<T>(std::f64::consts::SQRT_2);
let half = const_f64::<T>(0.5);
let one = T::one();
half * (one + crate::erf::erf(x / sqrt2))
}
#[allow(dead_code)]
pub fn log_ndtr<T: Float + FromPrimitive>(x: T) -> T {
if x >= T::zero() {
ndtr(x).ln()
} else {
let sqrt2 = const_f64::<T>(std::f64::consts::SQRT_2);
let log2 = const_f64::<T>(std::f64::consts::LN_2);
crate::erf::erfc(-x / sqrt2).ln() - log2
}
}
#[allow(dead_code)]
pub fn ndtri<T: Float + FromPrimitive + Display>(p: T) -> SpecialResult<T> {
check_probability(p, "p")?;
let sqrt2 = const_f64::<T>(std::f64::consts::SQRT_2);
let two = const_f64::<T>(2.0);
let one = T::one();
Ok(sqrt2 * crate::erf::erfinv(two * p - one))
}
#[allow(dead_code)]
pub fn ndtri_exp<T: Float + FromPrimitive + Display>(y: T) -> SpecialResult<T> {
if y > T::zero() {
return Err(SpecialError::DomainError(
"ndtri_exp: y must be <= 0".to_string(),
));
}
let p = y.exp();
ndtri(p)
}
#[allow(dead_code)]
pub fn bdtr<T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign>(
k: usize,
n: usize,
p: T,
) -> SpecialResult<T> {
check_probability(p, "p")?;
if k >= n {
return Ok(T::one());
}
let nminus_k = T::from_usize(n - k).expect("Failed to convert usize to Float type");
let k_plus_1 = T::from_usize(k + 1).expect("Failed to convert usize to Float type");
let oneminus_p = T::one() - p;
betainc_regularized(oneminus_p, nminus_k, k_plus_1)
}
#[allow(dead_code)]
pub fn bdtrc<T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign>(
k: usize,
n: usize,
p: T,
) -> SpecialResult<T> {
check_probability(p, "p")?;
if k >= n {
return Ok(T::zero());
}
let k_plus_1 = T::from_usize(k + 1).expect("Failed to convert usize to Float type");
let nminus_k = T::from_usize(n - k).expect("Failed to convert usize to Float type");
betainc_regularized(p, k_plus_1, nminus_k)
}
#[allow(dead_code)]
pub fn bdtri<T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign>(
n: usize,
p: T,
y: T,
) -> SpecialResult<usize> {
check_probability(p, "p")?;
check_probability(y, "y")?;
let mut low = 0;
let mut high = n;
while low < high {
let mid = (low + high) / 2;
let cdf = bdtr(mid, n, p)?;
if cdf < y {
low = mid + 1;
} else {
high = mid;
}
}
Ok(low)
}
#[allow(dead_code)]
pub fn pdtr<T: Float + FromPrimitive + Display + Debug + AddAssign + MulAssign>(
k: usize,
lambda: T,
) -> SpecialResult<T> {
check_finite(lambda, "lambda value")?;
if lambda <= T::zero() {
return Err(SpecialError::DomainError(
"pdtr: lambda must be positive".to_string(),
));
}
let k_plus_1 = T::from_usize(k + 1).expect("Failed to convert usize to Float type");
gammainc(k_plus_1, lambda)
}
#[allow(dead_code)]
pub fn pdtrc<T: Float + FromPrimitive + Display + Debug + AddAssign + MulAssign>(
k: usize,
lambda: T,
) -> SpecialResult<T> {
check_finite(lambda, "lambda value")?;
if lambda <= T::zero() {
return Err(SpecialError::DomainError(
"pdtrc: lambda must be positive".to_string(),
));
}
let k_plus_1 = T::from_usize(k + 1).expect("Failed to convert usize to Float type");
gammaincc(k_plus_1, lambda)
}
#[allow(dead_code)]
pub fn chdtr<T: Float + FromPrimitive + Display + Debug + AddAssign>(
df: T,
x: T,
) -> SpecialResult<T> {
check_finite(df, "df value")?;
check_finite(x, "x value")?;
if df <= T::zero() {
return Err(SpecialError::DomainError(
"chdtr: df must be positive".to_string(),
));
}
if x <= T::zero() {
return Ok(T::zero());
}
let half_df = df / const_f64::<T>(2.0);
let half_x = x / const_f64::<T>(2.0);
let gamma_full = gamma(half_df);
let gamma_inc = gamma_incomplete_lower(half_df, half_x)?;
Ok(gamma_inc / gamma_full)
}
#[allow(dead_code)]
pub fn chdtrc<T: Float + FromPrimitive + Display + Debug + AddAssign>(
df: T,
x: T,
) -> SpecialResult<T> {
let cdf = chdtr(df, x)?;
Ok(T::one() - cdf)
}
#[allow(dead_code)]
pub fn stdtr<T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign>(
df: T,
t: T,
) -> SpecialResult<T> {
check_finite(df, "df value")?;
check_finite(t, "t value")?;
if df <= T::zero() {
return Err(SpecialError::DomainError(
"stdtr: df must be positive".to_string(),
));
}
let x = df / (df + t * t);
let half = const_f64::<T>(0.5);
if t < T::zero() {
Ok(half * betainc_regularized(x, half * df, half)?)
} else {
Ok(T::one() - half * betainc_regularized(x, half * df, half)?)
}
}
#[allow(dead_code)]
pub fn fdtr<T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign>(
dfn: T,
dfd: T,
x: T,
) -> SpecialResult<T> {
check_finite(dfn, "dfn value")?;
check_finite(dfd, "dfd value")?;
check_finite(x, "x value")?;
if dfn <= T::zero() || dfd <= T::zero() {
return Err(SpecialError::DomainError(
"fdtr: degrees of freedom must be positive".to_string(),
));
}
if x <= T::zero() {
return Ok(T::zero());
}
let half_dfn = dfn / const_f64::<T>(2.0);
let half_dfd = dfd / const_f64::<T>(2.0);
let y = (dfn * x) / (dfn * x + dfd);
betainc_regularized(y, half_dfn, half_dfd)
}
#[allow(dead_code)]
pub fn fdtrc<T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign>(
dfn: T,
dfd: T,
x: T,
) -> SpecialResult<T> {
let cdf = fdtr(dfn, dfd, x)?;
Ok(T::one() - cdf)
}
#[allow(dead_code)]
pub fn gdtr<T: Float + FromPrimitive + Display + Debug + AddAssign>(
a: T,
x: T,
) -> SpecialResult<T> {
check_finite(a, "a value")?;
check_finite(x, "x value")?;
if a <= T::zero() {
return Err(SpecialError::DomainError(
"gdtr: shape parameter must be positive".to_string(),
));
}
if x <= T::zero() {
return Ok(T::zero());
}
let gamma_full = gamma(a);
let gamma_inc = gamma_incomplete_lower(a, x)?;
Ok(gamma_inc / gamma_full)
}
#[allow(dead_code)]
pub fn gdtrc<T: Float + FromPrimitive + Display + Debug + AddAssign>(
a: T,
x: T,
) -> SpecialResult<T> {
let cdf = gdtr(a, x)?;
Ok(T::one() - cdf)
}
#[allow(dead_code)]
pub fn kolmogorov<T: Float + FromPrimitive>(x: T) -> T {
if x <= T::zero() {
return T::zero();
}
if x >= const_f64::<T>(6.0) {
return T::one();
}
let pi = const_f64::<T>(PI);
let mut sum = T::zero();
let mut k = T::one();
let tol = const_f64::<T>(1e-12);
loop {
let term = const_f64::<T>(2.0)
* (-(const_f64::<T>(2.0) * k * k * x * x)).exp()
* ((const_f64::<T>(2.0) * k * k * x * x - T::one()) * const_f64::<T>(2.0)).exp();
sum = sum
+ if k
.to_isize()
.expect("Failed to convert Float to isize for parity check")
% 2
== 0
{
-term
} else {
term
};
if term.abs() < tol {
break;
}
k = k + T::one();
}
(const_f64::<T>(8.0) * x / pi.sqrt()) * sum
}
#[allow(dead_code)]
pub fn kolmogi<T: Float + FromPrimitive + Display>(p: T) -> SpecialResult<T> {
check_probability(p, "p")?;
let zero = T::zero();
let one = T::one();
let tol = const_f64::<T>(1e-12);
if p <= const_f64::<T>(1e-15) {
return Ok(zero);
}
if p >= one - const_f64::<T>(1e-15) {
return Ok(const_f64::<T>(10.0)); }
let initial_guess = kolmogorov_inverse_initial_guess(p)?;
if let Ok(result) = kolmogorov_inverse_halley(p, initial_guess, tol) {
return Ok(result);
}
if let Ok(result) = kolmogorov_inverse_newton_improved(p, initial_guess, tol) {
return Ok(result);
}
if let Ok(result) = kolmogorov_inverse_bracketed_newton(p, tol) {
return Ok(result);
}
kolmogorov_inverse_enhanced_bisection(p, tol)
}
#[allow(dead_code)]
fn kolmogorov_inverse_initial_guess<T: Float + FromPrimitive>(p: T) -> SpecialResult<T> {
let zero = T::zero();
let one = T::one();
let two = const_f64::<T>(2.0);
let _half = const_f64::<T>(0.5);
if p <= const_f64::<T>(0.1) {
let ln_p_over_2 = (p / two).ln();
let arg = -ln_p_over_2 / two;
if arg > zero {
Ok(arg.sqrt())
} else {
Ok(const_f64::<T>(0.1))
}
} else if p >= const_f64::<T>(0.9) {
let oneminus_p = one - p;
if oneminus_p > const_f64::<T>(1e-15) {
let ln_arg = two * oneminus_p;
let arg = -ln_arg.ln();
if arg > zero {
Ok(arg.sqrt())
} else {
Ok(const_f64::<T>(3.0))
}
} else {
Ok(const_f64::<T>(5.0))
}
} else {
let p_float = p.to_f64().unwrap_or(0.5);
let approx = if p_float < 0.5 {
0.895 + (1.36 - 0.895) * (p_float - 0.1) / 0.4
} else {
1.36 + (1.95 - 1.36) * (p_float - 0.5) / 0.4
};
Ok(const_f64::<T>(approx))
}
}
#[allow(dead_code)]
fn kolmogorov_inverse_halley<T: Float + FromPrimitive + Display>(
target_p: T,
initial_x: T,
tolerance: T,
) -> SpecialResult<T> {
let mut _x = initial_x;
let max_iterations = 50;
for _iteration in 0..max_iterations {
let fx = kolmogorov(_x) - target_p;
if fx.abs() < tolerance {
return Ok(_x);
}
let h = const_f64::<T>(1e-8);
let f_plus = kolmogorov(_x + h) - target_p;
let fminus = kolmogorov(_x - h) - target_p;
let fprime = (f_plus - fminus) / (const_f64::<T>(2.0) * h);
let f2prime = (f_plus - const_f64::<T>(2.0) * fx + fminus) / (h * h);
if fprime.abs() < const_f64::<T>(1e-15) {
return Err(SpecialError::ConvergenceError(
"Zero derivative in Halley's method".to_string(),
));
}
let numerator = const_f64::<T>(2.0) * fx * fprime;
let denominator = const_f64::<T>(2.0) * fprime * fprime - fx * f2prime;
if denominator.abs() < const_f64::<T>(1e-15) {
return Err(SpecialError::ConvergenceError(
"Zero denominator in Halley's method".to_string(),
));
}
let step = numerator / denominator;
_x = _x - step;
if _x < T::zero() {
_x = const_f64::<T>(0.01);
}
if step.abs() < tolerance {
return Ok(_x);
}
}
Err(SpecialError::ConvergenceError(
"Halley's method did not converge".to_string(),
))
}
#[allow(dead_code)]
fn kolmogorov_inverse_newton_improved<T: Float + FromPrimitive + Display>(
target_p: T,
initial_x: T,
tolerance: T,
) -> SpecialResult<T> {
let mut _x = initial_x;
let max_iterations = 100;
for _iteration in 0..max_iterations {
let fx = kolmogorov(_x) - target_p;
if fx.abs() < tolerance {
return Ok(_x);
}
let h = const_f64::<T>(1e-8) * (T::one() + _x.abs());
let f_plus = kolmogorov(_x + h);
let fminus = kolmogorov(_x - h);
let fprime = (f_plus - fminus) / (const_f64::<T>(2.0) * h);
if fprime.abs() < const_f64::<T>(1e-15) {
return Err(SpecialError::ConvergenceError(
"Zero derivative in Newton's method".to_string(),
));
}
let raw_step = fx / fprime;
let damping = if raw_step.abs() > const_f64::<T>(0.5) {
const_f64::<T>(0.5) / raw_step.abs()
} else {
T::one()
};
let step = raw_step * damping;
_x = _x - step;
if _x < T::zero() {
_x = const_f64::<T>(0.01);
}
if step.abs() < tolerance {
return Ok(_x);
}
}
Err(SpecialError::ConvergenceError(
"Improved Newton's method did not converge".to_string(),
))
}
#[allow(dead_code)]
fn kolmogorov_inverse_bracketed_newton<T: Float + FromPrimitive + Display>(
target_p: T,
tolerance: T,
) -> SpecialResult<T> {
let mut low = T::zero();
let mut high = const_f64::<T>(10.0);
let f_low = kolmogorov(low) - target_p;
let f_high = kolmogorov(high) - target_p;
if f_low * f_high > T::zero() {
while kolmogorov(high) < target_p && high < const_f64::<T>(20.0) {
high = high * const_f64::<T>(2.0);
}
}
let max_iterations = 100;
for _iteration in 0..max_iterations {
let mid = (low + high) / const_f64::<T>(2.0);
let f_mid = kolmogorov(mid) - target_p;
if f_mid.abs() < tolerance || (high - low) < tolerance {
return Ok(mid);
}
let h = const_f64::<T>(1e-8) * (T::one() + mid.abs());
let f_plus = kolmogorov(mid + h) - target_p;
let fminus = kolmogorov(mid - h) - target_p;
let fprime = (f_plus - fminus) / (const_f64::<T>(2.0) * h);
if fprime.abs() > const_f64::<T>(1e-15) {
let newton_x = mid - f_mid / fprime;
if newton_x > low && newton_x < high {
let f_newton = kolmogorov(newton_x) - target_p;
if f_newton.abs() < f_mid.abs() {
if f_newton * f_low < T::zero() {
high = newton_x;
} else {
low = newton_x;
}
continue;
}
}
}
if f_mid * f_low < T::zero() {
high = mid;
} else {
low = mid;
}
}
Ok((low + high) / const_f64::<T>(2.0))
}
#[allow(dead_code)]
fn kolmogorov_inverse_enhanced_bisection<T: Float + FromPrimitive + Display>(
target_p: T,
tolerance: T,
) -> SpecialResult<T> {
let mut low = T::zero();
let mut high = const_f64::<T>(10.0);
while kolmogorov(high) < target_p && high < const_f64::<T>(50.0) {
high = high * const_f64::<T>(2.0);
}
let max_iterations = 100;
for _iteration in 0..max_iterations {
let mid = (low + high) / const_f64::<T>(2.0);
let f_mid = kolmogorov(mid);
if (f_mid - target_p).abs() < tolerance || (high - low) < tolerance {
return Ok(mid);
}
if f_mid < target_p {
low = mid;
} else {
high = mid;
}
}
Ok((low + high) / const_f64::<T>(2.0))
}
#[allow(dead_code)]
fn gamma_incomplete_lower<T: Float + FromPrimitive + Debug + AddAssign>(
a: T,
x: T,
) -> SpecialResult<T> {
if x <= T::zero() {
return Ok(T::zero());
}
if x < a + T::one() {
let mut sum = T::one() / a;
let mut term = T::one() / a;
let mut n = T::one();
while term.abs() > const_f64::<T>(1e-12) * sum.abs() {
term = term * x / (a + n);
sum += term;
n += T::one();
}
Ok(x.powf(a) * (-x).exp() * sum)
} else {
let gamma_full = gamma(a);
let gamma_upper = gamma_incomplete_upper(a, x)?;
Ok(gamma_full - gamma_upper)
}
}
#[allow(dead_code)]
fn gamma_incomplete_upper<T: Float + FromPrimitive + Debug + AddAssign>(
a: T,
x: T,
) -> SpecialResult<T> {
if x <= T::zero() {
return Ok(gamma(a));
}
if x >= a + T::one() {
let mut b = x + T::one() - a;
let mut c = const_f64::<T>(1e30);
let mut d = T::one() / b;
let mut h = d;
for i in 1..100 {
let an = -T::from_usize(i).expect("Failed to convert usize to Float type")
* (T::from_usize(i).expect("Failed to convert usize to Float type") - a);
b += const_f64::<T>(2.0);
d = an * d + b;
if d.abs() < const_f64::<T>(1e-30) {
d = const_f64::<T>(1e-30);
}
c = b + an / c;
if c.abs() < const_f64::<T>(1e-30) {
c = const_f64::<T>(1e-30);
}
d = T::one() / d;
let delta = d * c;
h = h * delta;
if (delta - T::one()).abs() < const_f64::<T>(1e-10) {
break;
}
}
Ok(x.powf(a) * (-x).exp() * h)
} else {
let gamma_full = gamma(a);
let gamma_lower = gamma_incomplete_lower(a, x)?;
Ok(gamma_full - gamma_lower)
}
}
#[allow(dead_code)]
pub fn ndtr_array<T>(x: &ArrayView1<T>) -> Array1<T>
where
T: Float + FromPrimitive + Send + Sync + Debug,
{
#[cfg(feature = "parallel")]
{
if x.len() > 1000 {
use scirs2_core::parallel_ops::*;
let vec: Vec<T> = x
.as_slice()
.expect("Array should be contiguous for parallel processing")
.par_iter()
.map(|&val| ndtr(val))
.collect();
Array1::from_vec(vec)
} else {
x.mapv(ndtr)
}
}
#[cfg(not(feature = "parallel"))]
{
x.mapv(ndtr)
}
}
#[allow(dead_code)]
pub fn bdtr_array<T>(k: &[usize], n: usize, p: T) -> SpecialResult<Array1<T>>
where
T: Float + FromPrimitive + Send + Sync + Debug + Display + AddAssign + SubAssign + MulAssign,
{
check_probability(p, "p")?;
let results: Result<Vec<T>, _> = k.iter().map(|&ki| bdtr(ki, n, p)).collect();
Ok(Array1::from_vec(results?))
}
#[allow(dead_code)]
pub fn bdtrik<T>(y: T, n: T, p: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign,
{
check_probability(y, "y")?;
check_probability(p, "p")?;
let mut low = T::zero();
let mut high = n;
let tolerance = const_f64::<T>(1e-10);
for _ in 0..50 {
let mid = (low + high) / const_f64::<T>(2.0);
let cdf_val = bdtr(mid.to_usize().unwrap_or(0), n.to_usize().unwrap_or(0), p)?;
if (cdf_val - y).abs() < tolerance {
return Ok(mid);
}
if cdf_val < y {
low = mid;
} else {
high = mid;
}
}
Ok((low + high) / const_f64::<T>(2.0))
}
#[allow(dead_code)]
pub fn bdtrin<T>(y: T, k: T, p: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign,
{
check_probability(y, "y")?;
check_probability(p, "p")?;
let mut n = k + const_f64::<T>(10.0); let tolerance = const_f64::<T>(1e-10);
for _ in 0..50 {
let f_val = bdtr(k.to_usize().unwrap_or(0), n.to_usize().unwrap_or(0), p)? - y;
if f_val.abs() < tolerance {
return Ok(n);
}
let delta = const_f64::<T>(1e-6);
let f_prime = (bdtr(
k.to_usize().unwrap_or(0),
(n + delta).to_usize().unwrap_or(0),
p,
)? - bdtr(
k.to_usize().unwrap_or(0),
(n - delta).to_usize().unwrap_or(0),
p,
)?) / (const_f64::<T>(2.0) * delta);
if f_prime.abs() < T::epsilon() {
break;
}
n -= f_val / f_prime;
if n < k {
n = k + const_f64::<T>(0.1);
}
}
Ok(n)
}
#[allow(dead_code)]
pub fn btdtria<T>(y: T, x: T, b: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign,
{
check_probability(y, "y")?;
check_probability(x, "x")?;
if b <= T::zero() {
return Err(SpecialError::DomainError(
"btdtria: b must be positive".to_string(),
));
}
let mut a = T::one(); let tolerance = const_f64::<T>(1e-10);
for _ in 0..50 {
let f_val = betainc_regularized(a, b, x)? - y;
if f_val.abs() < tolerance {
return Ok(a);
}
let delta = const_f64::<T>(1e-6);
let f_plus = betainc_regularized(a + delta, b, x)?;
let fminus = betainc_regularized(a - delta, b, x)?;
let f_prime = (f_plus - fminus) / (const_f64::<T>(2.0) * delta);
if f_prime.abs() < T::epsilon() {
break;
}
a -= f_val / f_prime;
if a <= T::zero() {
a = const_f64::<T>(0.01);
}
}
Ok(a)
}
#[allow(dead_code)]
pub fn btdtrib<T>(y: T, x: T, a: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign,
{
check_probability(y, "y")?;
check_probability(x, "x")?;
if a <= T::zero() {
return Err(SpecialError::DomainError(
"btdtrib: a must be positive".to_string(),
));
}
let mut b = T::one(); let tolerance = const_f64::<T>(1e-10);
for _ in 0..50 {
let f_val = betainc_regularized(a, b, x)? - y;
if f_val.abs() < tolerance {
return Ok(b);
}
let delta = const_f64::<T>(1e-6);
let f_plus = betainc_regularized(a, b + delta, x)?;
let fminus = betainc_regularized(a, b - delta, x)?;
let f_prime = (f_plus - fminus) / (const_f64::<T>(2.0) * delta);
if f_prime.abs() < T::epsilon() {
break;
}
b -= f_val / f_prime;
if b <= T::zero() {
b = const_f64::<T>(0.01);
}
}
Ok(b)
}
#[allow(dead_code)]
pub fn fdtridfd<T>(y: T, x: T, dfd: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign,
{
check_probability(y, "y")?;
if x <= T::zero() || dfd <= T::zero() {
return Err(SpecialError::DomainError(
"fdtridfd: x and dfd must be positive".to_string(),
));
}
let mut dfn = const_f64::<T>(5.0); let tolerance = const_f64::<T>(1e-10);
for _ in 0..50 {
let f_val = fdtr(dfn, dfd, x)? - y;
if f_val.abs() < tolerance {
return Ok(dfn);
}
let delta = const_f64::<T>(1e-6);
let f_plus = fdtr(dfn + delta, dfd, x)?;
let fminus = fdtr(dfn - delta, dfd, x)?;
let f_prime = (f_plus - fminus) / (const_f64::<T>(2.0) * delta);
if f_prime.abs() < T::epsilon() {
break;
}
dfn -= f_val / f_prime;
if dfn <= T::zero() {
dfn = const_f64::<T>(0.1);
}
}
Ok(dfn)
}
#[allow(dead_code)]
pub fn gdtria<T>(y: T, x: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign,
{
check_probability(y, "y")?;
if x <= T::zero() {
return Err(SpecialError::DomainError(
"gdtria: x must be positive".to_string(),
));
}
let mut a = T::one(); let tolerance = const_f64::<T>(1e-10);
for _ in 0..50 {
let f_val = gdtr(a, x)? - y;
if f_val.abs() < tolerance {
return Ok(a);
}
let delta = const_f64::<T>(1e-6);
let f_plus = gdtr(a + delta, x)?;
let fminus = gdtr(a - delta, x)?;
let f_prime = (f_plus - fminus) / (const_f64::<T>(2.0) * delta);
if f_prime.abs() < T::epsilon() {
break;
}
a -= f_val / f_prime;
if a <= T::zero() {
a = const_f64::<T>(0.01);
}
}
Ok(a)
}
#[allow(dead_code)]
pub fn gdtrib<T>(y: T, a: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign,
{
check_probability(y, "y")?;
if a <= T::zero() {
return Err(SpecialError::DomainError(
"gdtrib: a must be positive".to_string(),
));
}
let mut x = a; let tolerance = const_f64::<T>(1e-10);
for _ in 0..50 {
let f_val = gdtr(a, x)? - y;
if f_val.abs() < tolerance {
return Ok(x);
}
let delta = const_f64::<T>(1e-6) * (T::one() + x.abs());
let f_plus = gdtr(a, x + delta)?;
let fminus = gdtr(a, x - delta)?;
let f_prime = (f_plus - fminus) / (const_f64::<T>(2.0) * delta);
if f_prime.abs() < T::epsilon() {
break;
}
x -= f_val / f_prime;
if x <= T::zero() {
x = const_f64::<T>(0.01);
}
}
Ok(x)
}
#[allow(dead_code)]
pub fn gdtrix<T>(y: T, a: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign,
{
check_probability(y, "y")?;
if a <= T::zero() {
return Err(SpecialError::DomainError(
"gdtrix: a must be positive".to_string(),
));
}
let mut x = a; let tolerance = const_f64::<T>(1e-10);
for _ in 0..50 {
let f_val = gdtr(a, x)? - y;
if f_val.abs() < tolerance {
return Ok(x);
}
let delta = const_f64::<T>(1e-6) * (T::one() + x.abs());
let f_plus = gdtr(a, x + delta)?;
let fminus = gdtr(a, x - delta)?;
let f_prime = (f_plus - fminus) / (const_f64::<T>(2.0) * delta);
if f_prime.abs() < T::epsilon() {
break;
}
x -= f_val / f_prime;
if x <= T::zero() {
x = const_f64::<T>(0.01);
}
}
Ok(x)
}
#[allow(dead_code)]
pub fn chdtri<T>(p: T, v: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign,
{
check_probability(p, "p")?;
check_finite(v, "v value")?;
if v <= T::zero() {
return Err(SpecialError::DomainError(
"Degrees of freedom must be positive".to_string(),
));
}
let _half = const_f64::<T>(0.5);
let two = const_f64::<T>(2.0);
if p == T::zero() {
return Ok(T::zero());
}
if p == T::one() {
return Ok(T::infinity());
}
let z = crate::erf::erfinv(two * p - T::one());
let h = two / (const_f64::<T>(9.0) * v);
let term = z * h.sqrt() - h / const_f64::<T>(3.0) + T::one();
Ok(v * term.powi(3))
}
#[allow(dead_code)]
pub fn pdtri<T>(p: T, k: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + MulAssign,
{
check_probability(p, "p")?;
check_finite(k, "k value")?;
if k < T::zero() {
return Err(SpecialError::DomainError(
"Count k must be non-negative".to_string(),
));
}
if p == T::zero() {
return Ok(T::zero());
}
if p == T::one() {
return Ok(T::infinity());
}
let one = T::one();
if k > const_f64::<T>(10.0) {
let z = crate::erf::erfinv(const_f64::<T>(2.0) * p - one);
let sqrt_k = k.sqrt();
Ok(k + z * sqrt_k)
} else {
Ok(k)
}
}
#[allow(dead_code)]
pub fn pdtrik<T>(p: T, m: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + MulAssign,
{
check_probability(p, "p")?;
check_finite(m, "m value")?;
if m <= T::zero() {
return Err(SpecialError::DomainError(
"Rate parameter m must be positive".to_string(),
));
}
if p == T::zero() {
return Ok(T::zero());
}
if p == T::one() {
return Ok(T::infinity());
}
if m > const_f64::<T>(10.0) {
let z = crate::erf::erfinv(const_f64::<T>(2.0) * p - T::one());
let sqrt_m = m.sqrt();
let result = m + z * sqrt_m - const_f64::<T>(0.5);
Ok(result.max(T::zero()))
} else {
Ok(m)
}
}
#[allow(dead_code)]
pub fn nbdtr<T>(k: T, r: T, p: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign,
{
check_finite(k, "k value")?;
check_finite(r, "r value")?;
check_probability(p, "p")?;
if k < T::zero() || r <= T::zero() {
return Err(SpecialError::DomainError(
"k must be non-negative and r must be positive".to_string(),
));
}
betainc_regularized(p, r, k + T::one())
}
#[allow(dead_code)]
pub fn nbdtrc<T>(k: T, r: T, p: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign,
{
let cdf = nbdtr(k, r, p)?;
Ok(T::one() - cdf)
}
#[allow(dead_code)]
pub fn nbdtri<T>(y: T, k: T, r: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Debug + AddAssign + SubAssign + MulAssign,
{
check_probability(y, "y")?;
check_finite(k, "k value")?;
check_finite(r, "r value")?;
if k < T::zero() || r <= T::zero() {
return Err(SpecialError::DomainError(
"k must be non-negative and r must be positive".to_string(),
));
}
if y == T::zero() {
return Ok(T::zero());
}
if y == T::one() {
return Ok(T::one());
}
let total_trials = k + r;
Ok(r / total_trials)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_normal_distribution() {
assert_relative_eq!(ndtr(0.0), 0.5, epsilon = 1e-10);
assert_relative_eq!(ndtr(1.0), 0.8413447460685429, epsilon = 1e-8);
assert_relative_eq!(ndtr(-1.0), 0.15865525393145707, epsilon = 1e-8);
}
#[test]
fn test_binomial_distribution() {
let cdf = bdtr(2, 5, 0.5).expect("test should not fail");
assert_relative_eq!(cdf, 0.5, epsilon = 1e-10);
let surv = bdtrc(2, 5, 0.5).expect("test should not fail");
assert_relative_eq!(cdf + surv, 1.0, epsilon = 1e-10);
}
#[test]
fn test_chi_square_distribution() {
let cdf = chdtr(2.0, 2.0).expect("test should not fail");
assert_relative_eq!(cdf, 0.6321205588285577, epsilon = 1e-8);
}
#[test]
fn test_student_t_distribution() {
let cdf = stdtr(10.0, 0.0).expect("test should not fail");
assert_relative_eq!(cdf, 0.5, epsilon = 1e-10);
}
#[test]
fn test_f_distribution() {
let cdf = fdtr(5.0, 10.0, 1.0).expect("test should not fail");
assert_relative_eq!(cdf, 0.5417926019448583, epsilon = 0.5);
}
}