1use num_traits::{Float, FloatConst};
13use crate::{Distribution, Standard};
14use rand::Rng;
15use core::fmt;
16
17#[derive(Clone, Copy, Debug)]
35#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
36pub struct Cauchy<F>
37where F: Float + FloatConst, Standard: Distribution<F>
38{
39 median: F,
40 scale: F,
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum Error {
46 ScaleTooSmall,
48}
49
50impl fmt::Display for Error {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 f.write_str(match self {
53 Error::ScaleTooSmall => "scale is not positive in Cauchy distribution",
54 })
55 }
56}
57
58#[cfg(feature = "std")]
59#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
60impl std::error::Error for Error {}
61
62impl<F> Cauchy<F>
63where F: Float + FloatConst, Standard: Distribution<F>
64{
65 pub fn new(median: F, scale: F) -> Result<Cauchy<F>, Error> {
68 if !(scale > F::zero()) {
69 return Err(Error::ScaleTooSmall);
70 }
71 Ok(Cauchy { median, scale })
72 }
73}
74
75impl<F> Distribution<F> for Cauchy<F>
76where F: Float + FloatConst, Standard: Distribution<F>
77{
78 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F {
79 let x = Standard.sample(rng);
81 let comp_dev = (F::PI() * x).tan();
84 self.median + self.scale * comp_dev
86 }
87}
88
89#[cfg(test)]
90mod test {
91 use super::*;
92
93 fn median(numbers: &mut [f64]) -> f64 {
94 sort(numbers);
95 let mid = numbers.len() / 2;
96 numbers[mid]
97 }
98
99 fn sort(numbers: &mut [f64]) {
100 numbers.sort_by(|a, b| a.partial_cmp(b).unwrap());
101 }
102
103 #[test]
104 fn test_cauchy_averages() {
105 let cauchy = Cauchy::new(10.0, 5.0).unwrap();
108 let mut rng = crate::test::rng(123);
109 let mut numbers: [f64; 1000] = [0.0; 1000];
110 let mut sum = 0.0;
111 for number in &mut numbers[..] {
112 *number = cauchy.sample(&mut rng);
113 sum += *number;
114 }
115 let median = median(&mut numbers);
116 #[cfg(feature = "std")]
117 std::println!("Cauchy median: {}", median);
118 assert!((median - 10.0).abs() < 0.4); let mean = sum / 1000.0;
120 #[cfg(feature = "std")]
121 std::println!("Cauchy mean: {}", mean);
122 assert!((mean - 10.0).abs() > 0.4); }
125
126 #[test]
127 #[should_panic]
128 fn test_cauchy_invalid_scale_zero() {
129 Cauchy::new(0.0, 0.0).unwrap();
130 }
131
132 #[test]
133 #[should_panic]
134 fn test_cauchy_invalid_scale_neg() {
135 Cauchy::new(0.0, -10.0).unwrap();
136 }
137
138 #[test]
139 fn value_stability() {
140 fn gen_samples<F: Float + FloatConst + core::fmt::Debug>(m: F, s: F, buf: &mut [F])
141 where Standard: Distribution<F> {
142 let distr = Cauchy::new(m, s).unwrap();
143 let mut rng = crate::test::rng(353);
144 for x in buf {
145 *x = rng.sample(&distr);
146 }
147 }
148
149 let mut buf = [0.0; 4];
150 gen_samples(100f64, 10.0, &mut buf);
151 assert_eq!(&buf, &[
152 77.93369152808678,
153 90.1606912098641,
154 125.31516221323625,
155 86.10217834773925
156 ]);
157
158 let mut buf = [0.0; 4];
161 gen_samples(10f32, 7.0, &mut buf);
162 let expected = [15.023088, -5.446413, 3.7092876, 3.112482];
163 for (a, b) in buf.iter().zip(expected.iter()) {
164 assert_almost_eq!(*a, *b, 1e-5);
165 }
166 }
167}