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 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    pub fn witness<T: ScoreOps>(v: T) -> Result<Witnessed<T, Self>, &'static str> {
53        if !v.is_finite() {
54            return Err("Value01: value must be finite");
55        }
56        let zero = T::from_f64(0.0);
57        let one = T::from_f64(1.0);
58        if v < zero || v > one {
59            return Err("Value01: value must be in [0, 1]");
60        }
61        v.witness().by(|_| Ok(Value01))
62    }
63}
64
65// ---------------------------------------------------------------------------
66// GtZero — strictly greater than zero, finite
67// ---------------------------------------------------------------------------
68
69/// Witness credential for a value validated to be finite and strictly > 0.
70pub struct GtZero;
71
72impl GtZero {
73    /// Validate `v` and return a `Witnessed` credential.
74    #[inline]
75    pub fn witness<T: ScoreOps>(v: T) -> Result<Witnessed<T, Self>, &'static str> {
76        if !v.is_finite() {
77            return Err("GtZero: value must be finite");
78        }
79        let zero = T::from_f64(0.0);
80        if v <= zero {
81            return Err("GtZero: value must be strictly positive");
82        }
83        v.witness().by(|_| Ok(GtZero))
84    }
85}
86
87// ---------------------------------------------------------------------------
88// NormalizedWeight — weight credential
89// ---------------------------------------------------------------------------
90
91/// Witness credential for a single normalized weight.
92///
93/// Created via [`NormalizedWeight::from_normalized_container`], which
94/// binary-searches a validated sorted container to confirm membership.
95pub struct NormalizedWeight;
96
97impl NormalizedWeight {
98    /// Verify `value` is a member of a validated normalized set.
99    ///
100    /// Binary-searches the sorted `container`.
101    #[inline]
102    pub fn from_normalized_container<T: ScoreOps>(
103        value: T,
104        container: &Witnessed<Vec<T>, NormalizedContainer>,
105    ) -> Result<Self, &'static str> {
106        if container
107            .binary_search_by(|a| a.partial_cmp(&value).unwrap_or(core::cmp::Ordering::Equal))
108            .is_ok()
109        {
110            Ok(NormalizedWeight)
111        } else {
112            Err("NormalizedWeight: value not found in validated set")
113        }
114    }
115}
116
117// ---------------------------------------------------------------------------
118// NormalizedContainer — validated set of normalized weights
119// ---------------------------------------------------------------------------
120
121/// Witness credential for a container validated as a complete set of
122/// normalized weights (each in `[0, 1]`, sum to 1).
123pub struct NormalizedContainer;
124
125impl NormalizedContainer {
126    /// Validate every value is finite, in `[0, 1]`, and the set sums to 1.
127    #[inline]
128    pub fn witness<T: ScoreOps>(weights: Vec<T>) -> Result<Witnessed<Vec<T>, Self>, &'static str> {
129        let zero = T::from_f64(0.0);
130        let one = T::from_f64(1.0);
131        let mut sum = zero;
132        let len = weights.len();
133        for &w in &weights {
134            if !w.is_finite() {
135                return Err("NormalizedContainer: value must be finite");
136            }
137            if w < zero || w > one {
138                return Err("NormalizedContainer: value must be in [0, 1]");
139            }
140            sum = sum + w;
141        }
142        let diff = if sum > one { sum - one } else { one - sum };
143        let len_f = T::from_f64(len as f64);
144        let scale = if len_f > one { len_f } else { one };
145        let tol = T::from_f64(1e-9) * scale;
146        if diff > tol {
147            return Err("NormalizedContainer: set must sum to 1");
148        }
149        weights.witness().by(|_| Ok(NormalizedContainer))
150    }
151}
152
153#[cfg(test)]
154mod tests_for_value;