Skip to main content

kriging_rs/
utils.rs

1use crate::Real;
2use crate::error::KrigingError;
3
4const PROB_EPSILON: Real = 1e-9;
5
6/// A probability in (0, 1), enforced at construction. Use for `logit` without clamping.
7#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct Probability(Real);
9
10impl Probability {
11    /// Creates a probability. Fails with [`KrigingError::InvalidInput`] if `p` is not finite
12    /// or not strictly in `(0, 1)`.
13    pub fn try_new(p: Real) -> Result<Self, KrigingError> {
14        if !p.is_finite() || p <= 0.0 || p >= 1.0 {
15            return Err(KrigingError::InvalidInput(format!(
16                "probability must be finite and in (0, 1), got {p}"
17            )));
18        }
19        Ok(Self(p))
20    }
21
22    /// For callers that have already ensured the value is in (0, 1) (e.g. from smoothed probability).
23    #[inline]
24    pub fn from_known_in_range(p: Real) -> Self {
25        debug_assert!(
26            p.is_finite() && p > 0.0 && p < 1.0,
27            "probability must be in (0, 1)"
28        );
29        Self(p)
30    }
31
32    #[inline]
33    pub fn get(self) -> Real {
34        self.0
35    }
36}
37
38pub fn clamp_probability(p: Real) -> Real {
39    p.clamp(PROB_EPSILON, 1.0 - PROB_EPSILON)
40}
41
42/// Logit of a validated probability. No clamping; use `Probability::try_new` at the boundary.
43#[inline]
44pub fn logit(p: Probability) -> Real {
45    let p = p.get();
46    (p / (1.0 - p)).ln()
47}
48
49/// Logit of a raw value, with clamping. Prefer building a `Probability` and using `logit(Probability)` when the value is already in (0, 1).
50pub fn logit_clamped(p: Real) -> Real {
51    logit(Probability::from_known_in_range(clamp_probability(p)))
52}
53
54pub fn logistic(x: Real) -> Real {
55    1.0 / (1.0 + (-x).exp())
56}