document utilities and python api

This commit is contained in:
Marcell Vazquez-Chanlatte 2018-09-24 17:11:24 -07:00
parent aa6ebbfc67
commit 7002246850
6 changed files with 139 additions and 76 deletions

View file

@ -71,16 +71,102 @@ phi2 = mtl.parse('G(x & y)')
# Note that since `U` is binary, it requires parens. # Note that since `U` is binary, it requires parens.
phi3 = mtl.parse('(x U y)') phi3 = mtl.parse('(x U y)')
# Weak until (`y` never has to hold).
phi4 = mtl.parse('(x W y)')
# Whenever `x` holds, then `y` holds in the next two time units. # Whenever `x` holds, then `y` holds in the next two time units.
phi4 = mtl.parse('G(x -> F[0, 2] y)') phi5 = mtl.parse('G(x -> F[0, 2] y)')
# We also support timed until. # We also support timed until.
phi5 = mtl.parse('(a U[0, 2] b)') phi6 = mtl.parse('(a U[0, 2] b)')
# Finally, if time is discretized, we also support the next operator. # Finally, if time is discretized, we also support the next operator.
# Thus, LTL can also be modeled. # Thus, LTL can also be modeled.
# `a` holds in two time steps. # `a` holds in two time steps.
phi6 = mtl.parse('XX a') phi7 = mtl.parse('XX a')
```
## Modal Logic (using python syntax)
```python
a, b = mtl.parse('a'), mtl.parse('b')
# Eventually `a` will hold.
phi1 = a.eventually()
# `a & b` will always hold.
phi2 = (a & b).always()
# `a` until `b`
phi3 = a.until()
# `a` weak until `b`
phi4 = a.weak_until(b)
# Whenever `a` holds, then `b` holds in the next two time units.
phi5 = (a.implies(b.eventually(lo=0, hi=2))).always()
# We also support timed until.
phi6 = a.timed_until(b, lo=0, hi=2)
# `a` holds in two time steps.
phi7 = a >> 2
```
## Boolean Evaluation
```python
# Assumes piece wise constant interpolation.
data = {
'a': [(0, True), (1, False), (3, False)]
'b': [(0, False), (0.2, True), (4, False)]
}
phi = mtl.parse('F(a | b)')
print(phi(data, quantitative=False))
# output: True
# Evaluate at t=3
print(phi(data, t=3, quantitative=False))
# output: False
# Evaluate with discrete time
phi = mtl.parse('X b')
print(phi(data, dt=0.2, quantitative=False))
# output: True
```
## Quantitative Evaluate
```python
# Assumes piece wise constant interpolation.
data = {
'a': [(0, 100), (1, -1), (3, -2)]
'b': [(0, 20), (0.2, 2), (4, -10)]
}
phi = mtl.parse('F(a | b)')
print(phi(data))
# output: 100
# Evaluate at t=3
print(phi(data, t=3))
# output: 2
# Evaluate with discrete time
phi = mtl.parse('X b')
print(phi(data, dt=0.2))
# output: 2
```
## Utilities
```python
import mtl
from mtl import utils
print(utils.scope(mtl.parse('XX a'), dt=0.1))
# output: 0.2
print(utils.discretize(mtl.parse('F[0, 0.2] a'), dt=0.1))
# output: (a | X a | XX a)
``` ```
[1]: https://link.springer.com/chapter/10.1007/BFb0031988 [1]: https://link.springer.com/chapter/10.1007/BFb0031988

View file

@ -3,4 +3,3 @@ from mtl.ast import TOP, BOT
from mtl.ast import (Interval, And, G, Neg, from mtl.ast import (Interval, And, G, Neg,
AtomicPred, WeakUntil, Next) AtomicPred, WeakUntil, Next)
from mtl.parser import parse from mtl.parser import parse
from mtl.utils import alw, env, andf, orf, until

View file

