openprocess 0.10.5__tar.gz → 0.11.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 (230) hide show
  1. {openprocess-0.10.5/openprocess.egg-info → openprocess-0.11.0}/PKG-INFO +45 -10
  2. {openprocess-0.10.5 → openprocess-0.11.0}/README.md +44 -9
  3. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/__init__.py +1 -1
  4. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/citation.py +37 -10
  5. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/__init__.py +2 -1
  6. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/box.py +6 -3
  7. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/boxes/science.py +26 -15
  8. openprocess-0.11.0/openprocess/flow/history.py +113 -0
  9. openprocess-0.11.0/openprocess/flow/provenance.py +110 -0
  10. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/record.py +67 -3
  11. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/reproducibility.py +9 -0
  12. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/runner.py +38 -1
  13. openprocess-0.11.0/openprocess/flow/script.py +87 -0
  14. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/types.py +22 -1
  15. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/workflow.py +18 -1
  16. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/flow/page.py +88 -6
  17. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/flow/viewers.py +122 -7
  18. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/learn/answer_boxes.py +3 -5
  19. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/app.py +124 -15
  20. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/connections.py +49 -13
  21. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/definition_view.py +9 -2
  22. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/graph_view.py +9 -0
  23. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/markdown_view.py +3 -1
  24. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/style.py +5 -9
  25. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/widgets.py +102 -10
  26. openprocess-0.11.0/openprocess/packages.py +139 -0
  27. {openprocess-0.10.5 → openprocess-0.11.0/openprocess.egg-info}/PKG-INFO +45 -10
  28. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess.egg-info/SOURCES.txt +6 -0
  29. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_citation.py +27 -0
  30. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_exercises_gui.py +6 -0
  31. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_flow.py +170 -0
  32. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_flow_gui.py +185 -4
  33. openprocess-0.11.0/tests/test_history.py +57 -0
  34. openprocess-0.11.0/tests/test_packages.py +69 -0
  35. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_studio_gui.py +100 -1
  36. {openprocess-0.10.5 → openprocess-0.11.0}/LICENSE +0 -0
  37. {openprocess-0.10.5 → openprocess-0.11.0}/cpnpy/__init__.py +0 -0
  38. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/analysis/__init__.py +0 -0
  39. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/analysis/state_space.py +0 -0
  40. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/analysis/state_space_process.py +0 -0
  41. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/cli.py +0 -0
  42. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
  43. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
  44. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
  45. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
  46. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
  47. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
  48. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
  49. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
  50. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
  51. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
  52. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
  53. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
  54. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
  55. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
  56. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
  57. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
  58. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
  59. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
  60. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
  61. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/exercises/pack.md +0 -0
  62. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/boxes/__init__.py +0 -0
  63. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/boxes/check.py +0 -0
  64. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/boxes/compare.py +0 -0
  65. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/boxes/cpn.py +0 -0
  66. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/boxes/discover.py +0 -0
  67. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/boxes/filter.py +0 -0
  68. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/boxes/input.py +0 -0
  69. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/boxes/output.py +0 -0
  70. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/boxes/predict.py +0 -0
  71. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/boxes/sweeps.py +0 -0
  72. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/convert.py +0 -0
  73. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/datasets.py +0 -0
  74. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/explain.py +0 -0
  75. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/figures.py +0 -0
  76. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/library.py +0 -0
  77. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/report.py +0 -0
  78. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/snapshots.py +0 -0
  79. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/flow/sweep.py +0 -0
  80. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/__init__.py +0 -0
  81. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/app.py +0 -0
  82. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/arc_editing.py +0 -0
  83. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/canvas.py +0 -0
  84. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/flow/__init__.py +0 -0
  85. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/flow/canvas.py +0 -0
  86. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/flow/picker.py +0 -0
  87. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/flow/runs.py +0 -0
  88. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/flow/stepper.py +0 -0
  89. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/flow/summary.py +0 -0
  90. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/flow/templates.py +0 -0
  91. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/items.py +0 -0
  92. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/learn/concealment.py +0 -0
  93. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/learn/mode.py +0 -0
  94. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/panning.py +0 -0
  95. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/resources/openprocess-icon.png +0 -0
  96. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/__init__.py +0 -0
  97. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/__main__.py +0 -0
  98. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/charts.py +0 -0
  99. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/cite.py +0 -0
  100. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/command_palette.py +0 -0
  101. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/compare_page.py +0 -0
  102. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/cpn_page.py +0 -0
  103. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/derivation_view.py +0 -0
  104. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/documents.py +0 -0
  105. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/dotted_chart.py +0 -0
  106. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/file_dialogs.py +0 -0
  107. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/filter_dialog.py +0 -0
  108. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/graph_builders.py +0 -0
  109. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/instances.py +0 -0
  110. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/log_editor.py +0 -0
  111. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/log_page.py +0 -0
  112. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/mathtext.py +0 -0
  113. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/ml_highlighter.py +0 -0
  114. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/model_page.py +0 -0
  115. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/motion.py +0 -0
  116. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/net_comparison.py +0 -0
  117. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/notes_overlay.py +0 -0
  118. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/petri_page.py +0 -0
  119. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/provenance.py +0 -0
  120. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/regions_view.py +0 -0
  121. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/sidebar.py +0 -0
  122. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/spaces.py +0 -0
  123. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/tool_icons.py +0 -0
  124. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/updates.py +0 -0
  125. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/workers.py +0 -0
  126. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/studio/workspace.py +0 -0
  127. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/theme.py +0 -0
  128. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/gui/tidy.py +0 -0
  129. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/io/__init__.py +0 -0
  130. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/io/cpn_reader.py +0 -0
  131. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/io/cpn_writer.py +0 -0
  132. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/learn/__init__.py +0 -0
  133. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/learn/answers.py +0 -0
  134. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/learn/checks.py +0 -0
  135. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/learn/computed.py +0 -0
  136. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/learn/context.py +0 -0
  137. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/learn/exam.py +0 -0
  138. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/learn/exercise-packs.md +0 -0
  139. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/learn/importer.py +0 -0
  140. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/learn/notation.py +0 -0
  141. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/learn/pack.py +0 -0
  142. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/learn/sheet.py +0 -0
  143. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/__init__.py +0 -0
  144. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/analysis.py +0 -0
  145. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/columns.py +0 -0
  146. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/compare_nets.py +0 -0
  147. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/conformance/__init__.py +0 -0
  148. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/conformance/alignments.py +0 -0
  149. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/conformance/quality.py +0 -0
  150. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/conformance/token_replay.py +0 -0
  151. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/csv_import.py +0 -0
  152. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/definitions.py +0 -0
  153. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/dfg.py +0 -0
  154. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/discovery/__init__.py +0 -0
  155. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/discovery/alpha.py +0 -0
  156. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/discovery/heuristics.py +0 -0
  157. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/discovery/inductive.py +0 -0
  158. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/discovery/state_regions.py +0 -0
  159. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/filtering.py +0 -0
  160. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/footprint.py +0 -0
  161. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/invariants.py +0 -0
  162. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/layout.py +0 -0
  163. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/log.py +0 -0
  164. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/ocel.py +0 -0
  165. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/petrinet.py +0 -0
  166. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/playout.py +0 -0
  167. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/pm4py_bridge.py +0 -0
  168. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/pnml.py +0 -0
  169. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/processtree.py +0 -0
  170. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/regions.py +0 -0
  171. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/stats.py +0 -0
  172. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/structure.py +0 -0
  173. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/transition_system.py +0 -0
  174. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/mining/xes.py +0 -0
  175. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/ml/__init__.py +0 -0
  176. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/ml/ast_nodes.py +0 -0
  177. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/ml/builtins.py +0 -0
  178. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/ml/colorsets.py +0 -0
  179. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/ml/errors.py +0 -0
  180. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/ml/evaluator.py +0 -0
  181. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/ml/lexer.py +0 -0
  182. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/ml/multiset.py +0 -0
  183. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/ml/parser.py +0 -0
  184. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/ml/values.py +0 -0
  185. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/model/__init__.py +0 -0
  186. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/model/declarations.py +0 -0
  187. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/model/examples.py +0 -0
  188. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/model/net.py +0 -0
  189. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/model/plain.py +0 -0
  190. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/references.py +0 -0
  191. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/sim/__init__.py +0 -0
  192. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/sim/binding.py +0 -0
  193. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/sim/export.py +0 -0
  194. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/sim/simulator.py +0 -0
  195. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/teaching/__init__.py +0 -0
  196. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/teaching/answers.py +0 -0
  197. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/teaching/checks.py +0 -0
  198. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/teaching/pack.py +0 -0
  199. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess/teaching/sheet.py +0 -0
  200. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess.egg-info/dependency_links.txt +0 -0
  201. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess.egg-info/entry_points.txt +0 -0
  202. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess.egg-info/requires.txt +0 -0
  203. {openprocess-0.10.5 → openprocess-0.11.0}/openprocess.egg-info/top_level.txt +0 -0
  204. {openprocess-0.10.5 → openprocess-0.11.0}/pyproject.toml +0 -0
  205. {openprocess-0.10.5 → openprocess-0.11.0}/setup.cfg +0 -0
  206. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_arc_editing.py +0 -0
  207. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_colorsets.py +0 -0
  208. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_columns.py +0 -0
  209. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_cpn_course_model.py +0 -0
  210. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_definitions.py +0 -0
  211. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_hierarchy.py +0 -0
  212. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_io.py +0 -0
  213. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_learn.py +0 -0
  214. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_learn_gui.py +0 -0
  215. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_log_editing.py +0 -0
  216. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_mining.py +0 -0
  217. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_ml.py +0 -0
  218. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_multiset.py +0 -0
  219. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_ocel.py +0 -0
  220. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_regions.py +0 -0
  221. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_rename.py +0 -0
  222. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_report.py +0 -0
  223. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_simulation.py +0 -0
  224. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_snapshots.py +0 -0
  225. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_state_space.py +0 -0
  226. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_teaching.py +0 -0
  227. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_tidy.py +0 -0
  228. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_updates.py +0 -0
  229. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_wf_structure.py +0 -0
  230. {openprocess-0.10.5 → openprocess-0.11.0}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openprocess
