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}