Skip to content

Commit a391f2c

Browse files
committed
Added Cauchy distribution
1 parent 5f3c375 commit a391f2c

File tree

5 files changed

+142
-32
lines changed

5 files changed

+142
-32
lines changed

benches/distributions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ distr_float!(distr_normal, f64, Normal::new(-1.23, 4.56));
109109
distr_float!(distr_log_normal, f64, LogNormal::new(-1.23, 4.56));
110110
distr_float!(distr_gamma_large_shape, f64, Gamma::new(10., 1.0));
111111
distr_float!(distr_gamma_small_shape, f64, Gamma::new(0.1, 1.0));
112+
distr_float!(distr_cauchy, f64, Cauchy::new(4.2, 6.9));
112113
distr_int!(distr_binomial, u64, Binomial::new(20, 0.7));
113114
distr_int!(distr_poisson, u64, Poisson::new(4.0));
114115
distr!(distr_bernoulli, bool, Bernoulli::new(0.18));

src/distributions/binomial.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
//! The binomial distribution.
1212
1313
use Rng;
14-
use distributions::{Distribution, Bernoulli};
14+
use distributions::{Distribution, Bernoulli, Cauchy};
1515
use distributions::log_gamma::log_gamma;
16-
use std::f64::consts::PI;
1716

1817
/// The binomial distribution `Binomial(n, p)`.
1918
///
@@ -90,20 +89,14 @@ impl Distribution<u64> for Binomial {
9089

9190
let mut lresult;
9291

92+
// we use the Cauchy distribution as the comparison distribution
93+
// f(x) ~ 1/(1+x^2)
94+
let cauchy = Cauchy::new(0.0, 1.0);
9395
loop {
94-
let mut comp_dev: f64;
95-
// we use the lorentzian distribution as the comparison distribution
96-
// f(x) ~ 1/(1+x/^2)
97-
loop {
98-
// draw from the lorentzian distribution
99-
comp_dev = (PI*rng.gen::<f64>()).tan();
100-
// shift the peak of the comparison ditribution
101-
lresult = expected + sq * comp_dev;
102-
// repeat the drawing until we are in the range of possible values
103-
if lresult >= 0.0 && lresult < float_n + 1.0 {
104-
break;
105-
}
106-
}
96+
// draw from the Cauchy distribution
97+
let comp_dev = rng.sample(cauchy);
98+
// shift the peak of the comparison distribution
99+
lresult = expected + sq * comp_dev;
107100

108101
// the result should be discrete
109102
lresult = lresult.floor();

src/distributions/cauchy.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2016-2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// https://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! The Cauchy distribution.
12+
13+
use Rng;
14+
use distributions::Distribution;
15+
use std::f64::consts::PI;
16+
17+
/// The Cauchy distribution `Cauchy(median, scale)`.
18+
///
19+
/// This distribution has a density function:
20+
/// `f(x) = 1 / (pi * scale * (1 + ((x - median) / scale)^2))`
21+
///
22+
/// # Example
23+
///
24+
/// ```
25+
/// use rand::distributions::{Cauchy, Distribution};
26+
///
27+
/// let cau = Cauchy::new(2.0, 5.0);
28+
/// let v = cau.sample(&mut rand::thread_rng());
29+
/// println!("{} is from a Cauchy(2, 5) distribution", v);
30+
/// ```
31+
#[derive(Clone, Copy, Debug)]
32+
pub struct Cauchy {
33+
median: f64,
34+
scale: f64
35+
}
36+
37+
impl Cauchy {
38+
/// Construct a new `Cauchy` with the given shape parameters
39+
/// `median` the peak location and `scale` the scale factor.
40+
/// Panics if `scale <= 0`.
41+
pub fn new(median: f64, scale: f64) -> Cauchy {
42+
assert!(scale > 0.0, "Cauchy::new called with scale factor <= 0");
43+
Cauchy {
44+
median,
45+
scale
46+
}
47+
}
48+
}
49+
50+
impl Distribution<f64> for Cauchy {
51+
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
52+
let mut x = rng.gen::<f64>();
53+
// guard against the extremely unlikely event we get 0 or 1 from the generator
54+
while x <= 0.0 || x >= 1.0 {
55+
x = rng.gen::<f64>();
56+
}
57+
// get standard cauchy random number
58+
let comp_dev = (2.0 * PI * x).tan();
59+
// shift and scale according to parameters
60+
let result = self.median + self.scale * comp_dev;
61+
result
62+
}
63+
}
64+
65+
#[cfg(test)]
66+
mod test {
67+
use distributions::Distribution;
68+
use super::Cauchy;
69+
70+
fn median(mut numbers: &mut [f64]) -> f64 {
71+
sort(&mut numbers);
72+
let mid = numbers.len() / 2;
73+
numbers[mid]
74+
}
75+
76+
fn sort(numbers: &mut [f64]) {
77+
numbers.sort_by(|a, b| a.partial_cmp(b).unwrap());
78+
}
79+
80+
#[test]
81+
fn test_cauchy_median() {
82+
let cauchy = Cauchy::new(10.0, 5.0);
83+
let mut rng = ::test::rng(123);
84+
let mut numbers: [f64; 1000] = [0.0; 1000];
85+
for i in 0..1000 {
86+
numbers[i] = cauchy.sample(&mut rng);
87+
}
88+
let median = median(&mut numbers);
89+
println!("Cauchy median: {}", median);
90+
assert!((median - 10.0).abs() < 0.5); // not 100% certain, but probable enough
91+
}
92+
93+
#[test]
94+
fn test_cauchy_mean() {
95+
let cauchy = Cauchy::new(10.0, 5.0);
96+
let mut rng = ::test::rng(123);
97+
let mut sum = 0.0;
98+
for _ in 0..1000 {
99+
sum += cauchy.sample(&mut rng);
100+
}
101+
let mean = sum / 1000.0;
102+
println!("Cauchy mean: {}", mean);
103+
// for a Cauchy distribution the mean should not converge
104+
assert!((mean - 10.0).abs() > 0.5); // not 100% certain, but probable enough
105+
}
106+
107+
#[test]
108+
#[should_panic]
109+
fn test_cauchy_invalid_scale_zero() {
110+
Cauchy::new(0.0, 0.0);
111+
}
112+
113+
#[test]
114+
#[should_panic]
115+
fn test_cauchy_invalid_scale_neg() {
116+
Cauchy::new(0.0, -10.0);
117+
}
118+
}

src/distributions/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
//! - Related to real-valued quantities that grow linearly
8282
//! (e.g. errors, offsets):
8383
//! - [`Normal`] distribution, and [`StandardNormal`] as a primitive
84+
//! - [`Cauchy`] distribution
8485
//! - Related to Bernoulli trials (yes/no events, with a given probability):
8586
//! - [`Binomial`] distribution
8687
//! - [`Bernoulli`] distribution, similar to [`Rng::gen_bool`].
@@ -147,6 +148,7 @@
147148
//! [`Alphanumeric`]: struct.Alphanumeric.html
148149
//! [`Bernoulli`]: struct.Bernoulli.html
149150
//! [`Binomial`]: struct.Binomial.html
151+
//! [`Cauchy`]: struct.Cauchy.html
150152
//! [`ChiSquared`]: struct.ChiSquared.html
151153
//! [`Exp`]: struct.Exp.html
152154
//! [`Exp1`]: struct.Exp1.html
@@ -180,6 +182,8 @@ pub use self::uniform::Uniform as Range;
180182
#[cfg(feature = "std")]
181183
#[doc(inline)] pub use self::binomial::Binomial;
182184
#[doc(inline)] pub use self::bernoulli::Bernoulli;
185+
#[cfg(feature = "std")]
186+
#[doc(inline)] pub use self::cauchy::Cauchy;
183187

184188
pub mod uniform;
185189
#[cfg(feature="std")]
@@ -193,6 +197,8 @@ pub mod uniform;
193197
#[cfg(feature = "std")]
194198
#[doc(hidden)] pub mod binomial;
195199
#[doc(hidden)] pub mod bernoulli;
200+
#[cfg(feature = "std")]
201+
#[doc(hidden)] pub mod cauchy;
196202

197203
mod float;
198204
mod integer;

src/distributions/poisson.rs

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
//! The Poisson distribution.
1212
1313
use Rng;
14-
use distributions::Distribution;
14+
use distributions::{Distribution, Cauchy};
1515
use distributions::log_gamma::log_gamma;
16-
use std::f64::consts::PI;
1716

1817
/// The Poisson distribution `Poisson(lambda)`.
1918
///
@@ -73,23 +72,16 @@ impl Distribution<u64> for Poisson {
7372
else {
7473
let mut int_result: u64;
7574

75+
// we use the Cauchy distribution as the comparison distribution
76+
// f(x) ~ 1/(1+x^2)
77+
let cauchy = Cauchy::new(0.0, 1.0);
7678
loop {
77-
let mut result;
78-
let mut comp_dev;
79+
// draw from the Cauchy distribution
80+
let comp_dev = rng.sample(cauchy);
81+
// shift the peak of the comparison ditribution
82+
let mut result = self.sqrt_2lambda * comp_dev + self.lambda;
7983

80-
// we use the lorentzian distribution as the comparison distribution
81-
// f(x) ~ 1/(1+x/^2)
82-
loop {
83-
// draw from the lorentzian distribution
84-
comp_dev = (PI * rng.gen::<f64>()).tan();
85-
// shift the peak of the comparison ditribution
86-
result = self.sqrt_2lambda * comp_dev + self.lambda;
87-
// repeat the drawing until we are in the range of possible values
88-
if result >= 0.0 {
89-
break;
90-
}
91-
}
92-
// now the result is a random variable greater than 0 with Lorentzian distribution
84+
// now the result is a random variable greater than 0 with Cauchy distribution
9385
// the result should be an integer value
9486
result = result.floor();
9587
int_result = result as u64;

0 commit comments

Comments
 (0)