use numra_core::Scalar;
pub fn caputo_weights<S: Scalar>(n: usize, alpha: S) -> Vec<S> {
let one_minus_alpha = S::ONE - alpha;
let mut b = Vec::with_capacity(n);
for j in 0..n {
let j_plus_1 = S::from_usize(j + 1);
let bj = if j == 0 {
S::ONE } else {
let j_s = S::from_usize(j);
j_plus_1.powf(one_minus_alpha) - j_s.powf(one_minus_alpha)
};
b.push(bj);
}
b
}
pub fn l1_coefficient<S: Scalar>(dt: S, alpha: S) -> S {
let two_minus_alpha = S::from_f64(2.0) - alpha;
dt.powf(-alpha) / gamma(two_minus_alpha)
}
pub fn gamma<S: Scalar>(z: S) -> S {
#[allow(clippy::excessive_precision)]
let p = [
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7,
];
let z_f = z.to_f64();
if z_f < 0.5 {
let pi = std::f64::consts::PI;
let sin_pz = (pi * z_f).sin();
let g1mz = gamma(S::ONE - z);
return S::from_f64(pi) / (S::from_f64(sin_pz) * g1mz);
}
let z_f = z_f - 1.0;
let mut x = p[0];
for (i, coeff) in p.iter().enumerate().skip(1) {
x += coeff / (z_f + i as f64);
}
let t = z_f + 7.5;
let result = (2.0 * std::f64::consts::PI).sqrt() * t.powf(z_f + 0.5) * (-t).exp() * x;
S::from_f64(result)
}
pub fn mittag_leffler<S: Scalar>(z: S, alpha: S, beta: S, max_terms: usize) -> S {
let tol = S::from_f64(1e-15);
let mut sum = S::ZERO;
let term = S::ONE / gamma(beta);
sum += term;
let mut z_pow = S::ONE;
for k in 1..max_terms {
z_pow *= z;
let gamma_arg = alpha * S::from_usize(k) + beta;
let new_term = z_pow / gamma(gamma_arg);
sum += new_term;
if new_term.abs() < tol * sum.abs() {
break;
}
}
sum
}
pub fn mittag_leffler_1<S: Scalar>(z: S, alpha: S) -> S {
mittag_leffler(z, alpha, S::ONE, 100)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gamma_integers() {
assert!((gamma(1.0_f64) - 1.0).abs() < 1e-10);
assert!((gamma(2.0_f64) - 1.0).abs() < 1e-10); assert!((gamma(3.0_f64) - 2.0).abs() < 1e-10); assert!((gamma(4.0_f64) - 6.0).abs() < 1e-10); assert!((gamma(5.0_f64) - 24.0).abs() < 1e-9); }
#[test]
fn test_gamma_half() {
let sqrt_pi = std::f64::consts::PI.sqrt();
assert!((gamma(0.5_f64) - sqrt_pi).abs() < 1e-10);
}
#[test]
fn test_caputo_weights() {
let alpha = 0.5_f64;
let b = caputo_weights(5, alpha);
assert!((b[0] - 1.0).abs() < 1e-10); assert!((b[1] - (2.0_f64.sqrt() - 1.0)).abs() < 1e-10);
assert!((b[2] - (3.0_f64.sqrt() - 2.0_f64.sqrt())).abs() < 1e-10);
}
#[test]
fn test_mittag_leffler_alpha_1() {
let z = 1.0_f64;
let ml = mittag_leffler_1(z, 1.0);
let exp_z = z.exp();
assert!((ml - exp_z).abs() < 1e-8);
}
#[test]
fn test_mittag_leffler_alpha_2() {
let z = 1.0_f64;
let ml = mittag_leffler_1(z, 2.0);
let cosh_sqrt_z = z.sqrt().cosh();
assert!((ml - cosh_sqrt_z).abs() < 1e-8);
}
#[test]
fn test_mittag_leffler_negative() {
let ml = mittag_leffler_1(-1.0_f64, 0.5);
assert!(ml > 0.0 && ml < 1.0);
}
#[test]
fn test_caputo_weights_alpha_1() {
let b = caputo_weights(5, 1.0_f64);
assert!((b[0] - 1.0).abs() < 1e-10, "b[0] should be 1, got {}", b[0]);
for j in 1..5 {
assert!(b[j].abs() < 1e-10, "b[{}] should be 0, got {}", j, b[j]);
}
}
#[test]
fn test_l1_coefficient() {
let dt = 0.1_f64;
let alpha = 0.5_f64;
let c = l1_coefficient(dt, alpha);
let expected = 10.0_f64.sqrt() / (std::f64::consts::PI.sqrt() / 2.0);
assert!(
(c - expected).abs() < 1e-10,
"l1_coefficient: got {}, expected {}",
c,
expected
);
}
#[test]
fn test_mittag_leffler_e2_negative() {
let z = -4.0_f64;
let ml = mittag_leffler_1(z, 2.0);
let expected = 2.0_f64.cos();
assert!(
(ml - expected).abs() < 1e-6,
"E_2(-4) = cos(2): got {}, expected {}",
ml,
expected
);
}
#[test]
fn test_gamma_half_integer() {
let sqrt_pi = std::f64::consts::PI.sqrt();
let g32 = gamma(1.5_f64);
let expected_32 = sqrt_pi / 2.0;
assert!(
(g32 - expected_32).abs() < 1e-10,
"Gamma(3/2): got {}, expected {}",
g32,
expected_32
);
let g52 = gamma(2.5_f64);
let expected_52 = 3.0 * sqrt_pi / 4.0;
assert!(
(g52 - expected_52).abs() < 1e-10,
"Gamma(5/2): got {}, expected {}",
g52,
expected_52
);
}
}