continue refactoring to optimize for mtl

This commit is contained in:
Marcell Vazquez-Chanlatte 2018-09-06 11:09:01 -07:00
parent 5fd66cfd2c
commit 98824c9ba1
21 changed files with 393 additions and 467 deletions

7
mtl/__init__.py Normal file
View 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

View file

@ -5,7 +5,7 @@ from functools import lru_cache
import funcy as fn import funcy as fn
from lenses import lens, bind from lenses import lens, bind
import stl import mtl
def flatten_binary(phi, op, dropT, shortT): def flatten_binary(phi, op, dropT, shortT):
@ -49,7 +49,7 @@ class AST(object):
return phi return phi
def __call__(self, trace, time=0): def __call__(self, trace, time=0):
return stl.pointwise_sat(self)(trace, time) return mtl.pointwise_sat(self)(trace, time)
@property @property
def children(self): def children(self):
@ -145,7 +145,7 @@ class Interval(namedtuple('I', ['lower', 'upper'])):
return f"[{self.lower},{self.upper}]" return f"[{self.lower},{self.upper}]"
class NaryOpSTL(namedtuple('NaryOp', ['args']), AST): class NaryOpMTL(namedtuple('NaryOp', ['args']), AST):
__slots__ = () __slots__ = ()
OP = "?" OP = "?"
@ -158,7 +158,7 @@ class NaryOpSTL(namedtuple('NaryOp', ['args']), AST):
return tuple(self.args) return tuple(self.args)
class Or(NaryOpSTL): class Or(NaryOpMTL):
__slots__ = () __slots__ = ()
OP = "|" OP = "|"
@ -168,7 +168,7 @@ class Or(NaryOpSTL):
return hash(repr(self)) return hash(repr(self))
class And(NaryOpSTL): class And(NaryOpMTL):
__slots__ = () __slots__ = ()
OP = "&" OP = "&"

View file

@ -7,9 +7,9 @@ from functools import singledispatch
import funcy as fn import funcy as fn
import traces import traces
import stl import mtl
import stl.ast import mtl.ast
from stl.utils import const_trace, andf, orf from mtl.utils import const_trace, andf, orf
TRUE_TRACE = const_trace(True) TRUE_TRACE = const_trace(True)
FALSE_TRACE = const_trace(False) FALSE_TRACE = const_trace(False)
@ -24,15 +24,15 @@ def negate_trace(x):
def pointwise_sat(phi, dt=0.1): def pointwise_sat(phi, dt=0.1):
ap_names = [z.id for z in phi.atomic_predicates] 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) 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 @singledispatch
def eval_stl(phi, dt): def eval_mtl(phi, dt):
raise NotImplementedError raise NotImplementedError
@ -42,9 +42,9 @@ def or_traces(xs):
return out return out
@eval_stl.register(stl.Or) @eval_mtl.register(mtl.Or)
def eval_stl_or(phi, dt): def eval_mtl_or(phi, dt):
fs = [eval_stl(arg, dt) for arg in phi.args] fs = [eval_mtl(arg, dt) for arg in phi.args]
def _eval(x): def _eval(x):
out = or_traces([f(x) for f in fs]) out = or_traces([f(x) for f in fs])
@ -60,9 +60,9 @@ def and_traces(xs):
return out return out
@eval_stl.register(stl.And) @eval_mtl.register(mtl.And)
def eval_stl_and(phi, dt): def eval_mtl_and(phi, dt):
fs = [eval_stl(arg, dt) for arg in phi.args] fs = [eval_mtl(arg, dt) for arg in phi.args]
def _eval(x): def _eval(x):
out = and_traces([f(x) for f in fs]) out = and_traces([f(x) for f in fs])
@ -80,9 +80,9 @@ def apply_until(y):
phi2_next = phi2 phi2_next = phi2
@eval_stl.register(stl.Until) @eval_mtl.register(mtl.Until)
def eval_stl_until(phi, dt): def eval_mtl_until(phi, dt):
f1, f2 = eval_stl(phi.arg1, dt), eval_stl(phi.arg2, dt) f1, f2 = eval_mtl(phi.arg1, dt), eval_mtl(phi.arg2, dt)
def _eval(x): def _eval(x):
y1, y2 = f1(x), f2(x) y1, y2 = f1(x), f2(x)
@ -95,15 +95,15 @@ def eval_stl_until(phi, dt):
return _eval return _eval
@eval_stl.register(stl.F) @eval_mtl.register(mtl.F)
def eval_stl_f(phi, dt): def eval_mtl_f(phi, dt):
phi = ~stl.G(phi.interval, ~phi.arg) phi = ~mtl.G(phi.interval, ~phi.arg)
return eval_stl(phi, dt) return eval_mtl(phi, dt)
@eval_stl.register(stl.G) @eval_mtl.register(mtl.G)
def eval_stl_g(phi, dt): def eval_mtl_g(phi, dt):
f = eval_stl(phi.arg, dt) f = eval_mtl(phi.arg, dt)
a, b = phi.interval a, b = phi.interval
if b < a: if b < a:
return lambda _: TRUE_TRACE return lambda _: TRUE_TRACE
@ -139,9 +139,9 @@ def eval_stl_g(phi, dt):
return _eval return _eval
@eval_stl.register(stl.Neg) @eval_mtl.register(mtl.Neg)
def eval_stl_neg(phi, dt): def eval_mtl_neg(phi, dt):
f = eval_stl(phi.arg, dt) f = eval_mtl(phi.arg, dt)
def _eval(x): def _eval(x):
out = negate_trace(f(x)) out = negate_trace(f(x))
@ -151,9 +151,9 @@ def eval_stl_neg(phi, dt):
return _eval return _eval
@eval_stl.register(stl.ast.Next) @eval_mtl.register(mtl.ast.Next)
def eval_stl_next(phi, dt): def eval_mtl_next(phi, dt):
f = eval_stl(phi.arg, dt) f = eval_mtl(phi.arg, dt)
def _eval(x): def _eval(x):
y = f(x) y = f(x)
@ -166,8 +166,8 @@ def eval_stl_next(phi, dt):
return _eval return _eval
@eval_stl.register(stl.AtomicPred) @eval_mtl.register(mtl.AtomicPred)
def eval_stl_ap(phi, _): def eval_mtl_ap(phi, _):
def _eval(x): def _eval(x):
out = x[str(phi.id)] out = x[str(phi.id)]
out.compact() out.compact()
@ -177,11 +177,11 @@ def eval_stl_ap(phi, _):
return _eval return _eval
@eval_stl.register(type(stl.TOP)) @eval_mtl.register(type(mtl.TOP))
def eval_stl_top(_, _1): def eval_mtl_top(_, _1):
return lambda *_: TRUE_TRACE return lambda *_: TRUE_TRACE
@eval_stl.register(type(stl.BOT)) @eval_mtl.register(type(mtl.BOT))
def eval_stl_bot(_, _1): def eval_mtl_bot(_, _1):
return lambda *_: FALSE_TRACE return lambda *_: FALSE_TRACE

View file

