// 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 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Sampling from random distributions // Some implementations use the Ziggurat method // https://en.wikipedia.org/wiki/Ziggurat_algorithm // // The version used here is ZIGNOR [Doornik 2005, "An Improved // Ziggurat Method to Generate Normal Random Samples"] which is slower // (about double, it generates an extra random number) than the // canonical version [Marsaglia & Tsang 2000, "The Ziggurat Method for // Generating Random Variables"], but more robust. If one wanted, one // could implement VIZIGNOR the ZIGNOR paper for more speed. use num; use rand::{Rng,Rand}; mod ziggurat_tables; // inlining should mean there is no performance penalty for this #[inline] fn ziggurat(rng: &mut R, center_u: bool, X: ziggurat_tables::ZigTable, F: ziggurat_tables::ZigTable, F_DIFF: ziggurat_tables::ZigTable, pdf: &'static fn(f64) -> f64, // probability density function zero_case: &'static fn(&mut R, f64) -> f64) -> f64 { loop { let u = if center_u {2.0 * rng.gen() - 1.0} else {rng.gen()}; let i: uint = rng.gen::() & 0xff; let x = u * X[i]; let test_x = if center_u {num::abs(x)} else {x}; // algebraically equivalent to |u| < X[i+1]/X[i] (or u < X[i+1]/X[i]) if test_x < X[i + 1] { return x; } if i == 0 { return zero_case(rng, u); } // algebraically equivalent to f1 + DRanU()*(f0 - f1) < 1 if F[i+1] + F_DIFF[i+1] * rng.gen() < pdf(x) { return x; } } } /// A wrapper around an `f64` to generate N(0, 1) random numbers (a.k.a. a /// standard normal, or Gaussian). Multiplying the generated values by the /// desired standard deviation `sigma` then adding the desired mean `mu` will /// give N(mu, sigma^2) distributed random numbers. /// /// Note that this has to be unwrapped before use as an `f64` (using either /// `*` or `cast::transmute` is safe). /// /// # Example /// /// ~~~ /// use std::rand::distributions::StandardNormal; /// /// fn main() { /// let normal = 2.0 + (*rand::random::()) * 3.0; /// printfln!("%f is from a N(2, 9) distribution", normal) /// } /// ~~~ pub struct StandardNormal(f64); impl Rand for StandardNormal { fn rand(rng: &mut R) -> StandardNormal { #[inline] fn pdf(x: f64) -> f64 { ((-x*x/2.0) as f64).exp() } #[inline] fn zero_case(rng: &mut R, u: f64) -> f64 { // compute a random number in the tail by hand // strange initial conditions, because the loop is not // do-while, so the condition should be true on the first // run, they get overwritten anyway (0 < 1, so these are // good). let mut x = 1.0f64; let mut y = 0.0f64; // XXX infinities? while -2.0 * y < x * x { x = rng.gen::().ln() / ziggurat_tables::ZIG_NORM_R; y = rng.gen::().ln(); } if u < 0.0 { x - ziggurat_tables::ZIG_NORM_R } else { ziggurat_tables::ZIG_NORM_R - x } } StandardNormal(ziggurat( rng, true, // this is symmetric &ziggurat_tables::ZIG_NORM_X, &ziggurat_tables::ZIG_NORM_F, &ziggurat_tables::ZIG_NORM_F_DIFF, pdf, zero_case)) } } /// A wrapper around an `f64` to generate Exp(1) random numbers. Dividing by /// the desired rate `lambda` will give Exp(lambda) distributed random /// numbers. /// /// Note that this has to be unwrapped before use as an `f64` (using either /// `*` or `cast::transmute` is safe). /// /// # Example /// /// ~~~ /// use std::rand::distributions::Exp1; /// /// fn main() { /// let exp2 = (*rand::random::()) * 0.5; /// printfln!("%f is from a Exp(2) distribution", exp2); /// } /// ~~~ pub struct Exp1(f64); // This could be done via `-rng.gen::().ln()` but that is slower. impl Rand for Exp1 { #[inline] fn rand(rng: &mut R) -> Exp1 { #[inline] fn pdf(x: f64) -> f64 { (-x).exp() } #[inline] fn zero_case(rng: &mut R, _u: f64) -> f64 { ziggurat_tables::ZIG_EXP_R - rng.gen::().ln() } Exp1(ziggurat(rng, false, &ziggurat_tables::ZIG_EXP_X, &ziggurat_tables::ZIG_EXP_F, &ziggurat_tables::ZIG_EXP_F_DIFF, pdf, zero_case)) } }