score_set/fixed.rs
1use crate::float::Float;
2use crate::value::NormalizedContainer;
3use core::marker::PhantomData;
4
5// ---------------------------------------------------------------------------
6// FixedScoreSet — compile-time fixed weighted set (Layer 1)
7// ---------------------------------------------------------------------------
8
9/// A compile-time fixed weighted set of scoring operators with normalized
10/// weights.
11///
12/// Construct via [`fixed_score_set!`](crate::fixed_score_set!). Call
13/// [`.score()`](FixedScoreSet::score) to enter the scoring stage.
14pub struct FixedScoreSet<T: Float, Members> {
15 pub(crate) members: Members,
16 _phantom: PhantomData<T>,
17}
18
19impl<T: Float, Members> FixedScoreSet<T, Members>
20where
21 Members: crate::Members<T>,
22{
23 /// Normalize raw weights and validate the resulting set.
24 #[doc(hidden)]
25 pub fn normalize(raw: Members::Raw) -> Result<Self, &'static str> {
26 let raw_weights = Members::extract_raw_weights(&raw);
27 let sum: T = raw_weights.iter().fold(T::zero(), |a, &b| a + b);
28 let normalized: Vec<T> = raw_weights.iter().map(|&w| w / sum).collect();
29
30 // Sort a clone for the validated container (required by binary search in
31 // NormalizedWeight::from_normalized_container). The unsorted `normalized`
32 // slice preserves insertion order for per-member lookup by index.
33 let mut sorted = normalized.clone();
34 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
35 let container = NormalizedContainer::witness(sorted)?;
36
37 Ok(FixedScoreSet {
38 members: Members::from_raw_with_weights(raw, &normalized, &container),
39 _phantom: PhantomData,
40 })
41 }
42
43 /// Enter the scoring stage.
44 #[inline]
45 pub fn score(&self) -> FixedScoreStage<'_, T, Members> {
46 FixedScoreStage {
47 members: &self.members,
48 _phantom: PhantomData,
49 }
50 }
51}
52
53// ---------------------------------------------------------------------------
54// ScoreStage — user-provided scoring closure
55// ---------------------------------------------------------------------------
56
57/// The scoring stage, created by [`FixedScoreSet::score`].
58///
59/// Call [`.by(closure)`](ScoreStage::by) to evaluate the set with an
60/// arbitrary composition of its members.
61pub struct FixedScoreStage<'a, T: Float, Members> {
62 members: &'a Members,
63 _phantom: PhantomData<T>,
64}
65
66impl<'a, T: Float, Members> FixedScoreStage<'a, T, Members> {
67 /// Score the set using a user-provided closure.
68 #[inline]
69 pub fn by<F, R>(self, f: F) -> R
70 where
71 F: FnOnce(&Members) -> R,
72 {
73 f(self.members)
74 }
75}
76
77#[cfg(test)]
78mod tests_for_fixed;