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,296 @@
|
|
|
1
|
+
"""An exercise as a worksheet: its question text with answer boxes in it.
|
|
2
|
+
|
|
3
|
+
An exercise is a folder with a ``question.md``. The question is ordinary
|
|
4
|
+
Markdown (with ``$…$`` maths); wherever the student should answer, the
|
|
5
|
+
author puts an *answer block*, a fenced code block marked ``answer``::
|
|
6
|
+
|
|
7
|
+
a. Give the start activities $T_I$.
|
|
8
|
+
|
|
9
|
+
```answer
|
|
10
|
+
type: set
|
|
11
|
+
compute: alpha.T_I
|
|
12
|
+
hint: Which activities does a trace begin with?
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The text between two answer blocks is the question for the second one, so
|
|
16
|
+
the sheet reads top to bottom like the paper version. Inside a block:
|
|
17
|
+
|
|
18
|
+
* ``key: value`` lines (keys are listed in :data:`KEYS`);
|
|
19
|
+
* lines that start with spaces continue the value above them (for a
|
|
20
|
+
``solution`` of several lines);
|
|
21
|
+
* ``- [x] option`` / ``- [ ] option`` lines are the options of a choice
|
|
22
|
+
(``[x]`` marks the right ones); ``- text`` lines are further accepted
|
|
23
|
+
answers of a ``text`` question.
|
|
24
|
+
|
|
25
|
+
A sheet may start with *front matter* between two ``---`` lines: settings of
|
|
26
|
+
the whole exercise (``points``, ``seed``, ``generate``, ``boxes``), read by
|
|
27
|
+
:mod:`.pack`.
|
|
28
|
+
|
|
29
|
+
The full reference, with every type and what each checks, is
|
|
30
|
+
``openprocess/learn/exercise-packs.md`` (*Help ▸ Writing Exercise Packs*).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import re
|
|
36
|
+
from dataclasses import dataclass, field
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
#: The kinds of answer box, and what each one is.
|
|
40
|
+
TYPES = {
|
|
41
|
+
"net": "draw or change a Petri net in the editor beside the question",
|
|
42
|
+
"footprint": "fill in a footprint matrix (→ ← ‖ #)",
|
|
43
|
+
"yesno": "answer yes or no",
|
|
44
|
+
"choice": "pick one option (or several, if more than one is right)",
|
|
45
|
+
"set": "write a set, e.g. {a, b} — or a set of sets or of pairs",
|
|
46
|
+
"trace": "write a firing sequence, e.g. ⟨register, send letter⟩",
|
|
47
|
+
"number": "write a number",
|
|
48
|
+
"text": "write a short answer",
|
|
49
|
+
"open": "write freely; compare with the model answer yourself",
|
|
50
|
+
"marking": "write a marking, e.g. [p1, p4^2]",
|
|
51
|
+
"markings": "write a set of markings, e.g. [p1], [p2, p3]",
|
|
52
|
+
"tuple": "write a net as (P, T, F, m0): four boxes",
|
|
53
|
+
"ts": "write a transition system: s0 -a-> s1 lines",
|
|
54
|
+
"matrix": "fill in a matrix with row and column headers",
|
|
55
|
+
"cut": "write a cut of the Inductive Miner, e.g. → {a} {b, c} {d}",
|
|
56
|
+
"log": "write a log in the course's notation, e.g. [<a, b>^2, <a, c>]",
|
|
57
|
+
"tree": "write a process tree, e.g. →(a, ×(b, c), d)",
|
|
58
|
+
"replay": "fill in produced, consumed, missing and remaining tokens per trace",
|
|
59
|
+
"alignment": "write an alignment as two rows of moves (≫ for no move)",
|
|
60
|
+
"ranking": "put the named models in order",
|
|
61
|
+
"predict": "predict a box's result before running it",
|
|
62
|
+
"workflow": "build a workflow in the Workflow tab",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#: Types whose editor is a text field with a notation (one line or several).
|
|
66
|
+
TEXT_TYPES = {"marking", "markings", "ts", "cut", "log", "tree", "alignment"}
|
|
67
|
+
|
|
68
|
+
#: Every key an answer block may use.
|
|
69
|
+
KEYS = {"type", "id", "answer", "compute", "hint", "solution", "start", "net", "log", "of",
|
|
70
|
+
"sound", "fits", "ends", "tolerance", "title", "labels", "pattern",
|
|
71
|
+
# the new blocks
|
|
72
|
+
"points", "pairs", "exact", "unseen", "trace", "alignment", "over", "by", "order",
|
|
73
|
+
"box", "value", "as", "settings", "needs", "result", "rows", "columns", "chart", "ts",
|
|
74
|
+
"material"}
|
|
75
|
+
|
|
76
|
+
#: Keys the front matter of a sheet may use.
|
|
77
|
+
FRONT_KEYS = {"points", "seed", "generate", "boxes", "title", "time"}
|
|
78
|
+
|
|
79
|
+
_FENCE = re.compile(r"^(```+|~~~+)\s*answer\s*$", re.I)
|
|
80
|
+
_OPTION = re.compile(r"^-\s*\[( |x|X)\]\s*(.+)$")
|
|
81
|
+
_ITEM = re.compile(r"^-\s+(.+)$")
|
|
82
|
+
_KEY = re.compile(r"^([A-Za-z_]+)\s*:\s?(.*)$")
|
|
83
|
+
#: "start: net.pnml # a comment" -- at least two spaces before the #, because
|
|
84
|
+
#: "a # b" is the α-algorithm's choice relation, often written in an answer.
|
|
85
|
+
_COMMENT = re.compile(r"\s{2,}#\s.*$")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class SheetError(ValueError):
|
|
89
|
+
"""A mistake in an answer block, with the line it is on."""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class Option:
|
|
94
|
+
text: str
|
|
95
|
+
correct: bool
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class Task:
|
|
100
|
+
"""One answer box."""
|
|
101
|
+
|
|
102
|
+
type: str
|
|
103
|
+
id: str
|
|
104
|
+
#: The question text just above the box (Markdown).
|
|
105
|
+
prompt: str = ""
|
|
106
|
+
#: Everything the block says, by key (values as written).
|
|
107
|
+
settings: dict[str, str] = field(default_factory=dict)
|
|
108
|
+
options: list[Option] = field(default_factory=list)
|
|
109
|
+
#: ``- text`` lines: more accepted answers (``text``).
|
|
110
|
+
alternatives: list[str] = field(default_factory=list)
|
|
111
|
+
#: The line in question.md where the block starts (for error messages).
|
|
112
|
+
line: int = 0
|
|
113
|
+
|
|
114
|
+
def get(self, key: str, default: str | None = None) -> str | None:
|
|
115
|
+
return self.settings.get(key, default)
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def hint(self) -> str | None:
|
|
119
|
+
return self.settings.get("hint")
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def solution(self) -> str | None:
|
|
123
|
+
return self.settings.get("solution")
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def points(self) -> float:
|
|
127
|
+
"""What a right answer earns (``points:``; 1 when the block says nothing)."""
|
|
128
|
+
try:
|
|
129
|
+
return float(self.settings.get("points", "") or 1)
|
|
130
|
+
except ValueError:
|
|
131
|
+
return 1.0
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def checkable(self) -> bool:
|
|
135
|
+
"""Can the app say whether the answer is right?"""
|
|
136
|
+
if self.type == "open":
|
|
137
|
+
return False
|
|
138
|
+
if self.type == "choice":
|
|
139
|
+
return any(o.correct for o in self.options) or "compute" in self.settings
|
|
140
|
+
if self.type in ("set", "number", "yesno", "marking", "markings", "ts", "matrix", "cut",
|
|
141
|
+
"log", "tree", "replay"):
|
|
142
|
+
return "answer" in self.settings or "compute" in self.settings
|
|
143
|
+
if self.type == "text":
|
|
144
|
+
return "answer" in self.settings or bool(self.alternatives) or \
|
|
145
|
+
"pattern" in self.settings or "compute" in self.settings
|
|
146
|
+
if self.type == "net":
|
|
147
|
+
return any(k in self.settings for k in ("answer", "sound", "fits", "compute"))
|
|
148
|
+
if self.type == "ranking":
|
|
149
|
+
return "over" in self.settings
|
|
150
|
+
if self.type == "predict":
|
|
151
|
+
return "box" in self.settings
|
|
152
|
+
if self.type == "workflow":
|
|
153
|
+
return "needs" in self.settings or "result" in self.settings
|
|
154
|
+
return True # footprint, trace, tuple, alignment
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class Sheet:
|
|
159
|
+
"""The question as blocks: Markdown strings and :class:`Task` objects, in order."""
|
|
160
|
+
|
|
161
|
+
title: str
|
|
162
|
+
blocks: list = field(default_factory=list)
|
|
163
|
+
#: The front matter: settings of the whole exercise.
|
|
164
|
+
settings: dict[str, str] = field(default_factory=dict)
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def tasks(self) -> list[Task]:
|
|
168
|
+
return [block for block in self.blocks if isinstance(block, Task)]
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def points(self) -> float:
|
|
172
|
+
return sum(t.points for t in self.tasks)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _parse_block(lines: list[str], start_line: int, index: int, prompt: str) -> Task:
|
|
176
|
+
settings: dict[str, str] = {}
|
|
177
|
+
options: list[Option] = []
|
|
178
|
+
alternatives: list[str] = []
|
|
179
|
+
current: str | None = None
|
|
180
|
+
for offset, raw in enumerate(lines):
|
|
181
|
+
number = start_line + offset + 1
|
|
182
|
+
if not raw.strip():
|
|
183
|
+
if current is not None:
|
|
184
|
+
settings[current] += "\n"
|
|
185
|
+
continue
|
|
186
|
+
if raw[0] in " \t" and current is not None:
|
|
187
|
+
settings[current] += ("\n" if settings[current] else "") + raw.strip()
|
|
188
|
+
continue
|
|
189
|
+
line = raw.strip()
|
|
190
|
+
option = _OPTION.match(line)
|
|
191
|
+
if option:
|
|
192
|
+
options.append(Option(option.group(2).strip(), option.group(1) != " "))
|
|
193
|
+
current = None
|
|
194
|
+
continue
|
|
195
|
+
item = _ITEM.match(line)
|
|
196
|
+
if item:
|
|
197
|
+
alternatives.append(item.group(1).strip())
|
|
198
|
+
current = None
|
|
199
|
+
continue
|
|
200
|
+
key = _KEY.match(line)
|
|
201
|
+
if not key:
|
|
202
|
+
raise SheetError(f"line {number}: expected “key: value”, found “{line}”")
|
|
203
|
+
name = key.group(1).lower()
|
|
204
|
+
if name not in KEYS:
|
|
205
|
+
raise SheetError(f"line {number}: unknown key “{name}” (known: "
|
|
206
|
+
+ ", ".join(sorted(KEYS)) + ")")
|
|
207
|
+
# "start: net.pnml # a comment": the comment goes.
|
|
208
|
+
settings[name] = _COMMENT.sub("", key.group(2)).strip()
|
|
209
|
+
current = name
|
|
210
|
+
settings = {k: v.strip() for k, v in settings.items()}
|
|
211
|
+
kind = settings.get("type", "").lower()
|
|
212
|
+
if not kind:
|
|
213
|
+
kind = "choice" if options else "text" if alternatives else ""
|
|
214
|
+
if kind not in TYPES:
|
|
215
|
+
raise SheetError(f"line {start_line}: answer block needs a type, one of "
|
|
216
|
+
+ ", ".join(TYPES) + (f" (found “{kind}”)" if kind else ""))
|
|
217
|
+
if kind == "choice" and not options:
|
|
218
|
+
raise SheetError(f"line {start_line}: a choice needs options, written as "
|
|
219
|
+
"“- [x] right” and “- [ ] wrong”")
|
|
220
|
+
task_id = settings.get("id") or f"q{index}"
|
|
221
|
+
return Task(kind, task_id, prompt.strip(), settings, options, alternatives, start_line)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def parse_front_matter(text: str) -> tuple[dict[str, str], str]:
|
|
225
|
+
"""``---`` lines at the top with ``key: value`` settings, and the rest."""
|
|
226
|
+
lines = text.splitlines()
|
|
227
|
+
if not lines or lines[0].strip() != "---":
|
|
228
|
+
return {}, text
|
|
229
|
+
settings: dict[str, str] = {}
|
|
230
|
+
for number, line in enumerate(lines[1:], 2):
|
|
231
|
+
if line.strip() == "---":
|
|
232
|
+
return settings, "\n".join(lines[number:])
|
|
233
|
+
if not line.strip():
|
|
234
|
+
continue
|
|
235
|
+
key = _KEY.match(line.strip())
|
|
236
|
+
if not key:
|
|
237
|
+
raise SheetError(f"line {number}: expected “key: value” in the front matter")
|
|
238
|
+
settings[key.group(1).lower()] = key.group(2).strip()
|
|
239
|
+
raise SheetError("the front matter is not closed (add a line ---)")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def parse_sheet(text: str, title: str = "") -> Sheet:
|
|
243
|
+
"""Split question.md into Markdown and answer blocks."""
|
|
244
|
+
settings, text = parse_front_matter(text)
|
|
245
|
+
lines = text.splitlines()
|
|
246
|
+
blocks: list = []
|
|
247
|
+
markdown: list[str] = []
|
|
248
|
+
index = 0
|
|
249
|
+
position = 0
|
|
250
|
+
heading = title
|
|
251
|
+
while position < len(lines):
|
|
252
|
+
line = lines[position]
|
|
253
|
+
fence = _FENCE.match(line.strip())
|
|
254
|
+
if fence:
|
|
255
|
+
end = position + 1
|
|
256
|
+
while end < len(lines) and not lines[end].strip().startswith(fence.group(1)):
|
|
257
|
+
end += 1
|
|
258
|
+
if end >= len(lines):
|
|
259
|
+
raise SheetError(f"line {position + 1}: the answer block is not closed "
|
|
260
|
+
f"(add a line {fence.group(1)})")
|
|
261
|
+
index += 1
|
|
262
|
+
# The prompt is the text since the previous block (or heading).
|
|
263
|
+
prompt = "\n".join(markdown)
|
|
264
|
+
if prompt.strip():
|
|
265
|
+
blocks.append(prompt)
|
|
266
|
+
task = _parse_block(lines[position + 1:end], position + 1, index,
|
|
267
|
+
_last_paragraphs(prompt))
|
|
268
|
+
blocks.append(task)
|
|
269
|
+
markdown = []
|
|
270
|
+
position = end + 1
|
|
271
|
+
continue
|
|
272
|
+
if not heading and line.startswith("# "):
|
|
273
|
+
heading = line[2:].strip()
|
|
274
|
+
position += 1
|
|
275
|
+
continue
|
|
276
|
+
markdown.append(line)
|
|
277
|
+
position += 1
|
|
278
|
+
if "\n".join(markdown).strip():
|
|
279
|
+
blocks.append("\n".join(markdown))
|
|
280
|
+
ids = [t.id for t in blocks if isinstance(t, Task)]
|
|
281
|
+
duplicates = sorted({i for i in ids if ids.count(i) > 1})
|
|
282
|
+
if duplicates:
|
|
283
|
+
raise SheetError("two answer blocks have the same id: " + ", ".join(duplicates))
|
|
284
|
+
sheet = Sheet(heading or settings.get("title") or "Exercise", blocks, settings)
|
|
285
|
+
return sheet
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _last_paragraphs(markdown: str) -> str:
|
|
289
|
+
"""The part of the text that asks the question (for listings and the CLI)."""
|
|
290
|
+
paragraphs = [p.strip() for p in re.split(r"\n\s*\n", markdown) if p.strip()]
|
|
291
|
+
return paragraphs[-1] if paragraphs else ""
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def read_sheet(path: str | Path) -> Sheet:
|
|
295
|
+
path = Path(path)
|
|
296
|
+
return parse_sheet(path.read_text(encoding="utf-8", errors="replace"))
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Process mining: event logs, discovery, conformance checking.
|
|
2
|
+
|
|
3
|
+
This package is the "ProM half" of the application. Like the CPN engine it
|
|
4
|
+
is pure standard-library Python: every algorithm is implemented here, with
|
|
5
|
+
the textbook definitions in the docstrings, so the code doubles as a study
|
|
6
|
+
companion for the course.
|
|
7
|
+
|
|
8
|
+
Quick start::
|
|
9
|
+
|
|
10
|
+
from openprocess.mining import read_xes, inductive_miner, align_log, precision
|
|
11
|
+
|
|
12
|
+
log = read_xes("PlaneBoarding.xes")
|
|
13
|
+
simple = log.simple_log() # multiset of activity sequences
|
|
14
|
+
model = inductive_miner(simple).net # a sound WF-net
|
|
15
|
+
fitness = align_log(model, simple).average_fitness
|
|
16
|
+
print(fitness, precision(model, simple))
|
|
17
|
+
|
|
18
|
+
Modules
|
|
19
|
+
-------
|
|
20
|
+
``log``, ``xes``, ``csv_import``, ``stats``
|
|
21
|
+
Event logs: data model, file formats, descriptive statistics.
|
|
22
|
+
``dfg``, ``footprint``
|
|
23
|
+
Directly-follows graphs and the ordering relations / footprint matrix.
|
|
24
|
+
``petrinet``, ``pnml``, ``analysis``, ``processtree``
|
|
25
|
+
Labelled P/T nets, PNML, state spaces, properties, WF-net soundness,
|
|
26
|
+
process trees.
|
|
27
|
+
``discovery``
|
|
28
|
+
α-algorithm, Heuristics Miner (dependency graph), Inductive Miner (IM/IMf),
|
|
29
|
+
state-based regions.
|
|
30
|
+
``transition_system``, ``regions``
|
|
31
|
+
Transition systems from logs, regions, elementary checks, net synthesis.
|
|
32
|
+
``compare_nets``
|
|
33
|
+
Do two nets allow the same complete traces?
|
|
34
|
+
``conformance``
|
|
35
|
+
Token-based replay, alignments, precision, generalisation, simplicity.
|
|
36
|
+
``layout``
|
|
37
|
+
Automatic layered graph layout for drawing discovered models.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from .analysis import (analyse, check_soundness, check_workflow_net, coverability_graph,
|
|
41
|
+
reachability_graph)
|
|
42
|
+
from .conformance.alignments import align_log, align_trace
|
|
43
|
+
from .conformance.quality import generalisation, precision, simplicity
|
|
44
|
+
from .conformance.token_replay import token_replay
|
|
45
|
+
from .compare_nets import compare_nets
|
|
46
|
+
from .csv_import import read_csv
|
|
47
|
+
from .dfg import discover_dfg
|
|
48
|
+
from .discovery.alpha import alpha_miner
|
|
49
|
+
from .discovery.heuristics import heuristics_miner
|
|
50
|
+
from .discovery.inductive import inductive_miner
|
|
51
|
+
from .discovery.state_regions import region_miner
|
|
52
|
+
from .footprint import compare_footprints, footprint_of_log, footprint_of_net
|
|
53
|
+
from .log import (BY_NAME, BY_NAME_COMPLETE, BY_NAME_LIFECYCLE, Classifier, Event, EventLog,
|
|
54
|
+
Trace, format_simple_log, parse_simple_log)
|
|
55
|
+
from .petrinet import Marking, PetriNet
|
|
56
|
+
from .pnml import read_pnml, write_pnml
|
|
57
|
+
from .processtree import Operator, ProcessTree, to_petri_net
|
|
58
|
+
from .regions import analyse_regions, check_region, synthesise
|
|
59
|
+
from .transition_system import (TransitionSystem, parse_transition_system,
|
|
60
|
+
transition_system_from_log)
|
|
61
|
+
from .stats import summarise
|
|
62
|
+
from .xes import read_xes, write_xes
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
"BY_NAME", "BY_NAME_COMPLETE", "BY_NAME_LIFECYCLE", "Classifier", "Event", "EventLog",
|
|
66
|
+
"Marking", "Operator", "PetriNet", "ProcessTree", "Trace", "TransitionSystem",
|
|
67
|
+
"align_log", "align_trace", "alpha_miner", "analyse", "analyse_regions", "check_region",
|
|
68
|
+
"check_soundness", "check_workflow_net", "compare_footprints", "compare_nets", "coverability_graph", "discover_dfg",
|
|
69
|
+
"footprint_of_log", "footprint_of_net", "format_simple_log", "generalisation",
|
|
70
|
+
"heuristics_miner", "inductive_miner", "parse_simple_log", "parse_transition_system", "precision",
|
|
71
|
+
"reachability_graph", "read_csv", "read_pnml", "read_xes", "region_miner", "simplicity", "summarise",
|
|
72
|
+
"synthesise", "to_petri_net", "token_replay", "transition_system_from_log", "write_pnml", "write_xes",
|
|
73
|
+
]
|