diff --git a/argus-core/src/signals.rs b/argus-core/src/signals.rs index 890b881..0b6dfcd 100644 --- a/argus-core/src/signals.rs +++ b/argus-core/src/signals.rs @@ -4,6 +4,7 @@ mod cast; mod cmp_ops; pub mod iter; mod num_ops; +mod shift_ops; pub mod traits; mod utils; @@ -16,6 +17,7 @@ pub use cmp_ops::*; use itertools::Itertools; pub use num_ops::*; use num_traits::{Num, NumCast}; +pub use shift_ops::*; pub use traits::*; use utils::intersect_bounds; diff --git a/argus-core/src/signals/shift_ops.rs b/argus-core/src/signals/shift_ops.rs new file mode 100644 index 0000000..484b0f1 --- /dev/null +++ b/argus-core/src/signals/shift_ops.rs @@ -0,0 +1,61 @@ +use core::iter::zip; +use core::time::Duration; + +use itertools::{enumerate, Itertools}; + +use super::traits::LinearInterpolatable; +use super::{InterpolationMethod, Signal}; + +impl Signal +where + T: Copy + LinearInterpolatable, +{ + /// Shift all samples in the signal by `delta` amount to the left. + /// + /// This essentially filters out all samples with time points greater than `delta`, + /// and subtracts `delta` from the rest of the time points. + pub fn shift_left(&self, delta: Duration) -> Self { + match self { + Signal::Sampled { values, time_points } => { + // We want to skip any time points < delta, and subtract the rest. + // Moreover, if the signal doesn't start at 0 after the shift, we may + // want to interpolate from the previous value. + + // Find the first index that satisfies `t >= delta` while also checking + // if we need to interpolate + let Some((idx, first_t)) = time_points.into_iter().find_position(|&t| t >= &delta) + else { + // Return an empty signal (we exhauseted all samples). + return Signal::Empty; + }; + + let mut new_samples: Vec<(Duration, T)> = Vec::with_capacity(time_points.len() - idx); + // Let's see if we need to find a new sample + if idx > 0 && first_t != &delta { + // The shifted signal will not start at 0, and we have a previous + // index to interpolate from. + let v = self.interpolate_at(delta, InterpolationMethod::Linear).unwrap(); + new_samples.push((Duration::ZERO, v)); + } + // Shift the rest of the samples + new_samples.extend(zip(&time_points[idx..], &values[idx..]).map(|(&t, &v)| (t - delta, v))); + new_samples.into_iter().collect() + } + // Empty and constant signals can't really be changed + sig => sig.clone(), + } + } + + /// Shift all samples in the signal by `delta` amount to the right. + /// + /// This essentially adds `delta` to all time points. + pub fn shift_right(&self, delta: Duration) -> Self { + match self { + Signal::Sampled { values, time_points } => { + zip(time_points, values).map(|(&t, &v)| (t + delta, v)).collect() + } + // Empty and constant signals can't really be changed + sig => sig.clone(), + } + } +}