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.
Files changed (173) hide show
  1. cpnpy/__init__.py +46 -0
  2. openprocess/__init__.py +57 -0
  3. openprocess/analysis/__init__.py +0 -0
  4. openprocess/analysis/state_space.py +521 -0
  5. openprocess/analysis/state_space_process.py +251 -0
  6. openprocess/cli.py +742 -0
  7. openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +31 -0
  8. openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +38 -0
  9. openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +27 -0
  10. openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +29 -0
  11. openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +65 -0
  12. openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +1 -0
  13. openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +67 -0
  14. openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +75 -0
  15. openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +5 -0
  16. openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +27 -0
  17. openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +65 -0
  18. openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +1 -0
  19. openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +62 -0
  20. openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +1 -0
  21. openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +36 -0
  22. openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +28 -0
  23. openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +30 -0
  24. openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +36 -0
  25. openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +69 -0
  26. openprocess/exercises/pack.md +14 -0
  27. openprocess/flow/__init__.py +50 -0
  28. openprocess/flow/box.py +466 -0
  29. openprocess/flow/boxes/__init__.py +7 -0
  30. openprocess/flow/boxes/check.py +119 -0
  31. openprocess/flow/boxes/compare.py +16 -0
  32. openprocess/flow/boxes/cpn.py +53 -0
  33. openprocess/flow/boxes/discover.py +124 -0
  34. openprocess/flow/boxes/filter.py +80 -0
  35. openprocess/flow/boxes/input.py +124 -0
  36. openprocess/flow/boxes/output.py +52 -0
  37. openprocess/flow/boxes/predict.py +186 -0
  38. openprocess/flow/boxes/science.py +159 -0
  39. openprocess/flow/boxes/sweeps.py +18 -0
  40. openprocess/flow/convert.py +187 -0
  41. openprocess/flow/datasets.py +198 -0
  42. openprocess/flow/explain.py +115 -0
  43. openprocess/flow/library.py +222 -0
  44. openprocess/flow/record.py +385 -0
  45. openprocess/flow/runner.py +357 -0
  46. openprocess/flow/sweep.py +92 -0
  47. openprocess/flow/types.py +290 -0
  48. openprocess/flow/workflow.py +628 -0
  49. openprocess/gui/__init__.py +0 -0
  50. openprocess/gui/app.py +90 -0
  51. openprocess/gui/arc_editing.py +295 -0
  52. openprocess/gui/canvas.py +1414 -0
  53. openprocess/gui/flow/__init__.py +8 -0
  54. openprocess/gui/flow/canvas.py +854 -0
  55. openprocess/gui/flow/page.py +972 -0
  56. openprocess/gui/flow/templates.py +131 -0
  57. openprocess/gui/flow/viewers.py +665 -0
  58. openprocess/gui/items.py +1275 -0
  59. openprocess/gui/learn/answer_boxes.py +978 -0
  60. openprocess/gui/learn/concealment.py +91 -0
  61. openprocess/gui/learn/mode.py +1181 -0
  62. openprocess/gui/panning.py +241 -0
  63. openprocess/gui/resources/openprocess-icon.png +0 -0
  64. openprocess/gui/studio/__init__.py +1 -0
  65. openprocess/gui/studio/__main__.py +3 -0
  66. openprocess/gui/studio/app.py +4031 -0
  67. openprocess/gui/studio/charts.py +115 -0
  68. openprocess/gui/studio/compare_page.py +487 -0
  69. openprocess/gui/studio/cpn_page.py +1858 -0
  70. openprocess/gui/studio/definition_view.py +284 -0
  71. openprocess/gui/studio/derivation_view.py +421 -0
  72. openprocess/gui/studio/documents.py +152 -0
  73. openprocess/gui/studio/dotted_chart.py +1401 -0
  74. openprocess/gui/studio/file_dialogs.py +143 -0
  75. openprocess/gui/studio/filter_dialog.py +247 -0
  76. openprocess/gui/studio/graph_builders.py +176 -0
  77. openprocess/gui/studio/graph_view.py +682 -0
  78. openprocess/gui/studio/instances.py +413 -0
  79. openprocess/gui/studio/log_editor.py +675 -0
  80. openprocess/gui/studio/log_page.py +800 -0
  81. openprocess/gui/studio/markdown_view.py +127 -0
  82. openprocess/gui/studio/mathtext.py +260 -0
  83. openprocess/gui/studio/ml_highlighter.py +75 -0
  84. openprocess/gui/studio/model_page.py +760 -0
  85. openprocess/gui/studio/net_comparison.py +124 -0
  86. openprocess/gui/studio/notes_overlay.py +275 -0
  87. openprocess/gui/studio/petri_page.py +844 -0
  88. openprocess/gui/studio/regions_view.py +502 -0
  89. openprocess/gui/studio/sidebar.py +149 -0
  90. openprocess/gui/studio/style.py +503 -0
  91. openprocess/gui/studio/tool_icons.py +134 -0
  92. openprocess/gui/studio/updates.py +439 -0
  93. openprocess/gui/studio/widgets.py +899 -0
  94. openprocess/gui/studio/workers.py +60 -0
  95. openprocess/gui/studio/workspace.py +447 -0
  96. openprocess/gui/theme.py +394 -0
  97. openprocess/gui/tidy.py +86 -0
  98. openprocess/io/__init__.py +0 -0
  99. openprocess/io/cpn_reader.py +389 -0
  100. openprocess/io/cpn_writer.py +357 -0
  101. openprocess/learn/__init__.py +23 -0
  102. openprocess/learn/answers.py +188 -0
  103. openprocess/learn/checks.py +953 -0
  104. openprocess/learn/computed.py +1180 -0
  105. openprocess/learn/context.py +145 -0
  106. openprocess/learn/exam.py +169 -0
  107. openprocess/learn/exercise-packs.md +325 -0
  108. openprocess/learn/importer.py +216 -0
  109. openprocess/learn/notation.py +474 -0
  110. openprocess/learn/pack.py +511 -0
  111. openprocess/learn/sheet.py +296 -0
  112. openprocess/mining/__init__.py +73 -0
  113. openprocess/mining/analysis.py +689 -0
  114. openprocess/mining/columns.py +282 -0
  115. openprocess/mining/compare_nets.py +246 -0
  116. openprocess/mining/conformance/__init__.py +0 -0
  117. openprocess/mining/conformance/alignments.py +263 -0
  118. openprocess/mining/conformance/quality.py +145 -0
  119. openprocess/mining/conformance/token_replay.py +252 -0
  120. openprocess/mining/csv_import.py +222 -0
  121. openprocess/mining/definitions.py +584 -0
  122. openprocess/mining/dfg.py +187 -0
  123. openprocess/mining/discovery/__init__.py +0 -0
  124. openprocess/mining/discovery/alpha.py +168 -0
  125. openprocess/mining/discovery/heuristics.py +332 -0
  126. openprocess/mining/discovery/inductive.py +477 -0
  127. openprocess/mining/discovery/state_regions.py +62 -0
  128. openprocess/mining/filtering.py +237 -0
  129. openprocess/mining/footprint.py +183 -0
  130. openprocess/mining/invariants.py +191 -0
  131. openprocess/mining/layout.py +279 -0
  132. openprocess/mining/log.py +364 -0
  133. openprocess/mining/petrinet.py +354 -0
  134. openprocess/mining/playout.py +75 -0
  135. openprocess/mining/pm4py_bridge.py +82 -0
  136. openprocess/mining/pnml.py +223 -0
  137. openprocess/mining/processtree.py +216 -0
  138. openprocess/mining/regions.py +476 -0
  139. openprocess/mining/stats.py +160 -0
  140. openprocess/mining/structure.py +374 -0
  141. openprocess/mining/transition_system.py +409 -0
  142. openprocess/mining/xes.py +399 -0
  143. openprocess/ml/__init__.py +0 -0
  144. openprocess/ml/ast_nodes.py +332 -0
  145. openprocess/ml/builtins.py +364 -0
  146. openprocess/ml/colorsets.py +522 -0
  147. openprocess/ml/errors.py +60 -0
  148. openprocess/ml/evaluator.py +754 -0
  149. openprocess/ml/lexer.py +277 -0
  150. openprocess/ml/multiset.py +417 -0
  151. openprocess/ml/parser.py +737 -0
  152. openprocess/ml/values.py +319 -0
  153. openprocess/model/__init__.py +0 -0
  154. openprocess/model/declarations.py +617 -0
  155. openprocess/model/examples.py +98 -0
  156. openprocess/model/net.py +701 -0
  157. openprocess/model/plain.py +192 -0
  158. openprocess/references.py +280 -0
  159. openprocess/sim/__init__.py +0 -0
  160. openprocess/sim/binding.py +620 -0
  161. openprocess/sim/export.py +66 -0
  162. openprocess/sim/simulator.py +315 -0
  163. openprocess/teaching/__init__.py +4 -0
  164. openprocess/teaching/answers.py +4 -0
  165. openprocess/teaching/checks.py +5 -0
  166. openprocess/teaching/pack.py +4 -0
  167. openprocess/teaching/sheet.py +4 -0
  168. openprocess-0.7.0.dist-info/METADATA +927 -0
  169. openprocess-0.7.0.dist-info/RECORD +173 -0
  170. openprocess-0.7.0.dist-info/WHEEL +5 -0
  171. openprocess-0.7.0.dist-info/entry_points.txt +6 -0
  172. openprocess-0.7.0.dist-info/licenses/LICENSE +21 -0
  173. openprocess-0.7.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,319 @@
