raptor-studio 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- raptor/__init__.py +4 -0
- raptor/api/__init__.py +5 -0
- raptor/api/analyse.py +624 -0
- raptor/api/annotations.py +554 -0
- raptor/api/batch.py +104 -0
- raptor/api/build.py +3386 -0
- raptor/api/distributions.py +94 -0
- raptor/api/estimate.py +660 -0
- raptor/api/importance.py +452 -0
- raptor/api/inspect.py +273 -0
- raptor/api/isolate.py +572 -0
- raptor/api/limits.py +74 -0
- raptor/api/markdown.py +833 -0
- raptor/api/metric_definitions.py +122 -0
- raptor/api/model_diff.py +1115 -0
- raptor/api/paths.py +294 -0
- raptor/api/presentation.py +433 -0
- raptor/api/rates.py +345 -0
- raptor/api/render.py +195 -0
- raptor/api/report_html.py +478 -0
- raptor/api/results.py +153 -0
- raptor/api/run.py +1178 -0
- raptor/api/run_tables.py +134 -0
- raptor/api/series.py +417 -0
- raptor/api/stepping.py +107 -0
- raptor/api/table_export.py +315 -0
- raptor/api/tables.py +794 -0
- raptor/api/validate.py +869 -0
- raptor/api/workspace.py +1308 -0
- raptor/bundled.py +40 -0
- raptor/cli/__init__.py +6 -0
- raptor/cli/__main__.py +3 -0
- raptor/cli/cmd_gui.py +52 -0
- raptor/cli/cmd_info.py +242 -0
- raptor/cli/cmd_mcp.py +23 -0
- raptor/cli/cmd_misc.py +225 -0
- raptor/cli/cmd_run.py +370 -0
- raptor/cli/cmd_tools.py +649 -0
- raptor/cli/common.py +227 -0
- raptor/cli/exitcodes.py +30 -0
- raptor/cli/main.py +196 -0
- raptor/cli/manpages.py +321 -0
- raptor/cli/runopts.py +118 -0
- raptor/core/__init__.py +0 -0
- raptor/core/annotations.py +69 -0
- raptor/core/dev_options.py +220 -0
- raptor/core/diagnostics.py +50 -0
- raptor/core/distro_rules.py +365 -0
- raptor/core/engine/__init__.py +0 -0
- raptor/core/engine/accounting.py +174 -0
- raptor/core/engine/block_env.py +168 -0
- raptor/core/engine/block_lifecycle.py +4039 -0
- raptor/core/engine/blocks.py +369 -0
- raptor/core/engine/capacity.py +785 -0
- raptor/core/engine/cascade.py +198 -0
- raptor/core/engine/cascade_cold.py +278 -0
- raptor/core/engine/cascade_dependency.py +206 -0
- raptor/core/engine/cascade_phase.py +361 -0
- raptor/core/engine/cascade_status.py +269 -0
- raptor/core/engine/cold_standby.py +931 -0
- raptor/core/engine/costs.py +330 -0
- raptor/core/engine/cumulative.py +259 -0
- raptor/core/engine/data_files.py +92 -0
- raptor/core/engine/dependency.py +1090 -0
- raptor/core/engine/diststats.py +158 -0
- raptor/core/engine/domain_tree.py +99 -0
- raptor/core/engine/driver.py +890 -0
- raptor/core/engine/errors.py +75 -0
- raptor/core/engine/eventlog.py +186 -0
- raptor/core/engine/events.py +201 -0
- raptor/core/engine/fev.py +555 -0
- raptor/core/engine/fmeca.py +185 -0
- raptor/core/engine/graph.py +645 -0
- raptor/core/engine/interrupts.py +63 -0
- raptor/core/engine/invariants.py +80 -0
- raptor/core/engine/kickoff.py +1131 -0
- raptor/core/engine/load.py +245 -0
- raptor/core/engine/model_checks.py +134 -0
- raptor/core/engine/negative_draws.py +35 -0
- raptor/core/engine/nodes.py +719 -0
- raptor/core/engine/orchestrator.py +1104 -0
- raptor/core/engine/phasing.py +244 -0
- raptor/core/engine/pool.py +570 -0
- raptor/core/engine/report_format.py +376 -0
- raptor/core/engine/reporting.py +540 -0
- raptor/core/engine/reports.py +1852 -0
- raptor/core/engine/run_control.py +97 -0
- raptor/core/engine/runner.py +678 -0
- raptor/core/engine/sim_details.py +130 -0
- raptor/core/engine/spf.py +90 -0
- raptor/core/engine/spf_fev.py +192 -0
- raptor/core/engine/stats.py +130 -0
- raptor/core/engine/step_session.py +160 -0
- raptor/core/engine/system_status.py +533 -0
- raptor/core/engine/triggers.py +148 -0
- raptor/core/engine/wait_sites.py +201 -0
- raptor/core/engine/weak_metric.py +283 -0
- raptor/core/fileio/__init__.py +0 -0
- raptor/core/fileio/builder.py +553 -0
- raptor/core/fileio/legacy_layout.py +868 -0
- raptor/core/fileio/phase_edit.py +221 -0
- raptor/core/fileio/rbd.py +900 -0
- raptor/core/fileio/rbdlib.py +410 -0
- raptor/core/fileio/rbds.py +877 -0
- raptor/core/fileio/rbl.py +322 -0
- raptor/core/fileio/typed.py +548 -0
- raptor/core/fileio/view_transform.py +307 -0
- raptor/core/fileio/xlsx_writer.py +284 -0
- raptor/core/input_rules.py +1050 -0
- raptor/core/model.py +635 -0
- raptor/core/rng.py +476 -0
- raptor/core/run_experiment.py +441 -0
- raptor/core/run_manifest.py +148 -0
- raptor/core/scheduler.py +1121 -0
- raptor/gui/__init__.py +1 -0
- raptor/gui/app.py +201 -0
- raptor/gui/clipboard_plan.py +287 -0
- raptor/gui/command_table.py +1263 -0
- raptor/gui/commands.py +553 -0
- raptor/gui/cores.py +40 -0
- raptor/gui/dev_inspector.py +157 -0
- raptor/gui/dev_settings.py +53 -0
- raptor/gui/examples.py +91 -0
- raptor/gui/hierarchy_plan.py +448 -0
- raptor/gui/model_edit.py +645 -0
- raptor/gui/new_model.py +144 -0
- raptor/gui/open_models.py +100 -0
- raptor/gui/resources/__init__.py +1 -0
- raptor/gui/resources/design_tokens.py +945 -0
- raptor/gui/resources/examples/AirlinerMission_dark.png +0 -0
- raptor/gui/resources/examples/AirlinerMission_light.png +0 -0
- raptor/gui/resources/examples/Bakery1_dark.png +0 -0
- raptor/gui/resources/examples/Bakery1_light.png +0 -0
- raptor/gui/resources/examples/CarCare0_dark.png +0 -0
- raptor/gui/resources/examples/CarCare0_light.png +0 -0
- raptor/gui/resources/examples/CarCare1_dark.png +0 -0
- raptor/gui/resources/examples/CarCare1_light.png +0 -0
- raptor/gui/resources/examples/CarWash1_dark.png +0 -0
- raptor/gui/resources/examples/CarWash1_light.png +0 -0
- raptor/gui/resources/examples/DC1_dark.png +0 -0
- raptor/gui/resources/examples/DC1_light.png +0 -0
- raptor/gui/resources/examples/DC2_dark.png +0 -0
- raptor/gui/resources/examples/DC2_light.png +0 -0
- raptor/gui/resources/examples/Drone1_dark.png +0 -0
- raptor/gui/resources/examples/Drone1_light.png +0 -0
- raptor/gui/resources/examples/Espresso1_dark.png +0 -0
- raptor/gui/resources/examples/Espresso1_light.png +0 -0
- raptor/gui/resources/examples/EspressoDep1_dark.png +0 -0
- raptor/gui/resources/examples/EspressoDep1_light.png +0 -0
- raptor/gui/resources/examples/FinaleShow1_dark.png +0 -0
- raptor/gui/resources/examples/FinaleShow1_light.png +0 -0
- raptor/gui/resources/examples/Flight1_dark.png +0 -0
- raptor/gui/resources/examples/Flight1_light.png +0 -0
- raptor/gui/resources/examples/HospitalPower_dark.png +0 -0
- raptor/gui/resources/examples/HospitalPower_light.png +0 -0
- raptor/gui/resources/examples/Lantern1_dark.png +0 -0
- raptor/gui/resources/examples/Lantern1_light.png +0 -0
- raptor/gui/resources/examples/Laundromat1_dark.png +0 -0
- raptor/gui/resources/examples/Laundromat1_light.png +0 -0
- raptor/gui/resources/examples/ProdLine0_dark.png +0 -0
- raptor/gui/resources/examples/ProdLine0_light.png +0 -0
- raptor/gui/resources/examples/ProdLine1_dark.png +0 -0
- raptor/gui/resources/examples/ProdLine1_light.png +0 -0
- raptor/gui/resources/examples/TacoTruck1_dark.png +0 -0
- raptor/gui/resources/examples/TacoTruck1_light.png +0 -0
- raptor/gui/resources/examples/Toaster_Expo_dark.png +0 -0
- raptor/gui/resources/examples/Toaster_Expo_light.png +0 -0
- raptor/gui/resources/examples/Toaster_Fixed_dark.png +0 -0
- raptor/gui/resources/examples/Toaster_Fixed_light.png +0 -0
- raptor/gui/resources/examples/Toaster_Weibull_dark.png +0 -0
- raptor/gui/resources/examples/Toaster_Weibull_light.png +0 -0
- raptor/gui/resources/examples/Van1_dark.png +0 -0
- raptor/gui/resources/examples/Van1_light.png +0 -0
- raptor/gui/resources/examples/VanFlat_dark.png +0 -0
- raptor/gui/resources/examples/VanFlat_light.png +0 -0
- raptor/gui/resources/examples/Vendo1_dark.png +0 -0
- raptor/gui/resources/examples/Vendo1_light.png +0 -0
- raptor/gui/resources/examples/Water1_dark.png +0 -0
- raptor/gui/resources/examples/Water1_light.png +0 -0
- raptor/gui/resources/examples/Water2_dark.png +0 -0
- raptor/gui/resources/examples/Water2_light.png +0 -0
- raptor/gui/resources/examples/WaterTreatment_dark.png +0 -0
- raptor/gui/resources/examples/WaterTreatment_light.png +0 -0
- raptor/gui/resources/icons.py +2147 -0
- raptor/gui/resources/indicator_images.py +96 -0
- raptor/gui/resources/raptor_icon.png +0 -0
- raptor/gui/resources/raptor_logo.png +0 -0
- raptor/gui/row_ops.py +152 -0
- raptor/gui/ui/__init__.py +1 -0
- raptor/gui/ui/backstage.py +743 -0
- raptor/gui/ui/batch_dialogs.py +1114 -0
- raptor/gui/ui/block_labels.py +202 -0
- raptor/gui/ui/block_library.py +68 -0
- raptor/gui/ui/contrast.py +44 -0
- raptor/gui/ui/data_tables.py +1848 -0
- raptor/gui/ui/diagram.py +6043 -0
- raptor/gui/ui/diagram_annotations.py +908 -0
- raptor/gui/ui/diagram_commands.py +1125 -0
- raptor/gui/ui/diagram_common.py +65 -0
- raptor/gui/ui/diagram_items.py +1886 -0
- raptor/gui/ui/diagram_live.py +889 -0
- raptor/gui/ui/diagram_menu.py +324 -0
- raptor/gui/ui/dialogs.py +1834 -0
- raptor/gui/ui/diff_dialog.py +389 -0
- raptor/gui/ui/distro_combo.py +98 -0
- raptor/gui/ui/distro_editor.py +340 -0
- raptor/gui/ui/empirical_dialog.py +180 -0
- raptor/gui/ui/event_log_model.py +155 -0
- raptor/gui/ui/explorer.py +239 -0
- raptor/gui/ui/expr_eval.py +199 -0
- raptor/gui/ui/fev_mixin.py +383 -0
- raptor/gui/ui/filedialogs.py +150 -0
- raptor/gui/ui/help_viewer.py +415 -0
- raptor/gui/ui/inspector.py +3790 -0
- raptor/gui/ui/isolate_mixin.py +168 -0
- raptor/gui/ui/main_window.py +9290 -0
- raptor/gui/ui/modality.py +17 -0
- raptor/gui/ui/msgbox.py +27 -0
- raptor/gui/ui/param_entry.py +148 -0
- raptor/gui/ui/phasing_dialog.py +1027 -0
- raptor/gui/ui/plots.py +695 -0
- raptor/gui/ui/pool_dialogs.py +684 -0
- raptor/gui/ui/preflight.py +282 -0
- raptor/gui/ui/printing.py +215 -0
- raptor/gui/ui/qtcompat.py +8 -0
- raptor/gui/ui/rates_dialog.py +428 -0
- raptor/gui/ui/report_pdf.py +263 -0
- raptor/gui/ui/result_streams.py +120 -0
- raptor/gui/ui/results.py +3626 -0
- raptor/gui/ui/ribbon_builders.py +723 -0
- raptor/gui/ui/rule_gate.py +127 -0
- raptor/gui/ui/run_eta.py +131 -0
- raptor/gui/ui/run_experiment_mixin.py +742 -0
- raptor/gui/ui/run_panel.py +1050 -0
- raptor/gui/ui/settings.py +293 -0
- raptor/gui/ui/shortcuts_window.py +87 -0
- raptor/gui/ui/splash.py +165 -0
- raptor/gui/ui/table_export.py +177 -0
- raptor/gui/ui/tables_dialog.py +345 -0
- raptor/gui/ui/units_conv.py +78 -0
- raptor/gui/widgets/__init__.py +1 -0
- raptor/gui/widgets/ribbon.py +725 -0
- raptor/mcp/__init__.py +1 -0
- raptor/mcp/__main__.py +4 -0
- raptor/mcp/async_runs.py +160 -0
- raptor/mcp/context.py +151 -0
- raptor/mcp/docs.py +195 -0
- raptor/mcp/examples.py +62 -0
- raptor/mcp/http_transport.py +302 -0
- raptor/mcp/jobs.py +103 -0
- raptor/mcp/output_schemas.json +1311 -0
- raptor/mcp/prompts.py +66 -0
- raptor/mcp/resources.py +220 -0
- raptor/mcp/result.py +337 -0
- raptor/mcp/schemas.py +378 -0
- raptor/mcp/server.py +500 -0
- raptor/mcp/tools_data.py +192 -0
- raptor/mcp/tools_metrics.py +150 -0
- raptor/mcp/tools_model.py +1930 -0
- raptor/mcp/tools_run.py +866 -0
- raptor/mcp/tools_tables.py +204 -0
- raptor/mcp/tools_view.py +123 -0
- raptor/render/__init__.py +10 -0
- raptor/render/scene_pixmap.py +114 -0
- raptor/render/worker.py +540 -0
- raptor_studio-0.8.0.data/data/docs/IEC61078.md +76 -0
- raptor_studio-0.8.0.data/data/docs/KEYBOARD_SHORTCUTS.md +91 -0
- raptor_studio-0.8.0.data/data/docs/MCP.md +222 -0
- raptor_studio-0.8.0.data/data/docs/RBDS_FORMAT.md +561 -0
- raptor_studio-0.8.0.data/data/docs/formulas.html +690 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/LICENSE +202 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/core.js +1 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/input/asciimath.js +1 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/tex.js +1 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_AMS-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Bold.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Fraktur-Bold.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Fraktur-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Bold.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Italic.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-BoldItalic.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-Italic.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Bold.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Italic.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Script-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Size1-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Size2-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Size3-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Size4-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Typewriter-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Vector-Bold.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Vector-Regular.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Zero.woff +0 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml.js +1 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax/startup.js +1 -0
- raptor_studio-0.8.0.data/data/docs/js/mathjax-config.js +7 -0
- raptor_studio-0.8.0.data/data/docs/manual.html +2639 -0
- raptor_studio-0.8.0.data/data/docs/ops_asciidoctor.css +556 -0
- raptor_studio-0.8.0.data/data/new_docs/book.adoc +70 -0
- raptor_studio-0.8.0.data/data/new_docs/book.html +11742 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/appendix-developer-options.adoc +72 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/appendix-glossary.adoc +209 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/appendix-iec61078.adoc +71 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch-capstone-flight.adoc +341 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch-capstone-line.adoc +256 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch-dependency.adoc +213 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch-events.adoc +186 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch-phasing.adoc +243 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch-transients.adoc +207 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch00-welcome.adoc +185 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch01-first-model.adoc +311 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch02-redundancy.adoc +181 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch03-voting.adoc +163 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch04-distributions.adoc +191 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch05-repair.adoc +195 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch06-many-trials.adoc +202 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch07-sparing.adoc +167 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch08-standby.adoc +158 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch09-preventive.adoc +188 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch10-hierarchy.adoc +286 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch11-capacity.adoc +192 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch12-cost.adoc +168 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch13-weaklink.adoc +241 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch14-capstone.adoc +296 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch15-debugger.adoc +211 -0
- raptor_studio-0.8.0.data/data/new_docs/chapters/ch16-mcp.adoc +1522 -0
- raptor_studio-0.8.0.data/data/new_docs/images/app-first-look.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/bakery-loaded.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/carcare-results.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/carwash-loaded.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/dc-loaded.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/dc-power-inside.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/dist-shapes.svg +1 -0
- raptor_studio-0.8.0.data/data/new_docs/images/drone-loaded.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/espresso-loaded.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/espressodep-badge.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/finale-loaded.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/flight1-loaded.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/flight1-results.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/kofn-drone.svg +53 -0
- raptor_studio-0.8.0.data/data/new_docs/images/lantern-loaded.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/lantern-results.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/laundromat-naive.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/parallel-pumps.svg +36 -0
- raptor_studio-0.8.0.data/data/new_docs/images/prodline-loaded.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/prodline1-results.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/runs_converge.svg +1 -0
- raptor_studio-0.8.0.data/data/new_docs/images/series-lantern.svg +25 -0
- raptor_studio-0.8.0.data/data/new_docs/images/simulate-cores.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/start-new.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/start-page.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/step-fail.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/step-ribbon.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/step-start.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/sweep-dialog.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/tacotruck-loaded.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/van-engine-inside.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/van-loaded.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/van-tires-inside.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/vendo-data-tables.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/vendo-results.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/water-loaded.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/images/water2-sysconfig.png +0 -0
- raptor_studio-0.8.0.data/data/new_docs/models/AirlinerMission.rbds +2121 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Bakery1.rbds +658 -0
- raptor_studio-0.8.0.data/data/new_docs/models/CarCare0.rbds +342 -0
- raptor_studio-0.8.0.data/data/new_docs/models/CarCare1.rbds +342 -0
- raptor_studio-0.8.0.data/data/new_docs/models/CarWash1.rbds +494 -0
- raptor_studio-0.8.0.data/data/new_docs/models/DC1.rbds +2456 -0
- raptor_studio-0.8.0.data/data/new_docs/models/DC2.rbds +2663 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Drone1.rbds +1275 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Espresso1.rbds +658 -0
- raptor_studio-0.8.0.data/data/new_docs/models/EspressoDep1.rbds +658 -0
- raptor_studio-0.8.0.data/data/new_docs/models/FinaleShow1.rbds +554 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Flight1.rbds +1182 -0
- raptor_studio-0.8.0.data/data/new_docs/models/HospitalPower.rbds +3087 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Lantern1.rbds +560 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Laundromat1.rbds +342 -0
- raptor_studio-0.8.0.data/data/new_docs/models/ProdLine0.rbds +1761 -0
- raptor_studio-0.8.0.data/data/new_docs/models/ProdLine1.rbds +1761 -0
- raptor_studio-0.8.0.data/data/new_docs/models/TacoTruck1.rbds +451 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Toaster_Expo.rbds +342 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Toaster_Fixed.rbds +342 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Toaster_Weibull.rbds +342 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Van1.rbds +1272 -0
- raptor_studio-0.8.0.data/data/new_docs/models/VanFlat.rbds +1021 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Vendo1.rbds +451 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Water1.rbds +678 -0
- raptor_studio-0.8.0.data/data/new_docs/models/Water2.rbds +679 -0
- raptor_studio-0.8.0.data/data/new_docs/models/WaterTreatment.rbds +2854 -0
- raptor_studio-0.8.0.data/data/new_docs/ops_asciidoctor.css +556 -0
- raptor_studio-0.8.0.data/data/share/bash-completion/completions/raptor +38 -0
- raptor_studio-0.8.0.data/data/share/fish/vendor_completions.d/raptor.fish +213 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-compare.1 +58 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-completion.1 +9 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-config.1 +9 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-convert.1 +17 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-diff.1 +61 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-doctor.1 +14 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-estimate.1 +20 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-extract.1 +51 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-fev.1 +87 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-get.1 +22 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-gui.1 +17 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-help.1 +9 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-info.1 +17 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-mcp.1 +85 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-render.1 +32 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-report.1 +20 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-run.1 +192 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-set.1 +38 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-spf.1 +20 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-step.1 +51 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-sweep.1 +130 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-tables.1 +67 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor-validate.1 +86 -0
- raptor_studio-0.8.0.data/data/share/man/man1/raptor.1 +117 -0
- raptor_studio-0.8.0.data/data/share/man/man5/raptor-config.5 +16 -0
- raptor_studio-0.8.0.data/data/share/man/man7/raptor-set-paths.7 +39 -0
- raptor_studio-0.8.0.data/data/share/zsh/site-functions/_raptor +11 -0
- raptor_studio-0.8.0.dist-info/METADATA +275 -0
- raptor_studio-0.8.0.dist-info/RECORD +433 -0
- raptor_studio-0.8.0.dist-info/WHEEL +5 -0
- raptor_studio-0.8.0.dist-info/entry_points.txt +6 -0
- raptor_studio-0.8.0.dist-info/licenses/LICENSE +49 -0
- raptor_studio-0.8.0.dist-info/licenses/LICENSES/Apache-2.0.txt +201 -0
- raptor_studio-0.8.0.dist-info/licenses/NOTICE.md +38 -0
- raptor_studio-0.8.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +36 -0
- raptor_studio-0.8.0.dist-info/top_level.txt +1 -0
raptor/__init__.py
ADDED
raptor/api/__init__.py
ADDED
raptor/api/analyse.py
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
"""Analyses API — single point failures, failure effects, weak link,
|
|
2
|
+
comparison and parameter sweep.
|
|
3
|
+
|
|
4
|
+
Wraps engine functions into MCP-friendly calls. No Qt imports.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import copy
|
|
9
|
+
import functools
|
|
10
|
+
import itertools
|
|
11
|
+
import threading
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from raptor.api.run import (run, run_dir, RunResult, RunNotFoundError,
|
|
15
|
+
RunCancelledError)
|
|
16
|
+
from raptor.api.results import Metric
|
|
17
|
+
from raptor.api.workspace import Workspace, UnknownHandle
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AnalysisError(Exception):
|
|
21
|
+
"""Base error for analysis operations."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
#: Serialises `sweep`/`compare`, which each create/close several scratch
|
|
25
|
+
#: models in the shared workspace. Two issued concurrently (an assistant
|
|
26
|
+
#: naturally reaches for Promise.all) used to race on model-id allocation
|
|
27
|
+
#: and eviction, surfacing as an expired handle or a missing model
|
|
28
|
+
#: directory. A single process-wide lock makes them safe at the cost of
|
|
29
|
+
#: running one at a time -- the right trade for what-if studies.
|
|
30
|
+
_ANALYSIS_LOCK = threading.RLock()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _serialised(fn):
|
|
34
|
+
@functools.wraps(fn)
|
|
35
|
+
def wrapper(*args, **kwargs):
|
|
36
|
+
with _ANALYSIS_LOCK:
|
|
37
|
+
return fn(*args, **kwargs)
|
|
38
|
+
return wrapper
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Single Point Failures
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
def single_point_failures(workspace: Workspace, model_id: str, *,
|
|
46
|
+
workers: int = 1,
|
|
47
|
+
use_cache: bool = True) -> list[dict]:
|
|
48
|
+
"""The Single Point Failure rows, found as Raptor 7 does: each Running or
|
|
49
|
+
Standby block is failed in Failure Effects View and the system tested
|
|
50
|
+
(`raptor.core.engine.spf_fev`), so dependencies and cold standby count.
|
|
51
|
+
|
|
52
|
+
Each row: ``{"kind": "Block"|"Event", "id": int, "name": str, "crit": float}``;
|
|
53
|
+
a phased model's rows also carry ``"phase"`` (one set per phase).
|
|
54
|
+
|
|
55
|
+
The result is content-addressed by the model hash, so asking again after
|
|
56
|
+
no edit returns without re-testing 3,000+ candidates. `workers > 1`
|
|
57
|
+
parallelises the independent candidate tests on an unphased model.
|
|
58
|
+
"""
|
|
59
|
+
import json
|
|
60
|
+
|
|
61
|
+
from raptor.api.run import model_hash as _hash
|
|
62
|
+
handle = workspace.get(model_id)
|
|
63
|
+
digest = _hash(handle.rbd)
|
|
64
|
+
cache_dir = workspace.root / ".spf_cache"
|
|
65
|
+
cache_path = cache_dir / f"{digest}.json"
|
|
66
|
+
if use_cache:
|
|
67
|
+
try:
|
|
68
|
+
cached = json.loads(cache_path.read_text())
|
|
69
|
+
if isinstance(cached, list):
|
|
70
|
+
return cached
|
|
71
|
+
except (OSError, ValueError):
|
|
72
|
+
pass
|
|
73
|
+
from raptor.core.engine.spf_fev import single_point_failures as _spf
|
|
74
|
+
rows = _spf(handle.rbd, workers=workers)
|
|
75
|
+
if use_cache:
|
|
76
|
+
try:
|
|
77
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
cache_path.write_text(json.dumps(rows))
|
|
79
|
+
except OSError:
|
|
80
|
+
pass
|
|
81
|
+
return rows
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _criticality_on(explicit: bool | None) -> bool:
|
|
85
|
+
if explicit is not None:
|
|
86
|
+
return explicit
|
|
87
|
+
from raptor.core.dev_options import from_environment
|
|
88
|
+
return from_environment().show_criticality
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def spf_listing(workspace: Workspace, model_id: str,
|
|
92
|
+
file_name: str | None = None, *, criticality: bool | None = None) -> str:
|
|
93
|
+
"""The Raptor 7-format Single Point Failure file text (CRLF), as the
|
|
94
|
+
original's `Create Single Point Failure File` writes it. Phased models
|
|
95
|
+
get the per-phase layout. `criticality=None` follows the `show_criticality` developer
|
|
96
|
+
option (`RAPTOR_DEV_FEATURES`)."""
|
|
97
|
+
handle = workspace.get(model_id)
|
|
98
|
+
criticality = _criticality_on(criticality)
|
|
99
|
+
from raptor.core.engine.spf import spf_date_string
|
|
100
|
+
from raptor.core.engine.spf_fev import build_spf_listing
|
|
101
|
+
return build_spf_listing(handle.rbd, file_name or f"{handle.name}.txt",
|
|
102
|
+
spf_date_string(), password=criticality)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Failure Effects
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def _resolve_fev_element(workspace: Workspace, model_id: str, selector: str | int) -> tuple[str, int]:
|
|
110
|
+
"""A block or event selector -> ("block"|"event", id)."""
|
|
111
|
+
if isinstance(selector, int):
|
|
112
|
+
return "block", selector
|
|
113
|
+
if isinstance(selector, str) and selector.strip().lstrip("-").isdigit():
|
|
114
|
+
return "block", int(selector)
|
|
115
|
+
from raptor.api.build import BuildError, resolve_element
|
|
116
|
+
try:
|
|
117
|
+
section, row = resolve_element(workspace.get(model_id).rbd, str(selector))
|
|
118
|
+
except BuildError as exc:
|
|
119
|
+
raise AnalysisError(str(exc)) from exc
|
|
120
|
+
if section == "BLOCK_DATA":
|
|
121
|
+
return "block", int(row["ID"])
|
|
122
|
+
if section == "EVENT_DATA":
|
|
123
|
+
return "event", int(row["ID"])
|
|
124
|
+
raise AnalysisError(
|
|
125
|
+
f"{selector!r} is not a block or an event; Failure Effects View "
|
|
126
|
+
"changes blocks and events only")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def failure_effects(
|
|
130
|
+
workspace: Workspace,
|
|
131
|
+
model_id: str,
|
|
132
|
+
*,
|
|
133
|
+
fail: list[str | int] | None = None,
|
|
134
|
+
repair: list[str | int] | None = None,
|
|
135
|
+
phase: int | None = None,
|
|
136
|
+
) -> dict:
|
|
137
|
+
"""Failure Effects View as a script: start from the all-operating state
|
|
138
|
+
(phase 1 of a phased model), optionally go to `phase`, fail the `fail`
|
|
139
|
+
elements in order, then repair the `repair` elements.
|
|
140
|
+
|
|
141
|
+
Runs `raptor.core.engine.fev.FevSession`, the port of Raptor 7's Failure
|
|
142
|
+
Effects View: nothing is simulated and no random number is drawn. Returns
|
|
143
|
+
``{"system_status", "affected", "log", "effects", "criticality", "phase"}``;
|
|
144
|
+
`log` is the Failure Effects text (what Raptor 7's Write To Failure
|
|
145
|
+
Effects Log appends), `effects` the same facts as lists.
|
|
146
|
+
"""
|
|
147
|
+
from raptor.core.engine.errors import UnrunnableModel
|
|
148
|
+
from raptor.core.engine.fev import FevSession
|
|
149
|
+
from raptor.core.engine.fmeca import build_fmeca, fmeca_lists
|
|
150
|
+
from raptor.core.model import BlockStatus, EventStatus
|
|
151
|
+
|
|
152
|
+
handle = workspace.get(model_id)
|
|
153
|
+
try:
|
|
154
|
+
session = FevSession(handle.rbd)
|
|
155
|
+
except UnrunnableModel as exc:
|
|
156
|
+
raise AnalysisError(str(exc)) from exc
|
|
157
|
+
|
|
158
|
+
if phase is not None:
|
|
159
|
+
if not session.phased:
|
|
160
|
+
raise AnalysisError("this model has no phases")
|
|
161
|
+
n = len(session.model.phases)
|
|
162
|
+
if not 1 <= phase <= n:
|
|
163
|
+
raise AnalysisError(f"phase {phase} is not in 1..{n}")
|
|
164
|
+
session.goto_phase(phase)
|
|
165
|
+
|
|
166
|
+
affected: list[str] = []
|
|
167
|
+
for selector in fail or []:
|
|
168
|
+
kind, eid = _resolve_fev_element(workspace, model_id, selector)
|
|
169
|
+
if kind == "event":
|
|
170
|
+
event = session.model.events_by_id.get(eid)
|
|
171
|
+
if event is None or event.op_status != EventStatus.SUCCESS:
|
|
172
|
+
raise AnalysisError(
|
|
173
|
+
f"Could not fail event {selector!r} (already failed or not found)")
|
|
174
|
+
session.click_event(eid)
|
|
175
|
+
else:
|
|
176
|
+
b = session.model.blocks_by_id.get(eid)
|
|
177
|
+
if b is None or not (b.op_status == BlockStatus.RUNNING or (
|
|
178
|
+
b.op_status == BlockStatus.STANDBY and b.sb_stress > 0.0)):
|
|
179
|
+
raise AnalysisError(
|
|
180
|
+
f"Could not fail block {selector!r} "
|
|
181
|
+
"(already failed, not operating, or not found)")
|
|
182
|
+
session.click_block(eid)
|
|
183
|
+
affected.append(str(selector))
|
|
184
|
+
for selector in repair or []:
|
|
185
|
+
kind, eid = _resolve_fev_element(workspace, model_id, selector)
|
|
186
|
+
if kind == "event":
|
|
187
|
+
event = session.model.events_by_id.get(eid)
|
|
188
|
+
if event is None or event.op_status != EventStatus.FAILURE:
|
|
189
|
+
raise AnalysisError(
|
|
190
|
+
f"Could not repair event {selector!r} (not failed or not found)")
|
|
191
|
+
session.click_event(eid)
|
|
192
|
+
else:
|
|
193
|
+
b = session.model.blocks_by_id.get(eid)
|
|
194
|
+
if b is None or b.op_status not in (BlockStatus.REPAIRING, BlockStatus.PM):
|
|
195
|
+
raise AnalysisError(
|
|
196
|
+
f"Could not repair block {selector!r} "
|
|
197
|
+
"(already running or not found)")
|
|
198
|
+
session.click_block(eid)
|
|
199
|
+
affected.append(str(selector))
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
"system_status": session.system_color.value,
|
|
203
|
+
"affected": affected,
|
|
204
|
+
"log": build_fmeca(session, show_criticality=_criticality_on(None)),
|
|
205
|
+
"effects": fmeca_lists(session),
|
|
206
|
+
"criticality": session.criticality,
|
|
207
|
+
"phase": session.phase_number if session.phased else None,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
FEV_SCRIPT_ACTIONS = {
|
|
212
|
+
"click": "left-click a block or event (cycles a hatched block)",
|
|
213
|
+
"fail": "click an operating block, or an event that has not failed",
|
|
214
|
+
"repair": "click a failed or PM block, or a failed event",
|
|
215
|
+
"phase": "go to phase N",
|
|
216
|
+
"next": "the forward phase arrow (wraps)",
|
|
217
|
+
"prev": "the back phase arrow (wraps)",
|
|
218
|
+
"erase": "the large eraser: everything operating, phase 1",
|
|
219
|
+
"erase-phase": "the small eraser: clear failures, keep the phase",
|
|
220
|
+
"write": "Write To Failure Effects Log: record the text now",
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def parse_fev_script(text: str) -> list[tuple[str, str]]:
|
|
225
|
+
"""One action per line: ``ACTION [ARGUMENT]``; blank lines and ``#`` comments
|
|
226
|
+
are ignored. Raises `AnalysisError` naming the line."""
|
|
227
|
+
actions: list[tuple[str, str]] = []
|
|
228
|
+
for n, raw in enumerate(text.splitlines(), 1):
|
|
229
|
+
line = raw.split("#", 1)[0].strip()
|
|
230
|
+
if not line:
|
|
231
|
+
continue
|
|
232
|
+
verb, _, arg = line.partition(" ")
|
|
233
|
+
verb, arg = verb.lower(), arg.strip()
|
|
234
|
+
if verb not in FEV_SCRIPT_ACTIONS:
|
|
235
|
+
raise AnalysisError(
|
|
236
|
+
f"line {n}: unknown action {verb!r}; use one of "
|
|
237
|
+
f"{', '.join(FEV_SCRIPT_ACTIONS)}")
|
|
238
|
+
needs_arg = verb in ("click", "fail", "repair", "phase")
|
|
239
|
+
if needs_arg and not arg:
|
|
240
|
+
raise AnalysisError(f"line {n}: {verb} needs an argument")
|
|
241
|
+
if not needs_arg and arg:
|
|
242
|
+
raise AnalysisError(f"line {n}: {verb} takes no argument")
|
|
243
|
+
actions.append((verb, arg))
|
|
244
|
+
return actions
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def fev_script(workspace: Workspace, model_id: str,
|
|
248
|
+
actions: list[tuple[str, str]], *,
|
|
249
|
+
criticality: bool | None = None) -> list[dict]:
|
|
250
|
+
"""Drive Failure Effects View through `actions` and return one record per
|
|
251
|
+
``write`` (or a single record of the final state when there is none)."""
|
|
252
|
+
from raptor.core.engine.errors import UnrunnableModel
|
|
253
|
+
from raptor.core.engine.fev import FevSession
|
|
254
|
+
from raptor.core.engine.fmeca import build_fmeca, fmeca_lists
|
|
255
|
+
from raptor.core.model import BlockStatus, EventStatus
|
|
256
|
+
|
|
257
|
+
criticality = _criticality_on(criticality)
|
|
258
|
+
try:
|
|
259
|
+
session = FevSession(workspace.get(model_id).rbd)
|
|
260
|
+
except UnrunnableModel as exc:
|
|
261
|
+
raise AnalysisError(str(exc)) from exc
|
|
262
|
+
|
|
263
|
+
def record(label: str) -> dict:
|
|
264
|
+
return {
|
|
265
|
+
"after": label,
|
|
266
|
+
"system_status": session.system_color.value,
|
|
267
|
+
"phase": session.phase_number if session.phased else None,
|
|
268
|
+
"criticality": session.criticality,
|
|
269
|
+
"lines": build_fmeca(session, show_criticality=criticality),
|
|
270
|
+
"effects": fmeca_lists(session),
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
def element(arg: str) -> tuple[str, int]:
|
|
274
|
+
return _resolve_fev_element(workspace, model_id, arg)
|
|
275
|
+
|
|
276
|
+
def need_phased() -> None:
|
|
277
|
+
if not session.phased:
|
|
278
|
+
raise AnalysisError("this model has no phases")
|
|
279
|
+
|
|
280
|
+
writes: list[dict] = []
|
|
281
|
+
last = "start"
|
|
282
|
+
for verb, arg in actions:
|
|
283
|
+
label = f"{verb} {arg}".strip()
|
|
284
|
+
if verb in ("click", "fail", "repair"):
|
|
285
|
+
kind, eid = element(arg)
|
|
286
|
+
if kind == "event":
|
|
287
|
+
event = session.model.events_by_id.get(eid)
|
|
288
|
+
if event is None:
|
|
289
|
+
raise AnalysisError(f"no event {arg!r}")
|
|
290
|
+
wanted = {"fail": EventStatus.SUCCESS, "repair": EventStatus.FAILURE}.get(verb)
|
|
291
|
+
if wanted is not None and event.op_status != wanted:
|
|
292
|
+
raise AnalysisError(f"cannot {verb} event {arg!r} (it is {event.op_status.value})")
|
|
293
|
+
session.click_event(eid)
|
|
294
|
+
else:
|
|
295
|
+
b = session.model.blocks_by_id.get(eid)
|
|
296
|
+
if b is None:
|
|
297
|
+
raise AnalysisError(f"no block {arg!r}")
|
|
298
|
+
if verb == "fail" and not (b.op_status == BlockStatus.RUNNING or (
|
|
299
|
+
b.op_status == BlockStatus.STANDBY and b.sb_stress > 0.0)):
|
|
300
|
+
raise AnalysisError(f"cannot fail block {arg!r} (it is {b.op_status.value})")
|
|
301
|
+
if verb == "repair" and b.op_status not in (BlockStatus.REPAIRING, BlockStatus.PM):
|
|
302
|
+
raise AnalysisError(f"cannot repair block {arg!r} (it is {b.op_status.value})")
|
|
303
|
+
session.click_block(eid)
|
|
304
|
+
elif verb == "phase":
|
|
305
|
+
need_phased()
|
|
306
|
+
try:
|
|
307
|
+
n = int(arg)
|
|
308
|
+
except ValueError:
|
|
309
|
+
raise AnalysisError(f"phase needs a number, got {arg!r}") from None
|
|
310
|
+
if not 1 <= n <= len(session.model.phases):
|
|
311
|
+
raise AnalysisError(f"phase {n} is not in 1..{len(session.model.phases)}")
|
|
312
|
+
session.goto_phase(n)
|
|
313
|
+
elif verb == "next":
|
|
314
|
+
need_phased()
|
|
315
|
+
session.next_phase()
|
|
316
|
+
elif verb == "prev":
|
|
317
|
+
need_phased()
|
|
318
|
+
session.previous_phase()
|
|
319
|
+
elif verb == "erase":
|
|
320
|
+
session.refresh_all()
|
|
321
|
+
elif verb == "erase-phase":
|
|
322
|
+
need_phased()
|
|
323
|
+
session.refresh_phase()
|
|
324
|
+
elif verb == "write":
|
|
325
|
+
writes.append(record(last))
|
|
326
|
+
continue
|
|
327
|
+
last = label
|
|
328
|
+
if not writes:
|
|
329
|
+
writes.append(record(last))
|
|
330
|
+
return writes
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
# ---------------------------------------------------------------------------
|
|
334
|
+
# Weak Link
|
|
335
|
+
# ---------------------------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
def weak_link(workspace: Workspace, run_id: str) -> list[dict]:
|
|
338
|
+
"""Extract weak-link rows from a completed run's final results."""
|
|
339
|
+
from raptor.api.run import get_report
|
|
340
|
+
|
|
341
|
+
report = get_report(workspace, run_id, section="weak_link", limit=2000)
|
|
342
|
+
rows: list[dict] = []
|
|
343
|
+
for line in report["lines"]:
|
|
344
|
+
stripped = line.strip()
|
|
345
|
+
if not stripped or stripped.startswith("***"):
|
|
346
|
+
continue
|
|
347
|
+
parts = stripped.split()
|
|
348
|
+
if len(parts) >= 2:
|
|
349
|
+
rows.append({
|
|
350
|
+
"block_name": parts[0],
|
|
351
|
+
"impact": " ".join(parts[1:]),
|
|
352
|
+
})
|
|
353
|
+
return rows
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# ---------------------------------------------------------------------------
|
|
357
|
+
# Compare
|
|
358
|
+
# ---------------------------------------------------------------------------
|
|
359
|
+
|
|
360
|
+
_HIGHER_IS_BETTER = {
|
|
361
|
+
"Availability": True,
|
|
362
|
+
"Reliability": True,
|
|
363
|
+
"Conditional_Reliability": True,
|
|
364
|
+
"Green_Percent": True,
|
|
365
|
+
"MDT": False,
|
|
366
|
+
"MMT": False,
|
|
367
|
+
"MTBM": False,
|
|
368
|
+
"MTBDE": True,
|
|
369
|
+
"System_Failures": False,
|
|
370
|
+
"Component_Failures": False,
|
|
371
|
+
"Red_Percent": False,
|
|
372
|
+
"Yellow_Percent": False,
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _result_from_run(workspace: Workspace, run_id: str) -> RunResult:
|
|
377
|
+
"""Build a RunResult from a cached run's summary.json."""
|
|
378
|
+
import json
|
|
379
|
+
path = run_dir(workspace, run_id) / "summary.json"
|
|
380
|
+
if not path.is_file():
|
|
381
|
+
raise RunNotFoundError(f"Run {run_id!r} not found")
|
|
382
|
+
data = json.loads(path.read_text())
|
|
383
|
+
metrics = [Metric(**v) for v in data.get("metrics", {}).values()]
|
|
384
|
+
return RunResult(
|
|
385
|
+
run_id=run_id,
|
|
386
|
+
model_id=data.get("model_id", run_id),
|
|
387
|
+
num_runs=data.get("num_runs", 0),
|
|
388
|
+
model_name=data.get("model_name", ""),
|
|
389
|
+
metrics=metrics,
|
|
390
|
+
model_hash=data.get("model_hash", ""),
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@_serialised
|
|
395
|
+
def compare(
|
|
396
|
+
workspace: Workspace,
|
|
397
|
+
models_or_runs: list[str],
|
|
398
|
+
*,
|
|
399
|
+
num_runs: int = 5,
|
|
400
|
+
metrics: list[str] | None = None,
|
|
401
|
+
abort: Any = None,
|
|
402
|
+
progress: Any = None,
|
|
403
|
+
) -> dict:
|
|
404
|
+
"""Compare two or more models (run afresh) or completed runs (cached).
|
|
405
|
+
|
|
406
|
+
Returns per-metric values, deltas against the first entry, and the
|
|
407
|
+
best entry per metric.
|
|
408
|
+
"""
|
|
409
|
+
if len(models_or_runs) < 2:
|
|
410
|
+
# Caller-input error (not a model-state problem): ValueError maps to
|
|
411
|
+
# the MCP "invalid_argument" code, unlike AnalysisError.
|
|
412
|
+
raise ValueError("need at least 2 models or runs to compare")
|
|
413
|
+
if num_runs < 1:
|
|
414
|
+
raise ValueError("num_runs must be at least 1")
|
|
415
|
+
|
|
416
|
+
results: list[RunResult] = []
|
|
417
|
+
for step, ref in enumerate(models_or_runs):
|
|
418
|
+
if abort is not None and abort.is_set():
|
|
419
|
+
raise RunCancelledError("comparison was cancelled")
|
|
420
|
+
if progress is not None:
|
|
421
|
+
progress(step, len(models_or_runs))
|
|
422
|
+
try:
|
|
423
|
+
workspace.get(ref)
|
|
424
|
+
is_model = True
|
|
425
|
+
except UnknownHandle:
|
|
426
|
+
is_model = False
|
|
427
|
+
|
|
428
|
+
if is_model:
|
|
429
|
+
results.append(run(workspace, ref, num_runs=num_runs,
|
|
430
|
+
timeout_s=120, confirm_long_run=True,
|
|
431
|
+
abort=abort))
|
|
432
|
+
else:
|
|
433
|
+
results.append(_result_from_run(workspace, ref))
|
|
434
|
+
|
|
435
|
+
# Human-readable labels for the raw model_id/run_id refs in
|
|
436
|
+
# `models_or_runs` -- both branches above populate `model_name` (a
|
|
437
|
+
# fresh run always does; a cached run's summary.json carries it from
|
|
438
|
+
# when it was made). Falls back to the raw ref only when a name is
|
|
439
|
+
# genuinely unavailable, and disambiguates duplicate names (two
|
|
440
|
+
# models can share a name) by appending a short ref suffix.
|
|
441
|
+
labels = [r.model_name or ref
|
|
442
|
+
for r, ref in zip(results, models_or_runs)]
|
|
443
|
+
seen: dict[str, int] = {}
|
|
444
|
+
for label in labels:
|
|
445
|
+
seen[label] = seen.get(label, 0) + 1
|
|
446
|
+
for i, label in enumerate(labels):
|
|
447
|
+
if seen[label] > 1:
|
|
448
|
+
# A repeated model/run ref makes the old "(ref)" suffix
|
|
449
|
+
# itself duplicate; include the position to stay unique.
|
|
450
|
+
labels[i] = f"{label} ({models_or_runs[i]}) #{i + 1}"
|
|
451
|
+
|
|
452
|
+
names = sorted({m.name for r in results for m in r.metrics})
|
|
453
|
+
if metrics:
|
|
454
|
+
wanted = {m.lower() for m in metrics}
|
|
455
|
+
names = [n for n in names if n.lower() in wanted]
|
|
456
|
+
|
|
457
|
+
values: dict[str, list[float | None]] = {}
|
|
458
|
+
for name in names:
|
|
459
|
+
row: list[float | None] = []
|
|
460
|
+
for r in results:
|
|
461
|
+
match = next((m for m in r.metrics if m.name == name), None)
|
|
462
|
+
row.append(match.mean if match else None)
|
|
463
|
+
values[name] = row
|
|
464
|
+
|
|
465
|
+
deltas: dict[str, float | None] = {}
|
|
466
|
+
for name in names:
|
|
467
|
+
base = values[name][0]
|
|
468
|
+
for i, label in enumerate(labels[1:], start=1):
|
|
469
|
+
other = values[name][i]
|
|
470
|
+
if base is not None and other is not None:
|
|
471
|
+
deltas[f"{name}_delta_{label}"] = other - base
|
|
472
|
+
|
|
473
|
+
best: dict[str, str] = {}
|
|
474
|
+
for name in names:
|
|
475
|
+
row = values[name]
|
|
476
|
+
candidates = [(v, i) for i, v in enumerate(row) if v is not None]
|
|
477
|
+
if not candidates:
|
|
478
|
+
continue
|
|
479
|
+
higher = _HIGHER_IS_BETTER.get(name, True)
|
|
480
|
+
chosen = max(candidates) if higher else min(candidates)
|
|
481
|
+
best[name] = labels[chosen[1]]
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
"models_or_runs": models_or_runs,
|
|
485
|
+
"labels": labels,
|
|
486
|
+
"metrics": values,
|
|
487
|
+
"deltas": deltas,
|
|
488
|
+
"best": best,
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
# ---------------------------------------------------------------------------
|
|
493
|
+
# Sweep
|
|
494
|
+
# ---------------------------------------------------------------------------
|
|
495
|
+
|
|
496
|
+
@_serialised
|
|
497
|
+
def sweep(
|
|
498
|
+
workspace: Workspace,
|
|
499
|
+
model_id: str,
|
|
500
|
+
selector: str,
|
|
501
|
+
field: str,
|
|
502
|
+
values: list[Any],
|
|
503
|
+
*,
|
|
504
|
+
num_runs: int = 5,
|
|
505
|
+
metrics: list[str] | None = None,
|
|
506
|
+
abort: Any = None,
|
|
507
|
+
progress: Any = None,
|
|
508
|
+
) -> dict:
|
|
509
|
+
"""Sweep a parameter across values, on copies of the model."""
|
|
510
|
+
from raptor.api.build import update_element
|
|
511
|
+
|
|
512
|
+
if not values:
|
|
513
|
+
raise ValueError("sweep needs at least one value")
|
|
514
|
+
if num_runs < 1:
|
|
515
|
+
raise ValueError("num_runs must be at least 1")
|
|
516
|
+
source = workspace.get(model_id)
|
|
517
|
+
rows: list[dict] = []
|
|
518
|
+
created: list[str] = []
|
|
519
|
+
try:
|
|
520
|
+
for step, val in enumerate(values):
|
|
521
|
+
if abort is not None and abort.is_set():
|
|
522
|
+
raise RunCancelledError("sweep was cancelled")
|
|
523
|
+
if progress is not None:
|
|
524
|
+
progress(step, len(values))
|
|
525
|
+
copy_result = workspace.create_model(
|
|
526
|
+
f"sweep_{selector}_{val}", template="blank"
|
|
527
|
+
)
|
|
528
|
+
copy_id = copy_result["model_id"]
|
|
529
|
+
created.append(copy_id)
|
|
530
|
+
copy_handle = workspace.get(copy_id)
|
|
531
|
+
copy_handle.rbd = copy.deepcopy(source.rbd)
|
|
532
|
+
copy_handle.rbd.file_name = f"sweep_{val}"
|
|
533
|
+
# A copy, not an edit: the source's own state is not re-judged.
|
|
534
|
+
# The swept value itself goes through `update_element` below,
|
|
535
|
+
# so a value Raptor 7 would refuse is refused for the sweep too.
|
|
536
|
+
workspace.save(copy_id, check_rules=False)
|
|
537
|
+
|
|
538
|
+
update_element(workspace, copy_id, selector, {field: val})
|
|
539
|
+
result = run(workspace, copy_id, num_runs=num_runs,
|
|
540
|
+
timeout_s=120, confirm_long_run=True,
|
|
541
|
+
abort=abort)
|
|
542
|
+
|
|
543
|
+
# Provenance: the run that produced this row, and how many
|
|
544
|
+
# replications it actually completed (a sweep row whose runs
|
|
545
|
+
# < num_runs was silently truncated by the timeout).
|
|
546
|
+
row: dict[str, Any] = {"value": val, "run_id": result.run_id,
|
|
547
|
+
"runs": result.num_runs}
|
|
548
|
+
wanted = {m.lower() for m in metrics} if metrics else None
|
|
549
|
+
for m in result.metrics:
|
|
550
|
+
if wanted is None or m.name.lower() in wanted:
|
|
551
|
+
row[m.name] = m.mean
|
|
552
|
+
rows.append(row)
|
|
553
|
+
workspace.close(copy_id, delete=True)
|
|
554
|
+
created.remove(copy_id)
|
|
555
|
+
finally:
|
|
556
|
+
# Never leave THIS call's sweep copies behind on error. A name
|
|
557
|
+
# prefix is not ownership: a user model called sweep_Casing_1
|
|
558
|
+
# must not be deleted by an unrelated sweep.
|
|
559
|
+
for cid in created:
|
|
560
|
+
try:
|
|
561
|
+
workspace.close(cid, delete=True)
|
|
562
|
+
except Exception:
|
|
563
|
+
pass
|
|
564
|
+
|
|
565
|
+
monotonic = _is_monotonic(rows)
|
|
566
|
+
warnings: list[str] = []
|
|
567
|
+
identical = _all_rows_identical(rows)
|
|
568
|
+
if identical:
|
|
569
|
+
warnings.append(
|
|
570
|
+
"Every sweep row has identical metric values -- either the "
|
|
571
|
+
"parameter genuinely has no effect, or it was not applied. "
|
|
572
|
+
"Check the per-row run_id and the field name."
|
|
573
|
+
)
|
|
574
|
+
short = [r for r in rows if r.get("runs") is not None and r["runs"] < num_runs]
|
|
575
|
+
if short:
|
|
576
|
+
warnings.append(
|
|
577
|
+
f"{len(short)} of {len(rows)} rows completed fewer than "
|
|
578
|
+
f"{num_runs} runs (timeout); their estimates are noisier than "
|
|
579
|
+
f"requested."
|
|
580
|
+
)
|
|
581
|
+
return {
|
|
582
|
+
"selector": selector,
|
|
583
|
+
"field": field,
|
|
584
|
+
"rows": rows,
|
|
585
|
+
"monotonic": monotonic,
|
|
586
|
+
"all_rows_identical": identical,
|
|
587
|
+
"warnings": warnings,
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def _all_rows_identical(rows: list[dict]) -> bool:
|
|
592
|
+
"""True when every row's metric values are bit-identical (so a reader
|
|
593
|
+
cannot tell "no effect" from "value never applied")."""
|
|
594
|
+
if len(rows) < 2:
|
|
595
|
+
return False
|
|
596
|
+
metric_keys = [k for k in rows[0] if k not in _NON_METRIC_KEYS]
|
|
597
|
+
if not metric_keys:
|
|
598
|
+
return False
|
|
599
|
+
first = rows[0]
|
|
600
|
+
return all(
|
|
601
|
+
all(r.get(k) == first.get(k) for k in metric_keys)
|
|
602
|
+
for r in rows[1:]
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
_NON_METRIC_KEYS = frozenset({"value", "run_id", "runs", "warnings"})
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _is_monotonic(rows: list[dict]) -> bool:
|
|
610
|
+
if len(rows) < 2:
|
|
611
|
+
return True
|
|
612
|
+
metric = next((k for k in rows[0] if k not in _NON_METRIC_KEYS), None)
|
|
613
|
+
if metric is None:
|
|
614
|
+
return True
|
|
615
|
+
vals = [r.get(metric) for r in rows]
|
|
616
|
+
increasing = all(
|
|
617
|
+
b is None or a is None or b >= a
|
|
618
|
+
for a, b in itertools.pairwise(vals)
|
|
619
|
+
)
|
|
620
|
+
decreasing = all(
|
|
621
|
+
b is None or a is None or b <= a
|
|
622
|
+
for a, b in itertools.pairwise(vals)
|
|
623
|
+
)
|
|
624
|
+
return increasing or decreasing
|