use num_traits::Float;
use crate::{Distribution, Standard};
use rand::Rng;
use core::fmt;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub struct Triangular<F>
where F: Float, Standard: Distribution<F>
{
min: F,
max: F,
mode: F,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TriangularError {
RangeTooSmall,
ModeRange,
}
impl fmt::Display for TriangularError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
TriangularError::RangeTooSmall => {
"requirement min <= max is not met in triangular distribution"
}
TriangularError::ModeRange => "mode is outside [min, max] in triangular distribution",
})
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
impl std::error::Error for TriangularError {}
impl<F> Triangular<F>
where F: Float, Standard: Distribution<F>
{
#[inline]
pub fn new(min: F, max: F, mode: F) -> Result<Triangular<F>, TriangularError> {
if !(max >= min) {
return Err(TriangularError::RangeTooSmall);
}
if !(mode >= min && max >= mode) {
return Err(TriangularError::ModeRange);
}
Ok(Triangular { min, max, mode })
}
}
impl<F> Distribution<F> for Triangular<F>
where F: Float, Standard: Distribution<F>
{
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F {
let f: F = rng.sample(Standard);
let diff_mode_min = self.mode - self.min;
let range = self.max - self.min;
let f_range = f * range;
if f_range < diff_mode_min {
self.min + (f_range * diff_mode_min).sqrt()
} else {
self.max - ((range - f_range) * (self.max - self.mode)).sqrt()
}
}
}
#[cfg(test)]
mod test {
use super::*;
use rand::{rngs::mock, Rng};
#[test]
fn test_triangular() {
let mut half_rng = mock::StepRng::new(0x8000_0000_0000_0000, 0);
assert_eq!(half_rng.gen::<f64>(), 0.5);
for &(min, max, mode, median) in &[
(-1., 1., 0., 0.),
(1., 2., 1., 2. - 0.5f64.sqrt()),
(5., 25., 25., 5. + 200f64.sqrt()),
(1e-5, 1e5, 1e-3, 1e5 - 4999999949.5f64.sqrt()),
(0., 1., 0.9, 0.45f64.sqrt()),
(-4., -0.5, -2., -4.0 + 3.5f64.sqrt()),
] {
#[cfg(feature = "std")]
std::println!("{} {} {} {}", min, max, mode, median);
let distr = Triangular::new(min, max, mode).unwrap();
assert_eq!(distr.sample(&mut half_rng), median);
}
for &(min, max, mode) in &[
(-1., 1., 2.),
(-1., 1., -2.),
(2., 1., 1.),
] {
assert!(Triangular::new(min, max, mode).is_err());
}
}
}