Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 4 random distributions trivially derivable from the currently implemented ones. #10859

Merged
merged 5 commits into from
Dec 9, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions src/libstd/rand/distributions/exponential.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// 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 exponential distribution.

use rand::{Rng, Rand};
use rand::distributions::{ziggurat, ziggurat_tables, Sample, IndependentSample};

/// A wrapper around an `f64` to generate Exp(1) random numbers.
///
/// See `Exp` for the general exponential distribution.Note that this
// has to be unwrapped before use as an `f64` (using either
/// `*` or `cast::transmute` is safe).
///
/// 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*](http://www.doornik.com/research/ziggurat.pdf). Nuffield
/// College, Oxford
pub struct Exp1(f64);

// This could be done via `-rng.gen::<f64>().ln()` but that is slower.
impl Rand for Exp1 {
#[inline]
fn rand<R:Rng>(rng: &mut R) -> Exp1 {
#[inline]
fn pdf(x: f64) -> f64 {
(-x).exp()
}
#[inline]
fn zero_case<R:Rng>(rng: &mut R, _u: f64) -> f64 {
ziggurat_tables::ZIG_EXP_R - rng.gen::<f64>().ln()
}

Exp1(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`.
///
/// # Example
///
/// ```rust
/// use std::rand;
/// use std::rand::distributions::{Exp, IndependentSample};
///
/// fn main() {
/// let exp = Exp::new(2.0);
/// let v = exp.ind_sample(&mut rand::task_rng());
/// println!("{} is from a Exp(2) distribution", v);
/// }
/// ```
pub struct Exp {
/// `lambda` stored as `1/lambda`, since this is what we scale by.
priv lambda_inverse: f64
}

impl Exp {
/// Construct a new `Exp` with the given shape parameter
/// `lambda`. Fails if `lambda <= 0`.
pub fn new(lambda: f64) -> Exp {
assert!(lambda > 0.0, "Exp::new called with `lambda` <= 0");
Exp { lambda_inverse: 1.0 / lambda }
}
}

impl Sample<f64> for Exp {
fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) }
}
impl IndependentSample<f64> for Exp {
fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 {
(*rng.gen::<Exp1>()) * self.lambda_inverse
}
}

#[cfg(test)]
mod test {
use rand::*;
use super::*;
use iter::range;
use option::{Some, None};

#[test]
fn test_exp() {
let mut exp = Exp::new(10.0);
let mut rng = task_rng();
for _ in range(0, 1000) {
assert!(exp.sample(&mut rng) >= 0.0);
assert!(exp.ind_sample(&mut rng) >= 0.0);
}
}
#[test]
#[should_fail]
fn test_exp_invalid_lambda_zero() {
Exp::new(0.0);
}
#[test]
#[should_fail]
fn test_exp_invalid_lambda_neg() {
Exp::new(-10.0);
}
}

#[cfg(test)]
mod bench {
use extra::test::BenchHarness;
use rand::{XorShiftRng, RAND_BENCH_N};
use super::*;
use iter::range;
use option::{Some, None};
use mem::size_of;

#[bench]
fn rand_exp(bh: &mut BenchHarness) {
let mut rng = XorShiftRng::new();
let mut exp = Exp::new(2.71828 * 3.14159);

bh.iter(|| {
for _ in range(0, RAND_BENCH_N) {
exp.sample(&mut rng);
}
});
bh.bytes = size_of::<f64>() as u64 * RAND_BENCH_N;
}
}
212 changes: 210 additions & 2 deletions src/libstd/rand/distributions/gamma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! The Gamma distribution.
//! The Gamma and derived distributions.

use rand::{Rng, Open01};
use super::{IndependentSample, Sample, StandardNormal, Exp};
use super::{IndependentSample, Sample, Exp};
use super::normal::StandardNormal;
use num;

/// The Gamma distribution `Gamma(shape, scale)` distribution.
Expand Down Expand Up @@ -168,6 +169,213 @@ impl IndependentSample<f64> for GammaLargeShape {
}
}

/// The chi-squared distribution `χ²(k)`, where `k` is the degrees of
/// freedom.
///
/// For `k > 0` integral, this distribution is the sum of the squares
/// of `k` independent standard normal random variables. For other
/// `k`, this uses the equivalent characterisation `χ²(k) = Gamma(k/2,
/// 2)`.
///
/// # Example
///
/// ```rust
/// use std::rand;
/// use std::rand::distributions::{ChiSquared, IndependentSample};
///
/// fn main() {
/// let chi = ChiSquared::new(11.0);
/// let v = chi.ind_sample(&mut rand::task_rng());
/// println!("{} is from a χ²(11) distribution", v)
/// }
/// ```
pub enum ChiSquared {
// k == 1, Gamma(alpha, ..) is particularly slow for alpha < 1,
// e.g. when alpha = 1/2 as it would be for this case, so special-
// casing and using the definition of N(0,1)^2 is faster.
priv DoFExactlyOne,
priv DoFAnythingElse(Gamma)
}

