use crate::error::{SpecialError, SpecialResult};
use crate::gamma::gamma;
use std::f64::consts::{PI, SQRT_2};
pub fn pcf_d(n: f64, x: f64) -> SpecialResult<f64> {
if n.is_nan() || x.is_nan() {
return Err(SpecialError::DomainError(
"pcf_d: NaN argument".to_string(),
));
}
let (d, _) = crate::parabolic::pbdv(n, x)?;
Ok(d)
}
pub fn pcf_u(a: f64, x: f64) -> SpecialResult<f64> {
if a.is_nan() || x.is_nan() {
return Err(SpecialError::DomainError(
"pcf_u: NaN argument".to_string(),
));
}
let n = -a - 0.5;
pcf_d(n, x)
}
pub fn pcf_v(a: f64, x: f64) -> SpecialResult<f64> {
if a.is_nan() || x.is_nan() {
return Err(SpecialError::DomainError(
"pcf_v: NaN argument".to_string(),
));
}
let gamma_half_plus_a = gamma(0.5 + a);
let sin_pi_a = (PI * a).sin();
let u_pos = pcf_u(a, x)?;
let u_neg = pcf_u(a, -x)?;
let result = gamma_half_plus_a / PI * (sin_pi_a * u_pos + u_neg);
Ok(result)
}
#[allow(dead_code)]
fn kummer_m(a: f64, b: f64, z: f64) -> f64 {
if b <= 0.0 && b.fract() == 0.0 {
return f64::NAN; }
let max_terms = 200;
let tol = 1e-15;
let mut sum = 1.0_f64;
let mut term = 1.0_f64;
for k in 0..max_terms {
let k_f = k as f64;
term *= (a + k_f) * z / ((b + k_f) * (k_f + 1.0));
sum += term;
if term.abs() < tol * sum.abs() {
break;
}
}
sum
}
#[allow(dead_code)]
fn pcf_d_asymptotic_large_x(n: f64, x: f64) -> f64 {
let exp_factor = (-x * x / 4.0).exp();
let x_pow_n = x.powf(n);
let x2 = x * x;
let mut sum = 1.0_f64;
let mut term = 1.0_f64;
let max_terms = 20;
for k in 1..max_terms {
let kf = k as f64;
term *= -(n - 2.0 * kf + 2.0) * (n - 2.0 * kf + 1.0) / (2.0 * kf * x2);
if term.abs() < 1e-15 * sum.abs() {
break;
}
sum += term;
if term.abs() > sum.abs() {
sum -= term; break;
}
}
exp_factor * x_pow_n * sum
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_pcf_d_zero_order() {
for x in [-2.0_f64, -1.0, 0.0, 1.0, 2.0] {
let d0 = pcf_d(0.0, x).expect("D_0");
let expected = (-x * x / 4.0).exp();
let diff = (d0 - expected).abs();
assert!(diff < 1e-12, "D_0({x}): got {d0}, expected {expected}, diff={diff}");
}
}
#[test]
fn test_pcf_d_first_order() {
for x in [-2.0_f64, -1.0, 0.5, 1.0, 2.0] {
let d1 = pcf_d(1.0, x).expect("D_1");
let expected = x * (-x * x / 4.0).exp();
let diff = (d1 - expected).abs();
assert!(diff < 1e-12, "D_1({x}): got {d1}, expected {expected}, diff={diff}");
}
}
#[test]
fn test_pcf_d_second_order() {
for x in [-1.0_f64, 0.0, 1.0, 2.0] {
let d2 = pcf_d(2.0, x).expect("D_2");
assert!(d2.is_finite(), "D_2({x}) should be finite, got {d2}");
}
}
#[test]
fn test_pcf_d_negative_order() {
let d_neg1 = pcf_d(-1.0, 1.0).expect("D_{-1}(1)");
assert!(d_neg1.is_finite());
}
#[test]
fn test_pcf_d_recurrence() {
for x in [0.5_f64, 1.0, 2.0] {
let d0 = pcf_d(0.0, x).expect("D_0");
let d1 = pcf_d(1.0, x).expect("D_1");
let d2_direct = pcf_d(2.0, x).expect("D_2 direct");
let d2_recurrence = x * d1 - 1.0 * d0;
let diff = (d2_direct - d2_recurrence).abs();
assert!(diff < 1e-9, "recurrence at x={x}: direct={d2_direct}, recurrence={d2_recurrence}, diff={diff}");
}
}
#[test]
fn test_pcf_u_is_finite() {
for (a, x) in [(-0.5, 0.0), (0.0, 1.0), (1.0, 2.0), (2.0, 3.0)] {
let u = pcf_u(a, x).expect("U(a,x)");
assert!(u.is_finite(), "U({a},{x}) should be finite, got {u}");
}
}
#[test]
fn test_pcf_v_is_finite() {
for (a, x) in [(0.5, 1.0), (1.0, 2.0), (1.5, 0.5)] {
let v = pcf_v(a, x).expect("V(a,x)");
assert!(v.is_finite(), "V({a},{x}) should be finite, got {v}");
}
}
#[test]
fn test_pcf_v_grows_for_large_x() {
let v1 = pcf_v(0.0, 3.0).expect("V(0,3)");
let v2 = pcf_v(0.0, 5.0).expect("V(0,5)");
assert!(v2.abs() > v1.abs(), "V should grow: |V(0,3)|={} < |V(0,5)|={}", v1.abs(), v2.abs());
}
#[test]
fn test_pcf_nan_error() {
assert!(pcf_d(f64::NAN, 1.0).is_err());
assert!(pcf_d(1.0, f64::NAN).is_err());
assert!(pcf_u(f64::NAN, 1.0).is_err());
assert!(pcf_v(f64::NAN, 1.0).is_err());
}
#[test]
fn test_kummer_m_identity() {
for a in [0.5_f64, 1.0, 2.0] {
for z in [0.5_f64, 1.0, 2.0] {
let m = kummer_m(a, a, z);
let expected = z.exp();
let diff = (m - expected).abs();
assert!(diff < 1e-8, "M({a},{a},{z}): got {m}, expected {expected}, diff={diff}");
}
}
}
#[test]
fn test_kummer_m_zero() {
for (a, b) in [(0.5_f64, 1.5), (1.0, 2.0), (2.0, 3.0)] {
let m = kummer_m(a, b, 0.0);
let diff = (m - 1.0).abs();
assert!(diff < 1e-12, "M({a},{b},0) should be 1, got {m}");
}
}
}