use crate::bessel::spherical::{spherical_jn, spherical_yn};
use crate::error::{SpecialError, SpecialResult};
use std::f64::consts::PI;
pub fn spherical_in(n: i32, x: f64) -> SpecialResult<f64> {
if n < 0 {
return Err(SpecialError::DomainError(
"Order n must be non-negative for spherical_in".to_string(),
));
}
if x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to spherical_in".to_string(),
));
}
if x == 0.0 {
return Ok(if n == 0 { 1.0 } else { 0.0 });
}
if x.abs() < 1e-15 {
return Ok(if n == 0 { 1.0 } else { 0.0 });
}
if n == 0 {
return spherical_i0(x);
}
if n == 1 {
return spherical_i1(x);
}
if x.abs() < 0.5 * (n as f64 + 1.0) {
return spherical_in_series(n, x);
}
let mut i_prev = spherical_i0(x)?;
let mut i_curr = spherical_i1(x)?;
for k in 1..n {
let i_next = i_prev - (2.0 * k as f64 + 1.0) / x * i_curr;
i_prev = i_curr;
i_curr = i_next;
if !i_curr.is_finite() {
return Ok(f64::INFINITY);
}
}
Ok(i_curr)
}
fn spherical_i0(x: f64) -> SpecialResult<f64> {
if x.abs() < 1e-8 {
let x2 = x * x;
return Ok(1.0 + x2 / 6.0 + x2 * x2 / 120.0);
}
Ok(x.sinh() / x)
}
fn spherical_i1(x: f64) -> SpecialResult<f64> {
if x.abs() < 1e-8 {
let x2 = x * x;
return Ok(x / 3.0 + x * x2 / 30.0 + x * x2 * x2 / 840.0);
}
Ok(x.cosh() / x - x.sinh() / (x * x))
}
fn spherical_in_series(n: i32, x: f64) -> SpecialResult<f64> {
let x_half = x / 2.0;
let v = n as f64 + 0.5;
let log_prefix = v * x_half.abs().ln();
if log_prefix > 700.0 {
return Ok(f64::INFINITY);
}
if log_prefix < -700.0 {
return Ok(0.0);
}
let x_half_sq = x_half * x_half;
let log_gamma_v_plus_1 = crate::gamma::gammaln(v + 1.0);
let mut term = (-log_gamma_v_plus_1).exp();
let mut sum = term;
for k in 1..100 {
term *= x_half_sq / (k as f64 * (v + k as f64));
sum += term;
if term.abs() < 1e-16 * sum.abs() && k > 3 {
break;
}
if !term.is_finite() {
break;
}
}
let prefix = (PI / (2.0 * x.abs())).sqrt() * x_half.abs().powf(v);
let sign = if x < 0.0 && n % 2 != 0 { -1.0 } else { 1.0 };
Ok(sign * prefix * sum)
}
pub fn spherical_kn(n: i32, x: f64) -> SpecialResult<f64> {
if n < 0 {
return Err(SpecialError::DomainError(
"Order n must be non-negative for spherical_kn".to_string(),
));
}
if x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to spherical_kn".to_string(),
));
}
if x <= 0.0 {
return Err(SpecialError::DomainError(
"spherical_kn requires positive x".to_string(),
));
}
let prefactor = (PI / (2.0 * x)) * (-x).exp();
if n == 0 {
return Ok(prefactor);
}
let mut sum = 1.0;
let two_x_inv = 1.0 / (2.0 * x);
let mut term = 1.0;
for k in 1..=n {
let k_f = k as f64;
let n_f = n as f64;
term *= (n_f + k_f) * (n_f - k_f + 1.0) / k_f * two_x_inv;
sum += term;
if !term.is_finite() {
break;
}
}
Ok(prefactor * sum)
}
pub fn spherical_in_derivative(n: i32, x: f64) -> SpecialResult<f64> {
if n < 0 {
return Err(SpecialError::DomainError(
"Order n must be non-negative for spherical_in_derivative".to_string(),
));
}
if x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to spherical_in_derivative".to_string(),
));
}
if n == 0 {
return spherical_in(1, x);
}
if x == 0.0 {
if n == 1 {
return Ok(1.0 / 3.0);
}
return Ok(0.0);
}
let i_n = spherical_in(n, x)?;
let i_nm1 = spherical_in(n - 1, x)?;
Ok(i_nm1 - (n as f64 + 1.0) / x * i_n)
}
pub fn spherical_kn_derivative(n: i32, x: f64) -> SpecialResult<f64> {
if n < 0 {
return Err(SpecialError::DomainError(
"Order n must be non-negative for spherical_kn_derivative".to_string(),
));
}
if x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to spherical_kn_derivative".to_string(),
));
}
if x <= 0.0 {
return Err(SpecialError::DomainError(
"spherical_kn_derivative requires positive x".to_string(),
));
}
if n == 0 {
let k1 = spherical_kn(1, x)?;
return Ok(-k1);
}
let k_n = spherical_kn(n, x)?;
let k_nm1 = spherical_kn(n - 1, x)?;
Ok(-k_nm1 - (n as f64 + 1.0) / x * k_n)
}
pub fn riccati_jn(n: i32, x: f64) -> SpecialResult<(Vec<f64>, Vec<f64>)> {
if n < 0 {
return Err(SpecialError::DomainError(
"Order n must be non-negative for riccati_jn".to_string(),
));
}
if x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to riccati_jn".to_string(),
));
}
let n_usize = n as usize;
let mut s_vals = vec![0.0; n_usize + 1];
let mut sp_vals = vec![0.0; n_usize + 1];
for k in 0..=n_usize {
let jn_val: f64 = spherical_jn(k as i32, x);
s_vals[k] = x * jn_val;
if k == 0 {
if x.abs() < 1e-15 {
sp_vals[0] = 1.0;
} else {
sp_vals[0] = x.cos();
}
} else if x.abs() < 1e-15 {
sp_vals[k] = 0.0;
} else {
let jn_prev: f64 = spherical_jn(k as i32 - 1, x);
let jn_prime = jn_prev - (k as f64 + 1.0) / x * jn_val;
sp_vals[k] = jn_val + x * jn_prime;
}
}
Ok((s_vals, sp_vals))
}
pub fn riccati_yn(n: i32, x: f64) -> SpecialResult<(Vec<f64>, Vec<f64>)> {
if n < 0 {
return Err(SpecialError::DomainError(
"Order n must be non-negative for riccati_yn".to_string(),
));
}
if x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to riccati_yn".to_string(),
));
}
if x <= 0.0 {
return Err(SpecialError::DomainError(
"riccati_yn requires positive x".to_string(),
));
}
let n_usize = n as usize;
let mut c_vals = vec![0.0; n_usize + 1];
let mut cp_vals = vec![0.0; n_usize + 1];
for k in 0..=n_usize {
let yn_val: f64 = spherical_yn(k as i32, x);
c_vals[k] = -x * yn_val;
if k == 0 {
cp_vals[0] = -x.sin();
} else {
let yn_prev: f64 = spherical_yn(k as i32 - 1, x);
let yn_prime = yn_prev - (k as f64 + 1.0) / x * yn_val;
cp_vals[k] = -yn_val - x * yn_prime;
}
}
Ok((c_vals, cp_vals))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_spherical_i0() {
let x = 1.0;
let val = spherical_in(0, x).expect("spherical_in(0, 1.0) failed");
let expected = x.sinh() / x;
assert_relative_eq!(val, expected, epsilon = 1e-10);
}
#[test]
fn test_spherical_i0_at_zero() {
let val = spherical_in(0, 0.0).expect("spherical_in(0, 0.0) failed");
assert_relative_eq!(val, 1.0, epsilon = 1e-10);
}
#[test]
fn test_spherical_i1() {
let x = 2.0;
let val = spherical_in(1, x).expect("spherical_in(1, 2.0) failed");
let expected = x.cosh() / x - x.sinh() / (x * x);
assert_relative_eq!(val, expected, epsilon = 1e-10);
}
#[test]
fn test_spherical_in_positive_values() {
for n in 0..=5 {
let val = spherical_in(n, 1.5).expect("spherical_in failed");
assert!(val > 0.0, "i_{}(1.5) should be positive, got {}", n, val);
}
}
#[test]
fn test_spherical_in_small_x() {
let val = spherical_in(0, 1e-10).expect("spherical_in failed");
assert_relative_eq!(val, 1.0, epsilon = 1e-8);
}
#[test]
fn test_spherical_in_nan_input() {
assert!(spherical_in(0, f64::NAN).is_err());
}
#[test]
fn test_spherical_in_negative_order() {
assert!(spherical_in(-1, 1.0).is_err());
}
#[test]
fn test_spherical_k0() {
let x = 1.0;
let val = spherical_kn(0, x).expect("spherical_kn(0, 1.0) failed");
let expected = (PI / (2.0 * x)) * (-x).exp();
assert_relative_eq!(val, expected, epsilon = 1e-10);
}
#[test]
fn test_spherical_k1() {
let x = 1.0;
let val = spherical_kn(1, x).expect("spherical_kn(1, 1.0) failed");
let expected = (PI / (2.0 * x)) * (-x).exp() * (1.0 + 1.0 / x);
assert_relative_eq!(val, expected, epsilon = 1e-10);
}
#[test]
fn test_spherical_kn_positive_values() {
for n in 0..=5 {
let val = spherical_kn(n, 1.5).expect("spherical_kn failed");
assert!(val > 0.0, "k_{}(1.5) should be positive, got {}", n, val);
}
}
#[test]
fn test_spherical_kn_decreasing() {
let k0_1 = spherical_kn(0, 1.0).expect("failed");
let k0_2 = spherical_kn(0, 2.0).expect("failed");
assert!(k0_1 > k0_2, "k_0 should decrease with x");
}
#[test]
fn test_spherical_kn_nonpositive() {
assert!(spherical_kn(0, 0.0).is_err());
assert!(spherical_kn(0, -1.0).is_err());
}
#[test]
fn test_spherical_in_derivative_numerical() {
let n = 2;
let x = 1.5;
let h = 1e-7;
let deriv = spherical_in_derivative(n, x).expect("derivative failed");
let i_plus = spherical_in(n, x + h).expect("failed");
let i_minus = spherical_in(n, x - h).expect("failed");
let numerical = (i_plus - i_minus) / (2.0 * h);
assert_relative_eq!(deriv, numerical, epsilon = 1e-5);
}
#[test]
fn test_spherical_kn_derivative_numerical() {
let n = 2;
let x = 1.5;
let h = 1e-7;
let deriv = spherical_kn_derivative(n, x).expect("derivative failed");
let k_plus = spherical_kn(n, x + h).expect("failed");
let k_minus = spherical_kn(n, x - h).expect("failed");
let numerical = (k_plus - k_minus) / (2.0 * h);
assert_relative_eq!(deriv, numerical, epsilon = 1e-5);
}
#[test]
fn test_riccati_jn_s0() {
let x = 1.5;
let (s_vals, sp_vals) = riccati_jn(0, x).expect("riccati_jn failed");
assert_relative_eq!(s_vals[0], x.sin(), epsilon = 1e-10);
assert_relative_eq!(sp_vals[0], x.cos(), epsilon = 1e-10);
}
#[test]
fn test_riccati_jn_sequence() {
let (s_vals, _) = riccati_jn(5, 2.0).expect("riccati_jn failed");
for (i, val) in s_vals.iter().enumerate() {
assert!(
val.is_finite(),
"S_{}(2.0) should be finite, got {}",
i,
val
);
}
}
#[test]
fn test_riccati_yn_c0() {
let x = 1.5;
let (c_vals, cp_vals) = riccati_yn(0, x).expect("riccati_yn failed");
assert_relative_eq!(c_vals[0], x.cos(), epsilon = 1e-10);
assert_relative_eq!(cp_vals[0], -x.sin(), epsilon = 1e-10);
}
#[test]
fn test_riccati_yn_sequence() {
let (c_vals, _) = riccati_yn(5, 2.0).expect("riccati_yn failed");
for (i, val) in c_vals.iter().enumerate() {
assert!(
val.is_finite(),
"C_{}(2.0) should be finite, got {}",
i,
val
);
}
}
#[test]
fn test_riccati_jn_negative_order() {
assert!(riccati_jn(-1, 1.0).is_err());
}
#[test]
fn test_riccati_yn_negative_x() {
assert!(riccati_yn(0, -1.0).is_err());
}
#[test]
fn test_riccati_wronskian() {
let x = 2.5;
let (s_vals, sp_vals) = riccati_jn(3, x).expect("failed");
let (c_vals, cp_vals) = riccati_yn(3, x).expect("failed");
for n in 0..=3 {
let wronskian = s_vals[n] * cp_vals[n] - sp_vals[n] * c_vals[n];
assert_relative_eq!(wronskian, -1.0, epsilon = 1e-8);
}
}
}