Skip to main content

probability_rs/dist/
poisson.rs

1use crate::dist::{Discrete, DistError, Distribution, Moments};
2use crate::num;
3use crate::rng::RngCore;
4
5/// Poisson(λ) distribution over non-negative integers.
6///
7/// - Support: k = 0,1,2,...
8/// - Mean = λ
9/// - Var = λ
10#[derive(Debug, Clone, Copy)]
11pub struct Poisson {
12    lambda: f64,
13}
14
15impl Poisson {
16    pub fn new(lambda: f64) -> Result<Self, DistError> {
17        if !(lambda.is_finite() && lambda > 0.0) {
18            return Err(DistError::InvalidParameter);
19        }
20        Ok(Self { lambda })
21    }
22    #[inline]
23    pub fn lambda(&self) -> f64 {
24        self.lambda
25    }
26
27    #[inline]
28    fn pmf_rec_start(&self) -> f64 {
29        // p(0) = e^{-lambda}
30        (-self.lambda).exp()
31    }
32
33    /// Compute pmf(k) using recurrence from 0..k: p(k) = p(k-1) * λ / k
34    /// Cost O(k). Accurate and avoids factorial overflow.
35    fn pmf_via_recurrence(&self, k: i64) -> f64 {
36        if k < 0 {
37            return 0.0;
38        }
39        let k = k as u64;
40        let mut p = self.pmf_rec_start();
41        for i in 1..=k {
42            p *= self.lambda / (i as f64);
43        }
44        p
45    }
46
47    /// CDF up to k by summing recurrence.
48    fn cdf_via_recurrence(&self, k: i64) -> f64 {
49        if k < 0 {
50            return 0.0;
51        }
52        let k = k as u64;
53        let mut p = self.pmf_rec_start();
54        let mut acc = p;
55        for i in 1..=k {
56            p *= self.lambda / (i as f64);
57            acc += p;
58        }
59        acc
60    }
61}
62
63impl Distribution for Poisson {
64    type Value = i64;
65
66    fn cdf(&self, x: Self::Value) -> f64 {
67        self.cdf_via_recurrence(x)
68    }
69
70    fn in_support(&self, x: Self::Value) -> bool {
71        x >= 0
72    }
73
74    fn sample<R: RngCore>(&self, rng: &mut R) -> Self::Value {
75        // Hybrid sampler:
76        // - λ < 30: simple inversion from 0 (exact, fast enough)
77        // - 30 ≤ λ < 400: inversion from the mode (exact, ~O(√λ))
78        // - λ ≥ 400: quantile-anchored inversion (exact, near O(1) with small constant)
79        if self.lambda < 30.0 {
80            // Small-λ inversion
81            let mut k: i64 = 0;
82            let mut p = self.pmf_rec_start();
83            let mut c = p;
84            let u = rng.next_f64();
85            while u > c {
86                k += 1;
87                p *= self.lambda / (k as f64);
88                c += p;
89            }
90            return k;
91        }
92        let lambda = self.lambda;
93        let m = lambda.floor() as i64; // mode = floor(λ)
94        if lambda < 400.0 {
95            // Mode-based symmetric inversion
96            let log_p_m = (m as f64) * lambda.ln() - lambda - ln_factorial_u64(m as u64);
97            let p_m = log_p_m.exp();
98            if p_m.partial_cmp(&0.0) != Some(std::cmp::Ordering::Greater) {
99                // Fallback to small-λ path (pathological underflow)
100                let mut k: i64 = 0;
101                let mut p = self.pmf_rec_start();
102                let mut c = p;
103                let u = rng.next_f64();
104                while u > c {
105                    k += 1;
106                    p *= self.lambda / (k as f64);
107                    c += p;
108                }
109                return k;
110            }
111            let u = rng.next_f64();
112            let mut c = p_m;
113            if u <= c {
114                return m;
115            }
116            let mut left = p_m;
117            let mut right = p_m;
118            let mut i: i64 = 1;
119            loop {
120                if i <= m {
121                    left *= (m - (i - 1)) as f64 / lambda; // p(m-(i-1)) -> p(m-i)
122                    c += left;
123                    if u <= c {
124                        return m - i;
125                    }
126                } else {
127                    left = 0.0;
128                }
129                right *= lambda / (m + i) as f64; // p(m+(i-1)) -> p(m+i)
130                c += right;
131                if u <= c {
132                    return m + i;
133                }
134                i += 1;
135            }
136        }
137        // Quantile-anchored inversion for very large λ
138        let u_anchor = rng.next_f64();
139        let z = num::standard_normal_inv_cdf(u_anchor);
140        let mut k0 = (lambda + z * lambda.sqrt()).floor() as i64;
141        if k0 < 0 {
142            k0 = 0;
143        }
144        let log_p0 = (k0 as f64) * lambda.ln() - lambda - ln_factorial_u64(k0 as u64);
145        let p0 = log_p0.exp();
146        if !(p0 > 0.0 && p0.is_finite()) {
147            // Fallback to mode-based
148            let log_p_m = (m as f64) * lambda.ln() - lambda - ln_factorial_u64(m as u64);
149            let p_m = log_p_m.exp();
150            let u = rng.next_f64();
151            let mut c = p_m;
152            if u <= c {
153                return m;
154            }
155            let mut left = p_m;
156            let mut right = p_m;
157            let mut i: i64 = 1;
158            loop {
159                if i <= m {
160                    left *= (m - (i - 1)) as f64 / lambda;
161                    c += left;
162                    if u <= c {
163                        return m - i;
164                    }
165                } else {
166                    left = 0.0;
167                }
168                right *= lambda / (m + i) as f64;
169                c += right;
170                if u <= c {
171                    return m + i;
172                }
173                i += 1;
174            }
175        }
176        let u = rng.next_f64();
177        let mut c = p0;
178        if u <= c {
179            return k0;
180        }
181        let mut left = p0;
182        let mut right = p0;
183        let mut i: i64 = 1;
184        loop {
185            if i <= k0 {
186                left *= (k0 - (i - 1)) as f64 / lambda;
187                c += left;
188                if u <= c {
189                    return k0 - i;
190                }
191            } else {
192                left = 0.0;
193            }
194            right *= lambda / (k0 + i) as f64;
195            c += right;
196            if u <= c {
197                return k0 + i;
198            }
199            i += 1;
200        }
201    }
202}
203
204impl Discrete for Poisson {
205    fn pmf(&self, x: Self::Value) -> f64 {
206        self.pmf_via_recurrence(x)
207    }
208
209    fn inv_cdf(&self, p: f64) -> Self::Value {
210        debug_assert!((0.0..=1.0).contains(&p));
211        if p <= 0.0 {
212            return 0;
213        }
214        if p >= 1.0 {
215            return i64::MAX;
216        }
217        let mut k: i64 = 0;
218        let mut pk = self.pmf_rec_start();
219        let mut acc = pk;
220        while acc < p {
221            k += 1;
222            pk *= self.lambda / (k as f64);
223            acc += pk;
224        }
225        k
226    }
227}
228
229impl Moments for Poisson {
230    fn mean(&self) -> f64 {
231        self.lambda
232    }
233    fn variance(&self) -> f64 {
234        self.lambda
235    }
236    fn skewness(&self) -> f64 { 1.0 / self.lambda.sqrt() }
237    fn kurtosis(&self) -> f64 { 1.0 / self.lambda }
238    fn entropy(&self) -> f64 {
239        // Shannon entropy: H = - sum p(k) ln p(k). Truncate at a reasonable range around the mean.
240        let lambda = self.lambda;
241        let mean = lambda;
242        let std = lambda.sqrt();
243        let mut k_min = (mean - 10.0 * std).floor() as i64;
244        if k_min < 0 { k_min = 0; }
245        let k_max = (mean + 10.0 * std).ceil() as i64;
246        let mut h = 0.0;
247        for k in k_min..=k_max {
248            let pk = self.pmf_via_recurrence(k);
249            if pk > 0.0 { h -= pk * pk.ln(); }
250        }
251        h
252    }
253}
254
255// -------- Internal helpers for large-λ sampling --------
256
257#[inline]
258fn ln_factorial_u64(n: u64) -> f64 {
259    // Exact table for 0..=20
260    const LN_FACT_SMALL: [f64; 21] = [
261        0.0,
262        0.0,
263        std::f64::consts::LN_2,
264        1.791759469228055,
265        3.1780538303479458,
266        4.787491742782046,
267        6.579251212010101,
268        8.525161361065415,
269        10.60460290274525,
270        12.80182748008147,
271        15.104412573075516,
272        17.502307845873887,
273        19.98721449566189,
274        22.552163853123425,
275        25.19122118273868,
276        27.899271383840894,
277        30.671860106080675,
278        33.50507345013689,
279        36.39544520803305,
280        39.339884187199495,
281        42.335616460753485,
282    ];
283    if n <= 20 {
284        return LN_FACT_SMALL[n as usize];
285    }
286    // Stirling with 1/(12n) - 1/(360n^3) + 1/(1260 n^5) correction
287    let x = n as f64;
288    let inv = 1.0 / x;
289    let inv2 = inv * inv;
290    let inv3 = inv2 * inv;
291    let inv5 = inv3 * inv2;
292    x * x.ln() - x + 0.5 * ((2.0 * std::f64::consts::PI * x).ln()) + (inv / 12.0) - (inv3 / 360.0)
293        + (inv5 / 1260.0)
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    #[test]
301    fn pmf_values() {
302        let p = Poisson::new(3.0).unwrap();
303        let e3 = (-3.0f64).exp();
304        assert!((p.pmf(0) - e3).abs() < 1e-15);
305        // pmf(3) = e^-3 * 3^3/3! = e^-3 * 27/6 = 4.5 e^-3
306        assert!((p.pmf(3) - 4.5 * e3).abs() < 1e-12);
307    }
308
309    #[test]
310    fn cdf_bounds() {
311        let p = Poisson::new(1.5).unwrap();
312        assert_eq!(p.cdf(-1), 0.0);
313        assert!(p.cdf(0) > 0.0);
314        assert!(p.cdf(10) < 1.0);
315    }
316
317    #[test]
318    fn inv_cdf_roundtrip() {
319        let pois = Poisson::new(2.5).unwrap();
320        let ps = [0.1, 0.3, 0.5, 0.8, 0.95];
321        for &prob in &ps {
322            let k = pois.inv_cdf(prob);
323            assert!(pois.cdf(k) >= prob - 1e-15);
324            if k > 0 {
325                assert!(pois.cdf(k - 1) <= prob + 1e-15);
326            }
327        }
328    }
329
330    #[test]
331    fn sampling_deterministic() {
332        let pois = Poisson::new(4.0).unwrap();
333        let mut r1 = crate::rng::SplitMix64::seed_from_u64(7);
334        let mut r2 = crate::rng::SplitMix64::seed_from_u64(7);
335        let x1 = pois.sample(&mut r1);
336        let x2 = pois.sample(&mut r2);
337        assert_eq!(x1, x2);
338    }
339
340    #[test]
341    fn moments_higher() {
342        let p = Poisson::new(4.0).unwrap();
343        assert!((p.skewness() - 0.5).abs() < 1e-15);
344        assert!((p.kurtosis() - 0.25).abs() < 1e-15);
345        assert!((p.kurtosis_full() - 3.25).abs() < 1e-15);
346    }
347}