rand_distr/
weibull.rs

1// Copyright 2018 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 Weibull distribution.
10
11use num_traits::Float;
12use crate::{Distribution, OpenClosed01};
13use rand::Rng;
14use core::fmt;
15
16/// Samples floating-point numbers according to the Weibull distribution
17///
18/// # Example
19/// ```
20/// use rand::prelude::*;
21/// use rand_distr::Weibull;
22///
23/// let val: f64 = thread_rng().sample(Weibull::new(1., 10.).unwrap());
24/// println!("{}", val);
25/// ```
26#[derive(Clone, Copy, Debug)]
27#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
28pub struct Weibull<F>
29where F: Float, OpenClosed01: Distribution<F>
30{
31    inv_shape: F,
32    scale: F,
33}
34
35/// Error type returned from `Weibull::new`.
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum Error {
38    /// `scale <= 0` or `nan`.
39    ScaleTooSmall,
40    /// `shape <= 0` or `nan`.
41    ShapeTooSmall,
42}
43
44impl fmt::Display for Error {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        f.write_str(match self {
47            Error::ScaleTooSmall => "scale is not positive in Weibull distribution",
48            Error::ShapeTooSmall => "shape is not positive in Weibull distribution",
49        })
50    }
51}
52
53#[cfg(feature = "std")]
54#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
55impl std::error::Error for Error {}
56
57impl<F> Weibull<F>
58where F: Float, OpenClosed01: Distribution<F>
59{
60    /// Construct a new `Weibull` distribution with given `scale` and `shape`.
61    pub fn new(scale: F, shape: F) -> Result<Weibull<F>, Error> {
62        if !(scale > F::zero()) {
63            return Err(Error::ScaleTooSmall);
64        }
65        if !(shape > F::zero()) {
66            return Err(Error::ShapeTooSmall);
67        }
68        Ok(Weibull {
69            inv_shape: F::from(1.).unwrap() / shape,
70            scale,
71        })
72    }
73}
74
75impl<F> Distribution<F> for Weibull<F>
76where F: Float, OpenClosed01: Distribution<F>
77{
78    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F {
79        let x: F = rng.sample(OpenClosed01);
80        self.scale * (-x.ln()).powf(self.inv_shape)
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    #[should_panic]
90    fn invalid() {
91        Weibull::new(0., 0.).unwrap();
92    }
93
94    #[test]
95    fn sample() {
96        let scale = 1.0;
97        let shape = 2.0;
98        let d = Weibull::new(scale, shape).unwrap();
99        let mut rng = crate::test::rng(1);
100        for _ in 0..1000 {
101            let r = d.sample(&mut rng);
102            assert!(r >= 0.);
103        }
104    }
105
106    #[test]
107    fn value_stability() {
108        fn test_samples<F: Float + core::fmt::Debug, D: Distribution<F>>(
109            distr: D, zero: F, expected: &[F],
110        ) {
111            let mut rng = crate::test::rng(213);
112            let mut buf = [zero; 4];
113            for x in &mut buf {
114                *x = rng.sample(&distr);
115            }
116            assert_eq!(buf, expected);
117        }
118
119        test_samples(Weibull::new(1.0, 1.0).unwrap(), 0f32, &[
120            0.041495778,
121            0.7531094,
122            1.4189332,
123            0.38386202,
124        ]);
125        test_samples(Weibull::new(2.0, 0.5).unwrap(), 0f64, &[
126            1.1343478702739669,
127            0.29470010050655226,
128            0.7556151370284702,
129            7.877212340241561,
130        ]);
131    }
132}