use crate::error::{StatsError, StatsResult};
use crate::traits::{CircularDistribution, Distribution};
use scirs2_core::ndarray::Array1;
use scirs2_core::numeric::Float;
use scirs2_core::random::prelude::*;
use scirs2_core::random::uniform::SampleUniform;
use scirs2_core::random::Distribution as RandDistribution;
use std::fmt::Debug;
use std::marker::PhantomData;
use statrs::statistics::Statistics;
use std::f64::consts::PI;
fn bessel_i0(x: f64) -> f64 {
let ax = x.abs();
if ax < 3.75 {
let y = (x / 3.75).powi(2);
1.0 + y
* (3.5156229
+ y * (3.0899424
+ y * (1.2067492 + y * (0.2659732 + y * (0.360768e-1 + y * 0.45813e-2)))))
} else {
let z = 3.75 / ax;
(ax.exp() / ax.sqrt())
* (0.39894228
+ z * (0.1328592e-1
+ z * (0.225319e-2
+ z * (-0.157565e-2
+ z * (0.916281e-2
+ z * (-0.2057706e-1
+ z * (0.2635537e-1
+ z * (-0.1647633e-1 + z * 0.392377e-2))))))))
}
}
fn bessel_i1(x: f64) -> f64 {
let ax = x.abs();
if ax < 3.75 {
let y = (x / 3.75).powi(2);
ax * (0.5
+ y * (0.87890594
+ y * (0.51498869
+ y * (0.15084934 + y * (0.2658733e-1 + y * (0.301532e-2 + y * 0.32411e-3))))))
} else {
let z = 3.75 / ax;
let result = (ax.exp() / ax.sqrt())
* (0.39894228
+ z * (-0.3988024e-1
+ z * (-0.362018e-2
+ z * (0.163801e-2
+ z * (-0.1031555e-1
+ z * (0.2282967e-1
+ z * (-0.2895312e-1
+ z * (0.1787654e-1 + z * (-0.420059e-2)))))))));
if x < 0.0 {
-result
} else {
result
}
}
}
#[derive(Debug, Clone)]
pub struct VonMises<F: Float> {
pub mu: F,
pub kappa: F,
_phantom: PhantomData<F>,
}
impl<F: Float + SampleUniform + Debug + 'static + std::fmt::Display> VonMises<F> {
pub fn new(mu: F, kappa: F) -> StatsResult<Self> {
if kappa < F::zero() {
return Err(StatsError::InvalidArgument(format!(
"Concentration parameter kappa must be >= 0, got {:?}",
kappa
)));
}
let two_pi = F::from(2.0 * PI).expect("Failed to convert to float");
let pi = F::from(PI).expect("Failed to convert to float");
let normalized_mu = ((mu % two_pi) + two_pi) % two_pi;
let normalized_mu = if normalized_mu > pi {
normalized_mu - two_pi
} else {
normalized_mu
};
Ok(Self {
mu: normalized_mu,
kappa,
_phantom: PhantomData,
})
}
fn bessel_i0(&self, x: F) -> F {
let x_f64 = x.to_f64().expect("Operation failed");
let result = bessel_i0(x_f64);
F::from(result).expect("Failed to convert to float")
}
fn bessel_i0e(&self, x: F) -> F {
let x_f64 = x.to_f64().expect("Operation failed");
let result = bessel_i0(x_f64) * (-x_f64).exp();
F::from(result).expect("Failed to convert to float")
}
fn bessel_i1(&self, x: F) -> F {
let x_f64 = x.to_f64().expect("Operation failed");
let result = bessel_i1(x_f64);
F::from(result).expect("Failed to convert to float")
}
fn bessel_i1e(&self, x: F) -> F {
let x_f64 = x.to_f64().expect("Operation failed");
let result = bessel_i1(x_f64) * (-x_f64).exp();
F::from(result).expect("Failed to convert to float")
}
fn sample_von_mises<R: scirs2_core::random::Rng>(
&self,
mu: f64,
kappa: f64,
rng: &mut R,
) -> f64 {
use scirs2_core::random::{Distribution, Uniform};
if kappa < 1e-6 {
let uniform = Uniform::new(0.0, 2.0 * PI).expect("Operation failed");
return uniform.sample(rng);
}
let uniform = Uniform::new(0.0, 1.0).expect("Operation failed");
loop {
let u1 = uniform.sample(rng);
let u2 = uniform.sample(rng);
let u3 = uniform.sample(rng);
let a = 1.0 + (1.0 + 4.0 * kappa * kappa).sqrt();
let b = (a - (2.0 * a).sqrt()) / (2.0 * kappa);
let r = (1.0 + b * b) / (2.0 * b);
let theta = (2.0 * u1 - 1.0) / r;
let z = (r * theta).cos();
if z * z < 1.0 {
let f = (1.0 + r * z) / (r + z);
let c = kappa * (r - f);
if c * (2.0 - c) - u2 > 0.0 || (c / u2.ln() + 1.0).ln() - c >= 0.0 {
let angle = if u3 > 0.5f64 {
theta.acos()
} else {
-theta.acos()
};
let result = mu + angle;
return ((result + PI) % (2.0 * PI)) - PI;
}
}
}
}
fn cosm1(&self, x: F) -> F {
let x_f64 = x.to_f64().expect("Operation failed");
if x_f64.abs() < 1e-4 {
return F::from(-x_f64 * x_f64 / 2.0).expect("Failed to convert to float");
}
x.cos() - F::one()
}
}
#[allow(dead_code)]
fn von_mises_cdf<F: Float + 'static>(kappa: F, x: F) -> F {
let kappa_f64 = kappa.to_f64().expect("Operation failed");
let x_f64 = x.to_f64().expect("Operation failed");
if kappa_f64 < 1e-8 {
return F::from((x_f64 + PI) / (2.0 * PI)).expect("Operation failed");
}
let two_pi = 2.0 * PI;
let pi = PI;
let mut x_norm = x_f64 % two_pi;
if x_norm > pi {
x_norm -= two_pi;
} else if x_norm < -pi {
x_norm += two_pi;
}
if kappa_f64 > 50.0 {
let sigma = 1.0 / kappa_f64.sqrt();
let z = x_norm / ((2.0_f64).sqrt() * sigma);
let cdf = 0.5 * (1.0 + z / (1.0 + z * z / 2.0).sqrt());
return F::from(cdf).expect("Failed to convert to float");
}
let mut cdf = 0.5;
cdf = cdf / two_pi + x_norm / two_pi;
F::from(cdf).expect("Failed to convert to float")
}
impl<F: Float + SampleUniform + Debug + 'static + std::fmt::Display> Distribution<F>
for VonMises<F>
{
fn mean(&self) -> F {
self.mu
}
fn var(&self) -> F {
let one = F::one();
if self.kappa == F::zero() {
return one;
}
one - self.bessel_i1(self.kappa) / self.bessel_i0(self.kappa)
}
fn std(&self) -> F {
let var = self.var();
let neg_two = F::from(-2.0).expect("Failed to convert constant to float");
(neg_two * (F::one() - var).ln()).sqrt()
}
fn rvs(&self, size: usize) -> StatsResult<Array1<F>> {
let mu_f64 = self.mu.to_f64().expect("Operation failed");
let kappa_f64 = self.kappa.to_f64().expect("Operation failed");
let mut rng = thread_rng();
let mut samples = Array1::zeros(size);
for i in 0..size {
let sample = self.sample_von_mises(mu_f64, kappa_f64, &mut rng);
samples[i] = F::from(sample).expect("Failed to convert to float");
}
Ok(samples)
}
fn entropy(&self) -> F {
let kappa = self.kappa;
let two_pi = F::from(2.0 * PI).expect("Failed to convert to float");
if kappa == F::zero() {
return two_pi.ln(); }
let i0e = self.bessel_i0e(kappa);
let i1e = self.bessel_i1e(kappa);
let ratio = i1e / i0e;
-kappa * ratio + (two_pi * self.bessel_i0(kappa)).ln()
}
}
impl<F: Float + SampleUniform + Debug + 'static + std::fmt::Display> CircularDistribution<F>
for VonMises<F>
{
fn pdf(&self, x: F) -> F {
let two_pi = F::from(2.0 * PI).expect("Failed to convert to float");
let cos_term = (x - self.mu).cos();
let i0 = self.bessel_i0(self.kappa);
(self.kappa * cos_term).exp() / (two_pi * i0)
}
fn cdf(&self, x: F) -> F {
von_mises_cdf(self.kappa, x - self.mu)
}
fn rvs_single(&self) -> StatsResult<F> {
let mu_f64 = self.mu.to_f64().expect("Operation failed");
let kappa_f64 = self.kappa.to_f64().expect("Operation failed");
let mut rng = thread_rng();
let sample = self.sample_von_mises(mu_f64, kappa_f64, &mut rng);
Ok(F::from(sample).expect("Failed to convert to float"))
}
fn circular_mean(&self) -> F {
self.mu
}
fn circular_variance(&self) -> F {
F::one() - self.mean_resultant_length()
}
fn circular_std(&self) -> F {
let neg_two = F::from(-2.0).expect("Failed to convert constant to float");
(neg_two * self.mean_resultant_length().ln()).sqrt()
}
fn mean_resultant_length(&self) -> F {
if self.kappa == F::zero() {
return F::zero(); }
self.bessel_i1(self.kappa) / self.bessel_i0(self.kappa)
}
fn concentration(&self) -> F {
self.kappa
}
}
#[allow(dead_code)]
pub fn von_mises<F: Float + SampleUniform + Debug + 'static + std::fmt::Display>(
mu: F,
kappa: F,
) -> StatsResult<VonMises<F>> {
VonMises::new(mu, kappa)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ScientificNumber;
#[test]
fn test_von_mises_creation() {
let vm = von_mises(0.0f64, 1.0);
assert!(vm.is_ok());
let vm = von_mises(0.0f64, -1.0);
assert!(vm.is_err());
}
#[test]
fn test_von_mises_pdf() {
let vm = von_mises(0.0f64, 1.0).expect("Operation failed");
let pdf_at_mean = vm.pdf(0.0);
let expected = 1.0_f64.exp() / (2.0 * PI * bessel_i0(1.0));
assert_abs_diff_eq!(pdf_at_mean, expected, epsilon = 1e-10);
let pdf_at_pi = vm.pdf(PI);
let expected = (-1.0_f64).exp() / (2.0 * PI * bessel_i0(1.0));
assert_abs_diff_eq!(pdf_at_pi, expected, epsilon = 1e-10);
let pdf_plus = vm.pdf(0.5);
let pdf_minus = vm.pdf(-0.5);
assert_abs_diff_eq!(pdf_plus, pdf_minus, epsilon = 1e-10);
}
#[test]
fn test_von_mises_circular_mean() {
for mu in [-PI / 2.0, 0.0, PI / 4.0, PI / 2.0, PI] {
let vm = von_mises(mu, 1.0).expect("Operation failed");
assert_abs_diff_eq!(
vm.circular_mean().to_f64().expect("Operation failed"),
mu,
epsilon = 1e-10
);
}
}
#[test]
fn test_von_mises_concentration() {
for kappa in [0.0, 0.5, 1.0, 5.0, 10.0] {
let vm = von_mises(0.0, kappa).expect("Operation failed");
assert_abs_diff_eq!(
vm.concentration().to_f64().expect("Operation failed"),
kappa,
epsilon = 1e-10
);
}
}
#[test]
fn test_von_mises_mean_resultant_length() {
let vm = von_mises(0.0f64, 0.0).expect("Operation failed");
assert_abs_diff_eq!(vm.mean_resultant_length(), 0.0, epsilon = 1e-10);
let vm = von_mises(0.0f64, 1.0).expect("Operation failed");
let expected = bessel_i1(1.0) / bessel_i0(1.0);
assert_abs_diff_eq!(vm.mean_resultant_length(), expected, epsilon = 1e-10);
}
#[test]
fn test_von_mises_rvs() {
let vm = von_mises(0.0f64, 5.0).expect("Operation failed");
let samples = vm.rvs(1000).expect("Operation failed");
for &sample in samples.iter() {
assert!(sample >= -PI && sample <= PI);
}
let sin_sum: f64 = samples.iter().map(|&x| x.sin()).sum();
let cos_sum: f64 = samples.iter().map(|&x| x.cos()).sum();
let circular_mean = sin_sum.atan2(cos_sum);
let deviation = (circular_mean - 0.0).abs();
assert!(
deviation < PI,
"Circular mean deviation {} should be less than π",
deviation
);
}
}