Skip to main content

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}