openprocess 0.7.0__py3-none-any.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.
- cpnpy/__init__.py +46 -0
- openprocess/__init__.py +57 -0
- openprocess/analysis/__init__.py +0 -0
- openprocess/analysis/state_space.py +521 -0
- openprocess/analysis/state_space_process.py +251 -0
- openprocess/cli.py +742 -0
- openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +31 -0
- openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +38 -0
- openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +27 -0
- openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +29 -0
- openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +65 -0
- openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +1 -0
- openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +67 -0
- openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +75 -0
- openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +5 -0
- openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +27 -0
- openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +65 -0
- openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +1 -0
- openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +62 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +1 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +36 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +28 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +30 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +36 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +69 -0
- openprocess/exercises/pack.md +14 -0
- openprocess/flow/__init__.py +50 -0
- openprocess/flow/box.py +466 -0
- openprocess/flow/boxes/__init__.py +7 -0
- openprocess/flow/boxes/check.py +119 -0
- openprocess/flow/boxes/compare.py +16 -0
- openprocess/flow/boxes/cpn.py +53 -0
- openprocess/flow/boxes/discover.py +124 -0
- openprocess/flow/boxes/filter.py +80 -0
- openprocess/flow/boxes/input.py +124 -0
- openprocess/flow/boxes/output.py +52 -0
- openprocess/flow/boxes/predict.py +186 -0
- openprocess/flow/boxes/science.py +159 -0
- openprocess/flow/boxes/sweeps.py +18 -0
- openprocess/flow/convert.py +187 -0
- openprocess/flow/datasets.py +198 -0
- openprocess/flow/explain.py +115 -0
- openprocess/flow/library.py +222 -0
- openprocess/flow/record.py +385 -0
- openprocess/flow/runner.py +357 -0
- openprocess/flow/sweep.py +92 -0
- openprocess/flow/types.py +290 -0
- openprocess/flow/workflow.py +628 -0
- openprocess/gui/__init__.py +0 -0
- openprocess/gui/app.py +90 -0
- openprocess/gui/arc_editing.py +295 -0
- openprocess/gui/canvas.py +1414 -0
- openprocess/gui/flow/__init__.py +8 -0
- openprocess/gui/flow/canvas.py +854 -0
- openprocess/gui/flow/page.py +972 -0
- openprocess/gui/flow/templates.py +131 -0
- openprocess/gui/flow/viewers.py +665 -0
- openprocess/gui/items.py +1275 -0
- openprocess/gui/learn/answer_boxes.py +978 -0
- openprocess/gui/learn/concealment.py +91 -0
- openprocess/gui/learn/mode.py +1181 -0
- openprocess/gui/panning.py +241 -0
- openprocess/gui/resources/openprocess-icon.png +0 -0
- openprocess/gui/studio/__init__.py +1 -0
- openprocess/gui/studio/__main__.py +3 -0
- openprocess/gui/studio/app.py +4031 -0
- openprocess/gui/studio/charts.py +115 -0
- openprocess/gui/studio/compare_page.py +487 -0
- openprocess/gui/studio/cpn_page.py +1858 -0
- openprocess/gui/studio/definition_view.py +284 -0
- openprocess/gui/studio/derivation_view.py +421 -0
- openprocess/gui/studio/documents.py +152 -0
- openprocess/gui/studio/dotted_chart.py +1401 -0
- openprocess/gui/studio/file_dialogs.py +143 -0
- openprocess/gui/studio/filter_dialog.py +247 -0
- openprocess/gui/studio/graph_builders.py +176 -0
- openprocess/gui/studio/graph_view.py +682 -0
- openprocess/gui/studio/instances.py +413 -0
- openprocess/gui/studio/log_editor.py +675 -0
- openprocess/gui/studio/log_page.py +800 -0
- openprocess/gui/studio/markdown_view.py +127 -0
- openprocess/gui/studio/mathtext.py +260 -0
- openprocess/gui/studio/ml_highlighter.py +75 -0
- openprocess/gui/studio/model_page.py +760 -0
- openprocess/gui/studio/net_comparison.py +124 -0
- openprocess/gui/studio/notes_overlay.py +275 -0
- openprocess/gui/studio/petri_page.py +844 -0
- openprocess/gui/studio/regions_view.py +502 -0
- openprocess/gui/studio/sidebar.py +149 -0
- openprocess/gui/studio/style.py +503 -0
- openprocess/gui/studio/tool_icons.py +134 -0
- openprocess/gui/studio/updates.py +439 -0
- openprocess/gui/studio/widgets.py +899 -0
- openprocess/gui/studio/workers.py +60 -0
- openprocess/gui/studio/workspace.py +447 -0
- openprocess/gui/theme.py +394 -0
- openprocess/gui/tidy.py +86 -0
- openprocess/io/__init__.py +0 -0
- openprocess/io/cpn_reader.py +389 -0
- openprocess/io/cpn_writer.py +357 -0
- openprocess/learn/__init__.py +23 -0
- openprocess/learn/answers.py +188 -0
- openprocess/learn/checks.py +953 -0
- openprocess/learn/computed.py +1180 -0
- openprocess/learn/context.py +145 -0
- openprocess/learn/exam.py +169 -0
- openprocess/learn/exercise-packs.md +325 -0
- openprocess/learn/importer.py +216 -0
- openprocess/learn/notation.py +474 -0
- openprocess/learn/pack.py +511 -0
- openprocess/learn/sheet.py +296 -0
- openprocess/mining/__init__.py +73 -0
- openprocess/mining/analysis.py +689 -0
- openprocess/mining/columns.py +282 -0
- openprocess/mining/compare_nets.py +246 -0
- openprocess/mining/conformance/__init__.py +0 -0
- openprocess/mining/conformance/alignments.py +263 -0
- openprocess/mining/conformance/quality.py +145 -0
- openprocess/mining/conformance/token_replay.py +252 -0
- openprocess/mining/csv_import.py +222 -0
- openprocess/mining/definitions.py +584 -0
- openprocess/mining/dfg.py +187 -0
- openprocess/mining/discovery/__init__.py +0 -0
- openprocess/mining/discovery/alpha.py +168 -0
- openprocess/mining/discovery/heuristics.py +332 -0
- openprocess/mining/discovery/inductive.py +477 -0
- openprocess/mining/discovery/state_regions.py +62 -0
- openprocess/mining/filtering.py +237 -0
- openprocess/mining/footprint.py +183 -0
- openprocess/mining/invariants.py +191 -0
- openprocess/mining/layout.py +279 -0
- openprocess/mining/log.py +364 -0
- openprocess/mining/petrinet.py +354 -0
- openprocess/mining/playout.py +75 -0
- openprocess/mining/pm4py_bridge.py +82 -0
- openprocess/mining/pnml.py +223 -0
- openprocess/mining/processtree.py +216 -0
- openprocess/mining/regions.py +476 -0
- openprocess/mining/stats.py +160 -0
- openprocess/mining/structure.py +374 -0
- openprocess/mining/transition_system.py +409 -0
- openprocess/mining/xes.py +399 -0
- openprocess/ml/__init__.py +0 -0
- openprocess/ml/ast_nodes.py +332 -0
- openprocess/ml/builtins.py +364 -0
- openprocess/ml/colorsets.py +522 -0
- openprocess/ml/errors.py +60 -0
- openprocess/ml/evaluator.py +754 -0
- openprocess/ml/lexer.py +277 -0
- openprocess/ml/multiset.py +417 -0
- openprocess/ml/parser.py +737 -0
- openprocess/ml/values.py +319 -0
- openprocess/model/__init__.py +0 -0
- openprocess/model/declarations.py +617 -0
- openprocess/model/examples.py +98 -0
- openprocess/model/net.py +701 -0
- openprocess/model/plain.py +192 -0
- openprocess/references.py +280 -0
- openprocess/sim/__init__.py +0 -0
- openprocess/sim/binding.py +620 -0
- openprocess/sim/export.py +66 -0
- openprocess/sim/simulator.py +315 -0
- openprocess/teaching/__init__.py +4 -0
- openprocess/teaching/answers.py +4 -0
- openprocess/teaching/checks.py +5 -0
- openprocess/teaching/pack.py +4 -0
- openprocess/teaching/sheet.py +4 -0
- openprocess-0.7.0.dist-info/METADATA +927 -0
- openprocess-0.7.0.dist-info/RECORD +173 -0
- openprocess-0.7.0.dist-info/WHEEL +5 -0
- openprocess-0.7.0.dist-info/entry_points.txt +6 -0
- openprocess-0.7.0.dist-info/licenses/LICENSE +21 -0
- openprocess-0.7.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""The simulation engine: firing transitions and advancing the clock.
|
|
2
|
+
|
|
3
|
+
The firing rule
|
|
4
|
+
---------------
|
|
5
|
+
For a transition ``t`` and a binding ``b`` that :mod:`openprocess.sim.binding` has
|
|
6
|
+
found to be enabled in marking ``M``, firing produces ``M'`` where, for every
|
|
7
|
+
place ``p``::
|
|
8
|
+
|
|
9
|
+
M'(p) = ( M(p) -- E(p,t)<b> ) ++ E(t,p)<b>
|
|
10
|
+
|
|
11
|
+
``E(p,t)`` is the inscription on the arc from ``p`` to ``t`` (what is consumed)
|
|
12
|
+
and ``E(t,p)`` the inscription on the arc back (what is produced). Everything
|
|
13
|
+
below is bookkeeping around those two multiset operations.
|
|
14
|
+
|
|
15
|
+
Timed nets
|
|
16
|
+
----------
|
|
17
|
+
A timed model adds a global clock. Three rules govern it:
|
|
18
|
+
|
|
19
|
+
1. A token in a timed place carries a **time stamp**: the model time at which
|
|
20
|
+
it becomes available. Only tokens whose stamp has been reached may be
|
|
21
|
+
consumed.
|
|
22
|
+
2. Produced tokens are stamped ``clock + delay``, where the delay is the
|
|
23
|
+
transition's own time inscription plus the output arc's ``@+`` expression
|
|
24
|
+
(either may be absent), as in CPN Tools.
|
|
25
|
+
3. When no transition is enabled at the current clock but some place holds a
|
|
26
|
+
token stamped for the future, the clock **jumps** to the earliest such
|
|
27
|
+
stamp. Model time therefore moves in event-driven jumps, never in ticks.
|
|
28
|
+
|
|
29
|
+
Only when no transition is enabled and no future stamp exists is the model
|
|
30
|
+
genuinely dead.
|
|
31
|
+
|
|
32
|
+
Determinism
|
|
33
|
+
-----------
|
|
34
|
+
Which enabled binding element fires is a free choice in CPN semantics. The
|
|
35
|
+
simulator uses its own seeded random generator for that choice, separate from
|
|
36
|
+
the one inscriptions draw from, so that a replayed run makes the same choices
|
|
37
|
+
even if the model's own random calls change. A seed given to the simulator
|
|
38
|
+
seeds both generators, so the whole run is reproducible.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import random
|
|
44
|
+
from dataclasses import dataclass, field
|
|
45
|
+
from typing import Any, Iterable, Sequence
|
|
46
|
+
|
|
47
|
+
from ..ml.errors import CPNMLError
|
|
48
|
+
from ..ml.evaluator import Environment, expand_lists, to_multiset
|
|
49
|
+
from ..ml.multiset import Multiset, TimedMultiset
|
|
50
|
+
from ..model.net import Arc, CPNet, Marking, Place, Transition
|
|
51
|
+
from .binding import Binder, BindingElement
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class FiringRecord:
|
|
56
|
+
"""One entry of the simulation log."""
|
|
57
|
+
|
|
58
|
+
step: int
|
|
59
|
+
time: int
|
|
60
|
+
binding: BindingElement
|
|
61
|
+
#: The marking *after* this firing. Kept so that the GUI can rewind.
|
|
62
|
+
marking: Marking
|
|
63
|
+
|
|
64
|
+
def describe(self, net: CPNet) -> str:
|
|
65
|
+
return f"{self.step:>4} t={self.time:<6} {self.binding.describe(net)}"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class DeadMarkingError(RuntimeError):
|
|
69
|
+
"""Raised by :meth:`Simulator.step` when nothing can happen at all."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Simulator:
|
|
73
|
+
"""Drives a :class:`~openprocess.model.net.CPNet` forward.
|
|
74
|
+
|
|
75
|
+
The simulator owns the *current* marking and clock. Every firing builds a
|
|
76
|
+
new :class:`~openprocess.model.net.Marking` rather than mutating the old one, so
|
|
77
|
+
the history in :attr:`log` stays valid and can be replayed or stepped back
|
|
78
|
+
through.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, net: CPNet, marking: Marking | None = None,
|
|
82
|
+
seed: int | None = None) -> None:
|
|
83
|
+
self.net = net
|
|
84
|
+
if seed is not None:
|
|
85
|
+
# Seed the model's own random functions too (``uniform``,
|
|
86
|
+
# ``discrete`` ...), before the initial marking may call them, so
|
|
87
|
+
# that the same seed replays the same run.
|
|
88
|
+
net.evaluator.rng.seed(seed)
|
|
89
|
+
self.marking = marking if marking is not None else net.initial_marking()
|
|
90
|
+
self.clock = 0
|
|
91
|
+
self.step_count = 0
|
|
92
|
+
self.log: list[FiringRecord] = []
|
|
93
|
+
self.binder = Binder(net)
|
|
94
|
+
#: Choice generator, deliberately separate from the model's own RNG.
|
|
95
|
+
self.choice_rng = random.Random(seed)
|
|
96
|
+
|
|
97
|
+
# =======================================================================
|
|
98
|
+
# Enabling
|
|
99
|
+
# =======================================================================
|
|
100
|
+
def enabled_bindings(self, transition: Transition) -> list[BindingElement]:
|
|
101
|
+
"""Enabled binding elements of one transition at the current time."""
|
|
102
|
+
if transition.is_substitution:
|
|
103
|
+
# A substitution transition is a drawing device: the behaviour
|
|
104
|
+
# lives on its subpage, so it never fires itself.
|
|
105
|
+
return []
|
|
106
|
+
return self.binder.bindings(transition, self.marking, self.clock)
|
|
107
|
+
|
|
108
|
+
def all_enabled(self) -> list[BindingElement]:
|
|
109
|
+
"""Every enabled binding element in the whole model, in a stable order."""
|
|
110
|
+
result: list[BindingElement] = []
|
|
111
|
+
for transition in self.net.all_transitions():
|
|
112
|
+
result.extend(self.enabled_bindings(transition))
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
def is_enabled(self, transition: Transition) -> bool:
|
|
116
|
+
return bool(self.enabled_bindings(transition))
|
|
117
|
+
|
|
118
|
+
# =======================================================================
|
|
119
|
+
# Firing
|
|
120
|
+
# =======================================================================
|
|
121
|
+
def fire(self, element: BindingElement, marking: Marking | None = None,
|
|
122
|
+
clock: int | None = None) -> tuple[Marking, int]:
|
|
123
|
+
"""Apply one binding element, returning the new marking and clock.
|
|
124
|
+
|
|
125
|
+
Pure with respect to the simulator's own state when ``marking`` and
|
|
126
|
+
``clock`` are supplied -- which is how the state space explorer uses it.
|
|
127
|
+
Called without them it reads (but still does not write) the simulator's
|
|
128
|
+
current state.
|
|
129
|
+
"""
|
|
130
|
+
current = self.marking if marking is None else marking
|
|
131
|
+
now = self.clock if clock is None else clock
|
|
132
|
+
|
|
133
|
+
transition = self.net.find_transition(element.transition_id)
|
|
134
|
+
if transition is None:
|
|
135
|
+
raise DeadMarkingError(f"unknown transition {element.transition_id}")
|
|
136
|
+
page = self.net.page_of(transition)
|
|
137
|
+
assert page is not None
|
|
138
|
+
|
|
139
|
+
bindings = element.as_dict()
|
|
140
|
+
environment = Environment(bindings, self.net.evaluator.globals)
|
|
141
|
+
self.net.evaluator.set_model_time(now)
|
|
142
|
+
|
|
143
|
+
# The transition's own time inscription delays every output token; an
|
|
144
|
+
# output arc's `@+` adds to it (CPN Tools adds the two delays).
|
|
145
|
+
default_delay = 0
|
|
146
|
+
if transition.time_ast is not None:
|
|
147
|
+
value = self.net.evaluator.evaluate(transition.time_ast, environment)
|
|
148
|
+
default_delay = value # int or real, like the model's clock
|
|
149
|
+
|
|
150
|
+
result = current.copy()
|
|
151
|
+
|
|
152
|
+
# --- 1. consume -----------------------------------------------------
|
|
153
|
+
for arc in page.arcs_of(transition):
|
|
154
|
+
if not arc.is_input or arc.expression_ast is None:
|
|
155
|
+
continue
|
|
156
|
+
key = self.net.marking_key(arc.place_id)
|
|
157
|
+
tokens, _stamp = self.net.evaluator.evaluate_arc(
|
|
158
|
+
arc.expression_ast, environment, now
|
|
159
|
+
)
|
|
160
|
+
current_tokens = result.get(key)
|
|
161
|
+
if isinstance(current_tokens, TimedMultiset):
|
|
162
|
+
result.set(key, current_tokens.remove_available(tokens, now))
|
|
163
|
+
else:
|
|
164
|
+
if not (tokens <= current_tokens):
|
|
165
|
+
raise DeadMarkingError(
|
|
166
|
+
f"transition '{transition.name}' is not enabled under "
|
|
167
|
+
f"{element.describe(self.net)}: place is missing {tokens}"
|
|
168
|
+
)
|
|
169
|
+
result.set(key, current_tokens - tokens)
|
|
170
|
+
|
|
171
|
+
# --- 2. produce -----------------------------------------------------
|
|
172
|
+
for arc in page.arcs_of(transition):
|
|
173
|
+
if not arc.is_output or arc.expression_ast is None:
|
|
174
|
+
continue
|
|
175
|
+
key = self.net.marking_key(arc.place_id)
|
|
176
|
+
place = self.net.find_place(arc.place_id)
|
|
177
|
+
colour_set = self.net.colour_set_of(place) if place else None
|
|
178
|
+
|
|
179
|
+
# One group per timed term: `1`x@+2 +++ 1`y@+5` stamps x and y
|
|
180
|
+
# differently.
|
|
181
|
+
groups = self.net.evaluator.evaluate_timed_arc(
|
|
182
|
+
arc.expression_ast, environment, now
|
|
183
|
+
)
|
|
184
|
+
if colour_set is not None and colour_set.timed:
|
|
185
|
+
existing = result.get(key)
|
|
186
|
+
if not isinstance(existing, TimedMultiset):
|
|
187
|
+
existing = TimedMultiset.from_multiset(existing, now)
|
|
188
|
+
for tokens, stamp in groups:
|
|
189
|
+
tokens = expand_lists(tokens, colour_set)
|
|
190
|
+
existing = existing.add(
|
|
191
|
+
TimedMultiset.from_multiset(tokens, stamp + default_delay))
|
|
192
|
+
result.set(key, existing)
|
|
193
|
+
else:
|
|
194
|
+
existing = result.get(key)
|
|
195
|
+
if isinstance(existing, TimedMultiset):
|
|
196
|
+
# Defensive: a place whose colour set lost its `timed` flag.
|
|
197
|
+
existing = existing.available_at(now)
|
|
198
|
+
for tokens, _stamp in groups:
|
|
199
|
+
existing = existing + expand_lists(tokens, colour_set)
|
|
200
|
+
result.set(key, existing)
|
|
201
|
+
|
|
202
|
+
return result, now
|
|
203
|
+
|
|
204
|
+
# =======================================================================
|
|
205
|
+
# Stepping
|
|
206
|
+
# =======================================================================
|
|
207
|
+
def step(self, element: BindingElement | None = None) -> BindingElement:
|
|
208
|
+
"""Fire one binding element, advancing the clock first if necessary.
|
|
209
|
+
|
|
210
|
+
With no argument, picks a random enabled binding element (see
|
|
211
|
+
:meth:`_random_enabled_element`) -- the usual way to explore a model's
|
|
212
|
+
behaviour. If nothing is enabled now, time passes, one token-release
|
|
213
|
+
moment at a time, until something is: stopping at the *first* moment
|
|
214
|
+
would be wrong, because the token released there may not enable
|
|
215
|
+
anything while a later one does (CPN Tools keeps advancing too).
|
|
216
|
+
Raises :class:`DeadMarkingError` when the marking is dead.
|
|
217
|
+
"""
|
|
218
|
+
if element is None:
|
|
219
|
+
element = self._random_enabled_element()
|
|
220
|
+
while element is None:
|
|
221
|
+
if not self.advance_time():
|
|
222
|
+
raise DeadMarkingError(
|
|
223
|
+
f"dead marking at time {self.clock} after {self.step_count} steps"
|
|
224
|
+
)
|
|
225
|
+
element = self._random_enabled_element()
|
|
226
|
+
return self._fire_and_record(element)
|
|
227
|
+
|
|
228
|
+
def _fire_and_record(self, element: BindingElement) -> BindingElement:
|
|
229
|
+
self.marking, self.clock = self.fire(element)
|
|
230
|
+
self.step_count += 1
|
|
231
|
+
self.log.append(FiringRecord(self.step_count, self.clock, element, self.marking))
|
|
232
|
+
return element
|
|
233
|
+
|
|
234
|
+
def _random_enabled_element(self) -> BindingElement | None:
|
|
235
|
+
"""Pick a random enabled binding element without computing them all.
|
|
236
|
+
|
|
237
|
+
Like CPN Tools' automatic simulation: visit the transitions in random
|
|
238
|
+
order, and fire a random binding of the first one that is enabled.
|
|
239
|
+
(Every enabled transition is equally likely to be chosen; its
|
|
240
|
+
bindings then share that chance.) Computing every binding of every
|
|
241
|
+
transition at every step was the simulator's main cost.
|
|
242
|
+
"""
|
|
243
|
+
transitions = list(self.net.all_transitions())
|
|
244
|
+
self.choice_rng.shuffle(transitions)
|
|
245
|
+
for transition in transitions:
|
|
246
|
+
bindings = self.enabled_bindings(transition)
|
|
247
|
+
if bindings:
|
|
248
|
+
return self.choice_rng.choice(bindings)
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
def advance_time(self) -> bool:
|
|
252
|
+
"""Jump the clock to the next moment something could happen.
|
|
253
|
+
|
|
254
|
+
Returns ``True`` if the clock moved. ``False`` means every timed place
|
|
255
|
+
is either empty or holds only tokens already available, so waiting
|
|
256
|
+
cannot help and the marking is dead.
|
|
257
|
+
"""
|
|
258
|
+
candidates: list[int] = []
|
|
259
|
+
for place in self.net.all_places():
|
|
260
|
+
tokens = self.marking.get(self.net.marking_key(place.id))
|
|
261
|
+
if isinstance(tokens, TimedMultiset):
|
|
262
|
+
next_time = tokens.next_time_after(self.clock)
|
|
263
|
+
if next_time is not None:
|
|
264
|
+
candidates.append(next_time)
|
|
265
|
+
if not candidates:
|
|
266
|
+
return False
|
|
267
|
+
self.clock = min(candidates)
|
|
268
|
+
self.net.evaluator.set_model_time(self.clock)
|
|
269
|
+
return True
|
|
270
|
+
|
|
271
|
+
def run(self, max_steps: int = 100, until_time: int | None = None) -> int:
|
|
272
|
+
"""Fire repeatedly. Returns the number of steps actually taken.
|
|
273
|
+
|
|
274
|
+
Stops early on a dead marking, or when the clock passes ``until_time``.
|
|
275
|
+
Both stopping conditions are normal outcomes, not errors -- inspect
|
|
276
|
+
:attr:`log` and :attr:`marking` afterwards to see where it got to.
|
|
277
|
+
"""
|
|
278
|
+
taken = 0
|
|
279
|
+
for _ in range(max_steps):
|
|
280
|
+
if until_time is not None and self.clock > until_time:
|
|
281
|
+
break
|
|
282
|
+
try:
|
|
283
|
+
self.step()
|
|
284
|
+
except DeadMarkingError:
|
|
285
|
+
break
|
|
286
|
+
taken += 1
|
|
287
|
+
return taken
|
|
288
|
+
|
|
289
|
+
# =======================================================================
|
|
290
|
+
# Inspection
|
|
291
|
+
# =======================================================================
|
|
292
|
+
def reset(self, marking: Marking | None = None) -> None:
|
|
293
|
+
"""Return to the initial marking (or a supplied one) at time zero."""
|
|
294
|
+
self.marking = marking if marking is not None else self.net.initial_marking()
|
|
295
|
+
self.clock = 0
|
|
296
|
+
self.step_count = 0
|
|
297
|
+
self.log.clear()
|
|
298
|
+
|
|
299
|
+
def rewind_to(self, step: int) -> None:
|
|
300
|
+
"""Restore the state as it was after ``step`` firings."""
|
|
301
|
+
if step <= 0:
|
|
302
|
+
self.reset()
|
|
303
|
+
return
|
|
304
|
+
if step > len(self.log):
|
|
305
|
+
raise IndexError(f"only {len(self.log)} steps have been taken")
|
|
306
|
+
record = self.log[step - 1]
|
|
307
|
+
self.marking = record.marking
|
|
308
|
+
self.clock = record.time
|
|
309
|
+
self.step_count = record.step
|
|
310
|
+
del self.log[step:]
|
|
311
|
+
|
|
312
|
+
def describe(self) -> str:
|
|
313
|
+
"""A short status line plus the current marking."""
|
|
314
|
+
header = f"step {self.step_count}, time {self.clock}"
|
|
315
|
+
return f"{header}\n{self.marking.describe(self.net)}"
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"""Moved to :mod:`openprocess.learn.checks`."""
|
|
2
|
+
|
|
3
|
+
from ..learn.checks import * # noqa: F401,F403
|
|
4
|
+
from ..learn.checks import CORRECT, INCORRECT, PARTIAL, UNKNOWN, Context, Result, TaskError, check, footprint_of, model_answer_text, validate # noqa: F401,E501
|
|
5
|
+
from ..learn.computed import COMPUTED, compute # noqa: F401
|