score_set/value.rs
1use crate::float::Float;
2use witnessed::{WitnessExt, Witnessed};
3
4// ---------------------------------------------------------------------------
5// Value01 — value in [0, 1], finite
6// ---------------------------------------------------------------------------
7
8/// Witness credential for a value validated to be finite and in `[0, 1]`.
9///
10/// ```ignore
11/// let v: Witnessed<f64, Value01> = Value01::witness(0.5)?;
12/// ```
13pub struct Value01;
14
15impl Value01 {
16 /// Validate `v` and return a `Witnessed` credential.
17 pub fn witness<T: Float>(v: T) -> Result<Witnessed<T, Self>, &'static str> {
18 if !v.is_finite() {
19 return Err("Value01: value must be finite");
20 }
21 if v < T::zero() || v > T::one() {
22 return Err("Value01: value must be in [0, 1]");
23 }
24 v.witness().by(|_| Ok(Value01))
25 }
26}
27
28// ---------------------------------------------------------------------------
29// GtZero — strictly greater than zero, finite
30// ---------------------------------------------------------------------------
31
32/// Witness credential for a value validated to be finite and strictly > 0.
33pub struct GtZero;
34
35impl GtZero {
36 /// Validate `v` and return a `Witnessed` credential.
37 pub fn witness<T: Float>(v: T) -> Result<Witnessed<T, Self>, &'static str> {
38 if !v.is_finite() {
39 return Err("GtZero: value must be finite");
40 }
41 if v <= T::zero() {
42 return Err("GtZero: value must be strictly positive");
43 }
44 v.witness().by(|_| Ok(GtZero))
45 }
46}
47
48// ---------------------------------------------------------------------------
49// NormalizedWeight — weight credential
50// ---------------------------------------------------------------------------
51
52/// Witness credential for a single normalized weight.
53///
54/// Created via [`NormalizedWeight::from_normalized_container`], which
55/// binary-searches a validated sorted container to confirm membership.
56///
57/// ```ignore
58/// let set = NormalizedContainer::witness(weights)?;
59/// let w = 0.3_f64.witness().by(
60/// |v| NormalizedWeight::from_normalized_container(*v, &set)
61/// )?;
62/// ```
63pub struct NormalizedWeight;
64
65impl NormalizedWeight {
66 /// Verify `value` is a member of a validated normalized set.
67 ///
68 /// Binary-searches the sorted `container`.
69 pub fn from_normalized_container<T: Float>(
70 value: T,
71 container: &Witnessed<Vec<T>, NormalizedContainer>,
72 ) -> Result<Self, &'static str> {
73 if container
74 .binary_search_by(|a| a.partial_cmp(&value).unwrap_or(core::cmp::Ordering::Equal))
75 .is_ok()
76 {
77 Ok(NormalizedWeight)
78 } else {
79 Err("NormalizedWeight: value not found in validated set")
80 }
81 }
82}
83
84// ---------------------------------------------------------------------------
85// NormalizedContainer — validated set of normalized weights
86// ---------------------------------------------------------------------------
87
88/// Witness credential for a container validated as a complete set of
89/// normalized weights (each in `[0, 1]`, sum to 1).
90///
91/// ```ignore
92/// let set = NormalizedContainer::witness(vec![0.2, 0.3, 0.5])?;
93/// ```
94pub struct NormalizedContainer;
95
96impl NormalizedContainer {
97 /// Validate every value is finite, in `[0, 1]`, and the set sums to 1.
98 pub fn witness<T: Float>(weights: Vec<T>) -> Result<Witnessed<Vec<T>, Self>, &'static str> {
99 let mut sum = T::zero();
100 let len = weights.len();
101 for &w in &weights {
102 if !w.is_finite() {
103 return Err("NormalizedContainer: value must be finite");
104 }
105 if w < T::zero() || w > T::one() {
106 return Err("NormalizedContainer: value must be in [0, 1]");
107 }
108 sum = sum + w;
109 }
110 let diff = if sum > T::one() {
111 sum - T::one()
112 } else {
113 T::one() - sum
114 };
115 let tol = T::from_f64(1e-9) * T::from_f64(len as f64).max(T::one());
116 if diff > tol {
117 return Err("NormalizedContainer: set must sum to 1");
118 }
119 weights.witness().by(|_| Ok(NormalizedContainer))
120 }
121}
122
123#[cfg(test)]
124mod tests_for_value;