Skip to main content

des_sim/modeling/sampler/instance/
exponential.rs

1//! The `exponential` module provides the `ExponentialSampler`, a `DurationSampler`
2//! that generates durations following an exponential distribution.
3//!
4//! This is commonly used to model the time between events in a Poisson process,
5//! such as arrival times in queuing systems, where events occur continuously
6//! and independently at a constant average rate.
7
8use crate::modeling::sampler::{DurationSampler, PendingDuration};
9use crate::primitive::time::SimTime;
10use rand::Rng;
11use rand::distr::Distribution;
12use rand_distr::{Exp, ExpError};
13
14/// A sampler that draws from an exponential distribution with a specified lambda (rate).
15#[derive(Debug, Clone)]
16pub struct ExponentialSampler {
17    dist: Exp<f64>,
18}
19
20impl DurationSampler for ExponentialSampler {
21    fn sample(&mut self, rng: &mut dyn Rng, _current_tick: SimTime) -> PendingDuration {
22        PendingDuration::new(self.dist.sample(rng))
23    }
24}
25
26impl ExponentialSampler {
27    /// Creates a new `ExponentialSampler` with the given lambda (rate parameter).
28    /// Returns an error if the lambda is invalid (e.g., negative or NaN).
29    pub fn new(lambda: f64) -> Result<Self, ExpError> {
30        Exp::new(lambda).map(|dist| ExponentialSampler { dist })
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use rand::SeedableRng;
38    use rand::rngs::SmallRng;
39
40    #[test]
41    fn test_new_valid_lambda() {
42        let lambda = 0.5;
43        let sampler = ExponentialSampler::new(lambda);
44        assert!(sampler.is_ok());
45    }
46
47    #[test]
48    fn test_new_invalid_lambda() {
49        let lambda = -0.5;
50        let sampler = ExponentialSampler::new(lambda);
51        assert_eq!(sampler.err(), Some(ExpError::LambdaTooSmall));
52
53        let lambda = f64::NAN;
54        let sampler = ExponentialSampler::new(lambda);
55        assert_eq!(sampler.err(), Some(ExpError::LambdaTooSmall));
56    }
57
58    #[test]
59    fn test_sample_produces_positive_duration() {
60        let lambda = 1.0;
61        let mut sampler = ExponentialSampler::new(lambda).unwrap();
62        let mut rng = SmallRng::seed_from_u64(2);
63        let current_tick = SimTime::from_ticks(0);
64
65        for _ in 0..1000 {
66            let duration = sampler.sample(&mut rng, current_tick);
67            assert!(duration.raw_value() > 0.0);
68        }
69    }
70
71    #[test]
72    fn test_sample_with_different_lambda() {
73        let lambda_high = 10.0; // Mean = 1/lambda = 0.1
74        let mut sampler_high = ExponentialSampler::new(lambda_high).unwrap();
75        let mut rng_high = SmallRng::seed_from_u64(2);
76        let current_tick = SimTime::from_ticks(0);
77
78        let mut sum_high = 0.0;
79        for _ in 0..1000 {
80            sum_high += sampler_high.sample(&mut rng_high, current_tick).raw_value();
81        }
82        let avg_high = sum_high / 1000.0;
83        // For Exp(lambda), mean is 1/lambda. So for lambda=10, mean should be 0.1
84        assert!(avg_high > 0.05 && avg_high < 0.2); // Check if it's in a reasonable range
85
86        let lambda_low = 0.1; // Mean = 1/lambda = 10.0
87        let mut sampler_low = ExponentialSampler::new(lambda_low).unwrap();
88        let mut rng_low = SmallRng::seed_from_u64(2);
89
90        let mut sum_low = 0.0;
91        for _ in 0..1000 {
92            sum_low += sampler_low.sample(&mut rng_low, current_tick).raw_value();
93        }
94        let avg_low = sum_low / 1000.0;
95        // For Exp(lambda), mean is 1/lambda. So for lambda=0.1, mean should be 10.0
96        assert!(avg_low > 5.0 && avg_low < 15.0); // Check if it's in a reasonable range
97
98        // Ensure that higher lambda generally leads to smaller samples
99        assert!(avg_high < avg_low);
100    }
101}