Skip to main content

u_numflow/distributions/
sampling.rs

1//! Random variates from the continuous distributions.
2//!
3//! # Methods
4//!
5//! - **Inverse transform** — `x = F⁻¹(u)` — wherever the quantile is closed
6//!   form or a direct formula: [`Uniform`], [`Triangular`], [`Normal`],
7//!   [`LogNormal`], [`Weibull`], [`Exponential`]. A larger `u` always gives a
8//!   larger `x`, so two runs on one seed are coupled point by point (common
9//!   random numbers).
10//! - **Marsaglia–Tsang** squeeze for the gamma family, whose quantile is a
11//!   Newton iteration costing dozens of incomplete-gamma evaluations per
12//!   draw: [`GammaDistribution`], [`ChiSquared`] (Gamma(k/2, rate ½)),
13//!   [`BetaDistribution`] (X/(X+Y) with X ~ Gamma(α), Y ~ Gamma(β)) and
14//!   [`Pert`] (a scaled Beta).
15//!
16//! Every uniform is drawn from the **open** interval (0, 1) — 52 random bits
17//! offset by half a step — so `F⁻¹(0)` and `ln 0` cannot occur and no variate
18//! is infinite.
19//!
20//! # References
21//!
22//! - Devroye (1986), *Non-Uniform Random Variate Generation*, §II.2
23//!   (inversion), §IX.4 (beta from gammas).
24//! - Marsaglia & Tsang (2000), "A simple method for generating gamma
25//!   variables", *ACM TOMS* 26(3), 363–372 — including the `U^(1/a)` boost
26//!   for shape `a < 1`.
27
28use rand::Rng;
29
30use super::{
31    BetaDistribution, ChiSquared, Exponential, GammaDistribution, LogNormal, Normal, Pert,
32    Triangular, Uniform, Weibull,
33};
34use crate::random::create_rng;
35
36/// A distribution that can draw random variates.
37///
38/// # Examples
39/// ```
40/// use u_numflow::distributions::{Sample, Weibull};
41///
42/// let w = Weibull::new(2.0, 100.0).unwrap();
43/// let a = w.sample_n(1000, 42);
44/// assert_eq!(a, w.sample_n(1000, 42)); // same seed, same draws
45/// assert!(a.iter().all(|&t| t > 0.0 && t.is_finite()));
46/// ```
47pub trait Sample {
48    /// One variate, drawn with `rng`.
49    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64;
50
51    /// `n` variates from a generator seeded with `seed` ([`create_rng`]): the
52    /// same `(n, seed)` always gives the same values.
53    fn sample_n(&self, n: usize, seed: u64) -> Vec<f64> {
54        let mut rng = create_rng(seed);
55        (0..n).map(|_| self.sample(&mut rng)).collect()
56    }
57}
58
59/// A uniform variate on the open interval (0, 1).
60///
61/// The top 52 bits of a `u64` give `k ∈ [0, 2⁵²)`; `(k + ½)·2⁻⁵²` lies in
62/// `[2⁻⁵³, 1 − 2⁻⁵³]` and is exact in `f64`, so it is never 0 or 1. (With 53
63/// bits the top value would be `1 − 2⁻⁵⁴`, which `f64` rounds to 1.)
64pub(crate) fn open_unit<R: Rng + ?Sized>(rng: &mut R) -> f64 {
65    const STEP: f64 = 1.0 / (1u64 << 52) as f64;
66    ((rng.next_u64() >> 12) as f64 + 0.5) * STEP
67}
68
69/// Standard normal by inversion of an open uniform.
70fn standard_normal<R: Rng + ?Sized>(rng: &mut R) -> f64 {
71    crate::special::inverse_normal_cdf(open_unit(rng))
72}
73
74/// Gamma(shape, rate 1) by Marsaglia & Tsang (2000).
75fn standard_gamma<R: Rng + ?Sized>(shape: f64, rng: &mut R) -> f64 {
76    if shape < 1.0 {
77        // Gamma(a) = Gamma(a + 1) · U^(1/a)  (Marsaglia & Tsang §4).
78        return standard_gamma(shape + 1.0, rng) * open_unit(rng).powf(1.0 / shape);
79    }
80    let d = shape - 1.0 / 3.0;
81    let c = 1.0 / (9.0 * d).sqrt();
82    loop {
83        let x = standard_normal(rng);
84        let v = 1.0 + c * x;
85        if v <= 0.0 {
86            continue;
87        }
88        let v = v * v * v;
89        let u = open_unit(rng);
90        let x2 = x * x;
91        // Squeeze, then the exact log test.
92        if u < 1.0 - 0.0331 * x2 * x2 || u.ln() < 0.5 * x2 + d * (1.0 - v + v.ln()) {
93            return d * v;
94        }
95    }
96}
97
98macro_rules! by_inversion {
99    ($($t:ty),*) => {$(
100        impl Sample for $t {
101            fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
102                self.quantile(open_unit(rng))
103                    .expect("the quantile is defined on the open unit interval")
104            }
105        }
106    )*};
107}
108
109by_inversion!(Uniform, Triangular, Normal, LogNormal, Weibull, Exponential);
110
111impl Sample for GammaDistribution {
112    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
113        standard_gamma(self.shape(), rng) / self.rate()
114    }
115}
116
117impl Sample for ChiSquared {
118    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
119        2.0 * standard_gamma(self.k / 2.0, rng)
120    }
121}
122
123/// `X / (X + Y)` with `X ~ Gamma(α)`, `Y ~ Gamma(β)`.
124fn beta_variate<R: Rng + ?Sized>(alpha: f64, beta: f64, rng: &mut R) -> f64 {
125    let x = standard_gamma(alpha, rng);
126    let y = standard_gamma(beta, rng);
127    x / (x + y)
128}
129
130impl Sample for BetaDistribution {
131    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
132        beta_variate(self.alpha, self.beta, rng)
133    }
134}
135
136impl Sample for Pert {
137    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
138        self.min() + (self.max() - self.min()) * beta_variate(self.alpha(), self.beta_param(), rng)
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    /// Kolmogorov–Smirnov distance between `xs` and `cdf`.
147    fn ks(mut xs: Vec<f64>, cdf: impl Fn(f64) -> f64) -> f64 {
148        xs.sort_by(|a, b| a.partial_cmp(b).expect("finite variates"));
149        let n = xs.len() as f64;
150        xs.iter()
151            .enumerate()
152            .map(|(i, &x)| {
153                let f = cdf(x);
154                (f - i as f64 / n).abs().max(((i + 1) as f64 / n - f).abs())
155            })
156            .fold(0.0, f64::max)
157    }
158
159    /// 20 000 draws; the 0.1 % critical value of D is 1.95/√n ≈ 0.0138.
160    const N: usize = 20_000;
161    const D_CRIT: f64 = 0.0138;
162
163    fn check<D: Sample>(d: &D, cdf: impl Fn(f64) -> f64, name: &str) {
164        let xs = d.sample_n(N, 7);
165        assert!(
166            xs.iter().all(|x| x.is_finite()),
167            "{name}: non-finite variate"
168        );
169        let dist = ks(xs, cdf);
170        assert!(dist < D_CRIT, "{name}: KS distance {dist} >= {D_CRIT}");
171    }
172
173    #[test]
174    fn every_distribution_matches_its_cdf() {
175        let u = Uniform::new(-2.0, 5.0).unwrap();
176        check(&u, |x| u.cdf(x), "uniform");
177        let t = Triangular::new(0.0, 1.0, 4.0).unwrap();
178        check(&t, |x| t.cdf(x), "triangular");
179        let n = Normal::new(3.0, 2.0).unwrap();
180        check(&n, |x| n.cdf(x), "normal");
181        let ln = LogNormal::new(0.5, 0.8).unwrap();
182        check(&ln, |x| ln.cdf(x), "lognormal");
183        let w = Weibull::new(1.7, 40.0).unwrap();
184        check(&w, |x| w.cdf(x), "weibull");
185        let e = Exponential::new(0.25).unwrap();
186        check(&e, |x| e.cdf(x), "exponential");
187        for shape in [0.3, 1.0, 2.5, 30.0] {
188            let g = GammaDistribution::new(shape, 2.0).unwrap();
189            check(&g, |x| g.cdf(x), &format!("gamma({shape})"));
190        }
191        for k in [1.0, 3.0, 17.0] {
192            let c = ChiSquared::new(k).unwrap();
193            check(&c, |x| c.cdf(x), &format!("chi2({k})"));
194        }
195        for (a, b) in [(0.5, 0.5), (2.0, 5.0), (8.0, 1.5)] {
196            let bd = BetaDistribution::new(a, b).unwrap();
197            check(&bd, |x| bd.cdf(x), &format!("beta({a},{b})"));
198        }
199        let p = Pert::new(2.0, 3.0, 10.0).unwrap();
200        check(&p, |x| p.cdf(x), "pert");
201    }
202
203    #[test]
204    fn same_seed_same_draws_different_seed_different_draws() {
205        let g = GammaDistribution::new(2.0, 1.0).unwrap();
206        assert_eq!(g.sample_n(50, 1), g.sample_n(50, 1));
207        assert_ne!(g.sample_n(50, 1), g.sample_n(50, 2));
208    }
209
210    #[test]
211    fn inversion_is_monotone_in_the_uniform() {
212        // Common random numbers: a larger scale never lowers any draw.
213        let small = Weibull::new(2.0, 10.0).unwrap().sample_n(200, 5);
214        let large = Weibull::new(2.0, 20.0).unwrap().sample_n(200, 5);
215        assert!(small.iter().zip(&large).all(|(a, b)| a < b));
216    }
217
218    #[test]
219    fn the_open_uniform_never_reaches_its_ends() {
220        struct Fixed(u64);
221        impl rand::TryRng for Fixed {
222            type Error = std::convert::Infallible;
223            fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
224                Ok(self.0 as u32)
225            }
226            fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
227                Ok(self.0)
228            }
229            fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
230                dst.fill(0);
231                Ok(())
232            }
233        }
234        let lo = open_unit(&mut Fixed(0));
235        let hi = open_unit(&mut Fixed(u64::MAX));
236        assert!(lo > 0.0 && hi < 1.0);
237        assert!(Exponential::new(1.0)
238            .unwrap()
239            .quantile(hi)
240            .unwrap()
241            .is_finite());
242    }
243}