1use num_traits::Float;
11use crate::{Distribution, Standard};
12use rand::Rng;
13use core::fmt;
14
15#[derive(Clone, Copy, Debug)]
35#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
36pub struct Triangular<F>
37where F: Float, Standard: Distribution<F>
38{
39 min: F,
40 max: F,
41 mode: F,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum TriangularError {
47 RangeTooSmall,
49 ModeRange,
51}
52
53impl fmt::Display for TriangularError {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 f.write_str(match self {
56 TriangularError::RangeTooSmall => {
57 "requirement min <= max is not met in triangular distribution"
58 }
59 TriangularError::ModeRange => "mode is outside [min, max] in triangular distribution",
60 })
61 }
62}
63
64#[cfg(feature = "std")]
65#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
66impl std::error::Error for TriangularError {}
67
68impl<F> Triangular<F>
69where F: Float, Standard: Distribution<F>
70{
71 #[inline]
73 pub fn new(min: F, max: F, mode: F) -> Result<Triangular<F>, TriangularError> {
74 if !(max >= min) {
75 return Err(TriangularError::RangeTooSmall);
76 }
77 if !(mode >= min && max >= mode) {
78 return Err(TriangularError::ModeRange);
79 }
80 Ok(Triangular { min, max, mode })
81 }
82}
83
84impl<F> Distribution<F> for Triangular<F>
85where F: Float, Standard: Distribution<F>
86{
87 #[inline]
88 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F {
89 let f: F = rng.sample(Standard);
90 let diff_mode_min = self.mode - self.min;
91 let range = self.max - self.min;
92 let f_range = f * range;
93 if f_range < diff_mode_min {
94 self.min + (f_range * diff_mode_min).sqrt()
95 } else {
96 self.max - ((range - f_range) * (self.max - self.mode)).sqrt()
97 }
98 }
99}
100
101#[cfg(test)]
102mod test {
103 use super::*;
104 use rand::{rngs::mock, Rng};
105
106 #[test]
107 fn test_triangular() {
108 let mut half_rng = mock::StepRng::new(0x8000_0000_0000_0000, 0);
109 assert_eq!(half_rng.gen::<f64>(), 0.5);
110 for &(min, max, mode, median) in &[
111 (-1., 1., 0., 0.),
112 (1., 2., 1., 2. - 0.5f64.sqrt()),
113 (5., 25., 25., 5. + 200f64.sqrt()),
114 (1e-5, 1e5, 1e-3, 1e5 - 4999999949.5f64.sqrt()),
115 (0., 1., 0.9, 0.45f64.sqrt()),
116 (-4., -0.5, -2., -4.0 + 3.5f64.sqrt()),
117 ] {
118 #[cfg(feature = "std")]
119 std::println!("{} {} {} {}", min, max, mode, median);
120 let distr = Triangular::new(min, max, mode).unwrap();
121 assert_eq!(distr.sample(&mut half_rng), median);
123 }
124
125 for &(min, max, mode) in &[
126 (-1., 1., 2.),
127 (-1., 1., -2.),
128 (2., 1., 1.),
129 ] {
130 assert!(Triangular::new(min, max, mode).is_err());
131 }
132 }
133}