use num_complex::Complex64;
pub fn spherical_jn(x: f64, nmax: usize, nnmax: usize) -> Vec<f64> {
assert!(x > 0.0, "spherical_jn requires x > 0 (got {x})");
let ntot = nmax + nnmax;
let mut y = vec![0.0f64; ntot + 2];
y[ntot + 1] = 0.0;
y[ntot] = 1.0;
for n in (1..=ntot).rev() {
let nf = n as f64;
y[n - 1] = (2.0 * nf + 1.0) / x * y[n] - y[n + 1];
}
let j0_true = x.sin() / x;
let scale = j0_true / y[0];
let mut out = Vec::with_capacity(nmax + 1);
for n in 0..=nmax {
out.push(y[n] * scale);
}
out
}
pub fn spherical_yn(x: f64, nmax: usize) -> Vec<f64> {
assert!(x > 0.0, "spherical_yn requires x > 0 (got {x})");
let mut y = vec![0.0f64; nmax + 1];
y[0] = -x.cos() / x;
if nmax >= 1 {
y[1] = -x.cos() / (x * x) - x.sin() / x;
}
for n in 1..nmax {
let nf = n as f64;
y[n + 1] = (2.0 * nf + 1.0) / x * y[n] - y[n - 1];
}
y
}
pub fn spherical_jn_complex(z: Complex64, nmax: usize, nnmax: usize) -> Vec<Complex64> {
assert!(z.norm() > 0.0, "spherical_jn_complex requires |z| > 0");
let ntot = nmax + nnmax;
let mut y = vec![Complex64::new(0.0, 0.0); ntot + 2];
y[ntot] = Complex64::new(1.0, 0.0);
for n in (1..=ntot).rev() {
let nf = n as f64;
y[n - 1] = Complex64::new(2.0 * nf + 1.0, 0.0) / z * y[n] - y[n + 1];
}
let j0_true = z.sin() / z;
let scale = j0_true / y[0];
(0..=nmax).map(|n| y[n] * scale).collect()
}
pub fn riccati_bessel_j(x: f64, nmax: usize, nnmax: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
let jn = spherical_jn(x, nmax, nnmax);
let mut psi = vec![0.0; nmax + 1];
let mut dpsi = vec![0.0; nmax + 1];
for n in 0..=nmax {
psi[n] = x * jn[n];
}
for n in 0..nmax {
dpsi[n] = (n as f64 + 1.0) * jn[n] - x * jn[n + 1];
}
let jn2 = spherical_jn(x, nmax + 1, nnmax);
dpsi[nmax] = (nmax as f64 + 1.0) * jn[nmax] - x * jn2[nmax + 1];
(psi, jn, dpsi)
}
pub fn riccati_bessel_y(x: f64, nmax: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
let yn = spherical_yn(x, nmax + 1);
let mut chi = vec![0.0; nmax + 1];
let mut dchi = vec![0.0; nmax + 1];
for n in 0..=nmax {
chi[n] = -x * yn[n];
dchi[n] = -((n as f64 + 1.0) * yn[n] - x * yn[n + 1]);
}
(chi, yn, dchi)
}
pub fn riccati_bessel_j_complex(
z: Complex64,
nmax: usize,
nnmax: usize,
) -> (Vec<Complex64>, Vec<Complex64>, Vec<Complex64>) {
let jn = spherical_jn_complex(z, nmax + 1, nnmax);
let mut psi = vec![Complex64::new(0.0, 0.0); nmax + 1];
let mut dpsi = vec![Complex64::new(0.0, 0.0); nmax + 1];
for n in 0..=nmax {
psi[n] = z * jn[n];
dpsi[n] = Complex64::new(n as f64 + 1.0, 0.0) * jn[n] - z * jn[n + 1];
}
(psi, jn[0..=nmax].to_vec(), dpsi)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn spherical_j0_matches_closed_form() {
for x in [0.1, 0.5, 1.0, 3.14159, 7.5, 15.0] {
let j = spherical_jn(x, 0, 20);
assert_abs_diff_eq!(j[0], x.sin() / x, epsilon = 1e-13);
}
}
#[test]
fn spherical_j1_matches_closed_form() {
for x in [0.1, 0.5, 1.0, 3.14159, 7.5, 15.0] {
let j = spherical_jn(x, 3, 30);
let j1_true = x.sin() / (x * x) - x.cos() / x;
assert_abs_diff_eq!(j[1], j1_true, epsilon = 1e-11);
}
}
#[test]
fn spherical_y_recurrence_consistent() {
for x in [0.5, 1.0, 3.0, 10.0] {
let y = spherical_yn(x, 10);
assert_abs_diff_eq!(y[0], -x.cos() / x, epsilon = 1e-13);
assert_abs_diff_eq!(y[1], -x.cos() / (x * x) - x.sin() / x, epsilon = 1e-13);
let j = spherical_jn(x, 10, 30);
for n in 0..10 {
let w = j[n + 1] * y[n] - j[n] * y[n + 1];
assert_abs_diff_eq!(w, 1.0 / (x * x), epsilon = 1e-11);
}
}
}
#[test]
fn riccati_bessel_j_has_right_derivative() {
let x = 5.5;
let (psi, _j, dpsi) = riccati_bessel_j(x, 6, 30);
let h = 1e-6;
let (psi_ph, _, _) = riccati_bessel_j(x + h, 6, 30);
let (psi_mh, _, _) = riccati_bessel_j(x - h, 6, 30);
for n in 1..=6 {
let num = (psi_ph[n] - psi_mh[n]) / (2.0 * h);
assert_abs_diff_eq!(dpsi[n], num, epsilon = 1e-7);
let _ = psi[n];
}
}
#[test]
fn complex_jn_reduces_to_real_for_real_argument() {
let x = 3.0;
let jr = spherical_jn(x, 8, 30);
let jc = spherical_jn_complex(Complex64::new(x, 0.0), 8, 30);
for n in 0..=8 {
assert_abs_diff_eq!(jc[n].re, jr[n], epsilon = 1e-12);
assert_abs_diff_eq!(jc[n].im, 0.0, epsilon = 1e-13);
}
}
}