From 169815a8545ca691a4db9d33041ae313b6cdb1f8 Mon Sep 17 00:00:00 2001 From: Niels Praet Date: Tue, 19 Sep 2023 12:57:08 +0200 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9E=95=20dependencies:=20add=20Polars=20?= =?UTF-8?q?dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- downsample_rs/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/downsample_rs/Cargo.toml b/downsample_rs/Cargo.toml index 9c52eb8..169e8e9 100644 --- a/downsample_rs/Cargo.toml +++ b/downsample_rs/Cargo.toml @@ -9,9 +9,12 @@ license = "MIT" [dependencies] # TODO: perhaps use polars? argminmax = { version = "0.6.1", features = ["half"] } +# For some reason we need to explicitely add chrono, otherwise polars refuses to compile? +chrono = "0.4.31" # argminmax = { path = "../../argminmax" , features = ["half", "ndarray"] } half = { version = "2.1", default-features = false , features=["num-traits"], optional = true} num-traits = { version = "0.2.15", default-features = false } +polars = { version = "0.33.2", features = ["lazy", "streaming", "zip_with"] } rayon = { version = "1.6.0", default-features = false } [dev-dependencies] From aa2f03850966bcedca4d050fea7952055a9e0610 Mon Sep 17 00:00:00 2001 From: Niels Praet Date: Tue, 19 Sep 2023 12:57:27 +0200 Subject: [PATCH 2/8] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20lttb=20for=20Pola?= =?UTF-8?q?rs=20ChunkedArray?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- downsample_rs/src/lttb.rs | 213 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) diff --git a/downsample_rs/src/lttb.rs b/downsample_rs/src/lttb.rs index 20d28d4..0a6b4a7 100644 --- a/downsample_rs/src/lttb.rs +++ b/downsample_rs/src/lttb.rs @@ -1,6 +1,7 @@ use super::helpers::Average; use super::types::Num; use num_traits::AsPrimitive; +use polars::prelude::*; use std::cmp; #[inline(always)] @@ -179,11 +180,205 @@ pub fn lttb_without_x>(y: &[Ty], n_out: usize) -> Vec sampled_indices } +pub fn lttb_with_x_ca( + x: &ChunkedArray, + y: &ChunkedArray, + n_out: usize, +) -> Vec +where + Tx: PolarsNumericType, + Ty: PolarsNumericType, +{ + assert_eq!(x.len(), y.len()); + if n_out >= x.len() { + return (0..x.len()).collect::>(); + } + assert!(n_out >= 3); // avoid division by 0 + + let x = x + .cast(&DataType::Float64) + .unwrap() + .f64() + .unwrap() + .to_owned(); + let y = y + .cast(&DataType::Float64) + .unwrap() + .f64() + .unwrap() + .to_owned(); + + // Bucket size. Leave room for start and end data points. + let every: f64 = (x.len() - 2) as f64 / (n_out - 2) as f64; + // Initially a is the first point in the triangle. + let mut a: usize = 0; + + let mut sampled_indices: Vec = vec![usize::default(); n_out]; + + // Always add the first point + sampled_indices[0] = 0; + + for i in 0..n_out - 2 { + // Calculate point average for next bucket (containing c). + let avg_range_start = (every * (i + 1) as f64) as usize + 1; + let avg_range_end = cmp::min((every * (i + 2) as f64) as usize + 1, x.len()); + + let y_slice = y.slice(avg_range_start as i64, avg_range_end - avg_range_start); + // TODO: there is an implementation for mean... but how do we need to type the parameters? + let avg_y: f64 = y_slice.mean().unwrap_or(0f64); + // TODO: avg_y could be approximated argminmax instead of mean? + // TODO: below is faster than above, but not as accurate + // let avg_x: f64 = (x_slice[avg_range_end - 1].as_() + x_slice[avg_range_start].as_()) / 2.0; + let avg_x: f64 = unsafe { + (x.get_unchecked(avg_range_end - 1).unwrap() + + x.get_unchecked(avg_range_start).unwrap()) + / 2.0f64 + }; + + // Get the range for this bucket + let range_offs = (every * i as f64) as usize + 1; + let range_to = avg_range_start; // = start of the next bucket + + // Point a + let point_ax = unsafe { x.get_unchecked(a).unwrap() }; + let point_ay = unsafe { y.get_unchecked(a).unwrap() }; + + let d1 = point_ax - avg_x; + let d2 = avg_y - point_ay; + let offset: f64 = d1 * point_ay + d2 * point_ax; + + let x_slice = x.slice(range_offs as i64, range_to - range_offs); + let y_slice = y.slice(range_offs as i64, range_to - range_offs); + (_, a) = y_slice + .into_no_null_iter() + .zip(x_slice.into_no_null_iter()) + .enumerate() + .fold((-1i64, a), |(max_area, a), (i, (y_, x_))| { + // Calculate triangle area over three buckets + // -> area = d1 * (y_ - point_ay) - (point_ax - x_) * d2; + // let area = d1 * y[i].as_() + d2 * x[i].as_() - offset; + // let area = d1 * y_slice[i].as_() + d2 * x_slice[i].as_() - offset; + let area = d1 * y_ + d2 * x_ - offset; + let area = f64_to_i64unsigned(area); // this is faster than abs + if area > max_area { + (area, i) + } else { + (max_area, a) + } + }); + a += range_offs; + + sampled_indices[i + 1] = a; + } + + // Always add the last point + sampled_indices[n_out - 1] = y.len() - 1; + + sampled_indices +} + +pub fn lttb_without_x_ca(y: &ChunkedArray, n_out: usize) -> Vec { + if n_out >= y.len() { + return (0..y.len()).collect::>(); + } + assert!(n_out >= 3); // avoid division by 0 + + let y = y + .cast(&DataType::Float64) + .unwrap() + .f64() + .unwrap() + .to_owned(); + + // Bucket size. Leave room for start and end data points. + let every: f64 = (y.len() - 2) as f64 / (n_out - 2) as f64; + // Initially a is the first point in the triangle. + let mut a: usize = 0; + + let mut sampled_indices: Vec = vec![usize::default(); n_out]; + + // Always add the first point + sampled_indices[0] = 0; + + for i in 0..n_out - 2 { + // Calculate point average for next bucket (containing c). + let avg_range_start = (every * (i + 1) as f64) as usize + 1; + let avg_range_end = cmp::min((every * (i + 2) as f64) as usize + 1, y.len()); + + let y_slice = y.slice(avg_range_start as i64, avg_range_end - avg_range_start); + let avg_y: f64 = y_slice.mean().unwrap_or(0f64); + let avg_x: f64 = (avg_range_start + avg_range_end - 1) as f64 / 2.0; + + // Get the range for this bucket + let range_offs = (every * i as f64) as usize + 1; + let range_to = avg_range_start; // = start of the next bucket + + // Point a + let point_ay = unsafe { y.get_unchecked(a).unwrap() }; + let point_ax = a as f64; + + let d1 = point_ax - avg_x; + let d2 = avg_y - point_ay; + let point_ax = point_ax - range_offs as f64; + + // let mut max_area = -1i64; + let mut ax_x = point_ax; // point_ax - x[i] + let offset: f64 = d1 * point_ay; + + // TODO: for some reason is this faster than the loop below -> check if this is true for other devices + let y_slice = y.slice(range_offs as i64, range_to - range_offs); + (_, a) = + y_slice + .into_no_null_iter() + .enumerate() + .fold((-1i64, a), |(max_area, a), (i, y)| { + // Calculate triangle area over three buckets + // -> area: f64 = d1 * y[i].as_() - ax_x * d2; + let area: f64 = d1 * y - ax_x * d2 - offset; + let area: i64 = f64_to_i64unsigned(area); + ax_x -= 1.0; + if area > max_area { + (area, i + range_offs) + } else { + (max_area, a) + } + }); + + // let y_slice = unsafe { std::slice::from_raw_parts(y_ptr.add(range_offs), range_to - range_offs) }; + // (_, a) = y_slice + // .iter() + // .enumerate() + // .fold((-1i64, a), |(max_area, a), (i, y_)| { + // // Calculate triangle area over three buckets + // // -> area: f64 = d1 * y[i].as_() - ax_x * d2; + // let area: f64 = d1 * y_.as_() - ax_x * d2 - offset; + // let area: i64 = f64_to_i64unsigned(area); + // ax_x -= 1.0; + // if area > max_area { + // (area, i) + // } else { + // (max_area, a) + // } + // }); + // a += range_offs; + + sampled_indices[i + 1] = a; + } + + // Always add the last point + sampled_indices[n_out - 1] = y.len() - 1; + + sampled_indices +} + // --------------------------------------- TESTS --------------------------------------- #[cfg(test)] mod tests { use dev_utils::utils; + use polars::prelude::{ChunkedArray, Float32Type, Int32Type}; + + use crate::{lttb_with_x_ca, lttb_without_x_ca}; use super::{lttb_with_x, lttb_without_x}; @@ -195,6 +390,24 @@ mod tests { assert_eq!(sampled_indices, vec![0, 1, 5, 9]); } + #[test] + fn test_ca_lttb_with_x() { + let x: ChunkedArray = + ChunkedArray::from_vec("x", vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let y: ChunkedArray = + ChunkedArray::from_vec("y", vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]); + let sampled_indices = lttb_with_x_ca(&x, &y, 4); + assert_eq!(sampled_indices, vec![0, 1, 5, 9]); + } + + #[test] + fn test_ca_lttb_without_x() { + let y: ChunkedArray = + ChunkedArray::from_vec("y", vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]); + let sampled_indices = lttb_without_x_ca(&y, 4); + assert_eq!(sampled_indices, vec![0, 1, 5, 9]); + } + #[test] fn test_lttb_without_x() { let y = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]; From 6c178e186dac70cea87aa2d523629a8394a8aa5d Mon Sep 17 00:00:00 2001 From: Niels Praet Date: Tue, 19 Sep 2023 16:46:25 +0200 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=92=A9=20refactor:=20first=20pass=20o?= =?UTF-8?q?f=20refactoring=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- downsample_rs/src/helpers.rs | 115 ++++++++++++++ downsample_rs/src/lttb.rs | 281 ++++++++--------------------------- 2 files changed, 181 insertions(+), 215 deletions(-) diff --git a/downsample_rs/src/helpers.rs b/downsample_rs/src/helpers.rs index f2961ab..4321735 100644 --- a/downsample_rs/src/helpers.rs +++ b/downsample_rs/src/helpers.rs @@ -1,4 +1,5 @@ use num_traits::AsPrimitive; +use polars::prelude::{ChunkAgg, ChunkAnyValue, ChunkedArray, Float64Type, TakeRandom}; use crate::types::Num; @@ -31,3 +32,117 @@ where self.iter().fold(0f64, |acc, &x| acc + x.as_()) as f64 / self.len() as f64 } } + +impl Average for Vec +where + T: Num + AsPrimitive, +{ + fn average(&self) -> f64 { + self.iter().fold(0f64, |acc, &x| acc + x.as_()) as f64 / self.len() as f64 + } +} + +pub trait LttbParam { + type IterType: Iterator; + type SliceType: LttbParam; + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType; + + fn get(&self, index: usize) -> f64; + + fn into_iter(&self) -> Self::IterType; + + fn len(&self) -> usize; + + fn average(&self) -> f64; +} + +impl LttbParam for [T] +where + T: Num + AsPrimitive, +{ + type IterType = std::vec::IntoIter; + type SliceType = std::vec::Vec; + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType { + self[start_idx..end_idx].iter().map(|v| v.as_()).collect() + } + + fn get(&self, index: usize) -> f64 { + self[index].as_() + } + + fn into_iter(&self) -> Self::IterType { + IntoIterator::into_iter(self.iter().map(|v| v.as_()).collect::>()) + } + + fn len(&self) -> usize { + self.len() + } + + fn average(&self) -> f64 { + Average::average(self) + } +} + +impl LttbParam for Vec +where + T: Num + AsPrimitive, +{ + type IterType = std::vec::IntoIter; + type SliceType = std::vec::Vec; + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType { + self[start_idx..end_idx].iter().map(|v| v.as_()).collect() + } + + fn get(&self, index: usize) -> f64 { + self[index].as_() + } + + fn into_iter(&self) -> Self::IterType { + IntoIterator::into_iter(self.iter().map(|v| v.as_()).collect::>()) + } + + fn len(&self) -> usize { + self.len() + } + + fn average(&self) -> f64 { + Average::average(self) + } +} + +impl LttbParam for ChunkedArray { + type IterType = std::vec::IntoIter; + type SliceType = ChunkedArray; + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType { + self.slice(start_idx as i64, end_idx - start_idx) + } + + fn get(&self, index: usize) -> f64 { + match self + .get_any_value(index) + .unwrap() + .cast(&polars::prelude::DataType::Float64) + .unwrap() + { + polars::prelude::AnyValue::Float64(x) => x, + _ => panic!(""), // this can never be reached, as it should have panicked when casting + } + } + + fn into_iter(&self) -> Self::IterType { + // TODO: fix this so we don't do any needless copying + self.into_no_null_iter().collect::>().into_iter() + } + + fn len(&self) -> usize { + self.len() + } + + fn average(&self) -> f64 { + self.mean().unwrap_or(0.0) + } +} diff --git a/downsample_rs/src/lttb.rs b/downsample_rs/src/lttb.rs index 0a6b4a7..ca25032 100644 --- a/downsample_rs/src/lttb.rs +++ b/downsample_rs/src/lttb.rs @@ -1,4 +1,4 @@ -use super::helpers::Average; +use super::helpers::LttbParam; use super::types::Num; use num_traits::AsPrimitive; use polars::prelude::*; @@ -38,51 +38,7 @@ pub fn lttb_with_x, Ty: Num + AsPrimitive>( for i in 0..n_out - 2 { // Calculate point average for next bucket (containing c). - let avg_range_start = (every * (i + 1) as f64) as usize + 1; - let avg_range_end = cmp::min((every * (i + 2) as f64) as usize + 1, x.len()); - - let y_slice = &y[avg_range_start..avg_range_end]; - let avg_y: f64 = y_slice.average(); - // TODO: avg_y could be approximated argminmax instead of mean? - // TODO: below is faster than above, but not as accurate - // let avg_x: f64 = (x_slice[avg_range_end - 1].as_() + x_slice[avg_range_start].as_()) / 2.0; - let avg_x: f64 = unsafe { - (x.get_unchecked(avg_range_end - 1).as_() + x.get_unchecked(avg_range_start).as_()) - / 2.0 - }; - - // Get the range for this bucket - let range_offs = (every * i as f64) as usize + 1; - let range_to = avg_range_start; // = start of the next bucket - - // Point a - let point_ax = unsafe { x.get_unchecked(a).as_() }; - let point_ay = unsafe { y.get_unchecked(a).as_() }; - - let d1 = point_ax - avg_x; - let d2 = avg_y - point_ay; - let offset: f64 = d1 * point_ay + d2 * point_ax; - - let x_slice = &x[range_offs..range_to]; - let y_slice = &y[range_offs..range_to]; - (_, a) = y_slice.iter().zip(x_slice.iter()).enumerate().fold( - (-1i64, a), - |(max_area, a), (i, (y_, x_))| { - // Calculate triangle area over three buckets - // -> area = d1 * (y_ - point_ay) - (point_ax - x_) * d2; - // let area = d1 * y[i].as_() + d2 * x[i].as_() - offset; - // let area = d1 * y_slice[i].as_() + d2 * x_slice[i].as_() - offset; - let area = d1 * y_.as_() + d2 * x_.as_() - offset; - let area = f64_to_i64unsigned(area); // this is faster than abs - if area > max_area { - (area, i) - } else { - (max_area, a) - } - }, - ); - a += range_offs; - + a = lttb_generic(x, y, i, every, a); sampled_indices[i + 1] = a; } @@ -107,70 +63,13 @@ pub fn lttb_without_x>(y: &[Ty], n_out: usize) -> Vec let mut sampled_indices: Vec = vec![usize::default(); n_out]; + let x = (0..y.len()).map(|v| v.as_()).collect::>(); + // Always add the first point sampled_indices[0] = 0; for i in 0..n_out - 2 { - // Calculate point average for next bucket (containing c). - let avg_range_start = (every * (i + 1) as f64) as usize + 1; - let avg_range_end = cmp::min((every * (i + 2) as f64) as usize + 1, y.len()); - - let y_slice = &y[avg_range_start..avg_range_end]; - let avg_y: f64 = y_slice.average(); - let avg_x: f64 = (avg_range_start + avg_range_end - 1) as f64 / 2.0; - - // Get the range for this bucket - let range_offs = (every * i as f64) as usize + 1; - let range_to = avg_range_start; // = start of the next bucket - - // Point a - let point_ay = unsafe { y.get_unchecked(a).as_() }; - let point_ax = a as f64; - - let d1 = point_ax - avg_x; - let d2 = avg_y - point_ay; - let point_ax = point_ax - range_offs as f64; - - // let mut max_area = -1i64; - let mut ax_x = point_ax; // point_ax - x[i] - let offset: f64 = d1 * point_ay; - - // TODO: for some reason is this faster than the loop below -> check if this is true for other devices - let y_slice = &y[range_offs..range_to]; - (_, a) = y_slice - .iter() - .enumerate() - .fold((-1i64, a), |(max_area, a), (i, y)| { - // Calculate triangle area over three buckets - // -> area: f64 = d1 * y[i].as_() - ax_x * d2; - let area: f64 = d1 * y.as_() - ax_x * d2 - offset; - let area: i64 = f64_to_i64unsigned(area); - ax_x -= 1.0; - if area > max_area { - (area, i + range_offs) - } else { - (max_area, a) - } - }); - - // let y_slice = unsafe { std::slice::from_raw_parts(y_ptr.add(range_offs), range_to - range_offs) }; - // (_, a) = y_slice - // .iter() - // .enumerate() - // .fold((-1i64, a), |(max_area, a), (i, y_)| { - // // Calculate triangle area over three buckets - // // -> area: f64 = d1 * y[i].as_() - ax_x * d2; - // let area: f64 = d1 * y_.as_() - ax_x * d2 - offset; - // let area: i64 = f64_to_i64unsigned(area); - // ax_x -= 1.0; - // if area > max_area { - // (area, i) - // } else { - // (max_area, a) - // } - // }); - // a += range_offs; - + a = lttb_generic::<[f64], [Ty]>(&x, y, i, every, a); sampled_indices[i + 1] = a; } @@ -219,55 +118,7 @@ where sampled_indices[0] = 0; for i in 0..n_out - 2 { - // Calculate point average for next bucket (containing c). - let avg_range_start = (every * (i + 1) as f64) as usize + 1; - let avg_range_end = cmp::min((every * (i + 2) as f64) as usize + 1, x.len()); - - let y_slice = y.slice(avg_range_start as i64, avg_range_end - avg_range_start); - // TODO: there is an implementation for mean... but how do we need to type the parameters? - let avg_y: f64 = y_slice.mean().unwrap_or(0f64); - // TODO: avg_y could be approximated argminmax instead of mean? - // TODO: below is faster than above, but not as accurate - // let avg_x: f64 = (x_slice[avg_range_end - 1].as_() + x_slice[avg_range_start].as_()) / 2.0; - let avg_x: f64 = unsafe { - (x.get_unchecked(avg_range_end - 1).unwrap() - + x.get_unchecked(avg_range_start).unwrap()) - / 2.0f64 - }; - - // Get the range for this bucket - let range_offs = (every * i as f64) as usize + 1; - let range_to = avg_range_start; // = start of the next bucket - - // Point a - let point_ax = unsafe { x.get_unchecked(a).unwrap() }; - let point_ay = unsafe { y.get_unchecked(a).unwrap() }; - - let d1 = point_ax - avg_x; - let d2 = avg_y - point_ay; - let offset: f64 = d1 * point_ay + d2 * point_ax; - - let x_slice = x.slice(range_offs as i64, range_to - range_offs); - let y_slice = y.slice(range_offs as i64, range_to - range_offs); - (_, a) = y_slice - .into_no_null_iter() - .zip(x_slice.into_no_null_iter()) - .enumerate() - .fold((-1i64, a), |(max_area, a), (i, (y_, x_))| { - // Calculate triangle area over three buckets - // -> area = d1 * (y_ - point_ay) - (point_ax - x_) * d2; - // let area = d1 * y[i].as_() + d2 * x[i].as_() - offset; - // let area = d1 * y_slice[i].as_() + d2 * x_slice[i].as_() - offset; - let area = d1 * y_ + d2 * x_ - offset; - let area = f64_to_i64unsigned(area); // this is faster than abs - if area > max_area { - (area, i) - } else { - (max_area, a) - } - }); - a += range_offs; - + a = lttb_generic(&x, &y, i, every, a); sampled_indices[i + 1] = a; } @@ -297,70 +148,13 @@ pub fn lttb_without_x_ca(y: &ChunkedArray, n_out: usi let mut sampled_indices: Vec = vec![usize::default(); n_out]; + let x = (0..y.len()).map(|v| v.as_()).collect::>(); + // Always add the first point sampled_indices[0] = 0; for i in 0..n_out - 2 { - // Calculate point average for next bucket (containing c). - let avg_range_start = (every * (i + 1) as f64) as usize + 1; - let avg_range_end = cmp::min((every * (i + 2) as f64) as usize + 1, y.len()); - - let y_slice = y.slice(avg_range_start as i64, avg_range_end - avg_range_start); - let avg_y: f64 = y_slice.mean().unwrap_or(0f64); - let avg_x: f64 = (avg_range_start + avg_range_end - 1) as f64 / 2.0; - - // Get the range for this bucket - let range_offs = (every * i as f64) as usize + 1; - let range_to = avg_range_start; // = start of the next bucket - - // Point a - let point_ay = unsafe { y.get_unchecked(a).unwrap() }; - let point_ax = a as f64; - - let d1 = point_ax - avg_x; - let d2 = avg_y - point_ay; - let point_ax = point_ax - range_offs as f64; - - // let mut max_area = -1i64; - let mut ax_x = point_ax; // point_ax - x[i] - let offset: f64 = d1 * point_ay; - - // TODO: for some reason is this faster than the loop below -> check if this is true for other devices - let y_slice = y.slice(range_offs as i64, range_to - range_offs); - (_, a) = - y_slice - .into_no_null_iter() - .enumerate() - .fold((-1i64, a), |(max_area, a), (i, y)| { - // Calculate triangle area over three buckets - // -> area: f64 = d1 * y[i].as_() - ax_x * d2; - let area: f64 = d1 * y - ax_x * d2 - offset; - let area: i64 = f64_to_i64unsigned(area); - ax_x -= 1.0; - if area > max_area { - (area, i + range_offs) - } else { - (max_area, a) - } - }); - - // let y_slice = unsafe { std::slice::from_raw_parts(y_ptr.add(range_offs), range_to - range_offs) }; - // (_, a) = y_slice - // .iter() - // .enumerate() - // .fold((-1i64, a), |(max_area, a), (i, y_)| { - // // Calculate triangle area over three buckets - // // -> area: f64 = d1 * y[i].as_() - ax_x * d2; - // let area: f64 = d1 * y_.as_() - ax_x * d2 - offset; - // let area: i64 = f64_to_i64unsigned(area); - // ax_x -= 1.0; - // if area > max_area { - // (area, i) - // } else { - // (max_area, a) - // } - // }); - // a += range_offs; + a = lttb_generic(&x, &y, i, every, a); sampled_indices[i + 1] = a; } @@ -371,6 +165,63 @@ pub fn lttb_without_x_ca(y: &ChunkedArray, n_out: usi sampled_indices } +fn lttb_generic( + x: &X, + y: &Y, + index: usize, + every: f64, + a: usize, +) -> usize { + // Calculate point average for next bucket (containing c). + let avg_range_start = (every * (index + 1) as f64) as usize + 1; + let avg_range_end = cmp::min((every * (index + 2) as f64) as usize + 1, x.len()); + + let y_slice = y.slice(avg_range_start, avg_range_end); + // TODO: there is an implementation for mean... but how do we need to type the parameters? + let avg_y: f64 = y_slice.average(); + // TODO: avg_y could be approximated argminmax instead of mean? + // TODO: below is faster than above, but not as accurate + // let avg_x: f64 = (x_slice[avg_range_end - 1].as_() + x_slice[avg_range_start].as_()) / 2.0; + let avg_x = (x.get(avg_range_end - 1) + x.get(avg_range_start)) / 2.0; + + // Get the range for this bucket + let range_offs = (every * index as f64) as usize + 1; + let range_to = avg_range_start; // = start of the next bucket + + // Point a + let point_ax = x.get(a); + let point_ay = y.get(a); + + let d1 = point_ax - avg_x; + let d2 = avg_y - point_ay; + + let offset = d1 * point_ay + d2 * point_ax; + + let x_slice = x.slice(range_offs, range_to); + let y_slice = y.slice(range_offs, range_to); + let mut a = a; + + (_, a) = y_slice + .into_iter() + .zip(x_slice.into_iter()) + .enumerate() + .fold((-1i64, a), |(max_area, a), (i, (y_, x_))| { + // Calculate triangle area over three buckets + // -> area = d1 * (y_ - point_ay) - (point_ax - x_) * d2; + // let area = d1 * y[i].as_() + d2 * x[i].as_() - offset; + // let area = d1 * y_slice[i].as_() + d2 * x_slice[i].as_() - offset; + let area = d1 * y_ + d2 * x_ - offset; + let area = f64_to_i64unsigned(area); // this is faster than abs + if area > max_area { + (area, i) + } else { + (max_area, a) + } + }); + + a + range_offs +} + // --------------------------------------- TESTS --------------------------------------- #[cfg(test)] From 8d9c8b85c0b58951a03a0c6e668c561754ceea30 Mon Sep 17 00:00:00 2001 From: Niels Praet Date: Thu, 21 Sep 2023 12:21:06 +0200 Subject: [PATCH 4/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20generalize?= =?UTF-8?q?=20non-parallel=20m4=20without=20x?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- downsample_rs/src/helpers.rs | 4 +-- downsample_rs/src/m4.rs | 59 +++++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/downsample_rs/src/helpers.rs b/downsample_rs/src/helpers.rs index 4321735..73c091c 100644 --- a/downsample_rs/src/helpers.rs +++ b/downsample_rs/src/helpers.rs @@ -1,5 +1,5 @@ use num_traits::AsPrimitive; -use polars::prelude::{ChunkAgg, ChunkAnyValue, ChunkedArray, Float64Type, TakeRandom}; +use polars::prelude::{ChunkAgg, ChunkAnyValue, ChunkedArray, Float64Type}; use crate::types::Num; @@ -129,7 +129,7 @@ impl LttbParam for ChunkedArray { .unwrap() { polars::prelude::AnyValue::Float64(x) => x, - _ => panic!(""), // this can never be reached, as it should have panicked when casting + _ => unreachable!(), // this can never be reached, as it should have panicked when casting } } diff --git a/downsample_rs/src/m4.rs b/downsample_rs/src/m4.rs index fa3dd50..458f7e0 100644 --- a/downsample_rs/src/m4.rs +++ b/downsample_rs/src/m4.rs @@ -10,6 +10,54 @@ use crate::types::Num; // ----------------------------------- NON-PARALLEL ------------------------------------ +pub trait M4Param { + type SliceType: M4Param; + + fn len(&self) -> usize; + + fn argminmax(&self) -> (usize, usize); + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType; +} + +impl M4Param for Vec +where + Vec: ArgMinMax, +{ + type SliceType = std::vec::Vec; + + fn len(&self) -> usize { + self.len() + } + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType { + self[start_idx..end_idx].to_vec() + } + + fn argminmax(&self) -> (usize, usize) { + ArgMinMax::argminmax(self) + } +} + +impl M4Param for [T] +where + for<'a> &'a [T]: ArgMinMax, +{ + type SliceType = std::vec::Vec; + + fn len(&self) -> usize { + self.len() + } + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType { + self[start_idx..end_idx].to_vec() + } + + fn argminmax(&self) -> (usize, usize) { + ArgMinMax::argminmax(&self) + } +} + // ----------- WITH X pub fn m4_with_x(x: &[Tx], arr: &[Ty], n_out: usize) -> Vec @@ -30,7 +78,7 @@ where for<'a> &'a [T]: ArgMinMax, { assert_eq!(n_out % 4, 0); - m4_generic(arr, n_out, |arr| arr.argminmax()) + m4_generic(arr, n_out) } // ------------------------------------- PARALLEL -------------------------------------- @@ -80,11 +128,7 @@ where // --------------------- WITHOUT X #[inline(always)] -pub(crate) fn m4_generic( - arr: &[T], - n_out: usize, - f_argminmax: fn(&[T]) -> (usize, usize), -) -> Vec { +pub(crate) fn m4_generic(arr: &T, n_out: usize) -> Vec { // Assumes n_out is a multiple of 4 if n_out >= arr.len() { return (0..arr.len()).collect(); @@ -96,13 +140,14 @@ pub(crate) fn m4_generic( let mut sampled_indices: Vec = vec![usize::default(); n_out]; let mut start_idx: usize = 0; + for i in 0..n_out / 4 { // Decided to use multiplication instead of adding to the accumulator (end) // as multiplication seems to be less prone to rounding errors. let end: f64 = block_size * (i + 1) as f64; let end_idx: usize = end as usize + 1; - let (min_index, max_index) = f_argminmax(&arr[start_idx..end_idx]); + let (min_index, max_index) = arr.slice(start_idx, end_idx).argminmax(); // Add the indexes in sorted order sampled_indices[4 * i] = start_idx; From f0d82b9a7802536c785e21f0db95890705b88681 Mon Sep 17 00:00:00 2001 From: Niels Praet Date: Thu, 21 Sep 2023 12:55:07 +0200 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=92=A9=20refactor:=20generalize=20bin?= =?UTF-8?q?ary=20search=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- downsample_rs/src/searchsorted.rs | 67 ++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/downsample_rs/src/searchsorted.rs b/downsample_rs/src/searchsorted.rs index af53a9c..f824728 100644 --- a/downsample_rs/src/searchsorted.rs +++ b/downsample_rs/src/searchsorted.rs @@ -6,6 +6,22 @@ use num_traits::{AsPrimitive, FromPrimitive}; // ---------------------- Binary search ---------------------- +pub trait BinSearchParam { + fn get(&self, index: usize) -> T; +} + +impl BinSearchParam for [T] { + fn get(&self, index: usize) -> T { + self[index] + } +} + +impl BinSearchParam for Vec { + fn get(&self, index: usize) -> T { + self[index] + } +} + /// Binary search for the index position of the given value in the given array. /// The array must be sorted in ascending order and contain no duplicates. /// @@ -13,27 +29,53 @@ use num_traits::{AsPrimitive, FromPrimitive}; /// https://docs.python.org/3/library/bisect.html#bisect.bisect /// // #[inline(always)] -fn binary_search(arr: &[T], value: T, left: usize, right: usize) -> usize { + +fn binary_search + ?Sized>( + arr: &T, + value: I, + left: usize, + right: usize, +) -> usize { let mut size: usize = right - left; let mut left: usize = left; let mut right: usize = right; - // Return the index where the value is >= arr[index] and arr[index-1] < value while left < right { let mid = left + size / 2; - if arr[mid] < value { + if arr.get(mid) < value { left = mid + 1; } else { right = mid; } size = right - left; } - if arr[left] <= value { + if arr.get(left) <= value { left + 1 } else { left } } +// fn binary_search(arr: &[T], value: T, left: usize, right: usize) -> usize { +// let mut size: usize = right - left; +// let mut left: usize = left; +// let mut right: usize = right; +// // Return the index where the value is >= arr[index] and arr[index-1] < value +// while left < right { +// let mid = left + size / 2; +// if arr[mid] < value { +// left = mid + 1; +// } else { +// right = mid; +// } +// size = right - left; +// } +// if arr[left] <= value { +// left + 1 +// } else { +// left +// } +// } + /// Binary search for the index position of the given value in the given array. /// The array must be sorted in ascending order and contain no duplicates. /// @@ -43,9 +85,9 @@ fn binary_search(arr: &[T], value: T, left: usize, right: /// https://docs.python.org/3/library/bisect.html#bisect.bisect /// // #[inline(always)] -fn binary_search_with_mid( - arr: &[T], - value: T, +fn binary_search_with_mid + ?Sized>( + arr: &T, + value: I, left: usize, right: usize, mid: usize, @@ -56,7 +98,7 @@ fn binary_search_with_mid( let mut mid: usize = mid; // Return the index where the value is <= arr[index] and arr[index+1] < value while left < right { - if arr[mid] < value { + if arr.get(mid) < value { left = mid + 1; } else { right = mid; @@ -64,7 +106,7 @@ fn binary_search_with_mid( let size = right - left; mid = left + size / 2; } - if arr[left] <= value { + if arr.get(left) <= value { left + 1 } else { left @@ -132,6 +174,7 @@ pub(crate) fn get_equidistant_bin_idx_iterator_parallel( ) -> impl IndexedParallelIterator> + '_> + '_ where T: Num + FromPrimitive + AsPrimitive + Sync + Send, + [T]: BinSearchParam, { assert!(nb_bins >= 2); // 1. Compute the step between each bin @@ -217,7 +260,8 @@ mod tests { #[test] fn test_binary_search() { - let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + // https://rust-lang.github.io/rfcs/2000-const-generics.html + let arr = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; assert_eq!(binary_search(&arr, 0, 0, arr.len() - 1), 0); assert_eq!(binary_search(&arr, 1, 0, arr.len() - 1), 1); assert_eq!(binary_search(&arr, 2, 0, arr.len() - 1), 2); @@ -234,7 +278,8 @@ mod tests { #[test] fn test_binary_search_with_mid() { - let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + // https://rust-lang.github.io/rfcs/2000-const-generics.html + let arr = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; assert_eq!(binary_search_with_mid(&arr, 0, 0, arr.len() - 1, 0), 0); assert_eq!(binary_search_with_mid(&arr, 1, 0, arr.len() - 1, 0), 1); assert_eq!(binary_search_with_mid(&arr, 2, 0, arr.len() - 1, 1), 2); From 0c5fb585dfa30e4fbdeef9cc2734c6478a28b351 Mon Sep 17 00:00:00 2001 From: Niels Praet Date: Thu, 21 Sep 2023 14:33:11 +0200 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=92=A9=20refactor:=20generalize=20get?= =?UTF-8?q?=5Fequidistant=5Fbin=5Fidx=5Fiterator=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- downsample_rs/src/searchsorted.rs | 71 ++++++++++++++++++------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/downsample_rs/src/searchsorted.rs b/downsample_rs/src/searchsorted.rs index f824728..3b89b2f 100644 --- a/downsample_rs/src/searchsorted.rs +++ b/downsample_rs/src/searchsorted.rs @@ -1,3 +1,4 @@ +use polars::prelude::{ChunkedArray, PolarsNumericType}; use rayon::iter::IndexedParallelIterator; use rayon::prelude::*; @@ -55,27 +56,6 @@ fn binary_search + ?Sized>( } } -// fn binary_search(arr: &[T], value: T, left: usize, right: usize) -> usize { -// let mut size: usize = right - left; -// let mut left: usize = left; -// let mut right: usize = right; -// // Return the index where the value is >= arr[index] and arr[index-1] < value -// while left < right { -// let mid = left + size / 2; -// if arr[mid] < value { -// left = mid + 1; -// } else { -// right = mid; -// } -// size = right - left; -// } -// if arr[left] <= value { -// left + 1 -// } else { -// left -// } -// } - /// Binary search for the index position of the given value in the given array. /// The array must be sorted in ascending order and contain no duplicates. /// @@ -117,23 +97,52 @@ fn binary_search_with_mid + ?Sized>( // --- Sequential version -pub(crate) fn get_equidistant_bin_idx_iterator( - arr: &[T], +pub trait EqBinIdxIteratorParam { + fn get(&self, index: usize) -> T; + + fn len(&self) -> usize; +} + +impl EqBinIdxIteratorParam for Vec { + fn get(&self, index: usize) -> T { + self[index].clone() + } + + fn len(&self) -> usize { + self.len() + } +} + +impl EqBinIdxIteratorParam for [T] { + fn get(&self, index: usize) -> T { + self[index].clone() + } + + fn len(&self) -> usize { + self.len() + } +} + +pub(crate) fn get_equidistant_bin_idx_iterator< + I, + T: EqBinIdxIteratorParam + BinSearchParam + ?Sized, +>( + arr: &T, nb_bins: usize, ) -> impl Iterator> + '_ where - T: Num + FromPrimitive + AsPrimitive, + I: Num + FromPrimitive + AsPrimitive, { assert!(nb_bins >= 2); // 1. Compute the step between each bin // Divide by nb_bins to avoid overflow! - let val_step: f64 = - (arr[arr.len() - 1].as_() / nb_bins as f64) - (arr[0].as_() / nb_bins as f64); + let val_step: f64 = (EqBinIdxIteratorParam::get(arr, arr.len() - 1).as_() / nb_bins as f64) + - (EqBinIdxIteratorParam::get(arr, 0).as_() / nb_bins as f64); // Estimate the step between each index (used to pre-guess the mid index) let idx_step: usize = arr.len() / nb_bins; // 2. The moving index & value - let arr0: f64 = arr[0].as_(); // The first value of the array + let arr0: f64 = EqBinIdxIteratorParam::get(arr, 0).as_(); // The first value of the array let mut idx: usize = 0; // Index of the search value // 3. Iterate over the bins @@ -141,8 +150,8 @@ where let start_idx: usize = idx; // Start index of the bin (previous end index) // Update the search value - let search_value: T = T::from_f64(arr0 + val_step * (i + 1) as f64).unwrap(); - if arr[start_idx] >= search_value { + let search_value: I = I::from_f64(arr0 + val_step * (i + 1) as f64).unwrap(); + if EqBinIdxIteratorParam::get(arr, start_idx) >= search_value { // If the first value of the bin is already >= the search value, // then the bin is empty. return None; @@ -246,6 +255,7 @@ mod tests { fn test_search_sorted_identicial_to_np_linspace_searchsorted() { // Create a 0..9999 array let arr: [u32; 10_000] = core::array::from_fn(|i| i.as_()); + let arr = Vec::from(arr); assert!(arr.len() == 10_000); let iterator = get_equidistant_bin_idx_iterator(&arr, 4); // Check the iterator @@ -300,6 +310,7 @@ mod tests { let expected_indices = vec![0, 4, 7]; let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let arr = Vec::from(arr); let bin_idxs_iter = get_equidistant_bin_idx_iterator(&arr, 3); let bin_idxs = bin_idxs_iter.map(|x| x.unwrap().0).collect::>(); assert_eq!(bin_idxs, expected_indices); @@ -323,7 +334,7 @@ mod tests { arr.sort_by(|a, b| a.partial_cmp(b).unwrap()); // Calculate the bin indexes - let bin_idxs_iter = get_equidistant_bin_idx_iterator(&arr[..], nb_bins); + let bin_idxs_iter = get_equidistant_bin_idx_iterator(&arr, nb_bins); let bin_idxs = bin_idxs_iter.map(|x| x.unwrap().0).collect::>(); // Calculate the bin indexes in parallel From 2f86acfbe0a3ff727542ec00fb43dc82acf8e390 Mon Sep 17 00:00:00 2001 From: Niels Praet Date: Thu, 21 Sep 2023 14:41:36 +0200 Subject: [PATCH 7/8] =?UTF-8?q?=E2=9C=A8=20feat:=20generalize=20m4=20with?= =?UTF-8?q?=20x?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- downsample_rs/src/m4.rs | 56 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/downsample_rs/src/m4.rs b/downsample_rs/src/m4.rs index 458f7e0..abdefe8 100644 --- a/downsample_rs/src/m4.rs +++ b/downsample_rs/src/m4.rs @@ -68,7 +68,7 @@ where { assert_eq!(n_out % 4, 0); let bin_idx_iterator = get_equidistant_bin_idx_iterator(x, n_out / 4); - m4_generic_with_x(arr, bin_idx_iterator, n_out, |arr| arr.argminmax()) + m4_generic_with_x::(arr, bin_idx_iterator, n_out) } // ----------- WITHOUT X @@ -220,11 +220,10 @@ pub(crate) fn m4_generic_parallel( // --------------------- WITH X #[inline(always)] -pub(crate) fn m4_generic_with_x( - arr: &[T], +pub(crate) fn m4_generic_with_x( + arr: &T, bin_idx_iterator: impl Iterator>, n_out: usize, - f_argminmax: fn(&[T]) -> (usize, usize), ) -> Vec { // Assumes n_out is a multiple of 4 if n_out >= arr.len() { @@ -242,8 +241,8 @@ pub(crate) fn m4_generic_with_x( } } else { // If the bin has > 4 elements, add the first and last + argmin and argmax - let step = &arr[start..end]; - let (min_index, max_index) = f_argminmax(step); + let step = arr.slice(start, end); + let (min_index, max_index) = step.argminmax(); sampled_indices.push(start); @@ -264,6 +263,51 @@ pub(crate) fn m4_generic_with_x( sampled_indices } +// #[inline(always)] +// pub(crate) fn m4_generic_with_x( +// arr: &[T], +// bin_idx_iterator: impl Iterator>, +// n_out: usize, +// f_argminmax: fn(&[T]) -> (usize, usize), +// ) -> Vec { +// // Assumes n_out is a multiple of 4 +// if n_out >= arr.len() { +// return (0..arr.len()).collect::>(); +// } +// +// let mut sampled_indices: Vec = Vec::with_capacity(n_out); +// +// bin_idx_iterator.for_each(|bin| { +// if let Some((start, end)) = bin { +// if end <= start + 4 { +// // If the bin has <= 4 elements, just add them all +// for i in start..end { +// sampled_indices.push(i); +// } +// } else { +// // If the bin has > 4 elements, add the first and last + argmin and argmax +// let step = &arr[start..end]; +// let (min_index, max_index) = f_argminmax(step); +// +// sampled_indices.push(start); +// +// // Add the indexes in sorted order +// if min_index < max_index { +// sampled_indices.push(min_index + start); +// sampled_indices.push(max_index + start); +// } else { +// sampled_indices.push(max_index + start); +// sampled_indices.push(min_index + start); +// } +// +// sampled_indices.push(end - 1); +// } +// } +// }); +// +// sampled_indices +// } + #[inline(always)] pub(crate) fn m4_generic_with_x_parallel( arr: &[T], From d3d71cdf21b4cc66a15a47b703163ad376e0b7b2 Mon Sep 17 00:00:00 2001 From: Niels Praet Date: Thu, 21 Sep 2023 15:47:19 +0200 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=9A=A7=20refactor:=20starting=20to=20?= =?UTF-8?q?clean=20up=20traits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- downsample_rs/src/m4.rs | 45 ------------------------------- downsample_rs/src/searchsorted.rs | 15 +++++------ downsample_rs/src/types.rs | 43 +++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 54 deletions(-) diff --git a/downsample_rs/src/m4.rs b/downsample_rs/src/m4.rs index abdefe8..7f4e582 100644 --- a/downsample_rs/src/m4.rs +++ b/downsample_rs/src/m4.rs @@ -263,51 +263,6 @@ pub(crate) fn m4_generic_with_x( sampled_indices } -// #[inline(always)] -// pub(crate) fn m4_generic_with_x( -// arr: &[T], -// bin_idx_iterator: impl Iterator>, -// n_out: usize, -// f_argminmax: fn(&[T]) -> (usize, usize), -// ) -> Vec { -// // Assumes n_out is a multiple of 4 -// if n_out >= arr.len() { -// return (0..arr.len()).collect::>(); -// } -// -// let mut sampled_indices: Vec = Vec::with_capacity(n_out); -// -// bin_idx_iterator.for_each(|bin| { -// if let Some((start, end)) = bin { -// if end <= start + 4 { -// // If the bin has <= 4 elements, just add them all -// for i in start..end { -// sampled_indices.push(i); -// } -// } else { -// // If the bin has > 4 elements, add the first and last + argmin and argmax -// let step = &arr[start..end]; -// let (min_index, max_index) = f_argminmax(step); -// -// sampled_indices.push(start); -// -// // Add the indexes in sorted order -// if min_index < max_index { -// sampled_indices.push(min_index + start); -// sampled_indices.push(max_index + start); -// } else { -// sampled_indices.push(max_index + start); -// sampled_indices.push(min_index + start); -// } -// -// sampled_indices.push(end - 1); -// } -// } -// }); -// -// sampled_indices -// } - #[inline(always)] pub(crate) fn m4_generic_with_x_parallel( arr: &[T], diff --git a/downsample_rs/src/searchsorted.rs b/downsample_rs/src/searchsorted.rs index 3b89b2f..5a0d552 100644 --- a/downsample_rs/src/searchsorted.rs +++ b/downsample_rs/src/searchsorted.rs @@ -1,25 +1,22 @@ -use polars::prelude::{ChunkedArray, PolarsNumericType}; use rayon::iter::IndexedParallelIterator; use rayon::prelude::*; +use crate::types::{BinSearchParam, Indexable}; + use super::types::Num; use num_traits::{AsPrimitive, FromPrimitive}; // ---------------------- Binary search ---------------------- -pub trait BinSearchParam { - fn get(&self, index: usize) -> T; -} - -impl BinSearchParam for [T] { +impl Indexable for Vec { fn get(&self, index: usize) -> T { - self[index] + self[index].clone() } } -impl BinSearchParam for Vec { +impl Indexable for [T] { fn get(&self, index: usize) -> T { - self[index] + self[index].clone() } } diff --git a/downsample_rs/src/types.rs b/downsample_rs/src/types.rs index 6d50ddf..f026d2d 100644 --- a/downsample_rs/src/types.rs +++ b/downsample_rs/src/types.rs @@ -1,5 +1,9 @@ use std::ops::{Add, Div, Mul, Sub}; +use argminmax::ArgMinMax; + +use crate::helpers::Average; + pub trait Num: Copy + PartialOrd @@ -15,3 +19,42 @@ impl Num for T where T: Copy + PartialOrd + Add + Sub + Mul + Div { } + +pub trait Indexable { + fn get(&self, index: usize) -> T; +} + +pub trait HasLength { + fn len(&self) -> usize; +} + +pub trait Sliceable { + type SliceType; + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType; +} + +pub trait IntoIter { + type IterType: Iterator; + + fn into_iter(&self) -> Self::IterType; +} + +pub trait M4Param: HasLength + ArgMinMax + Sliceable {} + +impl M4Param for T where T: HasLength + ArgMinMax + Sliceable {} + +pub trait LttbParam: HasLength + Indexable + IntoIter + Sliceable + Average {} + +impl> LttbParam for T where + T: HasLength + Indexable + IntoIter + Sliceable + Average +{ +} + +pub trait BinSearchParam: Indexable {} + +impl BinSearchParam for T where T: Indexable {} + +pub trait EqBinIdxIteratorParam: Indexable + HasLength {} + +impl EqBinIdxIteratorParam for T where T: Indexable + HasLength {}