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
|
// We will now loop over the sync points, compare across signals and (if
|
||||||
// an intersection happens) we will have to compute the intersection point
|
// an intersection happens) we will have to compute the intersection point
|
||||||
for t in sync_points {
|
for t in sync_points {
|
||||||
let lhs = self.at(*t).expect("value must be present at given time");
|
let lhs = self
|
||||||
let rhs = other.at(*t).expect("values must be present at given time");
|
.interpolate_at::<Interp>(*t)
|
||||||
let ord = lhs.partial_cmp(rhs).unwrap();
|
.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
|
// We will check for any intersections between the current sample and the
|
||||||
// previous one before we push the current sample time
|
// 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) {
|
if let (Less, Greater) | (Greater, Less) = (last, ord) {
|
||||||
// Find the point of intersection between the points.
|
// Find the point of intersection between the points.
|
||||||
let a = utils::Neighborhood {
|
let a = utils::Neighborhood {
|
||||||
first: self.at(tm1).cloned().map(|value| Sample { time: tm1, value }),
|
first: self
|
||||||
second: self.at(*t).cloned().map(|value| Sample { time: *t, value }),
|
.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 {
|
let b = utils::Neighborhood {
|
||||||
first: other.at(tm1).cloned().map(|value| Sample { time: tm1, value }),
|
first: other
|
||||||
second: other.at(*t).cloned().map(|value| Sample { time: *t, value }),
|
.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);
|
let intersect = Interp::find_intersection(&a, &b);
|
||||||
return_points.push(intersect.time);
|
return_points.push(intersect.time);
|
||||||
|
|
|
||||||
|
|
@ -113,8 +113,8 @@ macro_rules! interpolate_for_num {
|
||||||
|
|
||||||
// We need to do stable linear interpolation
|
// We need to do stable linear interpolation
|
||||||
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0811r3.html
|
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0811r3.html
|
||||||
let a: f64 = cast(first.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();
|
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]
|
// Set t to a value in [0, 1]
|
||||||
let t = (at - t1) / (t2 - t1);
|
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
|
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
|
||||||
use num_traits::cast;
|
use num_traits::cast;
|
||||||
|
|
||||||
let Sample { time: t1, value: y1 } = a.first.unwrap();
|
let Sample { time: t1, value: y1 } = (a.first).unwrap();
|
||||||
let Sample { time: t2, value: y2 } = a.second.unwrap();
|
let Sample { time: t2, value: y2 } = (a.second).unwrap();
|
||||||
let Sample { time: t3, value: y3 } = b.first.unwrap();
|
let Sample { time: t3, value: y3 } = (b.first).unwrap();
|
||||||
let Sample { time: t4, value: y4 } = b.second.unwrap();
|
let Sample { time: t4, value: y4 } = (b.second).unwrap();
|
||||||
|
|
||||||
let t1 = t1.as_secs_f64();
|
let t1 = t1.as_secs_f64();
|
||||||
let t2 = t2.as_secs_f64();
|
let t2 = t2.as_secs_f64();
|
||||||
let t3 = t3.as_secs_f64();
|
let t3 = t3.as_secs_f64();
|
||||||
let t4 = t4.as_secs_f64();
|
let t4 = t4.as_secs_f64();
|
||||||
|
|
||||||
let y1: f64 = cast(y1).unwrap();
|
let y1: f64 = cast(y1).unwrap_or_else(|| panic!("unable to cast {:?} to f64", y1));
|
||||||
let y2: f64 = cast(y2).unwrap();
|
let y2: f64 = cast(y2).unwrap_or_else(|| panic!("unable to cast {:?} to f64", y2));
|
||||||
let y3: f64 = cast(y3).unwrap();
|
let y3: f64 = cast(y3).unwrap_or_else(|| panic!("unable to cast {:?} to f64", y3));
|
||||||
let y4: f64 = cast(y4).unwrap();
|
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));
|
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 traits;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
pub use semantics::{BooleanSemantics, QuantitativeSemantics};
|
||||||
pub use traits::Trace;
|
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",
|
reason: "interval is either empty or singleton",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let z1 = !signal;
|
let ret = match signal {
|
||||||
let z2 = compute_eventually(z1, interval)?;
|
// if signal is empty or constant, return the signal itself.
|
||||||
Ok(!z2)
|
// 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, ..]`.
|
/// 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
|
// Compute the & in a expanding window fashion from the back
|
||||||
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].min(values[i]);
|
||||||
}
|
}
|
||||||
Ok(Signal::Sampled { values, time_points })
|
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
|
// Compute the | in a expanding window fashion from the back
|
||||||
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].max(values[i]);
|
||||||
}
|
}
|
||||||
Ok(Signal::Sampled { values, time_points })
|
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) {
|
let ret = match (lhs, rhs) {
|
||||||
// If either signals are empty, return empty
|
// If either signals are empty, return empty
|
||||||
(sig @ Signal::Empty, _) | (_, sig @ Signal::Empty) => sig,
|
(sig @ Signal::Empty, _) | (_, sig @ Signal::Empty) => sig,
|
||||||
(_, Signal::Constant { value: false }) => Signal::const_false(),
|
(lhs, rhs) => {
|
||||||
(_, 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: _,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
use Bound::*;
|
use Bound::*;
|
||||||
if interval.is_untimed() {
|
if interval.is_untimed() {
|
||||||
compute_untimed_until(lhs, rhs)?
|
compute_untimed_until(lhs, rhs)?
|
||||||
|
|
@ -309,21 +308,39 @@ fn compute_timed_until(
|
||||||
// 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
|
||||||
Ok(&ev_a_b_rhs & &unt_a_inf)
|
Ok(ev_a_b_rhs.min(&unt_a_inf))
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
assert_ne!(a, Duration::ZERO, "untimed case wasn't handled for Until");
|
||||||
// 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_timed_always(untimed_until, Duration::ZERO, Some(a))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute untimed until
|
/// Compute untimed until
|
||||||
fn compute_untimed_until(lhs: Signal<bool>, rhs: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
fn compute_untimed_until(lhs: Signal<bool>, rhs: Signal<bool>) -> ArgusResult<Signal<bool>> {
|
||||||
let sync_points = lhs.sync_with_intersection::<Linear>(&rhs);
|
let sync_points = lhs.sync_with_intersection::<Linear>(&rhs).unwrap();
|
||||||
todo!()
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -567,7 +567,7 @@ mod tests {
|
||||||
|
|
||||||
let trace = MyTrace { signals };
|
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![
|
let expected = Signal::from_iter(vec![
|
||||||
(Duration::from_secs_f64(0.0), 0.0 - 1.3),
|
(Duration::from_secs_f64(0.0), 0.0 - 1.3),
|
||||||
(Duration::from_secs_f64(0.7), 0.0 - 3.0),
|
(Duration::from_secs_f64(0.7), 0.0 - 3.0),
|
||||||
|
|
@ -613,4 +613,74 @@ mod tests {
|
||||||
assert_equal(&rob, &expected);
|
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