@ -6,6 +6,8 @@ import attr
import funcy as fn import funcy as fn
from lenses import bind from lenses import bind
from mtl import sugar
def flatten_binary(phi, op, dropT, shortT): def flatten_binary(phi, op, dropT, shortT):
def f(x): def f(x):
@ -109,6 +111,7 @@ class Param(NamedTuple):
def ast_class(cls): def ast_class(cls):
cls.__xor__ = sugar.xor
cls.__or__ = _or cls.__or__ = _or
cls.__and__ = _and cls.__and__ = _and
cls.__invert__ = _neg cls.__invert__ = _neg
@ -119,6 +122,14 @@ def ast_class(cls):
cls.params = property(_params) cls.params = property(_params)
cls.atomic_predicates = property(_atomic_predicates) cls.atomic_predicates = property(_atomic_predicates)
cls.evolve = attr.evolve cls.evolve = attr.evolve
cls.iff = sugar.iff
cls.implies = sugar.implies
# Avoid circular dependence.
cls.weak_until = lambda a, b: WeakUntil(a, b)
cls.until = sugar.until
cls.timed_until = sugar.timed_until
cls.always = sugar.alw
cls.eventually = sugar.env
if not hasattr(cls, "children"): if not hasattr(cls, "children"):
cls.children = property(lambda _: ()) cls.children = property(lambda _: ())

View file

