feat(core): Add casting and correct subtraction/cmp
This commit is contained in:
parent
7c8c833469
commit
b517043d0e
6 changed files with 446 additions and 195 deletions
|
|
@ -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)]
|
||||||
|
|
|
||||||
80
argus-core/src/signals/cast.rs
Normal file
80
argus-core/src/signals/cast.rs
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>>;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue