probability/distribution/
cauchy.rs

1use alloc::{vec, vec::Vec};
2#[allow(unused_imports)]
3use special::Primitive;
4
5use distribution;
6use source::Source;
7
8/// A Cauchy distribution.
9///
10/// The Cauchy distribution (also known as Lorentz or Cauchy–Lorentz
11/// distribution) is a continuous probability distribution with a location
12/// parameter `x_0`, a scale parameter `gamma > 0`, and the following
13/// probability density function:
14///
15/// `p(x) = 1 / (pi * gamma * (1 + ((x - x_0) / gamma)^2))`.
16///
17/// The distribution is long tailed and has no mean or variance. It is unimodal
18/// with the mode at `x_0`, around which it is symmetric.
19#[derive(Clone, Copy, Debug)]
20pub struct Cauchy {
21    x_0: f64,
22    gamma: f64,
23}
24
25impl Cauchy {
26    /// Create a Cauchy distribution with location `x_0` and scale `gamma`.
27    ///
28    /// It should hold that `gamma > 0`.
29    #[inline]
30    pub fn new(x_0: f64, gamma: f64) -> Self {
31        should!(gamma > 0.0);
32        Cauchy { x_0, gamma }
33    }
34
35    /// Return the location parameter.
36    #[inline(always)]
37    pub fn x_0(&self) -> f64 {
38        self.x_0
39    }
40
41    /// Return the scale parameter.
42    #[inline(always)]
43    pub fn gamma(&self) -> f64 {
44        self.gamma
45    }
46}
47
48impl distribution::Continuous for Cauchy {
49    #[inline]
50    fn density(&self, x: f64) -> f64 {
51        use core::f64::consts::PI;
52        let deviation = x - self.x_0;
53        self.gamma / (PI * (self.gamma * self.gamma + deviation * deviation))
54    }
55}
56
57impl distribution::Distribution for Cauchy {
58    type Value = f64;
59
60    #[inline]
61    fn distribution(&self, x: f64) -> f64 {
62        use core::f64::consts::FRAC_1_PI;
63        FRAC_1_PI * ((x - self.x_0) / self.gamma).atan() + 0.5
64    }
65}
66
67impl distribution::Entropy for Cauchy {
68    #[inline]
69    fn entropy(&self) -> f64 {
70        (core::f64::consts::PI * 4.0 * self.gamma).ln()
71    }
72}
73
74impl distribution::Inverse for Cauchy {
75    #[inline]
76    fn inverse(&self, p: f64) -> f64 {
77        use core::f64::{consts::PI, INFINITY, NEG_INFINITY};
78
79        should!((0.0..=1.0).contains(&p));
80
81        if p <= 0.0 {
82            NEG_INFINITY
83        } else if 1.0 <= p {
84            INFINITY
85        } else {
86            self.x_0 + self.gamma * (PI * (p - 0.5)).tan()
87        }
88    }
89}
90
91impl distribution::Median for Cauchy {
92    #[inline]
93    fn median(&self) -> f64 {
94        self.x_0
95    }
96}
97
98impl distribution::Modes for Cauchy {
99    #[inline]
100    fn modes(&self) -> Vec<f64> {
101        vec![self.x_0]
102    }
103}
104
105impl distribution::Sample for Cauchy {
106    #[inline]
107    fn sample<S>(&self, source: &mut S) -> f64
108    where
109        S: Source,
110    {
111        let gaussian = distribution::Gaussian::new(0.0, 1.0);
112        let a = gaussian.sample(source);
113        let b = gaussian.sample(source);
114        self.x_0() + self.gamma() * a / (b.abs() + f64::MIN_POSITIVE)
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use alloc::{vec, vec::Vec};
121    use assert;
122    use prelude::*;
123
124    macro_rules! new(
125        ($x_0:expr, $gamma:expr) => (Cauchy::new($x_0, $gamma));
126    );
127
128    #[test]
129    fn density() {
130        let d = new!(2.0, 8.0);
131        let x = vec![-1.0, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 6.0, 12.0];
132        let p = vec![
133            0.03488327519822364,
134            0.03744822190397538,
135            0.03843742021842001,
136            0.039176601376466544,
137            0.03963391578942141,
138            0.039788735772973836,
139            0.03963391578942141,
140            0.039176601376466544,
141            0.03744822190397538,
142            0.03183098861837907,
143            0.015527311521160521,
144        ];
145
146        assert::close(
147            &x.iter().map(|&x| d.density(x)).collect::<Vec<_>>(),
148            &p,
149            1e-15,
150        );
151    }
152
153    #[test]
154    fn distribution() {
155        let d = new!(2.0, 8.0);
156        let x = vec![
157            -1.0, 0.0, 0.01, 0.05, 0.1, 0.15, 0.25, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0,
158        ];
159        let p = vec![
160            0.3857997487800918,
161            0.4220208696226307,
162            0.4223954618429798,
163            0.4238960166273086,
164            0.4257765641957529,
165            0.42766240385764065,
166            0.43144951512041,
167            0.44100191513247144,
168            0.46041657583943446,
169            0.48013147569445913,
170            0.5,
171            0.5395834241605656,
172            0.5779791303773694,
173        ];
174
175        assert::close(
176            &x.iter().map(|&x| d.distribution(x)).collect::<Vec<_>>(),
177            &p,
178            1e-15,
179        );
180    }
181
182    #[test]
183    fn entropy() {
184        use core::f64::consts::PI;
185        assert_eq!(new!(2.0, 1.0).entropy(), (PI * 4.0).ln());
186        assert::close(new!(3.0, 5.2).entropy(), 4.1796828725566719243, 1e-15);
187    }
188
189    #[test]
190    fn inverse() {
191        use core::f64::{INFINITY, NEG_INFINITY};
192
193        let d = new!(2.0, 3.0);
194        let x = vec![
195            NEG_INFINITY,
196            -7.2330506115257585,
197            -0.9999999999999996,
198            2.0,
199            5.0,
200            11.233050611525758,
201            INFINITY,
202        ];
203        let p = vec![0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0];
204
205        assert::close(
206            &p.iter().map(|&p| d.inverse(p)).collect::<Vec<_>>(),
207            &x,
208            1e-14,
209        );
210
211        assert!(d.inverse(0.0) < -1e16);
212        assert!(d.inverse(1.0) > 1e16);
213    }
214
215    #[test]
216    fn median() {
217        assert_eq!(new!(2.0, 1.0).median(), 2.0);
218    }
219
220    #[test]
221    fn modes() {
222        assert_eq!(new!(2.0, 1.0).modes(), vec![2.0]);
223    }
224
225    #[test]
226    fn sampling() {
227        let n = 100000;
228        let d = Cauchy::new(35.4, 12.3);
229        let mut source = source::default(42);
230
231        let cross_entropy = -(0..n)
232            .map(|_| d.density(d.sample(&mut source)).ln())
233            .sum::<f64>()
234            / n as f64;
235        assert!((cross_entropy - d.entropy()).abs() < 0.01);
236    }
237}