feat(argus-semantics): complete boolean semantics
This commit is contained in:
parent
ad9afb4eba
commit
c916db3853
6 changed files with 149 additions and 48 deletions
|
|
@ -277,9 +277,13 @@ impl<T> Signal<T> {
|
|||
// We will now loop over the sync points, compare across signals and (if
|
||||
// an intersection happens) we will have to compute the intersection point
|
||||
for t in sync_points {
|
||||
let lhs = self.at(*t).expect("value must be present at given time");
|
||||
let rhs = other.at(*t).expect("values must be present at given time");
|
||||
let ord = lhs.partial_cmp(rhs).unwrap();
|
||||
let lhs = self
|
||||
.interpolate_at::<Interp>(*t)
|
||||
.unwrap_or_else(|| panic!("value must be present at given time {:?}.", t));
|
||||
let rhs = other
|
||||
.interpolate_at::<Interp>(*t)
|
||||
.unwrap_or_else(|| panic!("value must be present at given time {:?}.", t));
|
||||
let ord = lhs.partial_cmp(&rhs).unwrap();
|
||||
|
||||
// We will check for any intersections between the current sample and the
|
||||
// previous one before we push the current sample time
|
||||
|
|
@ -289,12 +293,20 @@ impl<T> Signal<T> {
|
|||
if let (Less, Greater) | (Greater, Less) = (last, ord) {
|
||||
// Find the point of intersection between the points.
|
||||
let a = utils::Neighborhood {
|
||||
first: self.at(tm1).cloned().map(|value| Sample { time: tm1, value }),
|
||||
second: self.at(*t).cloned().map(|value| Sample { time: *t, value }),
|
||||
first: self
|
||||
.interpolate_at::<Interp>(tm1)
|
||||
.map(|value| Sample { time: tm1, value }),
|
||||
second: self
|
||||
.interpolate_at::<Interp>(*t)
|
||||
.map(|value| Sample { time: *t, value }),
|
||||
};
|
||||
let b = utils::Neighborhood {
|
||||
first: other.at(tm1).cloned().map(|value| Sample { time: tm1, value }),
|
||||
second: other.at(*t).cloned().map(|value| Sample { time: *t, value }),
|
||||
first: other
|
||||
.interpolate_at::<Interp>(tm1)
|
||||
.map(|value| Sample { time: tm1, value }),
|
||||
second: other
|
||||
.interpolate_at::<Interp>(*t)
|
||||
.map(|value| Sample { time: *t, value }),
|
||||
};
|
||||
let intersect = Interp::find_intersection(&a, &b);
|
||||
return_points.push(intersect.time);
|
||||
|
|
|
|||
|
|
@ -113,8 +113,8 @@ macro_rules! interpolate_for_num {
|
|||
|
||||
// We need to do stable linear interpolation
|
||||
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0811r3.html
|
||||
let a: f64 = cast(first.value).unwrap();
|
||||
let b: f64 = cast(second.value).unwrap();
|
||||
let a: f64 = cast(first.value).unwrap_or_else(|| panic!("unable to cast {:?} to f64", first.value));
|
||||
let b: f64 = cast(second.value).unwrap_or_else(|| panic!("unable to cast {:?} to f64", second.value));
|
||||
|
||||
// Set t to a value in [0, 1]
|
||||
let t = (at - t1) / (t2 - t1);
|
||||
|
|
@ -137,20 +137,20 @@ macro_rules! interpolate_for_num {
|
|||
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
|
||||
use num_traits::cast;
|
||||
|
||||
let Sample { time: t1, value: y1 } = a.first.unwrap();
|
||||
let Sample { time: t2, value: y2 } = a.second.unwrap();
|
||||
let Sample { time: t3, value: y3 } = b.first.unwrap();
|
||||
let Sample { time: t4, value: y4 } = b.second.unwrap();
|
||||
let Sample { time: t1, value: y1 } = (a.first).unwrap();
|
||||
let Sample { time: t2, value: y2 } = (a.second).unwrap();
|
||||
let Sample { time: t3, value: y3 } = (b.first).unwrap();
|
||||
let Sample { time: t4, value: y4 } = (b.second).unwrap();
|
||||
|
||||
let t1 = t1.as_secs_f64();
|
||||
let t2 = t2.as_secs_f64();
|
||||
let t3 = t3.as_secs_f64();
|
||||
let t4 = t4.as_secs_f64();
|
||||
|
||||
let y1: f64 = cast(y1).unwrap();
|
||||
let y2: f64 = cast(y2).unwrap();
|
||||
let y3: f64 = cast(y3).unwrap();
|
||||
let y4: f64 = cast(y4).unwrap();
|
||||
let y1: f64 = cast(y1).unwrap_or_else(|| panic!("unable to cast {:?} to f64", y1));
|
||||
let y2: f64 = cast(y2).unwrap_or_else(|| panic!("unable to cast {:?} to f64", y2));
|
||||
let y3: f64 = cast(y3).unwrap_or_else(|| panic!("unable to cast {:?} to f64", y3));
|
||||
let y4: f64 = cast(y4).unwrap_or_else(|| panic!("unable to cast {:?} to f64", y4));
|
||||
|
||||
let denom = ((t1 - t2) * (y3 - y4)) - ((y1 - y2) * (t3 - t4));
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -8,4 +8,5 @@ pub mod semantics;
|
|||
pub mod traits;
|
||||
pub mod utils;
|
||||
|
||||
pub use semantics::{BooleanSemantics, QuantitativeSemantics};
|
||||
pub use traits::Trace;
|
||||
|
|
|
|||
|
|
@ -124,9 +124,28 @@ fn compute_always(signal: Signal<bool>, interval: &Interval) -> ArgusResult<Sign
|
|||
reason: "interval is either empty or singleton",
|
||||
});
|
||||
}
|
||||
let z1 = !signal;
|
||||
let z2 = compute_eventually(z1, interval)?;
|
||||
Ok(!z2)
|
||||
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, ..]`.
|
||||
|
|
@ -147,7 +166,7 @@ fn compute_untimed_always(signal: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
|||
};
|
||||
// Compute the & in a expanding window fashion from the back
|
||||
for i in (0..(time_points.len() - 1)).rev() {
|
||||
values[i] &= values[i + 1];
|
||||
values[i] = values[i + 1].min(values[i]);
|
||||
}
|
||||
Ok(Signal::Sampled { values, time_points })
|
||||
}
|
||||
|
|
@ -237,7 +256,7 @@ fn compute_untimed_eventually(signal: Signal<bool>) -> ArgusResult<Signal<bool>>
|
|||
};
|
||||
// Compute the | in a expanding window fashion from the back
|
||||
for i in (0..(time_points.len() - 1)).rev() {
|
||||
values[i] |= values[i + 1];
|
||||
values[i] = values[i + 1].max(values[i]);
|
||||
}
|
||||
Ok(Signal::Sampled { values, time_points })
|
||||
}
|
||||
|
|
@ -247,27 +266,7 @@ fn compute_until(lhs: Signal<bool>, rhs: Signal<bool>, interval: &Interval) -> A
|
|||
let ret = match (lhs, rhs) {
|
||||
// If either signals are empty, return empty
|
||||
(sig @ Signal::Empty, _) | (_, sig @ Signal::Empty) => sig,
|
||||
(_, Signal::Constant { value: false }) => Signal::const_false(),
|
||||
(_, Signal::Constant { value: true }) => Signal::const_true(),
|
||||
|
||||
(Signal::Constant { value: true }, sig) => {
|
||||
// This is the identity for eventually
|
||||
compute_eventually(sig, interval)?
|
||||
}
|
||||
(Signal::Constant { value: false }, sig) => {
|
||||
// This is the identity for next
|
||||
compute_next(sig)?
|
||||
}
|
||||
(
|
||||
lhs @ Signal::Sampled {
|
||||
values: _,
|
||||
time_points: _,
|
||||
},
|
||||
rhs @ Signal::Sampled {
|
||||
values: _,
|
||||
time_points: _,
|
||||
},
|
||||
) => {
|
||||
(lhs, rhs) => {
|
||||
use Bound::*;
|
||||
if interval.is_untimed() {
|
||||
compute_untimed_until(lhs, rhs)?
|
||||
|
|
@ -309,21 +308,39 @@ fn compute_timed_until(
|
|||
// 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 & &unt_a_inf)
|
||||
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_untimed_always(untimed_until)
|
||||
compute_timed_always(untimed_until, Duration::ZERO, Some(a))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute untimed until
|
||||
fn compute_untimed_until(lhs: Signal<bool>, rhs: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
||||
let sync_points = lhs.sync_with_intersection::<Linear>(&rhs);
|
||||
todo!()
|
||||
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 = false;
|
||||
|
||||
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 = (v1 && v2) || (v1 && next);
|
||||
if z == next && i < (expected_len - 2) {
|
||||
ret_samples.pop();
|
||||
}
|
||||
ret_samples.push((t, z));
|
||||
next = z;
|
||||
}
|
||||
|
||||
Signal::<bool>::try_from_iter(ret_samples.into_iter().rev())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -567,7 +567,7 @@ mod tests {
|
|||
|
||||
let trace = MyTrace { signals };
|
||||
|
||||
let rob = dbg!(QuantitativeSemantics::eval(&spec, &trace).unwrap());
|
||||
let rob = 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),
|
||||
|
|
@ -613,4 +613,74 @@ mod tests {
|
|||
assert_equal(&rob, &expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbounded_until() {
|
||||
let mut ctx = ExprBuilder::new();
|
||||
|
||||
let a = ctx.int_var("a".to_owned()).unwrap();
|
||||
let b = ctx.int_var("b".to_owned()).unwrap();
|
||||
let lhs = ctx.make_gt(a, ctx.int_const(0));
|
||||
let rhs = ctx.make_gt(b, ctx.int_const(0));
|
||||
let spec = ctx.make_until(lhs, rhs);
|
||||
|
||||
{
|
||||
let signals = HashMap::from_iter(vec![
|
||||
(
|
||||
"a".to_owned(),
|
||||
Box::new(Signal::<i64>::from_iter(vec![
|
||||
(Duration::from_secs(0), 2),
|
||||
(Duration::from_secs(5), 2),
|
||||
])) as Box<dyn AnySignal>,
|
||||
),
|
||||
(
|
||||
"b".to_owned(),
|
||||
Box::new(Signal::<i64>::from_iter(vec![
|
||||
(Duration::from_secs(0), 4),
|
||||
(Duration::from_secs(5), 4),
|
||||
])) as Box<dyn AnySignal>,
|
||||
),
|
||||
]);
|
||||
|
||||
let trace = MyTrace { signals };
|
||||
let rob = QuantitativeSemantics::eval(&spec, &trace).unwrap();
|
||||
|
||||
let expected = Signal::from_iter(vec![(Duration::from_secs(0), 2), (Duration::from_secs(5), 2)])
|
||||
.num_cast::<f64>()
|
||||
.unwrap();
|
||||
|
||||
assert_equal(&rob, &expected);
|
||||
}
|
||||
|
||||
{
|
||||
let signals = HashMap::from_iter(vec![
|
||||
(
|
||||
"a".to_owned(),
|
||||
Box::new(Signal::<i64>::from_iter(vec![
|
||||
(Duration::from_secs_f64(1.0), 1),
|
||||
(Duration::from_secs_f64(3.5), 7),
|
||||
(Duration::from_secs_f64(4.7), 3),
|
||||
(Duration::from_secs_f64(5.3), 5),
|
||||
(Duration::from_secs_f64(6.2), 1),
|
||||
])) as Box<dyn AnySignal>,
|
||||
),
|
||||
(
|
||||
"b".to_owned(),
|
||||
Box::new(Signal::<i64>::from_iter(vec![
|
||||
(Duration::from_secs(4), 2),
|
||||
(Duration::from_secs(6), 3),
|
||||
])) as Box<dyn AnySignal>,
|
||||
),
|
||||
]);
|
||||
|
||||
let trace = MyTrace { signals };
|
||||
let rob = QuantitativeSemantics::eval(&spec, &trace).unwrap();
|
||||
|
||||
let expected = Signal::from_iter(vec![(Duration::from_secs(4), 3), (Duration::from_secs(6), 3)])
|
||||
.num_cast::<f64>()
|
||||
.unwrap();
|
||||
|
||||
assert_equal(&rob, &expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue