use std::f64;
use std::f64::consts::PI;
const SQPIO2: f64 = 1.253_314_137_315_500_3;
const MAX_ITER: usize = 5000;
fn evalpoly(x: f64, coeffs: &[f64]) -> f64 {
let mut result = 0.0;
for &coeff in coeffs.iter().rev() {
result = result * x + coeff;
}
result
}
fn sinpi(x: f64) -> f64 {
(PI * x).sin()
}
pub fn gamma_func(x: f64) -> f64 {
let mut x = x;
let mut s = 0.0;
if x < 0.0 {
s = sinpi(x);
if s == 0.0 {
panic!("NaN result for non-NaN input.");
}
x = -x; s *= x;
}
if !x.is_finite() {
return x;
}
if x > 11.5 {
let mut w = 1.0 / x;
let coefs = [
1.0,
8.333_333_333_333_331e-2,
3.472_222_222_230_075e-3,
-2.681_327_161_876_304_3e-3,
-2.294_719_747_873_185_4e-4,
7.840_334_842_744_753e-4,
6.989_332_260_623_193e-5,
-5.950_237_554_056_33e-4,
-2.363_848_809_501_759e-5,
7.147_391_378_143_611e-4,
];
w = evalpoly(w, &coefs);
let v = x.powf(0.5 * x - 0.25);
let res = SQPIO2 * v * (v / x.exp()) * w;
return if x < 0.0 { PI / (res * s) } else { res };
}
let p = [
1.0,
8.378_004_301_573_126e-1,
3.629_515_436_640_239_3e-1,
1.113_062_816_019_361_6e-1,
2.385_363_243_461_108_3e-2,
4.092_666_828_394_036e-3,
4.542_931_960_608_009_3e-4,
4.212_760_487_471_622e-5,
];
let q = [
1.0,
4.150_160_950_588_455_7e-1,
-2.243_510_905_670_329_2e-1,
-4.633_887_671_244_534e-2,
2.773_706_565_840_073e-2,
-7.955_933_682_494_738e-4,
-1.237_799_246_653_152_3e-3,
2.346_584_059_160_635e-4,
-1.397_148_517_476_170_5e-5,
];
let mut z = 1.0;
while x >= 3.0 {
x -= 1.0;
z *= x;
}
while x < 0.0 {
z /= x;
x += 1.0;
}
while x < 2.0 {
z /= x;
x += 1.0;
}
if x == 2.0 {
return z;
}
x -= 2.0;
let p_val = evalpoly(x, &p);
let q_val = evalpoly(x, &q);
z * p_val / q_val
}
pub fn cyl_bessel_j(nu: f64, x: f64) -> f64 {
let eps = f64::EPSILON;
let mut _sum = 0.0;
let mut term = (x / 2.0).powf(nu) / gamma_func(nu + 1.0);
_sum = term;
for m in 1..1000 {
term *= -(x * x / 4.0) / (m as f64 * (nu + m as f64));
_sum += term;
if term.abs() < _sum.abs() * eps {
break;
}
}
_sum
}
fn spherical_bessel_j_generic(nu: f64, x: f64) -> f64 {
SQPIO2 * cyl_bessel_j(nu + 0.5, x) / x.sqrt()
}
fn spherical_bessel_j_small_args(nu: f64, x: f64) -> f64 {
if x == 0.0 {
return if nu == 0.0 { 1.0 } else { 0.0 };
}
let x2 = (x * x) / 4.0;
let coef = [
1.0,
-1.0 / (1.5 + nu), -1.0 / (5.0 + nu),
-1.0 / ((21.0 / 2.0) + nu), -1.0 / (18.0 + nu),
];
let a = SQPIO2 / (gamma_func(1.5 + nu) * 2.0_f64.powf(nu + 0.5));
x.powf(nu) * a * evalpoly(x2, &coef)
}
fn spherical_bessel_j_small_args_cutoff(nu: f64, x: f64) -> bool {
(x * x) / (4.0 * nu + 110.0) < f64::EPSILON
}
fn bessel_j_ratio_jnu_jnum1(n: f64, x: f64) -> f64 {
let xinv = 1.0 / x;
let xinv2 = 2.0 * xinv;
let mut d = x / (2.0 * n);
let mut a = d;
let mut h = a;
let mut b = (2.0 * n + 2.0) * xinv;
for _i in 0..MAX_ITER {
d = 1.0 / (b - d);
a *= b * d - 1.0;
h += a;
b += xinv2;
if (a / h).abs() <= f64::EPSILON {
break;
}
}
h
}
fn spherical_bessel_y_forward_recurrence(nu: i32, x: f64) -> (f64, f64) {
let xinv = 1.0 / x;
let s = x.sin();
let c = x.cos();
let mut s_y0 = -c * xinv;
let mut s_y1 = xinv * (s_y0 - s);
let mut nu_start = 1.0;
while nu_start < nu as f64 + 0.5 {
let temp = s_y1;
s_y1 = (2.0 * nu_start + 1.0) * xinv * s_y1 - s_y0;
s_y0 = temp;
nu_start += 1.0;
}
(s_y0, s_y1)
}
fn spherical_bessel_j_recurrence(nu: i32, x: f64) -> f64 {
if x >= nu as f64 {
let xinv = 1.0 / x;
let s = x.sin();
let c = x.cos();
let mut s_j0 = s * xinv;
let mut s_j1 = (s_j0 - c) * xinv;
let mut nu_start = 1.0;
while nu_start < nu as f64 + 0.5 {
let temp = s_j1;
s_j1 = (2.0 * nu_start + 1.0) * xinv * s_j1 - s_j0;
s_j0 = temp;
nu_start += 1.0;
}
s_j0
} else {
let (s_ynm1, s_yn) = spherical_bessel_y_forward_recurrence(nu, x);
let h = bessel_j_ratio_jnu_jnum1(nu as f64 + 1.5, x);
1.0 / (x * x * (h * s_ynm1 - s_yn))
}
}
fn spherical_bessel_j_positive_args(nu: i32, x: f64) -> f64 {
if spherical_bessel_j_small_args_cutoff(nu as f64, x) {
spherical_bessel_j_small_args(nu as f64, x)
} else if (x >= nu as f64 && nu < 250) || (x < nu as f64 && nu < 60) {
spherical_bessel_j_recurrence(nu, x)
} else {
spherical_bessel_j_generic(nu as f64, x)
}
}
pub fn spherical_bessel_j(n: i32, x: f64) -> f64 {
if x < 0.0 {
panic!("sphericalBesselJ requires non-negative x");
}
if n < 0 {
let result = spherical_bessel_j_positive_args(-n, x);
if n % 2 == 0 { result } else { -result }
} else {
spherical_bessel_j_positive_args(n, x)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gamma_function() {
assert!((gamma_func(1.0) - 1.0).abs() < 1e-10);
assert!((gamma_func(2.0) - 1.0).abs() < 1e-10);
assert!((gamma_func(3.0) - 2.0).abs() < 1e-10);
assert!((gamma_func(4.0) - 6.0).abs() < 1e-10);
assert!((gamma_func(0.5) - 1.7724538509055159).abs() < 1e-10); }
#[test]
fn test_cylindrical_bessel_j() {
let j0_1 = cyl_bessel_j(0.0, 1.0);
let expected_j0_1 = 0.765_197_686_557_966_6;
assert!((j0_1 - expected_j0_1).abs() < 1e-10);
let j1_1 = cyl_bessel_j(1.0, 1.0);
let expected_j1_1 = 0.440_050_585_744_933_5;
assert!((j1_1 - expected_j1_1).abs() < 1e-10);
}
#[test]
fn test_spherical_bessel_j_basic() {
let x = 1.0;
let j0 = spherical_bessel_j(0, x);
let expected_j0 = x.sin() / x;
println!("j_0({}) = {}, expected = {}", x, j0, expected_j0);
assert!((j0 - expected_j0).abs() < 1e-10);
let j1 = spherical_bessel_j(1, x);
let expected_j1 = x.sin() / (x * x) - x.cos() / x;
println!("j_1({}) = {}, expected = {}", x, j1, expected_j1);
assert!((j1 - expected_j1).abs() < 1e-10);
let j0_zero = spherical_bessel_j(0, 0.0);
println!("j_0(0) = {}, expected = 1.0", j0_zero);
assert!((j0_zero - 1.0).abs() < 1e-10);
let j1_zero = spherical_bessel_j(1, 0.0);
println!("j_1(0) = {}, expected = 0.0", j1_zero);
assert!(j1_zero.abs() < 1e-10);
}
#[test]
fn test_spherical_bessel_j_various_values() {
let test_cases = [
(0, 0.1, 0.9983341664682815),
(0, 0.5, 0.958_851_077_208_406),
(0, 1.0, 0.8414709848078965),
(0, 2.0, 0.4546487134128409),
(0, 5.0, -0.1917848549326277),
(1, 0.1, 0.0333000128900053),
(1, 0.5, 0.1625370306360665),
(1, 1.0, 0.3011686789397568),
(1, 2.0, 0.43539777497999166),
(1, 5.0, -0.0950894080791708),
];
for (n, x, expected) in test_cases {
let result = spherical_bessel_j(n, x);
println!(
"j_{}({}) = {}, expected = {}, diff = {}",
n,
x,
result,
expected,
(result - expected).abs()
);
assert!(
result.is_finite(),
"j_{}({}) should be finite, got {}",
n,
x,
result
);
if (result - expected).abs() > 1e-6 {
println!(
"WARNING: j_{}({}) accuracy issue: got {}, expected {}, diff = {}",
n,
x,
result,
expected,
(result - expected).abs()
);
}
}
}
#[test]
fn test_debug_spherical_bessel_j() {
println!("=== Debug j_1(0.1) ===");
let x = 0.1;
let n = 1;
let cutoff = spherical_bessel_j_small_args_cutoff(n as f64, x);
println!("small_args_cutoff: {}", cutoff);
if cutoff {
let small_result = spherical_bessel_j_small_args(n as f64, x);
println!("small_args result: {}", small_result);
}
let recurrence_result = spherical_bessel_j_recurrence(n, x);
println!("recurrence result: {}", recurrence_result);
let generic_result = spherical_bessel_j_generic(n as f64, x);
println!("generic result: {}", generic_result);
let final_result = spherical_bessel_j(n, x);
println!("final result: {}", final_result);
let expected = x.sin() / (x * x) - x.cos() / x;
println!("expected (sin(x)/x^2 - cos(x)/x): {}", expected);
}
#[test]
fn test_spherical_bessel_j_large_values() {
let large_x = 100.0;
let j0_large = spherical_bessel_j(0, large_x);
println!("j_0({}) = {}", large_x, j0_large);
assert!(j0_large.is_finite());
let j1_large = spherical_bessel_j(1, large_x);
println!("j_1({}) = {}", large_x, j1_large);
assert!(j1_large.is_finite());
}
#[test]
fn test_spherical_bessel_j_cpp_style_high_order() {
let refs = [
0.8414709848078965,
0.30116867893975674,
0.06203505201137386,
0.009006581117112517,
0.0010110158084137527,
9.256115861125818e-5,
7.156936310087086e-6,
4.790134198739489e-7,
2.82649880221473e-8,
1.4913765025551456e-9,
7.116552640047314e-11,
3.09955185479008e-12,
1.2416625969871055e-13,
4.604637677683788e-15,
1.5895759875169764e-16,
5.1326861154437626e-18,
];
let x = 1.0;
for (l, &expected) in refs.iter().enumerate() {
let expected: f64 = expected;
let result = spherical_bessel_j(l as i32, x);
let relative_tolerance = 1e-6;
let absolute_tolerance = 1e-12;
if expected.abs() > absolute_tolerance {
let relative_error = (result - expected).abs() / expected.abs();
assert!(
relative_error <= relative_tolerance,
"j_{}({}) relative error too large: {} > {}",
l,
x,
relative_error,
relative_tolerance
);
} else {
assert!(
(result - expected).abs() <= absolute_tolerance,
"j_{}({}) absolute error too large: {} > {}",
l,
x,
(result - expected).abs(),
absolute_tolerance
);
}
assert!(
result.is_finite(),
"j_{}({}) should be finite, got {}",
l,
x,
result
);
}
}
#[test]
fn test_spherical_bessel_j_zero_argument() {
let j0_zero = spherical_bessel_j(0, 0.0);
assert!(
(j0_zero - 1.0).abs() < 1e-15,
"j_0(0) should be 1, got {}",
j0_zero
);
for n in 1..=10 {
let jn_zero = spherical_bessel_j(n, 0.0);
assert!(
jn_zero.abs() < 1e-15,
"j_{}(0) should be 0, got {}",
n,
jn_zero
);
}
}
#[test]
fn test_spherical_bessel_j_negative_orders() {
for n in -5..0 {
let result_neg = spherical_bessel_j(n, 1.0);
let result_pos = spherical_bessel_j(-n, 1.0);
let expected = if n % 2 == 0 { result_pos } else { -result_pos };
assert!(
(result_neg - expected).abs() < 1e-15,
"j_{}(1.0) should be {} for negative n, got {}",
n,
expected,
result_neg
);
}
}
#[test]
fn test_spherical_bessel_j_small_arguments() {
let small_x_values = [1e-10, 1e-8, 1e-6, 1e-4, 1e-2];
for &x in &small_x_values {
for n in 0..=5 {
let result = spherical_bessel_j(n, x);
assert!(result.is_finite(), "j_{}({}) should be finite", n, x);
if x < 1e-6 && n < 3 {
let expected_approx = x.powi(n) / double_factorial(2 * n + 1);
let relative_error = (result - expected_approx).abs() / expected_approx.abs();
assert!(
relative_error < 1e-6,
"j_{}({}) small argument approximation failed: relative error = {}",
n,
x,
relative_error
);
}
}
}
}
#[test]
fn test_spherical_bessel_j_large_arguments() {
let large_x_values = [10.0, 50.0, 100.0, 500.0];
for &x in &large_x_values {
for n in 0..=3 {
let result = spherical_bessel_j(n, x);
assert!(result.is_finite(), "j_{}({}) should be finite", n, x);
let expected_approx = (x - (n as f64) * PI / 2.0).sin() / x;
let relative_error =
(result - expected_approx).abs() / expected_approx.abs().max(1e-10);
if x > 50.0 && relative_error > 1e-2 {
println!(
"WARNING: j_{}({}) large argument approximation: got {}, expected ≈ {}, relative error = {}",
n, x, result, expected_approx, relative_error
);
}
}
}
}
fn double_factorial(n: i32) -> f64 {
if n <= 0 {
1.0
} else if n % 2 == 0 {
let half_n = n / 2;
2.0_f64.powi(half_n) * factorial(half_n)
} else {
let half_n_minus_1 = (n - 1) / 2;
factorial(n) / (2.0_f64.powi(half_n_minus_1) * factorial(half_n_minus_1))
}
}
fn factorial(n: i32) -> f64 {
if n <= 1 {
1.0
} else {
let mut result = 1.0;
for i in 2..=n {
result *= i as f64;
}
result
}
}
}