feat(argus-semantics): implement efficient streaming MinMax
This commit is contained in:
parent
4084bb738b
commit
86cef692dc
8 changed files with 353 additions and 162 deletions
|
|
@ -1,93 +1,130 @@
|
|||
use std::ops::Bound;
|
||||
use std::time::Duration;
|
||||
|
||||
use argus_core::expr::{Always, And, BoolVar, Cmp, Eventually, Interval, Next, Not, Or, Oracle, Until};
|
||||
use argus_core::expr::*;
|
||||
use argus_core::prelude::*;
|
||||
use argus_core::signals::SignalPartialOrd;
|
||||
|
||||
use crate::eval::eval_num_expr;
|
||||
use crate::{Semantics, Trace};
|
||||
use crate::traits::{BooleanSemantics, QuantitativeSemantics, Trace};
|
||||
|
||||
/// Boolean semantics of Argus expressions
|
||||
pub struct BooleanSemantics;
|
||||
|
||||
impl Semantics for BooleanSemantics {
|
||||
type Output = Signal<bool>;
|
||||
type Context = ();
|
||||
|
||||
fn eval(expr: &BoolExpr, trace: &impl Trace, ctx: Self::Context) -> ArgusResult<Self::Output> {
|
||||
match expr {
|
||||
BoolExpr::BoolLit(val) => Ok(Signal::constant(val.0)),
|
||||
BoolExpr::BoolVar(BoolVar { name }) => {
|
||||
trace.get(name.as_str()).cloned().ok_or(ArgusError::SignalNotPresent)
|
||||
}
|
||||
BoolExpr::Cmp(Cmp { op, lhs, rhs }) => {
|
||||
use argus_core::expr::Ordering::*;
|
||||
let lhs = eval_num_expr::<f64>(lhs, trace)?;
|
||||
let rhs = eval_num_expr::<f64>(rhs, trace)?;
|
||||
let ret = match op {
|
||||
Eq => lhs.signal_eq(&rhs),
|
||||
NotEq => lhs.signal_ne(&rhs),
|
||||
Less { strict } if *strict => lhs.signal_lt(&rhs),
|
||||
Less { strict: _ } => lhs.signal_le(&rhs),
|
||||
Greater { strict } if *strict => lhs.signal_gt(&rhs),
|
||||
Greater { strict: _ } => lhs.signal_ge(&rhs),
|
||||
};
|
||||
ret.ok_or(ArgusError::InvalidOperation)
|
||||
}
|
||||
BoolExpr::Not(Not { arg }) => {
|
||||
let arg = Self::eval(arg, trace, ctx)?;
|
||||
Ok(!&arg)
|
||||
}
|
||||
BoolExpr::And(And { args }) => {
|
||||
let mut ret = Signal::constant(true);
|
||||
for arg in args.iter() {
|
||||
let arg = Self::eval(arg, trace, ctx)?;
|
||||
ret = &ret & &arg;
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
BoolExpr::Or(Or { args }) => {
|
||||
let mut ret = Signal::constant(false);
|
||||
for arg in args.iter() {
|
||||
let arg = Self::eval(arg, trace, ctx)?;
|
||||
ret = &ret | &arg;
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
BoolExpr::Next(Next { arg }) => {
|
||||
let arg = Self::eval(arg, trace, ctx)?;
|
||||
compute_next(arg)
|
||||
}
|
||||
BoolExpr::Oracle(Oracle { steps, arg }) => {
|
||||
let arg = Self::eval(arg, trace, ctx)?;
|
||||
compute_oracle(arg, *steps)
|
||||
}
|
||||
BoolExpr::Always(Always { arg, interval }) => {
|
||||
let arg = Self::eval(arg, trace, ctx)?;
|
||||
compute_always(arg, interval)
|
||||
}
|
||||
BoolExpr::Eventually(Eventually { arg, interval }) => {
|
||||
let arg = Self::eval(arg, trace, ctx)?;
|
||||
compute_eventually(arg, interval)
|
||||
}
|
||||
BoolExpr::Until(Until { lhs, rhs, interval }) => {
|
||||
let lhs = Self::eval(lhs, trace, ctx)?;
|
||||
let rhs = Self::eval(rhs, trace, ctx)?;
|
||||
compute_until(lhs, rhs, interval)
|
||||
}
|
||||
impl BooleanSemantics for BoolExpr {
|
||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||
match self {
|
||||
BoolExpr::BoolLit(sig) => BooleanSemantics::eval(sig, trace),
|
||||
BoolExpr::BoolVar(sig) => BooleanSemantics::eval(sig, trace),
|
||||
BoolExpr::Cmp(sig) => BooleanSemantics::eval(sig, trace),
|
||||
BoolExpr::Not(sig) => BooleanSemantics::eval(sig, trace),
|
||||
BoolExpr::And(sig) => BooleanSemantics::eval(sig, trace),
|
||||
BoolExpr::Or(sig) => BooleanSemantics::eval(sig, trace),
|
||||
BoolExpr::Next(sig) => BooleanSemantics::eval(sig, trace),
|
||||
BoolExpr::Oracle(sig) => BooleanSemantics::eval(sig, trace),
|
||||
BoolExpr::Always(sig) => BooleanSemantics::eval(sig, trace),
|
||||
BoolExpr::Eventually(sig) => BooleanSemantics::eval(sig, trace),
|
||||
BoolExpr::Until(sig) => BooleanSemantics::eval(sig, trace),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute next for a signal
|
||||
fn compute_next(signal: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
||||
unimplemented!()
|
||||
impl BooleanSemantics for BoolLit {
|
||||
fn eval(&self, _trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||
Ok(Signal::constant(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute oracle for a signal
|
||||
fn compute_oracle(signal: Signal<bool>, steps: usize) -> ArgusResult<Signal<bool>> {
|
||||
unimplemented!()
|
||||
impl BooleanSemantics for BoolVar {
|
||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||
trace
|
||||
.get(self.name.as_str())
|
||||
.cloned()
|
||||
.ok_or(ArgusError::SignalNotPresent)
|
||||
}
|
||||
}
|
||||
|
||||
impl BooleanSemantics for Cmp {
|
||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||
use argus_core::expr::Ordering::*;
|
||||
|
||||
let lhs = QuantitativeSemantics::eval(self.lhs.as_ref(), trace)?;
|
||||
let rhs = QuantitativeSemantics::eval(self.rhs.as_ref(), trace)?;
|
||||
let ret = match self.op {
|
||||
Eq => lhs.signal_eq(&rhs),
|
||||
NotEq => lhs.signal_ne(&rhs),
|
||||
Less { strict } if *strict => lhs.signal_lt(&rhs),
|
||||
Less { strict: _ } => lhs.signal_le(&rhs),
|
||||
Greater { strict } if *strict => lhs.signal_gt(&rhs),
|
||||
Greater { strict: _ } => lhs.signal_ge(&rhs),
|
||||
};
|
||||
ret.ok_or(ArgusError::InvalidOperation)
|
||||
}
|
||||
}
|
||||
|
||||
impl BooleanSemantics for Not {
|
||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||
let arg = BooleanSemantics::eval(self.arg.as_ref(), trace)?;
|
||||
Ok(arg.not())
|
||||
}
|
||||
}
|
||||
|
||||
impl BooleanSemantics for And {
|
||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||
let mut ret = Signal::constant(true);
|
||||
for arg in self.args.iter() {
|
||||
let arg = Self::eval(arg, trace)?;
|
||||
ret = ret.and(&arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BooleanSemantics for Or {
|
||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||
let mut ret = Signal::constant(true);
|
||||
for arg in self.args.iter() {
|
||||
let arg = Self::eval(arg, trace)?;
|
||||
ret = ret.or(&arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_next(arg: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
||||
match arg {
|
||||
Signal::Empty => Ok(Signal::Empty),
|
||||
sig @ Signal::Constant { value: _ } => {
|
||||
// Just return the signal as is
|
||||
Ok(sig)
|
||||
}
|
||||
Signal::Sampled {
|
||||
mut values,
|
||||
mut time_points,
|
||||
} => {
|
||||
// TODO(anand): Verify this
|
||||
// Just shift the signal by 1 timestamp
|
||||
assert!(values.len() == time_points.len());
|
||||
if values.len() <= 1 {
|
||||
return Ok(Signal::Empty);
|
||||
}
|
||||
values.remove(0);
|
||||
time_points.pop();
|
||||
Ok(Signal::Sampled { values, time_points })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BooleanSemantics for Next {
|
||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||
let arg = BooleanSemantics::eval(self.arg.as_ref(), trace)?;
|
||||
compute_next(arg)
|
||||
}
|
||||
}
|
||||
|
||||
impl BooleanSemantics for Oracle {
|
||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||
if self.steps == 0 {
|
||||
Ok(Signal::Empty)
|
||||
} else {
|
||||
(0..self.steps).try_fold(BooleanSemantics::eval(self.arg.as_ref(), trace)?, |arg, _| {
|
||||
compute_next(arg)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute always for a signal
|
||||
|
|
@ -122,7 +159,8 @@ fn compute_always(signal: Signal<bool>, interval: &Interval) -> ArgusResult<Sign
|
|||
fn compute_timed_always(signal: Signal<bool>, a: Duration, b: Option<Duration>) -> Signal<bool> {
|
||||
match b {
|
||||
Some(b) => {
|
||||
// We want to compute the
|
||||
// We want to compute the windowed min/and of the signal.
|
||||
// The window is dictated by the time duration though.
|
||||
todo!()
|
||||
}
|
||||
None => {
|
||||
|
|
@ -136,11 +174,11 @@ fn compute_timed_always(signal: Signal<bool>, a: Duration, b: Option<Duration>)
|
|||
/// Compute untimed always
|
||||
fn compute_untimed_always(signal: Signal<bool>) -> Signal<bool> {
|
||||
let Signal::Sampled {
|
||||
mut values,
|
||||
time_points,
|
||||
} = signal
|
||||
mut values,
|
||||
time_points,
|
||||
} = signal
|
||||
else {
|
||||
unreachable!("we shouldn't be passing non-sampled signals here")
|
||||
unreachable!("we shouldn't be passing non-sampled signals here")
|
||||
};
|
||||
// Compute the & in a expanding window fashion from the back
|
||||
for i in (0..(time_points.len() - 1)).rev() {
|
||||
|
|
@ -149,6 +187,13 @@ fn compute_untimed_always(signal: Signal<bool>) -> Signal<bool> {
|
|||
Signal::Sampled { values, time_points }
|
||||
}
|
||||
|
||||
impl BooleanSemantics for Always {
|
||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||
let arg = BooleanSemantics::eval(&self.arg, trace)?;
|
||||
compute_always(arg, &self.interval)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute eventually for a signal
|
||||
fn compute_eventually(signal: Signal<bool>, interval: &Interval) -> ArgusResult<Signal<bool>> {
|
||||
if interval.is_empty() || interval.is_singleton() {
|
||||
|
|
@ -180,15 +225,16 @@ fn compute_eventually(signal: Signal<bool>, interval: &Interval) -> ArgusResult<
|
|||
Ok(ret)
|
||||
}
|
||||
|
||||
/// 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>) -> Signal<bool> {
|
||||
match b {
|
||||
Some(b) => {
|
||||
// We want to compute the
|
||||
// We want to compute the windowed max/or of the signal.
|
||||
// The window is dictated by the time duration though.
|
||||
todo!()
|
||||
}
|
||||
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 untimedeventually.
|
||||
let shifted = signal.shift_left(a);
|
||||
compute_untimed_eventually(shifted)
|
||||
}
|
||||
|
|
@ -198,11 +244,11 @@ fn compute_timed_eventually(signal: Signal<bool>, a: Duration, b: Option<Duratio
|
|||
/// Compute untimed eventually
|
||||
fn compute_untimed_eventually(signal: Signal<bool>) -> Signal<bool> {
|
||||
let Signal::Sampled {
|
||||
mut values,
|
||||
time_points,
|
||||
} = signal
|
||||
mut values,
|
||||
time_points,
|
||||
} = signal
|
||||
else {
|
||||
unreachable!("we shouldn't be passing non-sampled signals here")
|
||||
unreachable!("we shouldn't be passing non-sampled signals here")
|
||||
};
|
||||
// Compute the | in a expanding window fashion from the back
|
||||
for i in (0..(time_points.len() - 1)).rev() {
|
||||
|
|
@ -211,6 +257,13 @@ fn compute_untimed_eventually(signal: Signal<bool>) -> Signal<bool> {
|
|||
Signal::Sampled { values, time_points }
|
||||
}
|
||||
|
||||
impl BooleanSemantics for Eventually {
|
||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||
let arg = BooleanSemantics::eval(&self.arg, trace)?;
|
||||
compute_eventually(arg, &self.interval)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute until
|
||||
fn compute_until(lhs: Signal<bool>, rhs: Signal<bool>, interval: &Interval) -> ArgusResult<Signal<bool>> {
|
||||
let ret = match (lhs, rhs) {
|
||||
|
|
@ -255,7 +308,6 @@ fn compute_until(lhs: Signal<bool>, rhs: Signal<bool>, interval: &Interval) -> A
|
|||
/// Compute timed until for the interval `[a, b]` (or, if `b` is `None`, `[a, ..]`.
|
||||
fn compute_timed_until(lhs: Signal<bool>, rhs: Signal<bool>, a: Duration, b: Option<Duration>) -> Signal<bool> {
|
||||
// For this, we will perform the Until rewrite defined in [1]:
|
||||
//
|
||||
// $$
|
||||
// \varphi_1 U_{[a, b]} \varphi_2 = F_{[a,b]} \varphi_2 \land (\varphi_1 U_{[a,
|
||||
// \infty)} \varphi_2)
|
||||
|
|
@ -265,9 +317,7 @@ fn compute_timed_until(lhs: Signal<bool>, rhs: Signal<bool>, a: Duration, b: Opt
|
|||
// \varphi_1 U_{[a, \infty)} \varphi_2 = G_{[0,a]} (\varphi_1 U \varphi_2)
|
||||
// $$
|
||||
//
|
||||
//
|
||||
// [1] A. Donzé, T. Ferrère, and O. Maler, "Efficient Robust Monitoring for STL." doi:
|
||||
// 10.1007/978-3-642-39799-8_19.
|
||||
// [1] A. Donzé, T. Ferrère, and O. Maler, "Efficient Robust Monitoring for STL."
|
||||
|
||||
match b {
|
||||
Some(b) => {
|
||||
|
|
@ -292,3 +342,9 @@ fn compute_untimed_until(lhs: Signal<bool>, rhs: Signal<bool>) -> Signal<bool> {
|
|||
let sync_points = lhs.sync_with_intersection(&rhs);
|
||||
todo!()
|
||||
}
|
||||
|
||||
impl BooleanSemantics for Until {
|
||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
pub mod boolean;
|
||||
pub mod quantitative;
|
||||
// pub mod quantitative;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use argus_core::signals::traits::{SignalAbs, SignalMinMax};
|
|||
use argus_core::signals::SignalNumCast;
|
||||
|
||||
use crate::eval::eval_num_expr;
|
||||
use crate::{Semantics, Trace};
|
||||
use crate::Trace;
|
||||
|
||||
fn top_or_bot(sig: &Signal<bool>) -> Signal<f64> {
|
||||
match sig {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue