use std::ops::Range;
use ndarray::{ArrayView1, ArrayView2};
pub struct L2Cost1D {
sums: Vec<Sums>,
}
impl L2Cost1D {
#[inline]
pub(crate) fn precalculate(signal: &ArrayView1<f64>) -> Self {
let mut sums = vec![Sums::default(); signal.len()];
let mut sum_counter = 0.0;
let mut sum_squared_counter = 0.0;
sums.iter_mut()
.zip(signal.iter())
.for_each(|(sums, signal)| {
sum_counter += *signal;
sum_squared_counter += signal.powi(2);
sums.sum = sum_counter;
sums.sum_squared = sum_squared_counter;
});
Self { sums }
}
#[inline]
pub(crate) fn loss(&self, total_loss: &mut f64, range: Range<usize>) {
let rows_length = range.end.saturating_sub(range.start) as f64;
let left = self
.sums
.get(range.start.wrapping_sub(1))
.cloned()
.unwrap_or_default();
let right = &self.sums[range.end.saturating_sub(1)];
let sum = right.sum - left.sum;
let sum_squared = right.sum_squared - left.sum_squared;
*total_loss += sum_squared - sum.powi(2) / rows_length;
}
}
pub struct L2Cost2D {
columns: Vec<L2Cost1D>,
}
impl L2Cost2D {
#[inline]
pub fn precalculate(signal: &ArrayView2<f64>) -> Self {
let columns = signal
.columns()
.into_iter()
.map(|column| L2Cost1D::precalculate(&column))
.collect();
Self { columns }
}
#[inline]
pub(crate) fn loss(&self, total_loss: &mut f64, range: Range<usize>) {
self.columns
.iter()
.for_each(|column| column.loss(total_loss, range.clone()));
}
}
#[derive(Default, Clone)]
struct Sums {
sum: f64,
sum_squared: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cost_1d() {
let array_1d = ndarray::array![10.0, 30.0, 20.0];
let cost = L2Cost1D::precalculate(&array_1d.view());
let mut loss = 0.0;
cost.loss(&mut loss, 0..3);
assert_eq!(loss, 200.0);
}
#[test]
fn cost_2d() {
let array_2d = ndarray::array![[10.0], [30.0], [20.0]];
let cost = L2Cost2D::precalculate(&array_2d.view());
let mut loss = 0.0;
cost.loss(&mut loss, 0..3);
assert_eq!(loss, 200.0);
}
}