feat(core): Add casting and correct subtraction/cmp

This commit is contained in:
Anand Balakrishnan 2023-04-04 09:57:20 -07:00
parent 7c8c833469
commit b517043d0e
No known key found for this signature in database
6 changed files with 446 additions and 195 deletions

View file

@ -9,6 +9,7 @@
//! its domain, and thus, do not require interpolation and extrapolation. Moreover, //! 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. //! since they are defined over the entire time domain, they cannot be iterated over.
pub mod bool_ops; pub mod bool_ops;
pub mod cast;
pub mod cmp_ops; pub mod cmp_ops;
pub mod iter; pub mod iter;
pub mod num_ops; pub mod num_ops;
@ -19,6 +20,7 @@ use std::ops::{RangeFull, RangeInclusive};
use std::time::Duration; use std::time::Duration;
pub use bool_ops::*; pub use bool_ops::*;
pub use cast::*;
pub use cmp_ops::*; pub use cmp_ops::*;
pub use num_ops::*; pub use num_ops::*;
@ -326,6 +328,40 @@ pub mod arbitrary {
{ {
any::<T>().prop_map(ConstantSignal::new) any::<T>().prop_map(ConstantSignal::new)
} }
/// Generate an arbitrary boolean signal
pub fn any_bool_signal(size: impl Into<SizeRange>) -> impl Strategy<Value = AnySignal> {
prop_oneof![
constant_signal::<bool>().prop_map(AnySignal::from),
sampled_signal::<bool>(size).prop_map(AnySignal::from),
]
}
/// Generate an arbitrary numeric signal
pub fn any_num_signal(size: impl Into<SizeRange> + Clone) -> impl Strategy<Value = AnySignal> {
prop_oneof![
constant_signal::<i64>().prop_map(AnySignal::from),
constant_signal::<u64>().prop_map(AnySignal::from),
constant_signal::<f64>().prop_map(AnySignal::from),
sampled_signal::<i64>(size.clone()).prop_map(AnySignal::from),
sampled_signal::<u64>(size.clone()).prop_map(AnySignal::from),
sampled_signal::<f64>(size).prop_map(AnySignal::from),
]
}
/// Generate an arbitrary signal
pub fn any_signal(size: impl Into<SizeRange> + Clone) -> impl Strategy<Value = AnySignal> {
prop_oneof![
constant_signal::<bool>().prop_map(AnySignal::from),
constant_signal::<i64>().prop_map(AnySignal::from),
constant_signal::<u64>().prop_map(AnySignal::from),
constant_signal::<f64>().prop_map(AnySignal::from),
sampled_signal::<bool>(size.clone()).prop_map(AnySignal::from),
sampled_signal::<i64>(size.clone()).prop_map(AnySignal::from),
sampled_signal::<u64>(size.clone()).prop_map(AnySignal::from),
sampled_signal::<f64>(size).prop_map(AnySignal::from),
]
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -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 [<to_ $type>](&self) -> Option<Signal<$type>> {
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<T> SignalNumCast for Signal<T>
where
T: Num + NumCast + Copy,
{
type Value = T;
type Output<U> = Signal<U>
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 [<to_ $type>](&self) -> Option<ConstantSignal<$type>> {
num_traits::cast::<_, $type>(self.value).map(ConstantSignal::new)
}
}
};
}
impl<T> SignalNumCast for ConstantSignal<T>
where
T: Num + NumCast + Copy,
{
type Value = T;
type Output<U> = ConstantSignal<U>
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);
}

View file

