1use crate::rating_type::{Rating, RatingDelta, RatingDeltaDelta, RatingDeltaExt, RatingExt};
19use crate::segments::{
20 combined_maximum_of_dual_iterators, DifferentialRatingBufferBuilder, OffsetBuffer, RatingBuffer, RatingIterator,
21 RatingSegment, SeparateDualBuffer,
22};
23use crate::time_types::{TimeDelta, TimePoint, TimeSpan};
24
25use std::convert::TryInto;
26
27pub trait ProgressHandler {
30 #[allow(unused_variables)]
36 fn init(&mut self, steps: i64) {}
37
38 fn inc(&mut self) {}
40
41 fn finish(&mut self) {}
43}
44
45#[derive(Debug, Clone, Eq, PartialEq, Hash)]
46pub struct NoProgressHandler;
48impl ProgressHandler for NoProgressHandler {}
49
50pub struct Aligner;
52
53impl Aligner {
54 pub fn get_offsets_bounds(ref_spans: &[TimeSpan], in_spans: &[TimeSpan]) -> (TimeDelta, TimeDelta) {
55 assert!(ref_spans.len() > 0);
56 assert!(in_spans.len() > 0);
57
58 let in_start: TimePoint = (*in_spans.first().unwrap()).start();
59 let in_end: TimePoint = (*in_spans.last().unwrap()).end();
60
61 let ref_start: TimePoint = (*ref_spans.first().unwrap()).start();
62 let ref_end: TimePoint = (*ref_spans.last().unwrap()).end();
63
64 assert!(in_start <= in_end);
65 assert!(ref_start <= ref_end);
66
67 (ref_start - in_end, ref_end - in_start)
68 }
69
70 pub fn align_constant_delta_bucket_sort(
71 ref_spans: &[TimeSpan],
72 in_spans: &[TimeSpan],
73 score_fn: impl Fn(TimeDelta, TimeDelta) -> f64 + Copy,
74 ) -> (TimeDelta, Rating) {
75 let (min_offset, max_offset) = Self::get_offsets_bounds(ref_spans, in_spans);
76
77 let len: usize = (max_offset - min_offset).as_i64().try_into().unwrap();
78
79 let mut deltas: Vec<RatingDeltaDelta> = vec![RatingDeltaDelta::zero(); len + 1];
81
82 for reference_ts in ref_spans {
84 for incorrect_ts in in_spans {
85 let rating_delta_delta: RatingDeltaDelta =
86 RatingDelta::compute_rating_delta(incorrect_ts.len(), reference_ts.len(), score_fn);
87
88 #[inline(always)]
89 fn accum(d: &mut [RatingDeltaDelta], idx: TimeDelta, x: RatingDeltaDelta, sigma_min: TimeDelta) {
90 let idx: usize = (idx - sigma_min).as_i64().try_into().unwrap();
91 d[idx] = d[idx] + x;
92 };
93
94 accum(
95 &mut deltas,
96 reference_ts.start() - incorrect_ts.end(),
97 rating_delta_delta,
98 min_offset,
99 );
100 accum(
101 &mut deltas,
102 reference_ts.end() - incorrect_ts.end(),
103 -rating_delta_delta,
104 min_offset,
105 );
106 accum(
107 &mut deltas,
108 reference_ts.start() - incorrect_ts.start(),
109 -rating_delta_delta,
110 min_offset,
111 );
112 accum(
113 &mut deltas,
114 reference_ts.end() - incorrect_ts.start(),
115 rating_delta_delta,
116 min_offset,
117 );
118 }
119 }
120 let mut delta: RatingDelta = RatingDelta::zero();
124 let mut rating: Rating = Rating::zero();
125 let mut maximum: (Rating, TimeDelta) = (Rating::zero(), min_offset);
126 for (sigma, jump_value) in deltas.into_iter().enumerate() {
128 rating += delta;
132 delta += jump_value;
133 if rating > maximum.0 {
134 maximum = (rating, sigma as i64 * TimeDelta::one() + min_offset);
135 }
136 }
137
138 assert!(Rating::is_zero(rating));
151
152 return (maximum.1, maximum.0);
153 }
154
155 pub fn align_constant_delta(
156 ref_spans: &[TimeSpan],
157 in_spans: &[TimeSpan],
158 score_fn: impl Fn(TimeDelta, TimeDelta) -> f64 + Copy,
159 ) -> (TimeDelta, Rating) {
160 let (min_offset, max_offset) = Self::get_offsets_bounds(ref_spans, in_spans);
161
162 let num_slots: usize = TryInto::<usize>::try_into((max_offset - min_offset).as_i64()).unwrap();
163 let num_entries: usize = in_spans.len() * ref_spans.len() * 4;
164
165 if num_entries as f64 > num_slots as f64 * 0.1 {
166 Self::align_constant_delta_bucket_sort(ref_spans, in_spans, score_fn)
167 } else {
168 Self::align_constant_delta_merge_sort(ref_spans, in_spans, score_fn)
169 }
170 }
171
172 pub fn align_constant_delta_merge_sort(
173 ref_spans: &[TimeSpan],
174 in_spans: &[TimeSpan],
175 score_fn: impl Fn(TimeDelta, TimeDelta) -> f64 + Copy,
176 ) -> (TimeDelta, Rating) {
177 #[derive(PartialEq, Eq, Clone)]
178 struct DeltaCorrect {
179 rating: RatingDeltaDelta,
180 time: TimeDelta,
181 }
182
183 impl DeltaCorrect {
184 fn new(rating: RatingDeltaDelta, time: TimeDelta) -> DeltaCorrect {
185 DeltaCorrect {
186 rating: rating,
187 time: time,
188 }
189 }
190 }
191
192 type OrderedDeltaCorrect = Vec<DeltaCorrect>;
193
194 let mut delta_corrects: Vec<OrderedDeltaCorrect> = Vec::new();
195
196 for incorrect_ts in in_spans {
197 let mut rise_ordered_delta_corrects: OrderedDeltaCorrect = OrderedDeltaCorrect::new();
198 let mut up_ordered_delta_corrects: OrderedDeltaCorrect = OrderedDeltaCorrect::new();
199 let mut fall_ordered_delta_corrects: OrderedDeltaCorrect = OrderedDeltaCorrect::new();
200 let mut down_ordered_delta_corrects: OrderedDeltaCorrect = OrderedDeltaCorrect::new();
201
202 for reference_ts in ref_spans {
203 let rise_time;
204 let up_time;
205 let fall_time;
206 let down_time;
207
208 if incorrect_ts.len() < reference_ts.len() {
209 rise_time = reference_ts.start() - incorrect_ts.len();
210 up_time = reference_ts.start();
211 fall_time = reference_ts.end() - incorrect_ts.len();
212 down_time = reference_ts.end();
213 } else {
214 rise_time = reference_ts.start() - incorrect_ts.len();
215 up_time = reference_ts.end() - incorrect_ts.len();
216 fall_time = reference_ts.start();
217 down_time = reference_ts.end();
218 }
219
220 let rating_delta_delta: RatingDeltaDelta =
221 RatingDelta::compute_rating_delta(incorrect_ts.len(), reference_ts.len(), score_fn);
222
223 rise_ordered_delta_corrects
224 .push(DeltaCorrect::new(rating_delta_delta, rise_time - incorrect_ts.start()));
225 up_ordered_delta_corrects.push(DeltaCorrect::new(-rating_delta_delta, up_time - incorrect_ts.start()));
226 fall_ordered_delta_corrects
227 .push(DeltaCorrect::new(-rating_delta_delta, fall_time - incorrect_ts.start()));
228 down_ordered_delta_corrects
229 .push(DeltaCorrect::new(rating_delta_delta, down_time - incorrect_ts.start()));
230 }
231
232 delta_corrects.push(rise_ordered_delta_corrects);
233 delta_corrects.push(up_ordered_delta_corrects);
234 delta_corrects.push(fall_ordered_delta_corrects);
235 delta_corrects.push(down_ordered_delta_corrects);
236 }
237
238 let mut all_delta_corrects: Vec<DeltaCorrect>;
251 let sorted_delta_corrects_iter; let first_delta_correct: DeltaCorrect;
253
254 #[cfg(not(feature = "nosplit-heap-sort"))]
255 {
256 all_delta_corrects = delta_corrects.into_iter().flat_map(|dc| dc).collect();
257 all_delta_corrects.sort_unstable_by_key(|dc| dc.time);
258
259 first_delta_correct = all_delta_corrects
260 .first()
261 .cloned()
262 .expect("delta corrects should have at least one element");
263
264 sorted_delta_corrects_iter = all_delta_corrects.iter();
265 }
266
267 #[cfg(feature = "nosplit-heap-sort")]
268 {
269 use std::cmp::Ordering;
270 use std::collections::BinaryHeap;
271
272 #[derive(PartialEq, Eq)]
273 struct MaxHeapInfo {
274 heap_id: usize,
275 data: DeltaCorrect,
276 }
277
278 impl Ord for MaxHeapInfo {
279 fn cmp(&self, other: &MaxHeapInfo) -> Ordering {
280 TimeDelta::cmp(&self.data.time, &other.data.time)
281 }
282 }
283
284 impl PartialOrd for MaxHeapInfo {
285 fn partial_cmp(&self, other: &MaxHeapInfo) -> Option<Ordering> {
286 Some(self.cmp(other))
287 }
288 }
289
290 let mut heap = BinaryHeap::new();
291
292 for (heap_id, data) in delta_corrects.iter_mut().enumerate() {
293 let last_elem: DeltaCorrect = data
294 .pop()
295 .expect("at least one element should be in every delta correct list");
296 heap.push(MaxHeapInfo {
297 heap_id: heap_id,
298 data: last_elem,
299 });
300 }
301
302 all_delta_corrects = Vec::with_capacity(4 * self.list.len() * self.reference.len());
303
304 loop {
305 let max_heap_elem: MaxHeapInfo;
306
307 match heap.pop() {
308 Some(x) => max_heap_elem = x,
309
310 None => break,
312 }
313
314 all_delta_corrects.push(max_heap_elem.data);
315
316 if let Some(new_delta_correct) = delta_corrects[max_heap_elem.heap_id].pop() {
317 heap.push(MaxHeapInfo {
318 heap_id: max_heap_elem.heap_id,
319 data: new_delta_correct,
320 });
321 }
322 }
323
324 assert!(all_delta_corrects.len() == 4 * self.list.len() * self.reference.len());
325 sorted_delta_corrects_iter = all_delta_corrects.iter().rev();
326
327 first_delta_correct = all_delta_corrects
328 .last()
329 .cloned()
330 .expect("delta corrects should have at least one element");
331 }
332
333 let mut delta: RatingDelta = RatingDelta::zero();
335 let mut rating: Rating = Rating::zero();
336 let mut maximum: (Rating, TimeDelta) = (Rating::zero(), first_delta_correct.time);
337 for (delta_correct, next_delta_correct) in sorted_delta_corrects_iter
338 .clone()
339 .zip(sorted_delta_corrects_iter.skip(1))
340 {
341 delta = delta + delta_correct.rating;
343 rating = Rating::add_mul(rating, delta, next_delta_correct.time - delta_correct.time);
344 if rating > maximum.0 {
345 maximum = (rating, next_delta_correct.time);
346 }
347 }
348
349 assert!(Rating::is_zero(rating));
350
351 return (maximum.1, maximum.0);
352 }
353
354 #[cfg(feature = "statistics")]
355 pub fn do_statistics(&self, f: impl Fn(&mut Statistics) -> std::io::Result<()>) {
356 if let Some(statistics) = &self.statistics {
357 f(&mut statistics.borrow_mut()).expect("failed to write statistics");
358 }
359 }
360
361 pub fn align_with_splits(
362 ref_spans: &[TimeSpan],
363 in_spans: &[TimeSpan],
364 split_penalty: RatingDelta,
365 speed_optimization_opt: Option<f64>,
366 score_fn: impl Fn(TimeDelta, TimeDelta) -> f64 + Copy,
367 mut progress_handler: impl ProgressHandler,
368 ) -> (Vec<TimeDelta>, Rating) {
369 assert!(in_spans.len() > 0);
376 assert!(ref_spans.len() > 0);
377
378 progress_handler.init(in_spans.len() as i64);
379
380 let speed_optimization = speed_optimization_opt.unwrap_or(0.0);
381
382 let (min_offset, max_offset) = Self::get_offsets_bounds(ref_spans, in_spans);
383 let (min_offset, max_offset) = (min_offset - TimeDelta::one(), max_offset + TimeDelta::one());
384
385 let mut offset_buffers: Vec<OffsetBuffer> = Vec::new();
388
389 let mut culmulative_rating_buffer: RatingBuffer =
390 Self::single_span_ratings(ref_spans, in_spans[0], score_fn, min_offset, max_offset).save();
391
392 progress_handler.inc();
393
394 for (line_nr, (&last_incorrect_span, &incorrect_span)) in
395 in_spans.iter().zip(in_spans.iter().skip(1)).enumerate()
396 {
397 assert!(last_incorrect_span.len() > TimeDelta::zero()); assert!(incorrect_span.len() > TimeDelta::zero()); let span_distance = incorrect_span.start - last_incorrect_span.end;
401
402 assert!(span_distance >= TimeDelta::zero()); assert!(culmulative_rating_buffer.first_end_point().unwrap() - min_offset > span_distance);
404
405 let best_split_offsets = culmulative_rating_buffer
406 .iter()
407 .add_rating(-split_penalty)
408 .shift_simple(-span_distance)
409 .extend_to(max_offset)
411 .annotate_with_segment_start_points()
412 .annotate_with_offset_info(|offset| offset + span_distance)
413 .left_to_right_maximum()
414 .discard_start_times()
415 .simplify()
416 .discard_start_times();
417
418 let nosplit_offsets = culmulative_rating_buffer
419 .iter()
420 .annotate_with_segment_start_points()
421 .annotate_with_offset_info(|offset| offset)
422 .discard_start_times();
423 let single_span_ratings =
427 Self::single_span_ratings(ref_spans, incorrect_span, score_fn, min_offset, max_offset).save();
428
429 let progress_factor = (line_nr + 1) as f64 / in_spans.len() as f64;
430 let epsilon = Rating::convert_from_f64(speed_optimization * 0.05 * (progress_factor * 0.8 + 0.2));
431
432 let combined_maximum_buffer: SeparateDualBuffer =
433 combined_maximum_of_dual_iterators(nosplit_offsets, best_split_offsets)
434 .discard_start_times()
435 .add_ratings_from(single_span_ratings.iter())
436 .discard_start_times()
437 .save_separate(epsilon);
438
439 culmulative_rating_buffer = combined_maximum_buffer.rating_buffer;
440
441 offset_buffers.push(combined_maximum_buffer.offset_buffer);
447
448 progress_handler.inc();
449 }
450
451 assert!(offset_buffers.len() == in_spans.len() - 1);
455
456 let (total_rating, mut span_offset) = culmulative_rating_buffer.maximum();
457
458 let mut result_deltas = Vec::new();
459 result_deltas.push(span_offset);
460
461 for offset_buffer in offset_buffers.into_iter().rev() {
465 span_offset = offset_buffer.get_offset_at(span_offset);
466
467 result_deltas.push(span_offset);
482 }
483
484 result_deltas.reverse();
486
487 progress_handler.finish();
488
489 (result_deltas, total_rating)
490 }
491
492 fn single_span_ratings(
500 ref_spans: &[TimeSpan],
501 in_span: TimeSpan,
502 score_fn: impl Fn(TimeDelta, TimeDelta) -> f64 + Copy,
503 min_offset: TimeDelta,
504 max_offset: TimeDelta,
505 ) -> RatingIterator<impl Iterator<Item = RatingSegment>> {
506 let mut builder = DifferentialRatingBufferBuilder::new(min_offset, max_offset);
528 let len = ref_spans.len();
529 let mut timepoints: Vec<Option<(TimeDelta, RatingDeltaDelta)>> = vec![None; 4 * len];
530 for (i, &ref_span) in ref_spans.iter().enumerate() {
531 let rise_delta = RatingDelta::compute_rating_delta(ref_span.len(), in_span.len(), score_fn);
532
533 timepoints[0 * len + i] = Some((ref_span.start() - in_span.end(), rise_delta));
534 timepoints[1 * len + i] = Some((ref_span.end() - in_span.end(), -rise_delta));
535 timepoints[2 * len + i] = Some((ref_span.start() - in_span.start(), -rise_delta));
536 timepoints[3 * len + i] = Some((ref_span.end() - in_span.start(), rise_delta));
537 }
538
539 let timepoints: Vec<(TimeDelta, RatingDeltaDelta)> = timepoints.into_iter().map(|x| x.unwrap()).collect();
540
541 fn merge(
543 a: &[(TimeDelta, RatingDeltaDelta)],
544 b: &[(TimeDelta, RatingDeltaDelta)],
545 ) -> Vec<(TimeDelta, RatingDeltaDelta)> {
546 let mut ai = 0;
547 let mut bi = 0;
548 let mut result = Vec::with_capacity(a.len() + b.len());
549 loop {
550 if ai == a.len() && bi == b.len() {
551 return result;
552 }
553 if bi == b.len() {
554 while ai < a.len() {
555 result.push(a[ai]);
556 ai = ai + 1;
557 }
558 return result;
559 }
560 if ai == a.len() {
561 while bi < b.len() {
562 result.push(b[bi]);
563 bi = bi + 1;
564 }
565 return result;
566 }
567 if a[ai].0 <= b[bi].0 {
568 result.push(a[ai]);
569 ai = ai + 1;
570 } else {
571 result.push(b[bi]);
572 bi = bi + 1;
573 }
574 }
575 }
576
577 let x = merge(&timepoints[len * 0..len * 1], &timepoints[len * 1..len * 2]);
578 let y = merge(&timepoints[len * 2..len * 3], &timepoints[len * 3..len * 4]);
579
580 let timepoints = merge(&x, &y);
581
582 for (segment_end, segment_end_delta_delta) in timepoints {
583 builder.add_segment(segment_end, segment_end_delta_delta);
584 }
585
586 builder.extend_to_end();
596
597 builder.build().into_rating_iter()
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604
605 use crate::rating_type::RatingExt;
606 use crate::segments::RatingFullSegment;
607 use crate::tests::get_random_prepared_test_time_spans;
608
609 fn get_dummy_spans() -> Vec<TimeSpan> {
610 loop {
611 let ts = get_random_prepared_test_time_spans();
612
613 if ts.is_empty() {
615 continue;
616 }
617
618 return ts;
620 }
621 }
622
623 #[test]
624 fn run_aligner() {
627 for _ in 0..40 {
628 let (ref_spans, in_spans) = (get_dummy_spans(), get_dummy_spans());
629 Aligner::align_with_splits(
630 &ref_spans,
631 &in_spans,
632 RatingDelta::convert_from_f64(0.001),
633 None,
634 crate::standard_scoring,
635 NoProgressHandler,
636 );
637 }
638 }
639
640 #[test]
641 fn test_single_span_ratings() {
642 for _ in 0..30 {
643 let (ref_spans, in_spans) = (get_dummy_spans(), get_dummy_spans());
644 let (min_offset, max_offset) = Aligner::get_offsets_bounds(&ref_spans, &in_spans);
645 let (min_offset, max_offset) = (min_offset - TimeDelta::one(), max_offset + TimeDelta::one());
646
647 for in_span in in_spans {
648 let last: RatingFullSegment =
649 Aligner::single_span_ratings(&ref_spans, in_span, crate::standard_scoring, min_offset, max_offset)
650 .annotate_with_segment_start_points()
651 .into_iter()
652 .last()
653 .unwrap();
654 assert!(Rating::is_zero(last.end_rating()));
655 }
657 }
658 }
659
660 }