@ -4,7 +4,7 @@ from operator import and_, or_
import funcy as fn import funcy as fn
from bitarray import bitarray from bitarray import bitarray
import stl.ast import mtl.ast
oo = float('inf') oo = float('inf')
@ -25,35 +25,35 @@ def get_times(x, tau, lo, hi):
return sorted(set(fn.pluck(0, all_times))) return sorted(set(fn.pluck(0, all_times)))
def pointwise_sat(stl): def pointwise_sat(mtl):
f = pointwise_satf(stl) f = pointwise_satf(mtl)
return lambda x, t: bool(int(f(x, [t]).to01())) return lambda x, t: bool(int(f(x, [t]).to01()))
@singledispatch @singledispatch
def pointwise_satf(stl): def pointwise_satf(mtl):
raise NotImplementedError raise NotImplementedError
def bool_op(stl, conjunction=False): def bool_op(mtl, conjunction=False):
binop = and_ if conjunction else or_ 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)) return lambda x, t: reduce(binop, (f(x, t) for f in fs))
@pointwise_satf.register(stl.Or) @pointwise_satf.register(mtl.Or)
def pointwise_satf_or(stl): def pointwise_satf_or(mtl):
return bool_op(stl, conjunction=False) return bool_op(mtl, conjunction=False)
@pointwise_satf.register(stl.And) @pointwise_satf.register(mtl.And)
def pointwise_satf_and(stl): def pointwise_satf_and(mtl):
return bool_op(stl, conjunction=True) 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 fold = bitarray.all if conjunction else bitarray.any
f = pointwise_satf(stl.arg) f = pointwise_satf(mtl.arg)
def sat_comp(x, t): def sat_comp(x, t):
return bitarray(fold(f(x, get_times(x, tau, lo, hi))) for tau in 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 return sat_comp
@pointwise_satf.register(stl.F) @pointwise_satf.register(mtl.F)
def pointwise_satf_f(stl): def pointwise_satf_f(mtl):
lo, hi = stl.interval lo, hi = mtl.interval
return temporal_op(stl, lo, hi, conjunction=False) return temporal_op(mtl, lo, hi, conjunction=False)
@pointwise_satf.register(stl.G) @pointwise_satf.register(mtl.G)
def pointwise_satf_g(stl): def pointwise_satf_g(mtl):
lo, hi = stl.interval lo, hi = mtl.interval
return temporal_op(stl, lo, hi, conjunction=True) return temporal_op(mtl, lo, hi, conjunction=True)
@pointwise_satf.register(stl.Neg) @pointwise_satf.register(mtl.Neg)
def pointwise_satf_neg(stl): def pointwise_satf_neg(mtl):
return lambda x, t: ~pointwise_satf(stl.arg)(x, t) return lambda x, t: ~pointwise_satf(mtl.arg)(x, t)
@pointwise_satf.register(stl.AtomicPred) @pointwise_satf.register(mtl.AtomicPred)
def pointwise_satf_(phi): def pointwise_satf_(phi):
return lambda x, t: bitarray(x[str(phi.id)][tau] for tau in t) 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): def pointwise_satf_until(phi):
raise NotImplementedError raise NotImplementedError
@pointwise_satf.register(type(stl.TOP)) @pointwise_satf.register(type(mtl.TOP))
def pointwise_satf_top(_): def pointwise_satf_top(_):
return lambda _, t: bitarray([True] * len(t)) return lambda _, t: bitarray([True] * len(t))
@pointwise_satf.register(type(stl.BOT)) @pointwise_satf.register(type(mtl.BOT))
def pointwise_satf_bot(_): def pointwise_satf_bot(_):
return lambda _, t: bitarray([False] * len(t)) return lambda _, t: bitarray([False] * len(t))

View file

