1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! Convinience types for calculating probabilities of independent events.
//!
//! This crate provides a simple type which represents a probability of an isolated event
//! happening.
//!
//! It intergrates nicely with the Rust syntax by overloading various operations.
//!
//! # Example
//!
//! ```rust
//! use prob::Probability;
//!
//! let p1 = Probability(0.5);
//! let p2 = Probability(0.5);
//!
//! let Probability(and) = p1 & p2;
//! let Probability(or)  = p1 | p2;
//! let Probability(xor) = p1 ^ p2;
//!
//! assert_eq!(or,  0.75);
//! assert_eq!(and, 0.25);
//! assert_eq!(xor, 0.5);
//! ```

#![warn(missing_docs)]

extern crate num;

use num::Num;
use std::ops;

/// An independent probability.
///
/// This represents some probability of some abstract, isolated event occuring. Note that the even
/// have to be isolated (independent of any other event) for the operations to be correct. As such,
/// you shouldn't do calculations with conditional events. Stronger methods needs to be used for
/// this.
///
/// This newtype simply wraps some numeral type and provides it with operations:
///
/// - `&` for **independent and**.
/// - `|` for **independent or**.
/// - `^` for **independent mutual exclusivity**.
/// - `!` for **inverse probability**.
#[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord)]
pub struct Probability<T: Num>(pub T);

impl<T: Num + PartialOrd> Probability<T> {
    /// The probability representing a "almost certain" condition.
    ///
    /// "Almost certain" (a.c.) should not be equated with "certain", because something can be
    /// practically certain, but not impossible to fail. For example, pick a random natural number
    /// from an uniform distribution. Not picking a specific number would be almost certain, but
    /// it is not impossible, because an argument for impossibility could be applied to any number.
    ///
    /// For practical purposes, though, there is no difference between certain and almost certain.
    pub fn certain() -> Probability<T> {
        Probability(T::one())
    }

    /// The inverse of an "almost certain" condition (almost never).
    pub fn never() -> Probability<T> {
        Probability(T::zero())
    }

    /// Half the probability.
    pub fn half(self) -> Probability<T> {
        Probability(self.check().0 / (T::one() + T::one())).check()
    }

    /// A fifty-fifty probability (0.5).
    pub fn fifty() -> Probability<T> {
        Probability::certain().half()
    }

    /// 'or' for mutually exclusive events
    pub fn disjointed_or(self, rhs: Probability<T>) -> Probability<T> {
        Probability(self.check().0 + rhs.check().0).check()
    }

    /// Check the probability for overflows or degenerate values.
    fn check(self) -> Probability<T> {
        debug_assert!(self.0 >= Probability::never().0, "Negative probability.");
        debug_assert!(self.0 <= Probability::certain().0, "Probability overflow (>1).");

        self
    }
}

impl<T: Num + PartialOrd> ops::Not for Probability<T> {
    type Output = Probability<T>;

    fn not(self) -> Probability<T> {
        Probability(T::one() - self.check().0).check()
    }
}

impl<T: Num + PartialOrd> ops::BitAnd for Probability<T> {
    type Output = Probability<T>;

    fn bitand(self, rhs: Probability<T>) -> Probability<T> {
        Probability(self.check().0 * rhs.check().0).check()
    }
}

impl<T: Num + Clone + PartialOrd> ops::BitOr for Probability<T> {
    type Output = Probability<T>;

    fn bitor(self, rhs: Probability<T>) -> Probability<T> {
        Probability(self.clone().check().0 + rhs.clone().check().0.clone() - self.0 * rhs.0).check()
    }
}

impl<T: Num + Clone + PartialOrd> ops::BitXor for Probability<T> {
    type Output = Probability<T>;

    fn bitxor(self, rhs: Probability<T>) -> Probability<T> {
        (self.clone().check() & !rhs.clone().check()).disjointed_or(rhs & !self).check()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    type F32Probability = Probability<f32>;

    #[test]
    fn and() {
        assert_eq!(F32Probability::fifty() & F32Probability::fifty(), F32Probability::fifty().half());
        assert_eq!(F32Probability::certain() & F32Probability::certain(), F32Probability::certain());
        assert_eq!(F32Probability::never() & F32Probability::certain(), F32Probability::never());
        assert_eq!(F32Probability::never() & F32Probability::never(), F32Probability::never());
        assert_eq!(F32Probability::fifty() & F32Probability::fifty(), Probability::fifty().half());
    }

    #[test]
    fn or() {
        assert_eq!(F32Probability::fifty() | F32Probability::never(), F32Probability::fifty());
        assert_eq!(F32Probability::fifty() | F32Probability::never(), F32Probability::fifty());
        assert_eq!(F32Probability::never() | F32Probability::never(), F32Probability::never());
        assert_eq!(F32Probability::certain() | F32Probability::certain(), F32Probability::certain());
        assert_eq!(Probability::fifty() | Probability::fifty(), Probability(0.75));
    }

    #[test]
    fn xor() {
        assert_eq!(F32Probability::fifty() ^ F32Probability::never(), F32Probability::fifty());
        assert_eq!(F32Probability::fifty() ^ F32Probability::never(), F32Probability::fifty());
        assert_eq!(F32Probability::never() ^ F32Probability::never(), F32Probability::never());
        assert_eq!(F32Probability::certain() ^ F32Probability::certain(), F32Probability::never());
        assert_eq!(F32Probability::certain() ^ F32Probability::never(), F32Probability::certain());
        assert_eq!(F32Probability::fifty() ^ F32Probability::fifty(), F32Probability::fifty());
    }

    #[test]
    fn not() {
        assert_eq!(!F32Probability::fifty(), F32Probability::fifty());
        assert_eq!(!F32Probability::fifty(), F32Probability::fifty());
        assert_eq!(!Probability(0.75), Probability::fifty().half());
    }
}