3
- Version: 0.10.5
3
+ Version: 0.11.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
@@ -528,17 +528,19 @@ links to the definitions it builds on; **Help ▸ Definitions** lists them all.
528
528
  for bounded nets; for unbounded ones, up to a trace length (and it says
529
529
  so).
530
530
 
531
- | Process map | Discovering a model |
531
+ | Process map | Discover in Mine →: the miner is a box, its result beside the canvas |
532
532
  |---|---|
533
533
  | ![Process map](docs/screenshots/studio-process-map.png) | ![Discover](docs/screenshots/studio-discover.png) |
534
534
 
535
- | α-algorithm result | Conformance (alignments on the model) |
535
+ | α-algorithm result | *How*: the eight steps, from the footprint to α(L) |
536
536
  |---|---|
537
- | ![Alpha](docs/screenshots/studio-alpha.png) | ![Conformance](docs/screenshots/studio-conformance.png) |
537
+ | ![Alpha](docs/screenshots/studio-alpha.png) | ![The eight steps of the α-algorithm](docs/screenshots/studio-alpha-how.png) |
538
538
 
539
- | Comparing two logs | Linked dotted charts of three boarding strategies |
539
+ | Check fit: every score, and how it was computed | Comparing two logs |
540
540
  |---|---|
541
- | ![Comparing two logs](docs/screenshots/compare.png) | ![Linked dotted charts](docs/screenshots/compare-dotted.png) |
541
+ | ![Conformance](docs/screenshots/studio-conformance.png) | ![Comparing two logs](docs/screenshots/compare.png) |
542
+
543
+ ![Linked dotted charts of two logs, one scale and one colour per activity](docs/screenshots/compare-dotted.png)
542
544
 
