use scirs2_core::numeric::Complex64;
use std::f64::consts::{PI, SQRT_2};
use scirs2_core::numeric::Zero;
use crate::bessel::{i0, i1, k0, k1};
use crate::error::{SpecialError, SpecialResult};
const SQRT_2_2: f64 = SQRT_2 / 2.0;
#[allow(dead_code)]
pub fn kelvin(x: f64) -> SpecialResult<(Complex64, Complex64, Complex64, Complex64)> {
if x.is_nan() {
return Err(SpecialError::DomainError("NaN input to kelvin".to_string()));
}
if x == 0.0 {
let be = Complex64::new(1.0, 0.0);
let ke = Complex64::new(f64::INFINITY, -PI / 2.0);
let bep = Complex64::new(0.0, 0.0);
let kep = Complex64::new(f64::NEG_INFINITY, 0.0);
return Ok((be, ke, bep, kep));
}
if x.abs() < 1e-200 {
let be = Complex64::new(1.0, 0.0);
let ke = Complex64::new(f64::INFINITY, -PI / 2.0);
let bep = Complex64::new(0.0, 0.0);
let kep = Complex64::new(f64::NEG_INFINITY, 0.0);
return Ok((be, ke, bep, kep));
}
if x < 0.0 {
let (be, ke, bep, kep) = kelvin(-x)?;
return Ok((
Complex64::new(be.re, -be.im),
Complex64::new(-ke.re, -ke.im),
Complex64::new(-bep.re, bep.im),
Complex64::new(kep.re, kep.im),
));
}
if x <= 8.0 {
return compute_kelvin_series(x);
}
compute_kelvin_bessel(x)
}
#[allow(dead_code)]
fn compute_kelvin_bessel(x: f64) -> SpecialResult<(Complex64, Complex64, Complex64, Complex64)> {
let z = Complex64::new(x * SQRT_2_2, x * SQRT_2_2);
let j0_z = compute_j0_complex(z)?;
let j1_z = compute_j1_complex(z)?;
let k0_z = compute_k0_complex(z)?;
let k1_z = compute_k1_complex(z)?;
let be = Complex64::new(j0_z.re, j0_z.im);
let ke = Complex64::new(k0_z.re, k0_z.im);
let bep = -j1_z * Complex64::new(SQRT_2_2, SQRT_2_2);
let kep = -k1_z * Complex64::new(SQRT_2_2, SQRT_2_2);
Ok((be, ke, bep, kep))
}
#[allow(dead_code)]
fn compute_kelvin_series(x: f64) -> SpecialResult<(Complex64, Complex64, Complex64, Complex64)> {
let x_2 = x / 2.0;
let x_2_sq = x_2 * x_2;
let mut ber = 1.0;
let mut bei = 0.0;
let mut term = 1.0;
let mut k = 1;
let _sign = 1.0;
while k <= 60 {
term *= x_2_sq / (k as f64 * k as f64);
if k % 4 == 0 {
ber += term;
} else if k % 4 == 2 {
ber -= term;
} else if k % 4 == 1 {
bei += term;
} else {
bei -= term;
}
let abs_tol = 1e-15;
let rel_tol = 1e-15 * (ber.abs().max(bei.abs()).max(1e-300));
if term.abs() < abs_tol || term.abs() < rel_tol {
break;
}
k += 1;
}
let mut _berp = 0.0;
let mut _beip = 0.0;
let mut berp_val = 0.0;
let mut beip_val = 0.0;
if x != 0.0 {
k = 1;
term = x / 2.0;
while k <= 60 {
if k % 4 == 1 {
berp_val -= term;
} else if k % 4 == 3 {
berp_val += term;
} else if k % 4 == 0 {
beip_val -= term;
} else {
beip_val += term;
}
term *= x_2_sq / (k as f64 * (k + 1) as f64);
let abs_tol = 1e-15;
let rel_tol = 1e-15 * (berp_val.abs().max(beip_val.abs()).max(1e-300));
if term.abs() < abs_tol || term.abs() < rel_tol {
break;
}
k += 1;
}
}
let (ker, kei) = compute_ker_kei_series(x)?;
let (kerp, keip) = compute_kerp_keip_series(x)?;
let be = Complex64::new(ber, bei);
let ke = Complex64::new(ker, kei);
let bep = Complex64::new(berp_val, beip_val);
let kep = Complex64::new(kerp, keip);
Ok((be, ke, bep, kep))
}
#[allow(dead_code)]
fn compute_ker_kei_series(x: f64) -> SpecialResult<(f64, f64)> {
if x == 0.0 {
return Ok((f64::INFINITY, -PI / 2.0));
}
let x_2 = x / 2.0;
let x_2_sq = x_2 * x_2;
let ln_x_2 = (x_2).ln();
let euler_gamma = 0.577_215_664_901_532_9;
let mut ker = -ln_x_2;
let mut kei = -PI / 2.0;
let mut term = 1.0;
let mut k = 1;
while k <= 60 {
term *= x_2_sq / (k as f64 * k as f64);
if k % 4 == 0 {
ker -= term * (euler_gamma + ln_x_2 + 0.25 * psi(k));
kei -= PI / 2.0 * term;
} else if k % 4 == 2 {
ker += term * (euler_gamma + ln_x_2 + 0.25 * psi(k));
kei += PI / 2.0 * term;
} else if k % 4 == 1 {
ker -= term * PI / 2.0;
kei += term * (euler_gamma + ln_x_2 + 0.25 * psi(k));
} else {
ker += term * PI / 2.0;
kei -= term * (euler_gamma + ln_x_2 + 0.25 * psi(k));
}
let abs_tol = 1e-15;
let rel_tol = 1e-15 * (ker.abs().max(kei.abs()).max(1e-300));
if term.abs() < abs_tol || term.abs() < rel_tol {
break;
}
k += 1;
}
Ok((ker, kei))
}
#[allow(dead_code)]
fn compute_kerp_keip_series(x: f64) -> SpecialResult<(f64, f64)> {
if x == 0.0 {
return Ok((f64::NEG_INFINITY, 0.0));
}
let (ker, kei) = compute_ker_kei_series(x)?;
let x_2 = x / 2.0;
let x_2_sq = x_2 * x_2;
let ln_x_2 = (x_2).ln();
let euler_gamma = 0.577_215_664_901_532_9;
let mut kerp = -1.0 / (2.0 * x);
let mut keip = -PI / (4.0 * x);
let mut term = 0.5;
let mut k = 1;
while k <= 60 {
term *= x_2_sq / (k as f64 * (k + 1) as f64);
if k % 4 == 0 {
kerp += term * (euler_gamma + ln_x_2 + 0.25 * psi(k + 1));
keip += PI / 2.0 * term;
} else if k % 4 == 2 {
kerp -= term * (euler_gamma + ln_x_2 + 0.25 * psi(k + 1));
keip -= PI / 2.0 * term;
} else if k % 4 == 1 {
kerp += term * PI / 2.0;
keip -= term * (euler_gamma + ln_x_2 + 0.25 * psi(k + 1));
} else {
kerp -= term * PI / 2.0;
keip += term * (euler_gamma + ln_x_2 + 0.25 * psi(k + 1));
}
let abs_tol = 1e-15;
let rel_tol = 1e-15 * (kerp.abs().max(keip.abs()).max(1e-300));
if term.abs() < abs_tol || term.abs() < rel_tol {
break;
}
k += 1;
}
let kerp_check = -kei / 2.0 - 1.0 / (2.0 * x);
let keip_check = ker / 2.0 - PI / (4.0 * x);
if (kerp - kerp_check).abs() > 0.01 * kerp.abs()
|| (keip - keip_check).abs() > 0.01 * keip.abs()
{
kerp = kerp_check;
keip = keip_check;
}
Ok((kerp, keip))
}
#[allow(dead_code)]
fn compute_j0_complex(z: Complex64) -> SpecialResult<Complex64> {
if z.norm() < 1e-10 {
return Ok(Complex64::new(1.0, 0.0));
}
if z.re.is_nan() || z.im.is_nan() || z.re.is_infinite() || z.im.is_infinite() {
return Err(SpecialError::DomainError(
"Invalid complex input to J₀".to_string(),
));
}
if z.norm() < 15.0 {
let mut sum = Complex64::new(1.0, 0.0);
let z2 = -z * z / 4.0;
let mut k_factorial_squared = 1.0;
for k in 1..100 {
k_factorial_squared *= k as f64 * k as f64;
if k_factorial_squared == f64::INFINITY {
break;
}
let term = z2.powf(k as f64) / k_factorial_squared;
sum += term;
let abs_tol = 1e-15;
let rel_tol = 1e-15 * sum.norm().max(1e-300);
let term_norm = (z2.powf(k as f64) / k_factorial_squared).norm();
if term_norm < abs_tol || term_norm < rel_tol {
break;
}
if k > 60 && term_norm < 1e-10 {
break;
}
}
return Ok(sum);
}
let scale_factor = if z.norm() > 1e100 {
let scale = 1.0 / z.norm();
z * scale
} else {
z
};
let sqrt_pi_2z = (PI / (2.0 * scale_factor)).sqrt();
let phase = z - PI / 4.0;
let one_over_z_sq = 1.0 / (z * z);
let p_term = 1.0 - 0.125 * one_over_z_sq + 0.073242 * one_over_z_sq * one_over_z_sq;
let q_term = -0.0625 * one_over_z_sq + 0.097656 * one_over_z_sq * one_over_z_sq;
let cos_phase = phase.cos();
let sin_phase = phase.sin();
let j0_z = sqrt_pi_2z * (cos_phase * p_term - sin_phase * q_term);
Ok(j0_z)
}
#[allow(dead_code)]
fn compute_j1_complex(z: Complex64) -> SpecialResult<Complex64> {
if z.is_zero() {
return Ok(Complex64::new(0.0, 0.0));
}
if z.re.is_nan() || z.im.is_nan() || z.re.is_infinite() || z.im.is_infinite() {
return Err(SpecialError::DomainError(
"Invalid complex input to J₁".to_string(),
));
}
if z.norm() < 15.0 {
let mut sum = Complex64::new(0.5, 0.0) * z;
let z2 = -z * z / 4.0;
let mut k_times_k_plus_1_factorial = 1.0;
for k in 1..100 {
k_times_k_plus_1_factorial *= k as f64 * (k + 1) as f64;
if k_times_k_plus_1_factorial == f64::INFINITY {
break;
}
let term = z * z2.powf(k as f64) / (2.0 * k_times_k_plus_1_factorial);
sum += term;
let abs_tol = 1e-15;
let rel_tol = 1e-15 * sum.norm().max(1e-300);
let term_norm = (z * z2.powf(k as f64) / (2.0 * k_times_k_plus_1_factorial)).norm();
if term_norm < abs_tol || term_norm < rel_tol {
break;
}
if k > 60 && term_norm < 1e-10 {
break;
}
}
return Ok(sum);
}
let scale_factor = if z.norm() > 1e100 {
let scale = 1.0 / z.norm();
z * scale
} else {
z
};
let sqrt_pi_2z = (PI / (2.0 * scale_factor)).sqrt();
let phase = z - 3.0 * PI / 4.0;
let one_over_z_sq = 1.0 / (z * z);
let p_term = 1.0 + 0.375 * one_over_z_sq - 0.073242 * one_over_z_sq * one_over_z_sq;
let q_term = 0.1875 * one_over_z_sq - 0.097656 * one_over_z_sq * one_over_z_sq;
let cos_phase = phase.cos();
let sin_phase = phase.sin();
let j1_z = sqrt_pi_2z * (cos_phase * p_term - sin_phase * q_term);
Ok(j1_z)
}
#[allow(dead_code)]
fn compute_k0_complex(z: Complex64) -> SpecialResult<Complex64> {
if z.is_zero() {
return Err(SpecialError::DomainError("K₀(0) is infinite".to_string()));
}
if z.re.is_nan() || z.im.is_nan() || z.re.is_infinite() || z.im.is_infinite() {
return Err(SpecialError::DomainError(
"Invalid complex input to K₀".to_string(),
));
}
if z.im.abs() < 0.01 * z.re.abs() && z.re > 0.0 {
let k0_re = k0(z.re);
let i0_re = i0(z.re);
return Ok(Complex64::new(k0_re, z.im * PI * i0_re / z.re));
}
let iz = Complex64::new(-z.im, z.re);
let sqrt_pi_2iz = (PI / (2.0 * iz)).sqrt();
let phase = iz - Complex64::new(PI / 4.0, 0.0);
let one_over_z_sq = 1.0 / (iz * iz);
let p_term = 1.0 - 0.125 * one_over_z_sq + 0.073242 * one_over_z_sq * one_over_z_sq;
let q_term = -0.0625 * one_over_z_sq + 0.097656 * one_over_z_sq * one_over_z_sq;
let exp_i_phase = (Complex64::i() * phase).exp();
let h0_iz = sqrt_pi_2iz * exp_i_phase * (p_term + Complex64::i() * q_term);
let k0_z = PI / 2.0 * Complex64::i() * h0_iz;
Ok(k0_z)
}
#[allow(dead_code)]
fn compute_k1_complex(z: Complex64) -> SpecialResult<Complex64> {
if z.is_zero() {
return Err(SpecialError::DomainError("K₁(0) is infinite".to_string()));
}
if z.re.is_nan() || z.im.is_nan() || z.re.is_infinite() || z.im.is_infinite() {
return Err(SpecialError::DomainError(
"Invalid complex input to K₁".to_string(),
));
}
if z.im.abs() < 0.01 * z.re.abs() && z.re > 0.0 {
let k1_re = k1(z.re);
let i1_re = i1(z.re);
return Ok(Complex64::new(k1_re, z.im * PI * i1_re / z.re));
}
let iz = Complex64::new(-z.im, z.re);
let sqrt_pi_2iz = (PI / (2.0 * iz)).sqrt();
let phase = iz - Complex64::new(3.0 * PI / 4.0, 0.0);
let one_over_z_sq = 1.0 / (iz * iz);
let p_term = 1.0 + 0.375 * one_over_z_sq - 0.073242 * one_over_z_sq * one_over_z_sq;
let q_term = 0.1875 * one_over_z_sq - 0.097656 * one_over_z_sq * one_over_z_sq;
let exp_i_phase = (Complex64::i() * phase).exp();
let h1_iz = sqrt_pi_2iz * exp_i_phase * (p_term + Complex64::i() * q_term);
let k1_z = -PI / 2.0 * Complex64::i() * h1_iz;
Ok(k1_z)
}
#[allow(dead_code)]
fn psi(n: usize) -> f64 {
let mut result = -0.5772156649015329;
for i in 1..n {
result += 1.0 / i as f64;
}
result
}
#[allow(dead_code)]
pub fn ber(x: f64) -> SpecialResult<f64> {
Ok(kelvin(x)?.0.re)
}
#[allow(dead_code)]
pub fn bei(x: f64) -> SpecialResult<f64> {
Ok(kelvin(x)?.0.im)
}
#[allow(dead_code)]
pub fn ker(x: f64) -> SpecialResult<f64> {
Ok(kelvin(x)?.1.re)
}
#[allow(dead_code)]
pub fn kei(x: f64) -> SpecialResult<f64> {
Ok(kelvin(x)?.1.im)
}
#[allow(dead_code)]
pub fn berp(x: f64) -> SpecialResult<f64> {
Ok(kelvin(x)?.2.re)
}
#[allow(dead_code)]
pub fn beip(x: f64) -> SpecialResult<f64> {
Ok(kelvin(x)?.2.im)
}
#[allow(dead_code)]
pub fn kerp(x: f64) -> SpecialResult<f64> {
Ok(kelvin(x)?.3.re)
}
#[allow(dead_code)]
pub fn keip(x: f64) -> SpecialResult<f64> {
Ok(kelvin(x)?.3.im)
}