1use crate::distribution::{Continuous, ContinuousCDF};
2use crate::statistics::*;
3use std::f64;
4
5#[derive(Copy, Clone, PartialEq, Debug)]
20pub struct Triangular {
21 min: f64,
22 max: f64,
23 mode: f64,
24}
25
26#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
28#[non_exhaustive]
29pub enum TriangularError {
30 MinInvalid,
32
33 MaxInvalid,
35
36 ModeInvalid,
38
39 ModeOutOfRange,
41
42 MinEqualsMax,
44}
45
46impl std::fmt::Display for TriangularError {
47 #[cfg_attr(coverage_nightly, coverage(off))]
48 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
49 match self {
50 TriangularError::MinInvalid => write!(f, "Minimum is NaN or infinite."),
51 TriangularError::MaxInvalid => write!(f, "Maximum is NaN or infinite."),
52 TriangularError::ModeInvalid => write!(f, "Mode is NaN or infinite."),
53 TriangularError::ModeOutOfRange => {
54 write!(f, "Mode is less than minimum or greater than maximum")
55 }
56 TriangularError::MinEqualsMax => write!(f, "Minimum equals Maximum"),
57 }
58 }
59}
60
61impl std::error::Error for TriangularError {}
62
63impl Triangular {
64 pub fn new(min: f64, max: f64, mode: f64) -> Result<Triangular, TriangularError> {
84 if !min.is_finite() {
85 return Err(TriangularError::MinInvalid);
86 }
87
88 if !max.is_finite() {
89 return Err(TriangularError::MaxInvalid);
90 }
91
92 if !mode.is_finite() {
93 return Err(TriangularError::ModeInvalid);
94 }
95
96 if max < mode || mode < min {
97 return Err(TriangularError::ModeOutOfRange);
98 }
99
100 if min == max {
101 return Err(TriangularError::MinEqualsMax);
102 }
103
104 Ok(Triangular { min, max, mode })
105 }
106}
107
108impl std::fmt::Display for Triangular {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 write!(f, "Triangular([{},{}], {})", self.min, self.max, self.mode)
111 }
112}
113
114#[cfg(feature = "rand")]
115#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
116impl ::rand::distributions::Distribution<f64> for Triangular {
117 fn sample<R: ::rand::Rng + ?Sized>(&self, rng: &mut R) -> f64 {
118 sample_unchecked(rng, self.min, self.max, self.mode)
119 }
120}
121
122impl ContinuousCDF<f64, f64> for Triangular {
123 fn cdf(&self, x: f64) -> f64 {
141 let a = self.min;
142 let b = self.max;
143 let c = self.mode;
144 if x <= a {
145 0.0
146 } else if x <= c {
147 (x - a) * (x - a) / ((b - a) * (c - a))
148 } else if x < b {
149 1.0 - (b - x) * (b - x) / ((b - a) * (b - c))
150 } else {
151 1.0
152 }
153 }
154
155 fn sf(&self, x: f64) -> f64 {
172 let a = self.min;
173 let b = self.max;
174 let c = self.mode;
175 if x <= a {
176 1.0
177 } else if x <= c {
178 1.0 - ((x - a) * (x - a) / ((b - a) * (c - a)))
179 } else if x < b {
180 (b - x) * (b - x) / ((b - a) * (b - c))
181 } else {
182 0.0
183 }
184 }
185
186 fn inverse_cdf(&self, p: f64) -> f64 {
200 let a = self.min;
201 let b = self.max;
202 let c = self.mode;
203 if !(0.0..=1.0).contains(&p) {
204 panic!("x must be in [0, 1]");
205 }
206
207 if p < (c - a) / (b - a) {
208 a + ((c - a) * (b - a) * p).sqrt()
209 } else {
210 b - ((b - a) * (b - c) * (1.0 - p)).sqrt()
211 }
212 }
213}
214
215impl Min<f64> for Triangular {
216 fn min(&self) -> f64 {
223 self.min
224 }
225}
226
227impl Max<f64> for Triangular {
228 fn max(&self) -> f64 {
235 self.max
236 }
237}
238
239impl Distribution<f64> for Triangular {
240 fn mean(&self) -> Option<f64> {
248 Some((self.min + self.max + self.mode) / 3.0)
249 }
250
251 fn variance(&self) -> Option<f64> {
259 let a = self.min;
260 let b = self.max;
261 let c = self.mode;
262 Some((a * a + b * b + c * c - a * b - a * c - b * c) / 18.0)
263 }
264
265 fn entropy(&self) -> Option<f64> {
273 Some(0.5 + ((self.max - self.min) / 2.0).ln())
274 }
275
276 fn skewness(&self) -> Option<f64> {
287 let a = self.min;
288 let b = self.max;
289 let c = self.mode;
290 let q = f64::consts::SQRT_2 * (a + b - 2.0 * c) * (2.0 * a - b - c) * (a - 2.0 * b + c);
291 let d = 5.0 * (a * a + b * b + c * c - a * b - a * c - b * c).powf(3.0 / 2.0);
292 Some(q / d)
293 }
294}
295
296impl Median<f64> for Triangular {
297 fn median(&self) -> f64 {
309 let a = self.min;
310 let b = self.max;
311 let c = self.mode;
312 if c >= (a + b) / 2.0 {
313 a + ((b - a) * (c - a) / 2.0).sqrt()
314 } else {
315 b - ((b - a) * (b - c) / 2.0).sqrt()
316 }
317 }
318}
319
320impl Mode<Option<f64>> for Triangular {
321 fn mode(&self) -> Option<f64> {
329 Some(self.mode)
330 }
331}
332
333impl Continuous<f64, f64> for Triangular {
334 fn pdf(&self, x: f64) -> f64 {
352 let a = self.min;
353 let b = self.max;
354 let c = self.mode;
355 if a <= x && x <= c {
356 2.0 * (x - a) / ((b - a) * (c - a))
357 } else if c < x && x <= b {
358 2.0 * (b - x) / ((b - a) * (b - c))
359 } else {
360 0.0
361 }
362 }
363
364 fn ln_pdf(&self, x: f64) -> f64 {
382 self.pdf(x).ln()
383 }
384}
385
386#[cfg(feature = "rand")]
387#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
388fn sample_unchecked<R: ::rand::Rng + ?Sized>(rng: &mut R, min: f64, max: f64, mode: f64) -> f64 {
389 let f: f64 = rng.gen();
390 if f < (mode - min) / (max - min) {
391 min + (f * (max - min) * (mode - min)).sqrt()
392 } else {
393 max - ((1.0 - f) * (max - min) * (max - mode)).sqrt()
394 }
395}
396
397#[rustfmt::skip]
398#[cfg(test)]
399mod tests {
400 use super::*;
401 use crate::distribution::internal::*;
402 use crate::testing_boiler;
403
404 testing_boiler!(min: f64, max: f64, mode: f64; Triangular; TriangularError);
405
406 #[test]
407 fn test_create() {
408 create_ok(-1.0, 1.0, 0.0);
409 create_ok(1.0, 2.0, 1.0);
410 create_ok(5.0, 25.0, 25.0);
411 create_ok(1.0e-5, 1.0e5, 1.0e-3);
412 create_ok(0.0, 1.0, 0.9);
413 create_ok(-4.0, -0.5, -2.0);
414 create_ok(-13.039, 8.42, 1.17);
415 }
416
417 #[test]
418 fn test_bad_create() {
419 let invalid = [
420 (0.0, 0.0, 0.0, TriangularError::MinEqualsMax),
421 (0.0, 1.0, -0.1, TriangularError::ModeOutOfRange),
422 (0.0, 1.0, 1.1, TriangularError::ModeOutOfRange),
423 (0.0, -1.0, 0.5, TriangularError::ModeOutOfRange),
424 (2.0, 1.0, 1.5, TriangularError::ModeOutOfRange),
425 (f64::NAN, 1.0, 0.5, TriangularError::MinInvalid),
426 (0.2, f64::NAN, 0.5, TriangularError::MaxInvalid),
427 (0.5, 1.0, f64::NAN, TriangularError::ModeInvalid),
428 (f64::NAN, f64::NAN, f64::NAN, TriangularError::MinInvalid),
429 (f64::NEG_INFINITY, 1.0, 0.5, TriangularError::MinInvalid),
430 (0.0, f64::INFINITY, 0.5, TriangularError::MaxInvalid),
431 ];
432
433 for (min, max, mode, err) in invalid {
434 test_create_err(min, max, mode, err);
435 }
436 }
437
438 #[test]
439 fn test_variance() {
440 let variance = |x: Triangular| x.variance().unwrap();
441 test_exact(0.0, 1.0, 0.5, 0.75 / 18.0, variance);
442 test_exact(0.0, 1.0, 0.75, 0.8125 / 18.0, variance);
443 test_exact(-5.0, 8.0, -3.5, 151.75 / 18.0, variance);
444 test_exact(-5.0, 8.0, 5.0, 139.0 / 18.0, variance);
445 test_exact(-5.0, -3.0, -4.0, 3.0 / 18.0, variance);
446 test_exact(15.0, 134.0, 21.0, 13483.0 / 18.0, variance);
447 }
448
449 #[test]
450 fn test_entropy() {
451 let entropy = |x: Triangular| x.entropy().unwrap();
452 test_absolute(0.0, 1.0, 0.5, -0.1931471805599453094172, 1e-16, entropy);
453 test_absolute(0.0, 1.0, 0.75, -0.1931471805599453094172, 1e-16, entropy);
454 test_exact(-5.0, 8.0, -3.5, 2.371802176901591426636, entropy);
455 test_exact(-5.0, 8.0, 5.0, 2.371802176901591426636, entropy);
456 test_exact(-5.0, -3.0, -4.0, 0.5, entropy);
457 test_exact(15.0, 134.0, 21.0, 4.585976312551584075938, entropy);
458 }
459
460 #[test]
461 fn test_skewness() {
462 let skewness = |x: Triangular| x.skewness().unwrap();
463 test_exact(0.0, 1.0, 0.5, 0.0, skewness);
464 test_exact(0.0, 1.0, 0.75, -0.4224039833745502226059, skewness);
465 test_exact(-5.0, 8.0, -3.5, 0.5375093589712976359809, skewness);
466 test_exact(-5.0, 8.0, 5.0, -0.4445991743012595633537, skewness);
467 test_exact(-5.0, -3.0, -4.0, 0.0, skewness);
468 test_exact(15.0, 134.0, 21.0, 0.5605920922751860613217, skewness);
469 }
470
471 #[test]
472 fn test_mode() {
473 let mode = |x: Triangular| x.mode().unwrap();
474 test_exact(0.0, 1.0, 0.5, 0.5, mode);
475 test_exact(0.0, 1.0, 0.75, 0.75, mode);
476 test_exact(-5.0, 8.0, -3.5, -3.5, mode);
477 test_exact(-5.0, 8.0, 5.0, 5.0, mode);
478 test_exact(-5.0, -3.0, -4.0, -4.0, mode);
479 test_exact(15.0, 134.0, 21.0, 21.0, mode);
480 }
481
482 #[test]
483 fn test_median() {
484 let median = |x: Triangular| x.median();
485 test_exact(0.0, 1.0, 0.5, 0.5, median);
486 test_exact(0.0, 1.0, 0.75, 0.6123724356957945245493, median);
487 test_absolute(-5.0, 8.0, -3.5, -0.6458082328952913226724, 1e-15, median);
488 test_absolute(-5.0, 8.0, 5.0, 3.062257748298549652367, 1e-15, median);
489 test_exact(-5.0, -3.0, -4.0, -4.0, median);
490 test_absolute(15.0, 134.0, 21.0, 52.00304883716712238797, 1e-14, median);
491 }
492
493 #[test]
494 fn test_pdf() {
495 let pdf = |arg: f64| move |x: Triangular| x.pdf(arg);
496 test_exact(0.0, 1.0, 0.5, 0.0, pdf(-1.0));
497 test_exact(0.0, 1.0, 0.5, 0.0, pdf(1.1));
498 test_exact(0.0, 1.0, 0.5, 1.0, pdf(0.25));
499 test_exact(0.0, 1.0, 0.5, 2.0, pdf(0.5));
500 test_exact(0.0, 1.0, 0.5, 1.0, pdf(0.75));
501 test_exact(-5.0, 8.0, -3.5, 0.0, pdf(-5.1));
502 test_exact(-5.0, 8.0, -3.5, 0.0, pdf(8.1));
503 test_exact(-5.0, 8.0, -3.5, 0.1025641025641025641026, pdf(-4.0));
504 test_exact(-5.0, 8.0, -3.5, 0.1538461538461538461538, pdf(-3.5));
505 test_exact(-5.0, 8.0, -3.5, 0.05351170568561872909699, pdf(4.0));
506 test_exact(-5.0, -3.0, -4.0, 0.0, pdf(-5.1));
507 test_exact(-5.0, -3.0, -4.0, 0.0, pdf(-2.9));
508 test_exact(-5.0, -3.0, -4.0, 0.5, pdf(-4.5));
509 test_exact(-5.0, -3.0, -4.0, 1.0, pdf(-4.0));
510 test_exact(-5.0, -3.0, -4.0, 0.5, pdf(-3.5));
511 }
512
513 #[test]
514 fn test_ln_pdf() {
515 let ln_pdf = |arg: f64| move |x: Triangular| x.ln_pdf(arg);
516 test_exact(0.0, 1.0, 0.5, f64::NEG_INFINITY, ln_pdf(-1.0));
517 test_exact(0.0, 1.0, 0.5, f64::NEG_INFINITY, ln_pdf(1.1));
518 test_exact(0.0, 1.0, 0.5, 0.0, ln_pdf(0.25));
519 test_exact(0.0, 1.0, 0.5, 2f64.ln(), ln_pdf(0.5));
520 test_exact(0.0, 1.0, 0.5, 0.0, ln_pdf(0.75));
521 test_exact(-5.0, 8.0, -3.5, f64::NEG_INFINITY, ln_pdf(-5.1));
522 test_exact(-5.0, 8.0, -3.5, f64::NEG_INFINITY, ln_pdf(8.1));
523 test_exact(-5.0, 8.0, -3.5, 0.1025641025641025641026f64.ln(), ln_pdf(-4.0));
524 test_exact(-5.0, 8.0, -3.5, 0.1538461538461538461538f64.ln(), ln_pdf(-3.5));
525 test_exact(-5.0, 8.0, -3.5, 0.05351170568561872909699f64.ln(), ln_pdf(4.0));
526 test_exact(-5.0, -3.0, -4.0, f64::NEG_INFINITY, ln_pdf(-5.1));
527 test_exact(-5.0, -3.0, -4.0, f64::NEG_INFINITY, ln_pdf(-2.9));
528 test_exact(-5.0, -3.0, -4.0, 0.5f64.ln(), ln_pdf(-4.5));
529 test_exact(-5.0, -3.0, -4.0, 0.0, ln_pdf(-4.0));
530 test_exact(-5.0, -3.0, -4.0, 0.5f64.ln(), ln_pdf(-3.5));
531 }
532
533 #[test]
534 fn test_cdf() {
535 let cdf = |arg: f64| move |x: Triangular| x.cdf(arg);
536 test_exact(0.0, 1.0, 0.5, 0.125, cdf(0.25));
537 test_exact(0.0, 1.0, 0.5, 0.5, cdf(0.5));
538 test_exact(0.0, 1.0, 0.5, 0.875, cdf(0.75));
539 test_exact(-5.0, 8.0, -3.5, 0.05128205128205128205128, cdf(-4.0));
540 test_exact(-5.0, 8.0, -3.5, 0.1153846153846153846154, cdf(-3.5));
541 test_exact(-5.0, 8.0, -3.5, 0.892976588628762541806, cdf(4.0));
542 test_exact(-5.0, -3.0, -4.0, 0.125, cdf(-4.5));
543 test_exact(-5.0, -3.0, -4.0, 0.5, cdf(-4.0));
544 test_exact(-5.0, -3.0, -4.0, 0.875, cdf(-3.5));
545 }
546
547 #[test]
548 fn test_cdf_lower_bound() {
549 let cdf = |arg: f64| move |x: Triangular| x.cdf(arg);
550 test_exact(0.0, 3.0, 1.5, 0.0, cdf(-1.0));
551 }
552
553 #[test]
554 fn test_cdf_upper_bound() {
555 let cdf = |arg: f64| move |x: Triangular| x.cdf(arg);
556 test_exact(0.0, 3.0, 1.5, 1.0, cdf(5.0));
557 }
558
559
560 #[test]
561 fn test_sf() {
562 let sf = |arg: f64| move |x: Triangular| x.sf(arg);
563 test_exact(0.0, 1.0, 0.5, 0.875, sf(0.25));
564 test_exact(0.0, 1.0, 0.5, 0.5, sf(0.5));
565 test_exact(0.0, 1.0, 0.5, 0.125, sf(0.75));
566 test_exact(-5.0, 8.0, -3.5, 0.9487179487179487, sf(-4.0));
567 test_exact(-5.0, 8.0, -3.5, 0.8846153846153846, sf(-3.5));
568 test_exact(-5.0, 8.0, -3.5, 0.10702341137123746, sf(4.0));
569 test_exact(-5.0, -3.0, -4.0, 0.875, sf(-4.5));
570 test_exact(-5.0, -3.0, -4.0, 0.5, sf(-4.0));
571 test_exact(-5.0, -3.0, -4.0, 0.125, sf(-3.5));
572 }
573
574 #[test]
575 fn test_sf_lower_bound() {
576 let sf = |arg: f64| move |x: Triangular| x.sf(arg);
577 test_exact(0.0, 3.0, 1.5, 1.0, sf(-1.0));
578 }
579
580 #[test]
581 fn test_sf_upper_bound() {
582 let sf = |arg: f64| move |x: Triangular| x.sf(arg);
583 test_exact(0.0, 3.0, 1.5, 0.0, sf(5.0));
584 }
585
586 #[test]
587 fn test_inverse_cdf() {
588 let func = |arg: f64| move |x: Triangular| x.inverse_cdf(x.cdf(arg));
589 test_absolute(0.0, 1.0, 0.5, 0.25, 1e-15, func(0.25));
590 test_absolute(0.0, 1.0, 0.5, 0.5, 1e-15, func(0.5));
591 test_absolute(0.0, 1.0, 0.5, 0.75, 1e-15, func(0.75));
592 test_absolute(-5.0, 8.0, -3.5, -4.0, 1e-15, func(-4.0));
593 test_absolute(-5.0, 8.0, -3.5, -3.5, 1e-15, func(-3.5));
594 test_absolute(-5.0, 8.0, -3.5, 4.0, 1e-15, func(4.0));
595 test_absolute(-5.0, -3.0, -4.0, -4.5, 1e-15, func(-4.5));
596 test_absolute(-5.0, -3.0, -4.0, -4.0, 1e-15, func(-4.0));
597 test_absolute(-5.0, -3.0, -4.0, -3.5, 1e-15, func(-3.5));
598 }
599
600 #[test]
601 fn test_continuous() {
602 test::check_continuous_distribution(&create_ok(-5.0, 5.0, 0.0), -5.0, 5.0);
603 test::check_continuous_distribution(&create_ok(-15.0, -2.0, -3.0), -15.0, -2.0);
604 }
605}