openprocess 0.11.3__tar.gz → 0.12.1__tar.gz

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 (240) hide show
  1. {openprocess-0.11.3/openprocess.egg-info → openprocess-0.12.1}/PKG-INFO +64 -21
  2. {openprocess-0.11.3 → openprocess-0.12.1}/README.md +63 -20
  3. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/__init__.py +1 -1
  4. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/cli.py +21 -4
  5. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/box.py +85 -29
  6. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/boxes/check.py +40 -14
  7. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/boxes/filter.py +56 -7
  8. openprocess-0.12.1/openprocess/flow/boxes/measure.py +265 -0
  9. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/boxes/predict.py +14 -8
  10. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/boxes/science.py +102 -9
  11. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/convert.py +7 -2
  12. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/figures.py +14 -1
  13. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/library.py +3 -3
  14. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/record.py +256 -18
  15. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/report.py +2 -2
  16. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/reproducibility.py +9 -1
  17. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/runner.py +75 -7
  18. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/script.py +1 -1
  19. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/snapshots.py +9 -6
  20. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/workflow.py +25 -10
  21. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/flow/canvas.py +4 -1
  22. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/flow/page.py +110 -38
  23. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/flow/picker.py +1 -1
  24. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/flow/summary.py +3 -3
  25. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/flow/templates.py +4 -3
  26. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/flow/viewers.py +37 -12
  27. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/learn/mode.py +1 -0
  28. openprocess-0.12.1/openprocess/gui/studio/alignment_view.py +235 -0
  29. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/app.py +117 -8
  30. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/dotted_chart.py +115 -166
  31. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/graph_view.py +6 -0
  32. openprocess-0.12.1/openprocess/gui/studio/loading.py +166 -0
  33. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/log_page.py +83 -28
  34. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/model_page.py +14 -48
  35. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/petri_page.py +8 -9
  36. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/style.py +3 -6
  37. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/tool_icons.py +36 -0
  38. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/analysis.py +22 -0
  39. openprocess-0.12.1/openprocess/mining/cases.py +86 -0
  40. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/columns.py +78 -3
  41. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/compare_nets.py +58 -0
  42. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/conformance/quality.py +54 -43
  43. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/conformance/token_replay.py +91 -5
  44. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/definitions.py +11 -1
  45. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/discovery/alpha.py +2 -2
  46. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/discovery/heuristics.py +21 -13
  47. openprocess-0.12.1/openprocess/mining/dotted.py +455 -0
  48. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/filtering.py +32 -22
  49. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/layout.py +43 -13
  50. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/log.py +8 -1
  51. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/stats.py +50 -0
  52. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/xes.py +181 -61
  53. openprocess-0.12.1/openprocess/palette.py +14 -0
  54. {openprocess-0.11.3 → openprocess-0.12.1/openprocess.egg-info}/PKG-INFO +64 -21
  55. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess.egg-info/SOURCES.txt +10 -0
  56. openprocess-0.12.1/tests/test_docs.py +16 -0
  57. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_flow.py +1 -1
  58. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_flow_gui.py +66 -0
  59. openprocess-0.12.1/tests/test_framework_contract.py +311 -0
  60. openprocess-0.12.1/tests/test_large_logs.py +204 -0
  61. openprocess-0.12.1/tests/test_measure.py +180 -0
  62. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_mining.py +19 -0
  63. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_studio_gui.py +96 -0
  64. {openprocess-0.11.3 → openprocess-0.12.1}/LICENSE +0 -0
  65. {openprocess-0.11.3 → openprocess-0.12.1}/cpnpy/__init__.py +0 -0
  66. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/analysis/__init__.py +0 -0
  67. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/analysis/state_space.py +0 -0
  68. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/analysis/state_space_process.py +0 -0
  69. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/citation.py +0 -0
  70. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
  71. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
  72. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
  73. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
  74. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
  75. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
  76. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
  77. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
  78. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
  79. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
  80. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
  81. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
  82. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
  83. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
  84. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
  85. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
  86. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
  87. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
  88. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
  89. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/exercises/pack.md +0 -0
  90. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/__init__.py +0 -0
  91. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/boxes/__init__.py +0 -0
  92. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/boxes/compare.py +0 -0
  93. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/boxes/cpn.py +0 -0
  94. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/boxes/discover.py +0 -0
  95. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/boxes/input.py +0 -0
  96. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/boxes/output.py +0 -0
  97. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/boxes/sweeps.py +0 -0
  98. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/datasets.py +0 -0
  99. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/explain.py +0 -0
  100. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/history.py +0 -0
  101. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/provenance.py +0 -0
  102. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/sweep.py +0 -0
  103. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/flow/types.py +0 -0
  104. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/__init__.py +0 -0
  105. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/app.py +0 -0
  106. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/arc_editing.py +0 -0
  107. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/canvas.py +0 -0
  108. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/flow/__init__.py +0 -0
  109. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/flow/runs.py +0 -0
  110. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/flow/stepper.py +0 -0
  111. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/items.py +0 -0
  112. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/learn/answer_boxes.py +0 -0
  113. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/learn/concealment.py +0 -0
  114. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/panning.py +0 -0
  115. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/resources/openprocess-icon.png +0 -0
  116. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/__init__.py +0 -0
  117. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/__main__.py +0 -0
  118. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/charts.py +0 -0
  119. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/cite.py +0 -0
  120. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/command_palette.py +0 -0
  121. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/compare_page.py +0 -0
  122. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/connections.py +0 -0
  123. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/cpn_page.py +0 -0
  124. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/definition_view.py +0 -0
  125. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/derivation_view.py +0 -0
  126. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/documents.py +0 -0
  127. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/file_dialogs.py +0 -0
  128. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/filter_dialog.py +0 -0
  129. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/graph_builders.py +0 -0
  130. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/instances.py +0 -0
  131. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/log_editor.py +0 -0
  132. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/markdown_view.py +0 -0
  133. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/mathtext.py +0 -0
  134. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/ml_highlighter.py +0 -0
  135. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/motion.py +0 -0
  136. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/net_comparison.py +0 -0
  137. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/notes_overlay.py +0 -0
  138. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/provenance.py +0 -0
  139. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/regions_view.py +0 -0
  140. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/sidebar.py +0 -0
  141. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/spaces.py +0 -0
  142. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/updates.py +0 -0
  143. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/widgets.py +0 -0
  144. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/workers.py +0 -0
  145. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/studio/workspace.py +0 -0
  146. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/theme.py +0 -0
  147. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/gui/tidy.py +0 -0
  148. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/io/__init__.py +0 -0
  149. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/io/cpn_reader.py +0 -0
  150. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/io/cpn_writer.py +0 -0
  151. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/learn/__init__.py +0 -0
  152. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/learn/answers.py +0 -0
  153. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/learn/checks.py +0 -0
  154. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/learn/computed.py +0 -0
  155. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/learn/context.py +0 -0
  156. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/learn/exam.py +0 -0
  157. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/learn/exercise-packs.md +0 -0
  158. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/learn/importer.py +0 -0
  159. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/learn/notation.py +0 -0
  160. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/learn/pack.py +0 -0
  161. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/learn/sheet.py +0 -0
  162. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/__init__.py +0 -0
  163. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/conformance/__init__.py +0 -0
  164. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/conformance/alignments.py +0 -0
  165. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/csv_import.py +0 -0
  166. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/dfg.py +0 -0
  167. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/discovery/__init__.py +0 -0
  168. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/discovery/inductive.py +0 -0
  169. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/discovery/state_regions.py +0 -0
  170. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/footprint.py +0 -0
  171. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/invariants.py +0 -0
  172. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/ocel.py +0 -0
  173. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/petrinet.py +0 -0
  174. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/playout.py +0 -0
  175. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/pm4py_bridge.py +0 -0
  176. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/pnml.py +0 -0
  177. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/processtree.py +0 -0
  178. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/regions.py +0 -0
  179. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/structure.py +0 -0
  180. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/mining/transition_system.py +0 -0
  181. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/ml/__init__.py +0 -0
  182. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/ml/ast_nodes.py +0 -0
  183. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/ml/builtins.py +0 -0
  184. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/ml/colorsets.py +0 -0
  185. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/ml/errors.py +0 -0
  186. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/ml/evaluator.py +0 -0
  187. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/ml/lexer.py +0 -0
  188. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/ml/multiset.py +0 -0
  189. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/ml/parser.py +0 -0
  190. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/ml/values.py +0 -0
  191. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/model/__init__.py +0 -0
  192. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/model/declarations.py +0 -0
  193. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/model/examples.py +0 -0
  194. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/model/net.py +0 -0
  195. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/model/plain.py +0 -0
  196. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/packages.py +0 -0
  197. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/references.py +0 -0
  198. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/sim/__init__.py +0 -0
  199. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/sim/binding.py +0 -0
  200. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/sim/export.py +0 -0
  201. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/sim/simulator.py +0 -0
  202. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/teaching/__init__.py +0 -0
  203. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/teaching/answers.py +0 -0
  204. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/teaching/checks.py +0 -0
  205. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/teaching/pack.py +0 -0
  206. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess/teaching/sheet.py +0 -0
  207. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess.egg-info/dependency_links.txt +0 -0
  208. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess.egg-info/entry_points.txt +0 -0
  209. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess.egg-info/requires.txt +0 -0
  210. {openprocess-0.11.3 → openprocess-0.12.1}/openprocess.egg-info/top_level.txt +0 -0
  211. {openprocess-0.11.3 → openprocess-0.12.1}/pyproject.toml +0 -0
  212. {openprocess-0.11.3 → openprocess-0.12.1}/setup.cfg +0 -0
  213. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_arc_editing.py +0 -0
  214. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_citation.py +0 -0
  215. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_colorsets.py +0 -0
  216. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_columns.py +0 -0
  217. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_cpn_course_model.py +0 -0
  218. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_definitions.py +0 -0
  219. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_exercises_gui.py +0 -0
  220. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_hierarchy.py +0 -0
  221. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_history.py +0 -0
  222. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_io.py +0 -0
  223. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_learn.py +0 -0
  224. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_learn_gui.py +0 -0
  225. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_log_editing.py +0 -0
  226. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_ml.py +0 -0
  227. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_multiset.py +0 -0
  228. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_ocel.py +0 -0
  229. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_packages.py +0 -0
  230. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_regions.py +0 -0
  231. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_rename.py +0 -0
  232. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_report.py +0 -0
  233. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_simulation.py +0 -0
  234. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_snapshots.py +0 -0
  235. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_state_space.py +0 -0
  236. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_teaching.py +0 -0
  237. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_tidy.py +0 -0
  238. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_updates.py +0 -0
  239. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_wf_structure.py +0 -0
  240. {openprocess-0.11.3 → openprocess-0.12.1}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openprocess
