prospicio_prob/distribution.rs
1//! The trait shared by every distribution representation.
2
3use prospicio_core::{Result, StreamRng};
4
5/// A univariate loss distribution.
6///
7/// Only operations that are exact for every representation live here; see
8/// `docs/design/distributions.md` for the representation-specific traits
9/// that will join it.
10pub trait Distribution {
11 /// Expected value.
12 fn mean(&self) -> f64;
13
14 /// Variance.
15 fn variance(&self) -> f64;
16
17 /// Standard deviation.
18 fn std_dev(&self) -> f64 {
19 self.variance().sqrt()
20 }
21
22 /// `P(X <= x)`.
23 fn cdf(&self, x: f64) -> f64;
24
25 /// `P(X > x)`. Representations with a direct form override the
26 /// default `1 - cdf(x)`, which loses all precision far in the tail.
27 fn survival(&self, x: f64) -> f64 {
28 1.0 - self.cdf(x)
29 }
30
31 /// Smallest `x` with `cdf(x) >= p`.
32 ///
33 /// Fails with [`prospicio_core::Error::InvalidProbability`] unless `p` is in
34 /// `[0, 1]`.
35 fn quantile(&self, p: f64) -> Result<f64>;
36
37 /// `n` draws from stream `rng`, by inverse transform unless the family
38 /// overrides it ([`crate::Gamma`] draws by Marsaglia and Tsang).
39 ///
40 /// Inverse transform keeps draws a pure function of `(seed, stream)` and
41 /// preserves ordering under common random numbers. An override keeps
42 /// the first but not the second; code that needs draws monotone in a
43 /// uniform calls `quantile` itself.
44 fn sample(&self, rng: &mut StreamRng, n: usize) -> Vec<f64> {
45 (0..n)
46 .map(|_| {
47 self.quantile(rng.next_open01())
48 .expect("next_open01 is always in (0, 1)")
49 })
50 .collect()
51 }
52
53 /// Whether the distribution may be evaluated on several threads at
54 /// once. True for every native family; false for a [`crate::Custom`]
55 /// whose callbacks must stay on the calling thread (an R function), so
56 /// the parallel simulations run single-threaded when they meet one.
57 fn is_parallel_safe(&self) -> bool {
58 true
59 }
60}
61
62/// A boxed distribution (for example `Box<dyn Distribution>`) is one too,
63/// so trait objects fit generic models.
64impl<T: Distribution + ?Sized> Distribution for Box<T> {
65 fn mean(&self) -> f64 {
66 (**self).mean()
67 }
68
69 fn variance(&self) -> f64 {
70 (**self).variance()
71 }
72
73 fn std_dev(&self) -> f64 {
74 (**self).std_dev()
75 }
76
77 fn cdf(&self, x: f64) -> f64 {
78 (**self).cdf(x)
79 }
80
81 fn survival(&self, x: f64) -> f64 {
82 (**self).survival(x)
83 }
84
85 fn quantile(&self, p: f64) -> Result<f64> {
86 (**self).quantile(p)
87 }
88
89 fn sample(&self, rng: &mut StreamRng, n: usize) -> Vec<f64> {
90 (**self).sample(rng, n)
91 }
92
93 fn is_parallel_safe(&self) -> bool {
94 (**self).is_parallel_safe()
95 }
96}
97
98/// Checks that `p` is a probability.
99pub(crate) fn check_probability(p: f64) -> Result<()> {
100 if (0.0..=1.0).contains(&p) {
101 Ok(())
102 } else {
103 Err(prospicio_core::Error::InvalidProbability(p))
104 }
105}