@ -4,8 +4,7 @@ from functools import partialmethod, reduce
from parsimonious import Grammar, NodeVisitor from parsimonious import Grammar, NodeVisitor
from mtl import ast from mtl import ast
from mtl.utils import iff, implies, xor, timed_until, until from mtl import sugar
from mtl.utils import env, alw
MTL_GRAMMAR = Grammar(u''' MTL_GRAMMAR = Grammar(u'''
phi = (neg / paren_phi / next / bot / top phi = (neg / paren_phi / next / bot / top
@ -81,10 +80,10 @@ class MTLVisitor(NodeVisitor):
visit_xor_inner = binop_inner visit_xor_inner = binop_inner
visit_and_outer = partialmethod(binop_outer, binop=op.and_) visit_and_outer = partialmethod(binop_outer, binop=op.and_)
visit_iff_outer = partialmethod(binop_outer, binop=iff) visit_iff_outer = partialmethod(binop_outer, binop=sugar.iff)
visit_implies_outer = partialmethod(binop_outer, binop=implies) visit_implies_outer = partialmethod(binop_outer, binop=sugar.implies)
visit_or_outer = partialmethod(binop_outer, binop=op.or_) visit_or_outer = partialmethod(binop_outer, binop=op.or_)
visit_xor_outer = partialmethod(binop_outer, binop=xor) visit_xor_outer = partialmethod(binop_outer, binop=sugar.xor)
def generic_visit(self, _, children): def generic_visit(self, _, children):
return children return children
@ -115,20 +114,20 @@ class MTLVisitor(NodeVisitor):
lo, hi = self.default_interval if not i else i[0] lo, hi = self.default_interval if not i else i[0]
return op(phi, lo=lo, hi=hi) return op(phi, lo=lo, hi=hi)
visit_f = partialmethod(unary_temp_op_visitor, op=env) visit_f = partialmethod(unary_temp_op_visitor, op=sugar.env)
visit_g = partialmethod(unary_temp_op_visitor, op=alw) visit_g = partialmethod(unary_temp_op_visitor, op=sugar.alw)
def visit_weak_until(self, _, children): def visit_weak_until(self, _, children):
_, _, phi1, _, _, _, phi2, _, _ = children _, _, phi1, _, _, _, phi2, _, _ = children
return ast.WeakUntil(phi1, phi2) return phi1.weak_until(phi2)
def visit_until(self, _, children): def visit_until(self, _, children):
_, _, phi1, _, _, _, phi2, _, _ = children _, _, phi1, _, _, _, phi2, _, _ = children
return until(phi1, phi2) return phi1.until(phi2)
def visit_timed_until(self, _, children): def visit_timed_until(self, _, children):
_, _, phi1, _, _, itvl, _, phi2, _, _ = children _, _, phi1, _, _, itvl, _, phi2, _, _ = children
return timed_until(phi1, phi2, itvl.lower, itvl.upper) return phi1.timed_until(phi2, itvl.lower, itvl.upper)
def visit_id(self, name, _): def visit_id(self, name, _):
return name.text return name.text

View file

@ -1,4 +1,6 @@
import mtl import mtl
from mtl import utils
from mtl import sugar
from mtl.hypothesis import MetricTemporalLogicStrategy from mtl.hypothesis import MetricTemporalLogicStrategy
from hypothesis import given from hypothesis import given
@ -28,49 +30,49 @@ def test_inline_context(phi):
@given(MetricTemporalLogicStrategy, MetricTemporalLogicStrategy) @given(MetricTemporalLogicStrategy, MetricTemporalLogicStrategy)
def test_timed_until_smoke_test(phi1, phi2): def test_timed_until_smoke_test(phi1, phi2):
mtl.utils.timed_until(phi1, phi2, lo=2, hi=20) sugar.timed_until(phi1, phi2, lo=2, hi=20)
def test_discretize(): def test_discretize():
dt = 0.3 dt = 0.3
phi = mtl.parse('@ ap1') phi = mtl.parse('@ ap1')
assert mtl.utils.is_discretizable(phi, dt) assert utils.is_discretizable(phi, dt)
phi2 = mtl.utils.discretize(phi, dt) phi2 = utils.discretize(phi, dt)
phi3 = mtl.utils.discretize(phi2, dt) phi3 = utils.discretize(phi2, dt)
assert phi2 == phi3 assert phi2 == phi3
phi = mtl.parse('G[0.3, 1.2] F[0.6, 1.5] ap1') phi = mtl.parse('G[0.3, 1.2] F[0.6, 1.5] ap1')
assert mtl.utils.is_discretizable(phi, dt) assert utils.is_discretizable(phi, dt)
phi2 = mtl.utils.discretize(phi, dt) phi2 = utils.discretize(phi, dt)
phi3 = mtl.utils.discretize(phi2, dt) phi3 = utils.discretize(phi2, dt)
assert phi2 == phi3 assert phi2 == phi3
phi = mtl.parse('G[0.3, 1.4] F[0.6, 1.5] ap1') phi = mtl.parse('G[0.3, 1.4] F[0.6, 1.5] ap1')
assert not mtl.utils.is_discretizable(phi, dt) assert not utils.is_discretizable(phi, dt)
phi = mtl.parse('G[0.3, 1.2] F ap1') phi = mtl.parse('G[0.3, 1.2] F ap1')
assert not mtl.utils.is_discretizable(phi, dt) assert not utils.is_discretizable(phi, dt)
phi = mtl.parse('G[0.3, 1.2] (ap1 U ap2)') phi = mtl.parse('G[0.3, 1.2] (ap1 U ap2)')
assert not mtl.utils.is_discretizable(phi, dt) assert not utils.is_discretizable(phi, dt)
phi = mtl.parse('G[0.3, 0.6] ~F[0, 0.3] a') phi = mtl.parse('G[0.3, 0.6] ~F[0, 0.3] a')
assert mtl.utils.is_discretizable(phi, dt) assert utils.is_discretizable(phi, dt)
phi2 = mtl.utils.discretize(phi, dt, distribute=True) phi2 = utils.discretize(phi, dt, distribute=True)
phi3 = mtl.utils.discretize(phi2, dt, distribute=True) phi3 = utils.discretize(phi2, dt, distribute=True)
assert phi2 == phi3 assert phi2 == phi3
phi = mtl.TOP phi = mtl.TOP
assert mtl.utils.is_discretizable(phi, dt) assert utils.is_discretizable(phi, dt)
phi2 = mtl.utils.discretize(phi, dt) phi2 = utils.discretize(phi, dt)
phi3 = mtl.utils.discretize(phi2, dt) phi3 = utils.discretize(phi2, dt)
assert phi2 == phi3 assert phi2 == phi3
phi = mtl.BOT phi = mtl.BOT
assert mtl.utils.is_discretizable(phi, dt) assert utils.is_discretizable(phi, dt)
phi2 = mtl.utils.discretize(phi, dt) phi2 = utils.discretize(phi, dt)
phi3 = mtl.utils.discretize(phi2, dt) phi3 = utils.discretize(phi2, dt)
assert phi2 == phi3 assert phi2 == phi3
@ -78,16 +80,16 @@ def test_scope():
dt = 0.3 dt = 0.3
phi = mtl.parse('@ap1') phi = mtl.parse('@ap1')
assert mtl.utils.scope(phi, dt) == 0.3 assert utils.scope(phi, dt) == 0.3
phi = mtl.parse('(@@ap1 | ap2)') phi = mtl.parse('(@@ap1 | ap2)')
assert mtl.utils.scope(phi, dt) == 0.6 assert utils.scope(phi, dt) == 0.6
phi = mtl.parse('G[0.3, 1.2] F[0.6, 1.5] ap1') phi = mtl.parse('G[0.3, 1.2] F[0.6, 1.5] ap1')
assert mtl.utils.scope(phi, dt) == 1.2 + 1.5 assert utils.scope(phi, dt) == 1.2 + 1.5
phi = mtl.parse('G[0.3, 1.2] F ap1') phi = mtl.parse('G[0.3, 1.2] F ap1')
assert mtl.utils.scope(phi, dt) == float('inf') assert utils.scope(phi, dt) == float('inf')
phi = mtl.parse('G[0.3, 1.2] (ap1 U ap2)') phi = mtl.parse('G[0.3, 1.2] (ap1 U ap2)')
assert mtl.utils.scope(phi, dt) == float('inf') assert utils.scope(phi, dt) == float('inf')

View file

@ -70,7 +70,7 @@ def _discretize(phi, dt, horizon):
upper = min(phi.interval.upper, horizon) upper = min(phi.interval.upper, horizon)
l, u = round(phi.interval.lower / dt), round(upper / dt) l, u = round(phi.interval.lower / dt), round(upper / dt)
psis = (next(_discretize(phi.arg, dt, horizon - i), i) psis = (_discretize(phi.arg, dt, horizon - i) >> i
for i in range(l, u + 1)) for i in range(l, u + 1))
opf = andf if isinstance(phi, G) else orf opf = andf if isinstance(phi, G) else orf
return opf(*psis) return opf(*psis)
@ -85,7 +85,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 mtl.utils.next(phi, i=i) return phi >> 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)
@ -105,44 +105,10 @@ def is_discretizable(phi, dt):
_interval_discretizable(c.interval, dt) for c in phi.walk() _interval_discretizable(c.interval, dt) for c in phi.walk()
if isinstance(c, G)) if isinstance(c, G))
# EDSL
def alw(phi, *, lo=0, hi=float('inf')):
return G(Interval(lo, hi), phi)
def env(phi, *, lo=0, hi=float('inf')):
return ~alw(~phi, lo=lo, hi=hi)
def andf(*args): def andf(*args):
return reduce(op.and_, args) if args else mtl.TOP return reduce(op.and_, args) if args else ast.TOP
def orf(*args): def orf(*args):
return reduce(op.or_, args) if args else mtl.TOP return reduce(op.or_, args) if args else ast.TOP
def implies(x, y):
return ~x | y
def xor(x, y):
return (x | y) & ~(x & y)
def iff(x, y):
return (x & y) | (~x & ~y)
def next(phi, i=1):
return phi >> i
def until(phi, psi):
return mtl.ast.WeakUntil(phi, psi) & env(psi)
def timed_until(phi, psi, lo, hi):
return env(psi, lo=lo, hi=hi) & alw(until(phi, psi), lo=0, hi=lo)