use num_traits::{Float, FloatConst};
use crate::{Distribution, Standard};
use rand::Rng;
use core::fmt;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub struct Cauchy<F>
where F: Float + FloatConst, Standard: 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")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
impl std::error::Error for Error {}
impl<F> Cauchy<F>
where F: Float + FloatConst, Standard: 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, Standard: Distribution<F>
{
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F {
let x = Standard.sample(rng);
let comp_dev = (F::PI() * x).tan();
self.median + self.scale * comp_dev
}
}
#[cfg(test)]
mod test {
use super::*;
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 + core::fmt::Debug>(m: F, s: F, buf: &mut [F])
where Standard: 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);
}
}
}