#[derive(Debug, Clone)]
pub struct AsymptoticTerm {
pub coefficient: f64,
pub power: f64,
}
#[derive(Debug, Clone)]
pub struct AsymptoticExpansion {
pub name: String,
pub valid_range: (f64, f64),
pub terms: Vec<(f64, f64)>,
}
impl Default for AsymptoticExpansion {
fn default() -> Self {
Self {
name: String::new(),
valid_range: (f64::NEG_INFINITY, f64::INFINITY),
terms: Vec::new(),
}
}
}
impl AsymptoticExpansion {
pub fn eval(&self, x: f64) -> f64 {
self.terms.iter().map(|(c, p)| c * x.powf(*p)).sum()
}
pub fn relative_error(&self, x: f64, exact: f64) -> f64 {
if exact == 0.0 {
return (self.eval(x) - exact).abs();
}
((self.eval(x) - exact) / exact).abs()
}
pub fn in_range(&self, x: f64) -> bool {
x >= self.valid_range.0 && x <= self.valid_range.1
}
}
pub fn asymptotic_gamma(order: usize) -> AsymptoticExpansion {
let bernoulli_coeffs: &[(f64, f64)] = &[
(1.0 / 12.0, -1.0),
(-1.0 / 360.0, -3.0),
(1.0 / 1260.0, -5.0),
(-1.0 / 1680.0, -7.0),
(1.0 / 1188.0, -9.0),
];
let n_terms = order.min(bernoulli_coeffs.len());
let mut terms: Vec<(f64, f64)> = bernoulli_coeffs[..n_terms].to_vec();
terms.push((0.5 * (2.0 * std::f64::consts::PI).ln(), 0.0)); terms.push((-1.0, 1.0));
AsymptoticExpansion {
name: "ln_gamma_stirling".to_string(),
valid_range: (10.0, f64::INFINITY),
terms,
}
}
pub fn eval_stirling_lngamma(x: f64, order: usize) -> f64 {
let bernoulli_coeffs: &[(f64, f64)] = &[
(1.0 / 12.0, -1.0),
(-1.0 / 360.0, -3.0),
(1.0 / 1260.0, -5.0),
(-1.0 / 1680.0, -7.0),
(1.0 / 1188.0, -9.0),
];
let n_terms = order.min(bernoulli_coeffs.len());
let leading = (x - 0.5) * x.ln() - x + 0.5 * (2.0 * std::f64::consts::PI).ln();
let correction: f64 = bernoulli_coeffs[..n_terms]
.iter()
.map(|(c, p)| c * x.powf(*p))
.sum();
leading + correction
}
pub fn eval_stirling_gamma(x: f64, order: usize) -> f64 {
eval_stirling_lngamma(x, order).exp()
}
pub fn asymptotic_bessel_j(n: i32, order: usize) -> AsymptoticExpansion {
let theta = std::f64::consts::FRAC_PI_4 + (n as f64) * std::f64::consts::FRAC_PI_2;
let mut terms = vec![(theta, f64::NAN)];
terms.push((order as f64, f64::NAN));
AsymptoticExpansion {
name: format!("bessel_j_{n}_large_x"),
valid_range: (10.0, f64::INFINITY),
terms,
}
}
pub fn eval_bessel_j_asymptotic(n: i32, x: f64, order: usize) -> f64 {
let mu = 4.0 * (n as f64) * (n as f64);
let theta = std::f64::consts::FRAC_PI_4 + (n as f64) * std::f64::consts::FRAC_PI_2;
let n_terms = order.min(6);
let mut p = 1.0_f64;
let mut q = 0.0_f64;
let u = 8.0 * x;
let mut term_p = 1.0_f64;
let mut term_q = (mu - 1.0) / u;
q += term_q;
for k in 1..n_terms {
let m = 2 * k;
let num_p = mu - ((2 * m - 1) as f64).powi(2);
let num_p2 = mu - ((2 * m + 1) as f64).powi(2);
term_p *= -num_p * num_p2 / (((2 * k) * (2 * k - 1)) as f64 * u * u);
p += term_p;
let num_q = mu - ((2 * (k + 1) - 1) as f64).powi(2);
term_q *= -num_q / ((2 * k + 1) as f64 * u);
q += term_q;
}
(2.0 / (std::f64::consts::PI * x)).sqrt() * (p * (x - theta).cos() - q * (x - theta).sin())
}
pub fn asymptotic_erf(order: usize) -> AsymptoticExpansion {
let n_terms = order.min(8);
let mut terms = Vec::with_capacity(n_terms);
let mut coeff = 1.0_f64;
terms.push((coeff, 0.0_f64)); for k in 1..n_terms {
coeff *= -((2 * k - 1) as f64) / 2.0;
terms.push((coeff, -((2 * k) as f64)));
}
AsymptoticExpansion {
name: "erfc_large_x".to_string(),
valid_range: (3.0, f64::INFINITY),
terms,
}
}
pub fn eval_erfc_asymptotic(x: f64, order: usize) -> f64 {
let n_terms = order.min(8);
let mut sum = 0.0_f64;
let mut coeff = 1.0_f64;
sum += coeff; for k in 1..n_terms {
coeff *= -((2 * k - 1) as f64) / (2.0 * x * x);
sum += coeff;
if coeff.abs() > 1e10 {
break;
}
}
(-x * x).exp() / (x * std::f64::consts::PI.sqrt()) * sum
}
pub fn asymptotic_1f1(a: f64, b: f64, order: usize) -> AsymptoticExpansion {
let n_terms = order.min(6);
let mut terms = Vec::with_capacity(n_terms);
let mut coeff = 1.0_f64;
terms.push((coeff, 0.0_f64));
for k in 1..n_terms {
coeff *= (b - a + (k - 1) as f64) * (1.0 - a + (k - 1) as f64) / (k as f64);
terms.push((coeff, -(k as f64)));
}
AsymptoticExpansion {
name: format!("hyp1f1_a{a}_b{b}_large_z"),
valid_range: (20.0, f64::INFINITY),
terms,
}
}
pub fn eval_1f1_asymptotic(a: f64, b: f64, z: f64, order: usize) -> f64 {
use crate::gamma::gamma;
let n_terms = order.min(6);
let prefactor = gamma(b) / gamma(a) * z.exp() * z.powf(a - b);
let mut sum = 0.0_f64;
let mut coeff = 1.0_f64;
sum += coeff;
for k in 1..n_terms {
coeff *= (b - a + (k - 1) as f64) * (1.0 - a + (k - 1) as f64) / ((k as f64) * z);
sum += coeff;
if coeff.abs() > 1e10 {
break;
}
}
prefactor * sum
}