erl-CaTL 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- erl_catl-1.0.0/LICENSE +28 -0
- erl_catl-1.0.0/PKG-INFO +67 -0
- erl_catl-1.0.0/README.md +49 -0
- erl_catl-1.0.0/pyproject.toml +30 -0
- erl_catl-1.0.0/setup.cfg +4 -0
- erl_catl-1.0.0/src/catl/__init__.py +10 -0
- erl_catl-1.0.0/src/catl/catl.py +316 -0
- erl_catl-1.0.0/src/catl/catl2stl.py +110 -0
- erl_catl-1.0.0/src/catl/catlLexer.py +135 -0
- erl_catl-1.0.0/src/catl/catlListener.py +93 -0
- erl_catl-1.0.0/src/catl/catlParser.py +862 -0
- erl_catl-1.0.0/src/catl/catlVisitor.py +62 -0
- erl_catl-1.0.0/src/catl/check_system_constraints.py +86 -0
- erl_catl-1.0.0/src/catl/route_planning.py +396 -0
- erl_catl-1.0.0/src/catl/visualization.py +88 -0
- erl_catl-1.0.0/src/erl_CaTL.egg-info/PKG-INFO +67 -0
- erl_catl-1.0.0/src/erl_CaTL.egg-info/SOURCES.txt +19 -0
- erl_catl-1.0.0/src/erl_CaTL.egg-info/dependency_links.txt +1 -0
- erl_catl-1.0.0/src/erl_CaTL.egg-info/requires.txt +5 -0
- erl_catl-1.0.0/src/erl_CaTL.egg-info/top_level.txt +1 -0
- erl_catl-1.0.0/tests/test_simple.py +77 -0
erl_catl-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Explainable Robotics Lab (ERL)
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
erl_catl-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: erl-CaTL
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Capability Temporal Logic Tool
|
|
5
|
+
Author-email: Crockett Hensley <crh325@lehigh.edu>, Cristian-Ioan Vasile <crv519@lehigh.edu>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/erl-lehigh/catl
|
|
8
|
+
Project-URL: Repository, https://github.com/erl-lehigh/catl
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: pytelo>=1.0.0
|
|
13
|
+
Requires-Dist: networkx>=3.0
|
|
14
|
+
Requires-Dist: shapely>=2.0
|
|
15
|
+
Requires-Dist: pyyaml>=5.4
|
|
16
|
+
Requires-Dist: matplotlib>=3.7
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# Capability Temporal Logic (CaTL)
|
|
20
|
+
|
|
21
|
+
Setup
|
|
22
|
+
-----
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git clone git@github.com:wasserfeder/catl.git
|
|
26
|
+
mkdir -p catl/lib
|
|
27
|
+
cd catl/lib
|
|
28
|
+
wget 'https://www.antlr.org/download/antlr-4.8-complete.jar'
|
|
29
|
+
pip install antlr4-python2-runtime==4.8
|
|
30
|
+
sudo apt-get install default-jre
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For permanent settings use:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd <clone-dir>
|
|
37
|
+
echo "export CLASSPATH=\".:$PWD/lib/antlr-4.8-complete.jar:$CLASSPATH\"" >> ~/.bashrc
|
|
38
|
+
echo "alias antlr4=\"java -jar $PWD/lib/antlr-4.8-complete.jar -visitor\"" >> ~/.bashrc
|
|
39
|
+
echo "alias grun=\"java org.antlr.v4.gui.TestRig\"" >> ~/.bashrc
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Otherwise
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cd <clone-dir>
|
|
46
|
+
export CLASSPATH=".:$PWD/lib/antlr-4.8-complete.jar:$CLASSPATH"
|
|
47
|
+
alias antlr4="java -jar $PWD/lib/antlr-4.8-complete.jar -visitor"'
|
|
48
|
+
alias grun="java org.antlr.v4.gui.TestRig"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
where `<clone-dir>` is the directory where you cloned the `catl` repository.
|
|
52
|
+
|
|
53
|
+
Install *Gurobi* with *gurobipy* for python2, and optionally for python3.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
Run
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cd <clone-dir>/catl
|
|
61
|
+
antlr4 -Dlanguage=Python2 catl.g4
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**NOTE:** At the moment the implementation only supports python2. However, you
|
|
65
|
+
can generate lexers, parsers, listeners, and visitors for other target languages,
|
|
66
|
+
such as Java (default), C++, Python3, C#, Go, JavaScript, and Swift.
|
|
67
|
+
See http://www.antlr.org/download.html for more details.
|
erl_catl-1.0.0/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Capability Temporal Logic (CaTL)
|
|
2
|
+
|
|
3
|
+
Setup
|
|
4
|
+
-----
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
git clone git@github.com:wasserfeder/catl.git
|
|
8
|
+
mkdir -p catl/lib
|
|
9
|
+
cd catl/lib
|
|
10
|
+
wget 'https://www.antlr.org/download/antlr-4.8-complete.jar'
|
|
11
|
+
pip install antlr4-python2-runtime==4.8
|
|
12
|
+
sudo apt-get install default-jre
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For permanent settings use:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd <clone-dir>
|
|
19
|
+
echo "export CLASSPATH=\".:$PWD/lib/antlr-4.8-complete.jar:$CLASSPATH\"" >> ~/.bashrc
|
|
20
|
+
echo "alias antlr4=\"java -jar $PWD/lib/antlr-4.8-complete.jar -visitor\"" >> ~/.bashrc
|
|
21
|
+
echo "alias grun=\"java org.antlr.v4.gui.TestRig\"" >> ~/.bashrc
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Otherwise
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd <clone-dir>
|
|
28
|
+
export CLASSPATH=".:$PWD/lib/antlr-4.8-complete.jar:$CLASSPATH"
|
|
29
|
+
alias antlr4="java -jar $PWD/lib/antlr-4.8-complete.jar -visitor"'
|
|
30
|
+
alias grun="java org.antlr.v4.gui.TestRig"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
where `<clone-dir>` is the directory where you cloned the `catl` repository.
|
|
34
|
+
|
|
35
|
+
Install *Gurobi* with *gurobipy* for python2, and optionally for python3.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Run
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd <clone-dir>/catl
|
|
43
|
+
antlr4 -Dlanguage=Python2 catl.g4
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**NOTE:** At the moment the implementation only supports python2. However, you
|
|
47
|
+
can generate lexers, parsers, listeners, and visitors for other target languages,
|
|
48
|
+
such as Java (default), C++, Python3, C#, Go, JavaScript, and Swift.
|
|
49
|
+
See http://www.antlr.org/download.html for more details.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "erl-CaTL" # This becomes the pip name
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Capability Temporal Logic Tool"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "BSD-3-Clause"}
|
|
11
|
+
authors = [{name = "Crockett Hensley", email = "crh325@lehigh.edu"}, {name = "Cristian-Ioan Vasile", email = "crv519@lehigh.edu"}]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"pytelo>=1.0.0",
|
|
15
|
+
"networkx>=3.0",
|
|
16
|
+
"shapely>=2.0",
|
|
17
|
+
"pyyaml>=5.4",
|
|
18
|
+
"matplotlib>=3.7"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
"Homepage" = "https://github.com/erl-lehigh/catl"
|
|
24
|
+
"Repository" = "https://github.com/erl-lehigh/catl"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools]
|
|
27
|
+
package-dir = {"" = "src"}
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
where = ["src"]
|
erl_catl-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Copyright (C) 2018-2020 Cristian Ioan Vasile <cvasile@lehigh.edu>
|
|
3
|
+
Explainable Robotics Lab (ERL), Autonomous and Intelligent Robotics (AIR) Lab,
|
|
4
|
+
Lehigh University
|
|
5
|
+
Hybrid and Networked Systems (HyNeSs) Group, BU Robotics Lab, Boston University
|
|
6
|
+
See license.txt file for license information.
|
|
7
|
+
'''
|
|
8
|
+
from .catl import Operation, CATLFormula, CapabilityRequest
|
|
9
|
+
from .catl2stl import catl2stl
|
|
10
|
+
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Copyright (C) 2018-2020 Cristian Ioan Vasile <cvasile@lehigh.edu>
|
|
3
|
+
Explainable Robotics Lab (ERL), Autonomous and Intelligent Robotics (AIR) Lab,
|
|
4
|
+
Lehigh University
|
|
5
|
+
Hybrid and Networked Systems (HyNeSs) Group, BU Robotics Lab, Boston University
|
|
6
|
+
See license.txt file for license information.
|
|
7
|
+
'''
|
|
8
|
+
|
|
9
|
+
import itertools as it
|
|
10
|
+
from collections import namedtuple
|
|
11
|
+
|
|
12
|
+
from antlr4 import InputStream, CommonTokenStream, TerminalNode
|
|
13
|
+
|
|
14
|
+
from .catlLexer import catlLexer
|
|
15
|
+
from .catlParser import catlParser
|
|
16
|
+
from .catlVisitor import catlVisitor
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Operation(object):
|
|
20
|
+
'''CATL operations'''
|
|
21
|
+
NOP, NOT, OR, AND, IMPLIES, UNTIL, EVENT, ALWAYS, PRED, LIMIT, BOOL = \
|
|
22
|
+
range(11)
|
|
23
|
+
opnames = [None, '!', '||', '&&', '=>', 'U', 'F', 'G', 'T', 'L', 'bool']
|
|
24
|
+
opcodes = {'!': NOT, '&&': AND, '||' : OR, '=>': IMPLIES,
|
|
25
|
+
'U': UNTIL, 'F': EVENT, 'G': ALWAYS, 'T': PRED, 'L': LIMIT}
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def getCode(cls, text):
|
|
29
|
+
''' Gets the code corresponding to the string representation.'''
|
|
30
|
+
return cls.opcodes.get(text, cls.NOP)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def getString(cls, op):
|
|
34
|
+
'''Gets custom string representation for each operation.'''
|
|
35
|
+
return cls.opnames[op]
|
|
36
|
+
|
|
37
|
+
CapabilityRequest = namedtuple('CapabilityRequest', ['capability', 'count'])
|
|
38
|
+
ResourceRequest = namedtuple('ResourceRequest', ['resource', 'quantity'])
|
|
39
|
+
|
|
40
|
+
class CATLFormula(object):
|
|
41
|
+
'''Abstract Syntax Tree representation of an CATL formula'''
|
|
42
|
+
|
|
43
|
+
def __init__(self, operation, **kwargs):
|
|
44
|
+
'''Constructor'''
|
|
45
|
+
self.op = operation
|
|
46
|
+
|
|
47
|
+
if self.op == Operation.BOOL:
|
|
48
|
+
self.value = kwargs['value']
|
|
49
|
+
elif self.op == Operation.PRED:
|
|
50
|
+
self.duration = kwargs['duration']
|
|
51
|
+
self.proposition = kwargs['proposition']
|
|
52
|
+
self.capability_requests = kwargs['capabilities']
|
|
53
|
+
self.resource_requests = kwargs['resources']
|
|
54
|
+
elif self.op == Operation.LIMIT:
|
|
55
|
+
self.proposition = kwargs['proposition']
|
|
56
|
+
self.capability_requests = kwargs['capabilities']
|
|
57
|
+
self.resource_requests = kwargs['resources']
|
|
58
|
+
elif self.op in (Operation.AND, Operation.OR):
|
|
59
|
+
self.children = kwargs['children']
|
|
60
|
+
elif self.op == Operation.IMPLIES:
|
|
61
|
+
self.left = kwargs['left']
|
|
62
|
+
self.right = kwargs['right']
|
|
63
|
+
elif self.op == Operation.NOT:
|
|
64
|
+
self.child = kwargs['child']
|
|
65
|
+
elif self.op in(Operation.ALWAYS, Operation.EVENT):
|
|
66
|
+
self.low = kwargs['low']
|
|
67
|
+
self.high = kwargs['high']
|
|
68
|
+
self.child = kwargs['child']
|
|
69
|
+
elif self.op == Operation.UNTIL:
|
|
70
|
+
self.low = kwargs['low']
|
|
71
|
+
self.high = kwargs['high']
|
|
72
|
+
self.left = kwargs['left']
|
|
73
|
+
self.right = kwargs['right']
|
|
74
|
+
|
|
75
|
+
self.__string = None
|
|
76
|
+
self.__hash = None
|
|
77
|
+
|
|
78
|
+
def robustness(self, s, t):
|
|
79
|
+
'''Computes the robustness of the CATL formula.'''
|
|
80
|
+
raise NotImplementedError
|
|
81
|
+
|
|
82
|
+
def bound(self):
|
|
83
|
+
'''Computes the bound of the CATL formula.'''
|
|
84
|
+
if self.op == Operation.BOOL:
|
|
85
|
+
return 0
|
|
86
|
+
elif self.op == Operation.PRED:
|
|
87
|
+
return self.duration
|
|
88
|
+
elif self.op == Operation.LIMIT:
|
|
89
|
+
return 0
|
|
90
|
+
elif self.op in (Operation.AND, Operation.OR):
|
|
91
|
+
return max([ch.bound() for ch in self.children])
|
|
92
|
+
elif self.op == Operation.IMPLIES:
|
|
93
|
+
return max(self.left.bound(), self.right.bound())
|
|
94
|
+
elif self.op == Operation.NOT:
|
|
95
|
+
return self.child.bound()
|
|
96
|
+
elif self.op == Operation.UNTIL:
|
|
97
|
+
return self.high + max(self.left.bound(), self.right.bound())
|
|
98
|
+
elif self.op in (Operation.ALWAYS, Operation.EVENT):
|
|
99
|
+
return self.high + self.child.bound()
|
|
100
|
+
|
|
101
|
+
def propositions(self):
|
|
102
|
+
'''Computes the set of propositions involved in the CATL formula.'''
|
|
103
|
+
if self.op in (Operation.PRED, Operation.LIMIT):
|
|
104
|
+
return {self.proposition}
|
|
105
|
+
elif self.op in (Operation.AND, Operation.OR):
|
|
106
|
+
return set.union(*[child.propositions() for child in self.children])
|
|
107
|
+
elif self.op in (Operation.IMPLIES, Operation.UNTIL):
|
|
108
|
+
return self.left.propositions() | self.right.propositions()
|
|
109
|
+
elif self.op in (Operation.NOT, Operation.ALWAYS, Operation.EVENT):
|
|
110
|
+
return self.child.propositions()
|
|
111
|
+
|
|
112
|
+
def capabilities(self):
|
|
113
|
+
'''Computes the set of capabilities involved in the CATL formula.'''
|
|
114
|
+
if self.op in (Operation.PRED, Operation.LIMIT):
|
|
115
|
+
return {cr.capability for cr in self.capability_requests}
|
|
116
|
+
elif self.op in (Operation.AND, Operation.OR):
|
|
117
|
+
return set.union(*[child.capabilities() for child in self.children])
|
|
118
|
+
elif self.op in (Operation.IMPLIES, Operation.UNTIL):
|
|
119
|
+
return self.left.capabilities() | self.right.capabilities()
|
|
120
|
+
elif self.op in (Operation.NOT, Operation.ALWAYS, Operation.EVENT):
|
|
121
|
+
return self.child.capabilities()
|
|
122
|
+
|
|
123
|
+
def resources(self):
|
|
124
|
+
'''Computes the set of resources involved in the CATL formula.'''
|
|
125
|
+
if self.op in (Operation.PRED, Operation.LIMIT):
|
|
126
|
+
return {cr.resource for cr in self.resource_requests}
|
|
127
|
+
elif self.op in (Operation.AND, Operation.OR):
|
|
128
|
+
return set.union(*[child.resources() for child in self.children])
|
|
129
|
+
elif self.op in (Operation.IMPLIES, Operation.UNTIL):
|
|
130
|
+
return self.left.resources() | self.right.resources()
|
|
131
|
+
elif self.op in (Operation.NOT, Operation.ALWAYS, Operation.EVENT):
|
|
132
|
+
return self.child.resources()
|
|
133
|
+
|
|
134
|
+
def identifier(self):
|
|
135
|
+
'''Computes an integer identifier for the formula based on the object's
|
|
136
|
+
hash value.
|
|
137
|
+
'''
|
|
138
|
+
h = hash(self)
|
|
139
|
+
if h < 0:
|
|
140
|
+
h = hex(ord('-'))[2:] + hex(-h)[1:]
|
|
141
|
+
else:
|
|
142
|
+
h = hex(ord('+'))[2:] + hex(h)[1:]
|
|
143
|
+
return h
|
|
144
|
+
|
|
145
|
+
def __hash__(self):
|
|
146
|
+
if self.__hash is None:
|
|
147
|
+
self.__hash = hash(str(self))
|
|
148
|
+
return self.__hash
|
|
149
|
+
|
|
150
|
+
def __eq__(self, other):
|
|
151
|
+
return str(self) == str(other)
|
|
152
|
+
|
|
153
|
+
def __ne__(self, other):
|
|
154
|
+
return not self.__eq__(other)
|
|
155
|
+
|
|
156
|
+
def __str__(self):
|
|
157
|
+
if self.__string is not None:
|
|
158
|
+
return self.__string
|
|
159
|
+
|
|
160
|
+
opname = Operation.getString(self.op)
|
|
161
|
+
if self.op == Operation.BOOL:
|
|
162
|
+
s = str(self.value)
|
|
163
|
+
elif self.op == Operation.PRED:
|
|
164
|
+
s = '({p} {d} {caps} {res})'.format(p=self.proposition,
|
|
165
|
+
d=self.duration,
|
|
166
|
+
caps=self.capability_requests,
|
|
167
|
+
res=self.resource_requests)
|
|
168
|
+
elif self.op == Operation.LIMIT:
|
|
169
|
+
s = '(! {p} {caps} {res})'.format(p=self.proposition,
|
|
170
|
+
caps=self.capability_requests,
|
|
171
|
+
res=self.resource_requests)
|
|
172
|
+
elif self.op == Operation.IMPLIES:
|
|
173
|
+
s = '({left} {op} {right})'.format(left=self.left, op=opname,
|
|
174
|
+
right=self.right)
|
|
175
|
+
elif self.op in (Operation.AND, Operation.OR):
|
|
176
|
+
children = [str(child) for child in self.children]
|
|
177
|
+
s = '(' + ' {op} '.format(op=opname).join(children) + ')'
|
|
178
|
+
elif self.op == Operation.NOT:
|
|
179
|
+
s = '{op} {child}'.format(op=opname, child=self.child)
|
|
180
|
+
elif self.op == Operation.UNTIL:
|
|
181
|
+
s = '({left} {op}[{low}, {high}] {right})'.format(op=opname,
|
|
182
|
+
left=self.left, right=self.right, low=self.low, high=self.high)
|
|
183
|
+
elif self.op in (Operation.ALWAYS, Operation.EVENT):
|
|
184
|
+
s = '({op}[{low}, {high}] {child})'.format(op=opname,
|
|
185
|
+
low=self.low, high=self.high, child=self.child)
|
|
186
|
+
else:
|
|
187
|
+
raise ValueError('Unknown operation {}!'.format(self.op))
|
|
188
|
+
self.__string = s
|
|
189
|
+
return self.__string
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
def from_formula(cls, formula):
|
|
193
|
+
'''Creates a CATLFormula object from a formula string.
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
----------
|
|
197
|
+
formula (str) CATL formula
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
(CATLFormula) an AST of the CATL formula
|
|
202
|
+
'''
|
|
203
|
+
lexer = catlLexer(InputStream(formula))
|
|
204
|
+
tokens = CommonTokenStream(lexer)
|
|
205
|
+
parser = catlParser(tokens)
|
|
206
|
+
t = parser.catlProperty()
|
|
207
|
+
return CATLAbstractSyntaxTreeExtractor().visit(t)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class CATLAbstractSyntaxTreeExtractor(catlVisitor):
|
|
211
|
+
'''Parse Tree visitor that constructs the AST of an CATL formula'''
|
|
212
|
+
|
|
213
|
+
def visitFormula(self, ctx):
|
|
214
|
+
op = Operation.getCode(ctx.op.text)
|
|
215
|
+
ret = None
|
|
216
|
+
low = -1
|
|
217
|
+
high = -1
|
|
218
|
+
if op in (Operation.AND, Operation.OR):
|
|
219
|
+
left = self.visit(ctx.left)
|
|
220
|
+
right = self.visit(ctx.right)
|
|
221
|
+
assert op != right.op
|
|
222
|
+
if left.op == op:
|
|
223
|
+
children = left.children
|
|
224
|
+
else:
|
|
225
|
+
children = [left]
|
|
226
|
+
children.append(right)
|
|
227
|
+
ret = CATLFormula(op, children=children)
|
|
228
|
+
elif op == Operation.IMPLIES:
|
|
229
|
+
ret = CATLFormula(op, left=self.visit(ctx.left),
|
|
230
|
+
right=self.visit(ctx.right))
|
|
231
|
+
elif op == Operation.NOT:
|
|
232
|
+
ret = CATLFormula(op, child=self.visit(ctx.child))
|
|
233
|
+
elif op == Operation.UNTIL:
|
|
234
|
+
low = float(ctx.low.text)
|
|
235
|
+
high = float(ctx.high.text)
|
|
236
|
+
ret = CATLFormula(op, left=self.visit(ctx.left),
|
|
237
|
+
right=self.visit(ctx.right), low=low, high=high)
|
|
238
|
+
elif op in (Operation.ALWAYS, Operation.EVENT):
|
|
239
|
+
low = float(ctx.low.text)
|
|
240
|
+
high = float(ctx.high.text)
|
|
241
|
+
ret = CATLFormula(op, child=self.visit(ctx.child),
|
|
242
|
+
low=low, high=high)
|
|
243
|
+
else:
|
|
244
|
+
print('Error: unknown operation!')
|
|
245
|
+
return ret
|
|
246
|
+
|
|
247
|
+
def visitCatlPredicate(self, ctx):
|
|
248
|
+
return self.visit(ctx.predicate())
|
|
249
|
+
|
|
250
|
+
def visitPredicate(self, ctx):
|
|
251
|
+
if Operation.getCode(ctx.op.text) == Operation.PRED:
|
|
252
|
+
if ctx.resources():
|
|
253
|
+
resources = self.visit(ctx.resources())
|
|
254
|
+
else:
|
|
255
|
+
resources = set()
|
|
256
|
+
return CATLFormula(Operation.PRED, duration=int(ctx.duration.text),
|
|
257
|
+
proposition=ctx.proposition.text,
|
|
258
|
+
capabilities=self.visit(ctx.capabilities()),
|
|
259
|
+
resources=resources)
|
|
260
|
+
return CATLFormula(Operation.BOOL, value=bool(ctx.op.text))
|
|
261
|
+
|
|
262
|
+
def visitCatlLimit(self, ctx):
|
|
263
|
+
return self.visit(ctx.limit())
|
|
264
|
+
|
|
265
|
+
def visitLimit(self, ctx):
|
|
266
|
+
if Operation.getCode(ctx.op.text) == Operation.LIMIT:
|
|
267
|
+
if ctx.capabilities():
|
|
268
|
+
capabilities = self.visit(ctx.capabilities())
|
|
269
|
+
else:
|
|
270
|
+
capabilities = set()
|
|
271
|
+
if ctx.resources():
|
|
272
|
+
resources = self.visit(ctx.resources())
|
|
273
|
+
else:
|
|
274
|
+
resources = set()
|
|
275
|
+
if not capabilities and not resources:
|
|
276
|
+
raise Exception('Limit operator must constrain either agents'
|
|
277
|
+
' or resources!')
|
|
278
|
+
return CATLFormula(Operation.LIMIT,
|
|
279
|
+
proposition=ctx.proposition.text,
|
|
280
|
+
capabilities=capabilities,
|
|
281
|
+
resources=resources)
|
|
282
|
+
return CATLFormula(Operation.BOOL, value=bool(ctx.op.text))
|
|
283
|
+
|
|
284
|
+
def visitCapabilities(self, ctx):
|
|
285
|
+
return {self.visit(ch) for ch in ctx.children
|
|
286
|
+
if not isinstance(ch, TerminalNode)}
|
|
287
|
+
|
|
288
|
+
def visitCapabilityRequest(self, ctx):
|
|
289
|
+
return CapabilityRequest(capability=ctx.cap.text,
|
|
290
|
+
count=int(ctx.count.text))
|
|
291
|
+
|
|
292
|
+
def visitResources(self, ctx):
|
|
293
|
+
return {self.visit(ch) for ch in ctx.children
|
|
294
|
+
if not isinstance(ch, TerminalNode)}
|
|
295
|
+
|
|
296
|
+
def visitResourceRequest(self, ctx):
|
|
297
|
+
return ResourceRequest(resource=ctx.res.text,
|
|
298
|
+
quantity=float(ctx.quantity.text))
|
|
299
|
+
|
|
300
|
+
def visitParprop(self, ctx):
|
|
301
|
+
return self.visit(ctx.child)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
if __name__ == '__main__':
|
|
305
|
+
ast = CATLFormula.from_formula(
|
|
306
|
+
'F[0, 2] T(4, test, {(a, 2), (b, 3)})'
|
|
307
|
+
'&& G[1, 7] T(2, test, {(a, 1), (c, 4)}, {(h1, 2.3), (h2, 5)})'
|
|
308
|
+
'&& F[3, 5] T(3, test2, {(b, 1), (d, 2)})'
|
|
309
|
+
'&& F[3, 5] L(test2, {(b, 1), (d, 2)})'
|
|
310
|
+
'&& F[3, 5] L(test2, {}, {(h1, 2.3), (h2, 5)})'
|
|
311
|
+
'&& F[3, 5] L(test2, {(b, 1), (d, 2)}, {(h1, 2.3), (h2, 5)})')
|
|
312
|
+
print('AST:', str(ast))
|
|
313
|
+
print('Propositions:', ast.propositions())
|
|
314
|
+
print('Capabilities:', ast.capabilities())
|
|
315
|
+
print('Resources:', ast.resources())
|
|
316
|
+
print('Bound:', ast.bound())
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Copyright (C) 2018-2020 Cristian Ioan Vasile <cvasile@lehigh.edu>
|
|
3
|
+
Explainable Robotics Lab (ERL), Autonomous and Intelligent Robotics (AIR) Lab,
|
|
4
|
+
Lehigh University
|
|
5
|
+
Hybrid and Networked Systems (HyNeSs) Group, BU Robotics Lab, Boston University
|
|
6
|
+
See license.txt file for license information.
|
|
7
|
+
'''
|
|
8
|
+
|
|
9
|
+
from antlr4 import InputStream, CommonTokenStream
|
|
10
|
+
|
|
11
|
+
from .catl import Operation as CATLOperation
|
|
12
|
+
from .catl import CATLFormula
|
|
13
|
+
from .catl import CATLAbstractSyntaxTreeExtractor
|
|
14
|
+
|
|
15
|
+
from .catlLexer import catlLexer
|
|
16
|
+
from .catlParser import catlParser
|
|
17
|
+
|
|
18
|
+
from pytelo.stl import Operation as STLOperation
|
|
19
|
+
from pytelo.stl import RelOperation as STLRelOperation
|
|
20
|
+
from pytelo.stl import STLFormula
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def catl2stl(catl_ast):
|
|
24
|
+
'''Translates a CATL abstract syntax tree to an STL one.
|
|
25
|
+
The set of variables in the STL formula is the Cartesian product between the
|
|
26
|
+
set of atomic propositions and the set of capabilities in the CATL formula.
|
|
27
|
+
The variable labels are in the form "z_{proposition}_{capability}".
|
|
28
|
+
The STL predicates capture the minimum number ``n''of agents with capability
|
|
29
|
+
``c'' needed at regions labeled with proposition ``p'', i.e.,
|
|
30
|
+
``z_{\pi,c} \geq n''. Thus, a CATL task is translated to STL as follows
|
|
31
|
+
|
|
32
|
+
.. math ::
|
|
33
|
+
|
|
34
|
+
T(d, \pi, \{(c_1, n_1), \ldots,(c_m, n_m)\}) \equiv
|
|
35
|
+
\box_{[0, d]} \bigcup_{i=1}^{m} (z_{\pi, c_i} \geq n_i)
|
|
36
|
+
'''
|
|
37
|
+
if catl_ast.op == CATLOperation.BOOL:
|
|
38
|
+
return STLFormula(STLOperation.BOOL, value=catl_ast.value)
|
|
39
|
+
elif catl_ast.op == CATLOperation.PRED:
|
|
40
|
+
var = catl_ast.proposition + '_{cap}'
|
|
41
|
+
conjunction_terms = \
|
|
42
|
+
[STLFormula(STLOperation.PRED, relation=STLRelOperation.GE,
|
|
43
|
+
variable=var.format(cap=cap), threshold=th)
|
|
44
|
+
for cap, th in catl_ast.capability_requests]
|
|
45
|
+
child = STLFormula(STLOperation.AND, children=conjunction_terms)
|
|
46
|
+
|
|
47
|
+
return STLFormula(STLOperation.ALWAYS, low=0, high=catl_ast.duration,
|
|
48
|
+
child=child)
|
|
49
|
+
elif catl_ast.op == CATLOperation.LIMIT:
|
|
50
|
+
var = catl_ast.proposition + '_{cap}'
|
|
51
|
+
conjunction_terms = \
|
|
52
|
+
[STLFormula(STLOperation.PRED, relation=STLRelOperation.LE,
|
|
53
|
+
variable=var.format(cap=cap), threshold=th)
|
|
54
|
+
for cap, th in catl_ast.capability_requests]
|
|
55
|
+
return STLFormula(STLOperation.AND, children=conjunction_terms)
|
|
56
|
+
elif catl_ast.op in (CATLOperation.AND, CATLOperation.OR):
|
|
57
|
+
children = [catl2stl(ch) for ch in catl_ast.children]
|
|
58
|
+
if catl_ast.op == CATLOperation.AND:
|
|
59
|
+
op = STLOperation.AND
|
|
60
|
+
else:
|
|
61
|
+
op = STLOperation.OR
|
|
62
|
+
return STLFormula(op, children=children)
|
|
63
|
+
elif catl_ast.op == CATLOperation.IMPLIES:
|
|
64
|
+
left = catl2stl(catl_ast.left)
|
|
65
|
+
right = catl2stl(catl_ast.right)
|
|
66
|
+
return STLFormula(STLOperation.IMPLIES, left=left, right=right)
|
|
67
|
+
elif catl_ast.op == CATLOperation.NOT:
|
|
68
|
+
child = catl2stl(catl_ast.child)
|
|
69
|
+
return STLFormula(STLOperation.NOT, child=child)
|
|
70
|
+
elif catl_ast.op in (CATLOperation.ALWAYS, CATLOperation.EVENT):
|
|
71
|
+
child = catl2stl(catl_ast.child)
|
|
72
|
+
if catl_ast.op == CATLOperation.ALWAYS:
|
|
73
|
+
op = STLOperation.ALWAYS
|
|
74
|
+
else:
|
|
75
|
+
op = STLOperation.EVENT
|
|
76
|
+
return STLFormula(op, child=child, low=catl_ast.low, high=catl_ast.high)
|
|
77
|
+
elif catl_ast.op == CATLOperation.UNTIL:
|
|
78
|
+
left = catl2stl(catl_ast.left)
|
|
79
|
+
right = catl2stl(catl_ast.right)
|
|
80
|
+
return STLFormula(STLOperation.UNTIL, left=left, right=right,
|
|
81
|
+
low=catl_ast.low, high=catl_ast.high)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == '__main__':
|
|
85
|
+
formulae = (
|
|
86
|
+
'F[0, 2] T(4, test, {(a, 2), (b, 3)})'
|
|
87
|
+
'&& G[1, 7] T(2, test, {(a, 1), (c, 4)})'
|
|
88
|
+
'&& F[3, 5] T(3, test2, {(b, 1), (d, 2)})'
|
|
89
|
+
'&& F[3, 5] L(test2, {(b, 1), (d, 2)})',
|
|
90
|
+
#
|
|
91
|
+
'F[0,25] T(3,blue,{(C1,1),(C2,1)})'
|
|
92
|
+
'&& F[0,25] T(2,orange,{(C1,1),(C2,1)})'
|
|
93
|
+
'&& (L(orange,{(C1,1),(C2,1)}) U[0,25] T(3,blue,{(C1,1),(C2,1)}))'
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
for formula in formulae:
|
|
97
|
+
print('Formula:', formula)
|
|
98
|
+
lexer = catlLexer(InputStream(formula))
|
|
99
|
+
|
|
100
|
+
tokens = CommonTokenStream(lexer)
|
|
101
|
+
|
|
102
|
+
parser = catlParser(tokens)
|
|
103
|
+
t = parser.catlProperty()
|
|
104
|
+
print(t.toStringTree())
|
|
105
|
+
|
|
106
|
+
ast = CATLAbstractSyntaxTreeExtractor().visit(t)
|
|
107
|
+
print('CATL:', ast)
|
|
108
|
+
|
|
109
|
+
stl = catl2stl(ast)
|
|
110
|
+
print('STL:', stl)
|