refactor!(argus-core): update find_intersection method

Don't separate the intersection finding code into a different trait, as
it can be bundled with interpolation method.
This commit is contained in:
Anand Balakrishnan 2023-09-07 13:28:30 -07:00
parent 3a9623b99b
commit 475d32c533
No known key found for this signature in database
5 changed files with 42 additions and 28 deletions

View file

@ -264,7 +264,7 @@ impl<T> Signal<T> {
pub fn sync_with_intersection<Interp>(&self, other: &Signal<T>) -> Option<Vec<Duration>> pub fn sync_with_intersection<Interp>(&self, other: &Signal<T>) -> Option<Vec<Duration>>
where where
T: PartialOrd + Clone, T: PartialOrd + Clone,
Interp: FindIntersectionMethod<T>, Interp: InterpolationMethod<T>,
{ {
use core::cmp::Ordering::*; use core::cmp::Ordering::*;
let sync_points: Vec<&Duration> = self.sync_points(other)?.into_iter().collect(); let sync_points: Vec<&Duration> = self.sync_points(other)?.into_iter().collect();
@ -308,7 +308,8 @@ impl<T> Signal<T> {
.interpolate_at::<Interp>(*t) .interpolate_at::<Interp>(*t)
.map(|value| Sample { time: *t, value }), .map(|value| Sample { time: *t, value }),
}; };
let intersect = Interp::find_intersection(&a, &b); let intersect = Interp::find_intersection(&a, &b)
.unwrap_or_else(|| panic!("unable to find intersection for crossing signals"));
return_points.push(intersect.time); return_points.push(intersect.time);
} }
} }

View file

@ -2,12 +2,12 @@ use std::cmp::Ordering;
use super::interpolation::Linear; use super::interpolation::Linear;
use super::traits::SignalPartialOrd; use super::traits::SignalPartialOrd;
use super::{FindIntersectionMethod, InterpolationMethod, Signal}; use super::{InterpolationMethod, Signal};
impl<T> SignalPartialOrd for Signal<T> impl<T> SignalPartialOrd for Signal<T>
where where
T: PartialOrd + Clone, T: PartialOrd + Clone,
Linear: InterpolationMethod<T> + FindIntersectionMethod<T>, Linear: InterpolationMethod<T>,
{ {
fn signal_cmp<F>(&self, other: &Self, op: F) -> Option<Signal<bool>> fn signal_cmp<F>(&self, other: &Self, op: F) -> Option<Signal<bool>>
where where
@ -35,7 +35,7 @@ where
impl<T> Signal<T> impl<T> Signal<T>
where where
T: PartialOrd + Clone, T: PartialOrd + Clone,
Linear: InterpolationMethod<T> + FindIntersectionMethod<T>, Linear: InterpolationMethod<T>,
{ {
/// Compute the time-wise min of two signals /// Compute the time-wise min of two signals
pub fn min(&self, other: &Self) -> Self { pub fn min(&self, other: &Self) -> Self {

View file

@ -1,9 +1,10 @@
//! Interpolation methods //! Interpolation methods
use std::cmp::Ordering;
use std::time::Duration; use std::time::Duration;
use super::utils::Neighborhood; use super::utils::Neighborhood;
use super::{FindIntersectionMethod, InterpolationMethod, Sample}; use super::{InterpolationMethod, Sample};
/// Constant interpolation. /// Constant interpolation.
/// ///
@ -20,6 +21,11 @@ impl<T: Clone> InterpolationMethod<T> for Constant {
None None
} }
} }
fn find_intersection(_a: &Neighborhood<T>, _b: &Neighborhood<T>) -> Option<Sample<T>> {
// The signals must be either constant or colinear. Thus, return None.
None
}
} }
/// Nearest interpolation. /// Nearest interpolation.
@ -41,6 +47,12 @@ impl<T: Clone> InterpolationMethod<T> for Nearest {
Some(b.value.clone()) Some(b.value.clone())
} }
} }
fn find_intersection(_a: &Neighborhood<T>, _b: &Neighborhood<T>) -> Option<Sample<T>> {
// For the same reason as Constant interpolation, the signals must be either parallel or
// colinear.
None
}
} }
/// Linear interpolation. /// Linear interpolation.
@ -58,10 +70,8 @@ impl InterpolationMethod<bool> for Linear {
None None
} }
} }
}
impl FindIntersectionMethod<bool> for Linear { fn find_intersection(a: &Neighborhood<bool>, b: &Neighborhood<bool>) -> Option<Sample<bool>> {
fn find_intersection(a: &Neighborhood<bool>, b: &Neighborhood<bool>) -> Sample<bool> {
let Sample { time: ta1, value: ya1 } = a.first.unwrap(); let Sample { time: ta1, value: ya1 } = a.first.unwrap();
let Sample { time: ta2, value: ya2 } = a.second.unwrap(); let Sample { time: ta2, value: ya2 } = a.second.unwrap();
let Sample { time: tb1, value: yb1 } = b.first.unwrap(); let Sample { time: tb1, value: yb1 } = b.first.unwrap();
@ -73,27 +83,31 @@ impl FindIntersectionMethod<bool> for Linear {
if left_cmp.is_eq() { if left_cmp.is_eq() {
// They already intersect, so we return the inner time-point // They already intersect, so we return the inner time-point
if ta1 < tb1 { if ta1 < tb1 {
Sample { time: tb1, value: yb1 } Some(Sample { time: tb1, value: yb1 })
} else { } else {
Sample { time: ta1, value: ya1 } Some(Sample { time: ta1, value: ya1 })
} }
} else if right_cmp.is_eq() { } else if right_cmp.is_eq() {
// They intersect at the end, so we return the outer time-point, as that is // They intersect at the end, so we return the outer time-point, as that is
// when they become equal. // when they become equal.
if ta2 < tb2 { if ta2 < tb2 {
Sample { time: tb2, value: yb2 } Some(Sample { time: tb2, value: yb2 })
} else { } else {
Sample { time: ta2, value: ya2 } Some(Sample { time: ta2, value: ya2 })
} }
} else { } else if let (Ordering::Less, Ordering::Greater) | (Ordering::Greater, Ordering::Less) = (left_cmp, right_cmp)
{
// The switched, so the one that switched earlier will intersect with the // The switched, so the one that switched earlier will intersect with the
// other. // other.
// So, we find the one that has a lower time point, i.e., the inner one. // So, we find the one that has a lower time point, i.e., the inner one.
if ta2 < tb2 { if ta2 < tb2 {
Sample { time: ta2, value: ya2 } Some(Sample { time: ta2, value: ya2 })
} else { } else {
Sample { time: tb2, value: yb2 } Some(Sample { time: tb2, value: yb2 })
} }
} else {
// The lines must be parallel.
None
} }
} }
} }
@ -130,10 +144,8 @@ macro_rules! interpolate_for_num {
cast(val) cast(val)
} }
}
impl FindIntersectionMethod<$ty> for Linear { fn find_intersection(a: &Neighborhood<$ty>, b: &Neighborhood<$ty>) -> Option<Sample<$ty>> {
fn find_intersection(a: &Neighborhood<$ty>, b: &Neighborhood<$ty>) -> Sample<$ty> {
// 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;
@ -153,13 +165,18 @@ macro_rules! interpolate_for_num {
let y4: f64 = cast(y4).unwrap_or_else(|| panic!("unable to cast {:?} to f64", y4)); let y4: f64 = cast(y4).unwrap_or_else(|| panic!("unable to cast {:?} to f64", y4));
let denom = ((t1 - t2) * (y3 - y4)) - ((y1 - y2) * (t3 - t4)); let denom = ((t1 - t2) * (y3 - y4)) - ((y1 - y2) * (t3 - t4));
if denom.abs() <= 1e-10 {
// The lines may be parallel or coincident.
// We just return None
return None;
}
let t_top = (((t1 * y2) - (y1 * t2)) * (t3 - t4)) - ((t1 - t2) * (t3 * y4 - y3 * t4)); let t_top = (((t1 * y2) - (y1 * t2)) * (t3 - t4)) - ((t1 - t2) * (t3 * y4 - y3 * t4));
let y_top = (((t1 * y2) - (y1 * t2)) * (y3 - y4)) - ((y1 - y2) * (t3 * y4 - y3 * t4)); let y_top = (((t1 * y2) - (y1 * t2)) * (y3 - y4)) - ((y1 - y2) * (t3 * y4 - y3 * t4));
let t = Duration::from_secs_f64(t_top / denom); let t = Duration::from_secs_f64(t_top / denom);
let y: $ty = num_traits::cast(y_top / denom).unwrap(); let y: $ty = num_traits::cast(y_top / denom).unwrap();
Sample { time: t, value: y } Some(Sample { time: t, value: y })
} }
} }
}; };

