statrs/distribution/
exponential.rs

1use crate::distribution::{Continuous, ContinuousCDF};
2use crate::statistics::*;
3use std::f64;
4
5/// Implements the
6/// [Exp](https://en.wikipedia.org/wiki/Exp_distribution)
7/// distribution and is a special case of the
8/// [Gamma](https://en.wikipedia.org/wiki/Gamma_distribution) distribution
9/// (referenced [here](./struct.Gamma.html))
10///
11/// # Examples
12///
13/// ```
14/// use statrs::distribution::{Exp, Continuous};
15/// use statrs::statistics::Distribution;
16///
17/// let n = Exp::new(1.0).unwrap();
18/// assert_eq!(n.mean().unwrap(), 1.0);
19/// assert_eq!(n.pdf(1.0), 0.3678794411714423215955);
20/// ```
21#[derive(Copy, Clone, PartialEq, Debug)]
22pub struct Exp {
23    rate: f64,
24}
25
26/// Represents the errors that can occur when creating a [`Exp`].
27#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
28#[non_exhaustive]
29pub enum ExpError {
30    /// The rate is NaN, zero or less than zero.
31    RateInvalid,
32}
33
34impl std::fmt::Display for ExpError {
35    #[cfg_attr(coverage_nightly, coverage(off))]
36    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
37        match self {
38            ExpError::RateInvalid => write!(f, "Rate is NaN, zero or less than zero"),
39        }
40    }
41}
42
43impl std::error::Error for ExpError {}
44
45impl Exp {
46    /// Constructs a new exponential distribution with a
47    /// rate (λ) of `rate`.
48    ///
49    /// # Errors
50    ///
51    /// Returns an error if rate is `NaN` or `rate <= 0.0`.
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// use statrs::distribution::Exp;
57    ///
58    /// let mut result = Exp::new(1.0);
59    /// assert!(result.is_ok());
60    ///
61    /// result = Exp::new(-1.0);
62    /// assert!(result.is_err());
63    /// ```
64    pub fn new(rate: f64) -> Result<Exp, ExpError> {
65        if rate.is_nan() || rate <= 0.0 {
66            Err(ExpError::RateInvalid)
67        } else {
68            Ok(Exp { rate })
69        }
70    }
71
72    /// Returns the rate of the exponential distribution
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// use statrs::distribution::Exp;
78    ///
79    /// let n = Exp::new(1.0).unwrap();
80    /// assert_eq!(n.rate(), 1.0);
81    /// ```
82    pub fn rate(&self) -> f64 {
83        self.rate
84    }
85}
86
87impl std::fmt::Display for Exp {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        write!(f, "Exp({})", self.rate)
90    }
91}
92
93#[cfg(feature = "rand")]
94#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
95impl ::rand::distributions::Distribution<f64> for Exp {
96    fn sample<R: ::rand::Rng + ?Sized>(&self, r: &mut R) -> f64 {
97        use crate::distribution::ziggurat;
98
99        ziggurat::sample_exp_1(r) / self.rate
100    }
101}
102
103impl ContinuousCDF<f64, f64> for Exp {
104    /// Calculates the cumulative distribution function for the
105    /// exponential distribution at `x`
106    ///
107    /// # Formula
108    ///
109    /// ```text
110    /// 1 - e^(-λ * x)
111    /// ```
112    ///
113    /// where `λ` is the rate
114    fn cdf(&self, x: f64) -> f64 {
115        if x < 0.0 {
116            0.0
117        } else {
118            1.0 - (-self.rate * x).exp()
119        }
120    }
121
122    /// Calculates the cumulative distribution function for the
123    /// exponential distribution at `x`
124    ///
125    /// # Formula
126    ///
127    /// ```text
128    /// e^(-λ * x)
129    /// ```
130    ///
131    /// where `λ` is the rate
132    fn sf(&self, x: f64) -> f64 {
133        if x < 0.0 {
134            1.0
135        } else {
136            (-self.rate * x).exp()
137        }
138    }
139
140    /// Calculates the inverse cumulative distribution function.
141    ///
142    /// # Formula
143    ///
144    /// ```text
145    /// -ln(1 - p) / λ
146    /// ```
147    ///
148    /// where `p` is the probability and `λ` is the rate
149    fn inverse_cdf(&self, p: f64) -> f64 {
150        -(-p).ln_1p() / self.rate
151    }
152}
153
154impl Min<f64> for Exp {
155    /// Returns the minimum value in the domain of the exponential
156    /// distribution representable by a double precision float
157    ///
158    /// # Formula
159    ///
160    /// ```text
161    /// 0
162    /// ```
163    fn min(&self) -> f64 {
164        0.0
165    }
166}
167
168impl Max<f64> for Exp {
169    /// Returns the maximum value in the domain of the exponential
170    /// distribution representable by a double precision float
171    ///
172    /// # Formula
173    ///
174    /// ```text
175    /// f64::INFINITY
176    /// ```
177    fn max(&self) -> f64 {
178        f64::INFINITY
179    }
180}
181
182impl Distribution<f64> for Exp {
183    /// Returns the mean of the exponential distribution
184    ///
185    /// # Formula
186    ///
187    /// ```text
188    /// 1 / λ
189    /// ```
190    ///
191    /// where `λ` is the rate
192    fn mean(&self) -> Option<f64> {
193        Some(1.0 / self.rate)
194    }
195
196    /// Returns the variance of the exponential distribution
197    ///
198    /// # Formula
199    ///
200    /// ```text
201    /// 1 / λ^2
202    /// ```
203    ///
204    /// where `λ` is the rate
205    fn variance(&self) -> Option<f64> {
206        Some(1.0 / (self.rate * self.rate))
207    }
208
209    /// Returns the entropy of the exponential distribution
210    ///
211    /// # Formula
212    ///
213    /// ```text
214    /// 1 - ln(λ)
215    /// ```
216    ///
217    /// where `λ` is the rate
218    fn entropy(&self) -> Option<f64> {
219        Some(1.0 - self.rate.ln())
220    }
221
222    /// Returns the skewness of the exponential distribution
223    ///
224    /// # Formula
225    ///
226    /// ```text
227    /// 2
228    /// ```
229    fn skewness(&self) -> Option<f64> {
230        Some(2.0)
231    }
232}
233
234impl Median<f64> for Exp {
235    /// Returns the median of the exponential distribution
236    ///
237    /// # Formula
238    ///
239    /// ```text
240    /// (1 / λ) * ln2
241    /// ```
242    ///
243    /// where `λ` is the rate
244    fn median(&self) -> f64 {
245        f64::consts::LN_2 / self.rate
246    }
247}
248
249impl Mode<Option<f64>> for Exp {
250    /// Returns the mode of the exponential distribution
251    ///
252    /// # Formula
253    ///
254    /// ```text
255    /// 0
256    /// ```
257    fn mode(&self) -> Option<f64> {
258        Some(0.0)
259    }
260}
261
262impl Continuous<f64, f64> for Exp {
263    /// Calculates the probability density function for the exponential
264    /// distribution at `x`
265    ///
266    /// # Formula
267    ///
268    /// ```text
269    /// λ * e^(-λ * x)
270    /// ```
271    ///
272    /// where `λ` is the rate
273    fn pdf(&self, x: f64) -> f64 {
274        if x < 0.0 {
275            0.0
276        } else {
277            self.rate * (-self.rate * x).exp()
278        }
279    }
280
281    /// Calculates the log probability density function for the exponential
282    /// distribution at `x`
283    ///
284    /// # Formula
285    ///
286    /// ```text
287    /// ln(λ * e^(-λ * x))
288    /// ```
289    ///
290    /// where `λ` is the rate
291    fn ln_pdf(&self, x: f64) -> f64 {
292        if x < 0.0 {
293            f64::NEG_INFINITY
294        } else {
295            self.rate.ln() - self.rate * x
296        }
297    }
298}
299
300#[rustfmt::skip]
301#[cfg(test)]
302mod tests {
303    use super::*;
304    use crate::distribution::internal::*;
305    use crate::testing_boiler;
306
307    testing_boiler!(rate: f64; Exp; ExpError);
308
309    #[test]
310    fn test_create() {
311        create_ok(0.1);
312        create_ok(1.0);
313        create_ok(10.0);
314    }
315
316    #[test]
317    fn test_bad_create() {
318        create_err(f64::NAN);
319        create_err(0.0);
320        create_err(-1.0);
321        create_err(-10.0);
322    }
323
324    #[test]
325    fn test_mean() {
326        let mean = |x: Exp| x.mean().unwrap();
327        test_exact(0.1, 10.0, mean);
328        test_exact(1.0, 1.0, mean);
329        test_exact(10.0, 0.1, mean);
330    }
331
332    #[test]
333    fn test_variance() {
334        let variance = |x: Exp| x.variance().unwrap();
335        test_absolute(0.1, 100.0, 1e-13, variance);
336        test_exact(1.0, 1.0, variance);
337        test_exact(10.0, 0.01, variance);
338    }
339
340    #[test]
341    fn test_entropy() {
342        let entropy = |x: Exp| x.entropy().unwrap();
343        test_absolute(0.1, 3.302585092994045684018, 1e-15, entropy);
344        test_exact(1.0, 1.0, entropy);
345        test_absolute(10.0, -1.302585092994045684018, 1e-15, entropy);
346    }
347
348    #[test]
349    fn test_skewness() {
350        let skewness = |x: Exp| x.skewness().unwrap();
351        test_exact(0.1, 2.0, skewness);
352        test_exact(1.0, 2.0, skewness);
353        test_exact(10.0, 2.0, skewness);
354    }
355
356    #[test]
357    fn test_median() {
358        let median = |x: Exp| x.median();
359        test_absolute(0.1, 6.931471805599453094172, 1e-15, median);
360        test_exact(1.0, f64::consts::LN_2, median);
361        test_exact(10.0, 0.06931471805599453094172, median);
362    }
363
364    #[test]
365    fn test_mode() {
366        let mode = |x: Exp| x.mode().unwrap();
367        test_exact(0.1, 0.0, mode);
368        test_exact(1.0, 0.0, mode);
369        test_exact(10.0, 0.0, mode);
370    }
371
372    #[test]
373    fn test_min_max() {
374        let min = |x: Exp| x.min();
375        let max = |x: Exp| x.max();
376        test_exact(0.1, 0.0, min);
377        test_exact(1.0, 0.0, min);
378        test_exact(10.0, 0.0, min);
379        test_exact(0.1, f64::INFINITY, max);
380        test_exact(1.0, f64::INFINITY, max);
381        test_exact(10.0, f64::INFINITY, max);
382    }
383
384    #[test]
385    fn test_pdf() {
386        let pdf = |arg: f64| move |x: Exp| x.pdf(arg);
387        test_exact(0.1, 0.1, pdf(0.0));
388        test_exact(1.0, 1.0, pdf(0.0));
389        test_exact(10.0, 10.0, pdf(0.0));
390        test_is_nan(f64::INFINITY, pdf(0.0));
391        test_exact(0.1, 0.09900498337491680535739, pdf(0.1));
392        test_absolute(1.0, 0.9048374180359595731642, 1e-15, pdf(0.1));
393        test_exact(10.0, 3.678794411714423215955, pdf(0.1));
394        test_is_nan(f64::INFINITY, pdf(0.1));
395        test_exact(0.1, 0.09048374180359595731642, pdf(1.0));
396        test_exact(1.0, 0.3678794411714423215955, pdf(1.0));
397        test_absolute(10.0, 4.539992976248485153559e-4, 1e-19, pdf(1.0));
398        test_is_nan(f64::INFINITY, pdf(1.0));
399        test_exact(0.1, 0.0, pdf(f64::INFINITY));
400        test_exact(1.0, 0.0, pdf(f64::INFINITY));
401        test_exact(10.0, 0.0, pdf(f64::INFINITY));
402        test_is_nan(f64::INFINITY, pdf(f64::INFINITY));
403    }
404
405    #[test]
406    fn test_neg_pdf() {
407        let pdf = |arg: f64| move |x: Exp| x.pdf(arg);
408        test_exact(0.1, 0.0, pdf(-1.0));
409    }
410
411    #[test]
412    fn test_ln_pdf() {
413        let ln_pdf = |arg: f64| move |x: Exp| x.ln_pdf(arg);
414        test_absolute(0.1, -2.302585092994045684018, 1e-15, ln_pdf(0.0));
415        test_exact(1.0, 0.0, ln_pdf(0.0));
416        test_exact(10.0, 2.302585092994045684018, ln_pdf(0.0));
417        test_is_nan(f64::INFINITY, ln_pdf(0.0));
418        test_absolute(0.1, -2.312585092994045684018, 1e-15, ln_pdf(0.1));
419        test_exact(1.0, -0.1, ln_pdf(0.1));
420        test_absolute(10.0, 1.302585092994045684018, 1e-15, ln_pdf(0.1));
421        test_is_nan(f64::INFINITY, ln_pdf(0.1));
422        test_exact(0.1, -2.402585092994045684018, ln_pdf(1.0));
423        test_exact(1.0, -1.0, ln_pdf(1.0));
424        test_exact(10.0, -7.697414907005954315982, ln_pdf(1.0));
425        test_is_nan(f64::INFINITY, ln_pdf(1.0));
426        test_exact(0.1, f64::NEG_INFINITY, ln_pdf(f64::INFINITY));
427        test_exact(1.0, f64::NEG_INFINITY, ln_pdf(f64::INFINITY));
428        test_exact(10.0, f64::NEG_INFINITY, ln_pdf(f64::INFINITY));
429        test_is_nan(f64::INFINITY, ln_pdf(f64::INFINITY));
430    }
431
432    #[test]
433    fn test_neg_ln_pdf() {
434        let ln_pdf = |arg: f64| move |x: Exp| x.ln_pdf(arg);
435        test_exact(0.1, f64::NEG_INFINITY, ln_pdf(-1.0));
436    }
437
438    #[test]
439    fn test_cdf() {
440        let cdf = |arg: f64| move |x: Exp| x.cdf(arg);
441        test_exact(0.1, 0.0, cdf(0.0));
442        test_exact(1.0, 0.0, cdf(0.0));
443        test_exact(10.0, 0.0, cdf(0.0));
444        test_is_nan(f64::INFINITY, cdf(0.0));
445        test_absolute(0.1, 0.009950166250831946426094, 1e-16, cdf(0.1));
446        test_absolute(1.0, 0.0951625819640404268358, 1e-16, cdf(0.1));
447        test_exact(10.0, 0.6321205588285576784045, cdf(0.1));
448        test_exact(f64::INFINITY, 1.0, cdf(0.1));
449        test_absolute(0.1, 0.0951625819640404268358, 1e-16, cdf(1.0));
450        test_exact(1.0, 0.6321205588285576784045, cdf(1.0));
451        test_exact(10.0, 0.9999546000702375151485, cdf(1.0));
452        test_exact(f64::INFINITY, 1.0, cdf(1.0));
453        test_exact(0.1, 1.0, cdf(f64::INFINITY));
454        test_exact(1.0, 1.0, cdf(f64::INFINITY));
455        test_exact(10.0, 1.0, cdf(f64::INFINITY));
456        test_exact(f64::INFINITY, 1.0, cdf(f64::INFINITY));
457    }
458
459    #[test]
460    fn test_inverse_cdf() {
461        let distribution = Exp::new(0.42).unwrap();
462        assert_eq!(distribution.median(), distribution.inverse_cdf(0.5));
463
464        let distribution = Exp::new(0.042).unwrap();
465        assert_eq!(distribution.median(), distribution.inverse_cdf(0.5));
466
467        let distribution = Exp::new(0.0042).unwrap();
468        assert_eq!(distribution.median(), distribution.inverse_cdf(0.5));
469
470        let distribution = Exp::new(0.33).unwrap();
471        assert_eq!(distribution.median(), distribution.inverse_cdf(0.5));
472
473        let distribution = Exp::new(0.033).unwrap();
474        assert_eq!(distribution.median(), distribution.inverse_cdf(0.5));
475
476        let distribution = Exp::new(0.0033).unwrap();
477        assert_eq!(distribution.median(), distribution.inverse_cdf(0.5));
478    }
479
480    #[test]
481    fn test_sf() {
482        let sf = |arg: f64| move |x: Exp| x.sf(arg);
483        test_exact(0.1, 1.0, sf(0.0));
484        test_exact(1.0, 1.0, sf(0.0));
485        test_exact(10.0, 1.0, sf(0.0));
486        test_is_nan(f64::INFINITY, sf(0.0));
487        test_absolute(0.1, 0.9900498337491681, 1e-16, sf(0.1));
488        test_absolute(1.0, 0.9048374180359595, 1e-16, sf(0.1));
489        test_absolute(10.0, 0.36787944117144233, 1e-15, sf(0.1));
490        test_exact(f64::INFINITY, 0.0, sf(0.1));
491    }
492
493    #[test]
494    fn test_neg_cdf() {
495        let cdf = |arg: f64| move |x: Exp| x.cdf(arg);
496        test_exact(0.1, 0.0, cdf(-1.0));
497    }
498
499    #[test]
500    fn test_neg_sf() {
501        let sf = |arg: f64| move |x: Exp| x.sf(arg);
502        test_exact(0.1, 1.0, sf(-1.0));
503    }
504
505    #[test]
506    fn test_continuous() {
507        test::check_continuous_distribution(&create_ok(0.5), 0.0, 10.0);
508        test::check_continuous_distribution(&create_ok(1.5), 0.0, 20.0);
509        test::check_continuous_distribution(&create_ok(2.5), 0.0, 50.0);
510    }
511}