use numra_core::Scalar;
pub fn dawson<S: Scalar>(x: S) -> S {
let xf = x.to_f64();
S::from_f64(dawson_f64(xf))
}
pub fn fresnel_s<S: Scalar>(x: S) -> S {
let xf = x.to_f64();
S::from_f64(fresnel_s_f64(xf))
}
pub fn fresnel_c<S: Scalar>(x: S) -> S {
let xf = x.to_f64();
S::from_f64(fresnel_c_f64(xf))
}
pub fn mittag_leffler<S: Scalar>(alpha: S, beta: S, z: S) -> S {
let alphaf = alpha.to_f64();
let betaf = beta.to_f64();
let zf = z.to_f64();
if alphaf <= 0.0 {
return S::NAN;
}
S::from_f64(mittag_leffler_f64(alphaf, betaf, zf))
}
pub fn mittag_leffler_1<S: Scalar>(alpha: S, z: S) -> S {
mittag_leffler(alpha, S::ONE, z)
}
fn dawson_f64(x: f64) -> f64 {
let sign = if x < 0.0 { -1.0 } else { 1.0 };
dawson_series(x.abs()) * sign
}
fn dawson_series(x: f64) -> f64 {
let max_terms = 100;
let eps = 1e-15;
let x2 = x * x;
let mut sum = 1.0;
let mut term = 1.0;
for n in 1..max_terms {
term *= -2.0 * x2 / (2 * n + 1) as f64;
sum += term;
if term.abs() < eps * sum.abs() {
break;
}
}
x * sum
}
fn fresnel_s_f64(x: f64) -> f64 {
let sign = if x < 0.0 { -1.0 } else { 1.0 };
let x = x.abs();
let pi_half = core::f64::consts::PI / 2.0;
if x > 4.0 {
return sign * fresnel_s_asymptotic(x);
}
let max_terms = 40;
let eps = 1e-15;
let x4 = x * x * x * x;
let pi_half_sq = pi_half * pi_half;
let mut coeff = pi_half; let mut x_pow = x * x * x;
let mut s = coeff * x_pow / 3.0;
for n in 1..max_terms {
let nf = n as f64;
coeff *= -pi_half_sq / ((2.0 * nf) * (2.0 * nf + 1.0));
x_pow *= x4;
let denom = 4.0 * nf + 3.0;
let term = coeff * x_pow / denom;
s += term;
if term.abs() < eps * s.abs() {
break;
}
}
sign * s
}
fn fresnel_c_f64(x: f64) -> f64 {
let sign = if x < 0.0 { -1.0 } else { 1.0 };
let x = x.abs();
let pi_half = core::f64::consts::PI / 2.0;
if x > 4.0 {
return sign * fresnel_c_asymptotic(x);
}
let max_terms = 40;
let eps = 1e-15;
let x4 = x * x * x * x;
let pi_half_sq = pi_half * pi_half;
let mut coeff = 1.0; let mut x_pow = x;
let mut c = x;
for n in 1..max_terms {
let nf = n as f64;
coeff *= -pi_half_sq / ((2.0 * nf - 1.0) * (2.0 * nf));
x_pow *= x4;
let denom = 4.0 * nf + 1.0;
let term = coeff * x_pow / denom;
c += term;
if term.abs() < eps * c.abs() {
break;
}
}
sign * c
}
fn fresnel_s_asymptotic(x: f64) -> f64 {
let pi = core::f64::consts::PI;
let pix2 = pi * x * x / 2.0;
0.5 - (pix2).cos() / (pi * x) - (pix2).sin() / (pi * pi * x * x * x)
}
fn fresnel_c_asymptotic(x: f64) -> f64 {
let pi = core::f64::consts::PI;
let pix2 = pi * x * x / 2.0;
0.5 + (pix2).sin() / (pi * x) - (pix2).cos() / (pi * pi * x * x * x)
}
fn mittag_leffler_f64(alpha: f64, beta: f64, z: f64) -> f64 {
let max_terms = 200;
let eps = 1e-15;
let mut sum = 0.0;
let mut z_pow = 1.0;
for k in 0..max_terms {
let gamma_arg = alpha * k as f64 + beta;
let g = libm::tgamma(gamma_arg);
if g.is_infinite() || g == 0.0 {
break;
}
let term = z_pow / g;
sum += term;
if k > 0 && term.abs() < eps * sum.abs() {
break;
}
z_pow *= z;
}
sum
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_dawson_at_zero() {
assert_relative_eq!(dawson(0.0_f64), 0.0, epsilon = 1e-14);
}
#[test]
fn test_dawson_known() {
assert!((dawson(1.0_f64).to_f64() - 0.53807950691276841).abs() < 0.01);
}
#[test]
fn test_dawson_odd() {
let x = 0.5_f64;
assert_relative_eq!(dawson(-x), -dawson(x), epsilon = 1e-10);
}
#[test]
fn test_fresnel_at_zero() {
assert_relative_eq!(fresnel_s(0.0_f64), 0.0, epsilon = 1e-14);
assert_relative_eq!(fresnel_c(0.0_f64), 0.0, epsilon = 1e-14);
}
#[test]
fn test_fresnel_limits() {
let s = fresnel_s(100.0_f64);
let c = fresnel_c(100.0_f64);
assert!((s.to_f64() - 0.5).abs() < 0.01);
assert!((c.to_f64() - 0.5).abs() < 0.01);
}
#[test]
fn test_fresnel_s_known() {
assert!((fresnel_s(1.0_f64).to_f64() - 0.43825914739).abs() < 0.01);
}
#[test]
fn test_fresnel_c_known() {
assert!((fresnel_c(1.0_f64).to_f64() - 0.77989340037).abs() < 0.01);
}
#[test]
fn test_mittag_leffler_exp() {
let z = 1.0_f64;
assert_relative_eq!(
mittag_leffler(1.0_f64, 1.0_f64, z),
z.exp(),
epsilon = 1e-10
);
}
#[test]
fn test_mittag_leffler_1() {
let z = 1.0_f64;
assert_relative_eq!(
mittag_leffler(2.0_f64, 1.0_f64, z * z),
z.cosh(),
epsilon = 1e-10
);
}
#[test]
fn test_mittag_leffler_shorthand() {
let z = 0.5_f64;
assert_relative_eq!(
mittag_leffler_1(1.0_f64, z),
mittag_leffler(1.0_f64, 1.0_f64, z),
epsilon = 1e-14
);
}
#[test]
fn test_misc_f32() {
assert!((dawson(0.5_f32).to_f64()).abs() > 0.0);
assert!((mittag_leffler(1.0_f32, 1.0_f32, 1.0_f32).to_f64() - 1.0_f64.exp()).abs() < 0.01);
}
}