impl ChiSquared {
/// Create a new chi-squared distribution with degrees-of-freedom
/// `k`. Fails if `k < 0`.
pub fn new(k: f64) -> ChiSquared {
if k == 1.0 {
DoFExactlyOne
} else {
assert!(k > 0.0, "ChiSquared::new called with `k` < 0");
DoFAnythingElse(Gamma::new(0.5 * k, 2.0))
}
}
}
impl Sample<f64> for ChiSquared {
fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) }
}
impl IndependentSample<f64> for ChiSquared {
fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 {
match *self {
DoFExactlyOne => {
// k == 1 => N(0,1)^2
let norm = *rng.gen::<StandardNormal>();
norm * norm
}
DoFAnythingElse(ref g) => g.ind_sample(rng)
}
}
}

/// The Fisher F distribution `F(m, n)`.
///
/// This distribution is equivalent to the ratio of two normalised
/// chi-squared distributions, that is, `F(m,n) = (χ²(m)/m) /
/// (χ²(n)/n)`.
///
/// # Example
///
/// ```rust
/// use std::rand;
/// use std::rand::distributions::{FisherF, IndependentSample};
///
/// fn main() {
/// let f = FisherF::new(2.0, 32.0);
/// let v = f.ind_sample(&mut rand::task_rng());
/// println!("{} is from an F(2, 32) distribution", v)
/// }
/// ```
pub struct FisherF {
priv numer: ChiSquared,
priv denom: ChiSquared,
// denom_dof / numer_dof so that this can just be a straight
// multiplication, rather than a division.
priv dof_ratio: f64,
}

impl FisherF {
/// Create a new `FisherF` distribution, with the given
/// parameter. Fails if either `m` or `n` are not positive.
pub fn new(m: f64, n: f64) -> FisherF {
assert!(m > 0.0, "FisherF::new called with `m < 0`");
assert!(n > 0.0, "FisherF::new called with `n < 0`");

FisherF {
numer: ChiSquared::new(m),
denom: ChiSquared::new(n),
dof_ratio: n / m
}
}
}
impl Sample<f64> for FisherF {
fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) }
}
impl IndependentSample<f64> for FisherF {
fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 {
self.numer.ind_sample(rng) / self.denom.ind_sample(rng) * self.dof_ratio
}
}

/// The Student t distribution, `t(nu)`, where `nu` is the degrees of
/// freedom.
///
/// # Example
///
/// ```rust
/// use std::rand;
/// use std::rand::distributions::{StudentT, IndependentSample};
///
/// fn main() {
/// let t = StudentT::new(11.0);
/// let v = t.ind_sample(&mut rand::task_rng());
/// println!("{} is from a t(11) distribution", v)
/// }
/// ```
pub struct StudentT {
priv chi: ChiSquared,
priv dof: f64
}

impl StudentT {
/// Create a new Student t distribution with `n` degrees of
/// freedom. Fails if `n <= 0`.
pub fn new(n: f64) -> StudentT {
assert!(n > 0.0, "StudentT::new called with `n <= 0`");
StudentT {
chi: ChiSquared::new(n),
dof: n
}
}
}
impl Sample<f64> for StudentT {
fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) }
}
impl IndependentSample<f64> for StudentT {
fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 {
let norm = *rng.gen::<StandardNormal>();
norm * (self.dof / self.chi.ind_sample(rng)).sqrt()
}
}

#[cfg(test)]
mod test {
use rand::*;
use super::*;
use iter::range;
use option::{Some, None};

#[test]
fn test_chi_squared_one() {
let mut chi = ChiSquared::new(1.0);
let mut rng = task_rng();
for _ in range(0, 1000) {
chi.sample(&mut rng);
chi.ind_sample(&mut rng);
}
}
#[test]
fn test_chi_squared_small() {
let mut chi = ChiSquared::new(0.5);
let mut rng = task_rng();
for _ in range(0, 1000) {
chi.sample(&mut rng);
chi.ind_sample(&mut rng);
}
}
#[test]
fn test_chi_squared_large() {
let mut chi = ChiSquared::new(30.0);
let mut rng = task_rng();
for _ in range(0, 1000) {
chi.sample(&mut rng);
chi.ind_sample(&mut rng);
}
}
#[test]
#[should_fail]
fn test_log_normal_invalid_dof() {
ChiSquared::new(-1.0);
}

#[test]
fn test_f() {
let mut f = FisherF::new(2.0, 32.0);
let mut rng = task_rng();
for _ in range(0, 1000) {
f.sample(&mut rng);
f.ind_sample(&mut rng);
}
}

#[test]
fn test_t() {
let mut t = StudentT::new(11.0);
let mut rng = task_rng();
for _ in range(0, 1000) {
t.sample(&mut rng);
t.ind_sample(&mut rng);
}
}
}

#[cfg(test)]
mod bench {
use super::*;
Expand Down
Loading