View file

@ -1,5 +1,5 @@
use super::interpolation::Linear; use super::interpolation::Linear;
use super::{FindIntersectionMethod, InterpolationMethod, SignalAbs}; use super::{InterpolationMethod, SignalAbs};
use crate::signals::Signal; use crate::signals::Signal;
impl<T> core::ops::Neg for Signal<T> impl<T> core::ops::Neg for Signal<T>
@ -98,7 +98,7 @@ impl<T> core::ops::Sub<&Signal<T>> for &Signal<T>
where where
for<'a, 'b> &'a T: core::ops::Sub<&'b T, Output = T>, for<'a, 'b> &'a T: core::ops::Sub<&'b T, Output = T>,
T: Clone + PartialOrd, T: Clone + PartialOrd,
Linear: InterpolationMethod<T> + FindIntersectionMethod<T>, Linear: InterpolationMethod<T>,
{ {
type Output = Signal<T>; type Output = Signal<T>;
@ -133,7 +133,7 @@ impl<T> core::ops::Sub<&Signal<T>> for Signal<T>
where where
for<'a, 'b> &'a T: core::ops::Sub<&'b T, Output = T>, for<'a, 'b> &'a T: core::ops::Sub<&'b T, Output = T>,
T: Clone + PartialOrd, T: Clone + PartialOrd,
Linear: InterpolationMethod<T> + FindIntersectionMethod<T>, Linear: InterpolationMethod<T>,
{ {
type Output = Signal<T>; type Output = Signal<T>;

View file

@ -16,14 +16,10 @@ pub trait InterpolationMethod<T> {
/// Returns `None` if it isn't possible to interpolate at the given time using the /// Returns `None` if it isn't possible to interpolate at the given time using the
/// given samples. /// given samples.
fn at(a: &Sample<T>, b: &Sample<T>, time: Duration) -> Option<T>; fn at(a: &Sample<T>, b: &Sample<T>, time: Duration) -> Option<T>;
}
/// Trait implemented by interpolation strategies that allow finding the intersection of
/// two signal segments defined by start and end samples (see [`Neighborhood`]).
pub trait FindIntersectionMethod<T>: InterpolationMethod<T> {
/// Given two signals with two sample points each, find the intersection of the two /// Given two signals with two sample points each, find the intersection of the two
/// lines. /// lines.
fn find_intersection(a: &Neighborhood<T>, b: &Neighborhood<T>) -> Sample<T>; fn find_intersection(a: &Neighborhood<T>, b: &Neighborhood<T>) -> Option<Sample<T>>;
} }
/// Simple trait to be used as a trait object for [`Signal<T>`] types. /// Simple trait to be used as a trait object for [`Signal<T>`] types.