Skip to main content

des_sim/modeling/sampler/combinator/
ensure_non_negative.rs

1//! The `ensure_non_negative` module provides the `EnsureNonNegativeSampler`,
2//! a combinator that guarantees a non-negative sampled duration.
3//!
4//! This sampler attempts to draw samples from a base sampler. If a negative
5//! duration is sampled, it retries a specified number of times. If all retries
6//! yield negative results, it falls back to a user-defined default duration,
7//! preventing invalid time values in the simulation.
8
9use crate::modeling::sampler::{DurationSampler, PendingDuration};
10use crate::primitive::time::{Duration, SimTime};
11use rand::Rng;
12
13/// A sampler that ensures the sampled duration is non-negative.
14///
15/// It attempts to sample from the base `sampler` up to `limit_try_count` times.
16/// If all attempts return a negative value, it falls back to the result of the
17/// `fallback` closure.
18#[derive(Debug, Clone)]
19pub struct EnsureNonNegativeSampler<S, F>
20where
21    S: DurationSampler,
22    F: FnMut(&mut dyn Rng, SimTime) -> Duration,
23{
24    sampler: S,
25    limit_try_count: u8,
26    fallback: F,
27}
28
29impl<S, F> DurationSampler for EnsureNonNegativeSampler<S, F>
30where
31    S: DurationSampler,
32    F: FnMut(&mut dyn Rng, SimTime) -> Duration,
33{
34    fn sample(&mut self, rng: &mut dyn Rng, current_tick: SimTime) -> PendingDuration {
35        for _ in 0..self.limit_try_count {
36            let sampled = self.sampler.sample(rng, current_tick);
37            let raw_value = sampled.raw_value();
38
39            if raw_value >= 0.0 {
40                return PendingDuration::new(raw_value);
41            }
42        }
43
44        PendingDuration::from_duration((self.fallback)(rng, current_tick))
45    }
46}
47
48impl<S, F> EnsureNonNegativeSampler<S, F>
49where
50    S: DurationSampler,
51    F: FnMut(&mut dyn Rng, SimTime) -> Duration,
52{
53    /// Creates a new `EnsureNonNegativeSampler`.
54    pub fn new(sampler: S, limit_try_count: u8, fallback: F) -> Self {
55        Self {
56            sampler,
57            limit_try_count,
58            fallback,
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::modeling::sampler::instance::ConstantSampler;
67    use crate::primitive::time::Duration;
68    use rand::SeedableRng;
69    use rand::rngs::SmallRng;
70
71    #[test]
72    fn test_ensure_non_negative_sampler_positive_value() {
73        let mut rng = SmallRng::seed_from_u64(2);
74        let base_sampler = ConstantSampler::new(10.0);
75        let mut sampler = EnsureNonNegativeSampler::new(base_sampler, 3, |_, _| Duration::ticks(0));
76
77        let sample = sampler.sample(&mut rng, SimTime::from_ticks(0));
78        assert_eq!(sample.raw_value(), 10.0);
79    }
80
81    #[test]
82    fn test_ensure_non_negative_sampler_negative_then_fallback() {
83        let mut rng = SmallRng::seed_from_u64(2);
84        // This mock sampler will always return -1.0
85        struct NegativeSampler;
86        impl DurationSampler for NegativeSampler {
87            fn sample(&mut self, _rng: &mut dyn Rng, _current_tick: SimTime) -> PendingDuration {
88                PendingDuration::new(-1.0)
89            }
90        }
91
92        let base_sampler = NegativeSampler;
93        let mut sampler = EnsureNonNegativeSampler::new(base_sampler, 1, |_, _| Duration::ticks(5));
94
95        let sample = sampler.sample(&mut rng, SimTime::from_ticks(0));
96        assert_eq!(sample.raw_value(), 5.0); // Should fall back to 5.0
97    }
98
99    #[test]
100    fn test_ensure_non_negative_sampler_multiple_tries_then_positive() {
101        let mut rng = SmallRng::seed_from_u64(2);
102        struct MixedSampler {
103            call_count: usize,
104        }
105        impl DurationSampler for MixedSampler {
106            fn sample(&mut self, _rng: &mut dyn Rng, _current_tick: SimTime) -> PendingDuration {
107                self.call_count += 1;
108                if self.call_count < 3 {
109                    PendingDuration::new(-1.0)
110                } else {
111                    PendingDuration::new(10.0)
112                }
113            }
114        }
115
116        let base_sampler = MixedSampler { call_count: 0 };
117        let mut sampler = EnsureNonNegativeSampler::new(base_sampler, 5, |_, _| Duration::ticks(0));
118
119        let sample = sampler.sample(&mut rng, SimTime::from_ticks(0));
120        assert_eq!(sample.raw_value(), 10.0);
121        assert_eq!(sampler.sampler.call_count, 3); // Should have tried 3 times
122    }
123}