fix!: add explicit interpolation method for more functions
This commit is contained in:
parent
f97d593926
commit
91441d4d3f
7 changed files with 185 additions and 110 deletions
|
|
@ -1,29 +1,28 @@
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use super::interpolation::Linear;
|
|
||||||
use super::traits::SignalPartialOrd;
|
use super::traits::SignalPartialOrd;
|
||||||
use super::{InterpolationMethod, Signal};
|
use super::{InterpolationMethod, Signal};
|
||||||
|
|
||||||
impl<T> SignalPartialOrd for Signal<T>
|
impl<T> SignalPartialOrd<T> for Signal<T>
|
||||||
where
|
where
|
||||||
T: PartialOrd + Clone,
|
T: PartialOrd + Clone,
|
||||||
Linear: InterpolationMethod<T>,
|
|
||||||
{
|
{
|
||||||
fn signal_cmp<F>(&self, other: &Self, op: F) -> Option<Signal<bool>>
|
fn signal_cmp<F, I>(&self, other: &Self, op: F) -> Option<Signal<bool>>
|
||||||
where
|
where
|
||||||
F: Fn(Ordering) -> bool,
|
F: Fn(Ordering) -> bool,
|
||||||
|
I: InterpolationMethod<T>,
|
||||||
{
|
{
|
||||||
// This has to be manually implemented and cannot use the apply2 functions.
|
// This has to be manually implemented and cannot use the apply2 functions.
|
||||||
// This is because if we have two signals that cross each other, then there is
|
// 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
|
// an intermediate point where the two signals are equal. This point must be
|
||||||
// added to the signal appropriately.
|
// added to the signal appropriately.
|
||||||
// the union of the sample points in self and other
|
// the union of the sample points in self and other
|
||||||
let sync_points = self.sync_with_intersection::<Linear>(other)?;
|
let sync_points = self.sync_with_intersection::<I>(other)?;
|
||||||
let sig: Option<Signal<bool>> = sync_points
|
let sig: Option<Signal<bool>> = sync_points
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|t| {
|
.map(|t| {
|
||||||
let lhs = self.interpolate_at::<Linear>(t).unwrap();
|
let lhs = self.interpolate_at::<I>(t).unwrap();
|
||||||
let rhs = other.interpolate_at::<Linear>(t).unwrap();
|
let rhs = other.interpolate_at::<I>(t).unwrap();
|
||||||
let cmp = lhs.partial_cmp(&rhs);
|
let cmp = lhs.partial_cmp(&rhs);
|
||||||
cmp.map(|v| (t, op(v)))
|
cmp.map(|v| (t, op(v)))
|
||||||
})
|
})
|
||||||
|
|
@ -35,16 +34,18 @@ where
|
||||||
impl<T> Signal<T>
|
impl<T> Signal<T>
|
||||||
where
|
where
|
||||||
T: PartialOrd + Clone,
|
T: PartialOrd + Clone,
|
||||||
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<I>(&self, other: &Self) -> Self
|
||||||
let time_points = self.sync_with_intersection::<Linear>(other).unwrap();
|
where
|
||||||
|
I: InterpolationMethod<T>,
|
||||||
|
{
|
||||||
|
let time_points = self.sync_with_intersection::<I>(other).unwrap();
|
||||||
time_points
|
time_points
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|t| {
|
.map(|t| {
|
||||||
let lhs = self.interpolate_at::<Linear>(t).unwrap();
|
let lhs = self.interpolate_at::<I>(t).unwrap();
|
||||||
let rhs = other.interpolate_at::<Linear>(t).unwrap();
|
let rhs = other.interpolate_at::<I>(t).unwrap();
|
||||||
if lhs < rhs {
|
if lhs < rhs {
|
||||||
(t, lhs)
|
(t, lhs)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -55,13 +56,16 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the time-wise max of two signals
|
/// Compute the time-wise max of two signals
|
||||||
pub fn max(&self, other: &Self) -> Self {
|
pub fn max<I>(&self, other: &Self) -> Self
|
||||||
let time_points = self.sync_with_intersection::<Linear>(other).unwrap();
|
where
|
||||||
|
I: InterpolationMethod<T>,
|
||||||
|
{
|
||||||
|
let time_points = self.sync_with_intersection::<I>(other).unwrap();
|
||||||
time_points
|
time_points
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|t| {
|
.map(|t| {
|
||||||
let lhs = self.interpolate_at::<Linear>(t).unwrap();
|
let lhs = self.interpolate_at::<I>(t).unwrap();
|
||||||
let rhs = other.interpolate_at::<Linear>(t).unwrap();
|
let rhs = other.interpolate_at::<I>(t).unwrap();
|
||||||
if lhs > rhs {
|
if lhs > rhs {
|
||||||
(t, lhs)
|
(t, lhs)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,10 @@ impl<T> Signal<T> {
|
||||||
pub fn sub<U, I>(&self, other: &Signal<T>) -> Signal<U>
|
pub fn sub<U, I>(&self, other: &Signal<T>) -> Signal<U>
|
||||||
where
|
where
|
||||||
for<'t> &'t T: core::ops::Sub<Output = U>,
|
for<'t> &'t T: core::ops::Sub<Output = U>,
|
||||||
T: Clone,
|
T: Clone + PartialOrd,
|
||||||
I: InterpolationMethod<T>,
|
I: InterpolationMethod<T>,
|
||||||
{
|
{
|
||||||
self.binary_op::<_, _, I>(other, |u, v| u - v)
|
self.binary_op_with_intersection::<_, _, I>(other, |u, v| u - v)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform sample-wise division of the two signals.
|
/// Perform sample-wise division of the two signals.
|
||||||
|
|
@ -86,7 +86,7 @@ impl<T> Signal<T> {
|
||||||
T: Clone + PartialOrd,
|
T: Clone + PartialOrd,
|
||||||
I: InterpolationMethod<T>,
|
I: InterpolationMethod<T>,
|
||||||
{
|
{
|
||||||
self.binary_op::<_, _, I>(other, |u, v| if u < v { v - u } else { u - v })
|
self.binary_op_with_intersection::<_, _, I>(other, |u, v| if u < v { v - u } else { u - v })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,17 @@ use core::time::Duration;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use super::interpolation::Linear;
|
|
||||||
use super::{InterpolationMethod, Signal};
|
use super::{InterpolationMethod, Signal};
|
||||||
|
|
||||||
impl<T> Signal<T>
|
impl<T> Signal<T>
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
Linear: InterpolationMethod<T>,
|
|
||||||
{
|
{
|
||||||
/// Shift all samples in the signal by `delta` amount to the left.
|
/// Shift all samples in the signal by `delta` amount to the left.
|
||||||
///
|
///
|
||||||
/// This essentially filters out all samples with time points greater than `delta`,
|
/// This essentially filters out all samples with time points greater than `delta`,
|
||||||
/// and subtracts `delta` from the rest of the time points.
|
/// and subtracts `delta` from the rest of the time points.
|
||||||
pub fn shift_left(&self, delta: Duration) -> Self {
|
pub fn shift_left<I: InterpolationMethod<T>>(&self, delta: Duration) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Signal::Sampled { values, time_points } => {
|
Signal::Sampled { values, time_points } => {
|
||||||
// We want to skip any time points < delta, and subtract the rest.
|
// We want to skip any time points < delta, and subtract the rest.
|
||||||
|
|
@ -34,7 +32,7 @@ where
|
||||||
if idx > 0 && first_t != &delta {
|
if idx > 0 && first_t != &delta {
|
||||||
// The shifted signal will not start at 0, and we have a previous
|
// The shifted signal will not start at 0, and we have a previous
|
||||||
// index to interpolate from.
|
// index to interpolate from.
|
||||||
let v = self.interpolate_at::<Linear>(delta).unwrap();
|
let v = self.interpolate_at::<I>(delta).unwrap();
|
||||||
new_samples.push((Duration::ZERO, v));
|
new_samples.push((Duration::ZERO, v));
|
||||||
}
|
}
|
||||||
// Shift the rest of the samples
|
// Shift the rest of the samples
|
||||||
|
|
|
||||||
|
|
@ -42,23 +42,27 @@ macro_rules! impl_signal_cmp {
|
||||||
($cmp:ident) => {
|
($cmp:ident) => {
|
||||||
paste! {
|
paste! {
|
||||||
/// Compute the time-wise comparison of two signals
|
/// Compute the time-wise comparison of two signals
|
||||||
fn [<signal_ $cmp>](&self, other: &Rhs) -> Option<Signal<bool>> {
|
fn [<signal_ $cmp>]<I>(&self, other: &Rhs) -> Option<Signal<bool>>
|
||||||
self.signal_cmp(other, |ord| ord.[<is_ $cmp>]())
|
where
|
||||||
|
I: InterpolationMethod<T>
|
||||||
|
{
|
||||||
|
self.signal_cmp::<_, I>(other, |ord| ord.[<is_ $cmp>]())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A time-wise partial ordering defined for signals
|
/// A time-wise partial ordering defined for signals
|
||||||
pub trait SignalPartialOrd<Rhs = Self> {
|
pub trait SignalPartialOrd<T, Rhs = Self> {
|
||||||
/// Compare two signals within each of their domains (using [`PartialOrd`]) and
|
/// Compare two signals within each of their domains (using [`PartialOrd`]) and
|
||||||
/// apply the given function `op` to the ordering to create a signal.
|
/// apply the given function `op` to the ordering to create a signal.
|
||||||
///
|
///
|
||||||
/// This function returns `None` if the comparison isn't possible, namely, when
|
/// This function returns `None` if the comparison isn't possible, namely, when
|
||||||
/// either of the signals are empty.
|
/// either of the signals are empty.
|
||||||
fn signal_cmp<F>(&self, other: &Rhs, op: F) -> Option<Signal<bool>>
|
fn signal_cmp<F, I>(&self, other: &Rhs, op: F) -> Option<Signal<bool>>
|
||||||
where
|
where
|
||||||
F: Fn(Ordering) -> bool;
|
F: Fn(Ordering) -> bool,
|
||||||
|
I: InterpolationMethod<T>;
|
||||||
|
|
||||||
impl_signal_cmp!(lt);
|
impl_signal_cmp!(lt);
|
||||||
impl_signal_cmp!(le);
|
impl_signal_cmp!(le);
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,35 @@ impl<T> Signal<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn binary_op_with_intersection<U, F, Interp>(&self, other: &Signal<T>, op: F) -> Signal<U>
|
||||||
|
where
|
||||||
|
T: Clone + PartialOrd,
|
||||||
|
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::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).
|
||||||
|
let time_points = lhs.sync_with_intersection::<Interp>(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::<Interp>(t).unwrap();
|
||||||
|
let v2 = rhs.interpolate_at::<Interp>(t).unwrap();
|
||||||
|
(t, op(&v1, &v2))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn partial_min<T>(a: T, b: T) -> Option<T>
|
fn partial_min<T>(a: T, b: T) -> Option<T>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use std::time::Duration;
|
||||||
|
|
||||||
use argus_core::expr::*;
|
use argus_core::expr::*;
|
||||||
use argus_core::prelude::*;
|
use argus_core::prelude::*;
|
||||||
use argus_core::signals::interpolation::Linear;
|
|
||||||
use argus_core::signals::{InterpolationMethod, SignalPartialOrd};
|
use argus_core::signals::{InterpolationMethod, SignalPartialOrd};
|
||||||
|
|
||||||
use crate::semantics::QuantitativeSemantics;
|
use crate::semantics::QuantitativeSemantics;
|
||||||
|
|
@ -30,12 +29,12 @@ impl BooleanSemantics {
|
||||||
let rhs = QuantitativeSemantics::eval_num_expr::<f64, NumI>(rhs, trace)?;
|
let rhs = QuantitativeSemantics::eval_num_expr::<f64, NumI>(rhs, trace)?;
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
Eq => lhs.signal_eq(&rhs).unwrap(),
|
Eq => lhs.signal_eq::<NumI>(&rhs).unwrap(),
|
||||||
NotEq => lhs.signal_ne(&rhs).unwrap(),
|
NotEq => lhs.signal_ne::<NumI>(&rhs).unwrap(),
|
||||||
Less { strict } if *strict => lhs.signal_lt(&rhs).unwrap(),
|
Less { strict } if *strict => lhs.signal_lt::<NumI>(&rhs).unwrap(),
|
||||||
Less { strict: _ } => lhs.signal_le(&rhs).unwrap(),
|
Less { strict: _ } => lhs.signal_le::<NumI>(&rhs).unwrap(),
|
||||||
Greater { strict } if *strict => lhs.signal_gt(&rhs).unwrap(),
|
Greater { strict } if *strict => lhs.signal_gt::<NumI>(&rhs).unwrap(),
|
||||||
Greater { strict: _ } => lhs.signal_ge(&rhs).unwrap(),
|
Greater { strict: _ } => lhs.signal_ge::<NumI>(&rhs).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BoolExpr::Not(Not { arg }) => {
|
BoolExpr::Not(Not { arg }) => {
|
||||||
|
|
@ -72,16 +71,16 @@ impl BooleanSemantics {
|
||||||
}
|
}
|
||||||
BoolExpr::Always(Always { arg, interval }) => {
|
BoolExpr::Always(Always { arg, interval }) => {
|
||||||
let arg = Self::eval::<BoolI, NumI>(arg, trace)?;
|
let arg = Self::eval::<BoolI, NumI>(arg, trace)?;
|
||||||
compute_always(arg, interval)?
|
compute_always::<BoolI>(arg, interval)?
|
||||||
}
|
}
|
||||||
BoolExpr::Eventually(Eventually { arg, interval }) => {
|
BoolExpr::Eventually(Eventually { arg, interval }) => {
|
||||||
let arg = Self::eval::<BoolI, NumI>(arg, trace)?;
|
let arg = Self::eval::<BoolI, NumI>(arg, trace)?;
|
||||||
compute_eventually(arg, interval)?
|
compute_eventually::<BoolI>(arg, interval)?
|
||||||
}
|
}
|
||||||
BoolExpr::Until(Until { lhs, rhs, interval }) => {
|
BoolExpr::Until(Until { lhs, rhs, interval }) => {
|
||||||
let lhs = Self::eval::<BoolI, NumI>(lhs, trace)?;
|
let lhs = Self::eval::<BoolI, NumI>(lhs, trace)?;
|
||||||
let rhs = Self::eval::<BoolI, NumI>(rhs, trace)?;
|
let rhs = Self::eval::<BoolI, NumI>(rhs, trace)?;
|
||||||
compute_until(lhs, rhs, interval)?
|
compute_until::<BoolI>(lhs, rhs, interval)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
|
|
@ -124,7 +123,10 @@ fn compute_oracle(arg: Signal<bool>, steps: usize) -> ArgusResult<Signal<bool>>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute always for a signal
|
/// Compute always for a signal
|
||||||
fn compute_always(signal: Signal<bool>, interval: &Interval) -> ArgusResult<Signal<bool>> {
|
fn compute_always<I: InterpolationMethod<bool>>(
|
||||||
|
signal: Signal<bool>,
|
||||||
|
interval: &Interval,
|
||||||
|
) -> ArgusResult<Signal<bool>> {
|
||||||
if interval.is_empty() || interval.is_singleton() {
|
if interval.is_empty() || interval.is_singleton() {
|
||||||
return Err(ArgusError::InvalidInterval {
|
return Err(ArgusError::InvalidInterval {
|
||||||
reason: "interval is either empty or singleton",
|
reason: "interval is either empty or singleton",
|
||||||
|
|
@ -143,9 +145,9 @@ fn compute_always(signal: Signal<bool>, interval: &Interval) -> ArgusResult<Sign
|
||||||
} else if interval.is_untimed() {
|
} else if interval.is_untimed() {
|
||||||
compute_untimed_always(sig)?
|
compute_untimed_always(sig)?
|
||||||
} else if let (Included(a), Included(b)) = interval.into() {
|
} else if let (Included(a), Included(b)) = interval.into() {
|
||||||
compute_timed_always(sig, *a, Some(*b))?
|
compute_timed_always::<I>(sig, *a, Some(*b))?
|
||||||
} else if let (Included(a), Unbounded) = interval.into() {
|
} else if let (Included(a), Unbounded) = interval.into() {
|
||||||
compute_timed_always(sig, *a, None)?
|
compute_timed_always::<I>(sig, *a, None)?
|
||||||
} else {
|
} else {
|
||||||
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
||||||
}
|
}
|
||||||
|
|
@ -155,9 +157,13 @@ fn compute_always(signal: Signal<bool>, interval: &Interval) -> ArgusResult<Sign
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute timed always for the interval `[a, b]` (or, if `b` is `None`, `[a, ..]`.
|
/// Compute timed always for the interval `[a, b]` (or, if `b` is `None`, `[a, ..]`.
|
||||||
fn compute_timed_always(signal: Signal<bool>, a: Duration, b: Option<Duration>) -> ArgusResult<Signal<bool>> {
|
fn compute_timed_always<I: InterpolationMethod<bool>>(
|
||||||
|
signal: Signal<bool>,
|
||||||
|
a: Duration,
|
||||||
|
b: Option<Duration>,
|
||||||
|
) -> ArgusResult<Signal<bool>> {
|
||||||
let z1 = !signal;
|
let z1 = !signal;
|
||||||
let z2 = compute_timed_eventually(z1, a, b)?;
|
let z2 = compute_timed_eventually::<I>(z1, a, b)?;
|
||||||
Ok(!z2)
|
Ok(!z2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,7 +184,10 @@ fn compute_untimed_always(signal: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute eventually for a signal
|
/// Compute eventually for a signal
|
||||||
fn compute_eventually(signal: Signal<bool>, interval: &Interval) -> ArgusResult<Signal<bool>> {
|
fn compute_eventually<I: InterpolationMethod<bool>>(
|
||||||
|
signal: Signal<bool>,
|
||||||
|
interval: &Interval,
|
||||||
|
) -> ArgusResult<Signal<bool>> {
|
||||||
if interval.is_empty() || interval.is_singleton() {
|
if interval.is_empty() || interval.is_singleton() {
|
||||||
return Err(ArgusError::InvalidInterval {
|
return Err(ArgusError::InvalidInterval {
|
||||||
reason: "interval is either empty or singleton",
|
reason: "interval is either empty or singleton",
|
||||||
|
|
@ -197,9 +206,9 @@ fn compute_eventually(signal: Signal<bool>, interval: &Interval) -> ArgusResult<
|
||||||
} else if interval.is_untimed() {
|
} else if interval.is_untimed() {
|
||||||
compute_untimed_eventually(sig)?
|
compute_untimed_eventually(sig)?
|
||||||
} else if let (Included(a), Included(b)) = interval.into() {
|
} else if let (Included(a), Included(b)) = interval.into() {
|
||||||
compute_timed_eventually(sig, *a, Some(*b))?
|
compute_timed_eventually::<I>(sig, *a, Some(*b))?
|
||||||
} else if let (Included(a), Unbounded) = interval.into() {
|
} else if let (Included(a), Unbounded) = interval.into() {
|
||||||
compute_timed_eventually(sig, *a, None)?
|
compute_timed_eventually::<I>(sig, *a, None)?
|
||||||
} else {
|
} else {
|
||||||
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +218,11 @@ fn compute_eventually(signal: Signal<bool>, interval: &Interval) -> ArgusResult<
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute timed eventually for the interval `[a, b]` (or, if `b` is `None`, `[a,..]`.
|
/// Compute timed eventually for the interval `[a, b]` (or, if `b` is `None`, `[a,..]`.
|
||||||
fn compute_timed_eventually(signal: Signal<bool>, a: Duration, b: Option<Duration>) -> ArgusResult<Signal<bool>> {
|
fn compute_timed_eventually<I: InterpolationMethod<bool>>(
|
||||||
|
signal: Signal<bool>,
|
||||||
|
a: Duration,
|
||||||
|
b: Option<Duration>,
|
||||||
|
) -> ArgusResult<Signal<bool>> {
|
||||||
match b {
|
match b {
|
||||||
Some(b) => {
|
Some(b) => {
|
||||||
// We want to compute the windowed max/or of the signal.
|
// We want to compute the windowed max/or of the signal.
|
||||||
|
|
@ -245,7 +258,7 @@ fn compute_timed_eventually(signal: Signal<bool>, a: Duration, b: Option<Duratio
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Shift the signal to the left by `a` and then run the untimed eventually.
|
// Shift the signal to the left by `a` and then run the untimed eventually.
|
||||||
let shifted = signal.shift_left(a);
|
let shifted = signal.shift_left::<I>(a);
|
||||||
compute_untimed_eventually(shifted)
|
compute_untimed_eventually(shifted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -268,18 +281,22 @@ fn compute_untimed_eventually(signal: Signal<bool>) -> ArgusResult<Signal<bool>>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute until
|
/// Compute until
|
||||||
fn compute_until(lhs: Signal<bool>, rhs: Signal<bool>, interval: &Interval) -> ArgusResult<Signal<bool>> {
|
fn compute_until<I: InterpolationMethod<bool>>(
|
||||||
|
lhs: Signal<bool>,
|
||||||
|
rhs: Signal<bool>,
|
||||||
|
interval: &Interval,
|
||||||
|
) -> ArgusResult<Signal<bool>> {
|
||||||
let ret = match (lhs, rhs) {
|
let ret = match (lhs, rhs) {
|
||||||
// If either signals are empty, return empty
|
// If either signals are empty, return empty
|
||||||
(sig @ Signal::Empty, _) | (_, sig @ Signal::Empty) => sig,
|
(sig @ Signal::Empty, _) | (_, sig @ Signal::Empty) => sig,
|
||||||
(lhs, rhs) => {
|
(lhs, rhs) => {
|
||||||
use Bound::*;
|
use Bound::*;
|
||||||
if interval.is_untimed() {
|
if interval.is_untimed() {
|
||||||
compute_untimed_until(lhs, rhs)?
|
compute_untimed_until::<I>(lhs, rhs)?
|
||||||
} else if let (Included(a), Included(b)) = interval.into() {
|
} else if let (Included(a), Included(b)) = interval.into() {
|
||||||
compute_timed_until(lhs, rhs, *a, Some(*b))?
|
compute_timed_until::<I>(lhs, rhs, *a, Some(*b))?
|
||||||
} else if let (Included(a), Unbounded) = interval.into() {
|
} else if let (Included(a), Unbounded) = interval.into() {
|
||||||
compute_timed_until(lhs, rhs, *a, None)?
|
compute_timed_until::<I>(lhs, rhs, *a, None)?
|
||||||
} else {
|
} else {
|
||||||
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
||||||
}
|
}
|
||||||
|
|
@ -301,7 +318,7 @@ fn compute_until(lhs: Signal<bool>, rhs: Signal<bool>, interval: &Interval) -> A
|
||||||
/// $$
|
/// $$
|
||||||
///
|
///
|
||||||
/// [1]: <> (A. Donzé, T. Ferrère, and O. Maler, "Efficient Robust Monitoring for STL.")
|
/// [1]: <> (A. Donzé, T. Ferrère, and O. Maler, "Efficient Robust Monitoring for STL.")
|
||||||
fn compute_timed_until(
|
fn compute_timed_until<I: InterpolationMethod<bool>>(
|
||||||
lhs: Signal<bool>,
|
lhs: Signal<bool>,
|
||||||
rhs: Signal<bool>,
|
rhs: Signal<bool>,
|
||||||
a: Duration,
|
a: Duration,
|
||||||
|
|
@ -310,33 +327,36 @@ fn compute_timed_until(
|
||||||
match b {
|
match b {
|
||||||
Some(b) => {
|
Some(b) => {
|
||||||
// First compute eventually [a, b]
|
// First compute eventually [a, b]
|
||||||
let ev_a_b_rhs = compute_timed_eventually(rhs.clone(), a, Some(b))?;
|
let ev_a_b_rhs = compute_timed_eventually::<I>(rhs.clone(), a, Some(b))?;
|
||||||
// Then compute until [a, \infty) (lhs, rhs)
|
// Then compute until [a, \infty) (lhs, rhs)
|
||||||
let unt_a_inf = compute_timed_until(lhs, rhs, a, None)?;
|
let unt_a_inf = compute_timed_until::<I>(lhs, rhs, a, None)?;
|
||||||
// Then & them
|
// Then & them
|
||||||
Ok(ev_a_b_rhs.min(&unt_a_inf))
|
Ok(ev_a_b_rhs.and::<I>(&unt_a_inf))
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
assert_ne!(a, Duration::ZERO, "untimed case wasn't handled for Until");
|
assert_ne!(a, Duration::ZERO, "untimed case wasn't handled for Until");
|
||||||
// First compute untimed until (lhs, rhs)
|
// First compute untimed until (lhs, rhs)
|
||||||
let untimed_until = compute_untimed_until(lhs, rhs)?;
|
let untimed_until = compute_untimed_until::<I>(lhs, rhs)?;
|
||||||
// Compute G [0, a]
|
// Compute G [0, a]
|
||||||
compute_timed_always(untimed_until, Duration::ZERO, Some(a))
|
compute_timed_always::<I>(untimed_until, Duration::ZERO, Some(a))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute untimed until
|
/// Compute untimed until
|
||||||
fn compute_untimed_until(lhs: Signal<bool>, rhs: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
fn compute_untimed_until<I: InterpolationMethod<bool>>(
|
||||||
let sync_points = lhs.sync_with_intersection::<Linear>(&rhs).unwrap();
|
lhs: Signal<bool>,
|
||||||
|
rhs: Signal<bool>,
|
||||||
|
) -> ArgusResult<Signal<bool>> {
|
||||||
|
let sync_points = lhs.sync_with_intersection::<I>(&rhs).unwrap();
|
||||||
let mut ret_samples = Vec::with_capacity(sync_points.len());
|
let mut ret_samples = Vec::with_capacity(sync_points.len());
|
||||||
let expected_len = sync_points.len();
|
let expected_len = sync_points.len();
|
||||||
|
|
||||||
let mut next = false;
|
let mut next = false;
|
||||||
|
|
||||||
for (i, t) in sync_points.into_iter().enumerate().rev() {
|
for (i, t) in sync_points.into_iter().enumerate().rev() {
|
||||||
let v1 = lhs.interpolate_at::<Linear>(t).unwrap();
|
let v1 = lhs.interpolate_at::<I>(t).unwrap();
|
||||||
let v2 = rhs.interpolate_at::<Linear>(t).unwrap();
|
let v2 = rhs.interpolate_at::<I>(t).unwrap();
|
||||||
|
|
||||||
#[allow(clippy::nonminimal_bool)]
|
#[allow(clippy::nonminimal_bool)]
|
||||||
let z = (v1 && v2) || (v1 && next);
|
let z = (v1 && v2) || (v1 && next);
|
||||||
|
|
@ -355,6 +375,7 @@ mod tests {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use argus_core::expr::ExprBuilder;
|
use argus_core::expr::ExprBuilder;
|
||||||
|
use argus_core::signals::interpolation::Linear;
|
||||||
use argus_core::signals::AnySignal;
|
use argus_core::signals::AnySignal;
|
||||||
use itertools::assert_equal;
|
use itertools::assert_equal;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use std::time::Duration;
|
||||||
|
|
||||||
use argus_core::expr::*;
|
use argus_core::expr::*;
|
||||||
use argus_core::prelude::*;
|
use argus_core::prelude::*;
|
||||||
use argus_core::signals::interpolation::Linear;
|
|
||||||
use argus_core::signals::{InterpolationMethod, SignalAbs};
|
use argus_core::signals::{InterpolationMethod, SignalAbs};
|
||||||
use num_traits::{Num, NumCast};
|
use num_traits::{Num, NumCast};
|
||||||
|
|
||||||
|
|
@ -13,7 +12,10 @@ use crate::utils::lemire_minmax::MonoWedge;
|
||||||
pub struct QuantitativeSemantics;
|
pub struct QuantitativeSemantics;
|
||||||
|
|
||||||
impl QuantitativeSemantics {
|
impl QuantitativeSemantics {
|
||||||
pub fn eval<I: InterpolationMethod<f64>>(expr: &BoolExpr, trace: &impl Trace) -> ArgusResult<Signal<f64>> {
|
pub fn eval<I>(expr: &BoolExpr, trace: &impl Trace) -> ArgusResult<Signal<f64>>
|
||||||
|
where
|
||||||
|
I: InterpolationMethod<f64>,
|
||||||
|
{
|
||||||
let ret = match expr {
|
let ret = match expr {
|
||||||
BoolExpr::BoolLit(val) => top_or_bot(&Signal::constant(val.0)),
|
BoolExpr::BoolLit(val) => top_or_bot(&Signal::constant(val.0)),
|
||||||
BoolExpr::BoolVar(BoolVar { name }) => trace
|
BoolExpr::BoolVar(BoolVar { name }) => trace
|
||||||
|
|
@ -39,7 +41,7 @@ impl QuantitativeSemantics {
|
||||||
Signal::constant(f64::INFINITY),
|
Signal::constant(f64::INFINITY),
|
||||||
|acc, item| {
|
|acc, item| {
|
||||||
let item = item?;
|
let item = item?;
|
||||||
Ok(acc.min(&item))
|
Ok(acc.min::<I>(&item))
|
||||||
},
|
},
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
|
@ -49,7 +51,7 @@ impl QuantitativeSemantics {
|
||||||
Signal::constant(f64::NEG_INFINITY),
|
Signal::constant(f64::NEG_INFINITY),
|
||||||
|acc, item| {
|
|acc, item| {
|
||||||
let item = item?;
|
let item = item?;
|
||||||
Ok(acc.max(&item))
|
Ok(acc.max::<I>(&item))
|
||||||
},
|
},
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
|
@ -63,16 +65,16 @@ impl QuantitativeSemantics {
|
||||||
}
|
}
|
||||||
BoolExpr::Always(Always { arg, interval }) => {
|
BoolExpr::Always(Always { arg, interval }) => {
|
||||||
let arg = Self::eval::<I>(arg, trace)?;
|
let arg = Self::eval::<I>(arg, trace)?;
|
||||||
compute_always(arg, interval)?
|
compute_always::<I>(arg, interval)?
|
||||||
}
|
}
|
||||||
BoolExpr::Eventually(Eventually { arg, interval }) => {
|
BoolExpr::Eventually(Eventually { arg, interval }) => {
|
||||||
let arg = Self::eval::<I>(arg, trace)?;
|
let arg = Self::eval::<I>(arg, trace)?;
|
||||||
compute_eventually(arg, interval)?
|
compute_eventually::<I>(arg, interval)?
|
||||||
}
|
}
|
||||||
BoolExpr::Until(Until { lhs, rhs, interval }) => {
|
BoolExpr::Until(Until { lhs, rhs, interval }) => {
|
||||||
let lhs = Self::eval::<I>(lhs, trace)?;
|
let lhs = Self::eval::<I>(lhs, trace)?;
|
||||||
let rhs = Self::eval::<I>(rhs, trace)?;
|
let rhs = Self::eval::<I>(rhs, trace)?;
|
||||||
compute_until(lhs, rhs, interval)?
|
compute_until::<I>(lhs, rhs, interval)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
|
|
@ -80,7 +82,7 @@ impl QuantitativeSemantics {
|
||||||
|
|
||||||
pub fn eval_num_expr<T, I>(root: &NumExpr, trace: &impl Trace) -> ArgusResult<Signal<T>>
|
pub fn eval_num_expr<T, I>(root: &NumExpr, trace: &impl Trace) -> ArgusResult<Signal<T>>
|
||||||
where
|
where
|
||||||
T: Num + NumCast + Clone,
|
T: Num + NumCast + Clone + PartialOrd,
|
||||||
for<'a> &'a T: core::ops::Neg<Output = T>,
|
for<'a> &'a T: core::ops::Neg<Output = T>,
|
||||||
for<'a> &'a T: core::ops::Add<&'a T, Output = T>,
|
for<'a> &'a T: core::ops::Add<&'a T, Output = T>,
|
||||||
for<'a> &'a T: core::ops::Sub<&'a T, Output = T>,
|
for<'a> &'a T: core::ops::Sub<&'a T, Output = T>,
|
||||||
|
|
@ -167,7 +169,7 @@ fn compute_oracle(arg: Signal<f64>, steps: usize) -> ArgusResult<Signal<f64>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute always for a signal
|
/// Compute always for a signal
|
||||||
fn compute_always(signal: Signal<f64>, interval: &Interval) -> ArgusResult<Signal<f64>> {
|
fn compute_always<I: InterpolationMethod<f64>>(signal: Signal<f64>, interval: &Interval) -> ArgusResult<Signal<f64>> {
|
||||||
if interval.is_empty() || interval.is_singleton() {
|
if interval.is_empty() || interval.is_singleton() {
|
||||||
return Err(ArgusError::InvalidInterval {
|
return Err(ArgusError::InvalidInterval {
|
||||||
reason: "interval is either empty or singleton",
|
reason: "interval is either empty or singleton",
|
||||||
|
|
@ -184,11 +186,11 @@ fn compute_always(signal: Signal<f64>, interval: &Interval) -> ArgusResult<Signa
|
||||||
// for singleton intervals, return the signal itself.
|
// for singleton intervals, return the signal itself.
|
||||||
sig
|
sig
|
||||||
} else if interval.is_untimed() {
|
} else if interval.is_untimed() {
|
||||||
compute_untimed_always(sig)?
|
compute_untimed_always::<I>(sig)?
|
||||||
} else if let (Included(a), Included(b)) = interval.into() {
|
} else if let (Included(a), Included(b)) = interval.into() {
|
||||||
compute_timed_always(sig, *a, Some(*b))?
|
compute_timed_always::<I>(sig, *a, Some(*b))?
|
||||||
} else if let (Included(a), Unbounded) = interval.into() {
|
} else if let (Included(a), Unbounded) = interval.into() {
|
||||||
compute_timed_always(sig, *a, None)?
|
compute_timed_always::<I>(sig, *a, None)?
|
||||||
} else {
|
} else {
|
||||||
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
||||||
}
|
}
|
||||||
|
|
@ -198,21 +200,26 @@ fn compute_always(signal: Signal<f64>, interval: &Interval) -> ArgusResult<Signa
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute timed always for the interval `[a, b]` (or, if `b` is `None`, `[a, ..]`.
|
/// Compute timed always for the interval `[a, b]` (or, if `b` is `None`, `[a, ..]`.
|
||||||
fn compute_timed_always(signal: Signal<f64>, a: Duration, b: Option<Duration>) -> ArgusResult<Signal<f64>> {
|
fn compute_timed_always<I: InterpolationMethod<f64>>(
|
||||||
|
signal: Signal<f64>,
|
||||||
|
a: Duration,
|
||||||
|
b: Option<Duration>,
|
||||||
|
) -> ArgusResult<Signal<f64>> {
|
||||||
let z1 = signal.negate();
|
let z1 = signal.negate();
|
||||||
let z2 = compute_timed_eventually(z1, a, b)?;
|
let z2 = compute_timed_eventually::<I>(z1, a, b)?;
|
||||||
Ok(z2.negate())
|
Ok(z2.negate())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute untimed always
|
/// Compute untimed always
|
||||||
fn compute_untimed_always(signal: Signal<f64>) -> ArgusResult<Signal<f64>> {
|
fn compute_untimed_always<I: InterpolationMethod<f64>>(signal: Signal<f64>) -> ArgusResult<Signal<f64>> {
|
||||||
let Signal::Sampled {
|
// Find all the points where the argument signal crosses 0
|
||||||
mut values,
|
let Some(time_points) = signal.sync_with_intersection::<I>(&Signal::constant(0.0)) else {
|
||||||
time_points,
|
|
||||||
} = signal
|
|
||||||
else {
|
|
||||||
unreachable!("we shouldn't be passing non-sampled signals here")
|
unreachable!("we shouldn't be passing non-sampled signals here")
|
||||||
};
|
};
|
||||||
|
let mut values: Vec<f64> = time_points
|
||||||
|
.iter()
|
||||||
|
.map(|&t| signal.interpolate_at::<I>(t).unwrap())
|
||||||
|
.collect();
|
||||||
// Compute the & in a expanding window fashion from the back
|
// Compute the & in a expanding window fashion from the back
|
||||||
for i in (0..(time_points.len() - 1)).rev() {
|
for i in (0..(time_points.len() - 1)).rev() {
|
||||||
values[i] = values[i + 1].min(values[i]);
|
values[i] = values[i + 1].min(values[i]);
|
||||||
|
|
@ -221,7 +228,10 @@ fn compute_untimed_always(signal: Signal<f64>) -> ArgusResult<Signal<f64>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute eventually for a signal
|
/// Compute eventually for a signal
|
||||||
fn compute_eventually(signal: Signal<f64>, interval: &Interval) -> ArgusResult<Signal<f64>> {
|
fn compute_eventually<I: InterpolationMethod<f64>>(
|
||||||
|
signal: Signal<f64>,
|
||||||
|
interval: &Interval,
|
||||||
|
) -> ArgusResult<Signal<f64>> {
|
||||||
if interval.is_empty() || interval.is_singleton() {
|
if interval.is_empty() || interval.is_singleton() {
|
||||||
return Err(ArgusError::InvalidInterval {
|
return Err(ArgusError::InvalidInterval {
|
||||||
reason: "interval is either empty or singleton",
|
reason: "interval is either empty or singleton",
|
||||||
|
|
@ -238,11 +248,11 @@ fn compute_eventually(signal: Signal<f64>, interval: &Interval) -> ArgusResult<S
|
||||||
// for singleton intervals, return the signal itself.
|
// for singleton intervals, return the signal itself.
|
||||||
sig
|
sig
|
||||||
} else if interval.is_untimed() {
|
} else if interval.is_untimed() {
|
||||||
compute_untimed_eventually(sig)?
|
compute_untimed_eventually::<I>(sig)?
|
||||||
} else if let (Included(a), Included(b)) = interval.into() {
|
} else if let (Included(a), Included(b)) = interval.into() {
|
||||||
compute_timed_eventually(sig, *a, Some(*b))?
|
compute_timed_eventually::<I>(sig, *a, Some(*b))?
|
||||||
} else if let (Included(a), Unbounded) = interval.into() {
|
} else if let (Included(a), Unbounded) = interval.into() {
|
||||||
compute_timed_eventually(sig, *a, None)?
|
compute_timed_eventually::<I>(sig, *a, None)?
|
||||||
} else {
|
} else {
|
||||||
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +262,11 @@ fn compute_eventually(signal: Signal<f64>, interval: &Interval) -> ArgusResult<S
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute timed eventually for the interval `[a, b]` (or, if `b` is `None`, `[a,..]`.
|
/// Compute timed eventually for the interval `[a, b]` (or, if `b` is `None`, `[a,..]`.
|
||||||
fn compute_timed_eventually(signal: Signal<f64>, a: Duration, b: Option<Duration>) -> ArgusResult<Signal<f64>> {
|
fn compute_timed_eventually<I: InterpolationMethod<f64>>(
|
||||||
|
signal: Signal<f64>,
|
||||||
|
a: Duration,
|
||||||
|
b: Option<Duration>,
|
||||||
|
) -> ArgusResult<Signal<f64>> {
|
||||||
match b {
|
match b {
|
||||||
Some(b) => {
|
Some(b) => {
|
||||||
// We want to compute the windowed max/or of the signal.
|
// We want to compute the windowed max/or of the signal.
|
||||||
|
|
@ -288,21 +302,22 @@ fn compute_timed_eventually(signal: Signal<f64>, a: Duration, b: Option<Duration
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Shift the signal to the left by `a` and then run the untimed eventually.
|
// Shift the signal to the left by `a` and then run the untimed eventually.
|
||||||
let shifted = signal.shift_left(a);
|
let shifted = signal.shift_left::<I>(a);
|
||||||
compute_untimed_eventually(shifted)
|
compute_untimed_eventually::<I>(shifted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute untimed eventually
|
/// Compute untimed eventually
|
||||||
fn compute_untimed_eventually(signal: Signal<f64>) -> ArgusResult<Signal<f64>> {
|
fn compute_untimed_eventually<I: InterpolationMethod<f64>>(signal: Signal<f64>) -> ArgusResult<Signal<f64>> {
|
||||||
let Signal::Sampled {
|
// Find all the points where the argument signal crosses 0
|
||||||
mut values,
|
let Some(time_points) = signal.sync_with_intersection::<I>(&Signal::constant(0.0)) else {
|
||||||
time_points,
|
|
||||||
} = signal
|
|
||||||
else {
|
|
||||||
unreachable!("we shouldn't be passing non-sampled signals here")
|
unreachable!("we shouldn't be passing non-sampled signals here")
|
||||||
};
|
};
|
||||||
|
let mut values: Vec<f64> = time_points
|
||||||
|
.iter()
|
||||||
|
.map(|&t| signal.interpolate_at::<I>(t).unwrap())
|
||||||
|
.collect();
|
||||||
// Compute the | in a expanding window fashion from the back
|
// Compute the | in a expanding window fashion from the back
|
||||||
for i in (0..(time_points.len() - 1)).rev() {
|
for i in (0..(time_points.len() - 1)).rev() {
|
||||||
values[i] = values[i + 1].max(values[i]);
|
values[i] = values[i + 1].max(values[i]);
|
||||||
|
|
@ -311,18 +326,22 @@ fn compute_untimed_eventually(signal: Signal<f64>) -> ArgusResult<Signal<f64>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute until
|
/// Compute until
|
||||||
fn compute_until(lhs: Signal<f64>, rhs: Signal<f64>, interval: &Interval) -> ArgusResult<Signal<f64>> {
|
fn compute_until<I: InterpolationMethod<f64>>(
|
||||||
|
lhs: Signal<f64>,
|
||||||
|
rhs: Signal<f64>,
|
||||||
|
interval: &Interval,
|
||||||
|
) -> ArgusResult<Signal<f64>> {
|
||||||
let ret = match (lhs, rhs) {
|
let ret = match (lhs, rhs) {
|
||||||
// If either signals are empty, return empty
|
// If either signals are empty, return empty
|
||||||
(sig @ Signal::Empty, _) | (_, sig @ Signal::Empty) => sig,
|
(sig @ Signal::Empty, _) | (_, sig @ Signal::Empty) => sig,
|
||||||
(lhs, rhs) => {
|
(lhs, rhs) => {
|
||||||
use Bound::*;
|
use Bound::*;
|
||||||
if interval.is_untimed() {
|
if interval.is_untimed() {
|
||||||
compute_untimed_until(lhs, rhs)?
|
compute_untimed_until::<I>(lhs, rhs)?
|
||||||
} else if let (Included(a), Included(b)) = interval.into() {
|
} else if let (Included(a), Included(b)) = interval.into() {
|
||||||
compute_timed_until(lhs, rhs, *a, Some(*b))?
|
compute_timed_until::<I>(lhs, rhs, *a, Some(*b))?
|
||||||
} else if let (Included(a), Unbounded) = interval.into() {
|
} else if let (Included(a), Unbounded) = interval.into() {
|
||||||
compute_timed_until(lhs, rhs, *a, None)?
|
compute_timed_until::<I>(lhs, rhs, *a, None)?
|
||||||
} else {
|
} else {
|
||||||
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
||||||
}
|
}
|
||||||
|
|
@ -344,7 +363,7 @@ fn compute_until(lhs: Signal<f64>, rhs: Signal<f64>, interval: &Interval) -> Arg
|
||||||
/// $$
|
/// $$
|
||||||
///
|
///
|
||||||
/// [1]: <> (A. Donzé, T. Ferrère, and O. Maler, "Efficient Robust Monitoring for STL.")
|
/// [1]: <> (A. Donzé, T. Ferrère, and O. Maler, "Efficient Robust Monitoring for STL.")
|
||||||
fn compute_timed_until(
|
fn compute_timed_until<I: InterpolationMethod<f64>>(
|
||||||
lhs: Signal<f64>,
|
lhs: Signal<f64>,
|
||||||
rhs: Signal<f64>,
|
rhs: Signal<f64>,
|
||||||
a: Duration,
|
a: Duration,
|
||||||
|
|
@ -353,33 +372,33 @@ fn compute_timed_until(
|
||||||
match b {
|
match b {
|
||||||
Some(b) => {
|
Some(b) => {
|
||||||
// First compute eventually [a, b]
|
// First compute eventually [a, b]
|
||||||
let ev_a_b_rhs = compute_timed_eventually(rhs.clone(), a, Some(b))?;
|
let ev_a_b_rhs = compute_timed_eventually::<I>(rhs.clone(), a, Some(b))?;
|
||||||
// Then compute until [a, \infty) (lhs, rhs)
|
// Then compute until [a, \infty) (lhs, rhs)
|
||||||
let unt_a_inf = compute_timed_until(lhs, rhs, a, None)?;
|
let unt_a_inf = compute_timed_until::<I>(lhs, rhs, a, None)?;
|
||||||
// Then & them
|
// Then & them
|
||||||
Ok(ev_a_b_rhs.min(&unt_a_inf))
|
Ok(ev_a_b_rhs.min::<I>(&unt_a_inf))
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
assert_ne!(a, Duration::ZERO, "untimed case wasn't handled for Until");
|
assert_ne!(a, Duration::ZERO, "untimed case wasn't handled for Until");
|
||||||
// First compute untimed until (lhs, rhs)
|
// First compute untimed until (lhs, rhs)
|
||||||
let untimed_until = compute_untimed_until(lhs, rhs)?;
|
let untimed_until = compute_untimed_until::<I>(lhs, rhs)?;
|
||||||
// Compute G [0, a]
|
// Compute G [0, a]
|
||||||
compute_timed_always(untimed_until, Duration::ZERO, Some(a))
|
compute_timed_always::<I>(untimed_until, Duration::ZERO, Some(a))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute untimed until
|
/// Compute untimed until
|
||||||
fn compute_untimed_until(lhs: Signal<f64>, rhs: Signal<f64>) -> ArgusResult<Signal<f64>> {
|
fn compute_untimed_until<I: InterpolationMethod<f64>>(lhs: Signal<f64>, rhs: Signal<f64>) -> ArgusResult<Signal<f64>> {
|
||||||
let sync_points = lhs.sync_with_intersection::<Linear>(&rhs).unwrap();
|
let sync_points = lhs.sync_with_intersection::<I>(&rhs).unwrap();
|
||||||
let mut ret_samples = Vec::with_capacity(sync_points.len());
|
let mut ret_samples = Vec::with_capacity(sync_points.len());
|
||||||
let expected_len = sync_points.len();
|
let expected_len = sync_points.len();
|
||||||
|
|
||||||
let mut next = f64::NEG_INFINITY;
|
let mut next = f64::NEG_INFINITY;
|
||||||
|
|
||||||
for (i, t) in sync_points.into_iter().enumerate().rev() {
|
for (i, t) in sync_points.into_iter().enumerate().rev() {
|
||||||
let v1 = lhs.interpolate_at::<Linear>(t).unwrap();
|
let v1 = lhs.interpolate_at::<I>(t).unwrap();
|
||||||
let v2 = rhs.interpolate_at::<Linear>(t).unwrap();
|
let v2 = rhs.interpolate_at::<I>(t).unwrap();
|
||||||
|
|
||||||
let z = f64::max(f64::min(v1, v2), f64::min(v1, next));
|
let z = f64::max(f64::min(v1, v2), f64::min(v1, next));
|
||||||
if z == next && i < (expected_len - 2) {
|
if z == next && i < (expected_len - 2) {
|
||||||
|
|
@ -416,7 +435,7 @@ mod tests {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use argus_core::expr::ExprBuilder;
|
use argus_core::expr::ExprBuilder;
|
||||||
use argus_core::signals::interpolation::Constant;
|
use argus_core::signals::interpolation::{Constant, Linear};
|
||||||
use argus_core::signals::AnySignal;
|
use argus_core::signals::AnySignal;
|
||||||
use itertools::assert_equal;
|
use itertools::assert_equal;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue