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