use crate::{normpdf, nctcdf, gammaln};
pub fn nctpdf(x: f64, nu: f64, delta: f64) -> f64 {
if x.is_nan() || !(delta.is_finite() && nu > 0.0) {
return f64::NAN;
}
if nu > 1e11 {
let s = 1.0 - 1.0 / (4.0 * nu);
let d = (1.0 + x * x / (2.0 * nu)).sqrt();
return normpdf(x * s, delta, d);
}
if x == 0.0 {
let log_pdf = -0.5 * delta.powi(2)
- 0.5 * (std::f64::consts::PI * nu).ln()
+ gammaln(0.5 * (nu + 1.0))
- gammaln(0.5 * nu);
return log_pdf.exp();
}
if !x.is_finite() {
return 0.0;
}
if x < 0.0 {
let sqrt_term = x * ((nu + 2.0) / nu).sqrt();
let cdf1 = nctcdf(sqrt_term, nu + 2.0, delta, false); let cdf2 = nctcdf(x, nu, delta, false);
(nu / x) * (cdf1 - cdf2)
} else {
let sqrt_term = -x * ((nu + 2.0) / nu).sqrt();
let cdf1 = nctcdf(sqrt_term, nu + 2.0, -delta, false);
let cdf2 = nctcdf(-x, nu, -delta, false);
println!("nctcdf({:.16e}, {:.16e}, {:.16e}, false) = {:.16e}", sqrt_term, nu + 2.0, -delta, cdf1);
(-nu / x) * (cdf1 - cdf2)
}
}
#[cfg(test)]
mod tests {
use super::{nctpdf};
use crate::tpdf;
#[test]
fn nan_for_invalid_parameters() {
assert!(nctpdf(0.0, -1.0, 1.0).is_nan());
assert!(nctpdf(0.0, 1.0, f64::INFINITY).is_nan());
assert!(nctpdf(f64::NAN, 5.0, 2.0).is_nan());
assert!(nctpdf(1.0, 5.0, f64::NAN).is_nan());
}
#[test]
fn zero_at_infinity() {
assert_eq!(nctpdf(f64::INFINITY, 5.0, 1.0), 0.0);
assert_eq!(nctpdf(f64::NEG_INFINITY, 5.0, 1.0), 0.0);
assert_eq!(nctpdf(f64::INFINITY, 5.0, 0.0), 0.0);
}
#[test]
fn normal_approximation_triggers_for_large_nu() {
let y = nctpdf(0.5, 2e11, 1.0);
assert!(y.is_finite() && y >= 0.0);
}
#[test]
fn zero_special_case_gives_finite_value() {
let y = nctpdf(0.0, 5.0, 1.0);
println!("{:16e}", y);
assert!((y - 2.302430960093666e-01).abs() < 1e-12);
}
#[test]
fn test_nctpdf_central_t() {
let x = 0.5;
let nu = 10.0;
let p_nct = nctpdf(x, nu, 0.0);
let p_t = tpdf(x, nu);
assert!((p_nct - p_t).abs() < 1e-12);
}
#[test]
fn test_nctpdf_known_values() {
let tol = 1e-14; println!("{:16e}", nctpdf(1.0, 5.0, 1.0));
assert!((nctpdf(1.0, 5.0, 1.0) - 3.623248392337183e-01).abs() < tol);
assert!((nctpdf(2.0, 10.0, 2.0) - 3.556436303616300e-1).abs() < tol);
}
#[test]
fn test_nctpdf_symmetry() {
assert!((nctpdf(1.0, 5.0, 1.0) - nctpdf(-1.0, 5.0, -1.0)).abs() < 1e-12);
}
}