Skip to main content

comnum_math/
special.rs

1//! Special functions: the error function and its inverse, the Gaussian tail, the
2//! Fresnel integrals, Bessel functions, the gamma function and the regularized
3//! incomplete gamma function, the binomial law, the regularized incomplete beta function
4//! and its inverse, the exponential integral, the generalized hypergeometric function
5//! $_{1}F_{2}$ and the Hermite polynomials (all re-exported at the root).
6//!
7//! The power series share one private summation (the terms until the last falls below
8//! $10^{-17}$ of the sum), the continued fractions one modified Lentz iteration (the
9//! partial denominators floored at $10^{-300}$, until a factor is within $10^{-16}$ of
10//! 1), and the periodic integrands one midpoint rule; the order of the operations of
11//! each function is the one it had before they were shared (the same results, bit for
12//! bit).
13//!
14//! # References
15//!
16//! 1. W. H. Press, S. A. Teukolsky, W. T. Vetterling and B. P. Flannery, *Numerical
17//!    Recipes*, 3rd ed., Cambridge University Press, 2007, §5.2 (the modified Lentz
18//!    method), §6.2, §6.3 and §6.4.
19//! 2. M. Abramowitz and I. A. Stegun, *Handbook of Mathematical Functions*, Dover, 1965.
20
21use crate::search::bisect;
22use crate::{C64, cis};
23use std::ops::{Add, AddAssign, Div, Mul, MulAssign};
24
25/// The smallest magnitude of a partial denominator of the modified Lentz method: a
26/// smaller one is replaced by it (Press et al., §5.2).
27const LENTZ_TINY: f64 = 1e-300;
28
29/// The convergence of the modified Lentz method: the last factor $\Delta_{k}$ is within
30/// this of 1.
31const LENTZ_TOLERANCE: f64 = 1e-16;
32
33/// The convergence of a power series: the last term is below this fraction of the sum.
34const SERIES_TOLERANCE: f64 = 1e-17;
35
36/// The real or complex numbers the series and continued fractions are evaluated in.
37trait Scalar: Copy + Add<Output = Self> + Mul<Output = Self> + MulAssign + AddAssign + Div<f64, Output = Self> {
38    /// [`LENTZ_TINY`] in this field.
39    fn tiny() -> Self;
40    /// The modulus.
41    fn magnitude(self) -> f64;
42    /// `a · x`.
43    fn scale(a: f64, x: Self) -> Self;
44    /// `a / x`.
45    fn over(a: f64, x: Self) -> Self;
46    /// `1 / x`.
47    fn recip(self) -> Self;
48    /// `|x - 1|`.
49    fn distance_to_one(self) -> f64;
50}
51
52impl Scalar for f64 {
53    fn tiny() -> f64 {
54        LENTZ_TINY
55    }
56    fn magnitude(self) -> f64 {
57        self.abs()
58    }
59    fn scale(a: f64, x: f64) -> f64 {
60        a * x
61    }
62    fn over(a: f64, x: f64) -> f64 {
63        a / x
64    }
65    fn recip(self) -> f64 {
66        1.0 / self
67    }
68    fn distance_to_one(self) -> f64 {
69        (self - 1.0).abs()
70    }
71}
72
73impl Scalar for C64 {
74    fn tiny() -> C64 {
75        C64::new(LENTZ_TINY, 0.0)
76    }
77    fn magnitude(self) -> f64 {
78        self.norm()
79    }
80    fn scale(a: f64, x: C64) -> C64 {
81        a * x
82    }
83    fn over(a: f64, x: C64) -> C64 {
84        a / x
85    }
86    fn recip(self) -> C64 {
87        self.inv()
88    }
89    fn distance_to_one(self) -> f64 {
90        (self - 1.0).norm()
91    }
92}
93
94/// The power series $\sum_{k} t_{k} / d_{k}$ from the partial sum `sum`, with $t_{k} =
95/// t_{k-1} r_{k}$ from `term` $= t_{0}$, the ratios `ratio(k)` and the divisors
96/// `divisor(k)` for $k = 1, 2, \dots$ below `max_terms`, until the last term is below
97/// [`SERIES_TOLERANCE`] of the sum.
98fn series<T: Scalar>(
99    mut term: T,
100    mut sum: T,
101    max_terms: usize,
102    mut ratio: impl FnMut(usize) -> T,
103    divisor: impl Fn(usize) -> f64,
104) -> T {
105    for k in 1..max_terms {
106        term *= ratio(k);
107        let t = term / divisor(k);
108        sum += t;
109        if t.magnitude() < SERIES_TOLERANCE * sum.magnitude() {
110            break;
111        }
112    }
113    sum
114}
115
116/// The modified Lentz method (Press et al., §5.2) for the continued fraction
117/// $f = b_{0} + \cfrac{a_{1}}{b_{1} + \cfrac{a_{2}}{b_{2} + \dotsb}}$ from the state
118/// $(f_{0}, C_{0}, D_{0})$ and the terms $(a_{k}, b_{k})$ = `term(k)`, $k = 1, 2, \dots$
119/// below `max_terms`: $D_{k} = 1/(b_{k} + a_{k} D_{k-1})$, $C_{k} = b_{k} + a_{k}/C_{k-1}$
120/// (both floored at [`LENTZ_TINY`]), $f_{k} = f_{k-1} C_{k} D_{k}$, until $|C_{k} D_{k} -
121/// 1|$ is below [`LENTZ_TOLERANCE`].
122fn lentz<T: Scalar>(f0: T, c0: T, d0: T, max_terms: usize, mut term: impl FnMut(usize) -> (f64, T)) -> T {
123    let (mut f, mut c, mut d) = (f0, c0, d0);
124    for k in 1..max_terms {
125        let (a, b) = term(k);
126        d = b + T::scale(a, d);
127        if d.magnitude() < LENTZ_TINY {
128            d = T::tiny();
129        }
130        c = b + T::over(a, c);
131        if c.magnitude() < LENTZ_TINY {
132            c = T::tiny();
133        }
134        d = d.recip();
135        let delta = c * d;
136        f *= delta;
137        if delta.distance_to_one() < LENTZ_TOLERANCE {
138            break;
139        }
140    }
141    f
142}
143
144/// The midpoint rule on $n$ points of $\frac{1}{\pi} \int_{0}^{\pi} f(\theta)\, d\theta$,
145/// $\frac{1}{n} \sum_{k} f(h (k + \frac{1}{2}))$ with $h = \pi/n$ (exponentially
146/// convergent on a smooth periodic integrand).
147fn midpoint_mean(f: impl Fn(f64) -> f64, n: usize) -> f64 {
148    let h = std::f64::consts::PI / n as f64;
149    (0..n).map(|k| f(h * (k as f64 + 0.5))).sum::<f64>() / n as f64
150}
151
152/// Complementary error function $\operatorname{erfc}(x) = \frac{2}{\sqrt{\pi}}
153/// \int_{x}^{\infty} e^{-t^{2}}\, dt$.
154///
155/// The rational Chebyshev approximations of Cody (1969), as in his `CALERF` (SPECFUN,
156/// 1990): $1 - x R_{1}(x^{2})$ for $|x| \le 0.46875$,
157/// $e^{-x^{2}} R_{2}(x)$ up to 4 and $\frac{e^{-x^{2}}}{x} \left(\frac{1}{\sqrt{\pi}} +
158/// \frac{1}{x^{2}} R_{3}(1/x^{2})\right)$ beyond, $e^{-x^{2}}$ computed in two factors so
159/// that its rounding does not grow with $x^{2}$; $2 - \operatorname{erfc}(-x)$ for
160/// $x < -0.46875$. Its relative error is below $10^{-15}$ wherever the result is a
161/// normal number (against mpmath, the tail included: $\operatorname{erfc}(26) \approx
162/// 5.7 \cdot 10^{-296}$); it underflows to 0 beyond 27.3.
163///
164/// # Examples
165/// ```
166/// use comnum_math::erfc;
167/// assert!((erfc(1.0) / 0.157_299_207_050_285_13 - 1.0).abs() < 1e-15);
168/// assert!((erfc(10.0) / 2.088_487_583_762_544_8e-45 - 1.0).abs() < 1e-15);
169/// ```
170///
171/// # References
172///
173/// 1. W. J. Cody, "Rational Chebyshev approximations for the error function," *Math.
174///    Comp.*, 23(107), 631–637, 1969.
175/// 2. W. J. Cody, "Algorithm 715: SPECFUN — a portable FORTRAN package of special
176///    function routines and test drivers," *ACM Trans. Math. Softw.*, 19(1), 22–32, 1993.
177#[allow(
178    clippy::unreadable_literal,
179    clippy::excessive_precision,
180    reason = "the coefficients of Cody's CALERF, digit for digit"
181)]
182pub fn erfc(x: f64) -> f64 {
183    const A: [f64; 5] = [
184        3.16112374387056560e00,
185        1.13864154151050156e02,
186        3.77485237685302021e02,
187        3.20937758913846947e03,
188        1.85777706184603153e-1,
189    ];
190    const B: [f64; 4] =
191        [2.36012909523441209e01, 2.44024637934444173e02, 1.28261652607737228e03, 2.84423683343917062e03];
192    const C: [f64; 9] = [
193        5.64188496988670089e-1,
194        8.88314979438837594e00,
195        6.61191906371416295e01,
196        2.98635138197400131e02,
197        8.81952221241769090e02,
198        1.71204761263407058e03,
199        2.05107837782607147e03,
200        1.23033935479799725e03,
201        2.15311535474403846e-8,
202    ];
203    const D: [f64; 8] = [
204        1.57449261107098347e01,
205        1.17693950891312499e02,
206        5.37181101862009858e02,
207        1.62138957456669019e03,
208        3.29079923573345963e03,
209        4.36261909014324716e03,
210        3.43936767414372164e03,
211        1.23033935480374942e03,
212    ];
213    const P: [f64; 6] = [
214        3.05326634961232344e-1,
215        3.60344899949804439e-1,
216        1.25781726111229246e-1,
217        1.60837851487422766e-2,
218        6.58749161529837803e-4,
219        1.63153871373020978e-2,
220    ];
221    const Q: [f64; 5] = [
222        2.56852019228982242e00,
223        1.87295284992346725e00,
224        5.27905102951428412e-1,
225        6.05183413124413191e-2,
226        2.33520497626869185e-3,
227    ];
228    const FRAC_1_SQRT_PI: f64 = 5.6418958354775628695e-1;
229    let y = x.abs();
230    if y <= 0.46875 {
231        let ysq = if y > 1.11e-16 { y * y } else { 0.0 };
232        let (mut num, mut den) = (A[4] * ysq, ysq);
233        for i in 0..3 {
234            num = (num + A[i]) * ysq;
235            den = (den + B[i]) * ysq;
236        }
237        return 1.0 - x * (num + A[3]) / (den + B[3]);
238    }
239    // erfc(27.3) is below the smallest subnormal number
240    let r = if y > 27.3 {
241        0.0
242    } else {
243        let r = if y <= 4.0 {
244            let (mut num, mut den) = (C[8] * y, y);
245            for i in 0..7 {
246                num = (num + C[i]) * y;
247                den = (den + D[i]) * y;
248            }
249            (num + C[7]) / (den + D[7])
250        } else {
251            let ysq = 1.0 / (y * y);
252            let (mut num, mut den) = (P[5] * ysq, ysq);
253            for i in 0..4 {
254                num = (num + P[i]) * ysq;
255                den = (den + Q[i]) * ysq;
256            }
257            (FRAC_1_SQRT_PI - ysq * (num + P[4]) / (den + Q[4])) / y
258        };
259        // e^{-y²} = e^{-u²} e^{-(y - u)(y + u)} with u = y rounded down to 1/16: exact u²
260        let u = (y * 16.0).trunc() / 16.0;
261        (-u * u).exp() * (-(y - u) * (y + u)).exp() * r
262    };
263    if x < 0.0 { 2.0 - r } else { r }
264}
265
266/// Inverse of [`erfc`] on `(0, 2)`, by bisection (`±∞` at the ends).
267pub fn erfcinv(y: f64) -> f64 {
268    if y <= 0.0 {
269        return f64::INFINITY;
270    }
271    if y >= 2.0 {
272        return f64::NEG_INFINITY;
273    }
274    bisect(|x| erfc(x) > y, -27.0, 27.0, 200)
275}
276
277/// Gaussian tail probability `Q(x) = P(N(0,1) > x) = ½ erfc(x/√2)`.
278#[inline]
279pub fn qfunc(x: f64) -> f64 {
280    0.5 * erfc(x / std::f64::consts::SQRT_2)
281}
282
283/// Inverse of [`qfunc`] on $(0, 1)$: $Q^{-1}(p) = \sqrt{2}\, \operatorname{erfc}^{-1}(2p)$
284/// (`±∞` at the ends).
285///
286/// # Examples
287/// ```
288/// use comnum_math::{qfunc, qfuncinv};
289/// assert!((qfuncinv(0.025) - 1.959_964).abs() < 1e-6);
290/// assert!((qfunc(qfuncinv(1e-6)) / 1e-6 - 1.0).abs() < 1e-9);
291/// ```
292#[inline]
293pub fn qfuncinv(p: f64) -> f64 {
294    std::f64::consts::SQRT_2 * erfcinv(2.0 * p)
295}
296
297/// The standard normal density $\varphi(x) = e^{-x^{2}/2}/\sqrt{2\pi}$ (its
298/// distribution function is $1 - Q(x)$, [`qfunc`]).
299///
300/// # Examples
301/// ```
302/// use comnum_math::normal_pdf;
303/// assert!((normal_pdf(0.0) - 0.398_942_280_401_432_7).abs() < 1e-16);
304/// ```
305pub fn normal_pdf(x: f64) -> f64 {
306    (-0.5 * x * x).exp() / std::f64::consts::TAU.sqrt()
307}
308
309/// The Fresnel integrals $C(x) = \int_{0}^{x} \cos(\pi t^{2}/2)\, dt$ and
310/// $S(x) = \int_{0}^{x} \sin(\pi t^{2}/2)\, dt$, as `(C, S)`, to about $10^{-15}$.
311///
312/// For $|x| < 1.5$ the power series $C + jS = \sum_{k} (j\pi/2)^{k} x^{2k+1} / (k!\,(2k+1))$
313/// (Abramowitz & Stegun 7.3.11, 7.3.13); beyond, the relation to the error function
314/// $C + jS = \frac{1+j}{2} \operatorname{erf}(z)$, $z = \frac{\sqrt{\pi}}{2}(1-j)x$
315/// (A&S 7.3.22), with $\operatorname{erfc}(z) = \frac{e^{-z^{2}}}{\sqrt{\pi}}
316/// \cfrac{1}{z + \cfrac{1/2}{z + \cfrac{1}{z + \cfrac{3/2}{z + \dotsb}}}}$
317/// (A&S 7.1.14, valid for $\operatorname{Re} z > 0$), evaluated by the modified Lentz
318/// method. $C$ and $S$ are odd.
319pub fn fresnel(x: f64) -> (f64, f64) {
320    let ax = x.abs();
321    let v = if ax < 1.5 {
322        let a = C64::new(0.0, std::f64::consts::FRAC_PI_2 * ax * ax);
323        series(C64::new(ax, 0.0), C64::new(ax, 0.0), 60, |k| a / k as f64, |k| (2 * k + 1) as f64)
324    } else {
325        let z = C64::new(1.0, -1.0) * (std::f64::consts::PI.sqrt() / 2.0 * ax);
326        // modified Lentz for z + (1/2)/(z + 1/(z + (3/2)/(z + ...)))
327        let f = lentz(z, z, C64::new(0.0, 0.0), 100_000, |k| (k as f64 / 2.0, z));
328        // e^{-z²} = e^{jπx²/2}
329        let erfc = cis(std::f64::consts::FRAC_PI_2 * ax * ax) / (std::f64::consts::PI.sqrt() * f);
330        C64::new(0.5, 0.5) * (1.0 - erfc)
331    };
332    (v.re.copysign(x), v.im.copysign(x))
333}
334
335/// The Bessel function of the first kind of order zero, $J_{0}(x) = \frac{1}{\pi}
336/// \int_{0}^{\pi} \cos(x \sin\theta)\, d\theta$, by the midpoint rule on that
337/// periodic integrand (exponentially convergent: $64 + 2|x|$ points give the machine
338/// precision), as for the correlation $J_{0}(2\pi f_{D} \tau)$ of Jakes' fading.
339pub fn bessel_j0(x: f64) -> f64 {
340    let n = 64 + 2 * x.abs().ceil() as usize;
341    midpoint_mean(|theta| (x * theta.sin()).cos(), n)
342}
343
344/// The natural logarithm of the modified Bessel function of the second kind $K_{\nu}(x)$
345/// of real order $\nu$ and $x > 0$, from $K_{\nu}(x) = \int_{0}^{\infty} e^{-x \cosh t}
346/// \cosh(\nu t)\, dt$ by the trapezoidal rule (exponentially convergent on this even,
347/// entire integrand), summed in the log domain so that neither the large values at
348/// small $x$ and large $\nu$ nor the small ones at large $x$ overflow; to about
349/// $10^{-13}$ relative. The limits: $-\infty$ for $x = +\infty$ ($K_{\nu}(x) \to 0$),
350/// $+\infty$ for an infinite order, and the leading term $\ln\left(\tfrac{1}{2}
351/// \Gamma(\nu)\right) + \nu \ln(2/x)$ when $\nu / x$ overflows (a subnormal $x$; for
352/// $\nu \ge 0.05$, where the next term is below $e^{-70}$ relative); `NaN` for
353/// $x \le 0$, a `NaN` argument, or an overflowing $\nu / x$ with a smaller order.
354///
355/// # Examples
356/// ```
357/// use comnum_math::{bessel_k, ln_bessel_k};
358/// // K₀(1) and K₁(1) (Abramowitz & Stegun, Table 9.8); K_{1/2}(x) = √(π/2x) e^{-x}
359/// assert!((bessel_k(0.0, 1.0) - 0.421_024_438_240_708_3).abs() < 1e-13);
360/// assert!((bessel_k(1.0, 1.0) - 0.601_907_230_197_234_6).abs() < 1e-13);
361/// let x = 3.7;
362/// let half = (std::f64::consts::PI / (2.0 * x)).sqrt() * (-x).exp();
363/// assert!((bessel_k(0.5, x) / half - 1.0).abs() < 1e-13);
364/// // small x: K_ν(x) ≈ ½ Γ(ν) (2/x)^ν, here about 10^{129}
365/// let asymptotic = (0.5f64).ln() + comnum_math::ln_gamma(30.0) + 30.0 * 2000f64.ln();
366/// assert!((ln_bessel_k(30.0, 1e-3) - asymptotic).abs() < 1e-3);
367/// ```
368pub fn ln_bessel_k(nu: f64, x: f64) -> f64 {
369    if x.is_nan() || nu.is_nan() || x <= 0.0 {
370        return f64::NAN;
371    }
372    let nu = nu.abs();
373    if x == f64::INFINITY {
374        return f64::NEG_INFINITY;
375    }
376    if nu == f64::INFINITY {
377        return f64::INFINITY;
378    }
379    if nu >= 0.05 && !(nu / x).is_finite() {
380        // K_ν(x) = ½ Γ(ν) (2/x)^ν (1 + O((x/2)^{2ν} + x²)), the correction below e^{-70}
381        return -std::f64::consts::LN_2 + ln_gamma(nu) + nu * (std::f64::consts::LN_2 - x.ln());
382    }
383    // ln cosh(νt) - x cosh t, without overflow
384    let f = |t: f64| {
385        let a = nu * t;
386        a + (-2.0 * a).exp().ln_1p() - std::f64::consts::LN_2 - x * t.cosh()
387    };
388    // the maximum of the integrand: sinh t = (ν/x) tanh(νt) ≈ ν/x
389    let t_max = if nu > 0.0 { (nu / x).asinh() } else { 0.0 };
390    let peak = f(t_max);
391    // step: a fraction of the width of the peak, (x cosh t_max)^{-1/2}
392    let width = 1.0 / (x * t_max.cosh()).sqrt().max(1.0);
393    let h = 0.1 * width.min(1.0);
394    let mut acc = 0.5 * (f(0.0) - peak).exp();
395    let mut k = 1usize;
396    loop {
397        let t = k as f64 * h;
398        let v = f(t) - peak;
399        if v.is_nan() {
400            return f64::NAN;
401        }
402        acc += v.exp();
403        if t > t_max && v < -50.0 {
404            break;
405        }
406        k += 1;
407    }
408    peak + (acc * h).ln()
409}
410
411/// The modified Bessel function of the second kind $K_{\nu}(x)$ of real order:
412/// `exp(`[`ln_bessel_k`]`)`.
413pub fn bessel_k(nu: f64, x: f64) -> f64 {
414    ln_bessel_k(nu, x).exp()
415}
416
417/// The scaled modified Bessel function of the first kind of order zero,
418/// $e^{-|x|} I_{0}(x)$, with $I_{0}(x) = \frac{1}{\pi} \int_{0}^{\pi} e^{x\cos\theta}
419/// \, d\theta$: the midpoint rule on the periodic integrand
420/// $e^{|x|(\cos\theta - 1)}$ for $|x| \le 60$ (exponentially convergent), and the
421/// asymptotic series $\frac{1}{\sqrt{2\pi x}} \sum_{k} \frac{((2k-1)!!)^{2}}{k!
422/// \, (8x)^{k}}$ beyond; to about $10^{-14}$ relative. The density of the Rice
423/// distribution, $y e^{-(y - v)^{2}/2} \, e^{-yv} I_{0}(yv)$, needs the scaled form.
424///
425/// # Examples
426/// ```
427/// use comnum_math::bessel_i0e;
428/// // I₀(1) = 1.266065877752008, I₀(10) = 2815.716628466254
429/// assert!((bessel_i0e(1.0) * 1f64.exp() - 1.266_065_877_752_008).abs() < 1e-13);
430/// assert!((bessel_i0e(10.0) * 10f64.exp() / 2815.716_628_466_254 - 1.0).abs() < 1e-13);
431/// assert!((bessel_i0e(1e4) * (2.0 * std::f64::consts::PI * 1e4).sqrt() - 1.0).abs() < 2e-5);
432/// ```
433pub fn bessel_i0e(x: f64) -> f64 {
434    let ax = x.abs();
435    if ax <= 60.0 {
436        let n = 32 + 8 * ax.sqrt().ceil() as usize + ax.ceil() as usize;
437        midpoint_mean(|theta| (ax * (theta.cos() - 1.0)).exp(), n)
438    } else {
439        // Σ_k ((2k-1)!!)² / (k! (8x)^k): the terms fall fast for x > 60
440        let ratio = |k: usize| {
441            let odd = (2 * k - 1) as f64;
442            odd * odd / (k as f64 * 8.0 * ax)
443        };
444        series(1.0, 1.0, 30, ratio, |_| 1.0) / (std::f64::consts::TAU * ax).sqrt()
445    }
446}
447
448/// The error function $\operatorname{erf}(x) = \frac{2}{\sqrt{\pi}} \int_{0}^{x}
449/// e^{-t^{2}}\, dt$: its Maclaurin series $\frac{2}{\sqrt{\pi}} \sum_{k} \frac{(-1)^{k}
450/// x^{2k+1}}{k!\,(2k+1)}$ for $|x| < 2$ (relative error about $10^{-15}$, small
451/// arguments included), $1 - \operatorname{erfc}(x)$ beyond (absolute error about
452/// $10^{-16}$). Odd.
453///
454/// # Examples
455/// ```
456/// use comnum_math::erf;
457/// assert!((erf(1e-4) / 1.128_379_163_334_248_7e-4 - 1.0).abs() < 1e-14);
458/// assert!((erf(0.9) - 0.796_908_212_422_831_9).abs() < 1e-15);
459/// ```
460pub fn erf(x: f64) -> f64 {
461    if x.abs() >= 2.0 {
462        return (1.0 - erfc(x.abs())).copysign(x);
463    }
464    let x2 = x * x;
465    series(x, x, 60, |k| -x2 / k as f64, |k| (2 * k + 1) as f64) * std::f64::consts::FRAC_2_SQRT_PI
466}
467
468/// The logarithm of the gamma function, $\ln \Gamma(x)$ for $x > 0$, by the Lanczos
469/// approximation ($g = 7$, nine coefficients; relative error about $10^{-15}$) and the
470/// reflection formula $\Gamma(x)\Gamma(1-x) = \pi / \sin(\pi x)$ below $1/2$.
471///
472/// # Examples
473/// ```
474/// use comnum_math::ln_gamma;
475/// // Γ(10) = 9! = 362 880, Γ(1/2) = √π
476/// assert!((ln_gamma(10.0) - 362_880f64.ln()).abs() < 1e-13);
477/// assert!((ln_gamma(0.5) - std::f64::consts::PI.sqrt().ln()).abs() < 1e-14);
478/// ```
479#[allow(clippy::unreadable_literal, reason = "the Lanczos coefficients, digit for digit")]
480pub fn ln_gamma(x: f64) -> f64 {
481    const G: f64 = 7.0;
482    const C: [f64; 9] = [
483        0.999_999_999_999_809_9,
484        676.5203681218851,
485        -1259.1392167224028,
486        771.323_428_777_653_1,
487        -176.615_029_162_140_6,
488        12.507343278686905,
489        -0.13857109526572012,
490        9.984_369_578_019_572e-6,
491        1.5056327351493116e-7,
492    ];
493    if x < 0.5 {
494        let pi = std::f64::consts::PI;
495        return (pi / (pi * x).sin()).ln() - ln_gamma(1.0 - x);
496    }
497    let x = x - 1.0;
498    let t = x + G + 0.5;
499    let a = C[0] + C.iter().enumerate().skip(1).map(|(i, c)| c / (x + i as f64)).sum::<f64>();
500    0.5 * std::f64::consts::TAU.ln() + (x + 0.5) * t.ln() - t + a.ln()
501}
502
503/// The regularized lower incomplete gamma function
504/// $P(a, x) = \frac{1}{\Gamma(a)} \int_{0}^{x} u^{a-1} e^{-u}\, du$, $a > 0$, $x \ge 0$:
505/// the distribution function of a gamma variable of shape $a$ and unit scale (a
506/// $\chi^{2}$ variable with $2a$ degrees of freedom, halved). The series
507/// $e^{-x} x^{a} \sum_{k} x^{k} / \Gamma(a + k + 1)$ for $x < a + 1$, and $1 - Q(a, x)$
508/// with the continued fraction of $Q$ (modified Lentz) beyond (Press et al., *Numerical
509/// Recipes*, §6.2); relative error about $10^{-14}$ on the side computed directly.
510///
511/// # Examples
512/// ```
513/// use comnum_math::gamma_p;
514/// // P(1, x) = 1 - e^{-x}
515/// assert!((gamma_p(1.0, 0.5) - (1.0 - (-0.5f64).exp())).abs() < 1e-15);
516/// assert!((gamma_p(20.0, 5.0) / 3.452_135_820_914_455e-7 - 1.0).abs() < 1e-12);
517/// ```
518pub fn gamma_p(a: f64, x: f64) -> f64 {
519    if x <= 0.0 {
520        return 0.0;
521    }
522    let ln_front = a * x.ln() - x - ln_gamma(a);
523    if x < a + 1.0 {
524        let mut ap = a;
525        let ratio = |_| {
526            ap += 1.0;
527            x / ap
528        };
529        series(1.0 / a, 1.0 / a, 10_001, ratio, |_| 1.0) * ln_front.exp()
530    } else {
531        // Q(a, x) = e^{-x} x^a / Γ(a) · 1/(x+1-a- 1·(1-a)/(x+3-a- 2(2-a)/(x+5-a- …)))
532        let mut b = x + 1.0 - a;
533        let d = 1.0 / b;
534        let term = |i: usize| {
535            b += 2.0;
536            (-(i as f64) * (i as f64 - a), b)
537        };
538        let h = lentz(d, 1.0 / LENTZ_TINY, d, 10_000, term);
539        1.0 - ln_front.exp() * h
540    }
541}
542
543/// The logarithms of the binomial probabilities
544/// $\ln \binom{n}{k} + k \ln p + (n - k) \ln (1 - p)$, $k = 0, \dots, n$: the law of
545/// the number of successes among $n$ trials of probability $p \in [0, 1]$ (the
546/// coefficients by the running product $\binom{n}{k} = \binom{n}{k-1} (n + 1 - k)/k$;
547/// at $p = 0$ or 1 the impossible counts are $-\infty$).
548///
549/// # Examples
550/// ```
551/// use comnum_math::ln_binomial_pmf;
552/// let l = ln_binomial_pmf(20, 0.3);
553/// assert!((l[7].exp() - 0.164_261_985_217_236_67).abs() < 1e-15);
554/// assert!((l.iter().map(|v| v.exp()).sum::<f64>() - 1.0).abs() < 1e-14);
555/// assert_eq!(ln_binomial_pmf(3, 0.0), [0.0, f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY]);
556/// ```
557pub fn ln_binomial_pmf(n: usize, p: f64) -> Vec<f64> {
558    if p <= 0.0 || p >= 1.0 {
559        let certain = if p <= 0.0 { 0 } else { n };
560        return (0..=n).map(|k| if k == certain { 0.0 } else { f64::NEG_INFINITY }).collect();
561    }
562    let (lp, lq) = (p.ln(), (1.0 - p).ln());
563    let mut terms = Vec::with_capacity(n + 1);
564    let mut log_c = 0.0;
565    for k in 0..=n {
566        if k > 0 {
567            log_c += ((n + 1 - k) as f64).ln() - (k as f64).ln();
568        }
569        terms.push(log_c + k as f64 * lp + (n - k) as f64 * lq);
570    }
571    terms
572}
573
574/// The regularized incomplete beta function
575/// $I_{x}(a, b) = \frac{1}{B(a, b)} \int_{0}^{x} u^{a-1} (1 - u)^{b-1}\, du$, $a, b > 0$,
576/// $x \in [0, 1]$: the distribution function of a $\mathrm{Beta}(a, b)$ variable, and
577/// the binomial tail $P(X \ge k) = I_{p}(k, n - k + 1)$ of $X \sim \mathrm{Bin}(n, p)$.
578/// The continued fraction of Press et al. (*Numerical Recipes*, §6.4) by the modified
579/// Lentz method, on $I_{1-x}(b, a) = 1 - I_{x}(a, b)$ when
580/// $x > (a + 1)/(a + b + 2)$, where it converges faster; relative error about
581/// $10^{-14}$.
582///
583/// # Examples
584/// ```
585/// use comnum_math::beta_inc;
586/// // I_x(a, 1) = x^a, and Beta(a, a) is symmetric about 1/2
587/// assert!((beta_inc(3.0, 1.0, 0.7) - 0.343).abs() < 1e-15);
588/// assert!((beta_inc(4.5, 4.5, 0.5) - 0.5).abs() < 1e-15);
589/// assert!((beta_inc(3.5, 4.5, 0.4) - 0.429_090_217_274_695_76).abs() < 1e-14);
590/// ```
591pub fn beta_inc(a: f64, b: f64, x: f64) -> f64 {
592    if x <= 0.0 {
593        return 0.0;
594    }
595    if x >= 1.0 {
596        return 1.0;
597    }
598    if x > (a + 1.0) / (a + b + 2.0) {
599        return 1.0 - beta_inc(b, a, 1.0 - x);
600    }
601    let ln_front = a * x.ln() + b * (1.0 - x).ln() + ln_gamma(a + b) - ln_gamma(a) - ln_gamma(b);
602    // 1/(1 + d_1/(1 + d_2/(1 + …))), d_{2m+1} = -(a+m)(a+b+m)x/((a+2m)(a+2m+1)),
603    // d_{2m} = m(b-m)x/((a+2m-1)(a+2m))
604    let term = |k: usize| {
605        let m = (k / 2) as f64;
606        let d = if k % 2 == 1 {
607            -(a + m) * (a + b + m) * x / ((a + 2.0 * m) * (a + 2.0 * m + 1.0))
608        } else {
609            m * (b - m) * x / ((a + 2.0 * m - 1.0) * (a + 2.0 * m))
610        };
611        (d, 1.0)
612    };
613    ln_front.exp() / (a * lentz(1.0, 1.0, 0.0, 10_000, term))
614}
615
616/// The inverse of [`beta_inc`] in $x$: the $q$ quantile of $\mathrm{Beta}(a, b)$, by
617/// bisection on $[0, 1]$ (0 and 1 at the ends of $q$).
618///
619/// # Examples
620/// ```
621/// use comnum_math::{beta_inc, beta_inv};
622/// let x = beta_inv(0.975, 3.5, 4.5);
623/// assert!((x - 0.765_498_750_747_906_2).abs() < 1e-14);
624/// assert!((beta_inc(3.5, 4.5, x) - 0.975).abs() < 1e-14);
625/// ```
626pub fn beta_inv(q: f64, a: f64, b: f64) -> f64 {
627    if q <= 0.0 {
628        return 0.0;
629    }
630    if q >= 1.0 {
631        return 1.0;
632    }
633    bisect(|x| beta_inc(a, b, x) < q, 0.0, 1.0, 200)
634}
635
636/// The exponential integral $E_{1}(x) = \int_{x}^{\infty} \frac{e^{-t}}{t}\, dt$,
637/// $x > 0$: the series $-\gamma - \ln x - \sum_{k \ge 1} \frac{(-x)^{k}}{k\, k!}$ for
638/// $x \le 1$, the continued fraction $\cfrac{e^{-x}}{x + 1 - \cfrac{1}{x + 3 -
639/// \cfrac{4}{x + 5 - \dotsb}}}$ beyond (Press et al., *Numerical Recipes*, §6.3);
640/// relative error about $10^{-15}$. $e^{1/\rho} E_{1}(1/\rho)$ is the ergodic capacity
641/// (nats) of the Rayleigh channel at the mean SNR $\rho$.
642///
643/// # Examples
644/// ```
645/// use comnum_math::expint_e1;
646/// assert!((expint_e1(1.0) - 0.219_383_934_395_520_5).abs() < 1e-15);
647/// assert!((expint_e1(10.0) / 4.156_968_929_685_325e-6 - 1.0).abs() < 1e-14);
648/// ```
649pub fn expint_e1(x: f64) -> f64 {
650    if x <= 0.0 {
651        return f64::INFINITY;
652    }
653    if x <= 1.0 {
654        const EULER: f64 = 0.577_215_664_901_532_9;
655        -EULER - x.ln() - series(1.0, 0.0, 60, |k| -x / k as f64, |k| k as f64)
656    } else {
657        // e^{-x} · 1/(x + 1 - 1/(x + 3 - 4/(x + 5 - …)))
658        let mut b = x + 1.0;
659        let d = 1.0 / b;
660        let term = |i: usize| {
661            b += 2.0;
662            (-((i * i) as f64), b)
663        };
664        lentz(d, 1.0 / LENTZ_TINY, d, 10_000, term) * (-x).exp()
665    }
666}
667/// The generalized hypergeometric function
668/// $_{1}F_{2}(a; b_{1}, b_{2}; z) = \sum_{k \ge 0} \frac{(a)_{k}}{(b_{1})_{k} (b_{2})_{k}}
669/// \frac{z^{k}}{k!}$ by its power series, which converges for every $z$ ($(x)_{k}$ the
670/// rising factorial; $b_{1}, b_{2}$ not zero or negative integers). The ICI of OFDM under
671/// Jakes' fading is $1 - {}_{1}F_{2}(\tfrac{1}{2}; \tfrac{3}{2}, 2; -(\pi f_{D} T_{u})^{2})$
672/// (Robertson & Kaiser 1999; Nissel, Schwarz & Rupp 2017, eq. (42)).
673///
674/// # Examples
675/// ```
676/// use comnum_math::hyp1f2;
677/// // 1F2(1/2; 3/2, 2; -x^2) = 1 - x^2/6 + x^4/60 - ...
678/// let x = 0.1;
679/// assert!((hyp1f2(0.5, 1.5, 2.0, -x * x) - (1.0 - x * x / 6.0 + x.powi(4) / 60.0)).abs() < 1e-8);
680/// // 0F1-like limit: 1F2(a; a, 1; z) = 0F1(; 1; z) = I0(2 sqrt z)
681/// assert!((hyp1f2(1.0, 1.0, 1.0, 1.0) - 2.279_585_302_336_067).abs() < 1e-12);
682/// ```
683pub fn hyp1f2(a: f64, b1: f64, b2: f64, z: f64) -> f64 {
684    let ratio = |k: usize| {
685        let j = (k - 1) as f64;
686        (a + j) / ((b1 + j) * (b2 + j)) * z / k as f64
687    };
688    series(1.0, 1.0, 10_000, ratio, |_| 1.0)
689}
690
691/// The physicists' Hermite polynomial $H_{n}(x)$, by the recurrence
692/// $H_{n+1}(x) = 2x H_{n}(x) - 2n H_{n-1}(x)$ from $H_{0} = 1$, $H_{1} = 2x$ (Abramowitz &
693/// Stegun, eq. 22.7.13); the Hermite prototype of FBMC is a sum of $H_{4i}$.
694///
695/// # Examples
696/// ```
697/// use comnum_math::hermite_h;
698/// assert_eq!(hermite_h(0, 0.3), 1.0);
699/// assert_eq!(hermite_h(2, 0.5), 4.0 * 0.25 - 2.0);
700/// assert!((hermite_h(4, 1.5) - (16.0 * 1.5f64.powi(4) - 48.0 * 1.5 * 1.5 + 12.0)).abs() < 1e-12);
701/// ```
702pub fn hermite_h(n: usize, x: f64) -> f64 {
703    let (mut h0, mut h1) = (1.0, 2.0 * x);
704    if n == 0 {
705        return h0;
706    }
707    for k in 1..n {
708        let h2 = 2.0 * x * h1 - 2.0 * k as f64 * h0;
709        h0 = h1;
710        h1 = h2;
711    }
712    h1
713}