/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rand_distr-0.5.1/src/frechet.rs
Line | Count | Source |
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 `Fréchet(μ, σ, α)`. |
10 | | |
11 | | use crate::{Distribution, OpenClosed01}; |
12 | | use core::fmt; |
13 | | use num_traits::Float; |
14 | | use rand::Rng; |
15 | | |
16 | | /// The [Fréchet distribution](https://en.wikipedia.org/wiki/Fr%C3%A9chet_distribution) `Fréchet(α, μ, σ)`. |
17 | | /// |
18 | | /// The Fréchet distribution is a continuous probability distribution |
19 | | /// with location parameter `μ` (`mu`), scale parameter `σ` (`sigma`), |
20 | | /// and shape parameter `α` (`alpha`). It describes the distribution |
21 | | /// of the maximum (or minimum) of a number of random variables. |
22 | | /// It is also known as the Type II extreme value distribution. |
23 | | /// |
24 | | /// # Density function |
25 | | /// |
26 | | /// `f(x) = [(x - μ) / σ]^(-1 - α) exp[-(x - μ) / σ]^(-α) α / σ` |
27 | | /// |
28 | | /// # Plot |
29 | | /// |
30 | | /// The plot shows the Fréchet distribution with various values of `μ`, `σ`, and `α`. |
31 | | /// Note how the location parameter `μ` shifts the distribution along the x-axis, |
32 | | /// the scale parameter `σ` stretches or compresses the distribution along the x-axis, |
33 | | /// and the shape parameter `α` changes the tail behavior. |
34 | | /// |
35 | | ///  |
36 | | /// |
37 | | /// # Example |
38 | | /// |
39 | | /// ``` |
40 | | /// use rand::prelude::*; |
41 | | /// use rand_distr::Frechet; |
42 | | /// |
43 | | /// let val: f64 = rand::rng().sample(Frechet::new(0.0, 1.0, 1.0).unwrap()); |
44 | | /// println!("{}", val); |
45 | | /// ``` |
46 | | #[derive(Clone, Copy, Debug, PartialEq)] |
47 | | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
48 | | pub struct Frechet<F> |
49 | | where |
50 | | F: Float, |
51 | | OpenClosed01: Distribution<F>, |
52 | | { |
53 | | location: F, |
54 | | scale: F, |
55 | | shape: F, |
56 | | } |
57 | | |
58 | | /// Error type returned from [`Frechet::new`]. |
59 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
60 | | pub enum Error { |
61 | | /// location is infinite or NaN |
62 | | LocationNotFinite, |
63 | | /// scale is not finite positive number |
64 | | ScaleNotPositive, |
65 | | /// shape is not finite positive number |
66 | | ShapeNotPositive, |
67 | | } |
68 | | |
69 | | impl fmt::Display for Error { |
70 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
71 | 0 | f.write_str(match self { |
72 | 0 | Error::LocationNotFinite => "location is not finite in Frechet distribution", |
73 | 0 | Error::ScaleNotPositive => "scale is not positive and finite in Frechet distribution", |
74 | 0 | Error::ShapeNotPositive => "shape is not positive and finite in Frechet distribution", |
75 | | }) |
76 | 0 | } |
77 | | } |
78 | | |
79 | | #[cfg(feature = "std")] |
80 | | impl std::error::Error for Error {} |
81 | | |
82 | | impl<F> Frechet<F> |
83 | | where |
84 | | F: Float, |
85 | | OpenClosed01: Distribution<F>, |
86 | | { |
87 | | /// Construct a new `Frechet` distribution with given `location`, `scale`, and `shape`. |
88 | 0 | pub fn new(location: F, scale: F, shape: F) -> Result<Frechet<F>, Error> { |
89 | 0 | if scale <= F::zero() || scale.is_infinite() || scale.is_nan() { |
90 | 0 | return Err(Error::ScaleNotPositive); |
91 | 0 | } |
92 | 0 | if shape <= F::zero() || shape.is_infinite() || shape.is_nan() { |
93 | 0 | return Err(Error::ShapeNotPositive); |
94 | 0 | } |
95 | 0 | if location.is_infinite() || location.is_nan() { |
96 | 0 | return Err(Error::LocationNotFinite); |
97 | 0 | } |
98 | 0 | Ok(Frechet { |
99 | 0 | location, |
100 | 0 | scale, |
101 | 0 | shape, |
102 | 0 | }) |
103 | 0 | } |
104 | | } |
105 | | |
106 | | impl<F> Distribution<F> for Frechet<F> |
107 | | where |
108 | | F: Float, |
109 | | OpenClosed01: Distribution<F>, |
110 | | { |
111 | 0 | fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F { |
112 | 0 | let x: F = rng.sample(OpenClosed01); |
113 | 0 | self.location + self.scale * (-x.ln()).powf(-self.shape.recip()) |
114 | 0 | } |
115 | | } |
116 | | |
117 | | #[cfg(test)] |
118 | | mod tests { |
119 | | use super::*; |
120 | | |
121 | | #[test] |
122 | | #[should_panic] |
123 | | fn test_zero_scale() { |
124 | | Frechet::new(0.0, 0.0, 1.0).unwrap(); |
125 | | } |
126 | | |
127 | | #[test] |
128 | | #[should_panic] |
129 | | fn test_infinite_scale() { |
130 | | Frechet::new(0.0, f64::INFINITY, 1.0).unwrap(); |
131 | | } |
132 | | |
133 | | #[test] |
134 | | #[should_panic] |
135 | | fn test_nan_scale() { |
136 | | Frechet::new(0.0, f64::NAN, 1.0).unwrap(); |
137 | | } |
138 | | |
139 | | #[test] |
140 | | #[should_panic] |
141 | | fn test_zero_shape() { |
142 | | Frechet::new(0.0, 1.0, 0.0).unwrap(); |
143 | | } |
144 | | |
145 | | #[test] |
146 | | #[should_panic] |
147 | | fn test_infinite_shape() { |
148 | | Frechet::new(0.0, 1.0, f64::INFINITY).unwrap(); |
149 | | } |
150 | | |
151 | | #[test] |
152 | | #[should_panic] |
153 | | fn test_nan_shape() { |
154 | | Frechet::new(0.0, 1.0, f64::NAN).unwrap(); |
155 | | } |
156 | | |
157 | | #[test] |
158 | | #[should_panic] |
159 | | fn test_infinite_location() { |
160 | | Frechet::new(f64::INFINITY, 1.0, 1.0).unwrap(); |
161 | | } |
162 | | |
163 | | #[test] |
164 | | #[should_panic] |
165 | | fn test_nan_location() { |
166 | | Frechet::new(f64::NAN, 1.0, 1.0).unwrap(); |
167 | | } |
168 | | |
169 | | #[test] |
170 | | fn test_sample_against_cdf() { |
171 | | fn quantile_function(x: f64) -> f64 { |
172 | | (-x.ln()).recip() |
173 | | } |
174 | | let location = 0.0; |
175 | | let scale = 1.0; |
176 | | let shape = 1.0; |
177 | | let iterations = 100_000; |
178 | | let increment = 1.0 / iterations as f64; |
179 | | let probabilities = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]; |
180 | | let mut quantiles = [0.0; 9]; |
181 | | for (i, p) in probabilities.iter().enumerate() { |
182 | | quantiles[i] = quantile_function(*p); |
183 | | } |
184 | | let mut proportions = [0.0; 9]; |
185 | | let d = Frechet::new(location, scale, shape).unwrap(); |
186 | | let mut rng = crate::test::rng(1); |
187 | | for _ in 0..iterations { |
188 | | let replicate = d.sample(&mut rng); |
189 | | for (i, q) in quantiles.iter().enumerate() { |
190 | | if replicate < *q { |
191 | | proportions[i] += increment; |
192 | | } |
193 | | } |
194 | | } |
195 | | assert!(proportions |
196 | | .iter() |
197 | | .zip(&probabilities) |
198 | | .all(|(p_hat, p)| (p_hat - p).abs() < 0.003)) |
199 | | } |
200 | | |
201 | | #[test] |
202 | | fn frechet_distributions_can_be_compared() { |
203 | | assert_eq!(Frechet::new(1.0, 2.0, 3.0), Frechet::new(1.0, 2.0, 3.0)); |
204 | | } |
205 | | } |