3
- Version: 0.11.3
3
+ Version: 0.12.1
4
4
  Summary: Process mining, Petri nets and workflows in one open app: every algorithm visible, every result reproducible, every step teachable
5
5
  Author: Sam Struthers
6
6
  License: MIT
@@ -766,9 +766,11 @@ report = check_soundness(net)
766
766
  print(report.sound, report.findings)
767
767
  ```
768
768
 
769
- [**docs/how-it-works.md**](docs/how-it-works.md) covers the architecture, the
770
- CPN ML subset, binding search, timed nets, the state space, the file format
771
- and the project layout. [**docs/design.md**](docs/design.md) is the *why*:
769
+ [**docs/architecture.md**](docs/architecture.md) maps the code: its four
770
+ layers, which package imports which (drawn from the code, and checked), how a
771
+ workflow runs, and every module. [**docs/how-it-works.md**](docs/how-it-works.md)
772
+ covers the CPN ML subset, binding search, timed nets, the state space and
773
+ the file format. [**docs/design.md**](docs/design.md) is the *why*:
772
774
  the problem, the claim, the three ideas and the decisions that follow from
773
775
  them. `tests/cross_check_inductive_miner.py` compares the built-in Inductive
774
776
  Miner with PM4Py's on hundreds of logs and reports where they differ.
@@ -791,8 +793,8 @@ extra) and *Predict the next activity*. Or start empty.
791
793
  and Save. The sidebar beside it lists the folder's analyses and, folded
792
794
  under them, its logs.
793
795
  - **+ Add box** (or a double-click on the canvas) opens every box, one
794
- section per group (Input, Filter, Discover, Check, Compare, Output, then
795
- Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
796
+ section per group (Input, Filter, Discover, Check, Measure, Compare, Output,
797
+ then Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
796
798
  scrolls. Under *Input*, **the folder's own files**: one click adds the box
797
799
  that reads the file, already set. Type to search them all: "alpha" finds
798
800
  the α-algorithm, "boarding" the log; Enter adds the first match.
@@ -853,8 +855,29 @@ extra) and *Predict the next activity*. Or start empty.
853
855
  metadata; a CSV gets a `.provenance.json` beside it.
854
856
  - **⌘Z and ⇧⌘Z** undo and redo on the canvas: boxes, connections, moves,
855
857
  settings, groups.
856
- - **The side panel** resizes by dragging the gap beside it; double-click the
857
- gap, or *View ▸ Reset Workflow Layout*, for its default width.
858
+ - **The side panel** resizes by dragging the gap beside it, never narrower
859
+ than what it shows; double-click the gap, or *View ▸ Reset Workflow Layout*,
860
+ for its default size. Its dock button (or *View ▸ Workflow Panel Under the
861
+ Canvas*) moves it under the canvas, where a wide table or the code has the
862
+ window's width; the app remembers where you put it.
863
+ - **Alignments** are drawn as ProM draws them: the log on the model (per
864
+ transition, ✓ synchronous moves and » model moves, its colour from green,
865
+ always matched, to purple, often skipped; *n* » log moves at the activity),
866
+ and every variant's alignment as a row of chevrons: green synchronous,
867
+ yellow log move, purple model move, black τ. On a large log, the box's
868
+ *variants* setting aligns the most frequent ones and says what share of the
869
+ cases they cover.
870
+ - **Measure** boxes answer the questions an audit asks, for every case: *Time
871
+ between* two activities (throughput), *Order rule* (does the payment block
872
+ go before the goods receipt?), *Segregation of duties* (who both recorded
873
+ and cleared an invoice?) and *Compliance by attribute* (per vendor: cases,
874
+ share that fits, value), with *Case attribute* to keep one category and
875
+ *Bubble chart* to draw it. [examples/bpi2019](examples/bpi2019/README.md)
876
+ uses them on the BPI Challenge 2019. *Dotted chart* saves a dotted chart
877
+ as a figure (the same dots as the log page's tab), and *Who does what*
878
+ counts each activity per resource;
879
+ [examples/energy_contracts](examples/energy_contracts/README.md) uses them
880
+ with the course's miners and checks on the 2AMI10 exam log.
858
881
  - **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
859
882
  draws what flows in, OpenProcess and its boxes, what flows out, and the
860
883
  optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
@@ -896,7 +919,7 @@ save(wf, "orders.cpnflow", run)
896
919
  ```bash
