openprocess 0.10.6__tar.gz → 0.11.3__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.
- {openprocess-0.10.6/openprocess.egg-info → openprocess-0.11.3}/PKG-INFO +46 -20
- {openprocess-0.10.6 → openprocess-0.11.3}/README.md +45 -19
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/__init__.py +1 -1
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/cli.py +2 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/boxes/check.py +6 -1
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/boxes/science.py +24 -13
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/convert.py +76 -8
- openprocess-0.11.3/openprocess/flow/history.py +113 -0
- openprocess-0.11.3/openprocess/flow/provenance.py +110 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/record.py +67 -3
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/runner.py +10 -0
- openprocess-0.11.3/openprocess/flow/script.py +87 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/types.py +10 -1
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/workflow.py +18 -1
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/canvas.py +10 -5
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/flow/canvas.py +2 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/flow/page.py +83 -3
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/flow/picker.py +113 -88
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/flow/viewers.py +87 -8
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/app.py +156 -33
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/connections.py +26 -1
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/cpn_page.py +55 -25
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/definition_view.py +9 -2
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/derivation_view.py +3 -1
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/graph_view.py +55 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/markdown_view.py +3 -1
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/model_page.py +21 -4
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/petri_page.py +5 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/sidebar.py +6 -4
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/style.py +2 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/tool_icons.py +27 -20
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/conformance/alignments.py +86 -7
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/discovery/inductive.py +133 -24
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/references.py +5 -2
- {openprocess-0.10.6 → openprocess-0.11.3/openprocess.egg-info}/PKG-INFO +46 -20
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess.egg-info/SOURCES.txt +4 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_exercises_gui.py +6 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_flow.py +139 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_flow_gui.py +141 -9
- openprocess-0.11.3/tests/test_history.py +57 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_mining.py +71 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_studio_gui.py +175 -4
- {openprocess-0.10.6 → openprocess-0.11.3}/LICENSE +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/cpnpy/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/analysis/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/analysis/state_space.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/analysis/state_space_process.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/citation.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/exercises/pack.md +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/box.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/boxes/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/boxes/compare.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/boxes/cpn.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/boxes/discover.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/boxes/filter.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/boxes/input.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/boxes/output.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/boxes/predict.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/boxes/sweeps.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/datasets.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/explain.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/figures.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/library.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/report.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/reproducibility.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/snapshots.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/flow/sweep.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/app.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/arc_editing.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/flow/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/flow/runs.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/flow/stepper.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/flow/summary.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/flow/templates.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/items.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/learn/answer_boxes.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/learn/concealment.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/learn/mode.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/panning.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/resources/openprocess-icon.png +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/__main__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/charts.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/cite.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/command_palette.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/compare_page.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/documents.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/dotted_chart.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/file_dialogs.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/filter_dialog.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/graph_builders.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/instances.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/log_editor.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/log_page.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/mathtext.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/ml_highlighter.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/motion.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/net_comparison.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/notes_overlay.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/provenance.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/regions_view.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/spaces.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/updates.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/widgets.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/workers.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/studio/workspace.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/theme.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/gui/tidy.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/io/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/io/cpn_reader.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/io/cpn_writer.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/learn/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/learn/answers.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/learn/checks.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/learn/computed.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/learn/context.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/learn/exam.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/learn/exercise-packs.md +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/learn/importer.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/learn/notation.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/learn/pack.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/learn/sheet.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/analysis.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/columns.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/compare_nets.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/conformance/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/conformance/quality.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/conformance/token_replay.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/csv_import.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/definitions.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/dfg.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/discovery/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/discovery/alpha.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/discovery/heuristics.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/discovery/state_regions.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/filtering.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/footprint.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/invariants.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/layout.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/log.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/ocel.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/petrinet.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/playout.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/pm4py_bridge.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/pnml.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/processtree.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/regions.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/stats.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/structure.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/transition_system.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/mining/xes.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/ml/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/ml/ast_nodes.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/ml/builtins.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/ml/colorsets.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/ml/errors.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/ml/evaluator.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/ml/lexer.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/ml/multiset.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/ml/parser.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/ml/values.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/model/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/model/declarations.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/model/examples.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/model/net.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/model/plain.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/packages.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/sim/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/sim/binding.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/sim/export.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/sim/simulator.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/teaching/__init__.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/teaching/answers.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/teaching/checks.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/teaching/pack.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess/teaching/sheet.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess.egg-info/dependency_links.txt +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess.egg-info/entry_points.txt +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess.egg-info/requires.txt +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/openprocess.egg-info/top_level.txt +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/pyproject.toml +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/setup.cfg +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_arc_editing.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_citation.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_colorsets.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_columns.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_cpn_course_model.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_definitions.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_hierarchy.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_io.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_learn.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_learn_gui.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_log_editing.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_ml.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_multiset.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_ocel.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_packages.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_regions.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_rename.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_report.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_simulation.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_snapshots.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_state_space.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_teaching.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_tidy.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_updates.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_wf_structure.py +0 -0
- {openprocess-0.10.6 → openprocess-0.11.3}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openprocess
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.3
|
|
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
|
|
@@ -281,8 +281,9 @@ which space you were in.
|
|
|
281
281
|
state; it stays in the folder.
|
|
282
282
|
- **Flat lists, the same in both spaces.** The folder's own files first,
|
|
283
283
|
then each subfolder's under a caption naming it ("Week 5 / Part 1").
|
|
284
|
-
|
|
285
|
-
|
|
284
|
+
Click a caption to fold its files away or bring them back; the app
|
|
285
|
+
remembers which are folded, per folder. An empty folder shows in both
|
|
286
|
+
spaces until it has files.
|
|
286
287
|
- **Organise from the app**: right-click for **New Folder…**, **Rename…**,
|
|
287
288
|
**Show in Finder** and **Move to Bin** (recoverable from the Bin; never a
|
|
288
289
|
hard delete). **Drag files onto a subfolder** to move them on disk. An open
|
|
@@ -373,7 +374,9 @@ Nets drawn as in the lectures and the book:
|
|
|
373
374
|
- **Places** are circles with black tokens inside, drawn as dots up to 5 and
|
|
374
375
|
as a number above that.
|
|
375
376
|
- **Transitions** are boxes with their name inside. A **silent (τ)**
|
|
376
|
-
transition is a black bar
|
|
377
|
+
transition is a black bar: the **τ** tool (toolbar or palette) adds one
|
|
378
|
+
with a click, and the Element tab's *Silent* box turns an ordinary
|
|
379
|
+
transition into τ and back.
|
|
377
380
|
- **Arc weights** above 1 are written on the arc.
|
|
378
381
|
- **Names** go inside the places and transitions, which grow to fit them.
|
|
379
382
|
Tick **Names outside** in the toolbar to put them underneath instead; you
|
|
@@ -528,17 +531,19 @@ links to the definitions it builds on; **Help ▸ Definitions** lists them all.
|
|
|
528
531
|
for bounded nets; for unbounded ones, up to a trace length (and it says
|
|
529
532
|
so).
|
|
530
533
|
|
|
531
|
-
| Process map |
|
|
534
|
+
| Process map | Discover in Mine →: the miner is a box, its result beside the canvas |
|
|
532
535
|
|---|---|
|
|
533
536
|
|  |  |
|
|
534
537
|
|
|
535
|
-
| α-algorithm result |
|
|
538
|
+
| α-algorithm result | *How*: the eight steps, from the footprint to α(L) |
|
|
536
539
|
|---|---|
|
|
537
|
-
|  |  |  |
|
|
538
541
|
|
|
539
|
-
|
|
|
542
|
+
| Check fit: every score, and how it was computed | Comparing two logs |
|
|
540
543
|
|---|---|
|
|
541
|
-
|  |  |
|
|
545
|
+
|
|
546
|
+

