continue refactoring to optimize for mtl
This commit is contained in:
parent
5fd66cfd2c
commit
98824c9ba1
21 changed files with 393 additions and 467 deletions
7
mtl/__init__.py
Normal file
7
mtl/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# flake8: noqa
|
||||
from mtl.ast import TOP, BOT
|
||||
from mtl.ast import (Interval, Or, And, F, G, Neg,
|
||||
AtomicPred, Until, Next)
|
||||
from mtl.parser import parse
|
||||
from mtl.fastboolean_eval import pointwise_sat
|
||||
from mtl.utils import alw, env, andf, orf
|
||||
|
|
@ -5,7 +5,7 @@ from functools import lru_cache
|
|||
import funcy as fn
|
||||
from lenses import lens, bind
|
||||
|
||||
import stl
|
||||
import mtl
|
||||
|
||||
|
||||
def flatten_binary(phi, op, dropT, shortT):
|
||||
|
|
@ -49,7 +49,7 @@ class AST(object):
|
|||
return phi
|
||||
|
||||
def __call__(self, trace, time=0):
|
||||
return stl.pointwise_sat(self)(trace, time)
|
||||
return mtl.pointwise_sat(self)(trace, time)
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
|
|
@ -145,7 +145,7 @@ class Interval(namedtuple('I', ['lower', 'upper'])):
|
|||
return f"[{self.lower},{self.upper}]"
|
||||
|
||||
|
||||
class NaryOpSTL(namedtuple('NaryOp', ['args']), AST):
|
||||
class NaryOpMTL(namedtuple('NaryOp', ['args']), AST):
|
||||
__slots__ = ()
|
||||
|
||||
OP = "?"
|
||||
|
|
@ -158,7 +158,7 @@ class NaryOpSTL(namedtuple('NaryOp', ['args']), AST):
|
|||
return tuple(self.args)
|
||||
|
||||
|
||||
class Or(NaryOpSTL):
|
||||
class Or(NaryOpMTL):
|
||||
__slots__ = ()
|
||||
|
||||
OP = "|"
|
||||
|
|
@ -168,7 +168,7 @@ class Or(NaryOpSTL):
|
|||
return hash(repr(self))
|
||||
|
||||
|
||||
class And(NaryOpSTL):
|
||||
class And(NaryOpMTL):
|
||||
__slots__ = ()
|
||||
|
||||
OP = "&"
|
||||
|
|
@ -7,9 +7,9 @@ from functools import singledispatch
|
|||
import funcy as fn
|
||||
import traces
|
||||
|
||||
import stl
|
||||
import stl.ast
|
||||
from stl.utils import const_trace, andf, orf
|
||||
import mtl
|
||||
import mtl.ast
|
||||
from mtl.utils import const_trace, andf, orf
|
||||
|
||||
TRUE_TRACE = const_trace(True)
|
||||
FALSE_TRACE = const_trace(False)
|
||||
|
|
@ -24,15 +24,15 @@ def negate_trace(x):
|
|||
def pointwise_sat(phi, dt=0.1):
|
||||
ap_names = [z.id for z in phi.atomic_predicates]
|
||||
|
||||
def _eval_stl(x, t=0):
|
||||
def _eval_mtl(x, t=0):
|
||||
evaluated = fn.project(x, ap_names)
|
||||
return bool(eval_stl(phi, dt)(evaluated)[t])
|
||||
return bool(eval_mtl(phi, dt)(evaluated)[t])
|
||||
|
||||
return _eval_stl
|
||||
return _eval_mtl
|
||||
|
||||
|
||||
@singledispatch
|
||||
def eval_stl(phi, dt):
|
||||
def eval_mtl(phi, dt):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
|
|
@ -42,9 +42,9 @@ def or_traces(xs):
|
|||
return out
|
||||
|
||||
|
||||
@eval_stl.register(stl.Or)
|
||||
def eval_stl_or(phi, dt):
|
||||
fs = [eval_stl(arg, dt) for arg in phi.args]
|
||||
@eval_mtl.register(mtl.Or)
|
||||
def eval_mtl_or(phi, dt):
|
||||
fs = [eval_mtl(arg, dt) for arg in phi.args]
|
||||
|
||||
def _eval(x):
|
||||
out = or_traces([f(x) for f in fs])
|
||||
|
|
@ -60,9 +60,9 @@ def and_traces(xs):
|
|||
return out
|
||||
|
||||
|
||||
@eval_stl.register(stl.And)
|
||||
def eval_stl_and(phi, dt):
|
||||
fs = [eval_stl(arg, dt) for arg in phi.args]
|
||||
@eval_mtl.register(mtl.And)
|
||||
def eval_mtl_and(phi, dt):
|
||||
fs = [eval_mtl(arg, dt) for arg in phi.args]
|
||||
|
||||
def _eval(x):
|
||||
out = and_traces([f(x) for f in fs])
|
||||
|
|
@ -80,9 +80,9 @@ def apply_until(y):
|
|||
phi2_next = phi2
|
||||
|
||||
|
||||
@eval_stl.register(stl.Until)
|
||||
def eval_stl_until(phi, dt):
|
||||
f1, f2 = eval_stl(phi.arg1, dt), eval_stl(phi.arg2, dt)
|
||||
@eval_mtl.register(mtl.Until)
|
||||
def eval_mtl_until(phi, dt):
|
||||
f1, f2 = eval_mtl(phi.arg1, dt), eval_mtl(phi.arg2, dt)
|
||||
|
||||
def _eval(x):
|
||||
y1, y2 = f1(x), f2(x)
|
||||
|
|
@ -95,15 +95,15 @@ def eval_stl_until(phi, dt):
|
|||
return _eval
|
||||
|
||||
|
||||
@eval_stl.register(stl.F)
|
||||
def eval_stl_f(phi, dt):
|
||||
phi = ~stl.G(phi.interval, ~phi.arg)
|
||||
return eval_stl(phi, dt)
|
||||
@eval_mtl.register(mtl.F)
|
||||
def eval_mtl_f(phi, dt):
|
||||
phi = ~mtl.G(phi.interval, ~phi.arg)
|
||||
return eval_mtl(phi, dt)
|
||||
|
||||
|
||||
@eval_stl.register(stl.G)
|
||||
def eval_stl_g(phi, dt):
|
||||
f = eval_stl(phi.arg, dt)
|
||||
@eval_mtl.register(mtl.G)
|
||||
def eval_mtl_g(phi, dt):
|
||||
f = eval_mtl(phi.arg, dt)
|
||||
a, b = phi.interval
|
||||
if b < a:
|
||||
return lambda _: TRUE_TRACE
|
||||
|
|
@ -139,9 +139,9 @@ def eval_stl_g(phi, dt):
|
|||
return _eval
|
||||
|
||||
|
||||
@eval_stl.register(stl.Neg)
|
||||
def eval_stl_neg(phi, dt):
|
||||
f = eval_stl(phi.arg, dt)
|
||||
@eval_mtl.register(mtl.Neg)
|
||||
def eval_mtl_neg(phi, dt):
|
||||
f = eval_mtl(phi.arg, dt)
|
||||
|
||||
def _eval(x):
|
||||
out = negate_trace(f(x))
|
||||
|
|
@ -151,9 +151,9 @@ def eval_stl_neg(phi, dt):
|
|||
return _eval
|
||||
|
||||
|
||||
@eval_stl.register(stl.ast.Next)
|
||||
def eval_stl_next(phi, dt):
|
||||
f = eval_stl(phi.arg, dt)
|
||||
@eval_mtl.register(mtl.ast.Next)
|
||||
def eval_mtl_next(phi, dt):
|
||||
f = eval_mtl(phi.arg, dt)
|
||||
|
||||
def _eval(x):
|
||||
y = f(x)
|
||||
|
|
@ -166,8 +166,8 @@ def eval_stl_next(phi, dt):
|
|||
return _eval
|
||||
|
||||
|
||||
@eval_stl.register(stl.AtomicPred)
|
||||
def eval_stl_ap(phi, _):
|
||||
@eval_mtl.register(mtl.AtomicPred)
|
||||
def eval_mtl_ap(phi, _):
|
||||
def _eval(x):
|
||||
out = x[str(phi.id)]
|
||||
out.compact()
|
||||
|
|
@ -177,11 +177,11 @@ def eval_stl_ap(phi, _):
|
|||
return _eval
|
||||
|
||||
|
||||
@eval_stl.register(type(stl.TOP))
|
||||
def eval_stl_top(_, _1):
|
||||
@eval_mtl.register(type(mtl.TOP))
|
||||
def eval_mtl_top(_, _1):
|
||||
return lambda *_: TRUE_TRACE
|
||||
|
||||
|
||||
@eval_stl.register(type(stl.BOT))
|
||||
def eval_stl_bot(_, _1):
|
||||
@eval_mtl.register(type(mtl.BOT))
|
||||
def eval_mtl_bot(_, _1):
|
||||
return lambda *_: FALSE_TRACE
|
||||
|
|
@ -4,7 +4,7 @@ from operator import and_, or_
|
|||
import funcy as fn
|
||||
from bitarray import bitarray
|
||||
|
||||
import stl.ast
|
||||
import mtl.ast
|
||||
|
||||
oo = float('inf')
|
||||
|
||||
|
|
@ -25,35 +25,35 @@ def get_times(x, tau, lo, hi):
|
|||
return sorted(set(fn.pluck(0, all_times)))
|
||||
|
||||
|
||||
def pointwise_sat(stl):
|
||||
f = pointwise_satf(stl)
|
||||
def pointwise_sat(mtl):
|
||||
f = pointwise_satf(mtl)
|
||||
return lambda x, t: bool(int(f(x, [t]).to01()))
|
||||
|
||||
|
||||
@singledispatch
|
||||
def pointwise_satf(stl):
|
||||
def pointwise_satf(mtl):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def bool_op(stl, conjunction=False):
|
||||
def bool_op(mtl, conjunction=False):
|
||||
binop = and_ if conjunction else or_
|
||||
fs = [pointwise_satf(arg) for arg in stl.args]
|
||||
fs = [pointwise_satf(arg) for arg in mtl.args]
|
||||
return lambda x, t: reduce(binop, (f(x, t) for f in fs))
|
||||
|
||||
|
||||
@pointwise_satf.register(stl.Or)
|
||||
def pointwise_satf_or(stl):
|
||||
return bool_op(stl, conjunction=False)
|
||||
@pointwise_satf.register(mtl.Or)
|
||||
def pointwise_satf_or(mtl):
|
||||
return bool_op(mtl, conjunction=False)
|
||||
|
||||
|
||||
@pointwise_satf.register(stl.And)
|
||||
def pointwise_satf_and(stl):
|
||||
return bool_op(stl, conjunction=True)
|
||||
@pointwise_satf.register(mtl.And)
|
||||
def pointwise_satf_and(mtl):
|
||||
return bool_op(mtl, conjunction=True)
|
||||
|
||||
|
||||
def temporal_op(stl, lo, hi, conjunction=False):
|
||||
def temporal_op(mtl, lo, hi, conjunction=False):
|
||||
fold = bitarray.all if conjunction else bitarray.any
|
||||
f = pointwise_satf(stl.arg)
|
||||
f = pointwise_satf(mtl.arg)
|
||||
|
||||
def sat_comp(x, t):
|
||||
return bitarray(fold(f(x, get_times(x, tau, lo, hi))) for tau in t)
|
||||
|
|
@ -61,38 +61,38 @@ def temporal_op(stl, lo, hi, conjunction=False):
|
|||
return sat_comp
|
||||
|
||||
|
||||
@pointwise_satf.register(stl.F)
|
||||
def pointwise_satf_f(stl):
|
||||
lo, hi = stl.interval
|
||||
return temporal_op(stl, lo, hi, conjunction=False)
|
||||
@pointwise_satf.register(mtl.F)
|
||||
def pointwise_satf_f(mtl):
|
||||
lo, hi = mtl.interval
|
||||
return temporal_op(mtl, lo, hi, conjunction=False)
|
||||
|
||||
|
||||
@pointwise_satf.register(stl.G)
|
||||
def pointwise_satf_g(stl):
|
||||
lo, hi = stl.interval
|
||||
return temporal_op(stl, lo, hi, conjunction=True)
|
||||
@pointwise_satf.register(mtl.G)
|
||||
def pointwise_satf_g(mtl):
|
||||
lo, hi = mtl.interval
|
||||
return temporal_op(mtl, lo, hi, conjunction=True)
|
||||
|
||||
|
||||
@pointwise_satf.register(stl.Neg)
|
||||
def pointwise_satf_neg(stl):
|
||||
return lambda x, t: ~pointwise_satf(stl.arg)(x, t)
|
||||
@pointwise_satf.register(mtl.Neg)
|
||||
def pointwise_satf_neg(mtl):
|
||||
return lambda x, t: ~pointwise_satf(mtl.arg)(x, t)
|
||||
|
||||
|
||||
@pointwise_satf.register(stl.AtomicPred)
|
||||
@pointwise_satf.register(mtl.AtomicPred)
|
||||
def pointwise_satf_(phi):
|
||||
return lambda x, t: bitarray(x[str(phi.id)][tau] for tau in t)
|
||||
|
||||
|
||||
@pointwise_satf.register(stl.Until)
|
||||
@pointwise_satf.register(mtl.Until)
|
||||
def pointwise_satf_until(phi):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@pointwise_satf.register(type(stl.TOP))
|
||||
@pointwise_satf.register(type(mtl.TOP))
|
||||
def pointwise_satf_top(_):
|
||||
return lambda _, t: bitarray([True] * len(t))
|
||||
|
||||
|
||||
@pointwise_satf.register(type(stl.BOT))
|
||||
@pointwise_satf.register(type(mtl.BOT))
|
||||
def pointwise_satf_bot(_):
|
||||
return lambda _, t: bitarray([False] * len(t))
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import hypothesis.strategies as st
|
||||
from hypothesis_cfg import ContextFreeGrammarStrategy
|
||||
|
||||
import stl
|
||||
import mtl
|
||||
|
||||
GRAMMAR = {
|
||||
'phi': (
|
||||
|
|
@ -18,7 +18,7 @@ GRAMMAR = {
|
|||
'AP': (('ap1', ), ('ap2', ), ('ap3', ), ('ap4', ), ('ap5', )),
|
||||
}
|
||||
|
||||
SignalTemporalLogicStrategy = st.builds(
|
||||
lambda term: stl.parse(''.join(term)),
|
||||
MetricTemporalLogicStrategy = st.builds(
|
||||
lambda term: mtl.parse(''.join(term)),
|
||||
ContextFreeGrammarStrategy(GRAMMAR, max_length=14, start='phi')
|
||||
)
|
||||
|
|
@ -3,10 +3,10 @@ import operator as op
|
|||
from functools import partialmethod, reduce
|
||||
|
||||
from parsimonious import Grammar, NodeVisitor
|
||||
from stl import ast
|
||||
from stl.utils import iff, implies, xor, timed_until
|
||||
from mtl import ast
|
||||
from mtl.utils import iff, implies, xor, timed_until
|
||||
|
||||
STL_GRAMMAR = Grammar(u'''
|
||||
MTL_GRAMMAR = Grammar(u'''
|
||||
phi = (neg / paren_phi / next / bot / top
|
||||
/ xor_outer / iff_outer / implies_outer / and_outer / or_outer
|
||||
/ timed_until / until / g / f / AP)
|
||||
|
|
@ -53,7 +53,7 @@ EOL = "\\n"
|
|||
oo = float('inf')
|
||||
|
||||
|
||||
class STLVisitor(NodeVisitor):
|
||||
class MTLVisitor(NodeVisitor):
|
||||
def __init__(self, H=oo):
|
||||
super().__init__()
|
||||
self.default_interval = ast.Interval(0.0, H)
|
||||
|
|
@ -137,5 +137,5 @@ class STLVisitor(NodeVisitor):
|
|||
return ast.Next(children[2])
|
||||
|
||||
|
||||
def parse(stl_str: str, rule: str = "phi", H=oo) -> "STL":
|
||||
return STLVisitor(H).visit(STL_GRAMMAR[rule].parse(stl_str))
|
||||
def parse(mtl_str: str, rule: str = "phi", H=oo) -> "MTL":
|
||||
return MTLVisitor(H).visit(MTL_GRAMMAR[rule].parse(mtl_str))
|
||||
31
mtl/test_ast.py
Normal file
31
mtl/test_ast.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import mtl
|
||||
from mtl.hypothesis import MetricTemporalLogicStrategy
|
||||
|
||||
from hypothesis import given
|
||||
|
||||
|
||||
@given(MetricTemporalLogicStrategy)
|
||||
def test_identities(phi):
|
||||
assert mtl.TOP == mtl.TOP | phi
|
||||
assert mtl.BOT == mtl.BOT & phi
|
||||
assert mtl.TOP == phi | mtl.TOP
|
||||
assert mtl.BOT == phi & mtl.BOT
|
||||
assert phi == phi & mtl.TOP
|
||||
assert phi == phi | mtl.BOT
|
||||
assert mtl.TOP == mtl.TOP & mtl.TOP
|
||||
assert mtl.BOT == mtl.BOT | mtl.BOT
|
||||
assert mtl.TOP == mtl.TOP | mtl.BOT
|
||||
assert mtl.BOT == mtl.TOP & mtl.BOT
|
||||
assert ~mtl.BOT == mtl.TOP
|
||||
assert ~mtl.TOP == mtl.BOT
|
||||
assert ~~mtl.BOT == mtl.BOT
|
||||
assert ~~mtl.TOP == mtl.TOP
|
||||
assert (phi & phi) & phi == phi & (phi & phi)
|
||||
assert (phi | phi) | phi == phi | (phi | phi)
|
||||
assert ~~phi == phi
|
||||
|
||||
|
||||
def test_walk():
|
||||
phi = mtl.parse(
|
||||
'(([ ][0, 1] ap1 & < >[1,2] ap2) | (@ap1 U ap2))')
|
||||
assert len(list((~phi).walk())) == 11
|
||||
127
mtl/test_boolean_eval.py
Normal file
127
mtl/test_boolean_eval.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import hypothesis.strategies as st
|
||||
import traces
|
||||
from hypothesis import given
|
||||
from pytest import raises
|
||||
|
||||
import mtl
|
||||
import mtl.boolean_eval
|
||||
import mtl.fastboolean_eval
|
||||
from mtl.hypothesis import MetricTemporalLogicStrategy
|
||||
"""
|
||||
TODO: property based test that fasteval should be the same as slow
|
||||
TODO: property based test that x |= phi == ~(x |= ~phi)
|
||||
TODO: property based test that ~~phi == phi
|
||||
TODO: property based test that ~~~phi == ~phi
|
||||
TODO: property based test that ~phi => phi
|
||||
TODO: property based test that phi => phi
|
||||
TODO: property based test that phi <=> phi
|
||||
TODO: property based test that phi & psi => phi
|
||||
TODO: property based test that psi => phi | psi
|
||||
TODO: property based test that (True U psi) => F(psi)
|
||||
TODO: property based test that G(psi) = ~F(~psi)
|
||||
TODO: Automatically generate input time series.
|
||||
"""
|
||||
|
||||
x = {
|
||||
"x":
|
||||
traces.TimeSeries([(0, 1), (0.1, 1), (0.2, 4)], domain=(0, 10)),
|
||||
"y":
|
||||
traces.TimeSeries([(0, 2), (0.1, 4), (0.2, 2)], domain=(0, 10)),
|
||||
"ap1":
|
||||
traces.TimeSeries([(0, True), (0.1, True), (0.2, False)], domain=(0, 10)),
|
||||
"ap2":
|
||||
traces.TimeSeries([(0, False), (0.2, True), (0.5, False)], domain=(0, 10)),
|
||||
"ap3":
|
||||
traces.TimeSeries([(0, True), (0.1, True), (0.3, False)], domain=(0, 10)),
|
||||
"ap4":
|
||||
traces.TimeSeries(
|
||||
[(0, False), (0.1, False), (0.3, False)], domain=(0, 10)),
|
||||
"ap5":
|
||||
traces.TimeSeries([(0, False), (0.1, False), (0.3, True)], domain=(0, 10)),
|
||||
"ap6":
|
||||
traces.TimeSeries([(0, True)], domain=(0, 10)),
|
||||
}
|
||||
|
||||
|
||||
@given(st.just(mtl.ast.Next(mtl.BOT) | mtl.ast.Next(mtl.TOP)))
|
||||
def test_eval_smoke_tests(phi):
|
||||
mtl_eval9 = mtl.boolean_eval.pointwise_sat(mtl.ast.Next(phi))
|
||||
mtl_eval10 = mtl.boolean_eval.pointwise_sat(~mtl.ast.Next(phi))
|
||||
assert mtl_eval9(x, 0) != mtl_eval10(x, 0)
|
||||
|
||||
phi4 = mtl.parse('~ap4')
|
||||
mtl_eval11 = mtl.boolean_eval.pointwise_sat(phi4)
|
||||
assert mtl_eval11(x, 0)
|
||||
|
||||
phi5 = mtl.parse('G[0.1, 0.03] ~ap4')
|
||||
mtl_eval12 = mtl.boolean_eval.pointwise_sat(phi5)
|
||||
assert mtl_eval12(x, 0)
|
||||
|
||||
phi6 = mtl.parse('G[0.1, 0.03] ~ap5')
|
||||
mtl_eval13 = mtl.boolean_eval.pointwise_sat(phi6)
|
||||
assert mtl_eval13(x, 0)
|
||||
assert mtl_eval13(x, 0.4)
|
||||
|
||||
phi7 = mtl.parse('G ~ap4')
|
||||
mtl_eval14 = mtl.boolean_eval.pointwise_sat(phi7)
|
||||
assert mtl_eval14(x, 0)
|
||||
|
||||
phi8 = mtl.parse('F ap5')
|
||||
mtl_eval15 = mtl.boolean_eval.pointwise_sat(phi8)
|
||||
assert mtl_eval15(x, 0)
|
||||
|
||||
phi9 = mtl.parse('(ap1 U ap2)')
|
||||
mtl_eval16 = mtl.boolean_eval.pointwise_sat(phi9)
|
||||
assert mtl_eval16(x, 0)
|
||||
|
||||
phi10 = mtl.parse('(ap2 U ap2)')
|
||||
mtl_eval17 = mtl.boolean_eval.pointwise_sat(phi10)
|
||||
assert not mtl_eval17(x, 0)
|
||||
|
||||
with raises(NotImplementedError):
|
||||
mtl.boolean_eval.eval_mtl(None, None)
|
||||
|
||||
|
||||
@given(MetricTemporalLogicStrategy)
|
||||
def test_temporal_identities(phi):
|
||||
mtl_eval = mtl.boolean_eval.pointwise_sat(phi)
|
||||
mtl_eval2 = mtl.boolean_eval.pointwise_sat(~phi)
|
||||
assert mtl_eval2(x, 0) == (not mtl_eval(x, 0))
|
||||
mtl_eval3 = mtl.boolean_eval.pointwise_sat(~~phi)
|
||||
assert mtl_eval3(x, 0) == mtl_eval(x, 0)
|
||||
mtl_eval4 = mtl.boolean_eval.pointwise_sat(phi & phi)
|
||||
assert mtl_eval4(x, 0) == mtl_eval(x, 0)
|
||||
mtl_eval5 = mtl.boolean_eval.pointwise_sat(phi & ~phi)
|
||||
assert not mtl_eval5(x, 0)
|
||||
mtl_eval6 = mtl.boolean_eval.pointwise_sat(phi | ~phi)
|
||||
assert mtl_eval6(x, 0)
|
||||
mtl_eval7 = mtl.boolean_eval.pointwise_sat(mtl.ast.Until(mtl.TOP, phi))
|
||||
mtl_eval8 = mtl.boolean_eval.pointwise_sat(mtl.env(phi))
|
||||
assert mtl_eval7(x, 0) == mtl_eval8(x, 0)
|
||||
|
||||
|
||||
@given(st.just(mtl.BOT))
|
||||
def test_fastboolean_equiv(phi):
|
||||
mtl_eval = mtl.fastboolean_eval.pointwise_sat(mtl.alw(phi, lo=0, hi=4))
|
||||
mtl_eval2 = mtl.fastboolean_eval.pointwise_sat(~mtl.env(~phi, lo=0, hi=4))
|
||||
assert mtl_eval2(x, 0) == mtl_eval(x, 0)
|
||||
|
||||
mtl_eval3 = mtl.fastboolean_eval.pointwise_sat(~mtl.alw(~phi, lo=0, hi=4))
|
||||
mtl_eval4 = mtl.fastboolean_eval.pointwise_sat(mtl.env(phi, lo=0, hi=4))
|
||||
assert mtl_eval4(x, 0) == mtl_eval3(x, 0)
|
||||
|
||||
|
||||
def test_fastboolean_smoketest():
|
||||
phi = mtl.parse(
|
||||
'(((G[0, 4] ap6 & F[2, 1] ap1) | ap2) & G[0,0](ap2))')
|
||||
mtl_eval = mtl.fastboolean_eval.pointwise_sat(phi)
|
||||
assert not mtl_eval(x, 0)
|
||||
|
||||
with raises(NotImplementedError):
|
||||
mtl.fastboolean_eval.pointwise_sat(mtl.ast.AST())
|
||||
|
||||
|
||||
def test_callable_interface():
|
||||
phi = mtl.parse(
|
||||
'(((G[0, 4] ap6 & F[2, 1] ap1) | ap2) & G[0,0](ap2))')
|
||||
assert not phi(x, 0)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import stl
|
||||
from stl.hypothesis import SignalTemporalLogicStrategy
|
||||
import mtl
|
||||
from mtl.hypothesis import MetricTemporalLogicStrategy
|
||||
|
||||
import hypothesis.strategies as st
|
||||
from hypothesis import given
|
||||
|
|
@ -7,14 +7,14 @@ from hypothesis import given
|
|||
|
||||
@given(st.integers(), st.integers(), st.integers())
|
||||
def test_params1(a, b, c):
|
||||
phi = stl.parse('G[a, b] x')
|
||||
phi = mtl.parse('G[a, b] x')
|
||||
assert {x.name for x in phi.params} == {'a', 'b'}
|
||||
|
||||
phi2 = phi.set_params({'a': a, 'b': b})
|
||||
assert phi2.params == set()
|
||||
assert phi2 == stl.parse(f'G[{a}, {b}](x)')
|
||||
assert phi2 == mtl.parse(f'G[{a}, {b}](x)')
|
||||
|
||||
|
||||
@given(SignalTemporalLogicStrategy)
|
||||
@given(MetricTemporalLogicStrategy)
|
||||
def test_hash_stable(phi):
|
||||
assert hash(phi) == hash(stl.parse(str(phi)))
|
||||
assert hash(phi) == hash(mtl.parse(str(phi)))
|
||||
22
mtl/test_parser.py
Normal file
22
mtl/test_parser.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from hypothesis import event, given
|
||||
|
||||
import mtl
|
||||
from mtl.hypothesis import MetricTemporalLogicStrategy
|
||||
|
||||
|
||||
@given(MetricTemporalLogicStrategy)
|
||||
def test_invertable_repr(phi):
|
||||
event(str(phi))
|
||||
assert str(phi) == str(mtl.parse(str(phi)))
|
||||
|
||||
|
||||
@given(MetricTemporalLogicStrategy)
|
||||
def test_hash_inheritance(phi):
|
||||
assert hash(repr(phi)) == hash(phi)
|
||||
|
||||
|
||||
def test_sugar_smoke():
|
||||
mtl.parse('(x <-> x)')
|
||||
mtl.parse('(x -> x)')
|
||||
mtl.parse('(x ^ x)')
|
||||
113
mtl/test_utils.py
Normal file
113
mtl/test_utils.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import mtl
|
||||
from mtl.hypothesis import MetricTemporalLogicStrategy
|
||||
|
||||
from hypothesis import given
|
||||
from pytest import raises
|
||||
|
||||
CONTEXT = {
|
||||
mtl.parse('ap1'): mtl.parse('x'),
|
||||
mtl.parse('ap2'): mtl.parse('(y U z)'),
|
||||
mtl.parse('ap3'): mtl.parse('x'),
|
||||
mtl.parse('ap4'): mtl.parse('(x -> y -> z)'),
|
||||
mtl.parse('ap5'): mtl.parse('(ap1 <-> y <-> z)'),
|
||||
}
|
||||
APS = set(CONTEXT.keys())
|
||||
|
||||
|
||||
@given(MetricTemporalLogicStrategy)
|
||||
def test_f_neg_or_canonical_form(phi):
|
||||
phi2 = mtl.utils.f_neg_or_canonical_form(phi)
|
||||
phi3 = mtl.utils.f_neg_or_canonical_form(phi2)
|
||||
assert phi2 == phi3
|
||||
assert not any(
|
||||
isinstance(x, (mtl.ast.G, mtl.ast.And)) for x in phi2.walk())
|
||||
|
||||
|
||||
def test_f_neg_or_canonical_form_not_implemented():
|
||||
with raises(NotImplementedError):
|
||||
mtl.utils.f_neg_or_canonical_form(mtl.ast.AST())
|
||||
|
||||
|
||||
def test_inline_context_rigid():
|
||||
phi = mtl.parse('G ap1')
|
||||
phi2 = phi.inline_context(CONTEXT)
|
||||
assert phi2 == mtl.parse('G x')
|
||||
|
||||
phi = mtl.parse('G ap5')
|
||||
phi2 = phi.inline_context(CONTEXT)
|
||||
assert phi2 == mtl.parse('G(x <-> y <-> z)')
|
||||
|
||||
|
||||
@given(MetricTemporalLogicStrategy)
|
||||
def test_inline_context(phi):
|
||||
phi2 = phi.inline_context(CONTEXT)
|
||||
assert not (APS & phi2.atomic_predicates)
|
||||
|
||||
|
||||
@given(MetricTemporalLogicStrategy, MetricTemporalLogicStrategy)
|
||||
def test_timed_until_smoke_test(phi1, phi2):
|
||||
mtl.utils.timed_until(phi1, phi2, lo=2, hi=20)
|
||||
|
||||
|
||||
def test_discretize():
|
||||
dt = 0.3
|
||||
|
||||
phi = mtl.parse('@ ap1')
|
||||
assert mtl.utils.is_discretizable(phi, dt)
|
||||
phi2 = mtl.utils.discretize(phi, dt)
|
||||
phi3 = mtl.utils.discretize(phi2, dt)
|
||||
assert phi2 == phi3
|
||||
|
||||
phi = mtl.parse('G[0.3, 1.2] F[0.6, 1.5] ap1')
|
||||
assert mtl.utils.is_discretizable(phi, dt)
|
||||
phi2 = mtl.utils.discretize(phi, dt)
|
||||
phi3 = mtl.utils.discretize(phi2, dt)
|
||||
assert phi2 == phi3
|
||||
|
||||
phi = mtl.parse('G[0.3, 1.4] F[0.6, 1.5] ap1')
|
||||
assert not mtl.utils.is_discretizable(phi, dt)
|
||||
|
||||
phi = mtl.parse('G[0.3, 1.2] F ap1')
|
||||
assert not mtl.utils.is_discretizable(phi, dt)
|
||||
|
||||
phi = mtl.parse('G[0.3, 1.2] (ap1 U ap2)')
|
||||
assert not mtl.utils.is_discretizable(phi, dt)
|
||||
|
||||
phi = mtl.parse('G[0.3, 0.6] ~F[0, 0.3] a')
|
||||
assert mtl.utils.is_discretizable(phi, dt)
|
||||
phi2 = mtl.utils.discretize(phi, dt, distribute=True)
|
||||
phi3 = mtl.utils.discretize(phi2, dt, distribute=True)
|
||||
assert phi2 == phi3
|
||||
assert phi2 == mtl.parse(
|
||||
'(~(@a | @@a) & ~(@@a | @@@a))')
|
||||
|
||||
phi = mtl.TOP
|
||||
assert mtl.utils.is_discretizable(phi, dt)
|
||||
phi2 = mtl.utils.discretize(phi, dt)
|
||||
phi3 = mtl.utils.discretize(phi2, dt)
|
||||
assert phi2 == phi3
|
||||
|
||||
phi = mtl.BOT
|
||||
assert mtl.utils.is_discretizable(phi, dt)
|
||||
phi2 = mtl.utils.discretize(phi, dt)
|
||||
phi3 = mtl.utils.discretize(phi2, dt)
|
||||
assert phi2 == phi3
|
||||
|
||||
|
||||
def test_scope():
|
||||
dt = 0.3
|
||||
|
||||
phi = mtl.parse('@ap1')
|
||||
assert mtl.utils.scope(phi, dt) == 0.3
|
||||
|
||||
phi = mtl.parse('(@@ap1 | ap2)')
|
||||
assert mtl.utils.scope(phi, dt) == 0.6
|
||||
|
||||
phi = mtl.parse('G[0.3, 1.2] F[0.6, 1.5] ap1')
|
||||
assert mtl.utils.scope(phi, dt) == 1.2 + 1.5
|
||||
|
||||
phi = mtl.parse('G[0.3, 1.2] F ap1')
|
||||
assert mtl.utils.scope(phi, dt) == float('inf')
|
||||
|
||||
phi = mtl.parse('G[0.3, 1.2] (ap1 U ap2)')
|
||||
assert mtl.utils.scope(phi, dt) == float('inf')
|
||||
|
|
@ -6,8 +6,8 @@ import traces
|
|||
import numpy as np
|
||||
from lenses import bind
|
||||
|
||||
import stl.ast
|
||||
from stl.ast import (And, F, G, Interval, Neg, Or, Next, Until,
|
||||
import mtl.ast
|
||||
from mtl.ast import (And, F, G, Interval, Neg, Or, Next, Until,
|
||||
AtomicPred, _Top, _Bot)
|
||||
|
||||
oo = float('inf')
|
||||
|
|
@ -40,50 +40,10 @@ def f_neg_or_canonical_form(phi):
|
|||
raise NotImplementedError
|
||||
|
||||
|
||||
def _lineq_lipschitz(lineq):
|
||||
return sum(map(abs, bind(lineq).terms.Each().coeff.collect()))
|
||||
|
||||
|
||||
def linear_stl_lipschitz(phi):
|
||||
"""Infinity norm lipschitz bound of linear inequality predicate."""
|
||||
if any(isinstance(psi, (AtomicPred, _Top, _Bot)) for psi in phi.walk()):
|
||||
return float('inf')
|
||||
|
||||
return float(max(map(_lineq_lipschitz, phi.lineqs), default=float('inf')))
|
||||
|
||||
|
||||
op_lookup = {
|
||||
">": op.gt,
|
||||
">=": op.ge,
|
||||
"<": op.lt,
|
||||
"<=": op.le,
|
||||
"=": op.eq,
|
||||
}
|
||||
|
||||
|
||||
def const_trace(x, start=0):
|
||||
return traces.TimeSeries([(start, x)], domain=traces.Domain(start, oo))
|
||||
|
||||
|
||||
def eval_lineq(lineq, x, domain, compact=True):
|
||||
lhs = sum(const_trace(term.coeff) * x[term.id] for term in lineq.terms)
|
||||
compare = op_lookup.get(lineq.op)
|
||||
output = lhs.operation(const_trace(lineq.const), compare)
|
||||
output.domain = domain
|
||||
|
||||
if compact:
|
||||
output.compact()
|
||||
return output
|
||||
|
||||
|
||||
def eval_lineqs(phi, x):
|
||||
lineqs = phi.lineqs
|
||||
start = max(y.domain.start() for y in x.values())
|
||||
end = min(y.domain.end() for y in x.values())
|
||||
domain = traces.Domain(start, end)
|
||||
return {lineq: eval_lineq(lineq, x, domain) for lineq in lineqs}
|
||||
|
||||
|
||||
def require_discretizable(func):
|
||||
@wraps(func)
|
||||
def _func(phi, dt, *args, **kwargs):
|
||||
|
|
@ -106,7 +66,7 @@ def scope(phi, dt, *, _t=0, horizon=oo):
|
|||
return min(_scope, horizon)
|
||||
|
||||
|
||||
# Code to discretize a bounded STL formula
|
||||
# Code to discretize a bounded MTL formula
|
||||
|
||||
|
||||
@require_discretizable
|
||||
|
|
@ -153,7 +113,7 @@ def _interval_discretizable(itvl, dt):
|
|||
|
||||
def _distribute_next(phi, i=0):
|
||||
if isinstance(phi, AtomicPred):
|
||||
return stl.utils.next(phi, i=i)
|
||||
return mtl.utils.next(phi, i=i)
|
||||
elif isinstance(phi, Next):
|
||||
return _distribute_next(phi.arg, i=i+1)
|
||||
|
||||
|
|
@ -185,11 +145,11 @@ def env(phi, *, lo=0, hi=float('inf')):
|
|||
|
||||
|
||||
def andf(*args):
|
||||
return reduce(op.and_, args) if args else stl.TOP
|
||||
return reduce(op.and_, args) if args else mtl.TOP
|
||||
|
||||
|
||||
def orf(*args):
|
||||
return reduce(op.or_, args) if args else stl.TOP
|
||||
return reduce(op.or_, args) if args else mtl.TOP
|
||||
|
||||
|
||||
def implies(x, y):
|
||||
|
|
@ -209,4 +169,4 @@ def next(phi, i=1):
|
|||
|
||||
|
||||
def timed_until(phi, psi, lo, hi):
|
||||
return env(psi, lo=lo, hi=hi) & alw(stl.ast.Until(phi, psi), lo=0, hi=lo)
|
||||
return env(psi, lo=lo, hi=hi) & alw(mtl.ast.Until(phi, psi), lo=0, hi=lo)
|
||||
|
|
@ -3,9 +3,7 @@
|
|||
bitarray==0.8.1
|
||||
funcy==1.9.1
|
||||
lenses==0.4.0
|
||||
pandas==0.19.2
|
||||
parsimonious==0.7.0
|
||||
sympy==1.0
|
||||
traces==0.3.1
|
||||
hypothesis==3.32.1
|
||||
|
||||
|
|
|
|||
5
setup.py
5
setup.py
|
|
@ -1,10 +1,10 @@
|
|||
from setuptools import find_packages, setup
|
||||
|
||||
setup(
|
||||
name='py-stl',
|
||||
name='metric-temporal-logic',
|
||||
version='0.2',
|
||||
description='TODO',
|
||||
url='http://github.com/mvcisback/py-stl',
|
||||
url='http://github.com/mvcisback/py-mtl',
|
||||
author='Marcell Vazquez-Chanlatte',
|
||||
author_email='marcell.vc@eecs.berkeley.edu',
|
||||
license='MIT',
|
||||
|
|
@ -12,7 +12,6 @@ setup(
|
|||
'funcy',
|
||||
'parsimonious',
|
||||
'lenses',
|
||||
'sympy',
|
||||
'bitarray',
|
||||
'traces',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
# flake8: noqa
|
||||
from stl.ast import TOP, BOT
|
||||
from stl.ast import (Interval, Or, And, F, G, Neg,
|
||||
AtomicPred, Until, Next)
|
||||
from stl.parser import parse
|
||||
from stl.fastboolean_eval import pointwise_sat
|
||||
from stl.utils import alw, env, andf, orf
|
||||
11
stl/load.py
11
stl/load.py
|
|
@ -1,11 +0,0 @@
|
|||
from traces import Domain, TimeSeries
|
||||
|
||||
|
||||
def from_pandas(df, compact=True):
|
||||
'''TODO'''
|
||||
domain = Domain(df.index[0], df.index[-1])
|
||||
data = {col: TimeSeries(df[col].T, domain=domain) for col in df.columns}
|
||||
if compact:
|
||||
for val in data.values():
|
||||
val.compact()
|
||||
return data
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import stl
|
||||
from stl.hypothesis import SignalTemporalLogicStrategy
|
||||
|
||||
from hypothesis import given
|
||||
|
||||
|
||||
@given(SignalTemporalLogicStrategy)
|
||||
def test_identities(phi):
|
||||
assert stl.TOP == stl.TOP | phi
|
||||
assert stl.BOT == stl.BOT & phi
|
||||
assert stl.TOP == phi | stl.TOP
|
||||
assert stl.BOT == phi & stl.BOT
|
||||
assert phi == phi & stl.TOP
|
||||
assert phi == phi | stl.BOT
|
||||
assert stl.TOP == stl.TOP & stl.TOP
|
||||
assert stl.BOT == stl.BOT | stl.BOT
|
||||
assert stl.TOP == stl.TOP | stl.BOT
|
||||
assert stl.BOT == stl.TOP & stl.BOT
|
||||
assert ~stl.BOT == stl.TOP
|
||||
assert ~stl.TOP == stl.BOT
|
||||
assert ~~stl.BOT == stl.BOT
|
||||
assert ~~stl.TOP == stl.TOP
|
||||
assert (phi & phi) & phi == phi & (phi & phi)
|
||||
assert (phi | phi) | phi == phi | (phi | phi)
|
||||
assert ~~phi == phi
|
||||
|
||||
|
||||
def test_walk():
|
||||
phi = stl.parse(
|
||||
'(([ ][0, 1] ap1 & < >[1,2] ap2) | (@ap1 U ap2))')
|
||||
assert len(list((~phi).walk())) == 11
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
import hypothesis.strategies as st
|
||||
import traces
|
||||
from hypothesis import given
|
||||
from pytest import raises
|
||||
|
||||
import stl
|
||||
import stl.boolean_eval
|
||||
import stl.fastboolean_eval
|
||||
from stl.hypothesis import SignalTemporalLogicStrategy
|
||||
"""
|
||||
TODO: property based test that fasteval should be the same as slow
|
||||
TODO: property based test that x |= phi == ~(x |= ~phi)
|
||||
TODO: property based test that ~~phi == phi
|
||||
TODO: property based test that ~~~phi == ~phi
|
||||
TODO: property based test that ~phi => phi
|
||||
TODO: property based test that phi => phi
|
||||
TODO: property based test that phi <=> phi
|
||||
TODO: property based test that phi & psi => phi
|
||||
TODO: property based test that psi => phi | psi
|
||||
TODO: property based test that (True U psi) => F(psi)
|
||||
TODO: property based test that G(psi) = ~F(~psi)
|
||||
TODO: Automatically generate input time series.
|
||||
"""
|
||||
|
||||
x = {
|
||||
"x":
|
||||
traces.TimeSeries([(0, 1), (0.1, 1), (0.2, 4)], domain=(0, 10)),
|
||||
"y":
|
||||
traces.TimeSeries([(0, 2), (0.1, 4), (0.2, 2)], domain=(0, 10)),
|
||||
"ap1":
|
||||
traces.TimeSeries([(0, True), (0.1, True), (0.2, False)], domain=(0, 10)),
|
||||
"ap2":
|
||||
traces.TimeSeries([(0, False), (0.2, True), (0.5, False)], domain=(0, 10)),
|
||||
"ap3":
|
||||
traces.TimeSeries([(0, True), (0.1, True), (0.3, False)], domain=(0, 10)),
|
||||
"ap4":
|
||||
traces.TimeSeries(
|
||||
[(0, False), (0.1, False), (0.3, False)], domain=(0, 10)),
|
||||
"ap5":
|
||||
traces.TimeSeries([(0, False), (0.1, False), (0.3, True)], domain=(0, 10)),
|
||||
"ap6":
|
||||
traces.TimeSeries([(0, True)], domain=(0, 10)),
|
||||
}
|
||||
|
||||
|
||||
@given(st.just(stl.ast.Next(stl.BOT) | stl.ast.Next(stl.TOP)))
|
||||
def test_eval_smoke_tests(phi):
|
||||
stl_eval9 = stl.boolean_eval.pointwise_sat(stl.ast.Next(phi))
|
||||
stl_eval10 = stl.boolean_eval.pointwise_sat(~stl.ast.Next(phi))
|
||||
assert stl_eval9(x, 0) != stl_eval10(x, 0)
|
||||
|
||||
phi4 = stl.parse('~ap4')
|
||||
stl_eval11 = stl.boolean_eval.pointwise_sat(phi4)
|
||||
assert stl_eval11(x, 0)
|
||||
|
||||
phi5 = stl.parse('G[0.1, 0.03] ~ap4')
|
||||
stl_eval12 = stl.boolean_eval.pointwise_sat(phi5)
|
||||
assert stl_eval12(x, 0)
|
||||
|
||||
phi6 = stl.parse('G[0.1, 0.03] ~ap5')
|
||||
stl_eval13 = stl.boolean_eval.pointwise_sat(phi6)
|
||||
assert stl_eval13(x, 0)
|
||||
assert stl_eval13(x, 0.4)
|
||||
|
||||
phi7 = stl.parse('G ~ap4')
|
||||
stl_eval14 = stl.boolean_eval.pointwise_sat(phi7)
|
||||
assert stl_eval14(x, 0)
|
||||
|
||||
phi8 = stl.parse('F ap5')
|
||||
stl_eval15 = stl.boolean_eval.pointwise_sat(phi8)
|
||||
assert stl_eval15(x, 0)
|
||||
|
||||
phi9 = stl.parse('(ap1 U ap2)')
|
||||
stl_eval16 = stl.boolean_eval.pointwise_sat(phi9)
|
||||
assert stl_eval16(x, 0)
|
||||
|
||||
phi10 = stl.parse('(ap2 U ap2)')
|
||||
stl_eval17 = stl.boolean_eval.pointwise_sat(phi10)
|
||||
assert not stl_eval17(x, 0)
|
||||
|
||||
with raises(NotImplementedError):
|
||||
stl.boolean_eval.eval_stl(None, None)
|
||||
|
||||
|
||||
@given(SignalTemporalLogicStrategy)
|
||||
def test_temporal_identities(phi):
|
||||
stl_eval = stl.boolean_eval.pointwise_sat(phi)
|
||||
stl_eval2 = stl.boolean_eval.pointwise_sat(~phi)
|
||||
assert stl_eval2(x, 0) == (not stl_eval(x, 0))
|
||||
stl_eval3 = stl.boolean_eval.pointwise_sat(~~phi)
|
||||
assert stl_eval3(x, 0) == stl_eval(x, 0)
|
||||
stl_eval4 = stl.boolean_eval.pointwise_sat(phi & phi)
|
||||
assert stl_eval4(x, 0) == stl_eval(x, 0)
|
||||
stl_eval5 = stl.boolean_eval.pointwise_sat(phi & ~phi)
|
||||
assert not stl_eval5(x, 0)
|
||||
stl_eval6 = stl.boolean_eval.pointwise_sat(phi | ~phi)
|
||||
assert stl_eval6(x, 0)
|
||||
stl_eval7 = stl.boolean_eval.pointwise_sat(stl.ast.Until(stl.TOP, phi))
|
||||
stl_eval8 = stl.boolean_eval.pointwise_sat(stl.env(phi))
|
||||
assert stl_eval7(x, 0) == stl_eval8(x, 0)
|
||||
|
||||
|
||||
@given(st.just(stl.BOT))
|
||||
def test_fastboolean_equiv(phi):
|
||||
stl_eval = stl.fastboolean_eval.pointwise_sat(stl.alw(phi, lo=0, hi=4))
|
||||
stl_eval2 = stl.fastboolean_eval.pointwise_sat(~stl.env(~phi, lo=0, hi=4))
|
||||
assert stl_eval2(x, 0) == stl_eval(x, 0)
|
||||
|
||||
stl_eval3 = stl.fastboolean_eval.pointwise_sat(~stl.alw(~phi, lo=0, hi=4))
|
||||
stl_eval4 = stl.fastboolean_eval.pointwise_sat(stl.env(phi, lo=0, hi=4))
|
||||
assert stl_eval4(x, 0) == stl_eval3(x, 0)
|
||||
|
||||
|
||||
def test_fastboolean_smoketest():
|
||||
phi = stl.parse(
|
||||
'(((G[0, 4] ap6 & F[2, 1] ap1) | ap2) & G[0,0](ap2))')
|
||||
stl_eval = stl.fastboolean_eval.pointwise_sat(phi)
|
||||
assert not stl_eval(x, 0)
|
||||
|
||||
with raises(NotImplementedError):
|
||||
stl.fastboolean_eval.pointwise_sat(stl.ast.AST())
|
||||
|
||||
|
||||
def test_callable_interface():
|
||||
phi = stl.parse(
|
||||
'(((G[0, 4] ap6 & F[2, 1] ap1) | ap2) & G[0,0](ap2))')
|
||||
assert not phi(x, 0)
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import pandas as pd
|
||||
|
||||
from stl.load import from_pandas
|
||||
|
||||
DATA = pd.DataFrame(
|
||||
data={
|
||||
'AP1': [True, False, True],
|
||||
'x': [0, 0, 0.1],
|
||||
'y': [-1, -1, 0],
|
||||
'z': [2, 3, 1],
|
||||
},
|
||||
index=[0, 1, 2],
|
||||
)
|
||||
|
||||
|
||||
def test_from_pandas():
|
||||
x = from_pandas(DATA)
|
||||
assert x['x'][0] == 0
|
||||
assert x['x'][0.2] == 0
|
||||
assert not x['AP1'][1.4]
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from hypothesis import event, given
|
||||
|
||||
import stl
|
||||
from stl.hypothesis import SignalTemporalLogicStrategy
|
||||
|
||||
|
||||
@given(SignalTemporalLogicStrategy)
|
||||
def test_invertable_repr(phi):
|
||||
event(str(phi))
|
||||
assert str(phi) == str(stl.parse(str(phi)))
|
||||
|
||||
|
||||
@given(SignalTemporalLogicStrategy)
|
||||
def test_hash_inheritance(phi):
|
||||
assert hash(repr(phi)) == hash(phi)
|
||||
|
||||
|
||||
def test_sugar_smoke():
|
||||
stl.parse('(x <-> x)')
|
||||
stl.parse('(x -> x)')
|
||||
stl.parse('(x ^ x)')
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
import stl
|
||||
from stl.hypothesis import SignalTemporalLogicStrategy
|
||||
|
||||
from hypothesis import given
|
||||
from pytest import raises
|
||||
|
||||
CONTEXT = {
|
||||
stl.parse('ap1'): stl.parse('x'),
|
||||
stl.parse('ap2'): stl.parse('(y U z)'),
|
||||
stl.parse('ap3'): stl.parse('x'),
|
||||
stl.parse('ap4'): stl.parse('(x -> y -> z)'),
|
||||
stl.parse('ap5'): stl.parse('(ap1 <-> y <-> z)'),
|
||||
}
|
||||
APS = set(CONTEXT.keys())
|
||||
|
||||
|
||||
@given(SignalTemporalLogicStrategy)
|
||||
def test_f_neg_or_canonical_form(phi):
|
||||
phi2 = stl.utils.f_neg_or_canonical_form(phi)
|
||||
phi3 = stl.utils.f_neg_or_canonical_form(phi2)
|
||||
assert phi2 == phi3
|
||||
assert not any(
|
||||
isinstance(x, (stl.ast.G, stl.ast.And)) for x in phi2.walk())
|
||||
|
||||
|
||||
def test_f_neg_or_canonical_form_not_implemented():
|
||||
with raises(NotImplementedError):
|
||||
stl.utils.f_neg_or_canonical_form(stl.ast.AST())
|
||||
|
||||
|
||||
def test_inline_context_rigid():
|
||||
phi = stl.parse('G ap1')
|
||||
phi2 = phi.inline_context(CONTEXT)
|
||||
assert phi2 == stl.parse('G x')
|
||||
|
||||
phi = stl.parse('G ap5')
|
||||
phi2 = phi.inline_context(CONTEXT)
|
||||
assert phi2 == stl.parse('G(x <-> y <-> z)')
|
||||
|
||||
|
||||
@given(SignalTemporalLogicStrategy)
|
||||
def test_inline_context(phi):
|
||||
phi2 = phi.inline_context(CONTEXT)
|
||||
assert not (APS & phi2.atomic_predicates)
|
||||
|
||||
|
||||
@given(SignalTemporalLogicStrategy, SignalTemporalLogicStrategy)
|
||||
def test_timed_until_smoke_test(phi1, phi2):
|
||||
stl.utils.timed_until(phi1, phi2, lo=2, hi=20)
|
||||
|
||||
|
||||
def test_discretize():
|
||||
dt = 0.3
|
||||
|
||||
phi = stl.parse('@ ap1')
|
||||
assert stl.utils.is_discretizable(phi, dt)
|
||||
phi2 = stl.utils.discretize(phi, dt)
|
||||
phi3 = stl.utils.discretize(phi2, dt)
|
||||
assert phi2 == phi3
|
||||
|
||||
phi = stl.parse('G[0.3, 1.2] F[0.6, 1.5] ap1')
|
||||
assert stl.utils.is_discretizable(phi, dt)
|
||||
phi2 = stl.utils.discretize(phi, dt)
|
||||
phi3 = stl.utils.discretize(phi2, dt)
|
||||
assert phi2 == phi3
|
||||
|
||||
phi = stl.parse('G[0.3, 1.4] F[0.6, 1.5] ap1')
|
||||
assert not stl.utils.is_discretizable(phi, dt)
|
||||
|
||||
phi = stl.parse('G[0.3, 1.2] F ap1')
|
||||
assert not stl.utils.is_discretizable(phi, dt)
|
||||
|
||||
phi = stl.parse('G[0.3, 1.2] (ap1 U ap2)')
|
||||
assert not stl.utils.is_discretizable(phi, dt)
|
||||
|
||||
phi = stl.parse('G[0.3, 0.6] ~F[0, 0.3] a')
|
||||
assert stl.utils.is_discretizable(phi, dt)
|
||||
phi2 = stl.utils.discretize(phi, dt, distribute=True)
|
||||
phi3 = stl.utils.discretize(phi2, dt, distribute=True)
|
||||
assert phi2 == phi3
|
||||
assert phi2 == stl.parse(
|
||||
'(~(@a | @@a) & ~(@@a | @@@a))')
|
||||
|
||||
phi = stl.TOP
|
||||
assert stl.utils.is_discretizable(phi, dt)
|
||||
phi2 = stl.utils.discretize(phi, dt)
|
||||
phi3 = stl.utils.discretize(phi2, dt)
|
||||
assert phi2 == phi3
|
||||
|
||||
phi = stl.BOT
|
||||
assert stl.utils.is_discretizable(phi, dt)
|
||||
phi2 = stl.utils.discretize(phi, dt)
|
||||
phi3 = stl.utils.discretize(phi2, dt)
|
||||
assert phi2 == phi3
|
||||
|
||||
|
||||
def test_scope():
|
||||
dt = 0.3
|
||||
|
||||
phi = stl.parse('@ap1')
|
||||
assert stl.utils.scope(phi, dt) == 0.3
|
||||
|
||||
phi = stl.parse('(@@ap1 | ap2)')
|
||||
assert stl.utils.scope(phi, dt) == 0.6
|
||||
|
||||
phi = stl.parse('G[0.3, 1.2] F[0.6, 1.5] ap1')
|
||||
assert stl.utils.scope(phi, dt) == 1.2 + 1.5
|
||||
|
||||
phi = stl.parse('G[0.3, 1.2] F ap1')
|
||||
assert stl.utils.scope(phi, dt) == float('inf')
|
||||
|
||||
phi = stl.parse('G[0.3, 1.2] (ap1 U ap2)')
|
||||
assert stl.utils.scope(phi, dt) == float('inf')
|
||||
Loading…
Add table
Add a link
Reference in a new issue