use std::f64::consts::{FRAC_2_PI, PI};
use crate::bessel::{j0, j1, y0, y1, yn};
use crate::error::{SpecialError, SpecialResult};
use crate::gamma::gamma;
const LN_PI: f64 = 1.1447298858494002;
#[allow(dead_code)]
pub fn struve(v: f64, x: f64) -> SpecialResult<f64> {
if x.is_nan() || v.is_nan() {
return Err(SpecialError::DomainError("NaN input to struve".to_string()));
}
if x == 0.0 {
if v > -1.0 {
return Ok(0.0);
} else if v == -1.0 {
return Ok(FRAC_2_PI); } else {
return Ok(f64::INFINITY);
}
}
if v == 0.0 {
return struve_h0(x);
}
if v == 1.0 {
return struve_h1(x);
}
if x.abs() < 20.0 {
return struve_series(v, x);
}
struve_asymptotic(v, x)
}
#[allow(dead_code)]
fn struve_h0(x: f64) -> SpecialResult<f64> {
if x.abs() < 20.0 {
let mut sum: f64 = 0.0;
let x2 = x * x;
for k in 0..30 {
let factor = if k == 0 { 1.0 } else { -1.0f64.powi(k as i32) };
let term = factor * x2.powi(k as i32) / ((2.0 * k as f64 + 1.0) * fact_squared(k));
sum += term;
if term.abs() < 1e-15 * sum.abs() {
break;
}
}
Ok(sum * 2.0 * x / PI)
} else {
let y0_val = y0(x);
let _j0_val = j0(x);
Ok(y0_val + 2.0 / (PI * x))
}
}
#[allow(dead_code)]
fn struve_h1(x: f64) -> SpecialResult<f64> {
if x.abs() < 20.0 {
let mut sum: f64 = 0.0;
let x2 = x * x;
for k in 0..30 {
let factor = if k % 2 == 0 { 1.0 } else { -1.0 };
let term = factor * x2.powi(k as i32) / ((2.0 * k as f64 + 2.0) * fact_squared(k));
sum += term;
if term.abs() < 1e-15 * sum.abs() {
break;
}
}
Ok(sum * 2.0 * x * x / PI)
} else {
let y1_val = y1(x);
let _j1_val = j1(x);
Ok(y1_val - 2.0 / (PI * x) * (1.0 - 2.0 / (x * x)))
}
}
#[allow(dead_code)]
fn struve_series(v: f64, x: f64) -> SpecialResult<f64> {
let z = 0.5 * x;
let z_squared = z * z;
if z.abs() > 100.0 && v > 10.0 {
return struve_asymptotic(v, x);
}
if z.abs() < 1e-15 {
if v > -1.0 {
return Ok(0.0);
}
if v == -1.0 {
return Ok(FRAC_2_PI);
}
return Ok(f64::INFINITY);
}
let mut sum: f64 = 0.0;
let mut k = 0;
let mut prev_sum = 0.0;
let mut num_equal_terms = 0;
let v_plus_1_5 = v + 1.5;
loop {
let sign = if k % 2 == 0 { 1.0 } else { -1.0 };
let k_f64 = k as f64;
let g1 = if k > 170 {
let log_g1 = LN_PI / 2.0 + (k_f64 + 1.5) * (k_f64 + 1.5).ln() - (k_f64 + 1.5);
log_g1.exp()
} else {
gamma(k_f64 + 1.5)
};
let g2 = if k_f64 + v_plus_1_5 > 170.0 {
let log_g2 = LN_PI / 2.0 + (k_f64 + v_plus_1_5) * (k_f64 + v_plus_1_5).ln()
- (k_f64 + v_plus_1_5);
log_g2.exp()
} else {
gamma(k_f64 + v_plus_1_5)
};
if g1.is_infinite() || g2.is_infinite() || (g1 * g2).abs() < 1e-300 {
break;
}
let pow_result = if k > 100 {
(2.0 * k as f64 * z_squared.ln()).exp()
} else {
z_squared.powi(k)
};
let term = sign * pow_result / (g1 * g2);
if !term.is_finite() {
break;
}
sum += term;
let abs_term = term.abs();
let abs_sum = sum.abs();
let abs_tol = 1e-15;
let rel_tol = 1e-15 * abs_sum.max(1e-300);
if abs_term < abs_tol || abs_term < rel_tol {
break;
}
if (sum - prev_sum).abs() < 1e-15 * abs_sum {
num_equal_terms += 1;
if num_equal_terms > 3 {
break;
}
} else {
num_equal_terms = 0;
}
prev_sum = sum;
if k > 70 {
break;
}
k += 1;
}
let z_pow_v_plus_1 = if v + 1.0 > 700.0 / z.ln() {
f64::INFINITY
} else {
z.powf(v + 1.0)
};
Ok(sum * z_pow_v_plus_1)
}
#[allow(dead_code)]
fn struve_asymptotic(v: f64, x: f64) -> SpecialResult<f64> {
if x.is_nan() || v.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to struve_asymptotic".to_string(),
));
}
if x <= 0.0 {
return Err(SpecialError::DomainError(
"Positive argument expected for asymptotic approximation".to_string(),
));
}
if x > 50.0 && v.abs() < x / 4.0 {
struve_asymptotic_series(v, x)
} else if x > 20.0 {
struve_asymptotic_bessel_based(v, x)
} else {
struve_series(v, x)
}
}
#[allow(dead_code)]
fn struve_asymptotic_series(v: f64, x: f64) -> SpecialResult<f64> {
let bessel_y = if v == 0.0 {
y0(x)
} else if v == 1.0 {
y1(x)
} else if v.fract() == 0.0 && v.abs() < 20.0 {
yn(v as i32, x)
} else {
struve_bessel_y_asymptotic(v, x)?
};
let mut correction = 0.0;
let x_inv = 1.0 / x;
let x_inv_sq = x_inv * x_inv;
let mut x_power = x_inv;
for k in 0..8 {
let u_k = struve_asymptotic_coefficient(k, v);
let term = u_k * x_power;
correction += if k % 2 == 0 { term } else { -term };
if term.abs() < 1e-15 * correction.abs() || term.abs() < 1e-15 {
break;
}
x_power *= x_inv_sq; }
let result = bessel_y + (2.0 / PI) * correction;
if result.is_nan() {
Err(SpecialError::DomainError(
"Result is NaN in asymptotic approximation".to_string(),
))
} else {
Ok(result)
}
}
#[allow(dead_code)]
fn struve_asymptotic_bessel_based(v: f64, x: f64) -> SpecialResult<f64> {
let bessel_y = if v == 0.0 {
y0(x)
} else if v == 1.0 {
y1(x)
} else if v.fract() == 0.0 && v.abs() < 20.0 {
yn(v as i32, x)
} else {
struve_bessel_y_asymptotic(v, x)?
};
let correction = struve_correction_term_enhanced(v, x)?;
Ok(bessel_y + correction)
}
#[allow(dead_code)]
fn struve_bessel_y_asymptotic(v: f64, x: f64) -> SpecialResult<f64> {
if x < 10.0 {
return Err(SpecialError::DomainError(
"x too small for Y_v asymptotic approximation".to_string(),
));
}
let phase = x - v * PI / 2.0 - PI / 4.0;
let amplitude = (2.0 / (PI * x)).sqrt();
let correction = -(v * v - 0.25) / (8.0 * x);
Ok(amplitude * (phase.sin() + correction * phase.cos()))
}
#[allow(dead_code)]
fn struve_asymptotic_coefficient(k: usize, v: f64) -> f64 {
match k {
0 => {
(4.0 * v * v - 1.0) / 8.0
}
1 => {
let v2 = v * v;
(4.0 * v2 - 1.0) * (4.0 * v2 - 9.0) / 128.0
}
2 => {
let v2 = v * v;
(4.0 * v2 - 1.0) * (4.0 * v2 - 9.0) * (4.0 * v2 - 25.0) / 3072.0
}
3 => {
let v2 = v * v;
let factor1 = 4.0 * v2 - 1.0;
let factor2 = 4.0 * v2 - 9.0;
let factor3 = 4.0 * v2 - 25.0;
let factor4 = 4.0 * v2 - 49.0;
factor1 * factor2 * factor3 * factor4 / 73728.0
}
_ => {
let mut product = 1.0;
let v2 = v * v;
for n in 1..=(k + 1) {
let odd_num = (2 * n - 1) as f64;
let odd_square = odd_num * odd_num;
product *= 4.0 * v2 - odd_square;
}
let denominator = 8.0_f64.powi(k as i32 + 1) * factorial_ratio(2 * k + 1, k);
product / denominator
}
}
}
#[allow(dead_code)]
fn struve_correction_term_enhanced(v: f64, x: f64) -> SpecialResult<f64> {
if v == 0.0 {
let leading = 2.0 / (PI * x);
let correction = -1.0 / (2.0 * PI * x.powi(3)); Ok(leading + correction)
} else if v == 1.0 {
let leading = -2.0 / (PI * x) * (1.0 - 2.0 / (x * x));
let correction = 8.0 / (3.0 * PI * x.powi(5)); Ok(leading + correction)
} else {
let log_gamma_v_plus_half = log_gamma(v + 0.5)?;
let log_sqrt_pi = 0.5 * PI.ln();
let log_x_half_pow = (v - 1.0) * (0.5 * x).ln();
let log_leading = (2.0 / PI).ln() + log_x_half_pow - log_gamma_v_plus_half - log_sqrt_pi;
if log_leading > 700.0 {
return Ok(f64::INFINITY);
} else if log_leading < -700.0 {
return Ok(0.0);
}
let leading_term = log_leading.exp();
let correction_factor = 1.0 - (v * v - 0.25) / (2.0 * x * x);
Ok(leading_term * correction_factor)
}
}
#[allow(dead_code)]
fn factorial_ratio(n: usize, k: usize) -> f64 {
if k > n {
return 0.0;
}
let mut result = 1.0;
for i in (k + 1)..=n {
result *= i as f64;
}
result
}
#[allow(dead_code)]
fn it_mod_struve_1_asymptotic(x: f64) -> SpecialResult<f64> {
if x < 5.0 {
return Err(SpecialError::DomainError(
"x too small for asymptotic approximation".to_string(),
));
}
let mod_struve_0 = mod_struve(0.0, x)?;
let it_mod_struve_0 = it_mod_struve0(x)?;
let main_term = x * mod_struve_0 - it_mod_struve_0;
let correction1 = 2.0 / PI; let correction2 = -1.0 / (PI * x); let correction3 = 1.0 / (2.0 * PI * x * x);
Ok(main_term + correction1 + correction2 + correction3)
}
#[allow(dead_code)]
fn log_gamma(x: f64) -> SpecialResult<f64> {
if x <= 0.0 {
return Err(SpecialError::DomainError(
"log_gamma requires positive argument".to_string(),
));
}
if x > 170.0 {
let log_2pi = PI.ln() + 2.0_f64.ln();
let result = (x - 0.5) * x.ln() - x + 0.5 * log_2pi + 1.0 / (12.0 * x);
Ok(result)
} else {
let g = gamma(x);
Ok(g.ln())
}
}
#[allow(dead_code)]
pub fn mod_struve(v: f64, x: f64) -> SpecialResult<f64> {
if x.is_nan() || v.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to mod_struve".to_string(),
));
}
if x == 0.0 {
if v > -1.0 {
return Ok(0.0);
} else if v == -1.0 {
return Ok(FRAC_2_PI); } else {
return Ok(f64::INFINITY);
}
}
if x.abs() < 20.0 {
let z = 0.5 * x;
let z_squared = z * z;
if z.abs() < 1e-15 {
if v > -1.0 {
return Ok(0.0);
}
if v == -1.0 {
return Ok(FRAC_2_PI);
}
return Ok(f64::INFINITY);
}
let mut sum: f64 = 0.0;
let mut k = 0;
let mut prev_sum = 0.0;
let mut num_equal_terms = 0;
let v_plus_1_5 = v + 1.5;
loop {
let k_f64 = k as f64;
let g1 = if k > 170 {
let log_g1 = LN_PI / 2.0 + (k_f64 + 1.5) * (k_f64 + 1.5).ln() - (k_f64 + 1.5);
log_g1.exp()
} else {
gamma(k_f64 + 1.5)
};
let g2 = if k_f64 + v_plus_1_5 > 170.0 {
let log_g2 = LN_PI / 2.0 + (k_f64 + v_plus_1_5) * (k_f64 + v_plus_1_5).ln()
- (k_f64 + v_plus_1_5);
log_g2.exp()
} else {
gamma(k_f64 + v_plus_1_5)
};
if g1.is_infinite() || g2.is_infinite() || (g1 * g2).abs() < 1e-300 {
break;
}
let pow_result = if k > 100 {
(2.0 * k as f64 * z_squared.ln()).exp()
} else {
z_squared.powi(k)
};
let term = pow_result / (g1 * g2);
if !term.is_finite() {
break;
}
sum += term;
let abs_term = term.abs();
let abs_sum = sum.abs();
let abs_tol = 1e-15;
let rel_tol = 1e-15 * abs_sum.max(1e-300);
if abs_term < abs_tol || abs_term < rel_tol {
break;
}
if (sum - prev_sum).abs() < 1e-15 * abs_sum {
num_equal_terms += 1;
if num_equal_terms > 3 {
break;
}
} else {
num_equal_terms = 0;
}
prev_sum = sum;
if k > 70 {
break;
}
k += 1;
}
let z_pow_v_plus_1 = if v + 1.0 > 700.0 / z.ln() {
f64::INFINITY
} else {
z.powf(v + 1.0)
};
Ok(sum * z_pow_v_plus_1)
} else {
if x > 100.0 && v > 20.0 {
let z = 0.5 * x;
let mut log_sum = f64::NEG_INFINITY; let mut k = 0;
while k < 50 {
let k_f64 = k as f64;
let log_g1 = log_gamma(k_f64 + 1.5)?;
let log_g2 = log_gamma(k_f64 + v + 1.5)?;
let log_term = 2.0 * k as f64 * z.ln() - log_g1 - log_g2;
if log_sum == f64::NEG_INFINITY {
log_sum = log_term;
} else {
let max_log = log_sum.max(log_term);
log_sum = max_log
+ (log_sum - max_log).exp().ln_1p()
+ (log_term - max_log).exp().ln();
}
if (log_term - log_sum).exp() < 1e-15 && k > 10 {
break;
}
k += 1;
}
let log_result = log_sum + (v + 1.0) * z.ln();
if log_result > 700.0 {
return Ok(f64::INFINITY);
}
return Ok(log_result.exp());
}
if v == 0.0
|| v == 1.0
|| (v.fract() == 0.0 && v.abs() < 20.0)
|| (v > 0.0 && v < 100.0 && x > 10.0)
{
let i_v = bessel_i_approximation(v, x)?;
let correction_term = if v == 0.0 {
2.0 / (PI * x)
} else if v == 1.0 {
let base_term = 2.0 / (PI * x);
if x > 10.0 {
base_term * (1.0 + 2.0 / (x * x))
} else {
let x_sq = x * x;
base_term * ((x_sq + 2.0) / x_sq)
}
} else {
let log_gamma_v_plus_half = log_gamma(v + 0.5)?;
let log_sqrt_pi = 0.5 * PI.ln();
let log_x_half_pow = (v - 1.0) * (0.5 * x).ln();
let log_correction =
(2.0 / PI).ln() + log_x_half_pow - log_gamma_v_plus_half - log_sqrt_pi;
if log_correction > 700.0 {
f64::INFINITY
} else if log_correction < -700.0 {
0.0 } else {
log_correction.exp()
}
};
if i_v.is_infinite() && correction_term.is_infinite() {
Err(SpecialError::DomainError(
"Indeterminate result (∞ - ∞) in mod_struve".to_string(),
))
} else if i_v.is_infinite() {
Ok(i_v) } else {
let result = i_v - correction_term;
if result.is_nan() {
Err(SpecialError::DomainError(
"Result is NaN in mod_struve calculation".to_string(),
))
} else {
Ok(result)
}
}
} else {
mod_struve(v, x)
}
}
}
#[allow(dead_code)]
fn bessel_i_approximation(v: f64, x: f64) -> SpecialResult<f64> {
if v.is_nan() || x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to bessel_i_approximation".to_string(),
));
}
if x <= 0.0 {
return Err(SpecialError::DomainError(
"Argument must be positive in bessel_i_approximation".to_string(),
));
}
if x < 5.0 {
let mut sum = 1.0; let x_half = x / 2.0;
let x_half_sq = x_half * x_half;
if v == 0.0 {
let mut term: f64 = 1.0;
for k in 1..40 {
term *= x_half_sq / (k as f64 * k as f64);
sum += term;
if term < 1e-15 * sum && k > 10 {
break;
}
}
return Ok(sum);
} else if v == 1.0 {
sum = 0.0;
let mut term = x_half;
sum += term;
for k in 1..40 {
term *= x_half_sq / (k as f64 * (k + 1) as f64);
sum += term;
if term < 1e-15 * sum && k > 10 {
break;
}
}
return Ok(sum);
}
}
let _one_over_x = 1.0 / x;
let v_squared = v * v;
let mu = 4.0 * v_squared - 1.0;
let mut series = 1.0;
if x > 10.0 {
series += mu / (8.0 * x);
if x > 20.0 {
let term2 = mu * (mu - 8.0) / (2.0 * 64.0 * x * x);
series += term2;
if x > 40.0 {
let term3 = mu * (mu - 8.0) * (mu - 24.0) / (6.0 * 512.0 * x * x * x);
series += term3;
}
}
}
let result = if x > 700.0 {
let log_result = x - 0.5 * (2.0 * PI * x).ln() + series.ln();
if log_result > 700.0 {
f64::INFINITY
} else {
log_result.exp()
}
} else {
(x.exp() / (2.0 * PI * x).sqrt()) * series
};
Ok(result)
}
#[allow(dead_code)]
fn fact_squared(n: usize) -> f64 {
if n == 0 {
return 1.0;
}
if n <= 10 {
let mut result = 1.0;
for i in 1..=n {
let i_f64 = i as f64;
result *= i_f64 * i_f64;
}
return result;
}
let mut log_result = 0.0;
for i in 1..=n {
log_result += 2.0 * (i as f64).ln();
}
if log_result > 700.0 {
return f64::INFINITY;
}
log_result.exp()
}
#[allow(dead_code)]
pub fn it_struve0(x: f64) -> SpecialResult<f64> {
if x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to it_struve0".to_string(),
));
}
if x == 0.0 {
return Ok(0.0);
}
if x.abs() < 1e-15 {
return Ok(0.0);
}
if x.abs() < 20.0 {
let mut sum: f64 = 0.0;
let x2 = x * x;
let mut prev_sum = 0.0;
let mut num_equal_terms = 0;
for k in 0..50 {
let factor = if k % 2 == 0 { 1.0 } else { -1.0 };
let k_f64 = k as f64;
let denom1 = 2.0 * k_f64 + 1.0;
let denom2 = 2.0 * k_f64 + 3.0;
let term = if k > 15 {
let log_x2k = 2.0 * k_f64 * x2.ln();
let log_fact_squared = fact_squared_log(k);
let log_term = log_x2k - (denom1 * denom2).ln() - log_fact_squared;
factor * log_term.exp()
} else {
let fac_sq = fact_squared(k);
factor * x2.powi(k as i32) / (denom1 * denom2 * fac_sq)
};
if !term.is_finite() {
break;
}
sum += term;
let abs_term = term.abs();
let abs_sum = sum.abs().max(1e-300);
if (abs_term < 1e-15) || (abs_term < 1e-15 * abs_sum) {
break;
}
if (sum - prev_sum).abs() < 1e-15 * abs_sum {
num_equal_terms += 1;
if num_equal_terms > 3 {
break;
}
} else {
num_equal_terms = 0;
}
prev_sum = sum;
}
Ok(sum * 2.0 * x * x / PI)
} else {
let struve_0 = struve(0.0, x)?;
let bessel_j1 = j1(x);
if x > 100.0 {
let term1 = struve_0 * x;
let term2 = bessel_j1 * x;
let term3 = 1.0 - 2.0 / PI;
let result = term3 + (term1 - term2);
if result.is_nan() {
Err(SpecialError::DomainError(
"NaN result in it_struve0 calculation".to_string(),
))
} else {
Ok(result)
}
} else {
Ok(struve_0 * x - bessel_j1 * x + 1.0 - 2.0 / PI)
}
}
}
#[allow(dead_code)]
fn fact_squared_log(n: usize) -> f64 {
if n == 0 {
return 0.0; }
let mut result = 0.0;
for i in 1..=n {
result += 2.0 * (i as f64).ln();
}
result
}
#[allow(dead_code)]
pub fn it2_struve0(x: f64) -> SpecialResult<f64> {
if x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to it2_struve0".to_string(),
));
}
if x == 0.0 {
return Ok(0.0);
}
if x.abs() < 20.0 {
let mut sum: f64 = 0.0;
let x2 = x * x;
for k in 0..30 {
let factor = if k % 2 == 0 { 1.0 } else { -1.0 };
let term = factor * x2.powi(k as i32)
/ ((2.0 * k as f64 + 1.0)
* (2.0 * k as f64 + 3.0)
* (2.0 * k as f64 + 5.0)
* fact_squared(k));
sum += term;
if term.abs() < 1e-15 * sum.abs() {
break;
}
}
Ok(sum * 2.0 * x * x * x / PI)
} else {
it_mod_struve_1_asymptotic(x)
}
}
#[allow(dead_code)]
pub fn it_mod_struve0(x: f64) -> SpecialResult<f64> {
if x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to it_mod_struve0".to_string(),
));
}
if x == 0.0 {
return Ok(0.0);
}
if x.abs() < 20.0 {
let mut sum: f64 = 0.0;
let x2 = x * x;
for k in 0..30 {
let term = x2.powi(k as i32)
/ ((2.0 * k as f64 + 1.0) * (2.0 * k as f64 + 3.0) * fact_squared(k));
sum += term;
if term.abs() < 1e-15 * sum.abs() {
break;
}
}
Ok(sum * 2.0 * x * x / PI)
} else {
let mod_struve_0 = mod_struve(0.0, x)?;
let bessel_i1 = bessel_i_approximation(1.0, x)?;
Ok(mod_struve_0 * x - bessel_i1 * x + 2.0 / PI * (x.exp() - 1.0))
}
}