1use crate::special;
27
28mod sampling;
29
30pub use sampling::Sample;
31
32#[derive(Debug, Clone, PartialEq)]
35pub enum DistributionError {
36 NotFinite {
38 parameter: &'static str,
40 },
41 NotPositive {
44 parameter: &'static str,
46 got: f64,
48 },
49 Unordered {
52 parameter: &'static str,
55 relation: &'static str,
57 },
58}
59
60impl std::fmt::Display for DistributionError {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 match self {
63 DistributionError::NotFinite { parameter } => {
64 write!(f, "{parameter} must be a finite number")
65 }
66 DistributionError::NotPositive { parameter, got } => {
67 write!(f, "{parameter} must be greater than 0, got {got}")
68 }
69 DistributionError::Unordered {
70 parameter,
71 relation,
72 } => write!(f, "{parameter} breaks the required order {relation}"),
73 }
74 }
75}
76
77fn finite(params: &[(&'static str, f64)]) -> Result<(), DistributionError> {
79 match params.iter().find(|(_, x)| !x.is_finite()) {
80 Some(&(parameter, _)) => Err(DistributionError::NotFinite { parameter }),
81 None => Ok(()),
82 }
83}
84
85fn positive(parameter: &'static str, x: f64) -> Result<(), DistributionError> {
87 finite(&[(parameter, x)])?;
88 if x > 0.0 {
89 Ok(())
90 } else {
91 Err(DistributionError::NotPositive { parameter, got: x })
92 }
93}
94
95fn ordered(min: f64, mode: Option<f64>, max: f64) -> Result<(), DistributionError> {
97 if min >= max {
98 return Err(DistributionError::Unordered {
99 parameter: "max",
100 relation: "min < max",
101 });
102 }
103 match mode {
104 Some(m) if m < min || m > max => Err(DistributionError::Unordered {
105 parameter: "mode",
106 relation: "min ≤ mode ≤ max",
107 }),
108 _ => Ok(()),
109 }
110}
111
112impl std::error::Error for DistributionError {}
113
114#[derive(Debug, Clone, PartialEq)]
126pub struct Uniform {
127 min: f64,
128 max: f64,
129}
130
131impl Uniform {
132 pub fn new(min: f64, max: f64) -> Result<Self, DistributionError> {
137 finite(&[("min", min), ("max", max)])?;
138 ordered(min, None, max)?;
139 Ok(Self { min, max })
140 }
141
142 pub fn min(&self) -> f64 {
143 self.min
144 }
145
146 pub fn max(&self) -> f64 {
147 self.max
148 }
149
150 pub fn mean(&self) -> f64 {
151 (self.min + self.max) / 2.0
152 }
153
154 pub fn variance(&self) -> f64 {
155 let range = self.max - self.min;
156 range * range / 12.0
157 }
158
159 pub fn cdf(&self, x: f64) -> f64 {
161 if x <= self.min {
162 0.0
163 } else if x >= self.max {
164 1.0
165 } else {
166 (x - self.min) / (self.max - self.min)
167 }
168 }
169
170 pub fn quantile(&self, p: f64) -> Option<f64> {
174 if !(0.0..=1.0).contains(&p) {
175 return None;
176 }
177 Some(self.min + p * (self.max - self.min))
178 }
179
180 pub fn pdf(&self, x: f64) -> f64 {
182 if x >= self.min && x <= self.max {
183 1.0 / (self.max - self.min)
184 } else {
185 0.0
186 }
187 }
188}
189
190#[derive(Debug, Clone, PartialEq)]
205pub struct Triangular {
206 min: f64,
207 mode: f64,
208 max: f64,
209}
210
211impl Triangular {
212 pub fn new(min: f64, mode: f64, max: f64) -> Result<Self, DistributionError> {
217 finite(&[("min", min), ("mode", mode), ("max", max)])?;
218 ordered(min, Some(mode), max)?;
219 Ok(Self { min, mode, max })
220 }
221
222 pub fn min(&self) -> f64 {
223 self.min
224 }
225
226 pub fn mode(&self) -> f64 {
227 self.mode
228 }
229
230 pub fn max(&self) -> f64 {
231 self.max
232 }
233
234 pub fn mean(&self) -> f64 {
236 (self.min + self.mode + self.max) / 3.0
237 }
238
239 pub fn variance(&self) -> f64 {
241 let (a, b, c) = (self.min, self.mode, self.max);
242 (a * a + b * b + c * c - a * b - a * c - b * c) / 18.0
243 }
244
245 pub fn pdf(&self, x: f64) -> f64 {
253 let (a, b, c) = (self.min, self.mode, self.max);
254 if x < a || x > c {
255 0.0
256 } else if x <= b {
257 2.0 * (x - a) / ((c - a) * (b - a).max(f64::MIN_POSITIVE))
258 } else {
259 2.0 * (c - x) / ((c - a) * (c - b).max(f64::MIN_POSITIVE))
260 }
261 }
262
263 pub fn cdf(&self, x: f64) -> f64 {
270 let (a, b, c) = (self.min, self.mode, self.max);
271 if x <= a {
272 0.0
273 } else if x <= b {
274 (x - a) * (x - a) / ((c - a) * (b - a).max(f64::MIN_POSITIVE))
275 } else if x < c {
276 1.0 - (c - x) * (c - x) / ((c - a) * (c - b).max(f64::MIN_POSITIVE))
277 } else {
278 1.0
279 }
280 }
281
282 pub fn quantile(&self, p: f64) -> Option<f64> {
291 if !(0.0..=1.0).contains(&p) {
292 return None;
293 }
294 let (a, b, c) = (self.min, self.mode, self.max);
295 let fc = (b - a) / (c - a); if p < fc {
297 Some(a + ((c - a) * (b - a) * p).sqrt())
298 } else {
299 Some(c - ((c - a) * (c - b) * (1.0 - p)).sqrt())
300 }
301 }
302}
303
304#[derive(Debug, Clone, PartialEq)]
316pub struct Normal {
317 mu: f64,
318 sigma: f64,
319}
320
321impl Normal {
322 pub fn new(mu: f64, sigma: f64) -> Result<Self, DistributionError> {
327 finite(&[("mu", mu)])?;
328 positive("sigma", sigma)?;
329 Ok(Self { mu, sigma })
330 }
331
332 pub fn mu(&self) -> f64 {
333 self.mu
334 }
335
336 pub fn sigma(&self) -> f64 {
337 self.sigma
338 }
339
340 pub fn mean(&self) -> f64 {
341 self.mu
342 }
343
344 pub fn variance(&self) -> f64 {
345 self.sigma * self.sigma
346 }
347
348 pub fn std_dev(&self) -> f64 {
349 self.sigma
350 }
351
352 pub fn pdf(&self, x: f64) -> f64 {
354 let z = (x - self.mu) / self.sigma;
355 special::standard_normal_pdf(z) / self.sigma
356 }
357
358 pub fn cdf(&self, x: f64) -> f64 {
360 let z = (x - self.mu) / self.sigma;
361 special::standard_normal_cdf(z)
362 }
363
364 pub fn quantile(&self, p: f64) -> Option<f64> {
368 if p <= 0.0 || p >= 1.0 {
369 return None;
370 }
371 Some(self.mu + self.sigma * special::inverse_normal_cdf(p))
372 }
373}
374
375#[derive(Debug, Clone, PartialEq)]
390pub struct LogNormal {
391 mu: f64,
392 sigma: f64,
393}
394
395impl LogNormal {
396 pub fn new(mu: f64, sigma: f64) -> Result<Self, DistributionError> {
403 finite(&[("mu", mu)])?;
404 positive("sigma", sigma)?;
405 Ok(Self { mu, sigma })
406 }
407
408 pub fn mu(&self) -> f64 {
409 self.mu
410 }
411
412 pub fn sigma(&self) -> f64 {
413 self.sigma
414 }
415
416 pub fn mean(&self) -> f64 {
418 (self.mu + self.sigma * self.sigma / 2.0).exp()
419 }
420
421 pub fn variance(&self) -> f64 {
423 let s2 = self.sigma * self.sigma;
424 (s2.exp() - 1.0) * (2.0 * self.mu + s2).exp()
425 }
426
427 pub fn pdf(&self, x: f64) -> f64 {
429 if x <= 0.0 {
430 return 0.0;
431 }
432 let ln_x = x.ln();
433 let z = (ln_x - self.mu) / self.sigma;
434 special::standard_normal_pdf(z) / (x * self.sigma)
435 }
436
437 pub fn cdf(&self, x: f64) -> f64 {
439 if x <= 0.0 {
440 return 0.0;
441 }
442 let z = (x.ln() - self.mu) / self.sigma;
443 special::standard_normal_cdf(z)
444 }
445
446 pub fn quantile(&self, p: f64) -> Option<f64> {
450 if p <= 0.0 || p >= 1.0 {
451 return None;
452 }
453 Some((self.mu + self.sigma * special::inverse_normal_cdf(p)).exp())
454 }
455}
456
457#[derive(Debug, Clone, PartialEq)]
493pub struct Pert {
494 min: f64,
495 mode: f64,
496 max: f64,
497 alpha: f64,
498 beta: f64,
499}
500
501impl Pert {
502 pub fn new(min: f64, mode: f64, max: f64) -> Result<Self, DistributionError> {
507 Self::with_shape(min, mode, max, 4.0)
508 }
509
510 pub fn with_shape(
520 min: f64,
521 mode: f64,
522 max: f64,
523 lambda: f64,
524 ) -> Result<Self, DistributionError> {
525 finite(&[("min", min), ("mode", mode), ("max", max)])?;
526 ordered(min, Some(mode), max)?;
527 positive("lambda", lambda)?;
528
529 let range = max - min;
530 let alpha = 1.0 + lambda * (mode - min) / range;
531 let beta = 1.0 + lambda * (max - mode) / range;
532
533 Ok(Self {
534 min,
535 mode,
536 max,
537 alpha,
538 beta,
539 })
540 }
541
542 pub fn min(&self) -> f64 {
543 self.min
544 }
545
546 pub fn mode(&self) -> f64 {
547 self.mode
548 }
549
550 pub fn max(&self) -> f64 {
551 self.max
552 }
553
554 pub fn alpha(&self) -> f64 {
555 self.alpha
556 }
557
558 pub fn beta_param(&self) -> f64 {
559 self.beta
560 }
561
562 pub fn mean(&self) -> f64 {
566 let ab = self.alpha + self.beta;
567 self.min + (self.alpha / ab) * (self.max - self.min)
569 }
570
571 pub fn variance(&self) -> f64 {
577 let ab = self.alpha + self.beta;
578 let range = self.max - self.min;
579 (self.alpha * self.beta) / (ab * ab * (ab + 1.0)) * range * range
580 }
581
582 pub fn std_dev(&self) -> f64 {
584 self.variance().sqrt()
585 }
586
587 pub fn cdf(&self, x: f64) -> f64 {
592 if x <= self.min {
593 return 0.0;
594 }
595 if x >= self.max {
596 return 1.0;
597 }
598 let t = (x - self.min) / (self.max - self.min);
599 special::regularized_incomplete_beta(t, self.alpha, self.beta)
600 }
601
602 pub fn quantile(&self, p: f64) -> Option<f64> {
620 if !(0.0..=1.0).contains(&p) {
621 return None;
622 }
623 let standard = BetaDistribution::new(self.alpha, self.beta)
624 .expect("PERT shape parameters are positive by construction")
625 .quantile(p)?;
626 Some(self.min + (self.max - self.min) * standard)
627 }
628}
629
630#[derive(Debug, Clone, PartialEq)]
653pub struct Weibull {
654 shape: f64, scale: f64, }
657
658impl Weibull {
659 pub fn new(shape: f64, scale: f64) -> Result<Self, DistributionError> {
664 positive("shape", shape)?;
665 positive("scale", scale)?;
666 Ok(Self { shape, scale })
667 }
668
669 pub fn shape(&self) -> f64 {
671 self.shape
672 }
673
674 pub fn scale(&self) -> f64 {
676 self.scale
677 }
678
679 pub fn mean(&self) -> f64 {
681 self.scale * special::gamma(1.0 + 1.0 / self.shape)
682 }
683
684 pub fn variance(&self) -> f64 {
686 let g1 = special::gamma(1.0 + 1.0 / self.shape);
687 let g2 = special::gamma(1.0 + 2.0 / self.shape);
688 self.scale * self.scale * (g2 - g1 * g1)
689 }
690
691 pub fn pdf(&self, t: f64) -> f64 {
693 if t < 0.0 {
694 return 0.0;
695 }
696 if t == 0.0 {
697 return if self.shape < 1.0 {
698 f64::INFINITY
699 } else if (self.shape - 1.0).abs() < f64::EPSILON {
700 1.0 / self.scale
701 } else {
702 0.0
703 };
704 }
705 let z = t / self.scale;
706 (self.shape / self.scale) * z.powf(self.shape - 1.0) * (-z.powf(self.shape)).exp()
707 }
708
709 pub fn cdf(&self, t: f64) -> f64 {
711 if t <= 0.0 {
712 return 0.0;
713 }
714 let z = t / self.scale;
715 1.0 - (-z.powf(self.shape)).exp()
716 }
717
718 pub fn quantile(&self, p: f64) -> Option<f64> {
722 if !(0.0..1.0).contains(&p) {
723 return None;
724 }
725 if p == 0.0 {
726 return Some(0.0);
727 }
728 Some(self.scale * (-(1.0 - p).ln()).powf(1.0 / self.shape))
729 }
730
731 pub fn hazard_rate(&self, t: f64) -> f64 {
737 if t <= 0.0 {
738 return if t == 0.0 && self.shape >= 1.0 {
739 if (self.shape - 1.0).abs() < f64::EPSILON {
740 1.0 / self.scale
741 } else {
742 0.0
743 }
744 } else {
745 0.0
746 };
747 }
748 let z = t / self.scale;
749 (self.shape / self.scale) * z.powf(self.shape - 1.0)
750 }
751
752 pub fn reliability(&self, t: f64) -> f64 {
754 1.0 - self.cdf(t)
755 }
756}
757
758#[derive(Debug, Clone, Copy)]
777pub struct Exponential {
778 rate: f64,
779}
780
781impl Exponential {
782 pub fn new(rate: f64) -> Result<Self, DistributionError> {
786 positive("rate", rate)?;
787 Ok(Self { rate })
788 }
789
790 pub fn rate(&self) -> f64 {
792 self.rate
793 }
794
795 pub fn mean(&self) -> f64 {
797 1.0 / self.rate
798 }
799
800 pub fn variance(&self) -> f64 {
802 1.0 / (self.rate * self.rate)
803 }
804
805 pub fn pdf(&self, x: f64) -> f64 {
807 if x < 0.0 {
808 0.0
809 } else {
810 self.rate * (-self.rate * x).exp()
811 }
812 }
813
814 pub fn cdf(&self, x: f64) -> f64 {
816 if x < 0.0 {
817 0.0
818 } else {
819 1.0 - (-self.rate * x).exp()
820 }
821 }
822
823 pub fn quantile(&self, p: f64) -> Option<f64> {
827 if !(0.0..=1.0).contains(&p) {
828 return None;
829 }
830 if p == 1.0 {
831 return Some(f64::INFINITY);
832 }
833 Some(-(1.0 - p).ln() / self.rate)
834 }
835
836 pub fn hazard_rate(&self) -> f64 {
838 self.rate
839 }
840
841 pub fn survival(&self, x: f64) -> f64 {
843 1.0 - self.cdf(x)
844 }
845}
846
847#[derive(Debug, Clone, Copy)]
874pub struct GammaDistribution {
875 shape: f64, rate: f64, }
878
879impl GammaDistribution {
880 pub fn new(shape: f64, rate: f64) -> Result<Self, DistributionError> {
884 positive("shape", shape)?;
885 positive("rate", rate)?;
886 Ok(Self { shape, rate })
887 }
888
889 pub fn from_shape_scale(shape: f64, scale: f64) -> Result<Self, DistributionError> {
891 positive("scale", scale)?;
892 Self::new(shape, 1.0 / scale)
893 }
894
895 pub fn shape(&self) -> f64 {
897 self.shape
898 }
899
900 pub fn rate(&self) -> f64 {
902 self.rate
903 }
904
905 pub fn scale(&self) -> f64 {
907 1.0 / self.rate
908 }
909
910 pub fn mean(&self) -> f64 {
912 self.shape / self.rate
913 }
914
915 pub fn variance(&self) -> f64 {
917 self.shape / (self.rate * self.rate)
918 }
919
920 pub fn mode(&self) -> f64 {
922 if self.shape >= 1.0 {
923 (self.shape - 1.0) / self.rate
924 } else {
925 0.0
926 }
927 }
928
929 pub fn pdf(&self, x: f64) -> f64 {
931 if x <= 0.0 {
932 if x == 0.0 && self.shape < 1.0 {
933 return f64::INFINITY;
934 }
935 return 0.0;
936 }
937 let log_pdf = self.shape * self.rate.ln() - special::ln_gamma(self.shape)
938 + (self.shape - 1.0) * x.ln()
939 - self.rate * x;
940 log_pdf.exp()
941 }
942
943 pub fn cdf(&self, x: f64) -> f64 {
947 if x <= 0.0 {
948 return 0.0;
949 }
950 special::regularized_lower_gamma(self.shape, self.rate * x)
951 }
952
953 pub fn quantile(&self, p: f64) -> Option<f64> {
957 if !(0.0..=1.0).contains(&p) {
958 return None;
959 }
960 if p == 0.0 {
961 return Some(0.0);
962 }
963 if p == 1.0 {
964 return Some(f64::INFINITY);
965 }
966
967 let z = crate::special::inverse_normal_cdf(p);
969 let v = 1.0 / (9.0 * self.shape);
970 let chi2_approx = self.shape * (1.0 - v + z * v.sqrt()).powi(3).max(0.001);
971 let mut x = chi2_approx / self.rate;
972
973 for _ in 0..50 {
975 let f = self.cdf(x) - p;
976 let fp = self.pdf(x);
977 if fp < 1e-300 {
978 break;
979 }
980 let step = f / fp;
981 x = (x - step).max(1e-15);
982 if step.abs() < 1e-12 * x {
983 break;
984 }
985 }
986
987 Some(x)
988 }
989}
990
991#[derive(Debug, Clone)]
1020pub struct BetaDistribution {
1021 pub alpha: f64,
1023 pub beta: f64,
1025}
1026
1027impl BetaDistribution {
1028 pub fn new(alpha: f64, beta: f64) -> Result<Self, DistributionError> {
1033 positive("alpha", alpha)?;
1034 positive("beta", beta)?;
1035 Ok(Self { alpha, beta })
1036 }
1037
1038 pub fn mean(&self) -> f64 {
1040 self.alpha / (self.alpha + self.beta)
1041 }
1042
1043 pub fn variance(&self) -> f64 {
1045 let ab = self.alpha + self.beta;
1046 self.alpha * self.beta / (ab * ab * (ab + 1.0))
1047 }
1048
1049 pub fn mode(&self) -> Option<f64> {
1054 if self.alpha > 1.0 && self.beta > 1.0 {
1055 Some((self.alpha - 1.0) / (self.alpha + self.beta - 2.0))
1056 } else {
1057 None
1058 }
1059 }
1060
1061 pub fn pdf(&self, x: f64) -> f64 {
1063 if x <= 0.0 || x >= 1.0 {
1064 return 0.0;
1065 }
1066 let ln_pdf = (self.alpha - 1.0) * x.ln() + (self.beta - 1.0) * (1.0 - x).ln()
1067 - special::ln_beta(self.alpha, self.beta);
1068 ln_pdf.exp()
1069 }
1070
1071 pub fn cdf(&self, x: f64) -> f64 {
1073 if x <= 0.0 {
1074 return 0.0;
1075 }
1076 if x >= 1.0 {
1077 return 1.0;
1078 }
1079 special::regularized_incomplete_beta(x, self.alpha, self.beta)
1080 }
1081
1082 pub fn quantile(&self, p: f64) -> Option<f64> {
1087 if !(0.0..=1.0).contains(&p) {
1088 return None;
1089 }
1090 if p == 0.0 {
1091 return Some(0.0);
1092 }
1093 if (p - 1.0).abs() < 1e-15 {
1094 return Some(1.0);
1095 }
1096
1097 let mut lo = 0.0_f64;
1099 let mut hi = 1.0_f64;
1100
1101 for _ in 0..100 {
1102 let mid = (lo + hi) / 2.0;
1103 if hi - lo < 1e-14 {
1104 break;
1105 }
1106 if self.cdf(mid) < p {
1107 lo = mid;
1108 } else {
1109 hi = mid;
1110 }
1111 }
1112
1113 Some((lo + hi) / 2.0)
1114 }
1115}
1116
1117#[derive(Debug, Clone)]
1148pub struct ChiSquared {
1149 pub k: f64,
1151}
1152
1153impl ChiSquared {
1154 pub fn new(k: f64) -> Result<Self, DistributionError> {
1159 positive("k", k)?;
1160 Ok(Self { k })
1161 }
1162
1163 pub fn mean(&self) -> f64 {
1165 self.k
1166 }
1167
1168 pub fn variance(&self) -> f64 {
1170 2.0 * self.k
1171 }
1172
1173 pub fn pdf(&self, x: f64) -> f64 {
1175 if x <= 0.0 {
1176 return 0.0;
1177 }
1178 let half_k = self.k / 2.0;
1179 let ln_pdf =
1180 (half_k - 1.0) * x.ln() - x / 2.0 - half_k * 2.0_f64.ln() - special::ln_gamma(half_k);
1181 ln_pdf.exp()
1182 }
1183
1184 pub fn cdf(&self, x: f64) -> f64 {
1186 if x <= 0.0 {
1187 return 0.0;
1188 }
1189 special::regularized_lower_gamma(self.k / 2.0, x / 2.0)
1190 }
1191
1192 pub fn quantile(&self, p: f64) -> Option<f64> {
1196 if !(0.0..=1.0).contains(&p) {
1197 return None;
1198 }
1199 if p == 0.0 {
1200 return Some(0.0);
1201 }
1202 if (p - 1.0).abs() < 1e-15 {
1203 return Some(f64::INFINITY);
1204 }
1205
1206 let gamma = GammaDistribution {
1208 shape: self.k / 2.0,
1209 rate: 0.5,
1210 };
1211 gamma.quantile(p)
1212 }
1213}
1214
1215#[cfg(test)]
1220mod tests {
1221 use super::*;
1222
1223 #[test]
1226 fn test_uniform_basic() {
1227 let u = Uniform::new(0.0, 10.0).unwrap();
1228 assert!((u.mean() - 5.0).abs() < 1e-15);
1229 assert!((u.variance() - 100.0 / 12.0).abs() < 1e-10);
1230 }
1231
1232 #[test]
1233 fn test_uniform_cdf() {
1234 let u = Uniform::new(0.0, 10.0).unwrap();
1235 assert_eq!(u.cdf(-1.0), 0.0);
1236 assert!((u.cdf(5.0) - 0.5).abs() < 1e-15);
1237 assert_eq!(u.cdf(11.0), 1.0);
1238 }
1239
1240 #[test]
1241 fn test_uniform_quantile() {
1242 let u = Uniform::new(2.0, 8.0).unwrap();
1243 assert_eq!(u.quantile(0.0), Some(2.0));
1244 assert_eq!(u.quantile(1.0), Some(8.0));
1245 assert!((u.quantile(0.5).unwrap() - 5.0).abs() < 1e-15);
1246 }
1247
1248 #[test]
1249 fn test_uniform_pdf() {
1250 let u = Uniform::new(0.0, 5.0).unwrap();
1251 assert!((u.pdf(2.5) - 0.2).abs() < 1e-15);
1252 assert_eq!(u.pdf(-1.0), 0.0);
1253 }
1254
1255 #[test]
1256 fn test_uniform_invalid() {
1257 assert!(Uniform::new(5.0, 5.0).is_err());
1258 assert!(Uniform::new(5.0, 3.0).is_err());
1259 assert!(Uniform::new(f64::NAN, 5.0).is_err());
1260 }
1261
1262 #[test]
1265 fn test_triangular_mean() {
1266 let t = Triangular::new(0.0, 3.0, 6.0).unwrap();
1267 assert!((t.mean() - 3.0).abs() < 1e-15);
1268 }
1269
1270 #[test]
1271 fn test_triangular_symmetric_variance() {
1272 let t = Triangular::new(0.0, 5.0, 10.0).unwrap();
1273 let expected = (0.0 + 25.0 + 100.0 - 0.0 - 0.0 - 50.0) / 18.0;
1275 assert!((t.variance() - expected).abs() < 1e-10);
1276 }
1277
1278 #[test]
1279 fn test_triangular_cdf() {
1280 let t = Triangular::new(0.0, 5.0, 10.0).unwrap();
1281 assert!((t.cdf(0.0)).abs() < 1e-15);
1282 assert!((t.cdf(10.0) - 1.0).abs() < 1e-15);
1283 assert!((t.cdf(5.0) - 0.5).abs() < 1e-15);
1285 }
1286
1287 #[test]
1288 fn test_triangular_quantile() {
1289 let t = Triangular::new(0.0, 5.0, 10.0).unwrap();
1290 assert!((t.quantile(0.0).unwrap() - 0.0).abs() < 1e-15);
1291 assert!((t.quantile(1.0).unwrap() - 10.0).abs() < 1e-15);
1292 assert!((t.quantile(0.5).unwrap() - 5.0).abs() < 1e-10);
1293 }
1294
1295 #[test]
1296 fn test_triangular_invalid() {
1297 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()); }
1301
1302 #[test]
1305 fn test_normal_standard() {
1306 let n = Normal::new(0.0, 1.0).unwrap();
1307 assert!((n.mean()).abs() < 1e-15);
1308 assert!((n.variance() - 1.0).abs() < 1e-15);
1309 assert!((n.cdf(0.0) - 0.5).abs() < 1e-7);
1310 }
1311
1312 #[test]
1313 fn test_normal_shifted() {
1314 let n = Normal::new(10.0, 2.0).unwrap();
1315 assert!((n.mean() - 10.0).abs() < 1e-15);
1316 assert!((n.variance() - 4.0).abs() < 1e-15);
1317 assert!((n.cdf(10.0) - 0.5).abs() < 1e-7);
1318 }
1319
1320 #[test]
1321 fn test_normal_quantile() {
1322 let n = Normal::new(0.0, 1.0).unwrap();
1323 assert!((n.quantile(0.5).unwrap()).abs() < 0.01);
1324 assert!((n.quantile(0.975).unwrap() - 1.96).abs() < 0.01);
1325 }
1326
1327 #[test]
1328 fn test_normal_invalid() {
1329 assert!(Normal::new(0.0, 0.0).is_err());
1330 assert!(Normal::new(0.0, -1.0).is_err());
1331 }
1332
1333 #[test]
1336 fn test_lognormal_mean() {
1337 let ln = LogNormal::new(0.0, 1.0).unwrap();
1338 let expected = (0.5_f64).exp(); assert!((ln.mean() - expected).abs() < 1e-10);
1340 }
1341
1342 #[test]
1343 fn test_lognormal_cdf() {
1344 let ln = LogNormal::new(0.0, 1.0).unwrap();
1345 assert_eq!(ln.cdf(0.0), 0.0);
1346 assert!((ln.cdf(1.0) - 0.5).abs() < 0.001);
1348 }
1349
1350 #[test]
1351 fn test_lognormal_quantile() {
1352 let ln = LogNormal::new(0.0, 1.0).unwrap();
1353 let q50 = ln.quantile(0.5).unwrap();
1355 assert!((q50 - 1.0).abs() < 0.01);
1356 }
1357
1358 #[test]
1361 fn test_pert_mean() {
1362 let p = Pert::new(1.0, 4.0, 7.0).unwrap();
1363 assert!((p.mean() - 4.0).abs() < 1e-10);
1365 }
1366
1367 #[test]
1368 fn test_pert_symmetric_variance() {
1369 let p = Pert::new(0.0, 5.0, 10.0).unwrap();
1370 let expected = 9.0 / (36.0 * 7.0) * 100.0;
1373 assert!(
1374 (p.variance() - expected).abs() < 1e-10,
1375 "PERT variance: {} vs expected: {}",
1376 p.variance(),
1377 expected
1378 );
1379 }
1380
1381 #[test]
1382 fn test_pert_shape_params() {
1383 let p = Pert::new(0.0, 5.0, 10.0).unwrap();
1384 assert!((p.alpha() - 3.0).abs() < 1e-15);
1387 assert!((p.beta_param() - 3.0).abs() < 1e-15);
1388 }
1389
1390 #[test]
1391 fn test_pert_cdf_bounds() {
1392 let p = Pert::new(1.0, 4.0, 7.0).unwrap();
1393 assert_eq!(p.cdf(0.0), 0.0);
1394 assert_eq!(p.cdf(8.0), 1.0);
1395 let p_sym = Pert::new(0.0, 5.0, 10.0).unwrap();
1397 assert!((p_sym.cdf(5.0) - 0.5).abs() < 0.01);
1398 }
1399
1400 #[test]
1401 fn test_pert_cdf_monotonic() {
1402 let p = Pert::new(0.0, 3.0, 10.0).unwrap();
1403 let mut prev = 0.0;
1404 for i in 0..=100 {
1405 let x = i as f64 * 0.1;
1406 let c = p.cdf(x);
1407 assert!(c >= prev - 1e-15, "CDF not monotonic at x={x}");
1408 prev = c;
1409 }
1410 }
1411
1412 #[test]
1413 fn test_pert_quantile_symmetric_median_is_the_mode() {
1414 let p = Pert::new(0.0, 5.0, 10.0).unwrap();
1415 assert!((p.quantile(0.5).unwrap() - 5.0).abs() < 1e-9);
1416 assert_eq!(p.quantile(0.0), Some(0.0));
1417 assert_eq!(p.quantile(1.0), Some(10.0));
1418 assert_eq!(p.quantile(1.5), None);
1419 }
1420
1421 #[test]
1426 fn test_pert_quantile_inverts_the_cdf_when_skewed() {
1427 let p = Pert::new(0.0, 1.0, 10.0).unwrap();
1428 for &q in &[1e-6, 0.01, 0.05, 0.25, 0.5, 0.75, 0.95, 0.99] {
1429 let x = p.quantile(q).unwrap();
1430 assert!((0.0..=10.0).contains(&x));
1431 assert!(
1432 (p.cdf(x) - q).abs() < 1e-9,
1433 "p={q}: x={x}, cdf={}",
1434 p.cdf(x)
1435 );
1436 }
1437 let normal_approx_at_5 = p.mean() + special::inverse_normal_cdf(0.05) * p.std_dev();
1438 assert!((p.quantile(0.05).unwrap() - 0.300).abs() < 1e-3);
1439 assert!((p.quantile(0.95).unwrap() - 5.4207).abs() < 1e-3);
1440 assert!(
1441 normal_approx_at_5 < 0.0,
1442 "the approximation leaves the support"
1443 );
1444 }
1445
1446 #[test]
1447 fn test_pert_invalid() {
1448 assert!(Pert::new(5.0, 3.0, 10.0).is_err());
1449 assert!(Pert::new(5.0, 5.0, 5.0).is_err());
1450 }
1451
1452 #[test]
1453 fn test_pert_with_lambda() {
1454 let p4 = Pert::with_shape(0.0, 5.0, 10.0, 4.0).unwrap();
1456 let p8 = Pert::with_shape(0.0, 5.0, 10.0, 8.0).unwrap();
1457 assert!(
1458 p8.variance() < p4.variance(),
1459 "higher λ should give lower variance"
1460 );
1461 }
1462
1463 #[test]
1466 fn test_regularized_beta_bounds() {
1467 assert_eq!(special::regularized_incomplete_beta(0.0, 2.0, 3.0), 0.0);
1468 assert_eq!(special::regularized_incomplete_beta(1.0, 2.0, 3.0), 1.0);
1469 }
1470
1471 #[test]
1472 fn test_regularized_beta_symmetric() {
1473 let result = special::regularized_incomplete_beta(0.5, 3.0, 3.0);
1475 assert!(
1476 (result - 0.5).abs() < 1e-8,
1477 "I_0.5(3,3) = {result}, expected 0.5"
1478 );
1479 }
1480
1481 #[test]
1482 fn test_regularized_beta_known_values() {
1483 for &x in &[0.1, 0.3, 0.5, 0.7, 0.9] {
1485 let result = special::regularized_incomplete_beta(x, 1.0, 1.0);
1486 assert!(
1487 (result - x).abs() < 1e-10,
1488 "I_{x}(1,1) = {result}, expected {x}"
1489 );
1490 }
1491
1492 for &x in &[0.1, 0.5, 0.9] {
1494 let result = special::regularized_incomplete_beta(x, 1.0, 3.0);
1495 let expected = 1.0 - (1.0 - x).powi(3);
1496 assert!(
1497 (result - expected).abs() < 1e-10,
1498 "I_{x}(1,3) = {result}, expected {expected}"
1499 );
1500 }
1501 }
1502
1503 #[test]
1506 fn test_ln_gamma_known() {
1507 assert!((special::ln_gamma(1.0)).abs() < 1e-10);
1509 assert!((special::ln_gamma(2.0)).abs() < 1e-10);
1511 assert!((special::ln_gamma(3.0) - 2.0_f64.ln()).abs() < 1e-10);
1513 assert!((special::ln_gamma(5.0) - 24.0_f64.ln()).abs() < 1e-10);
1515 assert!(
1517 (special::ln_gamma(0.5) - std::f64::consts::PI.sqrt().ln()).abs() < 1e-10,
1518 "ln Γ(0.5) = {}, expected {}",
1519 special::ln_gamma(0.5),
1520 std::f64::consts::PI.sqrt().ln()
1521 );
1522 }
1523
1524 #[test]
1527 fn test_weibull_exponential_special_case() {
1528 let w = Weibull::new(1.0, 5.0).unwrap();
1530 assert!((w.mean() - 5.0).abs() < 1e-10);
1531 assert!((w.variance() - 25.0).abs() < 1e-8);
1532 }
1533
1534 #[test]
1535 fn test_weibull_rayleigh_special_case() {
1536 let w = Weibull::new(2.0, 1.0).unwrap();
1538 let expected_mean = std::f64::consts::PI.sqrt() / 2.0;
1539 assert!(
1540 (w.mean() - expected_mean).abs() < 1e-10,
1541 "Weibull(2,1) mean = {}, expected {}",
1542 w.mean(),
1543 expected_mean
1544 );
1545 }
1546
1547 #[test]
1548 fn test_weibull_cdf_known_values() {
1549 let w = Weibull::new(2.0, 10.0).unwrap();
1551 assert!((w.cdf(10.0) - (1.0 - (-1.0_f64).exp())).abs() < 1e-10);
1553 assert_eq!(w.cdf(0.0), 0.0);
1555 assert_eq!(w.cdf(-1.0), 0.0);
1557 }
1558
1559 #[test]
1560 fn test_weibull_pdf_basic() {
1561 let w = Weibull::new(1.0, 1.0).unwrap();
1563 assert!((w.pdf(0.0) - 1.0).abs() < 1e-10);
1564 assert!((w.pdf(1.0) - (-1.0_f64).exp()).abs() < 1e-10);
1565 assert!((w.pdf(2.0) - (-2.0_f64).exp()).abs() < 1e-10);
1566 }
1567
1568 #[test]
1569 fn test_weibull_pdf_negative() {
1570 let w = Weibull::new(2.0, 5.0).unwrap();
1571 assert_eq!(w.pdf(-1.0), 0.0);
1572 }
1573
1574 #[test]
1575 fn test_weibull_quantile_roundtrip() {
1576 let w = Weibull::new(2.5, 100.0).unwrap();
1577 for &p in &[0.1, 0.25, 0.5, 0.75, 0.9] {
1578 let t = w.quantile(p).unwrap();
1579 let p_back = w.cdf(t);
1580 assert!(
1581 (p_back - p).abs() < 1e-10,
1582 "roundtrip: p={p} -> t={t} -> p_back={p_back}"
1583 );
1584 }
1585 }
1586
1587 #[test]
1588 fn test_weibull_quantile_edge_cases() {
1589 let w = Weibull::new(2.0, 10.0).unwrap();
1590 assert_eq!(w.quantile(0.0), Some(0.0));
1591 assert_eq!(w.quantile(1.0), None);
1592 assert_eq!(w.quantile(-0.1), None);
1593 }
1594
1595 #[test]
1596 fn test_weibull_reliability() {
1597 let w = Weibull::new(2.0, 10.0).unwrap();
1598 for &t in &[1.0, 5.0, 10.0, 20.0] {
1600 let r = w.reliability(t);
1601 let f = w.cdf(t);
1602 assert!((r + f - 1.0).abs() < 1e-14);
1603 }
1604 }
1605
1606 #[test]
1607 fn test_weibull_hazard_rate() {
1608 let w = Weibull::new(1.0, 5.0).unwrap();
1610 for &t in &[1.0, 5.0, 10.0] {
1611 assert!((w.hazard_rate(t) - 0.2).abs() < 1e-10);
1612 }
1613 let w2 = Weibull::new(2.0, 10.0).unwrap();
1615 assert!((w2.hazard_rate(5.0) - 2.0 * 5.0 / 100.0).abs() < 1e-10);
1616 }
1617
1618 #[test]
1619 fn test_weibull_invalid() {
1620 assert!(Weibull::new(0.0, 1.0).is_err());
1621 assert!(Weibull::new(-1.0, 1.0).is_err());
1622 assert!(Weibull::new(1.0, 0.0).is_err());
1623 assert!(Weibull::new(1.0, -1.0).is_err());
1624 assert!(Weibull::new(f64::NAN, 1.0).is_err());
1625 assert!(Weibull::new(1.0, f64::INFINITY).is_err());
1626 }
1627
1628 #[test]
1631 fn test_exponential_basic() {
1632 let e = Exponential::new(0.5).unwrap();
1633 assert!((e.rate() - 0.5).abs() < 1e-10);
1634 assert!((e.mean() - 2.0).abs() < 1e-10);
1635 assert!((e.variance() - 4.0).abs() < 1e-10);
1636 }
1637
1638 #[test]
1639 fn test_exponential_pdf() {
1640 let e = Exponential::new(1.0).unwrap();
1641 assert!((e.pdf(0.0) - 1.0).abs() < 1e-10);
1642 assert!((e.pdf(1.0) - (-1.0_f64).exp()).abs() < 1e-10);
1643 assert_eq!(e.pdf(-1.0), 0.0);
1644 }
1645
1646 #[test]
1647 fn test_exponential_cdf() {
1648 let e = Exponential::new(1.0).unwrap();
1649 assert_eq!(e.cdf(-1.0), 0.0);
1650 assert_eq!(e.cdf(0.0), 0.0);
1651 assert!((e.cdf(1.0) - (1.0 - (-1.0_f64).exp())).abs() < 1e-10);
1652 }
1653
1654 #[test]
1655 fn test_exponential_quantile() {
1656 let e = Exponential::new(2.0).unwrap();
1657 assert_eq!(e.quantile(0.0), Some(0.0));
1658 assert_eq!(e.quantile(1.0), Some(f64::INFINITY));
1659 let q = e.quantile(0.5).unwrap();
1660 assert!((q - 2.0_f64.ln() / 2.0).abs() < 1e-10);
1662 assert!(e.quantile(-0.1).is_none());
1663 assert!(e.quantile(1.1).is_none());
1664 }
1665
1666 #[test]
1667 fn test_exponential_quantile_roundtrip() {
1668 let e = Exponential::new(3.0).unwrap();
1669 for &p in &[0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99] {
1670 let x = e.quantile(p).unwrap();
1671 let p_back = e.cdf(x);
1672 assert!((p_back - p).abs() < 1e-10, "p={p}, x={x}, p_back={p_back}");
1673 }
1674 }
1675
1676 #[test]
1677 fn test_exponential_memoryless() {
1678 let e = Exponential::new(1.5).unwrap();
1680 let s = 2.0;
1681 let t = 3.0;
1682 let lhs = e.survival(s + t) / e.survival(s);
1683 let rhs = e.survival(t);
1684 assert!((lhs - rhs).abs() < 1e-10);
1685 }
1686
1687 #[test]
1688 fn test_exponential_invalid() {
1689 assert!(Exponential::new(0.0).is_err());
1690 assert!(Exponential::new(-1.0).is_err());
1691 assert!(Exponential::new(f64::NAN).is_err());
1692 assert!(Exponential::new(f64::INFINITY).is_err());
1693 }
1694
1695 #[test]
1698 fn test_gamma_basic() {
1699 let g = GammaDistribution::new(2.0, 1.0).unwrap();
1700 assert!((g.shape() - 2.0).abs() < 1e-10);
1701 assert!((g.rate() - 1.0).abs() < 1e-10);
1702 assert!((g.scale() - 1.0).abs() < 1e-10);
1703 assert!((g.mean() - 2.0).abs() < 1e-10);
1704 assert!((g.variance() - 2.0).abs() < 1e-10);
1705 assert!((g.mode() - 1.0).abs() < 1e-10);
1706 }
1707
1708 #[test]
1709 fn test_gamma_from_shape_scale() {
1710 let g = GammaDistribution::from_shape_scale(3.0, 2.0).unwrap();
1711 assert!((g.shape() - 3.0).abs() < 1e-10);
1712 assert!((g.rate() - 0.5).abs() < 1e-10);
1713 assert!((g.mean() - 6.0).abs() < 1e-10); }
1715
1716 #[test]
1717 fn test_gamma_pdf_integral() {
1718 let g = GammaDistribution::new(3.0, 2.0).unwrap();
1720 let n = 20_000;
1721 let dt = 20.0 / n as f64;
1722 let integral: f64 = (0..n)
1723 .map(|i| {
1724 let x = (i as f64 + 0.5) * dt;
1725 g.pdf(x) * dt
1726 })
1727 .sum();
1728 assert!((integral - 1.0).abs() < 0.01, "PDF integral = {integral}");
1729 }
1730
1731 #[test]
1732 fn test_gamma_cdf_known() {
1733 let g = GammaDistribution::new(1.0, 1.0).unwrap();
1735 let expected = 1.0 - (-1.0_f64).exp();
1736 assert!((g.cdf(1.0) - expected).abs() < 1e-10);
1737 }
1738
1739 #[test]
1740 fn test_gamma_cdf_chi2() {
1741 let g = GammaDistribution::new(1.0, 0.5).unwrap(); let x = 4.0;
1745 let expected = 1.0 - (-0.5_f64 * x).exp();
1746 assert!(
1747 (g.cdf(x) - expected).abs() < 1e-8,
1748 "CDF({x}) = {} vs {expected}",
1749 g.cdf(x)
1750 );
1751 }
1752
1753 #[test]
1754 fn test_gamma_quantile_roundtrip() {
1755 let g = GammaDistribution::new(5.0, 2.0).unwrap();
1756 for &p in &[0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99] {
1757 let x = g.quantile(p).unwrap();
1758 let p_back = g.cdf(x);
1759 assert!((p_back - p).abs() < 1e-6, "p={p}, x={x}, p_back={p_back}");
1760 }
1761 }
1762
1763 #[test]
1764 fn test_gamma_mode_small_shape() {
1765 let g = GammaDistribution::new(0.5, 1.0).unwrap();
1766 assert_eq!(g.mode(), 0.0); }
1768
1769 #[test]
1770 fn test_gamma_invalid() {
1771 assert!(GammaDistribution::new(0.0, 1.0).is_err());
1772 assert!(GammaDistribution::new(-1.0, 1.0).is_err());
1773 assert!(GammaDistribution::new(1.0, 0.0).is_err());
1774 assert!(GammaDistribution::new(1.0, -1.0).is_err());
1775 assert!(GammaDistribution::new(f64::NAN, 1.0).is_err());
1776 }
1777
1778 #[test]
1781 fn test_beta_mean_variance() {
1782 let b = BetaDistribution::new(2.0, 5.0).unwrap();
1783 assert!((b.mean() - 2.0 / 7.0).abs() < 1e-10);
1785 let expected_var = 2.0 * 5.0 / (7.0 * 7.0 * 8.0);
1787 assert!((b.variance() - expected_var).abs() < 1e-10);
1788 }
1789
1790 #[test]
1791 fn test_beta_symmetric() {
1792 let b = BetaDistribution::new(3.0, 3.0).unwrap();
1794 assert!((b.mean() - 0.5).abs() < 1e-10);
1795 assert!((b.mode().unwrap() - 0.5).abs() < 1e-10);
1796 assert!((b.pdf(0.3) - b.pdf(0.7)).abs() < 1e-10);
1798 }
1799
1800 #[test]
1801 fn test_beta_uniform_special_case() {
1802 let b = BetaDistribution::new(1.0, 1.0).unwrap();
1804 assert!((b.mean() - 0.5).abs() < 1e-10);
1805 assert!((b.cdf(0.5) - 0.5).abs() < 1e-10);
1806 assert!((b.cdf(0.25) - 0.25).abs() < 1e-10);
1807 }
1808
1809 #[test]
1810 fn test_beta_cdf_boundaries() {
1811 let b = BetaDistribution::new(2.0, 3.0).unwrap();
1812 assert_eq!(b.cdf(0.0), 0.0);
1813 assert_eq!(b.cdf(-1.0), 0.0);
1814 assert_eq!(b.cdf(1.0), 1.0);
1815 assert_eq!(b.cdf(2.0), 1.0);
1816 }
1817
1818 #[test]
1819 fn test_beta_pdf_integral() {
1820 let b = BetaDistribution::new(2.0, 5.0).unwrap();
1821 let n = 10_000;
1822 let dt = 1.0 / n as f64;
1823 let integral: f64 = (0..n)
1824 .map(|i| {
1825 let x = (i as f64 + 0.5) * dt;
1826 b.pdf(x) * dt
1827 })
1828 .sum();
1829 assert!((integral - 1.0).abs() < 0.01, "PDF integral = {integral}");
1830 }
1831
1832 #[test]
1833 fn test_beta_quantile_roundtrip() {
1834 let b = BetaDistribution::new(2.0, 5.0).unwrap();
1835 for &p in &[0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99] {
1836 let x = b.quantile(p).unwrap();
1837 let p_back = b.cdf(x);
1838 assert!((p_back - p).abs() < 1e-6, "p={p}, x={x}, p_back={p_back}");
1839 }
1840 }
1841
1842 #[test]
1843 fn test_beta_mode() {
1844 let b = BetaDistribution::new(5.0, 3.0).unwrap();
1845 assert!((b.mode().unwrap() - 2.0 / 3.0).abs() < 1e-10);
1847 }
1848
1849 #[test]
1850 fn test_beta_mode_undefined() {
1851 let b = BetaDistribution::new(0.5, 0.5).unwrap();
1853 assert!(b.mode().is_none());
1854 }
1855
1856 #[test]
1857 fn test_beta_invalid() {
1858 assert!(BetaDistribution::new(0.0, 1.0).is_err());
1859 assert!(BetaDistribution::new(-1.0, 1.0).is_err());
1860 assert!(BetaDistribution::new(1.0, 0.0).is_err());
1861 assert!(BetaDistribution::new(f64::NAN, 1.0).is_err());
1862 }
1863
1864 #[test]
1867 fn test_chi2_mean_variance() {
1868 let chi2 = ChiSquared::new(5.0).unwrap();
1869 assert!((chi2.mean() - 5.0).abs() < 1e-10);
1870 assert!((chi2.variance() - 10.0).abs() < 1e-10);
1871 }
1872
1873 #[test]
1874 fn test_chi2_exp_special_case() {
1875 let chi2 = ChiSquared::new(2.0).unwrap();
1877 let x = 4.0;
1878 let expected = 1.0 - (-x / 2.0_f64).exp();
1879 assert!(
1880 (chi2.cdf(x) - expected).abs() < 1e-8,
1881 "CDF({x}) = {} vs {expected}",
1882 chi2.cdf(x)
1883 );
1884 }
1885
1886 #[test]
1887 fn test_chi2_cdf_boundaries() {
1888 let chi2 = ChiSquared::new(3.0).unwrap();
1889 assert_eq!(chi2.cdf(0.0), 0.0);
1890 assert_eq!(chi2.cdf(-1.0), 0.0);
1891 assert!(chi2.cdf(100.0) > 0.999);
1893 }
1894
1895 #[test]
1896 fn test_chi2_pdf_integral() {
1897 let chi2 = ChiSquared::new(4.0).unwrap();
1898 let n = 20_000;
1899 let dt = 30.0 / n as f64;
1900 let integral: f64 = (0..n)
1901 .map(|i| {
1902 let x = (i as f64 + 0.5) * dt;
1903 chi2.pdf(x) * dt
1904 })
1905 .sum();
1906 assert!((integral - 1.0).abs() < 0.01, "PDF integral = {integral}");
1907 }
1908
1909 #[test]
1910 fn test_chi2_quantile_roundtrip() {
1911 let chi2 = ChiSquared::new(5.0).unwrap();
1912 for &p in &[0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99] {
1913 let x = chi2.quantile(p).unwrap();
1914 let p_back = chi2.cdf(x);
1915 assert!((p_back - p).abs() < 1e-6, "p={p}, x={x}, p_back={p_back}");
1916 }
1917 }
1918
1919 #[test]
1920 fn test_chi2_known_quantiles() {
1921 let chi2 = ChiSquared::new(5.0).unwrap();
1924 let q05 = chi2.quantile(0.05).unwrap();
1925 assert!((q05 - 1.1455).abs() < 0.01, "q(0.05) = {q05}");
1926 let q95 = chi2.quantile(0.95).unwrap();
1927 assert!((q95 - 11.0705).abs() < 0.01, "q(0.95) = {q95}");
1928 }
1929
1930 #[test]
1931 fn test_chi2_invalid() {
1932 assert!(ChiSquared::new(0.0).is_err());
1933 assert!(ChiSquared::new(-1.0).is_err());
1934 assert!(ChiSquared::new(f64::NAN).is_err());
1935 }
1936
1937 #[test]
1938 fn test_chi2_gamma_consistency() {
1939 let k = 6.0;
1941 let chi2 = ChiSquared::new(k).unwrap();
1942 let gamma = GammaDistribution::new(k / 2.0, 0.5).unwrap();
1943
1944 let x = 5.0;
1945 assert!(
1946 (chi2.cdf(x) - gamma.cdf(x)).abs() < 1e-10,
1947 "Chi2 and Gamma CDF should match"
1948 );
1949 }
1950
1951 #[test]
1952 fn test_weibull_mean_variance_known() {
1953 let w = Weibull::new(3.6, 1000.0).unwrap();
1956 let mean = w.mean();
1957 assert!(mean > 800.0 && mean < 1000.0, "mean={mean}");
1958 assert!(w.variance() > 0.0);
1959 }
1960
1961 #[test]
1962 fn test_weibull_pdf_integral_approx() {
1963 let w = Weibull::new(2.0, 10.0).unwrap();
1965 let n = 10_000;
1966 let dt = 50.0 / n as f64;
1967 let integral: f64 = (0..n)
1968 .map(|i| {
1969 let t = (i as f64 + 0.5) * dt;
1970 w.pdf(t) * dt
1971 })
1972 .sum();
1973 assert!(
1974 (integral - 1.0).abs() < 0.01,
1975 "PDF integral = {integral}, expected ≈ 1.0"
1976 );
1977 }
1978
1979 #[test]
1988 fn test_normal_cdf_reference_values() {
1989 assert!(
1991 (special::standard_normal_cdf(0.0) - 0.5).abs() < 1e-7,
1992 "Φ(0) error"
1993 );
1994 assert!(
1996 (special::standard_normal_cdf(1.96) - 0.975002).abs() < 1e-6,
1997 "Φ(1.96) = {}, expected 0.975002",
1998 special::standard_normal_cdf(1.96)
1999 );
2000 assert!(
2002 (special::standard_normal_cdf(-1.96) - 0.024998).abs() < 1e-6,
2003 "Φ(-1.96) = {}, expected 0.024998",
2004 special::standard_normal_cdf(-1.96)
2005 );
2006 assert!(
2008 (special::standard_normal_cdf(3.0) - 0.998650).abs() < 1e-6,
2009 "Φ(3.0) = {}, expected 0.998650",
2010 special::standard_normal_cdf(3.0)
2011 );
2012 for &x in &[0.5, 1.0, 1.96, 2.5, 3.0] {
2014 let sum = special::standard_normal_cdf(x) + special::standard_normal_cdf(-x);
2015 assert!(
2016 (sum - 1.0).abs() < 1e-14,
2017 "Symmetry violated at x={x}: sum={sum}"
2018 );
2019 }
2020 }
2021
2022 #[test]
2026 fn test_normal_quantile_reference_values() {
2027 assert!(
2029 special::inverse_normal_cdf(0.5).abs() < 5e-4,
2030 "Φ⁻¹(0.5) = {}, expected 0.0",
2031 special::inverse_normal_cdf(0.5)
2032 );
2033 let q975 = special::inverse_normal_cdf(0.975);
2035 assert!(
2036 (q975 - 1.95996).abs() < 5e-4,
2037 "Φ⁻¹(0.975) = {q975}, expected ≈ 1.95996"
2038 );
2039 let q025 = special::inverse_normal_cdf(0.025);
2041 assert!(
2042 (q025 + 1.95996).abs() < 5e-4,
2043 "Φ⁻¹(0.025) = {q025}, expected ≈ -1.95996"
2044 );
2045 for &p in &[0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99] {
2047 let z = special::inverse_normal_cdf(p);
2048 let p_back = special::standard_normal_cdf(z);
2049 assert!(
2050 (p_back - p).abs() < 1e-3,
2051 "Roundtrip failed: p={p}, z={z}, p_back={p_back}"
2052 );
2053 }
2054 }
2055
2056 #[test]
2064 fn test_gamma_reference_values() {
2065 let sqrt_pi = std::f64::consts::PI.sqrt();
2067 assert!(
2068 (special::gamma(0.5) - sqrt_pi).abs() < 1e-7,
2069 "Γ(0.5) = {}, expected √π = {}",
2070 special::gamma(0.5),
2071 sqrt_pi
2072 );
2073 assert!(
2075 (special::gamma(1.0) - 1.0).abs() < 1e-14,
2076 "Γ(1.0) = {}",
2077 special::gamma(1.0)
2078 );
2079 let gamma_1_5 = sqrt_pi / 2.0;
2081 assert!(
2082 (special::gamma(1.5) - gamma_1_5).abs() < 1e-7,
2083 "Γ(1.5) = {}, expected {}",
2084 special::gamma(1.5),
2085 gamma_1_5
2086 );
2087 assert!(
2089 (special::gamma(2.0) - 1.0).abs() < 1e-14,
2090 "Γ(2.0) = {}",
2091 special::gamma(2.0)
2092 );
2093 assert!(
2095 (special::gamma(3.0) - 2.0).abs() < 1e-13,
2096 "Γ(3.0) = {}",
2097 special::gamma(3.0)
2098 );
2099 assert!(
2101 (special::gamma(4.0) - 6.0).abs() < 1e-12,
2102 "Γ(4.0) = {}",
2103 special::gamma(4.0)
2104 );
2105 assert!(
2107 (special::gamma(5.0) - 24.0).abs() < 1e-8,
2108 "Γ(5.0) = {}, expected 24.0",
2109 special::gamma(5.0)
2110 );
2111 }
2112
2113 #[test]
2122 fn test_gamma_pdf_reference_value() {
2123 let g = GammaDistribution::new(2.0, 1.0).unwrap();
2124 let expected = (-1.0_f64).exp(); assert!(
2126 (g.pdf(1.0) - expected).abs() < 1e-5,
2127 "Gamma(2,1).pdf(1) = {}, expected e⁻¹ = {}",
2128 g.pdf(1.0),
2129 expected
2130 );
2131 }
2132}
2133
2134#[cfg(test)]
2135mod proptests {
2136 use super::*;
2137 use proptest::prelude::*;
2138
2139 proptest! {
2140 #![proptest_config(ProptestConfig::with_cases(300))]
2141
2142 #[test]
2145 fn uniform_cdf_in_01(
2146 min in -100.0_f64..0.0,
2147 max in 1.0_f64..100.0,
2148 x in -200.0_f64..200.0,
2149 ) {
2150 let u = Uniform::new(min, max).unwrap();
2151 let c = u.cdf(x);
2152 prop_assert!((0.0..=1.0).contains(&c));
2153 }
2154
2155 #[test]
2156 fn uniform_quantile_roundtrip(
2157 min in -100.0_f64..0.0,
2158 max in 1.0_f64..100.0,
2159 p in 0.0_f64..=1.0,
2160 ) {
2161 let u = Uniform::new(min, max).unwrap();
2162 let x = u.quantile(p).unwrap();
2163 let p_back = u.cdf(x);
2164 prop_assert!((p_back - p).abs() < 1e-12, "roundtrip: p={p} -> x={x} -> p_back={p_back}");
2165 }
2166
2167 #[test]
2170 fn triangular_cdf_in_01(
2171 min in -100.0_f64..-1.0,
2172 mode_frac in 0.0_f64..=1.0,
2173 range in 1.0_f64..100.0,
2174 x in -200.0_f64..200.0,
2175 ) {
2176 let max = min + range;
2177 let mode = min + mode_frac * range;
2178 let t = Triangular::new(min, mode, max).unwrap();
2179 let c = t.cdf(x);
2180 prop_assert!((0.0..=1.0).contains(&c));
2181 }
2182
2183 #[test]
2184 fn triangular_quantile_roundtrip(
2185 min in -50.0_f64..0.0,
2186 mode_frac in 0.01_f64..0.99,
2187 range in 1.0_f64..50.0,
2188 p in 0.001_f64..0.999,
2189 ) {
2190 let max = min + range;
2191 let mode = min + mode_frac * range;
2192 let t = Triangular::new(min, mode, max).unwrap();
2193 let x = t.quantile(p).unwrap();
2194 let p_back = t.cdf(x);
2195 prop_assert!(
2196 (p_back - p).abs() < 1e-8,
2197 "roundtrip: p={p} -> x={x} -> p_back={p_back}"
2198 );
2199 }
2200
2201 #[test]
2204 fn pert_mean_formula(
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 ) {
2209 let max = min + range;
2210 let mode = min + mode_frac * range;
2211 let p = Pert::new(min, mode, max).unwrap();
2212 let expected = (min + 4.0 * mode + max) / 6.0;
2213 prop_assert!(
2214 (p.mean() - expected).abs() < 1e-10,
2215 "PERT mean: {} vs expected: {}",
2216 p.mean(),
2217 expected
2218 );
2219 }
2220
2221 #[test]
2222 fn pert_variance_positive(
2223 min in -50.0_f64..0.0,
2224 mode_frac in 0.01_f64..0.99,
2225 range in 1.0_f64..50.0,
2226 ) {
2227 let max = min + range;
2228 let mode = min + mode_frac * range;
2229 let p = Pert::new(min, mode, max).unwrap();
2230 prop_assert!(p.variance() > 0.0);
2231 }
2232
2233 #[test]
2234 fn pert_cdf_monotonic(
2235 min in -50.0_f64..0.0,
2236 mode_frac in 0.05_f64..0.95,
2237 range in 2.0_f64..50.0,
2238 ) {
2239 let max = min + range;
2240 let mode = min + mode_frac * range;
2241 let p = Pert::new(min, mode, max).unwrap();
2242 let mut prev = 0.0;
2243 for i in 0..=20 {
2244 let x = min + (i as f64 / 20.0) * range;
2245 let c = p.cdf(x);
2246 prop_assert!(c >= prev - 1e-10, "CDF not monotonic at x={x}");
2247 prev = c;
2248 }
2249 }
2250
2251 #[test]
2254 fn weibull_cdf_in_01(
2255 shape in 0.1_f64..10.0,
2256 scale in 0.1_f64..100.0,
2257 t in 0.0_f64..200.0,
2258 ) {
2259 let w = Weibull::new(shape, scale).unwrap();
2260 let c = w.cdf(t);
2261 prop_assert!((0.0..=1.0).contains(&c), "CDF({t}) = {c} out of [0,1]");
2262 }
2263
2264 #[test]
2265 fn weibull_quantile_roundtrip(
2266 shape in 0.5_f64..10.0,
2267 scale in 1.0_f64..100.0,
2268 p in 0.001_f64..0.999,
2269 ) {
2270 let w = Weibull::new(shape, scale).unwrap();
2271 let t = w.quantile(p).unwrap();
2272 let p_back = w.cdf(t);
2273 prop_assert!(
2274 (p_back - p).abs() < 1e-8,
2275 "roundtrip: p={p} -> t={t} -> p_back={p_back}"
2276 );
2277 }
2278
2279 #[test]
2280 fn weibull_pdf_non_negative(
2281 shape in 0.1_f64..10.0,
2282 scale in 0.1_f64..100.0,
2283 t in 0.0_f64..200.0,
2284 ) {
2285 let w = Weibull::new(shape, scale).unwrap();
2286 prop_assert!(w.pdf(t) >= 0.0, "PDF({t}) must be >= 0");
2287 }
2288
2289 #[test]
2290 fn weibull_reliability_plus_cdf_is_one(
2291 shape in 0.5_f64..5.0,
2292 scale in 1.0_f64..50.0,
2293 t in 0.001_f64..100.0,
2294 ) {
2295 let w = Weibull::new(shape, scale).unwrap();
2296 let sum = w.cdf(t) + w.reliability(t);
2297 prop_assert!(
2298 (sum - 1.0).abs() < 1e-12,
2299 "CDF + R should = 1, got {sum}"
2300 );
2301 }
2302
2303 #[test]
2304 fn weibull_variance_positive(
2305 shape in 0.1_f64..10.0,
2306 scale in 0.1_f64..100.0,
2307 ) {
2308 let w = Weibull::new(shape, scale).unwrap();
2309 prop_assert!(w.variance() > 0.0, "variance must be positive");
2310 }
2311
2312 #[test]
2315 fn exponential_cdf_in_01(
2316 rate in 0.01_f64..10.0,
2317 x in 0.0_f64..100.0,
2318 ) {
2319 let e = Exponential::new(rate).unwrap();
2320 let c = e.cdf(x);
2321 prop_assert!((0.0..=1.0).contains(&c), "CDF({x}) = {c}");
2322 }
2323
2324 #[test]
2325 fn exponential_quantile_roundtrip(
2326 rate in 0.01_f64..10.0,
2327 p in 0.001_f64..0.999,
2328 ) {
2329 let e = Exponential::new(rate).unwrap();
2330 let x = e.quantile(p).unwrap();
2331 let p_back = e.cdf(x);
2332 prop_assert!((p_back - p).abs() < 1e-10);
2333 }
2334
2335 #[test]
2338 fn gamma_cdf_in_01(
2339 shape in 0.5_f64..10.0,
2340 rate in 0.1_f64..10.0,
2341 x in 0.001_f64..50.0,
2342 ) {
2343 let g = GammaDistribution::new(shape, rate).unwrap();
2344 let c = g.cdf(x);
2345 prop_assert!((0.0..=1.0).contains(&c), "CDF({x}) = {c}");
2346 }
2347
2348 #[test]
2349 fn gamma_variance_positive(
2350 shape in 0.1_f64..10.0,
2351 rate in 0.1_f64..10.0,
2352 ) {
2353 let g = GammaDistribution::new(shape, rate).unwrap();
2354 prop_assert!(g.variance() > 0.0, "variance must be positive");
2355 }
2356
2357 #[test]
2360 fn beta_cdf_in_01(
2361 alpha in 0.5_f64..10.0,
2362 beta_param in 0.5_f64..10.0,
2363 x in 0.001_f64..0.999,
2364 ) {
2365 let b = BetaDistribution::new(alpha, beta_param).unwrap();
2366 let c = b.cdf(x);
2367 prop_assert!((0.0..=1.0).contains(&c), "CDF({x}) = {c}");
2368 }
2369
2370 #[test]
2371 fn beta_quantile_roundtrip(
2372 alpha in 1.0_f64..10.0,
2373 beta_param in 1.0_f64..10.0,
2374 p in 0.01_f64..0.99,
2375 ) {
2376 let b = BetaDistribution::new(alpha, beta_param).unwrap();
2377 let x = b.quantile(p).unwrap();
2378 let p_back = b.cdf(x);
2379 prop_assert!((p_back - p).abs() < 1e-5, "p={p}, p_back={p_back}");
2380 }
2381
2382 #[test]
2385 fn chi2_cdf_in_01(
2386 k in 1.0_f64..20.0,
2387 x in 0.001_f64..50.0,
2388 ) {
2389 let chi2 = ChiSquared::new(k).unwrap();
2390 let c = chi2.cdf(x);
2391 prop_assert!((0.0..=1.0).contains(&c), "CDF({x}) = {c}");
2392 }
2393
2394 #[test]
2395 fn chi2_variance_positive(
2396 k in 0.5_f64..50.0,
2397 ) {
2398 let chi2 = ChiSquared::new(k).unwrap();
2399 prop_assert!(chi2.variance() > 0.0, "variance must be positive");
2400 }
2401 }
2402}