897
920
  openprocess run orders.cpnflow --check # re-run; fails if any result differs from the record
898
921
  openprocess run orders.cpnflow --sweep noise=0..0.5 step 0.1
899
- openprocess boxes # the 47 boxes, with a folder's boxes/
922
+ openprocess boxes # every box (listed in docs/boxes.md), with a folder's boxes/
900
923
  openprocess datasets list # the BPI Challenge, Sepsis, ... logs by name
901
924
  ```
902
925
 
@@ -912,11 +935,22 @@ def last_two(log: EventLog, representation: str = "multiset") -> TransitionSyste
912
935
  ```
913
936
 
914
937
  `pip install -e ".[science]"` adds the pandas, NumPy, SciPy and matplotlib
915
- boxes (describe, bootstrap intervals, tests, plots). Large logs are read
916
- into columns rather than objects, so a million events fit in memory.
917
- [**docs/workflows.md**](docs/workflows.md) has the whole framework: writing
918
- a box, the types, the *How* tab, sweeps, the prediction pipeline, the
919
- record and running in CI.
938
+ boxes (describe, bootstrap intervals, tests, plots).
939
+
940
+ **Large logs.** A log above 20 MB is read into columns rather than objects,
941
+ and everything that looks at every event reads the columns: the BPI
942
+ Challenge 2019 log (251,734 cases, 1.6 million events, 728 MB) opens in
943
+ about 12 seconds and 250 MB, every tab of its log page in a few seconds, and
944
+ the whole [BPI 2019 analysis](examples/bpi2019/README.md), from reading the
945
+ file to the last chart, runs in about two minutes.
946
+
947
+ [**docs/workflows.md**](docs/workflows.md) is the tutorial: writing a box,
948
+ the types, the *How* tab, sweeps, the prediction pipeline, the record and
949
+ running in CI. [**docs/framework.md**](docs/framework.md) is the
950
+ specification: what a box, a workflow and a run are, what a fingerprint
951
+ covers and what a reproduced result promises. [**docs/architecture.md**](docs/architecture.md)
952
+ maps the code, drawn from its imports, and [**docs/boxes.md**](docs/boxes.md)
953
+ lists every box.
920
954
 
921
955
  **Building the apps.** `packaging/` turns OpenProcess into a standalone app with
922
956
  PyInstaller. In the environment:
@@ -939,11 +973,20 @@ workflow* on the Actions tab.
939
973
  [CHANGELOG.md](CHANGELOG.md) (`## 0.4.0`, then a few bullet points). Write
940
974
  it for the people using the app: it becomes the text of the release on
941
975
  GitHub, and the app shows it when it offers the update.
