Coverage Report

Created: 2026-06-30 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
/// ![Fréchet distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/frechet.svg)
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
}