des_sim/modeling/sampler/instance/
empirical.rs1use crate::modeling::sampler::{DurationSampler, PendingDuration};
9use crate::primitive::time::{Duration, SimTime};
10use rand::{Rng, RngExt};
11
12#[derive(Debug, Clone)]
17pub struct EmpiricalSampler {
18 cdf: Vec<u64>, values: Vec<Duration>, total_weight: u64, }
22
23impl DurationSampler for EmpiricalSampler {
24 fn sample(&mut self, rng: &mut dyn Rng, _current_tick: SimTime) -> PendingDuration {
25 let r = rng.random_range(0..self.total_weight);
27
28 let idx = self.cdf.partition_point(|&x| x <= r) - 1;
32
33 self.values[idx].into()
34 }
35}
36
37impl EmpiricalSampler {
38 pub fn new(histogram: impl IntoIterator<Item = (Duration, u64)>) -> Self {
40 let mut cdf = Vec::new();
43 let mut values = Vec::new();
44 let mut current_sum = 0;
45
46 cdf.push(0);
48 for (duration, weight) in histogram.into_iter() {
49 current_sum += weight;
50 cdf.push(current_sum);
51 values.push(duration);
52 }
53
54 assert!(
55 !values.is_empty(),
56 "EmpiricalSampler must have at least one sampler"
57 );
58 assert!(current_sum > 0, "Total weight must be greater than 0");
59
60 Self {
61 cdf,
62 values,
63 total_weight: current_sum,
64 }
65 }
66
67 pub fn new_as_uniform(histogram: impl IntoIterator<Item = Duration>) -> Self {
69 EmpiricalSampler::new(histogram.into_iter().map(|d| (d, 1)))
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::modeling::sampler::instance::ExponentialSampler;
77 use crate::primitive::time::Duration;
78 use rand::SeedableRng;
79 use rand::rngs::SmallRng;
80 use std::collections::HashMap;
81
82 #[test]
83 fn test_empirical_sampler_weighted() {
84 let mut rng = SmallRng::seed_from_u64(2);
85 let d1 = Duration::ticks(10);
86 let d2 = Duration::ticks(20);
87 let d3 = Duration::ticks(30);
88
89 let mut sampler = EmpiricalSampler::new(vec![(d1, 1), (d2, 2), (d3, 1)]);
91
92 let mut results: HashMap<String, usize> = HashMap::new();
93 for _ in 0..1000 {
94 let sample = sampler.sample(&mut rng, SimTime::from_ticks(0));
95 let entry = results
96 .entry(format!("{:<.2}", sample.raw_value()))
97 .or_insert(0);
98 *entry += 1;
99 }
100
101 let count_10 = *results.get(&format!("{:<.2}", 10.0)).unwrap_or(&0);
104 let count_20 = *results.get(&format!("{:<.2}", 20.0)).unwrap_or(&0);
105 let count_30 = *results.get(&format!("{:<.2}", 30.0)).unwrap_or(&0);
106
107 assert!(count_10 > 200 && count_10 < 300);
109 assert!(count_20 > 450 && count_20 < 550);
110 assert!(count_30 > 200 && count_30 < 300);
111 }
112
113 #[test]
114 fn test_empirical_sampler_uniform() {
115 let mut rng = SmallRng::seed_from_u64(2);
116 let d1 = Duration::ticks(10);
117 let d2 = Duration::ticks(20);
118 let d3 = Duration::ticks(30);
119
120 let mut sampler = EmpiricalSampler::new_as_uniform(vec![d1, d2, d3]);
121
122 let mut results: HashMap<String, usize> = HashMap::new();
123 for _ in 0..1000 {
124 let sample = sampler.sample(&mut rng, SimTime::from_ticks(0));
125 let entry = results
126 .entry(format!("{:<.2}", sample.raw_value()))
127 .or_insert(0);
128 *entry += 1;
129 }
130
131 let count_10 = *results.get(&format!("{:<.2}", 10.0)).unwrap_or(&0);
133 let count_20 = *results.get(&format!("{:<.2}", 20.0)).unwrap_or(&0);
134 let count_30 = *results.get(&format!("{:<.2}", 30.0)).unwrap_or(&0);
135
136 assert!(count_10 > 280 && count_10 < 380);
137 assert!(count_20 > 280 && count_20 < 380);
138 assert!(count_30 > 280 && count_30 < 380);
139 }
140
141 #[test]
142 #[should_panic(expected = "EmpiricalSampler must have at least one sampler")]
143 fn test_empirical_sampler_empty_histogram() {
144 let _sampler = EmpiricalSampler::new(vec![]);
145 }
146
147 #[test]
148 #[should_panic(expected = "Total weight must be greater than 0")]
149 fn test_empirical_sampler_zero_total_weight() {
150 let d1 = Duration::ticks(10);
151 let _sampler = EmpiricalSampler::new(vec![(d1, 0)]);
152 }
153
154 #[test]
155 fn test_exponential_sampler() {
156 let mut rng = SmallRng::seed_from_u64(2);
157 let lambda = 0.5; let mut sampler = ExponentialSampler::new(lambda).unwrap();
159
160 let mut samples = Vec::new();
161 for _ in 0..10000 {
162 samples.push(sampler.sample(&mut rng, SimTime::from_ticks(0)).raw_value());
163 }
164
165 let mean: f64 = samples.iter().sum::<f64>() / samples.len() as f64;
166 let expected_mean = 1.0 / lambda;
167
168 assert!(
170 (mean - expected_mean).abs() < 0.1,
171 "Mean: {}, Expected: {}",
172 mean,
173 expected_mean
174 );
175
176 assert!(samples.iter().all(|&x| x >= 0.0));
178 }
179}