argus/argus-core/src/signals/utils.rs
Anand Balakrishnan 1c79847a77
feat!(core): Use new AST structure
Derive Expr methods using a derive proc-macro. These macros are present in
the `argus-derive` crate, but the traits are defined in `argus-core`
2023-06-06 10:46:27 -04:00

135 lines
4.4 KiB
Rust

//! A bunch of utility code for argus
//!
//! - The implementation for Range intersection is based on the library
//! [`range_ext`](https://github.com/AnickaBurova/range-ext), but adapted for my use a
//! bit.
use core::ops::{Bound, RangeBounds};
use core::time::Duration;
use std::iter::zip;
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.
///
/// The values of `first` and `second` are `None` if and only if `at` lies outside the
/// domain over which the signal is defined.
///
/// This can be used to interpolate the value at the given `at` time using strategies
/// like constant previous, constant following, and linear interpolation.
#[derive(Copy, Clone, Debug)]
pub struct Neighborhood<T> {
pub first: Option<Sample<T>>,
pub second: Option<Sample<T>>,
}
#[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)>,
{
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();
}
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()
}
}
}
fn partial_min<T>(a: T, b: T) -> Option<T>
where
T: PartialOrd,
{
a.partial_cmp(&b).map(|ord| if ord.is_lt() { a } else { b })
}
fn partial_max<T>(a: T, b: T) -> Option<T>
where
T: PartialOrd,
{
a.partial_cmp(&b).map(|ord| if ord.is_gt() { a } else { b })
}
/// Compute the intersection of two ranges
pub fn intersect_bounds<T>(lhs: &impl RangeBounds<T>, rhs: &impl RangeBounds<T>) -> (Bound<T>, Bound<T>)
where
T: PartialOrd + Copy,
{
use core::ops::Bound::*;
let start = match (lhs.start_bound(), rhs.start_bound()) {
(Included(&l), Included(&r)) => Included(partial_max(l, r).unwrap()),
(Excluded(&l), Excluded(&r)) => Excluded(partial_max(l, r).unwrap()),
(Included(l), Excluded(r)) | (Excluded(r), Included(l)) => {
if l > r {
Included(*l)
} else {
Excluded(*r)
}
}
(Unbounded, Included(&l)) | (Included(&l), Unbounded) => Included(l),
(Unbounded, Excluded(&l)) | (Excluded(&l), Unbounded) => Excluded(l),
(Unbounded, Unbounded) => Unbounded,
};
let end = match (lhs.end_bound(), rhs.end_bound()) {
(Included(&l), Included(&r)) => Included(partial_min(l, r).unwrap()),
(Excluded(&l), Excluded(&r)) => Excluded(partial_min(l, r).unwrap()),
(Included(l), Excluded(r)) | (Excluded(r), Included(l)) => {
if l < r {
Included(*l)
} else {
Excluded(*r)
}
}
(Unbounded, Included(&l)) | (Included(&l), Unbounded) => Included(l),
(Unbounded, Excluded(&l)) | (Excluded(&l), Unbounded) => Excluded(l),
(Unbounded, Unbounded) => Unbounded,
};
(start, end)
}