Skip to content

Commit c4d1446

Browse files
authored
Merge pull request #474 from MaximoB/add_cauchy_distribution
Added Cauchy distribution
2 parents cb03f24 + cc377b2 commit c4d1446

File tree

5 files changed

+140
-13
lines changed

5 files changed

+140
-13
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: 6 additions & 6 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,13 +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 {
9496
let mut comp_dev: f64;
95-
// we use the lorentzian distribution as the comparison distribution
96-
// f(x) ~ 1/(1+x/^2)
9797
loop {
98-
// draw from the lorentzian distribution
99-
comp_dev = (PI*rng.gen::<f64>()).tan();
98+
// draw from the Cauchy distribution
99+
comp_dev = rng.sample(cauchy);
100100
// shift the peak of the comparison ditribution
101101
lresult = expected + sq * comp_dev;
102102
// repeat the drawing until we are in the range of possible values

src/distributions/cauchy.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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+
// sample from [0, 1)
53+
let mut x = rng.gen::<f64>();
54+
// guard against the extremely unlikely case we get the invalid 0.5
55+
while x == 0.5 {
56+
x = rng.gen::<f64>();
57+
}
58+
// get standard cauchy random number
59+
let comp_dev = (PI * x).tan();
60+
// shift and scale according to parameters
61+
let result = self.median + self.scale * comp_dev;
62+
result
63+
}
64+
}
65+
66+
#[cfg(test)]
67+
mod test {
68+
use distributions::Distribution;
69+
use super::Cauchy;
70+
71+
fn median(mut numbers: &mut [f64]) -> f64 {
72+
sort(&mut numbers);
73+
let mid = numbers.len() / 2;
74+
numbers[mid]
75+
}
76+
77+
fn sort(numbers: &mut [f64]) {
78+
numbers.sort_by(|a, b| a.partial_cmp(b).unwrap());
79+
}
80+
81+
#[test]
82+
fn test_cauchy_median() {
83+
let cauchy = Cauchy::new(10.0, 5.0);
84+
let mut rng = ::test::rng(123);
85+
let mut numbers: [f64; 1000] = [0.0; 1000];
86+
for i in 0..1000 {
87+
numbers[i] = cauchy.sample(&mut rng);
88+
}
89+
let median = median(&mut numbers);
90+
println!("Cauchy median: {}", median);
91+
assert!((median - 10.0).abs() < 0.5); // not 100% certain, but probable enough
92+
}
93+
94+
#[test]
95+
fn test_cauchy_mean() {
96+
let cauchy = Cauchy::new(10.0, 5.0);
97+
let mut rng = ::test::rng(123);
98+
let mut sum = 0.0;
99+
for _ in 0..1000 {
100+
sum += cauchy.sample(&mut rng);
101+
}
102+
let mean = sum / 1000.0;
103+
println!("Cauchy mean: {}", mean);
104+
// for a Cauchy distribution the mean should not converge
105+
assert!((mean - 10.0).abs() > 0.5); // not 100% certain, but probable enough
106+
}
107+
108+
#[test]
109+
#[should_panic]
110+
fn test_cauchy_invalid_scale_zero() {
111+
Cauchy::new(0.0, 0.0);
112+
}
113+
114+
#[test]
115+
#[should_panic]
116+
fn test_cauchy_invalid_scale_neg() {
117+
Cauchy::new(0.0, -10.0);
118+
}
119+
}

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: 8 additions & 7 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,25 @@ 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);
78+
7679
loop {
7780
let mut result;
7881
let mut comp_dev;
7982

80-
// we use the lorentzian distribution as the comparison distribution
81-
// f(x) ~ 1/(1+x/^2)
8283
loop {
83-
// draw from the lorentzian distribution
84-
comp_dev = (PI * rng.gen::<f64>()).tan();
84+
// draw from the Cauchy distribution
85+
comp_dev = rng.sample(cauchy);
8586
// shift the peak of the comparison ditribution
8687
result = self.sqrt_2lambda * comp_dev + self.lambda;
8788
// repeat the drawing until we are in the range of possible values
8889
if result >= 0.0 {
8990
break;
9091
}
9192
}
92-
// now the result is a random variable greater than 0 with Lorentzian distribution
93+
// now the result is a random variable greater than 0 with Cauchy distribution
9394
// the result should be an integer value
9495
result = result.floor();
9596
int_result = result as u64;

0 commit comments

Comments
 (0)