use std::f64::consts::PI;
const LANCZOS_G: f64 = 7.0;
const LANCZOS_C: [f64; 9] = [
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7,
];
pub fn gamma(z: f64) -> f64 {
if z < 0.5 {
let sin_pi_z = (PI * z).sin();
if sin_pi_z.abs() < 1e-15 {
return f64::INFINITY; }
PI / (sin_pi_z * gamma(1.0 - z))
} else {
let z = z - 1.0;
let mut x = LANCZOS_C[0];
for i in 1..LANCZOS_C.len() {
x += LANCZOS_C[i] / (z + i as f64);
}
let t = z + LANCZOS_G + 0.5;
(2.0 * PI).sqrt() * t.powf(z + 0.5) * (-t).exp() * x
}
}
pub fn log_gamma(z: f64) -> f64 {
if z <= 0.0 {
return f64::NAN;
}
let g = gamma(z);
if g.is_infinite() || g <= 0.0 {
return f64::INFINITY;
}
g.ln()
}
pub fn beta(a: f64, b: f64) -> f64 {
gamma(a) * gamma(b) / gamma(a + b)
}
pub fn erf(x: f64) -> f64 {
if x.is_nan() {
return f64::NAN;
}
if x == 0.0 {
return 0.0;
}
if x.is_infinite() {
return if x > 0.0 { 1.0 } else { -1.0 };
}
let sign = if x < 0.0 { -1.0 } else { 1.0 };
sign * incomplete_gamma_p(0.5, x * x)
}
pub fn erfc(x: f64) -> f64 {
1.0 - erf(x)
}
pub fn sinc(x: f64) -> f64 {
if x.abs() < 1e-15 {
1.0
} else {
(PI * x).sin() / (PI * x)
}
}
pub fn sinc_unnorm(x: f64) -> f64 {
if x.abs() < 1e-15 {
1.0
} else {
x.sin() / x
}
}
pub fn bessel_j0(x: f64) -> f64 {
let ax = x.abs();
if ax < 8.0 {
let xsq = x * x / 4.0;
let mut term = 1.0_f64;
let mut sum = term;
for k in 1..60 {
let kf = k as f64;
term *= -xsq / (kf * kf);
sum += term;
if term.abs() < 1e-17 * sum.abs().max(1e-300) {
break;
}
}
sum
} else {
let z = 8.0 / ax;
let y = z * z;
let p = 1.0
+ y * (-0.1098628627e-2
+ y * (0.7464519654e-3
+ y * (-0.4724987825e-4
+ y * (0.2181196076e-5
+ y * (-0.6397653302e-7 + y * 0.9538904063e-9)))));
let q = -0.1562499995e-1
+ y * (0.1430484407e-3
+ y * (-0.4253339102e-4
+ y * (0.2493458662e-5
+ y * (-0.1248279047e-6 + y * 0.2860702546e-8))));
let xx = ax - PI / 4.0;
let result = (p * xx.cos() - z * q * xx.sin()) / ax.sqrt();
result.abs() * result.signum()
}
}
pub fn bessel_j1(x: f64) -> f64 {
let ax = x.abs();
let sign = if x < 0.0 { -1.0 } else { 1.0 };
if ax < 8.0 {
let xsq = x * x / 4.0;
let mut term = 0.5 * x; let mut sum = term;
for k in 1..60 {
let kf = k as f64;
term *= -xsq / (kf * (kf + 1.0));
sum += term;
if term.abs() < 1e-17 * sum.abs().max(1e-300) {
break;
}
}
sum
} else {
let z = 8.0 / ax;
let y = z * z;
let p = 1.0
+ y * (0.183105e-2
+ y * (-0.3516396496e-3
+ y * (0.2457529642e-4
+ y * (-0.2403370194e-5
+ y * 0.1058465960e-7))));
let q = 0.4687499995e-1
+ y * (-0.2002690873e-3
+ y * (0.4717512717e-4
+ y * (-0.9414049147e-6
+ y * (0.1344888788e-7 + y * -0.2199534093e-9))));
let xx = ax - 3.0 * PI / 4.0;
let result = (p * xx.cos() - z * q * xx.sin()) / ax.sqrt();
sign * result
}
}
pub fn bessel_jn(n: i32, x: f64) -> f64 {
if n == 0 {
return bessel_j0(x);
}
if n == 1 {
return bessel_j1(x);
}
if n < 0 {
let jn = bessel_jn(-n, x);
return if (-n) % 2 == 1 { -jn } else { jn };
}
if x.abs() < 1e-15 {
return 0.0;
}
let n_u = n as u32;
let half_x = x.abs() / 2.0;
if (n as f64) > x.abs() {
let mut term = 1.0 / factorial_u64(n_u);
let mut sum = term;
let xx = half_x * half_x;
for k in 1..200u32 {
term *= -xx / (k as f64 * (n_u + k) as f64);
let next = sum + term;
if (next - sum).abs() < 1e-18 * sum.abs().max(1e-300) {
break;
}
sum = next;
}
let mag = sum * half_x.powi(n);
if x < 0.0 && n_u % 2 == 1 { -mag } else { mag }
} else {
let tox = 2.0 / x;
let mut prev = bessel_j0(x);
let mut curr = bessel_j1(x);
for k in 1..n_u {
let next = (k as f64) * tox * curr - prev;
prev = curr;
curr = next;
if curr.abs() > 1e150 {
return 0.0;
}
}
if x < 0.0 && n_u % 2 == 1 { -curr } else { curr }
}
}
fn factorial_u64(n: u32) -> f64 {
let mut f = 1.0_f64;
for i in 2..=n {
f *= i as f64;
}
f
}
pub fn incomplete_gamma_p(a: f64, x: f64) -> f64 {
if x < 0.0 || a <= 0.0 {
return f64::NAN;
}
if x == 0.0 {
return 0.0;
}
let gln = log_gamma(a);
if x < a + 1.0 {
let mut term = 1.0 / a;
let mut sum = term;
for n in 1..200 {
term *= x / (a + n as f64);
sum += term;
if term.abs() < 1e-18 * sum.abs() {
break;
}
}
sum * x.powf(a) * (-x).exp() / gln.exp()
} else {
let tiny = 1e-30;
let mut b = x + 1.0 - a;
let mut c = 1.0 / tiny;
let mut d = 1.0 / b;
let mut f = d;
for n in 1..300 {
let an = -(n as f64) * (n as f64 - a);
b = b + 2.0;
d = an * d + b;
if d.abs() < tiny {
d = tiny;
}
c = b + an / c;
if c.abs() < tiny {
c = tiny;
}
d = 1.0 / d;
let delta = c * d;
f *= delta;
if (delta - 1.0).abs() < 1e-16 {
break;
}
}
let q = f * (-x + a * x.ln() - gln).exp();
1.0 - q
}
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: f64, b: f64, eps: f64) -> bool {
(a - b).abs() < eps
}
fn rel_close(a: f64, b: f64, tol: f64) -> bool {
if b.abs() < 1e-15 {
(a - b).abs() < tol
} else {
((a - b) / b).abs() < tol
}
}
#[test]
fn gamma_half() {
assert!(close(gamma(0.5), PI.sqrt(), 1e-10));
}
#[test]
fn gamma_integers() {
assert!(close(gamma(1.0), 1.0, 1e-10));
assert!(close(gamma(2.0), 1.0, 1e-10));
assert!(close(gamma(3.0), 2.0, 1e-10));
assert!(close(gamma(4.0), 6.0, 1e-10));
assert!(close(gamma(5.0), 24.0, 1e-10));
assert!(close(gamma(6.0), 120.0, 1e-10));
}
#[test]
fn gamma_reflection() {
let z = 0.3;
let product = gamma(z) * gamma(1.0 - z);
assert!(close(product, PI / (PI * z).sin(), 1e-9));
}
#[test]
fn beta_function() {
assert!(close(beta(1.0, 1.0), 1.0, 1e-10));
assert!(close(beta(2.0, 2.0), 1.0 / 6.0, 1e-10));
assert!(close(beta(0.5, 0.5), PI, 1e-9));
}
#[test]
fn erf_basic() {
assert!(close(erf(0.0), 0.0, 1e-15));
assert!(close(erf(0.5), 0.5204998778, 1e-8));
assert!(close(erf(1.0), 0.8427007929, 1e-8));
assert!(close(erf(2.0), 0.9953222650, 1e-8));
}
#[test]
fn erf_negative() {
assert!(close(erf(-1.0), -erf(1.0), 1e-14));
assert!(close(erf(-0.5), -erf(0.5), 1e-14));
}
#[test]
fn erfc_basic() {
assert!(close(erfc(0.0), 1.0, 1e-15));
assert!(close(erfc(1.0), 0.1572992071, 1e-8));
for &x in &[0.1, 0.5, 1.0, 2.0, 3.0] {
assert!(close(erfc(x), 1.0 - erf(x), 1e-12));
}
}
#[test]
fn erf_large() {
assert!(close(erf(f64::INFINITY), 1.0, 1e-15));
assert!(close(erf(f64::NEG_INFINITY), -1.0, 1e-15));
}
#[test]
fn sinc_basic() {
assert!(close(sinc(0.0), 1.0, 1e-15));
assert!(close(sinc(1.0), 0.0, 1e-15));
assert!(close(sinc(0.5), 2.0 / PI, 1e-14));
}
#[test]
fn sinc_unnorm_basic() {
assert!(close(sinc_unnorm(0.0), 1.0, 1e-15));
assert!(close(sinc_unnorm(PI), 0.0, 1e-14));
}
#[test]
fn log_gamma_positive() {
assert!(close(log_gamma(5.0), 24.0f64.ln(), 1e-10));
}
#[test]
fn incomplete_gamma_chi2() {
assert!(close(incomplete_gamma_p(1.0, 0.0), 0.0, 1e-15));
let p = incomplete_gamma_p(1.0, 2.0);
assert!(close(p, 1.0 - (-2.0f64).exp(), 1e-8));
}
#[test]
fn bessel_j0_basic() {
assert!(close(bessel_j0(0.0), 1.0, 1e-12));
assert!(close(bessel_j0(2.4048255576957727), 0.0, 1e-6));
assert!(close(bessel_j0(5.0), -0.17759677131434, 1e-8));
}
#[test]
fn bessel_j1_basic() {
assert!(close(bessel_j1(0.0), 0.0, 1e-12));
assert!(close(bessel_j1(2.0), 0.5767248077568736, 1e-8));
assert!(close(bessel_j1(3.8317059702075125), 0.0, 1e-6));
}
#[test]
fn bessel_jn_positive() {
for &x in &[0.5, 1.0, 2.0, 5.0, 10.0] {
assert!(close(bessel_jn(0, x), bessel_j0(x), 1e-10));
assert!(close(bessel_jn(1, x), bessel_j1(x), 1e-10));
}
assert!(close(bessel_jn(2, 5.0), 0.0465651, 1e-6));
assert!(close(bessel_jn(3, 2.0), 0.128943249, 1e-6));
let v = bessel_jn(10, 5.0);
assert!(v.is_finite());
}
#[test]
fn bessel_jn_negative() {
for &x in &[1.0, 3.0, 5.0] {
for n in [1, 2, 3, 4] {
let jn = bessel_jn(n, x);
let jneg = bessel_jn(-n, x);
let expected = if n % 2 == 1 { -jn } else { jn };
assert!(close(jneg, expected, 1e-10), "n={} x={}: {} vs {}", n, x, jneg, expected);
}
}
}
#[test]
fn bessel_jn_at_zero() {
assert!(close(bessel_jn(5, 0.0), 0.0, 1e-12));
}
#[test]
fn bessel_recurrence() {
for x in [1.0, 3.0, 5.0, 8.0] {
let jnm1 = bessel_jn(1, x);
let jn = bessel_jn(2, x);
let jnp1 = bessel_jn(3, x);
let lhs = jnp1;
let rhs = 2.0 * 2.0 / x * jn - jnm1;
assert!(close(lhs, rhs, 1e-8), "x={}: {} vs {}", x, lhs, rhs);
}
}
}