1use crate::{Distribution, OpenClosed01};
12use core::fmt;
13use num_traits::Float;
14use rand::Rng;
15
16#[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum Error {
45 LocationNotFinite,
47 ScaleNotPositive,
49 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 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}