1use rand::distributions::Distribution;
8use serde::{Deserialize, Serialize};
9use rand_distr::{Beta, Exp, Gamma, LogNormal, Normal, Triangular, Uniform, Weibull};
11use rand_distr::{Bernoulli, Geometric, Poisson, WeightedIndex};
13
14use super::dynamic_rng::DynRng;
15use crate::utils::errors::SimulationError;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub enum Continuous {
20 Beta { alpha: f64, beta: f64 },
21 Exp { lambda: f64 },
22 Gamma { shape: f64, scale: f64 },
23 LogNormal { mu: f64, sigma: f64 },
24 Normal { mean: f64, std_dev: f64 },
25 Triangular { min: f64, max: f64, mode: f64 },
26 Uniform { min: f64, max: f64 },
27 Weibull { shape: f64, scale: f64 },
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub enum Boolean {
33 Bernoulli { p: f64 },
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub enum Discrete {
39 Geometric {
40 p: f64,
41 },
42 Poisson {
43 lambda: f64,
44 },
45 Uniform {
47 min: u64,
48 max: u64,
49 },
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(rename_all = "camelCase")]
54pub enum Index {
55 Uniform {
57 min: usize,
58 max: usize,
59 },
60 WeightedIndex {
61 weights: Vec<u64>,
62 },
63}
64
65impl Continuous {
66 pub fn random_variate(&mut self, uniform_rng: DynRng) -> Result<f64, SimulationError> {
70 let mut rng = (*uniform_rng).borrow_mut();
71 match self {
72 Continuous::Beta { alpha, beta } => Ok(Beta::new(*alpha, *beta)?.sample(&mut *rng)),
73 Continuous::Exp { lambda } => Ok(Exp::new(*lambda)?.sample(&mut *rng)),
74 Continuous::Gamma { shape, scale } => Ok(Gamma::new(*shape, *scale)?.sample(&mut *rng)),
75 Continuous::LogNormal { mu, sigma } => {
76 Ok(LogNormal::new(*mu, *sigma)?.sample(&mut *rng))
77 }
78 Continuous::Normal { mean, std_dev } => {
79 Ok(Normal::new(*mean, *std_dev)?.sample(&mut *rng))
80 }
81 Continuous::Triangular { min, max, mode } => {
82 Ok(Triangular::new(*min, *max, *mode)?.sample(&mut *rng))
83 }
84 Continuous::Uniform { min, max } => Ok(Uniform::new(*min, *max).sample(&mut *rng)),
85 Continuous::Weibull { shape, scale } => {
86 Ok(Weibull::new(*shape, *scale)?.sample(&mut *rng))
87 }
88 }
89 }
90}
91
92impl Boolean {
93 pub fn random_variate(&mut self, uniform_rng: DynRng) -> Result<bool, SimulationError> {
97 let mut rng = (*uniform_rng).borrow_mut();
98 match self {
99 Boolean::Bernoulli { p } => Ok(Bernoulli::new(*p)?.sample(&mut *rng)),
100 }
101 }
102}
103
104impl Discrete {
105 pub fn random_variate(&mut self, uniform_rng: DynRng) -> Result<u64, SimulationError> {
109 let mut rng = (*uniform_rng).borrow_mut();
110 match self {
111 Discrete::Geometric { p } => Ok(Geometric::new(*p)?.sample(&mut *rng)),
112 Discrete::Poisson { lambda } => Ok(Poisson::new(*lambda)?.sample(&mut *rng) as u64),
113 Discrete::Uniform { min, max } => Ok(Uniform::new(*min, *max).sample(&mut *rng)),
114 }
115 }
116}
117
118impl Index {
119 pub fn random_variate(&mut self, uniform_rng: DynRng) -> Result<usize, SimulationError> {
123 let mut rng = (*uniform_rng).borrow_mut();
124 match self {
125 Index::Uniform { min, max } => Ok(Uniform::new(*min, *max).sample(&mut *rng)),
126 Index::WeightedIndex { weights } => {
127 Ok(WeightedIndex::new(weights.clone())?.sample(&mut *rng))
128 }
129 }
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use crate::input_modeling::dynamic_rng::default_rng;
136
137 use super::*;
138
139 enum RandomVariable {
140 Continuous(Continuous),
141 Discrete(Discrete),
142 }
143
144 enum ChiSquareTest {
145 Continuous {
146 variable: Continuous,
147 bin_mapping_fn: fn(f64) -> usize,
148 },
149 Boolean {
150 variable: Boolean,
151 bin_mapping_fn: fn(bool) -> usize,
152 },
153 Discrete {
154 variable: Discrete,
155 bin_mapping_fn: fn(u64) -> usize,
156 },
157 Index {
158 variable: Index,
159 bin_mapping_fn: fn(usize) -> usize,
160 },
161 }
162
163 fn empirical_mean(random_variable: &mut RandomVariable, sample_size: usize) -> f64 {
164 let uniform_rng = default_rng();
165 (0..sample_size)
166 .map(|_| match random_variable {
167 RandomVariable::Continuous(variable) => {
168 variable.random_variate(uniform_rng.clone()).unwrap()
169 }
170 RandomVariable::Discrete(variable) => {
171 variable.random_variate(uniform_rng.clone()).unwrap() as f64
172 }
173 })
174 .sum::<f64>()
175 / (sample_size as f64)
176 }
177
178 fn chi_square(test: &mut ChiSquareTest, expected_counts: &[usize]) -> f64 {
179 let mut class_counts = vec![0; expected_counts.len()];
180 let uniform_rng = default_rng();
181 let sample_size = expected_counts.iter().sum();
182 (0..sample_size).for_each(|_| {
183 let index = match test {
184 ChiSquareTest::Continuous {
185 variable,
186 bin_mapping_fn,
187 } => bin_mapping_fn(variable.random_variate(uniform_rng.clone()).unwrap()),
188 ChiSquareTest::Boolean {
189 variable,
190 bin_mapping_fn,
191 } => bin_mapping_fn(variable.random_variate(uniform_rng.clone()).unwrap()),
192 ChiSquareTest::Discrete {
193 variable,
194 bin_mapping_fn,
195 } => bin_mapping_fn(variable.random_variate(uniform_rng.clone()).unwrap()),
196 ChiSquareTest::Index {
197 variable,
198 bin_mapping_fn,
199 } => bin_mapping_fn(variable.random_variate(uniform_rng.clone()).unwrap()),
200 };
201 class_counts[index] += 1
202 });
203 class_counts.iter().zip(expected_counts.iter()).fold(
204 0.0,
205 |acc, (class_count, expected_count)| {
206 let f_class_count = *class_count as f64;
207 let f_expected_count = *expected_count as f64;
208 acc + (f_class_count - f_expected_count).powi(2) / f_expected_count
209 },
210 )
211 }
212
213 #[test]
214 fn beta_samples_match_expectation() {
215 let variable = Continuous::Beta {
216 alpha: 7.0,
217 beta: 11.0,
218 };
219 let mean = empirical_mean(&mut RandomVariable::Continuous(variable), 10000);
220 let expected = 7.0 / (7.0 + 11.0);
221 assert!((mean - expected).abs() / expected < 0.025);
222 }
223
224 #[test]
225 fn exponential_samples_match_expectation() {
226 let variable = Continuous::Exp { lambda: 7.0 };
227 let mean = empirical_mean(&mut RandomVariable::Continuous(variable), 10000);
228 let expected = 1.0 / 7.0;
229 assert!((mean - expected).abs() / expected < 0.025);
230 }
231
232 #[test]
233 fn gamma_samples_match_expectation() {
234 let variable = Continuous::Gamma {
235 shape: 7.0,
236 scale: 11.0,
237 };
238 let mean = empirical_mean(&mut RandomVariable::Continuous(variable), 10000);
239 let expected = 77.0;
240 assert!((mean - expected).abs() / expected < 0.025);
241 }
242
243 #[test]
244 fn lognormal_samples_match_expectation() {
245 let variable = Continuous::LogNormal {
246 mu: 11.0,
247 sigma: 1.0,
248 };
249 let mean = empirical_mean(&mut RandomVariable::Continuous(variable), 10000);
250 let expected = (11.0f64 + 1.0f64.powi(2) / 2.0f64).exp();
251 assert!((mean - expected).abs() / expected < 0.025);
252 }
253
254 #[test]
255 fn normal_samples_chi_square() {
256 fn bins_mapping(variate: f64) -> usize {
257 let mean = 11.0;
258 let std_dev = 3.0;
259 if variate < mean - 3.0 * std_dev {
260 0
261 } else if variate < mean - 2.0 * std_dev {
262 1
263 } else if variate < mean - std_dev {
264 2
265 } else if variate < mean {
266 3
267 } else if variate < mean + std_dev {
268 4
269 } else if variate < mean + 2.0 * std_dev {
270 5
271 } else if variate < mean + 3.0 * std_dev {
272 6
273 } else {
274 7
275 }
276 }
277 let variable = Continuous::Normal {
278 mean: 11.0,
279 std_dev: 3.0,
280 };
281 let expected_counts: [usize; 8] = [20, 210, 1360, 3410, 3410, 1360, 210, 20];
284 let chi_square_actual = chi_square(
285 &mut ChiSquareTest::Continuous {
286 variable: variable,
287 bin_mapping_fn: bins_mapping,
288 },
289 &expected_counts,
290 );
291 let chi_square_critical = 18.475;
294 assert![chi_square_actual < chi_square_critical];
295 }
296
297 #[test]
298 fn triangular_samples_chi_square() {
299 fn bins_mapping(variate: f64) -> usize {
300 ((variate - 5.0) / 5.0) as usize
301 }
302 let variable = Continuous::Triangular {
303 min: 5.0,
304 max: 25.0,
305 mode: 15.0,
306 };
307 let expected_counts: [usize; 4] = [125, 375, 375, 125];
309 let chi_square_actual = chi_square(
310 &mut ChiSquareTest::Continuous {
311 variable: variable,
312 bin_mapping_fn: bins_mapping,
313 },
314 &expected_counts,
315 );
316 let chi_square_critical = 11.345;
319 assert![chi_square_actual < chi_square_critical];
320 }
321
322 #[test]
323 fn continuous_uniform_samples_chi_square() {
324 fn bins_mapping(variate: f64) -> usize {
325 let min = 7.0;
326 let max = 11.0;
327 ((variate - min) * (max - 1.0)) as usize
328 }
329 let variable = Continuous::Uniform {
330 min: 7.0,
331 max: 11.0,
332 };
333 let expected_counts: [usize; 40] = [250; 40];
335 let chi_square_actual = chi_square(
336 &mut ChiSquareTest::Continuous {
337 variable: variable,
338 bin_mapping_fn: bins_mapping,
339 },
340 &expected_counts,
341 );
342 let chi_square_critical = 62.428;
345 assert![chi_square_actual < chi_square_critical];
346 }
347
348 #[test]
349 fn weibull_samples_match_expectation() {
350 let variable = Continuous::Weibull {
351 shape: 7.0,
352 scale: 0.5,
353 };
354 let mean = empirical_mean(&mut RandomVariable::Continuous(variable), 10000);
355 let expected = 14.0;
356 assert!((mean - expected).abs() / expected < 0.025);
357 }
358
359 #[test]
360 fn bernoulli_samples_chi_square() {
361 fn bins_mapping(variate: bool) -> usize {
362 variate as usize
363 }
364 let variable = Boolean::Bernoulli { p: 0.3 };
365 let expected_counts: [usize; 2] = [7000, 3000];
367 let chi_square_actual = chi_square(
368 &mut ChiSquareTest::Boolean {
369 variable: variable,
370 bin_mapping_fn: bins_mapping,
371 },
372 &expected_counts,
373 );
374 let chi_square_critical = 6.635;
377 assert![chi_square_actual < chi_square_critical];
378 }
379
380 #[test]
381 fn geometric_samples_match_expectation() {
382 let variable = Discrete::Geometric { p: 0.2 };
383 let mean = empirical_mean(&mut RandomVariable::Discrete(variable), 10000);
384 let expected = (1.0 - 0.2) / 0.2;
385 assert!((mean - expected).abs() / expected < 0.025);
386 }
387
388 #[test]
389 fn poisson_samples_match_expectation() {
390 let variable = Discrete::Poisson { lambda: 7.0 };
391 let mean = empirical_mean(&mut RandomVariable::Discrete(variable), 10000);
392 let expected = 7.0;
393 assert!((mean - expected).abs() / expected < 0.025);
394 }
395
396 #[test]
397 fn discrete_uniform_samples_chi_square() {
398 fn bins_mapping(variate: u64) -> usize {
399 let min = 7;
400 (variate - min) as usize
401 }
402 let variable = Discrete::Uniform { min: 7, max: 11 };
403 let expected_counts: [usize; 4] = [2500; 4];
405 let chi_square_actual = chi_square(
406 &mut ChiSquareTest::Discrete {
407 variable: variable,
408 bin_mapping_fn: bins_mapping,
409 },
410 &expected_counts,
411 );
412 let chi_square_critical = 13.277;
415 assert![chi_square_actual < chi_square_critical];
416 }
417
418 #[test]
419 fn weighted_index_samples_chi_square() {
420 fn bins_mapping(variate: usize) -> usize {
421 variate
422 }
423 let variable = Index::WeightedIndex {
424 weights: vec![1, 2, 3, 4],
425 };
426 let expected_counts: [usize; 4] = [1000, 2000, 3000, 4000];
428 let chi_square_actual = chi_square(
429 &mut ChiSquareTest::Index {
430 variable: variable,
431 bin_mapping_fn: bins_mapping,
432 },
433 &expected_counts,
434 );
435 let chi_square_critical = 11.345;
438 assert![chi_square_actual < chi_square_critical];
439 }
440
441 #[test]
442 fn index_uniform_samples_chi_square() {
443 fn bins_mapping(variate: usize) -> usize {
444 let min = 7;
445 variate - min
446 }
447 let variable = Index::Uniform { min: 7, max: 11 };
448 let expected_counts: [usize; 4] = [2500; 4];
450 let chi_square_actual = chi_square(
451 &mut ChiSquareTest::Index {
452 variable: variable,
453 bin_mapping_fn: bins_mapping,
454 },
455 &expected_counts,
456 );
457 let chi_square_critical = 13.277;
460 assert![chi_square_actual < chi_square_critical];
461 }
462}