Coverage Report

Created: 2026-11-03 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rand_distr-0.5.1/src/poisson.rs
Line
Count
Source
1
// Copyright 2018 Developers of the Rand project.
2
// Copyright 2016-2017 The Rust Project Developers.
3
//
4
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
7
// option. This file may not be copied, modified, or distributed
8
// except according to those terms.
9
10
//! The Poisson distribution `Poisson(λ)`.
11
12
use crate::{Distribution, Exp1, Normal, StandardNormal, StandardUniform};
13
use core::fmt;
14
use num_traits::{Float, FloatConst};
15
use rand::Rng;
16
17
/// The [Poisson distribution](https://en.wikipedia.org/wiki/Poisson_distribution) `Poisson(λ)`.
18
///
19
/// The Poisson distribution is a discrete probability distribution with
20
/// rate parameter `λ` (`lambda`). It models the number of events occurring in a fixed
21
/// interval of time or space.
22
///
23
/// This distribution has density function:
24
/// `f(k) = λ^k * exp(-λ) / k!` for `k >= 0`.
25
///
26
/// # Plot
27
///
28
/// The following plot shows the Poisson distribution with various values of `λ`.
29
/// Note how the expected number of events increases with `λ`.
30
///
31
/// ![Poisson distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/poisson.svg)
32
///
33
/// # Example
34
///
35
/// ```
36
/// use rand_distr::{Poisson, Distribution};
37
///
38
/// let poi = Poisson::new(2.0).unwrap();
39
/// let v: f64 = poi.sample(&mut rand::rng());
40
/// println!("{} is from a Poisson(2) distribution", v);
41
/// ```
42
///
43
/// # Integer vs FP return type
44
///
45
/// This implementation uses floating-point (FP) logic internally.
46
///
47
/// Due to the parameter limit <code>λ < [Self::MAX_LAMBDA]</code>, it
48
/// statistically impossible to sample a value larger [`u64::MAX`]. As such, it
49
/// is reasonable to cast generated samples to `u64` using `as`:
50
/// `distr.sample(&mut rng) as u64` (and memory safe since Rust 1.45).
51
/// Similarly, when `λ < 4.2e9` it can be safely assumed that samples are less
52
/// than `u32::MAX`.
53
#[derive(Clone, Copy, Debug, PartialEq)]
54
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55
pub struct Poisson<F>(Method<F>)
56
where
57
    F: Float + FloatConst,
58
    StandardUniform: Distribution<F>;
59
60
/// Error type returned from [`Poisson::new`].
61
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62
pub enum Error {
63
    /// `lambda <= 0`
64
    ShapeTooSmall,
65
    /// `lambda = ∞` or `lambda = nan`
66
    NonFinite,
67
    /// `lambda` is too large, see [Poisson::MAX_LAMBDA]
68
    ShapeTooLarge,
69
}
70
71
impl fmt::Display for Error {
72
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73
0
        f.write_str(match self {
74
0
            Error::ShapeTooSmall => "lambda is not positive in Poisson distribution",
75
0
            Error::NonFinite => "lambda is infinite or nan in Poisson distribution",
76
            Error::ShapeTooLarge => {
77
0
                "lambda is too large in Poisson distribution, see Poisson::MAX_LAMBDA"
78
            }
79
        })
80
0
    }
