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,290 @@
1
+ """The types boxes exchange.
2
+
3
+ A box's connection points are typed, and the types are the ones OpenProcess
4
+ already uses: an event log, a Petri net, a transition system, ... This
5
+ module re-exports them so a box author imports from one place, adds the few
6
+ small types that only workflows need (scores, tables, figures, datasets),
7
+ and keeps the registry the app uses to draw a connection point: its short
8
+ key, its plain name and its colour.
9
+
10
+ Registering a type of your own::
11
+
12
+ from openprocess.flow.types import register_type
13
+
14
+ class Embedding: ...
15
+ register_type(Embedding, "embedding", "Embedding")
16
+
17
+ A box may also take or give ``typing.Any``: such a connection point is grey,
18
+ connects only to a point of the same declared class, and is shown with a
19
+ generic viewer. That is how a *Train encoder* box feeds an *Embed prefixes*
20
+ box without OpenProcess knowing what an encoder is.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from dataclasses import dataclass, field
26
+ from typing import Any, Iterable
27
+
28
+ from ..mining.conformance.alignments import AlignmentResult
29
+ from ..mining.conformance.token_replay import ReplayResult
30
+ from ..mining.dfg import DFG
31
+ from ..mining.footprint import Footprint
32
+ from ..mining.log import EventLog, SimpleLog
33
+ from ..mining.petrinet import Marking, PetriNet
34
+ from ..mining.processtree import ProcessTree
35
+ from ..mining.regions import RegionAnalysis as Regions
36
+ from ..mining.transition_system import TransitionSystem
37
+ from ..model.net import CPNet
38
+
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Small types that only workflows need
42
+ # ---------------------------------------------------------------------------
43
+ @dataclass
44
+ class Scores:
45
+ """Named numbers about one model: the row of a Compare table.
46
+
47
+ ``metrics`` keeps insertion order; a value may be a number, a string
48
+ (``"Sound"``) or a boolean. ``context`` holds the settings a sweep gave
49
+ the boxes that produced it (``{"noise": 0.2}``), so stacked scores can be
50
+ told apart.
51
+ """
52
+
53
+ model: str
54
+ metrics: dict[str, Any] = field(default_factory=dict)
55
+ note: str = ""
56
+ context: dict[str, Any] = field(default_factory=dict)
57
+
58
+ def __getitem__(self, name: str) -> Any:
59
+ return self.metrics[name]
60
+
61
+ def items(self):
62
+ return self.metrics.items()
63
+
64
+
65
+ @dataclass
66
+ class Table:
67
+ """Anything tabular: rows of values under column names.
68
+
69
+ ``samples`` is set by boxes that produce a distribution (the bootstrap):
70
+ the raw numbers behind the summary rows, for tests and histograms.
71
+ """
72
+
73
+ name: str
74
+ columns: list[str]
75
+ rows: list[list[Any]] = field(default_factory=list)
76
+ samples: list[float] | None = None
77
+ note: str = ""
78
+ context: dict[str, Any] = field(default_factory=dict)
79
+
80
+ @classmethod
81
+ def from_scores(cls, scores: Iterable[Scores], name: str = "Scores") -> "Table":
82
+ """One row per :class:`Scores`, the columns being every metric seen
83
+ (and every context key, so a sweep's values become columns)."""
84
+ scores = list(scores)
85
+ context_keys: list[str] = []
86
+ metric_keys: list[str] = []
87
+ for s in scores:
88
+ for key in s.context:
89
+ if key not in context_keys:
90
+ context_keys.append(key)
91
+ for key in s.metrics:
92
+ if key not in metric_keys:
93
+ metric_keys.append(key)
94
+ columns = ["model", *context_keys, *metric_keys]
95
+ rows = [[s.model, *[s.context.get(k) for k in context_keys],
96
+ *[s.metrics.get(k) for k in metric_keys]] for s in scores]
97
+ return cls(name, columns, rows)
98
+
99
+ def column(self, name: str) -> list[Any]:
100
+ index = self.columns.index(name)
101
+ return [row[index] for row in self.rows]
102
+
103
+ def __len__(self) -> int:
104
+ return len(self.rows)
105
+
106
+ def to_csv(self, path) -> None:
107
+ import csv
108
+ with open(path, "w", newline="", encoding="utf-8") as handle:
109
+ writer = csv.writer(handle)
110
+ writer.writerow(self.columns)
111
+ writer.writerows(self.rows)
112
+
113
+
114
+ @dataclass
115
+ class Figure:
116
+ """A picture: SVG text, or PNG bytes, as a box made it."""
117
+
118
+ name: str = "Figure"
119
+ svg: str | None = None
120
+ png: bytes | None = None
121
+ caption: str = ""
122
+
123
+ def save(self, path) -> None:
124
+ path = str(path)
125
+ if self.svg is not None and path.lower().endswith(".svg"):
126
+ with open(path, "w", encoding="utf-8") as handle:
127
+ handle.write(self.svg)
128
+ elif self.png is not None:
129
+ with open(path, "wb") as handle:
130
+ handle.write(self.png)
131
+ else:
132
+ raise ValueError(f"This figure has no {path.rsplit('.', 1)[-1].upper()} form.")
133
+
134
+
135
+ @dataclass
136
+ class Text:
137
+ """A report or any other text a box produces."""
138
+
139
+ text: str
140
+ name: str = "Text"
141
+
142
+ def __str__(self) -> str:
143
+ return self.text
144
+
145
+
146
+ @dataclass
147
+ class Dataset:
148
+ """Prefixes of a log encoded for prediction: one row per prefix.
149
+
150
+ ``X`` holds one feature vector per row (plain lists, or a NumPy array if
151
+ the box that made it used NumPy), ``y`` the label of each row. ``kind``
152
+ is ``"classification"`` (next activity, outcome) or ``"regression"``
153
+ (remaining time, in hours). ``case_ids`` and ``prefix_lengths`` say
154
+ where each row came from, so evaluation can be shown per prefix length
155
+ and a split can be made by case.
156
+ """
157
+
158
+ X: Any
159
+ y: list[Any]
160
+ feature_names: list[str]
161
+ label: str
162
+ kind: str = "classification"
163
+ case_ids: list[str] = field(default_factory=list)
164
+ prefix_lengths: list[int] = field(default_factory=list)
165
+ case_starts: list[Any] = field(default_factory=list)
166
+ activities: list[str] = field(default_factory=list)
167
+ classes: list[str] | None = None
168
+ name: str = "Dataset"
169
+
170
+ def __len__(self) -> int:
171
+ return len(self.y)
172
+
173
+ def subset(self, indices: Iterable[int], name: str | None = None) -> "Dataset":
174
+ indices = list(indices)
175
+ X = self.X
176
+ rows = [X[i] for i in indices]
177
+ try: # keep a NumPy array a NumPy array
178
+ import numpy as np
179
+ if isinstance(X, np.ndarray):
180
+ rows = X[indices]
181
+ except ImportError:
182
+ pass
183
+ return Dataset(rows, [self.y[i] for i in indices], list(self.feature_names), self.label,
184
+ self.kind, [self.case_ids[i] for i in indices] if self.case_ids else [],
185
+ [self.prefix_lengths[i] for i in indices] if self.prefix_lengths else [],
186
+ [self.case_starts[i] for i in indices] if self.case_starts else [],
187
+ list(self.activities), self.classes, name or self.name)
188
+
189
+
190
+ @dataclass
191
+ class Predictions:
192
+ """What a model predicted for every row of a dataset."""
193
+
194
+ values: list[Any]
195
+ dataset: Dataset
196
+ name: str = "Predictions"
197
+
198
+ def __len__(self) -> int:
199
+ return len(self.values)
200
+
201
+
202
+ class Predictor:
203
+ """What a prediction model must be able to do to sit between *Prefixes*
204
+ and *Evaluate predictions*: ``fit`` on a dataset and ``predict`` the
205
+ labels of another. Subclass it, or wrap any object that has the two
206
+ methods (scikit-learn estimators fit the shape already)."""
207
+
208
+ name = "Predictor"
209
+
210
+ def fit(self, dataset: Dataset) -> "Predictor":
211
+ raise NotImplementedError
212
+
213
+ def predict(self, dataset: Dataset) -> list[Any]:
214
+ raise NotImplementedError
215
+
216
+
217
+ # ---------------------------------------------------------------------------
218
+ # The registry
219
+ # ---------------------------------------------------------------------------
220
+ @dataclass(frozen=True)
221
+ class TypeInfo:
222
+ cls: type
223
+ key: str # short id used in files and the GUI, e.g. "log"
224
+ name: str # plain name, e.g. "Log"
225
+ colour: str # a hex colour for the connection point
226
+ python: str # the name a box author writes, e.g. "EventLog"
227
+
228
+
229
+ #: Every type a connection point can have, by class.
230
+ TYPES: dict[type, TypeInfo] = {}
231
+ _BY_KEY: dict[str, TypeInfo] = {}
232
+
233
+
234
+ def register_type(cls: type, key: str, name: str, colour: str = "#86868b",
235
+ python: str | None = None) -> TypeInfo:
236
+ """Make ``cls`` usable as a connection point type."""
237
+ info = TypeInfo(cls, key, name, colour, python or cls.__name__)
238
+ TYPES[cls] = info
239
+ _BY_KEY[key] = info
240
+ return info
241
+
242
+
243
+ def type_info(cls) -> TypeInfo | None:
244
+ """The registry entry for ``cls`` (or a subclass of a registered type)."""
245
+ if cls in TYPES:
246
+ return TYPES[cls]
247
+ if isinstance(cls, type):
248
+ for known, info in TYPES.items():
249
+ if issubclass(cls, known):
250
+ return info
251
+ return None
252
+
253
+
254
+ def type_by_key(key: str) -> TypeInfo | None:
255
+ return _BY_KEY.get(key)
256
+
257
+
258
+ def is_known(cls) -> bool:
259
+ return type_info(cls) is not None or cls is Any
260
+
261
+
262
+ for _cls, _key, _name, _colour in (
263
+ (EventLog, "log", "Log", "#0a84ff"),
264
+ (SimpleLog, "simple_log", "Simple log", "#0a84ff"),
265
+ (PetriNet, "net", "Petri net", "#2e9e4f"),
266
+ (CPNet, "cpn", "Coloured Petri net", "#2e9e4f"),
267
+ (Marking, "marking", "Marking", "#2e9e4f"),
268
+ (ProcessTree, "tree", "Process tree", "#2e9e4f"),
269
+ (TransitionSystem, "ts", "Transition system", "#b0430f"),
270
+ (DFG, "dfg", "Directly-follows graph", "#b0430f"),
271
+ (Footprint, "footprint", "Footprint", "#b0430f"),
272
+ (ReplayResult, "replay", "Replay", "#8250c8"),
273
+ (AlignmentResult, "alignments", "Alignments", "#8250c8"),
274
+ (Regions, "regions", "Regions", "#b0430f"),
275
+ (Scores, "scores", "Scores", "#8250c8"),
276
+ (Table, "table", "Table", "#5a7d8c"),
277
+ (Figure, "figure", "Figure", "#c9871b"),
278
+ (Text, "text", "Text", "#5a7d8c"),
279
+ (Dataset, "dataset", "Dataset", "#5a7d8c"),
280
+ (Predictions, "predictions", "Predictions", "#5a7d8c"),
281
+ (Predictor, "predictor", "Prediction model", "#86868b"),
282
+ ):
283
+ register_type(_cls, _key, _name, _colour)
284
+
285
+ __all__ = [
286
+ "AlignmentResult", "Any", "CPNet", "DFG", "Dataset", "EventLog", "Figure", "Footprint",
287
+ "Marking", "PetriNet", "Predictions", "Predictor", "ProcessTree", "Regions", "ReplayResult",
288
+ "Scores", "SimpleLog", "Table", "Text", "TransitionSystem", "TypeInfo", "TYPES",
289
+ "is_known", "register_type", "type_by_key", "type_info",
290
+ ]