@ -1,77 +1,10 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::time::Duration;
use num_traits::NumCast; use num_traits::NumCast;
use super::traits::{BaseSignal, LinearInterpolatable, SignalPartialOrd, SignalSyncPoints}; use super::traits::{BaseSignal, LinearInterpolatable, SignalMinMax, SignalPartialOrd};
use super::{ConstantSignal, Signal}; use super::utils::sync_with_intersection;
use crate::signals::utils::{find_intersection, Neighborhood}; use super::{ConstantSignal, InterpolationMethod, Signal};
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<bool>
where
F: Fn(Ordering) -> bool,
T: PartialOrd + Copy + std::fmt::Debug + NumCast + LinearInterpolatable,
Sig1: BaseSignal<Value = T>,
Sig2: BaseSignal<Value = T>,
{
// 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::<bool>::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
}
impl<T> SignalPartialOrd<Self> for Signal<T> impl<T> SignalPartialOrd<Self> for Signal<T>
where where
@ -83,17 +16,22 @@ where
where where
F: Fn(Ordering) -> bool, F: Fn(Ordering) -> bool,
{ {
use super::InterpolationMethod::Linear;
// This has to be manually implemented and cannot use the apply2 functions. // 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 // 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 // an intermediate point where the two signals are equal. This point must be
// added to the signal appropriately. // added to the signal appropriately.
// the union of the sample points in self and other // the union of the sample points in self and other
let sync_points = match self.synchronization_points(other) { let sync_points = sync_with_intersection(self, other)?;
Some(points) => points, let sig: Signal<bool> = sync_points
None => return None, .into_iter()
}; .map(|t| {
let lhs = self.interpolate_at(t, Linear).unwrap();
Some(sync_with_intersection(self, other, &sync_points, op)) let rhs = other.interpolate_at(t, Linear).unwrap();
(t, op(lhs.partial_cmp(&rhs).unwrap()))
})
.collect();
Some(sig)
} }
} }
@ -107,13 +45,22 @@ where
where where
F: Fn(Ordering) -> bool, 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 // the union of the sample points in self and other
let sync_points = match self.synchronization_points(other) { let sync_points = sync_with_intersection(self, other)?;
Some(points) => points, let sig: Signal<bool> = sync_points
None => return None, .into_iter()
}; .map(|t| {
let lhs = self.interpolate_at(t, Linear).unwrap();
Some(sync_with_intersection(self, other, &sync_points, op)) 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) other.signal_cmp(self, op)
} }
} }
impl<T> SignalMinMax for ConstantSignal<T>
where
T: PartialOrd + Copy,
{
type Output = ConstantSignal<T>;
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<T> SignalMinMax for Signal<T>
where
T: PartialOrd + Copy + num_traits::NumCast + LinearInterpolatable,
{
type Output = Signal<T>;
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()
}
}

View file

