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,284 @@
1
+ """Definitions on screen: hover tooltips, click pop-ups and the reference dialog.
2
+
3
+ Every property an Analysis tab reports (sound, live, free-choice, ...) is
4
+ defined in :mod:`openprocess.mining.definitions`. Here that becomes:
5
+
6
+ * a **tooltip** when the mouse rests on the property: the definition's
7
+ formulas and, underneath, the same formulas **filled in for this net**
8
+ (its place names, and the marking and firing sequence that break the
9
+ property when it fails -- see :mod:`.instances`);
10
+ * a **pop-up** when the property is clicked, which also has the notes, the
11
+ definitions it builds on (click to follow) and the source;
12
+ * the **Definitions** dialog (Help ▸ Definitions, or *All definitions…*):
13
+ the whole reference, the same text as ``docs/definitions.md``.
14
+
15
+ Formulas are typeset by :mod:`.mathtext`.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from html import escape
21
+
22
+ from PySide6.QtCore import QEvent, QObject, QPoint, Qt, QUrl
23
+ from PySide6.QtGui import QCursor, QGuiApplication
24
+ from PySide6.QtWidgets import (
25
+ QDialog, QFrame, QHBoxLayout, QPushButton, QTextBrowser, QVBoxLayout, QWidget,
26
+ )
27
+
28
+ from ...mining.definitions import BY_KEY, DEFINITIONS, SECTIONS, Definition, lookup
29
+ from . import style
30
+ from .mathtext import display, render_text
31
+
32
+ WIDTH = 480
33
+
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # HTML
37
+ # ---------------------------------------------------------------------------
38
+ def definition_html(definition: Definition, instance: list[str] | None = None,
39
+ compact: bool = False) -> str:
40
+ """The definition (and its instance for this net) as rich text.
41
+
42
+ ``compact`` is the tooltip: formulas and instance only, with a hint that
43
+ a click shows more.
44
+ """
45
+ t = style.tokens()
46
+ parts = [f"<div style='font-size: 15px; font-weight: 600'>{escape(definition.name)}"
47
+ "</div>",
48
+ f"<div style='color: {t.text_secondary}; margin-top: 2px'>"
49
+ f"{render_text(definition.summary)}</div>"]
50
+ parts += [display(formula) for formula in definition.formulas]
51
+ if instance:
52
+ parts.append(f"<div style='margin-top: 8px; font-weight: 600; color: {t.accent}'>"
53
+ "For this net</div>")
54
+ parts += [display(line) for line in instance]
55
+ if compact:
56
+ parts.append(f"<div style='color: {t.text_muted}; font-size: 11px; margin-top: 6px'>"
57
+ "Click for notes and related definitions.</div>")
58
+ else:
59
+ for note in definition.notes:
60
+ parts.append(f"<p style='color: {t.text_secondary}'>{render_text(note)}</p>")
61
+ if definition.uses:
62
+ links = ", ".join(f"<a href='def:{key}'>{escape(BY_KEY[key].name)}</a>"
63
+ for key in definition.uses)
64
+ parts.append(f"<p>Builds on: {links}.</p>")
65
+ if definition.source:
66
+ parts.append(f"<p style='color: {t.text_muted}; font-size: 11px'>Source: "
67
+ f"{render_text(definition.source)}.</p>")
68
+ body = "".join(parts)
69
+ return (f"<table width='{WIDTH - 24}' cellpadding='0' cellspacing='0'><tr><td>"
70
+ f"{body}</td></tr></table>")
71
+
72
+
73
+ def reference_html() -> str:
74
+ """Every definition, grouped by section, with anchors for links."""
75
+ t = style.tokens()
76
+ parts = ["<h2>Definitions</h2>",
77
+ f"<p style='color: {t.text_secondary}'>Every property the analyses report. "
78
+ "Throughout, " + render_text("$N = (P, T, F)$") + " is a Petri net and, for "
79
+ "WF-nets, " + render_text("$i$ and $o$") + " are its start and end places. "
80
+ "The same text is in <code>docs/definitions.md</code>.</p>"]
81
+ for section, title, intro in SECTIONS:
82
+ parts.append(f"<h3 style='margin-top: 18px'>{escape(title)}</h3>"
83
+ f"<p style='color: {t.text_secondary}'>{render_text(intro)}</p>")
84
+ for definition in DEFINITIONS:
85
+ if definition.section == section:
86
+ parts.append(f"<a name='{definition.key}'></a>"
87
+ f"<div style='margin: 10px 0 14px 0'>"
88
+ f"{definition_html(definition)}</div>")
89
+ return "".join(parts)
90
+
91
+
92
+ # ---------------------------------------------------------------------------
93
+ # Widgets
94
+ # ---------------------------------------------------------------------------
95
+ class _Browser(QTextBrowser):
96
+ """Rich text with ``def:key`` links handled by the owner."""
97
+
98
+ def __init__(self, on_link) -> None:
99
+ super().__init__()
100
+ self.setOpenLinks(False)
101
+ self.setFrameShape(QFrame.NoFrame)
102
+ self.document().setDefaultStyleSheet(
103
+ f"a {{ color: {style.tokens().accent}; text-decoration: none; }}")
104
+ self.anchorClicked.connect(lambda url: on_link(url.toString()))
105
+
106
+
107
+ class DefinitionPopup(QFrame):
108
+ """The pop-up a click on a property opens (closes on any click outside)."""
109
+
110
+ def __init__(self, definition: Definition, instance: list[str] | None = None,
111
+ parent: QWidget | None = None) -> None:
112
+ super().__init__(parent, Qt.Popup)
113
+ self.setObjectName("definitionPopup")
114
+ t = style.tokens()
115
+ self.setStyleSheet(f"#definitionPopup {{ background: {t.surface}; "
116
+ f"border: 1px solid {t.border}; border-radius: 10px; }}"
117
+ f"QTextBrowser {{ background: transparent; }}")
118
+ layout = QVBoxLayout(self)
119
+ layout.setContentsMargins(12, 10, 12, 10)
120
+ self.browser = _Browser(self._link)
121
+ self.browser.setFixedWidth(WIDTH)
122
+ layout.addWidget(self.browser)
123
+ row = QHBoxLayout()
124
+ self.back_button = QPushButton("← Back")
125
+ self.back_button.clicked.connect(self._back)
126
+ everything = QPushButton("All definitions…")
127
+ everything.clicked.connect(lambda: self._open_reference(self.current.key))
128
+ row.addWidget(self.back_button)
129
+ row.addStretch(1)
130
+ row.addWidget(everything)
131
+ layout.addLayout(row)
132
+ self.history: list[tuple[Definition, list[str] | None]] = []
133
+ self.current = definition
134
+ self._show(definition, instance)
135
+
136
+ def _show(self, definition: Definition, instance) -> None:
137
+ self.current, self.instance = definition, instance
138
+ self.browser.setHtml(definition_html(definition, instance))
139
+ self.back_button.setVisible(bool(self.history))
140
+ document = self.browser.document()
141
+ document.setTextWidth(WIDTH - 8)
142
+ screen = QGuiApplication.screenAt(QCursor.pos()) or QGuiApplication.primaryScreen()
143
+ limit = int(screen.availableGeometry().height() * 0.7) if screen else 560
144
+ self.browser.setFixedHeight(min(int(document.size().height()) + 12, limit))
145
+ self.adjustSize()
146
+
147
+ def _link(self, target: str) -> None:
148
+ if target.startswith("def:") and target[4:] in BY_KEY:
149
+ self.history.append((self.current, self.instance))
150
+ self._show(BY_KEY[target[4:]], None)
151
+
152
+ def _back(self) -> None:
153
+ if self.history:
154
+ self._show(*self.history.pop())
155
+
156
+ def _open_reference(self, key: str) -> None:
157
+ self.close()
158
+ show_reference(key, self.parentWidget())
159
+
160
+ def show_near(self, point: QPoint) -> None:
161
+ """Open below-right of ``point``, kept on the screen."""
162
+ self.adjustSize()
163
+ screen = QGuiApplication.screenAt(point) or QGuiApplication.primaryScreen()
164
+ area = screen.availableGeometry()
165
+ x = min(point.x() + 8, area.right() - self.width() - 8)
166
+ y = point.y() + 12
167
+ if y + self.height() > area.bottom():
168
+ y = max(area.top() + 8, point.y() - self.height() - 12)
169
+ self.move(max(area.left() + 8, x), y)
170
+ self.show()
171
+
172
+
173
+ class DefinitionsDialog(QDialog):
174
+ """The complete reference (Help ▸ Definitions)."""
175
+
176
+ def __init__(self, parent: QWidget | None = None) -> None:
177
+ super().__init__(parent)
178
+ self.setWindowTitle("Definitions")
179
+ self.resize(640, 760)
180
+ layout = QVBoxLayout(self)
181
+ self.browser = _Browser(self._link)
182
+ self.browser.setHtml(reference_html())
183
+ layout.addWidget(self.browser)
184
+
185
+ def _link(self, target: str) -> None:
186
+ if target.startswith("def:"):
187
+ self.browser.scrollToAnchor(target[4:])
188
+
189
+ def show_key(self, key: str | None) -> None:
190
+ if key:
191
+ self.browser.scrollToAnchor(key)
192
+
193
+
194
+ _dialog: DefinitionsDialog | None = None
195
+
196
+
197
+ def show_reference(key: str | None = None, parent: QWidget | None = None) -> DefinitionsDialog:
198
+ """Open (or raise) the Definitions dialog, scrolled to ``key``."""
199
+ global _dialog
200
+ if _dialog is None:
201
+ _dialog = DefinitionsDialog(parent.window() if parent is not None else None)
202
+ _dialog.show()
203
+ _dialog.raise_()
204
+ _dialog.activateWindow()
205
+ _dialog.show_key(key)
206
+ return _dialog
207
+
208
+
209
+ class _ClickFilter(QObject):
210
+ """Opens the pop-up when the widget it watches is clicked."""
211
+
212
+ def __init__(self, widget: QWidget, definition: Definition, instance) -> None:
213
+ super().__init__(widget)
214
+ self.widget, self.definition, self.instance = widget, definition, instance
215
+
216
+ def eventFilter(self, watched, event) -> bool: # noqa: N802
217
+ if event.type() == QEvent.MouseButtonRelease and event.button() == Qt.LeftButton:
218
+ popup = DefinitionPopup(self.definition, self.instance, self.widget)
219
+ popup.show_near(QCursor.pos())
220
+ self.widget.last_popup = popup # for tests
221
+ return True
222
+ return False
223
+
224
+
225
+ def attach_definition(widget: QWidget, key: str | None, instance: list[str] | None = None,
226
+ targets: list[QWidget] | None = None) -> Definition | None:
227
+ """Give ``widget`` the definition ``key``: tooltip on hover, pop-up on click.
228
+
229
+ ``targets`` are the child widgets that react (default: the widget
230
+ itself); selectable text labels are best left out, so their text can
231
+ still be selected.
232
+ """
233
+ definition = lookup(key)
234
+ if definition is None:
235
+ return None
236
+ tooltip = definition_html(definition, instance, compact=True)
237
+ for target in targets or [widget]:
238
+ target.setToolTip(tooltip)
239
+ target.setCursor(Qt.PointingHandCursor)
240
+ target.installEventFilter(_ClickFilter(widget, definition, instance))
241
+ widget.definition = definition
242
+ return definition
243
+
244
+
245
+ # ---------------------------------------------------------------------------
246
+ # Guides: References, Writing Exercise Packs
247
+ # ---------------------------------------------------------------------------
248
+ GUIDES = {"references": "References", "exercise-packs": "Writing Exercise Packs"}
249
+ _guides: dict[str, QDialog] = {}
250
+
251
+
252
+ def guide_markdown(name: str) -> str:
253
+ """The text of a guide (Markdown)."""
254
+ if name == "references":
255
+ from ... import references
256
+ text = references.markdown()
257
+ return text.split("-->", 1)[-1].lstrip()
258
+ from pathlib import Path
259
+ from ...learn import __file__ as learn
260
+ return (Path(learn).parent / f"{name}.md").read_text(encoding="utf-8")
261
+
262
+
263
+ def show_guide(name: str, parent: QWidget | None = None) -> QDialog:
264
+ """Open (or raise) a guide in a window of its own."""
265
+ from .markdown_view import markdown_html
266
+ dialog = _guides.get(name)
267
+ if dialog is None:
268
+ dialog = QDialog(parent.window() if parent is not None else None)
269
+ dialog.setWindowTitle(GUIDES.get(name, name))
270
+ dialog.resize(760, 760)
271
+ layout = QVBoxLayout(dialog)
272
+ layout.setContentsMargins(0, 0, 0, 0)
273
+ browser = QTextBrowser()
274
+ browser.setObjectName("guideBrowser")
275
+ browser.setOpenExternalLinks(True)
276
+ browser.setFrameShape(QFrame.NoFrame)
277
+ browser.setHtml(markdown_html(guide_markdown(name)))
278
+ layout.addWidget(browser)
279
+ dialog.browser = browser
280
+ _guides[name] = dialog
281
+ dialog.show()
282
+ dialog.raise_()
283
+ dialog.activateWindow()
284
+ return dialog