1use std::ops::Bound;
27
28use crate::special;
29
30mod sampling;
31
32pub use sampling::Sample;
33
34#[derive(Debug, Clone, PartialEq)]
37pub enum DistributionError {
38 NotFinite {
40 parameter: &'static str,
42 },
43 NotPositive {
46 parameter: &'static str,
48 got: f64,
50 },
51 Unordered {
54 parameter: &'static str,
57 relation: &'static str,
59 low: Bound<f64>,
62 high: Bound<f64>,
64 got: f64,
66 },
67}
68
69impl std::fmt::Display for DistributionError {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 match self {
72 DistributionError::NotFinite { parameter } => {
73 write!(f, "{parameter} must be a finite number")
74 }
75 DistributionError::NotPositive { parameter, got } => {
76 write!(f, "{parameter} must be greater than 0, got {got}")
77 }
78 DistributionError::Unordered {
79 parameter,
80 relation,
81 got,
82 ..
83 } => write!(
84 f,
85 "{parameter} breaks the required order {relation}, got {got}"
86 ),
87 }
88 }
89}
90
91fn finite(params: &[(&'static str, f64)]) -> Result<(), DistributionError> {
93 match params.iter().find(|(_, x)| !x.is_finite()) {
94 Some(&(parameter, _)) => Err(DistributionError::NotFinite { parameter }),
95 None => Ok(()),
96 }
97}
98
99fn positive(parameter: &'static str, x: f64) -> Result<(), DistributionError> {
101 finite(&[(parameter, x)])?;
102 if x > 0.0 {
103 Ok(())
104 } else {
105 Err(DistributionError::NotPositive { parameter, got: x })
106 }
107}
108
109fn ordered(min: f64, mode: Option<f64>, max: f64) -> Result<(), DistributionError> {
111 if min >= max {
112 return Err(DistributionError::Unordered {
113 parameter: "max",
114 relation: "min < max",
115 low: Bound::Excluded(min),
116 high: Bound::Unbounded,
117 got: max,
118 });
119 }
120 match mode {
121 Some(m) if m < min || m > max => Err(DistributionError::Unordered {
122 parameter: "mode",
123 relation: "min ≤ mode ≤ max",
124 low: Bound::Included(min),
125 high: Bound::Included(max),
126 got: m,
127 }),
128 _ => Ok(()),
129 }
130}
131
132impl std::error::Error for DistributionError {}
133
134#[derive(Debug, Clone, PartialEq)]
146pub struct Uniform {
147 min: f64,
148 max: f64,
149}
150
151impl Uniform {
152 pub fn new(min: f64, max: f64) -> Result<Self, DistributionError> {
157 finite(&[("min", min), ("max", max)])?;
158 ordered(min, None, max)?;
159 Ok(Self { min, max })
160 }
161
162 pub fn min(&self) -> f64 {
163 self.min
164 }
165
166 pub fn max(&self) -> f64 {
167 self.max
168 }
169
170 pub fn mean(&self) -> f64 {
171 (self.min + self.max) / 2.0
172 }
173
174 pub fn variance(&self) -> f64 {
175 let range = self.max - self.min;
176 range * range / 12.0
177 }
178
179 pub fn cdf(&self, x: f64) -> f64 {
181 if x <= self.min {
182 0.0
183 } else if x >= self.max {
184 1.0
185 } else {
186 (x - self.min) / (self.max - self.min)
187 }
188 }
189
190 pub fn quantile(&self, p: f64) -> Option<f64> {
194 if !(0.0..=1.0).contains(&p) {
195 return None;
196 }
197 Some(self.min + p * (self.max - self.min))
198 }
199
200 pub fn pdf(&self, x: f64) -> f64 {
202 if x >= self.min && x <= self.max {
203 1.0 / (self.max - self.min)
204 } else {
205 0.0
206 }
207 }
208}
209
210#[derive(Debug, Clone, PartialEq)]
225pub struct Triangular {
226 min: f64,
227 mode: f64,
228 max: f64,
229}
230
231impl Triangular {
232 pub fn new(min: f64, mode: f64, max: f64) -> Result<Self, DistributionError> {
237 finite(&[("min", min), ("mode", mode), ("max", max)])?;
238 ordered(min, Some(mode), max)?;
239 Ok(Self { min, mode, max })
240 }
241
242 pub fn min(&self) -> f64 {
243 self.min
244 }
245
246 pub fn mode(&self) -> f64 {
247 self.mode
248 }
249
250 pub fn max(&self) -> f64 {
251 self.max
252 }
253
254 pub fn mean(&self) -> f64 {
256 (self.min + self.mode + self.max) / 3.0
257 }
258
259 pub fn variance(&self) -> f64 {
261 let (a, b, c) = (self.min, self.mode, self.max);
262 (a * a + b * b + c * c - a * b - a * c - b * c) / 18.0
263 }
264
265 pub fn pdf(&self, x: f64) -> f64 {
273 let (a, b, c) = (self.min, self.mode, self.max);
274 if x < a || x > c {
275 0.0
276 } else if x <= b {
277 2.0 * (x - a) / ((c - a) * (b - a).max(f64::MIN_POSITIVE))
278 } else {
279 2.0 * (c - x) / ((c - a) * (c - b).max(f64::MIN_POSITIVE))
280 }
281 }
282
283 pub fn cdf(&self, x: f64) -> f64 {
290 let (a, b, c) = (self.min, self.mode, self.max);
291 if x <= a {
292 0.0
293 } else if x <= b {
294 (x - a) * (x - a) / ((c - a) * (b - a).max(f64::MIN_POSITIVE))
295 } else if x < c {
296 1.0 - (c - x) * (c - x) / ((c - a) * (c - b).max(f64::MIN_POSITIVE))
297 } else {
298 1.0
299 }
300 }
301
302 pub fn quantile(&self, p: f64) -> Option<f64> {
311 if !(0.0..=1.0).contains(&p) {
312 return None;
313 }
314 let (a, b, c) = (self.min, self.mode, self.max);
315 let fc = (b - a) / (c - a); if p < fc {
317 Some(a + ((c - a) * (b - a) * p).sqrt())
318 } else {
319 Some(c - ((c - a) * (c - b) * (1.0 - p)).sqrt())
320 }
321 }
322}
323
324#[derive(Debug, Clone, PartialEq)]
336pub struct Normal {
337 mu: f64,
338 sigma: f64,
339}
340
341impl Normal {
342 pub fn new(mu: f64, sigma: f64) -> Result<Self, DistributionError> {
347 finite(&[("mu", mu)])?;
348 positive("sigma", sigma)?;
349 Ok(Self { mu, sigma })
350 }
351
352 pub fn mu(&self) -> f64 {
353 self.mu
354 }
355
356 pub fn sigma(&self) -> f64 {
357 self.sigma
358 }
359
360 pub fn mean(&self) -> f64 {
361 self.mu
362 }
363
364 pub fn variance(&self) -> f64 {
365 self.sigma * self.sigma
366 }
367
368 pub fn std_dev(&self) -> f64 {
369 self.sigma
370 }
371
372 pub fn pdf(&self, x: f64) -> f64 {
374 let z = (x - self.mu) / self.sigma;
375 special::standard_normal_pdf(z) / self.sigma
376 }
377
378 pub fn cdf(&self, x: f64) -> f64 {
380 let z = (x - self.mu) / self.sigma;
381 special::standard_normal_cdf(z)
382 }
383
384 pub fn quantile(&self, p: f64) -> Option<f64> {
388 if p <= 0.0 || p >= 1.0 {
389 return None;
390 }
391 Some(self.mu + self.sigma * special::inverse_normal_cdf(p))
392 }
393}
394
395#[derive(Debug, Clone, PartialEq)]
410pub struct LogNormal {
411 mu: f64,
412 sigma: f64,
413}
414
415impl LogNormal {
416 pub fn new(mu: f64, sigma: f64) -> Result<Self, DistributionError> {
423 finite(&[("mu", mu)])?;
424 positive("sigma", sigma)?;
425 Ok(Self { mu, sigma })
426 }
427
428 pub fn mu(&self) -> f64 {
429 self.mu
430 }
431
432 pub fn sigma(&self) -> f64 {
433 self.sigma
434 }
435
436 pub fn mean(&self) -> f64 {
438 (self.mu + self.sigma * self.sigma / 2.0).exp()
439 }
440
441 pub fn variance(&self) -> f64 {
443 let s2 = self.sigma * self.sigma;
444 (s2.exp() - 1.0) * (2.0 * self.mu + s2).exp()
445 }
446
447 pub fn pdf(&self, x: f64) -> f64 {
449 if x <= 0.0 {
450 return 0.0;
451 }
452 let ln_x = x.ln();
453 let z = (ln_x - self.mu) / self.sigma;
454 special::standard_normal_pdf(z) / (x * self.sigma)
455 }
456
457 pub fn cdf(&self, x: f64) -> f64 {
459 if x <= 0.0 {
460 return 0.0;
461 }
462 let z = (x.ln() - self.mu) / self.sigma;
463 special::standard_normal_cdf(z)
464 }
465
466 pub fn quantile(&self, p: f64) -> Option<f64> {
470 if p <= 0.0 || p >= 1.0 {
471 return None;
472 }
473 Some((self.mu + self.sigma * special::inverse_normal_cdf(p)).exp())
474 }
475}
476
477#[derive(Debug, Clone, PartialEq)]
513pub struct Pert {
514 min: f64,
515 mode: f64,
516 max: f64,
517 alpha: f64,
518 beta: f64,
519}
520
521impl Pert {
522 pub fn new(min: f64, mode: f64, max: f64) -> Result<Self, DistributionError> {
527 Self::with_shape(min, mode, max, 4.0)
528 }
529
530 pub fn with_shape(
540 min: f64,
541 mode: f64,
542 max: f64,
543 lambda: f64,
544 ) -> Result<Self, DistributionError> {
545 finite(&[("min", min), ("mode", mode), ("max", max)])?;
546 ordered(min, Some(mode), max)?;
547 positive("lambda", lambda)?;
548
549 let range = max - min;
550 let alpha = 1.0 + lambda * (mode - min) / range;
551 let beta = 1.0 + lambda * (max - mode) / range;
552
553 Ok(Self {
554 min,
555 mode,
556 max,
557 alpha,
558 beta,
559 })
560 }
561
562 pub fn min(&self) -> f64 {
563 self.min
564 }
565
566 pub fn mode(&self) -> f64 {
567 self.mode
568 }
569
570 pub fn max(&self) -> f64 {
571 self.max
572 }
573
574 pub fn alpha(&self) -> f64 {
575 self.alpha
576 }
577
578 pub fn beta_param(&self) -> f64 {
579 self.beta
580 }
581
582 pub fn mean(&self) -> f64 {
586 let ab = self.alpha + self.beta;
587 self.min + (self.alpha / ab) * (self.max - self.min)
589 }
590
591 pub fn variance(&self) -> f64 {
597 let ab = self.alpha + self.beta;
598 let range = self.max - self.min;
599 (self.alpha * self.beta) / (ab * ab * (ab + 1.0)) * range * range
600 }
601
602 pub fn std_dev(&self) -> f64 {
604 self.variance().sqrt()
605 }
606
607 pub fn cdf(&self, x: f64) -> f64 {
612 if x <= self.min {
613 return 0.0;
614 }
615 if x >= self.max {
616 return 1.0;
617 }
618 let t = (x - self.min) / (self.max - self.min);
619 special::regularized_incomplete_beta(t, self.alpha, self.beta)
620 }
621
622 pub fn quantile(&self, p: f64) -> Option<f64> {
640 if !(0.0..=1.0).contains(&p) {
641 return None;
642 }
643 let standard = BetaDistribution::new(self.alpha, self.beta)
644 .expect("PERT shape parameters are positive by construction")
645 .quantile(p)?;
646 Some(self.min + (self.max - self.min) * standard)
647 }
648}
649
650#[derive(Debug, Clone, PartialEq)]
673pub struct Weibull {
674 shape: f64, scale: f64, }
677
678impl Weibull {
679 pub fn new(shape: f64, scale: f64) -> Result<Self, DistributionError> {
684 positive("shape", shape)?;
685 positive("scale", scale)?;
686 Ok(Self { shape, scale })
687 }
688
689 pub fn shape(&self) -> f64 {
691 self.shape
692 }
693
694 pub fn scale(&self) -> f64 {
696 self.scale
697 }
698
699 pub fn mean(&self) -> f64 {
701 self.scale * special::gamma(1.0 + 1.0 / self.shape)
702 }
703
704 pub fn variance(&self) -> f64 {
706 let g1 = special::gamma(1.0 + 1.0 / self.shape);
707 let g2 = special::gamma(1.0 + 2.0 / self.shape);
708 self.scale * self.scale * (g2 - g1 * g1)
709 }
710
711 pub fn pdf(&self, t: f64) -> f64 {
713 if t < 0.0 {
714 return 0.0;
715 }
716 if t == 0.0 {
717 return if self.shape < 1.0 {
718 f64::INFINITY
719 } else if (self.shape - 1.0).abs() < f64::EPSILON {
720 1.0 / self.scale
721 } else {
722 0.0
723 };
724 }
725 let z = t / self.scale;
726 (self.shape / self.scale) * z.powf(self.shape - 1.0) * (-z.powf(self.shape)).exp()
727 }
728
729 pub fn cdf(&self, t: f64) -> f64 {
731 if t <= 0.0 {
732 return 0.0;
733 }
734 let z = t / self.scale;
735 1.0 - (-z.powf(self.shape)).exp()
736 }
737
738 pub fn quantile(&self, p: f64) -> Option<f64> {
742 if !(0.0..1.0).contains(&p) {
743 return None;
744 }
745 if p == 0.0 {
746 return Some(0.0);
747 }
748 Some(self.scale * (-(1.0 - p).ln()).powf(1.0 / self.shape))
749 }
750
751 pub fn hazard_rate(&self, t: f64) -> f64 {
757 if t <= 0.0 {
758 return if t == 0.0 && self.shape >= 1.0 {
759 if (self.shape - 1.0).abs() < f64::EPSILON {
760 1.0 / self.scale
761 } else {
762 0.0
763 }
764 } else {
765 0.0
766 };
767 }
768 let z = t / self.scale;
769 (self.shape / self.scale) * z.powf(self.shape - 1.0)
770 }
771
772 pub fn reliability(&self, t: f64) -> f64 {
774 1.0 - self.cdf(t)
775 }
776}
777
778#[derive(Debug, Clone, Copy)]
797pub struct Exponential {
798 rate: f64,
799}
800
801impl Exponential {
802 pub fn new(rate: f64) -> Result<Self, DistributionError> {
806 positive("rate", rate)?;
807 Ok(Self { rate })
808 }
809
810 pub fn rate(&self) -> f64 {
812 self.rate
813 }
814
815 pub fn mean(&self) -> f64 {
817 1.0 / self.rate
818 }
819
820 pub fn variance(&self) -> f64 {
822 1.0 / (self.rate * self.rate)
823 }
824
825 pub fn pdf(&self, x: f64) -> f64 {
827 if x < 0.0 {
828 0.0
829 } else {
830 self.rate * (-self.rate * x).exp()
831 }
832 }
833
834 pub fn cdf(&self, x: f64) -> f64 {
836 if x < 0.0 {
837 0.0
838 } else {
839 1.0 - (-self.rate * x).exp()
840 }
841 }
842
843 pub fn quantile(&self, p: f64) -> Option<f64> {
847 if !(0.0..=1.0).contains(&p) {
848 return None;
849 }
850 if p == 1.0 {
851 return Some(f64::INFINITY);
852 }
853 Some(-(1.0 - p).ln() / self.rate)
854 }
855
856 pub fn hazard_rate(&self) -> f64 {
858 self.rate
859 }
860
861 pub fn survival(&self, x: f64) -> f64 {
863 1.0 - self.cdf(x)
864 }
865}
866
867#[derive(Debug, Clone, Copy)]
894pub struct GammaDistribution {
895 shape: f64, rate: f64, }
898
899impl GammaDistribution {
900 pub fn new(shape: f64, rate: f64) -> Result<Self, DistributionError> {
904 positive("shape", shape)?;
905 positive("rate", rate)?;
906 Ok(Self { shape, rate })
907 }
908
909 pub fn from_shape_scale(shape: f64, scale: f64) -> Result<Self, DistributionError> {
911 positive("scale", scale)?;
912 Self::new(shape, 1.0 / scale)
913 }
914
915 pub fn shape(&self) -> f64 {
917 self.shape
918 }
919
920 pub fn rate(&self) -> f64 {
922 self.rate
923 }
924
925 pub fn scale(&self) -> f64 {
927 1.0 / self.rate
928 }
929
930 pub fn mean(&self) -> f64 {
932 self.shape / self.rate
933 }
934
935 pub fn variance(&self) -> f64 {
937 self.shape / (self.rate * self.rate)
938 }
939
940 pub fn mode(&self) -> f64 {
942 if self.shape >= 1.0 {
943 (self.shape - 1.0) / self.rate
944 } else {
945 0.0
946 }
947 }
948
949 pub fn pdf(&self, x: f64) -> f64 {
951 if x <= 0.0 {
952 if x == 0.0 && self.shape < 1.0 {
953 return f64::INFINITY;
954 }
955 return 0.0;
956 }
957 let log_pdf = self.shape * self.rate.ln() - special::ln_gamma(self.shape)
958 + (self.shape - 1.0) * x.ln()
959 - self.rate * x;
960 log_pdf.exp()
961 }
962
963 pub fn cdf(&self, x: f64) -> f64 {
967 if x <= 0.0 {
968 return 0.0;
969 }
970 special::regularized_lower_gamma(self.shape, self.rate * x)
971 }
972
973 pub fn quantile(&self, p: f64) -> Option<f64> {
977 if !(0.0..=1.0).contains(&p) {
978 return None;
979 }
980 if p == 0.0 {
981 return Some(0.0);
982 }
983 if p == 1.0 {
984 return Some(f64::INFINITY);
985 }
986
987 let z = crate::special::inverse_normal_cdf(p);
989 let v = 1.0 / (9.0 * self.shape);
990 let chi2_approx = self.shape * (1.0 - v + z * v.sqrt()).powi(3).max(0.001);
991 let mut x = chi2_approx / self.rate;
992
993 for _ in 0..50 {
995 let f = self.cdf(x) - p;
996 let fp = self.pdf(x);
997 if fp < 1e-300 {
998 break;
999 }
1000 let step = f / fp;
1001 x = (x - step).max(1e-15);
1002 if step.abs() < 1e-12 * x {
1003 break;
1004 }
1005 }
1006
1007 Some(x)
1008 }
1009}
1010
1011#[derive(Debug, Clone)]
1040pub struct BetaDistribution {
1041 pub alpha: f64,
1043 pub beta: f64,
1045}
1046
1047impl BetaDistribution {
1048 pub fn new(alpha: f64, beta: f64) -> Result<Self, DistributionError> {
1053 positive("alpha", alpha)?;
1054 positive("beta", beta)?;
1055 Ok(Self { alpha, beta })
1056 }
1057
1058 pub fn mean(&self) -> f64 {
1060 self.alpha / (self.alpha + self.beta)
1061 }
1062
1063 pub fn variance(&self) -> f64 {
1065 let ab = self.alpha + self.beta;
1066 self.alpha * self.beta / (ab * ab * (ab + 1.0))
1067 }
1068
1069 pub fn mode(&self) -> Option<f64> {
1074 if self.alpha > 1.0 && self.beta > 1.0 {
1075 Some((self.alpha - 1.0) / (self.alpha + self.beta - 2.0))
1076 } else {
1077 None
1078 }
1079 }
1080
1081 pub fn pdf(&self, x: f64) -> f64 {
1083 if x <= 0.0 || x >= 1.0 {
1084 return 0.0;
1085 }
1086 let ln_pdf = (self.alpha - 1.0) * x.ln() + (self.beta - 1.0) * (1.0 - x).ln()
1087 - special::ln_beta(self.alpha, self.beta);
1088 ln_pdf.exp()
1089 }
1090
1091 pub fn cdf(&self, x: f64) -> f64 {
1093 if x <= 0.0 {
1094 return 0.0;
1095 }
1096 if x >= 1.0 {
1097 return 1.0;
1098 }
1099 special::regularized_incomplete_beta(x, self.alpha, self.beta)
1100 }
1101
1102 pub fn quantile(&self, p: f64) -> Option<f64> {
1107 if !(0.0..=1.0).contains(&p) {
1108 return None;
1109 }
1110 if p == 0.0 {
1111 return Some(0.0);
1112 }
1113 if (p - 1.0).abs() < 1e-15 {
1114 return Some(1.0);
1115 }
1116
1117 let mut lo = 0.0_f64;
1119 let mut hi = 1.0_f64;
1120
1121 for _ in 0..100 {
1122 let mid = (lo + hi) / 2.0;
1123 if hi - lo < 1e-14 {
1124 break;
1125 }
1126 if self.cdf(mid) < p {
1127 lo = mid;
1128 } else {
1129 hi = mid;
1130 }
1131 }
1132
1133 Some((lo + hi) / 2.0)
1134 }
1135}
1136
1137#[derive(Debug, Clone)]
1168pub struct ChiSquared {
1169 pub k: f64,
1171}
1172
1173impl ChiSquared {
1174 pub fn new(k: f64) -> Result<Self, DistributionError> {
1179 positive("k", k)?;
1180 Ok(Self { k })
1181 }
1182
1183 pub fn mean(&self) -> f64 {
1185 self.k
1186 }
1187
1188 pub fn variance(&self) -> f64 {
1190 2.0 * self.k
1191 }
1192
1193 pub fn pdf(&self, x: f64) -> f64 {
1195 if x <= 0.0 {
1196 return 0.0;
1197 }
1198 let half_k = self.k / 2.0;
1199 let ln_pdf =
1200 (half_k - 1.0) * x.ln() - x / 2.0 - half_k * 2.0_f64.ln() - special::ln_gamma(half_k);
1201 ln_pdf.exp()
1202 }
1203
1204 pub fn cdf(&self, x: f64) -> f64 {
1206 if x <= 0.0 {
1207 return 0.0;
1208 }
1209 special::regularized_lower_gamma(self.k / 2.0, x / 2.0)
1210 }
1211
1212 pub fn quantile(&self, p: f64) -> Option<f64> {
1216 if !(0.0..=1.0).contains(&p) {
1217 return None;
1218 }
1219 if p == 0.0 {
1220 return Some(0.0);
1221 }
1222 if (p - 1.0).abs() < 1e-15 {
1223 return Some(f64::INFINITY);
1224 }
1225
1226 let gamma = GammaDistribution {
1228 shape: self.k / 2.0,
1229 rate: 0.5,
1230 };
1231 gamma.quantile(p)
1232 }
1233}
1234
1235#[cfg(test)]
1240mod tests {
1241 use super::*;
1242
1243 #[test]
1246 fn test_uniform_basic() {
1247 let u = Uniform::new(0.0, 10.0).unwrap();
1248 assert!((u.mean() - 5.0).abs() < 1e-15);
1249 assert!((u.variance() - 100.0 / 12.0).abs() < 1e-10);
1250 }
1251
1252 #[test]
1253 fn test_uniform_cdf() {
1254 let u = Uniform::new(0.0, 10.0).unwrap();
1255 assert_eq!(u.cdf(-1.0), 0.0);
1256 assert!((u.cdf(5.0) - 0.5).abs() < 1e-15);
1257 assert_eq!(u.cdf(11.0), 1.0);
1258 }
1259
1260 #[test]
1261 fn test_uniform_quantile() {
1262 let u = Uniform::new(2.0, 8.0).unwrap();
1263 assert_eq!(u.quantile(0.0), Some(2.0));
1264 assert_eq!(u.quantile(1.0), Some(8.0));
1265 assert!((u.quantile(0.5).unwrap() - 5.0).abs() < 1e-15);
1266 }
1267
1268 #[test]
1269 fn test_uniform_pdf() {
1270 let u = Uniform::new(0.0, 5.0).unwrap();
1271 assert!((u.pdf(2.5) - 0.2).abs() < 1e-15);
1272 assert_eq!(u.pdf(-1.0), 0.0);
1273 }
1274
1275 #[test]
1276 fn test_uniform_invalid() {
1277 assert!(Uniform::new(5.0, 5.0).is_err());
1278 assert!(Uniform::new(5.0, 3.0).is_err());
1279 assert!(Uniform::new(f64::NAN, 5.0).is_err());
1280 }
1281
1282 #[test]
1285 fn test_triangular_mean() {
1286 let t = Triangular::new(0.0, 3.0, 6.0).unwrap();
1287 assert!((t.mean() - 3.0).abs() < 1e-15);
1288 }
1289
1290 #[test]
1291 fn test_triangular_symmetric_variance() {
1292 let t = Triangular::new(0.0, 5.0, 10.0).unwrap();
1293 let expected = (0.0 + 25.0 + 100.0 - 0.0 - 0.0 - 50.0) / 18.0;
1295 assert!((t.variance() - expected).abs() < 1e-10);
1296 }
1297
1298 #[test]
1299 fn test_triangular_cdf() {
1300 let t = Triangular::new(0.0, 5.0, 10.0).unwrap();
1301 assert!((t.cdf(0.0)).abs() < 1e-15);
1302 assert!((t.cdf(10.0) - 1.0).abs() < 1e-15);
1303 assert!((t.cdf(5.0) - 0.5).abs() < 1e-15);
1305 }
1306
1307 #[test]
1308 fn test_triangular_quantile() {
1309 let t = Triangular::new(0.0, 5.0, 10.0).unwrap();
1310 assert!((t.quantile(0.0).unwrap() - 0.0).abs() < 1e-15);
1311 assert!((t.quantile(1.0).unwrap() - 10.0).abs() < 1e-15);
1312 assert!((t.quantile(0.5).unwrap() - 5.0).abs() < 1e-10);
1313 }
1314
1315 #[test]
1316 fn test_triangular_invalid() {
1317 assert!(Triangular::new(5.0, 3.0, 10.0).is_err()); assert!(Triangular::new(0.0, 11.0, 10.0).is_err()); assert!(Triangular::new(5.0, 5.0, 5.0).is_err()); }
1321
1322 #[test]
1325 fn test_normal_standard() {
1326 let n = Normal::new(0.0, 1.0).unwrap();
1327 assert!((n.mean()).abs() < 1e-15);
1328 assert!((n.variance() - 1.0).abs() < 1e-15);
1329 assert!((n.cdf(0.0) - 0.5).abs() < 1e-7);
1330 }
1331
1332 #[test]
1333 fn test_normal_shifted() {
1334 let n = Normal::new(10.0, 2.0).unwrap();
1335 assert!((n.mean() - 10.0).abs() < 1e-15);
1336 assert!((n.variance() - 4.0).abs() < 1e-15);
1337 assert!((n.cdf(10.0) - 0.5).abs() < 1e-7);
1338 }
1339
1340 #[test]
1341 fn test_normal_quantile() {
1342 let n = Normal::new(0.0, 1.0).unwrap();
1343 assert!((n.quantile(0.5).unwrap()).abs() < 0.01);
1344 assert!((n.quantile(0.975).unwrap() - 1.96).abs() < 0.01);
1345 }
1346
1347 #[test]
1348 fn test_normal_invalid() {
1349 assert!(Normal::new(0.0, 0.0).is_err());
1350 assert!(Normal::new(0.0, -1.0).is_err());
1351 }
1352
1353 #[test]
1356 fn test_lognormal_mean() {
1357 let ln = LogNormal::new(0.0, 1.0).unwrap();
1358 let expected = (0.5_f64).exp(); assert!((ln.mean() - expected).abs() < 1e-10);
1360 }
1361
1362 #[test]
1363 fn test_lognormal_cdf() {
1364 let ln = LogNormal::new(0.0, 1.0).unwrap();
1365 assert_eq!(ln.cdf(0.0), 0.0);
1366 assert!((ln.cdf(1.0) - 0.5).abs() < 0.001);
1368 }
1369
1370 #[test]
1371 fn test_lognormal_quantile() {
1372 let ln = LogNormal::new(0.0, 1.0).unwrap();
1373 let q50 = ln.quantile(0.5).unwrap();
1375 assert!((q50 - 1.0).abs() < 0.01);
1376 }
1377
1378 #[test]
1381 fn test_pert_mean() {
1382 let p = Pert::new(1.0, 4.0, 7.0).unwrap();
1383 assert!((p.mean() - 4.0).abs() < 1e-10);
1385 }
1386
1387 #[test]
1388 fn test_pert_symmetric_variance() {
1389 let p = Pert::new(0.0, 5.0, 10.0).unwrap();
1390 let expected = 9.0 / (36.0 * 7.0) * 100.0;
1393 assert!(
1394 (p.variance() - expected).abs() < 1e-10,
1395 "PERT variance: {} vs expected: {}",
1396 p.variance(),
1397 expected
1398 );
1399 }
1400
1401 #[test]
1402 fn test_pert_shape_params() {
1403 let p = Pert::new(0.0, 5.0, 10.0).unwrap();
1404 assert!((p.alpha() - 3.0).abs() < 1e-15);
1407 assert!((p.beta_param() - 3.0).abs() < 1e-15);
1408 }
1409
1410 #[test]
1411 fn test_pert_cdf_bounds() {
1412 let p = Pert::new(1.0, 4.0, 7.0).unwrap();
1413 assert_eq!(p.cdf(0.0), 0.0);
1414 assert_eq!(p.cdf(8.0), 1.0);
1415 let p_sym = Pert::new(0.0, 5.0, 10.0).unwrap();
1417 assert!((p_sym.cdf(5.0) - 0.5).abs() < 0.01);
1418 }
1419
1420 #[test]
1421 fn test_pert_cdf_monotonic() {
1422 let p = Pert::new(0.0, 3.0, 10.0).unwrap();
1423 let mut prev = 0.0;
1424 for i in 0..=100 {
1425 let x = i as f64 * 0.1;
1426 let c = p.cdf(x);
1427 assert!(c >= prev - 1e-15, "CDF not monotonic at x={x}");
1428 prev = c;
1429 }
1430 }
1431
1432 #[test]
1433 fn test_pert_quantile_symmetric_median_is_the_mode() {
1434 let p = Pert::new(0.0, 5.0, 10.0).unwrap();
1435 assert!((p.quantile(0.5).unwrap() - 5.0).abs() < 1e-9);
1436 assert_eq!(p.quantile(0.0), Some(0.0));
1437 assert_eq!(p.quantile(1.0), Some(10.0));
1438 assert_eq!(p.quantile(1.5), None);
1439 }
1440
1441 #[test]
1446 fn test_pert_quantile_inverts_the_cdf_when_skewed() {
1447 let p = Pert::new(0.0, 1.0, 10.0).unwrap();
1448 for &q in &[1e-6, 0.01, 0.05, 0.25, 0.5, 0.75, 0.95, 0.99] {
1449 let x = p.quantile(q).unwrap();
1450 assert!((0.0..=10.0).contains(&x));
1451 assert!(
1452 (p.cdf(x) - q).abs() < 1e-9,
1453 "p={q}: x={x}, cdf={}",
1454 p.cdf(x)
1455 );
1456 }
1457 let normal_approx_at_5 = p.mean() + special::inverse_normal_cdf(0.05) * p.std_dev();
1458 assert!((p.quantile(0.05).unwrap() - 0.300).abs() < 1e-3);
1459 assert!((p.quantile(0.95).unwrap() - 5.4207).abs() < 1e-3);
1460 assert!(
1461 normal_approx_at_5 < 0.0,
1462 "the approximation leaves the support"
1463 );
1464 }
1465
1466 #[test]
1467 fn test_pert_invalid() {
1468 assert!(Pert::new(5.0, 3.0, 10.0).is_err());
1469 assert!(Pert::new(5.0, 5.0, 5.0).is_err());
1470 }
1471
1472 #[test]
1473 fn test_pert_with_lambda() {
1474 let p4 = Pert::with_shape(0.0, 5.0, 10.0, 4.0).unwrap();
1476 let p8 = Pert::with_shape(0.0, 5.0, 10.0, 8.0).unwrap();
1477 assert!(
1478 p8.variance() < p4.variance(),
1479 "higher λ should give lower variance"
1480 );
1481 }
1482
1483 #[test]
1486 fn test_regularized_beta_bounds() {
1487 assert_eq!(special::regularized_incomplete_beta(0.0, 2.0, 3.0), 0.0);
1488 assert_eq!(special::regularized_incomplete_beta(1.0, 2.0, 3.0), 1.0);
1489 }
1490
1491 #[test]
1492 fn test_regularized_beta_symmetric() {
1493 let result = special::regularized_incomplete_beta(0.5, 3.0, 3.0);
1495 assert!(
1496 (result - 0.5).abs() < 1e-8,
1497 "I_0.5(3,3) = {result}, expected 0.5"
1498 );
1499 }
1500
1501 #[test]
1502 fn test_regularized_beta_known_values() {
1503 for &x in &[0.1, 0.3, 0.5, 0.7, 0.9] {
1505 let result = special::regularized_incomplete_beta(x, 1.0, 1.0);
1506 assert!(
1507 (result - x).abs() < 1e-10,
1508 "I_{x}(1,1) = {result}, expected {x}"
1509 );
1510 }
1511
1512 for &x in &[0.1, 0.5, 0.9] {
1514 let result = special::regularized_incomplete_beta(x, 1.0, 3.0);
1515 let expected = 1.0 - (1.0 - x).powi(3);
1516 assert!(
1517 (result - expected).abs() < 1e-10,
1518 "I_{x}(1,3) = {result}, expected {expected}"
1519 );
1520 }
1521 }
1522
1523 #[test]
1526 fn test_ln_gamma_known() {
1527 assert!((special::ln_gamma(1.0)).abs() < 1e-10);
1529 assert!((special::ln_gamma(2.0)).abs() < 1e-10);
1531 assert!((special::ln_gamma(3.0) - 2.0_f64.ln()).abs() < 1e-10);
1533 assert!((special::ln_gamma(5.0) - 24.0_f64.ln()).abs() < 1e-10);
1535 assert!(
1537 (special::ln_gamma(0.5) - std::f64::consts::PI.sqrt().ln()).abs() < 1e-10,
1538 "ln Γ(0.5) = {}, expected {}",
1539 special::ln_gamma(0.5),
1540 std::f64::consts::PI.sqrt().ln()
1541 );
1542 }
1543
1544 #[test]
1547 fn test_weibull_exponential_special_case() {
1548 let w = Weibull::new(1.0, 5.0).unwrap();
1550 assert!((w.mean() - 5.0).abs() < 1e-10);
1551 assert!((w.variance() - 25.0).abs() < 1e-8);
1552 }
1553
1554 #[test]
1555 fn test_weibull_rayleigh_special_case() {
1556 let w = Weibull::new(2.0, 1.0).unwrap();
1558 let expected_mean = std::f64::consts::PI.sqrt() / 2.0;
1559 assert!(
1560 (w.mean() - expected_mean).abs() < 1e-10,
1561 "Weibull(2,1) mean = {}, expected {}",
1562 w.mean(),
1563 expected_mean
1564 );
1565 }
1566
1567 #[test]
1568 fn test_weibull_cdf_known_values() {
1569 let w = Weibull::new(2.0, 10.0).unwrap();
1571 assert!((w.cdf(10.0) - (1.0 - (-1.0_f64).exp())).abs() < 1e-10);
1573 assert_eq!(w.cdf(0.0), 0.0);
1575 assert_eq!(w.cdf(-1.0), 0.0);
1577 }
1578
1579 #[test]
1580 fn test_weibull_pdf_basic() {
1581 let w = Weibull::new(1.0, 1.0).unwrap();
1583 assert!((w.pdf(0.0) - 1.0).abs() < 1e-10);
1584 assert!((w.pdf(1.0) - (-1.0_f64).exp()).abs() < 1e-10);
1585 assert!((w.pdf(2.0) - (-2.0_f64).exp()).abs() < 1e-10);
1586 }
1587
1588 #[test]
1589 fn test_weibull_pdf_negative() {
1590 let w = Weibull::new(2.0, 5.0).unwrap();
1591 assert_eq!(w.pdf(-1.0), 0.0);
1592 }
1593
1594 #[test]
1595 fn test_weibull_quantile_roundtrip() {
1596 let w = Weibull::new(2.5, 100.0).unwrap();
1597 for &p in &[0.1, 0.25, 0.5, 0.75, 0.9] {
1598 let t = w.quantile(p).unwrap();
1599 let p_back = w.cdf(t);
1600 assert!(
1601 (p_back - p).abs() < 1e-10,
1602 "roundtrip: p={p} -> t={t} -> p_back={p_back}"
1603 );
1604 }
1605 }
1606
1607 #[test]
1608 fn test_weibull_quantile_edge_cases() {
1609 let w = Weibull::new(2.0, 10.0).unwrap();
1610 assert_eq!(w.quantile(0.0), Some(0.0));
1611 assert_eq!(w.quantile(1.0), None);
1612 assert_eq!(w.quantile(-0.1), None);
1613 }
1614
1615 #[test]
1616 fn test_weibull_reliability() {
1617 let w = Weibull::new(2.0, 10.0).unwrap();
1618 for &t in &[1.0, 5.0, 10.0, 20.0] {
1620 let r = w.reliability(t);
1621 let f = w.cdf(t);
1622 assert!((r + f - 1.0).abs() < 1e-14);
1623 }
1624 }
1625
1626 #[test]
1627 fn test_weibull_hazard_rate() {
1628 let w = Weibull::new(1.0, 5.0).unwrap();
1630 for &t in &[1.0, 5.0, 10.0] {
1631 assert!((w.hazard_rate(t) - 0.2).abs() < 1e-10);
1632 }
1633 let w2 = Weibull::new(2.0, 10.0).unwrap();
1635 assert!((w2.hazard_rate(5.0) - 2.0 * 5.0 / 100.0).abs() < 1e-10);
1636 }
1637
1638 #[test]
1639 fn test_weibull_invalid() {
1640 assert!(Weibull::new(0.0, 1.0).is_err());
1641 assert!(Weibull::new(-1.0, 1.0).is_err());
1642 assert!(Weibull::new(1.0, 0.0).is_err());
1643 assert!(Weibull::new(1.0, -1.0).is_err());
1644 assert!(Weibull::new(f64::NAN, 1.0).is_err());
1645 assert!(Weibull::new(1.0, f64::INFINITY).is_err());
1646 }
1647
1648 #[test]
1651 fn test_exponential_basic() {
1652 let e = Exponential::new(0.5).unwrap();
1653 assert!((e.rate() - 0.5).abs() < 1e-10);
1654 assert!((e.mean() - 2.0).abs() < 1e-10);
1655 assert!((e.variance() - 4.0).abs() < 1e-10);
1656 }
1657
1658 #[test]
1659 fn test_exponential_pdf() {
1660 let e = Exponential::new(1.0).unwrap();
1661 assert!((e.pdf(0.0) - 1.0).abs() < 1e-10);
1662 assert!((e.pdf(1.0) - (-1.0_f64).exp()).abs() < 1e-10);
1663 assert_eq!(e.pdf(-1.0), 0.0);
1664 }
1665
1666 #[test]
1667 fn test_exponential_cdf() {
1668 let e = Exponential::new(1.0).unwrap();
1669 assert_eq!(e.cdf(-1.0), 0.0);
1670 assert_eq!(e.cdf(0.0), 0.0);
1671 assert!((e.cdf(1.0) - (1.0 - (-1.0_f64).exp())).abs() < 1e-10);
1672 }
1673
1674 #[test]
1675 fn test_exponential_quantile() {
1676 let e = Exponential::new(2.0).unwrap();
1677 assert_eq!(e.quantile(0.0), Some(0.0));
1678 assert_eq!(e.quantile(1.0), Some(f64::INFINITY));
1679 let q = e.quantile(0.5).unwrap();
1680 assert!((q - 2.0_f64.ln() / 2.0).abs() < 1e-10);
1682 assert!(e.quantile(-0.1).is_none());
1683 assert!(e.quantile(1.1).is_none());
1684 }
1685
1686 #[test]
1687 fn test_exponential_quantile_roundtrip() {
1688 let e = Exponential::new(3.0).unwrap();
1689 for &p in &[0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99] {
1690 let x = e.quantile(p).unwrap();
1691 let p_back = e.cdf(x);
1692 assert!((p_back - p).abs() < 1e-10, "p={p}, x={x}, p_back={p_back}");
1693 }
1694 }
1695
1696 #[test]
1697 fn test_exponential_memoryless() {
1698 let e = Exponential::new(1.5).unwrap();
1700 let s = 2.0;
1701 let t = 3.0;
1702 let lhs = e.survival(s + t) / e.survival(s);
1703 let rhs = e.survival(t);
1704 assert!((lhs - rhs).abs() < 1e-10);
1705 }
1706
1707 #[test]
1708 fn test_exponential_invalid() {
1709 assert!(Exponential::new(0.0).is_err());
1710 assert!(Exponential::new(-1.0).is_err());
1711 assert!(Exponential::new(f64::NAN).is_err());
1712 assert!(Exponential::new(f64::INFINITY).is_err());
1713 }
1714
1715 #[test]
1718 fn test_gamma_basic() {
1719 let g = GammaDistribution::new(2.0, 1.0).unwrap();
1720 assert!((g.shape() - 2.0).abs() < 1e-10);
1721 assert!((g.rate() - 1.0).abs() < 1e-10);
1722 assert!((g.scale() - 1.0).abs() < 1e-10);
1723 assert!((g.mean() - 2.0).abs() < 1e-10);
1724 assert!((g.variance() - 2.0).abs() < 1e-10);
1725 assert!((g.mode() - 1.0).abs() < 1e-10);
1726 }
1727
1728 #[test]
1729 fn test_gamma_from_shape_scale() {
1730 let g = GammaDistribution::from_shape_scale(3.0, 2.0).unwrap();
1731 assert!((g.shape() - 3.0).abs() < 1e-10);
1732 assert!((g.rate() - 0.5).abs() < 1e-10);
1733 assert!((g.mean() - 6.0).abs() < 1e-10); }
1735
1736 #[test]
1737 fn test_gamma_pdf_integral() {
1738 let g = GammaDistribution::new(3.0, 2.0).unwrap();
1740 let n = 20_000;
1741 let dt = 20.0 / n as f64;
1742 let integral: f64 = (0..n)
1743 .map(|i| {
1744 let x = (i as f64 + 0.5) * dt;
1745 g.pdf(x) * dt
1746 })
1747 .sum();
1748 assert!((integral - 1.0).abs() < 0.01, "PDF integral = {integral}");
1749 }
1750
1751 #[test]
1752 fn test_gamma_cdf_known() {
1753 let g = GammaDistribution::new(1.0, 1.0).unwrap();
1755 let expected = 1.0 - (-1.0_f64).exp();
1756 assert!((g.cdf(1.0) - expected).abs() < 1e-10);
1757 }
1758
1759 #[test]
1760 fn test_gamma_cdf_chi2() {
1761 let g = GammaDistribution::new(1.0, 0.5).unwrap(); let x = 4.0;
1765 let expected = 1.0 - (-0.5_f64 * x).exp();
1766 assert!(
1767 (g.cdf(x) - expected).abs() < 1e-8,
1768 "CDF({x}) = {} vs {expected}",
1769 g.cdf(x)
1770 );
1771 }
1772
1773 #[test]
1774 fn test_gamma_quantile_roundtrip() {
1775 let g = GammaDistribution::new(5.0, 2.0).unwrap();
1776 for &p in &[0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99] {
1777 let x = g.quantile(p).unwrap();
1778 let p_back = g.cdf(x);
1779 assert!((p_back - p).abs() < 1e-6, "p={p}, x={x}, p_back={p_back}");
1780 }
1781 }
1782
1783 #[test]
1784 fn test_gamma_mode_small_shape() {
1785 let g = GammaDistribution::new(0.5, 1.0).unwrap();
1786 assert_eq!(g.mode(), 0.0); }
1788
1789 #[test]
1790 fn test_gamma_invalid() {
1791 assert!(GammaDistribution::new(0.0, 1.0).is_err());
1792 assert!(GammaDistribution::new(-1.0, 1.0).is_err());
1793 assert!(GammaDistribution::new(1.0, 0.0).is_err());
1794 assert!(GammaDistribution::new(1.0, -1.0).is_err());
1795 assert!(GammaDistribution::new(f64::NAN, 1.0).is_err());
1796 }
1797
1798 #[test]
1801 fn test_beta_mean_variance() {
1802 let b = BetaDistribution::new(2.0, 5.0).unwrap();
1803 assert!((b.mean() - 2.0 / 7.0).abs() < 1e-10);
1805 let expected_var = 2.0 * 5.0 / (7.0 * 7.0 * 8.0);
1807 assert!((b.variance() - expected_var).abs() < 1e-10);
1808 }
1809
1810 #[test]
1811 fn test_beta_symmetric() {
1812 let b = BetaDistribution::new(3.0, 3.0).unwrap();
1814 assert!((b.mean() - 0.5).abs() < 1e-10);
1815 assert!((b.mode().unwrap() - 0.5).abs() < 1e-10);
1816 assert!((b.pdf(0.3) - b.pdf(0.7)).abs() < 1e-10);
1818 }
1819
1820 #[test]
1821 fn test_beta_uniform_special_case() {
1822 let b = BetaDistribution::new(1.0, 1.0).unwrap();
1824 assert!((b.mean() - 0.5).abs() < 1e-10);
1825 assert!((b.cdf(0.5) - 0.5).abs() < 1e-10);
1826 assert!((b.cdf(0.25) - 0.25).abs() < 1e-10);
1827 }
1828
1829 #[test]
1830 fn test_beta_cdf_boundaries() {
1831 let b = BetaDistribution::new(2.0, 3.0).unwrap();
1832 assert_eq!(b.cdf(0.0), 0.0);
1833 assert_eq!(b.cdf(-1.0), 0.0);
1834 assert_eq!(b.cdf(1.0), 1.0);
1835 assert_eq!(b.cdf(2.0), 1.0);
1836 }
1837
1838 #[test]
1839 fn test_beta_pdf_integral() {
1840 let b = BetaDistribution::new(2.0, 5.0).unwrap();
1841 let n = 10_000;
1842 let dt = 1.0 / n as f64;
1843 let integral: f64 = (0..n)
1844 .map(|i| {
1845 let x = (i as f64 + 0.5) * dt;
1846 b.pdf(x) * dt
1847 })
1848 .sum();
1849 assert!((integral - 1.0).abs() < 0.01, "PDF integral = {integral}");
1850 }
1851
1852 #[test]
1853 fn test_beta_quantile_roundtrip() {
1854 let b = BetaDistribution::new(2.0, 5.0).unwrap();
1855 for &p in &[0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99] {
1856 let x = b.quantile(p).unwrap();
1857 let p_back = b.cdf(x);
1858 assert!((p_back - p).abs() < 1e-6, "p={p}, x={x}, p_back={p_back}");
1859 }
1860 }
1861
1862 #[test]
1863 fn test_beta_mode() {
1864 let b = BetaDistribution::new(5.0, 3.0).unwrap();
1865 assert!((b.mode().unwrap() - 2.0 / 3.0).abs() < 1e-10);
1867 }
1868
1869 #[test]
1870 fn test_beta_mode_undefined() {
1871 let b = BetaDistribution::new(0.5, 0.5).unwrap();
1873 assert!(b.mode().is_none());
1874 }
1875
1876 #[test]
1877 fn test_beta_invalid() {
1878 assert!(BetaDistribution::new(0.0, 1.0).is_err());
1879 assert!(BetaDistribution::new(-1.0, 1.0).is_err());
1880 assert!(BetaDistribution::new(1.0, 0.0).is_err());
1881 assert!(BetaDistribution::new(f64::NAN, 1.0).is_err());
1882 }
1883
1884 #[test]
1887 fn test_chi2_mean_variance() {
1888 let chi2 = ChiSquared::new(5.0).unwrap();
1889 assert!((chi2.mean() - 5.0).abs() < 1e-10);
1890 assert!((chi2.variance() - 10.0).abs() < 1e-10);
1891 }
1892
1893 #[test]
1894 fn test_chi2_exp_special_case() {
1895 let chi2 = ChiSquared::new(2.0).unwrap();
1897 let x = 4.0;
1898 let expected = 1.0 - (-x / 2.0_f64).exp();
1899 assert!(
1900 (chi2.cdf(x) - expected).abs() < 1e-8,
1901 "CDF({x}) = {} vs {expected}",
1902 chi2.cdf(x)
1903 );
1904 }
1905
1906 #[test]
1907 fn test_chi2_cdf_boundaries() {
1908 let chi2 = ChiSquared::new(3.0).unwrap();
1909 assert_eq!(chi2.cdf(0.0), 0.0);
1910 assert_eq!(chi2.cdf(-1.0), 0.0);
1911 assert!(chi2.cdf(100.0) > 0.999);
1913 }
1914
1915 #[test]
1916 fn test_chi2_pdf_integral() {
1917 let chi2 = ChiSquared::new(4.0).unwrap();
1918 let n = 20_000;
1919 let dt = 30.0 / n as f64;
1920 let integral: f64 = (0..n)
1921 .map(|i| {
1922 let x = (i as f64 + 0.5) * dt;
1923 chi2.pdf(x) * dt
1924 })
1925 .sum();
1926 assert!((integral - 1.0).abs() < 0.01, "PDF integral = {integral}");
1927 }
1928
1929 #[test]
1930 fn test_chi2_quantile_roundtrip() {
1931 let chi2 = ChiSquared::new(5.0).unwrap();
1932 for &p in &[0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99] {
1933 let x = chi2.quantile(p).unwrap();
1934 let p_back = chi2.cdf(x);
1935 assert!((p_back - p).abs() < 1e-6, "p={p}, x={x}, p_back={p_back}");
1936 }
1937 }
1938
1939 #[test]
1940 fn test_chi2_known_quantiles() {
1941 let chi2 = ChiSquared::new(5.0).unwrap();
1944 let q05 = chi2.quantile(0.05).unwrap();
1945 assert!((q05 - 1.1455).abs() < 0.01, "q(0.05) = {q05}");
1946 let q95 = chi2.quantile(0.95).unwrap();
1947 assert!((q95 - 11.0705).abs() < 0.01, "q(0.95) = {q95}");
1948 }
1949
1950 #[test]
1951 fn test_chi2_invalid() {
1952 assert!(ChiSquared::new(0.0).is_err());
1953 assert!(ChiSquared::new(-1.0).is_err());
1954 assert!(ChiSquared::new(f64::NAN).is_err());
1955 }
1956
1957 #[test]
1958 fn test_chi2_gamma_consistency() {
1959 let k = 6.0;
1961 let chi2 = ChiSquared::new(k).unwrap();
1962 let gamma = GammaDistribution::new(k / 2.0, 0.5).unwrap();
1963
1964 let x = 5.0;
1965 assert!(
1966 (chi2.cdf(x) - gamma.cdf(x)).abs() < 1e-10,
1967 "Chi2 and Gamma CDF should match"
1968 );
1969 }
1970
1971 #[test]
1972 fn test_weibull_mean_variance_known() {
1973 let w = Weibull::new(3.6, 1000.0).unwrap();
1976 let mean = w.mean();
1977 assert!(mean > 800.0 && mean < 1000.0, "mean={mean}");
1978 assert!(w.variance() > 0.0);
1979 }
1980
1981 #[test]
1982 fn test_weibull_pdf_integral_approx() {
1983 let w = Weibull::new(2.0, 10.0).unwrap();
1985 let n = 10_000;
1986 let dt = 50.0 / n as f64;
1987 let integral: f64 = (0..n)
1988 .map(|i| {
1989 let t = (i as f64 + 0.5) * dt;
1990 w.pdf(t) * dt
1991 })
1992 .sum();
1993 assert!(
1994 (integral - 1.0).abs() < 0.01,
1995 "PDF integral = {integral}, expected ≈ 1.0"
1996 );
1997 }
1998
1999 #[test]
2008 fn test_normal_cdf_reference_values() {
2009 assert!(
2011 (special::standard_normal_cdf(0.0) - 0.5).abs() < 1e-7,
2012 "Φ(0) error"
2013 );
2014 assert!(
2016 (special::standard_normal_cdf(1.96) - 0.975002).abs() < 1e-6,
2017 "Φ(1.96) = {}, expected 0.975002",
2018 special::standard_normal_cdf(1.96)
2019 );
2020 assert!(
2022 (special::standard_normal_cdf(-1.96) - 0.024998).abs() < 1e-6,
2023 "Φ(-1.96) = {}, expected 0.024998",
2024 special::standard_normal_cdf(-1.96)
2025 );
2026 assert!(
2028 (special::standard_normal_cdf(3.0) - 0.998650).abs() < 1e-6,
2029 "Φ(3.0) = {}, expected 0.998650",
2030 special::standard_normal_cdf(3.0)
2031 );
2032 for &x in &[0.5, 1.0, 1.96, 2.5, 3.0] {
2034 let sum = special::standard_normal_cdf(x) + special::standard_normal_cdf(-x);
2035 assert!(
2036 (sum - 1.0).abs() < 1e-14,
2037 "Symmetry violated at x={x}: sum={sum}"
2038 );
2039 }
2040 }
2041
2042 #[test]
2046 fn test_normal_quantile_reference_values() {
2047 assert!(
2049 special::inverse_normal_cdf(0.5).abs() < 5e-4,
2050 "Φ⁻¹(0.5) = {}, expected 0.0",
2051 special::inverse_normal_cdf(0.5)
2052 );
2053 let q975 = special::inverse_normal_cdf(0.975);
2055 assert!(
2056 (q975 - 1.95996).abs() < 5e-4,
2057 "Φ⁻¹(0.975) = {q975}, expected ≈ 1.95996"
2058 );
2059 let q025 = special::inverse_normal_cdf(0.025);
2061 assert!(
2062 (q025 + 1.95996).abs() < 5e-4,
2063 "Φ⁻¹(0.025) = {q025}, expected ≈ -1.95996"
2064 );
2065 for &p in &[0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99] {
2067 let z = special::inverse_normal_cdf(p);
2068 let p_back = special::standard_normal_cdf(z);
2069 assert!(
2070 (p_back - p).abs() < 1e-3,
2071 "Roundtrip failed: p={p}, z={z}, p_back={p_back}"
2072 );
2073 }
2074 }
2075
2076 #[test]
2084 fn test_gamma_reference_values() {
2085 let sqrt_pi = std::f64::consts::PI.sqrt();
2087 assert!(
2088 (special::gamma(0.5) - sqrt_pi).abs() < 1e-7,
2089 "Γ(0.5) = {}, expected √π = {}",
2090 special::gamma(0.5),
2091 sqrt_pi
2092 );
2093 assert!(
2095 (special::gamma(1.0) - 1.0).abs() < 1e-14,
2096 "Γ(1.0) = {}",
2097 special::gamma(1.0)
2098 );
2099 let gamma_1_5 = sqrt_pi / 2.0;
2101 assert!(
2102 (special::gamma(1.5) - gamma_1_5).abs() < 1e-7,
2103 "Γ(1.5) = {}, expected {}",
2104 special::gamma(1.5),
2105 gamma_1_5
2106 );
2107 assert!(
2109 (special::gamma(2.0) - 1.0).abs() < 1e-14,
2110 "Γ(2.0) = {}",
2111 special::gamma(2.0)
2112 );
2113 assert!(
2115 (special::gamma(3.0) - 2.0).abs() < 1e-13,
2116 "Γ(3.0) = {}",
2117 special::gamma(3.0)
2118 );
2119 assert!(
2121 (special::gamma(4.0) - 6.0).abs() < 1e-12,
2122 "Γ(4.0) = {}",
2123 special::gamma(4.0)
2124 );
2125 assert!(
2127 (special::gamma(5.0) - 24.0).abs() < 1e-8,
2128 "Γ(5.0) = {}, expected 24.0",
2129 special::gamma(5.0)
2130 );
2131 }
2132
2133 #[test]
2142 fn test_gamma_pdf_reference_value() {
2143 let g = GammaDistribution::new(2.0, 1.0).unwrap();
2144 let expected = (-1.0_f64).exp(); assert!(
2146 (g.pdf(1.0) - expected).abs() < 1e-5,
2147 "Gamma(2,1).pdf(1) = {}, expected e⁻¹ = {}",
2148 g.pdf(1.0),
2149 expected
2150 );
2151 }
2152}
2153
2154#[cfg(test)]
2155mod proptests {
2156 use super::*;
2157 use proptest::prelude::*;
2158
2159 proptest! {
2160 #![proptest_config(ProptestConfig::with_cases(300))]
2161
2162 #[test]
2165 fn uniform_cdf_in_01(
2166 min in -100.0_f64..0.0,
2167 max in 1.0_f64..100.0,
2168 x in -200.0_f64..200.0,
2169 ) {
2170 let u = Uniform::new(min, max).unwrap();
2171 let c = u.cdf(x);
2172 prop_assert!((0.0..=1.0).contains(&c));
2173 }
2174
2175 #[test]
2176 fn uniform_quantile_roundtrip(
2177 min in -100.0_f64..0.0,
2178 max in 1.0_f64..100.0,
2179 p in 0.0_f64..=1.0,
2180 ) {
2181 let u = Uniform::new(min, max).unwrap();
2182 let x = u.quantile(p).unwrap();
2183 let p_back = u.cdf(x);
2184 prop_assert!((p_back - p).abs() < 1e-12, "roundtrip: p={p} -> x={x} -> p_back={p_back}");
2185 }
2186
2187 #[test]
2190 fn triangular_cdf_in_01(
2191 min in -100.0_f64..-1.0,
2192 mode_frac in 0.0_f64..=1.0,
2193 range in 1.0_f64..100.0,
2194 x in -200.0_f64..200.0,
2195 ) {
2196 let max = min + range;
2197 let mode = min + mode_frac * range;
2198 let t = Triangular::new(min, mode, max).unwrap();
2199 let c = t.cdf(x);
2200 prop_assert!((0.0..=1.0).contains(&c));
2201 }
2202
2203 #[test]
2204 fn triangular_quantile_roundtrip(
2205 min in -50.0_f64..0.0,
2206 mode_frac in 0.01_f64..0.99,
2207 range in 1.0_f64..50.0,
2208 p in 0.001_f64..0.999,
2209 ) {
2210 let max = min + range;
2211 let mode = min + mode_frac * range;
2212 let t = Triangular::new(min, mode, max).unwrap();
2213 let x = t.quantile(p).unwrap();
2214 let p_back = t.cdf(x);
2215 prop_assert!(
2216 (p_back - p).abs() < 1e-8,
2217 "roundtrip: p={p} -> x={x} -> p_back={p_back}"
2218 );
2219 }
2220
2221 #[test]
2224 fn pert_mean_formula(
2225 min in -50.0_f64..0.0,
2226 mode_frac in 0.01_f64..0.99,
2227 range in 1.0_f64..50.0,
2228 ) {
2229 let max = min + range;
2230 let mode = min + mode_frac * range;
2231 let p = Pert::new(min, mode, max).unwrap();
2232 let expected = (min + 4.0 * mode + max) / 6.0;
2233 prop_assert!(
2234 (p.mean() - expected).abs() < 1e-10,
2235 "PERT mean: {} vs expected: {}",
2236 p.mean(),
2237 expected
2238 );
2239 }
2240
2241 #[test]
2242 fn pert_variance_positive(
2243 min in -50.0_f64..0.0,
2244 mode_frac in 0.01_f64..0.99,
2245 range in 1.0_f64..50.0,
2246 ) {
2247 let max = min + range;
2248 let mode = min + mode_frac * range;
2249 let p = Pert::new(min, mode, max).unwrap();
2250 prop_assert!(p.variance() > 0.0);
2251 }
2252
2253 #[test]
2254 fn pert_cdf_monotonic(
2255 min in -50.0_f64..0.0,
2256 mode_frac in 0.05_f64..0.95,
2257 range in 2.0_f64..50.0,
2258 ) {
2259 let max = min + range;
2260 let mode = min + mode_frac * range;
2261 let p = Pert::new(min, mode, max).unwrap();
2262 let mut prev = 0.0;
2263 for i in 0..=20 {
2264 let x = min + (i as f64 / 20.0) * range;
2265 let c = p.cdf(x);
2266 prop_assert!(c >= prev - 1e-10, "CDF not monotonic at x={x}");
2267 prev = c;
2268 }
2269 }
2270
2271 #[test]
2274 fn weibull_cdf_in_01(
2275 shape in 0.1_f64..10.0,
2276 scale in 0.1_f64..100.0,
2277 t in 0.0_f64..200.0,
2278 ) {
2279 let w = Weibull::new(shape, scale).unwrap();
2280 let c = w.cdf(t);
2281 prop_assert!((0.0..=1.0).contains(&c), "CDF({t}) = {c} out of [0,1]");
2282 }
2283
2284 #[test]
2285 fn weibull_quantile_roundtrip(
2286 shape in 0.5_f64..10.0,
2287 scale in 1.0_f64..100.0,
2288 p in 0.001_f64..0.999,
2289 ) {
2290 let w = Weibull::new(shape, scale).unwrap();
2291 let t = w.quantile(p).unwrap();
2292 let p_back = w.cdf(t);
2293 prop_assert!(
2294 (p_back - p).abs() < 1e-8,
2295 "roundtrip: p={p} -> t={t} -> p_back={p_back}"
2296 );
2297 }
2298
2299 #[test]
2300 fn weibull_pdf_non_negative(
2301 shape in 0.1_f64..10.0,
2302 scale in 0.1_f64..100.0,
2303 t in 0.0_f64..200.0,
2304 ) {
2305 let w = Weibull::new(shape, scale).unwrap();
2306 prop_assert!(w.pdf(t) >= 0.0, "PDF({t}) must be >= 0");
2307 }
2308
2309 #[test]
2310 fn weibull_reliability_plus_cdf_is_one(
2311 shape in 0.5_f64..5.0,
2312 scale in 1.0_f64..50.0,
2313 t in 0.001_f64..100.0,
2314 ) {
2315 let w = Weibull::new(shape, scale).unwrap();
2316 let sum = w.cdf(t) + w.reliability(t);
2317 prop_assert!(
2318 (sum - 1.0).abs() < 1e-12,
2319 "CDF + R should = 1, got {sum}"
2320 );
2321 }
2322
2323 #[test]
2324 fn weibull_variance_positive(
2325 shape in 0.1_f64..10.0,
2326 scale in 0.1_f64..100.0,
2327 ) {
2328 let w = Weibull::new(shape, scale).unwrap();
2329 prop_assert!(w.variance() > 0.0, "variance must be positive");
2330 }
2331
2332 #[test]
2335 fn exponential_cdf_in_01(
2336 rate in 0.01_f64..10.0,
2337 x in 0.0_f64..100.0,
2338 ) {
2339 let e = Exponential::new(rate).unwrap();
2340 let c = e.cdf(x);
2341 prop_assert!((0.0..=1.0).contains(&c), "CDF({x}) = {c}");
2342 }
2343
2344 #[test]
2345 fn exponential_quantile_roundtrip(
2346 rate in 0.01_f64..10.0,
2347 p in 0.001_f64..0.999,
2348 ) {
2349 let e = Exponential::new(rate).unwrap();
2350 let x = e.quantile(p).unwrap();
2351 let p_back = e.cdf(x);
2352 prop_assert!((p_back - p).abs() < 1e-10);
2353 }
2354
2355 #[test]
2358 fn gamma_cdf_in_01(
2359 shape in 0.5_f64..10.0,
2360 rate in 0.1_f64..10.0,
2361 x in 0.001_f64..50.0,
2362 ) {
2363 let g = GammaDistribution::new(shape, rate).unwrap();
2364 let c = g.cdf(x);
2365 prop_assert!((0.0..=1.0).contains(&c), "CDF({x}) = {c}");
2366 }
2367
2368 #[test]
2369 fn gamma_variance_positive(
2370 shape in 0.1_f64..10.0,
2371 rate in 0.1_f64..10.0,
2372 ) {
2373 let g = GammaDistribution::new(shape, rate).unwrap();
2374 prop_assert!(g.variance() > 0.0, "variance must be positive");
2375 }
2376
2377 #[test]
2380 fn beta_cdf_in_01(
2381 alpha in 0.5_f64..10.0,
2382 beta_param in 0.5_f64..10.0,
2383 x in 0.001_f64..0.999,
2384 ) {
2385 let b = BetaDistribution::new(alpha, beta_param).unwrap();
2386 let c = b.cdf(x);
2387 prop_assert!((0.0..=1.0).contains(&c), "CDF({x}) = {c}");
2388 }
2389
2390 #[test]
2391 fn beta_quantile_roundtrip(
2392 alpha in 1.0_f64..10.0,
2393 beta_param in 1.0_f64..10.0,
2394 p in 0.01_f64..0.99,
2395 ) {
2396 let b = BetaDistribution::new(alpha, beta_param).unwrap();
2397 let x = b.quantile(p).unwrap();
2398 let p_back = b.cdf(x);
2399 prop_assert!((p_back - p).abs() < 1e-5, "p={p}, p_back={p_back}");
2400 }
2401
2402 #[test]
2405 fn chi2_cdf_in_01(
2406 k in 1.0_f64..20.0,
2407 x in 0.001_f64..50.0,
2408 ) {
2409 let chi2 = ChiSquared::new(k).unwrap();
2410 let c = chi2.cdf(x);
2411 prop_assert!((0.0..=1.0).contains(&c), "CDF({x}) = {c}");
2412 }
2413
2414 #[test]
2415 fn chi2_variance_positive(
2416 k in 0.5_f64..50.0,
2417 ) {
2418 let chi2 = ChiSquared::new(k).unwrap();
2419 prop_assert!(chi2.variance() > 0.0, "variance must be positive");
2420 }
2421 }
2422}