use crate::{Distribution, StandardUniform};
use core::fmt;
use num_traits::{Float, FloatConst};
use rand::Rng;
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Cauchy<F>
where
F: Float + FloatConst,
StandardUniform: Distribution<F>,
{
median: F,
scale: F,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
ScaleTooSmall,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Error::ScaleTooSmall => "scale is not positive in Cauchy distribution",
})
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl<F> Cauchy<F>
where
F: Float + FloatConst,
StandardUniform: Distribution<F>,
{
pub fn new(median: F, scale: F) -> Result<Cauchy<F>, Error> {
if !(scale > F::zero()) {
return Err(Error::ScaleTooSmall);
}
Ok(Cauchy { median, scale })
}
}
impl<F> Distribution<F> for Cauchy<F>
where
F: Float + FloatConst,
StandardUniform: Distribution<F>,
{
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F {
let x = StandardUniform.sample(rng);
let comp_dev = (F::PI() * x).tan();
self.median + self.scale * comp_dev
}
}
#[cfg(test)]
mod test {
use super::*;
use rand::RngExt;
fn median(numbers: &mut [f64]) -> f64 {
sort(numbers);
let mid = numbers.len() / 2;
numbers[mid]
}
fn sort(numbers: &mut [f64]) {
numbers.sort_by(|a, b| a.partial_cmp(b).unwrap());
}
#[test]
fn test_cauchy_averages() {
let cauchy = Cauchy::new(10.0, 5.0).unwrap();
let mut rng = crate::test::rng(123);
let mut numbers: [f64; 1000] = [0.0; 1000];
let mut sum = 0.0;
for number in &mut numbers[..] {
*number = cauchy.sample(&mut rng);
sum += *number;
}
let median = median(&mut numbers);
#[cfg(feature = "std")]
std::println!("Cauchy median: {median}");
assert!((median - 10.0).abs() < 0.4); let mean = sum / 1000.0;
#[cfg(feature = "std")]
std::println!("Cauchy mean: {mean}");
assert!((mean - 10.0).abs() > 0.4); }
#[test]
#[should_panic]
fn test_cauchy_invalid_scale_zero() {
Cauchy::new(0.0, 0.0).unwrap();
}
#[test]
#[should_panic]
fn test_cauchy_invalid_scale_neg() {
Cauchy::new(0.0, -10.0).unwrap();
}
#[test]
fn value_stability() {
fn gen_samples<F: Float + FloatConst + fmt::Debug>(m: F, s: F, buf: &mut [F])
where
StandardUniform: Distribution<F>,
{
let distr = Cauchy::new(m, s).unwrap();
let mut rng = crate::test::rng(353);
for x in buf {
*x = rng.sample(distr);
}
}
let mut buf = [0.0; 4];
gen_samples(100f64, 10.0, &mut buf);
assert_eq!(
&buf,
&[
77.93369152808678,
90.1606912098641,
125.31516221323625,
86.10217834773925
]
);
let mut buf = [0.0; 4];
gen_samples(10f32, 7.0, &mut buf);
let expected = [15.023088, -5.446413, 3.7092876, 3.112482];
for (a, b) in buf.iter().zip(expected.iter()) {
assert_almost_eq!(*a, *b, 1e-5);
}
}
#[test]
fn cauchy_distributions_can_be_compared() {
assert_eq!(Cauchy::new(1.0, 2.0), Cauchy::new(1.0, 2.0));
}
}