#![allow(clippy::approx_constant, clippy::excessive_precision)]
use super::erf::{error_f, error_fc, error_fc_scaled};
use super::eval_pol;
#[inline]
pub fn alnrel(a: f64) -> f64 {
const P1: f64 = -0.129418923021993e1;
const P2: f64 = 0.405303492862024;
const P3: f64 = -0.178874546012214e-1;
const Q1: f64 = -0.162752256355323e1;
const Q2: f64 = 0.747811014037616;
const Q3: f64 = -0.845104217945565e-1;
if a.abs() <= 0.375 {
let t = a / (a + 2.0);
let t2 = t * t;
let num = ((P3 * t2 + P2) * t2 + P1) * t2 + 1.0;
let den = ((Q3 * t2 + Q2) * t2 + Q1) * t2 + 1.0;
2.0 * t * (num / den)
} else {
(1.0 + a).ln()
}
}
#[inline]
pub fn rexp(x: f64) -> f64 {
const P1: f64 = 0.914041914819518e-9;
const P2: f64 = 0.238082361044469e-1;
const Q1: f64 = -0.499999999085958;
const Q2: f64 = 0.107141568980644;
const Q3: f64 = -0.119041179760821e-1;
const Q4: f64 = 0.595130811860248e-3;
if x.abs() <= 0.15 {
let num = (P2 * x + P1) * x + 1.0;
let den = (((Q4 * x + Q3) * x + Q2) * x + Q1) * x + 1.0;
x * (num / den)
} else {
let w = x.exp();
if x <= 0.0 {
(w - 0.5) - 0.5
} else {
w * (0.5 + (0.5 - 1.0 / w))
}
}
}
#[inline]
pub fn dexpm1(x: f64) -> f64 {
const P1: f64 = 0.914041914819518e-9;
const P2: f64 = 0.238082361044469e-1;
const Q1: f64 = -0.499999999085958;
const Q2: f64 = 0.107141568980644;
const Q3: f64 = -0.119041179760821e-1;
const Q4: f64 = 0.595130811860248e-3;
if x.abs() <= 0.15 {
let top = (P2 * x + P1) * x + 1.0;
let bot = (((Q4 * x + Q3) * x + Q2) * x + Q1) * x + 1.0;
x * (top / bot)
} else {
let w = x.exp();
if x <= 0.0 {
(w - 0.5) - 0.5
} else {
w * (0.5 + (0.5 - 1.0 / w))
}
}
}
#[inline]
pub fn rlog(x: f64) -> f64 {
const A: f64 = 0.566749439387324e-1;
const B: f64 = 0.456512608815524e-1;
const P0: f64 = 0.333333333333333;
const P1: f64 = -0.224696413112536;
const P2: f64 = 0.620886815375787e-2;
const Q1: f64 = -0.127408923933623e1;
const Q2: f64 = 0.354508718369557;
if x < 0.61 {
let r = x - 0.5 - 0.5;
return r - x.ln();
}
if x < 1.57 {
let (u, w1) = if x < 0.82 {
let u = (x - 0.7) / 0.7;
(u, A - u * 0.3)
} else if x < 1.18 {
let u = x - 0.5 - 0.5;
(u, 0.0)
} else {
let u = 0.75 * x - 1.0;
(u, B + u / 3.0)
};
let r = u / (u + 2.0);
let t = r * r;
let w = ((P2 * t + P1) * t + P0) / ((Q2 * t + Q1) * t + 1.0);
return 2.0 * t * (1.0 / (1.0 - r) - r * w) + w1;
}
let r = x - 0.5 - 0.5;
r - x.ln()
}
#[inline]
pub fn rlog1(x: f64) -> f64 {
const A: f64 = 0.566749439387324e-1;
const B: f64 = 0.456512608815524e-1;
const P0: f64 = 0.333333333333333;
const P1: f64 = -0.224696413112536;
const P2: f64 = 0.620886815375787e-2;
const Q1: f64 = -0.127408923933623e1;
const Q2: f64 = 0.354508718369557;
if !(-0.39..=0.57).contains(&x) {
let w = x + 0.5 + 0.5;
return x - w.ln();
}
let (h, w1) = if x < -0.18 {
let h = (x + 0.3) / 0.7;
(h, A - h * 0.3)
} else if x > 0.18 {
let h = 0.75 * x - 0.25;
(h, B + h / 3.0)
} else {
(x, 0.0)
};
let r = h / (h + 2.0);
let t = r * r;
let w = ((P2 * t + P1) * t + P0) / ((Q2 * t + Q1) * t + 1.0);
2.0 * t * (1.0 / (1.0 - r) - r * w) + w1
}
#[inline]
pub fn gam1(a: f64) -> f64 {
const S1: f64 = 0.273076135303957;
const S2: f64 = 0.559398236957378e-1;
const P: [f64; 7] = [
0.577215664901533,
-0.409078193005776,
-0.230975380857675,
0.597275330452234e-1,
0.766968181649490e-2,
-0.514889771323592e-2,
0.589597428611429e-3,
];
const Q: [f64; 5] = [
1.0,
0.427569613095214,
0.158451672430138,
0.261132021441447e-1,
0.423244297896961e-2,
];
const R: [f64; 9] = [
-0.422784335098468,
-0.771330383816272,
-0.244757765222226,
0.118378989872749,
0.930357293360349e-3,
-0.118290993445146e-1,
0.223047661158249e-2,
0.266505979058923e-3,
-0.132674909766242e-3,
];
let mut t = a;
let d = a - 0.5;
if d > 0.0 {
t = d - 0.5;
}
if t == 0.0 {
return 0.0;
}
if t > 0.0 {
let top =
((((((P[6] * t + P[5]) * t + P[4]) * t + P[3]) * t + P[2]) * t + P[1]) * t) + P[0];
let bot = (((Q[4] * t + Q[3]) * t + Q[2]) * t + Q[1]) * t + 1.0;
let w = top / bot;
if d <= 0.0 {
a * w
} else {
(t / a) * ((w - 0.5) - 0.5)
}
} else {
let top = (((((((R[8] * t + R[7]) * t + R[6]) * t + R[5]) * t + R[4]) * t + R[3]) * t
+ R[2])
* t
+ R[1])
* t
+ R[0];
let bot = (S2 * t + S1) * t + 1.0;
let w = top / bot;
if d <= 0.0 {
a * ((w + 0.5) + 0.5)
} else {
t * w / a
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, thiserror::Error)]
pub enum GammaDomainError {
#[error("Γ has a pole at {0}")]
Pole(f64),
#[error("Γ({0}) overflows f64")]
Overflow(f64),
#[error("Γ({0}) underflows f64")]
Underflow(f64),
}
#[inline]
pub fn gamma(a: f64) -> f64 {
if a.is_nan() {
return f64::NAN;
}
try_gamma(a).unwrap_or_else(|e| panic!("gamma({a}): {e}"))
}
#[inline]
pub fn try_gamma(a: f64) -> Result<f64, GammaDomainError> {
const D: f64 = 0.41893853320467274178;
const PI: f64 = 3.1415926535898;
const P_COEF: [f64; 7] = [
0.539637273585445e-3,
0.261939260042690e-2,
0.204493667594920e-1,
0.730981088720487e-1,
0.279648642639792,
0.553413866010467,
1.0,
];
const Q_COEF: [f64; 7] = [
-0.832979206704073e-3,
0.470059485860584e-2,
0.225211131035340e-1,
-0.170458969313360,
-0.567902761974940e-1,
0.113062953091122e1,
1.0,
];
const R1: f64 = 0.820756370353826e-3;
const R2: f64 = -0.595156336428591e-3;
const R3: f64 = 0.793650663183693e-3;
const R4: f64 = -0.277777777770481e-2;
const R5: f64 = 0.833333333333333e-1;
const POS_EXPARG: f64 = 709.775_615_066_259_888_4;
let mut x = a;
if a.abs() < 15.0 {
let mut t = 1.0;
let m = (a as i64) - 1;
if m >= 0 {
for _ in 1..=m {
x -= 1.0;
t *= x;
}
x -= 1.0;
} else {
t = a;
if a <= 0.0 {
let mneg = -m - 1;
for _ in 1..=mneg {
x += 1.0;
t *= x;
}
x += 0.5 + 0.5;
t *= x;
if t == 0.0 {
return Err(GammaDomainError::Pole(a));
}
}
if t.abs() < 1e-30 && t.abs() * f64::MAX <= 1.0001 {
return Err(GammaDomainError::Overflow(a));
}
if t.abs() < 1e-30 {
return Ok(1.0 / t);
}
}
let mut top = P_COEF[0];
let mut bot = Q_COEF[0];
for i in 1..7 {
top = P_COEF[i] + x * top;
bot = Q_COEF[i] + x * bot;
}
let g = top / bot;
return Ok(if a < 1.0 { g / t } else { g * t });
}
if a >= 1e3 {
return Err(GammaDomainError::Overflow(a));
}
if a <= -1e3 {
return Err(GammaDomainError::Underflow(a));
}
let mut s = 0.0;
if a <= 0.0 {
x = -a;
let n = x as i64;
let mut t = x - n as f64;
if t > 0.9 {
t = 1.0 - t;
}
s = (PI * t).sin() / PI;
if n % 2 == 0 {
s = -s;
}
if s == 0.0 {
return Err(GammaDomainError::Pole(a));
}
}
let t = 1.0 / (x * x);
let g = ((((R1 * t + R2) * t + R3) * t + R4) * t + R5) / x;
let lnx = x.ln();
let g = D + g + (x - 0.5) * (lnx - 1.0);
let w = g;
let t = g - w;
if w > 0.99999 * POS_EXPARG {
return Err(GammaDomainError::Overflow(a));
}
let mut result = w.exp() * (1.0 + t);
if a < 0.0 {
result = (1.0 / (result * s)) / x;
}
Ok(result)
}
#[inline]
pub fn gamma_ln1(a: f64) -> f64 {
const P0: f64 = 0.577215664901533;
const P1: f64 = 0.844203922187225;
const P2: f64 = -0.168860593646662;
const P3: f64 = -0.780427615533591;
const P4: f64 = -0.402055799310489;
const P5: f64 = -0.673562214325671e-1;
const P6: f64 = -0.271935708322958e-2;
const Q1: f64 = 0.288743195473681e1;
const Q2: f64 = 0.312755088914843e1;
const Q3: f64 = 0.156875193295039e1;
const Q4: f64 = 0.361951990101499;
const Q5: f64 = 0.325038868253937e-1;
const Q6: f64 = 0.667465618796164e-3;
const R0: f64 = 0.422784335098467;
const R1: f64 = 0.848044614534529;
const R2: f64 = 0.565221050691933;
const R3: f64 = 0.156513060486551;
const R4: f64 = 0.170502484022650e-1;
const R5: f64 = 0.497958207639485e-3;
const S1: f64 = 0.124313399877507e1;
const S2: f64 = 0.548042109832463;
const S3: f64 = 0.101552187439830;
const S4: f64 = 0.713309612391000e-2;
const S5: f64 = 0.116165475989616e-3;
if a < 0.6 {
let num = ((((((P6 * a + P5) * a + P4) * a + P3) * a + P2) * a + P1) * a) + P0;
let den = ((((((Q6 * a + Q5) * a + Q4) * a + Q3) * a + Q2) * a + Q1) * a) + 1.0;
-(a * (num / den))
} else {
let x = a - 0.5 - 0.5;
let num = (((((R5 * x + R4) * x + R3) * x + R2) * x + R1) * x) + R0;
let den = (((((S5 * x + S4) * x + S3) * x + S2) * x + S1) * x) + 1.0;
x * (num / den)
}
}
#[derive(Debug, Clone, Copy, PartialEq, thiserror::Error)]
pub enum PsiError {
#[error("ψ has a pole at {0}")]
Pole(f64),
#[error("ψ argument {0} is too large in magnitude (overflow)")]
Overflow(f64),
}
#[inline]
pub fn psi(xx: f64) -> f64 {
if xx.is_nan() {
return f64::NAN;
}
try_psi(xx).unwrap_or_else(|e| panic!("psi({xx}): {e}"))
}
#[inline]
pub fn try_psi(xx: f64) -> Result<f64, PsiError> {
const DX0: f64 = 1.461632144968362341262659542325721325;
const PIOV4: f64 = 0.785398163397448;
const P1: [f64; 7] = [
0.895385022981970e-2,
0.477762828042627e1,
0.142441585084029e3,
0.118645200713425e4,
0.363351846806499e4,
0.413810161269013e4,
0.130560269827897e4,
];
const P2: [f64; 4] = [
-0.212940445131011e1,
-0.701677227766759e1,
-0.448616543918019e1,
-0.648157123766197,
];
const Q1: [f64; 6] = [
0.448452573429826e2,
0.520752771467162e3,
0.221000799247830e4,
0.364127349079381e4,
0.190831076596300e4,
0.691091682714533e-5,
];
const Q2: [f64; 4] = [
0.322703493791143e2,
0.892920700481861e2,
0.546117738103215e2,
0.777788548522962e1,
];
let xmax1 = 2_147_483_647.0_f64;
let xsmall = 1.0e-9;
let mut x = xx;
let mut aug = 0.0;
if x == 0.0 {
return Err(PsiError::Pole(xx));
}
if x < 0.5 {
if x.abs() <= xsmall {
aug = -1.0 / x;
} else {
let mut w = -x;
let mut sgn = PIOV4;
if w <= 0.0 {
w = -w;
sgn = -sgn;
}
if w >= xmax1 {
return Err(PsiError::Overflow(xx));
}
let mut nq = w as i64;
w -= nq as f64;
nq = (w * 4.0) as i64;
w = 4.0 * (w - (nq as f64) * 0.25);
let mut n = nq / 2;
if n + n != nq {
w = 1.0 - w;
}
let z = PIOV4 * w;
let m = n / 2;
if m + m != n {
sgn = -sgn;
}
n = (nq + 1) / 2;
let m2 = n / 2;
if m2 + m2 == n {
if z == 0.0 {
return Err(PsiError::Pole(xx));
}
aug = 4.0 * sgn * (z.cos() / z.sin());
} else {
aug = 4.0 * sgn * (z.sin() / z.cos());
}
}
x = 1.0 - x;
}
if x <= 3.0 {
let mut den = x;
let mut upper = P1[0] * x;
for i in 1..=5 {
den = (den + Q1[i - 1]) * x;
upper = (upper + P1[i]) * x;
}
let den = (upper + P1[6]) / (den + Q1[5]);
Ok(den * (x - DX0) + aug)
} else if x < xmax1 {
let w = (1.0 / x) / x;
let mut den = w;
let mut upper = P2[0] * w;
for i in 1..=3 {
den = (den + Q2[i - 1]) * w;
upper = (upper + P2[i]) * w;
}
let aug = upper / (den + Q2[3]) - 0.5 / x + aug;
Ok(aug + x.ln())
} else {
Ok(aug + x.ln())
}
}
#[inline]
pub fn gamma_log(a: f64) -> f64 {
const C0: f64 = 0.833333333333333e-1;
const C1: f64 = -0.277777777760991e-2;
const C2: f64 = 0.793650666825390e-3;
const C3: f64 = -0.595202931351870e-3;
const C4: f64 = 0.837308034031215e-3;
const C5: f64 = -0.165322962780713e-2;
const D: f64 = 0.418938533204673;
if a <= 0.8 {
return gamma_ln1(a) - a.ln();
}
if a <= 2.25 {
let t = a - 0.5 - 0.5;
return gamma_ln1(t);
}
if a < 10.0 {
let n = (a - 1.25) as i64;
let mut t = a;
let mut w = 1.0;
for _ in 1..=n {
t -= 1.0;
w *= t;
}
return gamma_ln1(t - 1.0) + w.ln();
}
let t = 1.0 / (a * a);
let w = (((((C5 * t + C4) * t + C3) * t + C2) * t + C1) * t + C0) / a;
D + w + (a - 0.5) * (a.ln() - 1.0)
}
#[inline]
pub fn gsumln(a: f64, b: f64) -> f64 {
let x = a + b - 2.0;
if x <= 0.25 {
gamma_ln1(1.0 + x)
} else if x <= 1.25 {
gamma_ln1(x) + alnrel(x)
} else {
gamma_ln1(x - 1.0) + (x * (1.0 + x)).ln()
}
}
#[inline]
pub fn dstrem(z: f64) -> f64 {
const NCOEF: usize = 9;
const COEF: [f64; NCOEF + 1] = [
0.0,
0.0833333333333333333333333333333,
-0.00277777777777777777777777777778,
0.000793650793650793650793650793651,
-0.000595238095238095238095238095238,
0.000841750841750841750841750841751,
-0.00191752691752691752691752691753,
0.00641025641025641025641025641026,
-0.0295506535947712418300653594771,
0.179644372368830573164938490016,
];
const HLN2PI: f64 = 0.91893853320467274178;
if z <= 0.0 {
panic!("dstrem: argument z must be positive (got {z})");
}
if z > 6.0 {
eval_pol(&COEF, 1.0 / (z * z)) * z
} else {
let sterl = HLN2PI + (z - 0.5) * z.ln() - z;
gamma_log(z) - sterl
}
}
#[inline]
pub fn rcomp(a: f64, x: f64) -> f64 {
const RT2PIN: f64 = 0.398942280401433;
if a < 20.0 {
let t = a * x.ln() - x;
if a < 1.0 {
a * t.exp() * (1.0 + gam1(a))
} else {
t.exp() / gamma(a)
}
} else {
let u = x / a;
if u == 0.0 {
return 0.0;
}
let t = (1.0 / a).powi(2);
let mut t1 = (((0.75 * t - 1.0) * t + 3.5) * t - 105.0) / (a * 1260.0);
t1 -= a * rlog(u);
RT2PIN * a.sqrt() * t1.exp()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GammaIncAcc {
#[default]
Max,
Digits6,
Digits3,
}
#[derive(Debug, Clone, Copy, PartialEq, thiserror::Error)]
pub enum GammaIncError {
#[error("parameter a must be non-negative, got {0}")]
ANegative(f64),
#[error("argument x must be non-negative, got {0}")]
XNegative(f64),
#[error("both a and x are zero")]
BothZero,
#[error("indeterminate at a = {a}, x = {x} (deep asymptotic regime)")]
Indeterminate { a: f64, x: f64 },
}
#[inline]
pub fn gamma_inc(a: f64, x: f64) -> (f64, f64) {
gamma_inc_with_acc(a, x, GammaIncAcc::Max)
}
#[inline]
pub fn gamma_inc_with_acc(a: f64, x: f64, accuracy: GammaIncAcc) -> (f64, f64) {
if a.is_nan() || x.is_nan() {
return (f64::NAN, f64::NAN);
}
try_gamma_inc_with_acc(a, x, accuracy)
.unwrap_or_else(|e| panic!("gamma_inc_with_acc({a}, {x}, {accuracy:?}): {e}"))
}
#[inline]
pub fn try_gamma_inc(a: f64, x: f64) -> Result<(f64, f64), GammaIncError> {
try_gamma_inc_with_acc(a, x, GammaIncAcc::Max)
}
pub fn try_gamma_inc_with_acc(
a: f64,
x: f64,
accuracy: GammaIncAcc,
) -> Result<(f64, f64), GammaIncError> {
if a < 0.0 {
return Err(GammaIncError::ANegative(a));
}
if x < 0.0 {
return Err(GammaIncError::XNegative(x));
}
if a == 0.0 && x == 0.0 {
return Err(GammaIncError::BothZero);
}
let (p, q) = gamma_inc_core(a, x, accuracy);
if p == 2.0 {
return Err(GammaIncError::Indeterminate { a, x });
}
Ok((p, q))
}
fn gamma_inc_core(a: f64, x: f64, accuracy: GammaIncAcc) -> (f64, f64) {
use GammaIncAcc::{Digits3, Digits6, Max};
const ALOG10: f64 = 2.30258509299405;
const RT2PIN: f64 = 0.398942280401433;
let (acc_raw, e0, x0, big): (f64, f64, f64, f64) = match accuracy {
Max => (5.0e-15, 0.25e-3, 31.0, 20.0),
Digits6 => (5.0e-7, 0.25e-1, 17.0, 14.0),
Digits3 => (5.0e-4, 0.14, 9.7, 10.0),
};
let acc = acc_raw.max(f64::EPSILON);
let e = f64::EPSILON;
if a * x == 0.0 {
return if x <= a { (0.0, 1.0) } else { (1.0, 0.0) };
}
let r;
if a < 1.0 {
if a == 0.5 {
let rtx = x.sqrt();
return if x < 0.25 {
let ans = error_f(rtx);
(ans, 0.5 + (0.5 - ans))
} else {
let qans = error_fc(rtx);
(0.5 + (0.5 - qans), qans)
};
}
if x < 1.1 {
return taylor_p_over_xa(a, x, acc);
}
let t1 = a * x.ln() - x;
let u = a * t1.exp();
if u == 0.0 {
return (1.0, 0.0);
}
r = u * (1.0 + gam1(a));
return continued_fraction(a, x, r, acc, e);
}
if a < big {
if a <= x && x < x0 {
let twoa = a + a;
let m = twoa as i64;
if twoa == m as f64 {
let ii = m / 2;
let a_eq_i = a == ii as f64;
return finite_q_half_integer(a, x, ii, a_eq_i);
}
}
let t1 = a * x.ln() - x;
r = t1.exp() / gamma(a);
} else {
let l = x / a;
if l == 0.0 {
return (0.0, 1.0); }
let s = 0.5 + (0.5 - l);
let z = rlog(l);
if z >= 700.0 / a {
if s.abs() <= 2.0 * e {
return (2.0, 0.0);
}
return if x <= a { (0.0, 1.0) } else { (1.0, 0.0) };
}
let y = a * z;
let rta = a.sqrt();
if s.abs() <= e0 / rta {
return temme_for_l_eq_1(a, l, z, y, e, accuracy);
}
if s.abs() <= 0.4 {
return temme_general(a, l, z, y, rta, e, accuracy);
}
let t = (1.0 / a).powi(2);
let mut t1 = (((0.75 * t - 1.0) * t + 3.5) * t - 105.0) / (a * 1260.0);
t1 -= y;
r = RT2PIN * rta * t1.exp();
}
if r == 0.0 {
return if x <= a { (0.0, 1.0) } else { (1.0, 0.0) };
}
if x <= a.max(ALOG10) {
return taylor_p_over_r(a, x, r, acc);
}
if x < x0 {
return continued_fraction(a, x, r, acc, e);
}
asymptotic_expansion_q(a, x, r, acc)
}
fn taylor_p_over_xa(a: f64, x: f64, acc: f64) -> (f64, f64) {
let mut an: f64 = 3.0;
let mut c = x;
let mut sum = x / (a + 3.0);
let tol = 3.0 * acc / (a + 1.0);
loop {
an += 1.0;
c = -(c * (x / an));
let t = c / (a + an);
sum += t;
if t.abs() <= tol {
break;
}
}
let j = a * x * ((sum / 6.0 - 0.5 / (a + 2.0)) * x + 1.0 / (a + 1.0));
let z = a * x.ln();
let h = gam1(a);
let g = 1.0 + h;
let use_label_200 = if x < 0.25 { z > -0.13394 } else { a < x / 2.59 };
if use_label_200 {
let l = rexp(z);
let w = 0.5 + (0.5 + l);
let qans = (w * j - l) * g - h;
if qans < 0.0 {
return (1.0, 0.0);
}
let ans = 0.5 + (0.5 - qans);
(ans, qans)
} else {
let w = z.exp();
let ans = w * g * (0.5 + (0.5 - j));
let qans = 0.5 + (0.5 - ans);
(ans, qans)
}
}
fn taylor_p_over_r(a: f64, x: f64, r: f64, acc: f64) -> (f64, f64) {
let mut wk = [0.0_f64; 20];
let mut apn = a + 1.0;
let mut t = x / apn;
wk[0] = t;
let mut n_filled = 20;
for n in 2..=20 {
apn += 1.0;
t *= x / apn;
if t <= 1e-3 {
n_filled = n;
break;
}
wk[n - 1] = t;
}
let mut sum = t;
let tol = 0.5 * acc;
loop {
apn += 1.0;
t *= x / apn;
sum += t;
if t <= tol {
break;
}
}
let max = n_filled - 1;
for m in 1..=max {
let n = n_filled - m;
sum += wk[n - 1];
}
let ans = r / a * (1.0 + sum);
let qans = 0.5 + (0.5 - ans);
(ans, qans)
}
fn continued_fraction(a: f64, x: f64, r: f64, acc: f64, e: f64) -> (f64, f64) {
let tol = (5.0 * e).max(acc);
let mut a2n: f64 = 1.0;
let mut a2nm1: f64 = 1.0;
let mut b2nm1 = x;
let mut b2n = x + (1.0 - a);
let mut c: f64 = 1.0;
loop {
a2nm1 = x * a2n + c * a2nm1;
b2nm1 = x * b2n + c * b2nm1;
let am0 = a2nm1 / b2nm1;
c += 1.0;
let cma = c - a;
a2n = a2nm1 + cma * a2n;
b2n = b2nm1 + cma * b2n;
let an0 = a2n / b2n;
if (an0 - am0).abs() < tol * an0 {
let qans = r * an0;
let ans = 0.5 + (0.5 - qans);
return (ans, qans);
}
}
}
fn asymptotic_expansion_q(a: f64, x: f64, r: f64, acc: f64) -> (f64, f64) {
let mut wk = [0.0_f64; 20];
let mut amn = a - 1.0;
let mut t = amn / x;
wk[0] = t;
let mut n_filled = 20;
for n in 2..=20 {
amn -= 1.0;
t *= amn / x;
if t.abs() <= 1e-3 {
n_filled = n;
break;
}
wk[n - 1] = t;
}
let mut sum = t;
while t.abs() > acc {
amn -= 1.0;
t *= amn / x;
sum += t;
}
let max = n_filled - 1;
for m in 1..=max {
let n = n_filled - m;
sum += wk[n - 1];
}
let qans = r / x * (1.0 + sum);
let ans = 0.5 + (0.5 - qans);
(ans, qans)
}
fn finite_q_half_integer(_a: f64, x: f64, i: i64, a_eq_i: bool) -> (f64, f64) {
let mut sum;
let mut t;
let mut n;
let mut c;
if a_eq_i {
sum = (-x).exp();
t = sum;
n = 1_i64;
c = 0.0_f64;
} else {
let rtx = x.sqrt();
sum = error_fc(rtx);
t = (-x).exp() / (1.77245385090552 * rtx);
n = 0_i64;
c = -0.5_f64;
}
while n != i {
n += 1;
c += 1.0;
t = x * t / c;
sum += t;
}
let qans = sum;
let ans = 0.5 + (0.5 - qans);
(ans, qans)
}
fn temme_general(
a: f64,
l: f64,
z_in: f64,
y: f64,
rta: f64,
e: f64,
accuracy: GammaIncAcc,
) -> (f64, f64) {
use GammaIncAcc::{Digits3, Digits6, Max};
let s = 0.5 + (0.5 - l);
if s.abs() <= 2.0 * e && 3.28e-3 < a * e * e {
return (2.0, 0.0);
}
const D0: [f64; 13] = [
0.833333333333333e-1,
-0.148148148148148e-1,
0.115740740740741e-2,
0.352733686067019e-3,
-0.178755144032922e-3,
0.391926317852244e-4,
-0.218544851067999e-5,
-0.185406221071516e-5,
0.829671134095309e-6,
-0.176659527368261e-6,
0.670785354340150e-8,
0.102618097842403e-7,
-0.438203601845335e-8,
];
const D1: [f64; 12] = [
-0.347222222222222e-2,
0.264550264550265e-2,
-0.990226337448560e-3,
0.205761316872428e-3,
-0.401877572016461e-6,
-0.180985503344900e-4,
0.764916091608111e-5,
-0.161209008945634e-5,
0.464712780280743e-8,
0.137863344691572e-6,
-0.575254560351770e-7,
0.119516285997781e-7,
];
const D2: [f64; 10] = [
-0.268132716049383e-2,
0.771604938271605e-3,
0.200938786008230e-5,
-0.107366532263652e-3,
0.529234488291201e-4,
-0.127606351886187e-4,
0.342357873409614e-7,
0.137219573090629e-5,
-0.629899213838006e-6,
0.142806142060642e-6,
];
const D3: [f64; 8] = [
0.229472093621399e-3,
-0.469189494395256e-3,
0.267720632062839e-3,
-0.756180167188398e-4,
-0.239650511386730e-6,
0.110826541153473e-4,
-0.567495282699160e-5,
0.142309007324359e-5,
];
const D4: [f64; 6] = [
0.784039221720067e-3,
-0.299072480303190e-3,
-0.146384525788434e-5,
0.664149821546512e-4,
-0.396836504717943e-4,
0.113757269706784e-4,
];
const D5: [f64; 4] = [
-0.697281375836586e-4,
0.277275324495939e-3,
-0.199325705161888e-3,
0.679778047793721e-4,
];
const D6: [f64; 2] = [-0.592166437353694e-3, 0.270878209671804e-3];
const D10: f64 = -0.185185185185185e-2;
const D20: f64 = 0.413359788359788e-2;
const D30: f64 = 0.649434156378601e-3;
const D40: f64 = -0.861888290916712e-3;
const D50: f64 = -0.336798553366358e-3;
const D60: f64 = 0.531307936463992e-3;
const D70: f64 = 0.344367606892378e-3;
const THIRD: f64 = 1.0 / 3.0;
const RT2PIN: f64 = 0.398942280401433;
let c = (-y).exp();
let w = 0.5 * error_fc_scaled(y.sqrt());
let u = 1.0 / a;
let mut z = (z_in + z_in).sqrt();
if l < 1.0 {
z = -z;
}
let t = match accuracy {
Max => {
if s.abs() <= 1.0e-3 {
let c0 = ((((((D0[6] * z + D0[5]) * z + D0[4]) * z + D0[3]) * z + D0[2]) * z
+ D0[1])
* z
+ D0[0])
* z
- THIRD;
let c1 =
(((((D1[5] * z + D1[4]) * z + D1[3]) * z + D1[2]) * z + D1[1]) * z + D1[0]) * z
+ D10;
let c2 = ((((D2[4] * z + D2[3]) * z + D2[2]) * z + D2[1]) * z + D2[0]) * z + D20;
let c3 = (((D3[3] * z + D3[2]) * z + D3[1]) * z + D3[0]) * z + D30;
let c4 = (D4[1] * z + D4[0]) * z + D40;
let c5 = (D5[1] * z + D5[0]) * z + D50;
let c6 = D6[0] * z + D60;
((((((D70 * u + c6) * u + c5) * u + c4) * u + c3) * u + c2) * u + c1) * u + c0
} else {
let c0 = ((((((((((((D0[12] * z + D0[11]) * z + D0[10]) * z + D0[9]) * z
+ D0[8])
* z
+ D0[7])
* z
+ D0[6])
* z
+ D0[5])
* z
+ D0[4])
* z
+ D0[3])
* z
+ D0[2])
* z
+ D0[1])
* z
+ D0[0])
* z
- THIRD;
let c1 = (((((((((((D1[11] * z + D1[10]) * z + D1[9]) * z + D1[8]) * z
+ D1[7])
* z
+ D1[6])
* z
+ D1[5])
* z
+ D1[4])
* z
+ D1[3])
* z
+ D1[2])
* z
+ D1[1])
* z
+ D1[0])
* z
+ D10;
let c2 = (((((((((D2[9] * z + D2[8]) * z + D2[7]) * z + D2[6]) * z + D2[5])
* z
+ D2[4])
* z
+ D2[3])
* z
+ D2[2])
* z
+ D2[1])
* z
+ D2[0])
* z
+ D20;
let c3 = (((((((D3[7] * z + D3[6]) * z + D3[5]) * z + D3[4]) * z + D3[3]) * z
+ D3[2])
* z
+ D3[1])
* z
+ D3[0])
* z
+ D30;
let c4 =
(((((D4[5] * z + D4[4]) * z + D4[3]) * z + D4[2]) * z + D4[1]) * z + D4[0]) * z
+ D40;
let c5 = (((D5[3] * z + D5[2]) * z + D5[1]) * z + D5[0]) * z + D50;
let c6 = (D6[1] * z + D6[0]) * z + D60;
((((((D70 * u + c6) * u + c5) * u + c4) * u + c3) * u + c2) * u + c1) * u + c0
}
}
Digits6 => {
let c0 = (((((D0[5] * z + D0[4]) * z + D0[3]) * z + D0[2]) * z + D0[1]) * z + D0[0])
* z
- THIRD;
let c1 = (((D1[3] * z + D1[2]) * z + D1[1]) * z + D1[0]) * z + D10;
let c2 = D2[0] * z + D20;
(c2 * u + c1) * u + c0
}
Digits3 => {
((D0[2] * z + D0[1]) * z + D0[0]) * z - THIRD
}
};
if l < 1.0 {
let ans = c * (w - RT2PIN * t / rta);
let qans = 0.5 + (0.5 - ans);
return (ans, qans);
}
let qans = c * (w + RT2PIN * t / rta);
let ans = 0.5 + (0.5 - qans);
(ans, qans)
}
fn temme_for_l_eq_1(
a: f64,
l: f64,
z_in: f64,
y: f64,
e: f64,
accuracy: GammaIncAcc,
) -> (f64, f64) {
use GammaIncAcc::{Digits3, Digits6, Max};
if 3.28e-3 < a * e * e {
return (2.0, 0.0);
}
const D0: [f64; 13] = [
0.833333333333333e-1,
-0.148148148148148e-1,
0.115740740740741e-2,
0.352733686067019e-3,
-0.178755144032922e-3,
0.391926317852244e-4,
-0.218544851067999e-5,
-0.185406221071516e-5,
0.829671134095309e-6,
-0.176659527368261e-6,
0.670785354340150e-8,
0.102618097842403e-7,
-0.438203601845335e-8,
];
const D1: [f64; 12] = [
-0.347222222222222e-2,
0.264550264550265e-2,
-0.990226337448560e-3,
0.205761316872428e-3,
-0.401877572016461e-6,
-0.180985503344900e-4,
0.764916091608111e-5,
-0.161209008945634e-5,
0.464712780280743e-8,
0.137863344691572e-6,
-0.575254560351770e-7,
0.119516285997781e-7,
];
const D2: [f64; 10] = [
-0.268132716049383e-2,
0.771604938271605e-3,
0.200938786008230e-5,
-0.107366532263652e-3,
0.529234488291201e-4,
-0.127606351886187e-4,
0.342357873409614e-7,
0.137219573090629e-5,
-0.629899213838006e-6,
0.142806142060642e-6,
];
const D3: [f64; 8] = [
0.229472093621399e-3,
-0.469189494395256e-3,
0.267720632062839e-3,
-0.756180167188398e-4,
-0.239650511386730e-6,
0.110826541153473e-4,
-0.567495282699160e-5,
0.142309007324359e-5,
];
const D4: [f64; 6] = [
0.784039221720067e-3,
-0.299072480303190e-3,
-0.146384525788434e-5,
0.664149821546512e-4,
-0.396836504717943e-4,
0.113757269706784e-4,
];
const D5: [f64; 4] = [
-0.697281375836586e-4,
0.277275324495939e-3,
-0.199325705161888e-3,
0.679778047793721e-4,
];
const D6: [f64; 2] = [-0.592166437353694e-3, 0.270878209671804e-3];
const D10: f64 = -0.185185185185185e-2;
const D20: f64 = 0.413359788359788e-2;
const D30: f64 = 0.649434156378601e-3;
const D40: f64 = -0.861888290916712e-3;
const D50: f64 = -0.336798553366358e-3;
const D60: f64 = 0.531307936463992e-3;
const D70: f64 = 0.344367606892378e-3;
const THIRD: f64 = 1.0 / 3.0;
const RT2PIN: f64 = 0.398942280401433;
const RTPI: f64 = 1.77245385090552;
let c = 0.5 + (0.5 - y);
let w = (0.5 - y.sqrt() * (0.5 + (0.5 - y / 3.0)) / RTPI) / c;
let u = 1.0 / a;
let mut z = (z_in + z_in).sqrt();
if l < 1.0 {
z = -z;
}
let t = match accuracy {
Max => {
let c0 = ((((((D0[6] * z + D0[5]) * z + D0[4]) * z + D0[3]) * z + D0[2]) * z + D0[1])
* z
+ D0[0])
* z
- THIRD;
let c1 = (((((D1[5] * z + D1[4]) * z + D1[3]) * z + D1[2]) * z + D1[1]) * z + D1[0])
* z
+ D10;
let c2 = ((((D2[4] * z + D2[3]) * z + D2[2]) * z + D2[1]) * z + D2[0]) * z + D20;
let c3 = (((D3[3] * z + D3[2]) * z + D3[1]) * z + D3[0]) * z + D30;
let c4 = (D4[1] * z + D4[0]) * z + D40;
let c5 = (D5[1] * z + D5[0]) * z + D50;
let c6 = D6[0] * z + D60;
((((((D70 * u + c6) * u + c5) * u + c4) * u + c3) * u + c2) * u + c1) * u + c0
}
Digits6 => {
let c0 = (D0[1] * z + D0[0]) * z - THIRD;
let c1 = D1[0] * z + D10;
(D20 * u + c1) * u + c0
}
Digits3 => {
D0[0] * z - THIRD
}
};
let rta = a.sqrt();
if l < 1.0 {
let ans = c * (w - RT2PIN * t / rta);
(ans, 0.5 + (0.5 - ans))
} else {
let qans = c * (w + RT2PIN * t / rta);
(0.5 + (0.5 - qans), qans)
}
}
#[derive(Debug, Clone, Copy, PartialEq, thiserror::Error)]
pub enum GammaIncInvError {
#[error("parameter a must be positive, got {0}")]
ANotPositive(f64),
#[error("no solution: q/a is too large")]
NoSolution,
#[error("inconsistent inputs: p + q must equal 1")]
InconsistentPq,
#[error("iteration did not converge in 20 steps; last value: {partial}")]
NotConverged {
partial: f64,
},
#[error("iteration failed: intermediate x went non-positive")]
IterationFailed,
#[error("solution obtained but accuracy cannot be certified; value: {value}")]
UncertainAccuracy {
value: f64,
},
#[error("inverse is +∞ (q = 0 means P(a, x) = 1 only as x → +∞)")]
AtInfinity,
}
#[inline]
pub fn gamma_inc_inv(a: f64, x0: f64, p: f64, q: f64) -> (f64, u32) {
if a.is_nan() || x0.is_nan() || p.is_nan() || q.is_nan() {
return (f64::NAN, 0);
}
try_gamma_inc_inv(a, x0, p, q)
.unwrap_or_else(|e| panic!("gamma_inc_inv(a={a}, x0={x0}, p={p}, q={q}): {e}"))
}
#[inline]
pub fn try_gamma_inc_inv(a: f64, x0: f64, p: f64, q: f64) -> Result<(f64, u32), GammaIncInvError> {
const A0: f64 = 3.31125922108741;
const A1: f64 = 11.6616720288968;
const A2: f64 = 4.28342155967104;
const A3: f64 = 0.213623493715853;
const B1: f64 = 6.61053765625462;
const B2: f64 = 6.40691597760039;
const B3: f64 = 1.27364489782223;
const B4: f64 = 0.036117081018842;
const C: f64 = 0.577215664901533; const HALF: f64 = 0.5;
const TWO: f64 = 2.0;
const LN10: f64 = 2.302585;
const AMIN: [f64; 2] = [500.0, 100.0];
const BMIN: [f64; 2] = [1.0e-28, 1.0e-13];
const DMIN: [f64; 2] = [1.0e-6, 1.0e-4];
const EMIN: [f64; 2] = [2.0e-3, 6.0e-3];
const EPS0: [f64; 2] = [1.0e-10, 1.0e-8];
let e = f64::EPSILON;
if a <= 0.0 {
return Err(GammaIncInvError::ANotPositive(a));
}
if (p + q - 1.0).abs() > e {
return Err(GammaIncInvError::InconsistentPq);
}
if p == 0.0 {
return Ok((0.0, 0));
}
if q == 0.0 {
return Err(GammaIncInvError::AtInfinity);
}
if a == 1.0 {
return Ok((if q >= 0.9 { -alnrel(-p) } else { -q.ln() }, 0));
}
let e2 = TWO * e;
let amax = 0.4e-10 / (e * e);
let iop = if e > 1.0e-10 { 1 } else { 0 };
let eps = EPS0[iop];
let xn;
let use_q;
if x0 > 0.0 {
xn = x0;
use_q = p > 0.5;
} else if a < 1.0 {
let g = gamma(a + 1.0);
let qg = q * g;
if qg == 0.0 {
return Err(GammaIncInvError::UncertainAccuracy { value: f64::MAX });
}
let b = qg / a;
let go_to_40 = 0.6 * a < qg;
if !go_to_40 && a < 0.30 && b >= 0.35 {
let t = (-(b + C)).exp();
let u = t * t.exp();
xn = t * u.exp();
use_q = p > 0.5;
} else if !go_to_40 && b >= 0.45 {
xn = initial_approx_small_b(a, p, q, g, b, C);
if xn == 0.0 {
return Err(GammaIncInvError::NoSolution);
}
use_q = p > 0.5;
} else if !go_to_40 && b == 0.0 {
return Err(GammaIncInvError::UncertainAccuracy { value: f64::MAX });
} else if !go_to_40 {
let y = -(b.ln());
let s = HALF + (HALF - a);
let z = y.ln();
let t = y - s * z;
if b >= 0.15 {
xn = y - s * t.ln() - (1.0 + s / (t + 1.0)).ln();
use_q = true; } else if b > 0.01 {
let u = ((t + TWO * (3.0 - a)) * t + (TWO - a) * (3.0 - a))
/ ((t + (5.0 - a)) * t + TWO);
xn = y - s * t.ln() - u.ln();
use_q = true; } else {
let xn_30 = label_30(a, s, z, y);
if BMIN[iop] < b {
xn = xn_30;
use_q = true;
} else {
return Ok((xn_30, 0));
}
}
} else {
xn = initial_approx_small_b(a, p, q, g, b, C);
if xn == 0.0 {
return Err(GammaIncInvError::NoSolution);
}
use_q = p > 0.5;
}
} else {
let w = if q > 0.5 { p.ln() } else { q.ln() };
let t = (-TWO * w).sqrt();
let mut s = t
- (((A3 * t + A2) * t + A1) * t + A0) / ((((B4 * t + B3) * t + B2) * t + B1) * t + 1.0);
if q > 0.5 {
s = -s;
}
let rta = a.sqrt();
let s2 = s * s;
let xn0 = a + s * rta + (s2 - 1.0) / 3.0 + s * (s2 - 7.0) / (36.0 * rta)
- ((3.0 * s2 + 7.0) * s2 - 16.0) / (810.0 * a)
+ s * ((9.0 * s2 + 256.0) * s2 - 433.0) / (38880.0 * a * rta);
let xn0 = xn0.max(0.0);
if AMIN[iop] <= a {
let d = HALF + (HALF - xn0 / a);
if d.abs() <= DMIN[iop] {
return Ok((xn0, 0));
}
}
if p <= 0.5 {
let x_initial = if AMIN[iop] <= a { xn0 } else { 0.0 };
let (refined_xn, early_return) = label_130(a, p, q, xn0, x_initial, EMIN[iop]);
if early_return {
return Ok((refined_xn, 0));
}
xn = refined_xn;
use_q = false; } else if xn0 < 3.0 * a {
xn = xn0;
use_q = true;
} else {
let y = -(w + gamma_log(a));
let d = TWO.max(a * (a - 1.0));
if LN10 * d <= y {
let s = 1.0 - a;
let z = y.ln();
let xn_30 = label_30(a, s, z, y);
xn = xn_30;
use_q = true;
} else {
let t = a - 1.0;
let xn1 = y + t * xn0.ln() - alnrel(-t / (xn0 + 1.0));
let xn2 = y + t * xn1.ln() - alnrel(-t / (xn1 + 1.0));
xn = xn2;
use_q = true;
}
}
}
if use_q {
schroder_q(a, xn, p, q, eps, amax, e2)
} else {
schroder_p(a, xn, p, q, eps, amax, e2)
}
}
fn label_30(a: f64, s: f64, z: f64, y: f64) -> f64 {
const HALF: f64 = 0.5;
const TWO: f64 = 2.0;
let c1 = -s * z;
let c2 = -s * (1.0 + c1);
let c3 = s * ((HALF * c1 + (TWO - a)) * c1 + (2.5 - 1.5 * a));
let c4 = -s
* (((c1 / 3.0 + (2.5 - 1.5 * a)) * c1 + ((a - 6.0) * a + 7.0)) * c1
+ ((11.0 * a - 46.0) * a + 47.0) / 6.0);
let c5 = -s
* ((((-c1 / 4.0 + (11.0 * a - 17.0) / 6.0) * c1 + ((-3.0 * a + 13.0) * a - 13.0)) * c1
+ HALF * (((TWO * a - 25.0) * a + 72.0) * a - 61.0))
* c1
+ (((25.0 * a - 195.0) * a + 477.0) * a - 379.0) / 12.0);
((((c5 / y + c4) / y + c3) / y + c2) / y + c1) + y
}
fn initial_approx_small_b(a: f64, p: f64, q: f64, g: f64, b: f64, gamma_eu: f64) -> f64 {
const HALF: f64 = 0.5;
let xn = if b * q <= 1.0e-8 {
(-(q / a + gamma_eu)).exp()
} else if p > 0.9 {
((alnrel(-q) + gamma_ln1(a)) / a).exp()
} else {
((p * g).ln() / a).exp()
};
if xn == 0.0 {
return 0.0;
}
let t = HALF + (HALF - xn / (a + 1.0));
xn / t
}
fn label_130(a: f64, p: f64, _q: f64, xn0: f64, x_initial: f64, emin_iop: f64) -> (f64, bool) {
let ap1 = a + 1.0;
if 0.70 * ap1 < xn0 {
return (xn0, false);
}
let mut w = p.ln() + gamma_log(ap1);
let mut xn = xn0;
if xn <= 0.15 * ap1 {
let ap2 = a + 2.0;
let ap3 = a + 3.0;
let mut x = ((w + x_initial) / a).exp();
x = ((w + x - (1.0 + (x / ap1) * (1.0 + x / ap2)).ln()) / a).exp();
x = ((w + x - (1.0 + (x / ap1) * (1.0 + x / ap2)).ln()) / a).exp();
x = ((w + x - (1.0 + (x / ap1) * (1.0 + (x / ap2) * (1.0 + x / ap3))).ln()) / a).exp();
xn = x;
if xn <= 1.0e-2 * ap1 {
if xn <= emin_iop * ap1 {
return (xn, true);
}
return (xn, false);
}
}
let mut apn = ap1;
let mut t = xn / apn;
let mut sum1 = 1.0 + t;
loop {
apn += 1.0;
t *= xn / apn;
sum1 += t;
if t <= 1.0e-4 {
break;
}
}
let t = w - sum1.ln();
w = t; let mut xn_new = ((xn + w) / a).exp();
xn_new *= 1.0 - (a * xn_new.ln() - xn_new - w) / (a - xn_new);
(xn_new, false)
}
fn schroder_p(
a: f64,
xn0: f64,
p: f64,
_q: f64,
eps: f64,
amax: f64,
e2: f64,
) -> Result<(f64, u32), GammaIncInvError> {
const HALF: f64 = 0.5;
const TOL: f64 = 1.0e-5;
if p <= 1.0e10 * f64::MIN_POSITIVE {
return Err(GammaIncInvError::UncertainAccuracy { value: xn0 });
}
let am1 = (a - HALF) - HALF;
let mut xn = xn0;
let mut iter: u32 = 0;
loop {
if amax < a {
let d = HALF + (HALF - xn / a);
if d.abs() <= e2 {
return Err(GammaIncInvError::UncertainAccuracy { value: xn });
}
}
if iter >= 20 {
return Err(GammaIncInvError::NotConverged { partial: xn });
}
iter += 1;
let (pn, qn) =
try_gamma_inc(a, xn).map_err(|_| GammaIncInvError::UncertainAccuracy { value: xn })?;
if pn == 0.0 || qn == 0.0 {
return Err(GammaIncInvError::UncertainAccuracy { value: xn });
}
let r = rcomp(a, xn);
if r == 0.0 {
return Err(GammaIncInvError::UncertainAccuracy { value: xn });
}
let t = (pn - p) / r;
let w = HALF * (am1 - xn);
let (x, d) = if t.abs() <= 0.1 && (w * t).abs() <= 0.1 {
let h = t * (1.0 + w * t);
let x = xn * (1.0 - h);
if x <= 0.0 {
return Err(GammaIncInvError::IterationFailed);
}
if w.abs() >= 1.0 && w.abs() * t * t <= eps {
return Ok((x, iter));
}
(x, h.abs())
} else {
let x = xn * (1.0 - t);
if x <= 0.0 {
return Err(GammaIncInvError::IterationFailed);
}
(x, t.abs())
};
xn = x;
if d <= TOL {
if d <= eps {
return Ok((xn, iter));
}
if (p - pn).abs() <= TOL * p {
return Ok((xn, iter));
}
}
}
}
fn schroder_q(
a: f64,
xn0: f64,
_p: f64,
q: f64,
eps: f64,
amax: f64,
e2: f64,
) -> Result<(f64, u32), GammaIncInvError> {
const HALF: f64 = 0.5;
const TOL: f64 = 1.0e-5;
if q <= 1.0e10 * f64::MIN_POSITIVE {
return Err(GammaIncInvError::UncertainAccuracy { value: xn0 });
}
let am1 = (a - HALF) - HALF;
let mut xn = xn0;
let mut iter: u32 = 0;
loop {
if amax < a {
let d = HALF + (HALF - xn / a);
if d.abs() <= e2 {
return Err(GammaIncInvError::UncertainAccuracy { value: xn });
}
}
if iter >= 20 {
return Err(GammaIncInvError::NotConverged { partial: xn });
}
iter += 1;
let (pn, qn) =
try_gamma_inc(a, xn).map_err(|_| GammaIncInvError::UncertainAccuracy { value: xn })?;
if pn == 0.0 || qn == 0.0 {
return Err(GammaIncInvError::UncertainAccuracy { value: xn });
}
let r = rcomp(a, xn);
if r == 0.0 {
return Err(GammaIncInvError::UncertainAccuracy { value: xn });
}
let t = (q - qn) / r;
let w = HALF * (am1 - xn);
let (x, d) = if t.abs() <= 0.1 && (w * t).abs() <= 0.1 {
let h = t * (1.0 + w * t);
let x = xn * (1.0 - h);
if x <= 0.0 {
return Err(GammaIncInvError::IterationFailed);
}
if w.abs() >= 1.0 && w.abs() * t * t <= eps {
return Ok((x, iter));
}
(x, h.abs())
} else {
let x = xn * (1.0 - t);
if x <= 0.0 {
return Err(GammaIncInvError::IterationFailed);
}
(x, t.abs())
};
xn = x;
if d > TOL {
continue;
}
if d <= eps {
return Ok((xn, iter));
}
if (q - qn).abs() <= TOL * q {
return Ok((xn, iter));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dexpm1_at_zero() {
assert_eq!(dexpm1(0.0), 0.0);
}
#[test]
fn dexpm1_small_argument_matches_rational() {
for &x in &[-0.1_f64, -0.05, 0.0001, 0.05, 0.1, 0.15] {
let got = dexpm1(x);
let ref_val = x.exp() - 1.0;
assert!(
(got - ref_val).abs() < 1e-14,
"x={x}: dexpm1={got}, ref={ref_val}"
);
}
}
#[test]
fn dexpm1_large_argument_matches_exp_minus_one() {
for &x in &[-3.0_f64, -1.0, 0.5, 1.0, 5.0] {
let got = dexpm1(x);
let ref_val = x.exp() - 1.0;
let scale = ref_val.abs().max(1.0);
assert!(
(got - ref_val).abs() / scale < 1e-14,
"x={x}: dexpm1={got}, ref={ref_val}"
);
}
}
#[cfg(not(miri))]
#[test]
fn dexpm1_matches_rexp_in_overlap() {
for &x in &[-2.0_f64, -0.5, -0.01, 0.0, 0.01, 0.5, 2.0] {
let a = dexpm1(x);
let b = rexp(x);
assert!((a - b).abs() < 1e-15, "x={x}: dexpm1={a}, rexp={b}");
}
}
#[test]
fn dstrem_large_z_matches_bernoulli_lead() {
let r = dstrem(100.0);
let lead = 1.0 / 1200.0;
assert!((r - lead).abs() / lead < 1e-4, "r = {r}, leading = {lead}");
}
#[test]
fn dstrem_small_z_matches_explicit_difference() {
let r = dstrem(5.0);
let stirling = 0.91893853320467274178 + 4.5 * 5.0_f64.ln() - 5.0;
let expected = gamma_log(5.0) - stirling;
assert!((r - expected).abs() < 1e-14, "r = {r}");
}
#[test]
fn dstrem_continuous_across_z_eq_6() {
let just_below = dstrem(6.0);
let just_above = dstrem(6.0 + 1.0e-9);
assert!((just_below - just_above).abs() < 1e-7);
}
#[test]
#[should_panic(expected = "argument z must be positive")]
fn dstrem_panics_on_nonpositive() {
let _ = dstrem(0.0);
}
fn round_trip_residual(a: f64, p: f64, q: f64) -> (f64, f64) {
let (x, _iters) = gamma_inc_inv(a, -1.0, p, q);
let (pn, qn) = gamma_inc(a, x);
(pn - p, qn - q)
}
#[test]
fn gamma_inc_inv_rejects_invalid_a() {
assert_eq!(
try_gamma_inc_inv(0.0, -1.0, 0.5, 0.5),
Err(GammaIncInvError::ANotPositive(0.0))
);
assert_eq!(
try_gamma_inc_inv(-1.0, -1.0, 0.5, 0.5),
Err(GammaIncInvError::ANotPositive(-1.0))
);
}
#[test]
fn gamma_inc_inv_rejects_p_plus_q_not_one() {
assert_eq!(
try_gamma_inc_inv(2.0, -1.0, 0.5, 0.6),
Err(GammaIncInvError::InconsistentPq)
);
}
#[test]
fn gamma_inc_inv_trivial_endpoints() {
assert_eq!(try_gamma_inc_inv(2.0, -1.0, 0.0, 1.0), Ok((0.0, 0)));
assert_eq!(
try_gamma_inc_inv(2.0, -1.0, 1.0, 0.0),
Err(GammaIncInvError::AtInfinity)
);
}
#[test]
#[should_panic(expected = "inverse is +∞")]
fn gamma_inc_inv_at_infinity_panics() {
let _ = gamma_inc_inv(2.0, -1.0, 1.0, 0.0);
}
#[test]
fn gamma_inc_inv_a_equals_one_closed_form() {
for &(p, q) in &[(0.5, 0.5), (0.9, 0.1), (0.05, 0.95), (0.001, 0.999)] {
let (x, iters) = gamma_inc_inv(1.0, -1.0, p, q);
assert!(
(-q.ln() - x).abs() / x.abs().max(1.0) < 1e-13,
"p={p}: x={x}"
);
assert_eq!(iters, 0, "p={p}: unexpected Schröder iteration");
}
}
#[test]
fn gamma_inc_inv_round_trip_small_a() {
for &a in &[0.05_f64, 0.2, 0.5, 0.95] {
for &p in &[0.1_f64, 0.25, 0.5, 0.75, 0.9] {
let q = 1.0 - p;
let (dp, dq) = round_trip_residual(a, p, q);
assert!(
dp.abs() < 1e-7 && dq.abs() < 1e-7,
"a={a}, p={p}: |p_n − p| = {}, |q_n − q| = {}",
dp.abs(),
dq.abs(),
);
}
}
}
#[test]
fn gamma_inc_inv_round_trip_large_a() {
for &a in &[1.5_f64, 5.0, 50.0, 500.0] {
for &p in &[0.1_f64, 0.25, 0.5, 0.75, 0.9] {
let q = 1.0 - p;
let (dp, dq) = round_trip_residual(a, p, q);
assert!(
dp.abs() < 1e-7 && dq.abs() < 1e-7,
"a={a}, p={p}: |p_n − p| = {}, |q_n − q| = {}",
dp.abs(),
dq.abs(),
);
}
}
}
#[test]
fn gamma_inc_inv_deep_tails() {
let (x, _) = gamma_inc_inv(3.0, -1.0, 1.0e-6, 1.0 - 1.0e-6);
let (pn, _) = gamma_inc(3.0, x);
assert!((pn - 1.0e-6).abs() / 1.0e-6 < 1e-4);
let (x, _) = gamma_inc_inv(3.0, -1.0, 1.0 - 1.0e-6, 1.0e-6);
let (_, qn) = gamma_inc(3.0, x);
assert!((qn - 1.0e-6).abs() / 1.0e-6 < 1e-4);
}
#[test]
fn gamma_inc_inv_with_caller_supplied_x0() {
let (x_auto, _) = gamma_inc_inv(5.0, -1.0, 0.7, 0.3);
let (x_seeded, _) = gamma_inc_inv(5.0, x_auto * 1.1, 0.7, 0.3);
assert!((x_seeded - x_auto).abs() / x_auto < 1e-7);
}
#[test]
fn gamma_inc_inv_sweeps_all_regimes() {
let grid_a = [
0.01_f64, 0.05, 0.1, 0.25, 0.5, 0.95, 1.5, 2.0, 5.0, 25.0, 99.0, 150.0, 600.0, 2000.0, ];
let grid_p = [
1.0e-9_f64,
1.0e-5,
1.0e-3,
0.01,
0.05,
0.1,
0.3,
0.5,
0.7,
0.9,
0.95,
0.99,
0.999,
0.99999,
1.0 - 1.0e-9,
];
for &a in &grid_a {
for &p in &grid_p {
let q = 1.0 - p;
let result = try_gamma_inc_inv(a, -1.0, p, q);
let (x, _iters) = match result {
Ok(pair) => pair,
Err(GammaIncInvError::NoSolution)
| Err(GammaIncInvError::IterationFailed)
| Err(GammaIncInvError::UncertainAccuracy { .. }) => continue,
Err(e) => panic!("a={a}, p={p}: unexpected error {e:?}"),
};
assert!(x.is_finite() && x > 0.0, "a={a}, p={p}: x = {x}");
let (pn, qn) = gamma_inc(a, x);
let dp = (pn - p).abs() / p.max(1e-300);
let dq = (qn - q).abs() / q.max(1e-300);
assert!(dp.min(dq) < 1e-4, "a={a}, p={p}, x={x}: dp={dp}, dq={dq}",);
}
}
}
#[test]
fn gamma_inc_inv_caller_supplied_x0_p_le_half() {
let (x, _) = gamma_inc_inv(3.0, 1.5, 0.3, 0.7);
let (pn, _) = gamma_inc(3.0, x);
assert!((pn - 0.3).abs() < 1e-7);
}
#[test]
fn gamma_inc_inv_caller_supplied_x0_p_gt_half() {
let (x, _) = gamma_inc_inv(3.0, 5.0, 0.8, 0.2);
let (_, qn) = gamma_inc(3.0, x);
assert!((qn - 0.2).abs() < 1e-7);
}
#[test]
fn gamma_log_at_small_integer_arguments() {
assert!((gamma_log(1.0)).abs() < 1e-14);
assert!((gamma_log(2.0)).abs() < 1e-14);
assert!((gamma_log(3.0) - 2.0_f64.ln()).abs() < 1e-14);
assert!((gamma_log(4.0) - 6.0_f64.ln()).abs() < 1e-14);
assert!((gamma_log(10.0) - 362880.0_f64.ln()).abs() < 1e-12);
}
#[test]
fn gamma_at_small_integers() {
assert!((gamma(1.0) - 1.0).abs() < 1e-14);
assert!((gamma(2.0) - 1.0).abs() < 1e-14);
assert!((gamma(3.0) - 2.0).abs() < 1e-14);
assert!((gamma(4.0) - 6.0).abs() < 1e-14);
assert!((gamma(5.0) - 24.0).abs() < 1e-13);
}
#[test]
fn gamma_inc_basic_identities() {
let (p, q) = gamma_inc(2.5, 0.0);
assert_eq!(p, 0.0);
assert_eq!(q, 1.0);
for &a in &[0.5, 1.0, 2.5, 7.0, 25.0] {
for &x in &[0.1, 1.0, 5.0, 20.0] {
let (p, q) = gamma_inc(a, x);
if p == 2.0 {
continue; }
assert!((p + q - 1.0).abs() < 1e-12, "a={a}, x={x}: p+q = {}", p + q);
}
}
}
#[test]
fn psi_at_known_points() {
let γ = 0.5772156649015328606;
assert!((psi(1.0) + γ).abs() < 1e-9, "psi(1) = {}", psi(1.0));
assert!((psi(2.0) - (1.0 - γ)).abs() < 1e-9);
let expected = -γ - 2.0 * 2.0_f64.ln();
assert!(
(psi(0.5) - expected).abs() < 1e-9,
"psi(0.5) = {}",
psi(0.5)
);
}
#[test]
fn gamma_inc_at_a_half_uses_erf() {
for &x in &[0.1, 0.5, 1.0, 4.0, 9.0] {
let (p, _q) = gamma_inc(0.5, x);
let expected = error_f(x.sqrt());
assert!(
(p - expected).abs() < 1e-13,
"x={x}: P = {p}, erf(√x) = {expected}"
);
}
}
#[test]
fn gamma_inc_rejects_negative_a() {
assert!(matches!(
try_gamma_inc(-1.0, 1.0),
Err(GammaIncError::ANegative(_))
));
}
#[test]
fn gamma_inc_rejects_negative_x() {
assert!(matches!(
try_gamma_inc(1.0, -1.0),
Err(GammaIncError::XNegative(_))
));
}
#[test]
fn gamma_inc_rejects_both_zero() {
assert!(matches!(
try_gamma_inc(0.0, 0.0),
Err(GammaIncError::BothZero)
));
}
#[test]
fn gamma_inc_a_zero_x_positive() {
let (p, q) = gamma_inc(0.0, 1.0);
assert_eq!(p, 1.0);
assert_eq!(q, 0.0);
}
#[test]
fn gamma_inc_regime_a_lt_1_x_lt_1() {
let (p, q) = gamma_inc(0.3, 0.2);
assert!((p + q - 1.0).abs() < 1e-14);
assert!(p > 0.0 && p < 1.0);
}
#[test]
fn gamma_inc_regime_a_lt_1_x_ge_1() {
let (p, q) = gamma_inc(0.3, 5.0);
assert!((p + q - 1.0).abs() < 1e-14);
assert!(p > 0.99); }
#[test]
fn gamma_inc_regime_a_eq_1() {
let (p, q) = gamma_inc(1.0, 2.0);
let expected_p = 1.0 - (-2.0_f64).exp();
assert!((p - expected_p).abs() < 1e-14);
assert!((q - (-2.0_f64).exp()).abs() < 1e-14);
}
#[test]
fn gamma_inc_regime_a_large_x_near_a() {
let (p, q) = gamma_inc(100.0, 100.0);
assert!((p + q - 1.0).abs() < 1e-12);
assert!(p > 0.5 && p < 0.55, "p={p}");
}
#[test]
fn gamma_inc_regime_a_very_large() {
let (p, q) = gamma_inc(1e6, 1e6);
assert!((p + q - 1.0).abs() < 1e-12);
assert!(p.is_finite() && q.is_finite());
assert!(p > 0.499 && p < 0.501);
}
#[test]
fn gamma_inc_half_integer_a_uses_finite_sum() {
for &a in &[1.5_f64, 2.5, 3.5, 10.5] {
let (p, q) = gamma_inc(a, a);
assert!((p + q - 1.0).abs() < 1e-12, "a={a}");
}
}
#[test]
fn gamma_inc_a_x_very_unbalanced() {
let (p, q) = gamma_inc(2.0, 50.0);
assert!(p > 0.9999);
assert!(q > 0.0 && q < 1e-15);
let (p, q) = gamma_inc(50.0, 2.0);
assert!(q > 0.9999);
assert!(p > 0.0 && p < 1e-15);
}
#[test]
fn gamma_at_half_integer() {
assert!((gamma(0.5) - std::f64::consts::PI.sqrt()).abs() < 1e-13);
assert!((gamma(1.5) - std::f64::consts::PI.sqrt() / 2.0).abs() < 1e-13);
}
#[test]
fn try_gamma_overflow_is_err() {
assert_eq!(try_gamma(1001.0), Err(GammaDomainError::Overflow(1001.0)));
assert_eq!(try_gamma(1e10), Err(GammaDomainError::Overflow(1e10)));
}
#[test]
#[should_panic(expected = "gamma(1001): Γ(1001) overflows f64")]
fn gamma_overflow_panics() {
let _ = gamma(1001.0);
}
#[test]
fn gamma_at_negative_non_integer() {
let expected = -2.0 * std::f64::consts::PI.sqrt();
let got = gamma(-0.5);
assert!(
(got - expected).abs() < 1e-12,
"got = {got}, expected = {expected}"
);
let expected = 4.0 / 3.0 * std::f64::consts::PI.sqrt();
let got = gamma(-1.5);
assert!(
(got - expected).abs() < 1e-12,
"got = {got}, expected = {expected}"
);
}
#[test]
fn gamma_at_negative_mid_range() {
let g = gamma(-3.5);
assert!(g.is_finite());
let g_45 = gamma(4.5);
let expected = std::f64::consts::PI / ((-3.5_f64 * std::f64::consts::PI).sin() * g_45);
assert!((g - expected).abs() / expected.abs() < 1e-10);
}
#[test]
fn gamma_at_negative_large_magnitude() {
let g = gamma(-20.5);
assert!(g.is_finite() && g.abs() < 1e-15);
}
#[test]
fn try_gamma_at_negative_integer_is_pole() {
assert_eq!(try_gamma(-3.0), Err(GammaDomainError::Pole(-3.0)));
assert_eq!(try_gamma(-10.0), Err(GammaDomainError::Pole(-10.0)));
}
#[test]
fn try_gamma_at_zero_is_pole() {
assert_eq!(try_gamma(0.0), Err(GammaDomainError::Pole(0.0)));
}
#[test]
#[should_panic(expected = "gamma(0): Γ has a pole at 0")]
fn gamma_at_zero_panics() {
let _ = gamma(0.0);
}
#[test]
fn gamma_at_large_positive() {
let ln_gamma_50 = gamma_log(50.0);
let g_50 = gamma(50.0);
assert!((g_50.ln() - ln_gamma_50).abs() < 1e-9);
let g_100 = gamma(100.0);
assert!(g_100.is_finite() && g_100 > 1e150);
}
#[test]
fn gamma_log_at_a_lt_8_branches() {
for &a in &[0.5_f64, 0.8, 1.0, 1.5, 2.0, 2.25, 5.0, 8.0, 10.0, 50.0] {
let h = gamma_log(a);
assert!(h.is_finite(), "a={a}");
}
}
#[test]
fn psi_at_a_lt_05_and_large() {
assert!(psi(0.1).is_finite());
assert!(psi(0.3).is_finite());
assert!(psi(2.5).is_finite());
assert!(psi(50.0).is_finite());
assert!(psi(1000.0).is_finite());
}
}