use std::ops::Range;
use ndarray::{ArrayView1, ArrayView2};
use crate::cost::tree::KthSmallestTree;
pub struct L1Cost1D {
kth_smallest_tree: KthSmallestTree,
}
impl L1Cost1D {
#[inline]
pub(crate) fn precalculate(signal: &ArrayView1<f64>) -> Self {
let kth_smallest_tree = KthSmallestTree::build(signal);
Self { kth_smallest_tree }
}
#[inline]
pub(crate) fn loss(&self, total_loss: &mut f64, signal: &ArrayView1<f64>, range: Range<usize>) {
let median = self.median(range.clone());
signal
.slice(ndarray::s!(range))
.iter()
.for_each(|signal| *total_loss += (*signal - median).abs());
}
#[inline]
fn median(&self, range: Range<usize>) -> f64 {
let len = range.len();
let range_inclusive = range.start..=(range.end - 1);
if len.is_multiple_of(2) {
let kth = len / 2;
let median1 = self.kth_smallest_tree.kth(range_inclusive.clone(), kth);
let median2 = self.kth_smallest_tree.kth(range_inclusive, kth + 1);
median1.midpoint(median2)
} else {
let kth = len / 2 + 1;
self.kth_smallest_tree.kth(range_inclusive, kth)
}
}
}
pub struct L1Cost2D {
columns: Vec<L1Cost1D>,
}
impl L1Cost2D {
#[inline]
pub fn precalculate(signal: &ArrayView2<f64>) -> Self {
let columns = signal
.columns()
.into_iter()
.map(|column| L1Cost1D::precalculate(&column))
.collect();
Self { columns }
}
#[inline]
pub(crate) fn loss(&self, total_loss: &mut f64, signal: &ArrayView2<f64>, range: Range<usize>) {
self.columns
.iter()
.zip(signal.columns())
.for_each(|(column, signal_column)| {
column.loss(total_loss, &signal_column, range.clone())
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cost_1d() {
let array_1d = ndarray::array![10.0, 30.0, 20.0];
let cost = L1Cost1D::precalculate(&array_1d.view());
let mut loss = 0.0;
cost.loss(&mut loss, &array_1d.view(), 0..3);
assert_eq!(loss, 20.0);
}
#[test]
fn cost_2d() {
let array_2d = ndarray::array![[10.0], [30.0], [20.0]];
let cost = L1Cost2D::precalculate(&array_2d.view());
let mut loss = 0.0;
cost.loss(&mut loss, &array_2d.view(), 0..3);
assert_eq!(loss, 20.0);
}
}