@ -1,7 +1,7 @@
use num_traits::{Num, NumCast, Signed}; use num_traits::{Num, NumCast, Signed};
use super::traits::LinearInterpolatable; use super::traits::{BaseSignal, LinearInterpolatable};
use crate::signals::utils::{apply1, apply2, apply2_const}; use crate::signals::utils::{apply1, apply2, apply2_const, sync_with_intersection};
use crate::signals::{ConstantSignal, Signal}; use crate::signals::{ConstantSignal, Signal};
impl<T> core::ops::Neg for &Signal<T> impl<T> core::ops::Neg for &Signal<T>
@ -16,113 +16,6 @@ where
} }
} }
impl<T> core::ops::Add for &Signal<T>
where
T: core::ops::Add<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Add the given signal with another
fn add(self, rhs: Self) -> Self::Output {
apply2(self, rhs, |lhs, rhs| lhs + rhs)
}
}
impl<T> core::ops::Add<&ConstantSignal<T>> for &Signal<T>
where
T: core::ops::Add<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Add the given signal with another
fn add(self, rhs: &ConstantSignal<T>) -> Self::Output {
apply2_const(self, rhs, |lhs, rhs| lhs + rhs)
}
}
impl<T> core::ops::Mul for &Signal<T>
where
T: core::ops::Mul<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Multiply the given signal with another
fn mul(self, rhs: Self) -> Self::Output {
apply2(self, rhs, |lhs, rhs| lhs * rhs)
}
}
impl<T> core::ops::Mul<&ConstantSignal<T>> for &Signal<T>
where
T: core::ops::Mul<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Multiply the given signal with another
fn mul(self, rhs: &ConstantSignal<T>) -> Self::Output {
apply2_const(self, rhs, |lhs, rhs| lhs * rhs)
}
}
impl<T> core::ops::Sub for &Signal<T>
where
T: core::ops::Sub<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Subtract the given signal with another
fn sub(self, rhs: Self) -> Self::Output {
apply2(self, rhs, |lhs, rhs| lhs - rhs)
}
}
impl<T> core::ops::Sub<&ConstantSignal<T>> for &Signal<T>
where
T: core::ops::Sub<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Subtiply the given signal with another
fn sub(self, rhs: &ConstantSignal<T>) -> Self::Output {
apply2_const(self, rhs, |lhs, rhs| lhs - rhs)
}
}
impl<T> core::ops::Div for &Signal<T>
where
T: core::ops::Div<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Divide the given signal with another
fn div(self, rhs: Self) -> Self::Output {
apply2(self, rhs, |lhs, rhs| lhs / rhs)
}
}
impl<T> core::ops::Div<&ConstantSignal<T>> for &Signal<T>
where
T: core::ops::Div<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Divide the given signal with another
fn div(self, rhs: &ConstantSignal<T>) -> Self::Output {
apply2_const(self, rhs, |lhs, rhs| lhs / rhs)
}
}
impl<T> num_traits::Pow<Self> for &Signal<T>
where
T: num_traits::Pow<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// 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<T> core::ops::Neg for &ConstantSignal<T> impl<T> core::ops::Neg for &ConstantSignal<T>
where where
T: Signed + Copy, T: Signed + Copy,
@ -135,6 +28,18 @@ where
} }
} }
impl<T> core::ops::Add for &Signal<T>
where
T: core::ops::Add<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Add the given signal with another
fn add(self, rhs: Self) -> Self::Output {
apply2(self, rhs, |lhs, rhs| lhs + rhs)
}
}
impl<T> core::ops::Add for &ConstantSignal<T> impl<T> core::ops::Add for &ConstantSignal<T>
where where
T: core::ops::Add<T, Output = T> + Num + Copy, T: core::ops::Add<T, Output = T> + Num + Copy,
@ -147,6 +52,18 @@ where
} }
} }
impl<T> core::ops::Add<&ConstantSignal<T>> for &Signal<T>
where
T: core::ops::Add<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Add the given signal with another
fn add(self, rhs: &ConstantSignal<T>) -> Self::Output {
apply2_const(self, rhs, |lhs, rhs| lhs + rhs)
}
}
impl<T> core::ops::Add<&Signal<T>> for &ConstantSignal<T> impl<T> core::ops::Add<&Signal<T>> for &ConstantSignal<T>
where where
T: core::ops::Add<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable, T: core::ops::Add<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
@ -159,6 +76,18 @@ where
} }
} }
impl<T> core::ops::Mul for &Signal<T>
where
T: core::ops::Mul<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Multiply the given signal with another
fn mul(self, rhs: Self) -> Self::Output {
apply2(self, rhs, |lhs, rhs| lhs * rhs)
}
}
impl<T> core::ops::Mul for &ConstantSignal<T> impl<T> core::ops::Mul for &ConstantSignal<T>
where where
T: core::ops::Mul<T, Output = T> + Num + Copy, T: core::ops::Mul<T, Output = T> + Num + Copy,
@ -171,6 +100,18 @@ where
} }
} }
impl<T> core::ops::Mul<&ConstantSignal<T>> for &Signal<T>
where
T: core::ops::Mul<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Multiply the given signal with another
fn mul(self, rhs: &ConstantSignal<T>) -> Self::Output {
apply2_const(self, rhs, |lhs, rhs| lhs * rhs)
}
}
impl<T> core::ops::Mul<&Signal<T>> for &ConstantSignal<T> impl<T> core::ops::Mul<&Signal<T>> for &ConstantSignal<T>
where where
T: core::ops::Mul<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable, T: core::ops::Mul<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
@ -183,6 +124,39 @@ where
} }
} }
impl<T> core::ops::Sub for &Signal<T>
where
T: core::ops::Sub<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable + PartialOrd,
Signal<T>: BaseSignal<Value = T>,
{
type Output = Signal<T>;
/// 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<T> core::ops::Sub for &ConstantSignal<T> impl<T> core::ops::Sub for &ConstantSignal<T>
where where
T: core::ops::Sub<T, Output = T> + Num + Copy, T: core::ops::Sub<T, Output = T> + Num + Copy,
@ -194,15 +168,70 @@ where
ConstantSignal::<T>::new(self.value - rhs.value) ConstantSignal::<T>::new(self.value - rhs.value)
} }
} }
impl<T> core::ops::Sub<&ConstantSignal<T>> for &Signal<T>
where
T: core::ops::Sub<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable + PartialOrd,
Signal<T>: BaseSignal<Value = T>,
ConstantSignal<T>: BaseSignal<Value = T>,
{
type Output = Signal<T>;
/// Subtract the given signal with another
fn sub(self, rhs: &ConstantSignal<T>) -> 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<T> core::ops::Sub<&Signal<T>> for &ConstantSignal<T> impl<T> core::ops::Sub<&Signal<T>> for &ConstantSignal<T>
where where
T: core::ops::Sub<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable, T: core::ops::Sub<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable + PartialOrd,
{ {
type Output = Signal<T>; type Output = Signal<T>;
/// Subtract the given signal with another /// Subtract the given signal with another
fn sub(self, rhs: &Signal<T>) -> Self::Output { fn sub(self, rhs: &Signal<T>) -> 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<T> core::ops::Div for &Signal<T>
where
T: core::ops::Div<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// 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<T> core::ops::Div<&ConstantSignal<T>> for &Signal<T>
where
T: core::ops::Div<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// Divide the given signal with another
fn div(self, rhs: &ConstantSignal<T>) -> Self::Output {
apply2_const(self, rhs, |lhs, rhs| lhs / rhs)
}
}
impl<T> core::ops::Div<&Signal<T>> for &ConstantSignal<T> impl<T> core::ops::Div<&Signal<T>> for &ConstantSignal<T>
where where
T: core::ops::Div<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable, T: core::ops::Div<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
@ -229,3 +270,15 @@ where
apply2_const(rhs, self, |rhs, lhs| lhs / rhs) apply2_const(rhs, self, |rhs, lhs| lhs / rhs)
} }
} }
impl<T> num_traits::Pow<Self> for &Signal<T>
where
T: num_traits::Pow<T, Output = T> + Num + NumCast + Copy + LinearInterpolatable,
{
type Output = Signal<T>;
/// 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))
}
}

