rand_distr/
chi_squared.rs1use self::ChiSquaredRepr::*;
13
14use crate::{Distribution, Exp1, Gamma, Open01, StandardNormal};
15use core::fmt;
16use num_traits::Float;
17use rand::Rng;
18#[cfg(feature = "serde")]
19use serde::{Deserialize, Serialize};
20
21#[derive(Clone, Copy, Debug, PartialEq)]
48#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
49pub struct ChiSquared<F>
50where
51 F: Float,
52 StandardNormal: Distribution<F>,
53 Exp1: Distribution<F>,
54 Open01: Distribution<F>,
55{
56 repr: ChiSquaredRepr<F>,
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
62pub enum Error {
63 DoFTooSmall,
65}
66
67impl fmt::Display for Error {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 f.write_str(match self {
70 Error::DoFTooSmall => {
71 "degrees-of-freedom k is not positive in chi-squared distribution"
72 }
73 })
74 }
75}
76
77#[cfg(feature = "std")]
78impl std::error::Error for Error {}
79
80#[derive(Clone, Copy, Debug, PartialEq)]
81#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
82enum ChiSquaredRepr<F>
83where
84 F: Float,
85 StandardNormal: Distribution<F>,
86 Exp1: Distribution<F>,
87 Open01: Distribution<F>,
88{
89 DoFExactlyOne,
93 DoFAnythingElse(Gamma<F>),
94}
95
96impl<F> ChiSquared<F>
97where
98 F: Float,
99 StandardNormal: Distribution<F>,
100 Exp1: Distribution<F>,
101 Open01: Distribution<F>,
102{
103 pub fn new(k: F) -> Result<ChiSquared<F>, Error> {
106 let repr = if k == F::one() {
107 DoFExactlyOne
108 } else {
109 if !(F::from(0.5).unwrap() * k > F::zero()) {
110 return Err(Error::DoFTooSmall);
111 }
112 DoFAnythingElse(Gamma::new(F::from(0.5).unwrap() * k, F::from(2.0).unwrap()).unwrap())
113 };
114 Ok(ChiSquared { repr })
115 }
116}
117impl<F> Distribution<F> for ChiSquared<F>
118where
119 F: Float,
120 StandardNormal: Distribution<F>,
121 Exp1: Distribution<F>,
122 Open01: Distribution<F>,
123{
124 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F {
125 match self.repr {
126 DoFExactlyOne => {
127 let norm: F = rng.sample(StandardNormal);
129 norm * norm
130 }
131 DoFAnythingElse(ref g) => g.sample(rng),
132 }
133 }
134}
135
136#[cfg(test)]
137mod test {
138 use super::*;
139
140 #[test]
141 fn test_chi_squared_one() {
142 let chi = ChiSquared::new(1.0).unwrap();
143 let mut rng = crate::test::rng(201);
144 for _ in 0..1000 {
145 chi.sample(&mut rng);
146 }
147 }
148 #[test]
149 fn test_chi_squared_small() {
150 let chi = ChiSquared::new(0.5).unwrap();
151 let mut rng = crate::test::rng(202);
152 for _ in 0..1000 {
153 chi.sample(&mut rng);
154 }
155 }
156 #[test]
157 fn test_chi_squared_large() {
158 let chi = ChiSquared::new(30.0).unwrap();
159 let mut rng = crate::test::rng(203);
160 for _ in 0..1000 {
161 chi.sample(&mut rng);
162 }
163 }
164 #[test]
165 #[should_panic]
166 fn test_chi_squared_invalid_dof() {
167 ChiSquared::new(-1.0).unwrap();
168 }
169
170 #[test]
171 fn gamma_distributions_can_be_compared() {
172 assert_eq!(Gamma::new(1.0, 2.0), Gamma::new(1.0, 2.0));
173 }
174
175 #[test]
176 fn chi_squared_distributions_can_be_compared() {
177 assert_eq!(ChiSquared::new(1.0), ChiSquared::new(1.0));
178 }
179}