1use nereids_core::constants::NEAR_ZERO_FLOOR;
19
20pub fn penetrability(l: u32, rho: f64) -> f64 {
27 let rho2 = rho * rho;
28 match l {
29 0 => rho,
30 1 => {
31 let denom = 1.0 + rho2;
32 rho2 * rho / denom
33 }
34 2 => {
35 let rho4 = rho2 * rho2;
36 let denom = 9.0 + 3.0 * rho2 + rho4;
37 rho4 * rho / denom
38 }
39 3 => {
40 let rho4 = rho2 * rho2;
41 let rho6 = rho4 * rho2;
42 let denom = 225.0 + 45.0 * rho2 + 6.0 * rho4 + rho6;
43 rho6 * rho / denom
44 }
45 4 => {
46 let rho4 = rho2 * rho2;
47 let rho6 = rho4 * rho2;
48 let rho8 = rho4 * rho4;
49 let denom = 11025.0 + 1575.0 * rho2 + 135.0 * rho4 + 10.0 * rho6 + rho8;
50 rho8 * rho / denom
51 }
52 _ => penetrability_general(l, rho),
53 }
54}
55
56pub fn shift_factor(l: u32, rho: f64) -> f64 {
63 let rho2 = rho * rho;
64 match l {
65 0 => 0.0,
66 1 => {
67 let denom = 1.0 + rho2;
68 -1.0 / denom
69 }
70 2 => {
71 let rho4 = rho2 * rho2;
72 let denom = 9.0 + 3.0 * rho2 + rho4;
73 -(18.0 + 3.0 * rho2) / denom
74 }
75 3 => {
76 let rho4 = rho2 * rho2;
77 let rho6 = rho4 * rho2;
78 let denom = 225.0 + 45.0 * rho2 + 6.0 * rho4 + rho6;
79 -(675.0 + 90.0 * rho2 + 6.0 * rho4) / denom
80 }
81 4 => {
82 let rho4 = rho2 * rho2;
83 let rho6 = rho4 * rho2;
84 let rho8 = rho4 * rho4;
85 let denom = 11025.0 + 1575.0 * rho2 + 135.0 * rho4 + 10.0 * rho6 + rho8;
86 -(44100.0 + 4725.0 * rho2 + 270.0 * rho4 + 10.0 * rho6) / denom
87 }
88 _ => shift_factor_general(l, rho),
89 }
90}
91
92pub fn phase_shift(l: u32, rho: f64) -> f64 {
96 let rho2 = rho * rho;
97 match l {
98 0 => rho,
99 1 => rho - rho.atan(),
100 2 => rho - (3.0 * rho / (3.0 - rho2)).atan(),
101 3 => {
102 let num = rho * (15.0 - rho2);
103 let den = 15.0 - 6.0 * rho2;
104 rho - (num / den).atan()
105 }
106 4 => {
107 let rho4 = rho2 * rho2;
108 let num = rho * (105.0 - 10.0 * rho2);
109 let den = 105.0 - 45.0 * rho2 + rho4;
110 rho - (num / den).atan()
111 }
112 _ => phase_shift_general(l, rho),
113 }
114}
115
116pub fn shift_factor_closed(l: u32, kappa: f64) -> f64 {
136 let k2 = kappa * kappa;
138 match l {
139 0 => 0.0,
140 1 => -1.0 / (1.0 - k2),
141 2 => {
142 let k4 = k2 * k2;
143 let denom = 9.0 - 3.0 * k2 + k4;
144 -(18.0 - 3.0 * k2) / denom
145 }
146 3 => {
147 let k4 = k2 * k2;
148 let k6 = k4 * k2;
149 let denom = 225.0 - 45.0 * k2 + 6.0 * k4 - k6;
150 -(675.0 - 90.0 * k2 + 6.0 * k4) / denom
151 }
152 4 => {
153 let k4 = k2 * k2;
154 let k6 = k4 * k2;
155 let k8 = k4 * k4;
156 let denom = 11025.0 - 1575.0 * k2 + 135.0 * k4 - 10.0 * k6 + k8;
157 -(44100.0 - 4725.0 * k2 + 270.0 * k4 - 10.0 * k6) / denom
158 }
159 _ => shift_factor_closed_general(l, kappa),
160 }
161}
162
163fn shift_factor_closed_general(l: u32, kappa: f64) -> f64 {
170 if kappa < NEAR_ZERO_FLOOR {
171 return 0.0;
172 }
173
174 let (f, g) = bessel_fg_imaginary(l, kappa);
175 let h = kappa * 1e-6 + 1e-12;
176 let (fp, gp) = bessel_fg_imaginary(l, kappa + h);
177 let (fm, gm) = bessel_fg_imaginary(l, kappa - h);
178
179 let df_re = (fp.0 - fm.0) / (2.0 * h);
181 let df_im = (fp.1 - fm.1) / (2.0 * h);
182 let dg_re = (gp.0 - gm.0) / (2.0 * h);
183 let dg_im = (gp.1 - gm.1) / (2.0 * h);
184
185 let num_im = (f.1 * df_im - f.0 * df_re) + (g.1 * dg_im - g.0 * dg_re);
190 let numerator = -kappa * num_im;
191
192 let denom = f.0 * f.0 - f.1 * f.1 + g.0 * g.0 - g.1 * g.1;
194 if denom.abs() < NEAR_ZERO_FLOOR {
195 return 0.0;
196 }
197 numerator / denom
198}
199
200fn bessel_fg_imaginary(l: u32, kappa: f64) -> ((f64, f64), (f64, f64)) {
207 let sh = kappa.sinh();
208 let ch = kappa.cosh();
209 let inv_k = 1.0 / kappa;
210
211 let mut f_prev = (0.0_f64, sh); let mut g_prev = (ch, 0.0_f64); if l == 0 {
215 return (f_prev, g_prev);
216 }
217
218 let mut f_curr = (sh * inv_k - ch, 0.0);
221 let mut g_curr = (0.0, sh - ch * inv_k);
222
223 if l == 1 {
224 return (f_curr, g_curr);
225 }
226
227 for n in 1..(l as usize) {
230 let a = -((2 * n + 1) as f64) * inv_k; let f_next = (-a * f_curr.1 - f_prev.0, a * f_curr.0 - f_prev.1);
232 let g_next = (-a * g_curr.1 - g_prev.0, a * g_curr.0 - g_prev.1);
233 f_prev = f_curr;
234 g_prev = g_curr;
235 f_curr = f_next;
236 g_curr = g_next;
237 }
238
239 (f_curr, g_curr)
240}
241
242#[cfg_attr(
249 not(test),
250 expect(dead_code, reason = "reserved for future width-conversion utilities")
251)]
252pub(crate) fn penetrability_derivative(l: u32, rho: f64) -> f64 {
253 let rho2 = rho * rho;
254 match l {
255 0 => 1.0,
256 1 => {
257 let denom = 1.0 + rho2;
258 rho2 * (3.0 + rho2) / (denom * denom)
259 }
260 2 => {
261 let rho4 = rho2 * rho2;
262 let denom = 9.0 + 3.0 * rho2 + rho4;
263 rho4 * (45.0 + 9.0 * rho2 + rho4) / (denom * denom)
264 }
265 3 => {
266 let rho4 = rho2 * rho2;
267 let rho6 = rho4 * rho2;
268 let denom = 225.0 + 45.0 * rho2 + 6.0 * rho4 + rho6;
269 rho6 * (1575.0 + 225.0 * rho2 + 18.0 * rho4 + rho6) / (denom * denom)
272 }
273 _ => {
274 let h = rho * 1e-6 + 1e-12;
276 (penetrability(l, rho + h) - penetrability(l, rho - h)) / (2.0 * h)
277 }
278 }
279}
280
281fn penetrability_general(l: u32, rho: f64) -> f64 {
291 let (fl, gl) = bessel_fg(l, rho);
296 rho / (fl * fl + gl * gl)
297}
298
299fn shift_factor_general(l: u32, rho: f64) -> f64 {
301 let (fl, gl) = bessel_fg(l, rho);
302 let denom = fl * fl + gl * gl;
303 let h = rho * 1e-6 + 1e-12;
309 let (fl_p, gl_p) = bessel_fg(l, rho + h);
310 let (fl_m, gl_m) = bessel_fg(l, rho - h);
311 let p_p = (rho + h) / (fl_p * fl_p + gl_p * gl_p);
312 let p_m = (rho - h) / (fl_m * fl_m + gl_m * gl_m);
313 let _dp_drho = (p_p - p_m) / (2.0 * h);
314 let dfl = (fl_p - fl_m) / (2.0 * h);
317 let dgl = (gl_p - gl_m) / (2.0 * h);
318 rho * (fl * dfl + gl * dgl) / denom
319}
320
321fn phase_shift_general(l: u32, rho: f64) -> f64 {
323 let (fl, gl) = bessel_fg(l, rho);
324 fl.atan2(gl)
325}
326
327fn bessel_fg(l: u32, rho: f64) -> (f64, f64) {
331 if rho.abs() < NEAR_ZERO_FLOOR {
336 return if l == 0 {
337 (0.0, -1.0)
338 } else {
339 (0.0, f64::NEG_INFINITY)
340 };
341 }
342
343 let mut f_prev = rho.sin();
347 let mut g_prev = rho.cos();
348
349 if l == 0 {
350 return (f_prev, g_prev);
351 }
352
353 let mut f_curr = f_prev / rho - g_prev;
357 let mut g_curr = g_prev / rho + f_prev;
358
359 if l == 1 {
360 return (f_curr, g_curr);
361 }
362
363 for n in 1..(l as i64) {
367 let factor = (2 * n + 1) as f64 / rho;
368 let f_next = factor * f_curr - f_prev;
369 let g_next = factor * g_curr - g_prev;
370 f_prev = f_curr;
371 g_prev = g_curr;
372 f_curr = f_next;
373 g_curr = g_next;
374 }
375
376 (f_curr, g_curr)
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[test]
384 fn test_penetrability_l0() {
385 assert!((penetrability(0, 0.5) - 0.5).abs() < 1e-15);
387 assert!((penetrability(0, 1.0) - 1.0).abs() < 1e-15);
388 assert!((penetrability(0, 0.001) - 0.001).abs() < 1e-15);
389 }
390
391 #[test]
392 fn test_penetrability_l1() {
393 let rho = 0.5;
395 let expected = 0.5_f64.powi(3) / (1.0 + 0.25);
396 assert!((penetrability(1, rho) - expected).abs() < 1e-15);
397
398 let rho = 1.0;
399 let expected = 1.0 / 2.0;
400 assert!((penetrability(1, rho) - expected).abs() < 1e-15);
401 }
402
403 #[test]
404 fn test_penetrability_l2() {
405 let rho = 1.0;
407 let expected = 1.0 / (9.0 + 3.0 + 1.0);
408 assert!((penetrability(2, rho) - expected).abs() < 1e-15);
409 }
410
411 #[test]
412 fn test_shift_factor_l0() {
413 assert!((shift_factor(0, 0.5)).abs() < 1e-15);
415 assert!((shift_factor(0, 5.0)).abs() < 1e-15);
416 }
417
418 #[test]
419 fn test_shift_factor_l1() {
420 let rho = 1.0;
422 assert!((shift_factor(1, rho) - (-0.5)).abs() < 1e-15);
423 let rho = 0.0;
424 assert!((shift_factor(1, rho) - (-1.0)).abs() < 1e-15);
425 }
426
427 #[test]
428 fn test_phase_shift_l0() {
429 assert!((phase_shift(0, 0.5) - 0.5).abs() < 1e-15);
431 assert!((phase_shift(0, 1.0) - 1.0).abs() < 1e-15);
432 }
433
434 #[test]
435 fn test_phase_shift_l1() {
436 let rho = 1.0;
438 let expected = 1.0 - 1.0_f64.atan();
439 assert!((phase_shift(1, rho) - expected).abs() < 1e-14);
440 }
441
442 #[test]
443 fn test_phase_shift_small_rho() {
444 assert!((phase_shift(0, 0.01) - 0.01).abs() < 1e-10);
447 let expected = 0.01 - 0.01_f64.atan();
449 assert!((phase_shift(1, 0.01) - expected).abs() < 1e-15);
450 }
451
452 #[test]
453 fn test_penetrability_derivative_l0() {
454 assert!((penetrability_derivative(0, 1.0) - 1.0).abs() < 1e-15);
456 }
457
458 #[test]
459 fn test_penetrability_derivative_l1() {
460 let rho = 1.0;
462 let expected = 1.0 * (3.0 + 1.0) / (2.0 * 2.0);
463 assert!((penetrability_derivative(1, rho) - expected).abs() < 1e-14);
464 }
465
466 #[test]
467 fn test_penetrability_derivative_l2() {
468 let rho = 2.0;
470 let rho2 = rho * rho;
471 let rho4 = rho2 * rho2;
472 let denom = 9.0 + 3.0 * rho2 + rho4;
473 let expected = rho4 * (45.0 + 9.0 * rho2 + rho4) / (denom * denom);
474 assert!(
475 (penetrability_derivative(2, rho) - expected).abs() < 1e-14,
476 "L=2 derivative: got {}, expected {}",
477 penetrability_derivative(2, rho),
478 expected
479 );
480 }
481
482 #[test]
483 fn test_penetrability_derivative_l3() {
484 let rho = 1.5;
488 let rho2 = rho * rho;
489 let rho4 = rho2 * rho2;
490 let rho6 = rho4 * rho2;
491 let denom = 225.0 + 45.0 * rho2 + 6.0 * rho4 + rho6;
492 let expected = rho6 * (1575.0 + 225.0 * rho2 + 18.0 * rho4 + rho6) / (denom * denom);
493 assert!(
494 (penetrability_derivative(3, rho) - expected).abs() < 1e-14,
495 "L=3 derivative: got {}, expected {}",
496 penetrability_derivative(3, rho),
497 expected
498 );
499 }
500
501 #[test]
502 fn test_penetrability_derivative_l3_vs_numerical() {
503 let rho = 2.0;
506 let h = 1e-6;
507 let numerical = (penetrability(3, rho + h) - penetrability(3, rho - h)) / (2.0 * h);
508 let analytic = penetrability_derivative(3, rho);
509 assert!(
510 (analytic - numerical).abs() < 1e-6,
511 "L=3 analytic={}, numerical={}",
512 analytic,
513 numerical
514 );
515 }
516
517 #[test]
518 fn test_penetrability_derivative_l2_vs_numerical() {
519 let rho = 2.0;
521 let h = 1e-6;
522 let numerical = (penetrability(2, rho + h) - penetrability(2, rho - h)) / (2.0 * h);
523 let analytic = penetrability_derivative(2, rho);
524 assert!(
525 (analytic - numerical).abs() < 1e-6,
526 "L=2 analytic={}, numerical={}",
527 analytic,
528 numerical
529 );
530 }
531
532 #[test]
533 fn test_general_matches_explicit_l2() {
534 let rho = 2.0;
536 let p_explicit = penetrability(2, rho);
537 let p_general = penetrability_general(2, rho);
538 assert!(
539 (p_explicit - p_general).abs() < 1e-10,
540 "explicit={}, general={}",
541 p_explicit,
542 p_general
543 );
544 }
545
546 #[test]
549 fn test_shift_factor_closed_l0() {
550 assert_eq!(shift_factor_closed(0, 0.0), 0.0);
552 assert_eq!(shift_factor_closed(0, 2.0), 0.0);
553 }
554
555 #[test]
556 fn test_shift_factor_closed_l1_continuity() {
557 let s = shift_factor_closed(1, 1e-10);
560 assert!((s - (-1.0)).abs() < 1e-8, "got {s}");
561 }
562
563 #[test]
564 fn test_shift_factor_closed_l1_exact() {
565 let expected = -4.0 / 3.0;
567 let got = shift_factor_closed(1, 0.5);
568 assert!(
569 (got - expected).abs() < 1e-12,
570 "got {got}, expected {expected}"
571 );
572 }
573
574 #[test]
575 fn test_shift_factor_closed_l2_exact() {
576 let expected = -17.25 / 8.3125;
580 let got = shift_factor_closed(2, 0.5);
581 assert!(
582 (got - expected).abs() < 1e-12,
583 "got {got}, expected {expected}"
584 );
585 }
586
587 #[test]
588 fn test_shift_factor_closed_general_matches_explicit_l4() {
589 let kappa = 0.3;
591 let explicit = shift_factor_closed(4, kappa);
592 let general = shift_factor_closed_general(4, kappa);
593 assert!(
594 (explicit - general).abs() < 1e-8,
595 "l=4 kappa={kappa}: explicit={explicit}, general={general}"
596 );
597 }
598
599 #[test]
600 fn test_general_matches_explicit_l3() {
601 let rho = 1.5;
602 let p_explicit = penetrability(3, rho);
603 let p_general = penetrability_general(3, rho);
604 assert!(
605 (p_explicit - p_general).abs() < 1e-10,
606 "explicit={}, general={}",
607 p_explicit,
608 p_general
609 );
610 }
611}