543
545
  The app follows the system's light or dark appearance:
544
546
 
@@ -555,6 +557,8 @@ sidebar, **Learn ▸ Open Exercise Pack…**, or **Learn ▸ Open Demo Exercises
555
557
  exercises): the window switches to a view made for working through a pack
556
558
  without distractions, and *Exit* brings your folder back exactly as it was.
557
559
 
560
+ ![An exercise in Learn: the worksheet with its answer boxes on the left, the exercise's log, given net and workflow on the right](docs/screenshots/learn-exercise.png)
561
+
558
562
  - **The worksheet** on the left is the question top to bottom, with an
559
563
  answer box wherever one is needed: yes/no, multiple choice, a set
560
564
  (`{a, b}`, sets of sets, or pairs like `({a}, {b,d})`), a number, a short
@@ -742,7 +746,10 @@ generated from `openprocess/mining/definitions.py`; after editing a definition,
742
746
  Likewise [`docs/references.md`](docs/references.md) — every source OpenProcess's
743
747
  notation and algorithms follow, also in the app under **Help ▸ References** —
744
748
  is generated from `openprocess/references.py` with
745
- `python -m openprocess.references > docs/references.md`.
749
+ `python -m openprocess.references > docs/references.md`. The screenshots in
750
+ this README come from the app itself: `python docs/make_screenshots.py`
751
+ renders every page offscreen into `docs/screenshots/`, so run it after a
752
+ change to the app's looks.
746
753
 
747
754
  The engines (`openprocess.mining`, `openprocess.ml`, `openprocess.sim`, `openprocess.analysis`)
748
755
  have **no dependencies**, so they work in a notebook or a script:
@@ -769,6 +776,10 @@ Miner, a fitness check), *Compare discovery* (three miners side by side),
769
776
  confidence* (bootstrap intervals, a test and a plot; needs the `science`
770
777
  extra) and *Predict the next activity*. Or start empty.
771
778
 
779
+ | The canvas: *Compare discovery*, three miners side by side | Summary: every box's key figure, and the process map |
780
+ |---|---|
781
+ | ![The canvas of a Compare discovery analysis, the Compare box's table beside it](docs/screenshots/workflow-canvas.png) | ![The Summary view: a tile per box, the process map under them](docs/screenshots/workflow-summary.png) |
782
+
772
783
  - **The page is the canvas.** Its header has **Canvas | Summary**, then
773
784
  **+ Add box**, **Run ▶**, and **⋯** for Re-run, Record, Export experiment
774
785
  and Save. The sidebar beside it lists the folder's analyses and, folded
@@ -825,15 +836,29 @@ extra) and *Predict the next activity*. Or start empty.
825
836
  `boxes/`, so it is listed under *Yours* and can be used in any workflow.
826
837
  - **Export experiment** (under ⋯) writes a zip with the workflow file and its record,
827
838
  the inputs, your boxes, every result as a file (CSV, PNML, SVG, XES), a
828
- `requirements.lock` and a README that says what was run: supplementary
829
- material for a paper, which `openprocess run --check` can verify.
839
+ `requirements.lock`, a `run.py` that runs the same analysis as a plain
840
+ script, and a README that says what was run: supplementary material for a
841
+ paper, which `openprocess run --check` can verify. Drop such a zip on
842
+ OpenProcess and it is unpacked into a folder of its own and run again.
843
+ - **Export as Python script** (under ⋯) writes the analysis as a `.py` file:
844
+ the same boxes, settings and connections, run with `python analysis.py`,
845
+ the results written beside it.
846
+ - **Every result knows where it came from**: a figure saved as SVG carries
847
+ the analysis, the box, its settings and the result's fingerprint in its
848
+ metadata; a CSV gets a `.provenance.json` beside it.
849
+ - **⌘Z and ⇧⌘Z** undo and redo on the canvas: boxes, connections, moves,
850
+ settings, groups.
830
851
  - **The side panel** resizes by dragging the gap beside it; double-click the
831
852
  gap, or *View ▸ Reset Workflow Layout*, for its default width.
832
853
  - **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
833
854
  draws what flows in, OpenProcess and its boxes, what flows out, and the
834
855
  optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
835
856
  installed, how to install it and the boxes it brings. All of it is read
836
- from the box library.
857
+ from the box library. *Install…* works in the downloaded app too: it carries
858
+ pip and installs into a folder of your own (on a Mac,
859
+ `~/Library/Application Support/OpenProcess/packages`), and the boxes that
860
+ need the package come alive without a restart. PM4Py needs a Python
861
+ install (`pip install pm4py`), as parts of it are built from source.
837
862
  - **Motion** is kept short: a space switch, the side panel and Canvas ↔
838
863
  Summary settle in a fifth of a second, and a box pulses once when it
839
864
  finishes. `OPENPROCESS_NO_MOTION=1` turns it off.
@@ -969,6 +994,16 @@ Plainly stated:
969
994
  Next up: several instances of a subpage, CPN monitors, and signed apps that
970
995
  open without a security prompt.
971
996
 
997
+ ## Cite
998
+
999
+ If OpenProcess helped with a course, a thesis or a paper, cite it. *Help ▸
1000
+ Cite OpenProcess…* in the app gives BibTeX and a one-line citation, and
1001
+ each algorithm's Code tab has *Cite* for the paper it follows. GitHub's
1002
+ *Cite this repository* reads [CITATION.cff](CITATION.cff). Releases are
1003
+ archived on Zenodo, which gives each version a DOI; the all-versions DOI
1004
+ goes into `CITATION.cff` (`doi:`) once the first release is archived, and
1005
+ the app's citations pick it up from there.
1006
+
972
1007
  ## Licence
973
1008
 
974
1009
  MIT. See [LICENSE](LICENSE).
@@ -489,17 +489,19 @@ links to the definitions it builds on; **Help ▸ Definitions** lists them all.
489
489
  for bounded nets; for unbounded ones, up to a trace length (and it says
490
490
  so).
491
491
 
492
- | Process map | Discovering a model |
492
+ | Process map | Discover in Mine →: the miner is a box, its result beside the canvas |
493
493
  |---|---|
494
494
  | ![Process map](docs/screenshots/studio-process-map.png) | ![Discover](docs/screenshots/studio-discover.png) |
495
495
 
496
- | α-algorithm result | Conformance (alignments on the model) |
496
+ | α-algorithm result | *How*: the eight steps, from the footprint to α(L) |
497
497
  |---|---|
498
- | ![Alpha](docs/screenshots/studio-alpha.png) | ![Conformance](docs/screenshots/studio-conformance.png) |
498
+ | ![Alpha](docs/screenshots/studio-alpha.png) | ![The eight steps of the α-algorithm](docs/screenshots/studio-alpha-how.png) |
499
499
 
500
- | Comparing two logs | Linked dotted charts of three boarding strategies |
500
+ | Check fit: every score, and how it was computed | Comparing two logs |
501
501
  |---|---|
502
- | ![Comparing two logs](docs/screenshots/compare.png) | ![Linked dotted charts](docs/screenshots/compare-dotted.png) |
502
+ | ![Conformance](docs/screenshots/studio-conformance.png) | ![Comparing two logs](docs/screenshots/compare.png) |
503
+
504
+ ![Linked dotted charts of two logs, one scale and one colour per activity](docs/screenshots/compare-dotted.png)
503
505
 
504
506
  The app follows the system's light or dark appearance:
505
507
 
@@ -516,6 +518,8 @@ sidebar, **Learn ▸ Open Exercise Pack…**, or **Learn ▸ Open Demo Exercises
516
518
  exercises): the window switches to a view made for working through a pack
517
519
  without distractions, and *Exit* brings your folder back exactly as it was.
518
520
 
521
+ ![An exercise in Learn: the worksheet with its answer boxes on the left, the exercise's log, given net and workflow on the right](docs/screenshots/learn-exercise.png)
522
+
519
523
  - **The worksheet** on the left is the question top to bottom, with an
520
524
  answer box wherever one is needed: yes/no, multiple choice, a set
521
525
  (`{a, b}`, sets of sets, or pairs like `({a}, {b,d})`), a number, a short
@@ -703,7 +707,10 @@ generated from `openprocess/mining/definitions.py`; after editing a definition,
703
707
  Likewise [`docs/references.md`](docs/references.md) — every source OpenProcess's
704
708
  notation and algorithms follow, also in the app under **Help ▸ References** —
705
709
  is generated from `openprocess/references.py` with
706
- `python -m openprocess.references > docs/references.md`.
710
+ `python -m openprocess.references > docs/references.md`. The screenshots in
711
+ this README come from the app itself: `python docs/make_screenshots.py`
712
+ renders every page offscreen into `docs/screenshots/`, so run it after a
713
+ change to the app's looks.
707
714
 
708
715
  The engines (`openprocess.mining`, `openprocess.ml`, `openprocess.sim`, `openprocess.analysis`)
709
716
  have **no dependencies**, so they work in a notebook or a script:
@@ -730,6 +737,10 @@ Miner, a fitness check), *Compare discovery* (three miners side by side),
730
737
  confidence* (bootstrap intervals, a test and a plot; needs the `science`
731
738
  extra) and *Predict the next activity*. Or start empty.
732
739
 
