1use crate::error::{Fault, OpError, OpResult};
8use std::f64::consts::{PI, SQRT_2};
9use std::fmt;
10
11pub const Z95: f64 = 1.644853626951472;
14
15#[derive(Clone, Copy, Debug, PartialEq)]
17pub enum Family {
18 Normal {
19 mean: f64,
20 sd: f64,
21 },
22 Lognormal {
23 mu: f64,
24 sigma: f64,
25 },
26 Uniform {
27 lo: f64,
28 hi: f64,
29 },
30 Beta {
31 a: f64,
32 b: f64,
33 },
34 Gamma {
35 shape: f64,
36 scale: f64,
37 },
38 Exponential {
39 rate: f64,
40 },
41 Triangular {
42 lo: f64,
43 mode: f64,
44 hi: f64,
45 },
46 Pert {
48 lo: f64,
49 mode: f64,
50 hi: f64,
51 },
52}
53
54fn check(ok: bool, message: impl FnOnce() -> String) -> OpResult<()> {
55 if ok {
56 Ok(())
57 } else {
58 Err(OpError::fault(Fault::DomainError, message()))
59 }
60}
61
62fn finite(values: &[f64], what: &str) -> OpResult<()> {
63 check(values.iter().all(|x| x.is_finite()), || {
64 format!("{what} needs finite numbers")
65 })
66}
67
68fn ratio(a: f64, b: f64) -> f64 {
70 if a >= b {
71 1.0 / (1.0 + b / a)
72 } else {
73 let r = a / b;
74 r / (1.0 + r)
75 }
76}
77
78fn interval_fraction(lo: f64, hi: f64, x: f64) -> f64 {
79 if x <= lo {
80 return 0.0;
81 }
82 if x >= hi {
83 return 1.0;
84 }
85 let width = hi - lo;
86 if width.is_finite() {
87 (x - lo) / width
88 } else {
89 (x / 2.0 - lo / 2.0) / (hi / 2.0 - lo / 2.0)
90 }
91}
92
93fn inverse_width(lo: f64, hi: f64) -> f64 {
94 let width = hi - lo;
95 if width.is_finite() {
96 1.0 / width
97 } else {
98 0.5 / (hi / 2.0 - lo / 2.0)
99 }
100}
101
102fn standardized(x: f64, mean: f64, sd: f64) -> f64 {
103 if !x.is_finite() {
104 return x;
105 }
106 let delta = x - mean;
107 if delta.is_finite() {
108 delta / sd
109 } else {
110 x / sd - mean / sd
111 }
112}
113
114impl Family {
115 pub fn normal(mean: f64, sd: f64) -> OpResult<Family> {
116 finite(&[mean, sd], "normal")?;
117 check(sd > 0.0, || "normal needs a standard deviation above 0".into())?;
118 Ok(Family::Normal { mean, sd })
119 }
120
121 pub fn lognormal(mu: f64, sigma: f64) -> OpResult<Family> {
122 finite(&[mu, sigma], "lognormal")?;
123 check(sigma > 0.0, || "lognormal needs a sigma above 0".into())?;
124 Ok(Family::Lognormal { mu, sigma })
125 }
126
127 pub fn uniform(lo: f64, hi: f64) -> OpResult<Family> {
128 finite(&[lo, hi], "uniform")?;
129 check(lo < hi, || "uniform needs its lower end below its upper end".into())?;
130 Ok(Family::Uniform { lo, hi })
131 }
132
133 pub fn beta(a: f64, b: f64) -> OpResult<Family> {
134 finite(&[a, b], "beta")?;
135 check(a > 0.0 && b > 0.0, || "beta needs two parameters above 0".into())?;
136 Ok(Family::Beta { a, b })
137 }
138
139 pub fn gamma(shape: f64, scale: f64) -> OpResult<Family> {
140 finite(&[shape, scale], "gamma")?;
141 check(shape > 0.0 && scale > 0.0, || {
142 "gamma needs a shape and a scale above 0".into()
143 })?;
144 Ok(Family::Gamma { shape, scale })
145 }
146
147 pub fn exponential(rate: f64) -> OpResult<Family> {
148 finite(&[rate], "exponential")?;
149 check(rate > 0.0, || "exponential needs a rate above 0".into())?;
150 Ok(Family::Exponential { rate })
151 }
152
153 pub fn triangular(lo: f64, mode: f64, hi: f64) -> OpResult<Family> {
154 finite(&[lo, mode, hi], "triangular")?;
155 check(lo < hi && lo <= mode && mode <= hi, || {
156 "triangular needs lo < hi, with the mode between them".into()
157 })?;
158 Ok(Family::Triangular { lo, mode, hi })
159 }
160
161 pub fn pert(lo: f64, mode: f64, hi: f64) -> OpResult<Family> {
162 finite(&[lo, mode, hi], "pert")?;
163 check(lo < hi && lo <= mode && mode <= hi, || {
164 "pert needs lo < hi, with the mode between them".into()
165 })?;
166 Ok(Family::Pert { lo, mode, hi })
167 }
168
169 pub fn estimate(a: f64, b: f64) -> OpResult<Family> {
171 finite(&[a, b], "`to`")?;
172 if a <= 0.0 || b <= 0.0 {
173 return Err(
174 OpError::fault(Fault::DomainError, "`a to b` needs two positive numbers")
175 .help("for a quantity that can be zero or negative, use `normal_range(lo, hi)`"),
176 );
177 }
178 check(a < b, || "`a to b` needs a below b".into())?;
179 let (la, lb) = (libm::log(a), libm::log(b));
180 Family::lognormal((la + lb) / 2.0, (lb - la) / (2.0 * Z95))
181 }
182
183 pub fn normal_range(lo: f64, hi: f64) -> OpResult<Family> {
186 finite(&[lo, hi], "normal_range")?;
187 check(lo < hi, || {
188 "normal_range needs its lower end below its upper end".into()
189 })?;
190 Family::normal(
191 crate::stats::midpoint(lo, hi),
192 crate::stats::scaled_difference(hi, lo, 1.0 / (2.0 * Z95)),
193 )
194 }
195
196 fn pert_shape(lo: f64, mode: f64, hi: f64) -> (f64, f64) {
198 let p = interval_fraction(lo, hi, mode);
199 (1.0 + 4.0 * p, 1.0 + 4.0 * (1.0 - p))
200 }
201
202 pub fn support(&self) -> (f64, f64) {
204 match *self {
205 Family::Normal { .. } => (f64::NEG_INFINITY, f64::INFINITY),
206 Family::Lognormal { .. } | Family::Gamma { .. } | Family::Exponential { .. } => (0.0, f64::INFINITY),
207 Family::Uniform { lo, hi } | Family::Triangular { lo, hi, .. } | Family::Pert { lo, hi, .. } => (lo, hi),
208 Family::Beta { .. } => (0.0, 1.0),
209 }
210 }
211
212 pub fn mean(&self) -> f64 {
213 match *self {
214 Family::Normal { mean, .. } => mean,
215 Family::Lognormal { mu, sigma } => crate::math::exp(mu + sigma * sigma / 2.0),
216 Family::Uniform { lo, hi } => crate::stats::midpoint(lo, hi),
217 Family::Beta { a, b } => ratio(a, b),
218 Family::Gamma { shape, scale } => shape * scale,
219 Family::Exponential { rate } => 1.0 / rate,
220 Family::Triangular { lo, mode, hi } => crate::stats::lerp(crate::stats::midpoint(lo, hi), mode, 1.0 / 3.0),
221 Family::Pert { lo, mode, hi } => {
222 let (a, b) = Family::pert_shape(lo, mode, hi);
223 crate::stats::lerp(lo, hi, ratio(a, b))
224 }
225 }
226 }
227
228 pub fn variance(&self) -> f64 {
229 let sd = self.sd();
230 sd * sd
231 }
232
233 pub fn sd(&self) -> f64 {
234 match *self {
235 Family::Normal { sd, .. } => sd,
236 Family::Lognormal { mu, sigma } => {
237 let s2 = sigma * sigma;
238 let log_sd = if s2 == 0.0 {
239 mu + libm::log(sigma)
240 } else {
241 mu + s2 + 0.5 * libm::log(-libm::expm1(-s2))
242 };
243 crate::math::exp(log_sd)
244 }
245 Family::Uniform { lo, hi } => crate::stats::scaled_difference(hi, lo, 1.0 / libm::sqrt(12.0)),
246 Family::Beta { a, b } => {
247 let max = a.max(b);
248 let inv = if max < 1.0 {
249 1.0 / (1.0 + a + b)
250 } else {
251 (1.0 / max) / (1.0 + a.min(b) / max + 1.0 / max)
252 };
253 libm::sqrt(ratio(a, b)) * libm::sqrt(ratio(b, a)) * libm::sqrt(inv)
254 }
255 Family::Gamma { shape, scale } => libm::sqrt(shape) * scale,
256 Family::Exponential { rate } => 1.0 / rate,
257 Family::Triangular { lo, mode, hi } => {
258 let d = |a, b| crate::stats::scaled_difference(a, b, 1.0 / 6.0);
259 libm::hypot(libm::hypot(d(mode, lo), d(hi, mode)), d(hi, lo))
260 }
261 Family::Pert { lo, mode, hi } => {
262 let (a, b) = Family::pert_shape(lo, mode, hi);
263 crate::stats::scaled_difference(hi, lo, Family::Beta { a, b }.sd())
264 }
265 }
266 }
267
268 pub fn interval_moments(&self, lo: f64, hi: f64) -> (f64, f64) {
272 let mass = self.cdf(hi) - self.cdf(lo);
273 let raw = |first: f64, second: f64| {
274 let mean = first / mass;
275 (mean, (second / mass - mean * mean).max(0.0))
276 };
277 match *self {
278 Family::Uniform { .. } => (crate::stats::midpoint(lo, hi), Family::Uniform { lo, hi }.variance()),
279 Family::Normal { mean, sd } => {
280 let (a, b) = ((lo - mean) / sd, (hi - mean) / sd);
281 let (pa, pb) = (std_normal_pdf(a), std_normal_pdf(b));
282 let shift = (pa - pb) / mass;
283 let edge = |z: f64, p: f64| if z.is_finite() { z * p } else { 0.0 };
284 (
285 mean + sd * shift,
286 sd * sd * (1.0 + (edge(a, pa) - edge(b, pb)) / mass - shift * shift).max(0.0),
287 )
288 }
289 Family::Beta { a, b } => {
290 let moment = |n: f64| beta_cdf(a + n, b, hi) - beta_cdf(a + n, b, lo);
291 raw(
292 a / (a + b) * moment(1.0),
293 a * (a + 1.0) / ((a + b) * (a + b + 1.0)) * moment(2.0),
294 )
295 }
296 Family::Gamma { shape, scale } => {
297 let moment = |n: f64| gamma_cdf(shape + n, hi / scale) - gamma_cdf(shape + n, lo / scale);
298 raw(
299 shape * scale * moment(1.0),
300 shape * (shape + 1.0) * scale * scale * moment(2.0),
301 )
302 }
303 Family::Exponential { rate } => Family::Gamma {
304 shape: 1.0,
305 scale: 1.0 / rate,
306 }
307 .interval_moments(lo, hi),
308 Family::Lognormal { mu, sigma } => {
309 let moment = |n: f64| {
310 let cdf = |x| std_normal_cdf((libm::log(x) - mu - n * sigma * sigma) / sigma);
311 crate::math::exp(n * mu + n * n * sigma * sigma / 2.0) * (cdf(hi) - cdf(lo))
312 };
313 raw(moment(1.0), moment(2.0))
314 }
315 Family::Pert { lo: a, mode, hi: b } => {
316 let (alpha, beta) = Self::pert_shape(a, mode, b);
317 let (m, v) =
318 Family::Beta { a: alpha, b: beta }.interval_moments((lo - a) / (b - a), (hi - a) / (b - a));
319 (a + (b - a) * m, (b - a).powi(2) * v)
320 }
321 Family::Triangular { lo: a, mode, hi: b } => {
322 let width = b - a;
323 let (l, h, m) = ((lo - a) / width, (hi - a) / width, (mode - a) / width);
324 let moment = |n: i32| {
325 let integral = |l: f64, h: f64, k: i32| (h.powi(k + 1) - l.powi(k + 1)) / (k + 1) as f64;
326 let left = if l < m {
327 2.0 / m * integral(l, h.min(m), n + 1)
328 } else {
329 0.0
330 };
331 let right = if h > m {
332 2.0 / (1.0 - m) * (integral(l.max(m), h, n) - integral(l.max(m), h, n + 1))
333 } else {
334 0.0
335 };
336 left + right
337 };
338 let (m, v) = raw(moment(1), moment(2));
339 (a + width * m, width * width * v)
340 }
341 }
342 }
343
344 pub fn pdf(&self, x: f64) -> f64 {
345 match *self {
346 Family::Normal { mean, sd } => std_normal_pdf(standardized(x, mean, sd)) / sd,
347 Family::Lognormal { mu, sigma } => {
348 if x <= 0.0 {
349 0.0
350 } else {
351 std_normal_pdf((libm::log(x) - mu) / sigma) / (sigma * x)
352 }
353 }
354 Family::Uniform { lo, hi } => {
355 if (lo..=hi).contains(&x) {
356 inverse_width(lo, hi)
357 } else {
358 0.0
359 }
360 }
361 Family::Beta { a, b } => beta_pdf(a, b, x),
362 Family::Gamma { shape, scale } => {
363 if x < 0.0 {
364 return 0.0;
365 }
366 if x == 0.0 {
367 return if shape < 1.0 {
368 f64::INFINITY
369 } else if shape == 1.0 {
370 1.0 / scale
371 } else {
372 0.0
373 };
374 }
375 let y = x / scale;
376 crate::math::exp((shape - 1.0) * libm::log(y) - y - libm::lgamma(shape)) / scale
377 }
378 Family::Exponential { rate } => {
379 if x < 0.0 {
380 0.0
381 } else {
382 rate * crate::math::exp(-rate * x)
383 }
384 }
385 Family::Triangular { lo, mode, hi } => {
386 if x < lo || x > hi {
387 0.0
388 } else if x < mode {
389 2.0 * interval_fraction(lo, mode, x) * inverse_width(lo, hi)
390 } else if x > mode {
391 2.0 * (1.0 - interval_fraction(mode, hi, x)) * inverse_width(lo, hi)
392 } else {
393 2.0 * inverse_width(lo, hi)
394 }
395 }
396 Family::Pert { lo, mode, hi } => {
397 let (a, b) = Family::pert_shape(lo, mode, hi);
398 if x < lo || x > hi {
399 0.0
400 } else {
401 beta_pdf(a, b, interval_fraction(lo, hi, x)) * inverse_width(lo, hi)
402 }
403 }
404 }
405 }
406
407 pub fn ln_pdf(&self, x: f64) -> f64 {
410 match *self {
411 Family::Normal { mean, sd } => {
412 let z = standardized(x, mean, sd);
413 -0.5 * z * z - libm::log(sd) - LN_SQRT_2PI
414 }
415 Family::Lognormal { mu, sigma } if x > 0.0 => {
416 let z = (libm::log(x) - mu) / sigma;
417 -0.5 * z * z - libm::log(sigma) - libm::log(x) - LN_SQRT_2PI
418 }
419 Family::Beta { a, b } if x > 0.0 && x < 1.0 => {
420 (a - 1.0) * libm::log(x) + (b - 1.0) * libm::log1p(-x) - ln_beta(a, b)
421 }
422 Family::Gamma { shape, scale } if x > 0.0 => {
423 let y = x / scale;
424 (shape - 1.0) * libm::log(y) - y - libm::lgamma(shape) - libm::log(scale)
425 }
426 Family::Exponential { rate } if x >= 0.0 => libm::log(rate) - rate * x,
427 Family::Pert { lo, mode, hi } if x > lo && x < hi => {
428 let (a, b) = Family::pert_shape(lo, mode, hi);
429 Family::Beta { a, b }.ln_pdf(interval_fraction(lo, hi, x)) + libm::log(inverse_width(lo, hi))
430 }
431 _ => libm::log(self.pdf(x)),
433 }
434 }
435
436 pub fn cdf(&self, x: f64) -> f64 {
438 if x.is_nan() {
439 return f64::NAN;
440 }
441 match *self {
442 Family::Normal { mean, sd } => std_normal_cdf(standardized(x, mean, sd)),
443 Family::Lognormal { mu, sigma } => {
444 if x <= 0.0 {
445 0.0
446 } else {
447 std_normal_cdf((libm::log(x) - mu) / sigma)
448 }
449 }
450 Family::Uniform { lo, hi } => interval_fraction(lo, hi, x).clamp(0.0, 1.0),
451 Family::Beta { a, b } => beta_cdf(a, b, x),
452 Family::Gamma { shape, scale } => gamma_cdf(shape, x / scale),
453 Family::Exponential { rate } => {
454 if x <= 0.0 {
455 0.0
456 } else {
457 -libm::expm1(-rate * x)
458 }
459 }
460 Family::Triangular { lo, mode, hi } => {
461 if x <= lo {
462 0.0
463 } else if x >= hi {
464 1.0
465 } else if x <= mode {
466 interval_fraction(lo, hi, x) * interval_fraction(lo, mode, x)
467 } else {
468 1.0 - (1.0 - interval_fraction(lo, hi, x)) * (1.0 - interval_fraction(mode, hi, x))
469 }
470 }
471 Family::Pert { lo, mode, hi } => {
472 let (a, b) = Family::pert_shape(lo, mode, hi);
473 beta_cdf(a, b, interval_fraction(lo, hi, x))
474 }
475 }
476 }
477
478 pub fn quantile(&self, p: f64) -> f64 {
480 let (lo, hi) = self.support();
481 if p <= 0.0 {
482 return lo;
483 }
484 if p >= 1.0 {
485 return hi;
486 }
487 match *self {
488 Family::Normal { mean, sd } => mean + sd * std_normal_quantile(p),
489 Family::Lognormal { mu, sigma } => crate::math::exp(mu + sigma * std_normal_quantile(p)),
490 Family::Uniform { lo, hi } => crate::stats::lerp(lo, hi, p),
491 Family::Exponential { rate } => -libm::log1p(-p) / rate,
492 Family::Triangular { lo, mode, hi } => {
493 let split = interval_fraction(lo, hi, mode);
494 if p <= split {
495 crate::stats::lerp(lo, hi, libm::sqrt(p * split))
496 } else {
497 crate::stats::lerp(hi, lo, libm::sqrt((1.0 - p) * (1.0 - split)))
498 }
499 }
500 Family::Beta { .. } | Family::Pert { .. } => invert(|x| self.cdf(x), p, lo, hi),
501 Family::Gamma { .. } => {
502 let mut top = self.mean() + 10.0 * libm::sqrt(self.variance());
503 while self.cdf(top) < p && top < f64::MAX / 4.0 {
504 top *= 2.0;
505 }
506 invert(|x| self.cdf(x), p, 0.0, top)
507 }
508 }
509 }
510
511 pub fn sample(&self, rng: &mut Rng) -> f64 {
513 match *self {
514 Family::Normal { mean, sd } => mean + sd * rng.normal(),
515 Family::Lognormal { mu, sigma } => crate::math::exp(mu + sigma * rng.normal()),
516 Family::Uniform { lo, hi } => crate::stats::lerp(lo, hi, rng.uniform()),
517 Family::Beta { a, b } => rng.beta(a, b),
518 Family::Gamma { shape, scale } => scale * rng.gamma(shape),
519 Family::Exponential { rate } => -libm::log(rng.open()) / rate,
520 Family::Triangular { .. } => self.quantile(rng.uniform()),
521 Family::Pert { lo, mode, hi } => {
522 let (a, b) = Family::pert_shape(lo, mode, hi);
523 crate::stats::lerp(lo, hi, rng.beta(a, b))
524 }
525 }
526 }
527
528 pub fn name(&self) -> &'static str {
529 match self {
530 Family::Normal { .. } => "normal",
531 Family::Lognormal { .. } => "lognormal",
532 Family::Uniform { .. } => "uniform",
533 Family::Beta { .. } => "beta",
534 Family::Gamma { .. } => "gamma",
535 Family::Exponential { .. } => "exponential",
536 Family::Triangular { .. } => "triangular",
537 Family::Pert { .. } => "pert",
538 }
539 }
540
541 pub fn params(&self) -> Vec<f64> {
543 match *self {
544 Family::Normal { mean, sd } => vec![mean, sd],
545 Family::Lognormal { mu, sigma } => vec![mu, sigma],
546 Family::Uniform { lo, hi } => vec![lo, hi],
547 Family::Beta { a, b } => vec![a, b],
548 Family::Gamma { shape, scale } => vec![shape, scale],
549 Family::Exponential { rate } => vec![rate],
550 Family::Triangular { lo, mode, hi } | Family::Pert { lo, mode, hi } => vec![lo, mode, hi],
551 }
552 }
553}
554
555impl fmt::Display for Family {
556 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557 let params: Vec<String> = self
558 .params()
559 .iter()
560 .map(|x| {
561 format!("{:.4}", x)
562 .trim_end_matches('0')
563 .trim_end_matches('.')
564 .to_string()
565 })
566 .collect();
567 write!(f, "{}({})", self.name(), params.join(", "))
568 }
569}
570
571fn std_normal_pdf(z: f64) -> f64 {
574 crate::math::exp(-z * z / 2.0) / libm::sqrt(2.0 * PI)
575}
576
577pub fn std_normal_cdf(z: f64) -> f64 {
578 0.5 * libm::erfc(-z / SQRT_2)
579}
580
581pub fn std_normal_quantile(p: f64) -> f64 {
584 if p == 0.5 {
585 return 0.0;
586 }
587 if p <= 0.0 {
588 return f64::NEG_INFINITY;
589 }
590 if p >= 1.0 {
591 return f64::INFINITY;
592 }
593 let tail = |q: f64| {
594 let t = libm::sqrt(-2.0 * libm::log(q));
595 t - (2.515517 + 0.802853 * t + 0.010328 * t * t)
596 / (1.0 + 1.432788 * t + 0.189269 * t * t + 0.001308 * t * t * t)
597 };
598 let mut z = if p < 0.5 { -tail(p) } else { tail(1.0 - p) };
599 for _ in 0..6 {
600 let density = std_normal_pdf(z);
601 if density == 0.0 {
602 break;
603 }
604 let step = (std_normal_cdf(z) - p) / density;
605 z -= step;
606 if step.abs() < 1e-15 * z.abs().max(1.0) {
607 break;
608 }
609 }
610 z
611}
612
613fn beta_pdf(a: f64, b: f64, x: f64) -> f64 {
614 if !(0.0..=1.0).contains(&x) {
615 return 0.0;
616 }
617 if x == 0.0 || x == 1.0 {
618 let edge = if x == 0.0 { a } else { b };
621 return if edge < 1.0 {
622 f64::INFINITY
623 } else if edge == 1.0 {
624 crate::math::exp(libm::lgamma(a + b) - libm::lgamma(a) - libm::lgamma(b))
625 } else {
626 0.0
627 };
628 }
629 crate::math::exp(
630 (a - 1.0) * libm::log(x) + (b - 1.0) * libm::log1p(-x) + libm::lgamma(a + b)
631 - libm::lgamma(a)
632 - libm::lgamma(b),
633 )
634}
635
636const LN_SQRT_2PI: f64 = 0.918_938_533_204_672_7;
638
639pub fn ln_beta(a: f64, b: f64) -> f64 {
643 let (p, q) = if a < b { (a, b) } else { (b, a) };
644 let share = p / (p + q);
645 if p >= 10.0 {
646 let rest = stirling_rest(p) + stirling_rest(q) - stirling_rest(p + q);
647 -0.5 * libm::log(q) + LN_SQRT_2PI + rest + (p - 0.5) * libm::log(share) + q * libm::log1p(-share)
648 } else if q >= 10.0 {
649 let rest = stirling_rest(q) - stirling_rest(p + q);
650 libm::lgamma(p) + rest + p - p * libm::log(p + q) + (q - 0.5) * libm::log1p(-share)
651 } else {
652 libm::lgamma(p) + libm::lgamma(q) - libm::lgamma(p + q)
653 }
654}
655
656fn stirling_rest(x: f64) -> f64 {
660 let r = 1.0 / (x * x);
661 let series = 1.0 / 12.0
662 + r * (-1.0 / 360.0
663 + r * (1.0 / 1260.0 + r * (-1.0 / 1680.0 + r * (1.0 / 1188.0 + r * (-691.0 / 360_360.0 + r / 156.0)))));
664 series / x
665}
666
667pub fn beta_cdf(a: f64, b: f64, x: f64) -> f64 {
670 if x <= 0.0 {
671 return 0.0;
672 }
673 if x >= 1.0 {
674 return 1.0;
675 }
676 let front = crate::math::exp(
677 libm::lgamma(a + b) - libm::lgamma(a) - libm::lgamma(b) + a * libm::log(x) + b * libm::log1p(-x),
678 );
679 if x < (a + 1.0) / (a + b + 2.0) {
680 front * beta_fraction(a, b, x) / a
681 } else {
682 1.0 - front * beta_fraction(b, a, 1.0 - x) / b
683 }
684}
685
686fn beta_fraction(a: f64, b: f64, x: f64) -> f64 {
687 const TINY: f64 = 1e-300;
688 let (qab, qap, qam) = (a + b, a + 1.0, a - 1.0);
689 let mut c = 1.0;
690 let mut d = 1.0 - qab * x / qap;
691 if d.abs() < TINY {
692 d = TINY;
693 }
694 d = 1.0 / d;
695 let mut h = d;
696 for m in 1..=1000 {
697 let m = m as f64;
698 let m2 = 2.0 * m;
699 let aa = m * (b - m) * x / ((qam + m2) * (a + m2));
700 d = 1.0 + aa * d;
701 if d.abs() < TINY {
702 d = TINY;
703 }
704 c = 1.0 + aa / c;
705 if c.abs() < TINY {
706 c = TINY;
707 }
708 d = 1.0 / d;
709 h *= d * c;
710 let aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
711 d = 1.0 + aa * d;
712 if d.abs() < TINY {
713 d = TINY;
714 }
715 c = 1.0 + aa / c;
716 if c.abs() < TINY {
717 c = TINY;
718 }
719 d = 1.0 / d;
720 let delta = d * c;
721 h *= delta;
722 if (delta - 1.0).abs() < 1e-16 {
723 break;
724 }
725 }
726 h
727}
728
729pub fn gamma_cdf(a: f64, x: f64) -> f64 {
732 if x == f64::INFINITY {
733 return 1.0;
734 }
735 if x <= 0.0 {
736 return 0.0;
737 }
738 let front = crate::math::exp(-x + a * libm::log(x) - libm::lgamma(a));
739 if x < a + 1.0 {
740 let (mut sum, mut term, mut n) = (1.0 / a, 1.0 / a, a);
741 for _ in 0..10_000 {
742 n += 1.0;
743 term *= x / n;
744 sum += term;
745 if term.abs() < sum.abs() * 1e-17 {
746 break;
747 }
748 }
749 (sum * front).min(1.0)
750 } else {
751 const TINY: f64 = 1e-300;
752 let mut b = x + 1.0 - a;
753 let mut c = 1.0 / TINY;
754 let mut d = 1.0 / b;
755 let mut h = d;
756 for i in 1..10_000 {
757 let i = i as f64;
758 let an = -i * (i - a);
759 b += 2.0;
760 d = an * d + b;
761 if d.abs() < TINY {
762 d = TINY;
763 }
764 c = b + an / c;
765 if c.abs() < TINY {
766 c = TINY;
767 }
768 d = 1.0 / d;
769 let delta = d * c;
770 h *= delta;
771 if (delta - 1.0).abs() < 1e-16 {
772 break;
773 }
774 }
775 (1.0 - front * h).max(0.0)
776 }
777}
778
779pub fn invert(f: impl Fn(f64) -> f64, p: f64, mut lo: f64, mut hi: f64) -> f64 {
782 if !lo.is_finite() || !hi.is_finite() {
783 return f64::NAN;
784 }
785 for _ in 0..300 {
786 let mid = crate::stats::midpoint(lo, hi);
787 if mid <= lo || mid >= hi {
788 break;
789 }
790 let cumulative = f(mid);
791 if !cumulative.is_finite() {
792 return f64::NAN;
793 }
794 if cumulative >= p {
795 hi = mid;
796 } else {
797 lo = mid;
798 }
799 }
800 hi
801}
802
803#[derive(Clone, Debug)]
807pub struct Rng {
808 s: [u64; 4],
809}
810
811const GOLDEN: u64 = 0x9E37_79B9_7F4A_7C15;
813
814pub(crate) fn mix(z: u64) -> u64 {
815 let z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
816 let z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
817 z ^ (z >> 31)
818}
819
820impl Rng {
821 pub fn new(seed: u64) -> Rng {
822 let mut z = seed;
823 let mut next = || {
824 z = z.wrapping_add(GOLDEN);
825 mix(z)
826 };
827 Rng {
828 s: [next(), next(), next(), next()],
829 }
830 }
831
832 pub fn stream(seed: u64, index: u64) -> Rng {
837 Rng::new(mix(seed.wrapping_add(index.wrapping_add(1).wrapping_mul(GOLDEN))))
840 }
841
842 pub fn next_u64(&mut self) -> u64 {
843 let s = &mut self.s;
844 let result = s[0].wrapping_add(s[3]).rotate_left(23).wrapping_add(s[0]);
845 let t = s[1] << 17;
846 s[2] ^= s[0];
847 s[3] ^= s[1];
848 s[1] ^= s[2];
849 s[0] ^= s[3];
850 s[2] ^= t;
851 s[3] = s[3].rotate_left(45);
852 result
853 }
854
855 pub fn uniform(&mut self) -> f64 {
857 (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
858 }
859
860 pub fn open(&mut self) -> f64 {
862 1.0 - self.uniform()
863 }
864
865 pub fn normal(&mut self) -> f64 {
867 let (u, v) = (self.open(), self.uniform());
868 libm::sqrt(-2.0 * libm::log(u)) * libm::cos(2.0 * PI * v)
869 }
870
871 pub fn gamma(&mut self, shape: f64) -> f64 {
873 if shape < 1.0 {
874 let u = self.open();
875 return self.gamma(shape + 1.0) * libm::pow(u, 1.0 / shape);
876 }
877 let d = shape - 1.0 / 3.0;
878 let c = 1.0 / libm::sqrt(9.0 * d);
879 loop {
880 let x = self.normal();
881 let v = 1.0 + c * x;
882 if v <= 0.0 {
883 continue;
884 }
885 let v = v * v * v;
886 let u = self.open();
887 if u < 1.0 - 0.0331 * x.powi(4) || libm::log(u) < 0.5 * x * x + d * (1.0 - v + libm::log(v)) {
888 return d * v;
889 }
890 }
891 }
892
893 pub fn beta(&mut self, a: f64, b: f64) -> f64 {
894 for _ in 0..16 {
895 let x = self.gamma(a);
896 let y = self.gamma(b);
897 if x + y > 0.0 {
898 return if x == 0.0 {
899 0.0
900 } else if y == 0.0 {
901 1.0
902 } else {
903 ratio(x, y)
904 };
905 }
906 }
907 if self.uniform() < ratio(a, b) { 1.0 } else { 0.0 }
910 }
911
912 pub fn choose(&mut self, weights: impl Iterator<Item = f64> + Clone) -> Option<usize> {
915 let total: f64 = weights.clone().sum();
916 if total <= 0.0 {
917 return None;
918 }
919 let target = self.uniform() * total;
920 let mut acc = 0.0;
921 let mut last = None;
922 for (i, w) in weights.enumerate() {
923 if w <= 0.0 {
924 continue;
925 }
926 acc += w;
927 last = Some(i);
928 if target < acc {
929 return Some(i);
930 }
931 }
932 last
933 }
934}
935
936#[derive(Clone, Debug)]
940pub enum Part {
941 Point(f64),
942 Continuous(Family),
943 Analytic(crate::analytic::Analytic),
944}
945
946#[derive(Clone, Debug)]
949pub struct Mixture {
950 pub parts: Vec<(Part, f64)>,
951}
952
953impl Mixture {
954 fn total(&self) -> f64 {
955 crate::stats::sum(self.parts.iter().map(|(_, p)| *p))
956 }
957
958 pub fn mean(&self) -> f64 {
959 crate::stats::weighted_mean(self.parts.iter().map(|(part, p)| {
960 (
961 match part {
962 Part::Point(x) => *x,
963 Part::Continuous(f) => f.mean(),
964 Part::Analytic(a) => a.moments().map_or(f64::NAN, |m| m.0),
966 },
967 *p,
968 )
969 }))
970 }
971
972 pub fn variance(&self) -> f64 {
973 let sd = self.sd();
974 sd * sd
975 }
976
977 pub fn sd(&self) -> f64 {
978 crate::stats::weighted_sd(self.parts.iter().map(|(part, p)| {
979 let (m, sd) = match part {
980 Part::Point(x) => (*x, 0.0),
981 Part::Continuous(f) => (f.mean(), f.sd()),
982 Part::Analytic(a) => (a.moments().map_or(f64::NAN, |m| m.0), a.sd().unwrap_or(f64::NAN)),
983 };
984 (m, sd, *p)
985 }))
986 }
987
988 pub fn cdf(&self, x: f64) -> f64 {
989 let below = crate::stats::sum(self.parts.iter().map(|(part, p)| {
990 p * match part {
991 Part::Point(v) => {
992 if *v <= x {
993 1.0
994 } else {
995 0.0
996 }
997 }
998 Part::Continuous(f) => f.cdf(x),
999 Part::Analytic(a) => a.cdf(x),
1000 }
1001 }));
1002 below / self.total()
1003 }
1004
1005 pub fn median_bounds(&self) -> (f64, f64) {
1009 let mut intervals = Vec::new();
1010 for (part, weight) in &self.parts {
1011 if *weight <= 0.0 {
1012 continue;
1013 }
1014 match part {
1015 Part::Point(x) => intervals.push((*x, *x, *weight)),
1016 Part::Continuous(f) => {
1017 let (lo, hi) = f.support();
1018 intervals.push((lo, hi, *weight));
1019 }
1020 Part::Analytic(a) => {
1021 let total = a.measure(&a.domain);
1022 for (lo, hi, f) in a.segments() {
1023 let (x, y) = f.ends(&a.family, lo, hi);
1024 let share = a.measure(&crate::analytic::Domain(vec![(lo, hi)])) / total;
1025 intervals.push((x.min(y), x.max(y), weight * share));
1026 }
1027 }
1028 }
1029 }
1030 intervals.sort_by(|a, b| a.0.total_cmp(&b.0));
1031 let total = crate::stats::sum(intervals.iter().map(|x| x.2));
1032 let mut acc = crate::stats::Sum::default();
1033 let mut end = f64::NEG_INFINITY;
1034 for (lo, hi, weight) in intervals {
1035 if lo > end && acc.value() > 0.0 && crate::stats::half_split(acc.value(), total) {
1036 return (end, lo);
1037 }
1038 end = end.max(hi);
1039 acc.add(weight);
1040 }
1041 let x = self.quantile(0.5);
1042 (x, x)
1043 }
1044
1045 pub fn median(&self) -> f64 {
1046 let (lo, hi) = self.median_bounds();
1047 crate::stats::midpoint(lo, hi)
1048 }
1049
1050 pub fn quantile(&self, q: f64) -> f64 {
1051 let ends = |q: f64| {
1052 self.parts.iter().map(move |(part, _)| match part {
1053 Part::Point(x) => *x,
1054 Part::Continuous(f) => f.quantile(q),
1055 Part::Analytic(a) => a.quantile(q),
1056 })
1057 };
1058 if self.parts.len() == 1 {
1059 return match &self.parts[0].0 {
1060 Part::Point(x) => *x,
1061 Part::Continuous(f) => f.quantile(q),
1062 Part::Analytic(a) => a.quantile(q),
1063 };
1064 }
1065 if q <= 0.0 {
1066 return ends(0.0).fold(f64::INFINITY, f64::min);
1067 }
1068 if q >= 1.0 {
1069 return ends(1.0).fold(f64::NEG_INFINITY, f64::max);
1070 }
1071 let lo = ends(q.min(1e-12)).fold(f64::INFINITY, f64::min).max(-f64::MAX);
1072 let hi = ends(q.max(1.0 - 1e-12)).fold(f64::NEG_INFINITY, f64::max).min(f64::MAX);
1073 if lo >= hi {
1074 return lo;
1075 }
1076 if self.cdf(hi) < q {
1077 return f64::INFINITY;
1078 }
1079 invert(|x| self.cdf(x), q, (lo - 1e-9 * lo.abs().max(1.0)).max(-f64::MAX), hi)
1080 }
1081}
1082
1083#[cfg(test)]
1084mod tests {
1085 use super::*;
1086
1087 fn close(a: f64, b: f64, tol: f64) {
1088 assert!((a - b).abs() <= tol * (1.0 + b.abs()), "{a} vs {b}");
1089 }
1090
1091 #[test]
1092 fn normal_quantiles() {
1093 close(std_normal_cdf(Z95), 0.95, 1e-15);
1094 close(std_normal_quantile(0.95), Z95, 1e-14);
1095 close(std_normal_quantile(0.5), 0.0, 1e-15);
1096 close(std_normal_quantile(0.025), -1.9599639845400545, 1e-13);
1097 close(std_normal_quantile(1e-10), -6.361340902404056, 1e-10);
1098 close(std_normal_cdf(-1.959963984540054), 0.025, 1e-14);
1099 }
1100
1101 #[test]
1102 fn estimates_have_the_right_intervals() {
1103 let e = Family::estimate(3.0, 7.0).unwrap();
1104 close(e.quantile(0.05), 3.0, 1e-12);
1105 close(e.quantile(0.95), 7.0, 1e-12);
1106 let r = Family::normal_range(-0.08, 0.0).unwrap();
1107 close(r.quantile(0.05), -0.08, 1e-12);
1108 close(r.cdf(0.0), 0.95, 1e-12);
1109 assert!(Family::estimate(-1.0, 3.0).is_err());
1110 assert!(Family::estimate(3.0, 1.0).is_err());
1111 }
1112
1113 #[test]
1114 fn incomplete_functions() {
1115 close(beta_cdf(2.0, 3.0, 0.5), 11.0 / 16.0, 1e-14);
1117 close(beta_cdf(1.0, 1.0, 0.3), 0.3, 1e-14);
1118 close(gamma_cdf(1.0, 2.0), 1.0 - (-2.0f64).exp(), 1e-14);
1119 close(gamma_cdf(0.5, 3.0), libm::erf(3.0f64.sqrt()), 1e-13);
1120 close(gamma_cdf(10.0, 30.0), 0.9999928782491372, 1e-13);
1121 let b = Family::beta(2.0, 40.0).unwrap();
1122 close(b.quantile(b.cdf(0.05)), 0.05, 1e-10);
1123 let g = Family::gamma(3.0, 2.0).unwrap();
1124 close(g.quantile(0.5), 5.348120627447122, 1e-10);
1125 }
1126
1127 #[test]
1128 fn densities_integrate_to_their_cdfs() {
1129 let families = [
1130 Family::normal(1.0, 2.0).unwrap(),
1131 Family::lognormal(0.5, 0.3).unwrap(),
1132 Family::uniform(-1.0, 3.0).unwrap(),
1133 Family::beta(2.5, 4.0).unwrap(),
1134 Family::gamma(2.0, 1.5).unwrap(),
1135 Family::exponential(0.7).unwrap(),
1136 Family::triangular(0.0, 1.0, 4.0).unwrap(),
1137 Family::pert(1.0, 2.0, 6.0).unwrap(),
1138 ];
1139 for f in families {
1140 let (a, b) = (f.quantile(0.1), f.quantile(0.8));
1141 let n = 20_000;
1142 let h = (b - a) / n as f64;
1143 let integral: f64 = (0..n).map(|i| f.pdf(a + (i as f64 + 0.5) * h) * h).sum();
1144 close(integral, 0.7, 1e-6);
1145 close(f.cdf(b) - f.cdf(a), 0.7, 1e-9);
1146 }
1147 }
1148
1149 #[test]
1150 fn log_densities_agree_and_reach_far_tails() {
1151 let families = [
1152 Family::normal(1.0, 2.0).unwrap(),
1153 Family::lognormal(0.5, 0.3).unwrap(),
1154 Family::uniform(-1.0, 3.0).unwrap(),
1155 Family::beta(2.5, 4.0).unwrap(),
1156 Family::gamma(2.0, 1.5).unwrap(),
1157 Family::exponential(0.7).unwrap(),
1158 Family::triangular(0.0, 1.0, 4.0).unwrap(),
1159 Family::pert(1.0, 2.0, 6.0).unwrap(),
1160 ];
1161 for f in families {
1162 for q in [0.01, 0.3, 0.5, 0.9, 0.999] {
1163 let x = f.quantile(q);
1164 close(f.ln_pdf(x), libm::log(f.pdf(x)), 1e-12);
1165 }
1166 if f.support().0 > -10.0 {
1167 assert_eq!(f.ln_pdf(-10.0), f64::NEG_INFINITY, "{f}");
1168 }
1169 }
1170 let n = Family::normal(0.0, 1.0).unwrap();
1172 assert_eq!(n.pdf(50.0), 0.0);
1173 close(n.ln_pdf(50.0), -1250.0 - LN_SQRT_2PI, 1e-15);
1174 let b = Family::beta(2000.0, 2000.0).unwrap();
1175 assert!(b.ln_pdf(0.01).is_finite());
1176 }
1177
1178 #[test]
1180 fn draws_follow_the_distributions() {
1181 let families = [
1182 Family::normal(1.0, 2.0).unwrap(),
1183 Family::lognormal(0.5, 0.3).unwrap(),
1184 Family::uniform(-1.0, 3.0).unwrap(),
1185 Family::beta(2.0, 40.0).unwrap(),
1186 Family::beta(0.5, 0.5).unwrap(),
1187 Family::gamma(0.4, 1.5).unwrap(),
1188 Family::gamma(7.0, 0.5).unwrap(),
1189 Family::exponential(0.7).unwrap(),
1190 Family::triangular(0.0, 1.0, 4.0).unwrap(),
1191 Family::pert(1.0, 2.0, 6.0).unwrap(),
1192 Family::estimate(60.0, 150.0).unwrap(),
1193 ];
1194 let mut rng = Rng::new(42);
1195 let n = 20_000;
1196 for f in families {
1197 let mut xs: Vec<f64> = (0..n).map(|_| f.sample(&mut rng)).collect();
1198 xs.sort_by(f64::total_cmp);
1199 let d = xs
1200 .iter()
1201 .enumerate()
1202 .map(|(i, x)| {
1203 let c = f.cdf(*x);
1204 (c - i as f64 / n as f64)
1205 .abs()
1206 .max((c - (i + 1) as f64 / n as f64).abs())
1207 })
1208 .fold(0.0, f64::max);
1209 assert!(d < 1.95 / (n as f64).sqrt(), "{f}: D = {d}");
1211 let mean = xs.iter().sum::<f64>() / n as f64;
1212 close(mean, f.mean(), 6.0 * f.variance().sqrt() / (n as f64).sqrt());
1213 }
1214 }
1215
1216 #[test]
1217 fn batches_have_streams_of_their_own() {
1218 let firsts: Vec<u64> = (0..1000).map(|i| Rng::stream(11, i).next_u64()).collect();
1219 let mut distinct = firsts.clone();
1220 distinct.sort();
1221 distinct.dedup();
1222 assert_eq!(distinct.len(), firsts.len());
1223 assert_eq!(Rng::stream(11, 3).next_u64(), Rng::stream(11, 3).next_u64());
1224 assert_ne!(Rng::stream(11, 3).next_u64(), Rng::stream(12, 3).next_u64());
1225 }
1226
1227 #[test]
1228 fn seeds_repeat() {
1229 let (mut a, mut b) = (Rng::new(7), Rng::new(7));
1230 for _ in 0..100 {
1231 assert_eq!(a.next_u64(), b.next_u64());
1232 }
1233 assert_ne!(Rng::new(7).next_u64(), Rng::new(8).next_u64());
1234 }
1235
1236 #[test]
1237 fn mixtures() {
1238 let m = Mixture {
1239 parts: vec![
1240 (Part::Point(0.0), 0.5),
1241 (Part::Continuous(Family::uniform(1.0, 3.0).unwrap()), 0.5),
1242 ],
1243 };
1244 close(m.mean(), 1.0, 1e-12);
1245 close(m.cdf(2.0), 0.75, 1e-12);
1246 close(m.quantile(0.75), 2.0, 1e-9);
1247 close(m.quantile(0.25), 0.0, 1e-9);
1248 }
1249}