use rand::Rng;
use rand_distr::Distribution;
use super::SimdFloatExt;
use super::gamma::SimdGamma;
pub struct SimdChiSquared<T: SimdFloatExt> {
df: T,
gamma: SimdGamma<T>,
}
impl<T: SimdFloatExt> SimdChiSquared<T> {
#[inline]
pub fn new(k: T) -> Self {
Self::from_seed_source(k, &crate::simd_rng::Unseeded)
}
#[inline]
pub fn with_seed(k: T, seed: u64) -> Self {
Self::from_seed_source(k, &crate::simd_rng::Deterministic::new(seed))
}
pub fn from_seed_source(k: T, seed: &impl crate::simd_rng::SeedExt) -> Self {
Self {
df: k,
gamma: SimdGamma::from_seed_source(k * T::from(0.5).unwrap(), T::from(2.0).unwrap(), seed),
}
}
#[inline]
pub fn sample_fast(&self) -> T {
self.gamma.sample_fast()
}
pub fn fill_slice<R: Rng + ?Sized>(&self, _rng: &mut R, out: &mut [T]) {
self.gamma.fill_slice_fast(out);
}
pub fn fill_slice_fast(&self, out: &mut [T]) {
self.gamma.fill_slice_fast(out);
}
}
impl<T: SimdFloatExt> Clone for SimdChiSquared<T> {
fn clone(&self) -> Self {
Self::new(self.df)
}
}
impl<T: SimdFloatExt> Distribution<T> for SimdChiSquared<T> {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> T {
self.gamma.sample(rng)
}
}
impl<T: SimdFloatExt> crate::traits::DistributionExt for SimdChiSquared<T> {
fn pdf(&self, x: f64) -> f64 {
if x <= 0.0 {
return 0.0;
}
let k = self.df.to_f64().unwrap();
let half_k = 0.5 * k;
let log_pdf = (half_k - 1.0) * x.ln()
- 0.5 * x
- half_k * std::f64::consts::LN_2
- crate::special::ln_gamma(half_k);
log_pdf.exp()
}
fn cdf(&self, x: f64) -> f64 {
if x <= 0.0 {
return 0.0;
}
let k = self.df.to_f64().unwrap();
crate::special::gamma_p(0.5 * k, 0.5 * x)
}
fn inv_cdf(&self, p: f64) -> f64 {
if p <= 0.0 {
return 0.0;
}
if p >= 1.0 {
return f64::INFINITY;
}
let k = self.df.to_f64().unwrap();
let z = crate::special::ndtri(p);
let mut x = k * (1.0 - 2.0 / (9.0 * k) + z * (2.0 / (9.0 * k)).sqrt()).powi(3);
if x <= 0.0 {
x = 0.5 * k;
}
let half_k = 0.5 * k;
for _ in 0..30 {
let f = crate::special::gamma_p(half_k, 0.5 * x) - p;
let log_pdf = (half_k - 1.0) * x.ln()
- 0.5 * x
- half_k * std::f64::consts::LN_2
- crate::special::ln_gamma(half_k);
let pdf = log_pdf.exp();
if pdf <= 0.0 {
break;
}
let dx = f / pdf;
let new_x = (x - dx).max(x * 1e-12);
if (new_x - x).abs() < 1e-14 * x.max(1.0) {
return new_x;
}
x = new_x;
}
x
}
fn mean(&self) -> f64 {
self.df.to_f64().unwrap()
}
fn median(&self) -> f64 {
let k = self.df.to_f64().unwrap();
k * (1.0 - 2.0 / (9.0 * k)).powi(3)
}
fn mode(&self) -> f64 {
let k = self.df.to_f64().unwrap();
(k - 2.0).max(0.0)
}
fn variance(&self) -> f64 {
2.0 * self.df.to_f64().unwrap()
}
fn skewness(&self) -> f64 {
(8.0 / self.df.to_f64().unwrap()).sqrt()
}
fn kurtosis(&self) -> f64 {
12.0 / self.df.to_f64().unwrap()
}
fn entropy(&self) -> f64 {
let k = self.df.to_f64().unwrap();
let half_k = 0.5 * k;
half_k
+ std::f64::consts::LN_2
+ crate::special::ln_gamma(half_k)
+ (1.0 - half_k) * crate::special::digamma(half_k)
}
fn characteristic_function(&self, t: f64) -> num_complex::Complex64 {
let k = self.df.to_f64().unwrap();
let one_minus_2it = num_complex::Complex64::new(1.0, -2.0 * t);
one_minus_2it.powf(-0.5 * k)
}
fn moment_generating_function(&self, t: f64) -> f64 {
let k = self.df.to_f64().unwrap();
if t < 0.5 {
(1.0 - 2.0 * t).powf(-0.5 * k)
} else {
f64::INFINITY
}
}
}
py_distribution!(PyChiSquared, SimdChiSquared,
sig: (k, seed=None, dtype=None),
params: (k: f64)
);