use crate::core::{
RobustnessSemantics, SignalIdentifier, StlOperatorAndSignalIdentifier, StlOperatorTrait,
TimeInterval,
};
use crate::ring_buffer::{RingBufferTrait, Step, guarded_prune};
use std::collections::{HashSet, VecDeque};
use std::fmt::{Debug, Display};
use std::time::Duration;
struct WindowParams<'a> {
interval: &'a TimeInterval,
max_lookahead: Duration,
current_time: Duration,
upper_bound: Option<Duration>,
}
struct OpParams<Y, FCombine, FIdentity>
where
FCombine: Fn(Y, Y) -> Y,
FIdentity: Fn() -> Y,
{
combine: FCombine,
identity: FIdentity,
eager_short_circuit: Y,
}
#[allow(clippy::skip_while_next)]
fn process_eval_buffer<C, Y, FCombine, FIdentity, const IS_EAGER: bool, const IS_ROSI: bool>(
eval_buffer: &mut VecDeque<Duration>,
eval_buffer_set: &mut HashSet<Duration>,
cache: &C,
window: WindowParams<'_>,
op: OpParams<Y, FCombine, FIdentity>,
) -> Vec<Step<Y>>
where
C: RingBufferTrait<Value = Y>,
Y: RobustnessSemantics + Debug,
FCombine: Fn(Y, Y) -> Y,
FIdentity: Fn() -> Y,
{
let mut output_robustness = Vec::new();
let mut tasks_to_remove = Vec::new();
let mut n_front_to_pop: usize = 0;
for &t_eval in eval_buffer.iter() {
if let Some(bound) = window.upper_bound
&& t_eval >= bound
{
break;
}
let time_finalized = window.current_time >= t_eval + window.max_lookahead;
if !time_finalized && !IS_EAGER && !IS_ROSI {
break;
}
let window_start = t_eval + window.interval.start;
let window_end = t_eval + window.interval.end;
let windowed_value = if IS_ROSI {
cache
.iter()
.skip_while(|s| s.timestamp < window_start)
.take_while(|s| s.timestamp <= window_end)
.map(|s| s.value.clone())
.reduce(&op.combine)
.unwrap_or_else(&op.identity)
} else {
cache
.iter()
.skip_while(|f| f.timestamp < window_start)
.next()
.map(|entry| entry.value.clone())
.unwrap_or_else(&op.identity)
};
if time_finalized || (IS_EAGER && windowed_value == op.eager_short_circuit) {
output_robustness.push(Step::new("output", windowed_value, t_eval));
tasks_to_remove.push(t_eval);
if time_finalized {
n_front_to_pop += 1;
}
} else if IS_ROSI {
let intermediate_value = (op.combine)(windowed_value, Y::unknown());
output_robustness.push(Step::new("output", intermediate_value, t_eval));
} else {
break;
}
}
for &t in &tasks_to_remove {
eval_buffer_set.remove(&t);
}
for _ in 0..n_front_to_pop {
eval_buffer.pop_front();
}
if tasks_to_remove.len() > n_front_to_pop {
eval_buffer.retain(|t| eval_buffer_set.contains(t));
}
output_robustness
}
fn pop_dominated_values<C, Y>(
cache: &mut C,
sub_step: &Step<Y>,
is_max: bool,
window_length: Duration,
) where
C: RingBufferTrait<Value = Y>,
Y: RobustnessSemantics + Debug,
{
while let Some(back) = cache.get_back() {
if Y::prune_dominated(back.value.clone(), sub_step.value.clone(), is_max)
&& back.timestamp + window_length >= sub_step.timestamp
{
cache.pop_back();
} else {
break;
}
}
}
#[derive(Clone)]
pub struct Eventually<T, C, Y, const IS_EAGER: bool, const IS_ROSI: bool> {
interval: TimeInterval,
operand: Box<dyn StlOperatorAndSignalIdentifier<T, Y>>,
cache: C,
eval_buffer: VecDeque<Duration>,
eval_buffer_set: HashSet<Duration>,
max_lookahead: Duration,
}
impl<T, C, Y, const IS_EAGER: bool, const IS_ROSI: bool> Eventually<T, C, Y, IS_EAGER, IS_ROSI> {
pub fn new(
interval: TimeInterval,
operand: Box<dyn StlOperatorAndSignalIdentifier<T, Y>>,
cache: Option<C>,
eval_buffer: Option<VecDeque<Duration>>,
) -> Self
where
T: Clone + 'static,
C: RingBufferTrait<Value = Y> + Clone + 'static,
Y: RobustnessSemantics + 'static,
{
let max_lookahead = interval.end + operand.get_max_lookahead();
let eval_buffer = eval_buffer.unwrap_or_default();
let eval_buffer_set: HashSet<Duration> = eval_buffer.iter().copied().collect();
#[cfg(feature = "track-cache-size")]
{
let mut c = cache.unwrap_or_else(|| C::new());
c.set_tracked(true); Eventually {
interval,
operand,
cache: c,
eval_buffer,
eval_buffer_set,
max_lookahead,
}
}
#[cfg(not(feature = "track-cache-size"))]
{
let c = cache.unwrap_or_else(|| C::new());
Eventually {
interval,
operand,
cache: c,
eval_buffer,
eval_buffer_set,
max_lookahead,
}
}
}
}
impl<T, C, Y, const IS_EAGER: bool, const IS_ROSI: bool> StlOperatorTrait<T>
for Eventually<T, C, Y, IS_EAGER, IS_ROSI>
where
T: Clone + 'static,
C: RingBufferTrait<Value = Y> + Clone + 'static,
Y: RobustnessSemantics + Debug + 'static,
{
type Output = Y;
fn get_max_lookahead(&self) -> Duration {
self.max_lookahead
}
fn reset(&mut self) {
self.cache.clear();
self.eval_buffer.clear();
self.eval_buffer_set.clear();
self.operand.reset();
}
fn update(&mut self, step: &Step<T>) -> Vec<Step<Self::Output>> {
let sub_robustness_vec = self.operand.update(step);
let mut output_robustness = Vec::new();
let current_time = step.timestamp;
if let Some(first) = sub_robustness_vec.first()
&& let Some(split_key) = first.timestamp.checked_sub(self.max_lookahead)
{
output_robustness.extend(process_eval_buffer::<_, _, _, _, IS_EAGER, IS_ROSI>(
&mut self.eval_buffer,
&mut self.eval_buffer_set,
&self.cache,
WindowParams {
interval: &self.interval,
max_lookahead: self.max_lookahead,
current_time: first.timestamp,
upper_bound: Some(split_key),
},
OpParams {
combine: Y::or,
identity: Y::eventually_identity,
eager_short_circuit: Y::atomic_true(),
},
));
}
for sub_step in sub_robustness_vec {
if self.eval_buffer_set.insert(sub_step.timestamp) {
self.eval_buffer.push_back(sub_step.timestamp);
}
if IS_ROSI {
if !self.cache.update_step(sub_step.clone()) {
let is_new_step = self
.cache
.get_back()
.is_none_or(|b| sub_step.timestamp > b.timestamp);
if is_new_step {
pop_dominated_values(
&mut self.cache,
&sub_step,
true,
self.interval.window_length(),
);
self.cache.add_step(sub_step);
}
}
} else {
pop_dominated_values(
&mut self.cache,
&sub_step,
true,
self.interval.window_length(),
);
self.cache.add_step(sub_step);
}
}
let phase_c_time = if IS_ROSI {
self.cache
.get_back()
.map(|s| s.timestamp)
.unwrap_or(Duration::ZERO)
} else {
current_time
};
output_robustness.extend(process_eval_buffer::<_, _, _, _, IS_EAGER, IS_ROSI>(
&mut self.eval_buffer,
&mut self.eval_buffer_set,
&self.cache,
WindowParams {
interval: &self.interval,
max_lookahead: self.max_lookahead,
current_time: phase_c_time,
upper_bound: None,
},
OpParams {
combine: Y::or,
identity: Y::eventually_identity,
eager_short_circuit: Y::atomic_true(),
},
));
let protected_ts = self.eval_buffer.front().copied().unwrap_or(Duration::ZERO);
guarded_prune(&mut self.cache, self.max_lookahead, protected_ts);
output_robustness
}
}
impl<T, C, Y, const IS_EAGER: bool, const IS_ROSI: bool> SignalIdentifier
for Eventually<T, C, Y, IS_EAGER, IS_ROSI>
{
fn get_signal_identifiers(&mut self) -> HashSet<&'static str> {
self.operand.get_signal_identifiers()
}
}
#[derive(Clone)]
pub struct Globally<T, C, Y, const IS_EAGER: bool, const IS_ROSI: bool> {
interval: TimeInterval,
operand: Box<dyn StlOperatorAndSignalIdentifier<T, Y> + 'static>,
cache: C,
eval_buffer: VecDeque<Duration>,
eval_buffer_set: HashSet<Duration>,
max_lookahead: Duration,
}
impl<T, C, Y, const IS_EAGER: bool, const IS_ROSI: bool> Globally<T, C, Y, IS_EAGER, IS_ROSI> {
pub fn new(
interval: TimeInterval,
operand: Box<dyn StlOperatorAndSignalIdentifier<T, Y>>,
cache: Option<C>,
eval_buffer: Option<VecDeque<Duration>>,
) -> Self
where
T: Clone + 'static,
C: RingBufferTrait<Value = Y> + Clone + 'static,
Y: RobustnessSemantics + 'static,
{
let max_lookahead = interval.end + operand.get_max_lookahead();
let eval_buffer = eval_buffer.unwrap_or_default();
let eval_buffer_set: HashSet<Duration> = eval_buffer.iter().copied().collect();
#[cfg(feature = "track-cache-size")]
{
let mut c = cache.unwrap_or_else(|| C::new());
c.set_tracked(true); Globally {
interval,
operand,
cache: c,
eval_buffer,
eval_buffer_set,
max_lookahead,
}
}
#[cfg(not(feature = "track-cache-size"))]
{
let c = cache.unwrap_or_else(|| C::new());
Globally {
interval,
operand,
cache: c,
eval_buffer,
eval_buffer_set,
max_lookahead,
}
}
}
}
impl<T, C, Y, const IS_EAGER: bool, const IS_ROSI: bool> StlOperatorTrait<T>
for Globally<T, C, Y, IS_EAGER, IS_ROSI>
where
T: Clone + 'static,
C: RingBufferTrait<Value = Y> + Clone + 'static,
Y: RobustnessSemantics + Debug + 'static,
{
type Output = Y;
fn get_max_lookahead(&self) -> Duration {
self.max_lookahead
}
fn reset(&mut self) {
self.cache.clear();
self.eval_buffer.clear();
self.eval_buffer_set.clear();
self.operand.reset();
}
fn update(&mut self, step: &Step<T>) -> Vec<Step<Self::Output>> {
let sub_robustness_vec = self.operand.update(step);
let mut output_robustness = Vec::new();
let current_time = step.timestamp;
if let Some(first) = sub_robustness_vec.first()
&& let Some(split_key) = first.timestamp.checked_sub(self.max_lookahead)
{
output_robustness.extend(process_eval_buffer::<_, _, _, _, IS_EAGER, IS_ROSI>(
&mut self.eval_buffer,
&mut self.eval_buffer_set,
&self.cache,
WindowParams {
interval: &self.interval,
max_lookahead: self.max_lookahead,
current_time: first.timestamp,
upper_bound: Some(split_key),
},
OpParams {
combine: Y::and,
identity: Y::globally_identity,
eager_short_circuit: Y::atomic_false(),
},
));
}
for sub_step in sub_robustness_vec {
if self.eval_buffer_set.insert(sub_step.timestamp) {
self.eval_buffer.push_back(sub_step.timestamp);
}
if IS_ROSI {
if !self.cache.update_step(sub_step.clone()) {
let is_new_step = self
.cache
.get_back()
.is_none_or(|b| sub_step.timestamp > b.timestamp);
if is_new_step {
pop_dominated_values(
&mut self.cache,
&sub_step,
false,
self.interval.window_length(),
);
self.cache.add_step(sub_step);
}
}
} else {
pop_dominated_values(
&mut self.cache,
&sub_step,
false,
self.interval.window_length(),
);
self.cache.add_step(sub_step);
}
}
let phase_c_time = if IS_ROSI {
self.cache
.get_back()
.map(|s| s.timestamp)
.unwrap_or(Duration::ZERO)
} else {
current_time
};
output_robustness.extend(process_eval_buffer::<_, _, _, _, IS_EAGER, IS_ROSI>(
&mut self.eval_buffer,
&mut self.eval_buffer_set,
&self.cache,
WindowParams {
interval: &self.interval,
max_lookahead: self.max_lookahead,
current_time: phase_c_time,
upper_bound: None,
},
OpParams {
combine: Y::and,
identity: Y::globally_identity,
eager_short_circuit: Y::atomic_false(),
},
));
let protected_ts = self.eval_buffer.front().copied().unwrap_or(Duration::ZERO);
guarded_prune(&mut self.cache, self.max_lookahead, protected_ts);
output_robustness
}
}
impl<T, C, Y, const IS_EAGER: bool, const IS_ROSI: bool> SignalIdentifier
for Globally<T, C, Y, IS_EAGER, IS_ROSI>
{
fn get_signal_identifiers(&mut self) -> HashSet<&'static str> {
self.operand.get_signal_identifiers()
}
}
impl<T, C, Y, const IS_EAGER: bool, const IS_ROSI: bool> Display
for Globally<T, Y, C, IS_EAGER, IS_ROSI>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"G[{}, {}]({})",
self.interval.start.as_secs_f64(),
self.interval.end.as_secs_f64(),
self.operand
)
}
}
impl<T, C, Y, const IS_EAGER: bool, const IS_ROSI: bool> Display
for Eventually<T, C, Y, IS_EAGER, IS_ROSI>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"F[{}, {}]({})",
self.interval.start.as_secs_f64(),
self.interval.end.as_secs_f64(),
self.operand
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{StlOperatorTrait, TimeInterval};
use crate::operators::atomic_operators::Atomic;
use crate::ring_buffer::RingBuffer;
use crate::step;
use pretty_assertions::assert_eq;
use std::time::Duration;
#[test]
fn eventually_operator_robustness() {
let interval = TimeInterval {
start: Duration::from_secs(0),
end: Duration::from_secs(4),
};
let atomic = Atomic::<f64>::new_greater_than("x", 10.0);
let mut eventually = Eventually::<f64, RingBuffer<f64>, f64, false, false>::new(
interval,
Box::new(atomic),
None,
None,
);
eventually.get_signal_identifiers();
let signal_values = vec![15.0, 12.0, 8.0, 5.0, 12.0];
let signal_timestamps = vec![0, 2, 4, 6, 8];
let signal: Vec<_> = signal_values
.into_iter()
.zip(signal_timestamps)
.map(|(val, ts)| step!("x", val, Duration::from_secs(ts)))
.collect();
let mut all_outputs = Vec::new();
for s in &signal {
all_outputs.extend(eventually.update(s));
}
let expected_outputs = [
step!("output", 5.0, Duration::from_secs(0)),
step!("output", 2.0, Duration::from_secs(2)),
step!("output", 2.0, Duration::from_secs(4)),
];
assert_eq!(all_outputs.len(), expected_outputs.len());
for (output, expected) in all_outputs.iter().zip(expected_outputs.iter()) {
assert_eq!(output.timestamp, expected.timestamp);
assert!(
(output.value - expected.value).abs() < 1e-9,
"t={:?} output: {}, expected: {}",
output.timestamp,
output.value,
expected.value
);
}
}
#[test]
fn globally_operator_robustness() {
let interval = TimeInterval {
start: Duration::from_secs(0),
end: Duration::from_secs(4),
};
let atomic = Atomic::<f64>::new_greater_than("x", 10.0);
let mut globally = Globally::<f64, RingBuffer<f64>, f64, false, false>::new(
interval,
Box::new(atomic),
None,
None,
);
globally.get_signal_identifiers();
let signal_values = vec![15.0, 12.0, 8.0, 5.0, 12.0];
let signal_timestamps = vec![0, 2, 4, 6, 8];
let signal: Vec<_> = signal_values
.into_iter()
.zip(signal_timestamps)
.map(|(val, ts)| step!("x", val, Duration::from_secs(ts)))
.collect();
let mut all_outputs = Vec::new();
for s in &signal {
all_outputs.extend(globally.update(s));
}
let expected_outputs = [
step!("output", -2.0, Duration::from_secs(0)),
step!("output", -5.0, Duration::from_secs(2)),
step!("output", -5.0, Duration::from_secs(4)),
];
assert_eq!(all_outputs.len(), expected_outputs.len());
for (output, expected) in all_outputs.iter().zip(expected_outputs.iter()) {
assert_eq!(output.timestamp, expected.timestamp);
assert!(
(output.value - expected.value).abs() < 1e-9,
"left: {}, right: {}",
output.value,
expected.value
);
}
}
#[test]
fn unary_temporal_signal_identifiers() {
let interval = TimeInterval {
start: Duration::from_secs(0),
end: Duration::from_secs(4),
};
let atomic = Atomic::<f64>::new_greater_than("x", 10.0);
let mut globally = Globally::<f64, RingBuffer<f64>, f64, false, false>::new(
interval,
Box::new(atomic),
None,
None,
);
let ids = globally.get_signal_identifiers();
let expected_ids: HashSet<&'static str> = vec!["x"].into_iter().collect();
assert_eq!(ids, expected_ids);
}
#[test]
fn globally_display() {
let interval = TimeInterval {
start: Duration::from_secs(1),
end: Duration::from_secs(5),
};
let atomic = Atomic::<f64>::new_greater_than("x", 10.0);
let globally = Globally::<f64, RingBuffer<f64>, f64, false, false>::new(
interval,
Box::new(atomic),
None,
None,
);
assert_eq!(format!("{globally}"), "G[1, 5](x > 10)");
}
#[test]
fn eventually_display() {
let interval = TimeInterval {
start: Duration::from_secs(0),
end: Duration::from_secs(3),
};
let atomic = Atomic::<f64>::new_less_than("y", 5.0);
let eventually = Eventually::<f64, RingBuffer<f64>, f64, false, false>::new(
interval,
Box::new(atomic),
None,
None,
);
assert_eq!(format!("{eventually}"), "F[0, 3](y < 5)");
}
}
#[cfg(test)]
mod sparse_timestamp_tests {
use super::*;
use crate::core::{RobustnessInterval, StlOperatorTrait, TimeInterval};
use crate::operators::atomic_operators::Atomic;
use crate::ring_buffer::{RingBuffer, Step};
use crate::step;
use std::time::Duration;
fn secs(s: u64) -> Duration {
Duration::from_secs(s)
}
fn millis(ms: u64) -> Duration {
Duration::from_millis(ms)
}
fn g02_globally_f64() -> Globally<f64, RingBuffer<f64>, f64, false, false> {
let interval = TimeInterval {
start: secs(0),
end: secs(2),
};
let atomic = Atomic::<f64>::new_greater_than("x", 3.0);
Globally::new(interval, Box::new(atomic), None, None)
}
fn g02_globally_rosi()
-> Globally<f64, RingBuffer<RobustnessInterval>, RobustnessInterval, true, true> {
let interval = TimeInterval {
start: secs(0),
end: secs(2),
};
let atomic = Atomic::<RobustnessInterval>::new_greater_than("x", 3.0);
Globally::new(interval, Box::new(atomic), None, None)
}
fn g02_globally_eager_qual() -> Globally<f64, RingBuffer<bool>, bool, true, false> {
let interval = TimeInterval {
start: secs(0),
end: secs(2),
};
let atomic = Atomic::<bool>::new_greater_than("x", 3.0);
Globally::new(interval, Box::new(atomic), None, None)
}
fn sparse_steps() -> Vec<Step<f64>> {
vec![
step!("x", 100.0, secs(0)),
step!("x", 15.0, secs(1)),
step!("x", 16.0, secs(2)),
step!("x", 2.0, secs(5)),
step!("x", 2.0, secs(10)),
]
}
fn find_output_secs<Y>(outputs: &[Step<Y>], ts: u64) -> Y
where
Y: Copy,
{
outputs
.iter()
.find(|s| s.timestamp == secs(ts))
.unwrap_or_else(|| panic!("no output for t_eval={ts}"))
.value
}
fn find_output_millis<Y>(outputs: &[Step<Y>], ts: u64) -> Y
where
Y: Copy,
{
outputs
.iter()
.find(|s| s.timestamp == millis(ts))
.unwrap_or_else(|| panic!("no output for t_eval={ts}"))
.value
}
#[test]
fn globally_sparse_timestamps() {
let mut op_f64 = g02_globally_f64();
let mut op_rosi = g02_globally_rosi();
let mut op_eager_qual = g02_globally_eager_qual();
for step in &sparse_steps() {
let outputs_f64 = op_f64.update(step);
let outputs_rosi = op_rosi.update(step);
let outputs_eager_qual = op_eager_qual.update(step);
match step.timestamp.as_secs() {
0 => {
assert!(
outputs_f64.is_empty() && outputs_eager_qual.is_empty(),
"t_eval={} expected no output, got {:?}",
step.timestamp.as_secs(),
outputs_f64
);
assert!(
outputs_rosi.len() == 1,
"t_eval={} expected no output, got {:?}",
step.timestamp.as_secs(),
outputs_rosi
);
}
1 => {
assert!(
outputs_f64.is_empty() && outputs_eager_qual.is_empty(),
"t_eval={} expected no output, got {:?}",
step.timestamp.as_secs(),
outputs_f64
);
assert!(
outputs_rosi.len() == 2,
"t_eval={} expected no output, got {:?}",
step.timestamp.as_secs(),
outputs_rosi
);
}
2 => {
assert!(
(find_output_secs(&outputs_f64, 0) - 12.0).abs() < 1e-9,
"t_eval=0 expected 12.0, got {}",
find_output_secs(&outputs_f64, 0)
);
let rosi_val = find_output_secs(&outputs_rosi, 0);
assert!(
rosi_val.0 == 12.0 && rosi_val.1 == 12.0,
"t_eval=0 expected ROSI bounds to contain 12.0, got {:?}",
rosi_val
);
assert!(
find_output_secs(&outputs_eager_qual, 0),
"t_eval=0 expected eager qual to be true, got {}",
find_output_secs(&outputs_eager_qual, 0)
)
}
5 => {
assert!(
(find_output_secs(&outputs_f64, 1) - 12.0).abs() < 1e-9,
"t_eval=1 expected 12.0, got {}",
find_output_secs(&outputs_f64, 1)
);
assert!(
(find_output_secs(&outputs_f64, 2) - 13.0).abs() < 1e-9,
"t_eval=2 expected 13.0, got {}",
find_output_secs(&outputs_f64, 2)
);
let rosi_val_1 = find_output_secs(&outputs_rosi, 1);
let rosi_val_2 = find_output_secs(&outputs_rosi, 2);
let rosi_val_5 = find_output_secs(&outputs_rosi, 5);
assert!(
rosi_val_1.0 == 12.0 && rosi_val_1.1 == 12.0,
"t_eval=1 expected ROSI bounds to contain 12.0, got {:?}",
rosi_val_1
);
assert!(
rosi_val_2.0 == 13.0 && rosi_val_2.1 == 13.0,
"t_eval=2 expected ROSI bounds to contain 13.0, got {:?}",
rosi_val_2
);
assert!(
find_output_secs(&outputs_eager_qual, 1),
"t_eval=1 expected eager qual to be true, got {}",
find_output_secs(&outputs_eager_qual, 1)
);
assert!(
find_output_secs(&outputs_eager_qual, 2),
"t_eval=2 expected eager qual to be true, got {}",
find_output_secs(&outputs_eager_qual, 2)
);
assert!(
!find_output_secs(&outputs_eager_qual, 5),
"t_eval=5 expected eager qual to be false, got {}",
find_output_secs(&outputs_eager_qual, 5)
);
assert!(
rosi_val_5.1 < 0.0,
"t_eval=5 expected ROSI upper bound to be negative, got {:?}",
rosi_val_5
);
}
10 => {
assert!(
(find_output_secs(&outputs_f64, 5) + 1.0).abs() < 1e-9,
"t_eval=5 expected -1.0, got {}",
find_output_secs(&outputs_f64, 5)
);
let rosi_val = find_output_secs(&outputs_rosi, 5);
assert!(
rosi_val.0 == -1.0 && rosi_val.1 == -1.0,
"t_eval=5 expected ROSI bounds to contain -1.0, got {:?}",
rosi_val
);
assert!(
!find_output_secs(&outputs_eager_qual, 10),
"t_eval=5 expected eager qual to be false, got {}",
find_output_secs(&outputs_eager_qual, 10)
);
}
_ => panic!("unexpected output at t={}", step.timestamp.as_secs()),
}
}
}
#[test]
fn overlapping_intervals() {
let interval = TimeInterval {
start: secs(0),
end: secs(2),
};
let atomic = Atomic::<f64>::new_greater_than("x", 0.0);
let mut globally = Globally::<f64, RingBuffer<f64>, f64, false, false>::new(
interval,
Box::new(atomic.clone()),
None,
None,
);
let mut eventually = Eventually::<f64, RingBuffer<f64>, f64, false, false>::new(
interval,
Box::new(atomic.clone()),
None,
None,
);
let mut globally_rosi =
Globally::<f64, RingBuffer<RobustnessInterval>, RobustnessInterval, false, true>::new(
interval,
Box::new(Atomic::<RobustnessInterval>::new_greater_than("x", 0.0)),
None,
None,
);
let mut eventually_rosi = Eventually::<
f64,
RingBuffer<RobustnessInterval>,
RobustnessInterval,
false,
true,
>::new(
interval,
Box::new(Atomic::<RobustnessInterval>::new_greater_than("x", 0.0)),
None,
None,
);
let signal_values = vec![1.0, 2.0, 3.0];
let signal_timestamps = vec![1000, 2000, 3500];
let signal: Vec<_> = signal_values
.into_iter()
.zip(signal_timestamps)
.map(|(val, ts)| step!("x", val, Duration::from_millis(ts)))
.collect();
for s in &signal {
let globally_out = globally.update(s);
let eventually_out = eventually.update(s);
let globally_rosi_out = globally_rosi.update(s);
let eventually_rosi_out = eventually_rosi.update(s);
if s.timestamp == Duration::from_millis(1000) {
assert!(
globally_out.is_empty() && eventually_out.is_empty(),
"t_eval={} expected no output, got {:?}",
s.timestamp.as_millis(),
globally_out
);
let glob_rosi_val = find_output_millis(&globally_rosi_out, 1000);
let even_rosi_val = find_output_millis(&eventually_rosi_out, 1000);
assert!(
glob_rosi_val.0 == f64::NEG_INFINITY && glob_rosi_val.1 == 1.0,
"t_eval=1000 expected G ROSI bounds to be [-inf, 1.0], got {:?}",
glob_rosi_val
);
assert!(
even_rosi_val.0 == 1.0 && even_rosi_val.1 == f64::INFINITY,
"t_eval=1000 expected F ROSI bounds to be [1.0, +inf], got {:?}",
even_rosi_val
);
assert!(
globally_rosi_out.len() == 1 && eventually_rosi_out.len() == 1,
"t_eval=1000 expected exactly one ROSI output for G and F, got {:?} and {:?}",
globally_rosi_out,
eventually_rosi_out
);
} else if s.timestamp == Duration::from_millis(2000) {
assert!(
globally_out.is_empty() && eventually_out.is_empty(),
"t_eval={} expected no output, got {:?}",
s.timestamp.as_millis(),
globally_out
);
let glob_rosi_val_1 = find_output_millis(&globally_rosi_out, 1000);
let glob_rosi_val_2 = find_output_millis(&globally_rosi_out, 2000);
let even_rosi_val_1 = find_output_millis(&eventually_rosi_out, 1000);
let even_rosi_val_2 = find_output_millis(&eventually_rosi_out, 2000);
assert!(
glob_rosi_val_1.0 == f64::NEG_INFINITY && glob_rosi_val_1.1 == 1.0,
"t_eval=1000 expected G ROSI bounds to be [-inf, 1.0], got {:?}",
glob_rosi_val_1
);
assert!(
glob_rosi_val_2.0 == f64::NEG_INFINITY && glob_rosi_val_2.1 == 2.0,
"t_eval=2000 expected G ROSI bounds to be [-inf, 2.0], got {:?}",
glob_rosi_val_2
);
assert!(
even_rosi_val_1.0 == 2.0 && even_rosi_val_1.1 == f64::INFINITY,
"t_eval=1000 expected F ROSI bounds to be [2.0, +inf], got {:?}",
even_rosi_val_1
);
assert!(
even_rosi_val_2.0 == 2.0 && even_rosi_val_2.1 == f64::INFINITY,
"t_eval=2000 expected F ROSI bounds to be [2.0, +inf], got {:?}",
even_rosi_val_2
);
assert!(
globally_rosi_out.len() == 2 && eventually_rosi_out.len() == 2,
"t_eval=2000 expected exactly two ROSI outputs for G and F, got {:?} and {:?}",
globally_rosi_out,
eventually_rosi_out
);
} else if s.timestamp == Duration::from_millis(3500) {
let glob_val = find_output_millis(&globally_out, 1000);
let ev_val = find_output_millis(&eventually_out, 1000);
assert!(
(glob_val - 1.0).abs() < 1e-9,
"t_eval=1000 expected 1.0, got {}",
glob_val
);
assert!(
(ev_val - 2.0).abs() < 1e-9,
"t_eval=1000 expected 2.0, got {}",
ev_val
);
let glob_rosi_val_1 = find_output_millis(&globally_rosi_out, 1000);
let glob_rosi_val_2 = find_output_millis(&globally_rosi_out, 2000);
let glob_rosi_val_3 = find_output_millis(&globally_rosi_out, 3500);
let even_rosi_val_1 = find_output_millis(&eventually_rosi_out, 1000);
let even_rosi_val_2 = find_output_millis(&eventually_rosi_out, 2000);
let even_rosi_val_3 = find_output_millis(&eventually_rosi_out, 3500);
assert!(
glob_rosi_val_1.0 == 1.0 && glob_rosi_val_1.1 == 1.0,
"t_eval=1000 expected G ROSI bounds to be [1.0, 1.0], got {:?}",
glob_rosi_val_1
);
assert!(
glob_rosi_val_2.0 == f64::NEG_INFINITY && glob_rosi_val_2.1 == 2.0,
"t_eval=2000 expected G ROSI bounds to be [-inf, 2.0], got {:?}",
glob_rosi_val_2
);
assert!(
glob_rosi_val_3.0 == f64::NEG_INFINITY && glob_rosi_val_3.1 == 3.0,
"t_eval=3500 expected G ROSI bounds to be [-inf, 3.0], got {:?}",
glob_rosi_val_3
);
assert!(
even_rosi_val_1.0 == 2.0 && even_rosi_val_1.1 == 2.0,
"t_eval=1000 expected F ROSI bounds to be [2.0, 2.0], got {:?}",
even_rosi_val_1
);
assert!(
even_rosi_val_2.0 == 3.0 && even_rosi_val_2.1 == f64::INFINITY,
"t_eval=2000 expected F ROSI bounds to be [3.0, +inf], got {:?}",
even_rosi_val_2
);
assert!(
even_rosi_val_3.0 == 3.0 && even_rosi_val_3.1 == f64::INFINITY,
"t_eval=3500 expected F ROSI bounds to be [3.0, +inf], got {:?}",
even_rosi_val_3
);
assert!(
globally_rosi_out.len() == 3 && eventually_rosi_out.len() == 3,
"t_eval=3500 expected exactly three ROSI outputs for G and F, got {:?} and {:?}",
globally_rosi_out,
eventually_rosi_out
);
}
}
}
}