1use crate::distribution::{Continuous, ContinuousCDF};
2use crate::statistics::*;
3use crate::{Result, StatsError};
4use rand::Rng;
5use std::f64;
6
7#[derive(Debug, Copy, Clone, PartialEq)]
22pub struct Triangular {
23 min: f64,
24 max: f64,
25 mode: f64,
26}
27
28impl Triangular {
29 pub fn new(min: f64, max: f64, mode: f64) -> Result<Triangular> {
49 if !min.is_finite() || !max.is_finite() || !mode.is_finite() {
50 return Err(StatsError::BadParams);
51 }
52 if max < mode || mode < min {
53 return Err(StatsError::BadParams);
54 }
55 if ulps_eq!(max, min, max_ulps = 0) {
56 return Err(StatsError::BadParams);
57 }
58 Ok(Triangular { min, max, mode })
59 }
60}
61
62impl ::rand::distributions::Distribution<f64> for Triangular {
63 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
64 sample_unchecked(rng, self.min, self.max, self.mode)
65 }
66}
67
68impl ContinuousCDF<f64, f64> for Triangular {
69 fn cdf(&self, x: f64) -> f64 {
87 let a = self.min;
88 let b = self.max;
89 let c = self.mode;
90 if x <= a {
91 0.0
92 } else if x <= c {
93 (x - a) * (x - a) / ((b - a) * (c - a))
94 } else if x < b {
95 1.0 - (b - x) * (b - x) / ((b - a) * (b - c))
96 } else {
97 1.0
98 }
99 }
100
101 fn sf(&self, x: f64) -> f64 {
118 let a = self.min;
119 let b = self.max;
120 let c = self.mode;
121 if x <= a {
122 1.0
123 } else if x <= c {
124 1.0 - ((x - a) * (x - a) / ((b - a) * (c - a)))
125 } else if x < b {
126 (b - x) * (b - x) / ((b - a) * (b - c))
127 } else {
128 0.0
129 }
130 }
131}
132
133impl Min<f64> for Triangular {
134 fn min(&self) -> f64 {
141 self.min
142 }
143}
144
145impl Max<f64> for Triangular {
146 fn max(&self) -> f64 {
153 self.max
154 }
155}
156
157impl Distribution<f64> for Triangular {
158 fn mean(&self) -> Option<f64> {
166 Some((self.min + self.max + self.mode) / 3.0)
167 }
168 fn variance(&self) -> Option<f64> {
176 let a = self.min;
177 let b = self.max;
178 let c = self.mode;
179 Some((a * a + b * b + c * c - a * b - a * c - b * c) / 18.0)
180 }
181 fn entropy(&self) -> Option<f64> {
189 Some(0.5 + ((self.max - self.min) / 2.0).ln())
190 }
191 fn skewness(&self) -> Option<f64> {
202 let a = self.min;
203 let b = self.max;
204 let c = self.mode;
205 let q = f64::consts::SQRT_2 * (a + b - 2.0 * c) * (2.0 * a - b - c) * (a - 2.0 * b + c);
206 let d = 5.0 * (a * a + b * b + c * c - a * b - a * c - b * c).powf(3.0 / 2.0);
207 Some(q / d)
208 }
209}
210
211impl Median<f64> for Triangular {
212 fn median(&self) -> f64 {
224 let a = self.min;
225 let b = self.max;
226 let c = self.mode;
227 if c >= (a + b) / 2.0 {
228 a + ((b - a) * (c - a) / 2.0).sqrt()
229 } else {
230 b - ((b - a) * (b - c) / 2.0).sqrt()
231 }
232 }
233}
234
235impl Mode<Option<f64>> for Triangular {
236 fn mode(&self) -> Option<f64> {
244 Some(self.mode)
245 }
246}
247
248impl Continuous<f64, f64> for Triangular {
249 fn pdf(&self, x: f64) -> f64 {
267 let a = self.min;
268 let b = self.max;
269 let c = self.mode;
270 if a <= x && x <= c {
271 2.0 * (x - a) / ((b - a) * (c - a))
272 } else if c < x && x <= b {
273 2.0 * (b - x) / ((b - a) * (b - c))
274 } else {
275 0.0
276 }
277 }
278
279 fn ln_pdf(&self, x: f64) -> f64 {
297 self.pdf(x).ln()
298 }
299}
300
301fn sample_unchecked<R: Rng + ?Sized>(rng: &mut R, min: f64, max: f64, mode: f64) -> f64 {
302 let f: f64 = rng.gen();
303 if f < (mode - min) / (max - min) {
304 min + (f * (max - min) * (mode - min)).sqrt()
305 } else {
306 max - ((1.0 - f) * (max - min) * (max - mode)).sqrt()
307 }
308}
309
310#[rustfmt::skip]
311#[cfg(all(test, feature = "nightly"))]
312mod tests {
313 use std::fmt::Debug;
314 use crate::statistics::*;
315 use crate::distribution::{ContinuousCDF, Continuous, Triangular};
316 use crate::distribution::internal::*;
317 use crate::consts::ACC;
318
319 fn try_create(min: f64, max: f64, mode: f64) -> Triangular {
320 let n = Triangular::new(min, max, mode);
321 assert!(n.is_ok());
322 n.unwrap()
323 }
324
325 fn create_case(min: f64, max: f64, mode: f64) {
326 let n = try_create(min, max, mode);
327 assert_eq!(n.min(), min);
328 assert_eq!(n.max(), max);
329 assert_eq!(n.mode().unwrap(), mode);
330 }
331
332 fn bad_create_case(min: f64, max: f64, mode: f64) {
333 let n = Triangular::new(min, max, mode);
334 assert!(n.is_err());
335 }
336
337 fn get_value<T, F>(min: f64, max: f64, mode: f64, eval: F) -> T
338 where T: PartialEq + Debug,
339 F: Fn(Triangular) -> T
340 {
341 let n = try_create(min, max, mode);
342 eval(n)
343 }
344
345 fn test_case<F>(min: f64, max: f64, mode: f64, expected: f64, eval: F)
346 where F: Fn(Triangular) -> f64
347 {
348 let x = get_value(min, max, mode, eval);
349 assert_eq!(expected, x);
350 }
351
352 fn test_almost<F>(min: f64, max: f64, mode: f64, expected: f64, acc: f64, eval: F)
353 where F: Fn(Triangular) -> f64
354 {
355 let x = get_value(min, max, mode, eval);
356 assert_almost_eq!(expected, x, acc);
357 }
358
359 #[test]
360 fn test_create() {
361 create_case(-1.0, 1.0, 0.0);
362 create_case(1.0, 2.0, 1.0);
363 create_case(5.0, 25.0, 25.0);
364 create_case(1.0e-5, 1.0e5, 1.0e-3);
365 create_case(0.0, 1.0, 0.9);
366 create_case(-4.0, -0.5, -2.0);
367 create_case(-13.039, 8.42, 1.17);
368 }
369
370 #[test]
371 fn test_bad_create() {
372 bad_create_case(0.0, 0.0, 0.0);
373 bad_create_case(0.0, 1.0, -0.1);
374 bad_create_case(0.0, 1.0, 1.1);
375 bad_create_case(0.0, -1.0, 0.5);
376 bad_create_case(2.0, 1.0, 1.5);
377 bad_create_case(f64::NAN, 1.0, 0.5);
378 bad_create_case(0.2, f64::NAN, 0.5);
379 bad_create_case(0.5, 1.0, f64::NAN);
380 bad_create_case(f64::NAN, f64::NAN, f64::NAN);
381 bad_create_case(f64::NEG_INFINITY, 1.0, 0.5);
382 bad_create_case(0.0, f64::INFINITY, 0.5);
383 }
384
385 #[test]
386 fn test_variance() {
387 let variance = |x: Triangular| x.variance().unwrap();
388 test_case(0.0, 1.0, 0.5, 0.75 / 18.0, variance);
389 test_case(0.0, 1.0, 0.75, 0.8125 / 18.0, variance);
390 test_case(-5.0, 8.0, -3.5, 151.75 / 18.0, variance);
391 test_case(-5.0, 8.0, 5.0, 139.0 / 18.0, variance);
392 test_case(-5.0, -3.0, -4.0, 3.0 / 18.0, variance);
393 test_case(15.0, 134.0, 21.0, 13483.0 / 18.0, variance);
394 }
395
396 #[test]
397 fn test_entropy() {
398 let entropy = |x: Triangular| x.entropy().unwrap();
399 test_almost(0.0, 1.0, 0.5, -0.1931471805599453094172, 1e-16, entropy);
400 test_almost(0.0, 1.0, 0.75, -0.1931471805599453094172, 1e-16, entropy);
401 test_case(-5.0, 8.0, -3.5, 2.371802176901591426636, entropy);
402 test_case(-5.0, 8.0, 5.0, 2.371802176901591426636, entropy);
403 test_case(-5.0, -3.0, -4.0, 0.5, entropy);
404 test_case(15.0, 134.0, 21.0, 4.585976312551584075938, entropy);
405 }
406
407 #[test]
408 fn test_skewness() {
409 let skewness = |x: Triangular| x.skewness().unwrap();
410 test_case(0.0, 1.0, 0.5, 0.0, skewness);
411 test_case(0.0, 1.0, 0.75, -0.4224039833745502226059, skewness);
412 test_case(-5.0, 8.0, -3.5, 0.5375093589712976359809, skewness);
413 test_case(-5.0, 8.0, 5.0, -0.4445991743012595633537, skewness);
414 test_case(-5.0, -3.0, -4.0, 0.0, skewness);
415 test_case(15.0, 134.0, 21.0, 0.5605920922751860613217, skewness);
416 }
417
418 #[test]
419 fn test_mode() {
420 let mode = |x: Triangular| x.mode().unwrap();
421 test_case(0.0, 1.0, 0.5, 0.5, mode);
422 test_case(0.0, 1.0, 0.75, 0.75, mode);
423 test_case(-5.0, 8.0, -3.5, -3.5, mode);
424 test_case(-5.0, 8.0, 5.0, 5.0, mode);
425 test_case(-5.0, -3.0, -4.0, -4.0, mode);
426 test_case(15.0, 134.0, 21.0, 21.0, mode);
427 }
428
429 #[test]
430 fn test_median() {
431 let median = |x: Triangular| x.median();
432 test_case(0.0, 1.0, 0.5, 0.5, median);
433 test_case(0.0, 1.0, 0.75, 0.6123724356957945245493, median);
434 test_almost(-5.0, 8.0, -3.5, -0.6458082328952913226724, 1e-15, median);
435 test_almost(-5.0, 8.0, 5.0, 3.062257748298549652367, 1e-15, median);
436 test_case(-5.0, -3.0, -4.0, -4.0, median);
437 test_almost(15.0, 134.0, 21.0, 52.00304883716712238797, 1e-14, median);
438 }
439
440 #[test]
441 fn test_pdf() {
442 let pdf = |arg: f64| move |x: Triangular| x.pdf(arg);
443 test_case(0.0, 1.0, 0.5, 0.0, pdf(-1.0));
444 test_case(0.0, 1.0, 0.5, 0.0, pdf(1.1));
445 test_case(0.0, 1.0, 0.5, 1.0, pdf(0.25));
446 test_case(0.0, 1.0, 0.5, 2.0, pdf(0.5));
447 test_case(0.0, 1.0, 0.5, 1.0, pdf(0.75));
448 test_case(-5.0, 8.0, -3.5, 0.0, pdf(-5.1));
449 test_case(-5.0, 8.0, -3.5, 0.0, pdf(8.1));
450 test_case(-5.0, 8.0, -3.5, 0.1025641025641025641026, pdf(-4.0));
451 test_case(-5.0, 8.0, -3.5, 0.1538461538461538461538, pdf(-3.5));
452 test_case(-5.0, 8.0, -3.5, 0.05351170568561872909699, pdf(4.0));
453 test_case(-5.0, -3.0, -4.0, 0.0, pdf(-5.1));
454 test_case(-5.0, -3.0, -4.0, 0.0, pdf(-2.9));
455 test_case(-5.0, -3.0, -4.0, 0.5, pdf(-4.5));
456 test_case(-5.0, -3.0, -4.0, 1.0, pdf(-4.0));
457 test_case(-5.0, -3.0, -4.0, 0.5, pdf(-3.5));
458 }
459
460 #[test]
461 fn test_ln_pdf() {
462 let ln_pdf = |arg: f64| move |x: Triangular| x.ln_pdf(arg);
463 test_case(0.0, 1.0, 0.5, f64::NEG_INFINITY, ln_pdf(-1.0));
464 test_case(0.0, 1.0, 0.5, f64::NEG_INFINITY, ln_pdf(1.1));
465 test_case(0.0, 1.0, 0.5, 0.0, ln_pdf(0.25));
466 test_case(0.0, 1.0, 0.5, 2f64.ln(), ln_pdf(0.5));
467 test_case(0.0, 1.0, 0.5, 0.0, ln_pdf(0.75));
468 test_case(-5.0, 8.0, -3.5, f64::NEG_INFINITY, ln_pdf(-5.1));
469 test_case(-5.0, 8.0, -3.5, f64::NEG_INFINITY, ln_pdf(8.1));
470 test_case(-5.0, 8.0, -3.5, 0.1025641025641025641026f64.ln(), ln_pdf(-4.0));
471 test_case(-5.0, 8.0, -3.5, 0.1538461538461538461538f64.ln(), ln_pdf(-3.5));
472 test_case(-5.0, 8.0, -3.5, 0.05351170568561872909699f64.ln(), ln_pdf(4.0));
473 test_case(-5.0, -3.0, -4.0, f64::NEG_INFINITY, ln_pdf(-5.1));
474 test_case(-5.0, -3.0, -4.0, f64::NEG_INFINITY, ln_pdf(-2.9));
475 test_case(-5.0, -3.0, -4.0, 0.5f64.ln(), ln_pdf(-4.5));
476 test_case(-5.0, -3.0, -4.0, 0.0, ln_pdf(-4.0));
477 test_case(-5.0, -3.0, -4.0, 0.5f64.ln(), ln_pdf(-3.5));
478 }
479
480 #[test]
481 fn test_cdf() {
482 let cdf = |arg: f64| move |x: Triangular| x.cdf(arg);
483 test_case(0.0, 1.0, 0.5, 0.125, cdf(0.25));
484 test_case(0.0, 1.0, 0.5, 0.5, cdf(0.5));
485 test_case(0.0, 1.0, 0.5, 0.875, cdf(0.75));
486 test_case(-5.0, 8.0, -3.5, 0.05128205128205128205128, cdf(-4.0));
487 test_case(-5.0, 8.0, -3.5, 0.1153846153846153846154, cdf(-3.5));
488 test_case(-5.0, 8.0, -3.5, 0.892976588628762541806, cdf(4.0));
489 test_case(-5.0, -3.0, -4.0, 0.125, cdf(-4.5));
490 test_case(-5.0, -3.0, -4.0, 0.5, cdf(-4.0));
491 test_case(-5.0, -3.0, -4.0, 0.875, cdf(-3.5));
492 }
493
494 #[test]
495 fn test_cdf_lower_bound() {
496 let cdf = |arg: f64| move |x: Triangular| x.cdf(arg);
497 test_case(0.0, 3.0, 1.5, 0.0, cdf(-1.0));
498 }
499
500 #[test]
501 fn test_cdf_upper_bound() {
502 let cdf = |arg: f64| move |x: Triangular| x.cdf(arg);
503 test_case(0.0, 3.0, 1.5, 1.0, cdf(5.0));
504 }
505
506
507 #[test]
508 fn test_sf() {
509 let sf = |arg: f64| move |x: Triangular| x.sf(arg);
510 test_case(0.0, 1.0, 0.5, 0.875, sf(0.25));
511 test_case(0.0, 1.0, 0.5, 0.5, sf(0.5));
512 test_case(0.0, 1.0, 0.5, 0.125, sf(0.75));
513 test_case(-5.0, 8.0, -3.5, 0.9487179487179487, sf(-4.0));
514 test_case(-5.0, 8.0, -3.5, 0.8846153846153846, sf(-3.5));
515 test_case(-5.0, 8.0, -3.5, 0.10702341137123746, sf(4.0));
516 test_case(-5.0, -3.0, -4.0, 0.875, sf(-4.5));
517 test_case(-5.0, -3.0, -4.0, 0.5, sf(-4.0));
518 test_case(-5.0, -3.0, -4.0, 0.125, sf(-3.5));
519 }
520
521 #[test]
522 fn test_sf_lower_bound() {
523 let sf = |arg: f64| move |x: Triangular| x.sf(arg);
524 test_case(0.0, 3.0, 1.5, 1.0, sf(-1.0));
525 }
526
527 #[test]
528 fn test_sf_upper_bound() {
529 let sf = |arg: f64| move |x: Triangular| x.sf(arg);
530 test_case(0.0, 3.0, 1.5, 0.0, sf(5.0));
531 }
532
533 #[test]
534 fn test_continuous() {
535 test::check_continuous_distribution(&try_create(-5.0, 5.0, 0.0), -5.0, 5.0);
536 test::check_continuous_distribution(&try_create(-15.0, -2.0, -3.0), -15.0, -2.0);
537 }
538}