1
+ """Runtime values of the CPN ML subset.
2
+
3
+ Design decisions worth stating up front, because everything else depends on
4
+ them:
5
+
6
+ 1. **Values must be hashable.** A marking is a multiset of values, and we
7
+ implement multisets with dictionaries. So every value we can put in a place
8
+ has to be usable as a dict key. That rules out Python ``list`` and ``dict``
9
+ as representations and is why :class:`MLList` and :class:`Record` exist.
10
+
11
+ 2. **Values must be totally ordered.** State space analysis, canonical
12
+ printing of markings, and deterministic test output all need a stable order
13
+ over values of *different* shapes. Python will happily compare two ints but
14
+ refuses to compare an int with a string, so we define our own ordering via
15
+ :func:`sort_key`, which prefixes a type rank.
16
+
17
+ 3. **We reuse Python primitives where the semantics coincide.** CPN ML's
18
+ ``INT``, ``BOOL``, ``STRING`` and ``REAL`` map onto Python ``int``, ``bool``,
19
+ ``str`` and ``float`` exactly. Only the constructs Python lacks (ML lists
20
+ as values, records, and union constructors) get wrapper classes.
21
+
22
+ Mapping from CPN ML to this module:
23
+
24
+ ================== ==========================================
25
+ CPN ML Python representation
26
+ ================== ==========================================
27
+ unit ``()`` :data:`UNIT` (the single ``Unit`` instance)
28
+ ``true`` / ``false`` ``bool``
29
+ ``3`` ``int``
30
+ ``3.0`` ``float``
31
+ ``"abc"`` ``str``
32
+ ``(a, b)`` ``tuple`` (product colour set)
33
+ ``[a, b]`` :class:`MLList`
34
+ ``{f=a, g=b}`` :class:`Record`
35
+ ``Cons(x)`` :class:`Constructor` (union colour set / enum)
36
+ ================== ==========================================
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ from typing import Any, Iterable, Iterator
42
+
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # unit
46
+ # ---------------------------------------------------------------------------
47
+ class Unit:
48
+ """The single value ``()`` of the ``UNIT`` colour set.
49
+
50
+ Implemented as a singleton so that ``UNIT is UNIT`` and equality is cheap.
51
+ In CPN models the unit colour set is used for tokens that carry no data --
52
+ i.e. exactly the tokens of a classical place/transition Petri net.
53
+ """
54
+
55
+ _instance: "Unit | None" = None
56
+
57
+ def __new__(cls) -> "Unit":
58
+ if cls._instance is None:
59
+ cls._instance = super().__new__(cls)
60
+ return cls._instance
61
+
62
+ def __repr__(self) -> str:
63
+ return "()"
64
+
65
+ __str__ = __repr__
66
+
67
+ def __eq__(self, other: object) -> bool:
68
+ return isinstance(other, Unit)
69
+
70
+ def __hash__(self) -> int:
71
+ return hash("()")
72
+
73
+
74
+ UNIT = Unit()
75
+
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # lists
79
+ # ---------------------------------------------------------------------------
80
+ class MLList:
81
+ """An immutable CPN ML list value, e.g. ``[1, 2, 3]``.
82
+
83
+ We cannot use a Python ``tuple`` because a tuple already represents a
84
+ *product* value, and ``(1, 2)`` and ``[1, 2]`` are different CPN values
85
+ belonging to different colour sets. Backed by a tuple internally so that
86
+ hashing and equality come for free.
87
+ """
88
+
89
+ __slots__ = ("items",)
90
+
91
+ def __init__(self, items: Iterable[Any] = ()) -> None:
92
+ self.items: tuple[Any, ...] = tuple(items)
93
+
94
+ # -- sequence protocol ---------------------------------------------------
95
+ def __iter__(self) -> Iterator[Any]:
96
+ return iter(self.items)
97
+
98
+ def __len__(self) -> int:
99
+ return len(self.items)
100
+
101
+ def __getitem__(self, index: int) -> Any:
102
+ return self.items[index]
103
+
104
+ # -- value protocol ------------------------------------------------------
105
+ def __eq__(self, other: object) -> bool:
106
+ return isinstance(other, MLList) and self.items == other.items
107
+
108
+ def __hash__(self) -> int:
109
+ return hash(("MLList", self.items))
110
+
111
+ def __repr__(self) -> str:
112
+ return "[" + ",".join(format_value(x) for x in self.items) + "]"
113
+
114
+ # -- ML operations -------------------------------------------------------
115
+ def cons(self, head: Any) -> "MLList":
116
+ """``head :: self`` -- prepend, returning a new list."""
117
+ return MLList((head,) + self.items)
118
+
119
+ def append(self, other: "MLList") -> "MLList":
120
+ """``self ^^ other`` / ``self @ other`` -- concatenation."""
121
+ return MLList(self.items + other.items)
122
+
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # records
126
+ # ---------------------------------------------------------------------------
127
+ class Record:
128
+ """An immutable CPN ML record value, e.g. ``{name = "a", age = 3}``.
129
+
130
+ Field order is preserved as given by the colour set declaration, because
131
+ CPN Tools prints records in declaration order and we want byte-identical
132
+ round-trips. Equality and hashing ignore order only in the sense that two
133
+ records with the same field/value pairs in the same declared order are
134
+ equal -- CPN ML has no notion of two records with permuted fields, so this
135
+ is not a limitation.
136
+ """
137
+
138
+ __slots__ = ("fields",)
139
+
140
+ def __init__(self, fields: Iterable[tuple[str, Any]] | dict[str, Any]) -> None:
141
+ if isinstance(fields, dict):
142
+ fields = tuple(fields.items())
143
+ self.fields: tuple[tuple[str, Any], ...] = tuple(fields)
144
+
145
+ def get(self, name: str) -> Any:
146
+ """Field selection, i.e. ML's ``#name record``."""
147
+ for field_name, value in self.fields:
148
+ if field_name == name:
149
+ return value
150
+ raise KeyError(name)
151
+
152
+ def names(self) -> tuple[str, ...]:
153
+ return tuple(n for n, _ in self.fields)
154
+
155
+ def __eq__(self, other: object) -> bool:
156
+ return isinstance(other, Record) and self.fields == other.fields
157
+
158
+ def __hash__(self) -> int:
159
+ return hash(("Record", self.fields))
160
+
161
+ def __repr__(self) -> str:
162
+ inner = ",".join(f"{n}={format_value(v)}" for n, v in self.fields)
163
+ return "{" + inner + "}"
164
+
165
+
166
+ # ---------------------------------------------------------------------------
167
+ # union / enumeration constructors
168
+ # ---------------------------------------------------------------------------
169
+ class Constructor:
170
+ """A tagged value from a union or enumeration colour set.
171
+
172
+ Two shapes exist and both are represented here:
173
+
174
+ * ``colset E = with red | green | blue;`` -- an *enumeration*. Its values
175
+ are constructors with ``argument is None``: ``Constructor("red")``.
176
+ * ``colset U = union Car:CARS + Bike:BIKES;`` -- a *union*. Its values
177
+ carry a payload: ``Constructor("Car", some_car_value)``.
178
+
179
+ Keeping them in one class means pattern matching, printing and ordering
180
+ only have to handle one case.
181
+ """
182
+
183
+ __slots__ = ("name", "argument", "_has_arg")
184
+
185
+ # Sentinel distinguishing "no argument at all" (enumeration constant) from
186
+ # "argument that happens to be unit".
187
+ _NO_ARG = object()
188
+
189
+ def __init__(self, name: str, argument: Any = _NO_ARG) -> None:
190
+ self.name = name
191
+ self.argument = None if argument is Constructor._NO_ARG else argument
192
+ self._has_arg = argument is not Constructor._NO_ARG
193
+
194
+ @property
195
+ def has_argument(self) -> bool:
196
+ return self._has_arg
197
+
198
+ def __eq__(self, other: object) -> bool:
199
+ return (
200
+ isinstance(other, Constructor)
201
+ and self.name == other.name
202
+ and self._has_arg == other._has_arg
203
+ and self.argument == other.argument
204
+ )
205
+
206
+ def __hash__(self) -> int:
207
+ return hash(("Constructor", self.name, self._has_arg, self.argument))
208
+
209
+ def __repr__(self) -> str:
210
+ if not self._has_arg:
211
+ return self.name
212
+ return f"{self.name}({format_value(self.argument)})"
213
+
214
+
215
+ # ---------------------------------------------------------------------------
216
+ # ordering and printing
217
+ # ---------------------------------------------------------------------------
218
+ # Rank of each value shape. Values of different shapes are ordered by rank
219
+ # first, which gives us a total order over the whole value universe. The
220
+ # specific numbers are arbitrary but must never change, or saved state spaces
221
+ # would reorder between versions.
222
+ _TYPE_RANK: dict[type, int] = {
223
+ Unit: 0,
224
+ bool: 1,
225
+ int: 2,
226
+ float: 3,
227
+ str: 4,
228
+ Constructor: 5,
229
+ tuple: 6,
230
+ MLList: 7,
231
+ Record: 8,
232
+ }
233
+
234
+
235
+ _SORT_KEYS: dict = {}
236
+
237
+
238
+ def sort_key(value: Any) -> tuple:
239
+ """Memoised :func:`_sort_key` -- the simulator sorts the same token
240
+ values over and over, and building nested key tuples dominated its run
241
+ time. Values are immutable and hashable, so caching is safe; the cache
242
+ is bounded so a long run cannot grow it without limit."""
243
+ try:
244
+ return _SORT_KEYS[value]
245
+ except KeyError:
246
+ pass
247
+ except TypeError: # an unhashable value: compute directly
248
+ return _sort_key(value)
249
+ key = _sort_key(value)
250
+ if len(_SORT_KEYS) > 200_000:
251
+ _SORT_KEYS.clear()
252
+ _SORT_KEYS[value] = key
253
+ return key
254
+
255
+
256
+ def _sort_key(value: Any) -> tuple:
257
+ """Return a tuple that sorts values of *any* shape into a total order.
258
+
259
+ Used for canonical marking output and for deterministic iteration during
260
+ state space exploration. The recursion mirrors the value structure, so
261
+ ``sort_key`` on nested values is itself nested and compares element-wise,
262
+ exactly like ML's structural comparison.
263
+ """
264
+ # ``bool`` must be tested before ``int`` because ``bool`` subclasses ``int``.
265
+ if isinstance(value, Unit):
266
+ return (_TYPE_RANK[Unit],)
267
+ if isinstance(value, bool):
268
+ return (_TYPE_RANK[bool], value)
269
+ if isinstance(value, int):
270
+ return (_TYPE_RANK[int], value)
271
+ if isinstance(value, float):
272
+ return (_TYPE_RANK[float], value)
273
+ if isinstance(value, str):
274
+ return (_TYPE_RANK[str], value)
275
+ if isinstance(value, Constructor):
276
+ arg = sort_key(value.argument) if value.has_argument else ()
277
+ return (_TYPE_RANK[Constructor], value.name, arg)
278
+ if isinstance(value, tuple):
279
+ return (_TYPE_RANK[tuple], tuple(sort_key(v) for v in value))
280
+ if isinstance(value, MLList):
281
+ return (_TYPE_RANK[MLList], tuple(sort_key(v) for v in value.items))
282
+ if isinstance(value, Record):
283
+ return (
284
+ _TYPE_RANK[Record],
285
+ tuple((n, sort_key(v)) for n, v in value.fields),
286
+ )
287
+ # Anything else is a bug in the evaluator, not a user error.
288
+ raise AssertionError(f"value of unsupported shape: {value!r}")
289
+
290
+
291
+ def format_value(value: Any) -> str:
292
+ """Render a value the way CPN Tools would print it.
293
+
294
+ Notable conventions we follow deliberately:
295
+
296
+ * strings are double-quoted;
297
+ * reals always show a decimal point (``3.0``, not ``3``) so that they are
298
+ visually distinct from integers;
299
+ * negative numbers use ML's ``~`` prefix, not ``-`` (``~3``), because that
300
+ is what CPN Tools writes and what our lexer reads back.
301
+ """
302
+ if isinstance(value, Unit):
303
+ return "()"
304
+ if isinstance(value, bool):
305
+ return "true" if value else "false"
306
+ if isinstance(value, int):
307
+ return f"~{-value}" if value < 0 else str(value)
308
+ if isinstance(value, float):
309
+ text = repr(value)
310
+ if value < 0:
311
+ text = "~" + repr(-value)
312
+ return text
313
+ if isinstance(value, str):
314
+ escaped = value.replace("\\", "\\\\").replace('"', '\\"')
315
+ return f'"{escaped}"'
316
+ if isinstance(value, tuple):
317
+ return "(" + ",".join(format_value(v) for v in value) + ")"
318
+ # MLList, Record and Constructor already format themselves in __repr__.
319
+ return repr(value)
File without changes