#![deny(
missing_debug_implementations,
trivial_casts,
unstable_features,
unused_import_braces,
unused_qualifications
)]
#![allow(unknown_lints)]
#[cfg(test)]
extern crate rand;
mod alass;
mod rating_type;
#[allow(dead_code)]
mod segments;
mod time_types;
mod timespan_ops;
use crate::alass::Aligner;
pub use crate::alass::NoProgressHandler;
pub use crate::alass::ProgressHandler;
use crate::rating_type::{Rating, RatingDelta, RatingExt};
pub use crate::time_types::{TimeDelta, TimePoint, TimeSpan};
use crate::timespan_ops::prepare_time_spans;
use std::cmp::{max, min};
fn denormalize_split_penalty(ref_list_len: usize, in_list_len: usize, split_penalty_normalized: f64) -> RatingDelta {
RatingDelta::convert_from_f64(min(ref_list_len, in_list_len) as f64 * split_penalty_normalized / 1000.0)
}
pub type Score = f64;
pub fn standard_scoring(a: TimeDelta, b: TimeDelta) -> Score {
let min: f64 = min(a, b).as_f64();
let max: f64 = max(a, b).as_f64();
min / max
}
pub fn overlap_scoring(a: TimeDelta, b: TimeDelta) -> Score {
let min: f64 = min(a, b).as_f64();
min * 0.00001
}
pub fn align_nosplit(
reference: &[TimeSpan],
list: &[TimeSpan],
score_fn: impl Fn(TimeDelta, TimeDelta) -> f64 + Copy,
mut progress_handler: impl ProgressHandler,
) -> (TimeDelta, Score) {
progress_handler.init(1);
let (ref_nonoverlapping, _) = prepare_time_spans(reference);
let (list_nonoverlapping, _) = prepare_time_spans(list);
if list_nonoverlapping.is_empty() || ref_nonoverlapping.is_empty() {
return (TimeDelta::zero(), 0.);
}
let (delta, score) = Aligner::align_constant_delta(&ref_nonoverlapping, &list_nonoverlapping, score_fn);
progress_handler.inc();
progress_handler.finish();
return (delta, score.as_readable_f64());
}
pub fn align(
reference: &[TimeSpan],
list: &[TimeSpan],
split_penalty: f64,
speed_optimization: Option<f64>,
score_fn: impl Fn(TimeDelta, TimeDelta) -> f64 + Copy,
progress_handler: impl ProgressHandler,
) -> (Vec<TimeDelta>, f64) {
let (list_nonoverlapping, list_indices) = prepare_time_spans(&list);
let (ref_nonoverlapping, _) = prepare_time_spans(&reference);
if list_nonoverlapping.is_empty() || ref_nonoverlapping.is_empty() {
return (vec![TimeDelta::zero(); list.len()], 0.);
}
let nosplit_bonus = denormalize_split_penalty(ref_nonoverlapping.len(), list_nonoverlapping.len(), split_penalty);
let (deltas, score) = Aligner::align_with_splits(
&ref_nonoverlapping,
&list_nonoverlapping,
nosplit_bonus,
speed_optimization,
score_fn,
progress_handler,
);
(
list_indices.into_iter().map(|i| deltas[i]).collect(),
score.as_readable_f64(),
)
}
pub fn get_split_rating(
ref_spans: &[TimeSpan],
in_spans: &[TimeSpan],
offets: &[TimeDelta],
split_penalty: f64,
score_fn: impl Fn(TimeDelta, TimeDelta) -> f64 + Copy,
) -> Score {
let mut total_rating = get_nosplit_rating_iter(ref_spans.iter().cloned(), in_spans.iter().cloned(), score_fn);
let nosplit_bonus = denormalize_split_penalty(ref_spans.len(), in_spans.len(), split_penalty);
total_rating = Rating::add_mul_usize(
total_rating,
-nosplit_bonus,
offets
.iter()
.cloned()
.zip(offets.iter().skip(1).cloned())
.filter(|(o1, o2)| o1 != o2)
.count(),
);
total_rating.as_readable_f64()
}
pub fn get_nosplit_score(
ref_spans: impl Iterator<Item = TimeSpan>,
in_spans: impl Iterator<Item = TimeSpan>,
score_fn: impl Fn(TimeDelta, TimeDelta) -> f64 + Copy,
) -> Score {
get_nosplit_rating_iter(ref_spans, in_spans, score_fn).as_readable_f64()
}
fn get_nosplit_rating_iter(
mut ref_spans: impl Iterator<Item = TimeSpan>,
mut in_spans: impl Iterator<Item = TimeSpan>,
score_fn: impl Fn(TimeDelta, TimeDelta) -> f64 + Copy,
) -> Rating {
let mut total_rating = Rating::zero();
let mut ref_span;
let mut in_span;
let ref_span_opt = ref_spans.next();
match ref_span_opt {
None => return total_rating,
Some(v) => ref_span = v,
}
let in_span_opt = in_spans.next();
match in_span_opt {
None => return total_rating,
Some(v) => in_span = v,
}
loop {
let rating = Rating::from_timespans(ref_span, in_span, score_fn);
total_rating += rating;
if ref_span.end() <= in_span.end() {
let ref_span_opt = ref_spans.next();
match ref_span_opt {
None => return total_rating,
Some(v) => ref_span = v,
}
} else {
let in_span_opt = in_spans.next();
match in_span_opt {
None => return total_rating,
Some(v) => in_span = v,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{prepare_time_spans, TimePoint};
use rand;
use rand::RngCore;
fn predefined_time_spans() -> Vec<Vec<TimeSpan>> {
let t0 = TimePoint::from(0);
let t1000 = TimePoint::from(1000);
let t2000 = TimePoint::from(2000);
vec![
vec![],
vec![TimeSpan::new(t0, t0)],
vec![TimeSpan::new(t0, t1000)],
vec![TimeSpan::new(t0, t1000), TimeSpan::new(t1000, t1000)],
vec![
TimeSpan::new(t0, t1000),
TimeSpan::new(t1000, t1000),
TimeSpan::new(t1000, t2000),
],
vec![TimeSpan::new(t1000, t1000), TimeSpan::new(t1000, t1000)],
]
}
fn generate_random_time_spans() -> Vec<TimeSpan> {
let mut rng = rand::thread_rng();
let len: usize = (rng.next_u32() % 400) as usize;
let mut v = Vec::with_capacity(len);
let mut current_pos = 0i64;
for _ in 0..len {
current_pos += (rng.next_u32() % 200) as i64 - 50;
let current_len = (rng.next_u32() % 400) as i64;
v.push(TimeSpan::new(
TimePoint::from(current_pos),
TimePoint::from(current_pos + current_len),
));
}
v
}
pub fn get_test_time_spans() -> Vec<Vec<TimeSpan>> {
(0..1000)
.map(|_| generate_random_time_spans())
.chain(predefined_time_spans().into_iter())
.collect()
}
pub fn get_random_prepared_test_time_spans() -> Vec<TimeSpan> {
prepare_time_spans(&generate_random_time_spans()).0
}
}