feat(argus-semantics): finish quantitative semantics
The Boolean semantics are still incomplete. The decision to keep the computations separate stays, as using the quantitative semantics for Boolean values (while sound) interpolates in weird places. May revisit this decision in the future
This commit is contained in:
parent
28a79cb88c
commit
ad9afb4eba
7 changed files with 858 additions and 337 deletions
|
|
@ -1,61 +0,0 @@
|
||||||
use argus_core::expr::*;
|
|
||||||
use argus_core::signals::traits::{SignalAbs, TrySignalCast};
|
|
||||||
use argus_core::signals::Signal;
|
|
||||||
use argus_core::ArgusResult;
|
|
||||||
use num_traits::{Num, NumCast};
|
|
||||||
|
|
||||||
use crate::Trace;
|
|
||||||
|
|
||||||
pub fn eval_num_expr<T>(root: &NumExpr, trace: &impl Trace) -> ArgusResult<Signal<T>>
|
|
||||||
where
|
|
||||||
T: Num + NumCast,
|
|
||||||
Signal<i64>: TrySignalCast<Signal<T>>,
|
|
||||||
Signal<u64>: TrySignalCast<Signal<T>>,
|
|
||||||
Signal<f64>: TrySignalCast<Signal<T>>,
|
|
||||||
for<'a> &'a Signal<T>: std::ops::Neg<Output = Signal<T>>,
|
|
||||||
for<'a> &'a Signal<T>: std::ops::Add<&'a Signal<T>, Output = Signal<T>>,
|
|
||||||
for<'a> &'a Signal<T>: std::ops::Sub<&'a Signal<T>, Output = Signal<T>>,
|
|
||||||
for<'a> &'a Signal<T>: std::ops::Mul<&'a Signal<T>, Output = Signal<T>>,
|
|
||||||
for<'a> &'a Signal<T>: std::ops::Div<&'a Signal<T>, Output = Signal<T>>,
|
|
||||||
Signal<T>: SignalAbs,
|
|
||||||
{
|
|
||||||
match root {
|
|
||||||
NumExpr::IntLit(val) => Signal::constant(val.0).try_cast(),
|
|
||||||
NumExpr::UIntLit(val) => Signal::constant(val.0).try_cast(),
|
|
||||||
NumExpr::FloatLit(val) => Signal::constant(val.0).try_cast(),
|
|
||||||
NumExpr::IntVar(IntVar { name }) => trace.get::<i64>(name.as_str()).unwrap().try_cast(),
|
|
||||||
NumExpr::UIntVar(UIntVar { name }) => trace.get::<u64>(name.as_str()).unwrap().try_cast(),
|
|
||||||
NumExpr::FloatVar(FloatVar { name }) => trace.get::<f64>(name.as_str()).unwrap().try_cast(),
|
|
||||||
NumExpr::Neg(Neg { arg }) => eval_num_expr(arg, trace).map(|sig| -&sig),
|
|
||||||
NumExpr::Add(Add { args }) => {
|
|
||||||
let mut ret: Signal<T> = Signal::<T>::zero();
|
|
||||||
for arg in args.iter() {
|
|
||||||
let arg = eval_num_expr(arg, trace)?;
|
|
||||||
ret = &ret + &arg;
|
|
||||||
}
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
NumExpr::Sub(Sub { lhs, rhs }) => {
|
|
||||||
let lhs = eval_num_expr(lhs, trace)?;
|
|
||||||
let rhs = eval_num_expr(rhs, trace)?;
|
|
||||||
Ok(&lhs - &rhs)
|
|
||||||
}
|
|
||||||
NumExpr::Mul(Mul { args }) => {
|
|
||||||
let mut ret: Signal<T> = Signal::<T>::one();
|
|
||||||
for arg in args.iter() {
|
|
||||||
let arg = eval_num_expr(arg, trace)?;
|
|
||||||
ret = &ret * &arg;
|
|
||||||
}
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
NumExpr::Div(Div { dividend, divisor }) => {
|
|
||||||
let dividend = eval_num_expr(dividend, trace)?;
|
|
||||||
let divisor = eval_num_expr(divisor, trace)?;
|
|
||||||
Ok(÷nd / &divisor)
|
|
||||||
}
|
|
||||||
NumExpr::Abs(Abs { arg }) => {
|
|
||||||
let arg = eval_num_expr(arg, trace)?;
|
|
||||||
Ok(arg.abs())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,9 +4,8 @@
|
||||||
//! traces_, i.e., a collection of signals that have been extracted from observing and
|
//! traces_, i.e., a collection of signals that have been extracted from observing and
|
||||||
//! sampling from some system.
|
//! sampling from some system.
|
||||||
|
|
||||||
// pub mod eval;
|
pub mod semantics;
|
||||||
// pub mod semantics;
|
pub mod traits;
|
||||||
// pub mod traits;
|
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
// pub use traits::{BooleanSemantics, QuantitativeSemantics, Trace};
|
pub use traits::Trace;
|
||||||
|
|
|
||||||
|
|
@ -3,88 +3,93 @@ 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::SignalPartialOrd;
|
||||||
|
|
||||||
use crate::traits::{BooleanSemantics, QuantitativeSemantics, Trace};
|
use crate::semantics::QuantitativeSemantics;
|
||||||
|
use crate::traits::Trace;
|
||||||
|
use crate::utils::lemire_minmax::MonoWedge;
|
||||||
|
|
||||||
impl BooleanSemantics for BoolExpr {
|
pub struct BooleanSemantics;
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BooleanSemantics for BoolLit {
|
impl BooleanSemantics {
|
||||||
fn eval(&self, _trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
pub fn eval(expr: &BoolExpr, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
||||||
Ok(Signal::constant(self.0))
|
let ret = match expr {
|
||||||
}
|
BoolExpr::BoolLit(val) => Signal::constant(val.0),
|
||||||
}
|
BoolExpr::BoolVar(BoolVar { name }) => trace
|
||||||
|
.get::<bool>(name.as_str())
|
||||||
impl BooleanSemantics for BoolVar {
|
.ok_or(ArgusError::SignalNotPresent)?
|
||||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
.clone(),
|
||||||
trace
|
BoolExpr::Cmp(Cmp { op, lhs, rhs }) => {
|
||||||
.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::*;
|
use argus_core::expr::Ordering::*;
|
||||||
|
let lhs = QuantitativeSemantics::eval_num_expr::<f64>(lhs, trace)?;
|
||||||
|
let rhs = QuantitativeSemantics::eval_num_expr::<f64>(rhs, trace)?;
|
||||||
|
|
||||||
let lhs = QuantitativeSemantics::eval(self.lhs.as_ref(), trace)?;
|
match op {
|
||||||
let rhs = QuantitativeSemantics::eval(self.rhs.as_ref(), trace)?;
|
Eq => lhs.signal_eq(&rhs).unwrap(),
|
||||||
let ret = match self.op {
|
NotEq => lhs.signal_ne(&rhs).unwrap(),
|
||||||
Eq => lhs.signal_eq(&rhs),
|
Less { strict } if *strict => lhs.signal_lt(&rhs).unwrap(),
|
||||||
NotEq => lhs.signal_ne(&rhs),
|
Less { strict: _ } => lhs.signal_le(&rhs).unwrap(),
|
||||||
Less { strict } if *strict => lhs.signal_lt(&rhs),
|
Greater { strict } if *strict => lhs.signal_gt(&rhs).unwrap(),
|
||||||
Less { strict: _ } => lhs.signal_le(&rhs),
|
Greater { strict: _ } => lhs.signal_ge(&rhs).unwrap(),
|
||||||
Greater { strict } if *strict => lhs.signal_gt(&rhs),
|
}
|
||||||
Greater { strict: _ } => lhs.signal_ge(&rhs),
|
}
|
||||||
|
BoolExpr::Not(Not { arg }) => {
|
||||||
|
let arg = Self::eval(arg, trace)?;
|
||||||
|
!&arg
|
||||||
|
}
|
||||||
|
BoolExpr::And(And { args }) => {
|
||||||
|
assert!(args.len() >= 2);
|
||||||
|
args.iter()
|
||||||
|
.map(|arg| Self::eval(arg, trace))
|
||||||
|
.try_fold(Signal::const_true(), |acc, item| {
|
||||||
|
let item = item?;
|
||||||
|
Ok(acc.and(&item))
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
BoolExpr::Or(Or { args }) => {
|
||||||
|
assert!(args.len() >= 2);
|
||||||
|
args.iter()
|
||||||
|
.map(|arg| Self::eval(arg, trace))
|
||||||
|
.try_fold(Signal::const_true(), |acc, item| {
|
||||||
|
let item = item?;
|
||||||
|
Ok(acc.or(&item))
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
BoolExpr::Next(Next { arg }) => {
|
||||||
|
let arg = Self::eval(arg, trace)?;
|
||||||
|
compute_next(arg)?
|
||||||
|
}
|
||||||
|
BoolExpr::Oracle(Oracle { steps, arg }) => {
|
||||||
|
let arg = Self::eval(arg, trace)?;
|
||||||
|
compute_oracle(arg, *steps)?
|
||||||
|
}
|
||||||
|
BoolExpr::Always(Always { arg, interval }) => {
|
||||||
|
let arg = Self::eval(arg, trace)?;
|
||||||
|
compute_always(arg, interval)?
|
||||||
|
}
|
||||||
|
BoolExpr::Eventually(Eventually { arg, interval }) => {
|
||||||
|
let arg = Self::eval(arg, trace)?;
|
||||||
|
compute_eventually(arg, interval)?
|
||||||
|
}
|
||||||
|
BoolExpr::Until(Until { lhs, rhs, interval }) => {
|
||||||
|
let lhs = Self::eval(lhs, trace)?;
|
||||||
|
let rhs = Self::eval(rhs, trace)?;
|
||||||
|
compute_until(lhs, rhs, interval)?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
ret.ok_or(ArgusError::InvalidOperation)
|
Ok(ret)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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>> {
|
fn compute_next(arg: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
||||||
|
compute_oracle(arg, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_oracle(arg: Signal<bool>, steps: usize) -> ArgusResult<Signal<bool>> {
|
||||||
|
if steps == 0 {
|
||||||
|
return Ok(Signal::Empty);
|
||||||
|
}
|
||||||
match arg {
|
match arg {
|
||||||
Signal::Empty => Ok(Signal::Empty),
|
Signal::Empty => Ok(Signal::Empty),
|
||||||
sig @ Signal::Constant { value: _ } => {
|
sig @ Signal::Constant { value: _ } => {
|
||||||
|
|
@ -96,37 +101,22 @@ fn compute_next(arg: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
||||||
mut time_points,
|
mut time_points,
|
||||||
} => {
|
} => {
|
||||||
// TODO(anand): Verify this
|
// TODO(anand): Verify this
|
||||||
// Just shift the signal by 1 timestamp
|
// Just shift the signal by `steps` timestamps
|
||||||
assert!(values.len() == time_points.len());
|
assert_eq!(values.len(), time_points.len());
|
||||||
if values.len() <= 1 {
|
if values.len() <= steps {
|
||||||
return Ok(Signal::Empty);
|
return Ok(Signal::Empty);
|
||||||
}
|
}
|
||||||
values.remove(0);
|
let expected_len = values.len() - steps;
|
||||||
time_points.pop();
|
let values = values.split_off(steps);
|
||||||
|
let _ = time_points.split_off(steps);
|
||||||
|
|
||||||
|
assert_eq!(values.len(), expected_len);
|
||||||
|
assert_eq!(values.len(), time_points.len());
|
||||||
Ok(Signal::Sampled { values, time_points })
|
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
|
/// Compute always for a signal
|
||||||
fn compute_always(signal: Signal<bool>, interval: &Interval) -> ArgusResult<Signal<bool>> {
|
fn compute_always(signal: Signal<bool>, interval: &Interval) -> ArgusResult<Signal<bool>> {
|
||||||
if interval.is_empty() || interval.is_singleton() {
|
if interval.is_empty() || interval.is_singleton() {
|
||||||
|
|
@ -134,45 +124,20 @@ fn compute_always(signal: Signal<bool>, interval: &Interval) -> ArgusResult<Sign
|
||||||
reason: "interval is either empty or singleton",
|
reason: "interval is either empty or singleton",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let ret = match signal {
|
let z1 = !signal;
|
||||||
// if signal is empty or constant, return the signal itself.
|
let z2 = compute_eventually(z1, interval)?;
|
||||||
// This works because if a signal is True everythere, then it must
|
Ok(!z2)
|
||||||
// "always be true".
|
|
||||||
sig @ (Signal::Empty | Signal::Constant { value: _ }) => sig,
|
|
||||||
sig => {
|
|
||||||
use Bound::*;
|
|
||||||
if interval.is_untimed() {
|
|
||||||
compute_untimed_always(sig)
|
|
||||||
} else if let (Included(a), Included(b)) = interval.into() {
|
|
||||||
compute_timed_always(sig, *a, Some(*b))
|
|
||||||
} else if let (Included(a), Unbounded) = interval.into() {
|
|
||||||
compute_timed_always(sig, *a, None)
|
|
||||||
} else {
|
|
||||||
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(ret)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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>) -> Signal<bool> {
|
fn compute_timed_always(signal: Signal<bool>, a: Duration, b: Option<Duration>) -> ArgusResult<Signal<bool>> {
|
||||||
match b {
|
let z1 = !signal;
|
||||||
Some(b) => {
|
let z2 = compute_timed_eventually(z1, a, b)?;
|
||||||
// We want to compute the windowed min/and of the signal.
|
Ok(!z2)
|
||||||
// The window is dictated by the time duration though.
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// Shift the signal to the left by `a` and then run the untimed always.
|
|
||||||
let shifted = signal.shift_left(a);
|
|
||||||
compute_untimed_always(shifted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute untimed always
|
/// Compute untimed always
|
||||||
fn compute_untimed_always(signal: Signal<bool>) -> Signal<bool> {
|
fn compute_untimed_always(signal: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
||||||
let Signal::Sampled {
|
let Signal::Sampled {
|
||||||
mut values,
|
mut values,
|
||||||
time_points,
|
time_points,
|
||||||
|
|
@ -184,14 +149,7 @@ fn compute_untimed_always(signal: Signal<bool>) -> Signal<bool> {
|
||||||
for i in (0..(time_points.len() - 1)).rev() {
|
for i in (0..(time_points.len() - 1)).rev() {
|
||||||
values[i] &= values[i + 1];
|
values[i] &= values[i + 1];
|
||||||
}
|
}
|
||||||
Signal::Sampled { values, time_points }
|
Ok(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
|
/// Compute eventually for a signal
|
||||||
|
|
@ -212,11 +170,11 @@ fn compute_eventually(signal: Signal<bool>, interval: &Interval) -> ArgusResult<
|
||||||
// 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(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(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(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")
|
||||||
}
|
}
|
||||||
|
|
@ -226,12 +184,39 @@ 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>) -> Signal<bool> {
|
fn compute_timed_eventually(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.
|
||||||
// The window is dictated by the time duration though.
|
// The window is dictated by the time duration though.
|
||||||
todo!()
|
let Signal::Sampled { values, time_points } = signal else {
|
||||||
|
unreachable!("we shouldn't be passing non-sampled signals here")
|
||||||
|
};
|
||||||
|
assert!(b > a);
|
||||||
|
assert!(!time_points.is_empty());
|
||||||
|
let signal_duration = *time_points.last().unwrap() - *time_points.first().unwrap();
|
||||||
|
let width = if signal_duration < (b - a) {
|
||||||
|
signal_duration
|
||||||
|
} else {
|
||||||
|
b - a
|
||||||
|
};
|
||||||
|
let mut ret_vals = Vec::with_capacity(values.len());
|
||||||
|
|
||||||
|
// For boolean signals we dont need to worry about intersections with ZERO as much as
|
||||||
|
// for quantitative signals, as linear interpolation is just a discrte switch.
|
||||||
|
let mut wedge = MonoWedge::<bool>::max_wedge(width);
|
||||||
|
for (i, value) in time_points.iter().zip(&values) {
|
||||||
|
wedge.update((i, value));
|
||||||
|
if i >= &(time_points[0] + width) {
|
||||||
|
ret_vals.push(
|
||||||
|
wedge
|
||||||
|
.front()
|
||||||
|
.map(|(&t, &v)| (t, v))
|
||||||
|
.unwrap_or_else(|| panic!("wedge should have at least 1 element")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Signal::try_from_iter(ret_vals.into_iter())
|
||||||
}
|
}
|
||||||
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.
|
||||||
|
|
@ -242,7 +227,7 @@ fn compute_timed_eventually(signal: Signal<bool>, a: Duration, b: Option<Duratio
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute untimed eventually
|
/// Compute untimed eventually
|
||||||
fn compute_untimed_eventually(signal: Signal<bool>) -> Signal<bool> {
|
fn compute_untimed_eventually(signal: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
||||||
let Signal::Sampled {
|
let Signal::Sampled {
|
||||||
mut values,
|
mut values,
|
||||||
time_points,
|
time_points,
|
||||||
|
|
@ -254,14 +239,7 @@ fn compute_untimed_eventually(signal: Signal<bool>) -> Signal<bool> {
|
||||||
for i in (0..(time_points.len() - 1)).rev() {
|
for i in (0..(time_points.len() - 1)).rev() {
|
||||||
values[i] |= values[i + 1];
|
values[i] |= values[i + 1];
|
||||||
}
|
}
|
||||||
Signal::Sampled { values, time_points }
|
Ok(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
|
/// Compute until
|
||||||
|
|
@ -292,11 +270,11 @@ fn compute_until(lhs: Signal<bool>, rhs: Signal<bool>, interval: &Interval) -> A
|
||||||
) => {
|
) => {
|
||||||
use Bound::*;
|
use Bound::*;
|
||||||
if interval.is_untimed() {
|
if interval.is_untimed() {
|
||||||
compute_untimed_until(lhs, rhs)
|
compute_untimed_until(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(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(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")
|
||||||
}
|
}
|
||||||
|
|
@ -306,31 +284,36 @@ 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, ..]`.
|
/// 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]:
|
/// 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,
|
/// \varphi_1 U_{[a, b]} \varphi_2 = F_{[a,b]} \varphi_2 \land (\varphi_1 U_{[a,
|
||||||
// \infty)} \varphi_2)
|
/// \infty)} \varphi_2)
|
||||||
// $$
|
/// $$
|
||||||
//
|
///
|
||||||
// $$
|
/// $$
|
||||||
// \varphi_1 U_{[a, \infty)} \varphi_2 = G_{[0,a]} (\varphi_1 U \varphi_2)
|
/// \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."
|
/// [1]: <> (A. Donzé, T. Ferrère, and O. Maler, "Efficient Robust Monitoring for STL.")
|
||||||
|
fn compute_timed_until(
|
||||||
|
lhs: Signal<bool>,
|
||||||
|
rhs: Signal<bool>,
|
||||||
|
a: Duration,
|
||||||
|
b: Option<Duration>,
|
||||||
|
) -> ArgusResult<Signal<bool>> {
|
||||||
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, a, Some(b));
|
let ev_a_b_rhs = compute_timed_eventually(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(lhs, rhs, a, None)?;
|
||||||
// Then & them
|
// Then & them
|
||||||
&ev_a_b_rhs & &unt_a_inf
|
Ok(&ev_a_b_rhs & &unt_a_inf)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// 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(lhs, rhs)?;
|
||||||
// Compute G [0, a]
|
// Compute G [0, a]
|
||||||
compute_untimed_always(untimed_until)
|
compute_untimed_always(untimed_until)
|
||||||
}
|
}
|
||||||
|
|
@ -338,13 +321,116 @@ fn compute_timed_until(lhs: Signal<bool>, rhs: Signal<bool>, a: Duration, b: Opt
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute untimed until
|
/// Compute untimed until
|
||||||
fn compute_untimed_until(lhs: Signal<bool>, rhs: Signal<bool>) -> Signal<bool> {
|
fn compute_untimed_until(lhs: Signal<bool>, rhs: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
||||||
let sync_points = lhs.sync_with_intersection(&rhs);
|
let sync_points = lhs.sync_with_intersection::<Linear>(&rhs);
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BooleanSemantics for Until {
|
#[cfg(test)]
|
||||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>> {
|
mod tests {
|
||||||
todo!()
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use argus_core::expr::ExprBuilder;
|
||||||
|
use argus_core::signals::AnySignal;
|
||||||
|
use itertools::assert_equal;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MyTrace {
|
||||||
|
signals: HashMap<String, Box<dyn AnySignal>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Trace for MyTrace {
|
||||||
|
fn signal_names(&self) -> Vec<&str> {
|
||||||
|
self.signals.keys().map(|key| key.as_str()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get<T: 'static>(&self, name: &str) -> Option<&Signal<T>> {
|
||||||
|
let signal = self.signals.get(name)?;
|
||||||
|
signal.as_any().downcast_ref::<Signal<T>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn less_than() {
|
||||||
|
let mut ctx = ExprBuilder::new();
|
||||||
|
|
||||||
|
let a = ctx.float_var("a".to_owned()).unwrap();
|
||||||
|
let spec = ctx.make_lt(a, ctx.float_const(0.0));
|
||||||
|
|
||||||
|
let signals = HashMap::from_iter(vec![(
|
||||||
|
"a".to_owned(),
|
||||||
|
Box::new(Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 1.3),
|
||||||
|
(Duration::from_secs_f64(0.7), 3.0),
|
||||||
|
(Duration::from_secs_f64(1.3), 0.1),
|
||||||
|
(Duration::from_secs_f64(2.1), -2.2),
|
||||||
|
])) as Box<dyn AnySignal>,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let trace = MyTrace { signals };
|
||||||
|
|
||||||
|
let rob = BooleanSemantics::eval(&spec, &trace).unwrap();
|
||||||
|
let expected = Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), false),
|
||||||
|
(Duration::from_secs_f64(0.7), false),
|
||||||
|
(Duration::from_secs_f64(1.3), false),
|
||||||
|
(Duration::from_secs_f64(1.334782609), true), // interpolated at
|
||||||
|
(Duration::from_secs_f64(2.1), true),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_equal(&rob, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eventually_unbounded() {
|
||||||
|
let mut ctx = ExprBuilder::new();
|
||||||
|
|
||||||
|
let a = ctx.float_var("a".to_owned()).unwrap();
|
||||||
|
let cmp = ctx.make_ge(a, ctx.float_const(0.0));
|
||||||
|
let spec = ctx.make_eventually(cmp);
|
||||||
|
|
||||||
|
{
|
||||||
|
let signals = HashMap::from_iter(vec![(
|
||||||
|
"a".to_owned(),
|
||||||
|
Box::new(Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 2.5),
|
||||||
|
(Duration::from_secs_f64(0.7), 4.0),
|
||||||
|
(Duration::from_secs_f64(1.3), -1.0),
|
||||||
|
(Duration::from_secs_f64(2.1), 1.7),
|
||||||
|
])) as Box<dyn AnySignal>,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let trace = MyTrace { signals };
|
||||||
|
let rob = BooleanSemantics::eval(&spec, &trace).unwrap();
|
||||||
|
|
||||||
|
let Signal::Sampled { values, time_points: _ } = rob else {
|
||||||
|
panic!("boolean semantics should remain sampled");
|
||||||
|
};
|
||||||
|
assert!(values.into_iter().all(|v| v));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let signals = HashMap::from_iter(vec![(
|
||||||
|
"a".to_owned(),
|
||||||
|
Box::new(Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 2.5),
|
||||||
|
(Duration::from_secs_f64(0.7), 4.0),
|
||||||
|
(Duration::from_secs_f64(1.3), 1.7),
|
||||||
|
(Duration::from_secs_f64(1.4), 0.0),
|
||||||
|
(Duration::from_secs_f64(2.1), -2.0),
|
||||||
|
])) as Box<dyn AnySignal>,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let trace = MyTrace { signals };
|
||||||
|
let rob = BooleanSemantics::eval(&spec, &trace).unwrap();
|
||||||
|
println!("{:#?}", rob);
|
||||||
|
|
||||||
|
let Signal::Sampled { values, time_points: _ } = rob else {
|
||||||
|
panic!("boolean semantics should remain sampled");
|
||||||
|
};
|
||||||
|
assert!(values[..values.len() - 1].iter().all(|&v| v));
|
||||||
|
assert!(!values[values.len() - 1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
pub mod boolean;
|
mod boolean;
|
||||||
// pub mod quantitative;
|
mod quantitative;
|
||||||
|
|
||||||
|
pub use boolean::BooleanSemantics;
|
||||||
|
pub use quantitative::QuantitativeSemantics;
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,29 @@
|
||||||
use std::iter::zip;
|
use std::ops::Bound;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use argus_core::expr::{Always, And, BoolExpr, BoolVar, Cmp, Eventually, Next, Not, Or, Oracle, Until};
|
use argus_core::expr::*;
|
||||||
use argus_core::prelude::*;
|
use argus_core::prelude::*;
|
||||||
use argus_core::signals::traits::{SignalAbs, SignalMinMax};
|
use argus_core::signals::interpolation::Linear;
|
||||||
use argus_core::signals::SignalNumCast;
|
use argus_core::signals::SignalAbs;
|
||||||
|
use num_traits::{Num, NumCast};
|
||||||
|
|
||||||
use crate::eval::eval_num_expr;
|
use crate::traits::Trace;
|
||||||
use crate::Trace;
|
use crate::utils::lemire_minmax::MonoWedge;
|
||||||
|
|
||||||
fn top_or_bot(sig: &Signal<bool>) -> Signal<f64> {
|
|
||||||
match sig {
|
|
||||||
Signal::Empty => Signal::Empty,
|
|
||||||
Signal::Constant { value } => Signal::constant(*value).to_f64().unwrap(),
|
|
||||||
Signal::Sampled { values, time_points } => zip(time_points, values)
|
|
||||||
.map(|(&t, &v)| if v { (t, f64::INFINITY) } else { (t, f64::NEG_INFINITY) })
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Quantitative semantics for Argus expressions
|
|
||||||
pub struct QuantitativeSemantics;
|
pub struct QuantitativeSemantics;
|
||||||
|
|
||||||
impl Semantics for QuantitativeSemantics {
|
impl QuantitativeSemantics {
|
||||||
type Output = Signal<f64>;
|
pub fn eval(expr: &BoolExpr, trace: &impl Trace) -> ArgusResult<Signal<f64>> {
|
||||||
type Context = ();
|
let ret = match expr {
|
||||||
|
|
||||||
fn eval(expr: &BoolExpr, trace: &impl Trace, ctx: Self::Context) -> ArgusResult<Self::Output> {
|
|
||||||
let ret: Self::Output = 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 }) => {
|
BoolExpr::BoolVar(BoolVar { name }) => trace
|
||||||
let sig = trace.get::<bool>(name.as_str()).ok_or(ArgusError::SignalNotPresent)?;
|
.get::<bool>(name.as_str())
|
||||||
top_or_bot(sig)
|
.ok_or(ArgusError::SignalNotPresent)
|
||||||
}
|
.map(top_or_bot)?,
|
||||||
BoolExpr::Cmp(Cmp { op, lhs, rhs }) => {
|
BoolExpr::Cmp(Cmp { op, lhs, rhs }) => {
|
||||||
use argus_core::expr::Ordering::*;
|
use argus_core::expr::Ordering::*;
|
||||||
let lhs = eval_num_expr::<f64>(lhs, trace)?;
|
let lhs = Self::eval_num_expr::<f64>(lhs, trace)?;
|
||||||
let rhs = eval_num_expr::<f64>(rhs, trace)?;
|
let rhs = Self::eval_num_expr::<f64>(rhs, trace)?;
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
Eq => -&((&lhs - &rhs).abs()),
|
Eq => -&((&lhs - &rhs).abs()),
|
||||||
|
|
@ -45,69 +33,584 @@ impl Semantics for QuantitativeSemantics {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BoolExpr::Not(Not { arg }) => {
|
BoolExpr::Not(Not { arg }) => {
|
||||||
let arg = Self::eval(arg, trace, ctx)?;
|
let arg = Self::eval(arg, trace)?;
|
||||||
-&arg
|
-&arg
|
||||||
}
|
}
|
||||||
BoolExpr::And(And { args }) => {
|
BoolExpr::And(And { args }) => {
|
||||||
assert!(args.len() >= 2);
|
assert!(args.len() >= 2);
|
||||||
let args = args
|
args.iter().map(|arg| Self::eval(arg, trace)).try_fold(
|
||||||
.iter()
|
Signal::constant(f64::INFINITY),
|
||||||
.map(|arg| Self::eval(arg, trace, ctx))
|
|acc, item| {
|
||||||
.collect::<ArgusResult<Vec<_>>>()?;
|
let item = item?;
|
||||||
args.into_iter()
|
Ok(acc.min(&item))
|
||||||
.reduce(|lhs, rhs| lhs.min(&rhs))
|
},
|
||||||
.ok_or(ArgusError::InvalidOperation)?
|
)?
|
||||||
}
|
}
|
||||||
BoolExpr::Or(Or { args }) => {
|
BoolExpr::Or(Or { args }) => {
|
||||||
assert!(args.len() >= 2);
|
assert!(args.len() >= 2);
|
||||||
let args = args
|
args.iter().map(|arg| Self::eval(arg, trace)).try_fold(
|
||||||
.iter()
|
Signal::constant(f64::NEG_INFINITY),
|
||||||
.map(|arg| Self::eval(arg, trace, ctx))
|
|acc, item| {
|
||||||
.collect::<ArgusResult<Vec<_>>>()?;
|
let item = item?;
|
||||||
args.into_iter()
|
Ok(acc.max(&item))
|
||||||
.reduce(|lhs, rhs| lhs.max(&rhs))
|
},
|
||||||
.ok_or(ArgusError::InvalidOperation)?
|
)?
|
||||||
}
|
}
|
||||||
BoolExpr::Next(Next { arg: _ }) => todo!(),
|
BoolExpr::Next(Next { arg }) => {
|
||||||
BoolExpr::Oracle(Oracle { steps: _, arg: _ }) => todo!(),
|
let arg = Self::eval(arg, trace)?;
|
||||||
BoolExpr::Always(Always { arg, interval: _ }) => {
|
compute_next(arg)?
|
||||||
let mut arg = Self::eval(arg, trace, ctx)?;
|
|
||||||
match &mut arg {
|
|
||||||
// if signal is empty or constant, return the signal itself.
|
|
||||||
// This works because if a signal is positive everywhere, then it must
|
|
||||||
// "always be positive" (and vice versa).
|
|
||||||
Signal::Empty | Signal::Constant { value: _ } => (),
|
|
||||||
Signal::Sampled { values, time_points } => {
|
|
||||||
// Compute the min in a expanding window fashion from the back
|
|
||||||
for i in (0..(time_points.len() - 1)).rev() {
|
|
||||||
values[i] = values[i].min(values[i + 1]);
|
|
||||||
}
|
}
|
||||||
|
BoolExpr::Oracle(Oracle { steps, arg }) => {
|
||||||
|
let arg = Self::eval(arg, trace)?;
|
||||||
|
compute_oracle(arg, *steps)?
|
||||||
}
|
}
|
||||||
|
BoolExpr::Always(Always { arg, interval }) => {
|
||||||
|
let arg = Self::eval(arg, trace)?;
|
||||||
|
compute_always(arg, interval)?
|
||||||
}
|
}
|
||||||
arg
|
BoolExpr::Eventually(Eventually { arg, interval }) => {
|
||||||
|
let arg = Self::eval(arg, trace)?;
|
||||||
|
compute_eventually(arg, interval)?
|
||||||
}
|
}
|
||||||
BoolExpr::Eventually(Eventually { arg, interval: _ }) => {
|
BoolExpr::Until(Until { lhs, rhs, interval }) => {
|
||||||
let mut arg = Self::eval(arg, trace, ctx)?;
|
let lhs = Self::eval(lhs, trace)?;
|
||||||
match &mut arg {
|
let rhs = Self::eval(rhs, trace)?;
|
||||||
// if signal is empty or constant, return the signal itself.
|
compute_until(lhs, rhs, interval)?
|
||||||
// This works because if a signal is positive somewhere, then it must
|
|
||||||
// "eventually be positive" (and vice versa).
|
|
||||||
Signal::Empty | Signal::Constant { value: _ } => (),
|
|
||||||
Signal::Sampled { values, time_points } => {
|
|
||||||
// Compute the max in a expanding window fashion from the back
|
|
||||||
for i in (0..(time_points.len() - 1)).rev() {
|
|
||||||
values[i] = values[i].max(values[i + 1]);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
arg
|
|
||||||
}
|
|
||||||
BoolExpr::Until(Until {
|
|
||||||
lhs: _,
|
|
||||||
rhs: _,
|
|
||||||
interval: _,
|
|
||||||
}) => todo!(),
|
|
||||||
};
|
};
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn eval_num_expr<T>(root: &NumExpr, trace: &impl Trace) -> ArgusResult<Signal<T>>
|
||||||
|
where
|
||||||
|
T: Num + NumCast,
|
||||||
|
for<'a> &'a Signal<T>: std::ops::Neg<Output = Signal<T>>,
|
||||||
|
for<'a> &'a Signal<T>: std::ops::Add<&'a Signal<T>, Output = Signal<T>>,
|
||||||
|
for<'a> &'a Signal<T>: std::ops::Sub<&'a Signal<T>, Output = Signal<T>>,
|
||||||
|
for<'a> &'a Signal<T>: std::ops::Mul<&'a Signal<T>, Output = Signal<T>>,
|
||||||
|
for<'a> &'a Signal<T>: std::ops::Div<&'a Signal<T>, Output = Signal<T>>,
|
||||||
|
Signal<T>: SignalAbs,
|
||||||
|
{
|
||||||
|
match root {
|
||||||
|
NumExpr::IntLit(val) => Signal::constant(val.0).num_cast(),
|
||||||
|
NumExpr::UIntLit(val) => Signal::constant(val.0).num_cast(),
|
||||||
|
NumExpr::FloatLit(val) => Signal::constant(val.0).num_cast(),
|
||||||
|
NumExpr::IntVar(IntVar { name }) => trace.get::<i64>(name.as_str()).unwrap().num_cast(),
|
||||||
|
NumExpr::UIntVar(UIntVar { name }) => trace.get::<u64>(name.as_str()).unwrap().num_cast(),
|
||||||
|
NumExpr::FloatVar(FloatVar { name }) => trace.get::<f64>(name.as_str()).unwrap().num_cast(),
|
||||||
|
NumExpr::Neg(Neg { arg }) => Self::eval_num_expr::<T>(arg, trace).map(|sig| -&sig),
|
||||||
|
NumExpr::Add(Add { args }) => {
|
||||||
|
let mut ret: Signal<T> = Signal::<T>::zero();
|
||||||
|
for arg in args.iter() {
|
||||||
|
let arg = Self::eval_num_expr::<T>(arg, trace)?;
|
||||||
|
ret = &ret + &arg;
|
||||||
|
}
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
NumExpr::Sub(Sub { lhs, rhs }) => {
|
||||||
|
let lhs = Self::eval_num_expr::<T>(lhs, trace)?;
|
||||||
|
let rhs = Self::eval_num_expr::<T>(rhs, trace)?;
|
||||||
|
Ok(&lhs - &rhs)
|
||||||
|
}
|
||||||
|
NumExpr::Mul(Mul { args }) => {
|
||||||
|
let mut ret: Signal<T> = Signal::<T>::one();
|
||||||
|
for arg in args.iter() {
|
||||||
|
let arg = Self::eval_num_expr::<T>(arg, trace)?;
|
||||||
|
ret = &ret * &arg;
|
||||||
|
}
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
NumExpr::Div(Div { dividend, divisor }) => {
|
||||||
|
let dividend = Self::eval_num_expr::<T>(dividend, trace)?;
|
||||||
|
let divisor = Self::eval_num_expr::<T>(divisor, trace)?;
|
||||||
|
Ok(÷nd / &divisor)
|
||||||
|
}
|
||||||
|
NumExpr::Abs(Abs { arg }) => {
|
||||||
|
let arg = Self::eval_num_expr::<T>(arg, trace)?;
|
||||||
|
Ok(arg.abs())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_next(arg: Signal<f64>) -> ArgusResult<Signal<f64>> {
|
||||||
|
compute_oracle(arg, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_oracle(arg: Signal<f64>, steps: usize) -> ArgusResult<Signal<f64>> {
|
||||||
|
if steps == 0 {
|
||||||
|
return Ok(Signal::Empty);
|
||||||
|
}
|
||||||
|
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 `steps` timestamps
|
||||||
|
assert_eq!(values.len(), time_points.len());
|
||||||
|
if values.len() <= steps {
|
||||||
|
return Ok(Signal::Empty);
|
||||||
|
}
|
||||||
|
let expected_len = values.len() - steps;
|
||||||
|
let values = values.split_off(steps);
|
||||||
|
let _ = time_points.split_off(steps);
|
||||||
|
|
||||||
|
assert_eq!(values.len(), expected_len);
|
||||||
|
assert_eq!(values.len(), time_points.len());
|
||||||
|
Ok(Signal::Sampled { values, time_points })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute always for a signal
|
||||||
|
fn compute_always(signal: Signal<f64>, interval: &Interval) -> ArgusResult<Signal<f64>> {
|
||||||
|
if interval.is_empty() || interval.is_singleton() {
|
||||||
|
return Err(ArgusError::InvalidInterval {
|
||||||
|
reason: "interval is either empty or singleton",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let ret = match signal {
|
||||||
|
// if signal is empty or constant, return the signal itself.
|
||||||
|
// This works because if a signal is True everythere, then it must
|
||||||
|
// "always be true".
|
||||||
|
sig @ (Signal::Empty | Signal::Constant { value: _ }) => sig,
|
||||||
|
sig => {
|
||||||
|
use Bound::*;
|
||||||
|
if interval.is_singleton() {
|
||||||
|
// for singleton intervals, return the signal itself.
|
||||||
|
sig
|
||||||
|
} else if interval.is_untimed() {
|
||||||
|
compute_untimed_always(sig)?
|
||||||
|
} else if let (Included(a), Included(b)) = interval.into() {
|
||||||
|
compute_timed_always(sig, *a, Some(*b))?
|
||||||
|
} else if let (Included(a), Unbounded) = interval.into() {
|
||||||
|
compute_timed_always(sig, *a, None)?
|
||||||
|
} else {
|
||||||
|
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>> {
|
||||||
|
let z1 = -signal;
|
||||||
|
let z2 = compute_timed_eventually(z1, a, b)?;
|
||||||
|
Ok(-z2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute untimed always
|
||||||
|
fn compute_untimed_always(signal: Signal<f64>) -> ArgusResult<Signal<f64>> {
|
||||||
|
let Signal::Sampled {
|
||||||
|
mut values,
|
||||||
|
time_points,
|
||||||
|
} = signal
|
||||||
|
else {
|
||||||
|
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() {
|
||||||
|
values[i] = values[i + 1].min(values[i]);
|
||||||
|
}
|
||||||
|
Ok(Signal::Sampled { values, time_points })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute eventually for a signal
|
||||||
|
fn compute_eventually(signal: Signal<f64>, interval: &Interval) -> ArgusResult<Signal<f64>> {
|
||||||
|
if interval.is_empty() || interval.is_singleton() {
|
||||||
|
return Err(ArgusError::InvalidInterval {
|
||||||
|
reason: "interval is either empty or singleton",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let ret = match signal {
|
||||||
|
// if signal is empty or constant, return the signal itself.
|
||||||
|
// This works because if a signal is True everythere, then it must
|
||||||
|
// "eventually be true".
|
||||||
|
sig @ (Signal::Empty | Signal::Constant { value: _ }) => sig,
|
||||||
|
sig => {
|
||||||
|
use Bound::*;
|
||||||
|
if interval.is_singleton() {
|
||||||
|
// for singleton intervals, return the signal itself.
|
||||||
|
sig
|
||||||
|
} else if interval.is_untimed() {
|
||||||
|
compute_untimed_eventually(sig)?
|
||||||
|
} else if let (Included(a), Included(b)) = interval.into() {
|
||||||
|
compute_timed_eventually(sig, *a, Some(*b))?
|
||||||
|
} else if let (Included(a), Unbounded) = interval.into() {
|
||||||
|
compute_timed_eventually(sig, *a, None)?
|
||||||
|
} else {
|
||||||
|
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>> {
|
||||||
|
match b {
|
||||||
|
Some(b) => {
|
||||||
|
// We want to compute the windowed max/or of the signal.
|
||||||
|
// The window is dictated by the time duration though.
|
||||||
|
let Signal::Sampled { values, time_points } = signal else {
|
||||||
|
unreachable!("we shouldn't be passing non-sampled signals here")
|
||||||
|
};
|
||||||
|
assert!(b > a);
|
||||||
|
assert!(!time_points.is_empty());
|
||||||
|
let signal_duration = *time_points.last().unwrap() - *time_points.first().unwrap();
|
||||||
|
let width = if signal_duration < (b - a) {
|
||||||
|
signal_duration
|
||||||
|
} else {
|
||||||
|
b - a
|
||||||
|
};
|
||||||
|
let mut ret_vals = Vec::with_capacity(values.len());
|
||||||
|
|
||||||
|
// For boolean signals we dont need to worry about intersections with ZERO as much as
|
||||||
|
// for quantitative signals, as linear interpolation is just a discrte switch.
|
||||||
|
let mut wedge = MonoWedge::<f64>::max_wedge(width);
|
||||||
|
for (i, value) in time_points.iter().zip(&values) {
|
||||||
|
wedge.update((i, value));
|
||||||
|
if i >= &(time_points[0] + width) {
|
||||||
|
ret_vals.push(
|
||||||
|
wedge
|
||||||
|
.front()
|
||||||
|
.map(|(&t, &v)| (t, v))
|
||||||
|
.unwrap_or_else(|| panic!("wedge should have at least 1 element")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Signal::try_from_iter(ret_vals.into_iter())
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Shift the signal to the left by `a` and then run the untimed eventually.
|
||||||
|
let shifted = signal.shift_left(a);
|
||||||
|
compute_untimed_eventually(shifted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute untimed eventually
|
||||||
|
fn compute_untimed_eventually(signal: Signal<f64>) -> ArgusResult<Signal<f64>> {
|
||||||
|
let Signal::Sampled {
|
||||||
|
mut values,
|
||||||
|
time_points,
|
||||||
|
} = signal
|
||||||
|
else {
|
||||||
|
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() {
|
||||||
|
values[i] = values[i + 1].max(values[i]);
|
||||||
|
}
|
||||||
|
Ok(Signal::Sampled { values, time_points })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute until
|
||||||
|
fn compute_until(lhs: Signal<f64>, rhs: Signal<f64>, interval: &Interval) -> ArgusResult<Signal<f64>> {
|
||||||
|
let ret = match (lhs, rhs) {
|
||||||
|
// If either signals are empty, return empty
|
||||||
|
(sig @ Signal::Empty, _) | (_, sig @ Signal::Empty) => sig,
|
||||||
|
(lhs, rhs) => {
|
||||||
|
use Bound::*;
|
||||||
|
if interval.is_untimed() {
|
||||||
|
compute_untimed_until(lhs, rhs)?
|
||||||
|
} else if let (Included(a), Included(b)) = interval.into() {
|
||||||
|
compute_timed_until(lhs, rhs, *a, Some(*b))?
|
||||||
|
} else if let (Included(a), Unbounded) = interval.into() {
|
||||||
|
compute_timed_until(lhs, rhs, *a, None)?
|
||||||
|
} else {
|
||||||
|
unreachable!("interval should be created using Interval::new, and is_untimed checks this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute timed until for the interval `[a, b]` (or, if `b` is `None`, `[a, ..]`.
|
||||||
|
///
|
||||||
|
/// 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)
|
||||||
|
/// $$
|
||||||
|
///
|
||||||
|
/// $$
|
||||||
|
/// \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.")
|
||||||
|
fn compute_timed_until(
|
||||||
|
lhs: Signal<f64>,
|
||||||
|
rhs: Signal<f64>,
|
||||||
|
a: Duration,
|
||||||
|
b: Option<Duration>,
|
||||||
|
) -> ArgusResult<Signal<f64>> {
|
||||||
|
match b {
|
||||||
|
Some(b) => {
|
||||||
|
// First compute eventually [a, b]
|
||||||
|
let ev_a_b_rhs = compute_timed_eventually(rhs.clone(), a, Some(b))?;
|
||||||
|
// Then compute until [a, \infty) (lhs, rhs)
|
||||||
|
let unt_a_inf = compute_timed_until(lhs, rhs, a, None)?;
|
||||||
|
// Then & them
|
||||||
|
Ok(ev_a_b_rhs.min(&unt_a_inf))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
assert_ne!(a, Duration::ZERO, "untimed case wasn't handled for Until");
|
||||||
|
// First compute untimed until (lhs, rhs)
|
||||||
|
let untimed_until = compute_untimed_until(lhs, rhs)?;
|
||||||
|
// Compute G [0, a]
|
||||||
|
compute_timed_always(untimed_until, Duration::ZERO, Some(a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute untimed until
|
||||||
|
fn compute_untimed_until(lhs: Signal<f64>, rhs: Signal<f64>) -> ArgusResult<Signal<f64>> {
|
||||||
|
let sync_points = lhs.sync_with_intersection::<Linear>(&rhs).unwrap();
|
||||||
|
let mut ret_samples = Vec::with_capacity(sync_points.len());
|
||||||
|
let expected_len = sync_points.len();
|
||||||
|
|
||||||
|
let mut next = f64::NEG_INFINITY;
|
||||||
|
|
||||||
|
for (i, t) in sync_points.into_iter().enumerate().rev() {
|
||||||
|
let v1 = lhs.interpolate_at::<Linear>(t).unwrap();
|
||||||
|
let v2 = rhs.interpolate_at::<Linear>(t).unwrap();
|
||||||
|
|
||||||
|
let z = f64::max(f64::min(v1, v2), f64::min(v1, next));
|
||||||
|
if z == next && i < (expected_len - 2) {
|
||||||
|
ret_samples.pop();
|
||||||
|
}
|
||||||
|
ret_samples.push((t, z));
|
||||||
|
next = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
Signal::<f64>::try_from_iter(ret_samples.into_iter().rev())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn top_or_bot(sig: &Signal<bool>) -> Signal<f64> {
|
||||||
|
let bool2float = |&v| {
|
||||||
|
if v {
|
||||||
|
f64::INFINITY
|
||||||
|
} else {
|
||||||
|
f64::NEG_INFINITY
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match sig {
|
||||||
|
Signal::Empty => Signal::Empty,
|
||||||
|
Signal::Constant { value } => Signal::constant(bool2float(value)),
|
||||||
|
Signal::Sampled { values, time_points } => {
|
||||||
|
time_points.iter().copied().zip(values.iter().map(bool2float)).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::iter::zip;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use argus_core::expr::ExprBuilder;
|
||||||
|
use argus_core::signals::AnySignal;
|
||||||
|
use itertools::assert_equal;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const FLOAT_EPS: f64 = 1.0e-8;
|
||||||
|
|
||||||
|
fn assert_approx_eq(lhs: &Signal<f64>, rhs: &Signal<f64>) {
|
||||||
|
zip(lhs, rhs).enumerate().for_each(|(i, (s1, s2))| {
|
||||||
|
assert_eq!(
|
||||||
|
s1.0, s2.0,
|
||||||
|
"Failed assertion {:?} != {:?} for iteration {}",
|
||||||
|
s1.0, s2.0, i
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
(s2.1 - s1.1).abs() <= FLOAT_EPS,
|
||||||
|
"Failed approx equal assertion: {} != {} for iteration {}",
|
||||||
|
s1.1,
|
||||||
|
s2.1,
|
||||||
|
i
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MyTrace {
|
||||||
|
signals: HashMap<String, Box<dyn AnySignal>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Trace for MyTrace {
|
||||||
|
fn signal_names(&self) -> Vec<&str> {
|
||||||
|
self.signals.keys().map(|key| key.as_str()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get<T: 'static>(&self, name: &str) -> Option<&Signal<T>> {
|
||||||
|
let signal = self.signals.get(name)?;
|
||||||
|
signal.as_any().downcast_ref::<Signal<T>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn num_constant() {
|
||||||
|
let expr_builder = ExprBuilder::new();
|
||||||
|
|
||||||
|
let spec = expr_builder.float_const(5.0);
|
||||||
|
let trace = MyTrace::default();
|
||||||
|
|
||||||
|
let robustness = QuantitativeSemantics::eval_num_expr::<f64>(&spec, &trace).unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(robustness, Signal::Constant { value } if value == 5.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn addition() {
|
||||||
|
let mut ctx = ExprBuilder::new();
|
||||||
|
|
||||||
|
let a = ctx.float_var("a".to_owned()).unwrap();
|
||||||
|
let b = ctx.float_var("b".to_owned()).unwrap();
|
||||||
|
let spec = ctx.make_add([*a, *b]).unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let signals = HashMap::from_iter(vec![
|
||||||
|
(
|
||||||
|
"a".to_owned(),
|
||||||
|
Box::new(Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 1.3),
|
||||||
|
(Duration::from_secs_f64(0.7), 3.0),
|
||||||
|
(Duration::from_secs_f64(1.3), 0.1),
|
||||||
|
(Duration::from_secs_f64(2.1), -2.2),
|
||||||
|
])) as Box<dyn AnySignal>,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"b".to_owned(),
|
||||||
|
Box::new(Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 2.5),
|
||||||
|
(Duration::from_secs_f64(0.7), 4.0),
|
||||||
|
(Duration::from_secs_f64(1.3), -1.2),
|
||||||
|
(Duration::from_secs_f64(2.1), 1.7),
|
||||||
|
])) as Box<dyn AnySignal>,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let trace = MyTrace { signals };
|
||||||
|
|
||||||
|
let rob = QuantitativeSemantics::eval_num_expr::<f64>(&spec, &trace).unwrap();
|
||||||
|
let expected = Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 1.3 + 2.5),
|
||||||
|
(Duration::from_secs_f64(0.7), 3.0 + 4.0),
|
||||||
|
(Duration::from_secs_f64(1.3), 0.1 + -1.2),
|
||||||
|
(Duration::from_secs_f64(2.1), -2.2 + 1.7),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_equal(&rob, &expected);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let signals = HashMap::from_iter(vec![
|
||||||
|
(
|
||||||
|
"a".to_owned(),
|
||||||
|
Box::new(Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 1.3),
|
||||||
|
(Duration::from_secs_f64(0.7), 3.0),
|
||||||
|
(Duration::from_secs_f64(1.3), 4.0),
|
||||||
|
(Duration::from_secs_f64(2.1), 3.0),
|
||||||
|
])) as Box<dyn AnySignal>,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"b".to_owned(),
|
||||||
|
Box::new(Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 2.5),
|
||||||
|
(Duration::from_secs_f64(0.7), 4.0),
|
||||||
|
(Duration::from_secs_f64(1.3), 3.0),
|
||||||
|
(Duration::from_secs_f64(2.1), 4.0),
|
||||||
|
])) as Box<dyn AnySignal>,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let trace = MyTrace { signals };
|
||||||
|
|
||||||
|
let rob = QuantitativeSemantics::eval_num_expr::<f64>(&spec, &trace).unwrap();
|
||||||
|
let expected = Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 1.3 + 2.5),
|
||||||
|
(Duration::from_secs_f64(0.7), 3.0 + 4.0),
|
||||||
|
(Duration::from_secs_f64(1.3), 4.0 + 3.0),
|
||||||
|
(Duration::from_secs_f64(2.1), 3.0 + 4.0),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_equal(&rob, &expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn less_than() {
|
||||||
|
let mut ctx = ExprBuilder::new();
|
||||||
|
|
||||||
|
let a = ctx.float_var("a".to_owned()).unwrap();
|
||||||
|
let spec = ctx.make_lt(a, ctx.float_const(0.0));
|
||||||
|
|
||||||
|
let signals = HashMap::from_iter(vec![(
|
||||||
|
"a".to_owned(),
|
||||||
|
Box::new(Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 1.3),
|
||||||
|
(Duration::from_secs_f64(0.7), 3.0),
|
||||||
|
(Duration::from_secs_f64(1.3), 0.1),
|
||||||
|
(Duration::from_secs_f64(2.1), -2.2),
|
||||||
|
])) as Box<dyn AnySignal>,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let trace = MyTrace { signals };
|
||||||
|
|
||||||
|
let rob = dbg!(QuantitativeSemantics::eval(&spec, &trace).unwrap());
|
||||||
|
let expected = Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 0.0 - 1.3),
|
||||||
|
(Duration::from_secs_f64(0.7), 0.0 - 3.0),
|
||||||
|
(Duration::from_secs_f64(1.3), 0.0 - 0.1),
|
||||||
|
(Duration::from_secs_f64(1.334782609), 0.0), // interpolated at
|
||||||
|
(Duration::from_secs_f64(2.1), 0.0 - (-2.2)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_approx_eq(&rob, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eventually_unbounded() {
|
||||||
|
let mut ctx = ExprBuilder::new();
|
||||||
|
|
||||||
|
let a = ctx.float_var("a".to_owned()).unwrap();
|
||||||
|
let cmp = ctx.make_ge(a, ctx.float_const(0.0));
|
||||||
|
let spec = ctx.make_eventually(cmp);
|
||||||
|
|
||||||
|
{
|
||||||
|
let signals = HashMap::from_iter(vec![(
|
||||||
|
"a".to_owned(),
|
||||||
|
Box::new(Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 2.5),
|
||||||
|
(Duration::from_secs_f64(0.7), 4.0),
|
||||||
|
(Duration::from_secs_f64(1.3), -1.0),
|
||||||
|
(Duration::from_secs_f64(2.1), 1.7),
|
||||||
|
])) as Box<dyn AnySignal>,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let trace = MyTrace { signals };
|
||||||
|
let rob = QuantitativeSemantics::eval(&spec, &trace).unwrap();
|
||||||
|
println!("{:#?}", rob);
|
||||||
|
let expected = Signal::from_iter(vec![
|
||||||
|
(Duration::from_secs_f64(0.0), 4.0),
|
||||||
|
(Duration::from_secs_f64(0.7), 4.0),
|
||||||
|
(Duration::from_secs_f64(1.18), 1.7),
|
||||||
|
(Duration::from_secs_f64(1.3), 1.7),
|
||||||
|
(Duration::from_secs_f64(1.596296296), 1.7), // interpolated at
|
||||||
|
(Duration::from_secs_f64(2.1), 1.7),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_equal(&rob, &expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
//! Traits to define semantics for temporal logic specifications
|
//! Traits to define semantics for temporal logic specifications
|
||||||
|
|
||||||
use argus_core::expr::{IsBoolExpr, IsNumExpr};
|
|
||||||
use argus_core::prelude::*;
|
use argus_core::prelude::*;
|
||||||
|
|
||||||
/// A trace is a collection of signals
|
/// A trace is a collection of signals
|
||||||
|
|
@ -50,13 +49,3 @@ pub trait Trace {
|
||||||
/// Query a signal using its name
|
/// Query a signal using its name
|
||||||
fn get<T: 'static>(&self, name: &str) -> Option<&Signal<T>>;
|
fn get<T: 'static>(&self, name: &str) -> Option<&Signal<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Boolean semantics for a [`BoolExpr`] or type that is
|
|
||||||
/// convertable to a [`BoolExpr`]
|
|
||||||
pub trait BooleanSemantics: IsBoolExpr {
|
|
||||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait QuantitativeSemantics: IsNumExpr {
|
|
||||||
fn eval(&self, trace: &impl Trace) -> ArgusResult<Signal<bool>>;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
//! [^2]: Daniel Lemire. 2007. Streaming Maximum-Minimum Filter Using No More than Three
|
//! [^2]: Daniel Lemire. 2007. Streaming Maximum-Minimum Filter Using No More than Three
|
||||||
//! Comparisons per Element. arXiv:cs/0610046.
|
//! Comparisons per Element. arXiv:cs/0610046.
|
||||||
|
|
||||||
|
// TODO: Make a MonoWedge iterator adapter.
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue