feat!: make interpolation method explicit

All methods that need to perform interpolation of some sort need an
explicit interpolation method. In Rust, this manifests as a generic
parameter, while in Python, this is a string parameter.
This commit is contained in:
Anand Balakrishnan 2023-10-04 14:42:51 -07:00
parent e2cff9449e
commit 50d5a0a78a
8 changed files with 221 additions and 296 deletions

View file

@ -1,186 +1,92 @@
use super::interpolation::Linear;
use super::{InterpolationMethod, SignalAbs};
use crate::signals::Signal;
impl<T> core::ops::Neg for Signal<T>
where
T: core::ops::Neg<Output = T>,
{
type Output = Signal<T>;
fn neg(self) -> Self::Output {
use Signal::*;
match self {
Empty => Signal::Empty,
Constant { value } => Signal::constant(value.neg()),
Sampled { values, time_points } => time_points.into_iter().zip(values.into_iter().map(|v| -v)).collect(),
}
impl<T> Signal<T> {
/// Perform sample-wise arithmetic negation over the signal.
pub fn negate<U>(&self) -> Signal<U>
where
for<'a> &'a T: core::ops::Neg<Output = U>,
{
self.unary_op(|v| -v)
}
}
impl<T> core::ops::Neg for &Signal<T>
where
for<'a> &'a T: core::ops::Neg<Output = T>,
{
type Output = Signal<T>;
fn neg(self) -> Self::Output {
use Signal::*;
match self {
Empty => Signal::Empty,
Constant { value } => Signal::constant(value.neg()),
Sampled { values, time_points } => time_points
.iter()
.copied()
.zip(values.iter().map(|v| v.neg()))
.collect(),
}
/// Perform sample-wise addition of the two signals.
///
/// Here, a new point is computed for all signal points where either of the signals
/// are sampled and where they intersect (using interpolation).
pub fn add<U, I>(&self, other: &Signal<T>) -> Signal<U>
where
for<'t> &'t T: core::ops::Add<Output = U>,
T: Clone,
I: InterpolationMethod<T>,
{
self.binary_op::<_, _, I>(other, |u, v| u + v)
}
}
impl<T> core::ops::Add<&Signal<T>> for Signal<T>
where
T: Clone,
for<'a, 'b> &'a T: core::ops::Add<&'b T, Output = T>,
Linear: InterpolationMethod<T>,
{
type Output = Signal<T>;
/// Add the given signal with another
fn add(self, other: &Signal<T>) -> Signal<T> {
self.binary_op::<_, _, Linear>(other, |lhs, rhs| lhs + rhs)
/// Perform sample-wise multiplication of the two signals.
///
/// Here, a new point is computed for all signal points where either of the signals
/// are sampled and where they intersect (using interpolation).
pub fn mul<U, I>(&self, other: &Signal<T>) -> Signal<U>
where
for<'t> &'t T: core::ops::Mul<Output = U>,
T: Clone,
I: InterpolationMethod<T>,
{
self.binary_op::<_, _, I>(other, |u, v| u * v)
}
}
impl<T> core::ops::Add<&Signal<T>> for &Signal<T>
where
T: Clone,
for<'a, 'b> &'a T: core::ops::Add<&'b T, Output = T>,
Linear: InterpolationMethod<T>,
{
type Output = Signal<T>;
/// Add the given signal with another
fn add(self, other: &Signal<T>) -> Signal<T> {
self.binary_op::<_, _, Linear>(other, |lhs, rhs| lhs + rhs)
/// Perform sample-wise subtraction of the two signals.
///
/// Here, a new point is computed for all signal points where either of the signals
/// are sampled and where they intersect (using interpolation).
pub fn sub<U, I>(&self, other: &Signal<T>) -> Signal<U>
where
for<'t> &'t T: core::ops::Sub<Output = U>,
T: Clone,
I: InterpolationMethod<T>,
{
self.binary_op::<_, _, I>(other, |u, v| u - v)
}
}
impl<T> core::ops::Mul<&Signal<T>> for Signal<T>
where
for<'a, 'b> &'a T: core::ops::Mul<&'b T, Output = T>,
T: Clone,
Linear: InterpolationMethod<T>,
{
type Output = Signal<T>;
/// Multiply the given signal with another
fn mul(self, rhs: &Signal<T>) -> Signal<T> {
self.binary_op::<_, _, Linear>(rhs, |lhs, rhs| lhs * rhs)
/// Perform sample-wise division of the two signals.
///
/// Here, a new point is computed for all signal points where either of the signals
/// are sampled and where they intersect (using interpolation).
pub fn div<U, I>(&self, other: &Signal<T>) -> Signal<U>
where
for<'t> &'t T: core::ops::Div<Output = U>,
T: Clone,
I: InterpolationMethod<T>,
{
self.binary_op::<_, _, I>(other, |u, v| u / v)
}
}
impl<T> core::ops::Mul<&Signal<T>> for &Signal<T>
where
for<'a, 'b> &'a T: core::ops::Mul<&'b T, Output = T>,
T: Clone,
Linear: InterpolationMethod<T>,
{
type Output = Signal<T>;
/// Multiply the given signal with another
fn mul(self, rhs: &Signal<T>) -> Signal<T> {
self.binary_op::<_, _, Linear>(rhs, |lhs, rhs| lhs * rhs)
}
}
impl<T> core::ops::Sub<&Signal<T>> for &Signal<T>
where
for<'a, 'b> &'a T: core::ops::Sub<&'b T, Output = T>,
T: Clone + PartialOrd,
Linear: InterpolationMethod<T>,
{
type Output = Signal<T>;
/// Subtract the given signal with another
fn sub(self, other: &Signal<T>) -> Signal<T> {
// This has to be manually implemented and cannot use the binary_op 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 Signal::*;
match (self, other) {
// If either of the signals are empty, we return an empty signal.
(Empty, _) | (_, Empty) => Signal::Empty,
(Constant { value: v1 }, Constant { value: v2 }) => Signal::constant(v1 - v2),
(lhs, rhs) => {
// the union of the sample points in self and other
let sync_points = lhs.sync_with_intersection::<Linear>(rhs).unwrap();
sync_points
.into_iter()
.map(|t| {
let lhs = lhs.interpolate_at::<Linear>(t).unwrap();
let rhs = rhs.interpolate_at::<Linear>(t).unwrap();
(t, &lhs - &rhs)
})
.collect()
}
}
}
}
impl<T> core::ops::Sub<&Signal<T>> for Signal<T>
where
for<'a, 'b> &'a T: core::ops::Sub<&'b T, Output = T>,
T: Clone + PartialOrd,
Linear: InterpolationMethod<T>,
{
type Output = Signal<T>;
/// Subtract the given signal with another
fn sub(self, other: &Signal<T>) -> Signal<T> {
<&Self as core::ops::Sub>::sub(&self, other)
}
}
impl<T> core::ops::Div<&Signal<T>> for Signal<T>
where
for<'a, 'b> &'a T: core::ops::Div<&'b T, Output = T>,
T: Clone,
Linear: InterpolationMethod<T>,
{
type Output = Signal<T>;
/// Divide the given signal with another
fn div(self, rhs: &Signal<T>) -> Self {
self.binary_op::<_, _, Linear>(rhs, |lhs, rhs| lhs / rhs)
}
}
impl<T> core::ops::Div<&Signal<T>> for &Signal<T>
where
for<'a, 'b> &'a T: core::ops::Div<&'b T, Output = T>,
T: Clone,
Linear: InterpolationMethod<T>,
{
type Output = Signal<T>;
/// Divide the given signal with another
fn div(self, rhs: &Signal<T>) -> Signal<T> {
self.binary_op::<_, _, Linear>(rhs, |lhs, rhs| lhs / rhs)
}
}
impl<T> Signal<T>
where
for<'a, 'b> &'a T: num_traits::Pow<&'b T, Output = T>,
T: Clone,
Linear: InterpolationMethod<T>,
{
/// Returns the values in `self` to the power of the values in `other`
pub fn pow(&self, other: &Self) -> Self {
/// Perform sample-wise exponentiation of the two signals.
///
/// Here, a new point is computed for all signal points where either of the signals
/// are sampled and where they intersect (using interpolation).
pub fn pow<U, I>(&self, exponent: &Signal<T>) -> Signal<U>
where
for<'a, 'b> &'a T: num_traits::Pow<&'b T, Output = U>,
T: Clone,
I: InterpolationMethod<T>,
{
use num_traits::Pow;
self.binary_op::<_, _, Linear>(other, |lhs, rhs| lhs.pow(rhs))
self.binary_op::<_, _, I>(exponent, |u, v| u.pow(v))
}
/// Perform sample-wise absolute difference of the two signals.
///
/// Here, a new point is computed for all signal points where either of the signals
/// are sampled and where they intersect (using interpolation).
pub fn abs_diff<U, I>(&self, other: &Signal<T>) -> Signal<U>
where
for<'t> &'t T: core::ops::Sub<Output = U>,
T: Clone + PartialOrd,
I: InterpolationMethod<T>,
{
self.binary_op::<_, _, I>(other, |u, v| if u < v { v - u } else { u - v })
}
}
@ -202,6 +108,6 @@ signal_abs_impl!(i64, f32, f64);
impl SignalAbs for Signal<u64> {
/// Return the absolute value for each sample in the signal
fn abs(self) -> Signal<u64> {
self.unary_op(|v| v)
self.unary_op(|&v| v)
}
}