diff options
| author | bors <bors@rust-lang.org> | 2013-11-01 18:36:42 -0700 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2013-11-01 18:36:42 -0700 |
| commit | c15038db0843835bb075e2b37547e22a65604b0b (patch) | |
| tree | 89b00a21b8842cdf5f9b6a8804a7ecd42bac5905 /src/libstd | |
| parent | 894c1f6398648c273043ca385f5185e49dccea08 (diff) | |
| parent | 3c25baa540be6186bba380e4352ef5f676deb6d6 (diff) | |
| download | rust-c15038db0843835bb075e2b37547e22a65604b0b.tar.gz rust-c15038db0843835bb075e2b37547e22a65604b0b.zip | |
auto merge of #10223 : huonw/rust/gamma, r=cmr
Implements the [Gamma distribution](https://en.wikipedia.org/wiki/Gamma_distribution), using the algorithm described by Marsaglia & Tsang 2000[1]. I added tests checking that the mean and variance of this implementation is as expected for a range of values of the parameters in https://github.com/huonw/random-tests/commit/5d87c00a0fb69c8fa173593714cef76ddfddb651 (they pass locally, but obviously won't even build on Travis until this is merged). Also, moves `std::rand::distributions` to a subfolder, and performs a minor clean-up of the benchmarking (makes the number of iterations shared by the whole `std::rand` subtree). [1]: George Marsaglia and Wai Wan Tsang. 2000. "A Simple Method for Generating Gamma Variables" *ACM Trans. Math. Softw.* 26, 3 (September 2000), 363-372. DOI:[10.1145/358407.358414](http://doi.acm.org/10.1145/358407.358414).
Diffstat (limited to 'src/libstd')
| -rw-r--r-- | src/libstd/rand/distributions/gamma.rs | 216 | ||||
| -rw-r--r-- | src/libstd/rand/distributions/mod.rs (renamed from src/libstd/rand/distributions.rs) | 14 | ||||
| -rw-r--r-- | src/libstd/rand/distributions/range.rs (renamed from src/libstd/rand/range.rs) | 0 | ||||
| -rw-r--r-- | src/libstd/rand/distributions/ziggurat_tables.rs (renamed from src/libstd/rand/ziggurat_tables.rs) | 0 | ||||
| -rw-r--r-- | src/libstd/rand/mod.rs | 22 |
5 files changed, 234 insertions, 18 deletions
diff --git a/src/libstd/rand/distributions/gamma.rs b/src/libstd/rand/distributions/gamma.rs new file mode 100644 index 00000000000..735b083df75 --- /dev/null +++ b/src/libstd/rand/distributions/gamma.rs @@ -0,0 +1,216 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The Gamma distribution. + +use rand::Rng; +use super::{IndependentSample, Sample, StandardNormal, Exp}; +use num; + +/// The Gamma distribution `Gamma(shape, scale)` distribution. +/// +/// The density function of this distribution is +/// +/// ``` +/// f(x) = x^(k - 1) * exp(-x / θ) / (Γ(k) * θ^k) +/// ``` +/// +/// where `Γ` is the Gamma function, `k` is the shape and `θ` is the +/// scale and both `k` and `θ` are strictly positive. +/// +/// The algorithm used is that described by Marsaglia & Tsang 2000[1], +/// falling back to directly sampling from an Exponential for `shape +/// == 1`, and using the boosting technique described in [1] for +/// `shape < 1`. +/// +/// # Example +/// +/// ```rust +/// use std::rand; +/// use std::rand::distributions::{IndependentSample, Gamma}; +/// +/// fn main() { +/// let gamma = Gamma::new(2.0, 5.0); +/// let v = gamma.ind_sample(rand::task_rng()); +/// println!("{} is from a Gamma(2, 5) distribution", v); +/// } +/// ``` +/// +/// [1]: George Marsaglia and Wai Wan Tsang. 2000. "A Simple Method +/// for Generating Gamma Variables" *ACM Trans. Math. Softw.* 26, 3 +/// (September 2000), +/// 363-372. DOI:[10.1145/358407.358414](http://doi.acm.org/10.1145/358407.358414) +pub enum Gamma { + priv Large(GammaLargeShape), + priv One(Exp), + priv Small(GammaSmallShape) +} + +// These two helpers could be made public, but saving the +// match-on-Gamma-enum branch from using them directly (e.g. if one +// knows that the shape is always > 1) doesn't appear to be much +// faster. + +/// Gamma distribution where the shape parameter is less than 1. +/// +/// Note, samples from this require a compulsory floating-point `pow` +/// call, which makes it significantly slower than sampling from a +/// gamma distribution where the shape parameter is greater than or +/// equal to 1. +/// +/// See `Gamma` for sampling from a Gamma distribution with general +/// shape parameters. +struct GammaSmallShape { + inv_shape: f64, + large_shape: GammaLargeShape +} + +/// Gamma distribution where the shape parameter is larger than 1. +/// +/// See `Gamma` for sampling from a Gamma distribution with general +/// shape parameters. +struct GammaLargeShape { + shape: f64, + scale: f64, + c: f64, + d: f64 +} + +impl Gamma { + /// Construct an object representing the `Gamma(shape, scale)` + /// distribution. + /// + /// Fails if `shape <= 0` or `scale <= 0`. + pub fn new(shape: f64, scale: f64) -> Gamma { + assert!(shape > 0.0, "Gamma::new called with shape <= 0"); + assert!(scale > 0.0, "Gamma::new called with scale <= 0"); + + match shape { + 1.0 => One(Exp::new(1.0 / scale)), + 0.0 .. 1.0 => Small(GammaSmallShape::new_raw(shape, scale)), + _ => Large(GammaLargeShape::new_raw(shape, scale)) + } + } +} + +impl GammaSmallShape { + fn new_raw(shape: f64, scale: f64) -> GammaSmallShape { + GammaSmallShape { + inv_shape: 1. / shape, + large_shape: GammaLargeShape::new_raw(shape + 1.0, scale) + } + } +} + +impl GammaLargeShape { + fn new_raw(shape: f64, scale: f64) -> GammaLargeShape { + let d = shape - 1. / 3.; + GammaLargeShape { + shape: shape, + scale: scale, + c: 1. / num::sqrt(9. * d), + d: d + } + } +} + +impl Sample<f64> for Gamma { + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } +} +impl Sample<f64> for GammaSmallShape { + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } +} +impl Sample<f64> for GammaLargeShape { + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } +} + +impl IndependentSample<f64> for Gamma { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { + match *self { + Small(ref g) => g.ind_sample(rng), + One(ref g) => g.ind_sample(rng), + Large(ref g) => g.ind_sample(rng), + } + } +} +impl IndependentSample<f64> for GammaSmallShape { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { + // Need (0, 1) here. + let mut u = rng.gen::<f64>(); + while u == 0. { + u = rng.gen(); + } + + self.large_shape.ind_sample(rng) * num::pow(u, self.inv_shape) + } +} +impl IndependentSample<f64> for GammaLargeShape { + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { + loop { + let x = *rng.gen::<StandardNormal>(); + let v_cbrt = 1.0 + self.c * x; + if v_cbrt <= 0.0 { // a^3 <= 0 iff a <= 0 + continue + } + + let v = v_cbrt * v_cbrt * v_cbrt; + // Need (0, 1) here, not [0, 1). This would be faster if + // we were generating an f64 in (0, 1) directly. + let mut u = rng.gen::<f64>(); + while u == 0.0 { + u = rng.gen(); + } + + let x_sqr = x * x; + if u < 1.0 - 0.0331 * x_sqr * x_sqr || + num::ln(u) < 0.5 * x_sqr + self.d * (1.0 - v + num::ln(v)) { + return self.d * v * self.scale + } + } + } +} + +#[cfg(test)] +mod bench { + use super::*; + use mem::size_of; + use rand::distributions::IndependentSample; + use rand::{StdRng, RAND_BENCH_N}; + use extra::test::BenchHarness; + use iter::range; + use option::{Some, None}; + + + #[bench] + fn bench_gamma_large_shape(bh: &mut BenchHarness) { + let gamma = Gamma::new(10., 1.0); + let mut rng = StdRng::new(); + + do bh.iter { + for _ in range(0, RAND_BENCH_N) { + gamma.ind_sample(&mut rng); + } + } + bh.bytes = size_of::<f64>() as u64 * RAND_BENCH_N; + } + + #[bench] + fn bench_gamma_small_shape(bh: &mut BenchHarness) { + let gamma = Gamma::new(0.1, 1.0); + let mut rng = StdRng::new(); + + do bh.iter { + for _ in range(0, RAND_BENCH_N) { + gamma.ind_sample(&mut rng); + } + } + bh.bytes = size_of::<f64>() as u64 * RAND_BENCH_N; + } +} diff --git a/src/libstd/rand/distributions.rs b/src/libstd/rand/distributions/mod.rs index a6fa1a2bd87..c8324386470 100644 --- a/src/libstd/rand/distributions.rs +++ b/src/libstd/rand/distributions/mod.rs @@ -27,8 +27,10 @@ use rand::{Rng,Rand}; use clone::Clone; pub use self::range::Range; +pub use self::gamma::Gamma; pub mod range; +pub mod gamma; /// Types that can be used to create a random instance of `Support`. pub trait Sample<Support> { @@ -554,25 +556,23 @@ mod tests { #[cfg(test)] mod bench { use extra::test::BenchHarness; - use rand::*; + use rand::{XorShiftRng, RAND_BENCH_N}; use super::*; use iter::range; use option::{Some, None}; use mem::size_of; - static N: u64 = 100; - #[bench] fn rand_normal(bh: &mut BenchHarness) { let mut rng = XorShiftRng::new(); let mut normal = Normal::new(-2.71828, 3.14159); do bh.iter { - for _ in range(0, N) { + for _ in range(0, RAND_BENCH_N) { normal.sample(&mut rng); } } - bh.bytes = size_of::<f64>() as u64 * N; + bh.bytes = size_of::<f64>() as u64 * RAND_BENCH_N; } #[bench] fn rand_exp(bh: &mut BenchHarness) { @@ -580,10 +580,10 @@ mod bench { let mut exp = Exp::new(2.71828 * 3.14159); do bh.iter { - for _ in range(0, N) { + for _ in range(0, RAND_BENCH_N) { exp.sample(&mut rng); } } - bh.bytes = size_of::<f64>() as u64 * N; + bh.bytes = size_of::<f64>() as u64 * RAND_BENCH_N; } } diff --git a/src/libstd/rand/range.rs b/src/libstd/rand/distributions/range.rs index 1b805a0b8f7..1b805a0b8f7 100644 --- a/src/libstd/rand/range.rs +++ b/src/libstd/rand/distributions/range.rs diff --git a/src/libstd/rand/ziggurat_tables.rs b/src/libstd/rand/distributions/ziggurat_tables.rs index 049ef3dbb59..049ef3dbb59 100644 --- a/src/libstd/rand/ziggurat_tables.rs +++ b/src/libstd/rand/distributions/ziggurat_tables.rs diff --git a/src/libstd/rand/mod.rs b/src/libstd/rand/mod.rs index a6efdfc66db..e3208981751 100644 --- a/src/libstd/rand/mod.rs +++ b/src/libstd/rand/mod.rs @@ -833,58 +833,58 @@ mod test { } } +static RAND_BENCH_N: u64 = 100; + #[cfg(test)] mod bench { use extra::test::BenchHarness; - use rand::*; + use rand::{XorShiftRng, StdRng, IsaacRng, Isaac64Rng, Rng, RAND_BENCH_N}; use mem::size_of; use iter::range; use option::{Some, None}; - static N: u64 = 100; - #[bench] fn rand_xorshift(bh: &mut BenchHarness) { let mut rng = XorShiftRng::new(); do bh.iter { - for _ in range(0, N) { + for _ in range(0, RAND_BENCH_N) { rng.gen::<uint>(); } } - bh.bytes = size_of::<uint>() as u64 * N; + bh.bytes = size_of::<uint>() as u64 * RAND_BENCH_N; } #[bench] fn rand_isaac(bh: &mut BenchHarness) { let mut rng = IsaacRng::new(); do bh.iter { - for _ in range(0, N) { + for _ in range(0, RAND_BENCH_N) { rng.gen::<uint>(); } } - bh.bytes = size_of::<uint>() as u64 * N; + bh.bytes = size_of::<uint>() as u64 * RAND_BENCH_N; } #[bench] fn rand_isaac64(bh: &mut BenchHarness) { let mut rng = Isaac64Rng::new(); do bh.iter { - for _ in range(0, N) { + for _ in range(0, RAND_BENCH_N) { rng.gen::<uint>(); } } - bh.bytes = size_of::<uint>() as u64 * N; + bh.bytes = size_of::<uint>() as u64 * RAND_BENCH_N; } #[bench] fn rand_std(bh: &mut BenchHarness) { let mut rng = StdRng::new(); do bh.iter { - for _ in range(0, N) { + for _ in range(0, RAND_BENCH_N) { rng.gen::<uint>(); } } - bh.bytes = size_of::<uint>() as u64 * N; + bh.bytes = size_of::<uint>() as u64 * RAND_BENCH_N; } #[bench] |
