forge_reasoning/hypothesis/
confidence.rs1use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
12pub struct Confidence(f64);
13
14#[derive(Error, Debug)]
15pub enum ConfidenceError {
16 #[error("Confidence value cannot be NaN")]
17 NaN,
18
19 #[error("Confidence out of bounds: {value} (must be {min} to {max})")]
20 OutOfBounds { value: f64, min: f64, max: f64 },
21
22 #[error("Evidence probability is zero, cannot compute Bayes update")]
23 ZeroEvidenceProbability,
24}
25
26impl Confidence {
27 const MIN: f64 = 0.0;
28 const MAX: f64 = 1.0;
29
30 pub fn new(value: f64) -> Result<Self, ConfidenceError> {
36 if value.is_nan() {
37 return Err(ConfidenceError::NaN);
38 }
39 if value < Self::MIN || value > Self::MAX {
40 return Err(ConfidenceError::OutOfBounds {
41 value,
42 min: Self::MIN,
43 max: Self::MAX,
44 });
45 }
46 Ok(Self(value))
47 }
48
49 pub fn get(self) -> f64 {
51 self.0
52 }
53
54 pub fn update_with_evidence(
68 self,
69 likelihood_h: f64, likelihood_not_h: f64, ) -> Result<Self, ConfidenceError> {
72 let prior = self.0;
73
74 let p_e = (likelihood_h * prior) + (likelihood_not_h * (1.0 - prior));
76
77 const MIN_PROB: f64 = 1e-10;
79 let p_e = p_e.max(MIN_PROB);
80
81 let posterior = (likelihood_h * prior) / p_e;
83
84 Self::new(posterior)
85 }
86
87 pub fn max_uncertainty() -> Self {
89 Self(0.5)
90 }
91}
92
93impl TryFrom<f64> for Confidence {
94 type Error = ConfidenceError;
95 fn try_from(value: f64) -> Result<Self, Self::Error> {
96 Self::new(value)
97 }
98}
99
100impl Default for Confidence {
101 fn default() -> Self {
102 Self::max_uncertainty()
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_confidence_valid() {
112 assert!(Confidence::new(0.0).is_ok());
113 assert!(Confidence::new(0.5).is_ok());
114 assert!(Confidence::new(1.0).is_ok());
115 }
116
117 #[test]
118 fn test_confidence_rejects_nan() {
119 assert!(matches!(
120 Confidence::new(f64::NAN),
121 Err(ConfidenceError::NaN)
122 ));
123 }
124
125 #[test]
126 fn test_confidence_rejects_out_of_bounds() {
127 assert!(Confidence::new(-0.1).is_err());
128 assert!(Confidence::new(1.1).is_err());
129 assert!(Confidence::new(2.0).is_err());
130 }
131
132 #[test]
133 fn test_confidence_get() {
134 let c = Confidence::new(0.75).unwrap();
135 assert_eq!(c.get(), 0.75);
136 }
137
138 #[test]
139 fn test_bayes_update_supporting_evidence() {
140 let prior = Confidence::new(0.5).unwrap();
144 let posterior = prior.update_with_evidence(0.9, 0.1).unwrap();
145 assert!(posterior.get() > 0.8);
146 }
147
148 #[test]
149 fn test_bayes_update_contradictory_evidence() {
150 let prior = Confidence::new(0.5).unwrap();
154 let posterior = prior.update_with_evidence(0.1, 0.9).unwrap();
155 assert!(posterior.get() < 0.2);
156 }
157
158 #[test]
159 fn test_bayes_update_neutral_evidence() {
160 let prior = Confidence::new(0.5).unwrap();
164 let posterior = prior.update_with_evidence(0.5, 0.5).unwrap();
165 assert!((posterior.get() - 0.5).abs() < 0.01);
166 }
167
168 #[test]
169 fn test_max_uncertainty() {
170 let c = Confidence::max_uncertainty();
171 assert_eq!(c.get(), 0.5);
172 }
173
174 #[test]
175 fn test_default() {
176 let c = Confidence::default();
177 assert_eq!(c.get(), 0.5);
178 }
179
180 #[test]
181 fn test_try_from_f64() {
182 assert!(Confidence::try_from(0.5).is_ok());
183 assert!(Confidence::try_from(f64::NAN).is_err());
184 assert!(Confidence::try_from(1.5).is_err());
185 }
186}