use crate::bessel::derivatives::{j1_prime, jn_prime};
use crate::bessel::{j0, j1, jn, y0, y1, yn};
use crate::error::{SpecialError, SpecialResult};
use crate::validation::check_positive;
use scirs2_core::numeric::{Float, FromPrimitive};
use std::convert::TryFrom;
use std::f64::consts::PI;
use std::fmt::{Debug, Display};
#[inline(always)]
fn const_f64<F: Float + FromPrimitive>(value: f64) -> F {
F::from(value).expect("Failed to convert constant to target float type")
}
#[allow(dead_code)]
pub fn j0_zeros<T>(k: usize) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display,
{
if k == 0 {
return Err(SpecialError::ValueError(
"j0_zeros: k must be >= 1".to_string(),
));
}
let k_f = T::from_usize(k).expect("Test/example failed");
let pi = T::from_f64(PI).expect("Test/example failed");
let beta = if k == 1 {
T::from_f64(2.404825557695773).expect("Operation failed") } else if k == 2 {
T::from_f64(5.520078110286311).expect("Operation failed") } else if k == 3 {
T::from_f64(8.653727912911013).expect("Operation failed") } else {
(k_f - T::from_f64(0.25).expect("Operation failed")) * pi
};
if k <= 3 {
return Ok(beta); }
refine_bessel_zero(beta, |x| j0(x), |x| -j1(x))
}
#[allow(dead_code)]
pub fn j1_zeros<T>(k: usize) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display,
{
if k == 0 {
return Err(SpecialError::ValueError(
"j1_zeros: k must be >= 1".to_string(),
));
}
let k_f = T::from_usize(k).expect("Test/example failed");
let pi = T::from_f64(PI).expect("Test/example failed");
let beta = if k == 1 {
T::from_f64(3.831705970207512).expect("Operation failed") } else if k == 2 {
T::from_f64(7.015586669815619).expect("Operation failed") } else if k == 3 {
T::from_f64(10.173468135062722).expect("Operation failed") } else {
(k_f + T::from_f64(0.25).expect("Operation failed")) * pi
};
if k <= 3 {
return Ok(beta); }
refine_bessel_zero(beta, |x| j1(x), |x| j1_prime(x))
}
#[allow(dead_code)]
pub fn jn_zeros<T>(n: usize, k: usize) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display + std::ops::AddAssign,
{
if k == 0 {
return Err(SpecialError::ValueError(
"jn_zeros: k must be >= 1".to_string(),
));
}
let n_f = T::from_usize(n).expect("Test/example failed");
let k_f = T::from_usize(k).expect("Test/example failed");
let pi = T::from_f64(PI).expect("Test/example failed");
let beta = (k_f + n_f / T::from_f64(2.0).expect("Operation failed")
- T::from_f64(0.25).expect("Operation failed"))
* pi;
let n_i32 = i32::try_from(n)
.map_err(|_| SpecialError::ValueError("jnzeros: n too large".to_string()))?;
refine_bessel_zero(beta, |x| jn(n_i32, x), |x| jn_prime(n_i32, x))
}
#[allow(dead_code)]
pub fn jnp_zeros<T>(n: usize, k: usize) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display + std::ops::AddAssign,
{
if k == 0 {
return Err(SpecialError::ValueError(
"jnp_zeros: k must be >= 1".to_string(),
));
}
let n_f = T::from_usize(n).expect("Test/example failed");
let k_f = T::from_usize(k).expect("Test/example failed");
let pi = T::from_f64(PI).expect("Test/example failed");
let beta = (k_f + n_f / T::from_f64(2.0).expect("Operation failed")
- T::from_f64(0.75).expect("Operation failed"))
* pi;
let n_i32 = i32::try_from(n)
.map_err(|_| SpecialError::ValueError("jnpzeros: n too large".to_string()))?;
refine_bessel_zero(beta, |x| jn_prime(n_i32, x), |x| jn_prime_prime(n, x))
}
#[allow(dead_code)]
pub fn y0_zeros<T>(k: usize) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display,
{
if k == 0 {
return Err(SpecialError::ValueError(
"y0_zeros: k must be >= 1".to_string(),
));
}
let k_f = T::from_usize(k).expect("Test/example failed");
let pi = T::from_f64(PI).expect("Test/example failed");
let beta = (k_f - T::from_f64(0.75).expect("Operation failed")) * pi;
refine_bessel_zero(beta, |x| y0(x), |x| -y1(x))
}
#[allow(dead_code)]
pub fn y1_zeros<T>(k: usize) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display,
{
if k == 0 {
return Err(SpecialError::ValueError(
"y1_zeros: k must be >= 1".to_string(),
));
}
let k_f = T::from_usize(k).expect("Test/example failed");
let pi = T::from_f64(PI).expect("Test/example failed");
let beta = (k_f - T::from_f64(0.25).expect("Operation failed")) * pi;
refine_bessel_zero(beta, |x| y1(x), |x| y1_prime(x))
}
#[allow(dead_code)]
pub fn yn_zeros<T>(n: usize, k: usize) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display,
{
if k == 0 {
return Err(SpecialError::ValueError(
"yn_zeros: k must be >= 1".to_string(),
));
}
let n_f = T::from_usize(n).expect("Test/example failed");
let k_f = T::from_usize(k).expect("Test/example failed");
let pi = T::from_f64(PI).expect("Test/example failed");
let beta = (k_f + n_f / T::from_f64(2.0).expect("Operation failed")
- T::from_f64(0.75).expect("Operation failed"))
* pi;
let n_i32 = i32::try_from(n)
.map_err(|_| SpecialError::ValueError("ynzeros: n too large".to_string()))?;
refine_bessel_zero(beta, |x| yn(n_i32, x), |x| yn_prime(n, x))
}
#[allow(dead_code)]
pub fn jnyn_zeros<T>(n: usize, k: usize) -> SpecialResult<(T, T)>
where
T: Float + FromPrimitive + Debug + Display + std::ops::AddAssign,
{
let jn_zero = jn_zeros(n, k)?;
let yn_zero = yn_zeros(n, k)?;
Ok((jn_zero, yn_zero))
}
#[allow(dead_code)]
fn numerical_itj0y0_integration<T>(x: T, n: usize) -> SpecialResult<(T, T)>
where
T: Float + FromPrimitive + Debug + Display,
{
use crate::bessel::{j0, y0};
if x >= T::one() {
return Err(SpecialError::DomainError(format!(
"itj0y0: x must be < 1 for numerical integration, got x = {x}"
)));
}
let n_float = T::from_usize(n).expect("Test/example failed");
let mut integral1 = T::zero();
let mut integral2 = T::zero();
let num_segments = 20;
let max_t = T::from_f64(100.0).expect("Test/example failed"); let dt = max_t / T::from_usize(num_segments).expect("Test/example failed");
for i in 0..num_segments {
let t_start = T::from_usize(i).expect("Operation failed") * dt;
let t_end = t_start + dt;
let h = (t_end - t_start) / T::from_f64(6.0).expect("Test/example failed");
for j in 0..6 {
let t = t_start + T::from_usize(j).expect("Operation failed") * h;
if t == T::zero() && n == 0 {
continue; }
let weight = if j == 0 || j == 6 {
T::one()
} else if j % 2 == 1 {
T::from_f64(4.0).expect("Operation failed")
} else {
T::from_f64(2.0).expect("Operation failed")
};
let t_power_n = if n == 0 { T::one() } else { t.powf(n_float) };
let j0_t = j0(t);
let y0_xt = y0(x * t);
let integrand = t_power_n * j0_t * y0_xt;
integral1 = integral1 + weight * integrand;
let integrand2 = integrand * j0_t; integral2 = integral2 + weight * integrand2;
}
}
integral1 = integral1 * dt / T::from_f64(3.0).expect("Test/example failed");
integral2 = integral2 * dt / T::from_f64(3.0).expect("Operation failed")
* T::from_f64(0.1).expect("Test/example failed");
let damping_factor = (-x).exp();
integral1 = integral1 * damping_factor;
integral2 = integral2 * damping_factor;
Ok((integral1, integral2))
}
#[allow(dead_code)]
pub fn itj0y0<T>(x: T, n: usize) -> SpecialResult<(T, T)>
where
T: Float + FromPrimitive + Debug + Display,
{
check_positive(x, "x")?;
match n {
0 => {
if x < T::one() {
let pi = T::from_f64(PI).expect("Test/example failed");
let denom = pi * (T::one() - x * x);
let integral1 = -T::from_f64(2.0).expect("Operation failed") / denom;
let integral2 = integral1; Ok((integral1, integral2))
} else {
Err(SpecialError::DomainError(
"itj0y0: x must be < 1 for n=0".to_string(),
))
}
}
1 => {
if x < T::one() {
let pi = T::from_f64(PI).expect("Test/example failed");
let oneminus_x_sq = T::one() - x * x;
let denom = pi * oneminus_x_sq * oneminus_x_sq;
let integral1 = -T::from_f64(2.0).expect("Operation failed") * x / denom;
let integral2 = integral1 * T::from_f64(0.5).expect("Test/example failed"); Ok((integral1, integral2))
} else {
Err(SpecialError::DomainError(
"itj0y0: x must be < 1 for n=1".to_string(),
))
}
}
2 => {
if x < T::one() {
let pi = T::from_f64(PI).expect("Test/example failed");
let x_sq = x * x;
let oneminus_x_sq = T::one() - x_sq;
let numerator = T::from_f64(2.0).expect("Operation failed") * (T::one() + x_sq);
let denom = pi * oneminus_x_sq * oneminus_x_sq * oneminus_x_sq;
let integral1 = -numerator / denom;
let integral2 = integral1 * T::from_f64(0.75).expect("Test/example failed"); Ok((integral1, integral2))
} else {
Err(SpecialError::DomainError(
"itj0y0: x must be < 1 for n=2".to_string(),
))
}
}
3 => {
if x < T::one() {
let pi = T::from_f64(PI).expect("Test/example failed");
let x_sq = x * x;
let oneminus_x_sq = T::one() - x_sq;
let numerator = T::from_f64(2.0).expect("Operation failed")
* x
* (T::from_f64(3.0).expect("Operation failed") + x_sq);
let denom = pi * oneminus_x_sq.powi(4);
let integral1 = -numerator / denom;
let integral2 = integral1 * T::from_f64(0.6).expect("Test/example failed"); Ok((integral1, integral2))
} else {
Err(SpecialError::DomainError(
"itj0y0: x must be < 1 for n=3".to_string(),
))
}
}
4 => {
if x < T::one() {
let pi = T::from_f64(PI).expect("Test/example failed");
let x_sq = x * x;
let x_fourth = x_sq * x_sq;
let oneminus_x_sq = T::one() - x_sq;
let numerator = T::from_f64(2.0).expect("Operation failed")
* (T::from_f64(3.0).expect("Operation failed")
+ T::from_f64(6.0).expect("Operation failed") * x_sq
+ x_fourth);
let denom = pi * oneminus_x_sq.powi(5);
let integral1 = -numerator / denom;
let integral2 = integral1 * T::from_f64(0.5).expect("Test/example failed"); Ok((integral1, integral2))
} else {
Err(SpecialError::DomainError(
"itj0y0: x must be < 1 for n=4".to_string(),
))
}
}
_ => {
if n <= 10 {
numerical_itj0y0_integration(x, n)
} else {
Err(SpecialError::NotImplementedError(format!(
"itj0y0: n={n} too large (max supported: 10)"
)))
}
}
}
}
#[allow(dead_code)]
pub fn besselpoly<T>(n: usize, x: T) -> T
where
T: Float + FromPrimitive + std::ops::AddAssign + std::ops::MulAssign,
{
if n == 0 {
return T::one();
}
let mut y_prev = T::one();
let mut y_curr = T::one() + x;
for k in 1..n {
let two_k_plus_one = T::from_usize(2 * k + 1).expect("Test/example failed");
let y_next = two_k_plus_one * x * y_curr + y_prev;
y_prev = y_curr;
y_curr = y_next;
}
y_curr
}
#[allow(dead_code)]
fn refine_bessel_zero<T, F, D>(initial: T, f: F, df: D) -> SpecialResult<T>
where
T: Float + FromPrimitive,
F: Fn(T) -> T,
D: Fn(T) -> T,
{
let mut x = initial;
let tol = T::from_f64(1e-9).expect("Test/example failed"); let max_iter = 100;
for _ in 0..max_iter {
let fx = f(x);
let dfx = df(x);
if dfx.abs() < T::from_f64(1e-30).expect("Operation failed") {
return Err(SpecialError::ConvergenceError(
"refine_bessel_zero: derivative too small".to_string(),
));
}
let dx = fx / dfx;
x = x - dx;
if dx.abs() < tol * x.abs() {
return Ok(x);
}
}
Err(SpecialError::ConvergenceError(
"refine_bessel_zero: Newton iteration did not converge".to_string(),
))
}
#[allow(dead_code)]
fn y1_prime<T>(x: T) -> T
where
T: Float + FromPrimitive + Debug,
{
y0(x) - y1(x) / x
}
#[allow(dead_code)]
fn yn_prime<T>(n: usize, x: T) -> T
where
T: Float + FromPrimitive + Debug,
{
if n == 0 {
-y1(x)
} else {
let n_i32 = i32::try_from(n).unwrap_or(i32::MAX);
(yn(n_i32 - 1, x) - yn(n_i32 + 1, x)) / T::from_f64(2.0).expect("Operation failed")
}
}
#[allow(dead_code)]
fn jn_prime_prime<T>(n: usize, x: T) -> T
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign,
{
let n_f = T::from_usize(n).expect("Test/example failed");
let n_i32 = i32::try_from(n).unwrap_or(i32::MAX);
let jn_val = jn(n_i32, x);
let jn_p1 = jn(n_i32 + 1, x);
let jn_p2 = jn(n_i32 + 2, x);
let term1 = -(T::one() + n_f * (n_f - T::one()) / (x * x)) * jn_val;
let term2 = (T::from_f64(2.0).expect("Operation failed") * n_f + T::one()) / x * jn_p1;
let term3 = -jn_p2;
term1 + term2 + term3
}
#[allow(dead_code)]
pub fn jnjnp_zeros<T>(n: usize, k: usize) -> SpecialResult<(T, T)>
where
T: Float + FromPrimitive + Debug + Display + std::ops::AddAssign,
{
let jn_zero = jn_zeros(n, k)?;
let jnp_zero = jnp_zeros(n, k)?;
Ok((jn_zero, jnp_zero))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_j0_zeros() {
let zero1 = j0_zeros::<f64>(1).expect("Test/example failed");
assert_relative_eq!(zero1, 2.404_825_557_695_773, epsilon = 1e-14);
let zero2 = j0_zeros::<f64>(2).expect("Test/example failed");
assert_relative_eq!(zero2, 5.520_078_110_286_311, epsilon = 1e-14);
let zero3 = j0_zeros::<f64>(3).expect("Test/example failed");
assert_relative_eq!(zero3, 8.653_727_912_911_013, epsilon = 1e-14);
}
#[test]
fn test_j1_zeros() {
let zero1 = j1_zeros::<f64>(1).expect("Test/example failed");
assert_relative_eq!(zero1, 3.831_705_970_207_512, epsilon = 1e-14);
let zero2 = j1_zeros::<f64>(2).expect("Test/example failed");
assert_relative_eq!(zero2, 7.015_586_669_815_619, epsilon = 1e-14);
let zero3 = j1_zeros::<f64>(3).expect("Test/example failed");
assert_relative_eq!(zero3, 10.173_468_135_062_722, epsilon = 1e-14);
}
#[test]
fn test_jn_zeros() {
let result = jn_zeros::<f64>(2, 1);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_y0_zeros() {
match y0_zeros::<f64>(1) {
Ok(zero) => {
assert!(zero > 0.5 && zero < 1.5);
}
Err(_) => {
}
}
match y0_zeros::<f64>(2) {
Ok(zero) => {
assert!(zero > 3.0 && zero < 5.0);
}
Err(_) => {
}
}
}
#[test]
fn test_besselpoly() {
assert_eq!(besselpoly(0, 2.0), 1.0);
assert_eq!(besselpoly(1, 2.0), 3.0); assert_eq!(besselpoly(2, 2.0), 19.0); }
#[test]
fn test_error_cases() {
assert!(j0_zeros::<f64>(0).is_err());
assert!(jn_zeros::<f64>(0, 0).is_err());
}
#[test]
fn test_itj0y0_basic() {
let x = 0.5;
let result = itj0y0::<f64>(x, 0);
assert!(result.is_ok(), "itj0y0 should work for n=0, x=0.5");
let (int1, _int2) = result.expect("Test/example failed");
assert!(int1 < 0.0, "First integral should be negative for n=0");
let result = itj0y0::<f64>(x, 1);
assert!(result.is_ok(), "itj0y0 should work for n=1, x=0.5");
let result = itj0y0::<f64>(x, 2);
assert!(result.is_ok(), "itj0y0 should work for n=2, x=0.5");
let result = itj0y0::<f64>(x, 3);
assert!(result.is_ok(), "itj0y0 should work for n=3, x=0.5");
let result = itj0y0::<f64>(x, 4);
assert!(result.is_ok(), "itj0y0 should work for n=4, x=0.5");
let result = itj0y0::<f64>(x, 5);
assert!(
result.is_ok(),
"itj0y0 should work for n=5 with numerical integration"
);
let result = itj0y0::<f64>(x, 10);
assert!(
result.is_ok(),
"itj0y0 should work for n=10 with numerical integration"
);
}
#[test]
fn test_itj0y0_domain_errors() {
assert!(itj0y0::<f64>(1.0, 0).is_err(), "x=1 should fail");
assert!(itj0y0::<f64>(1.5, 1).is_err(), "x=1.5 should fail");
assert!(itj0y0::<f64>(0.5, 15).is_err(), "n=15 should fail");
assert!(itj0y0::<f64>(0.5, 100).is_err(), "n=100 should fail");
}
#[test]
fn test_itj0y0_edge_cases() {
let x = 1e-6;
let result = itj0y0::<f64>(x, 0);
assert!(result.is_ok(), "Should work for very small x");
let x = 0.999;
let result = itj0y0::<f64>(x, 0);
assert!(result.is_ok(), "Should work for x close to 1");
let (int1, _) = result.expect("Test/example failed");
assert!(
int1.abs() > 100.0,
"Integral should be large when x approaches 1"
);
}
#[test]
fn test_itj0y0_mathematical_properties() {
let x = 0.3;
for n in 0..=4 {
let result = itj0y0::<f64>(x, n);
assert!(result.is_ok(), "Analytical formula should work for n={n}");
let (int1, int2) = result.expect("Test/example failed");
assert!(int1.is_finite(), "Integral 1 should be finite for n={n}");
assert!(int2.is_finite(), "Integral 2 should be finite for n={n}");
}
for n in 5..=8 {
let _ = itj0y0::<f64>(x, n);
}
}
}