From b517043d0e0fe7676ee97e022762d50fab88cc43 Mon Sep 17 00:00:00 2001 From: Anand Balakrishnan Date: Tue, 4 Apr 2023 09:57:20 -0700 Subject: [PATCH] feat(core): Add casting and correct subtraction/cmp --- argus-core/src/signals.rs | 36 ++++ argus-core/src/signals/cast.rs | 80 +++++++++ argus-core/src/signals/cmp_ops.rs | 167 +++++++++--------- argus-core/src/signals/num_ops.rs | 275 ++++++++++++++++++------------ argus-core/src/signals/traits.rs | 32 +++- argus-core/src/signals/utils.rs | 51 +++++- 6 files changed, 446 insertions(+), 195 deletions(-) create mode 100644 argus-core/src/signals/cast.rs diff --git a/argus-core/src/signals.rs b/argus-core/src/signals.rs index ddbe3f6..ca970e0 100644 --- a/argus-core/src/signals.rs +++ b/argus-core/src/signals.rs @@ -9,6 +9,7 @@ //! its domain, and thus, do not require interpolation and extrapolation. Moreover, //! since they are defined over the entire time domain, they cannot be iterated over. pub mod bool_ops; +pub mod cast; pub mod cmp_ops; pub mod iter; pub mod num_ops; @@ -19,6 +20,7 @@ use std::ops::{RangeFull, RangeInclusive}; use std::time::Duration; pub use bool_ops::*; +pub use cast::*; pub use cmp_ops::*; pub use num_ops::*; @@ -326,6 +328,40 @@ pub mod arbitrary { { any::().prop_map(ConstantSignal::new) } + + /// Generate an arbitrary boolean signal + pub fn any_bool_signal(size: impl Into) -> impl Strategy { + prop_oneof![ + constant_signal::().prop_map(AnySignal::from), + sampled_signal::(size).prop_map(AnySignal::from), + ] + } + + /// Generate an arbitrary numeric signal + pub fn any_num_signal(size: impl Into + Clone) -> impl Strategy { + prop_oneof![ + constant_signal::().prop_map(AnySignal::from), + constant_signal::().prop_map(AnySignal::from), + constant_signal::().prop_map(AnySignal::from), + sampled_signal::(size.clone()).prop_map(AnySignal::from), + sampled_signal::(size.clone()).prop_map(AnySignal::from), + sampled_signal::(size).prop_map(AnySignal::from), + ] + } + + /// Generate an arbitrary signal + pub fn any_signal(size: impl Into + Clone) -> impl Strategy { + prop_oneof![ + constant_signal::().prop_map(AnySignal::from), + constant_signal::().prop_map(AnySignal::from), + constant_signal::().prop_map(AnySignal::from), + constant_signal::().prop_map(AnySignal::from), + sampled_signal::(size.clone()).prop_map(AnySignal::from), + sampled_signal::(size.clone()).prop_map(AnySignal::from), + sampled_signal::(size.clone()).prop_map(AnySignal::from), + sampled_signal::(size).prop_map(AnySignal::from), + ] + } } #[cfg(test)] diff --git a/argus-core/src/signals/cast.rs b/argus-core/src/signals/cast.rs new file mode 100644 index 0000000..23fa6c0 --- /dev/null +++ b/argus-core/src/signals/cast.rs @@ -0,0 +1,80 @@ +use itertools::Itertools; +use num_traits::{Num, NumCast}; + +use crate::signals::traits::SignalNumCast; +use crate::signals::{AnySignal, ConstantSignal, Signal}; + +macro_rules! impl_cast { + ($type:ty) => { + paste::paste! { + #[inline] + fn [](&self) -> Option> { + let samples = self + .iter() + .map_while(|(&t, &v)| num_traits::cast::<_, $type>(v).map(|v| (t, v))) + .collect_vec(); + if samples.len() < self.time_points.len() { + // Failed to convert some item + None + } else { + Some(samples.into_iter().collect()) + } + } + } + }; +} + +impl SignalNumCast for Signal +where + T: Num + NumCast + Copy, +{ + type Value = T; + + type Output = Signal + where + U: Num + NumCast + Copy; + + impl_cast!(i8); + impl_cast!(i16); + impl_cast!(i32); + impl_cast!(i64); + impl_cast!(u8); + impl_cast!(u16); + impl_cast!(u32); + impl_cast!(u64); + impl_cast!(f32); + impl_cast!(f64); +} + +macro_rules! impl_cast { + ($type:ty) => { + paste::paste! { + #[inline] + fn [](&self) -> Option> { + num_traits::cast::<_, $type>(self.value).map(ConstantSignal::new) + } + } + }; +} + +impl SignalNumCast for ConstantSignal +where + T: Num + NumCast + Copy, +{ + type Value = T; + + type Output = ConstantSignal + where + U: Num + NumCast + Copy; + + impl_cast!(i8); + impl_cast!(i16); + impl_cast!(i32); + impl_cast!(i64); + impl_cast!(u8); + impl_cast!(u16); + impl_cast!(u32); + impl_cast!(u64); + impl_cast!(f32); + impl_cast!(f64); +} diff --git a/argus-core/src/signals/cmp_ops.rs b/argus-core/src/signals/cmp_ops.rs index 54e20ee..15fa482 100644 --- a/argus-core/src/signals/cmp_ops.rs +++ b/argus-core/src/signals/cmp_ops.rs @@ -1,77 +1,10 @@ use std::cmp::Ordering; -use std::time::Duration; use num_traits::NumCast; -use super::traits::{BaseSignal, LinearInterpolatable, SignalPartialOrd, SignalSyncPoints}; -use super::{ConstantSignal, Signal}; -use crate::signals::utils::{find_intersection, Neighborhood}; -use crate::signals::{InterpolationMethod, Sample}; - -fn sync_with_intersection<'a, T, Sig1, Sig2, F>( - sig1: &'a Sig1, - sig2: &'a Sig2, - sync_points: &[&'a Duration], - op: F, -) -> Signal -where - F: Fn(Ordering) -> bool, - T: PartialOrd + Copy + std::fmt::Debug + NumCast + LinearInterpolatable, - Sig1: BaseSignal, - Sig2: BaseSignal, -{ - // This has to be manually implemented and cannot use the apply2 functions. - // This is because if we have two signals that cross each other, then there is - // an intermediate point where the two signals are equal. This point must be - // added to the signal appropriately. - use Ordering::*; - // This will contain the new signal with an initial capacity of twice the input - // signals sample points (as that is the upper limit of the number of new points - // that will be added - let mut return_signal = Signal::::new_with_capacity(sync_points.len() * 2); - // this will contain the last sample point and ordering - let mut last_sample = None; - // We will now loop over the sync points, compare across signals and (if - // an intersection happens) we will have to compute the intersection point - for &t in sync_points { - let lhs = sig1.at(*t).expect("value must be present at given time"); - let rhs = sig2.at(*t).expect("values must be present at given time"); - let ord = lhs.partial_cmp(rhs).unwrap(); - - if let Some((tm1, last)) = last_sample { - // Check if the signals crossed, this will happen essentiall if the last - // and the current are opposites and were not Equal. - if let (Less, Greater) | (Greater, Less) = (last, ord) { - // Find the point of intersection between the points. - let a = Neighborhood { - first: sig1.at(tm1).copied().map(|value| Sample { time: tm1, value }), - second: sig1.at(*t).copied().map(|value| Sample { time: *t, value }), - }; - let b = Neighborhood { - first: sig2.at(tm1).copied().map(|value| Sample { time: tm1, value }), - second: sig2.at(*t).copied().map(|value| Sample { time: *t, value }), - }; - let intersect = find_intersection(&a, &b); - { - let lhs = sig1 - .interpolate_at(intersect.time, InterpolationMethod::Linear) - .unwrap(); - let rhs = sig2 - .interpolate_at(intersect.time, InterpolationMethod::Linear) - .unwrap(); - assert_eq!(lhs, rhs); - } - return_signal - .push(intersect.time, op(Equal)) - .expect("Signal should already be monotonic"); - } - } - last_sample = Some((*t, ord)); - } - return_signal.time_points.shrink_to_fit(); - return_signal.values.shrink_to_fit(); - return_signal -} +use super::traits::{BaseSignal, LinearInterpolatable, SignalMinMax, SignalPartialOrd}; +use super::utils::sync_with_intersection; +use super::{ConstantSignal, InterpolationMethod, Signal}; impl SignalPartialOrd for Signal where @@ -83,17 +16,22 @@ where where F: Fn(Ordering) -> bool, { + use super::InterpolationMethod::Linear; // This has to be manually implemented and cannot use the apply2 functions. // This is because if we have two signals that cross each other, then there is // an intermediate point where the two signals are equal. This point must be // added to the signal appropriately. // the union of the sample points in self and other - let sync_points = match self.synchronization_points(other) { - Some(points) => points, - None => return None, - }; - - Some(sync_with_intersection(self, other, &sync_points, op)) + let sync_points = sync_with_intersection(self, other)?; + let sig: Signal = sync_points + .into_iter() + .map(|t| { + let lhs = self.interpolate_at(t, Linear).unwrap(); + let rhs = other.interpolate_at(t, Linear).unwrap(); + (t, op(lhs.partial_cmp(&rhs).unwrap())) + }) + .collect(); + Some(sig) } } @@ -107,13 +45,22 @@ where where F: Fn(Ordering) -> bool, { + use super::InterpolationMethod::Linear; + // This has to be manually implemented and cannot use the apply2 functions. + // This is because if we have two signals that cross each other, then there is + // an intermediate point where the two signals are equal. This point must be + // added to the signal appropriately. // the union of the sample points in self and other - let sync_points = match self.synchronization_points(other) { - Some(points) => points, - None => return None, - }; - - Some(sync_with_intersection(self, other, &sync_points, op)) + let sync_points = sync_with_intersection(self, other)?; + let sig: Signal = sync_points + .into_iter() + .map(|t| { + let lhs = self.interpolate_at(t, Linear).unwrap(); + let rhs = other.interpolate_at(t, Linear).unwrap(); + (t, op(lhs.partial_cmp(&rhs).unwrap())) + }) + .collect(); + Some(sig) } } @@ -144,3 +91,59 @@ where other.signal_cmp(self, op) } } + +impl SignalMinMax for ConstantSignal +where + T: PartialOrd + Copy, +{ + type Output = ConstantSignal; + + fn min(&self, rhs: &Self) -> Self::Output { + let value = if self.value < rhs.value { self.value } else { rhs.value }; + ConstantSignal::new(value) + } + + fn max(&self, rhs: &Self) -> Self::Output { + let value = if self.value > rhs.value { self.value } else { rhs.value }; + ConstantSignal::new(value) + } +} + +impl SignalMinMax for Signal +where + T: PartialOrd + Copy + num_traits::NumCast + LinearInterpolatable, +{ + type Output = Signal; + + fn min(&self, other: &Self) -> Self::Output { + let time_points = sync_with_intersection(self, other).unwrap(); + time_points + .into_iter() + .map(|t| { + let lhs = self.interpolate_at(t, InterpolationMethod::Linear).unwrap(); + let rhs = other.interpolate_at(t, InterpolationMethod::Linear).unwrap(); + if lhs < rhs { + (t, lhs) + } else { + (t, rhs) + } + }) + .collect() + } + + fn max(&self, other: &Self) -> Self::Output { + let time_points = sync_with_intersection(self, other).unwrap(); + time_points + .into_iter() + .map(|t| { + let lhs = self.interpolate_at(t, InterpolationMethod::Linear).unwrap(); + let rhs = other.interpolate_at(t, InterpolationMethod::Linear).unwrap(); + if lhs > rhs { + (t, lhs) + } else { + (t, rhs) + } + }) + .collect() + } +} diff --git a/argus-core/src/signals/num_ops.rs b/argus-core/src/signals/num_ops.rs index d5d6f12..1ecf2ad 100644 --- a/argus-core/src/signals/num_ops.rs +++ b/argus-core/src/signals/num_ops.rs @@ -1,7 +1,7 @@ use num_traits::{Num, NumCast, Signed}; -use super::traits::LinearInterpolatable; -use crate::signals::utils::{apply1, apply2, apply2_const}; +use super::traits::{BaseSignal, LinearInterpolatable}; +use crate::signals::utils::{apply1, apply2, apply2_const, sync_with_intersection}; use crate::signals::{ConstantSignal, Signal}; impl core::ops::Neg for &Signal @@ -16,113 +16,6 @@ where } } -impl core::ops::Add for &Signal -where - T: core::ops::Add + Num + NumCast + Copy + LinearInterpolatable, -{ - type Output = Signal; - - /// Add the given signal with another - fn add(self, rhs: Self) -> Self::Output { - apply2(self, rhs, |lhs, rhs| lhs + rhs) - } -} - -impl core::ops::Add<&ConstantSignal> for &Signal -where - T: core::ops::Add + Num + NumCast + Copy + LinearInterpolatable, -{ - type Output = Signal; - - /// Add the given signal with another - fn add(self, rhs: &ConstantSignal) -> Self::Output { - apply2_const(self, rhs, |lhs, rhs| lhs + rhs) - } -} - -impl core::ops::Mul for &Signal -where - T: core::ops::Mul + Num + NumCast + Copy + LinearInterpolatable, -{ - type Output = Signal; - - /// Multiply the given signal with another - fn mul(self, rhs: Self) -> Self::Output { - apply2(self, rhs, |lhs, rhs| lhs * rhs) - } -} - -impl core::ops::Mul<&ConstantSignal> for &Signal -where - T: core::ops::Mul + Num + NumCast + Copy + LinearInterpolatable, -{ - type Output = Signal; - - /// Multiply the given signal with another - fn mul(self, rhs: &ConstantSignal) -> Self::Output { - apply2_const(self, rhs, |lhs, rhs| lhs * rhs) - } -} - -impl core::ops::Sub for &Signal -where - T: core::ops::Sub + Num + NumCast + Copy + LinearInterpolatable, -{ - type Output = Signal; - - /// Subtract the given signal with another - fn sub(self, rhs: Self) -> Self::Output { - apply2(self, rhs, |lhs, rhs| lhs - rhs) - } -} - -impl core::ops::Sub<&ConstantSignal> for &Signal -where - T: core::ops::Sub + Num + NumCast + Copy + LinearInterpolatable, -{ - type Output = Signal; - - /// Subtiply the given signal with another - fn sub(self, rhs: &ConstantSignal) -> Self::Output { - apply2_const(self, rhs, |lhs, rhs| lhs - rhs) - } -} - -impl core::ops::Div for &Signal -where - T: core::ops::Div + Num + NumCast + Copy + LinearInterpolatable, -{ - type Output = Signal; - - /// Divide the given signal with another - fn div(self, rhs: Self) -> Self::Output { - apply2(self, rhs, |lhs, rhs| lhs / rhs) - } -} -impl core::ops::Div<&ConstantSignal> for &Signal -where - T: core::ops::Div + Num + NumCast + Copy + LinearInterpolatable, -{ - type Output = Signal; - - /// Divide the given signal with another - fn div(self, rhs: &ConstantSignal) -> Self::Output { - apply2_const(self, rhs, |lhs, rhs| lhs / rhs) - } -} - -impl num_traits::Pow for &Signal -where - T: num_traits::Pow + Num + NumCast + Copy + LinearInterpolatable, -{ - type Output = Signal; - - /// Returns the values in `self` to the power of the values in `other` - fn pow(self, other: Self) -> Self::Output { - apply2(self, other, |lhs, rhs| lhs.pow(rhs)) - } -} - impl core::ops::Neg for &ConstantSignal where T: Signed + Copy, @@ -135,6 +28,18 @@ where } } +impl core::ops::Add for &Signal +where + T: core::ops::Add + Num + NumCast + Copy + LinearInterpolatable, +{ + type Output = Signal; + + /// Add the given signal with another + fn add(self, rhs: Self) -> Self::Output { + apply2(self, rhs, |lhs, rhs| lhs + rhs) + } +} + impl core::ops::Add for &ConstantSignal where T: core::ops::Add + Num + Copy, @@ -147,6 +52,18 @@ where } } +impl core::ops::Add<&ConstantSignal> for &Signal +where + T: core::ops::Add + Num + NumCast + Copy + LinearInterpolatable, +{ + type Output = Signal; + + /// Add the given signal with another + fn add(self, rhs: &ConstantSignal) -> Self::Output { + apply2_const(self, rhs, |lhs, rhs| lhs + rhs) + } +} + impl core::ops::Add<&Signal> for &ConstantSignal where T: core::ops::Add + Num + NumCast + Copy + LinearInterpolatable, @@ -159,6 +76,18 @@ where } } +impl core::ops::Mul for &Signal +where + T: core::ops::Mul + Num + NumCast + Copy + LinearInterpolatable, +{ + type Output = Signal; + + /// Multiply the given signal with another + fn mul(self, rhs: Self) -> Self::Output { + apply2(self, rhs, |lhs, rhs| lhs * rhs) + } +} + impl core::ops::Mul for &ConstantSignal where T: core::ops::Mul + Num + Copy, @@ -171,6 +100,18 @@ where } } +impl core::ops::Mul<&ConstantSignal> for &Signal +where + T: core::ops::Mul + Num + NumCast + Copy + LinearInterpolatable, +{ + type Output = Signal; + + /// Multiply the given signal with another + fn mul(self, rhs: &ConstantSignal) -> Self::Output { + apply2_const(self, rhs, |lhs, rhs| lhs * rhs) + } +} + impl core::ops::Mul<&Signal> for &ConstantSignal where T: core::ops::Mul + Num + NumCast + Copy + LinearInterpolatable, @@ -183,6 +124,39 @@ where } } +impl core::ops::Sub for &Signal +where + T: core::ops::Sub + Num + NumCast + Copy + LinearInterpolatable + PartialOrd, + Signal: BaseSignal, +{ + type Output = Signal; + + /// Subtract the given signal with another + fn sub(self, rhs: Self) -> Self::Output { + use super::InterpolationMethod::Linear; + // This has to be manually implemented and cannot use the apply2 functions. + // This is because if we have two signals that cross each other, then there is + // an intermediate point where the two signals are equal. This point must be + // added to the signal appropriately. + + // If either of the signals are empty, we return an empty signal. + if self.is_empty() || rhs.is_empty() { + return Signal::new(); + } + + // the union of the sample points in self and other + let sync_points = sync_with_intersection(self, rhs).unwrap(); + sync_points + .into_iter() + .map(|t| { + let lhs = self.interpolate_at(t, Linear).unwrap(); + let rhs = rhs.interpolate_at(t, Linear).unwrap(); + (t, lhs - rhs) + }) + .collect() + } +} + impl core::ops::Sub for &ConstantSignal where T: core::ops::Sub + Num + Copy, @@ -194,15 +168,70 @@ where ConstantSignal::::new(self.value - rhs.value) } } + +impl core::ops::Sub<&ConstantSignal> for &Signal +where + T: core::ops::Sub + Num + NumCast + Copy + LinearInterpolatable + PartialOrd, + Signal: BaseSignal, + ConstantSignal: BaseSignal, +{ + type Output = Signal; + + /// Subtract the given signal with another + fn sub(self, rhs: &ConstantSignal) -> Self::Output { + use super::InterpolationMethod::Linear; + // This has to be manually implemented and cannot use the apply2 functions. + // This is because if we have two signals that cross each other, then there is + // an intermediate point where the two signals are equal. This point must be + // added to the signal appropriately. + // the union of the sample points in self and other + let sync_points = sync_with_intersection(self, rhs).unwrap(); + sync_points + .into_iter() + .map(|t| { + let lhs = self.interpolate_at(t, Linear).unwrap(); + let rhs = rhs.interpolate_at(t, Linear).unwrap(); + (t, lhs - rhs) + }) + .collect() + } +} + impl core::ops::Sub<&Signal> for &ConstantSignal where - T: core::ops::Sub + Num + NumCast + Copy + LinearInterpolatable, + T: core::ops::Sub + Num + NumCast + Copy + LinearInterpolatable + PartialOrd, { type Output = Signal; /// Subtract the given signal with another fn sub(self, rhs: &Signal) -> Self::Output { - apply2_const(rhs, self, |rhs, lhs| lhs - rhs) + use super::InterpolationMethod::Linear; + // This has to be manually implemented and cannot use the apply2 functions. + // This is because if we have two signals that cross each other, then there is + // an intermediate point where the two signals are equal. This point must be + // added to the signal appropriately. + // the union of the sample points in self and other + let sync_points = sync_with_intersection(self, rhs).unwrap(); + sync_points + .into_iter() + .map(|t| { + let lhs = self.interpolate_at(t, Linear).unwrap(); + let rhs = rhs.interpolate_at(t, Linear).unwrap(); + (t, lhs - rhs) + }) + .collect() + } +} + +impl core::ops::Div for &Signal +where + T: core::ops::Div + Num + NumCast + Copy + LinearInterpolatable, +{ + type Output = Signal; + + /// Divide the given signal with another + fn div(self, rhs: Self) -> Self::Output { + apply2(self, rhs, |lhs, rhs| lhs / rhs) } } @@ -218,6 +247,18 @@ where } } +impl core::ops::Div<&ConstantSignal> for &Signal +where + T: core::ops::Div + Num + NumCast + Copy + LinearInterpolatable, +{ + type Output = Signal; + + /// Divide the given signal with another + fn div(self, rhs: &ConstantSignal) -> Self::Output { + apply2_const(self, rhs, |lhs, rhs| lhs / rhs) + } +} + impl core::ops::Div<&Signal> for &ConstantSignal where T: core::ops::Div + Num + NumCast + Copy + LinearInterpolatable, @@ -229,3 +270,15 @@ where apply2_const(rhs, self, |rhs, lhs| lhs / rhs) } } + +impl num_traits::Pow for &Signal +where + T: num_traits::Pow + Num + NumCast + Copy + LinearInterpolatable, +{ + type Output = Signal; + + /// Returns the values in `self` to the power of the values in `other` + fn pow(self, other: Self) -> Self::Output { + apply2(self, other, |lhs, rhs| lhs.pow(rhs)) + } +} diff --git a/argus-core/src/signals/traits.rs b/argus-core/src/signals/traits.rs index 9b97383..aafe789 100644 --- a/argus-core/src/signals/traits.rs +++ b/argus-core/src/signals/traits.rs @@ -4,7 +4,7 @@ use std::ops::RangeBounds; use std::time::Duration; use itertools::Itertools; -use num_traits::Num; +use num_traits::{Num, NumCast}; use paste::paste; use super::{ConstantSignal, InterpolationMethod, Sample, Signal}; @@ -326,3 +326,33 @@ pub trait SignalPartialOrd: BaseSignal { impl_signal_cmp!(eq); impl_signal_cmp!(ne); } + +/// Time-wise min-max of signal types +pub trait SignalMinMax: BaseSignal { + type Output: BaseSignal; + + /// Compute the time-wise min of two signals + fn min(&self, rhs: &Rhs) -> Self::Output; + + /// Compute the time-wise max of two signals + fn max(&self, rhs: &Rhs) -> Self::Output; +} + +/// Trait for converting between numeric signal types +pub trait SignalNumCast { + type Value: Num + NumCast; + type Output: BaseSignal + where + T: Num + NumCast + Copy; + + fn to_i8(&self) -> Option>; + fn to_i16(&self) -> Option>; + fn to_i32(&self) -> Option>; + fn to_i64(&self) -> Option>; + fn to_u8(&self) -> Option>; + fn to_u16(&self) -> Option>; + fn to_u32(&self) -> Option>; + fn to_u64(&self) -> Option>; + fn to_f32(&self) -> Option>; + fn to_f64(&self) -> Option>; +} diff --git a/argus-core/src/signals/utils.rs b/argus-core/src/signals/utils.rs index 6e63ae8..96343cf 100644 --- a/argus-core/src/signals/utils.rs +++ b/argus-core/src/signals/utils.rs @@ -6,6 +6,7 @@ use core::ops::{Bound, RangeBounds}; use core::time::Duration; +use std::cmp::Ordering; use num_traits::NumCast; @@ -30,7 +31,7 @@ pub struct Neighborhood { /// lines. pub fn find_intersection(a: &Neighborhood, b: &Neighborhood) -> Sample where - T: Copy + std::fmt::Debug + NumCast, + T: Copy + NumCast, { // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line use num_traits::cast; @@ -60,6 +61,54 @@ where Sample { time: t, value: y } } +/// Augment synchronization points with time points where signals intersect +pub fn sync_with_intersection<'a, T, Sig1, Sig2>(sig1: &'a Sig1, sig2: &'a Sig2) -> Option> +where + T: PartialOrd + Copy + NumCast + LinearInterpolatable, + Sig1: BaseSignal + SignalSyncPoints, + Sig2: BaseSignal + SignalSyncPoints, +{ + use Ordering::*; + let sync_points: Vec<&Duration> = sig1.synchronization_points(sig2)?.into_iter().collect(); + // This will contain the new signal with an initial capacity of twice the input + // signals sample points (as that is the upper limit of the number of new points + // that will be added + let mut return_points = Vec::::with_capacity(sync_points.len() * 2); + // this will contain the last sample point and ordering + let mut last_sample = None; + // We will now loop over the sync points, compare across signals and (if + // an intersection happens) we will have to compute the intersection point + for t in sync_points { + let lhs = sig1.at(*t).expect("value must be present at given time"); + let rhs = sig2.at(*t).expect("values must be present at given time"); + let ord = lhs.partial_cmp(rhs).unwrap(); + + // We will check for any intersections between the current sample and the + // previous one before we push the current sample time + if let Some((tm1, last)) = last_sample { + // Check if the signals crossed, this will happen essentially if the last + // and the current are opposites and were not Equal. + if let (Less, Greater) | (Greater, Less) = (last, ord) { + // Find the point of intersection between the points. + let a = Neighborhood { + first: sig1.at(tm1).copied().map(|value| Sample { time: tm1, value }), + second: sig1.at(*t).copied().map(|value| Sample { time: *t, value }), + }; + let b = Neighborhood { + first: sig2.at(tm1).copied().map(|value| Sample { time: tm1, value }), + second: sig2.at(*t).copied().map(|value| Sample { time: *t, value }), + }; + let intersect = find_intersection(&a, &b); + return_points.push(intersect.time); + } + } + return_points.push(*t); + last_sample = Some((*t, ord)); + } + return_points.shrink_to_fit(); + Some(return_points) +} + pub fn apply1(signal: &Signal, op: F) -> Signal where T: Copy,