use crate::error::{StatsError, StatsResult};
use crate::sampling::SampleableDistribution;
use crate::traits::{ContinuousDistribution, Distribution as ScirsDist};
use scirs2_core::ndarray::Array1;
use scirs2_core::numeric::{Float, NumCast};
use scirs2_core::random::prelude::*;
use scirs2_core::random::{Distribution, Uniform as RandUniform};
pub struct Cauchy<F: Float> {
pub loc: F,
pub scale: F,
rand_distr: RandUniform<f64>,
}
impl<F: Float + NumCast + std::fmt::Display> Cauchy<F> {
pub fn new(loc: F, scale: F) -> StatsResult<Self> {
if scale <= F::zero() {
return Err(StatsError::DomainError(
"Scale parameter must be positive".to_string(),
));
}
let rand_distr = match RandUniform::new(0.0, 1.0) {
Ok(distr) => distr,
Err(_) => {
return Err(StatsError::ComputationError(
"Failed to create uniform distribution for sampling".to_string(),
))
}
};
Ok(Cauchy {
loc,
scale,
rand_distr,
})
}
pub fn pdf(&self, x: F) -> F {
let pi = F::from(std::f64::consts::PI).expect("Failed to convert to float");
let one = F::one();
let z = (x - self.loc) / self.scale;
one / (pi * self.scale * (one + z * z))
}
pub fn cdf(&self, x: F) -> F {
let pi = F::from(std::f64::consts::PI).expect("Failed to convert to float");
let half = F::from(0.5).expect("Failed to convert constant to float");
let z = (x - self.loc) / self.scale;
half + z.atan() / pi
}
pub fn ppf(&self, p: F) -> StatsResult<F> {
if p < F::zero() || p > F::one() {
return Err(StatsError::DomainError(
"Probability must be between 0 and 1".to_string(),
));
}
let pi = F::from(std::f64::consts::PI).expect("Failed to convert to float");
let half = F::from(0.5).expect("Failed to convert constant to float");
let tan_term = (pi * (p - half)).tan();
let quantile = self.loc + self.scale * tan_term;
Ok(quantile)
}
pub fn rvs_vec(&self, size: usize) -> StatsResult<Vec<F>> {
let mut rng = thread_rng();
let mut samples = Vec::with_capacity(size);
for _ in 0..size {
let u = self.rand_distr.sample(&mut rng);
if (u - 0.5).abs() < 1e-10 {
continue;
}
let u_f = F::from(u).expect("Failed to convert to float");
let sample = match self.ppf(u_f) {
Ok(s) => s,
Err(_) => continue, };
samples.push(sample);
}
while samples.len() < size {
let u = self.rand_distr.sample(&mut rng);
if (u - 0.5).abs() < 1e-10 {
continue;
}
let u_f = F::from(u).expect("Failed to convert to float");
let sample = match self.ppf(u_f) {
Ok(s) => s,
Err(_) => continue, };
samples.push(sample);
}
Ok(samples)
}
pub fn rvs(&self, size: usize) -> StatsResult<Array1<F>> {
let samples_vec = self.rvs_vec(size)?;
Ok(Array1::from(samples_vec))
}
pub fn median(&self) -> F {
self.loc
}
pub fn mode(&self) -> F {
self.loc
}
pub fn iqr(&self) -> F {
self.scale + self.scale
}
pub fn entropy(&self) -> F {
let pi = F::from(std::f64::consts::PI).expect("Failed to convert to float");
let four = F::from(4.0).expect("Failed to convert constant to float");
(four * pi * self.scale).ln()
}
}
#[allow(dead_code)]
pub fn cauchy<F>(loc: F, scale: F) -> StatsResult<Cauchy<F>>
where
F: Float + NumCast + std::fmt::Display,
{
Cauchy::new(loc, scale)
}
impl<F: Float + NumCast + std::fmt::Display> SampleableDistribution<F> for Cauchy<F> {
fn rvs(&self, size: usize) -> StatsResult<Vec<F>> {
self.rvs_vec(size)
}
}
impl<F: Float + NumCast + std::fmt::Display> ScirsDist<F> for Cauchy<F> {
fn mean(&self) -> F {
F::nan()
}
fn var(&self) -> F {
F::nan()
}
fn std(&self) -> F {
F::nan()
}
fn rvs(&self, size: usize) -> StatsResult<Array1<F>> {
self.rvs(size)
}
fn entropy(&self) -> F {
self.entropy()
}
}
impl<F: Float + NumCast + std::fmt::Display> ContinuousDistribution<F> for Cauchy<F> {
fn pdf(&self, x: F) -> F {
self.pdf(x)
}
fn cdf(&self, x: F) -> F {
self.cdf(x)
}
fn ppf(&self, p: F) -> StatsResult<F> {
self.ppf(p)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_cauchy_creation() {
let cauchy = Cauchy::new(0.0, 1.0).expect("Operation failed");
assert_eq!(cauchy.loc, 0.0);
assert_eq!(cauchy.scale, 1.0);
let custom = Cauchy::new(-2.0, 0.5).expect("Operation failed");
assert_eq!(custom.loc, -2.0);
assert_eq!(custom.scale, 0.5);
assert!(Cauchy::<f64>::new(0.0, 0.0).is_err());
assert!(Cauchy::<f64>::new(0.0, -1.0).is_err());
}
#[test]
fn test_cauchy_pdf() {
let cauchy = Cauchy::new(0.0, 1.0).expect("Operation failed");
let pdf_at_zero = cauchy.pdf(0.0);
assert_relative_eq!(pdf_at_zero, 0.3183099, epsilon = 1e-7);
let pdf_at_one = cauchy.pdf(1.0);
assert_relative_eq!(pdf_at_one, 0.1591549, epsilon = 1e-7);
let pdf_at_neg_one = cauchy.pdf(-1.0);
assert_relative_eq!(pdf_at_neg_one, pdf_at_one, epsilon = 1e-10);
let custom = Cauchy::new(-2.0, 0.5).expect("Operation failed");
let pdf_at_loc = custom.pdf(-2.0);
assert_relative_eq!(pdf_at_loc, 0.6366198, epsilon = 1e-7);
let pdf_at_custom = custom.pdf(-1.5);
assert_relative_eq!(pdf_at_custom, 0.3183099, epsilon = 1e-7);
}
#[test]
fn test_cauchy_cdf() {
let cauchy = Cauchy::new(0.0, 1.0).expect("Operation failed");
let cdf_at_zero = cauchy.cdf(0.0);
assert_relative_eq!(cdf_at_zero, 0.5, epsilon = 1e-10);
let cdf_at_one = cauchy.cdf(1.0);
assert_relative_eq!(cdf_at_one, 0.75, epsilon = 1e-7);
let cdf_at_neg_one = cauchy.cdf(-1.0);
assert_relative_eq!(cdf_at_neg_one, 0.25, epsilon = 1e-7);
let custom = Cauchy::new(-2.0, 0.5).expect("Operation failed");
let cdf_at_loc = custom.cdf(-2.0);
assert_relative_eq!(cdf_at_loc, 0.5, epsilon = 1e-10);
let cdf_at_custom = custom.cdf(-1.5);
assert_relative_eq!(cdf_at_custom, 0.75, epsilon = 1e-7);
}
#[test]
fn test_cauchy_ppf() {
let cauchy = Cauchy::new(0.0, 1.0).expect("Operation failed");
let ppf_at_half = cauchy.ppf(0.5).expect("Operation failed");
assert_relative_eq!(ppf_at_half, 0.0, epsilon = 1e-10);
let ppf_at_75 = cauchy.ppf(0.75).expect("Operation failed");
assert_relative_eq!(ppf_at_75, 1.0, epsilon = 1e-7);
let ppf_at_25 = cauchy.ppf(0.25).expect("Operation failed");
assert_relative_eq!(ppf_at_25, -1.0, epsilon = 1e-7);
let custom = Cauchy::new(-2.0, 0.5).expect("Operation failed");
let ppf_at_half_custom = custom.ppf(0.5).expect("Operation failed");
assert_relative_eq!(ppf_at_half_custom, -2.0, epsilon = 1e-10);
let ppf_at_75_custom = custom.ppf(0.75).expect("Operation failed");
assert_relative_eq!(ppf_at_75_custom, -1.5, epsilon = 1e-7);
assert!(cauchy.ppf(-0.1).is_err());
assert!(cauchy.ppf(1.1).is_err());
}
#[test]
fn test_cauchy_properties() {
let cauchy = Cauchy::new(0.0, 1.0).expect("Operation failed");
let median = cauchy.median();
assert_eq!(median, 0.0);
let mode = cauchy.mode();
assert_eq!(mode, 0.0);
let iqr = cauchy.iqr();
assert_eq!(iqr, 2.0);
let entropy = cauchy.entropy();
assert_relative_eq!(entropy, 2.5310242, epsilon = 1e-7);
let custom = Cauchy::new(-2.0, 0.5).expect("Operation failed");
let median_custom = custom.median();
assert_eq!(median_custom, -2.0);
let mode_custom = custom.mode();
assert_eq!(mode_custom, -2.0);
let iqr_custom = custom.iqr();
assert_eq!(iqr_custom, 1.0);
let entropy_custom = custom.entropy();
assert_relative_eq!(entropy_custom, 1.837877, epsilon = 1e-6);
}
#[test]
fn test_cauchy_rvs() {
let cauchy = Cauchy::new(0.0, 1.0).expect("Operation failed");
let samples_vec = cauchy.rvs_vec(100).expect("Operation failed");
let samples = cauchy.rvs(100).expect("Operation failed");
assert_eq!(samples_vec.len(), 100);
assert_eq!(samples.len(), 100);
let mut sorted_samples = samples_vec.clone();
sorted_samples.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
let median = if sorted_samples.len() % 2 == 0 {
(sorted_samples[49] + sorted_samples[50]) / 2.0
} else {
sorted_samples[50]
};
assert!(median.abs() < 5.0);
}
#[test]
fn test_cauchy_inverse_cdf() {
let cauchy = Cauchy::new(0.0, 1.0).expect("Operation failed");
let probabilities = [0.1, 0.25, 0.5, 0.75, 0.9];
for &p in &probabilities {
let x = cauchy.ppf(p).expect("Operation failed");
let p_back = cauchy.cdf(x);
assert_relative_eq!(p_back, p, epsilon = 1e-7);
}
let x_values = [-3.0, -1.0, 0.0, 1.0, 3.0];
for &x in &x_values {
let p = cauchy.cdf(x);
let x_back = cauchy.ppf(p).expect("Operation failed");
assert_relative_eq!(x_back, x, epsilon = 1e-7);
}
}
}