740
+ | The canvas: *Compare discovery*, three miners side by side | Summary: every box's key figure, and the process map |
741
+ |---|---|
742
+ | ![The canvas of a Compare discovery analysis, the Compare box's table beside it](docs/screenshots/workflow-canvas.png) | ![The Summary view: a tile per box, the process map under them](docs/screenshots/workflow-summary.png) |
743
+
733
744
  - **The page is the canvas.** Its header has **Canvas | Summary**, then
734
745
  **+ Add box**, **Run ▶**, and **⋯** for Re-run, Record, Export experiment
735
746
  and Save. The sidebar beside it lists the folder's analyses and, folded
@@ -786,15 +797,29 @@ extra) and *Predict the next activity*. Or start empty.
786
797
  `boxes/`, so it is listed under *Yours* and can be used in any workflow.
787
798
  - **Export experiment** (under ⋯) writes a zip with the workflow file and its record,
788
799
  the inputs, your boxes, every result as a file (CSV, PNML, SVG, XES), a
789
- `requirements.lock` and a README that says what was run: supplementary
790
- material for a paper, which `openprocess run --check` can verify.
800
+ `requirements.lock`, a `run.py` that runs the same analysis as a plain
801
+ script, and a README that says what was run: supplementary material for a
802
+ paper, which `openprocess run --check` can verify. Drop such a zip on
803
+ OpenProcess and it is unpacked into a folder of its own and run again.
804
+ - **Export as Python script** (under ⋯) writes the analysis as a `.py` file:
805
+ the same boxes, settings and connections, run with `python analysis.py`,
806
+ the results written beside it.
807
+ - **Every result knows where it came from**: a figure saved as SVG carries
808
+ the analysis, the box, its settings and the result's fingerprint in its
809
+ metadata; a CSV gets a `.provenance.json` beside it.
810
+ - **⌘Z and ⇧⌘Z** undo and redo on the canvas: boxes, connections, moves,
811
+ settings, groups.
791
812
  - **The side panel** resizes by dragging the gap beside it; double-click the
792
813
  gap, or *View ▸ Reset Workflow Layout*, for its default width.
793
814
  - **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
794
815
  draws what flows in, OpenProcess and its boxes, what flows out, and the
795
816
  optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
796
817
  installed, how to install it and the boxes it brings. All of it is read
797
- from the box library.
818
+ from the box library. *Install…* works in the downloaded app too: it carries
819
+ pip and installs into a folder of your own (on a Mac,
820
+ `~/Library/Application Support/OpenProcess/packages`), and the boxes that
821
+ need the package come alive without a restart. PM4Py needs a Python
822
+ install (`pip install pm4py`), as parts of it are built from source.
798
823
  - **Motion** is kept short: a space switch, the side panel and Canvas ↔
799
824
  Summary settle in a fifth of a second, and a box pulses once when it
800
825
  finishes. `OPENPROCESS_NO_MOTION=1` turns it off.
@@ -930,6 +955,16 @@ Plainly stated:
930
955
  Next up: several instances of a subpage, CPN monitors, and signed apps that
931
956
  open without a security prompt.
932
957
 
958
+ ## Cite
959
+
960
+ If OpenProcess helped with a course, a thesis or a paper, cite it. *Help ▸
961
+ Cite OpenProcess…* in the app gives BibTeX and a one-line citation, and
962
+ each algorithm's Code tab has *Cite* for the paper it follows. GitHub's
963
+ *Cite this repository* reads [CITATION.cff](CITATION.cff). Releases are
964
+ archived on Zenodo, which gives each version a DOI; the all-versions DOI
965
+ goes into `CITATION.cff` (`doi:`) once the first release is archived, and
966
+ the app's citations pick it up from there.
967
+
933
968
  ## Licence
934
969
 
935
970
  MIT. See [LICENSE](LICENSE).
@@ -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.10.5"
51
+ __version__ = "0.11.0"
52
52
 
