use crate::error::SpecialResult;
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::Debug;
#[inline(always)]
fn const_f64<F: Float + FromPrimitive>(value: f64) -> F {
F::from(value).unwrap_or_else(|| {
if value > 0.0 {
F::infinity()
} else if value < 0.0 {
F::neg_infinity()
} else {
F::zero()
}
})
}
const DEBYE_U_COEFFS: [[f64; 7]; 4] = [
[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.125, 0.0, -0.208333333333333, 0.0, 0.0, 0.0], [
0.0,
0.0,
0.0703125,
0.0,
-0.4010416666666667,
0.0,
0.3342013888889,
], [
0.0,
0.0,
0.0,
0.0732421875,
0.0,
-0.8912109375,
1.8464626736111,
], ];
#[allow(dead_code)]
fn hankel_pq<F>(v: F, x: F) -> (F, F)
where
F: Float + FromPrimitive + Debug,
{
let mu = const_f64::<F>(4.0) * v * v;
let eight_x = const_f64::<F>(8.0) * x;
let mut p = F::one();
let mut q = F::zero();
let mut term = F::one();
let mut prev_term_abs = F::infinity();
for k in 1..=30u32 {
let k_f = const_f64::<F>(k as f64);
let sq = const_f64::<F>((2 * k - 1) as f64);
term = term * (mu - sq * sq) / (k_f * eight_x);
let term_abs = term.abs();
if term_abs > prev_term_abs && k > 2 {
break;
}
prev_term_abs = term_abs;
if k % 2 == 0 {
p = p + term;
} else {
q = q + term;
}
if term_abs < const_f64::<F>(1e-16) * (p.abs() + q.abs() + F::epsilon()) {
break;
}
}
(p, q)
}
#[allow(dead_code)]
pub fn hankel_j<F>(v: F, x: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let abs_x = x.abs();
let v_f64 = v.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert v to f64".to_string())
})?;
let pi = const_f64::<F>(std::f64::consts::PI);
let chi = abs_x - (const_f64::<F>(2.0) * v + F::one()) * pi / const_f64::<F>(4.0);
let (p, q) = hankel_pq(v, abs_x);
let amplitude = (const_f64::<F>(2.0) / (pi * abs_x)).sqrt();
let result = amplitude * (p * chi.cos() - q * chi.sin());
if x.is_sign_negative() && v_f64.fract() == 0.0 {
let n = v_f64 as i32;
if n % 2 != 0 {
return Ok(-result);
}
}
Ok(result)
}
#[allow(dead_code)]
pub fn hankel_y<F>(v: F, x: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
if x <= F::zero() {
return Err(crate::error::SpecialError::DomainError(
"Y_v(x) undefined for x <= 0".to_string(),
));
}
let pi = const_f64::<F>(std::f64::consts::PI);
let chi = x - (const_f64::<F>(2.0) * v + F::one()) * pi / const_f64::<F>(4.0);
let (p, q) = hankel_pq(v, x);
let amplitude = (const_f64::<F>(2.0) / (pi * x)).sqrt();
Ok(amplitude * (p * chi.sin() + q * chi.cos()))
}
#[allow(dead_code)]
pub fn debye_j<F>(v: F, x: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let v_f64 = v.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert v to f64".to_string())
})?;
let x_f64 = x.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert x to f64".to_string())
})?;
let ratio = x_f64 / v_f64;
if ratio >= 1.0 {
debye_oscillatory(v, x)
} else {
debye_exponential(v, x)
}
}
#[allow(dead_code)]
fn debye_oscillatory<F>(v: F, x: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let v_f64 = v.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert v to f64".to_string())
})?;
let x_f64 = x.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert x to f64".to_string())
})?;
let ratio = x_f64 / v_f64;
let beta = (ratio + (ratio * ratio - 1.0).sqrt()).ln();
let tau = (ratio * ratio - 1.0).sqrt();
let xi = v_f64 * (tau - tau.atan());
let pi = std::f64::consts::PI;
let amplitude = 1.0 / (tau * (2.0 * pi * v_f64).sqrt());
let cot_beta = 1.0 / tau; let mut u_sum = 1.0;
for (k, coeffs) in DEBYE_U_COEFFS.iter().enumerate().skip(1) {
let mut u_k = 0.0;
let mut cot_power = 1.0;
for &c in coeffs.iter() {
u_k += c * cot_power;
cot_power *= cot_beta;
}
u_sum += u_k / v_f64.powi(k as i32);
}
let result = amplitude * u_sum * (xi - pi / 4.0).cos();
Ok(const_f64(result))
}
#[allow(dead_code)]
fn debye_exponential<F>(v: F, x: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let v_f64 = v.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert v to f64".to_string())
})?;
let x_f64 = x.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert x to f64".to_string())
})?;
let t = x_f64 / v_f64;
let sqrt_term = (1.0 - t * t).sqrt();
let alpha = ((1.0 + sqrt_term) / t).ln();
let tanh_alpha = sqrt_term;
let eta = v_f64 * (tanh_alpha - alpha);
let pi = std::f64::consts::PI;
let amplitude = eta.exp() / (2.0 * pi * v_f64 * tanh_alpha).sqrt();
let coth_alpha = 1.0 / tanh_alpha;
let mut u_sum = 1.0;
for (k, coeffs) in DEBYE_U_COEFFS.iter().enumerate().skip(1) {
let mut u_k = 0.0;
let mut coth_power = 1.0;
for &c in coeffs.iter() {
u_k += c * coth_power;
coth_power *= coth_alpha;
}
u_sum += u_k / v_f64.powi(k as i32);
}
let result = amplitude * u_sum;
Ok(const_f64(result))
}
#[allow(dead_code)]
pub fn uniform_j<F>(v: F, x: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let v_f64 = v.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert v to f64".to_string())
})?;
let x_f64 = x.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert x to f64".to_string())
})?;
let t = x_f64 / v_f64;
let zeta: f64;
let phi: f64;
if t < 1.0 {
let sqrt_1_minus_t2 = (1.0 - t * t).sqrt();
let half_arg = ((1.0 + sqrt_1_minus_t2) / t).ln() - sqrt_1_minus_t2;
zeta = -((3.0 / 2.0) * half_arg).powf(2.0 / 3.0);
phi = (4.0 * zeta.abs() / (1.0 - t * t)).powf(0.25);
} else if t > 1.0 {
let sqrt_t2_minus_1 = (t * t - 1.0).sqrt();
let half_arg = sqrt_t2_minus_1 - (sqrt_t2_minus_1 / t).acos();
zeta = ((3.0 / 2.0) * half_arg).powf(2.0 / 3.0);
phi = (4.0 * zeta / (t * t - 1.0)).powf(0.25);
} else {
zeta = 0.0;
phi = (2.0_f64).powf(1.0 / 3.0);
}
let airy_arg = v_f64.powf(2.0 / 3.0) * zeta;
let (ai, _aip) = airy_approx(airy_arg);
let result = phi / v_f64.powf(1.0 / 3.0) * ai;
Ok(const_f64(result))
}
#[allow(dead_code)]
fn airy_approx(x: f64) -> (f64, f64) {
if x > 0.0 {
let xi = (2.0 / 3.0) * x.powf(1.5);
let ai = (-xi).exp() / (2.0 * std::f64::consts::PI.sqrt() * x.powf(0.25));
let aip = -x.powf(0.25) * ai;
(ai, aip)
} else if x < 0.0 {
let abs_x = x.abs();
let xi = (2.0 / 3.0) * abs_x.powf(1.5);
let ai = (xi - std::f64::consts::PI / 4.0).sin()
/ (std::f64::consts::PI.sqrt() * abs_x.powf(0.25));
let aip = -abs_x.powf(0.25) * (xi - std::f64::consts::PI / 4.0).cos()
/ std::f64::consts::PI.sqrt();
(ai, aip)
} else {
let ai_0 = 1.0 / (3.0_f64.powf(2.0 / 3.0) * std::f64::consts::PI.sqrt() / 3.0_f64.sqrt());
let aip_0 = -1.0 / (3.0_f64.powf(1.0 / 3.0) * std::f64::consts::PI.sqrt() / 3.0_f64.sqrt());
(ai_0, aip_0)
}
}
#[allow(dead_code)]
pub fn adaptive_bessel_j<F>(v: F, x: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug + std::ops::AddAssign,
{
let v_f64 = v.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert v to f64".to_string())
})?;
let x_f64 = x.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert x to f64".to_string())
})?;
let abs_v = v_f64.abs();
let abs_x = x_f64.abs();
if abs_x > 2.0 * abs_v + 25.0 {
return hankel_j(v, x);
}
if abs_v > 25.0 && abs_x < abs_v * 0.9 {
return debye_j(v, x);
}
if abs_v > 25.0 && abs_x > abs_v * 1.1 {
return debye_j(v, x);
}
if abs_v > 10.0 && (abs_x / abs_v - 1.0).abs() < 0.2 {
return uniform_j(v, x);
}
Ok(crate::bessel::jv(v, x))
}
#[allow(dead_code)]
pub fn debye_i<F>(v: F, x: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let v_f64 = v.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert v to f64".to_string())
})?;
let x_f64 = x.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert x to f64".to_string())
})?;
let t = x_f64 / v_f64;
let pi = std::f64::consts::PI;
if t <= 1.0 {
let sqrt_1_minus_t2 = (1.0 - t * t).max(0.0).sqrt();
let eta = (sqrt_1_minus_t2 + ((1.0 + sqrt_1_minus_t2) / t).ln()) * v_f64
- v_f64 * sqrt_1_minus_t2;
let amplitude = (eta).exp() / (2.0 * pi * v_f64 * sqrt_1_minus_t2.max(0.01)).sqrt();
Ok(const_f64(amplitude))
} else {
let sqrt_t2_minus_1 = (t * t - 1.0).sqrt();
let eta = v_f64 * (sqrt_t2_minus_1 - (t / sqrt_t2_minus_1).atan());
let amplitude = (eta).exp() / (2.0 * pi * v_f64 * sqrt_t2_minus_1).sqrt();
Ok(const_f64(amplitude))
}
}
#[allow(dead_code)]
pub fn debye_k<F>(v: F, x: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let v_f64 = v.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert v to f64".to_string())
})?;
let x_f64 = x.to_f64().ok_or_else(|| {
crate::error::SpecialError::ValueError("Failed to convert x to f64".to_string())
})?;
let t = x_f64 / v_f64;
let pi = std::f64::consts::PI;
if t <= 1.0 {
let sqrt_1_minus_t2 = (1.0 - t * t).max(0.0).sqrt();
let eta = v_f64 * (sqrt_1_minus_t2 - ((1.0 + sqrt_1_minus_t2) / t).ln());
let amplitude = (pi / (2.0 * v_f64 * sqrt_1_minus_t2.max(0.01))).sqrt() * (-eta).exp();
Ok(const_f64(amplitude))
} else {
let sqrt_t2_minus_1 = (t * t - 1.0).sqrt();
let eta = v_f64 * ((t / sqrt_t2_minus_1).atan() - sqrt_t2_minus_1);
let amplitude = (pi / (2.0 * v_f64 * sqrt_t2_minus_1)).sqrt() * (-eta).exp();
Ok(const_f64(amplitude))
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_hankel_j_large_x() {
let x = 100.0_f64;
let v = 0.0_f64;
let result = hankel_j(v, x).expect("Hankel expansion failed");
let expected = 0.019985850304223122_f64;
assert_relative_eq!(result, expected, epsilon = 1e-6);
}
#[test]
fn test_hankel_y_large_x() {
let x = 100.0_f64;
let v = 0.0_f64;
let result = hankel_y(v, x).expect("Hankel expansion failed");
let expected = -0.07724431336886536_f64;
assert_relative_eq!(result, expected, epsilon = 2e-5);
}
#[test]
fn test_debye_large_order_small_x() {
let v = 50.0_f64;
let x = 25.0_f64;
let result = debye_j(v, x).expect("Debye expansion failed");
assert!(result.abs() < 1e-10);
}
#[test]
fn test_debye_large_order_large_x() {
let v = 20.0_f64;
let x = 30.0_f64;
let result = debye_j(v, x).expect("Debye expansion failed");
assert!(result.is_finite());
assert!(result.abs() < 0.5);
}
#[test]
fn test_uniform_at_turning_point() {
let v = 20.0_f64;
let x = 20.0_f64;
let result = uniform_j(v, x).expect("Uniform expansion failed");
assert!(result.is_finite());
}
#[test]
fn test_adaptive_selection() {
let x = 100.0_f64;
let v = 5.0_f64;
let result = adaptive_bessel_j(v, x).expect("Adaptive Bessel failed");
assert!(result.is_finite());
assert!(result.abs() < 0.2);
}
}