1use std::f64;
8use std::f64::consts::PI;
9
10const SQPIO2: f64 = 1.253_314_137_315_500_3;
12
13const MAX_ITER: usize = 5000;
15
16fn evalpoly(x: f64, coeffs: &[f64]) -> f64 {
18 let mut result = 0.0;
19 for &coeff in coeffs.iter().rev() {
20 result = result * x + coeff;
21 }
22 result
23}
24
25fn sinpi(x: f64) -> f64 {
27 (PI * x).sin()
28}
29
30pub fn gamma_func(x: f64) -> f64 {
34 let mut x = x;
35 let mut s = 0.0;
36
37 if x < 0.0 {
38 s = sinpi(x);
39 if s == 0.0 {
40 panic!("NaN result for non-NaN input.");
41 }
42 x = -x; s *= x;
44 }
45
46 if !x.is_finite() {
47 return x;
48 }
49
50 if x > 11.5 {
51 let mut w = 1.0 / x;
52 let coefs = [
53 1.0,
54 8.333_333_333_333_331e-2,
55 3.472_222_222_230_075e-3,
56 -2.681_327_161_876_304_3e-3,
57 -2.294_719_747_873_185_4e-4,
58 7.840_334_842_744_753e-4,
59 6.989_332_260_623_193e-5,
60 -5.950_237_554_056_33e-4,
61 -2.363_848_809_501_759e-5,
62 7.147_391_378_143_611e-4,
63 ];
64 w = evalpoly(w, &coefs);
65
66 let v = x.powf(0.5 * x - 0.25);
68 let res = SQPIO2 * v * (v / x.exp()) * w;
69
70 return if x < 0.0 { PI / (res * s) } else { res };
71 }
72
73 let p = [
74 1.0,
75 8.378_004_301_573_126e-1,
76 3.629_515_436_640_239_3e-1,
77 1.113_062_816_019_361_6e-1,
78 2.385_363_243_461_108_3e-2,
79 4.092_666_828_394_036e-3,
80 4.542_931_960_608_009_3e-4,
81 4.212_760_487_471_622e-5,
82 ];
83
84 let q = [
85 1.0,
86 4.150_160_950_588_455_7e-1,
87 -2.243_510_905_670_329_2e-1,
88 -4.633_887_671_244_534e-2,
89 2.773_706_565_840_073e-2,
90 -7.955_933_682_494_738e-4,
91 -1.237_799_246_653_152_3e-3,
92 2.346_584_059_160_635e-4,
93 -1.397_148_517_476_170_5e-5,
94 ];
95
96 let mut z = 1.0;
97 while x >= 3.0 {
98 x -= 1.0;
99 z *= x;
100 }
101
102 while x < 0.0 {
103 z /= x;
104 x += 1.0;
105 }
106
107 while x < 2.0 {
108 z /= x;
109 x += 1.0;
110 }
111
112 if x == 2.0 {
113 return z;
114 }
115
116 x -= 2.0;
117 let p_val = evalpoly(x, &p);
118 let q_val = evalpoly(x, &q);
119
120 z * p_val / q_val
121}
122
123pub fn cyl_bessel_j(nu: f64, x: f64) -> f64 {
128 let eps = f64::EPSILON;
129 let mut _sum = 0.0;
130 let mut term = (x / 2.0).powf(nu) / gamma_func(nu + 1.0);
131 _sum = term;
132
133 for m in 1..1000 {
134 term *= -(x * x / 4.0) / (m as f64 * (nu + m as f64));
135 _sum += term;
136 if term.abs() < _sum.abs() * eps {
137 break;
138 }
139 }
140
141 _sum
142}
143
144fn spherical_bessel_j_generic(nu: f64, x: f64) -> f64 {
147 SQPIO2 * cyl_bessel_j(nu + 0.5, x) / x.sqrt()
148}
149
150fn spherical_bessel_j_small_args(nu: f64, x: f64) -> f64 {
152 if x == 0.0 {
153 return if nu == 0.0 { 1.0 } else { 0.0 };
154 }
155
156 let x2 = (x * x) / 4.0;
157 let coef = [
158 1.0,
159 -1.0 / (1.5 + nu), -1.0 / (5.0 + nu),
161 -1.0 / ((21.0 / 2.0) + nu), -1.0 / (18.0 + nu),
163 ];
164
165 let a = SQPIO2 / (gamma_func(1.5 + nu) * 2.0_f64.powf(nu + 0.5));
166 x.powf(nu) * a * evalpoly(x2, &coef)
167}
168
169fn spherical_bessel_j_small_args_cutoff(nu: f64, x: f64) -> bool {
171 (x * x) / (4.0 * nu + 110.0) < f64::EPSILON
172}
173
174fn bessel_j_ratio_jnu_jnum1(n: f64, x: f64) -> f64 {
176 let xinv = 1.0 / x;
177 let xinv2 = 2.0 * xinv;
178 let mut d = x / (2.0 * n);
179 let mut a = d;
180 let mut h = a;
181 let mut b = (2.0 * n + 2.0) * xinv;
182
183 for _i in 0..MAX_ITER {
184 d = 1.0 / (b - d);
185 a *= b * d - 1.0;
186 h += a;
187 b += xinv2;
188
189 if (a / h).abs() <= f64::EPSILON {
190 break;
191 }
192 }
193
194 h
195}
196
197fn spherical_bessel_y_forward_recurrence(nu: i32, x: f64) -> (f64, f64) {
200 let xinv = 1.0 / x;
201 let s = x.sin();
202 let c = x.cos();
203 let mut s_y0 = -c * xinv;
204 let mut s_y1 = xinv * (s_y0 - s);
205 let mut nu_start = 1.0;
206
207 while nu_start < nu as f64 + 0.5 {
208 let temp = s_y1;
209 s_y1 = (2.0 * nu_start + 1.0) * xinv * s_y1 - s_y0;
210 s_y0 = temp;
211 nu_start += 1.0;
212 }
213
214 (s_y0, s_y1)
215}
216
217fn spherical_bessel_j_recurrence(nu: i32, x: f64) -> f64 {
219 if x >= nu as f64 {
220 let xinv = 1.0 / x;
221 let s = x.sin();
222 let c = x.cos();
223 let mut s_j0 = s * xinv;
224 let mut s_j1 = (s_j0 - c) * xinv;
225 let mut nu_start = 1.0;
226
227 while nu_start < nu as f64 + 0.5 {
228 let temp = s_j1;
229 s_j1 = (2.0 * nu_start + 1.0) * xinv * s_j1 - s_j0;
230 s_j0 = temp;
231 nu_start += 1.0;
232 }
233
234 s_j0
235 } else {
236 let (s_ynm1, s_yn) = spherical_bessel_y_forward_recurrence(nu, x);
239 let h = bessel_j_ratio_jnu_jnum1(nu as f64 + 1.5, x);
240 1.0 / (x * x * (h * s_ynm1 - s_yn))
241 }
242}
243
244fn spherical_bessel_j_positive_args(nu: i32, x: f64) -> f64 {
246 if spherical_bessel_j_small_args_cutoff(nu as f64, x) {
247 spherical_bessel_j_small_args(nu as f64, x)
248 } else if (x >= nu as f64 && nu < 250) || (x < nu as f64 && nu < 60) {
249 spherical_bessel_j_recurrence(nu, x)
251 } else {
252 spherical_bessel_j_generic(nu as f64, x)
253 }
254}
255
256pub fn spherical_bessel_j(n: i32, x: f64) -> f64 {
260 if x < 0.0 {
262 panic!("sphericalBesselJ requires non-negative x");
263 }
264
265 if n < 0 {
267 let result = spherical_bessel_j_positive_args(-n, x);
268 if n % 2 == 0 { result } else { -result }
269 } else {
270 spherical_bessel_j_positive_args(n, x)
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn test_gamma_function() {
280 assert!((gamma_func(1.0) - 1.0).abs() < 1e-10);
282 assert!((gamma_func(2.0) - 1.0).abs() < 1e-10);
283 assert!((gamma_func(3.0) - 2.0).abs() < 1e-10);
284 assert!((gamma_func(4.0) - 6.0).abs() < 1e-10);
285
286 assert!((gamma_func(0.5) - 1.7724538509055159).abs() < 1e-10); }
289
290 #[test]
291 fn test_cylindrical_bessel_j() {
292 let j0_1 = cyl_bessel_j(0.0, 1.0);
294 let expected_j0_1 = 0.765_197_686_557_966_6;
295 assert!((j0_1 - expected_j0_1).abs() < 1e-10);
296
297 let j1_1 = cyl_bessel_j(1.0, 1.0);
298 let expected_j1_1 = 0.440_050_585_744_933_5;
299 assert!((j1_1 - expected_j1_1).abs() < 1e-10);
300 }
301
302 #[test]
303 fn test_spherical_bessel_j_basic() {
304 let x = 1.0;
306 let j0 = spherical_bessel_j(0, x);
307 let expected_j0 = x.sin() / x;
308 println!("j_0({}) = {}, expected = {}", x, j0, expected_j0);
309 assert!((j0 - expected_j0).abs() < 1e-10);
310
311 let j1 = spherical_bessel_j(1, x);
313 let expected_j1 = x.sin() / (x * x) - x.cos() / x;
314 println!("j_1({}) = {}, expected = {}", x, j1, expected_j1);
315 assert!((j1 - expected_j1).abs() < 1e-10);
316
317 let j0_zero = spherical_bessel_j(0, 0.0);
319 println!("j_0(0) = {}, expected = 1.0", j0_zero);
320 assert!((j0_zero - 1.0).abs() < 1e-10);
321
322 let j1_zero = spherical_bessel_j(1, 0.0);
324 println!("j_1(0) = {}, expected = 0.0", j1_zero);
325 assert!(j1_zero.abs() < 1e-10);
326 }
327
328 #[test]
329 fn test_spherical_bessel_j_various_values() {
330 let test_cases = [
333 (0, 0.1, 0.9983341664682815),
334 (0, 0.5, 0.958_851_077_208_406),
335 (0, 1.0, 0.8414709848078965),
336 (0, 2.0, 0.4546487134128409),
337 (0, 5.0, -0.1917848549326277),
338 (1, 0.1, 0.0333000128900053),
340 (1, 0.5, 0.1625370306360665),
341 (1, 1.0, 0.3011686789397568),
342 (1, 2.0, 0.43539777497999166),
344 (1, 5.0, -0.0950894080791708),
346 ];
347
348 for (n, x, expected) in test_cases {
349 let result = spherical_bessel_j(n, x);
350 println!(
351 "j_{}({}) = {}, expected = {}, diff = {}",
352 n,
353 x,
354 result,
355 expected,
356 (result - expected).abs()
357 );
358
359 assert!(
361 result.is_finite(),
362 "j_{}({}) should be finite, got {}",
363 n,
364 x,
365 result
366 );
367
368 if (result - expected).abs() > 1e-6 {
370 println!(
371 "WARNING: j_{}({}) accuracy issue: got {}, expected {}, diff = {}",
372 n,
373 x,
374 result,
375 expected,
376 (result - expected).abs()
377 );
378 }
379 }
380 }
381
382 #[test]
383 fn test_debug_spherical_bessel_j() {
384 println!("=== Debug j_1(0.1) ===");
386 let x = 0.1;
387 let n = 1;
388
389 let cutoff = spherical_bessel_j_small_args_cutoff(n as f64, x);
391 println!("small_args_cutoff: {}", cutoff);
392
393 if cutoff {
394 let small_result = spherical_bessel_j_small_args(n as f64, x);
395 println!("small_args result: {}", small_result);
396 }
397
398 let recurrence_result = spherical_bessel_j_recurrence(n, x);
399 println!("recurrence result: {}", recurrence_result);
400
401 let generic_result = spherical_bessel_j_generic(n as f64, x);
402 println!("generic result: {}", generic_result);
403
404 let final_result = spherical_bessel_j(n, x);
405 println!("final result: {}", final_result);
406
407 let expected = x.sin() / (x * x) - x.cos() / x;
409 println!("expected (sin(x)/x^2 - cos(x)/x): {}", expected);
410 }
411
412 #[test]
413 fn test_spherical_bessel_j_large_values() {
414 let large_x = 100.0;
416 let j0_large = spherical_bessel_j(0, large_x);
417 println!("j_0({}) = {}", large_x, j0_large);
418 assert!(j0_large.is_finite());
419
420 let j1_large = spherical_bessel_j(1, large_x);
421 println!("j_1({}) = {}", large_x, j1_large);
422 assert!(j1_large.is_finite());
423 }
424
425 #[test]
426 fn test_spherical_bessel_j_cpp_style_high_order() {
427 let refs = [
432 0.8414709848078965,
433 0.30116867893975674,
434 0.06203505201137386,
435 0.009006581117112517,
436 0.0010110158084137527,
437 9.256115861125818e-5,
438 7.156936310087086e-6,
439 4.790134198739489e-7,
440 2.82649880221473e-8,
441 1.4913765025551456e-9,
442 7.116552640047314e-11,
443 3.09955185479008e-12,
444 1.2416625969871055e-13,
445 4.604637677683788e-15,
446 1.5895759875169764e-16,
447 5.1326861154437626e-18,
448 ];
449
450 let x = 1.0;
451 for (l, &expected) in refs.iter().enumerate() {
452 let expected: f64 = expected;
453 let result = spherical_bessel_j(l as i32, x);
454
455 let relative_tolerance = 1e-6;
457 let absolute_tolerance = 1e-12;
458
459 if expected.abs() > absolute_tolerance {
461 let relative_error = (result - expected).abs() / expected.abs();
462 assert!(
463 relative_error <= relative_tolerance,
464 "j_{}({}) relative error too large: {} > {}",
465 l,
466 x,
467 relative_error,
468 relative_tolerance
469 );
470 } else {
471 assert!(
473 (result - expected).abs() <= absolute_tolerance,
474 "j_{}({}) absolute error too large: {} > {}",
475 l,
476 x,
477 (result - expected).abs(),
478 absolute_tolerance
479 );
480 }
481
482 assert!(
484 result.is_finite(),
485 "j_{}({}) should be finite, got {}",
486 l,
487 x,
488 result
489 );
490 }
491 }
492
493 #[test]
494 fn test_spherical_bessel_j_zero_argument() {
495 let j0_zero = spherical_bessel_j(0, 0.0);
498 assert!(
499 (j0_zero - 1.0).abs() < 1e-15,
500 "j_0(0) should be 1, got {}",
501 j0_zero
502 );
503
504 for n in 1..=10 {
505 let jn_zero = spherical_bessel_j(n, 0.0);
506 assert!(
507 jn_zero.abs() < 1e-15,
508 "j_{}(0) should be 0, got {}",
509 n,
510 jn_zero
511 );
512 }
513 }
514
515 #[test]
516 fn test_spherical_bessel_j_negative_orders() {
517 for n in -5..0 {
519 let result_neg = spherical_bessel_j(n, 1.0);
520 let result_pos = spherical_bessel_j(-n, 1.0);
521 let expected = if n % 2 == 0 { result_pos } else { -result_pos };
522 assert!(
523 (result_neg - expected).abs() < 1e-15,
524 "j_{}(1.0) should be {} for negative n, got {}",
525 n,
526 expected,
527 result_neg
528 );
529 }
530 }
531
532 #[test]
533 fn test_spherical_bessel_j_small_arguments() {
534 let small_x_values = [1e-10, 1e-8, 1e-6, 1e-4, 1e-2];
536
537 for &x in &small_x_values {
538 for n in 0..=5 {
539 let result = spherical_bessel_j(n, x);
540 assert!(result.is_finite(), "j_{}({}) should be finite", n, x);
541
542 if x < 1e-6 && n < 3 {
544 let expected_approx = x.powi(n) / double_factorial(2 * n + 1);
545 let relative_error = (result - expected_approx).abs() / expected_approx.abs();
546 assert!(
547 relative_error < 1e-6,
548 "j_{}({}) small argument approximation failed: relative error = {}",
549 n,
550 x,
551 relative_error
552 );
553 }
554 }
555 }
556 }
557
558 #[test]
559 fn test_spherical_bessel_j_large_arguments() {
560 let large_x_values = [10.0, 50.0, 100.0, 500.0];
562
563 for &x in &large_x_values {
564 for n in 0..=3 {
565 let result = spherical_bessel_j(n, x);
566 assert!(result.is_finite(), "j_{}({}) should be finite", n, x);
567
568 let expected_approx = (x - (n as f64) * PI / 2.0).sin() / x;
570 let relative_error =
571 (result - expected_approx).abs() / expected_approx.abs().max(1e-10);
572
573 if x > 50.0 && relative_error > 1e-2 {
574 println!(
575 "WARNING: j_{}({}) large argument approximation: got {}, expected ≈ {}, relative error = {}",
576 n, x, result, expected_approx, relative_error
577 );
578 }
579 }
580 }
581 }
582
583 fn double_factorial(n: i32) -> f64 {
585 if n <= 0 {
586 1.0
587 } else if n % 2 == 0 {
588 let half_n = n / 2;
590 2.0_f64.powi(half_n) * factorial(half_n)
591 } else {
592 let half_n_minus_1 = (n - 1) / 2;
594 factorial(n) / (2.0_f64.powi(half_n_minus_1) * factorial(half_n_minus_1))
595 }
596 }
597
598 fn factorial(n: i32) -> f64 {
600 if n <= 1 {
601 1.0
602 } else {
603 let mut result = 1.0;
604 for i in 2..=n {
605 result *= i as f64;
606 }
607 result
608 }
609 }
610}