numeris/stats/
chi_squared.rs1use crate::FloatScalar;
2use crate::special::{lgamma, gamma_inc};
3use super::{ContinuousDistribution, StatsError, quantile_newton, normal_quantile_standard};
4
5#[derive(Debug, Clone, Copy)]
19pub struct ChiSquared<T> {
20 k: T, }
22
23impl<T: FloatScalar> ChiSquared<T> {
24 pub fn new(k: T) -> Result<Self, StatsError> {
26 if k <= T::zero() {
27 return Err(StatsError::InvalidParameter);
28 }
29 Ok(Self { k })
30 }
31}
32
33impl<T: FloatScalar> ChiSquared<T> {
34 pub fn sample(&self, rng: &mut super::Rng) -> T {
38 let two = T::one() + T::one();
39 rng.next_gamma(self.k / two) * two
40 }
41
42 pub fn sample_array<const K: usize>(&self, rng: &mut super::Rng) -> [T; K] {
44 let mut out = [T::zero(); K];
45 for v in out.iter_mut() {
46 *v = self.sample(rng);
47 }
48 out
49 }
50}
51
52impl<T: FloatScalar> ContinuousDistribution<T> for ChiSquared<T> {
53 fn pdf(&self, x: T) -> T {
54 if x <= T::zero() {
55 return T::zero();
56 }
57 self.ln_pdf(x).exp()
58 }
59
60 fn ln_pdf(&self, x: T) -> T {
61 if x <= T::zero() {
62 return T::neg_infinity();
63 }
64 let one = T::one();
65 let two = one + one;
66 let half_k = self.k / two;
67 (half_k - one) * x.ln() - x / two - half_k * two.ln() - lgamma(half_k)
68 }
69
70 fn cdf(&self, x: T) -> T {
71 if x <= T::zero() {
72 return T::zero();
73 }
74 let two = T::one() + T::one();
75 gamma_inc(self.k / two, x / two).unwrap_or(T::nan())
76 }
77
78 fn quantile(&self, p: T) -> T {
79 let two = T::one() + T::one();
80 let nine = T::from(9.0).unwrap();
81 let z = normal_quantile_standard(p);
83 let v = T::one() - two / (nine * self.k) + z * (two / (nine * self.k)).sqrt();
84 let x0 = if v > T::zero() { self.k * v * v * v } else { self.mean() };
85 let hi = self.mean() + T::from(40.0).unwrap() * self.variance().sqrt();
86 quantile_newton(|x| self.cdf(x), |x| self.pdf(x), p, x0, T::zero(), hi)
87 }
88
89 fn mean(&self) -> T {
90 self.k
91 }
92
93 fn variance(&self) -> T {
94 let two = T::one() + T::one();
95 two * self.k
96 }
97}