Skip to main content

des_sim/modeling/sampler/instance/
poisson.rs

1//! The `poisson` module provides the `PoissonSampler`, a `DurationSampler`
2//! that generates durations following a Poisson distribution.
3//!
4//! This is typically used to model the number of events occurring within a fixed
5//! interval of time, given a known average rate of occurrence. The sampled values
6//! are discrete (integer-valued) and non-negative.
7
8use crate::modeling::sampler::{DurationSampler, PendingDuration};
9use crate::primitive::time::SimTime;
10use rand::Rng;
11use rand::distr::Distribution;
12use rand_distr::{Poisson, PoissonError};
13
14/// A sampler that draws from a Poisson distribution with a specified lambda (rate).
15#[derive(Debug, Clone)]
16pub struct PoissonSampler {
17    dist: Poisson<f64>,
18}
19
20impl DurationSampler for PoissonSampler {
21    fn sample(&mut self, rng: &mut dyn Rng, _current_tick: SimTime) -> PendingDuration {
22        // The Poisson distribution returns discrete integer values represented as f64.
23        PendingDuration::new(self.dist.sample(rng))
24    }
25}
26
27impl PoissonSampler {
28    /// Creates a new `PoissonSampler` with the given lambda.
29    /// Returns an error if the lambda is invalid (e.g., non-finite, too small, or too large).
30    pub fn new(lambda: f64) -> Result<Self, PoissonError> {
31        Poisson::new(lambda).map(|dist| PoissonSampler { dist })
32    }
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38    use rand::SeedableRng;
39    use rand::rngs::SmallRng;
40
41    #[test]
42    fn test_poisson_sampler() {
43        let mut rng = SmallRng::seed_from_u64(2);
44        let lambda = 5.0; // Expected mean and variance
45        let mut sampler = PoissonSampler::new(lambda).unwrap();
46
47        let mut samples = Vec::new();
48        for _ in 0..10000 {
49            samples.push(sampler.sample(&mut rng, SimTime::from_ticks(0)).raw_value());
50        }
51
52        let sample_mean: f64 = samples.iter().sum::<f64>() / samples.len() as f64;
53        let variance: f64 = samples
54            .iter()
55            .map(|x| (x - sample_mean).powi(2))
56            .sum::<f64>()
57            / samples.len() as f64;
58
59        // Check if the sampled mean and variance are close to the expected value (lambda)
60        assert!(
61            (sample_mean - lambda).abs() < 0.1,
62            "Mean: {}, Expected: {}",
63            sample_mean,
64            lambda
65        );
66        assert!(
67            (variance - lambda).abs() < 0.1,
68            "Variance: {}, Expected: {}",
69            variance,
70            lambda
71        );
72
73        // Verify that all samples are non-negative and integer-valued
74        assert!(samples.iter().all(|&x| x >= 0.0 && x.fract() == 0.0));
75    }
76
77    #[test]
78    fn test_poisson_sampler_invalid_zero_lambda() {
79        let sampler = PoissonSampler::new(0.0);
80        assert_eq!(sampler.err(), Some(PoissonError::ShapeTooSmall));
81        let sampler = PoissonSampler::new(-0.01);
82        assert_eq!(sampler.err(), Some(PoissonError::ShapeTooSmall));
83    }
84
85    #[test]
86    fn test_poisson_sampler_invalid_infinity_lambda() {
87        let sampler = PoissonSampler::new(f64::INFINITY);
88        assert_eq!(sampler.err(), Some(PoissonError::NonFinite));
89        let sampler = PoissonSampler::new(f64::NAN);
90        assert_eq!(sampler.err(), Some(PoissonError::NonFinite));
91    }
92
93    #[test]
94    fn test_poisson_sampler_invalid_lambda() {
95        // Too large lambda
96        let sampler = PoissonSampler::new(Poisson::<f64>::MAX_LAMBDA * 2.0);
97        assert_eq!(sampler.err(), Some(PoissonError::ShapeTooLarge));
98    }
99}