1#![allow(
2 clippy::cast_precision_loss,
3 clippy::cast_possible_truncation,
4 clippy::cast_sign_loss
5)]
6
7use crate::Uncertain;
8use crate::cache;
9use crate::computation::AdaptiveSampling;
10use crate::traits::Shareable;
11use std::cell::RefCell;
12use std::collections::HashMap;
13use std::hash::Hash;
14use std::sync::Arc;
15
16#[derive(Debug)]
19pub struct LazyStats<T>
20where
21 T: Shareable,
22{
23 uncertain: Arc<Uncertain<T>>,
24 sample_count: usize,
25 samples_cache: RefCell<Option<Vec<T>>>,
26 mean_cache: RefCell<Option<f64>>,
27 variance_cache: RefCell<Option<f64>>,
28 sorted_samples_cache: RefCell<Option<Vec<f64>>>,
29}
30
31impl<T> LazyStats<T>
32where
33 T: Shareable + Into<f64> + Clone,
34{
35 fn get_or_compute<V, F>(cache: &RefCell<Option<V>>, compute: F) -> V
37 where
38 V: Clone,
39 F: FnOnce() -> V,
40 {
41 let mut cache_ref = cache.borrow_mut();
42 if let Some(value) = cache_ref.as_ref() {
43 value.clone()
44 } else {
45 let computed = compute();
46 *cache_ref = Some(computed.clone());
47 computed
48 }
49 }
50 #[must_use]
52 pub fn new(uncertain: &Uncertain<T>, sample_count: usize) -> Self {
53 Self {
54 uncertain: Arc::new(uncertain.clone()),
55 sample_count,
56 samples_cache: RefCell::new(None),
57 mean_cache: RefCell::new(None),
58 variance_cache: RefCell::new(None),
59 sorted_samples_cache: RefCell::new(None),
60 }
61 }
62
63 pub fn samples(&self) -> Vec<T> {
65 Self::get_or_compute(&self.samples_cache, || {
66 self.uncertain.take_samples(self.sample_count)
67 })
68 }
69
70 pub fn mean(&self) -> f64 {
72 Self::get_or_compute(&self.mean_cache, || {
73 let samples = self.samples();
74 let sum: f64 = samples.into_iter().map(Into::into).sum();
75 sum / self.sample_count as f64
76 })
77 }
78
79 pub fn variance(&self) -> f64 {
81 Self::get_or_compute(&self.variance_cache, || {
82 let samples = self.samples();
83 let mean = self.mean();
84
85 let sum_sq_diff: f64 = samples
87 .iter()
88 .map(|x| {
89 let diff = Into::<f64>::into(x.clone()) - mean;
90 diff * diff
91 })
92 .sum();
93
94 sum_sq_diff / self.sample_count as f64
95 })
96 }
97
98 pub fn std_dev(&self) -> f64 {
100 self.variance().sqrt()
101 }
102
103 fn sorted_samples(&self) -> Vec<f64>
105 where
106 T: PartialOrd,
107 {
108 Self::get_or_compute(&self.sorted_samples_cache, || {
109 let samples = self.samples();
110 let mut sorted: Vec<f64> = samples
111 .iter()
112 .map(|x| Into::<f64>::into(x.clone()))
113 .collect();
114 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
115 sorted
116 })
117 }
118
119 pub fn quantile(&self, q: f64) -> f64
121 where
122 T: PartialOrd,
123 {
124 let samples = self.sorted_samples();
125 if samples.is_empty() {
126 return 0.0;
127 }
128 if samples.len() == 1 {
129 return samples[0];
130 }
131
132 let position = q * (samples.len() - 1) as f64;
133 let lower_idx = position.floor() as usize;
134 let upper_idx = position.ceil() as usize;
135
136 if lower_idx == upper_idx {
137 samples[lower_idx.min(samples.len() - 1)]
138 } else {
139 let lower_val = samples[lower_idx];
140 let upper_val = samples[upper_idx.min(samples.len() - 1)];
141 let weight = position - lower_idx as f64;
142 lower_val + weight * (upper_val - lower_val)
143 }
144 }
145
146 pub fn confidence_interval(&self, confidence: f64) -> (f64, f64)
148 where
149 T: PartialOrd,
150 {
151 let samples = self.sorted_samples();
152 let alpha = 1.0 - confidence;
153 let lower_idx = ((alpha / 2.0) * samples.len() as f64) as usize;
154 let upper_idx = (((1.0 - alpha / 2.0) * samples.len() as f64) as usize).saturating_sub(1);
155
156 let lower_idx = lower_idx.min(samples.len() - 1);
157 let upper_idx = upper_idx.min(samples.len() - 1);
158
159 (samples[lower_idx], samples[upper_idx])
160 }
161}
162
163#[derive(Debug)]
165pub struct ProgressiveStats {
166 count: usize,
167 sum: f64,
168 sum_squares: f64,
169 min_val: f64,
170 max_val: f64,
171}
172
173impl ProgressiveStats {
174 #[must_use]
176 pub fn new() -> Self {
177 Self {
178 count: 0,
179 sum: 0.0,
180 sum_squares: 0.0,
181 min_val: f64::INFINITY,
182 max_val: f64::NEG_INFINITY,
183 }
184 }
185
186 pub fn add_sample(&mut self, value: f64) {
188 self.count += 1;
189 self.sum += value;
190 self.sum_squares += value * value;
191 self.min_val = self.min_val.min(value);
192 self.max_val = self.max_val.max(value);
193 }
194
195 #[must_use]
197 pub fn mean(&self) -> f64 {
198 if self.count == 0 {
199 0.0
200 } else {
201 self.sum / self.count as f64
202 }
203 }
204
205 #[must_use]
207 pub fn variance(&self) -> f64 {
208 if self.count <= 1 {
209 0.0
210 } else {
211 let mean = self.mean();
212 (self.sum_squares - self.count as f64 * mean * mean) / (self.count - 1) as f64
213 }
214 }
215
216 #[must_use]
218 pub fn std_dev(&self) -> f64 {
219 self.variance().sqrt()
220 }
221
222 #[must_use]
224 pub fn range(&self) -> f64 {
225 if self.count == 0 {
226 0.0
227 } else {
228 self.max_val - self.min_val
229 }
230 }
231
232 #[must_use]
234 pub fn count(&self) -> usize {
235 self.count
236 }
237}
238
239impl Default for ProgressiveStats {
240 fn default() -> Self {
241 Self::new()
242 }
243}
244
245#[derive(Debug)]
248pub struct AdaptiveLazyStats<T>
249where
250 T: Shareable,
251{
252 uncertain: Arc<Uncertain<T>>,
253 config: AdaptiveSampling,
254 progressive_stats: RefCell<ProgressiveStats>,
255 current_samples: RefCell<usize>,
256 converged_stats: RefCell<HashMap<String, f64>>,
257}
258
259impl<T> AdaptiveLazyStats<T>
260where
261 T: Shareable + Into<f64> + Clone,
262{
263 #[must_use]
265 pub fn new(uncertain: &Uncertain<T>, config: &AdaptiveSampling) -> Self {
266 Self {
267 uncertain: Arc::new(uncertain.clone()),
268 config: config.clone(),
269 progressive_stats: RefCell::new(ProgressiveStats::new()),
270 current_samples: RefCell::new(0),
271 converged_stats: RefCell::new(HashMap::new()),
272 }
273 }
274
275 fn ensure_samples(&self, target_samples: usize) {
277 let mut current = self.current_samples.borrow_mut();
278 let mut stats = self.progressive_stats.borrow_mut();
279
280 if *current < target_samples {
281 let additional_samples = target_samples - *current;
282 let samples = self.uncertain.take_samples(additional_samples);
283
284 for sample in samples {
285 stats.add_sample(sample.into());
286 }
287
288 *current = target_samples;
289 }
290 }
291
292 pub fn mean(&self) -> f64 {
294 let mut converged = self.converged_stats.borrow_mut();
295
296 if let Some(&cached) = converged.get("mean") {
297 return cached;
298 }
299
300 let mut sample_count = self.config.min_samples;
301 let mut prev_mean = 0.0;
302
303 loop {
304 self.ensure_samples(sample_count);
305 let stats = self.progressive_stats.borrow();
306 let mean = stats.mean();
307
308 if sample_count > self.config.min_samples {
309 let relative_error = if mean == 0.0 {
310 (mean - prev_mean).abs()
311 } else {
312 ((mean - prev_mean) / mean).abs()
313 };
314
315 if relative_error < self.config.error_threshold
316 || sample_count >= self.config.max_samples
317 {
318 converged.insert("mean".to_string(), mean);
319 return mean;
320 }
321 }
322
323 prev_mean = mean;
324 sample_count = ((sample_count as f64 * self.config.growth_factor) as usize)
325 .min(self.config.max_samples);
326 }
327 }
328
329 pub fn variance(&self) -> f64 {
331 let mut converged = self.converged_stats.borrow_mut();
332
333 if let Some(&cached) = converged.get("variance") {
334 return cached;
335 }
336
337 let mut sample_count = self.config.min_samples;
338 let mut prev_variance = 0.0;
339
340 loop {
341 self.ensure_samples(sample_count);
342 let stats = self.progressive_stats.borrow();
343 let variance = stats.variance();
344
345 if sample_count > self.config.min_samples {
346 let relative_error = if variance == 0.0 {
347 (variance - prev_variance).abs()
348 } else {
349 ((variance - prev_variance) / variance).abs()
350 };
351
352 if relative_error < self.config.error_threshold
353 || sample_count >= self.config.max_samples
354 {
355 converged.insert("variance".to_string(), variance);
356 return variance;
357 }
358 }
359
360 prev_variance = variance;
361 sample_count = ((sample_count as f64 * self.config.growth_factor) as usize)
362 .min(self.config.max_samples);
363 }
364 }
365
366 pub fn std_dev(&self) -> f64 {
368 self.variance().sqrt()
369 }
370
371 pub fn sample_count(&self) -> usize {
373 *self.current_samples.borrow()
374 }
375
376 #[must_use]
378 pub fn convergence_info(&self) -> HashMap<String, bool> {
379 let converged = self.converged_stats.borrow();
380 let mut info = HashMap::new();
381 info.insert("mean".to_string(), converged.contains_key("mean"));
382 info.insert("variance".to_string(), converged.contains_key("variance"));
383 info
384 }
385}
386
387impl<T> Uncertain<T>
389where
390 T: Shareable,
391{
392 #[must_use]
409 pub fn mode(&self, sample_count: usize) -> Option<T>
410 where
411 T: Hash + Eq,
412 {
413 let samples = self.take_samples(sample_count);
414 if samples.is_empty() {
415 return None;
416 }
417
418 let mut counts = HashMap::new();
419 for sample in samples {
420 *counts.entry(sample).or_insert(0) += 1;
421 }
422
423 counts
424 .into_iter()
425 .max_by_key(|(_, count)| *count)
426 .map(|(value, _)| value)
427 }
428
429 #[must_use]
440 pub fn histogram(&self, sample_count: usize) -> HashMap<T, usize>
441 where
442 T: Hash + Eq,
443 {
444 let samples = self.take_samples(sample_count);
445 let mut histogram = HashMap::new();
446
447 for sample in samples {
448 *histogram.entry(sample).or_insert(0) += 1;
449 }
450
451 histogram
452 }
453
454 #[must_use]
465 pub fn entropy(&self, sample_count: usize) -> f64
466 where
467 T: Hash + Eq,
468 {
469 let histogram = self.histogram(sample_count);
470 let total = sample_count as f64;
471
472 histogram
473 .values()
474 .map(|&count| {
475 let p = count as f64 / total;
476 if p > 0.0 { -p * p.log2() } else { 0.0 }
477 })
478 .sum()
479 }
480}
481
482impl<T> Uncertain<T>
484where
485 T: Shareable + Into<f64> + Clone,
486{
487 #[must_use]
512 pub fn lazy_stats(&self, sample_count: usize) -> LazyStats<T> {
513 LazyStats::new(self, sample_count)
514 }
515
516 #[must_use]
520 pub fn stats(&self, sample_count: usize) -> LazyStats<T> {
521 LazyStats::new(self, sample_count)
522 }
523
524 #[must_use]
543 pub fn progressive_stats() -> ProgressiveStats {
544 ProgressiveStats::new()
545 }
546
547 #[must_use]
561 pub fn compute_stats_batch(&self, sample_count: usize) -> ProgressiveStats
562 where
563 T: Into<f64>,
564 {
565 let mut stats = ProgressiveStats::new();
566 let samples = self.take_samples(sample_count);
567
568 for sample in samples {
569 stats.add_sample(sample.into());
570 }
571
572 stats
573 }
574}
575
576impl<T> Uncertain<T>
578where
579 T: Clone + Send + Sync + Into<f64> + 'static,
580{
581 #[must_use]
601 pub fn expected_value(&self, sample_count: usize) -> f64
602 where
603 T: Into<f64>,
604 {
605 let samples: Vec<f64> = self
606 .take_samples(sample_count)
607 .into_iter()
608 .map(Into::into)
609 .collect();
610 samples.iter().sum::<f64>() / sample_count as f64
611 }
612
613 #[must_use]
629 pub fn expected_value_adaptive(&self, config: &AdaptiveSampling) -> f64 {
630 let mut sample_count = config.min_samples;
631 let mut prev_mean = 0.0;
632
633 loop {
634 let mean = self.expected_value(sample_count);
635
636 if sample_count > config.min_samples {
637 let relative_error = ((mean - prev_mean) / mean).abs();
638 if relative_error < config.error_threshold || sample_count >= config.max_samples {
639 return mean;
640 }
641 }
642
643 prev_mean = mean;
644 sample_count =
645 ((sample_count as f64 * config.growth_factor) as usize).min(config.max_samples);
646 }
647 }
648
649 #[must_use]
666 pub fn adaptive_lazy_stats(&self, config: &AdaptiveSampling) -> AdaptiveLazyStats<T> {
667 AdaptiveLazyStats::new(self, config)
668 }
669
670 #[must_use]
690 pub fn variance(&self, sample_count: usize) -> f64
691 where
692 T: Into<f64>,
693 {
694 let samples: Vec<f64> = self
695 .take_samples(sample_count)
696 .into_iter()
697 .map(Into::into)
698 .collect();
699
700 let mean = samples.iter().sum::<f64>() / sample_count as f64;
701
702 samples
704 .iter()
705 .map(|x| {
706 let diff = x - mean;
707 diff * diff
708 })
709 .sum::<f64>()
710 / sample_count as f64
711 }
712
713 #[must_use]
733 pub fn standard_deviation(&self, sample_count: usize) -> f64
734 where
735 T: Into<f64>,
736 {
737 self.variance(sample_count).sqrt()
738 }
739
740 #[must_use]
753 pub fn skewness(&self, sample_count: usize) -> f64 {
754 cache::stats_cache().get_or_compute_skewness(self.id, sample_count, || {
755 let samples: Vec<f64> = self
756 .take_samples(sample_count)
757 .into_iter()
758 .map(Into::into)
759 .collect();
760
761 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
762 let std_dev = self.standard_deviation(sample_count);
763
764 if std_dev == 0.0 {
765 return 0.0;
766 }
767
768 let n = samples.len() as f64;
769 samples
770 .iter()
771 .map(|x| ((x - mean) / std_dev).powi(3))
772 .sum::<f64>()
773 / n
774 })
775 }
776
777 #[must_use]
790 pub fn kurtosis(&self, sample_count: usize) -> f64 {
791 cache::stats_cache().get_or_compute_kurtosis(self.id, sample_count, || {
792 let samples: Vec<f64> = self
793 .take_samples(sample_count)
794 .into_iter()
795 .map(Into::into)
796 .collect();
797
798 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
799 let std_dev = self.standard_deviation(sample_count);
800
801 if std_dev == 0.0 {
802 return 0.0;
803 }
804
805 let n = samples.len() as f64;
806 let kurt = samples
807 .iter()
808 .map(|x| ((x - mean) / std_dev).powi(4))
809 .sum::<f64>()
810 / n;
811
812 kurt - 3.0 })
814 }
815}
816
817impl<T> Uncertain<T>
819where
820 T: Clone + Send + Sync + PartialOrd + Into<f64> + 'static,
821{
822 #[must_use]
842 pub fn confidence_interval(&self, confidence: f64, sample_count: usize) -> (f64, f64)
843 where
844 T: Into<f64> + PartialOrd,
845 {
846 let mut samples: Vec<f64> = self
847 .take_samples(sample_count)
848 .into_iter()
849 .map(Into::into)
850 .collect();
851 samples.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
852
853 let alpha = 1.0 - confidence;
854 let lower_idx = ((alpha / 2.0) * samples.len() as f64) as usize;
855 let upper_idx = (((1.0 - alpha / 2.0) * samples.len() as f64) as usize).saturating_sub(1);
856
857 let lower_idx = lower_idx.min(samples.len() - 1);
858 let upper_idx = upper_idx.min(samples.len() - 1);
859
860 (samples[lower_idx], samples[upper_idx])
861 }
862
863 #[must_use]
876 pub fn cdf(&self, value: f64, sample_count: usize) -> f64 {
877 cache::stats_cache().get_or_compute_cdf(self.id, sample_count, value, || {
878 let samples: Vec<f64> = self
879 .take_samples(sample_count)
880 .into_iter()
881 .map(Into::into)
882 .collect();
883
884 let count = samples.iter().filter(|&&x| x <= value).count();
885 count as f64 / samples.len() as f64
886 })
887 }
888
889 #[must_use]
909 pub fn quantile(&self, q: f64, sample_count: usize) -> f64
910 where
911 T: Into<f64> + PartialOrd,
912 {
913 let mut samples: Vec<f64> = self
914 .take_samples(sample_count)
915 .into_iter()
916 .map(Into::into)
917 .collect();
918 samples.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
919
920 if samples.is_empty() {
921 return 0.0;
922 }
923 if samples.len() == 1 {
924 return samples[0];
925 }
926
927 let position = q * (samples.len() - 1) as f64;
928 let lower_idx = position.floor() as usize;
929 let upper_idx = position.ceil() as usize;
930
931 if lower_idx == upper_idx {
932 samples[lower_idx.min(samples.len() - 1)]
933 } else {
934 let lower_val = samples[lower_idx];
935 let upper_val = samples[upper_idx.min(samples.len() - 1)];
936 let weight = position - lower_idx as f64;
937 lower_val + weight * (upper_val - lower_val)
938 }
939 }
940
941 #[must_use]
952 pub fn interquartile_range(&self, sample_count: usize) -> f64 {
953 let q75 = self.quantile(0.75, sample_count);
954 let q25 = self.quantile(0.25, sample_count);
955 q75 - q25
956 }
957
958 #[must_use]
972 pub fn median_absolute_deviation(&self, sample_count: usize) -> f64 {
973 let samples: Vec<f64> = self
974 .take_samples(sample_count)
975 .into_iter()
976 .map(std::convert::Into::into)
977 .collect();
978
979 let median = self.quantile(0.5, sample_count);
980 let mut deviations: Vec<f64> = samples.iter().map(|x| (x - median).abs()).collect();
981
982 deviations.sort_by(|a, b| a.partial_cmp(b).unwrap());
983 let mad_index = (deviations.len() - 1) / 2;
984 deviations[mad_index]
985 }
986}
987
988impl Uncertain<f64> {
990 #[must_use]
1002 pub fn pdf_kde(&self, x: f64, sample_count: usize, bandwidth: f64) -> f64 {
1003 cache::dist_cache().get_or_compute_pdf_kde(self.id, sample_count, x, bandwidth, || {
1004 let samples = self.take_samples(sample_count);
1005
1006 let kernel_sum: f64 = samples
1007 .iter()
1008 .map(|&xi| {
1009 let z = (x - xi) / bandwidth;
1010 (-0.5 * z * z).exp()
1011 })
1012 .sum();
1013
1014 kernel_sum / (sample_count as f64 * bandwidth * (2.0 * std::f64::consts::PI).sqrt())
1015 })
1016 }
1017
1018 #[must_use]
1028 pub fn log_likelihood(&self, x: f64, sample_count: usize, bandwidth: f64) -> f64 {
1029 let pdf = self.pdf_kde(x, sample_count, bandwidth);
1030 if pdf > 0.0 {
1031 pdf.ln()
1032 } else {
1033 f64::NEG_INFINITY
1034 }
1035 }
1036
1037 #[must_use]
1049 pub fn correlation(&self, other: &Uncertain<f64>, sample_count: usize) -> f64 {
1050 let samples_x = self.take_samples(sample_count);
1051 let samples_y = other.take_samples(sample_count);
1052
1053 let mean_x = samples_x.iter().sum::<f64>() / sample_count as f64;
1054 let mean_y = samples_y.iter().sum::<f64>() / sample_count as f64;
1055
1056 let numerator: f64 = samples_x
1057 .iter()
1058 .zip(samples_y.iter())
1059 .map(|(x, y)| (x - mean_x) * (y - mean_y))
1060 .sum();
1061
1062 let var_x: f64 = samples_x.iter().map(|x| (x - mean_x).powi(2)).sum();
1063
1064 let var_y: f64 = samples_y.iter().map(|y| (y - mean_y).powi(2)).sum();
1065
1066 if var_x * var_y > 0.0 {
1067 numerator / (var_x * var_y).sqrt()
1068 } else {
1069 0.0
1070 }
1071 }
1072}
1073
1074#[cfg(test)]
1075mod tests {
1076 use super::*;
1077 use std::collections::HashMap;
1078
1079 #[test]
1080 fn test_expected_value() {
1081 let normal = Uncertain::normal(10.0, 1.0);
1082 let mean = normal.expected_value(1000);
1083 assert!((mean - 10.0).abs() < 0.2);
1084 }
1085
1086 #[test]
1087 fn test_standard_deviation() {
1088 let normal = Uncertain::normal(0.0, 2.0);
1089 let std_dev = normal.standard_deviation(1000);
1090 assert!((std_dev - 2.0).abs() < 0.3);
1091 }
1092
1093 #[test]
1094 fn test_confidence_interval() {
1095 let normal = Uncertain::normal(0.0, 1.0);
1096 let (lower, upper) = normal.confidence_interval(0.95, 1000);
1097
1098 assert!(lower < -1.5 && lower > -2.5);
1100 assert!(upper > 1.5 && upper < 2.5);
1101 }
1102
1103 #[test]
1104 fn test_cdf() {
1105 let normal = Uncertain::normal(0.0, 1.0);
1106 let prob = normal.cdf(0.0, 1000);
1107
1108 assert!((prob - 0.5).abs() < 0.1);
1110 }
1111
1112 #[test]
1113 fn test_quantile() {
1114 let normal = Uncertain::normal(0.0, 1.0);
1115 let median = normal.quantile(0.5, 1000);
1116
1117 assert!(median.abs() < 0.2);
1119 }
1120
1121 #[test]
1122 fn test_mode_categorical() {
1123 let mut probs = HashMap::new();
1124 probs.insert("red", 0.6);
1125 probs.insert("blue", 0.4);
1126
1127 let categorical = Uncertain::categorical(&probs).unwrap();
1128 let mode = categorical.mode(1000);
1129
1130 assert_eq!(mode, Some("red"));
1132 }
1133
1134 #[test]
1135 fn test_entropy() {
1136 let fair_coin = Uncertain::bernoulli(0.5);
1137 let entropy = fair_coin.entropy(1000);
1138
1139 assert!((entropy - 1.0).abs() < 0.1);
1141
1142 let biased_coin = Uncertain::bernoulli(0.9);
1143 let biased_entropy = biased_coin.entropy(1000);
1144
1145 assert!(biased_entropy < entropy);
1147 }
1148
1149 #[test]
1150 fn test_skewness_normal() {
1151 let normal = Uncertain::normal(0.0, 1.0);
1152 let skewness = normal.skewness(1000);
1153
1154 assert!(skewness.abs() < 0.3);
1156 }
1157
1158 #[test]
1159 fn test_kurtosis_normal() {
1160 let normal = Uncertain::normal(0.0, 1.0);
1161 let kurtosis = normal.kurtosis(1000);
1162
1163 assert!(kurtosis.abs() < 2.0);
1166 }
1167
1168 #[test]
1169 fn test_mode_empty_samples() {
1170 let uncertain = Uncertain::new(|| None::<i32>);
1171 let mode = uncertain.mode(0);
1172 assert_eq!(mode, None);
1173 }
1174
1175 #[test]
1176 fn test_mode_integers() {
1177 let uniform = Uncertain::new(|| if rand::random::<f64>() < 0.7 { 1 } else { 2 });
1178 let mode = uniform.mode(1000);
1179 assert_eq!(mode, Some(1));
1180 }
1181
1182 #[test]
1183 fn test_histogram_empty() {
1184 let uncertain = Uncertain::new(|| 42);
1185 let histogram = uncertain.histogram(0);
1186 assert!(histogram.is_empty());
1187 }
1188
1189 #[test]
1190 fn test_histogram_bernoulli() {
1191 let bernoulli = Uncertain::bernoulli(0.3);
1192 let histogram = bernoulli.histogram(1000);
1193
1194 let true_count = histogram.get(&true).copied().unwrap_or(0);
1195 let false_count = histogram.get(&false).copied().unwrap_or(0);
1196
1197 assert!(true_count > 200);
1198 assert!(true_count < 400);
1199 assert!(false_count > 600);
1200 assert!(false_count < 800);
1201 assert_eq!(true_count + false_count, 1000);
1202 }
1203
1204 #[test]
1205 fn test_variance_standalone() {
1206 let normal = Uncertain::normal(5.0, 3.0);
1207 let variance = normal.variance(1000);
1208 assert!((variance - 9.0).abs() < 1.5);
1209 }
1210
1211 #[test]
1212 fn test_variance_zero() {
1213 let constant = Uncertain::new(|| 42.0);
1214 let variance = constant.variance(1000);
1215 assert!(variance < 0.001);
1216 }
1217
1218 #[test]
1219 fn test_skewness_zero_std_dev() {
1220 let constant = Uncertain::new(|| 5.0);
1221 let skewness = constant.skewness(1000);
1222 assert!(skewness.abs() < f64::EPSILON);
1223 }
1224
1225 #[test]
1226 fn test_kurtosis_zero_std_dev() {
1227 let constant = Uncertain::new(|| 5.0);
1228 let kurtosis = constant.kurtosis(1000);
1229 assert!(kurtosis.abs() < f64::EPSILON);
1230 }
1231
1232 #[test]
1233 fn test_pdf_kde() {
1234 let normal = Uncertain::normal(0.0, 1.0);
1235 let density_at_mean = normal.pdf_kde(0.0, 1000, 0.1);
1236 let density_at_tail = normal.pdf_kde(3.0, 1000, 0.1);
1237
1238 assert!(density_at_mean > density_at_tail);
1239 assert!(density_at_mean > 0.0);
1240 assert!(density_at_tail > 0.0);
1241 }
1242
1243 #[test]
1244 fn test_log_likelihood() {
1245 let normal = Uncertain::normal(0.0, 1.0);
1246 let ll_at_mean = normal.log_likelihood(0.0, 1000, 0.1);
1247 let ll_at_tail = normal.log_likelihood(3.0, 1000, 0.1);
1248
1249 assert!(ll_at_mean > ll_at_tail);
1250 assert!(ll_at_mean.is_finite());
1251 }
1252
1253 #[test]
1254 fn test_log_likelihood_zero_pdf() {
1255 let point = Uncertain::new(|| 0.0);
1256 let ll = point.log_likelihood(10.0, 100, 0.01);
1257 assert!(ll.is_infinite() && ll.is_sign_negative());
1258 }
1259
1260 #[test]
1261 fn test_correlation_positive() {
1262 use std::sync::Arc;
1263 use std::sync::atomic::{AtomicUsize, Ordering};
1264
1265 let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1266 let y_values = vec![2.0, 4.0, 6.0, 8.0, 10.0];
1267
1268 let x_counter = Arc::new(AtomicUsize::new(0));
1269 let x = Uncertain::new({
1270 let values = values.clone();
1271 let counter = x_counter.clone();
1272 move || {
1273 let idx = counter.fetch_add(1, Ordering::SeqCst);
1274 values[idx % values.len()]
1275 }
1276 });
1277
1278 let y_counter = Arc::new(AtomicUsize::new(0));
1279 let y = Uncertain::new({
1280 let values = y_values.clone();
1281 let counter = y_counter.clone();
1282 move || {
1283 let idx = counter.fetch_add(1, Ordering::SeqCst);
1284 values[idx % values.len()]
1285 }
1286 });
1287
1288 let correlation = x.correlation(&y, 5);
1289 assert!((correlation - 1.0).abs() < 0.001);
1290 }
1291
1292 #[test]
1293 fn test_correlation_negative() {
1294 use std::sync::Arc;
1295 use std::sync::atomic::{AtomicUsize, Ordering};
1296
1297 let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1298 let y_values = vec![5.0, 4.0, 3.0, 2.0, 1.0];
1299
1300 let x_counter = Arc::new(AtomicUsize::new(0));
1301 let x = Uncertain::new({
1302 let values = values.clone();
1303 let counter = x_counter.clone();
1304 move || {
1305 let idx = counter.fetch_add(1, Ordering::SeqCst);
1306 values[idx % values.len()]
1307 }
1308 });
1309
1310 let y_counter = Arc::new(AtomicUsize::new(0));
1311 let y = Uncertain::new({
1312 let values = y_values.clone();
1313 let counter = y_counter.clone();
1314 move || {
1315 let idx = counter.fetch_add(1, Ordering::SeqCst);
1316 values[idx % values.len()]
1317 }
1318 });
1319
1320 let correlation = x.correlation(&y, 5);
1321 assert!((correlation + 1.0).abs() < 0.001);
1322 }
1323
1324 #[test]
1325 fn test_correlation_independent() {
1326 let x = Uncertain::normal(0.0, 1.0);
1327 let y = Uncertain::normal(10.0, 1.0);
1328 let correlation = x.correlation(&y, 1000);
1329
1330 assert!(correlation.abs() < 0.2);
1331 }
1332
1333 #[test]
1334 fn test_correlation_zero_variance() {
1335 let x = Uncertain::new(|| 5.0);
1336 let y = Uncertain::normal(0.0, 1.0);
1337 let correlation = x.correlation(&y, 1000);
1338
1339 assert!(correlation.abs() < f64::EPSILON);
1340 }
1341
1342 #[test]
1343 fn test_interquartile_range() {
1344 let normal = Uncertain::normal(0.0, 1.0);
1345 let iqr = normal.interquartile_range(1000);
1346
1347 assert!(iqr > 1.0);
1348 assert!(iqr < 2.0);
1349 }
1350
1351 #[test]
1352 fn test_median_absolute_deviation() {
1353 let normal = Uncertain::normal(0.0, 1.0);
1354 let mad = normal.median_absolute_deviation(1000);
1355
1356 assert!(mad > 0.5);
1357 assert!(mad < 1.0);
1358 }
1359
1360 #[test]
1361 fn test_quantile_extremes() {
1362 let normal = Uncertain::normal(0.0, 1.0);
1363 let min_quantile = normal.quantile(0.0, 1000);
1364 let max_quantile = normal.quantile(1.0, 1000);
1365
1366 assert!(min_quantile < max_quantile);
1367 assert!(min_quantile < -2.0);
1368 assert!(max_quantile > 2.0);
1369 }
1370
1371 #[test]
1372 fn test_cdf_extremes() {
1373 let normal = Uncertain::normal(0.0, 1.0);
1374 let prob_low = normal.cdf(-5.0, 1000);
1375 let prob_high = normal.cdf(5.0, 1000);
1376
1377 assert!(prob_low < 0.1);
1378 assert!(prob_high > 0.9);
1379 assert!(prob_low < prob_high);
1380 }
1381
1382 #[test]
1383 fn test_confidence_interval_different_levels() {
1384 let normal = Uncertain::normal(0.0, 1.0);
1385 let (lower_90, upper_90) = normal.confidence_interval(0.90, 1000);
1386 let (lower_99, upper_99) = normal.confidence_interval(0.99, 1000);
1387
1388 assert!(lower_99 < lower_90);
1389 assert!(upper_99 > upper_90);
1390 assert!(upper_90 - lower_90 < upper_99 - lower_99);
1391 }
1392
1393 #[test]
1394 fn test_entropy_deterministic() {
1395 let constant = Uncertain::new(|| "always");
1396 let entropy = constant.entropy(1000);
1397 assert!(entropy < 0.01);
1398 }
1399
1400 #[test]
1401 fn test_entropy_maximum() {
1402 let mut probs = HashMap::new();
1403 probs.insert("a", 0.25);
1404 probs.insert("b", 0.25);
1405 probs.insert("c", 0.25);
1406 probs.insert("d", 0.25);
1407
1408 let uniform = Uncertain::categorical(&probs).unwrap();
1409 let entropy = uniform.entropy(2000);
1410
1411 assert!((entropy - 2.0).abs() < 0.2);
1412 }
1413
1414 #[test]
1415 fn test_mode_tie_handling() {
1416 let balanced = Uncertain::new(|| if rand::random::<bool>() { 1 } else { 2 });
1417 let mode = balanced.mode(1000);
1418 assert!(mode == Some(1) || mode == Some(2));
1419 }
1420
1421 #[test]
1422 fn test_statistical_consistency() {
1423 let normal = Uncertain::normal(10.0, 2.0);
1424
1425 let mean = normal.expected_value(2000);
1426 let median = normal.quantile(0.5, 2000);
1427 let mode_samples: Vec<f64> = (0..1000).map(|_| normal.sample()).collect();
1428 let empirical_mean = mode_samples.iter().sum::<f64>() / mode_samples.len() as f64;
1429
1430 assert!((mean - 10.0).abs() < 0.3);
1431 assert!((median - 10.0).abs() < 0.3);
1432 assert!((empirical_mean - 10.0).abs() < 0.3);
1433 assert!((mean - median).abs() < 0.3);
1434 }
1435
1436 #[test]
1437 fn test_lazy_stats_basic() {
1438 let normal = Uncertain::normal(5.0, 2.0);
1439 let lazy_stats = normal.lazy_stats(1000);
1440
1441 let mean = lazy_stats.mean();
1442 assert!((mean - 5.0).abs() < 0.3);
1443
1444 let variance = lazy_stats.variance();
1445 assert!((variance - 4.0).abs() < 0.8);
1446
1447 let std_dev = lazy_stats.std_dev();
1448 assert!((std_dev - 2.0).abs() < 0.4);
1449 }
1450
1451 #[test]
1452 fn test_lazy_stats_sample_reuse() {
1453 let normal = Uncertain::normal(0.0, 1.0);
1454 let lazy_stats = normal.lazy_stats(100);
1455
1456 let samples1 = lazy_stats.samples();
1458 assert_eq!(samples1.len(), 100);
1459
1460 let samples2 = lazy_stats.samples();
1462 assert_eq!(samples1, samples2);
1463
1464 let mean1 = lazy_stats.mean();
1466 let mean2 = lazy_stats.mean();
1467 assert!((mean1 - mean2).abs() < f64::EPSILON);
1468 }
1469
1470 #[test]
1471 fn test_progressive_stats_basic() {
1472 let mut progressive = ProgressiveStats::new();
1473
1474 assert_eq!(progressive.count(), 0);
1475 assert!(progressive.mean().abs() < f64::EPSILON);
1476 assert!(progressive.variance().abs() < f64::EPSILON);
1477
1478 progressive.add_sample(1.0);
1479 progressive.add_sample(2.0);
1480 progressive.add_sample(3.0);
1481
1482 assert_eq!(progressive.count(), 3);
1483 assert!((progressive.mean() - 2.0).abs() < f64::EPSILON);
1484 assert!((progressive.range() - 2.0).abs() < f64::EPSILON);
1485 }
1486
1487 #[test]
1488 fn test_progressive_stats_variance() {
1489 let mut progressive = ProgressiveStats::new();
1490
1491 let samples = [1.0, 2.0, 3.0, 4.0, 5.0];
1493 for &sample in &samples {
1494 progressive.add_sample(sample);
1495 }
1496
1497 let expected_mean = 3.0;
1498 let expected_variance = 2.5; assert!((progressive.mean() - expected_mean).abs() < 0.001);
1501 assert!((progressive.variance() - expected_variance).abs() < 0.001);
1502 assert!((progressive.std_dev() - expected_variance.sqrt()).abs() < 0.001);
1503 }
1504
1505 #[test]
1506 fn test_compute_stats_batch() {
1507 let normal = Uncertain::normal(10.0, 2.0);
1508 let batch_stats = normal.compute_stats_batch(1000);
1509
1510 assert!(batch_stats.count() == 1000);
1511 assert!((batch_stats.mean() - 10.0).abs() < 0.3);
1512 assert!((batch_stats.std_dev() - 2.0).abs() < 0.4);
1513 assert!(batch_stats.range() > 0.0);
1514 }
1515
1516 #[test]
1517 fn test_adaptive_lazy_stats_convergence() {
1518 let normal = Uncertain::normal(5.0, 1.0);
1519 let config = AdaptiveSampling {
1520 min_samples: 50,
1521 max_samples: 500,
1522 error_threshold: 0.05,
1523 growth_factor: 1.5,
1524 };
1525
1526 let adaptive_stats = normal.adaptive_lazy_stats(&config);
1527
1528 let mean = adaptive_stats.mean();
1529 assert!((mean - 5.0).abs() < 0.3);
1530
1531 let std_dev = adaptive_stats.std_dev();
1532 assert!((std_dev - 1.0).abs() < 0.3);
1533
1534 let sample_count = adaptive_stats.sample_count();
1536 assert!(sample_count >= config.min_samples);
1537 assert!(sample_count <= config.max_samples);
1538
1539 let convergence = adaptive_stats.convergence_info();
1541 assert!(convergence.get("mean").copied().unwrap_or(false));
1542 assert!(convergence.get("variance").copied().unwrap_or(false));
1543 }
1544
1545 #[test]
1546 fn test_lazy_stats_with_quantiles() {
1547 let normal = Uncertain::normal(0.0, 1.0);
1548 let lazy_stats = normal.lazy_stats(1000);
1549
1550 let median = lazy_stats.quantile(0.5);
1551 assert!(median.abs() < 0.3); let q25 = lazy_stats.quantile(0.25);
1554 let q75 = lazy_stats.quantile(0.75);
1555 assert!(q25 < median);
1556 assert!(median < q75);
1557
1558 let (lower, upper) = lazy_stats.confidence_interval(0.95);
1559 assert!(lower < upper);
1560 assert!(lower < median);
1561 assert!(median < upper);
1562 }
1563
1564 #[test]
1565 fn test_progressive_stats_default() {
1566 let progressive = ProgressiveStats::default();
1567 assert_eq!(progressive.count(), 0);
1568 assert!(progressive.mean().abs() < f64::EPSILON);
1569 }
1570
1571 #[test]
1572 fn test_lazy_stats_debug_and_clone() {
1573 let normal = Uncertain::normal(1.0, 1.0);
1574 let lazy_stats = normal.lazy_stats(100);
1575
1576 let debug_str = format!("{lazy_stats:?}");
1577 assert!(debug_str.contains("LazyStats"));
1578
1579 let mean1 = lazy_stats.mean();
1581 let mean2 = lazy_stats.mean();
1582 assert!((mean1 - mean2).abs() < f64::EPSILON);
1583 }
1584
1585 #[test]
1586 fn test_adaptive_lazy_stats_early_convergence() {
1587 let constant = Uncertain::new(|| 42.0);
1589 let config = AdaptiveSampling {
1590 min_samples: 10,
1591 max_samples: 1000,
1592 error_threshold: 0.01,
1593 growth_factor: 2.0,
1594 };
1595
1596 let adaptive_stats = constant.adaptive_lazy_stats(&config);
1597 let mean = adaptive_stats.mean();
1598
1599 assert!((mean - 42.0).abs() < 0.01);
1600
1601 let sample_count = adaptive_stats.sample_count();
1603 assert!(sample_count <= 100); }
1605
1606 #[test]
1607 fn test_progressive_stats_edge_cases() {
1608 let mut progressive = ProgressiveStats::new();
1609
1610 progressive.add_sample(5.0);
1612 assert_eq!(progressive.count(), 1);
1613 assert!((progressive.mean() - 5.0).abs() < f64::EPSILON);
1614 assert!(progressive.variance().abs() < f64::EPSILON); assert!(progressive.range().abs() < f64::EPSILON);
1616
1617 progressive.add_sample(7.0);
1619 assert_eq!(progressive.count(), 2);
1620 assert!((progressive.mean() - 6.0).abs() < f64::EPSILON);
1621 assert!(progressive.variance() > 0.0);
1622 assert!((progressive.range() - 2.0).abs() < f64::EPSILON);
1623 }
1624
1625 #[test]
1626 fn test_caching_behavior() {
1627 let normal = Uncertain::normal(5.0, 1.0);
1628
1629 let mean1 = normal.expected_value(1000);
1632 let mean2 = normal.expected_value(1000);
1633 assert!((mean1 - mean2).abs() < 0.2); let var1 = normal.variance(1000);
1636 let var2 = normal.variance(1000);
1637 assert!((var1 - var2).abs() < 0.2); let stats = normal.lazy_stats(1000);
1641 let cached_mean1 = stats.mean();
1642 let cached_mean2 = stats.mean();
1643 assert!((cached_mean1 - cached_mean2).abs() < f64::EPSILON);
1644
1645 let cached_var1 = stats.variance();
1646 let cached_var2 = stats.variance();
1647 assert!((cached_var1 - cached_var2).abs() < f64::EPSILON);
1648 }
1649
1650 #[test]
1651 fn test_large_sample_counts() {
1652 let normal = Uncertain::normal(0.0, 1.0);
1653 let mean = normal.expected_value(10000);
1654 let std_dev = normal.standard_deviation(10000);
1655
1656 assert!((mean - 0.0).abs() < 0.1);
1657 assert!((std_dev - 1.0).abs() < 0.1);
1658 }
1659
1660 #[test]
1661 fn test_histogram_with_different_types() {
1662 let chars = Uncertain::new(|| match rand::random::<u8>() % 3 {
1663 0 => 'A',
1664 1 => 'B',
1665 _ => 'C',
1666 });
1667
1668 let histogram = chars.histogram(300);
1669 assert_eq!(histogram.len(), 3);
1670 assert!(histogram.contains_key(&'A'));
1671 assert!(histogram.contains_key(&'B'));
1672 assert!(histogram.contains_key(&'C'));
1673
1674 let total: usize = histogram.values().sum();
1675 assert_eq!(total, 300);
1676 }
1677}