summaryrefslogtreecommitdiff
path: root/rand/rand_distr/src/exponential.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rand/rand_distr/src/exponential.rs')
-rw-r--r--rand/rand_distr/src/exponential.rs145
1 files changed, 145 insertions, 0 deletions
diff --git a/rand/rand_distr/src/exponential.rs b/rand/rand_distr/src/exponential.rs
new file mode 100644
index 0000000..8322489
--- /dev/null
+++ b/rand/rand_distr/src/exponential.rs
@@ -0,0 +1,145 @@
+// Copyright 2018 Developers of the Rand project.
+// Copyright 2013 The Rust Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! The exponential distribution.
+
+use rand::Rng;
+use crate::{ziggurat_tables, Distribution};
+use crate::utils::{ziggurat, Float};
+
+/// Samples floating-point numbers according to the exponential distribution,
+/// with rate parameter `λ = 1`. This is equivalent to `Exp::new(1.0)` or
+/// sampling with `-rng.gen::<f64>().ln()`, but faster.
+///
+/// See `Exp` for the general exponential distribution.
+///
+/// Implemented via the ZIGNOR variant[^1] of the Ziggurat method. The exact
+/// description in the paper was adjusted to use tables for the exponential
+/// distribution rather than normal.
+///
+/// [^1]: Jurgen A. Doornik (2005). [*An Improved Ziggurat Method to
+/// Generate Normal Random Samples*](
+/// https://www.doornik.com/research/ziggurat.pdf).
+/// Nuffield College, Oxford
+///
+/// # Example
+/// ```
+/// use rand::prelude::*;
+/// use rand_distr::Exp1;
+///
+/// let val: f64 = thread_rng().sample(Exp1);
+/// println!("{}", val);
+/// ```
+#[derive(Clone, Copy, Debug)]
+pub struct Exp1;
+
+impl Distribution<f32> for Exp1 {
+ #[inline]
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f32 {
+ // TODO: use optimal 32-bit implementation
+ let x: f64 = self.sample(rng);
+ x as f32
+ }
+}
+
+// This could be done via `-rng.gen::<f64>().ln()` but that is slower.
+impl Distribution<f64> for Exp1 {
+ #[inline]
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
+ #[inline]
+ fn pdf(x: f64) -> f64 {
+ (-x).exp()
+ }
+ #[inline]
+ fn zero_case<R: Rng + ?Sized>(rng: &mut R, _u: f64) -> f64 {
+ ziggurat_tables::ZIG_EXP_R - rng.gen::<f64>().ln()
+ }
+
+ ziggurat(rng, false,
+ &ziggurat_tables::ZIG_EXP_X,
+ &ziggurat_tables::ZIG_EXP_F,
+ pdf, zero_case)
+ }
+}
+
+/// The exponential distribution `Exp(lambda)`.
+///
+/// This distribution has density function: `f(x) = lambda * exp(-lambda * x)`
+/// for `x > 0`.
+///
+/// Note that [`Exp1`](crate::Exp1) is an optimised implementation for `lambda = 1`.
+///
+/// # Example
+///
+/// ```
+/// use rand_distr::{Exp, Distribution};
+///
+/// let exp = Exp::new(2.0).unwrap();
+/// let v = exp.sample(&mut rand::thread_rng());
+/// println!("{} is from a Exp(2) distribution", v);
+/// ```
+#[derive(Clone, Copy, Debug)]
+pub struct Exp<N> {
+ /// `lambda` stored as `1/lambda`, since this is what we scale by.
+ lambda_inverse: N
+}
+
+/// Error type returned from `Exp::new`.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Error {
+ /// `lambda <= 0` or `nan`.
+ LambdaTooSmall,
+}
+
+impl<N: Float> Exp<N>
+where Exp1: Distribution<N>
+{
+ /// Construct a new `Exp` with the given shape parameter
+ /// `lambda`.
+ #[inline]
+ pub fn new(lambda: N) -> Result<Exp<N>, Error> {
+ if !(lambda > N::from(0.0)) {
+ return Err(Error::LambdaTooSmall);
+ }
+ Ok(Exp { lambda_inverse: N::from(1.0) / lambda })
+ }
+}
+
+impl<N: Float> Distribution<N> for Exp<N>
+where Exp1: Distribution<N>
+{
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> N {
+ rng.sample(Exp1) * self.lambda_inverse
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::Distribution;
+ use super::Exp;
+
+ #[test]
+ fn test_exp() {
+ let exp = Exp::new(10.0).unwrap();
+ let mut rng = crate::test::rng(221);
+ for _ in 0..1000 {
+ assert!(exp.sample(&mut rng) >= 0.0);
+ }
+ }
+ #[test]
+ #[should_panic]
+ fn test_exp_invalid_lambda_zero() {
+ Exp::new(0.0).unwrap();
+ }
+ #[test]
+ #[should_panic]
+ fn test_exp_invalid_lambda_neg() {
+ Exp::new(-10.0).unwrap();
+ }
+}