Skip to main content

des_sim/modeling/sampler/instance/
normal.rs

1//! The `normal` module provides the `NormalSampler`, a `DurationSampler`
2//! that generates durations following a normal (Gaussian) distribution.
3//!
4//! This sampler is useful for modeling phenomena where values tend to cluster
5//! around a mean, with deviations described by a standard deviation.
6//! Users should be aware that normal distributions can produce negative values,
7//! which might require additional handling (e.g., clamping or re-sampling)
8//! depending on the simulation context.
9
10use crate::modeling::sampler::{DurationSampler, PendingDuration};
11use crate::primitive::time::SimTime;
12use rand::Rng;
13use rand::distr::Distribution;
14use rand_distr::{Normal, NormalError};
15
16/// A sampler that draws from a normal (Gaussian) distribution.
17///
18/// Note: This sampler can produce negative values depending on the provided
19/// mean and standard deviation. Ensure parameters are appropriate for the
20/// expected domain of the simulation.
21#[derive(Debug, Clone)]
22pub struct NormalSampler {
23    dist: Normal<f64>,
24}
25
26impl DurationSampler for NormalSampler {
27    fn sample(&mut self, rng: &mut dyn Rng, _current_tick: SimTime) -> PendingDuration {
28        PendingDuration::new(self.dist.sample(rng))
29    }
30}
31
32impl NormalSampler {
33    /// Creates a new `NormalSampler` with the given mean and standard deviation.
34    /// Returns an error if the standard deviation is non-positive or invalid.
35    pub fn new(mean: f64, std_dev: f64) -> Result<Self, NormalError> {
36        Normal::new(mean, std_dev).map(|dist| NormalSampler { dist })
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use rand::SeedableRng;
44    use rand::rngs::SmallRng;
45
46    #[test]
47    fn test_normal_sampler() {
48        let mut rng = SmallRng::seed_from_u64(2);
49        let mean = 10.0;
50        let std_dev = 2.0;
51        let mut sampler = NormalSampler::new(mean, std_dev).unwrap();
52
53        let mut samples = Vec::new();
54        for _ in 0..10000 {
55            samples.push(sampler.sample(&mut rng, SimTime::from_ticks(0)).raw_value());
56        }
57
58        let sample_mean: f64 = samples.iter().sum::<f64>() / samples.len() as f64;
59        let variance: f64 = samples
60            .iter()
61            .map(|x| (x - sample_mean).powi(2))
62            .sum::<f64>()
63            / samples.len() as f64;
64        let sample_std_dev = variance.sqrt();
65
66        // Check if the sampled mean and standard deviation are close to the expected values
67        assert!(
68            (sample_mean - mean).abs() < 0.1,
69            "Mean: {}, Expected: {}",
70            sample_mean,
71            mean
72        );
73        assert!(
74            (sample_std_dev - std_dev).abs() < 0.1,
75            "Std Dev: {}, Expected: {}",
76            sample_std_dev,
77            std_dev
78        );
79    }
80
81    #[test]
82    fn test_normal_sampler_infinite_std_dev() {
83        let sampler = NormalSampler::new(0.0, f64::INFINITY);
84        assert_eq!(sampler.err(), Some(NormalError::BadVariance));
85    }
86}