feat(argus-semantics): implement efficient streaming MinMax

This commit is contained in:
Anand Balakrishnan 2023-08-28 13:02:51 -07:00
parent 4084bb738b
commit 86cef692dc
No known key found for this signature in database
8 changed files with 353 additions and 162 deletions

View file

@ -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!()
}
}

View file

@ -1,2 +1,2 @@
pub mod boolean;
pub mod quantitative;
// pub mod quantitative;

View file

@ -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 {