81
}
82
83
#[cfg(feature = "std")]
84
impl std::error::Error for Error {}
85
86
#[derive(Clone, Copy, Debug, PartialEq)]
87
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
88
pub(crate) struct KnuthMethod<F> {
89
    exp_lambda: F,
90
}
91
92
impl<F: Float> KnuthMethod<F> {
93
0
    pub(crate) fn new(lambda: F) -> Self {
94
0
        KnuthMethod {
95
0
            exp_lambda: (-lambda).exp(),
96
0
        }
97
0
    }
98
}
99
100
#[derive(Clone, Copy, Debug, PartialEq)]
101
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102
struct RejectionMethod<F> {
103
    lambda: F,
104
    s: F,
105
    d: F,
106
    l: F,
107
    c: F,
108
    c0: F,
109
    c1: F,
110
    c2: F,
111
    c3: F,
112
    omega: F,
113
}
114
115
impl<F: Float + FloatConst> RejectionMethod<F> {
116
0
    pub(crate) fn new(lambda: F) -> Self {
117
0
        let b1 = F::from(1.0 / 24.0).unwrap() / lambda;
118
0
        let b2 = F::from(0.3).unwrap() * b1 * b1;
119
0
        let c3 = F::from(1.0 / 7.0).unwrap() * b1 * b2;
120
0
        let c2 = b2 - F::from(15).unwrap() * c3;
121
0
        let c1 = b1 - F::from(6).unwrap() * b2 + F::from(45).unwrap() * c3;
122
0
        let c0 = F::one() - b1 + F::from(3).unwrap() * b2 - F::from(15).unwrap() * c3;
123
124
0
        RejectionMethod {
125
0
            lambda,
126
0
            s: lambda.sqrt(),
127
0
            d: F::from(6.0).unwrap() * lambda.powi(2),
128
0
            l: (lambda - F::from(1.1484).unwrap()).floor(),
129
0
            c: F::from(0.1069).unwrap() / lambda,
130
0
            c0,
131
0
            c1,
132
0
            c2,
133
0
            c3,
134
0
            omega: F::one() / (F::from(2).unwrap() * F::PI()).sqrt() / lambda.sqrt(),
135
0
        }
136
0
    }
137
}
138
139
#[derive(Clone, Copy, Debug, PartialEq)]
140
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
141
enum Method<F> {
142
    Knuth(KnuthMethod<F>),
143
    Rejection(RejectionMethod<F>),
144
}
145
146
impl<F> Poisson<F>
147
where
148
    F: Float + FloatConst,
149
    StandardUniform: Distribution<F>,
150
{
151
    /// Construct a new `Poisson` with the given shape parameter
152
    /// `lambda`.
153
    ///
154
    /// The maximum allowed lambda is [MAX_LAMBDA](Self::MAX_LAMBDA).
155
0
    pub fn new(lambda: F) -> Result<Poisson<F>, Error> {
156
0
        if !lambda.is_finite() {
157
0
            return Err(Error::NonFinite);
158
0
        }
159
0
        if !(lambda > F::zero()) {
160
0
            return Err(Error::ShapeTooSmall);
161
0
        }
162
163
        // Use the Knuth method only for low expected values
164
0
        let method = if lambda < F::from(12.0).unwrap() {
165
0
            Method::Knuth(KnuthMethod::new(lambda))
166
        } else {
167
0
            if lambda > F::from(Self::MAX_LAMBDA).unwrap() {
168
0
                return Err(Error::ShapeTooLarge);
169
0
            }
170
0
            Method::Rejection(RejectionMethod::new(lambda))
171
        };
172
173
0
        Ok(Poisson(method))
174
0
    }
175
176
    /// The maximum supported value of `lambda`
177
    ///
178
    /// This value was selected such that
179
    /// `MAX_LAMBDA + 1e6 * sqrt(MAX_LAMBDA) < 2^64 - 1`,
180
    /// thus ensuring that the probability of sampling a value larger than
181
    /// `u64::MAX` is less than 1e-1000.
182
    ///
183
    /// Applying this limit also solves
184
    /// [#1312](https://github.com/rust-random/rand/issues/1312).
185
    pub const MAX_LAMBDA: f64 = 1.844e19;
186
}
187
188
impl<F> Distribution<F> for KnuthMethod<F>
189
where
190
    F: Float + FloatConst,
191
    StandardUniform: Distribution<F>,
192
{
193
0
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F {
194
0
        let mut result = F::one();
195
0
        let mut p = rng.random::<F>();
196
0
        while p > self.exp_lambda {
197
0
            p = p * rng.random::<F>();
198
0
            result = result + F::one();
199
0
        }
200
0
        result - F::one()
201
0
    }
202
}
203
204
impl<F> Distribution<F> for RejectionMethod<F>
205
where
206
    F: Float + FloatConst,
207
    StandardUniform: Distribution<F>,
208
    StandardNormal: Distribution<F>,
209
    Exp1: Distribution<F>,
210
{
211
0
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F {
212
        // The algorithm is based on:
213
        // J. H. Ahrens and U. Dieter. 1982.
214
        // Computer Generation of Poisson Deviates from Modified Normal Distributions.
215
        // ACM Trans. Math. Softw. 8, 2 (June 1982), 163–179. https://doi.org/10.1145/355993.355997
216
217
        // Step F
218
0
        let f = |k: F| {
219
            const FACT: [f64; 10] = [
220
                1.0, 1.0, 2.0, 6.0, 24.0, 120.0, 720.0, 5040.0, 40320.0, 362880.0,
221
            ]; // factorial of 0..10
222
            const A: [f64; 10] = [
223
                -0.5000000002,
224
                0.3333333343,
225
                -0.2499998565,
226
                0.1999997049,
227
                -0.1666848753,
228
                0.1428833286,
229
                -0.1241963125,
230
                0.1101687109,
231
                -0.1142650302,
232
                0.1055093006,
233
            ]; // coefficients from Table 1
234
0
            let (px, py) = if k < F::from(10.0).unwrap() {
235
0
                let px = -self.lambda;
236
0
                let py = self.lambda.powf(k) / F::from(FACT[k.to_usize().unwrap()]).unwrap();
237
238
0
                (px, py)
239
            } else {
240
0
                let delta = (F::from(12.0).unwrap() * k).recip();
241
0
                let delta = delta - F::from(4.8).unwrap() * delta.powi(3);
242
0
                let v = (self.lambda - k) / k;
243
244
0
                let px = if v.abs() <= F::from(0.25).unwrap() {
245
0
                    k * v.powi(2)
246
0
                        * A.iter()
247
0
                            .rev()
248
0
                            .fold(F::zero(), |acc, &a| {
249
0
                                acc * v + F::from(a).unwrap()
250
0
                            }) // Σ a_i * v^i
251
0
                        - delta
252
                } else {
253
0
                    k * (F::one() + v).ln() - (self.lambda - k) - delta
254
                };
255
256
0
                let py = F::one() / (F::from(2.0).unwrap() * F::PI()).sqrt() / k.sqrt();
257
258
0
                (px, py)
259
            };
260
261
0
            let x = (k - self.lambda + F::from(0.5).unwrap()) / self.s;
262
0
            let fx = -F::from(0.5).unwrap() * x * x;
263
0
            let fy =
264
0
                self.omega * (((self.c3 * x * x + self.c2) * x * x + self.c1) * x * x + self.c0);
265
266
0
            (px, py, fx, fy)
267
0
        };
268
269
        // Step N
270
0
        let normal = Normal::new(self.lambda, self.s).unwrap();
271
0
        let g = normal.sample(rng);
272
0
        if g >= F::zero() {
273
0
            let k1 = g.floor();
274
275
            // Step I
276
0
            if k1 >= self.l {
277
0
                return k1;
278
0
            }
279
280
            // Step S
281
0
            let u: F = rng.random();
282
0
            if self.d * u >= (self.lambda - k1).powi(3) {
283
0
                return k1;
284
0
            }
285
286
0
            let (px, py, fx, fy) = f(k1);
287
288
0
            if fy * (F::one() - u) <= py * (px - fx).exp() {
289
0
                return k1;
290
0
            }
291
0
        }
292
293
        loop {
294
            // Step E
295
0
            let e = Exp1.sample(rng);
296
0
            let u: F = rng.random() * F::from(2.0).unwrap() - F::one();
297
0
            let t = F::from(1.8).unwrap() + e * u.signum();
298
0
            if t > F::from(-0.6744).unwrap() {
299
0
                let k2 = (self.lambda + self.s * t).floor();
300
0
                let (px, py, fx, fy) = f(k2);
301
                // Step H
302
0
                if self.c * u.abs() <= py * (px + e).exp() - fy * (fx + e).exp() {
303
0
                    return k2;
304
0
                }
305
0
            }
306
        }
307
0
    }
308
}
309
310
impl<F> Distribution<F> for Poisson<F>
311
where
312
    F: Float + FloatConst,
313
    StandardUniform: Distribution<F>,
314
    StandardNormal: Distribution<F>,
315
    Exp1: Distribution<F>,
316
{
317
    #[inline]
318
0
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F {
319
0
        match &self.0 {
320
0
            Method::Knuth(method) => method.sample(rng),
321
0
            Method::Rejection(method) => method.sample(rng),
322
        }
323
0
    }
324
}
325
326
#[cfg(test)]
327
mod test {
328
    use super::*;
329
330
    #[test]
331
    #[should_panic]
332
    fn test_poisson_invalid_lambda_zero() {
333
        Poisson::new(0.0).unwrap();
334
    }
335
336
    #[test]
337
    #[should_panic]
338
    fn test_poisson_invalid_lambda_infinity() {
339
        Poisson::new(f64::INFINITY).unwrap();
340
    }
341
342
    #[test]
343
    #[should_panic]
344
    fn test_poisson_invalid_lambda_neg() {
345
        Poisson::new(-10.0).unwrap();
346
    }
347
348
    #[test]
349
    fn poisson_distributions_can_be_compared() {
350
        assert_eq!(Poisson::new(1.0), Poisson::new(1.0));
351
    }
352
}