53
53
  __all__ = [
54
54
  "Arc", "BindingElement", "CPNet", "Marking", "Multiset", "Page", "Place",
@@ -14,6 +14,7 @@ from __future__ import annotations
14
14
  import datetime as _dt
15
15
  import re
16
16
  from dataclasses import dataclass, field
17
+ from pathlib import Path
17
18
 
18
19
  from . import references as _references
19
20
 
@@ -27,6 +28,23 @@ def app_version() -> str:
27
28
  return __version__
28
29
 
29
30
 
31
+ def concept_doi() -> str:
32
+ """The DOI Zenodo gives the software (all versions), from ``CITATION.cff``
33
+ at the top of the repository or installed beside the package; "" until
34
+ the first release is archived."""
35
+ import re
36
+ for candidate in (Path(__file__).resolve().parents[1] / "CITATION.cff",
37
+ Path(__file__).resolve().parent / "CITATION.cff"):
38
+ try:
39
+ text = candidate.read_text(encoding="utf-8")
40
+ except OSError:
41
+ continue
42
+ found = re.search(r"^doi:\s*(\S+)\s*$", text, re.M)
43
+ if found:
44
+ return found.group(1).strip("'\"")
45
+ return ""
46
+
47
+
30
48
  # ---------------------------------------------------------------------------
31
49
  # One entry
32
50
  # ---------------------------------------------------------------------------
@@ -137,45 +155,54 @@ def entries_for_module(module: str) -> list[Entry]:
137
155
  # ---------------------------------------------------------------------------
138
156
  # The app itself
139
157
  # ---------------------------------------------------------------------------
140
- def app_entry(version: str | None = None, year: int | None = None) -> Entry:
158
+ def app_entry(version: str | None = None, year: int | None = None, doi: str | None = None) -> Entry:
141
159
  version = version or app_version()
142
160
  year = year or _dt.date.today().year
161
+ doi = concept_doi() if doi is None else doi
143
162
  return Entry("software", "openprocess", {
144
163
  "author": " and ".join(f"{last}, {first}" for last, first in APP_AUTHORS),
145
164
  "title": APP_TITLE,
146
165
  "version": version,
147
166
  "year": str(year),
167
+ "doi": doi,
148
168
  "url": APP_URL,
149
169
  "note": "Every algorithm shows its code and the paper it follows; every analysis keeps a record "
150
170
  "that runs again",
151
171
  })
152
172
 
153
173
 
154
- def app_bibtex(version: str | None = None, year: int | None = None) -> str:
155
- return app_entry(version, year).bibtex()
174
+ def app_bibtex(version: str | None = None, year: int | None = None, doi: str | None = None) -> str:
175
+ return app_entry(version, year, doi).bibtex()
156
176
 
157
177
 
158
- def app_text(version: str | None = None, year: int | None = None) -> str:
178
+ def app_text(version: str | None = None, year: int | None = None, doi: str | None = None) -> str:
159
179
  """The citation in one line, as a reference list would have it."""
160
180
  version = version or app_version()
161
181
  year = year or _dt.date.today().year
182
+ doi = concept_doi() if doi is None else doi
162
183
  authors = ", ".join(f"{first} {last}" for last, first in APP_AUTHORS)
163
- return f"{authors}. {APP_TITLE} (version {version}) [software], {year}. {APP_URL}"
184
+ where = f"https://doi.org/{doi}" if doi else APP_URL
185
+ return f"{authors}. {APP_TITLE} (version {version}) [software], {year}. {where}"
164
186
 
165
187
 
166
- def cff_text(version: str | None = None, released: _dt.date | None = None) -> str:
167
- """The contents of ``CITATION.cff`` (Citation File Format 1.2.0) for ``version``."""
188
+ def cff_text(version: str | None = None, released: _dt.date | None = None, doi: str = "") -> str:
189
+ """The contents of ``CITATION.cff`` (Citation File Format 1.2.0) for
190
+ ``version``; ``doi`` is Zenodo's concept DOI once the first release is archived."""
168
191
  version = version or app_version()
169
192
  released = released or _dt.date.today()
170
193
  authors = "\n".join(f" - family-names: {last}\n given-names: {first}" for last, first in APP_AUTHORS)
194
+ def quoted(text: str) -> str: # a YAML string: a colon in the title would otherwise break it
195
+ return '"' + text.replace('\\', '\\\\').replace('"', '\\"') + '"'
196
+
171
197
  return (
172
198
  "cff-version: 1.2.0\n"
173
- "message: If you use OpenProcess in teaching or research, please cite it as below.\n"
199
+ f"message: {quoted('If you use OpenProcess in teaching or research, please cite it as below.')}\n"
174
200
  "type: software\n"
175
- f"title: {APP_TITLE}\n"
201
+ f"title: {quoted(APP_TITLE)}\n"
176
202
  f"version: {version}\n"
177
203
  f"date-released: {released.isoformat()}\n"
178
- f"repository-code: {APP_URL}\n"
204
+ + (f"doi: {doi}\n" if doi else "")
205
+ + f"repository-code: {APP_URL}\n"
179
206
  f"url: {APP_URL}\n"
180
207
  "license: MIT\n"
181
208
  "keywords:\n - process mining\n - Petri nets\n - workflow nets\n - conformance checking\n"
@@ -35,12 +35,13 @@ from .library import Library, library_for, standard_library
35
35
  from .record import check, differences, load, save
36
36
  from .runner import Cache, Result, Run, Runner
37
37
  from .sweep import Sweep
38
- from .types import (AlignmentResult, Any, CPNet, DFG, Dataset, EventLog, Figure, Footprint, Marking,
38
+ from .types import (Column, AlignmentResult, Any, CPNet, DFG, Dataset, EventLog, Figure, Footprint, Marking,
39
39
  PetriNet, Predictions, Predictor, ProcessTree, Regions, ReplayResult, Scores,
40
40
  SimpleLog, Table, Text, TransitionSystem, register_type)
41
41
  from .workflow import Edge, Group, Node, Workflow, WorkflowError, to_python, workflow
42
42
 
43
43
  __all__ = [
44
+ "Column",
44
45
  "AlignmentResult", "Any", "Box", "BoxError", "BoxSpec", "CPNet", "Cache", "DFG", "Dataset", "Edge",
45
46
  "EventLog", "Explanation", "Figure", "Footprint", "Group", "Library", "Marking", "Node", "PetriNet",
46
47
  "Port", "Predictions", "Predictor", "ProcessTree", "Regions", "ReplayResult", "Result", "Run",
@@ -18,6 +18,7 @@ parameters whose type is a OpenProcess type input connection points,
18
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
20
  ``Literal["a", "b"]`` a setting that is a choice
21
+ ``Column`` a setting that is a column of a connected table
21
22
  ``Path`` a setting that is a file in the folder
22
23
  the docstring the help text
23
24
  ``inspect.getsource`` the Code tab
@@ -44,7 +45,7 @@ from dataclasses import dataclass, field
44
45
  from pathlib import Path
45
46
  from typing import Any, Callable, Literal, Union, get_args, get_origin, get_type_hints
46
47
 
47
- from .types import TypeInfo, is_known, type_info
48
+ from .types import Column, TypeInfo, is_known, type_info
48
49
 
49
50
  SETTING_TYPES = (int, float, bool, str)
50
51
 
@@ -106,7 +107,7 @@ class Setting:
106
107
  """A parameter that is not an input: shown as a control in the side panel."""
107
108
 
108
109
  name: str
109
- kind: str # "int", "float", "bool", "str", "choice", "path"
110
+ kind: str # "int", "float", "bool", "str", "choice", "path", "column"
110
111
  default: Any
111
112
  choices: tuple = ()
112
113
  help: str = ""
@@ -273,12 +274,14 @@ def _setting_from(name: str, annotation, default, has_default: bool, doc_hints:
273
274
  kind = "int"
274
275
  elif annotation is float:
275
276
  kind = "float"
277
+ elif annotation is Column:
278
+ kind = "column"
276
279
  elif annotation is str:
277
280
  kind = "str"
278
281
  else:
279
282
  raise BoxError(
280
283
  f"parameter {name!r} has type {_type_text(annotation)}, which is neither a OpenProcess "
281
- f"type (an input) nor int, float, bool, str, Literal[...] or Path (a setting)")
284
+ f"type (an input) nor int, float, bool, str, Literal[...], Column or Path (a setting)")
282
285
  if not has_default:
283
286
  raise BoxError(f"setting {name!r} needs a default value (e.g. {name}: {kind} = ...)")
284
287
  if kind == "float" and isinstance(default, int) and not isinstance(default, bool):
@@ -17,7 +17,7 @@ from ...mining.log import EventLog
17
17
  from ...mining.petrinet import PetriNet
18
18
  from ..box import box
19
19
  from ..convert import convert
20
- from ..types import Figure, Scores, Table
20
+ from ..types import Column, Figure, Scores, Table
21
21
 
22
22
 
23
23
  @box(name="Describe log", group="Science", needs="pandas")
@@ -109,12 +109,14 @@ def correlate(table: Table) -> Table:
109
109
 
110
110
  @box(name="Plot", group="Science", needs="matplotlib")
111
111
  def plot(scores: list[Scores] = (), tables: list[Table] = (),
112
- x: str = "", y: str = "") -> Figure:
113
- """Bars per model for scores, histograms for bootstrap tables, or a line
114
- of column *y* against column *x* of a table (a sweep). The figure is a
112
+ x: Column = "", y: Column = "") -> Figure:
113
+ """Bars per model for scores, histograms for bootstrap tables, or column
114
+ *y* of a table against column *x*: bars when *x* holds names (a model per
115
+ bar), a line when it holds numbers (a sweep). Once *x* and *y* are set
116
+ they decide the figure, whatever else is connected. The figure is a
115
117
  matplotlib figure, converted to SVG; its code is this box's code.
116
118
 
117
- x: a column of the table to put on the x axis (lines only)
119
+ x: a column of the table to put on the x axis
118
120
  y: the column to plot against it
119
121
  """
120
122
  import matplotlib
@@ -122,7 +124,24 @@ def plot(scores: list[Scores] = (), tables: list[Table] = (),
122
124
  import matplotlib.pyplot as plt
123
125
  fig, ax = plt.subplots(figsize=(5.5, 3.2))
124
126
  scores, tables = list(scores), list(tables)
125
- if scores:
127
+ if tables and x and y:
128
+ for table in tables:
129
+ for name in (x, y):
130
+ if name not in table.columns:
131
+ raise ValueError(f"{table.name} has no column {name!r}; it has {', '.join(table.columns)}")
132
+ numeric = all(isinstance(v, (int, float)) and not isinstance(v, bool) for t in tables for v in t.column(x))
133
+ for table in tables:
134
+ values = [float(v) for v in table.column(y)]
135
+ if numeric:
136
+ ax.plot(table.column(x), values, marker="o", label=table.name)
137
+ else:
138
+ ax.bar([str(v) for v in table.column(x)], values, label=table.name)
139
+ ax.set_xlabel(x)
140
+ ax.set_ylabel(y)
141
+ if len(tables) > 1:
142
+ ax.legend(fontsize=8)
143
+ caption = f"{y} against {x}."
144
+ elif scores:
126
145
  metrics = [m for m in dict.fromkeys(k for s in scores for k in s.metrics)
127
146
  if all(isinstance(s.metrics.get(m, 0.0), (int, float)) for s in scores)]
128
147
  width = 0.8 / max(1, len(metrics))
@@ -134,14 +153,6 @@ def plot(scores: list[Scores] = (), tables: list[Table] = (),
134
153
  ax.set_ylim(0, 1.05)
135
154
  ax.legend(fontsize=8)
136
155
  caption = f"{len(scores)} models, {len(metrics)} scores each."
137
- elif tables and x and y:
138
- for table in tables:
139
- ax.plot(table.column(x), table.column(y), marker="o", label=table.name)
140
- ax.set_xlabel(x)
141
- ax.set_ylabel(y)
142
- if len(tables) > 1:
143
- ax.legend(fontsize=8)
144
- caption = f"{y} against {x}."
145
156
  elif tables:
146
157
  for table in tables:
147
158
  if table.samples:
@@ -150,7 +161,7 @@ def plot(scores: list[Scores] = (), tables: list[Table] = (),
150
161
  ax.legend(fontsize=8)
151
162
  caption = f"{len(tables)} bootstrap distribution{'s' if len(tables) != 1 else ''}."
152
163
  else:
153
- raise ValueError("Connect scores, or tables with samples or an x and y column")
164
+ raise ValueError("Connect scores, or tables with samples, or a table and set both x and y")
154
165
  fig.tight_layout()
155
166
  figure = convert(fig, Figure)
156
167
  plt.close(fig)
@@ -0,0 +1,113 @@
1
+ """Undo and redo for a workflow, by snapshots.
2
+
3
+ A workflow is small (its dict form, :meth:`Workflow.to_dict`, is a few
4
+ kilobytes), so every change keeps the whole state before it: adding,
5
+ removing, wiring, moving or grouping boxes, a setting, a rename. Undo puts
6
+ a snapshot back; the page then runs the boxes again, and the cache serves
7
+ the ones that did not change. Snapshots are taken where the page already
8
+ learns of a change, so no edit is instrumented by hand, and a box author
9
+ never sees any of this.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import time
15
+
16
+ #: How many steps back are kept.
17
+ LIMIT = 100
18
+ #: A drag arrives as several moves; those within this many seconds are one step.
19
+ MOVE_SECONDS = 1.5
20
+
21
+
22
+ def describe(before: dict, after: dict) -> str:
23
+ """What changed from ``before`` to ``after``, for "Undo adding a box"."""
24
+ b = {n["id"]: n for n in before.get("boxes", [])}
25
+ a = {n["id"]: n for n in after.get("boxes", [])}
26
+ added, removed = set(a) - set(b), set(b) - set(a)
27
+ if added:
28
+ return "adding a box" if len(added) == 1 else f"adding {len(added)} boxes"
29
+ if removed:
30
+ return "removing a box" if len(removed) == 1 else f"removing {len(removed)} boxes"
31
+ if before.get("connections") != after.get("connections"):
32
+ return "a connection"
33
+ if before.get("groups") != after.get("groups"):
34
+ return "grouping"
35
+ for node_id in a:
36
+ if a[node_id].get("settings") != b[node_id].get("settings"):
37
+ return "a setting"
38
+ if a[node_id].get("title", "") != b[node_id].get("title", ""):
39
+ return "a rename"
40
+ if any(a[i].get("position") != b[i].get("position") for i in a):
41
+ return "a move"
42
+ if any(a[i].get("layout") != b[i].get("layout") for i in a):
43
+ return "tidying a drawing"
44
+ if before.get("name") != after.get("name"):
45
+ return "a rename"
46
+ return "a change"
47
+
48
+
49
+ class History:
50
+ """The states a workflow went through; :attr:`state` is the current one."""
51
+
52
+ def __init__(self, snapshot: dict, limit: int = LIMIT) -> None:
53
+ self.state = snapshot
54
+ self.limit = limit
55
+ self.undo_stack: list[dict] = []
56
+ self.redo_stack: list[dict] = []
57
+ self._last: tuple[str | None, float] = (None, 0.0)
58
+
59
+ def record(self, snapshot: dict, kind: str = "change", now: float | None = None) -> bool:
60
+ """The workflow is now ``snapshot``; the state before it becomes undoable.
61
+ Returns whether anything changed. ``kind`` "move" folds a drag's
62
+ steps into one."""
63
+ if snapshot == self.state:
64
+ return False
65
+ now = time.monotonic() if now is None else now
66
+ last_kind, last_time = self._last
67
+ if not (kind == "move" and last_kind == "move" and now - last_time < MOVE_SECONDS and self.undo_stack):
68
+ self.undo_stack.append(self.state)
69
+ del self.undo_stack[:-self.limit]
70
+ self.redo_stack.clear()
71
+ self.state = snapshot
72
+ self._last = (kind, now)
73
+ return True
74
+
75
+ @property
76
+ def can_undo(self) -> bool:
77
+ return bool(self.undo_stack)
78
+
79
+ @property
80
+ def can_redo(self) -> bool:
81
+ return bool(self.redo_stack)
82
+
83
+ @property
84
+ def undo_label(self) -> str:
85
+ return describe(self.undo_stack[-1], self.state) if self.undo_stack else ""
86
+
87
+ @property
88
+ def redo_label(self) -> str:
89
+ return describe(self.state, self.redo_stack[-1]) if self.redo_stack else ""
90
+
91
+ def undo(self) -> tuple[str, dict] | None:
92
+ """Step back: (what is undone, the state to restore), or None."""
93
+ if not self.undo_stack:
94
+ return None
95
+ before = self.undo_stack.pop()
96
+ label = describe(before, self.state)
97
+ self.redo_stack.append(self.state)
98
+ self.state = before
99
+ self._last = (None, 0.0)
100
+ return label, before
101
+
102
+ def redo(self) -> tuple[str, dict] | None:
103
+ if not self.redo_stack:
104
+ return None
105
+ after = self.redo_stack.pop()
106
+ label = describe(self.state, after)
107
+ self.undo_stack.append(self.state)
108
+ self.state = after
109
+ self._last = (None, 0.0)
110
+ return label, after
111
+
112
+
113
+ __all__ = ["History", "describe", "LIMIT", "MOVE_SECONDS"]