alass_core/
alass.rs

1// This file is part of the Rust library and binary `alass`.
2//
3// Copyright (C) 2017 kaegi
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18use 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
27/// Use this trait if you want more detailed information about the progress of the align operation
28/// (which might take some seconds).
29pub trait ProgressHandler {
30    /// Will be called one time before `inc()` is called. `steps` is the
31    /// number of times `inc()` will be called.
32    ///
33    /// The number of steps is around the number of lines in the "incorrect" subtitle.
34    /// Be aware that this number can be zero!
35    #[allow(unused_variables)]
36    fn init(&mut self, steps: i64) {}
37
38    /// We made (small) progress!
39    fn inc(&mut self) {}
40
41    /// Will be called after the last `inc()`, when `inc()` was called `steps` times.
42    fn finish(&mut self) {}
43}
44
45#[derive(Debug, Clone, Eq, PartialEq, Hash)]
46/// `impl ProgressHandler` with empty functions
47pub struct NoProgressHandler;
48impl ProgressHandler for NoProgressHandler {}
49
50/// The "main" structure which holds the infomation needed to align the subtitles to each other.
51pub 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 ta = std::time::Instant::now();
80        let mut deltas: Vec<RatingDeltaDelta> = vec![RatingDeltaDelta::zero(); len + 1];
81
82        //let tb = std::time::Instant::now();
83        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 tc = std::time::Instant::now();
121
122        // compute maximum rating
123        let mut delta: RatingDelta = RatingDelta::zero();
124        let mut rating: Rating = Rating::zero();
125        let mut maximum: (Rating, TimeDelta) = (Rating::zero(), min_offset);
126        //let mut nonzero: i64 = 0;
127        for (sigma, jump_value) in deltas.into_iter().enumerate() {
128            /*if !RatingDeltaDelta::is_zero(jump_value) {
129                nonzero = nonzero + 1;
130            }*/
131            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        /*let td = std::time::Instant::now();
139        println!("init {}ms", (tb - ta).as_millis());
140        println!("insert {}ms", (tc - tb).as_millis());
141        println!("calc {}ms", (td - tc).as_millis());
142
143        println!(
144            "{}MB {}% nonzero {}% max",
145            len * std::mem::size_of::<RatingDeltaDelta>() / (1024 * 1024),
146            nonzero as f64 / len as f64 * 100.0,
147            (self.list.len() * self.reference.len() * 4) as f64 / len as f64 * 100.0
148        );*/
149
150        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        // test if all delta correct arrays are sorted (should be true)
239        /*for dc in &delta_corrects {
240            for (a, b) in dc.iter().zip(dc.iter().skip(1)) {
241                assert!(a.time < b.time);
242            }
243        }
244        println!("b");*/
245
246        // we now have "4 * len(incorrect_list)" sorted arrays with each "4 * len(reference_list)" elements
247        //  -> sort with max heap (pop from end)
248
249        // in heap sort implementation, the delta corrects are sorted descending by time, in the "simple" sort ascending
250        let mut all_delta_corrects: Vec<DeltaCorrect>;
251        let sorted_delta_corrects_iter; // : impl Iter<DeltaCorrect>
252        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                    // are all vectors empty?
311                    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        // compute maximum rating
334        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            //println!("rating: {}", rating);
342            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        // For each segment the full rating can only be 1. So the maximum rating
370        // without the split penalty is `min(list.len(), reference.len())`. So to get
371        // from the normalized rating `[0, 1]` to a unnormalized rating (where only
372        // values between `[0, max_rating]` are interesting) we multiply by
373        // `min(list.len(), reference.len())`.
374
375        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        // these buffers save the offsets of a subtitle line dependent on the offset of the next line,
386        //  -> this allows to compute the final corrected line offsets
387        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()); // otherwise shift_simple/extend_to creates a zero-length segment
398            assert!(incorrect_span.len() > TimeDelta::zero()); // otherwise shift_simple/extend_to creates a zero-length segment
399
400            let span_distance = incorrect_span.start - last_incorrect_span.end;
401
402            assert!(span_distance >= TimeDelta::zero()); // otherwise shift_simple/extend_to creates a zero-length segment
403            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                //.clamp_end(self.get_max_offset())
410                .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            //.simplify()
424            //.discard_start_times();
425
426            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            /*println!(
442                "Last rating buffer length: {}",
443                combined_maximum_buffer.rating_buffer.buffer.len()
444            );*/
445
446            offset_buffers.push(combined_maximum_buffer.offset_buffer);
447
448            progress_handler.inc();
449        }
450
451        // ------------------------------------------------------------------------------
452        // Extract the best offset for each incorrect span from offset buffers
453
454        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        //let sum: usize = offset_buffers.iter().map(|ob| ob.len()).sum();
462        //println!("{} {}MB", sum, (sum * std::mem::size_of::<crate::segments::OffsetSegment>()) as f64 / (1024 * 1024) as f64);
463
464        for offset_buffer in offset_buffers.into_iter().rev() {
465            span_offset = offset_buffer.get_offset_at(span_offset);
466
467            // Due to ''aggressive optimization'' of the rating curve in each step
468            // the maximum of the nosplit curve might be at the start. Then the
469            // annotated offsets are negative. In that case - to avoid a
470            // out of bounds error - we simply copy the current result delta
471            // to all remaining spans.
472            /*if span_offset < self.get_min_offset() {
473                span_offset = std::cmp::max(span_offset, self.get_start());
474                let error_delta = span_offset - incorrect_span.start;
475                for _ in 0..self.list.len() - result_deltas.len() {
476                    result_deltas.push(error_delta);
477                }
478                break;
479            }*/
480
481            result_deltas.push(span_offset);
482        }
483
484        // the deltas were inserted back-to-front
485        result_deltas.reverse();
486
487        progress_handler.finish();
488
489        (result_deltas, total_rating)
490    }
491
492    /// Requires "start1 <= start2". Returns the compressed rating vector for
493    /// the overlapping ratings of a timespan of length
494    /// "length" on all start offset from "start1" to "start2".
495    ///
496    /// This function has O(n) runtime, where n is the number of spans in the
497    /// reference list.
498
499    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        // If we fix one timespan and let an other timespan variable, we get such a
507        // curve for the rating:
508        //
509        //          / --------- \
510        //         /             \
511        // -------                --------------------------
512        //
513        // at first the rating be zero, then rise linearly, then it will be constant
514        // for a time and then fall to zero again
515        //
516        // The next function will return these special changepoints and their
517        // "delta"-change (delta-delta).
518        // Because the timespans in "self.reference" are sorted and non overlapping,
519        // the changepoints of a certain type (first rise, start of constant, ...)
520        // will also be sorted. That means we only have to compare the current first
521        // changepoints of each type to get the really first
522        // changepoint. We then apply this changepoint-delta to the current total delta
523        // and add the segment with the
524        // previous total delta to the buffer. This way we get the segments with the
525        // same delta very efficently in O(n).
526
527        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        // standard merge sort
542        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        // the rating values are continuous, so the first value of a segment is the
587        // last value of the previous segment.
588        // To avoid having each of these segment-break values two times in the buffer,
589        // every segments stops one timepoint
590        // before the real segment end. The real segment end is then the first value of
591        // the next value.
592        //
593        // The last rating has to be 0, so we extend the last segment with the missing
594        // timepoint.
595        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            // this is unlikely
614            if ts.is_empty() {
615                continue;
616            }
617
618            // new will return None, if both lists are empty -> highly unlikely
619            return ts;
620        }
621    }
622
623    #[test]
624    /// Aligns random timespans to each other and calls alass. General test whether any internal
625    /// assertions are invalidated.
626    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                //assert_eq!(dbg!(last.data.delta), RatingDelta::zero());
656            }
657        }
658    }
659
660    /*#[test]
661    /// `get_compressed_overlapping_ratings()` is highly optimized -> compare the results of slow and fast
662    /// implemntations.
663    fn get_compressed_overlapping_ratings() {
664        let mut rng = rand::thread_rng();
665
666        for _ in 0..30 {
667            let alass = get_dummy_aligner();
668            let len: i64 = (rng.next_u32() % 100) as i64;
669            let rating_buffer1 = alass.get_compressed_overlapping_ratings(
670                alass.get_start(),
671                alass.get_end(),
672                TimeDelta::one() * len,
673            );
674            let rating_buffer2 = alass.get_compressed_overlapping_ratings_slow(
675                alass.get_start(),
676                alass.get_end(),
677                TimeDelta::one() * len,
678            );
679            assert_eq!(
680                rating_buffer1.iter().collect::<Vec<_>>(),
681                rating_buffer2.iter().collect::<Vec<_>>()
682            );
683        }
684    }*/
685}