@ -1,7 +1,7 @@
import hypothesis.strategies as st import hypothesis.strategies as st
from hypothesis_cfg import ContextFreeGrammarStrategy from hypothesis_cfg import ContextFreeGrammarStrategy
import stl import mtl
GRAMMAR = { GRAMMAR = {
'phi': ( 'phi': (
@ -18,7 +18,7 @@ GRAMMAR = {
'AP': (('ap1', ), ('ap2', ), ('ap3', ), ('ap4', ), ('ap5', )), 'AP': (('ap1', ), ('ap2', ), ('ap3', ), ('ap4', ), ('ap5', )),
} }
SignalTemporalLogicStrategy = st.builds( MetricTemporalLogicStrategy = st.builds(
lambda term: stl.parse(''.join(term)), lambda term: mtl.parse(''.join(term)),
ContextFreeGrammarStrategy(GRAMMAR, max_length=14, start='phi') ContextFreeGrammarStrategy(GRAMMAR, max_length=14, start='phi')
) )

View file

@ -3,10 +3,10 @@ import operator as op
from functools import partialmethod, reduce from functools import partialmethod, reduce
from parsimonious import Grammar, NodeVisitor from parsimonious import Grammar, NodeVisitor
from stl import ast from mtl import ast
from stl.utils import iff, implies, xor, timed_until from mtl.utils import iff, implies, xor, timed_until
STL_GRAMMAR = Grammar(u''' MTL_GRAMMAR = Grammar(u'''
phi = (neg / paren_phi / next / bot / top phi = (neg / paren_phi / next / bot / top
/ xor_outer / iff_outer / implies_outer / and_outer / or_outer / xor_outer / iff_outer / implies_outer / and_outer / or_outer
/ timed_until / until / g / f / AP) / timed_until / until / g / f / AP)
@ -53,7 +53,7 @@ EOL = "\\n"
oo = float('inf') oo = float('inf')
class STLVisitor(NodeVisitor): class MTLVisitor(NodeVisitor):
def __init__(self, H=oo): def __init__(self, H=oo):
super().__init__() super().__init__()
self.default_interval = ast.Interval(0.0, H) self.default_interval = ast.Interval(0.0, H)
@ -137,5 +137,5 @@ class STLVisitor(NodeVisitor):
return ast.Next(children[2]) return ast.Next(children[2])
def parse(stl_str: str, rule: str = "phi", H=oo) -> "STL": def parse(mtl_str: str, rule: str = "phi", H=oo) -> "MTL":
return STLVisitor(H).visit(STL_GRAMMAR[rule].parse(stl_str)) return MTLVisitor(H).visit(MTL_GRAMMAR[rule].parse(mtl_str))

31
mtl/test_ast.py Normal file
View 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
View 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)

View file

@ -1,5 +1,5 @@
import stl import mtl
from stl.hypothesis import SignalTemporalLogicStrategy from mtl.hypothesis import MetricTemporalLogicStrategy
import hypothesis.strategies as st import hypothesis.strategies as st
from hypothesis import given from hypothesis import given
@ -7,14 +7,14 @@ from hypothesis import given
@given(st.integers(), st.integers(), st.integers()) @given(st.integers(), st.integers(), st.integers())
def test_params1(a, b, c): 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'} assert {x.name for x in phi.params} == {'a', 'b'}
phi2 = phi.set_params({'a': a, 'b': b}) phi2 = phi.set_params({'a': a, 'b': b})
assert phi2.params == set() 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): 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
View 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
View 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')

View file

@ -6,8 +6,8 @@ import traces
import numpy as np import numpy as np
from lenses import bind from lenses import bind
import stl.ast import mtl.ast
from stl.ast import (And, F, G, Interval, Neg, Or, Next, Until, from mtl.ast import (And, F, G, Interval, Neg, Or, Next, Until,
AtomicPred, _Top, _Bot) AtomicPred, _Top, _Bot)
oo = float('inf') oo = float('inf')
@ -40,50 +40,10 @@ def f_neg_or_canonical_form(phi):
raise NotImplementedError 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): def const_trace(x, start=0):
return traces.TimeSeries([(start, x)], domain=traces.Domain(start, oo)) 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): def require_discretizable(func):
@wraps(func) @wraps(func)
def _func(phi, dt, *args, **kwargs): def _func(phi, dt, *args, **kwargs):
@ -106,7 +66,7 @@ def scope(phi, dt, *, _t=0, horizon=oo):
return min(_scope, horizon) return min(_scope, horizon)
# Code to discretize a bounded STL formula # Code to discretize a bounded MTL formula
@require_discretizable @require_discretizable
@ -153,7 +113,7 @@ def _interval_discretizable(itvl, dt):
def _distribute_next(phi, i=0): def _distribute_next(phi, i=0):
if isinstance(phi, AtomicPred): if isinstance(phi, AtomicPred):
return stl.utils.next(phi, i=i) return mtl.utils.next(phi, i=i)
elif isinstance(phi, Next): elif isinstance(phi, Next):
return _distribute_next(phi.arg, i=i+1) return _distribute_next(phi.arg, i=i+1)
@ -185,11 +145,11 @@ def env(phi, *, lo=0, hi=float('inf')):
def andf(*args): 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): 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): def implies(x, y):
@ -209,4 +169,4 @@ def next(phi, i=1):
def timed_until(phi, psi, lo, hi): 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)

View file

@ -3,9 +3,7 @@
bitarray==0.8.1 bitarray==0.8.1
funcy==1.9.1 funcy==1.9.1
lenses==0.4.0 lenses==0.4.0
pandas==0.19.2
parsimonious==0.7.0 parsimonious==0.7.0
sympy==1.0
traces==0.3.1 traces==0.3.1
hypothesis==3.32.1 hypothesis==3.32.1

View file

@ -1,10 +1,10 @@
from setuptools import find_packages, setup from setuptools import find_packages, setup
setup( setup(
name='py-stl', name='metric-temporal-logic',
version='0.2', version='0.2',
description='TODO', description='TODO',
url='http://github.com/mvcisback/py-stl', url='http://github.com/mvcisback/py-mtl',
author='Marcell Vazquez-Chanlatte', author='Marcell Vazquez-Chanlatte',
author_email='marcell.vc@eecs.berkeley.edu', author_email='marcell.vc@eecs.berkeley.edu',
license='MIT', license='MIT',
@ -12,7 +12,6 @@ setup(
'funcy', 'funcy',
'parsimonious', 'parsimonious',
'lenses', 'lenses',
'sympy',
'bitarray', 'bitarray',
'traces', 'traces',
], ],

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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]

View file

@ -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)')

View file

@ -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')