openprocess 0.11.3__tar.gz → 0.12.0__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 (237) hide show
  1. {openprocess-0.11.3/openprocess.egg-info → openprocess-0.12.0}/PKG-INFO +60 -21
  2. {openprocess-0.11.3 → openprocess-0.12.0}/README.md +59 -20
  3. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/__init__.py +1 -1
  4. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/cli.py +21 -4
  5. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/box.py +85 -29
  6. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/check.py +40 -14
  7. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/filter.py +56 -7
  8. openprocess-0.12.0/openprocess/flow/boxes/measure.py +195 -0
  9. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/predict.py +14 -8
  10. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/science.py +102 -9
  11. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/convert.py +7 -2
  12. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/figures.py +14 -1
  13. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/library.py +3 -3
  14. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/record.py +256 -18
  15. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/report.py +2 -2
  16. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/reproducibility.py +9 -1
  17. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/runner.py +75 -7
  18. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/script.py +1 -1
  19. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/snapshots.py +9 -6
  20. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/workflow.py +25 -10
  21. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/canvas.py +4 -1
  22. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/page.py +110 -38
  23. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/picker.py +1 -1
  24. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/summary.py +3 -3
  25. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/templates.py +4 -3
  26. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/viewers.py +37 -12
  27. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/learn/mode.py +1 -0
  28. openprocess-0.12.0/openprocess/gui/studio/alignment_view.py +235 -0
  29. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/app.py +15 -0
  30. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/dotted_chart.py +273 -80
  31. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/graph_view.py +6 -0
  32. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/log_page.py +83 -28
  33. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/model_page.py +14 -48
  34. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/petri_page.py +8 -9
  35. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/tool_icons.py +36 -0
  36. openprocess-0.12.0/openprocess/mining/cases.py +86 -0
  37. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/columns.py +78 -3
  38. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/compare_nets.py +58 -0
  39. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/conformance/quality.py +54 -43
  40. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/conformance/token_replay.py +91 -5
  41. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/discovery/alpha.py +2 -2
  42. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/discovery/heuristics.py +21 -13
  43. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/filtering.py +32 -22
  44. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/layout.py +43 -13
  45. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/log.py +8 -1
  46. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/stats.py +50 -0
  47. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/xes.py +136 -56
  48. {openprocess-0.11.3 → openprocess-0.12.0/openprocess.egg-info}/PKG-INFO +60 -21
  49. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess.egg-info/SOURCES.txt +7 -0
  50. openprocess-0.12.0/tests/test_docs.py +16 -0
  51. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_flow.py +1 -1
  52. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_flow_gui.py +66 -0
  53. openprocess-0.12.0/tests/test_framework_contract.py +311 -0
  54. openprocess-0.12.0/tests/test_large_logs.py +179 -0
  55. openprocess-0.12.0/tests/test_measure.py +156 -0
  56. {openprocess-0.11.3 → openprocess-0.12.0}/LICENSE +0 -0
  57. {openprocess-0.11.3 → openprocess-0.12.0}/cpnpy/__init__.py +0 -0
  58. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/analysis/__init__.py +0 -0
  59. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/analysis/state_space.py +0 -0
  60. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/analysis/state_space_process.py +0 -0
  61. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/citation.py +0 -0
  62. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
  63. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
  64. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
  65. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
  66. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
  67. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
  68. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
  69. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
  70. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
  71. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
  72. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
  73. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
  74. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
  75. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
  76. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
  77. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
  78. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
  79. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
  80. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
  81. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/pack.md +0 -0
  82. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/__init__.py +0 -0
  83. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/__init__.py +0 -0
  84. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/compare.py +0 -0
  85. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/cpn.py +0 -0
  86. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/discover.py +0 -0
  87. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/input.py +0 -0
  88. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/output.py +0 -0
  89. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/sweeps.py +0 -0
  90. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/datasets.py +0 -0
  91. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/explain.py +0 -0
  92. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/history.py +0 -0
  93. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/provenance.py +0 -0
  94. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/sweep.py +0 -0
  95. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/types.py +0 -0
  96. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/__init__.py +0 -0
  97. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/app.py +0 -0
  98. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/arc_editing.py +0 -0
  99. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/canvas.py +0 -0
  100. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/__init__.py +0 -0
  101. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/runs.py +0 -0
  102. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/stepper.py +0 -0
  103. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/items.py +0 -0
  104. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/learn/answer_boxes.py +0 -0
  105. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/learn/concealment.py +0 -0
  106. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/panning.py +0 -0
  107. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/resources/openprocess-icon.png +0 -0
  108. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/__init__.py +0 -0
  109. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/__main__.py +0 -0
  110. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/charts.py +0 -0
  111. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/cite.py +0 -0
  112. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/command_palette.py +0 -0
  113. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/compare_page.py +0 -0
  114. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/connections.py +0 -0
  115. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/cpn_page.py +0 -0
  116. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/definition_view.py +0 -0
  117. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/derivation_view.py +0 -0
  118. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/documents.py +0 -0
  119. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/file_dialogs.py +0 -0
  120. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/filter_dialog.py +0 -0
  121. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/graph_builders.py +0 -0
  122. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/instances.py +0 -0
  123. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/log_editor.py +0 -0
  124. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/markdown_view.py +0 -0
  125. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/mathtext.py +0 -0
  126. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/ml_highlighter.py +0 -0
  127. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/motion.py +0 -0
  128. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/net_comparison.py +0 -0
  129. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/notes_overlay.py +0 -0
  130. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/provenance.py +0 -0
  131. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/regions_view.py +0 -0
  132. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/sidebar.py +0 -0
  133. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/spaces.py +0 -0
  134. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/style.py +0 -0
  135. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/updates.py +0 -0
  136. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/widgets.py +0 -0
  137. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/workers.py +0 -0
  138. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/workspace.py +0 -0
  139. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/theme.py +0 -0
  140. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/tidy.py +0 -0
  141. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/io/__init__.py +0 -0
  142. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/io/cpn_reader.py +0 -0
  143. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/io/cpn_writer.py +0 -0
  144. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/__init__.py +0 -0
  145. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/answers.py +0 -0
  146. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/checks.py +0 -0
  147. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/computed.py +0 -0
  148. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/context.py +0 -0
  149. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/exam.py +0 -0
  150. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/exercise-packs.md +0 -0
  151. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/importer.py +0 -0
  152. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/notation.py +0 -0
  153. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/pack.py +0 -0
  154. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/sheet.py +0 -0
  155. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/__init__.py +0 -0
  156. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/analysis.py +0 -0
  157. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/conformance/__init__.py +0 -0
  158. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/conformance/alignments.py +0 -0
  159. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/csv_import.py +0 -0
  160. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/definitions.py +0 -0
  161. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/dfg.py +0 -0
  162. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/discovery/__init__.py +0 -0
  163. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/discovery/inductive.py +0 -0
  164. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/discovery/state_regions.py +0 -0
  165. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/footprint.py +0 -0
  166. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/invariants.py +0 -0
  167. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/ocel.py +0 -0
  168. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/petrinet.py +0 -0
  169. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/playout.py +0 -0
  170. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/pm4py_bridge.py +0 -0
  171. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/pnml.py +0 -0
  172. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/processtree.py +0 -0
  173. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/regions.py +0 -0
  174. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/structure.py +0 -0
  175. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/transition_system.py +0 -0
  176. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/__init__.py +0 -0
  177. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/ast_nodes.py +0 -0
  178. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/builtins.py +0 -0
  179. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/colorsets.py +0 -0
  180. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/errors.py +0 -0
  181. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/evaluator.py +0 -0
  182. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/lexer.py +0 -0
  183. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/multiset.py +0 -0
  184. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/parser.py +0 -0
  185. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/values.py +0 -0
  186. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/model/__init__.py +0 -0
  187. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/model/declarations.py +0 -0
  188. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/model/examples.py +0 -0
  189. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/model/net.py +0 -0
  190. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/model/plain.py +0 -0
  191. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/packages.py +0 -0
  192. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/references.py +0 -0
  193. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/sim/__init__.py +0 -0
  194. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/sim/binding.py +0 -0
  195. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/sim/export.py +0 -0
  196. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/sim/simulator.py +0 -0
  197. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/teaching/__init__.py +0 -0
  198. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/teaching/answers.py +0 -0
  199. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/teaching/checks.py +0 -0
  200. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/teaching/pack.py +0 -0
  201. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/teaching/sheet.py +0 -0
  202. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess.egg-info/dependency_links.txt +0 -0
  203. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess.egg-info/entry_points.txt +0 -0
  204. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess.egg-info/requires.txt +0 -0
  205. {openprocess-0.11.3 → openprocess-0.12.0}/openprocess.egg-info/top_level.txt +0 -0
  206. {openprocess-0.11.3 → openprocess-0.12.0}/pyproject.toml +0 -0
  207. {openprocess-0.11.3 → openprocess-0.12.0}/setup.cfg +0 -0
  208. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_arc_editing.py +0 -0
  209. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_citation.py +0 -0
  210. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_colorsets.py +0 -0
  211. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_columns.py +0 -0
  212. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_cpn_course_model.py +0 -0
  213. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_definitions.py +0 -0
  214. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_exercises_gui.py +0 -0
  215. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_hierarchy.py +0 -0
  216. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_history.py +0 -0
  217. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_io.py +0 -0
  218. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_learn.py +0 -0
  219. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_learn_gui.py +0 -0
  220. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_log_editing.py +0 -0
  221. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_mining.py +0 -0
  222. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_ml.py +0 -0
  223. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_multiset.py +0 -0
  224. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_ocel.py +0 -0
  225. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_packages.py +0 -0
  226. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_regions.py +0 -0
  227. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_rename.py +0 -0
  228. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_report.py +0 -0
  229. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_simulation.py +0 -0
  230. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_snapshots.py +0 -0
  231. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_state_space.py +0 -0
  232. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_studio_gui.py +0 -0
  233. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_teaching.py +0 -0
  234. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_tidy.py +0 -0
  235. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_updates.py +0 -0
  236. {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_wf_structure.py +0 -0
  237. {openprocess-0.11.3 → openprocess-0.12.0}/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.0
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,25 @@ 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.
858
877
  - **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
859
878
  draws what flows in, OpenProcess and its boxes, what flows out, and the
860
879
  optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
@@ -896,7 +915,7 @@ save(wf, "orders.cpnflow", run)
896
915
  ```bash
897
916
  openprocess run orders.cpnflow --check # re-run; fails if any result differs from the record
898
917
  openprocess run orders.cpnflow --sweep noise=0..0.5 step 0.1
899
- openprocess boxes # the 47 boxes, with a folder's boxes/
918
+ openprocess boxes # every box (listed in docs/boxes.md), with a folder's boxes/
900
919
  openprocess datasets list # the BPI Challenge, Sepsis, ... logs by name
901
920
  ```
902
921
 
@@ -912,11 +931,22 @@ def last_two(log: EventLog, representation: str = "multiset") -> TransitionSyste
912
931
  ```
913
932
 
914
933
  `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.
934
+ boxes (describe, bootstrap intervals, tests, plots).
935
+
936
+ **Large logs.** A log above 20 MB is read into columns rather than objects,
937
+ and everything that looks at every event reads the columns: the BPI
938
+ Challenge 2019 log (251,734 cases, 1.6 million events, 728 MB) opens in
939
+ about 12 seconds and 250 MB, every tab of its log page in a few seconds, and
940
+ the whole [BPI 2019 analysis](examples/bpi2019/README.md), from reading the
941
+ file to the last chart, runs in about two minutes.
942
+
943
+ [**docs/workflows.md**](docs/workflows.md) is the tutorial: writing a box,
944
+ the types, the *How* tab, sweeps, the prediction pipeline, the record and
945
+ running in CI. [**docs/framework.md**](docs/framework.md) is the
946
+ specification: what a box, a workflow and a run are, what a fingerprint
947
+ covers and what a reproduced result promises. [**docs/architecture.md**](docs/architecture.md)
948
+ maps the code, drawn from its imports, and [**docs/boxes.md**](docs/boxes.md)
949
+ lists every box.
920
950
 
921
951
  **Building the apps.** `packaging/` turns OpenProcess into a standalone app with
922
952
  PyInstaller. In the environment:
@@ -939,11 +969,20 @@ workflow* on the Actions tab.
939
969
  [CHANGELOG.md](CHANGELOG.md) (`## 0.4.0`, then a few bullet points). Write
940
970
  it for the people using the app: it becomes the text of the release on
941
971
  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:
972
+ 2. Bring the documentation up to date with the code:
973
+ - run `python docs/make_docs.py`, which rewrites the code map in
974
+ [docs/architecture.md](docs/architecture.md) and the box catalogue in
975
+ [docs/boxes.md](docs/boxes.md) (the test suite, and so the release
976
+ build, fails while they are out of date, and when a package imports
977
+ across its layer);
978
+ - read [docs/framework.md](docs/framework.md) against what changed in
979
+ `openprocess/flow` (a test fails when a public name is not described
980
+ there, but only you can tell whether a description is still right).
981
+ 3. Set `__version__` in `openprocess/__init__.py` and `version` and
982
+ `date-released` in `CITATION.cff` to the same version, and merge them into
983
+ `main`. `pyproject.toml`, the download names and the app's update check
984
+ all read the version from `__version__`.
985
+ 4. Tag that commit with the same version and push the tag:
947
986
 
948
987
  ```bash
949
988
  git pull
@@ -954,8 +993,8 @@ workflow* on the Actions tab.
954
993
  About five minutes later the
955
994
  [Releases page](https://github.com/s4mstruthers/openprocess/releases) has the
956
995
  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`.
996
+ tag that does not match `__version__`, a version with no section in
997
+ `CHANGELOG.md`, and code whose tests fail, stale documentation included.
959
998
 
960
999
  **Updates.** Each time it opens, the app checks the latest release on GitHub
961
1000
  (*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,25 @@ 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.
819
838
  - **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
820
839
  draws what flows in, OpenProcess and its boxes, what flows out, and the
821
840
  optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
@@ -857,7 +876,7 @@ save(wf, "orders.cpnflow", run)
857
876
  ```bash
858
877
  openprocess run orders.cpnflow --check # re-run; fails if any result differs from the record
859
878
  openprocess run orders.cpnflow --sweep noise=0..0.5 step 0.1
860
- openprocess boxes # the 47 boxes, with a folder's boxes/
879
+ openprocess boxes # every box (listed in docs/boxes.md), with a folder's boxes/
861
880
  openprocess datasets list # the BPI Challenge, Sepsis, ... logs by name
862
881
  ```
863
882
 
@@ -873,11 +892,22 @@ def last_two(log: EventLog, representation: str = "multiset") -> TransitionSyste
873
892
  ```
874
893
 
875
894
  `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.
895
+ boxes (describe, bootstrap intervals, tests, plots).
896
+
897
+ **Large logs.** A log above 20 MB is read into columns rather than objects,
898
+ and everything that looks at every event reads the columns: the BPI
899
+ Challenge 2019 log (251,734 cases, 1.6 million events, 728 MB) opens in
900
+ about 12 seconds and 250 MB, every tab of its log page in a few seconds, and
901
+ the whole [BPI 2019 analysis](examples/bpi2019/README.md), from reading the
902
+ file to the last chart, runs in about two minutes.
903
+
904
+ [**docs/workflows.md**](docs/workflows.md) is the tutorial: writing a box,
905
+ the types, the *How* tab, sweeps, the prediction pipeline, the record and
906
+ running in CI. [**docs/framework.md**](docs/framework.md) is the
907
+ specification: what a box, a workflow and a run are, what a fingerprint
908
+ covers and what a reproduced result promises. [**docs/architecture.md**](docs/architecture.md)
909
+ maps the code, drawn from its imports, and [**docs/boxes.md**](docs/boxes.md)
910
+ lists every box.
881
911
 
882
912
  **Building the apps.** `packaging/` turns OpenProcess into a standalone app with
883
913
  PyInstaller. In the environment:
@@ -900,11 +930,20 @@ workflow* on the Actions tab.
900
930
  [CHANGELOG.md](CHANGELOG.md) (`## 0.4.0`, then a few bullet points). Write
901
931
  it for the people using the app: it becomes the text of the release on
902
932
  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:
933
+ 2. Bring the documentation up to date with the code:
934
+ - run `python docs/make_docs.py`, which rewrites the code map in
935
+ [docs/architecture.md](docs/architecture.md) and the box catalogue in
936
+ [docs/boxes.md](docs/boxes.md) (the test suite, and so the release
937
+ build, fails while they are out of date, and when a package imports
938
+ across its layer);
939
+ - read [docs/framework.md](docs/framework.md) against what changed in
940
+ `openprocess/flow` (a test fails when a public name is not described
941
+ there, but only you can tell whether a description is still right).
942
+ 3. Set `__version__` in `openprocess/__init__.py` and `version` and
943
+ `date-released` in `CITATION.cff` to the same version, and merge them into
944
+ `main`. `pyproject.toml`, the download names and the app's update check
945
+ all read the version from `__version__`.
946
+ 4. Tag that commit with the same version and push the tag:
908
947
 
909
948
  ```bash
910
949
  git pull
@@ -915,8 +954,8 @@ workflow* on the Actions tab.
915
954
  About five minutes later the
916
955
  [Releases page](https://github.com/s4mstruthers/openprocess/releases) has the
917
956
  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`.
957
+ tag that does not match `__version__`, a version with no section in
958
+ `CHANGELOG.md`, and code whose tests fail, stale documentation included.
920
959
 
921
960
  **Updates.** Each time it opens, the app checks the latest release on GitHub
922
961
  (*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.0"
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")