use crate::error::{SpecialError, SpecialResult};
#[derive(Debug, Clone)]
pub struct FoxHParams {
pub m: usize,
pub n: usize,
pub p: usize,
pub q: usize,
pub a: Vec<(f64, f64)>,
pub b: Vec<(f64, f64)>,
}
impl FoxHParams {
pub fn validate(&self) -> SpecialResult<()> {
if self.m > self.q {
return Err(SpecialError::ValueError(format!(
"Fox H: m={} must be ≤ q={}",
self.m, self.q
)));
}
if self.n > self.p {
return Err(SpecialError::ValueError(format!(
"Fox H: n={} must be ≤ p={}",
self.n, self.p
)));
}
if self.a.len() != self.p {
return Err(SpecialError::ValueError(format!(
"Fox H: a.len()={} must equal p={}",
self.a.len(),
self.p
)));
}
if self.b.len() != self.q {
return Err(SpecialError::ValueError(format!(
"Fox H: b.len()={} must equal q={}",
self.b.len(),
self.q
)));
}
for (j, &(_, bj)) in self.b.iter().enumerate() {
if bj.abs() < f64::MIN_POSITIVE {
return Err(SpecialError::ValueError(format!(
"Fox H: B_{j} = {bj} must be nonzero"
)));
}
}
for (j, &(_, aj)) in self.a.iter().enumerate() {
if aj.abs() < f64::MIN_POSITIVE {
return Err(SpecialError::ValueError(format!(
"Fox H: A_{j} = {aj} must be nonzero"
)));
}
}
Ok(())
}
}
fn log_abs_gamma(x: f64) -> f64 {
if x <= 0.0 {
let y = 1.0 - x;
let ln_gamma_y = log_abs_gamma(y);
let s = (std::f64::consts::PI * x).sin().abs();
if s < f64::MIN_POSITIVE {
return f64::INFINITY; }
std::f64::consts::PI.ln() - s.ln() - ln_gamma_y
} else {
lanczos_ln_gamma(x)
}
}
fn gamma_sign(x: f64) -> f64 {
if x > 0.0 {
1.0
} else {
let k = (-x).floor() as i64;
if k % 2 == 0 { -1.0 } else { 1.0 }
}
}
fn lanczos_ln_gamma(x: f64) -> f64 {
const G: f64 = 7.0;
const C: [f64; 9] = [
0.999_999_999_999_809_9,
676.520_368_121_885_1,
-1_259.139_216_722_403,
771.323_428_777_653_1,
-176.615_029_162_140_6,
12.507_343_278_686_905,
-0.138_571_095_265_720_12,
9.984_369_578_019_572e-6,
1.505_632_735_149_312_5e-7,
];
let mut y = x;
let mut result = C[0];
for k in 1..9 {
result += C[k] / (y + k as f64 - 1.0);
y += 0.0; }
let t = x + G - 0.5;
(2.0 * std::f64::consts::PI).ln() * 0.5 + (t.ln()) * (x - 0.5) - t + result.ln()
}
fn gamma_fn(x: f64) -> f64 {
if x <= 0.0 && x.fract() == 0.0 {
return f64::INFINITY; }
let sign = gamma_sign(x);
let ln_g = log_abs_gamma(x);
sign * ln_g.exp()
}
pub fn fox_h_function(x: f64, params: &FoxHParams, n_terms: usize) -> SpecialResult<f64> {
if x <= 0.0 {
return Err(SpecialError::DomainError(
"fox_h_function: x must be positive for residue series".to_string(),
));
}
params.validate()?;
let m = params.m;
let n = params.n;
let p = params.p;
let q = params.q;
let mut total = 0.0;
for j in 0..m {
let (bj, bj_cap) = params.b[j];
for k in 0..n_terms {
let kf = k as f64;
let s0 = -(bj + kf) / bj_cap;
let sign_k = if k % 2 == 0 { 1.0 } else { -1.0 };
let factorial_k = factorial_f64(k);
let pole_factor = sign_k / (factorial_k * bj_cap);
let x_power = x.powf(-s0);
if !x_power.is_finite() {
continue;
}
let mut log_num = 0.0f64;
let mut sign_num = 1.0f64;
for i in 0..m {
if i == j {
continue; }
let (bi, bi_cap) = params.b[i];
let arg = bi + bi_cap * s0;
if arg <= 0.0 && arg.fract() == 0.0 {
log_num = f64::NEG_INFINITY;
break;
}
log_num += log_abs_gamma(arg);
sign_num *= gamma_sign(arg);
}
if !log_num.is_finite() {
continue;
}
for i in 0..n {
let (ai, ai_cap) = params.a[i];
let arg = 1.0 - ai - ai_cap * s0;
if arg <= 0.0 && arg.fract() == 0.0 {
log_num = f64::NEG_INFINITY;
break;
}
log_num += log_abs_gamma(arg);
sign_num *= gamma_sign(arg);
}
if !log_num.is_finite() {
continue;
}
let mut log_den = 0.0f64;
let mut sign_den = 1.0f64;
for i in m..q {
let (bi, bi_cap) = params.b[i];
let arg = 1.0 - bi - bi_cap * s0;
if arg <= 0.0 && arg.fract() == 0.0 {
log_den = f64::INFINITY;
break;
}
log_den += log_abs_gamma(arg);
sign_den *= gamma_sign(arg);
}
if log_den.is_infinite() {
continue;
}
for i in n..p {
let (ai, ai_cap) = params.a[i];
let arg = ai + ai_cap * s0;
if arg <= 0.0 && arg.fract() == 0.0 {
log_den = f64::INFINITY;
break;
}
log_den += log_abs_gamma(arg);
sign_den *= gamma_sign(arg);
}
if log_den.is_infinite() {
continue;
}
let log_term = log_num - log_den;
if log_term < -700.0 {
continue;
}
if log_term > 700.0 {
let term = pole_factor * x_power * sign_num / sign_den * (700.0_f64).exp();
total += term;
continue;
}
let gamma_ratio = sign_num / sign_den * log_term.exp();
let term = pole_factor * x_power * gamma_ratio;
if term.is_finite() {
total += term;
}
}
}
Ok(total)
}
pub fn fox_h_asymptotic(
x: f64,
params: &FoxHParams,
n_terms: usize,
) -> SpecialResult<f64> {
if x <= 1.0 {
return Err(SpecialError::DomainError(
"fox_h_asymptotic: x must be > 1 for asymptotic expansion".to_string(),
));
}
params.validate()?;
let n = params.n;
let m = params.m;
let p = params.p;
let q = params.q;
let mut total = 0.0;
for j in 0..n {
let (aj, aj_cap) = params.a[j];
for k in 0..n_terms {
let kf = k as f64;
let s0 = (1.0 - aj + kf) / aj_cap;
let sign_k = if k % 2 == 0 { 1.0 } else { -1.0 };
let factorial_k = factorial_f64(k);
let pole_factor = sign_k / (factorial_k * aj_cap);
let x_power = x.powf(-s0);
if !x_power.is_finite() {
continue;
}
let mut log_num = 0.0f64;
let mut sign_num = 1.0f64;
for i in 0..m {
let (bi, bi_cap) = params.b[i];
let arg = bi + bi_cap * s0;
if arg <= 0.0 && arg.fract() == 0.0 {
log_num = f64::NEG_INFINITY;
break;
}
log_num += log_abs_gamma(arg);
sign_num *= gamma_sign(arg);
}
if !log_num.is_finite() {
continue;
}
for i in 0..n {
if i == j {
continue;
}
let (ai, ai_cap) = params.a[i];
let arg = 1.0 - ai - ai_cap * s0;
if arg <= 0.0 && arg.fract() == 0.0 {
log_num = f64::NEG_INFINITY;
break;
}
log_num += log_abs_gamma(arg);
sign_num *= gamma_sign(arg);
}
if !log_num.is_finite() {
continue;
}
let mut log_den = 0.0f64;
let mut sign_den = 1.0f64;
for i in m..q {
let (bi, bi_cap) = params.b[i];
let arg = 1.0 - bi - bi_cap * s0;
if arg <= 0.0 && arg.fract() == 0.0 {
log_den = f64::INFINITY;
break;
}
log_den += log_abs_gamma(arg);
sign_den *= gamma_sign(arg);
}
if log_den.is_infinite() {
continue;
}
for i in n..p {
let (ai, ai_cap) = params.a[i];
let arg = ai + ai_cap * s0;
if arg <= 0.0 && arg.fract() == 0.0 {
log_den = f64::INFINITY;
break;
}
log_den += log_abs_gamma(arg);
sign_den *= gamma_sign(arg);
}
if log_den.is_infinite() {
continue;
}
let log_term = log_num - log_den;
if !log_term.is_finite() || log_term < -700.0 {
continue;
}
let gamma_ratio = sign_num / sign_den * log_term.min(700.0).exp();
let term = pole_factor * x_power * gamma_ratio;
if term.is_finite() {
total += term;
}
}
}
Ok(total)
}
pub fn meijer_g(
x: f64,
m: usize,
n: usize,
p: usize,
q: usize,
a: &[f64],
b: &[f64],
n_terms: usize,
) -> SpecialResult<f64> {
let a_fox: Vec<(f64, f64)> = a.iter().map(|&ai| (ai, 1.0)).collect();
let b_fox: Vec<(f64, f64)> = b.iter().map(|&bi| (bi, 1.0)).collect();
let params = FoxHParams { m, n, p, q, a: a_fox, b: b_fox };
fox_h_function(x, ¶ms, n_terms)
}
pub fn wright_m_function(x: f64, beta: f64, n_terms: usize) -> SpecialResult<f64> {
if beta <= 0.0 || beta >= 1.0 {
return Err(SpecialError::DomainError(format!(
"wright_m_function: beta={beta} must be in (0,1)"
)));
}
let mut total = 0.0f64;
let mut x_power = 1.0f64; let mut factorial = 1.0f64;
for k in 0..n_terms {
let gamma_arg = -beta * (k as f64) + (1.0 - beta);
let g = gamma_fn(gamma_arg);
if g.is_finite() && g != 0.0 {
let term = x_power / (factorial * g);
if term.is_finite() {
total += term;
}
}
x_power *= -x;
factorial *= (k + 1) as f64;
if !factorial.is_finite() {
break;
}
}
Ok(total)
}
pub fn wright_m_half(x: f64) -> SpecialResult<f64> {
let val = 1.0 / std::f64::consts::PI.sqrt() * (-x * x / 4.0).exp();
Ok(val)
}
fn factorial_f64(n: usize) -> f64 {
let mut result = 1.0f64;
for i in 2..=n {
result *= i as f64;
if !result.is_finite() {
return f64::INFINITY;
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fox_h_exp_minus_x() {
let params = FoxHParams {
m: 1,
n: 0,
p: 0,
q: 1,
a: vec![],
b: vec![(0.0, 1.0)],
};
let x = 1.0;
let v = fox_h_function(x, ¶ms, 25).expect("fox_h exp(-x)");
let expected = (-x).exp();
assert!(
(v - expected).abs() < 1e-8,
"exp(-x): computed={v}, expected={expected}"
);
}
#[test]
fn test_meijer_g_exp() {
let v = meijer_g(1.0, 1, 0, 0, 1, &[], &[0.0], 25).expect("meijer_g exp");
let expected = (-1.0_f64).exp();
assert!(
(v - expected).abs() < 1e-8,
"Meijer G=exp(-x): {v} vs {expected}"
);
}
#[test]
fn test_wright_m_at_zero() {
let beta = 0.5;
let v = wright_m_function(0.0, beta, 30).expect("wright_m x=0");
let expected = 1.0 / gamma_fn(1.0 - beta);
assert!(
(v - expected).abs() < 1e-10,
"M(0;0.5) = {v}, expected {expected}"
);
}
#[test]
fn test_wright_m_half_formula() {
let x = 2.0;
let v_series = wright_m_function(x, 0.5, 50).expect("wright_m series");
let v_exact = wright_m_half(x).expect("wright_m exact");
assert!(
(v_series - v_exact).abs() < 1e-5,
"M(2;0.5): series={v_series}, exact={v_exact}"
);
}
#[test]
fn test_fox_h_validation_m_gt_q() {
let params = FoxHParams {
m: 3,
n: 0,
p: 0,
q: 2,
a: vec![],
b: vec![(0.0, 1.0), (1.0, 1.0)],
};
let result = fox_h_function(1.0, ¶ms, 5);
assert!(result.is_err(), "m > q should fail validation");
}
#[test]
fn test_fox_h_positive_x_required() {
let params = FoxHParams {
m: 1,
n: 0,
p: 0,
q: 1,
a: vec![],
b: vec![(0.0, 1.0)],
};
let result = fox_h_function(-1.0, ¶ms, 5);
assert!(result.is_err(), "negative x should fail");
}
#[test]
fn test_fox_h_asymptotic_large_x() {
let params = FoxHParams {
m: 1,
n: 1,
p: 1,
q: 1,
a: vec![(1.0, 1.0)],
b: vec![(0.0, 1.0)],
};
let v = fox_h_asymptotic(100.0, ¶ms, 5).expect("asymptotic");
assert!(v.is_finite(), "asymptotic result must be finite: {v}");
}
#[test]
fn test_wright_m_beta_range() {
let r1 = wright_m_function(1.0, 0.0, 10);
let r2 = wright_m_function(1.0, 1.0, 10);
assert!(r1.is_err());
assert!(r2.is_err());
}
#[test]
fn test_wright_m_half_x0() {
let v = wright_m_half(0.0).expect("wright_m_half x=0");
let expected = 1.0 / std::f64::consts::PI.sqrt();
assert!((v - expected).abs() < 1e-14, "M(0;1/2): {v} vs {expected}");
}
}