refactor!(argus-core): remove unnecessary traits and Copy constraints

This commit is contained in:
Anand Balakrishnan 2023-08-29 18:16:10 -07:00
parent 86cef692dc
commit 28a79cb88c
No known key found for this signature in database
9 changed files with 255 additions and 271 deletions

View file

@ -87,6 +87,16 @@ pub enum Error {
},
}
impl Error {
/// An [`InvalidCast`](Error::InvalidCast) error from `T` to `U`.
pub fn invalid_cast<T, U>() -> Self {
Self::InvalidCast {
from: std::any::type_name::<T>(),
to: std::any::type_name::<U>(),
}
}
}
/// Alias for [`Error`](enum@Error)
pub type ArgusError = Error;
/// Alias for [`Result<T, ArgusError>`]

View file

@ -263,7 +263,7 @@ impl<T> Signal<T> {
/// Augment synchronization points with time points where signals intersect
pub fn sync_with_intersection<Interp>(&self, other: &Signal<T>) -> Option<Vec<Duration>>
where
T: PartialOrd + Copy,
T: PartialOrd + Clone,
Interp: FindIntersectionMethod<T>,
{
use core::cmp::Ordering::*;
@ -289,12 +289,12 @@ impl<T> Signal<T> {
if let (Less, Greater) | (Greater, Less) = (last, ord) {
// Find the point of intersection between the points.
let a = utils::Neighborhood {
first: self.at(tm1).copied().map(|value| Sample { time: tm1, value }),
second: self.at(*t).copied().map(|value| Sample { time: *t, value }),
first: self.at(tm1).cloned().map(|value| Sample { time: tm1, value }),
second: self.at(*t).cloned().map(|value| Sample { time: *t, value }),
};
let b = utils::Neighborhood {
first: other.at(tm1).copied().map(|value| Sample { time: tm1, value }),
second: other.at(*t).copied().map(|value| Sample { time: *t, value }),
first: other.at(tm1).cloned().map(|value| Sample { time: tm1, value }),
second: other.at(*t).cloned().map(|value| Sample { time: *t, value }),
};
let intersect = Interp::find_intersection(&a, &b);
return_points.push(intersect.time);
@ -309,7 +309,7 @@ impl<T> Signal<T> {
}
}
impl<T: Copy> Signal<T> {
impl<T: Clone> Signal<T> {
/// Interpolate the value of the signal at the given time point
///
/// If there exists a sample at the given time point then `Some(value)` is returned
@ -324,7 +324,7 @@ impl<T: Copy> Signal<T> {
{
match self {
Signal::Empty => None,
Signal::Constant { value } => Some(*value),
Signal::Constant { value } => Some(value.clone()),
Signal::Sampled { values, time_points } => {
assert_eq!(
time_points.len(),
@ -339,7 +339,7 @@ impl<T: Copy> Signal<T> {
// We will use binary search to find the appropriate index
let hint_idx = match time_points.binary_search(&time) {
Ok(idx) => return values.get(idx).copied(),
Ok(idx) => return values.get(idx).cloned(),
Err(idx) => idx,
};
@ -349,22 +349,22 @@ impl<T: Copy> Signal<T> {
// Sample appears before the start of the signal
// So, let's return just the following sample, which is the first sample
// (since we know that the signal is non-empty).
Some(values[hint_idx])
Some(values[hint_idx].clone())
} else if hint_idx == time_points.len() {
// Sample appears past the end of the signal
// So, let's return just the preceding sample, which is the last sample
// (since we know the signal is non-empty)
Some(values[hint_idx - 1])
Some(values[hint_idx - 1].clone())
} else {
// The sample should exist within the signal.
assert!(time_points.len() >= 2, "There should be at least 2 elements");
let first = Sample {
time: time_points[hint_idx - 1],
value: values[hint_idx - 1],
value: values[hint_idx - 1].clone(),
};
let second = Sample {
time: time_points[hint_idx],
value: values[hint_idx],
value: values[hint_idx].clone(),
};
Interp::at(&first, &second, time)
}

View file

@ -1,12 +1,29 @@
use super::interpolation::Linear;
use crate::signals::utils::{apply1, apply2};
use crate::signals::Signal;
impl core::ops::Not for Signal<bool> {
type Output = Signal<bool>;
fn not(self) -> Self::Output {
use Signal::*;
match self {
Empty => self,
Constant { value } => Signal::constant(!value),
signal => signal.into_iter().map(|(&t, v)| (t, !v)).collect(),
}
}
}
impl core::ops::Not for &Signal<bool> {
type Output = Signal<bool>;
fn not(self) -> Self::Output {
apply1(self, |v| !v)
use Signal::*;
match self {
Empty => Empty,
Constant { value } => Signal::constant(!value),
signal => signal.into_iter().map(|(&t, &v)| (t, !v)).collect(),
}
}
}
@ -18,7 +35,7 @@ impl Signal<bool> {
///
/// See [`Signal::sync_with_intersection`].
pub fn and(&self, other: &Self) -> Self {
apply2::<_, _, _, Linear>(self, other, |lhs, rhs| lhs && rhs)
self.binary_op::<_, _, Linear>(other, |lhs, rhs| *lhs && *rhs)
}
/// Apply logical disjunction for each sample across the two signals.
@ -28,22 +45,38 @@ impl Signal<bool> {
///
/// See [`Signal::sync_with_intersection`].
pub fn or(&self, other: &Self) -> Self {
apply2::<_, _, _, Linear>(self, other, |lhs, rhs| lhs || rhs)
self.binary_op::<_, _, Linear>(other, |lhs, rhs| *lhs || *rhs)
}
}
impl core::ops::BitAnd<Self> for &Signal<bool> {
impl core::ops::BitAnd<&Signal<bool>> for Signal<bool> {
type Output = Signal<bool>;
fn bitand(self, other: Self) -> Self::Output {
fn bitand(self, other: &Signal<bool>) -> Self::Output {
self.and(other)
}
}
impl core::ops::BitOr<Self> for &Signal<bool> {
impl core::ops::BitAnd<&Signal<bool>> for &Signal<bool> {
type Output = Signal<bool>;
fn bitor(self, other: Self) -> Self::Output {
fn bitand(self, other: &Signal<bool>) -> Self::Output {
self.and(other)
}
}
impl core::ops::BitOr<&Signal<bool>> for Signal<bool> {
type Output = Signal<bool>;
fn bitor(self, other: &Signal<bool>) -> Self::Output {
self.or(other)
}
}
impl core::ops::BitOr<&Signal<bool>> for &Signal<bool> {
type Output = Signal<bool>;
fn bitor(self, other: &Signal<bool>) -> Self::Output {
self.or(other)
}
}

View file

@ -1,124 +1,28 @@
use core::iter::zip;
use crate::signals::traits::{SignalNumCast, TrySignalCast};
use crate::signals::Signal;
use crate::{ArgusError, ArgusResult};
macro_rules! impl_bool_to_num {
($to:ty) => {
paste::paste! {
#[inline]
fn [<to_ $to>](&self) -> Option<Signal<$to>> {
match self {
impl<T> Signal<T>
where
T: num_traits::NumCast + Copy,
{
/// Cast a numeric signal to another numeric signal
pub fn num_cast<U>(&self) -> ArgusResult<Signal<U>>
where
U: num_traits::NumCast,
{
let ret: Option<_> = match self {
Signal::Empty => Some(Signal::Empty),
Signal::Constant { value } => num_traits::cast::<_, $to>(*value as i64).map(Signal::constant),
Signal::Sampled { values, time_points } => {
zip(time_points, values)
.map(|(&t, &v)| {
let val = num_traits::cast::<_, $to>(v as i64)?;
Some((t, val))
})
.collect()
}
}
}
}
};
}
impl SignalNumCast for Signal<bool> {
impl_bool_to_num!(i8);
impl_bool_to_num!(i16);
impl_bool_to_num!(i32);
impl_bool_to_num!(i64);
impl_bool_to_num!(u8);
impl_bool_to_num!(u16);
impl_bool_to_num!(u32);
impl_bool_to_num!(u64);
#[inline]
fn to_f32(&self) -> Option<Signal<f32>> {
match self {
Signal::Empty => Some(Signal::Empty),
Signal::Constant { value } => {
let value: f32 = if *value { f32::INFINITY } else { f32::NEG_INFINITY };
Some(Signal::Constant { value })
}
Signal::Constant { value } => num_traits::cast(*value).map(Signal::constant),
Signal::Sampled { values, time_points } => zip(time_points, values)
.map(|(&t, &v)| {
let val = num_traits::cast::<_, f32>(v as i64)?;
let val: U = num_traits::cast(v)?;
Some((t, val))
})
.collect(),
}
}
#[inline]
fn to_f64(&self) -> Option<Signal<f64>> {
match self {
Signal::Empty => Some(Signal::Empty),
Signal::Constant { value } => {
let value: f64 = if *value { f64::INFINITY } else { f64::NEG_INFINITY };
Some(Signal::Constant { value })
}
Signal::Sampled { values, time_points } => zip(time_points, values)
.map(|(&t, &v)| {
let val = num_traits::cast::<_, f64>(v as i64)?;
Some((t, val))
})
.collect(),
}
}
}
macro_rules! impl_cast {
($from:ty, [$( $to:ty ),*]) => {
paste::paste! {
impl SignalNumCast for Signal<$from> {
$(
#[inline]
fn [<to_ $to>](&self) -> Option<Signal<$to>> {
match self {
Signal::Empty => Some(Signal::Empty),
Signal::Constant { value } => num_traits::cast::<_, $to>(*value).map(Signal::constant),
Signal::Sampled { values, time_points } => {
zip(time_points, values)
.map(|(&t, &v)| {
let val = num_traits::cast::<_, $to>(v)?;
Some((t, val))
})
.collect()
}
}
}
)*
}
$(
impl TrySignalCast<Signal<$to>> for Signal<$from> {
fn try_cast(&self) -> ArgusResult<Signal<$to>> {
self.[<to_ $to>]().ok_or(ArgusError::InvalidCast {
from: std::any::type_name::<$from>(),
to: std::any::type_name::<$to>(),
})
}
}
)*
}
};
($from:ty) => {
impl_cast!($from, [i8, i16, i32, i64, u8, u16, u32, u64, f32, f64]);
};
ret.ok_or(ArgusError::invalid_cast::<T, U>())
}
}
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,12 +1,12 @@
use std::cmp::Ordering;
use super::interpolation::Linear;
use super::traits::{SignalMinMax, SignalPartialOrd};
use super::traits::SignalPartialOrd;
use super::{FindIntersectionMethod, InterpolationMethod, Signal};
impl<T> SignalPartialOrd<Self> for Signal<T>
impl<T> SignalPartialOrd for Signal<T>
where
T: PartialOrd + Copy,
T: PartialOrd + Clone,
Linear: InterpolationMethod<T> + FindIntersectionMethod<T>,
{
fn signal_cmp<F>(&self, other: &Self, op: F) -> Option<Signal<bool>>
@ -32,14 +32,13 @@ where
}
}
impl<T> SignalMinMax<Self> for Signal<T>
impl<T> Signal<T>
where
T: PartialOrd + Copy,
T: PartialOrd + Clone,
Linear: InterpolationMethod<T> + FindIntersectionMethod<T>,
{
type Output = Signal<T>;
fn min(&self, other: &Self) -> Self::Output {
/// Compute the time-wise min of two signals
pub fn min(&self, other: &Self) -> Self {
let time_points = self.sync_with_intersection::<Linear>(other).unwrap();
time_points
.into_iter()
@ -55,7 +54,8 @@ where
.collect()
}
fn max(&self, other: &Self) -> Self::Output {
/// Compute the time-wise max of two signals
pub fn max(&self, other: &Self) -> Self {
let time_points = self.sync_with_intersection::<Linear>(other).unwrap();
time_points
.into_iter()

View file

@ -44,10 +44,7 @@ impl<'a, T> IntoIterator for &'a Signal<T> {
}
}
impl<T> FromIterator<(Duration, T)> for Signal<T>
where
T: Copy,
{
impl<T> FromIterator<(Duration, T)> for Signal<T> {
/// Takes a sequence of sample points and creates a signal.
///
/// # Panics

View file

@ -1,104 +1,186 @@
use num_traits::Signed;
use super::interpolation::Linear;
use super::traits::SignalAbs;
use super::{FindIntersectionMethod, InterpolationMethod};
use crate::signals::utils::{apply1, apply2};
use super::{FindIntersectionMethod, InterpolationMethod, SignalAbs};
use crate::signals::Signal;
impl<T> core::ops::Neg for &Signal<T>
impl<T> core::ops::Neg for Signal<T>
where
T: Signed + Copy,
T: core::ops::Neg<Output = T>,
{
type Output = Signal<T>;
/// Negate the signal at each time point
fn neg(self) -> Self::Output {
apply1(self, |v| -v)
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> core::ops::Add for &Signal<T>
impl<T> core::ops::Neg for &Signal<T>
where
T: core::ops::Add<T, Output = T> + Copy,
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(),
}
}
}
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, rhs: Self) -> Self::Output {
apply2::<_, _, _, Linear>(self, rhs, |lhs, rhs| lhs + rhs)
fn add(self, other: &Signal<T>) -> Signal<T> {
self.binary_op::<_, _, Linear>(other, |lhs, rhs| lhs + rhs)
}
}
impl<T> core::ops::Mul for &Signal<T>
impl<T> core::ops::Add<&Signal<T>> for &Signal<T>
where
T: core::ops::Mul<T, Output = T> + Copy,
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)
}
}
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: Self) -> Self::Output {
apply2::<_, _, _, Linear>(self, rhs, |lhs, rhs| lhs * rhs)
fn mul(self, rhs: &Signal<T>) -> Signal<T> {
self.binary_op::<_, _, Linear>(rhs, |lhs, rhs| lhs * rhs)
}
}
impl<T> core::ops::Sub for &Signal<T>
impl<T> core::ops::Mul<&Signal<T>> for &Signal<T>
where
T: core::ops::Sub<T, Output = T> + Copy + PartialOrd,
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> + FindIntersectionMethod<T>,
{
type Output = Signal<T>;
/// Subtract the given signal with another
fn sub(self, rhs: Self) -> Self::Output {
// This has to be manually implemented and cannot use the apply2 functions.
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.
if self.is_empty() || rhs.is_empty() {
return Signal::new();
}
(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 = self.sync_with_intersection::<Linear>(rhs).unwrap();
let sync_points = lhs.sync_with_intersection::<Linear>(rhs).unwrap();
sync_points
.into_iter()
.map(|t| {
let lhs = self.interpolate_at::<Linear>(t).unwrap();
let lhs = lhs.interpolate_at::<Linear>(t).unwrap();
let rhs = rhs.interpolate_at::<Linear>(t).unwrap();
(t, lhs - rhs)
(t, &lhs - &rhs)
})
.collect()
}
}
}
}
impl<T> core::ops::Div for &Signal<T>
impl<T> core::ops::Sub<&Signal<T>> for Signal<T>
where
T: core::ops::Div<T, Output = T> + Copy,
for<'a, 'b> &'a T: core::ops::Sub<&'b T, Output = T>,
T: Clone + PartialOrd,
Linear: InterpolationMethod<T> + FindIntersectionMethod<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: Self) -> Self::Output {
apply2::<_, _, _, Linear>(self, rhs, |lhs, rhs| lhs / rhs)
fn div(self, rhs: &Signal<T>) -> Self {
self.binary_op::<_, _, Linear>(rhs, |lhs, rhs| lhs / rhs)
}
}
impl<T> num_traits::Pow<Self> for &Signal<T>
impl<T> core::ops::Div<&Signal<T>> for &Signal<T>
where
T: num_traits::Pow<T, Output = T> + Copy,
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`
fn pow(self, other: Self) -> Self::Output {
apply2::<_, _, _, Linear>(self, other, |lhs, rhs| lhs.pow(rhs))
pub fn pow(&self, other: &Self) -> Self {
use num_traits::Pow;
self.binary_op::<_, _, Linear>(other, |lhs, rhs| lhs.pow(rhs))
}
}
@ -107,8 +189,8 @@ macro_rules! signal_abs_impl {
$(
impl SignalAbs for Signal<$ty> {
/// Return the absolute value for each sample in the signal
fn abs(&self) -> Signal<$ty> {
apply1(self, |v| v.abs())
fn abs(self) -> Signal<$ty> {
self.unary_op(|v| v.abs())
}
}
)*
@ -119,7 +201,7 @@ 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> {
apply1(self, |v| v)
fn abs(self) -> Signal<u64> {
self.unary_op(|v| v)
}
}

View file

@ -8,7 +8,6 @@ use paste::paste;
use super::utils::Neighborhood;
use super::{Sample, Signal};
use crate::ArgusResult;
/// Trait implemented by interpolation strategies
pub trait InterpolationMethod<T> {
@ -85,41 +84,8 @@ pub trait SignalMinMax<Rhs = Self> {
fn max(&self, rhs: &Rhs) -> Self::Output;
}
/// Trait for converting between signal types
pub trait SignalNumCast {
/// Try to convert the signal values/samples to `i8`
fn to_i8(&self) -> Option<Signal<i8>>;
/// Try to convert the signal values/samples to `i16`
fn to_i16(&self) -> Option<Signal<i16>>;
/// Try to convert the signal values/samples to `i32`
fn to_i32(&self) -> Option<Signal<i32>>;
/// Try to convert the signal values/samples to `i64`
fn to_i64(&self) -> Option<Signal<i64>>;
/// Try to convert the signal values/samples to `u8`
fn to_u8(&self) -> Option<Signal<u8>>;
/// Try to convert the signal values/samples to `u16`
fn to_u16(&self) -> Option<Signal<u16>>;
/// Try to convert the signal values/samples to `u32`
fn to_u32(&self) -> Option<Signal<u32>>;
/// Try to convert the signal values/samples to `u64`
fn to_u64(&self) -> Option<Signal<u64>>;
/// Try to convert the signal values/samples to `f32`
fn to_f32(&self) -> Option<Signal<f32>>;
/// Try to convert the signal values/samples to `f64`
fn to_f64(&self) -> Option<Signal<f64>>;
}
/// Trait to cast signal onto some type
pub trait TrySignalCast<T>: Sized + SignalNumCast {
/// Try to cast the given signal to another numeric type.
///
/// This returns a [`ArgusError::InvalidCast`](crate::Error::InvalidCast) if
/// some value in the signal isn't castable to the destination type.
fn try_cast(&self) -> ArgusResult<T>;
}
/// Trait for computing the absolute value of the samples in a signal
pub trait SignalAbs {
/// Compute the absolute value of the given signal
fn abs(&self) -> Self;
fn abs(self) -> Self;
}

View file

@ -24,40 +24,31 @@ pub struct Neighborhood<T> {
pub second: Option<Sample<T>>,
}
#[inline]
pub fn apply1<T, U, F>(signal: &Signal<T>, op: F) -> Signal<U>
where
T: Copy,
impl<T> Signal<T> {
pub(crate) fn unary_op<U, F>(self, op: F) -> Signal<U>
where
F: Fn(T) -> U,
Signal<U>: std::iter::FromIterator<(Duration, U)>,
{
match signal {
Signal::Empty => Signal::Empty,
Signal::Constant { value } => Signal::Constant { value: op(*value) },
Signal::Sampled { values, time_points } => {
zip(time_points.iter().copied(), values.iter().map(|v| op(*v))).collect()
}
}
}
#[inline]
pub fn apply2<'a, T, U, F, Interp>(lhs: &'a Signal<T>, rhs: &'a Signal<T>, op: F) -> Signal<U>
where
T: Copy,
U: Copy,
F: Fn(T, T) -> U,
Interp: InterpolationMethod<T>,
{
{
use Signal::*;
// If either of the signals are empty, we return an empty signal.
if lhs.is_empty() || rhs.is_empty() {
// Intersection with empty signal should yield an empty signal
return Signal::<U>::new();
match self {
Empty => Signal::Empty,
Constant { value } => Signal::constant(op(value)),
Sampled { values, time_points } => zip(time_points.into_iter(), values.into_iter().map(op)).collect(),
}
match (lhs, rhs) {
}
pub(crate) fn binary_op<U, F, Interp>(&self, other: &Signal<T>, op: F) -> Signal<U>
where
T: Clone,
F: Fn(&T, &T) -> U,
Interp: InterpolationMethod<T>,
{
use Signal::*;
match (self, other) {
// If either of the signals are empty, we return an empty signal.
(Empty, _) | (_, Empty) => Signal::new(),
(Constant { value: v1 }, Constant { value: v2 }) => Signal::constant(op(*v1, *v2)),
(Empty, _) | (_, Empty) => Signal::Empty,
(Constant { value: v1 }, Constant { value: v2 }) => Signal::constant(op(v1, v2)),
(lhs, rhs) => {
// We determine the range of the signal (as the output signal can only be
// defined in the domain where both signals are defined).
@ -69,11 +60,12 @@ where
.map(|t| {
let v1 = lhs.interpolate_at::<Interp>(*t).unwrap();
let v2 = rhs.interpolate_at::<Interp>(*t).unwrap();
(*t, op(v1, v2))
(*t, op(&v1, &v2))
})
.collect()
}
}
}
}
fn partial_min<T>(a: T, b: T) -> Option<T>