|
|
542
547
|
|
|
543
548
|
The app follows the system's light or dark appearance:
|
|
544
549
|
|
|
@@ -555,6 +560,8 @@ sidebar, **Learn ▸ Open Exercise Pack…**, or **Learn ▸ Open Demo Exercises
|
|
|
555
560
|
exercises): the window switches to a view made for working through a pack
|
|
556
561
|
without distractions, and *Exit* brings your folder back exactly as it was.
|
|
557
562
|
|
|
563
|
+

|
|
564
|
+
|
|
558
565
|
- **The worksheet** on the left is the question top to bottom, with an
|
|
559
566
|
answer box wherever one is needed: yes/no, multiple choice, a set
|
|
560
567
|
(`{a, b}`, sets of sets, or pairs like `({a}, {b,d})`), a number, a short
|
|
@@ -742,7 +749,10 @@ generated from `openprocess/mining/definitions.py`; after editing a definition,
|
|
|
742
749
|
Likewise [`docs/references.md`](docs/references.md) — every source OpenProcess's
|
|
743
750
|
notation and algorithms follow, also in the app under **Help ▸ References** —
|
|
744
751
|
is generated from `openprocess/references.py` with
|
|
745
|
-
`python -m openprocess.references > docs/references.md`.
|
|
752
|
+
`python -m openprocess.references > docs/references.md`. The screenshots in
|
|
753
|
+
this README come from the app itself: `python docs/make_screenshots.py`
|
|
754
|
+
renders every page offscreen into `docs/screenshots/`, so run it after a
|
|
755
|
+
change to the app's looks.
|
|
746
756
|
|
|
747
757
|
The engines (`openprocess.mining`, `openprocess.ml`, `openprocess.sim`, `openprocess.analysis`)
|
|
748
758
|
have **no dependencies**, so they work in a notebook or a script:
|
|
@@ -758,7 +768,10 @@ print(report.sound, report.findings)
|
|
|
758
768
|
|
|
759
769
|
[**docs/how-it-works.md**](docs/how-it-works.md) covers the architecture, the
|
|
760
770
|
CPN ML subset, binding search, timed nets, the state space, the file format
|
|
761
|
-
and the project layout.
|
|
771
|
+
and the project layout. [**docs/design.md**](docs/design.md) is the *why*:
|
|
772
|
+
the problem, the claim, the three ideas and the decisions that follow from
|
|
773
|
+
them. `tests/cross_check_inductive_miner.py` compares the built-in Inductive
|
|
774
|
+
Miner with PM4Py's on hundreds of logs and reports where they differ.
|
|
762
775
|
|
|
763
776
|
## Workflows
|
|
764
777
|
|
|
@@ -769,17 +782,20 @@ Miner, a fitness check), *Compare discovery* (three miners side by side),
|
|
|
769
782
|
confidence* (bootstrap intervals, a test and a plot; needs the `science`
|
|
770
783
|
extra) and *Predict the next activity*. Or start empty.
|
|
771
784
|
|
|
785
|
+
| The canvas: *Compare discovery*, three miners side by side | Summary: every box's key figure, and the process map |
|
|
786
|
+
|---|---|
|
|
787
|
+
|  |  |
|
|
788
|
+
|
|
772
789
|
- **The page is the canvas.** Its header has **Canvas | Summary**, then
|
|
773
790
|
**+ Add box**, **Run ▶**, and **⋯** for Re-run, Record, Export experiment
|
|
774
791
|
and Save. The sidebar beside it lists the folder's analyses and, folded
|
|
775
792
|
under them, its logs.
|
|
776
|
-
- **+ Add box** (or a double-click on the canvas) opens every box,
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
first match.
|
|
793
|
+
- **+ 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
|
+
scrolls. Under *Input*, **the folder's own files**: one click adds the box
|
|
797
|
+
that reads the file, already set. Type to search them all: "alpha" finds
|
|
798
|
+
the α-algorithm, "boarding" the log; Enter adds the first match.
|
|
783
799
|
- **Files flow into the canvas.** Drop a log, an OCEL 2.0 object-centric log
|
|
784
800
|
(`.jsonocel`), a PNML file, a .cpn or a ts.txt on the canvas and it becomes
|
|
785
801
|
its input box at that spot; an input
|
|
@@ -825,8 +841,18 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
825
841
|
`boxes/`, so it is listed under *Yours* and can be used in any workflow.
|
|
826
842
|
- **Export experiment** (under ⋯) writes a zip with the workflow file and its record,
|
|
827
843
|
the inputs, your boxes, every result as a file (CSV, PNML, SVG, XES), a
|
|
828
|
-
`requirements.lock
|
|
829
|
-
|
|
844
|
+
`requirements.lock`, a `run.py` that runs the same analysis as a plain
|
|
845
|
+
script, and a README that says what was run: supplementary material for a
|
|
846
|
+
paper, which `openprocess run --check` can verify. Drop such a zip on
|
|
847
|
+
OpenProcess and it is unpacked into a folder of its own and run again.
|
|
848
|
+
- **Export as Python script** (under ⋯) writes the analysis as a `.py` file:
|
|
849
|
+
the same boxes, settings and connections, run with `python analysis.py`,
|
|
850
|
+
the results written beside it.
|
|
851
|
+
- **Every result knows where it came from**: a figure saved as SVG carries
|
|
852
|
+
the analysis, the box, its settings and the result's fingerprint in its
|
|
853
|
+
metadata; a CSV gets a `.provenance.json` beside it.
|
|
854
|
+
- **⌘Z and ⇧⌘Z** undo and redo on the canvas: boxes, connections, moves,
|
|
855
|
+
settings, groups.
|
|
830
856
|
- **The side panel** resizes by dragging the gap beside it; double-click the
|
|
831
857
|
gap, or *View ▸ Reset Workflow Layout*, for its default width.
|
|
832
858
|
- **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
|
|
@@ -242,8 +242,9 @@ which space you were in.
|
|
|
242
242
|
state; it stays in the folder.
|
|
243
243
|
- **Flat lists, the same in both spaces.** The folder's own files first,
|
|
244
244
|
then each subfolder's under a caption naming it ("Week 5 / Part 1").
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
Click a caption to fold its files away or bring them back; the app
|
|
246
|
+
remembers which are folded, per folder. An empty folder shows in both
|
|
247
|
+
spaces until it has files.
|
|
247
248
|
- **Organise from the app**: right-click for **New Folder…**, **Rename…**,
|
|
248
249
|
**Show in Finder** and **Move to Bin** (recoverable from the Bin; never a
|
|
249
250
|
hard delete). **Drag files onto a subfolder** to move them on disk. An open
|
|
@@ -334,7 +335,9 @@ Nets drawn as in the lectures and the book:
|
|
|
334
335
|
- **Places** are circles with black tokens inside, drawn as dots up to 5 and
|
|
335
336
|
as a number above that.
|
|
336
337
|
- **Transitions** are boxes with their name inside. A **silent (τ)**
|
|
337
|
-
transition is a black bar
|
|
338
|
+
transition is a black bar: the **τ** tool (toolbar or palette) adds one
|
|
339
|
+
with a click, and the Element tab's *Silent* box turns an ordinary
|
|
340
|
+
transition into τ and back.
|
|
338
341
|
- **Arc weights** above 1 are written on the arc.
|
|
339
342
|
- **Names** go inside the places and transitions, which grow to fit them.
|
|
340
343
|
Tick **Names outside** in the toolbar to put them underneath instead; you
|
|
@@ -489,17 +492,19 @@ links to the definitions it builds on; **Help ▸ Definitions** lists them all.
|
|
|
489
492
|
for bounded nets; for unbounded ones, up to a trace length (and it says
|
|
490
493
|
so).
|
|
491
494
|
|
|
492
|
-
| Process map |
|
|
495
|
+
| Process map | Discover in Mine →: the miner is a box, its result beside the canvas |
|
|
493
496
|
|---|---|
|
|
494
497
|
|  |  |
|
|
495
498
|
|
|
496
|
-
| α-algorithm result |
|
|
499
|
+
| α-algorithm result | *How*: the eight steps, from the footprint to α(L) |
|
|
497
500
|
|---|---|
|
|
498
|
-
|  |  |  |
|
|
499
502
|
|
|
500
|
-
|
|
|
503
|
+
| Check fit: every score, and how it was computed | Comparing two logs |
|
|
501
504
|
|---|---|
|
|
502
|
-
|  |  |
|
|
506
|
+
|
|
507
|
+

|
|
503
508
|
|
|
504
509
|
The app follows the system's light or dark appearance:
|
|
505
510
|
|
|
@@ -516,6 +521,8 @@ sidebar, **Learn ▸ Open Exercise Pack…**, or **Learn ▸ Open Demo Exercises
|
|
|
516
521
|
exercises): the window switches to a view made for working through a pack
|
|
517
522
|
without distractions, and *Exit* brings your folder back exactly as it was.
|
|
518
523
|
|
|
524
|
+

|
|
525
|
+
|
|
519
526
|
- **The worksheet** on the left is the question top to bottom, with an
|
|
520
527
|
answer box wherever one is needed: yes/no, multiple choice, a set
|
|
521
528
|
(`{a, b}`, sets of sets, or pairs like `({a}, {b,d})`), a number, a short
|
|
@@ -703,7 +710,10 @@ generated from `openprocess/mining/definitions.py`; after editing a definition,
|
|
|
703
710
|
Likewise [`docs/references.md`](docs/references.md) — every source OpenProcess's
|
|
704
711
|
notation and algorithms follow, also in the app under **Help ▸ References** —
|
|
705
712
|
is generated from `openprocess/references.py` with
|
|
706
|
-
`python -m openprocess.references > docs/references.md`.
|
|
713
|
+
`python -m openprocess.references > docs/references.md`. The screenshots in
|
|
714
|
+
this README come from the app itself: `python docs/make_screenshots.py`
|
|
715
|
+
renders every page offscreen into `docs/screenshots/`, so run it after a
|
|
716
|
+
change to the app's looks.
|
|
707
717
|
|
|
708
718
|
The engines (`openprocess.mining`, `openprocess.ml`, `openprocess.sim`, `openprocess.analysis`)
|
|
709
719
|
have **no dependencies**, so they work in a notebook or a script:
|
|
@@ -719,7 +729,10 @@ print(report.sound, report.findings)
|
|
|
719
729
|
|
|
720
730
|
[**docs/how-it-works.md**](docs/how-it-works.md) covers the architecture, the
|
|
721
731
|
CPN ML subset, binding search, timed nets, the state space, the file format
|
|
722
|
-
and the project layout.
|
|
732
|
+
and the project layout. [**docs/design.md**](docs/design.md) is the *why*:
|
|
733
|
+
the problem, the claim, the three ideas and the decisions that follow from
|
|
734
|
+
them. `tests/cross_check_inductive_miner.py` compares the built-in Inductive
|
|
735
|
+
Miner with PM4Py's on hundreds of logs and reports where they differ.
|
|
723
736
|
|
|
724
737
|
## Workflows
|
|
725
738
|
|
|
@@ -730,17 +743,20 @@ Miner, a fitness check), *Compare discovery* (three miners side by side),
|
|
|
730
743
|
confidence* (bootstrap intervals, a test and a plot; needs the `science`
|
|
731
744
|
extra) and *Predict the next activity*. Or start empty.
|
|
732
745
|
|
|
746
|
+
| The canvas: *Compare discovery*, three miners side by side | Summary: every box's key figure, and the process map |
|
|
747
|
+
|---|---|
|
|
748
|
+
|  |  |
|
|
749
|
+
|
|
733
750
|
- **The page is the canvas.** Its header has **Canvas | Summary**, then
|
|
734
751
|
**+ Add box**, **Run ▶**, and **⋯** for Re-run, Record, Export experiment
|
|
735
752
|
and Save. The sidebar beside it lists the folder's analyses and, folded
|
|
736
753
|
under them, its logs.
|
|
737
|
-
- **+ Add box** (or a double-click on the canvas) opens every box,
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
first match.
|
|
754
|
+
- **+ 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
|
+
scrolls. Under *Input*, **the folder's own files**: one click adds the box
|
|
758
|
+
that reads the file, already set. Type to search them all: "alpha" finds
|
|
759
|
+
the α-algorithm, "boarding" the log; Enter adds the first match.
|
|
744
760
|
- **Files flow into the canvas.** Drop a log, an OCEL 2.0 object-centric log
|
|
745
761
|
(`.jsonocel`), a PNML file, a .cpn or a ts.txt on the canvas and it becomes
|
|
746
762
|
its input box at that spot; an input
|
|
@@ -786,8 +802,18 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
786
802
|
`boxes/`, so it is listed under *Yours* and can be used in any workflow.
|
|
787
803
|
- **Export experiment** (under ⋯) writes a zip with the workflow file and its record,
|
|
788
804
|
the inputs, your boxes, every result as a file (CSV, PNML, SVG, XES), a
|
|
789
|
-
`requirements.lock
|
|
790
|
-
|
|
805
|
+
`requirements.lock`, a `run.py` that runs the same analysis as a plain
|
|
806
|
+
script, and a README that says what was run: supplementary material for a
|
|
807
|
+
paper, which `openprocess run --check` can verify. Drop such a zip on
|
|
808
|
+
OpenProcess and it is unpacked into a folder of its own and run again.
|
|
809
|
+
- **Export as Python script** (under ⋯) writes the analysis as a `.py` file:
|
|
810
|
+
the same boxes, settings and connections, run with `python analysis.py`,
|
|
811
|
+
the results written beside it.
|
|
812
|
+
- **Every result knows where it came from**: a figure saved as SVG carries
|
|
813
|
+
the analysis, the box, its settings and the result's fingerprint in its
|
|
814
|
+
metadata; a CSV gets a `.provenance.json` beside it.
|
|
815
|
+
- **⌘Z and ⇧⌘Z** undo and redo on the canvas: boxes, connections, moves,
|
|
816
|
+
settings, groups.
|
|
791
817
|
- **The side panel** resizes by dragging the gap beside it; double-click the
|
|
792
818
|
gap, or *View ▸ Reset Workflow Layout*, for its default width.
|
|
793
819
|
- **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
|
|
@@ -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.
|
|
51
|
+
__version__ = "0.11.3"
|
|
52
52
|
|
|
53
53
|
__all__ = [
|
|
54
54
|
"Arc", "BindingElement", "CPNet", "Marking", "Multiset", "Page", "Place",
|
|
@@ -249,6 +249,8 @@ def command_mine_conform(arguments: argparse.Namespace) -> int:
|
|
|
249
249
|
print(f"Generalisation {generalisation(replay):.4f}")
|
|
250
250
|
print(f"Simplicity {simplicity(net):.4f}")
|
|
251
251
|
print(f"Fitting cases {alignments.fitting_traces} / {alignments.trace_count}")
|
|
252
|
+
for note in alignments.notes:
|
|
253
|
+
print(f"Note: {note}")
|
|
252
254
|
return 0
|
|
253
255
|
|
|
254
256
|
|
|
@@ -40,6 +40,8 @@ def check_fit(model: PetriNet, log: EventLog,
|
|
|
40
40
|
alignments = align_log(model, simple)
|
|
41
41
|
fitness = alignments.average_fitness
|
|
42
42
|
flow.note(f"{alignments.fitting_traces} of {alignments.trace_count} cases align without a deviation")
|
|
43
|
+
for note in alignments.notes:
|
|
44
|
+
flow.note(note)
|
|
43
45
|
else:
|
|
44
46
|
fitness = replay.fitness
|
|
45
47
|
flow.note(f"{replay.fitting_traces} of {replay.trace_count} cases replay without missing or remaining tokens")
|
|
@@ -63,8 +65,11 @@ def alignments(model: PetriNet, log: EventLog) -> AlignmentResult:
|
|
|
63
65
|
"""Optimal alignments of every variant with the model (A* over the
|
|
64
66
|
synchronous product, standard cost function)."""
|
|
65
67
|
result = align_log(model, log.simple_log())
|
|
66
|
-
rows = [["⟨" + ", ".join(a.trace) + "⟩", a.count, a.cost, round(a.fitness, 4)]
|
|
68
|
+
rows = [["⟨" + ", ".join(a.trace) + "⟩", a.count, a.cost if a.complete else "?", round(a.fitness, 4)]
|
|
69
|
+
for a in result.alignments]
|
|
67
70
|
flow.show(Table("Alignments", ["variant", "cases", "cost", "fitness"], rows), "Alignment per variant")
|
|
71
|
+
for note in result.notes:
|
|
72
|
+
flow.note(note)
|
|
68
73
|
return result
|
|
69
74
|
|
|
70
75
|
|
|
@@ -110,11 +110,13 @@ def correlate(table: Table) -> Table:
|
|
|
110
110
|
@box(name="Plot", group="Science", needs="matplotlib")
|
|
111
111
|
def plot(scores: list[Scores] = (), tables: list[Table] = (),
|
|
112
112
|
x: Column = "", y: Column = "") -> Figure:
|
|
113
|
-
"""Bars per model for scores, histograms for bootstrap tables, or
|
|
114
|
-
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
@@ -15,6 +15,7 @@ class and the target (a class, or a dotted name).
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import importlib
|
|
18
|
+
import importlib.util
|
|
18
19
|
from typing import Any, Callable
|
|
19
20
|
|
|
20
21
|
from ..mining.log import KEY_NAME, KEY_RESOURCE, KEY_TIME, EventLog, SimpleLog
|
|
@@ -25,11 +26,49 @@ _converters: dict[tuple[str, str], Callable] = {}
|
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def _name(target) -> str:
|
|
29
|
+
"""The name a type is registered under: ``module.qualname`` for a class,
|
|
30
|
+
a dotted string as given. Nothing is imported here, so registering a
|
|
31
|
+
converter for ``"pandas.core.frame.DataFrame"`` costs nothing when pandas
|
|
32
|
+
is absent, and importing this module never imports pandas."""
|
|
28
33
|
if isinstance(target, str):
|
|
29
34
|
return target
|
|
30
35
|
return f"{target.__module__}.{target.__qualname__}"
|
|
31
36
|
|
|
32
37
|
|
|
38
|
+
def _resolve(dotted: str):
|
|
39
|
+
"""The class a dotted name points at, or None when it cannot be found.
|
|
40
|
+
|
|
41
|
+
Tries the longest module prefix first (``a.b.c.D`` as module ``a.b.c``,
|
|
42
|
+
attribute ``D``), then shorter ones, so both ``pandas.DataFrame`` and
|
|
43
|
+
``pandas.core.frame.DataFrame`` resolve to the same class. A name whose
|
|
44
|
+
top-level package is not installed resolves to None without importing
|
|
45
|
+
anything.
|
|
46
|
+
"""
|
|
47
|
+
parts = dotted.split(".")
|
|
48
|
+
if len(parts) < 2 or importlib.util.find_spec(parts[0]) is None:
|
|
49
|
+
return None
|
|
50
|
+
for split in range(len(parts) - 1, 0, -1):
|
|
51
|
+
try:
|
|
52
|
+
obj = importlib.import_module(".".join(parts[:split]))
|
|
53
|
+
except ImportError:
|
|
54
|
+
continue
|
|
55
|
+
for attribute in parts[split:]:
|
|
56
|
+
obj = getattr(obj, attribute, None)
|
|
57
|
+
if obj is None:
|
|
58
|
+
break
|
|
59
|
+
else:
|
|
60
|
+
if isinstance(obj, type):
|
|
61
|
+
return obj
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _canonical(name: str) -> str:
|
|
66
|
+
"""``name`` as the class it points at is defined (``module.qualname``),
|
|
67
|
+
or ``name`` itself when it points at nothing importable."""
|
|
68
|
+
resolved = _resolve(name)
|
|
69
|
+
return name if resolved is None else _name(resolved)
|
|
70
|
+
|
|
71
|
+
|
|
33
72
|
def register(source, target, function: Callable) -> None:
|
|
34
73
|
"""``function(value) -> converted`` for a value of class ``source``."""
|
|
35
74
|
_converters[(_name(source), _name(target))] = function
|
|
@@ -39,20 +78,49 @@ def _class_names(value) -> list[str]:
|
|
|
39
78
|
return [f"{cls.__module__}.{cls.__qualname__}" for cls in type(value).__mro__]
|
|
40
79
|
|
|
41
80
|
|
|
81
|
+
def _find(value: Any, target) -> Callable | None:
|
|
82
|
+
"""The registered converter from ``value``'s class (or a base class) to
|
|
83
|
+
``target``, or None.
|
|
84
|
+
|
|
85
|
+
A class may be named by where it is imported from (``"pandas.DataFrame"``)
|
|
86
|
+
or where it is defined (``"pandas.core.frame.DataFrame"``, which pandas
|
|
87
|
+
2 reports as the class's module, while pandas 3 reports ``"pandas"``).
|
|
88
|
+
Both the source and the target are matched by resolving the names to the
|
|
89
|
+
class. Only registered names in the same top-level package as the value
|
|
90
|
+
or the target are resolved, so looking up a pandas conversion imports
|
|
91
|
+
pandas and nothing else, and only once a conversion is asked for.
|
|
92
|
+
"""
|
|
93
|
+
wanted = _name(target)
|
|
94
|
+
wanted_names = {wanted, _canonical(wanted)}
|
|
95
|
+
target_package = wanted.split(".")[0]
|
|
96
|
+
value_names = set(_class_names(value))
|
|
97
|
+
value_package = type(value).__module__.split(".")[0]
|
|
98
|
+
|
|
99
|
+
def matches(registered: str, names: set[str], package: str) -> bool:
|
|
100
|
+
if registered in names:
|
|
101
|
+
return True
|
|
102
|
+
return registered.split(".")[0] == package and _canonical(registered) in names
|
|
103
|
+
|
|
104
|
+
for (source, registered), function in _converters.items():
|
|
105
|
+
if matches(source, value_names, value_package) and \
|
|
106
|
+
matches(registered, wanted_names, target_package):
|
|
107
|
+
return function
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
42
111
|
def can_convert(value: Any, target) -> bool:
|
|
43
|
-
return
|
|
112
|
+
return _name(target) in _class_names(value) or _find(value, target) is not None
|
|
44
113
|
|
|
45
114
|
|
|
46
115
|
def convert(value: Any, target):
|
|
47
116
|
"""Convert ``value`` to ``target`` (a class or a dotted name)."""
|
|
48
117
|
wanted = _name(target)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
raise TypeError(f"No converter from {type(value).__name__} to {wanted}")
|
|
118
|
+
if wanted in _class_names(value):
|
|
119
|
+
return value
|
|
120
|
+
function = _find(value, target)
|
|
121
|
+
if function is None:
|
|
122
|
+
raise TypeError(f"No converter from {type(value).__name__} to {wanted}")
|
|
123
|
+
return function(value)
|
|
56
124
|
|
|
57
125
|
|
|
58
126
|
def converters() -> list[tuple[str, str]]:
|
|
@@ -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"]
|