1 // Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! The Gamma and derived distributions.
13 use self::GammaRepr::*;
14 use self::ChiSquaredRepr::*;
19 use super::normal::StandardNormal;
20 use super::{IndependentSample, Sample, Exp};
22 /// The Gamma distribution `Gamma(shape, scale)` distribution.
24 /// The density function of this distribution is
27 /// f(x) = x^(k - 1) * exp(-x / θ) / (Γ(k) * θ^k)
30 /// where `Γ` is the Gamma function, `k` is the shape and `θ` is the
31 /// scale and both `k` and `θ` are strictly positive.
33 /// The algorithm used is that described by Marsaglia & Tsang 2000[1],
34 /// falling back to directly sampling from an Exponential for `shape
35 /// == 1`, and using the boosting technique described in [1] for
38 /// [1]: George Marsaglia and Wai Wan Tsang. 2000. "A Simple Method
39 /// for Generating Gamma Variables" *ACM Trans. Math. Softw.* 26, 3
41 /// 363-372. DOI:[10.1145/358407.358414](http://doi.acm.org/10.1145/358407.358414)
47 Large(GammaLargeShape),
49 Small(GammaSmallShape),
52 // These two helpers could be made public, but saving the
53 // match-on-Gamma-enum branch from using them directly (e.g. if one
54 // knows that the shape is always > 1) doesn't appear to be much
57 /// Gamma distribution where the shape parameter is less than 1.
59 /// Note, samples from this require a compulsory floating-point `pow`
60 /// call, which makes it significantly slower than sampling from a
61 /// gamma distribution where the shape parameter is greater than or
64 /// See `Gamma` for sampling from a Gamma distribution with general
66 struct GammaSmallShape {
68 large_shape: GammaLargeShape,
71 /// Gamma distribution where the shape parameter is larger than 1.
73 /// See `Gamma` for sampling from a Gamma distribution with general
75 struct GammaLargeShape {
82 /// Construct an object representing the `Gamma(shape, scale)`
85 /// Panics if `shape <= 0` or `scale <= 0`.
86 pub fn new(shape: f64, scale: f64) -> Gamma {
87 assert!(shape > 0.0, "Gamma::new called with shape <= 0");
88 assert!(scale > 0.0, "Gamma::new called with scale <= 0");
90 let repr = match shape {
91 1.0 => One(Exp::new(1.0 / scale)),
92 0.0 ... 1.0 => Small(GammaSmallShape::new_raw(shape, scale)),
93 _ => Large(GammaLargeShape::new_raw(shape, scale)),
99 impl GammaSmallShape {
100 fn new_raw(shape: f64, scale: f64) -> GammaSmallShape {
102 inv_shape: 1. / shape,
103 large_shape: GammaLargeShape::new_raw(shape + 1.0, scale),
108 impl GammaLargeShape {
109 fn new_raw(shape: f64, scale: f64) -> GammaLargeShape {
110 let d = shape - 1. / 3.;
113 c: 1. / (9. * d).sqrt(),
119 impl Sample<f64> for Gamma {
120 fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 {
124 impl Sample<f64> for GammaSmallShape {
125 fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 {
129 impl Sample<f64> for GammaLargeShape {
130 fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 {
135 impl IndependentSample<f64> for Gamma {
136 fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 {
138 Small(ref g) => g.ind_sample(rng),
139 One(ref g) => g.ind_sample(rng),
140 Large(ref g) => g.ind_sample(rng),
144 impl IndependentSample<f64> for GammaSmallShape {
145 fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 {
146 let Open01(u) = rng.gen::<Open01<f64>>();
148 self.large_shape.ind_sample(rng) * u.powf(self.inv_shape)
151 impl IndependentSample<f64> for GammaLargeShape {
152 fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 {
154 let StandardNormal(x) = rng.gen::<StandardNormal>();
155 let v_cbrt = 1.0 + self.c * x;
156 if v_cbrt <= 0.0 { // a^3 <= 0 iff a <= 0
160 let v = v_cbrt * v_cbrt * v_cbrt;
161 let Open01(u) = rng.gen::<Open01<f64>>();
164 if u < 1.0 - 0.0331 * x_sqr * x_sqr ||
165 u.ln() < 0.5 * x_sqr + self.d * (1.0 - v + v.ln()) {
166 return self.d * v * self.scale;
172 /// The chi-squared distribution `χ²(k)`, where `k` is the degrees of
175 /// For `k > 0` integral, this distribution is the sum of the squares
176 /// of `k` independent standard normal random variables. For other
177 /// `k`, this uses the equivalent characterization `χ²(k) = Gamma(k/2,
179 pub struct ChiSquared {
180 repr: ChiSquaredRepr,
183 enum ChiSquaredRepr {
184 // k == 1, Gamma(alpha, ..) is particularly slow for alpha < 1,
185 // e.g. when alpha = 1/2 as it would be for this case, so special-
186 // casing and using the definition of N(0,1)^2 is faster.
188 DoFAnythingElse(Gamma),
192 /// Create a new chi-squared distribution with degrees-of-freedom
193 /// `k`. Panics if `k < 0`.
194 pub fn new(k: f64) -> ChiSquared {
195 let repr = if k == 1.0 {
198 assert!(k > 0.0, "ChiSquared::new called with `k` < 0");
199 DoFAnythingElse(Gamma::new(0.5 * k, 2.0))
201 ChiSquared { repr: repr }
204 impl Sample<f64> for ChiSquared {
205 fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 {
209 impl IndependentSample<f64> for ChiSquared {
210 fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 {
213 // k == 1 => N(0,1)^2
214 let StandardNormal(norm) = rng.gen::<StandardNormal>();
217 DoFAnythingElse(ref g) => g.ind_sample(rng),
222 /// The Fisher F distribution `F(m, n)`.
224 /// This distribution is equivalent to the ratio of two normalised
225 /// chi-squared distributions, that is, `F(m,n) = (χ²(m)/m) /
230 // denom_dof / numer_dof so that this can just be a straight
231 // multiplication, rather than a division.
236 /// Create a new `FisherF` distribution, with the given
237 /// parameter. Panics if either `m` or `n` are not positive.
238 pub fn new(m: f64, n: f64) -> FisherF {
239 assert!(m > 0.0, "FisherF::new called with `m < 0`");
240 assert!(n > 0.0, "FisherF::new called with `n < 0`");
243 numer: ChiSquared::new(m),
244 denom: ChiSquared::new(n),
249 impl Sample<f64> for FisherF {
250 fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 {
254 impl IndependentSample<f64> for FisherF {
255 fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 {
256 self.numer.ind_sample(rng) / self.denom.ind_sample(rng) * self.dof_ratio
260 /// The Student t distribution, `t(nu)`, where `nu` is the degrees of
262 pub struct StudentT {
268 /// Create a new Student t distribution with `n` degrees of
269 /// freedom. Panics if `n <= 0`.
270 pub fn new(n: f64) -> StudentT {
271 assert!(n > 0.0, "StudentT::new called with `n <= 0`");
273 chi: ChiSquared::new(n),
278 impl Sample<f64> for StudentT {
279 fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 {
283 impl IndependentSample<f64> for StudentT {
284 fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 {
285 let StandardNormal(norm) = rng.gen::<StandardNormal>();
286 norm * (self.dof / self.chi.ind_sample(rng)).sqrt()
292 use distributions::{Sample, IndependentSample};
293 use super::{ChiSquared, StudentT, FisherF};
296 fn test_chi_squared_one() {
297 let mut chi = ChiSquared::new(1.0);
298 let mut rng = ::test::rng();
300 chi.sample(&mut rng);
301 chi.ind_sample(&mut rng);
305 fn test_chi_squared_small() {
306 let mut chi = ChiSquared::new(0.5);
307 let mut rng = ::test::rng();
309 chi.sample(&mut rng);
310 chi.ind_sample(&mut rng);
314 fn test_chi_squared_large() {
315 let mut chi = ChiSquared::new(30.0);
316 let mut rng = ::test::rng();
318 chi.sample(&mut rng);
319 chi.ind_sample(&mut rng);
324 fn test_chi_squared_invalid_dof() {
325 ChiSquared::new(-1.0);
330 let mut f = FisherF::new(2.0, 32.0);
331 let mut rng = ::test::rng();
334 f.ind_sample(&mut rng);
340 let mut t = StudentT::new(11.0);
341 let mut rng = ::test::rng();
344 t.ind_sample(&mut rng);
352 use self::test::Bencher;
353 use std::mem::size_of;
354 use distributions::IndependentSample;
359 fn bench_gamma_large_shape(b: &mut Bencher) {
360 let gamma = Gamma::new(10., 1.0);
361 let mut rng = ::test::weak_rng();
364 for _ in 0..::RAND_BENCH_N {
365 gamma.ind_sample(&mut rng);
368 b.bytes = size_of::<f64>() as u64 * ::RAND_BENCH_N;
372 fn bench_gamma_small_shape(b: &mut Bencher) {
373 let gamma = Gamma::new(0.1, 1.0);
374 let mut rng = ::test::weak_rng();
377 for _ in 0..::RAND_BENCH_N {
378 gamma.ind_sample(&mut rng);
381 b.bytes = size_of::<f64>() as u64 * ::RAND_BENCH_N;