Skip to main content

meta_language/
semantics.rs

1/// Many-valued semantic truth value.
2#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3pub enum TruthValue {
4    True,
5    False,
6    Unknown,
7    Both,
8}
9
10impl TruthValue {
11    /// Logical conjunction.
12    #[must_use]
13    pub const fn and(self, other: Self) -> Self {
14        match (self, other) {
15            (Self::False, _) | (_, Self::False) => Self::False,
16            (Self::True, value) | (value, Self::True) => value,
17            (Self::Both, _) | (_, Self::Both) => Self::Both,
18            (Self::Unknown, Self::Unknown) => Self::Unknown,
19        }
20    }
21
22    /// Logical disjunction.
23    #[must_use]
24    pub const fn or(self, other: Self) -> Self {
25        match (self, other) {
26            (Self::True, _) | (_, Self::True) => Self::True,
27            (Self::False, value) | (value, Self::False) => value,
28            (Self::Both, _) | (_, Self::Both) => Self::Both,
29            (Self::Unknown, Self::Unknown) => Self::Unknown,
30        }
31    }
32
33    /// Logical negation.
34    #[must_use]
35    pub const fn negate(self) -> Self {
36        match self {
37            Self::True => Self::False,
38            Self::False => Self::True,
39            Self::Unknown => Self::Unknown,
40            Self::Both => Self::Both,
41        }
42    }
43}
44
45const PROBABILITY_SCALE_BASIS_POINTS: u16 = 10_000;
46const PROBABILITY_SCALE_U32: u32 = 10_000;
47const PROBABILITY_SCALE_U128: u128 = 10_000;
48
49/// Fixed-point probability stored as basis points from `0` to `10_000`.
50#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
51pub struct Probability {
52    basis_points: u16,
53}
54
55impl Probability {
56    /// Probability `0.0`.
57    pub const ZERO: Self = Self { basis_points: 0 };
58
59    /// Probability `1.0`.
60    pub const ONE: Self = Self {
61        basis_points: PROBABILITY_SCALE_BASIS_POINTS,
62    };
63
64    /// Creates a probability from basis points, where `10_000` means certainty.
65    #[must_use]
66    pub const fn from_basis_points(basis_points: u16) -> Option<Self> {
67        if basis_points <= PROBABILITY_SCALE_BASIS_POINTS {
68            Some(Self { basis_points })
69        } else {
70            None
71        }
72    }
73
74    /// Creates a probability from a ratio, rounded to the nearest basis point.
75    #[must_use]
76    pub fn from_ratio(numerator: u64, denominator: u64) -> Option<Self> {
77        if denominator == 0 || numerator > denominator {
78            return None;
79        }
80
81        let scaled = (u128::from(numerator) * PROBABILITY_SCALE_U128 + u128::from(denominator) / 2)
82            / u128::from(denominator);
83        let basis_points =
84            u16::try_from(scaled).expect("scaled probability must fit into basis points");
85
86        Self::from_basis_points(basis_points)
87    }
88
89    /// Returns the fixed-point probability in basis points.
90    #[must_use]
91    pub const fn basis_points(self) -> u16 {
92        self.basis_points
93    }
94
95    /// Returns `1 - self`.
96    #[must_use]
97    pub const fn complement(self) -> Self {
98        Self {
99            basis_points: PROBABILITY_SCALE_BASIS_POINTS - self.basis_points,
100        }
101    }
102}
103
104/// Probabilistic truth value for relative-meta-logic-style confidence links.
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub struct ProbabilisticTruthValue {
107    true_probability: Probability,
108}
109
110impl ProbabilisticTruthValue {
111    /// Creates a probabilistic truth value from the probability of truth.
112    #[must_use]
113    pub const fn new(true_probability: Probability) -> Self {
114        Self { true_probability }
115    }
116
117    /// Creates a probabilistic truth value from a ratio.
118    #[must_use]
119    pub fn from_ratio(numerator: u64, denominator: u64) -> Option<Self> {
120        Some(Self::new(Probability::from_ratio(numerator, denominator)?))
121    }
122
123    /// Probability that the proposition is true.
124    #[must_use]
125    pub const fn true_probability(self) -> Probability {
126        self.true_probability
127    }
128
129    /// Probability that the proposition is false.
130    #[must_use]
131    pub const fn false_probability(self) -> Probability {
132        self.true_probability.complement()
133    }
134
135    /// Logical negation, represented as probability complement.
136    #[must_use]
137    pub const fn negate(self) -> Self {
138        Self::new(self.false_probability())
139    }
140
141    /// Independent probabilistic conjunction.
142    #[must_use]
143    pub fn and(self, other: Self) -> Self {
144        Self::new(multiply_probabilities(
145            self.true_probability(),
146            other.true_probability(),
147        ))
148    }
149
150    /// Independent probabilistic disjunction.
151    #[must_use]
152    pub fn or(self, other: Self) -> Self {
153        self.negate().and(other.negate()).negate()
154    }
155}
156
157fn multiply_probabilities(left: Probability, right: Probability) -> Probability {
158    let product = u32::from(left.basis_points()) * u32::from(right.basis_points());
159    let rounded = (product + PROBABILITY_SCALE_U32 / 2) / PROBABILITY_SCALE_U32;
160    let basis_points =
161        u16::try_from(rounded).expect("probability product must fit into basis points");
162
163    Probability::from_basis_points(basis_points).expect("probability product stays in range")
164}