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,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,4 @@
1
+ """The old name of :mod:`openprocess.learn` (``cpnpy.teaching``, CPNpy 0.5): kept so imports keep working."""
2
+
3
+ from ..learn import * # noqa: F401,F403
4
+ from ..learn import __all__ # noqa: F401
@@ -0,0 +1,4 @@
1
+ """Moved to :mod:`openprocess.learn.answers`."""
2
+
3
+ from ..learn.answers import * # noqa: F401,F403
4
+ from ..learn.answers import AnswerSyntaxError, Sequence, as_set, name, normalised, number, parse, readings, show, show_set, trace # noqa: F401,E501
@@ -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
@@ -0,0 +1,4 @@
1
+ """Moved to :mod:`openprocess.learn.pack`."""
2
+
3
+ from ..learn.pack import * # noqa: F401,F403
4
+ from ..learn.pack import Exercise, ExerciseFiles, Pack, ProgressSummary, exercise_files, find_exercises, load_pack, natural_key, pack_root, split_parts # noqa: F401,E501
@@ -0,0 +1,4 @@
1
+ """Moved to :mod:`openprocess.learn.sheet`."""
2
+
3
+ from ..learn.sheet import * # noqa: F401,F403
4
+ from ..learn.sheet import KEYS, TYPES, Option, Sheet, SheetError, Task, parse_sheet, read_sheet # noqa: F401,E501