942
- 2. Set `__version__` in `openprocess/__init__.py` to the same version and merge
943
- both into `main`. It is the only place the version is written:
944
- `pyproject.toml`, the download names and the app's update check all read
945
- it from there.
946
- 3. Tag that commit with the same version and push the tag:
976
+ 2. Bring the documentation up to date with the code:
977
+ - run `python docs/make_docs.py`, which rewrites the code map in
978
+ [docs/architecture.md](docs/architecture.md) and the box catalogue in
979
+ [docs/boxes.md](docs/boxes.md) (the test suite, and so the release
980
+ build, fails while they are out of date, and when a package imports
981
+ across its layer);
982
+ - read [docs/framework.md](docs/framework.md) against what changed in
983
+ `openprocess/flow` (a test fails when a public name is not described
984
+ there, but only you can tell whether a description is still right).
985
+ 3. Set `__version__` in `openprocess/__init__.py` and `version` and
986
+ `date-released` in `CITATION.cff` to the same version, and merge them into
987
+ `main`. `pyproject.toml`, the download names and the app's update check
988
+ all read the version from `__version__`.
989
+ 4. Tag that commit with the same version and push the tag:
947
990
 
948
991
  ```bash
949
992
  git pull
@@ -954,8 +997,8 @@ workflow* on the Actions tab.
954
997
  About five minutes later the
955
998
  [Releases page](https://github.com/s4mstruthers/openprocess/releases) has the
956
999
  macOS (Apple silicon and Intel), Windows and Linux apps. The build refuses a
957
- tag that does not match `__version__`, or a version with no section in
958
- `CHANGELOG.md`.
1000
+ tag that does not match `__version__`, a version with no section in
1001
+ `CHANGELOG.md`, and code whose tests fail, stale documentation included.
959
1002
 
960
1003
  **Updates.** Each time it opens, the app checks the latest release on GitHub
961
1004
  (*Settings* turns that off). If there is a newer version, a slim bar at the
@@ -727,9 +727,11 @@ report = check_soundness(net)
727
727
  print(report.sound, report.findings)
728
728
  ```
729
729
 
730
- [**docs/how-it-works.md**](docs/how-it-works.md) covers the architecture, the
731
- CPN ML subset, binding search, timed nets, the state space, the file format
732
- and the project layout. [**docs/design.md**](docs/design.md) is the *why*:
730
+ [**docs/architecture.md**](docs/architecture.md) maps the code: its four
731
+ layers, which package imports which (drawn from the code, and checked), how a
732
+ workflow runs, and every module. [**docs/how-it-works.md**](docs/how-it-works.md)
733
+ covers the CPN ML subset, binding search, timed nets, the state space and
734
+ the file format. [**docs/design.md**](docs/design.md) is the *why*:
733
735
  the problem, the claim, the three ideas and the decisions that follow from
734
736
  them. `tests/cross_check_inductive_miner.py` compares the built-in Inductive
735
737
  Miner with PM4Py's on hundreds of logs and reports where they differ.
@@ -752,8 +754,8 @@ extra) and *Predict the next activity*. Or start empty.
752
754
  and Save. The sidebar beside it lists the folder's analyses and, folded
753
755
  under them, its logs.
754
756
  - **+ Add box** (or a double-click on the canvas) opens every box, one
