Skip to main content

uncertain_numerics/
posterior.rs

1use crate::PosteriorError;
2
3/// Gaussian posterior for a scalar computational quantity.
4///
5/// This type is intentionally generic: Bayesian quadrature can use it for an
6/// integral, while later probabilistic numerical methods can use the same
7/// representation for other scalar quantities.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct ScalarNormalPosterior {
10    mean: f64,
11    variance: f64,
12}
13
14impl ScalarNormalPosterior {
15    /// Construct a validated scalar Gaussian posterior.
16    ///
17    /// # Errors
18    ///
19    /// Returns [`PosteriorError`] when the mean or variance is non-finite, or
20    /// when the variance is negative.
21    pub fn new(mean: f64, variance: f64) -> Result<Self, PosteriorError> {
22        if !mean.is_finite() {
23            return Err(PosteriorError::NonFiniteMean);
24        }
25        if !variance.is_finite() {
26            return Err(PosteriorError::NonFiniteVariance);
27        }
28        if variance < 0.0 {
29            return Err(PosteriorError::NegativeVariance);
30        }
31
32        Ok(Self { mean, variance })
33    }
34
35    /// Posterior mean of the computational quantity.
36    #[must_use]
37    pub const fn mean(self) -> f64 {
38        self.mean
39    }
40
41    /// Posterior variance of the computational quantity.
42    #[must_use]
43    pub const fn variance(self) -> f64 {
44        self.variance
45    }
46
47    /// Posterior standard deviation of the computational quantity.
48    #[must_use]
49    pub fn standard_deviation(self) -> f64 {
50        self.variance.sqrt()
51    }
52}
53
54#[cfg(test)]
55#[allow(clippy::float_cmp)] // exact round-trips of constructor inputs are intended
56mod tests {
57    use super::*;
58
59    #[test]
60    fn constructs_valid_posterior() {
61        let posterior = ScalarNormalPosterior::new(1.25, 0.09).expect("valid posterior");
62
63        assert_eq!(posterior.mean(), 1.25);
64        assert_eq!(posterior.variance(), 0.09);
65        assert!((posterior.standard_deviation() - 0.3).abs() < 1.0e-12);
66    }
67
68    #[test]
69    fn accepts_zero_variance() {
70        let posterior = ScalarNormalPosterior::new(2.0, 0.0).expect("zero variance is valid");
71
72        assert_eq!(posterior.standard_deviation(), 0.0);
73    }
74
75    #[test]
76    fn rejects_negative_variance() {
77        assert_eq!(
78            ScalarNormalPosterior::new(0.0, -1.0),
79            Err(PosteriorError::NegativeVariance)
80        );
81    }
82
83    #[test]
84    fn rejects_non_finite_parameters() {
85        assert_eq!(
86            ScalarNormalPosterior::new(f64::NAN, 1.0),
87            Err(PosteriorError::NonFiniteMean)
88        );
89        assert_eq!(
90            ScalarNormalPosterior::new(0.0, f64::INFINITY),
91            Err(PosteriorError::NonFiniteVariance)
92        );
93    }
94}