rustev_core/ranking/mod.rs
1//! Pure ranking building blocks (spec 039).
2//!
3//! Ranking and selection cannot convert to or from other value kinds (039, 3.9.1).
4//!
5//! ```compile_fail,E0277
6//! use rustev_core::kinds::ModelScore;
7//! use rustev_contract::ranking_result::ModelRanking;
8//! fn convert(value: ModelRanking) -> ModelScore { value.into() }
9//! ```
10//!
11//! ```compile_fail,E0277
12//! use rustev_core::kinds::ModelScore;
13//! use rustev_contract::ranking_result::ModelRanking;
14//! fn convert(value: ModelScore) -> ModelRanking { value.into() }
15//! ```
16//!
17//! ```compile_fail,E0277
18//! use rustev_core::kinds::Distribution;
19//! use rustev_contract::ranking_result::ModelRanking;
20//! fn convert(value: ModelRanking) -> Distribution { value.into() }
21//! ```
22//!
23//! ```compile_fail,E0277
24//! use rustev_core::kinds::Distribution;
25//! use rustev_contract::ranking_result::ModelRanking;
26//! fn convert(value: Distribution) -> ModelRanking { value.into() }
27//! ```
28//!
29//! ```compile_fail,E0277
30//! use rustev_core::kinds::CalibratedProbability;
31//! use rustev_contract::ranking_result::ModelRanking;
32//! fn convert(value: ModelRanking) -> CalibratedProbability { value.into() }
33//! ```
34//!
35//! ```compile_fail,E0277
36//! use rustev_core::kinds::CalibratedProbability;
37//! use rustev_contract::ranking_result::ModelRanking;
38//! fn convert(value: CalibratedProbability) -> ModelRanking { value.into() }
39//! ```
40//!
41//! ```compile_fail,E0277
42//! use rustev_core::kinds::SelectedLabel;
43//! use rustev_contract::ranking_result::ModelRanking;
44//! fn convert(value: ModelRanking) -> SelectedLabel { value.into() }
45//! ```
46//!
47//! ```compile_fail,E0277
48//! use rustev_core::kinds::SelectedLabel;
49//! use rustev_contract::ranking_result::ModelRanking;
50//! fn convert(value: SelectedLabel) -> ModelRanking { value.into() }
51//! ```
52//!
53//! ```compile_fail,E0277
54//! use rustev_core::kinds::OrdinalLevel;
55//! use rustev_contract::ranking_result::ModelRanking;
56//! fn convert(value: ModelRanking) -> OrdinalLevel { value.into() }
57//! ```
58//!
59//! ```compile_fail,E0277
60//! use rustev_core::kinds::OrdinalLevel;
61//! use rustev_contract::ranking_result::ModelRanking;
62//! fn convert(value: OrdinalLevel) -> ModelRanking { value.into() }
63//! ```
64//!
65//! ```compile_fail,E0277
66//! use rustev_core::kinds::RankPosition;
67//! use rustev_contract::ranking_result::ModelRanking;
68//! fn convert(value: ModelRanking) -> RankPosition { value.into() }
69//! ```
70//!
71//! ```compile_fail,E0277
72//! use rustev_core::kinds::RankPosition;
73//! use rustev_contract::ranking_result::ModelRanking;
74//! fn convert(value: RankPosition) -> ModelRanking { value.into() }
75//! ```
76//!
77//! ```compile_fail,E0277
78//! use rustev_core::kinds::ModelScore;
79//! use rustev_contract::ranking_result::Selection;
80//! fn convert(value: Selection) -> ModelScore { value.into() }
81//! ```
82//!
83//! ```compile_fail,E0277
84//! use rustev_core::kinds::ModelScore;
85//! use rustev_contract::ranking_result::Selection;
86//! fn convert(value: ModelScore) -> Selection { value.into() }
87//! ```
88//!
89//! ```compile_fail,E0277
90//! use rustev_core::kinds::Distribution;
91//! use rustev_contract::ranking_result::Selection;
92//! fn convert(value: Selection) -> Distribution { value.into() }
93//! ```
94//!
95//! ```compile_fail,E0277
96//! use rustev_core::kinds::Distribution;
97//! use rustev_contract::ranking_result::Selection;
98//! fn convert(value: Distribution) -> Selection { value.into() }
99//! ```
100//!
101//! ```compile_fail,E0277
102//! use rustev_core::kinds::CalibratedProbability;
103//! use rustev_contract::ranking_result::Selection;
104//! fn convert(value: Selection) -> CalibratedProbability { value.into() }
105//! ```
106//!
107//! ```compile_fail,E0277
108//! use rustev_core::kinds::CalibratedProbability;
109//! use rustev_contract::ranking_result::Selection;
110//! fn convert(value: CalibratedProbability) -> Selection { value.into() }
111//! ```
112//!
113//! ```compile_fail,E0277
114//! use rustev_core::kinds::SelectedLabel;
115//! use rustev_contract::ranking_result::Selection;
116//! fn convert(value: Selection) -> SelectedLabel { value.into() }
117//! ```
118//!
119//! ```compile_fail,E0277
120//! use rustev_core::kinds::SelectedLabel;
121//! use rustev_contract::ranking_result::Selection;
122//! fn convert(value: SelectedLabel) -> Selection { value.into() }
123//! ```
124//!
125//! ```compile_fail,E0277
126//! use rustev_core::kinds::OrdinalLevel;
127//! use rustev_contract::ranking_result::Selection;
128//! fn convert(value: Selection) -> OrdinalLevel { value.into() }
129//! ```
130//!
131//! ```compile_fail,E0277
132//! use rustev_core::kinds::OrdinalLevel;
133//! use rustev_contract::ranking_result::Selection;
134//! fn convert(value: OrdinalLevel) -> Selection { value.into() }
135//! ```
136//!
137//! ```compile_fail,E0277
138//! use rustev_core::kinds::RankPosition;
139//! use rustev_contract::ranking_result::Selection;
140//! fn convert(value: Selection) -> RankPosition { value.into() }
141//! ```
142//!
143//! ```compile_fail,E0277
144//! use rustev_core::kinds::RankPosition;
145//! use rustev_contract::ranking_result::Selection;
146//! fn convert(value: RankPosition) -> Selection { value.into() }
147//! ```
148pub mod permute;
149pub mod schedules;
150pub mod select;
151pub mod value;
152
153use rustev_contract::ranking::{Bias, PairwiseSchedule, RankMode};
154
155/// Invalid controls or a bound that cannot be represented.
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum BoundError {
158 InvalidBias,
159 Overflow,
160}
161
162/// Worst-case logical pairs before presentation multiplication (039, 3.7.5).
163/// This is independent of the execution cap, which compilation must compare
164/// against the presentation count returned by `max_requests`.
165pub fn pairwise_bound(n: u64, schedule: PairwiseSchedule) -> Result<u64, BoundError> {
166 let count = match schedule {
167 PairwiseSchedule::RoundRobin => u128::from(n) * u128::from(n.saturating_sub(1)) / 2,
168 PairwiseSchedule::MergeSort => {
169 // At each width, full merges cost 2*width-1. A trailing run
170 // contributes only when it has a nonempty right side.
171 let n = u128::from(n);
172 let mut width = 1u128;
173 let mut total = 0u128;
174 while width < n {
175 let span = width * 2;
176 let remainder = n % span;
177 total += (n / span) * (span - 1);
178 if remainder > width {
179 total += remainder - 1;
180 }
181 width = span;
182 }
183 total
184 }
185 };
186 // u64 inputs require at most 64 levels; intermediates fit in u128.
187 u64::try_from(count).map_err(|_| BoundError::Overflow)
188}
189
190/// Declared request bound (039, 3.5.4, 3.6.3, 3.7.5), not actual requests.
191/// In particular listwise's declared bound is orders even for a zero pool
192/// bound; request planning must suppress requests for an empty actual pool.
193pub fn max_requests(n: u64, mode: &RankMode, bias: &Bias) -> Result<u64, BoundError> {
194 let orders = match bias {
195 Bias::None | Bias::Shuffle { .. } => 1,
196 Bias::PermutationAverage { orders, .. } if (2..=8).contains(orders) => *orders,
197 Bias::PermutationAverage { .. } => return Err(BoundError::InvalidBias),
198 };
199 match mode {
200 RankMode::Listwise { .. } => Ok(orders),
201 RankMode::Pointwise if *bias == Bias::None => Ok(n),
202 RankMode::Pointwise => Err(BoundError::InvalidBias),
203 RankMode::Pairwise { schedule, .. } => {
204 if orders > 2 {
205 return Err(BoundError::InvalidBias);
206 }
207 pairwise_bound(n, *schedule)?
208 .checked_mul(orders)
209 .ok_or(BoundError::Overflow)
210 }
211 }
212}