use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::Debug;
#[allow(dead_code)]
fn small_arg_series_jn<F: Float + FromPrimitive + Debug>(n: i32, x: F) -> F {
let _n_f = F::from(n).expect("Failed to convert to float");
let x_sq = x * x;
let mut factorial = F::one();
for i in 1..=n {
factorial = factorial * F::from(2 * i + 1).expect("Failed to convert to float");
}
let mut term = F::from(1.0).expect("Failed to convert constant to float");
let mut series = term;
let terms_to_compute = if n < 5 { 4 } else { 3 };
for i in 1..=terms_to_compute {
let denom = F::from(2.0 * i as f64).expect("Failed to convert to float")
* F::from((2 * n + 1 + 2 * i) as f64).expect("Failed to convert to float");
term = term * x_sq.neg() / denom;
series = series + term;
if term.abs() < F::from(1e-15).expect("Failed to convert constant to float") * series.abs()
{
break;
}
}
let mut x_pow_n = F::one();
for _ in 0..n {
x_pow_n = x_pow_n * x;
}
x_pow_n / factorial * series
}
#[allow(dead_code)]
pub fn spherical_jn<F: Float + FromPrimitive + Debug>(n: i32, x: F) -> F {
if n < 0 {
panic!("Order n must be non-negative");
}
if x == F::zero() {
if n == 0 {
return F::one();
} else {
return F::zero();
}
}
if n == 0 {
if x.abs() < F::from(0.01).expect("Failed to convert constant to float") {
let x2 = x * x;
return F::one() - x2 / F::from(6.0).expect("Failed to convert constant to float")
+ x2 * x2 / F::from(120.0).expect("Failed to convert constant to float");
} else {
return x.sin() / x;
}
} else if n == 1 {
if x.abs() < F::from(0.01).expect("Failed to convert constant to float") {
let x2 = x * x;
return x / F::from(3.0).expect("Failed to convert constant to float")
- x * x2 / F::from(30.0).expect("Failed to convert constant to float");
} else {
return (x.sin() / x - x.cos()) / x;
}
}
if x.abs()
< F::from(0.1).expect("Failed to convert constant to float")
* (F::from(n).expect("Failed to convert to float") + F::one())
{
return small_arg_series_jn(n, x);
}
if x > F::from(n).expect("Failed to convert to float")
* F::from(10.0).expect("Failed to convert constant to float")
{
let scaling = x.sin();
return spherical_jn_scaled(n, x) * scaling / x;
}
let max_n = n.min(1000);
let mut j_nminus_2 = if x.abs() < F::from(0.01).expect("Failed to convert constant to float") {
let x2 = x * x;
F::one() - x2 / F::from(6.0).expect("Failed to convert constant to float")
+ x2 * x2 / F::from(120.0).expect("Failed to convert constant to float")
} else {
x.sin() / x
}; let mut j_nminus_1 = if x.abs() < F::from(0.01).expect("Failed to convert constant to float") {
let x2 = x * x;
x / F::from(3.0).expect("Failed to convert constant to float")
- x * x2 / F::from(30.0).expect("Failed to convert constant to float")
} else {
(x.sin() / x - x.cos()) / x
};
for k in 2..=max_n {
let j_n = F::from(2.0 * k as f64 - 1.0).expect("Failed to convert to float") / x
* j_nminus_1
- j_nminus_2;
j_nminus_2 = j_nminus_1;
j_nminus_1 = j_n;
}
j_nminus_1
}
#[allow(dead_code)]
pub fn spherical_yn<F: Float + FromPrimitive + Debug>(n: i32, x: F) -> F {
if n < 0 {
panic!("Order n must be non-negative");
}
if x <= F::zero() {
return F::neg_infinity();
}
if n == 0 {
return -x.cos() / x;
} else if n == 1 {
return -(x.cos() / x + x.sin()) / x;
}
if x > F::from(n).expect("Failed to convert to float")
* F::from(10.0).expect("Failed to convert constant to float")
{
let scaling = -x.cos();
return spherical_yn_scaled(n, x) * scaling / x;
}
let max_n = n.min(1000);
let mut y_nminus_2 = -x.cos() / x; let mut y_nminus_1 = -(x.cos() / x + x.sin()) / x;
for k in 2..=max_n {
let y_n = F::from(2.0 * k as f64 - 1.0).expect("Failed to convert to float") / x
* y_nminus_1
- y_nminus_2;
y_nminus_2 = y_nminus_1;
y_nminus_1 = y_n;
}
y_nminus_1
}
#[allow(dead_code)]
pub fn spherical_jn_scaled<F: Float + FromPrimitive + Debug>(n: i32, x: F) -> F {
if n < 0 {
panic!("Order n must be non-negative");
}
if n == 0 {
if x == F::zero() {
return F::one();
}
if x > F::from(10.0).expect("Failed to convert constant to float") {
let x_sq = x * x;
return F::one()
- F::one() / (F::from(2.0).expect("Failed to convert constant to float") * x_sq);
} else {
return F::one();
}
}
if n == 1 {
if x == F::zero() {
return F::zero();
}
if x > F::from(10.0).expect("Failed to convert constant to float") {
let x_sq = x * x;
return F::one()
- F::from(3.0).expect("Failed to convert constant to float")
/ (F::from(2.0).expect("Failed to convert constant to float") * x_sq);
} else {
let x2 = x * x;
return F::one()
- F::from(2.0).expect("Failed to convert constant to float")
/ F::from(3.0).expect("Failed to convert constant to float")
* x2;
}
}
if x < F::from(5.0).expect("Failed to convert constant to float") {
if x == F::zero() {
return F::zero();
}
let j_n = spherical_jn(n, x);
return j_n * x / x.sin();
}
if x > F::from(n * n).expect("Failed to convert to float")
|| x > F::from(1000.0).expect("Failed to convert constant to float")
{
let x_sq = x * x;
let n_f = F::from(n).expect("Failed to convert to float");
let mut factor = F::one();
factor = factor
- n_f * (n_f + F::one())
/ (F::from(2.0).expect("Failed to convert constant to float") * x_sq);
if x > F::from(100.0).expect("Failed to convert constant to float") {
let term2 = n_f
* (n_f + F::one())
* (n_f * (n_f + F::one())
- F::from(2.0).expect("Failed to convert constant to float"))
/ (F::from(8.0).expect("Failed to convert constant to float") * x_sq * x_sq);
factor = factor + term2;
}
return factor;
}
let safe_n = n.min(50);
let nmax = (n * 2).min(100);
let mut j_n_plus_1 = F::from(1e-100).expect("Failed to convert constant to float");
let mut j_n = F::from(1e-100).expect("Failed to convert constant to float");
for k in (0..=nmax).rev() {
let j_nminus_1 = F::from(2.0 * k as f64 + 1.0).expect("Failed to convert to float") / x
* j_n
- j_n_plus_1;
j_n_plus_1 = j_n;
j_n = j_nminus_1;
if j_n.abs() > F::from(1e50).expect("Failed to convert constant to float") {
let scale = F::from(1e-50).expect("Failed to convert constant to float");
j_n = j_n * scale;
j_n_plus_1 = j_n_plus_1 * scale;
}
}
let j_0_scaled = F::one();
let scale = j_0_scaled / j_n;
j_n = j_0_scaled;
j_n_plus_1 = j_n_plus_1 * scale;
for k in 0..safe_n {
let j_n_plus_1_new = F::from(2.0 * k as f64 + 1.0).expect("Failed to convert to float") / x
* j_n
- j_n_plus_1;
j_n_plus_1 = j_n;
j_n = j_n_plus_1_new;
}
j_n
}
#[allow(dead_code)]
pub fn spherical_yn_scaled<F: Float + FromPrimitive + Debug>(n: i32, x: F) -> F {
if n < 0 {
panic!("Order n must be non-negative");
}
if x <= F::zero() {
return F::neg_infinity();
}
if n == 0 {
if x > F::from(10.0).expect("Failed to convert constant to float") {
let x_sq = x * x;
return F::one()
- F::one() / (F::from(2.0).expect("Failed to convert constant to float") * x_sq);
} else {
return -x.cos() / x * x / (-x.cos()); }
}
if n == 1 {
if x > F::from(10.0).expect("Failed to convert constant to float") {
let x_sq = x * x;
return -F::one()
+ F::from(3.0).expect("Failed to convert constant to float")
/ (F::from(2.0).expect("Failed to convert constant to float") * x_sq);
} else {
return -(x.cos() / x + x.sin()) / x * x / (-x.cos());
}
}
if x < F::from(5.0).expect("Failed to convert constant to float") {
let y_n = spherical_yn(n, x);
return y_n * x / (-x.cos());
}
if x > F::from(n * n).expect("Failed to convert to float")
|| x > F::from(1000.0).expect("Failed to convert constant to float")
{
let x_sq = x * x;
let n_f = F::from(n).expect("Failed to convert to float");
let sign = if n % 2 == 0 { F::one() } else { F::one().neg() };
let mut factor = sign;
factor = factor
- sign * n_f * (n_f + F::one())
/ (F::from(2.0).expect("Failed to convert constant to float") * x_sq);
if x > F::from(100.0).expect("Failed to convert constant to float") {
let term2 = sign
* n_f
* (n_f + F::one())
* (n_f * (n_f + F::one())
- F::from(2.0).expect("Failed to convert constant to float"))
/ (F::from(8.0).expect("Failed to convert constant to float") * x_sq * x_sq);
factor = factor + term2;
}
return factor;
}
spherical_yn(n, x) * x / (-x.cos())
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_spherical_jn_special_cases() {
let x = 1.5f64;
let j0_exact = x.sin() / x;
let j0 = spherical_jn(0, x);
assert_relative_eq!(j0, j0_exact, epsilon = 1e-10);
}
#[test]
fn test_spherical_j1_special_cases() {
let x = 1.5f64;
let j1_exact = (x.sin() / x - x.cos()) / x;
let j1 = spherical_jn(1, x);
assert_relative_eq!(j1, j1_exact, epsilon = 1e-10);
}
#[test]
fn test_spherical_y0_special_cases() {
let x = 1.5f64;
let y0_exact = -x.cos() / x;
let y0 = spherical_yn(0, x);
assert_relative_eq!(y0, y0_exact, epsilon = 1e-10);
}
#[test]
fn test_spherical_y1_special_cases() {
let x = 1.5f64;
let y1_exact = -(x.cos() / x + x.sin()) / x;
let y1 = spherical_yn(1, x);
assert_relative_eq!(y1, y1_exact, epsilon = 1e-10);
}
#[test]
fn test_spherical_jn_small_arguments() {
let x = 1e-6;
let j0_series = 1.0 - x * x / 6.0;
let j0 = spherical_jn(0, x);
assert_relative_eq!(j0, j0_series, epsilon = 1e-12);
}
#[test]
fn test_spherical_j1_small_arguments() {
let x = 1e-6;
let j1_series = x / 3.0;
let j1 = spherical_jn(1, x);
assert_relative_eq!(j1, j1_series, epsilon = 2e-6);
}
}