uncertain_rs/
distributions.rs1#![allow(clippy::cast_precision_loss)]
2
3use crate::Uncertain;
4use crate::traits::Shareable;
5use rand::prelude::*;
6use rand::random;
7use rand::rng;
8use std::collections::HashMap;
9use std::f64::consts::PI;
10
11impl<T> Uncertain<T>
12where
13 T: Shareable,
14{
15 #[must_use]
25 pub fn point(value: T) -> Self {
26 Uncertain::new(move || value.clone())
27 }
28
29 pub fn mixture(
55 components: Vec<Uncertain<T>>,
56 weights: Option<Vec<f64>>,
57 ) -> Result<Self, &'static str> {
58 if components.is_empty() {
59 return Err("At least one component required");
60 }
61
62 if components.len() == 1 {
63 return Ok(components.into_iter().next().unwrap());
64 }
65
66 let weights = match weights {
67 Some(w) => {
68 if w.len() != components.len() {
69 return Err("Weights count must match components count");
70 }
71 w
72 }
73 None => vec![1.0; components.len()],
74 };
75
76 let total: f64 = weights.iter().sum();
77 let normalized: Vec<f64> = weights.iter().map(|&w| w / total).collect();
78 let cumulative: Vec<f64> = normalized
79 .iter()
80 .scan(0.0, |acc, &x| {
81 *acc += x;
82 Some(*acc)
83 })
84 .collect();
85
86 Ok(Uncertain::new(move || {
87 let r: f64 = random();
88 let idx = cumulative
89 .iter()
90 .position(|&x| r <= x)
91 .unwrap_or_else(|| components.len().saturating_sub(1));
92 components.get(idx).map_or_else(
93 || components[0].sample(),
94 super::uncertain::Uncertain::sample,
95 )
96 }))
97 }
98
99 pub fn empirical(data: Vec<T>) -> Result<Self, &'static str> {
119 if data.is_empty() {
120 return Err("Data cannot be empty");
121 }
122
123 Ok(Uncertain::new(move || {
124 data.choose(&mut rng())
125 .expect("Data vector should not be empty")
126 .clone()
127 }))
128 }
129}
130
131impl<T> Uncertain<T>
133where
134 T: Clone + Send + Sync + std::hash::Hash + Eq + 'static,
135{
136 pub fn categorical(probabilities: &HashMap<T, f64>) -> Result<Self, &'static str> {
161 if probabilities.is_empty() {
162 return Err("Probabilities cannot be empty");
163 }
164
165 let total: f64 = probabilities.values().sum();
166 let mut cumulative = Vec::new();
167 let mut sum = 0.0;
168
169 for (value, &prob) in probabilities {
170 sum += prob / total;
171 cumulative.push((value.clone(), sum));
172 }
173
174 Ok(Uncertain::new(move || {
175 let r: f64 = random();
176 cumulative.iter().find(|(_, cum)| r <= *cum).map_or_else(
177 || {
178 cumulative
179 .last()
180 .expect("Cumulative vector should not be empty")
181 .0
182 .clone()
183 },
184 |(val, _)| val.clone(),
185 )
186 }))
187 }
188}
189
190impl Uncertain<f64> {
192 #[must_use]
206 pub fn normal(mean: f64, std_dev: f64) -> Self {
207 Uncertain::new(move || {
208 let u1: f64 = random::<f64>().clamp(0.001, 0.999);
210 let u2: f64 = random::<f64>().clamp(0.001, 0.999);
211 let z0 = (-2.0 * u1.ln()).sqrt() * (2.0 * PI * u2).cos();
212 mean + std_dev * z0
213 })
214 }
215
216 #[must_use]
225 pub fn uniform(min: f64, max: f64) -> Self {
226 Uncertain::new(move || min + (max - min) * random::<f64>())
227 }
228
229 #[must_use]
241 pub fn exponential(rate: f64) -> Self {
242 Uncertain::new(move || -random::<f64>().ln() / rate)
243 }
244
245 #[must_use]
258 pub fn log_normal(mu: f64, sigma: f64) -> Self {
259 let normal = Self::normal(mu, sigma);
260 normal.map(f64::exp)
261 }
262
263 #[must_use]
276 pub fn beta(alpha: f64, beta: f64) -> Self {
277 Uncertain::new(move || {
278 loop {
280 let u1: f64 = random();
281 let u2: f64 = random();
282
283 let x = u1.powf(1.0 / alpha);
284 let y = u2.powf(1.0 / beta);
285
286 if x + y <= 1.0 {
287 return x / (x + y);
288 }
289 }
290 })
291 }
292
293 #[must_use]
306 pub fn gamma(shape: f64, scale: f64) -> Self {
307 Uncertain::new(move || {
308 if shape >= 1.0 {
310 let d = shape - 1.0 / 3.0;
311 let c = 1.0 / (9.0 * d).sqrt();
312
313 loop {
314 let normal_sample = Self::normal(0.0, 1.0).sample();
315 let v = (1.0 + c * normal_sample).powi(3);
316
317 if v > 0.0 {
318 let u: f64 = random();
319 if u < 1.0 - 0.0331 * normal_sample.powi(4) {
320 return d * v * scale;
321 }
322 if u.ln() < 0.5 * normal_sample.powi(2) + d * (1.0 - v + v.ln()) {
323 return d * v * scale;
324 }
325 }
326 }
327 } else {
328 let gamma_1_plus_shape = Self::gamma(shape + 1.0, scale).sample();
330 let u: f64 = random();
331 gamma_1_plus_shape * u.powf(1.0 / shape)
332 }
333 })
334 }
335}
336
337impl Uncertain<bool> {
339 #[must_use]
351 pub fn bernoulli(probability: f64) -> Self {
352 Uncertain::new(move || random::<f64>() < probability)
353 }
354}
355
356impl<T> Uncertain<T>
358where
359 T: Clone
360 + Send
361 + Sync
362 + From<u32>
363 + std::ops::AddAssign
364 + std::ops::Sub<Output = T>
365 + Default
366 + 'static,
367{
368 #[must_use]
381 pub fn binomial(trials: u32, probability: f64) -> Self {
382 Uncertain::new(move || {
383 let mut count = T::default();
384 for _ in 0..trials {
385 if random::<f64>() < probability {
386 count += T::from(1);
387 }
388 }
389 count
390 })
391 }
392
393 #[must_use]
405 pub fn poisson(lambda: f64) -> Self {
406 Uncertain::new(move || {
407 let l = (-lambda).exp();
408 let mut k = T::from(0);
409 let mut p = 1.0;
410
411 loop {
412 k += T::from(1);
413 p *= random::<f64>();
414 if p <= l {
415 break;
416 }
417 }
418
419 k - T::from(1)
421 })
422 }
423
424 #[must_use]
436 pub fn geometric(probability: f64) -> Self {
437 Uncertain::new(move || {
438 let mut trials = T::from(1);
439 while random::<f64>() >= probability {
440 trials += T::from(1);
441 }
442 trials
443 })
444 }
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450
451 #[test]
452 fn test_normal_distribution() {
453 let normal = Uncertain::normal(0.0, 1.0);
454 let samples: Vec<f64> = normal.take_samples(1000);
455 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
456 assert!((mean - 0.0).abs() < 0.1);
457 }
458
459 #[test]
460 fn test_uniform_distribution() {
461 let uniform = Uncertain::uniform(0.0, 10.0);
462 let samples: Vec<f64> = uniform.take_samples(1000);
463 assert!(samples.iter().all(|&x| (0.0..=10.0).contains(&x)));
464 }
465
466 #[test]
467 fn test_bernoulli_distribution() {
468 let bernoulli = Uncertain::bernoulli(0.7);
469 let samples: Vec<bool> = bernoulli.take_samples(1000);
470 let true_ratio = samples.iter().filter(|&&x| x).count() as f64 / samples.len() as f64;
471 assert!((true_ratio - 0.7).abs() < 0.1);
472 }
473
474 #[test]
475 #[allow(clippy::float_cmp)]
476 fn test_point_mass() {
477 let point = Uncertain::point(42.0);
478 for _ in 0..10 {
479 assert_eq!(point.sample(), 42.0);
480 }
481 }
482
483 #[test]
484 fn test_categorical() {
485 let mut probs = HashMap::new();
486 probs.insert("a", 0.5);
487 probs.insert("b", 0.3);
488 probs.insert("c", 0.2);
489
490 let categorical = Uncertain::categorical(&probs).unwrap();
491 let samples: Vec<&str> = categorical.take_samples(1000);
492
493 assert!(samples.iter().all(|&x| ["a", "b", "c"].contains(&x)));
494 }
495
496 #[test]
497 fn test_exponential_distribution() {
498 let exponential = Uncertain::exponential(2.0);
499 let samples: Vec<f64> = exponential.take_samples(1000);
500
501 assert!(samples.iter().all(|&x| x >= 0.0));
502
503 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
504 assert!((mean - 0.5).abs() < 0.1);
505 }
506
507 #[test]
508 fn test_log_normal_distribution() {
509 let log_normal = Uncertain::log_normal(0.0, 1.0);
510 let samples: Vec<f64> = log_normal.take_samples(1000);
511
512 assert!(samples.iter().all(|&x| x > 0.0));
513 }
514
515 #[test]
516 fn test_beta_distribution() {
517 let beta = Uncertain::beta(2.0, 5.0);
518 let samples: Vec<f64> = beta.take_samples(1000);
519
520 assert!(samples.iter().all(|&x| (0.0..=1.0).contains(&x)));
521 }
522
523 #[test]
524 fn test_gamma_distribution() {
525 let gamma = Uncertain::gamma(2.0, 1.0);
526 let samples: Vec<f64> = gamma.take_samples(1000);
527
528 assert!(samples.iter().all(|&x| x >= 0.0));
529 }
530
531 #[test]
532 fn test_binomial_distribution() {
533 let binomial: Uncertain<u32> = Uncertain::binomial(10, 0.5);
534 let samples: Vec<u32> = binomial.take_samples(1000);
535
536 assert!(samples.iter().all(|&x| x <= 10));
537
538 let mean = f64::from(samples.iter().sum::<u32>()) / samples.len() as f64;
539 assert!((mean - 5.0).abs() < 1.0);
540 }
541
542 #[test]
543 fn test_poisson_distribution() {
544 let poisson: Uncertain<u32> = Uncertain::poisson(3.0);
545 let samples: Vec<u32> = poisson.take_samples(1000);
546
547 let mean = f64::from(samples.iter().sum::<u32>()) / samples.len() as f64;
548 assert!((mean - 3.0).abs() < 1.0);
549 }
550
551 #[test]
552 fn test_geometric_distribution() {
553 let geometric: Uncertain<u32> = Uncertain::geometric(0.2);
554 let samples: Vec<u32> = geometric.take_samples(1000);
555
556 assert!(samples.iter().all(|&x| x >= 1));
557
558 let mean = f64::from(samples.iter().sum::<u32>()) / samples.len() as f64;
559 assert!((mean - 5.0).abs() < 2.0);
560 }
561
562 #[test]
563 fn test_mixture_distribution() {
564 let normal1 = Uncertain::normal(0.0, 1.0);
565 let normal2 = Uncertain::normal(10.0, 1.0);
566 let mixture = Uncertain::mixture(vec![normal1, normal2], Some(vec![0.5, 0.5])).unwrap();
567
568 let samples: Vec<f64> = mixture.take_samples(1000);
569
570 let low_count = samples.iter().filter(|&&x| x < 5.0).count();
571 let high_count = samples.iter().filter(|&&x| x > 5.0).count();
572
573 assert!(low_count > 100);
574 assert!(high_count > 100);
575 }
576
577 #[test]
578 fn test_mixture_single_component() {
579 let normal = Uncertain::normal(0.0, 1.0);
580 let mixture = Uncertain::mixture(vec![normal], None).unwrap();
581 let samples: Vec<f64> = mixture.take_samples(100);
582 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
583 assert!((mean - 0.0).abs() < 0.5);
584 }
585
586 #[test]
587 fn test_mixture_uniform_weights() {
588 let normal1 = Uncertain::normal(0.0, 1.0);
589 let normal2 = Uncertain::normal(5.0, 1.0);
590 let mixture = Uncertain::mixture(vec![normal1, normal2], None).unwrap();
591 let _samples: Vec<f64> = mixture.take_samples(100);
592 }
593
594 #[test]
595 fn test_empirical_distribution() {
596 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
597 let empirical = Uncertain::empirical(data.clone()).unwrap();
598 let samples: Vec<f64> = empirical.take_samples(1000);
599
600 assert!(samples.iter().all(|&x| data.contains(&x)));
601 }
602
603 #[test]
604 fn test_mixture_empty_components() {
605 let result: Result<Uncertain<f64>, &str> = Uncertain::mixture(vec![], None);
606 assert!(result.is_err());
607 assert_eq!(result.unwrap_err(), "At least one component required");
608 }
609
610 #[test]
611 fn test_mixture_mismatched_weights() {
612 let normal1 = Uncertain::normal(0.0, 1.0);
613 let normal2 = Uncertain::normal(1.0, 1.0);
614 let result = Uncertain::mixture(vec![normal1, normal2], Some(vec![0.5]));
615 assert!(result.is_err());
616 assert_eq!(
617 result.unwrap_err(),
618 "Weights count must match components count"
619 );
620 }
621
622 #[test]
623 fn test_empirical_empty_data() {
624 let result: Result<Uncertain<f64>, &str> = Uncertain::empirical(vec![]);
625 assert!(result.is_err());
626 assert_eq!(result.unwrap_err(), "Data cannot be empty");
627 }
628
629 #[test]
630 fn test_categorical_empty_probabilities() {
631 let probs: HashMap<&str, f64> = HashMap::new();
632 let result = Uncertain::categorical(&probs);
633 assert!(result.is_err());
634 assert_eq!(result.unwrap_err(), "Probabilities cannot be empty");
635 }
636
637 #[test]
638 fn test_categorical_with_unnormalized_probabilities() {
639 let mut probs = HashMap::new();
640 probs.insert("a", 2.0);
641 probs.insert("b", 3.0);
642 probs.insert("c", 5.0);
643
644 let categorical = Uncertain::categorical(&probs).unwrap();
645 let samples: Vec<&str> = categorical.take_samples(1000);
646
647 assert!(samples.iter().all(|&x| ["a", "b", "c"].contains(&x)));
648 }
649
650 #[test]
651 fn test_normal_distribution_edge_cases() {
652 let normal_zero_std = Uncertain::normal(5.0, 0.0);
653 let samples: Vec<f64> = normal_zero_std.take_samples(10);
654 assert!(samples.iter().all(|&x| (x - 5.0).abs() < 0.01));
655
656 let normal_negative = Uncertain::normal(-10.0, 2.0);
657 let samples: Vec<f64> = normal_negative.take_samples(1000);
658 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
659 assert!((mean - (-10.0)).abs() < 0.5);
660 }
661
662 #[test]
663 fn test_uniform_edge_cases() {
664 let uniform_point = Uncertain::uniform(5.0, 5.0);
665 let samples: Vec<f64> = uniform_point.take_samples(10);
666 assert!(samples.iter().all(|&x| (x - 5.0).abs() < f64::EPSILON));
667
668 let uniform_negative = Uncertain::uniform(-10.0, -5.0);
669 let samples: Vec<f64> = uniform_negative.take_samples(100);
670 assert!(samples.iter().all(|&x| (-10.0..=-5.0).contains(&x)));
671 }
672
673 #[test]
674 fn test_bernoulli_edge_cases() {
675 let bernoulli_false = Uncertain::bernoulli(0.0);
676 let samples: Vec<bool> = bernoulli_false.take_samples(100);
677 assert!(samples.iter().all(|&x| !x));
678
679 let bernoulli_true = Uncertain::bernoulli(1.0);
680 let samples: Vec<bool> = bernoulli_true.take_samples(100);
681 assert!(samples.iter().all(|&x| x));
682 }
683
684 #[test]
685 fn test_exponential_edge_cases() {
686 let exponential_high_rate = Uncertain::exponential(100.0);
687 let samples: Vec<f64> = exponential_high_rate.take_samples(100);
688 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
689 assert!(mean < 0.1);
690 }
691
692 #[test]
693 fn test_binomial_edge_cases() {
694 let binomial_zero: Uncertain<u32> = Uncertain::binomial(0, 0.5);
695 let samples: Vec<u32> = binomial_zero.take_samples(10);
696 assert!(samples.iter().all(|&x| x == 0));
697
698 let binomial_p_zero: Uncertain<u32> = Uncertain::binomial(10, 0.0);
699 let samples: Vec<u32> = binomial_p_zero.take_samples(10);
700 assert!(samples.iter().all(|&x| x == 0));
701
702 let binomial_p_one: Uncertain<u32> = Uncertain::binomial(10, 1.0);
703 let samples: Vec<u32> = binomial_p_one.take_samples(10);
704 assert!(samples.iter().all(|&x| x == 10));
705 }
706}