uqa_scoring/
score_domain.rs1use crate::error::{require_finite, require_probability};
15use crate::prob::{logit, sigmoid};
16use crate::ScoringResult;
17
18#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
20#[repr(transparent)]
21pub struct RawBm25Score(f64);
22
23impl RawBm25Score {
24 pub fn new(value: f64) -> ScoringResult<Self> {
25 require_finite(value, "raw BM25 score")?;
26 Ok(Self(value))
27 }
28
29 pub const fn value(self) -> f64 {
30 self.0
31 }
32}
33
34impl TryFrom<f64> for RawBm25Score {
35 type Error = crate::ScoringError;
36
37 fn try_from(value: f64) -> Result<Self, Self::Error> {
38 Self::new(value)
39 }
40}
41
42impl From<RawBm25Score> for f64 {
43 fn from(score: RawBm25Score) -> Self {
44 score.value()
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
53#[repr(transparent)]
54pub struct EvidenceLogit(f64);
55
56impl EvidenceLogit {
57 pub fn new(value: f64) -> ScoringResult<Self> {
58 require_finite(value, "evidence logit")?;
59 Ok(Self(value))
60 }
61
62 pub fn from_prior_free_probability(probability: f64) -> ScoringResult<Self> {
67 require_probability(probability, "prior-free evidence probability")?;
68 Ok(Self(logit(probability)))
69 }
70
71 pub const fn neutral() -> Self {
72 Self(0.0)
73 }
74
75 pub const fn value(self) -> f64 {
76 self.0
77 }
78}
79
80impl TryFrom<f64> for EvidenceLogit {
81 type Error = crate::ScoringError;
82
83 fn try_from(value: f64) -> Result<Self, Self::Error> {
84 Self::new(value)
85 }
86}
87
88impl From<EvidenceLogit> for f64 {
89 fn from(logit: EvidenceLogit) -> Self {
90 logit.value()
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
96#[repr(transparent)]
97pub struct PriorLogit(f64);
98
99impl PriorLogit {
100 pub fn new(value: f64) -> ScoringResult<Self> {
101 require_finite(value, "prior logit")?;
102 Ok(Self(value))
103 }
104
105 pub fn from_probability(probability: f64) -> ScoringResult<Self> {
106 require_probability(probability, "prior probability")?;
107 if probability == 0.0 || probability == 1.0 {
108 return Err(crate::ScoringError::InvalidInput(format!(
109 "prior probability must be strictly between 0 and 1, got {probability}"
110 )));
111 }
112 Ok(Self(logit(probability)))
113 }
114
115 pub const fn neutral() -> Self {
116 Self(0.0)
117 }
118
119 pub const fn value(self) -> f64 {
120 self.0
121 }
122}
123
124impl TryFrom<f64> for PriorLogit {
125 type Error = crate::ScoringError;
126
127 fn try_from(value: f64) -> Result<Self, Self::Error> {
128 Self::new(value)
129 }
130}
131
132impl From<PriorLogit> for f64 {
133 fn from(logit: PriorLogit) -> Self {
134 logit.value()
135 }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
146#[repr(transparent)]
147pub struct PosteriorProbability(f64);
148
149impl PosteriorProbability {
150 pub fn new(value: f64) -> ScoringResult<Self> {
151 require_probability(value, "posterior probability")?;
152 Ok(Self(value))
153 }
154
155 pub fn from_logit(value: f64) -> ScoringResult<Self> {
156 require_finite(value, "posterior logit")?;
157 Self::new(sigmoid(value))
158 }
159
160 pub const fn value(self) -> f64 {
161 self.0
162 }
163}
164
165impl TryFrom<f64> for PosteriorProbability {
166 type Error = crate::ScoringError;
167
168 fn try_from(value: f64) -> Result<Self, Self::Error> {
169 Self::new(value)
170 }
171}
172
173impl From<PosteriorProbability> for f64 {
174 fn from(probability: PosteriorProbability) -> Self {
175 probability.value()
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn domain_constructors_reject_cross_domain_invalid_values() {
185 assert!(RawBm25Score::new(f64::NAN).is_err());
186 assert!(EvidenceLogit::new(f64::INFINITY).is_err());
187 assert!(PriorLogit::from_probability(0.0).is_err());
188 assert!(PriorLogit::from_probability(1.0).is_err());
189 assert!(PosteriorProbability::new(-0.1).is_err());
190 assert!(PosteriorProbability::new(1.1).is_err());
191 }
192
193 #[test]
194 fn neutral_evidence_and_prior_have_zero_logit() {
195 assert_eq!(EvidenceLogit::neutral().value(), 0.0);
196 assert_eq!(PriorLogit::neutral().value(), 0.0);
197 assert_eq!(
198 EvidenceLogit::from_prior_free_probability(0.5)
199 .unwrap()
200 .value(),
201 0.0
202 );
203 assert_eq!(PriorLogit::from_probability(0.5).unwrap().value(), 0.0);
204 }
205
206 #[test]
207 fn posterior_logit_conversion_is_explicit() {
208 let posterior = PosteriorProbability::from_logit(0.0).unwrap();
209 assert_eq!(posterior.value(), 0.5);
210 }
211}