1use super::{max_requests, permute};
3use rustev_contract::ranking::{Bias, PairwiseSchedule, RankMode};
4use rustev_contract::ranking_result::CycleCount;
5use std::collections::{BTreeMap, BTreeSet, VecDeque};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum ScheduleError {
9 InvalidControls,
10 InvalidPool,
11 Permutation(permute::PermuteError),
12 UnknownRequest,
13 AlreadyAnswered,
14 Finished,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct PresentedRequest {
19 pub suffix: String,
21 pub presented: Vec<String>,
22}
23
24#[derive(Debug)]
25pub enum RequestPlan {
26 Independent(Vec<PresentedRequest>),
27 Pairwise(Box<Pairwise>),
28}
29
30pub fn plan_requests(
34 pool: &[String],
35 mode: &RankMode,
36 bias: &Bias,
37 step: &str,
38 instance: &[String],
39 tie_order: Option<&[String]>,
40) -> Result<RequestPlan, ScheduleError> {
41 if pool.len() > 1024 || pool.iter().collect::<BTreeSet<_>>().len() != pool.len() {
42 return Err(ScheduleError::InvalidPool);
43 }
44 max_requests(pool.len() as u64, mode, bias).map_err(|_| ScheduleError::InvalidControls)?;
45 if let Bias::Shuffle { seed } | Bias::PermutationAverage { seed, .. } = bias
46 && (!(1..=64).contains(&seed.len())
47 || !seed
48 .bytes()
49 .all(|b| b.is_ascii_alphanumeric() || b"._-".contains(&b)))
50 {
51 return Err(ScheduleError::InvalidControls);
52 }
53 let context = Context {
54 bias: bias.clone(),
55 step: step.into(),
56 instance: instance.to_vec(),
57 };
58 if let RankMode::Pairwise {
59 schedule,
60 max_comparisons,
61 } = mode
62 {
63 if !(1..=4096).contains(max_comparisons) {
64 return Err(ScheduleError::InvalidControls);
65 }
66 return Ok(RequestPlan::Pairwise(Box::new(Pairwise::new(
67 pool,
68 *schedule,
69 *max_comparisons,
70 context,
71 tie_order,
72 )?)));
73 }
74 let mut requests = Vec::new();
75 if !pool.is_empty() {
76 match mode {
77 RankMode::Pointwise => {
78 requests.extend(pool.iter().map(|id| PresentedRequest {
79 suffix: format!("cand/{id}"),
80 presented: vec![id.clone()],
81 }));
82 }
83 RankMode::Listwise { .. } => {
84 for j in 0..context.orders() {
85 let order = context.order(pool.len(), j, true)?;
86 requests.push(PresentedRequest {
87 suffix: format!("perm/{j}"),
88 presented: order.into_iter().map(|p| pool[p].clone()).collect(),
89 });
90 }
91 }
92 RankMode::Pairwise { .. } => return Err(ScheduleError::InvalidControls),
93 }
94 }
95 Ok(RequestPlan::Independent(requests))
96}
97
98#[derive(Debug, Clone)]
99struct Context {
100 bias: Bias,
101 step: String,
102 instance: Vec<String>,
103}
104impl Context {
105 fn orders(&self) -> u64 {
106 match self.bias {
107 Bias::PermutationAverage { orders, .. } => orders,
108 _ => 1,
109 }
110 }
111 fn order(&self, size: usize, j: u64, listwise: bool) -> Result<Vec<usize>, ScheduleError> {
112 let seed = match &self.bias {
113 Bias::Shuffle { seed } => Some(seed),
114 Bias::PermutationAverage { seed, .. } if listwise => Some(seed),
115 _ => None,
116 };
117 match seed {
118 Some(seed) => permute::permutation(size, seed, &self.step, &self.instance, j)
119 .map_err(ScheduleError::Permutation),
120 None => Ok((0..size).collect()),
121 }
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum Answer {
127 First,
128 Second,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct PairwiseResult {
133 pub order: Vec<String>,
134 pub wins: Vec<u64>,
136 pub comparisons: u64,
137 pub inconsistent_pairs: u64,
138 pub cyclic_triples: CycleCount,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
142pub enum ScheduleState {
143 Pending(Vec<PresentedRequest>),
144 Complete(PairwiseResult),
145 BudgetExhausted { comparisons: u64 },
146}
147
148type Key = (usize, usize, usize);
150#[derive(Debug)]
151struct Comparison {
152 pair: [usize; 2],
153 requests: Vec<PresentedRequest>,
154 answers: Vec<Option<Answer>>,
155 reversed: bool,
156}
157impl Comparison {
158 fn winner(&self) -> Option<Option<usize>> {
159 let mut winners = self.answers.iter().enumerate().map(|(o, answer)| {
160 answer.map(|a| self.pair[usize::from((a == Answer::Second) ^ self.reversed ^ (o == 1))])
161 });
162 let first = winners.next()??;
163 let mut agrees = true;
164 for winner in winners {
165 agrees &= winner? == first;
166 }
167 Some(agrees.then_some(first))
168 }
169}
170
171#[derive(Debug)]
172struct Merge {
173 left: VecDeque<usize>,
174 right: VecDeque<usize>,
175 out: Vec<usize>,
176 n: usize,
177}
178impl Merge {
179 fn pair(&self) -> Option<[usize; 2]> {
180 Some([*self.left.front()?, *self.right.front()?])
181 }
182 fn drain_tail(&mut self) {
183 if self.pair().is_none() {
184 self.out.extend(self.left.drain(..));
185 self.out.extend(self.right.drain(..));
186 }
187 }
188}
189
190#[derive(Debug)]
196pub struct Pairwise {
197 pool: Vec<String>,
198 schedule: PairwiseSchedule,
199 cap: u64,
200 context: Context,
201 tie: Vec<usize>,
202 pending: BTreeMap<Key, Comparison>,
203 merges: Vec<Merge>,
204 level: usize,
205 wins: Vec<u64>,
206 edges: BTreeSet<(usize, usize)>,
207 issued: u64,
208 inconsistent: u64,
209 terminal: Option<ScheduleState>,
210}
211impl Pairwise {
212 fn new(
213 pool: &[String],
214 schedule: PairwiseSchedule,
215 cap: u64,
216 context: Context,
217 tie_order: Option<&[String]>,
218 ) -> Result<Self, ScheduleError> {
219 let mut tie: Vec<_> = (0..pool.len()).collect();
220 tie.sort_by(|&a, &b| {
222 let position = |p: usize| {
223 tie_order
224 .and_then(|ids| ids.iter().position(|id| id == &pool[p]))
225 .unwrap_or(usize::MAX)
226 };
227 position(a)
228 .cmp(&position(b))
229 .then_with(|| pool[a].cmp(&pool[b]))
230 });
231 let order = if schedule == PairwiseSchedule::MergeSort {
232 context.order(pool.len(), 0, false)?
233 } else {
234 Vec::new()
235 };
236 let mut state = Self {
237 pool: pool.to_vec(),
238 schedule,
239 cap,
240 context,
241 tie,
242 pending: BTreeMap::new(),
243 merges: Vec::new(),
244 level: 0,
245 wins: vec![0; pool.len()],
246 edges: BTreeSet::new(),
247 issued: 0,
248 inconsistent: 0,
249 terminal: None,
250 };
251 match schedule {
252 PairwiseSchedule::RoundRobin => {
253 let n = pool.len() as u64;
256 if n * n.saturating_sub(1) / 2 * state.context.orders() > cap {
257 state.terminal = Some(ScheduleState::BudgetExhausted { comparisons: 0 });
258 return Ok(state);
259 }
260 let mut c = 0;
261 for a in 0..pool.len() {
262 for b in a + 1..pool.len() {
263 if !state.issue((0, 0, c), [a, b])? {
264 return Ok(state);
265 }
266 c += 1;
267 }
268 }
269 }
270 PairwiseSchedule::MergeSort => {
271 state.start_level(order.chunks(1).map(Vec::from).collect())
272 }
273 }
274 Ok(state)
275 }
276
277 fn start_level(&mut self, runs: Vec<Vec<usize>>) {
278 self.merges = runs
279 .chunks(2)
280 .map(|pair| {
281 let mut merge = Merge {
282 left: pair.first().cloned().unwrap_or_default().into(),
283 right: pair.get(1).cloned().unwrap_or_default().into(),
284 out: Vec::new(),
285 n: 0,
286 };
287 merge.drain_tail();
288 merge
289 })
290 .collect();
291 }
292
293 fn issue(&mut self, key: Key, pair: [usize; 2]) -> Result<bool, ScheduleError> {
297 let orders = self.context.orders();
298 if self.issued + orders > self.cap {
299 self.terminal = Some(ScheduleState::BudgetExhausted {
300 comparisons: self.issued,
301 });
302 return Ok(false);
303 }
304 let c = match self.schedule {
305 PairwiseSchedule::RoundRobin => key.2.to_string(),
306 PairwiseSchedule::MergeSort => format!("{}.{}.{}", key.0, key.1, key.2),
307 };
308 let reversed = match &self.context.bias {
310 Bias::Shuffle { seed } => {
311 permute::presentation_bit(seed, &self.context.step, &self.context.instance, &c)
312 .map_err(ScheduleError::Permutation)?
313 }
314 _ => false,
315 };
316 let mut requests = Vec::new();
317 for o in 0..orders {
318 let flip = usize::from(reversed ^ (o == 1));
319 requests.push(PresentedRequest {
320 suffix: format!("pair/{c}/{o}"),
321 presented: vec![
322 self.pool[pair[flip]].clone(),
323 self.pool[pair[1 - flip]].clone(),
324 ],
325 });
326 }
327 self.issued += orders;
328 self.pending.insert(
329 key,
330 Comparison {
331 pair,
332 requests,
333 answers: vec![None; orders as usize],
334 reversed,
335 },
336 );
337 Ok(true)
338 }
339
340 pub fn answer(&mut self, suffix: &str, answer: Answer) -> Result<(), ScheduleError> {
341 if self.terminal.is_some() {
342 return Err(ScheduleError::Finished);
343 }
344 for comparison in self.pending.values_mut() {
345 if let Some(o) = comparison.requests.iter().position(|r| r.suffix == suffix) {
346 let slot = &mut comparison.answers[o];
347 if slot.is_some() {
348 return Err(ScheduleError::AlreadyAnswered);
349 }
350 *slot = Some(answer);
351 return Ok(());
352 }
353 }
354 Err(ScheduleError::UnknownRequest)
355 }
356
357 pub fn state(&mut self) -> Result<ScheduleState, ScheduleError> {
358 if let Some(state) = &self.terminal {
359 return Ok(state.clone());
360 }
361 let ready: Vec<_> = self
362 .pending
363 .iter()
364 .filter_map(|(&key, c)| c.winner().map(|winner| (key, c.pair, winner)))
365 .collect();
366 for (key, pair, winner) in ready {
367 self.pending.remove(&key);
368 if let Some(winner) = winner {
369 self.wins[winner] += 1;
370 let loser = if winner == pair[0] { pair[1] } else { pair[0] };
371 self.edges.insert((winner, loser));
372 } else {
373 self.inconsistent += 1;
374 }
375 if let Some(merge) = self.merges.get_mut(key.1) {
376 let winner = winner.unwrap_or_else(|| {
377 self.tie
378 .iter()
379 .copied()
380 .find(|p| pair.contains(p))
381 .unwrap_or(pair[0])
382 });
383 if merge.left.front() == Some(&winner) {
384 merge.left.pop_front();
385 } else {
386 merge.right.pop_front();
387 }
388 merge.out.push(winner);
389 merge.n += 1;
390 merge.drain_tail();
391 }
392 }
393 if self.schedule == PairwiseSchedule::MergeSort {
394 if self.merges.iter().all(|m| m.pair().is_none()) {
395 let runs: Vec<_> = self.merges.iter().map(|m| m.out.clone()).collect();
396 if runs.len() <= 1 {
397 return Ok(self.finish(runs.into_iter().next().unwrap_or_default()));
398 }
399 self.level += 1;
400 self.start_level(runs);
401 }
402 let next: Vec<_> = self
403 .merges
404 .iter()
405 .enumerate()
406 .filter_map(|(m, merge)| {
407 let key = (self.level, m, merge.n);
408 merge
409 .pair()
410 .filter(|_| !self.pending.contains_key(&key))
411 .map(|pair| (key, pair))
412 })
413 .collect();
414 if self.issued + next.len() as u64 * self.context.orders() > self.cap {
417 let state = ScheduleState::BudgetExhausted {
418 comparisons: self.issued,
419 };
420 self.terminal = Some(state.clone());
421 return Ok(state);
422 }
423 for (key, pair) in next {
424 self.issue(key, pair)?;
425 }
426 } else if self.pending.is_empty() {
427 let mut order = self.tie.clone();
428 order.sort_by_key(|&p| std::cmp::Reverse(self.wins[p]));
429 return Ok(self.finish(order));
430 }
431 Ok(ScheduleState::Pending(
432 self.pending
433 .values()
434 .flat_map(|c| {
435 c.requests
436 .iter()
437 .zip(&c.answers)
438 .filter(|(_, a)| a.is_none())
439 .map(|(r, _)| r.clone())
440 })
441 .collect(),
442 ))
443 }
444
445 fn finish(&mut self, order: Vec<usize>) -> ScheduleState {
446 let cyclic_triples = if self.schedule == PairwiseSchedule::MergeSort {
447 CycleCount::NotObservable
448 } else {
449 let mut count = 0;
450 for a in 0..self.pool.len() {
451 for b in a + 1..self.pool.len() {
452 for c in b + 1..self.pool.len() {
453 let edge = |x, y| self.edges.contains(&(x, y));
454 if (edge(a, b) && edge(b, c) && edge(c, a))
455 || (edge(b, a) && edge(c, b) && edge(a, c))
456 {
457 count += 1;
458 }
459 }
460 }
461 }
462 CycleCount::Count(count)
463 };
464 let state = ScheduleState::Complete(PairwiseResult {
465 order: order.into_iter().map(|p| self.pool[p].clone()).collect(),
466 wins: self.wins.clone(),
467 comparisons: self.issued,
468 inconsistent_pairs: self.inconsistent,
469 cyclic_triples,
470 });
471 self.terminal = Some(state.clone());
472 state
473 }
474}