#![cfg(not(miri))]
mod common;
use cdflib::special::internal::{
apser, beta_grat, beta_rcomp, beta_rcomp1, beta_up, fpser, gam1, gamma_rat1, rcomp, rexp,
};
use cdflib::special::{
beta_inc, gamma, gamma_inc, gamma_inc_inv, gamma_log, psi, try_gamma_inc, try_gamma_inc_inv,
GammaIncError, GammaIncInvError,
};
use cdflib::{ContinuousCdf, DiscreteCdf, FisherSnedecorNoncentral, NegativeBinomial, Poisson};
#[test]
fn rexp_large_positive_and_negative() {
let pos = rexp(0.5); assert!((pos - (0.5_f64.exp() - 1.0)).abs() < 1e-13);
let neg = rexp(-0.5); assert!((neg - ((-0.5_f64).exp() - 1.0)).abs() < 1e-13);
}
#[test]
fn gam1_above_one_and_negative() {
let g_big = gam1(1.2);
let expected_big = 1.0 / gamma(2.2) - 1.0;
assert!((g_big - expected_big).abs() < 1e-12, "gam1(1.2) = {g_big}");
let g_neg = gam1(-0.3);
let expected_neg = 1.0 / gamma(0.7) - 1.0;
assert!(
(g_neg - expected_neg).abs() < 1e-12,
"gam1(-0.3) = {g_neg}, want {expected_neg}"
);
}
#[test]
fn gamma_negative_argument_and_overflow_paths() {
use cdflib::special::{try_gamma, GammaDomainError};
let g_307 = gamma(-15.95);
assert!(g_307.is_finite() && g_307 != 0.0, "g_307 = {g_307}");
assert_eq!(try_gamma(-20.0), Err(GammaDomainError::Pole(-20.0)));
assert_eq!(try_gamma(-25.0), Err(GammaDomainError::Pole(-25.0)));
assert_eq!(try_gamma(-2.0), Err(GammaDomainError::Pole(-2.0)));
assert_eq!(try_gamma(-5.0), Err(GammaDomainError::Pole(-5.0)));
assert_eq!(try_gamma(-200.7), Err(GammaDomainError::Overflow(-200.7)));
assert_eq!(try_gamma(200.0), Err(GammaDomainError::Overflow(200.0)));
}
#[test]
fn psi_reflection_and_overflow_branches() {
use cdflib::special::{try_psi, PsiError};
assert_eq!(try_psi(0.0), Err(PsiError::Pole(0.0)));
let small = 1e-12;
let p_small = psi(small);
assert!(
(p_small + 1.0 / small).abs() < 1.0,
"psi(1e-12) = {p_small}"
);
let p462 = psi(-0.6);
assert!(p462.is_finite());
assert_eq!(try_psi(-2.0), Err(PsiError::Pole(-2.0)));
assert_eq!(try_psi(-3.0), Err(PsiError::Pole(-3.0)));
assert_eq!(try_psi(-1e20), Err(PsiError::Overflow(-1e20)));
let big = 1e20_f64;
let p_big = psi(big);
assert!((p_big - big.ln()).abs() < 1.0, "psi(1e20) = {p_big}");
}
#[test]
#[should_panic(expected = "psi(0): ψ has a pole at 0")]
fn psi_panics_on_pole() {
let _ = psi(0.0);
}
#[test]
fn rcomp_branches() {
let r1 = rcomp(0.5, 1.0);
assert!(r1.is_finite() && r1 > 0.0);
let r2 = rcomp(5.0, 3.0);
assert!(r2.is_finite() && r2 > 0.0);
let r3 = rcomp(50.0, 50.0);
assert!(r3.is_finite() && r3 > 0.0);
let r4 = rcomp(1e20, f64::MIN_POSITIVE);
assert_eq!(r4, 0.0);
}
#[test]
fn gamma_inc_taylor_qans_negative_branch() {
let (p, q) = gamma_inc(0.99, 1.0e-8);
assert!(p.is_finite() && q.is_finite());
assert!((p + q - 1.0).abs() < 1e-10);
}
#[test]
fn gamma_inc_temme_indeterminate_sentinel() {
assert!(matches!(
try_gamma_inc(1e30, 1e30),
Err(GammaIncError::Indeterminate { .. })
));
}
#[test]
fn fpser_full_body() {
let eps = f64::EPSILON.max(1e-15);
let r = fpser(2.0, 0.1, 0.1, eps);
assert!(r.is_finite() && r > 0.0, "fpser(2, 0.1, 0.1) = {r}");
let r2 = fpser(1e-20, 1.0, 0.5, eps);
assert!(r2.is_finite());
}
#[test]
fn apser_large_b_eps_else_branch() {
let r = apser(1e-15, 5.0, 0.05, 0.1);
assert!(r.is_finite());
}
#[test]
fn beta_rcomp_degenerate_and_small_branches() {
assert_eq!(beta_rcomp(2.0, 3.0, 0.0, 1.0), 0.0);
assert_eq!(beta_rcomp(2.0, 3.0, 1.0, 0.0), 0.0);
let r1 = beta_rcomp(2.0, 5.0, 0.8, 0.2);
assert!(r1.is_finite() && r1 > 0.0);
let r2 = beta_rcomp(2.0, 3.0, 0.4, 0.6);
assert!(r2.is_finite() && r2 > 0.0);
let r3 = beta_rcomp(0.5, 3.5, 0.3, 0.7);
assert!(r3.is_finite() && r3 > 0.0);
let r4 = beta_rcomp(0.4, 0.8, 0.3, 0.7);
assert!(r4.is_finite() && r4 > 0.0);
}
#[test]
fn gamma_rat1_branches() {
let eps = f64::EPSILON.max(1e-15);
let (p, q) = gamma_rat1(0.0, 1.0, 1.0, eps);
assert_eq!((p, q), (1.0, 0.0));
let (p, q) = gamma_rat1(1.0, 0.0, 1.0, eps);
assert_eq!((p, q), (0.0, 1.0));
let (p, q) = gamma_rat1(0.5, 0.1, 0.0, eps);
assert!(p.is_finite() && q.is_finite() && (p + q - 1.0).abs() < 1e-12);
let (p, q) = gamma_rat1(0.5, 0.5, 0.0, eps);
assert!((p + q - 1.0).abs() < 1e-12);
let (p, q) = gamma_rat1(0.05, 0.3, 0.0, eps);
assert!((p + q - 1.0).abs() < 1e-10);
let (p, q) = gamma_rat1(0.7, 2.0, 1.0, eps);
assert!((p + q - 1.0).abs() < 1e-10);
}
#[test]
fn beta_rcomp1_branches() {
let r1 = beta_rcomp1(0, 10.0, 12.0, 0.45, 0.55);
let r1_ref = beta_rcomp(10.0, 12.0, 0.45, 0.55);
assert!(r1.is_finite() && (r1 - r1_ref).abs() / r1_ref.abs() < 1e-12);
let r2 = beta_rcomp1(0, 15.0, 10.0, 0.6, 0.4);
let r2_ref = beta_rcomp(15.0, 10.0, 0.6, 0.4);
assert!(r2.is_finite() && (r2 - r2_ref).abs() / r2_ref.abs() < 1e-12);
let r_467 = beta_rcomp1(0, 10.0, 12.0, 0.1, 0.9);
assert!(r_467.is_finite() && r_467 > 0.0);
let r_474 = beta_rcomp1(0, 15.0, 8.0, 0.95, 0.05);
assert!(r_474.is_finite() && r_474 > 0.0);
let r3 = beta_rcomp1(0, 0.5, 12.0, 0.3, 0.7);
let r3_ref = beta_rcomp(0.5, 12.0, 0.3, 0.7);
assert!(r3.is_finite() && (r3 - r3_ref).abs() / r3_ref.abs() < 1e-12);
let r_528 = beta_rcomp1(0, 0.5, 0.7, 0.4, 0.6);
assert!(r_528.is_finite() && r_528 > 0.0);
let r_underflow = beta_rcomp1(-800, 0.5, 0.5, 0.4, 0.6);
assert_eq!(r_underflow, 0.0);
}
#[test]
fn beta_up_b_gt_1_branches() {
let eps = f64::EPSILON.max(1e-15);
let r = beta_up(2.0, 3.0, 0.4, 0.6, 10, eps);
assert!(r.is_finite() && r > 0.0);
let r2 = beta_up(2.0, 3.0, 0.99999, 1e-5, 5, eps);
assert!(r2.is_finite());
}
#[test]
fn beta_grat_overflow_sentinel() {
use cdflib::special::internal::BetaGratError;
let eps = f64::EPSILON.max(1e-15);
assert_eq!(
beta_grat(20.0, 0.0, 0.5, 0.5, 0.0, eps),
Err(BetaGratError::BzZero)
);
let w2 = beta_grat(100.0, 0.5, 0.5, 0.5, 0.0, eps).unwrap();
assert!(w2.is_finite());
}
#[test]
fn beta_inc_fpser_apser_dispatch() {
let (w, w1) = beta_inc(5.0, 1e-17, 0.5, 0.5);
assert!((w + w1 - 1.0).abs() < 1e-10);
let (w, w1) = beta_inc(1e-17, 5.0, 0.05, 0.95);
assert!((w + w1 - 1.0).abs() < 1e-10);
}
#[test]
fn poisson_inverse_cdf_high_quantile() {
let d = Poisson::new(4.0);
let p = 1.0 - 1e-12;
let s = d.inverse_cdf(p).unwrap();
assert!(d.cdf(s) >= p);
assert!(s > 0 && s < 100);
}
#[test]
fn negative_binomial_inverse_cdf_high_quantile() {
let d = NegativeBinomial::new(5, 0.05);
let p = 1.0 - 1e-10;
let s = d.inverse_cdf(p).unwrap();
assert!(d.cdf(s) >= p);
}
#[test]
fn fisher_snedecor_noncentral_pdf_basic() {
let d = FisherSnedecorNoncentral::new(4.0, 8.0, 2.5);
let x = 1.0;
let c = d.cdf(x);
assert!(c.is_finite() && (0.0..=1.0).contains(&c));
}
#[test]
fn gamma_log_does_not_regress() {
let v = gamma_log(50.0);
assert!((v - 144.56574394634488).abs() < 1e-10);
}
#[test]
fn gamma_inc_inv_small_a_label_30_early_return() {
let q = 1.0e-29;
let p = 1.0 - q;
let (r, _) = gamma_inc_inv(0.5, -1.0, p, q);
assert!(r.is_finite() && r > 0.0);
}
#[test]
fn gamma_inc_inv_amin_early_return() {
let (r, _) = gamma_inc_inv(1.0e7, -1.0, 0.5, 0.5);
assert!(r.is_finite() && r > 0.0);
assert!((r - 1.0e7).abs() < 1.0);
}
#[test]
fn gamma_inc_inv_initial_approx_small_b_bq_branch() {
let r = try_gamma_inc_inv(1.0e-9, -1.0, 1.0 - 1.0e-9, 1.0e-9);
match r {
Ok((x, _iters)) => assert!(x.is_finite() && x > 0.0),
Err(GammaIncInvError::NoSolution)
| Err(GammaIncInvError::IterationFailed)
| Err(GammaIncInvError::UncertainAccuracy { .. }) => {}
Err(e) => panic!("unexpected error {e:?}"),
}
}
#[test]
fn gamma_inc_inv_schroder_p_subnormal_p() {
let p = 1.0e-300;
let q = 1.0; let r = try_gamma_inc_inv(2.0, 1.0, p, q);
assert!(matches!(r, Err(GammaIncInvError::UncertainAccuracy { .. })));
}
#[test]
fn gamma_inc_inv_schroder_q_subnormal_q() {
let q = 1.0e-300;
let p = 1.0;
let r = try_gamma_inc_inv(2.0, 1.0, p, q);
assert!(matches!(r, Err(GammaIncInvError::UncertainAccuracy { .. })));
}
#[test]
fn gamma_inc_inv_schroder_p_amax_certify_fail() {
let a = 1.0e25;
let r = try_gamma_inc_inv(a, a, 0.5, 0.5);
assert!(matches!(r, Err(GammaIncInvError::UncertainAccuracy { .. })));
}
#[test]
fn gamma_inc_inv_schroder_q_amax_certify_fail() {
let a = 1.0e25;
let r = try_gamma_inc_inv(a, a, 0.7, 0.3);
assert!(matches!(r, Err(GammaIncInvError::UncertainAccuracy { .. })));
}
#[test]
fn gamma_inc_inv_schroder_p_saturates_to_zero() {
let r = try_gamma_inc_inv(10.0, 1.0e-100, 0.1, 0.9);
assert!(matches!(r, Err(GammaIncInvError::UncertainAccuracy { .. })));
}
#[test]
fn gamma_inc_inv_schroder_q_saturates_to_zero() {
let r = try_gamma_inc_inv(10.0, 1.0e10, 0.9, 0.1);
assert!(matches!(r, Err(GammaIncInvError::UncertainAccuracy { .. })));
}
#[test]
fn gamma_inc_inv_schroder_p_first_order_negative() {
let r = try_gamma_inc_inv(2.0, 10.0, 0.01, 0.99);
assert!(matches!(r, Err(GammaIncInvError::IterationFailed)));
}
#[test]
fn gamma_inc_inv_schroder_q_first_order_negative() {
let r = try_gamma_inc_inv(2.0, 0.01, 0.99, 0.01);
assert!(matches!(r, Err(GammaIncInvError::IterationFailed)));
}