755
- section per group (Input, Filter, Discover, Check, Compare, Output, then
756
- Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
757
+ section per group (Input, Filter, Discover, Check, Measure, Compare, Output,
758
+ then Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
757
759
  scrolls. Under *Input*, **the folder's own files**: one click adds the box
758
760
  that reads the file, already set. Type to search them all: "alpha" finds
759
761
  the α-algorithm, "boarding" the log; Enter adds the first match.
@@ -814,8 +816,29 @@ extra) and *Predict the next activity*. Or start empty.
814
816
  metadata; a CSV gets a `.provenance.json` beside it.
815
817
  - **⌘Z and ⇧⌘Z** undo and redo on the canvas: boxes, connections, moves,
816
818
  settings, groups.
817
- - **The side panel** resizes by dragging the gap beside it; double-click the
818
- gap, or *View ▸ Reset Workflow Layout*, for its default width.
819
+ - **The side panel** resizes by dragging the gap beside it, never narrower
820
+ than what it shows; double-click the gap, or *View ▸ Reset Workflow Layout*,
821
+ for its default size. Its dock button (or *View ▸ Workflow Panel Under the
822
+ Canvas*) moves it under the canvas, where a wide table or the code has the
823
+ window's width; the app remembers where you put it.
824
+ - **Alignments** are drawn as ProM draws them: the log on the model (per
825
+ transition, ✓ synchronous moves and » model moves, its colour from green,
826
+ always matched, to purple, often skipped; *n* » log moves at the activity),
827
+ and every variant's alignment as a row of chevrons: green synchronous,
828
+ yellow log move, purple model move, black τ. On a large log, the box's
829
+ *variants* setting aligns the most frequent ones and says what share of the
830
+ cases they cover.
831
+ - **Measure** boxes answer the questions an audit asks, for every case: *Time
832
+ between* two activities (throughput), *Order rule* (does the payment block
833
+ go before the goods receipt?), *Segregation of duties* (who both recorded
834
+ and cleared an invoice?) and *Compliance by attribute* (per vendor: cases,
835
+ share that fits, value), with *Case attribute* to keep one category and
836
+ *Bubble chart* to draw it. [examples/bpi2019](examples/bpi2019/README.md)
837
+ uses them on the BPI Challenge 2019. *Dotted chart* saves a dotted chart
838
+ as a figure (the same dots as the log page's tab), and *Who does what*
839
+ counts each activity per resource;
840
+ [examples/energy_contracts](examples/energy_contracts/README.md) uses them
841
+ with the course's miners and checks on the 2AMI10 exam log.
819
842
  - **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
820
843
  draws what flows in, OpenProcess and its boxes, what flows out, and the
821
844
  optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
@@ -857,7 +880,7 @@ save(wf, "orders.cpnflow", run)
857
880
  ```bash
858
881
  openprocess run orders.cpnflow --check # re-run; fails if any result differs from the record
859
882
  openprocess run orders.cpnflow --sweep noise=0..0.5 step 0.1
860
- openprocess boxes # the 47 boxes, with a folder's boxes/
883
+ openprocess boxes # every box (listed in docs/boxes.md), with a folder's boxes/
861
884
  openprocess datasets list # the BPI Challenge, Sepsis, ... logs by name
862
885
  ```
863
886
 
@@ -873,11 +896,22 @@ def last_two(log: EventLog, representation: str = "multiset") -> TransitionSyste
873
896
  ```
874
897
 
875
898
  `pip install -e ".[science]"` adds the pandas, NumPy, SciPy and matplotlib
876
- boxes (describe, bootstrap intervals, tests, plots). Large logs are read
877
- into columns rather than objects, so a million events fit in memory.
878
- [**docs/workflows.md**](docs/workflows.md) has the whole framework: writing
879
- a box, the types, the *How* tab, sweeps, the prediction pipeline, the
880
- record and running in CI.
899
+ boxes (describe, bootstrap intervals, tests, plots).
900
+
901
+ **Large logs.** A log above 20 MB is read into columns rather than objects,
902
+ and everything that looks at every event reads the columns: the BPI
903
+ Challenge 2019 log (251,734 cases, 1.6 million events, 728 MB) opens in
904
+ about 12 seconds and 250 MB, every tab of its log page in a few seconds, and
905
+ the whole [BPI 2019 analysis](examples/bpi2019/README.md), from reading the
906
+ file to the last chart, runs in about two minutes.
907
+
908
+ [**docs/workflows.md**](docs/workflows.md) is the tutorial: writing a box,
909
+ the types, the *How* tab, sweeps, the prediction pipeline, the record and
910
+ running in CI. [**docs/framework.md**](docs/framework.md) is the
911
+ specification: what a box, a workflow and a run are, what a fingerprint
912
+ covers and what a reproduced result promises. [**docs/architecture.md**](docs/architecture.md)
913
+ maps the code, drawn from its imports, and [**docs/boxes.md**](docs/boxes.md)
914
+ lists every box.
881
915
 
882
916
  **Building the apps.** `packaging/` turns OpenProcess into a standalone app with
883
917
  PyInstaller. In the environment:
@@ -900,11 +934,20 @@ workflow* on the Actions tab.
900
934
  [CHANGELOG.md](CHANGELOG.md) (`## 0.4.0`, then a few bullet points). Write
901
935
  it for the people using the app: it becomes the text of the release on
902
936
  GitHub, and the app shows it when it offers the update.
903
- 2. Set `__version__` in `openprocess/__init__.py` to the same version and merge
904
- both into `main`. It is the only place the version is written:
905
- `pyproject.toml`, the download names and the app's update check all read
906
- it from there.
907
- 3. Tag that commit with the same version and push the tag:
937
+ 2. Bring the documentation up to date with the code:
938
+ - run `python docs/make_docs.py`, which rewrites the code map in
939
+ [docs/architecture.md](docs/architecture.md) and the box catalogue in
940
+ [docs/boxes.md](docs/boxes.md) (the test suite, and so the release
941
+ build, fails while they are out of date, and when a package imports
942
+ across its layer);
943
+ - read [docs/framework.md](docs/framework.md) against what changed in
944
+ `openprocess/flow` (a test fails when a public name is not described
945
+ there, but only you can tell whether a description is still right).
946
+ 3. Set `__version__` in `openprocess/__init__.py` and `version` and
947
+ `date-released` in `CITATION.cff` to the same version, and merge them into
948
+ `main`. `pyproject.toml`, the download names and the app's update check
949
+ all read the version from `__version__`.
950
+ 4. Tag that commit with the same version and push the tag:
908
951
 
909
952
  ```bash
910
953
  git pull
@@ -915,8 +958,8 @@ workflow* on the Actions tab.
915
958
  About five minutes later the
916
959
  [Releases page](https://github.com/s4mstruthers/openprocess/releases) has the
917
960
  macOS (Apple silicon and Intel), Windows and Linux apps. The build refuses a
918
- tag that does not match `__version__`, or a version with no section in
919
- `CHANGELOG.md`.
961
+ tag that does not match `__version__`, a version with no section in
962
+ `CHANGELOG.md`, and code whose tests fail, stale documentation included.
920
963
 
921
964
  **Updates.** Each time it opens, the app checks the latest release on GitHub
922
965
  (*Settings* turns that off). If there is a newer version, a slim bar at the
@@ -48,7 +48,7 @@ from .sim.simulator import Simulator
48
48
  #: The one place the version number is written (pyproject.toml, the update
49
49
  #: check and packaging/build.py read it from here). A release is tagged
50
50
  #: "v" + this, e.g. v0.2.0.
51
- __version__ = "0.11.3"
51
+ __version__ = "0.12.1"
52
52
 
53
53
  __all__ = [
54
54
  "Arc", "BindingElement", "CPNet", "Marking", "Multiset", "Page", "Place",
@@ -20,7 +20,8 @@ Commands
20
20
  ``exercises`` ``check``, ``marks``, ``import``, ``computes``: exercise packs (OpenProcess Learn)
21
21
  ``run`` run a ``.cpnflow`` workflow headless; ``--check`` re-runs and
22
22
  compares with the record, ``--sweep`` varies a setting,
23
- ``--lock`` writes the requirements the record names
23
+ ``--lock`` writes the requirements the record names,
24
+ ``--export`` and ``--report`` write a zip and an HTML page
24
25
  ``boxes`` list the boxes (OpenProcess's own, a folder's ``boxes/``, packages)
25
26
  ``datasets`` the public logs known by name: ``list``, ``fetch``, ``where``
26
27
  """
@@ -468,7 +469,7 @@ def command_run(arguments: argparse.Namespace) -> int:
468
469
  print(f"openprocess: {line}", file=sys.stderr)
469
470
  if problems:
470
471
  return 2
471
- run = Runner(library, folder=folder).run(workflow) # file settings are relative to the folder
472
+ run = Runner(library, folder=folder, strict=arguments.strict).run(workflow) # file settings: relative to the folder
472
473
  for node in workflow.order():
473
474
  result = run.result(node)
474
475
  value = run.value(node)
@@ -490,13 +491,21 @@ def command_run(arguments: argparse.Namespace) -> int:
490
491
  if arguments.export:
491
492
  from .flow.record import export_experiment
492
493
  print(f"Wrote {export_experiment(workflow, run, arguments.export, folder, library)}")
494
+ if arguments.report:
495
+ from .flow.report import write_report
496
+ print(f"Wrote {write_report(arguments.report, workflow=workflow, run=run, folder=folder)}")
493
497
  if arguments.check:
494
- report = check(workflow, record, run)
498
+ if not record.results:
499
+ # Nothing to compare is not a pass: a CI job would claim a reproduction that never happened.
500
+ print(f"openprocess: {path.name} has no recorded results to check against; "
501
+ f"record them first with: openprocess run {path.name} --save", file=sys.stderr)
502
+ return 1
503
+ report = check(workflow, record, run, complete=True)
495
504
  if report:
496
505
  for line in report:
497
506
  print(f"DIFFERS: {line}")
498
507
  return 1
499
- print("Every recorded result was reproduced.")
508
+ print(f"Every recorded result was reproduced ({len(record.results)} boxes).")
500
509
  elif arguments.save:
501
510
  save(workflow, path, run, folder)
502
511
  print(f"Recorded the results in {path.name}")
@@ -504,7 +513,11 @@ def command_run(arguments: argparse.Namespace) -> int:
504
513
 
505
514
 
506
515
  def _brief(value) -> str:
516
+ import dataclasses
517
+ from .flow.figures import tile_parts
507
518
  from .flow.types import Figure, Scores, Table
519
+ if dataclasses.is_dataclass(value) and not isinstance(value, type) and tile_parts(value):
520
+ return " · ".join(tile_parts(value))
508
521
  if isinstance(value, Scores):
509
522
  return ", ".join(f"{k} {v:.3f}" if isinstance(v, float) else f"{k} {v}" for k, v in value.metrics.items())
510
523
  if isinstance(value, Table):
@@ -650,6 +663,10 @@ def build_parser() -> argparse.ArgumentParser:
650
663
  run.add_argument("-o", "--output", metavar="FOLDER", help="write every result as a file")
651
664
  run.add_argument("--export", metavar="FILE.zip",
652
665
  help="zip the workflow, its inputs, boxes and results, with a README")
666
+ run.add_argument("--strict", action="store_true",
667
+ help="also fail a box that changes its inputs in place (slower; for tests and CI)")
668
+ run.add_argument("--report", metavar="FILE.html",
669
+ help="write the analysis as one HTML page: results, how, code, inputs, citations")
653
670
  run.add_argument("-v", "--verbose", action="store_true", help="also print the boxes' notes")
654
671
  run.set_defaults(handler=command_run)
655
672
 
@@ -11,18 +11,18 @@ Writing a box is writing a function with type hints, nothing more::
11
11
 
12
12
  What the decorator reads, and nothing else:
13
13
 
14
- ================================================ ===============================================
15
- parameters whose type is a OpenProcess type input connection points, named after the parameter
16
- ``list[Type]`` an input that takes any number of connections
17
- ``Type | None`` (or a default of ``None``) an optional input
18
- the return type the output; a dataclass of typed fields gives several
14
+ ==================================================== ========================================================
15
+ parameters whose type is an OpenProcess type input connection points, named after the parameter
16
+ ``list[Type]`` an input that takes any number of connections
17
+ ``Type | None`` (or a default of ``None``) an optional input
18
+ the return type the output; a dataclass of typed fields gives several
19
19
  ``int``, ``float``, ``bool``, ``str`` with a default a setting, with the default as its value
20
- ``Literal["a", "b"]`` a setting that is a choice
21
- ``Column`` a setting that is a column of a connected table
22
- ``Path`` a setting that is a file in the folder
23
- the docstring the help text
24
- ``inspect.getsource`` the Code tab
25
- ================================================ ===============================================
20
+ ``Literal["a", "b"]`` a setting that is a choice
21
+ ``Column`` a setting that is a column of a connected table
22
+ ``Path`` a setting that is a file in the folder
23
+ the docstring the help text; a ``name: text`` line is a setting's help
24
+ ``inspect.getsource`` the Code tab
25
+ ==================================================== ========================================================
26
26
 
27
27
  A box has no base class and no registration call: the decorator returns a
28
28
  :class:`Box`, which is still the function (call it from a script or a
@@ -39,6 +39,7 @@ import hashlib
39
39
  import inspect
40
40
  import os
41
41
  import importlib.util
42
+ import re
42
43
  import types
43
44
  import typing
44
45
  from dataclasses import dataclass, field
@@ -81,10 +82,11 @@ class Port:
81
82
  return "Anything" if self.type is None else getattr(self.type, "__name__", str(self.type))
82
83
 
83
84
  def accepts(self, other: "Port") -> bool:
84
- """Can an output ``other`` be connected to this input?"""
85
+ """Can an output ``other`` be connected to this input? The same type
86
+ or a subclass of it; a ``typing.Any`` point only to another one."""
85
87
  if self.type is None or other.type is None:
86
- return self.type is other.type and other.type is None or self.type is other.type
87
- return issubclass(other.type, self.type) or self.type is other.type
88
+ return self.type is other.type
89
+ return issubclass(other.type, self.type)
88
90
 
89
91
 
90
92
  def clean_path(value: Any) -> str:
@@ -113,8 +115,22 @@ class Setting:
113
115
  help: str = ""
114
116
 
115
117
  def coerce(self, value: Any) -> Any:
116
- """A value as the GUI or a file gives it, in the setting's type."""
118
+ """A value as the GUI or a file gives it, in the setting's type.
119
+ Raises ``ValueError`` for a value that is not one, rather than
120
+ rounding it: ``2.5`` is not an int setting's value, ``"2"`` and
121
+ ``2.0`` are."""
122
+ if value is None and self.default is None and self.kind != "bool":
123
+ return None # "no value" stays no value (str | None = None)
117
124
  if self.kind == "int":
125
+ if isinstance(value, (bool, int)):
126
+ return int(value)
127
+ if isinstance(value, str):
128
+ try:
129
+ return int(value.strip())
130
+ except ValueError:
131
+ value = float(value)
132
+ if isinstance(value, float) and not value.is_integer():
133
+ raise ValueError(f"{self.name!r} must be a whole number, not {value!r}")
118
134
  return int(value)
119
135
  if self.kind == "float":
120
136
  return float(value)
@@ -168,6 +184,20 @@ class BoxSpec:
168
184
  def defaults(self) -> dict[str, Any]:
169
185
  return {s.name: s.default for s in self.settings}
170
186
 
187
+ @property
188
+ def description(self) -> str:
189
+ """The docstring's prose: :attr:`help` without the ``name: text``
190
+ lines of its settings (they are shown next to each setting) and
191
+ without an ``Args:`` heading left with nothing under it."""
192
+ names = {s.name for s in self.settings} | {p.name for p in self.inputs}
193
+ lines = (self.help or "").splitlines()
194
+ dropped = {number for key, _text, first, last in _hint_blocks(self.help) if key in names
195
+ for number in range(first, last + 1)}
196
+ text = "\n".join(line for number, line in enumerate(lines) if number not in dropped)
197
+ for heading in ("Args:", "Arguments:", "Parameters:"):
198
+ text = re.sub(rf"(?m)^\s*{heading}\s*$\n?(?=\s*(\n|$))", "", text)
199
+ return text.strip()
200
+
171
201
  @property
172
202
  def fingerprint(self) -> str:
173
203
  """Changes when the code changes: part of every cache key."""
@@ -280,7 +310,7 @@ def _setting_from(name: str, annotation, default, has_default: bool, doc_hints:
280
310
  kind = "str"
281
311
  else:
282
312
  raise BoxError(
283
- f"parameter {name!r} has type {_type_text(annotation)}, which is neither a OpenProcess "
313
+ f"parameter {name!r} has type {_type_text(annotation)}, which is neither an OpenProcess "
284
314
  f"type (an input) nor int, float, bool, str, Literal[...], Column or Path (a setting)")
285
315
  if not has_default:
286
316
  raise BoxError(f"setting {name!r} needs a default value (e.g. {name}: {kind} = ...)")
@@ -298,39 +328,65 @@ def _outputs_from(annotation) -> tuple[list[Port], type | None]:
298
328
  raise BoxError("the function needs a return type hint (-> PetriNet), or -> None")
299
329
  if annotation is None or annotation is type(None):
300
330
  return [], None
301
- annotation, _ = _unwrap_optional(annotation)
331
+ annotation, optional = _unwrap_optional(annotation)
302
332
  if annotation is Any:
303
333
  return [Port("out", None)], None
304
334
  if isinstance(annotation, type) and is_known(annotation):
305
- return [Port("out", annotation)], None
335
+ return [Port("out", annotation, optional=optional)], None
306
336
  if isinstance(annotation, type) and dataclasses.is_dataclass(annotation):
307
337
  ports = []
308
338
  hints = get_type_hints(annotation)
309
339
  for f in dataclasses.fields(annotation):
310
- inner, _ = _unwrap_optional(hints.get(f.name, f.type))
340
+ inner, optional = _unwrap_optional(hints.get(f.name, f.type))
311
341
  if not (isinstance(inner, type) and is_known(inner)) and inner is not Any:
312
342
  raise BoxError(f"result field {f.name!r} has type {_type_text(inner)}, "
313
- f"which is not a OpenProcess type")
314
- ports.append(Port(f.name, None if inner is Any else inner, label=f.name.replace("_", " ")))
343
+ f"which is not an OpenProcess type")
344
+ ports.append(Port(f.name, None if inner is Any else inner, optional=optional,
345
+ label=f.name.replace("_", " ")))
315
346
  if not ports:
316
347
  raise BoxError(f"result class {annotation.__name__} has no fields")
317
348
  return ports, annotation
318
- raise BoxError(f"the return type {_type_text(annotation)} is not a OpenProcess type "
349
+ raise BoxError(f"the return type {_type_text(annotation)} is not an OpenProcess type "
319
350
  f"(nor a dataclass of them)")
320
351
 
321
352
 
322
353
  def _doc_hints(doc: str) -> dict[str, str]:
323
- """``name: text`` lines of a docstring, as help for settings."""
354
+ """``name: text`` lines of a docstring, as help for settings. The
355
+ Google style ``name (float): text`` is read the same way, so a function
356
+ documented for Sphinx needs no second docstring to be a box."""
324
357
  hints: dict[str, str] = {}
325
- for line in (doc or "").splitlines():
326
- stripped = line.strip()
327
- if ":" in stripped and not stripped.startswith(":"):
328
- key, _, text = stripped.partition(":")
329
- if key.isidentifier() and text.strip():
330
- hints[key] = text.strip()
358
+ for key, text, _first, _last in _hint_blocks(doc):
359
+ hints.setdefault(key, text)
331
360
  return hints
332
361
 
333
362
 
363
+ def _hint_blocks(doc: str):
364
+ """``(name, help, first line, last line)`` for each ``name: text`` block of a
365
+ docstring: the line and the lines under it until a blank line or the next
366
+ ``name:``, so a help that goes on over several lines stays one help."""
367
+ lines = (doc or "").splitlines()
368
+ index = 0
369
+ while index < len(lines):
370
+ stripped = lines[index].strip()
371
+ key, _, text = stripped.partition(":")
372
+ key = key.split("(", 1)[0].strip() # "noise (float)" -> "noise"
373
+ if ":" in stripped and not stripped.startswith(":") and key.isidentifier() and text.strip():
374
+ parts, end = [text.strip()], index + 1
375
+ while end < len(lines) and lines[end].strip() and not _starts_hint(lines[end]):
376
+ parts.append(lines[end].strip())
377
+ end += 1
378
+ yield key, " ".join(parts), index, end - 1
379
+ index = end
380
+ else:
381
+ index += 1
382
+
383
+
384
+ def _starts_hint(line: str) -> bool:
385
+ stripped = line.strip()
386
+ key, _, text = stripped.partition(":")
387
+ return ":" in stripped and key.split("(", 1)[0].strip().isidentifier() and bool(text.strip())
388
+
389
+
334
390
  def _plain_name(function_name: str) -> str:
335
391
  words = function_name.replace("_", " ").strip()
336
392
  return words[:1].upper() + words[1:]
@@ -6,7 +6,7 @@ from typing import Literal
6
6
 
7
7
  from ... import flow
8
8
  from ...mining.analysis import check_soundness
9
- from ...mining.compare_nets import compare_nets
9
+ from ...mining.compare_nets import compare_nets, trace_overlap
10
10
  from ...mining.conformance.alignments import AlignmentResult, align_log
11
11
  from ...mining.conformance.quality import generalisation, precision, simplicity
12
12
  from ...mining.conformance.token_replay import ReplayResult, token_replay as _replay
@@ -61,10 +61,23 @@ def token_replay(model: PetriNet, log: EventLog) -> ReplayResult:
61
61
 
62
62
 
63
63
  @box(name="Alignments", group="Check")
64
- def alignments(model: PetriNet, log: EventLog) -> AlignmentResult:
64
+ def alignments(model: PetriNet, log: EventLog, variants: int = 0) -> AlignmentResult:
65
65
  """Optimal alignments of every variant with the model (A* over the
66
- synchronous product, standard cost function)."""
67
- result = align_log(model, log.simple_log())
66
+ synchronous product, standard cost function). Shown as ProM shows them:
67
+ the log on the model, and every variant's moves.
68
+
69
+ variants: align only this many of the most frequent variants (0: all); on a large log
70
+ the most frequent few hundred usually cover most cases, and the notes say how many
71
+ """
72
+ simple = log.simple_log()
73
+ if variants > 0 and len(simple) > variants:
74
+ from collections import Counter
75
+ kept = Counter(dict(sorted(simple.items(), key=lambda item: (-item[1], item[0]))[:variants]))
76
+ covered, total = sum(kept.values()), sum(simple.values())
77
+ flow.note(f"Aligned the {variants:,} most frequent of {len(simple):,} variants: "
78
+ f"{covered:,} of {total:,} cases ({100 * covered / total:.1f} %); fitness is over those cases")
79
+ simple = kept
80
+ result = align_log(model, simple)
68
81
  rows = [["⟨" + ", ".join(a.trace) + "⟩", a.count, a.cost if a.complete else "?", round(a.fitness, 4)]
69
82
  for a in result.alignments]
70
83
  flow.show(Table("Alignments", ["variant", "cases", "cost", "fitness"], rows), "Alignment per variant")
@@ -94,23 +107,36 @@ def _yes(value) -> str:
94
107
 
95
108
  @box(name="Compare to true net", group="Check")
96
109
  def compare_to_true_net(model: PetriNet, true_net: PetriNet, max_length: int = 12) -> Scores:
97
- """Model to model: the share of the true net's complete traces the
98
- model allows (recall), the share of the model's traces the true net
99
- allows (precision), simplicity, and soundness."""
110
+ """Model to model, on behaviour: of the true net's complete traces of up
111
+ to *max_length* activities, the share the model allows (recall); of the
112
+ model's, the share the true net allows (precision). Traces are counted
113
+ exactly, each distinct trace once; silent steps leave no trace. Also the
114
+ model's simplicity and soundness.
115
+
116
+ max_length: the longest trace counted (loops make the number of traces grow with it)
117
+ """
100
118
  comparison = compare_nets(model, true_net, max_length=max_length)
101
- only_model, only_truth = len(comparison.only_first), len(comparison.only_second)
102
119
  for trace in comparison.only_first[:5]:
103
120
  flow.note("The model allows ⟨" + ", ".join(trace) + "⟩, the true net does not")
104
121
  for trace in comparison.only_second[:5]:
105
122
  flow.note("The true net allows ⟨" + ", ".join(trace) + "⟩, the model does not")
106
- sound = check_soundness(model).sound
107
- recall = 1.0 if comparison.equivalent else max(0.0, 1 - only_truth / max(1, only_truth + 3))
108
- prec = 1.0 if comparison.equivalent else max(0.0, 1 - only_model / max(1, only_model + 3))
123
+ overlap = trace_overlap(model, true_net, max_length=max_length)
124
+ flow.note(f"Complete traces of up to {max_length} activities: the true net has {overlap.second:,}, "
125
+ f"the model {overlap.first:,}, both {overlap.both:,}")
126
+ if not overlap.complete:
127
+ flow.note("The nets have too many states to count every trace: the counts are a lower bound")
128
+ for net, count in (("true net", overlap.second), ("model", overlap.first)):
129
+ if not count:
130
+ flow.note(f"The {net} has no complete trace of up to {max_length} activities, so "
131
+ f"{'recall' if net == 'true net' else 'precision'} is not defined (n/a)")
109
132
  flow.note(comparison.summary("the model", "the true net"))
110
- return Scores(model.name, {"recall": recall, "precision": prec, "simplicity": simplicity(model),
133
+ sound = check_soundness(model).sound
134
+ return Scores(model.name, {"recall": overlap.recall if overlap.second else "n/a",
135
+ "precision": overlap.precision if overlap.first else "n/a",
136
+ "simplicity": simplicity(model),
111
137
  "soundness": {True: "Sound", False: "Not sound", None: "Undecided"}[sound]},
112
- note="Recall and precision here count the shortest differing traces; "
113
- "equivalent nets score 1.")
138
+ note=f"Recall and precision over the distinct complete traces of up to {max_length} "
139
+ "activities.")
114
140
 
115
141
 
116
142
  @box(name="Footprint conformance", group="Check")