1#[cfg(feature = "serde1")]
3use serde::{Deserialize, Serialize};
4
5use crate::consts::LN_PI;
6use crate::impl_display;
7use crate::traits::*;
8use rand::Rng;
9use rand_distr::Cauchy as RCauchy;
10use std::f64::consts::{FRAC_1_PI, PI};
11use std::fmt;
12
13#[derive(Debug, Clone, PartialEq)]
26#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
27#[cfg_attr(feature = "serde1", serde(rename_all = "snake_case"))]
28pub struct Cauchy {
29 loc: f64,
31 scale: f64,
33}
34
35pub struct CauchyParameters {
36 pub loc: f64,
37 pub scale: f64,
38}
39
40impl Parameterized for Cauchy {
41 type Parameters = CauchyParameters;
42
43 fn emit_params(&self) -> Self::Parameters {
44 Self::Parameters {
45 loc: self.loc(),
46 scale: self.scale(),
47 }
48 }
49
50 fn from_params(params: Self::Parameters) -> Self {
51 Self::new_unchecked(params.loc, params.scale)
52 }
53}
54
55#[derive(Debug, Clone, PartialEq)]
56#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
57#[cfg_attr(feature = "serde1", serde(rename_all = "snake_case"))]
58pub enum CauchyError {
59 LocNotFinite { loc: f64 },
61 ScaleTooLow { scale: f64 },
63 ScaleNotFinite { scale: f64 },
65}
66
67impl Cauchy {
68 pub fn new(loc: f64, scale: f64) -> Result<Self, CauchyError> {
74 if !loc.is_finite() {
75 Err(CauchyError::LocNotFinite { loc })
76 } else if scale <= 0.0 {
77 Err(CauchyError::ScaleTooLow { scale })
78 } else if !scale.is_finite() {
79 Err(CauchyError::ScaleNotFinite { scale })
80 } else {
81 Ok(Cauchy { loc, scale })
82 }
83 }
84
85 #[inline]
87 pub fn new_unchecked(loc: f64, scale: f64) -> Self {
88 Cauchy { loc, scale }
89 }
90
91 #[inline]
101 pub fn loc(&self) -> f64 {
102 self.loc
103 }
104
105 #[inline]
128 pub fn set_loc(&mut self, loc: f64) -> Result<(), CauchyError> {
129 if loc.is_finite() {
130 self.set_loc_unchecked(loc);
131 Ok(())
132 } else {
133 Err(CauchyError::LocNotFinite { loc })
134 }
135 }
136
137 #[inline]
139 pub fn set_loc_unchecked(&mut self, loc: f64) {
140 self.loc = loc;
141 }
142
143 #[inline]
153 pub fn scale(&self) -> f64 {
154 self.scale
155 }
156
157 #[inline]
180 pub fn set_scale(&mut self, scale: f64) -> Result<(), CauchyError> {
181 if !scale.is_finite() {
182 Err(CauchyError::ScaleNotFinite { scale })
183 } else if scale > 0.0 {
184 self.set_scale_unchecked(scale);
185 Ok(())
186 } else {
187 Err(CauchyError::ScaleTooLow { scale })
188 }
189 }
190
191 #[inline]
193 pub fn set_scale_unchecked(&mut self, scale: f64) {
194 self.scale = scale;
195 }
196}
197
198impl Default for Cauchy {
199 fn default() -> Self {
200 Cauchy::new_unchecked(0.0, 1.0)
201 }
202}
203
204impl From<&Cauchy> for String {
205 fn from(cauchy: &Cauchy) -> String {
206 format!("Cauchy(loc: {}, scale: {})", cauchy.loc, cauchy.scale)
207 }
208}
209
210impl_display!(Cauchy);
211use crate::misc::logaddexp;
212macro_rules! impl_traits {
213 ($kind:ty) => {
214 impl HasDensity<$kind> for Cauchy {
215 fn ln_f(&self, x: &$kind) -> f64 {
216 let ln_scale = self.scale.ln();
217 let term = 2.0_f64.mul_add(
218 ((f64::from(*x) - self.loc).abs().ln() - ln_scale),
219 ln_scale,
220 );
221 -logaddexp(ln_scale, term) - LN_PI
222 }
223 }
224
225 impl Sampleable<$kind> for Cauchy {
226 fn draw<R: Rng>(&self, rng: &mut R) -> $kind {
227 let cauchy = RCauchy::new(self.loc, self.scale).unwrap();
228 rng.sample(cauchy) as $kind
229 }
230
231 fn sample<R: Rng>(&self, n: usize, rng: &mut R) -> Vec<$kind> {
232 let cauchy = RCauchy::new(self.loc, self.scale).unwrap();
233 (0..n).map(|_| rng.sample(cauchy) as $kind).collect()
234 }
235 }
236
237 impl Support<$kind> for Cauchy {
238 fn supports(&self, x: &$kind) -> bool {
239 x.is_finite()
240 }
241 }
242
243 impl ContinuousDistr<$kind> for Cauchy {}
244
245 impl Cdf<$kind> for Cauchy {
246 fn cdf(&self, x: &$kind) -> f64 {
247 FRAC_1_PI.mul_add(
248 ((f64::from(*x) - self.loc) / self.scale).atan(),
249 0.5,
250 )
251 }
252 }
253
254 impl InverseCdf<$kind> for Cauchy {
255 fn invcdf(&self, p: f64) -> $kind {
256 self.scale.mul_add((PI * (p - 0.5)).tan(), self.loc) as $kind
257 }
258 }
259
260 impl Median<$kind> for Cauchy {
261 fn median(&self) -> Option<$kind> {
262 Some(self.loc as $kind)
263 }
264 }
265
266 impl Mode<$kind> for Cauchy {
267 fn mode(&self) -> Option<$kind> {
268 Some(self.loc as $kind)
269 }
270 }
271 };
272}
273
274impl Entropy for Cauchy {
275 fn entropy(&self) -> f64 {
276 (4.0 * PI * self.scale).ln()
277 }
278}
279
280impl_traits!(f64);
281impl_traits!(f32);
282
283impl std::error::Error for CauchyError {}
284
285impl fmt::Display for CauchyError {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 match self {
288 Self::LocNotFinite { loc } => {
289 write!(f, "loc ({}) must be finite", loc)
290 }
291 Self::ScaleTooLow { scale } => {
292 write!(f, "scale ({}) must be greater than zero", scale)
293 }
294 Self::ScaleNotFinite { scale } => {
295 write!(f, "scale ({}) must be finite", scale)
296 }
297 }
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use crate::misc::ks_test;
305 use crate::test_basic_impls;
306 use std::f64;
307
308 const TOL: f64 = 1E-12;
309 const KS_PVAL: f64 = 0.2;
310 const N_TRIES: usize = 5;
311
312 test_basic_impls!(f64, Cauchy);
313
314 #[test]
315 fn ln_pdf_loc_zero() {
316 let c = Cauchy::new(0.0, 1.0).unwrap();
317 assert::close(c.ln_pdf(&0.2), -1.183_950_599_002_681_5, TOL);
318 }
319
320 #[test]
321 fn ln_pdf_loc_nonzero() {
322 let c = Cauchy::new(1.2, 3.4).unwrap();
323 assert::close(c.ln_pdf(&0.2), -2.451_471_615_267_337, TOL);
324 }
325
326 #[test]
327 fn cdf_at_loc() {
328 let c = Cauchy::new(1.2, 3.4).unwrap();
329 assert::close(c.cdf(&1.2), 0.5, TOL);
330 }
331
332 #[test]
333 fn cdf_off_loc() {
334 let c = Cauchy::new(1.2, 3.4).unwrap();
335 assert::close(c.cdf(&2.2), 0.591_053_001_855_748_8, TOL);
336 assert::close(c.cdf(&0.2), 1.0 - 0.591_053_001_855_748_8, TOL);
337 }
338
339 #[test]
340 fn inv_cdf_ident() {
341 let mut rng = rand::thread_rng();
342 let c = Cauchy::default();
343 for _ in 0..100 {
344 let x: f64 = c.draw(&mut rng);
345 let p: f64 = c.cdf(&x);
346 let y: f64 = c.invcdf(p);
347 assert::close(x, y, 1E-8); }
349 }
350
351 #[test]
352 fn inv_cdf() {
353 let c = Cauchy::new(1.2, 3.4).unwrap();
354 let lower: f64 = c.invcdf(0.4);
355 let upper: f64 = c.invcdf(0.6);
356 assert::close(lower, 0.095_273_032_808_118_61, TOL);
357 assert::close(upper, 2.304_726_967_191_881_3, TOL);
358 }
359
360 #[test]
361 fn median_should_be_loc() {
362 let m: f64 = Cauchy::new(1.2, 3.4).unwrap().median().unwrap();
363 assert::close(m, 1.2, TOL);
364 }
365
366 #[test]
367 fn mode_should_be_loc() {
368 let m: f64 = Cauchy::new(-0.2, 3.4).unwrap().median().unwrap();
369 assert::close(m, -0.2, TOL);
370 }
371
372 #[test]
373 fn finite_numbers_should_be_in_support() {
374 let c = Cauchy::default();
375 assert!(c.supports(&0.0_f64));
376 assert!(c.supports(&f64::MIN_POSITIVE));
377 assert!(c.supports(&f64::MAX));
378 assert!(c.supports(&f64::MIN));
379 }
380
381 #[test]
382 fn non_finite_numbers_should_not_be_in_support() {
383 let c = Cauchy::default();
384 assert!(!c.supports(&f64::INFINITY));
385 assert!(!c.supports(&f64::NEG_INFINITY));
386 assert!(!c.supports(&f64::NAN));
387 }
388
389 #[test]
390 fn entropy() {
391 let c = Cauchy::new(1.2, 3.4).unwrap();
392 assert::close(c.entropy(), 3.754_799_678_591_406_4, TOL);
393 }
394
395 #[test]
396 fn loc_does_not_affect_entropy() {
397 let c1 = Cauchy::new(1.2, 3.4).unwrap();
398 let c2 = Cauchy::new(-99999.9, 3.4).unwrap();
399 assert::close(c1.entropy(), c2.entropy(), TOL);
400 }
401
402 #[test]
403 fn draw_test() {
404 let mut rng = rand::thread_rng();
405 let c = Cauchy::new(1.2, 3.4).unwrap();
406 let cdf = |x: f64| c.cdf(&x);
407
408 let passes = (0..N_TRIES).fold(0, |acc, _| {
410 let xs: Vec<f64> = c.sample(1000, &mut rng);
411 let (_, p) = ks_test(&xs, cdf);
412 if p > KS_PVAL {
413 acc + 1
414 } else {
415 acc
416 }
417 });
418
419 assert!(passes > 0);
420 }
421}