use crate::constants;
use crate::gamma::gamma;
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::Debug;
#[inline(always)]
fn const_f64<F: Float + FromPrimitive>(value: f64) -> F {
F::from(value).expect("Failed to convert constant to target float type - this indicates an incompatible numeric type")
}
#[allow(dead_code)]
pub fn j0<F: Float + FromPrimitive + Debug>(x: F) -> F {
if x == F::zero() {
return F::one();
}
let abs_x = x.abs();
if abs_x > const_f64::<F>(25.0) {
return enhanced_asymptotic_j0(x);
}
let half_x = abs_x / const_f64::<F>(2.0);
let half_x_sq = half_x * half_x;
let mut sum = F::one();
let mut term = F::one();
for k in 1..80 {
let k_f = const_f64::<F>(k as f64);
term = term * (-half_x_sq) / (k_f * k_f);
let new_sum = sum + term;
if (new_sum - sum).abs() < const_f64::<F>(1e-15) * sum.abs().max(const_f64::<F>(1e-300)) {
return new_sum;
}
sum = new_sum;
}
sum
}
#[allow(dead_code)]
fn enhanced_asymptotic_j0<F: Float + FromPrimitive>(x: F) -> F {
let abs_x = x.abs();
let theta = abs_x - const_f64::<F>(constants::f64::PI_4);
let one_over_sqrt_pi_x = const_f64::<F>(constants::f64::ONE_OVER_SQRT_PI) / abs_x.sqrt();
let mut p = F::one();
let mut q = const_f64::<F>(-0.125) / abs_x;
if abs_x > const_f64::<F>(100.0) {
return one_over_sqrt_pi_x * p * theta.cos() * const_f64::<F>(constants::f64::SQRT_2);
}
let z = const_f64::<F>(8.0) * abs_x;
let z2 = z * z;
p = p - const_f64::<F>(9.0) / z2 + const_f64::<F>(225.0) / (z2 * z2)
- const_f64::<F>(11025.0) / (z2 * z2 * z2);
q = q + const_f64::<F>(15.0) / z2 - const_f64::<F>(735.0) / (z2 * z2)
+ const_f64::<F>(51975.0) / (z2 * z2 * z2);
one_over_sqrt_pi_x
* const_f64::<F>(constants::f64::SQRT_2)
* (p * theta.cos() - q * theta.sin())
}
#[allow(dead_code)]
pub fn j1<F: Float + FromPrimitive + Debug>(x: F) -> F {
if x == F::zero() {
return F::zero();
}
let abs_x = x.abs();
let sign = if x.is_sign_positive() {
F::one()
} else {
-F::one()
};
if abs_x > const_f64::<F>(25.0) {
return sign * enhanced_asymptotic_j1(abs_x);
}
let half_x = abs_x / const_f64::<F>(2.0);
let half_x_sq = half_x * half_x;
let mut sum = F::one(); let mut term = F::one();
for k in 1..80 {
let k_f = const_f64::<F>(k as f64);
let k_plus_1 = const_f64::<F>((k + 1) as f64);
term = term * (-half_x_sq) / (k_f * k_plus_1);
let new_sum = sum + term;
if (new_sum - sum).abs() < const_f64::<F>(1e-15) * sum.abs().max(const_f64::<F>(1e-300)) {
return sign * half_x * new_sum;
}
sum = new_sum;
}
sign * half_x * sum
}
#[allow(dead_code)]
fn enhanced_asymptotic_j1<F: Float + FromPrimitive>(x: F) -> F {
let theta = x - const_f64::<F>(3.0 * constants::f64::PI_4);
let one_over_sqrt_pi_x = const_f64::<F>(constants::f64::ONE_OVER_SQRT_PI) / x.sqrt();
let mut p = F::one();
let mut q = const_f64::<F>(0.375) / x;
if x > const_f64::<F>(100.0) {
return one_over_sqrt_pi_x * p * theta.cos() * const_f64::<F>(constants::f64::SQRT_2);
}
let z = const_f64::<F>(8.0) * x;
let z2 = z * z;
p = p - const_f64::<F>(15.0) / z2 + const_f64::<F>(735.0) / (z2 * z2)
- const_f64::<F>(67725.0) / (z2 * z2 * z2);
q = q - const_f64::<F>(63.0) / z2 + const_f64::<F>(3465.0) / (z2 * z2)
- const_f64::<F>(360855.0) / (z2 * z2 * z2);
one_over_sqrt_pi_x
* const_f64::<F>(constants::f64::SQRT_2)
* (p * theta.cos() - q * theta.sin())
}
#[allow(dead_code)]
pub fn jn<F: Float + FromPrimitive + Debug + std::ops::AddAssign>(n: i32, x: F) -> F {
if n < 0 {
let sign = if n % 2 == 0 { F::one() } else { -F::one() };
return sign * jn(-n, x);
}
if n == 0 {
return j0(x);
}
if n == 1 {
return j1(x);
}
if x == F::zero() {
return F::zero();
}
let abs_x = x.abs();
if abs_x > const_f64::<F>(n as f64 * 2.0) && abs_x > const_f64::<F>(25.0) {
return enhanced_asymptotic_jn(n, x);
}
if abs_x < const_f64::<F>(0.1) && n > 2 {
let half_x = abs_x / const_f64::<F>(2.0);
let log_term = const_f64::<F>(n as f64) * half_x.ln() - log_factorial::<F>(n);
if log_term < const_f64::<F>(constants::f64::LN_MAX)
&& log_term > const_f64::<F>(constants::f64::LN_MIN)
{
let prefactor = log_term.exp();
let mut sum = F::one();
let mut term = F::one();
let x2 = -half_x * half_x;
for k in 1..=50 {
term = term * x2 / (const_f64::<F>(k as f64) * const_f64::<F>((n + k) as f64));
sum += term;
if term.abs() < const_f64::<F>(1e-15) * sum.abs() {
break;
}
}
let result = prefactor * sum;
return if x.is_sign_negative() && n % 2 != 0 {
-result
} else {
return result;
};
}
}
let mut j_prev = j0(abs_x); let mut j_curr = j1(abs_x);
for k in 2..=n {
let k_f = const_f64::<F>((k - 1) as f64); let j_next = (k_f + k_f) / abs_x * j_curr - j_prev;
j_prev = j_curr;
j_curr = j_next;
}
if x.is_sign_negative() && n % 2 != 0 {
-j_curr
} else {
j_curr
}
}
#[allow(dead_code)]
pub fn jv<F: Float + FromPrimitive + Debug + std::ops::AddAssign>(v: F, x: F) -> F {
if x == F::zero() {
if v == F::zero() {
return F::one();
} else if v.is_sign_positive() {
return F::zero();
} else {
return F::infinity();
}
}
let abs_x = x.abs();
let abs_v = v.abs();
let v_f64 = v.to_f64().expect("Failed to convert Float to f64");
if v_f64.fract() == 0.0 && (0.0..=100.0).contains(&v_f64) {
return jn(v_f64 as i32, x);
}
if abs_x
> const_f64::<F>(max(
30.0,
abs_v.to_f64().expect("Failed to convert abs_v to f64") * 2.0,
))
{
return enhanced_asymptotic_jv(v, x);
}
if abs_x < const_f64::<F>(0.1) && abs_v > const_f64::<F>(1.0) {
let half_x = abs_x / const_f64::<F>(2.0);
let log_term = v * half_x.ln() - gamma(v + F::one()).ln();
if log_term < const_f64::<F>(constants::f64::LN_MAX)
&& log_term > const_f64::<F>(constants::f64::LN_MIN)
{
let prefactor = log_term.exp();
let mut sum = F::one();
let mut term = F::one();
let x2 = -half_x * half_x;
for k in 1..=100 {
let k_f = const_f64::<F>(k as f64);
term = term * x2 / (k_f * (v + k_f));
sum += term;
if term.abs() < const_f64::<F>(1e-15) * sum.abs() {
break;
}
}
let result = prefactor * sum;
if x.is_sign_negative() {
if v_f64.fract() == 0.0 {
if (v_f64 as i32) % 2 != 0 {
return -result;
}
return result;
} else {
let v_floor = v_f64.floor() as i32;
if v_floor % 2 != 0 {
return -result;
}
return result;
}
}
return result;
}
}
let v_nearest_int = v_f64.round();
if (v_f64 - v_nearest_int).abs() < 1e-6 {
return jn(v_nearest_int as i32, x);
}
let half_x = abs_x / const_f64::<F>(2.0);
let log_prefactor = v * half_x.ln() - gamma(v + F::one()).ln();
if log_prefactor > const_f64::<F>(constants::f64::LN_MIN)
&& log_prefactor < const_f64::<F>(constants::f64::LN_MAX)
{
let prefactor = log_prefactor.exp();
let mut sum = F::one();
let mut term = F::one();
let neg_x2_over_4 = -half_x * half_x;
for k in 1..=100 {
let k_f = const_f64::<F>(k as f64);
term = term * neg_x2_over_4 / (k_f * (v + k_f));
sum += term;
if term.abs() < const_f64::<F>(1e-15) * sum.abs() {
break;
}
}
let result = prefactor * sum;
if x.is_sign_negative() {
let cos_pi_v = (const_f64::<F>(constants::f64::PI) * v).cos();
return result * cos_pi_v;
}
return result;
}
enhanced_asymptotic_jv(v, x)
}
#[allow(dead_code)]
fn enhanced_asymptotic_jv<F: Float + FromPrimitive>(v: F, x: F) -> F {
let abs_x = x.abs();
let v_f64 = v.to_f64().expect("Failed to convert Float to f64");
let phase_adjustment =
v * const_f64::<F>(constants::f64::PI_2) + const_f64::<F>(constants::f64::PI_4);
let theta = abs_x - phase_adjustment;
let one_over_sqrt_pi_x = const_f64::<F>(constants::f64::ONE_OVER_SQRT_PI) / abs_x.sqrt();
let mu = const_f64::<F>(4.0) * v * v;
let muminus_1 = mu - F::one();
if abs_x > const_f64::<F>(100.0) {
let result = one_over_sqrt_pi_x * const_f64::<F>(constants::f64::SQRT_2) * theta.cos();
if x.is_sign_negative() && v_f64.fract() != 0.0 {
let cos_pi_v = (const_f64::<F>(constants::f64::PI) * v).cos();
return result * cos_pi_v;
} else if x.is_sign_negative() && (v_f64 as i32) % 2 != 0 {
return -result;
}
return result;
}
let term1 = muminus_1 / (const_f64::<F>(8.0) * abs_x);
let term2 =
muminus_1 * (muminus_1 - const_f64::<F>(8.0)) / (const_f64::<F>(128.0) * abs_x * abs_x);
let term3 = muminus_1 * (muminus_1 - const_f64::<F>(8.0)) * (muminus_1 - const_f64::<F>(24.0))
/ (const_f64::<F>(3072.0) * abs_x * abs_x * abs_x);
let p = F::one() + term1 + term2 + term3;
let result = one_over_sqrt_pi_x * const_f64::<F>(constants::f64::SQRT_2) * p * theta.cos();
if x.is_sign_negative() {
if v_f64.fract() == 0.0 {
if (v_f64 as i32) % 2 != 0 {
return -result;
}
return result;
} else {
let cos_pi_v = (const_f64::<F>(constants::f64::PI) * v).cos();
return result * cos_pi_v;
}
}
result
}
#[allow(dead_code)]
fn log_factorial<F: Float + FromPrimitive>(n: i32) -> F {
if n <= 1 {
return F::zero();
}
let mut result = F::zero();
for i in 2..=n {
result = result + const_f64::<F>(i as f64).ln();
}
result
}
#[allow(dead_code)]
fn enhanced_asymptotic_jn<F: Float + FromPrimitive>(n: i32, x: F) -> F {
let abs_x = x.abs();
let n_f = const_f64::<F>(n as f64);
let theta =
abs_x - (n_f * const_f64::<F>(constants::f64::PI_2) + const_f64::<F>(constants::f64::PI_4));
let one_over_sqrt_pi_x = const_f64::<F>(constants::f64::ONE_OVER_SQRT_PI) / abs_x.sqrt();
let mu = const_f64::<F>(4.0) * n_f * n_f;
let muminus_1 = mu - F::one();
let term_1 = muminus_1 / (const_f64::<F>(8.0) * abs_x);
let term_2 =
muminus_1 * (muminus_1 - const_f64::<F>(8.0)) / (const_f64::<F>(128.0) * abs_x * abs_x);
let ampl = F::one() + term_1 + term_2;
let result = one_over_sqrt_pi_x * const_f64::<F>(constants::f64::SQRT_2) * ampl * theta.cos();
if x.is_sign_negative() && n % 2 != 0 {
-result
} else {
result
}
}
#[allow(dead_code)]
pub fn j0e<F: Float + FromPrimitive + Debug>(x: F) -> F {
j0(x)
}
#[allow(dead_code)]
pub fn j1e<F: Float + FromPrimitive + Debug>(x: F) -> F {
j1(x)
}
#[allow(dead_code)]
pub fn jne<F: Float + FromPrimitive + Debug + std::ops::AddAssign>(n: i32, x: F) -> F {
jn(n, x)
}
#[allow(dead_code)]
pub fn jve<F: Float + FromPrimitive + Debug + std::ops::AddAssign>(v: F, x: F) -> F {
jv(v, x)
}
#[allow(dead_code)]
fn max<T: PartialOrd>(a: T, b: T) -> T {
if a > b {
a
} else {
b
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_j0_special_cases() {
assert_relative_eq!(j0(0.0), 1.0, epsilon = 1e-10);
let j0_small = j0(1e-10);
assert_relative_eq!(j0_small, 1.0, epsilon = 1e-10);
let first_zero = 2.404825557695773f64;
let j0_at_zero = j0(first_zero);
assert!(
j0_at_zero.abs() < 1e-10,
"J₀ should be close to zero at its first zero, got {}",
j0_at_zero
);
}
#[test]
fn test_j0_moderate_values() {
assert_relative_eq!(j0(0.5), 0.9384698072408130, epsilon = 1e-10);
assert_relative_eq!(j0(1.0), 0.7651976865579665, epsilon = 1e-10);
assert_relative_eq!(j0(5.0), -0.1775967713143383, epsilon = 1e-10);
assert_relative_eq!(j0(10.0), -0.2459357644513483, epsilon = 1e-10);
}
#[test]
fn test_j0_large_values() {
let j0_50 = j0(50.0);
let j0_100 = j0(100.0);
let j0_1000 = j0(1000.0);
assert!(j0_50.abs() < 0.1);
assert!(j0_100.abs() < 0.1);
assert!(j0_1000.abs() < 0.03);
}
#[test]
fn test_j1_special_cases() {
assert_relative_eq!(j1(0.0), 0.0, epsilon = 1e-10);
let j1_small = j1(1e-10);
assert_relative_eq!(j1_small, 5e-11, epsilon = 1e-11);
}
#[test]
fn test_j1_moderate_values() {
assert_relative_eq!(j1(0.5), 0.2422684576748739, epsilon = 1e-10);
assert_relative_eq!(j1(1.0), 0.4400505857449335, epsilon = 1e-10);
assert_relative_eq!(j1(5.0), -0.3275791375914653, epsilon = 1e-10);
assert_relative_eq!(j1(10.0), 0.04347274616886141, epsilon = 1e-10);
}
#[test]
fn test_jn_integer_orders() {
let x = 5.0;
assert_relative_eq!(jn(0, x), j0(x), epsilon = 1e-10);
assert_relative_eq!(jn(1, x), j1(x), epsilon = 1e-10);
assert_relative_eq!(jn(2, x), 0.04656511627775229, epsilon = 1e-10);
assert_relative_eq!(jn(3, x), 0.36483123061366701, epsilon = 1e-10);
assert_relative_eq!(jn(5, x), 0.26114054612017007, epsilon = 1e-10);
}
#[test]
fn test_jv_integer_orders() {
let x = 5.0;
assert_relative_eq!(jv(0.0, x), j0(x), epsilon = 1e-10);
assert_relative_eq!(jv(1.0, x), j1(x), epsilon = 1e-10);
assert_relative_eq!(jv(2.0, x), jn(2, x), epsilon = 1e-10);
assert_relative_eq!(jv(5.0, x), jn(5, x), epsilon = 1e-10);
}
#[test]
fn test_jv_half_integer_orders() {
let x = 2.0;
let j_half = jv(0.5, x);
let exact = (2.0 / (std::f64::consts::PI * x)).sqrt() * x.sin();
assert_relative_eq!(j_half, exact, epsilon = 1e-8);
let j_three_half = jv(1.5, x);
let exact = (2.0 / (std::f64::consts::PI * x)).sqrt() * (x.sin() / x - x.cos());
assert_relative_eq!(j_three_half, exact, epsilon = 1e-8);
}
#[test]
fn test_jv_negative_orders() {
let x = 3.0;
assert_relative_eq!(jv(-1.0, x), -j1(x), epsilon = 1e-10);
assert_relative_eq!(jv(-2.0, x), jn(2, x), epsilon = 1e-10);
}
#[test]
fn test_jv_negative_argument() {
let x = 4.0;
assert_relative_eq!(jv(0.0, -x), j0(x), epsilon = 1e-10);
assert_relative_eq!(jv(1.0, -x), -j1(x), epsilon = 1e-10);
assert_relative_eq!(jv(2.0, -x), jn(2, x), epsilon = 1e-10);
assert_relative_eq!(jv(3.0, -x), -jn(3, x), epsilon = 1e-10);
let v = 0.5;
let cos_pi_v = (std::f64::consts::PI * v).cos();
let expected = cos_pi_v * jv(v, x);
assert_relative_eq!(jv(v, -x), expected, epsilon = 1e-8);
}
}