Skip to main content

score_set/
value.rs

1use alloc::vec::Vec;
2use core::ops::{Add, Mul, Sub};
3use witnessed::{WitnessExt, Witnessed};
4
5// ---------------------------------------------------------------------------
6// Minimal private trait — only what witness validation needs
7// ---------------------------------------------------------------------------
8
9/// Minimal trait for types usable as scores (weights, normalized values).
10///
11/// Implemented for `f32` and `f64`. Only needed as a bound on witness
12/// constructors — downstream code uses concrete types directly.
13pub(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
42// ---------------------------------------------------------------------------
43// Value01 — value in [0, 1], finite
44// ---------------------------------------------------------------------------
45
46/// Witness credential for a value validated to be finite and in `[0, 1]`.
47pub struct Value01;
48
49impl Value01 {
50    /// Validate `v` and return a `Witnessed` credential.
51    #[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
66// ---------------------------------------------------------------------------
67// GtZero — strictly greater than zero, finite
68// ---------------------------------------------------------------------------
69
70/// Witness credential for a value validated to be finite and strictly > 0.
71pub struct GtZero;
72
73impl GtZero {
74    /// Validate `v` and return a `Witnessed` credential.
75    #[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
89// ---------------------------------------------------------------------------
90// NormalizedWeight — weight credential
91// ---------------------------------------------------------------------------
92
93/// Witness credential for a single normalized weight.
94///
95/// Created via [`NormalizedWeight::from_normalized_container`], which
96/// binary-searches a validated sorted container to confirm membership.
97pub struct NormalizedWeight;
98
99impl NormalizedWeight {
100    /// Verify `value` is a member of a validated normalized set.
101    ///
102    /// Binary-searches the sorted `container`.
103    #[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
120// ---------------------------------------------------------------------------
121// NormalizedContainer — validated set of normalized weights
122// ---------------------------------------------------------------------------
123
124/// Witness credential for a container validated as a complete set of
125/// normalized weights (each in `[0, 1]`, sum to 1).
126pub struct NormalizedContainer;
127
128impl NormalizedContainer {
129    /// Validate every value is finite, in `[0, 1]`, and the set sums to 1.
130    #[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;