|
8 | 8 | // option. This file may not be copied, modified, or distributed
|
9 | 9 | // except according to those terms.
|
10 | 10 |
|
11 |
| -//! The Gamma distribution. |
| 11 | +//! The Gamma and derived distributions. |
12 | 12 |
|
13 | 13 | use rand::{Rng, Open01};
|
14 |
| -use super::{IndependentSample, Sample, StandardNormal, Exp}; |
| 14 | +use super::{IndependentSample, Sample, Exp}; |
| 15 | +use super::normal::StandardNormal; |
15 | 16 | use num;
|
16 | 17 |
|
17 | 18 | /// The Gamma distribution `Gamma(shape, scale)` distribution.
|
@@ -168,6 +169,213 @@ impl IndependentSample<f64> for GammaLargeShape {
|
168 | 169 | }
|
169 | 170 | }
|
170 | 171 |
|
| 172 | +/// The chi-squared distribution `χ²(k)`, where `k` is the degrees of |
| 173 | +/// freedom. |
| 174 | +/// |
| 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 characterisation `χ²(k) = Gamma(k/2, |
| 178 | +/// 2)`. |
| 179 | +/// |
| 180 | +/// # Example |
| 181 | +/// |
| 182 | +/// ```rust |
| 183 | +/// use std::rand; |
| 184 | +/// use std::rand::distributions::{ChiSquared, IndependentSample}; |
| 185 | +/// |
| 186 | +/// fn main() { |
| 187 | +/// let chi = ChiSquared::new(11.0); |
| 188 | +/// let v = chi.ind_sample(&mut rand::task_rng()); |
| 189 | +/// println!("{} is from a χ²(11) distribution", v) |
| 190 | +/// } |
| 191 | +/// ``` |
| 192 | +pub enum ChiSquared { |
| 193 | + // k == 1, Gamma(alpha, ..) is particularly slow for alpha < 1, |
| 194 | + // e.g. when alpha = 1/2 as it would be for this case, so special- |
| 195 | + // casing and using the definition of N(0,1)^2 is faster. |
| 196 | + priv DoFExactlyOne, |
| 197 | + priv DoFAnythingElse(Gamma) |
| 198 | +} |
| 199 | + |
| 200 | +impl ChiSquared { |
| 201 | + /// Create a new chi-squared distribution with degrees-of-freedom |
| 202 | + /// `k`. Fails if `k < 0`. |
| 203 | + pub fn new(k: f64) -> ChiSquared { |
| 204 | + if k == 1.0 { |
| 205 | + DoFExactlyOne |
| 206 | + } else { |
| 207 | + assert!(k > 0.0, "ChiSquared::new called with `k` < 0"); |
| 208 | + DoFAnythingElse(Gamma::new(0.5 * k, 2.0)) |
| 209 | + } |
| 210 | + } |
| 211 | +} |
| 212 | +impl Sample<f64> for ChiSquared { |
| 213 | + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } |
| 214 | +} |
| 215 | +impl IndependentSample<f64> for ChiSquared { |
| 216 | + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { |
| 217 | + match *self { |
| 218 | + DoFExactlyOne => { |
| 219 | + // k == 1 => N(0,1)^2 |
| 220 | + let norm = *rng.gen::<StandardNormal>(); |
| 221 | + norm * norm |
| 222 | + } |
| 223 | + DoFAnythingElse(ref g) => g.ind_sample(rng) |
| 224 | + } |
| 225 | + } |
| 226 | +} |
| 227 | + |
| 228 | +/// The Fisher F distribution `F(m, n)`. |
| 229 | +/// |
| 230 | +/// This distribution is equivalent to the ratio of two normalised |
| 231 | +/// chi-squared distributions, that is, `F(m,n) = (χ²(m)/m) / |
| 232 | +/// (χ²(n)/n)`. |
| 233 | +/// |
| 234 | +/// # Example |
| 235 | +/// |
| 236 | +/// ```rust |
| 237 | +/// use std::rand; |
| 238 | +/// use std::rand::distributions::{FisherF, IndependentSample}; |
| 239 | +/// |
| 240 | +/// fn main() { |
| 241 | +/// let f = FisherF::new(2.0, 32.0); |
| 242 | +/// let v = f.ind_sample(&mut rand::task_rng()); |
| 243 | +/// println!("{} is from an F(2, 32) distribution", v) |
| 244 | +/// } |
| 245 | +/// ``` |
| 246 | +pub struct FisherF { |
| 247 | + priv numer: ChiSquared, |
| 248 | + priv denom: ChiSquared, |
| 249 | + // denom_dof / numer_dof so that this can just be a straight |
| 250 | + // multiplication, rather than a division. |
| 251 | + priv dof_ratio: f64, |
| 252 | +} |
| 253 | + |
| 254 | +impl FisherF { |
| 255 | + /// Create a new `FisherF` distribution, with the given |
| 256 | + /// parameter. Fails if either `m` or `n` are not positive. |
| 257 | + pub fn new(m: f64, n: f64) -> FisherF { |
| 258 | + assert!(m > 0.0, "FisherF::new called with `m < 0`"); |
| 259 | + assert!(n > 0.0, "FisherF::new called with `n < 0`"); |
| 260 | + |
| 261 | + FisherF { |
| 262 | + numer: ChiSquared::new(m), |
| 263 | + denom: ChiSquared::new(n), |
| 264 | + dof_ratio: n / m |
| 265 | + } |
| 266 | + } |
| 267 | +} |
| 268 | +impl Sample<f64> for FisherF { |
| 269 | + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } |
| 270 | +} |
| 271 | +impl IndependentSample<f64> for FisherF { |
| 272 | + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { |
| 273 | + self.numer.ind_sample(rng) / self.denom.ind_sample(rng) * self.dof_ratio |
| 274 | + } |
| 275 | +} |
| 276 | + |
| 277 | +/// The Student t distribution, `t(nu)`, where `nu` is the degrees of |
| 278 | +/// freedom. |
| 279 | +/// |
| 280 | +/// # Example |
| 281 | +/// |
| 282 | +/// ```rust |
| 283 | +/// use std::rand; |
| 284 | +/// use std::rand::distributions::{StudentT, IndependentSample}; |
| 285 | +/// |
| 286 | +/// fn main() { |
| 287 | +/// let t = StudentT::new(11.0); |
| 288 | +/// let v = t.ind_sample(&mut rand::task_rng()); |
| 289 | +/// println!("{} is from a t(11) distribution", v) |
| 290 | +/// } |
| 291 | +/// ``` |
| 292 | +pub struct StudentT { |
| 293 | + priv chi: ChiSquared, |
| 294 | + priv dof: f64 |
| 295 | +} |
| 296 | + |
| 297 | +impl StudentT { |
| 298 | + /// Create a new Student t distribution with `n` degrees of |
| 299 | + /// freedom. Fails if `n <= 0`. |
| 300 | + pub fn new(n: f64) -> StudentT { |
| 301 | + assert!(n > 0.0, "StudentT::new called with `n <= 0`"); |
| 302 | + StudentT { |
| 303 | + chi: ChiSquared::new(n), |
| 304 | + dof: n |
| 305 | + } |
| 306 | + } |
| 307 | +} |
| 308 | +impl Sample<f64> for StudentT { |
| 309 | + fn sample<R: Rng>(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } |
| 310 | +} |
| 311 | +impl IndependentSample<f64> for StudentT { |
| 312 | + fn ind_sample<R: Rng>(&self, rng: &mut R) -> f64 { |
| 313 | + let norm = *rng.gen::<StandardNormal>(); |
| 314 | + norm * (self.dof / self.chi.ind_sample(rng)).sqrt() |
| 315 | + } |
| 316 | +} |
| 317 | + |
| 318 | +#[cfg(test)] |
| 319 | +mod test { |
| 320 | + use rand::*; |
| 321 | + use super::*; |
| 322 | + use iter::range; |
| 323 | + use option::{Some, None}; |
| 324 | + |
| 325 | + #[test] |
| 326 | + fn test_chi_squared_one() { |
| 327 | + let mut chi = ChiSquared::new(1.0); |
| 328 | + let mut rng = task_rng(); |
| 329 | + for _ in range(0, 1000) { |
| 330 | + chi.sample(&mut rng); |
| 331 | + chi.ind_sample(&mut rng); |
| 332 | + } |
| 333 | + } |
| 334 | + #[test] |
| 335 | + fn test_chi_squared_small() { |
| 336 | + let mut chi = ChiSquared::new(0.5); |
| 337 | + let mut rng = task_rng(); |
| 338 | + for _ in range(0, 1000) { |
| 339 | + chi.sample(&mut rng); |
| 340 | + chi.ind_sample(&mut rng); |
| 341 | + } |
| 342 | + } |
| 343 | + #[test] |
| 344 | + fn test_chi_squared_large() { |
| 345 | + let mut chi = ChiSquared::new(30.0); |
| 346 | + let mut rng = task_rng(); |
| 347 | + for _ in range(0, 1000) { |
| 348 | + chi.sample(&mut rng); |
| 349 | + chi.ind_sample(&mut rng); |
| 350 | + } |
| 351 | + } |
| 352 | + #[test] |
| 353 | + #[should_fail] |
| 354 | + fn test_log_normal_invalid_dof() { |
| 355 | + ChiSquared::new(-1.0); |
| 356 | + } |
| 357 | + |
| 358 | + #[test] |
| 359 | + fn test_f() { |
| 360 | + let mut f = FisherF::new(2.0, 32.0); |
| 361 | + let mut rng = task_rng(); |
| 362 | + for _ in range(0, 1000) { |
| 363 | + f.sample(&mut rng); |
| 364 | + f.ind_sample(&mut rng); |
| 365 | + } |
| 366 | + } |
| 367 | + |
| 368 | + #[test] |
| 369 | + fn test_t() { |
| 370 | + let mut t = StudentT::new(11.0); |
| 371 | + let mut rng = task_rng(); |
| 372 | + for _ in range(0, 1000) { |
| 373 | + t.sample(&mut rng); |
| 374 | + t.ind_sample(&mut rng); |
| 375 | + } |
| 376 | + } |
| 377 | +} |
| 378 | + |
171 | 379 | #[cfg(test)]
|
172 | 380 | mod bench {
|
173 | 381 | use super::*;
|
|
0 commit comments