mstlo-python 0.1.0__cp311-cp311-win_amd64.whl
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.
- mstlo_python/__init__.py +11 -0
- mstlo_python/mstlo_python.cp311-win_amd64.pyd +0 -0
- mstlo_python/mstlo_python.pyi +1022 -0
- mstlo_python-0.1.0.dist-info/METADATA +80 -0
- mstlo_python-0.1.0.dist-info/RECORD +7 -0
- mstlo_python-0.1.0.dist-info/WHEEL +4 -0
- mstlo_python-0.1.0.dist-info/sboms/mstlo-python.cyclonedx.json +586 -0
|
@@ -0,0 +1,1022 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Online Signal Temporal Logic (STL) monitoring library.
|
|
3
|
+
|
|
4
|
+
This library provides efficient online monitoring of STL formulas with multiple semantics:
|
|
5
|
+
|
|
6
|
+
* DelayedQualitative/EagerQualitative: classic true/false evaluation
|
|
7
|
+
* DelayedQuantitative: robustness as a single float value
|
|
8
|
+
* Rosi: robustness as an interval (min, max)
|
|
9
|
+
|
|
10
|
+
Examples using `parse_formula` with Rust-style DSL syntax:
|
|
11
|
+
|
|
12
|
+
>>> import mstlo_python
|
|
13
|
+
>>> # Parse formula using the same DSL syntax as Rust's stl! macro
|
|
14
|
+
>>> phi = mstlo_python.parse_formula('G[0, 5](x > 0.5)')
|
|
15
|
+
>>> # Create monitor with DelayedQuantitative semantics
|
|
16
|
+
>>> monitor = mstlo_python.Monitor(phi, semantics='DelayedQuantitative')
|
|
17
|
+
>>> # Feed data
|
|
18
|
+
>>> output = monitor.update('x', 1.0, 0.0)
|
|
19
|
+
>>> # Print using Rust's Display formatting
|
|
20
|
+
>>> print(output)
|
|
21
|
+
>>> # Access verdicts
|
|
22
|
+
>>> for ts, val in output.verdicts():
|
|
23
|
+
... print(f"t={ts}: {val}")
|
|
24
|
+
>>> # Access structured data
|
|
25
|
+
>>> print(output.to_dict())
|
|
26
|
+
|
|
27
|
+
Examples using Formula builder methods:
|
|
28
|
+
|
|
29
|
+
>>> import mstlo_python
|
|
30
|
+
>>> # Create formula: Always[0,5](x > 0.5)
|
|
31
|
+
>>> phi = mstlo_python.Formula.always(0, 5, mstlo_python.Formula.gt('x', 0.5))
|
|
32
|
+
>>> # Create monitor with Rosi semantics
|
|
33
|
+
>>> monitor = mstlo_python.Monitor(phi, semantics='Rosi')
|
|
34
|
+
>>> # Feed data
|
|
35
|
+
>>> output = monitor.update('x', 1.0, 0.0)
|
|
36
|
+
>>> # Use __str__ and __repr__ for Rust-style formatting
|
|
37
|
+
>>> print(str(output)) # Display format
|
|
38
|
+
>>> print(repr(output)) # Debug format
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from typing import Literal, TypedDict, Union, List, Tuple
|
|
42
|
+
|
|
43
|
+
__version__: str
|
|
44
|
+
|
|
45
|
+
def parse_formula(formula_str: str) -> "Formula":
|
|
46
|
+
"""
|
|
47
|
+
Parse an STL formula from a string using the same DSL syntax as Rust's `stl!` macro.
|
|
48
|
+
|
|
49
|
+
This allows you to write formulas using the same syntax as Rust, making it easy
|
|
50
|
+
to port formulas between Python and Rust code.
|
|
51
|
+
|
|
52
|
+
**Syntax:**
|
|
53
|
+
|
|
54
|
+
*Predicates:*
|
|
55
|
+
|
|
56
|
+
* `signal > value` - Signal greater than value
|
|
57
|
+
* `signal < value` - Signal less than value
|
|
58
|
+
* `signal >= value` - Signal greater than or equal
|
|
59
|
+
* `signal <= value` - Signal less than or equal
|
|
60
|
+
|
|
61
|
+
*Variable Predicates (for runtime-updateable thresholds):*
|
|
62
|
+
|
|
63
|
+
* `signal > $variable` - Signal greater than variable
|
|
64
|
+
* `signal < $variable` - Signal less than variable
|
|
65
|
+
* `signal >= $variable` - Signal greater than or equal to variable
|
|
66
|
+
* `signal <= $variable` - Signal less than or equal to variable
|
|
67
|
+
|
|
68
|
+
*Boolean Constants:*
|
|
69
|
+
|
|
70
|
+
* `true` - Always true
|
|
71
|
+
* `false` - Always false
|
|
72
|
+
|
|
73
|
+
*Unary Operators:*
|
|
74
|
+
|
|
75
|
+
* `!(sub)` or `not(sub)` - Negation
|
|
76
|
+
* `G[start, end](sub)` or `globally[start, end](sub)` - Globally (always)
|
|
77
|
+
* `F[start, end](sub)` or `eventually[start, end](sub)` - Eventually (finally)
|
|
78
|
+
|
|
79
|
+
*Binary Operators:*
|
|
80
|
+
|
|
81
|
+
* `left && right` or `left and right` - Conjunction
|
|
82
|
+
* `left || right` or `left or right` - Disjunction
|
|
83
|
+
* `left -> right` or `left implies right` - Implication
|
|
84
|
+
* `left U[start, end] right` or `left until[start, end] right` - Until
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
formula_str: A string containing an STL formula
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
The parsed Formula object
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If the formula string cannot be parsed
|
|
94
|
+
|
|
95
|
+
Examples:
|
|
96
|
+
>>> # Simple predicate
|
|
97
|
+
>>> f = parse_formula("x > 5")
|
|
98
|
+
>>> # Globally operator
|
|
99
|
+
>>> f = parse_formula("G[0, 10](x > 5)")
|
|
100
|
+
>>> # Complex formula
|
|
101
|
+
>>> f = parse_formula("G[0, 10](x > 5) && F[0, 5](y < 3)")
|
|
102
|
+
>>> # Using keyword syntax
|
|
103
|
+
>>> f = parse_formula("globally[0, 10](x > 5) and eventually[0, 5](y < 3)")
|
|
104
|
+
>>> # Using variables for runtime-updateable thresholds
|
|
105
|
+
>>> f = parse_formula("G[0, 10](x > $threshold)")
|
|
106
|
+
>>> f = parse_formula("x > $min && x < $max")
|
|
107
|
+
"""
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
class OutputDict(TypedDict):
|
|
111
|
+
"""A single output verdict."""
|
|
112
|
+
|
|
113
|
+
timestamp: float
|
|
114
|
+
"""The timestamp this verdict is for."""
|
|
115
|
+
value: Union[bool, float, Tuple[float, float]]
|
|
116
|
+
"""The verdict value. Type depends on monitor semantics:
|
|
117
|
+
|
|
118
|
+
* bool for qualitative semantics
|
|
119
|
+
* float for quantitative semantics
|
|
120
|
+
* tuple[float, float] for Rosi semantics (min, max)
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
class EvaluationDict(TypedDict):
|
|
124
|
+
"""Result of evaluating a synchronized step."""
|
|
125
|
+
|
|
126
|
+
sync_step_signal: str
|
|
127
|
+
"""Signal name of the synchronized step (may be interpolated)."""
|
|
128
|
+
sync_step_timestamp: float
|
|
129
|
+
"""Timestamp of the synchronized step."""
|
|
130
|
+
sync_step_value: float
|
|
131
|
+
"""Value of the synchronized step."""
|
|
132
|
+
outputs: List[OutputDict]
|
|
133
|
+
"""List of output verdicts produced by evaluating this sync step."""
|
|
134
|
+
|
|
135
|
+
class MonitorOutputDict(TypedDict):
|
|
136
|
+
"""Output from a monitor update operation."""
|
|
137
|
+
|
|
138
|
+
input_signal: str
|
|
139
|
+
"""The signal name that was updated."""
|
|
140
|
+
input_timestamp: float
|
|
141
|
+
"""The timestamp of the input that triggered this update."""
|
|
142
|
+
evaluations: List[EvaluationDict]
|
|
143
|
+
"""List of evaluations, one for each synchronized step. May be empty if data is buffered."""
|
|
144
|
+
|
|
145
|
+
class Variables:
|
|
146
|
+
"""
|
|
147
|
+
Container for runtime variable values.
|
|
148
|
+
|
|
149
|
+
Variables allow you to define STL formulas with dynamic thresholds that
|
|
150
|
+
can be updated at runtime. Use the `$variable` syntax in formulas to
|
|
151
|
+
reference variables.
|
|
152
|
+
|
|
153
|
+
Examples:
|
|
154
|
+
>>> # Create variables and set initial values
|
|
155
|
+
>>> vars = Variables()
|
|
156
|
+
>>> vars.set("threshold", 5.0)
|
|
157
|
+
>>> vars.set("limit", 10.0)
|
|
158
|
+
>>>
|
|
159
|
+
>>> # Parse a formula using variables
|
|
160
|
+
>>> formula = parse_formula("x > $threshold && y < $limit")
|
|
161
|
+
>>>
|
|
162
|
+
>>> # Create a monitor with variables
|
|
163
|
+
>>> monitor = Monitor(formula, variables=vars)
|
|
164
|
+
>>>
|
|
165
|
+
>>> # Update a variable value at runtime
|
|
166
|
+
>>> vars.set("threshold", 7.0) # Affects future evaluations
|
|
167
|
+
|
|
168
|
+
Note:
|
|
169
|
+
Variable predicates require the Incremental algorithm.
|
|
170
|
+
Using variables with the Naive algorithm will raise an error.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def __init__(self) -> None:
|
|
174
|
+
"""
|
|
175
|
+
Create a new empty Variables container.
|
|
176
|
+
|
|
177
|
+
Examples:
|
|
178
|
+
>>> vars = Variables()
|
|
179
|
+
"""
|
|
180
|
+
...
|
|
181
|
+
|
|
182
|
+
def set(self, name: str, value: float) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Set a variable to a value.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
name: The variable name (without $ prefix)
|
|
188
|
+
value: The variable value
|
|
189
|
+
|
|
190
|
+
Examples:
|
|
191
|
+
>>> vars = Variables()
|
|
192
|
+
>>> vars.set("threshold", 5.0)
|
|
193
|
+
"""
|
|
194
|
+
...
|
|
195
|
+
|
|
196
|
+
def get(self, name: str) -> Union[float, None]:
|
|
197
|
+
"""
|
|
198
|
+
Get a variable's value.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
name: The variable name (without $ prefix)
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
The variable's value, or None if not set.
|
|
205
|
+
|
|
206
|
+
Examples:
|
|
207
|
+
>>> vars = Variables()
|
|
208
|
+
>>> vars.set("x", 5.0)
|
|
209
|
+
>>> print(vars.get("x")) # prints 5.0
|
|
210
|
+
>>> print(vars.get("y")) # prints None
|
|
211
|
+
"""
|
|
212
|
+
...
|
|
213
|
+
|
|
214
|
+
def contains(self, name: str) -> bool:
|
|
215
|
+
"""
|
|
216
|
+
Check if a variable exists.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
name: The variable name (without $ prefix)
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
True if the variable exists, False otherwise.
|
|
223
|
+
|
|
224
|
+
Examples:
|
|
225
|
+
>>> vars = Variables()
|
|
226
|
+
>>> vars.set("x", 5.0)
|
|
227
|
+
>>> vars.contains("x") # True
|
|
228
|
+
>>> vars.contains("y") # False
|
|
229
|
+
"""
|
|
230
|
+
...
|
|
231
|
+
|
|
232
|
+
def names(self) -> List[str]:
|
|
233
|
+
"""
|
|
234
|
+
Get a list of all variable names.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
A list of variable names.
|
|
238
|
+
|
|
239
|
+
Examples:
|
|
240
|
+
>>> vars = Variables()
|
|
241
|
+
>>> vars.set("a", 1.0)
|
|
242
|
+
>>> vars.set("b", 2.0)
|
|
243
|
+
>>> vars.names() # ['a', 'b']
|
|
244
|
+
"""
|
|
245
|
+
...
|
|
246
|
+
|
|
247
|
+
def remove(self, name: str) -> Union[float, None]:
|
|
248
|
+
"""
|
|
249
|
+
Remove a variable.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
name: The variable name to remove (without $ prefix)
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
The variable's previous value, or None if it didn't exist.
|
|
256
|
+
|
|
257
|
+
Examples:
|
|
258
|
+
>>> vars = Variables()
|
|
259
|
+
>>> vars.set("x", 5.0)
|
|
260
|
+
>>> vars.remove("x") # returns 5.0
|
|
261
|
+
>>> vars.remove("x") # returns None
|
|
262
|
+
"""
|
|
263
|
+
...
|
|
264
|
+
|
|
265
|
+
def clear(self) -> None:
|
|
266
|
+
"""
|
|
267
|
+
Remove all variables.
|
|
268
|
+
|
|
269
|
+
Examples:
|
|
270
|
+
>>> vars = Variables()
|
|
271
|
+
>>> vars.set("x", 5.0)
|
|
272
|
+
>>> vars.clear()
|
|
273
|
+
>>> vars.names() # []
|
|
274
|
+
"""
|
|
275
|
+
...
|
|
276
|
+
|
|
277
|
+
def __str__(self) -> str:
|
|
278
|
+
"""Return string representation of the Variables."""
|
|
279
|
+
...
|
|
280
|
+
|
|
281
|
+
def __repr__(self) -> str:
|
|
282
|
+
"""Return detailed representation of the Variables."""
|
|
283
|
+
...
|
|
284
|
+
|
|
285
|
+
class Formula:
|
|
286
|
+
"""
|
|
287
|
+
Signal Temporal Logic (STL) formula.
|
|
288
|
+
|
|
289
|
+
Use static methods to construct formulas. Formulas can be composed using
|
|
290
|
+
boolean and temporal operators.
|
|
291
|
+
|
|
292
|
+
Examples:
|
|
293
|
+
>>> # G[0,5](x > 0.5)
|
|
294
|
+
>>> phi = Formula.always(0, 5, Formula.gt('x', 0.5))
|
|
295
|
+
>>> # (x > 0.3) AND F[0,3](y < 0.8)
|
|
296
|
+
>>> phi2 = Formula.and_(
|
|
297
|
+
... Formula.gt('x', 0.3),
|
|
298
|
+
... Formula.eventually(0, 3, Formula.lt('y', 0.8))
|
|
299
|
+
... )
|
|
300
|
+
>>> # Using variables for dynamic thresholds
|
|
301
|
+
>>> phi3 = Formula.gt_var('x', 'threshold')
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
@staticmethod
|
|
305
|
+
def gt(signal: str, value: float) -> "Formula":
|
|
306
|
+
"""
|
|
307
|
+
Create a greater-than atomic predicate.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
signal: Name of the signal to compare
|
|
311
|
+
value: Threshold value to compare against
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
Formula representing: `signal > value`
|
|
315
|
+
|
|
316
|
+
Examples:
|
|
317
|
+
>>> Formula.gt('x', 0.5) # x > 0.5
|
|
318
|
+
"""
|
|
319
|
+
...
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def lt(signal: str, value: float) -> "Formula":
|
|
323
|
+
"""
|
|
324
|
+
Create a less-than atomic predicate.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
signal: Name of the signal to compare
|
|
328
|
+
value: Threshold value to compare against
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Formula representing: `signal < value`
|
|
332
|
+
|
|
333
|
+
Examples:
|
|
334
|
+
>>> Formula.lt('x', 0.5) # x < 0.5
|
|
335
|
+
"""
|
|
336
|
+
...
|
|
337
|
+
|
|
338
|
+
@staticmethod
|
|
339
|
+
def gt_var(signal: str, variable: str) -> "Formula":
|
|
340
|
+
"""
|
|
341
|
+
Create a greater-than atomic predicate with a variable threshold.
|
|
342
|
+
|
|
343
|
+
The variable value is looked up at runtime from a Variables object,
|
|
344
|
+
allowing dynamic thresholds that can be updated on-the-fly.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
signal: Name of the signal to compare
|
|
348
|
+
variable: Name of the variable (without $ prefix)
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Formula representing: `signal > $variable`
|
|
352
|
+
|
|
353
|
+
Note:
|
|
354
|
+
Requires Incremental algorithm.
|
|
355
|
+
The Naive algorithm does not support variables.
|
|
356
|
+
|
|
357
|
+
Examples:
|
|
358
|
+
>>> Formula.gt_var('x', 'threshold') # x > $threshold
|
|
359
|
+
"""
|
|
360
|
+
...
|
|
361
|
+
|
|
362
|
+
@staticmethod
|
|
363
|
+
def lt_var(signal: str, variable: str) -> "Formula":
|
|
364
|
+
"""
|
|
365
|
+
Create a less-than atomic predicate with a variable threshold.
|
|
366
|
+
|
|
367
|
+
The variable value is looked up at runtime from a Variables object,
|
|
368
|
+
allowing dynamic thresholds that can be updated on-the-fly.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
signal: Name of the signal to compare
|
|
372
|
+
variable: Name of the variable (without $ prefix)
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Formula representing: `signal < $variable`
|
|
376
|
+
|
|
377
|
+
Note:
|
|
378
|
+
Requires Incremental algorithm.
|
|
379
|
+
The Naive algorithm does not support variables.
|
|
380
|
+
|
|
381
|
+
Examples:
|
|
382
|
+
>>> Formula.lt_var('y', 'limit') # y < $limit
|
|
383
|
+
"""
|
|
384
|
+
...
|
|
385
|
+
|
|
386
|
+
@staticmethod
|
|
387
|
+
def true_() -> "Formula":
|
|
388
|
+
"""
|
|
389
|
+
Create a constant true formula.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Formula that is always satisfied (⊤)
|
|
393
|
+
|
|
394
|
+
Examples:
|
|
395
|
+
>>> Formula.true_()
|
|
396
|
+
"""
|
|
397
|
+
...
|
|
398
|
+
|
|
399
|
+
@staticmethod
|
|
400
|
+
def false_() -> "Formula":
|
|
401
|
+
"""
|
|
402
|
+
Create a constant false formula.
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
Formula that is never satisfied (⊥)
|
|
406
|
+
|
|
407
|
+
Examples:
|
|
408
|
+
>>> Formula.false_()
|
|
409
|
+
"""
|
|
410
|
+
...
|
|
411
|
+
|
|
412
|
+
@staticmethod
|
|
413
|
+
def and_(left: "Formula", right: "Formula") -> "Formula":
|
|
414
|
+
"""
|
|
415
|
+
Create a conjunction (AND) of two formulas.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
left: First formula
|
|
419
|
+
right: Second formula
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
Formula representing: `left ∧ right`
|
|
423
|
+
|
|
424
|
+
Examples:
|
|
425
|
+
>>> Formula.and_(Formula.gt('x', 0.5), Formula.lt('x', 1.0))
|
|
426
|
+
"""
|
|
427
|
+
...
|
|
428
|
+
|
|
429
|
+
@staticmethod
|
|
430
|
+
def or_(left: "Formula", right: "Formula") -> "Formula":
|
|
431
|
+
"""
|
|
432
|
+
Create a disjunction (OR) of two formulas.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
left: First formula
|
|
436
|
+
right: Second formula
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
Formula representing: `left ∨ right`
|
|
440
|
+
|
|
441
|
+
Examples:
|
|
442
|
+
>>> Formula.or_(Formula.gt('x', 1.0), Formula.lt('x', 0.0))
|
|
443
|
+
"""
|
|
444
|
+
...
|
|
445
|
+
|
|
446
|
+
@staticmethod
|
|
447
|
+
def not_(child: "Formula") -> "Formula":
|
|
448
|
+
"""
|
|
449
|
+
Create a negation (NOT) of a formula.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
child: Formula to negate
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
Formula representing: `¬child`
|
|
456
|
+
|
|
457
|
+
Examples:
|
|
458
|
+
>>> Formula.not_(Formula.gt('x', 0.5)) # x <= 0.5
|
|
459
|
+
"""
|
|
460
|
+
...
|
|
461
|
+
|
|
462
|
+
@staticmethod
|
|
463
|
+
def implies(left: "Formula", right: "Formula") -> "Formula":
|
|
464
|
+
"""
|
|
465
|
+
Create an implication between two formulas.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
left: Antecedent (condition)
|
|
469
|
+
right: Consequent (result)
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
Formula representing: `left → right` (if left then right)
|
|
473
|
+
|
|
474
|
+
Examples:
|
|
475
|
+
>>> # If x > 2.0, then y < 1.0
|
|
476
|
+
>>> Formula.implies(Formula.gt('x', 2.0), Formula.lt('y', 1.0))
|
|
477
|
+
"""
|
|
478
|
+
...
|
|
479
|
+
|
|
480
|
+
@staticmethod
|
|
481
|
+
def always(start: float, end: float, child: "Formula") -> "Formula":
|
|
482
|
+
"""
|
|
483
|
+
Create a globally (always) temporal formula.
|
|
484
|
+
|
|
485
|
+
The formula must hold at all time points in the interval [start, end]
|
|
486
|
+
relative to the current time.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
start: Start of time interval (seconds)
|
|
490
|
+
end: End of time interval (seconds)
|
|
491
|
+
child: Formula that must hold throughout the interval
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
Formula representing: `G[start,end](child)`
|
|
495
|
+
|
|
496
|
+
Examples:
|
|
497
|
+
>>> # For the next 5 seconds, x must be > 0.5
|
|
498
|
+
>>> Formula.always(0, 5, Formula.gt('x', 0.5))
|
|
499
|
+
"""
|
|
500
|
+
...
|
|
501
|
+
|
|
502
|
+
@staticmethod
|
|
503
|
+
def eventually(start: float, end: float, child: "Formula") -> "Formula":
|
|
504
|
+
"""
|
|
505
|
+
Create an eventually (finally) temporal formula.
|
|
506
|
+
|
|
507
|
+
The formula must hold at some time point in the interval [start, end]
|
|
508
|
+
relative to the current time.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
start: Start of time interval (seconds)
|
|
512
|
+
end: End of time interval (seconds)
|
|
513
|
+
child: Formula that must hold at some point in the interval
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
Formula representing: `F[start,end](child)`
|
|
517
|
+
|
|
518
|
+
Examples:
|
|
519
|
+
>>> # Within 3 seconds, y must drop below 0.8
|
|
520
|
+
>>> Formula.eventually(0, 3, Formula.lt('y', 0.8))
|
|
521
|
+
"""
|
|
522
|
+
...
|
|
523
|
+
|
|
524
|
+
@staticmethod
|
|
525
|
+
def until(start: float, end: float, left: "Formula", right: "Formula") -> "Formula":
|
|
526
|
+
"""
|
|
527
|
+
Create an until temporal formula.
|
|
528
|
+
|
|
529
|
+
The left formula must hold until the right formula becomes true,
|
|
530
|
+
and the right formula must become true within the interval [start, end].
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
start: Start of time interval (seconds)
|
|
534
|
+
end: End of time interval (seconds)
|
|
535
|
+
left: Formula that must hold until right becomes true
|
|
536
|
+
right: Formula that must eventually become true
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
Formula representing: `left U[start,end] right`
|
|
540
|
+
|
|
541
|
+
Examples:
|
|
542
|
+
>>> # x > 0 must hold until y < 0.5 (within 5 seconds)
|
|
543
|
+
>>> Formula.until(0, 5, Formula.gt('x', 0), Formula.lt('y', 0.5))
|
|
544
|
+
"""
|
|
545
|
+
...
|
|
546
|
+
|
|
547
|
+
def __str__(self) -> str:
|
|
548
|
+
"""Return string representation of the formula."""
|
|
549
|
+
...
|
|
550
|
+
|
|
551
|
+
def __repr__(self) -> str:
|
|
552
|
+
"""Return detailed representation of the formula."""
|
|
553
|
+
...
|
|
554
|
+
|
|
555
|
+
SemanticsType = Literal[
|
|
556
|
+
"DelayedQualitative", "EagerQualitative", "DelayedQuantitative", "Rosi"
|
|
557
|
+
]
|
|
558
|
+
AlgorithmType = Literal["Incremental", "Naive"]
|
|
559
|
+
SynchronizationType = Literal["ZeroOrderHold", "Linear", "None"]
|
|
560
|
+
|
|
561
|
+
class MonitorOutput:
|
|
562
|
+
"""
|
|
563
|
+
Output from a monitor update operation.
|
|
564
|
+
|
|
565
|
+
This class wraps the Rust MonitorOutput structure and provides:
|
|
566
|
+
|
|
567
|
+
* Rust-style Display formatting via `__str__()`
|
|
568
|
+
* Rust-style Debug formatting via `__repr__()`
|
|
569
|
+
* Structured data access via `to_dict()`
|
|
570
|
+
* Convenient properties and methods
|
|
571
|
+
|
|
572
|
+
The string representation shows verdicts in the format:
|
|
573
|
+
`t={timestamp}: {value}`
|
|
574
|
+
|
|
575
|
+
For multiple verdicts, they are shown on separate lines.
|
|
576
|
+
If no verdicts are available, it shows "No verdicts available".
|
|
577
|
+
|
|
578
|
+
Examples:
|
|
579
|
+
>>> output = monitor.update('x', 1.0, 0.0)
|
|
580
|
+
>>> # Rust-style Display
|
|
581
|
+
>>> print(output)
|
|
582
|
+
t=5s: 0.5
|
|
583
|
+
>>> # Access properties
|
|
584
|
+
>>> print(output.input_signal, output.input_timestamp)
|
|
585
|
+
>>> # Get verdicts
|
|
586
|
+
>>> for ts, val in output.verdicts():
|
|
587
|
+
... print(f"Verdict at {ts}: {val}")
|
|
588
|
+
"""
|
|
589
|
+
|
|
590
|
+
@property
|
|
591
|
+
def input_signal(self) -> str:
|
|
592
|
+
"""The name of the signal that was updated."""
|
|
593
|
+
...
|
|
594
|
+
|
|
595
|
+
@property
|
|
596
|
+
def input_timestamp(self) -> float:
|
|
597
|
+
"""The timestamp (in seconds) of the input that triggered this update."""
|
|
598
|
+
...
|
|
599
|
+
|
|
600
|
+
@property
|
|
601
|
+
def input_value(self) -> float:
|
|
602
|
+
"""The value of the input signal."""
|
|
603
|
+
...
|
|
604
|
+
|
|
605
|
+
def has_verdicts(self) -> bool:
|
|
606
|
+
"""
|
|
607
|
+
Check if there are any output verdicts.
|
|
608
|
+
|
|
609
|
+
Returns:
|
|
610
|
+
True if there is at least one output verdict, False otherwise.
|
|
611
|
+
"""
|
|
612
|
+
...
|
|
613
|
+
|
|
614
|
+
def total_raw_outputs(self) -> int:
|
|
615
|
+
"""
|
|
616
|
+
Get the total number of raw output verdicts across all evaluations.
|
|
617
|
+
|
|
618
|
+
Returns:
|
|
619
|
+
The total count of raw output verdicts.
|
|
620
|
+
"""
|
|
621
|
+
...
|
|
622
|
+
|
|
623
|
+
def is_pending(self) -> bool:
|
|
624
|
+
"""
|
|
625
|
+
Check if the evaluations list is empty (i.e., data is being buffered).
|
|
626
|
+
|
|
627
|
+
Returns:
|
|
628
|
+
True if no evaluations were triggered, False otherwise.
|
|
629
|
+
"""
|
|
630
|
+
...
|
|
631
|
+
|
|
632
|
+
def verdicts(self) -> List[Tuple[float, Union[bool, float, Tuple[float, float]]]]:
|
|
633
|
+
"""
|
|
634
|
+
Get the finalized verdicts as a list of (timestamp, value) tuples.
|
|
635
|
+
|
|
636
|
+
This returns the latest verdict for each unique timestamp.
|
|
637
|
+
|
|
638
|
+
Returns:
|
|
639
|
+
List of (timestamp, value) tuples. The value type depends on the
|
|
640
|
+
monitor semantics:
|
|
641
|
+
|
|
642
|
+
* bool for qualitative semantics
|
|
643
|
+
* float for robustness semantics
|
|
644
|
+
* tuple[float, float] for Rosi semantics
|
|
645
|
+
"""
|
|
646
|
+
...
|
|
647
|
+
|
|
648
|
+
def has_outputs(self) -> bool:
|
|
649
|
+
"""
|
|
650
|
+
.. deprecated::
|
|
651
|
+
Use :meth:`has_verdicts` instead.
|
|
652
|
+
|
|
653
|
+
Check if there are any output verdicts.
|
|
654
|
+
"""
|
|
655
|
+
...
|
|
656
|
+
|
|
657
|
+
def total_outputs(self) -> int:
|
|
658
|
+
"""
|
|
659
|
+
.. deprecated::
|
|
660
|
+
Use :meth:`total_raw_outputs` instead.
|
|
661
|
+
|
|
662
|
+
Get the total number of output verdicts.
|
|
663
|
+
"""
|
|
664
|
+
...
|
|
665
|
+
|
|
666
|
+
def is_empty(self) -> bool:
|
|
667
|
+
"""
|
|
668
|
+
.. deprecated::
|
|
669
|
+
Use :meth:`is_pending` instead.
|
|
670
|
+
|
|
671
|
+
Check if the evaluations list is empty.
|
|
672
|
+
"""
|
|
673
|
+
...
|
|
674
|
+
|
|
675
|
+
def finalize(self) -> List[Tuple[float, Union[bool, float, Tuple[float, float]]]]:
|
|
676
|
+
"""
|
|
677
|
+
.. deprecated::
|
|
678
|
+
Use :meth:`verdicts` instead.
|
|
679
|
+
|
|
680
|
+
Get the finalized verdicts as a list of (timestamp, value) tuples.
|
|
681
|
+
"""
|
|
682
|
+
...
|
|
683
|
+
|
|
684
|
+
def to_dict(self) -> MonitorOutputDict:
|
|
685
|
+
"""
|
|
686
|
+
Convert the monitor output to a dictionary.
|
|
687
|
+
|
|
688
|
+
Returns:
|
|
689
|
+
Dictionary containing:
|
|
690
|
+
* 'input_signal': the signal name
|
|
691
|
+
* 'input_timestamp': the input timestamp
|
|
692
|
+
* 'input_value': the input value
|
|
693
|
+
* 'evaluations': list of evaluation dictionaries
|
|
694
|
+
|
|
695
|
+
Examples:
|
|
696
|
+
>>> output = monitor.update('x', 1.0, 0.0)
|
|
697
|
+
>>> d = output.to_dict()
|
|
698
|
+
>>> print(d['input_signal'], d['input_timestamp'])
|
|
699
|
+
"""
|
|
700
|
+
...
|
|
701
|
+
|
|
702
|
+
def __str__(self) -> str:
|
|
703
|
+
"""
|
|
704
|
+
Return Rust-style Display representation of the output.
|
|
705
|
+
Shows verdicts in the format: `t={timestamp}: {value}`
|
|
706
|
+
For multiple verdicts, they are shown on separate lines.
|
|
707
|
+
If no verdicts are available, returns "No verdicts available".
|
|
708
|
+
"""
|
|
709
|
+
...
|
|
710
|
+
|
|
711
|
+
def __repr__(self) -> str:
|
|
712
|
+
"""
|
|
713
|
+
Return Rust-style Debug representation of the output.
|
|
714
|
+
Shows the full internal structure of the MonitorOutput,
|
|
715
|
+
matching Rust's Debug formatting.
|
|
716
|
+
"""
|
|
717
|
+
...
|
|
718
|
+
|
|
719
|
+
class Monitor:
|
|
720
|
+
"""
|
|
721
|
+
Online STL monitor.
|
|
722
|
+
|
|
723
|
+
Monitors signal traces against an STL formula and produces verdicts.
|
|
724
|
+
Supports multiple semantics (DelayedQualitative, EagerQualitative, DelayedQuantitative, Rosi),
|
|
725
|
+
algorithms (Incremental, Naive), and synchronization strategies (ZeroOrderHold, Linear, None).
|
|
726
|
+
|
|
727
|
+
The monitor processes signals incrementally and produces verdicts when
|
|
728
|
+
sufficient information is available.
|
|
729
|
+
|
|
730
|
+
Examples:
|
|
731
|
+
>>> phi = Formula.always(0, 5, Formula.gt('x', 0.5))
|
|
732
|
+
>>> monitor = Monitor(phi, semantics='Rosi')
|
|
733
|
+
>>> for t in range(10):
|
|
734
|
+
... result = monitor.update('x', 0.8, float(t))
|
|
735
|
+
... print(result)
|
|
736
|
+
"""
|
|
737
|
+
|
|
738
|
+
def __init__(
|
|
739
|
+
self,
|
|
740
|
+
formula: Formula,
|
|
741
|
+
semantics: SemanticsType = "DelayedQuantitative",
|
|
742
|
+
algorithm: AlgorithmType = "Incremental",
|
|
743
|
+
synchronization: SynchronizationType = "ZeroOrderHold",
|
|
744
|
+
variables: Union[Variables, None] = None,
|
|
745
|
+
) -> None:
|
|
746
|
+
"""
|
|
747
|
+
Create a new STL monitor.
|
|
748
|
+
|
|
749
|
+
Args:
|
|
750
|
+
formula: The STL formula to monitor
|
|
751
|
+
semantics: Output semantics. Options:
|
|
752
|
+
|
|
753
|
+
* "DelayedQualitative": Returns True/False with delayed evaluation
|
|
754
|
+
* "EagerQualitative": Returns True/False with eager evaluation
|
|
755
|
+
* "DelayedQuantitative": Returns float robustness value (+ = satisfied, - = violated, default)
|
|
756
|
+
* "Rosi": Returns (min, max) robustness interval
|
|
757
|
+
|
|
758
|
+
algorithm: Monitoring algorithm. Options:
|
|
759
|
+
|
|
760
|
+
* "Incremental": Efficient online monitoring with sliding windows (default)
|
|
761
|
+
* "Naive": Simple baseline implementation
|
|
762
|
+
|
|
763
|
+
synchronization: Signal synchronization method. Options:
|
|
764
|
+
|
|
765
|
+
* "ZeroOrderHold": Zero-order hold (default)
|
|
766
|
+
* "Linear": Linear interpolation
|
|
767
|
+
* "None": No interpolation
|
|
768
|
+
|
|
769
|
+
variables: A Variables object containing runtime variable values.
|
|
770
|
+
Required if the formula contains variable predicates (e.g., `x > $threshold`).
|
|
771
|
+
Note: Variable predicates require the Incremental algorithm.
|
|
772
|
+
|
|
773
|
+
Raises:
|
|
774
|
+
ValueError: If invalid semantics, algorithm, or synchronization is specified
|
|
775
|
+
ValueError: If Naive algorithm is used with EagerQualitative (not supported)
|
|
776
|
+
ValueError: If Naive algorithm is used with variable predicates (not supported)
|
|
777
|
+
|
|
778
|
+
Note:
|
|
779
|
+
For single-signal formulas, signal synchronization is automatically disabled
|
|
780
|
+
for better performance.
|
|
781
|
+
|
|
782
|
+
Examples:
|
|
783
|
+
>>> # DelayedQualitative monitoring
|
|
784
|
+
>>> m1 = Monitor(phi, semantics="DelayedQualitative", algorithm="Incremental")
|
|
785
|
+
>>>
|
|
786
|
+
>>> # DelayedQuantitative (quantitative)
|
|
787
|
+
>>> m2 = Monitor(phi, semantics="DelayedQuantitative")
|
|
788
|
+
>>>
|
|
789
|
+
>>> # Rosi intervals
|
|
790
|
+
>>> m3 = Monitor(phi, semantics="Rosi")
|
|
791
|
+
>>>
|
|
792
|
+
>>> # Using variables for dynamic thresholds
|
|
793
|
+
>>> vars = Variables()
|
|
794
|
+
>>> vars.set("threshold", 5.0)
|
|
795
|
+
>>> phi = parse_formula("G[0, 5](x > $threshold)")
|
|
796
|
+
>>> m4 = Monitor(phi, variables=vars)
|
|
797
|
+
"""
|
|
798
|
+
...
|
|
799
|
+
|
|
800
|
+
def update(self, signal: str, value: float, timestamp: float) -> MonitorOutput:
|
|
801
|
+
"""
|
|
802
|
+
Update the monitor with a new signal value.
|
|
803
|
+
|
|
804
|
+
Processes the new data point and produces verdicts when sufficient
|
|
805
|
+
information is available. Each update may trigger multiple evaluations,
|
|
806
|
+
one for each synchronized step (which may include interpolated values).
|
|
807
|
+
|
|
808
|
+
Args:
|
|
809
|
+
signal: Name of the signal being updated
|
|
810
|
+
value: Signal value at this timestamp
|
|
811
|
+
timestamp: Timestamp in seconds (must be monotonically increasing)
|
|
812
|
+
|
|
813
|
+
Returns:
|
|
814
|
+
MonitorOutput object containing:
|
|
815
|
+
|
|
816
|
+
* Display/Debug formatting via `__str__()` and `__repr__()`
|
|
817
|
+
* Properties: input_signal, input_timestamp, input_value
|
|
818
|
+
* Methods: has_verdicts(), total_raw_outputs(), is_pending(), verdicts()
|
|
819
|
+
* Structured data access via `to_dict()`
|
|
820
|
+
|
|
821
|
+
Note:
|
|
822
|
+
* Evaluations list may be empty if data is being buffered
|
|
823
|
+
* For multi-signal formulas, the synchronizer may produce interpolated steps
|
|
824
|
+
* Each evaluation corresponds to a specific synchronized step
|
|
825
|
+
* Multiple outputs may be produced for each synchronized step
|
|
826
|
+
|
|
827
|
+
Examples:
|
|
828
|
+
>>> output = monitor.update('x', 0.8, 1.0)
|
|
829
|
+
>>> # Use Rust-style Display formatting
|
|
830
|
+
>>> print(output)
|
|
831
|
+
>>> # Access properties
|
|
832
|
+
>>> print(f"Input: {output.input_signal} at t={output.input_timestamp}")
|
|
833
|
+
>>> # Get verdicts
|
|
834
|
+
>>> for ts, val in output.verdicts():
|
|
835
|
+
... print(f"Verdict at {ts}: {val}")
|
|
836
|
+
>>> # Access as dictionary
|
|
837
|
+
>>> d = output.to_dict()
|
|
838
|
+
>>> for eval in d['evaluations']:
|
|
839
|
+
... for out in eval['outputs']:
|
|
840
|
+
... print(f" t={out['timestamp']}: {out['value']}")
|
|
841
|
+
"""
|
|
842
|
+
...
|
|
843
|
+
|
|
844
|
+
def get_signal_identifiers(self) -> set[str]:
|
|
845
|
+
"""
|
|
846
|
+
Get the set of signal identifiers used in the monitor's formula.
|
|
847
|
+
|
|
848
|
+
Returns:
|
|
849
|
+
Set[str]: A set of signal names (identifiers) used in the formula.
|
|
850
|
+
|
|
851
|
+
Examples:
|
|
852
|
+
>>> phi = parse_formula("G[0,5](x > 5) && F[0,3](y < 10)")
|
|
853
|
+
>>> monitor = Monitor(phi)
|
|
854
|
+
>>> signals = monitor.get_signal_identifiers()
|
|
855
|
+
>>> print(signals) # {'x', 'y'}
|
|
856
|
+
"""
|
|
857
|
+
...
|
|
858
|
+
|
|
859
|
+
def get_variables(self) -> Variables:
|
|
860
|
+
"""
|
|
861
|
+
Get the Variables object associated with this monitor.
|
|
862
|
+
|
|
863
|
+
This allows you to update variable values at runtime, which will
|
|
864
|
+
affect future evaluations of the formula.
|
|
865
|
+
|
|
866
|
+
Returns:
|
|
867
|
+
The Variables object containing runtime variable values.
|
|
868
|
+
|
|
869
|
+
Examples:
|
|
870
|
+
>>> vars = Variables()
|
|
871
|
+
>>> vars.set("threshold", 5.0)
|
|
872
|
+
>>> monitor = Monitor(formula, variables=vars)
|
|
873
|
+
>>> # Update a variable at runtime
|
|
874
|
+
>>> monitor.get_variables().set("threshold", 10.0)
|
|
875
|
+
"""
|
|
876
|
+
...
|
|
877
|
+
|
|
878
|
+
def get_specification(self) -> str:
|
|
879
|
+
"""
|
|
880
|
+
Get the STL specification as a string representation.
|
|
881
|
+
|
|
882
|
+
Returns:
|
|
883
|
+
String representation of the STL formula being monitored.
|
|
884
|
+
|
|
885
|
+
Examples:
|
|
886
|
+
>>> phi = parse_formula("G[0,5](x > 5)")
|
|
887
|
+
>>> monitor = Monitor(phi)
|
|
888
|
+
>>> print(monitor.get_specification()) # "G[0s,5s](x > 5)"
|
|
889
|
+
"""
|
|
890
|
+
...
|
|
891
|
+
|
|
892
|
+
def get_algorithm(self) -> str:
|
|
893
|
+
"""
|
|
894
|
+
Get the algorithm used by this monitor.
|
|
895
|
+
|
|
896
|
+
Returns:
|
|
897
|
+
"Incremental" or "Naive"
|
|
898
|
+
|
|
899
|
+
Examples:
|
|
900
|
+
>>> monitor = Monitor(phi, algorithm="Incremental")
|
|
901
|
+
>>> print(monitor.get_algorithm()) # "Incremental"
|
|
902
|
+
"""
|
|
903
|
+
...
|
|
904
|
+
|
|
905
|
+
def get_semantics(self) -> str:
|
|
906
|
+
"""
|
|
907
|
+
Get the semantics used by this monitor.
|
|
908
|
+
|
|
909
|
+
Returns:
|
|
910
|
+
One of: "DelayedQualitative", "EagerQualitative", "DelayedQuantitative", or "Rosi"
|
|
911
|
+
|
|
912
|
+
Examples:
|
|
913
|
+
>>> monitor = Monitor(phi, semantics="DelayedQuantitative")
|
|
914
|
+
>>> print(monitor.get_semantics()) # "DelayedQuantitative"
|
|
915
|
+
"""
|
|
916
|
+
...
|
|
917
|
+
|
|
918
|
+
def get_synchronization_strategy(self) -> str:
|
|
919
|
+
"""
|
|
920
|
+
Get the synchronization strategy used by this monitor.
|
|
921
|
+
|
|
922
|
+
Returns:
|
|
923
|
+
One of: "ZeroOrderHold", "Linear", or "None"
|
|
924
|
+
|
|
925
|
+
Examples:
|
|
926
|
+
>>> monitor = Monitor(phi, synchronization="Linear")
|
|
927
|
+
>>> print(monitor.get_synchronization_strategy()) # "Linear"
|
|
928
|
+
"""
|
|
929
|
+
...
|
|
930
|
+
|
|
931
|
+
def get_temporal_depth(self) -> float:
|
|
932
|
+
"""
|
|
933
|
+
Get the maximum lookahead required by the formula (temporal depth).
|
|
934
|
+
|
|
935
|
+
This corresponds to the maximum time interval in any temporal operator.
|
|
936
|
+
|
|
937
|
+
Returns:
|
|
938
|
+
The temporal depth in seconds.
|
|
939
|
+
|
|
940
|
+
Examples:
|
|
941
|
+
>>> phi = parse_formula("G[0,5](x > 5) && F[0,3](y < 10)")
|
|
942
|
+
>>> monitor = Monitor(phi)
|
|
943
|
+
>>> print(monitor.get_temporal_depth()) # 5.0
|
|
944
|
+
"""
|
|
945
|
+
...
|
|
946
|
+
|
|
947
|
+
def update_batch(
|
|
948
|
+
self, steps: dict[str, List[Tuple[float, float]]]
|
|
949
|
+
) -> MonitorOutput:
|
|
950
|
+
"""
|
|
951
|
+
Update the monitor with multiple signal values in batch.
|
|
952
|
+
|
|
953
|
+
This method processes multiple data points at once. Steps are automatically
|
|
954
|
+
sorted by timestamp in chronological order before processing, which is
|
|
955
|
+
optimal for the Incremental algorithm.
|
|
956
|
+
|
|
957
|
+
Args:
|
|
958
|
+
steps: A dictionary mapping signal names to lists of (value, timestamp)
|
|
959
|
+
tuples. Examples: `{"x": [(1.0, 0.0), (2.0, 1.0)], "y": [(5.0, 0.5)]}`
|
|
960
|
+
|
|
961
|
+
Returns:
|
|
962
|
+
A single MonitorOutput containing all evaluation results from processing
|
|
963
|
+
the batch. The input metadata reflects the last step processed.
|
|
964
|
+
|
|
965
|
+
Raises:
|
|
966
|
+
ValueError: If the steps dictionary is empty or contains no steps.
|
|
967
|
+
|
|
968
|
+
Note:
|
|
969
|
+
Steps are processed in chronological order (sorted by timestamp) regardless
|
|
970
|
+
of the order in which signals appear in the dictionary. If you need a
|
|
971
|
+
specific processing order, use `update()` directly.
|
|
972
|
+
|
|
973
|
+
Examples:
|
|
974
|
+
>>> steps = {
|
|
975
|
+
... "temperature": [(25.0, 1.0), (26.0, 2.0)],
|
|
976
|
+
... "pressure": [(101.3, 1.5)]
|
|
977
|
+
... }
|
|
978
|
+
>>> output = monitor.update_batch(steps)
|
|
979
|
+
>>> print(output) # Display all finalized verdicts
|
|
980
|
+
"""
|
|
981
|
+
...
|
|
982
|
+
|
|
983
|
+
def __repr__(self) -> str:
|
|
984
|
+
"""
|
|
985
|
+
Return a brief representation of the monitor.
|
|
986
|
+
|
|
987
|
+
Returns:
|
|
988
|
+
String in the format: "Monitor(semantics='...', algorithm='...', synchronization='...')"
|
|
989
|
+
"""
|
|
990
|
+
...
|
|
991
|
+
|
|
992
|
+
def __str__(self) -> str:
|
|
993
|
+
"""
|
|
994
|
+
Return the complete monitor configuration using Rust's Display trait.
|
|
995
|
+
|
|
996
|
+
This provides a detailed view of all monitor configuration including:
|
|
997
|
+
- STL specification formula
|
|
998
|
+
- Algorithm (Naive/Incremental)
|
|
999
|
+
- Semantics (DelayedQuantitative/Rosi/DelayedQualitative/EagerQualitative)
|
|
1000
|
+
- Synchronization strategy
|
|
1001
|
+
- Temporal depth
|
|
1002
|
+
- Variables (if any are defined)
|
|
1003
|
+
|
|
1004
|
+
Returns:
|
|
1005
|
+
Formatted string showing complete monitor configuration.
|
|
1006
|
+
|
|
1007
|
+
Examples:
|
|
1008
|
+
>>> phi = parse_formula("G[0,5](x > $threshold)")
|
|
1009
|
+
>>> vars = Variables()
|
|
1010
|
+
>>> vars.set("threshold", 10.0)
|
|
1011
|
+
>>> monitor = Monitor(phi, semantics="DelayedQuantitative", variables=vars)
|
|
1012
|
+
>>> print(monitor)
|
|
1013
|
+
STL Monitor Configuration:
|
|
1014
|
+
Specification: G[0s,5s](x > 10)
|
|
1015
|
+
Algorithm: Incremental
|
|
1016
|
+
Semantics: DelayedQuantitative
|
|
1017
|
+
Synchronization: ZeroOrderHold
|
|
1018
|
+
Temporal Depth: 5s
|
|
1019
|
+
Variables:
|
|
1020
|
+
$threshold = 10
|
|
1021
|
+
"""
|
|
1022
|
+
...
|