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