use itertools::Itertools;
use ndarray::{Array1, ArrayView1, s};
use num_traits::{AsPrimitive, One, Zero};
use std::{
cmp::Ordering,
ops::{Div, Range},
};
use crate::{Sample, processors::MeanVar};
#[derive(Debug)]
pub struct CenteredProduct {
acc: Array1<i64>,
count: usize,
mean: Array1<f32>,
intervals: Vec<Range<i32>>,
processed: bool,
}
impl CenteredProduct {
pub fn new(size: usize, intervals: Vec<Range<i32>>) -> Self {
Self {
acc: Array1::zeros(size),
count: 0,
intervals,
processed: false,
mean: Array1::zeros(size),
}
}
pub fn process<T: Into<i64> + Copy>(&mut self, trace: ArrayView1<T>) {
let size = self.acc.len();
for i in 0..size {
let x = trace[i].into();
self.acc[i] += x;
}
self.count += 1
}
pub fn finalize(&mut self) {
if self.count != 0 {
self.mean = self.acc.mapv(|x| x as f32 / self.count as f32)
}
self.processed = true
}
pub fn apply<T: Into<f32> + Copy>(&self, trace: ArrayView1<T>) -> Array1<f32> {
let centered_trace: Array1<f32> = trace.mapv(|x| x.into()) - &self.mean;
let length_out_trace: usize = self.intervals.iter().map(|x| x.len()).product();
let mut centered_product_trace = Array1::ones(length_out_trace);
let multi_prod = (0..self.intervals.len())
.map(|i| self.intervals[i].clone())
.multi_cartesian_product();
for (idx, combination) in multi_prod.enumerate() {
for i in combination {
centered_product_trace[idx] *= centered_trace[i as usize] as f32;
}
}
centered_product_trace
}
}
#[derive(Debug)]
pub struct Power {
intervals: Vec<Range<i32>>,
power: i32,
}
impl Power {
pub fn new(intervals: Vec<Range<i32>>, power: i32) -> Self {
Self { intervals, power }
}
pub fn process<T: Into<i64> + Copy>(&self, trace: ArrayView1<T>) -> Array1<f32> {
let result: Array1<_> = self
.intervals
.iter()
.flat_map(|range| trace.slice(s![range.clone()]).to_owned())
.map(|val| val.into() as f32)
.collect();
result.mapv(|result| result.powi(self.power))
}
}
pub struct StandardScaler<T>
where
T: Sample,
{
meanvar: MeanVar<T>,
mean: Array1<f32>,
std: Array1<f32>,
}
impl<T> StandardScaler<T>
where
T: Sample + Copy,
{
pub fn new(size: usize) -> Self {
Self {
meanvar: MeanVar::new(size),
mean: Array1::zeros(size),
std: Array1::zeros(size),
}
}
pub fn process(&mut self, trace: ArrayView1<T>) {
self.meanvar.process(trace);
}
pub fn finalize(&mut self) {
self.mean = self.meanvar.mean();
self.std = self.meanvar.var().mapv(f32::sqrt);
}
pub fn apply(&self, trace: ArrayView1<T>) -> Array1<f32> {
(trace.mapv(|x| <T as Sample>::Container::from(x).as_()) - &self.mean) / &self.std
}
}
pub use dtw::dist;
pub struct ElasticAlignment<T, D>
where
D: Fn(T, T) -> T,
{
reference_trace: Array1<T>,
radius: usize,
dist: D,
}
impl<T, D> ElasticAlignment<T, D>
where
T: dtw::Average + dtw::SumContainer + Copy + Zero + 'static,
T::Container: Zero + One + Div<Output = T::Container> + AsPrimitive<T>,
D: Fn(T, T) -> T,
{
pub fn new(reference_trace: Array1<T>, radius: usize, dist: D) -> Self {
Self {
reference_trace,
radius,
dist,
}
}
pub fn align_with_cmp<C>(&self, trace: ArrayView1<T>, cmp: &C) -> Array1<T>
where
C: Fn(&T::Container, &T::Container) -> Ordering,
{
let warp_path = dtw::fast_dtw_with_cmp(
self.reference_trace.as_slice().unwrap(),
trace.as_slice().unwrap(),
self.radius,
&self.dist,
cmp,
);
let mut aligned_trace = Array1::zeros([trace.len()]);
let mut k = 0;
for j in 0..trace.len() {
let mut count = T::Container::zero();
let mut sum = T::Container::zero();
while k < warp_path.len() && warp_path[k].0 == j {
count = count + T::Container::one();
sum = sum + T::Container::from(trace[warp_path[k].1]);
k += 1;
}
aligned_trace[j] = (sum / count).as_();
}
aligned_trace
}
}
impl<T, D> ElasticAlignment<T, D>
where
T: dtw::Average + dtw::SumContainer + Copy + Zero + 'static,
T::Container: Zero + One + Div<Output = T::Container> + AsPrimitive<T> + Ord,
D: Fn(T, T) -> T,
{
pub fn align(&self, trace: ArrayView1<T>) -> Array1<T> {
self.align_with_cmp(trace, &T::Container::cmp)
}
}
#[cfg(test)]
mod tests {
use crate::preprocessors::{CenteredProduct, ElasticAlignment, Power, StandardScaler, dist};
use ndarray::array;
fn round_to_2_digits(x: f32) -> f32 {
(x * 100f32).round() / 100f32
}
#[test]
fn test_centered_product() {
let mut processor = CenteredProduct::new(5, vec![0..2, 3..5]);
processor.process(array![0i16, 1i16, 2i16, -3i16, -4i16].view());
processor.finalize();
assert_eq!(
processor.apply(array![0i16, 1i16, 2i16, -3i16, -4i16].view()),
array![0f32, 0f32, 0f32, 0f32]
);
let traces = [
array![77, 137, 51, 91],
array![72, 61, 91, 83],
array![39, 49, 52, 23],
array![26, 114, 63, 45],
array![30, 8, 97, 91],
array![13, 68, 7, 45],
array![17, 181, 60, 34],
array![43, 88, 76, 78],
array![0, 36, 35, 0],
array![93, 191, 49, 26],
];
let mut processor2 = CenteredProduct::new(4, vec![0..1, 1..2, 2..4]);
for t in traces.iter() {
processor2.process(t.view());
}
processor2.finalize();
let expected_results = [
array![-11169.72, 61984.08],
array![-32942.77, -31440.83],
array![-540.46, -2533.96],
array![-1521.45, 2049.30],
array![36499.88, 36969.02],
array![-36199.24, -4675.44],
array![-3999.12, 37044.47],
array![-189.74, -279.84],
array![-54268.83, -121223.88],
array![-46231.64, -130058.23],
];
for (i, t) in traces.iter().enumerate() {
assert_eq!(
processor2
.apply(t.mapv(|x| x as f32).view())
.mapv(round_to_2_digits),
expected_results[i]
);
}
}
#[test]
fn test_power() {
let t = array![-1, 2, -3, 4, -5, 6];
let processor1 = Power {
intervals: vec![0..2, 4..6],
power: 1,
};
let processor2 = Power {
intervals: vec![0..2, 4..6],
power: 2,
};
let processor3 = Power {
intervals: vec![0..2, 4..6],
power: 3,
};
let expected_results = [
array![-1.0, 2.0, -5.0, 6.0],
array![1.0, 4.0, 25.0, 36.0],
array![-1.0, 8.0, -125.0, 216.0],
];
assert_eq!(
processor1.process(t.view()).mapv(round_to_2_digits),
expected_results[0]
);
assert_eq!(
processor2.process(t.view()).mapv(round_to_2_digits),
expected_results[1]
);
assert_eq!(
processor3.process(t.view()).mapv(round_to_2_digits),
expected_results[2]
);
}
#[test]
fn test_standard_scaler() {
let traces = [
array![77, 137, 51, 91],
array![72, 61, 91, 83],
array![39, 49, 52, 23],
array![26, 114, 63, 45],
array![30, 8, 97, 91],
array![13, 68, 7, 45],
array![17, 181, 60, 34],
array![43, 88, 76, 78],
array![0, 36, 35, 0],
array![93, 191, 49, 26],
];
let mut processor = StandardScaler::new(4);
for t in traces.iter() {
processor.process(t.view());
}
processor.finalize();
let expected_results = [
array![1.25, 0.75, -0.28, 1.29],
array![1.07, -0.56, 1.32, 1.03],
array![-0.07, -0.76, -0.24, -0.94],
array![-0.52, 0.36, 0.20, -0.22],
array![-0.38, -1.47, 1.55, 1.29],
array![-0.97, -0.44, -2.04, -0.22],
array![-0.83, 1.51, 0.08, -0.58],
array![0.07, -0.09, 0.72, 0.86],
array![-1.42, -0.99, -0.92, -1.69],
array![1.80, 1.68, -0.36, -0.84],
];
for (i, t) in traces.iter().enumerate() {
assert_eq!(
processor.apply(t.view()).mapv(round_to_2_digits),
expected_results[i]
);
}
}
#[test]
fn test_elastic_align() {
let reference_trace = array![77, 117, 5, 51, 91, -12, -33];
let trace = array![77, 117, 13, 15, 5, 51, 91];
let elastic_alignment =
ElasticAlignment::new(reference_trace.clone(), 1, dist::euclidean_distance);
assert_eq!(
elastic_alignment.align(trace.view()),
array![77, 117, 11, 51, 51, 51, 91]
);
}
#[test]
fn test_elastic_align_same_trace() {
let reference_trace = array![77, 117, 5, 51, 91, -12, -33];
let elastic_alignment =
ElasticAlignment::new(reference_trace.clone(), 1, dist::euclidean_distance);
assert_eq!(
elastic_alignment.align(reference_trace.view()),
reference_trace
);
}
}