Skip to content

Commit f7f9287

Browse files
committed
Merge branch 'stable-test-all-features' into no-std-fix
2 parents 6d4baef + cda0465 commit f7f9287

File tree

4 files changed

+190
-1
lines changed

4 files changed

+190
-1
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ jobs:
8484
cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng
8585
cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng
8686
# all stable features:
87-
cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng
87+
cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng,min_const_gen
8888
cargo test --target ${{ matrix.target }} --examples
8989
- name: Test rand_core
9090
run: |

rand_distr/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## Unreleased
88
- New `Zeta` and `Zipf` distributions (#1136)
9+
- New `Gumbel` and `Frechet` distributions (#1168, #1171)
910

1011
## [0.4.1] - 2021-06-15
1112
- Empirically test PDF of normal distribution (#1121)

rand_distr/src/frechet.rs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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+
11+
use crate::{Distribution, OpenClosed01};
12+
use core::fmt;
13+
use num_traits::Float;
14+
use 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))]
32+
pub struct Frechet<F>
33+
where
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)]
44+
pub 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+
53+
impl 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")))]
65+
impl std::error::Error for Error {}
66+
67+
impl<F> Frechet<F>
68+
where
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+
91+
impl<F> Distribution<F> for Frechet<F>
92+
where
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)]
103+
mod 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+
}

rand_distr/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
//! - [`Exp`]onential distribution, and [`Exp1`] as a primitive
5858
//! - [`Weibull`] distribution
5959
//! - [`Gumbel`] distribution
60+
//! - [`Frechet`] distribution
6061
//! - [`Zeta`] distribution
6162
//! - [`Zipf`] distribution
6263
//! - Gamma and derived distributions:
@@ -100,6 +101,7 @@ pub use self::cauchy::{Cauchy, Error as CauchyError};
100101
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
101102
pub use self::dirichlet::{Dirichlet, Error as DirichletError};
102103
pub use self::exponential::{Error as ExpError, Exp, Exp1};
104+
pub use self::frechet::{Error as FrechetError, Frechet};
103105
pub use self::gamma::{
104106
Beta, BetaError, ChiSquared, ChiSquaredError, Error as GammaError, FisherF, FisherFError,
105107
Gamma, StudentT,
@@ -186,6 +188,7 @@ mod binomial;
186188
mod cauchy;
187189
mod dirichlet;
188190
mod exponential;
191+
mod frechet;
189192
mod gamma;
190193
mod geometric;
191194
mod gumbel;

0 commit comments

Comments
 (0)