View file

@ -4,7 +4,7 @@ use std::ops::RangeBounds;
use std::time::Duration; use std::time::Duration;
use itertools::Itertools; use itertools::Itertools;
use num_traits::Num; use num_traits::{Num, NumCast};
use paste::paste; use paste::paste;
use super::{ConstantSignal, InterpolationMethod, Sample, Signal}; use super::{ConstantSignal, InterpolationMethod, Sample, Signal};
@ -326,3 +326,33 @@ pub trait SignalPartialOrd<Rhs = Self>: BaseSignal {
impl_signal_cmp!(eq); impl_signal_cmp!(eq);
impl_signal_cmp!(ne); impl_signal_cmp!(ne);
} }
/// Time-wise min-max of signal types
pub trait SignalMinMax<Rhs = Self>: 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<T>: BaseSignal<Value = T>
where
T: Num + NumCast + Copy;
fn to_i8(&self) -> Option<Self::Output<i8>>;
fn to_i16(&self) -> Option<Self::Output<i16>>;
fn to_i32(&self) -> Option<Self::Output<i32>>;
fn to_i64(&self) -> Option<Self::Output<i64>>;
fn to_u8(&self) -> Option<Self::Output<u8>>;
fn to_u16(&self) -> Option<Self::Output<u16>>;
fn to_u32(&self) -> Option<Self::Output<u32>>;
fn to_u64(&self) -> Option<Self::Output<u64>>;
fn to_f32(&self) -> Option<Self::Output<f32>>;
fn to_f64(&self) -> Option<Self::Output<f64>>;
}

View file

@ -6,6 +6,7 @@
use core::ops::{Bound, RangeBounds}; use core::ops::{Bound, RangeBounds};
use core::time::Duration; use core::time::Duration;
use std::cmp::Ordering;
use num_traits::NumCast; use num_traits::NumCast;
@ -30,7 +31,7 @@ pub struct Neighborhood<T: ?Sized + Copy> {
/// lines. /// lines.
pub fn find_intersection<T>(a: &Neighborhood<T>, b: &Neighborhood<T>) -> Sample<T> pub fn find_intersection<T>(a: &Neighborhood<T>, b: &Neighborhood<T>) -> Sample<T>
where 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 // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
use num_traits::cast; use num_traits::cast;
@ -60,6 +61,54 @@ where
Sample { time: t, value: y } 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<Vec<Duration>>
where
T: PartialOrd + Copy + NumCast + LinearInterpolatable,
Sig1: BaseSignal<Value = T> + SignalSyncPoints<Sig2>,
Sig2: BaseSignal<Value = T> + SignalSyncPoints<Sig1>,
{
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::<Duration>::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<T, F>(signal: &Signal<T>, op: F) -> Signal<T> pub fn apply1<T, F>(signal: &Signal<T>, op: F) -> Signal<T>
where where
T: Copy, T: Copy,