feat!(core): Change Signal to be a sumtype
We want to be able to reason about if a signal is empty, constant, or sampled at compile time without using any trait objects. Moreover, the core Argus library shouldn't care about how it deals with interfacing with other languages like Python. Thus, we remove the need for having an `AnySignal` type and what not.
This commit is contained in:
parent
a6a3805107
commit
4431b79bcd
10 changed files with 442 additions and 966 deletions
|
|
@ -6,12 +6,12 @@
|
|||
|
||||
use core::ops::{Bound, RangeBounds};
|
||||
use core::time::Duration;
|
||||
use std::cmp::Ordering;
|
||||
use std::iter::zip;
|
||||
|
||||
use num_traits::NumCast;
|
||||
|
||||
use super::traits::{LinearInterpolatable, SignalSyncPoints};
|
||||
use super::{BaseSignal, ConstantSignal, InterpolationMethod, Sample, Signal};
|
||||
use super::traits::LinearInterpolatable;
|
||||
use super::{InterpolationMethod, Sample, Signal};
|
||||
|
||||
/// The neighborhood around a signal such that the time `at` is between the `first` and
|
||||
/// `second` samples.
|
||||
|
|
@ -61,108 +61,55 @@ 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<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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn apply1<T, U, F>(signal: &Signal<T>, op: F) -> Signal<U>
|
||||
where
|
||||
T: Copy,
|
||||
F: Fn(T) -> U,
|
||||
Signal<U>: std::iter::FromIterator<(Duration, U)>,
|
||||
{
|
||||
signal.iter().map(|(t, v)| (*t, op(*v))).collect()
|
||||
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>(lhs: &'a Signal<T>, rhs: &'a Signal<T>, op: F) -> Signal<U>
|
||||
where
|
||||
T: Copy + LinearInterpolatable,
|
||||
U: Copy,
|
||||
F: Fn(T, T) -> U,
|
||||
{
|
||||
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();
|
||||
}
|
||||
// We determine the range of the signal (as the output signal can only be
|
||||
// defined in the domain where both signals are defined).
|
||||
let time_points = lhs.synchronization_points(rhs).unwrap();
|
||||
// Now, at each of the merged time points, we sample each signal and operate on
|
||||
// them
|
||||
time_points
|
||||
.into_iter()
|
||||
.map(|t| {
|
||||
let v1 = lhs.interpolate_at(*t, InterpolationMethod::Linear).unwrap();
|
||||
let v2 = rhs.interpolate_at(*t, InterpolationMethod::Linear).unwrap();
|
||||
(*t, op(v1, v2))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn apply2_const<'a, T, U, F>(lhs: &'a Signal<T>, rhs: &'a ConstantSignal<T>, op: F) -> Signal<U>
|
||||
where
|
||||
T: Copy + LinearInterpolatable,
|
||||
U: Copy,
|
||||
F: Fn(T, T) -> U,
|
||||
{
|
||||
// If either of the signals are empty, we return an empty signal.
|
||||
if lhs.is_empty() {
|
||||
// Intersection with empty signal should yield an empty signal
|
||||
return Signal::<U>::new();
|
||||
match (lhs, rhs) {
|
||||
// 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)),
|
||||
(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).
|
||||
let time_points = lhs.sync_points(rhs).unwrap();
|
||||
// Now, at each of the merged time points, we sample each signal and operate on
|
||||
// them
|
||||
time_points
|
||||
.into_iter()
|
||||
.map(|t| {
|
||||
let v1 = lhs.interpolate_at(*t, InterpolationMethod::Linear).unwrap();
|
||||
let v2 = rhs.interpolate_at(*t, InterpolationMethod::Linear).unwrap();
|
||||
(*t, op(v1, v2))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
lhs.time_points
|
||||
.iter()
|
||||
.map(|&t| {
|
||||
let v1 = lhs.interpolate_at(t, InterpolationMethod::Linear).unwrap();
|
||||
let v2 = rhs.interpolate_at(t, InterpolationMethod::Linear).unwrap();
|
||||
(t, op(v1, v2))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn partial_min<T>(a: T, b: T) -> Option<T>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue