rand_distr/
frechet.rs

1// Copyright 2021 Developers of the Rand project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! The Fréchet distribution.
10
11use crate::{Distribution, OpenClosed01};
12use core::fmt;
13use num_traits::Float;
14use rand::Rng;
15
16/// Samples floating-point numbers according to the Fréchet distribution
17///
18/// This distribution has density function:
19/// `f(x) = [(x - μ) / σ]^(-1 - α) exp[-(x - μ) / σ]^(-α) α / σ`,
20/// where `μ` is the location parameter, `σ` the scale parameter, and `α` the shape parameter.
21///
22/// # Example
23/// ```
24/// use rand::prelude::*;
25/// use rand_distr::Frechet;
26///
27/// let val: f64 = thread_rng().sample(Frechet::new(0.0, 1.0, 1.0).unwrap());
28/// println!("{}", val);
29/// ```
30#[derive(Clone, Copy, Debug)]
31#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
32pub struct Frechet<F>
33where
34    F: Float,
35    OpenClosed01: Distribution<F>,
36{
37    location: F,
38    scale: F,
39    shape: F,
40}
41
42/// Error type returned from `Frechet::new`.
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum Error {
45    /// location is infinite or NaN
46    LocationNotFinite,
47    /// scale is not finite positive number
48    ScaleNotPositive,
49    /// shape is not finite positive number
50    ShapeNotPositive,
51}
52
53impl fmt::Display for Error {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        f.write_str(match self {
56            Error::LocationNotFinite => "location is not finite in Frechet distribution",
57            Error::ScaleNotPositive => "scale is not positive and finite in Frechet distribution",
58            Error::ShapeNotPositive => "shape is not positive and finite in Frechet distribution",
59        })
60    }
61}
62
63#[cfg(feature = "std")]
64#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
65impl std::error::Error for Error {}
66
67impl<F> Frechet<F>
68where
69    F: Float,
70    OpenClosed01: Distribution<F>,
71{
72    /// Construct a new `Frechet` distribution with given `location`, `scale`, and `shape`.
73    pub fn new(location: F, scale: F, shape: F) -> Result<Frechet<F>, Error> {
74        if scale <= F::zero() || scale.is_infinite() || scale.is_nan() {
75            return Err(Error::ScaleNotPositive);
76        }
77        if shape <= F::zero() || shape.is_infinite() || shape.is_nan() {
78            return Err(Error::ShapeNotPositive);
79        }
80        if location.is_infinite() || location.is_nan() {
81            return Err(Error::LocationNotFinite);
82        }
83        Ok(Frechet {
84            location,
85            scale,
86            shape,
87        })
88    }
89}
90
91impl<F> Distribution<F> for Frechet<F>
92where
93    F: Float,
94    OpenClosed01: Distribution<F>,
95{
96    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F {
97        let x: F = rng.sample(OpenClosed01);
98        self.location + self.scale * (-x.ln()).powf(-self.shape.recip())
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    #[should_panic]
108    fn test_zero_scale() {
109        Frechet::new(0.0, 0.0, 1.0).unwrap();
110    }
111
112    #[test]
113    #[should_panic]
114    fn test_infinite_scale() {
115        Frechet::new(0.0, core::f64::INFINITY, 1.0).unwrap();
116    }
117
118    #[test]
119    #[should_panic]
120    fn test_nan_scale() {
121        Frechet::new(0.0, core::f64::NAN, 1.0).unwrap();
122    }
123
124    #[test]
125    #[should_panic]
126    fn test_zero_shape() {
127        Frechet::new(0.0, 1.0, 0.0).unwrap();
128    }
129
130    #[test]
131    #[should_panic]
132    fn test_infinite_shape() {
133        Frechet::new(0.0, 1.0, core::f64::INFINITY).unwrap();
134    }
135
136    #[test]
137    #[should_panic]
138    fn test_nan_shape() {
139        Frechet::new(0.0, 1.0, core::f64::NAN).unwrap();
140    }
141
142    #[test]
143    #[should_panic]
144    fn test_infinite_location() {
145        Frechet::new(core::f64::INFINITY, 1.0, 1.0).unwrap();
146    }
147
148    #[test]
149    #[should_panic]
150    fn test_nan_location() {
151        Frechet::new(core::f64::NAN, 1.0, 1.0).unwrap();
152    }
153
154    #[test]
155    fn test_sample_against_cdf() {
156        fn quantile_function(x: f64) -> f64 {
157            (-x.ln()).recip()
158        }
159        let location = 0.0;
160        let scale = 1.0;
161        let shape = 1.0;
162        let iterations = 100_000;
163        let increment = 1.0 / iterations as f64;
164        let probabilities = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
165        let mut quantiles = [0.0; 9];
166        for (i, p) in probabilities.iter().enumerate() {
167            quantiles[i] = quantile_function(*p);
168        }
169        let mut proportions = [0.0; 9];
170        let d = Frechet::new(location, scale, shape).unwrap();
171        let mut rng = crate::test::rng(1);
172        for _ in 0..iterations {
173            let replicate = d.sample(&mut rng);
174            for (i, q) in quantiles.iter().enumerate() {
175                if replicate < *q {
176                    proportions[i] += increment;
177                }
178            }
179        }
180        assert!(proportions
181            .iter()
182            .zip(&probabilities)
183            .all(|(p_hat, p)| (p_hat - p).abs() < 0.003))
184    }
185}