1use rand::Rng;
29
30use super::{
31 BetaDistribution, ChiSquared, Exponential, GammaDistribution, LogNormal, Normal, Pert,
32 Triangular, Uniform, Weibull,
33};
34use crate::random::create_rng;
35
36pub trait Sample {
48 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64;
50
51 fn sample_n(&self, n: usize, seed: u64) -> Vec<f64> {
54 let mut rng = create_rng(seed);
55 (0..n).map(|_| self.sample(&mut rng)).collect()
56 }
57}
58
59pub(crate) fn open_unit<R: Rng + ?Sized>(rng: &mut R) -> f64 {
65 const STEP: f64 = 1.0 / (1u64 << 52) as f64;
66 ((rng.next_u64() >> 12) as f64 + 0.5) * STEP
67}
68
69fn standard_normal<R: Rng + ?Sized>(rng: &mut R) -> f64 {
71 crate::special::inverse_normal_cdf(open_unit(rng))
72}
73
74fn standard_gamma<R: Rng + ?Sized>(shape: f64, rng: &mut R) -> f64 {
76 if shape < 1.0 {
77 return standard_gamma(shape + 1.0, rng) * open_unit(rng).powf(1.0 / shape);
79 }
80 let d = shape - 1.0 / 3.0;
81 let c = 1.0 / (9.0 * d).sqrt();
82 loop {
83 let x = standard_normal(rng);
84 let v = 1.0 + c * x;
85 if v <= 0.0 {
86 continue;
87 }
88 let v = v * v * v;
89 let u = open_unit(rng);
90 let x2 = x * x;
91 if u < 1.0 - 0.0331 * x2 * x2 || u.ln() < 0.5 * x2 + d * (1.0 - v + v.ln()) {
93 return d * v;
94 }
95 }
96}
97
98macro_rules! by_inversion {
99 ($($t:ty),*) => {$(
100 impl Sample for $t {
101 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
102 self.quantile(open_unit(rng))
103 .expect("the quantile is defined on the open unit interval")
104 }
105 }
106 )*};
107}
108
109by_inversion!(Uniform, Triangular, Normal, LogNormal, Weibull, Exponential);
110
111impl Sample for GammaDistribution {
112 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
113 standard_gamma(self.shape(), rng) / self.rate()
114 }
115}
116
117impl Sample for ChiSquared {
118 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
119 2.0 * standard_gamma(self.k / 2.0, rng)
120 }
121}
122
123fn beta_variate<R: Rng + ?Sized>(alpha: f64, beta: f64, rng: &mut R) -> f64 {
125 let x = standard_gamma(alpha, rng);
126 let y = standard_gamma(beta, rng);
127 x / (x + y)
128}
129
130impl Sample for BetaDistribution {
131 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
132 beta_variate(self.alpha, self.beta, rng)
133 }
134}
135
136impl Sample for Pert {
137 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
138 self.min() + (self.max() - self.min()) * beta_variate(self.alpha(), self.beta_param(), rng)
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 fn ks(mut xs: Vec<f64>, cdf: impl Fn(f64) -> f64) -> f64 {
148 xs.sort_by(|a, b| a.partial_cmp(b).expect("finite variates"));
149 let n = xs.len() as f64;
150 xs.iter()
151 .enumerate()
152 .map(|(i, &x)| {
153 let f = cdf(x);
154 (f - i as f64 / n).abs().max(((i + 1) as f64 / n - f).abs())
155 })
156 .fold(0.0, f64::max)
157 }
158
159 const N: usize = 20_000;
161 const D_CRIT: f64 = 0.0138;
162
163 fn check<D: Sample>(d: &D, cdf: impl Fn(f64) -> f64, name: &str) {
164 let xs = d.sample_n(N, 7);
165 assert!(
166 xs.iter().all(|x| x.is_finite()),
167 "{name}: non-finite variate"
168 );
169 let dist = ks(xs, cdf);
170 assert!(dist < D_CRIT, "{name}: KS distance {dist} >= {D_CRIT}");
171 }
172
173 #[test]
174 fn every_distribution_matches_its_cdf() {
175 let u = Uniform::new(-2.0, 5.0).unwrap();
176 check(&u, |x| u.cdf(x), "uniform");
177 let t = Triangular::new(0.0, 1.0, 4.0).unwrap();
178 check(&t, |x| t.cdf(x), "triangular");
179 let n = Normal::new(3.0, 2.0).unwrap();
180 check(&n, |x| n.cdf(x), "normal");
181 let ln = LogNormal::new(0.5, 0.8).unwrap();
182 check(&ln, |x| ln.cdf(x), "lognormal");
183 let w = Weibull::new(1.7, 40.0).unwrap();
184 check(&w, |x| w.cdf(x), "weibull");
185 let e = Exponential::new(0.25).unwrap();
186 check(&e, |x| e.cdf(x), "exponential");
187 for shape in [0.3, 1.0, 2.5, 30.0] {
188 let g = GammaDistribution::new(shape, 2.0).unwrap();
189 check(&g, |x| g.cdf(x), &format!("gamma({shape})"));
190 }
191 for k in [1.0, 3.0, 17.0] {
192 let c = ChiSquared::new(k).unwrap();
193 check(&c, |x| c.cdf(x), &format!("chi2({k})"));
194 }
195 for (a, b) in [(0.5, 0.5), (2.0, 5.0), (8.0, 1.5)] {
196 let bd = BetaDistribution::new(a, b).unwrap();
197 check(&bd, |x| bd.cdf(x), &format!("beta({a},{b})"));
198 }
199 let p = Pert::new(2.0, 3.0, 10.0).unwrap();
200 check(&p, |x| p.cdf(x), "pert");
201 }
202
203 #[test]
204 fn same_seed_same_draws_different_seed_different_draws() {
205 let g = GammaDistribution::new(2.0, 1.0).unwrap();
206 assert_eq!(g.sample_n(50, 1), g.sample_n(50, 1));
207 assert_ne!(g.sample_n(50, 1), g.sample_n(50, 2));
208 }
209
210 #[test]
211 fn inversion_is_monotone_in_the_uniform() {
212 let small = Weibull::new(2.0, 10.0).unwrap().sample_n(200, 5);
214 let large = Weibull::new(2.0, 20.0).unwrap().sample_n(200, 5);
215 assert!(small.iter().zip(&large).all(|(a, b)| a < b));
216 }
217
218 #[test]
219 fn the_open_uniform_never_reaches_its_ends() {
220 struct Fixed(u64);
221 impl rand::TryRng for Fixed {
222 type Error = std::convert::Infallible;
223 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
224 Ok(self.0 as u32)
225 }
226 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
227 Ok(self.0)
228 }
229 fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
230 dst.fill(0);
231 Ok(())
232 }
233 }
234 let lo = open_unit(&mut Fixed(0));
235 let hi = open_unit(&mut Fixed(u64::MAX));
236 assert!(lo > 0.0 && hi < 1.0);
237 assert!(Exponential::new(1.0)
238 .unwrap()
239 .quantile(hi)
240 .unwrap()
241 .is_finite());
242 }
243}