1use alloc::vec::Vec;
2use core::ops::{Add, Mul, Sub};
3use witnessed::{WitnessExt, Witnessed};
4
5pub(crate) trait ScoreOps:
14 Copy + PartialOrd + PartialEq + Add<Output = Self> + Sub<Output = Self> + Mul<Output = Self>
15{
16 fn is_finite(self) -> bool;
17 fn from_f64(v: f64) -> Self;
18}
19
20impl ScoreOps for f32 {
21 #[inline]
22 fn is_finite(self) -> bool {
23 f32::is_finite(self)
24 }
25 #[inline]
26 fn from_f64(v: f64) -> Self {
27 v as f32
28 }
29}
30
31impl ScoreOps for f64 {
32 #[inline]
33 fn is_finite(self) -> bool {
34 f64::is_finite(self)
35 }
36 #[inline]
37 fn from_f64(v: f64) -> Self {
38 v
39 }
40}
41
42pub struct Value01;
48
49impl Value01 {
50 #[inline]
52 #[allow(private_bounds)]
53 pub fn witness<T: ScoreOps>(v: T) -> Result<Witnessed<T, Self>, &'static str> {
54 if !v.is_finite() {
55 return Err("Value01: value must be finite");
56 }
57 let zero = T::from_f64(0.0);
58 let one = T::from_f64(1.0);
59 if v < zero || v > one {
60 return Err("Value01: value must be in [0, 1]");
61 }
62 v.witness().by(|_| Ok(Value01))
63 }
64}
65
66pub struct GtZero;
72
73impl GtZero {
74 #[inline]
76 #[allow(private_bounds)]
77 pub fn witness<T: ScoreOps>(v: T) -> Result<Witnessed<T, Self>, &'static str> {
78 if !v.is_finite() {
79 return Err("GtZero: value must be finite");
80 }
81 let zero = T::from_f64(0.0);
82 if v <= zero {
83 return Err("GtZero: value must be strictly positive");
84 }
85 v.witness().by(|_| Ok(GtZero))
86 }
87}
88
89pub struct NormalizedWeight;
98
99impl NormalizedWeight {
100 #[inline]
104 #[allow(private_bounds)]
105 pub fn from_normalized_container<T: ScoreOps>(
106 value: T,
107 container: &Witnessed<Vec<T>, NormalizedContainer>,
108 ) -> Result<Self, &'static str> {
109 if container
110 .binary_search_by(|a| a.partial_cmp(&value).unwrap_or(core::cmp::Ordering::Equal))
111 .is_ok()
112 {
113 Ok(NormalizedWeight)
114 } else {
115 Err("NormalizedWeight: value not found in validated set")
116 }
117 }
118}
119
120pub struct NormalizedContainer;
127
128impl NormalizedContainer {
129 #[inline]
131 #[allow(private_bounds)]
132 pub fn witness<T: ScoreOps>(weights: Vec<T>) -> Result<Witnessed<Vec<T>, Self>, &'static str> {
133 let zero = T::from_f64(0.0);
134 let one = T::from_f64(1.0);
135 let mut sum = zero;
136 let len = weights.len();
137 for &w in &weights {
138 if !w.is_finite() {
139 return Err("NormalizedContainer: value must be finite");
140 }
141 if w < zero || w > one {
142 return Err("NormalizedContainer: value must be in [0, 1]");
143 }
144 sum = sum + w;
145 }
146 let diff = if sum > one { sum - one } else { one - sum };
147 let len_f = T::from_f64(len as f64);
148 let scale = if len_f > one { len_f } else { one };
149 let tol = T::from_f64(1e-9) * scale;
150 if diff > tol {
151 return Err("NormalizedContainer: set must sum to 1");
152 }
153 weights.witness().by(|_| Ok(NormalizedContainer))
154 }
155}
156
157#[cfg(test)]
158mod tests_for_value;