flextool 4.0.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.
- flextool/__init__.py +41 -0
- flextool/_mem_sampler.py +193 -0
- flextool/_resources.py +43 -0
- flextool/calibrate/__init__.py +51 -0
- flextool/calibrate/__main__.py +11 -0
- flextool/calibrate/_cli.py +316 -0
- flextool/calibrate/_db_alt.py +166 -0
- flextool/calibrate/_final_outputs.py +110 -0
- flextool/calibrate/_guard.py +151 -0
- flextool/calibrate/_loop.py +558 -0
- flextool/calibrate/_readers.py +223 -0
- flextool/calibrate/_report.py +263 -0
- flextool/calibrate/_sizing.py +699 -0
- flextool/calibrate/_solve.py +134 -0
- flextool/calibrate/_solve_status.py +495 -0
- flextool/cli/__init__.py +9 -0
- flextool/cli/_console.py +51 -0
- flextool/cli/_timing.py +147 -0
- flextool/cli/cmd_execute_flextool_workflow.py +187 -0
- flextool/cli/cmd_export_to_tabular.py +56 -0
- flextool/cli/cmd_import_sensitivities.py +75 -0
- flextool/cli/cmd_migrate_database.py +13 -0
- flextool/cli/cmd_open_results_db.py +269 -0
- flextool/cli/cmd_read_matpower.py +66 -0
- flextool/cli/cmd_read_old_flextool.py +63 -0
- flextool/cli/cmd_read_self_describing_tabular_input.py +50 -0
- flextool/cli/cmd_read_tabular_input.py +81 -0
- flextool/cli/cmd_run_flextool.py +1095 -0
- flextool/cli/cmd_scenario_results.py +284 -0
- flextool/cli/cmd_solve_mps.py +169 -0
- flextool/cli/cmd_update_flextool.py +17 -0
- flextool/cli/cmd_write_outputs.py +125 -0
- flextool/common_utils/__init__.py +1 -0
- flextool/common_utils/plot_mem_shape.py +77 -0
- flextool/common_utils/precision.py +451 -0
- flextool/decomposition/__init__.py +0 -0
- flextool/decomposition/region_decomposition.py +128 -0
- flextool/decomposition/region_filter.py +1261 -0
- flextool/engine_polars/__init__.py +110 -0
- flextool/engine_polars/_axis_enums.py +742 -0
- flextool/engine_polars/_benders.py +3462 -0
- flextool/engine_polars/_block_layout.py +1479 -0
- flextool/engine_polars/_blocks.py +1515 -0
- flextool/engine_polars/_commodity_ladder.py +660 -0
- flextool/engine_polars/_cumulative_invest.py +1165 -0
- flextool/engine_polars/_db_loader.py +153 -0
- flextool/engine_polars/_db_reader.py +127 -0
- flextool/engine_polars/_dc_power_flow.py +445 -0
- flextool/engine_polars/_delay.py +442 -0
- flextool/engine_polars/_derived_arithmetic.py +432 -0
- flextool/engine_polars/_derived_block.py +990 -0
- flextool/engine_polars/_derived_branch.py +769 -0
- flextool/engine_polars/_derived_existing.py +1353 -0
- flextool/engine_polars/_derived_npv.py +1297 -0
- flextool/engine_polars/_derived_params.py +9850 -0
- flextool/engine_polars/_derived_profile.py +881 -0
- flextool/engine_polars/_derived_walks.py +276 -0
- flextool/engine_polars/_determinism.py +70 -0
- flextool/engine_polars/_direct_params.py +2186 -0
- flextool/engine_polars/_dump_csvs.py +1009 -0
- flextool/engine_polars/_emit_arc_unions.py +1631 -0
- flextool/engine_polars/_emit_calc_params.py +729 -0
- flextool/engine_polars/_emit_chain_params.py +709 -0
- flextool/engine_polars/_emit_co2_accumulators.py +400 -0
- flextool/engine_polars/_emit_dispatchers.py +690 -0
- flextool/engine_polars/_emit_energy_margin.py +125 -0
- flextool/engine_polars/_emit_energy_margin_adder.py +290 -0
- flextool/engine_polars/_emit_entity_annual.py +428 -0
- flextool/engine_polars/_emit_inflow_scaling.py +1420 -0
- flextool/engine_polars/_emit_leaf_sets.py +550 -0
- flextool/engine_polars/_emit_lp_scaling.py +665 -0
- flextool/engine_polars/_emit_mid_sets.py +859 -0
- flextool/engine_polars/_emit_pdt_params.py +759 -0
- flextool/engine_polars/_emit_per_solve.py +774 -0
- flextool/engine_polars/_emit_period_calc.py +504 -0
- flextool/engine_polars/_emit_period_params.py +2398 -0
- flextool/engine_polars/_emit_provider_io.py +141 -0
- flextool/engine_polars/_emit_reserve.py +574 -0
- flextool/engine_polars/_emit_solve_time.py +311 -0
- flextool/engine_polars/_emit_solve_writers.py +1249 -0
- flextool/engine_polars/_flex_data_accumulator.py +388 -0
- flextool/engine_polars/_flex_data_provider.py +478 -0
- flextool/engine_polars/_group_slack.py +1253 -0
- flextool/engine_polars/_inmemory_reader.py +140 -0
- flextool/engine_polars/_input_source.py +336 -0
- flextool/engine_polars/_invest_seeds.py +191 -0
- flextool/engine_polars/_native_input_writer.py +100 -0
- flextool/engine_polars/_native_run_model.py +1348 -0
- flextool/engine_polars/_orchestration.py +4314 -0
- flextool/engine_polars/_output_writer.py +439 -0
- flextool/engine_polars/_param_shapes.py +1595 -0
- flextool/engine_polars/_parquet_bundle.py +723 -0
- flextool/engine_polars/_pdt_join.py +167 -0
- flextool/engine_polars/_pdt_lookup.py +547 -0
- flextool/engine_polars/_per_solve_sets.py +335 -0
- flextool/engine_polars/_projection_params.py +2056 -0
- flextool/engine_polars/_provider_keys.py +173 -0
- flextool/engine_polars/_provider_translators.py +225 -0
- flextool/engine_polars/_recursive_solve.py +703 -0
- flextool/engine_polars/_region_filter.py +2508 -0
- flextool/engine_polars/_reserve.py +649 -0
- flextool/engine_polars/_solve_acceptance.py +331 -0
- flextool/engine_polars/_solve_config.py +1001 -0
- flextool/engine_polars/_solve_context.py +885 -0
- flextool/engine_polars/_solve_handoff.py +164 -0
- flextool/engine_polars/_solve_state.py +232 -0
- flextool/engine_polars/_solver_base.py +36 -0
- flextool/engine_polars/_solver_dispatch.py +511 -0
- flextool/engine_polars/_spinedb_reader.py +1165 -0
- flextool/engine_polars/_stochastic.py +593 -0
- flextool/engine_polars/_subprocess_solve.py +1838 -0
- flextool/engine_polars/_timeline.py +1416 -0
- flextool/engine_polars/_vectorize.py +438 -0
- flextool/engine_polars/_warm.py +858 -0
- flextool/engine_polars/autoscale/__init__.py +107 -0
- flextool/engine_polars/autoscale/_config.py +218 -0
- flextool/engine_polars/autoscale/_layer2.py +1253 -0
- flextool/engine_polars/autoscale/_layer2_types.py +584 -0
- flextool/engine_polars/autoscale/_quantity_types.py +621 -0
- flextool/engine_polars/autoscale/_report.py +336 -0
- flextool/engine_polars/chain.py +259 -0
- flextool/engine_polars/input.py +6638 -0
- flextool/engine_polars/model.py +4754 -0
- flextool/env_check.py +388 -0
- flextool/export_to_tabular/__init__.py +5 -0
- flextool/export_to_tabular/db_reader.py +224 -0
- flextool/export_to_tabular/excel_writer.py +3559 -0
- flextool/export_to_tabular/export_settings.yaml +377 -0
- flextool/export_to_tabular/export_to_excel.py +227 -0
- flextool/export_to_tabular/formatting.py +543 -0
- flextool/export_to_tabular/sheet_config.py +876 -0
- flextool/gui/__init__.py +0 -0
- flextool/gui/__main__.py +118 -0
- flextool/gui/calibrate_commands.py +184 -0
- flextool/gui/calibrate_jobs.py +424 -0
- flextool/gui/check_tree.py +142 -0
- flextool/gui/cli_format.py +83 -0
- flextool/gui/config_parser.py +68 -0
- flextool/gui/data_models.py +362 -0
- flextool/gui/db_editor_integration.py +202 -0
- flextool/gui/db_version_check.py +269 -0
- flextool/gui/dialogs/__init__.py +0 -0
- flextool/gui/dialogs/add_dialog.py +1098 -0
- flextool/gui/dialogs/calibrate_dialog.py +1259 -0
- flextool/gui/dialogs/file_picker.py +473 -0
- flextool/gui/dialogs/group_picker.py +299 -0
- flextool/gui/dialogs/migration_consent_dialog.py +106 -0
- flextool/gui/dialogs/migration_progress_dialog.py +237 -0
- flextool/gui/dialogs/plot_dialog.py +459 -0
- flextool/gui/dialogs/plot_settings_picker.py +2184 -0
- flextool/gui/dialogs/project_dialog.py +426 -0
- flextool/gui/dialogs/update_dialog.py +212 -0
- flextool/gui/downsampling.py +88 -0
- flextool/gui/error_handling.py +50 -0
- flextool/gui/execution_manager.py +1715 -0
- flextool/gui/execution_window.py +1377 -0
- flextool/gui/hover_tooltip.py +111 -0
- flextool/gui/input_sources.py +730 -0
- flextool/gui/main_window.py +6181 -0
- flextool/gui/network_graph.py +215 -0
- flextool/gui/output_actions.py +393 -0
- flextool/gui/output_log_window.py +159 -0
- flextool/gui/platform_utils.py +421 -0
- flextool/gui/plot_cache.py +88 -0
- flextool/gui/plot_canvas.py +543 -0
- flextool/gui/plot_config_reader.py +272 -0
- flextool/gui/project_utils.py +100 -0
- flextool/gui/result_viewer.py +4394 -0
- flextool/gui/scenario_key.py +162 -0
- flextool/gui/scenario_lists.py +516 -0
- flextool/gui/settings_io.py +360 -0
- flextool/gui/solve_reader.py +103 -0
- flextool/gui/tree_reorder.py +88 -0
- flextool/gui/ui_metrics.py +420 -0
- flextool/input_derivation/__init__.py +281 -0
- flextool/input_derivation/_commodity_ladder.py +375 -0
- flextool/input_derivation/_commodity_ladder_sets.py +70 -0
- flextool/input_derivation/_dc_power_flow.py +377 -0
- flextool/input_derivation/_method_constants.py +77 -0
- flextool/input_derivation/_process_method.py +258 -0
- flextool/input_derivation/_specs.py +1026 -0
- flextool/input_derivation/_validators.py +321 -0
- flextool/lean_parquet.py +159 -0
- flextool/model_builder/__init__.py +5 -0
- flextool/model_builder/build_model.py +589 -0
- flextool/model_builder/encoding.py +67 -0
- flextool/model_builder/names.py +34 -0
- flextool/model_builder/profiles.py +129 -0
- flextool/plot_outputs/__init__.py +14 -0
- flextool/plot_outputs/axis_helpers.py +355 -0
- flextool/plot_outputs/color_template.py +888 -0
- flextool/plot_outputs/config.py +171 -0
- flextool/plot_outputs/format_helpers.py +345 -0
- flextool/plot_outputs/legend_helpers.py +143 -0
- flextool/plot_outputs/orchestrator.py +1141 -0
- flextool/plot_outputs/perf.py +37 -0
- flextool/plot_outputs/plan.py +1787 -0
- flextool/plot_outputs/plot_bars.py +1510 -0
- flextool/plot_outputs/plot_bars_detail.py +753 -0
- flextool/plot_outputs/plot_lines.py +951 -0
- flextool/plot_outputs/shared_manifest.py +564 -0
- flextool/plot_outputs/subplot_helpers.py +137 -0
- flextool/process_inputs/__init__.py +188 -0
- flextool/process_inputs/import_old_excel_input.json +4159 -0
- flextool/process_inputs/read_matpower.py +451 -0
- flextool/process_inputs/read_old_flextool.py +1288 -0
- flextool/process_inputs/read_self_describing_excel.py +1423 -0
- flextool/process_inputs/read_tabular_with_specification.py +1114 -0
- flextool/process_inputs/write_old_flextool_to_db.py +3077 -0
- flextool/process_inputs/write_self_describing_to_db.py +977 -0
- flextool/process_inputs/write_to_input_db.py +269 -0
- flextool/process_outputs/__init__.py +7 -0
- flextool/process_outputs/_annualize.py +55 -0
- flextool/process_outputs/_inmemory_helpers.py +292 -0
- flextool/process_outputs/_output_meta.py +672 -0
- flextool/process_outputs/calc_capacity_flows.py +107 -0
- flextool/process_outputs/calc_connections.py +136 -0
- flextool/process_outputs/calc_costs.py +260 -0
- flextool/process_outputs/calc_group_flows.py +192 -0
- flextool/process_outputs/calc_slacks.py +103 -0
- flextool/process_outputs/calc_storage_vre.py +160 -0
- flextool/process_outputs/drop_levels.py +208 -0
- flextool/process_outputs/handoff_writers.py +1315 -0
- flextool/process_outputs/out_ancillary.py +544 -0
- flextool/process_outputs/out_capacity.py +179 -0
- flextool/process_outputs/out_costs.py +334 -0
- flextool/process_outputs/out_flowgroup.py +189 -0
- flextool/process_outputs/out_flows.py +301 -0
- flextool/process_outputs/out_group.py +475 -0
- flextool/process_outputs/out_node.py +190 -0
- flextool/process_outputs/persist_realized_slice.py +601 -0
- flextool/process_outputs/process_results.py +24 -0
- flextool/process_outputs/read_highs_solution.py +2256 -0
- flextool/process_outputs/read_parameters.py +1799 -0
- flextool/process_outputs/read_sets.py +1095 -0
- flextool/process_outputs/read_variables.py +553 -0
- flextool/process_outputs/solve_order.py +81 -0
- flextool/process_outputs/spinedb_replay.py +412 -0
- flextool/process_outputs/union_realized_slice.py +224 -0
- flextool/process_outputs/write_outputs.py +1286 -0
- flextool/process_outputs/write_spinedb.py +1267 -0
- flextool/representative_periods/__init__.py +5 -0
- flextool/representative_periods/clustering.py +165 -0
- flextool/representative_periods/force_include.py +563 -0
- flextool/representative_periods/netload.py +365 -0
- flextool/representative_periods/netload_inputs.py +345 -0
- flextool/representative_periods/netload_iterate.py +722 -0
- flextool/representative_periods/preprocess.py +948 -0
- flextool/representative_periods/scenario_stack.py +195 -0
- flextool/representative_periods/weights.py +124 -0
- flextool/scenario_comparison/__init__.py +13 -0
- flextool/scenario_comparison/config_builder.py +158 -0
- flextool/scenario_comparison/constants.py +20 -0
- flextool/scenario_comparison/data_models.py +222 -0
- flextool/scenario_comparison/db_reader.py +399 -0
- flextool/scenario_comparison/dispatch_data.py +1002 -0
- flextool/scenario_comparison/dispatch_mappings.py +205 -0
- flextool/scenario_comparison/dispatch_plots.py +691 -0
- flextool/scenario_comparison/input_entity_colors.py +319 -0
- flextool/scenario_comparison/orchestrator.py +453 -0
- flextool/scenario_comparison/plan_union.py +244 -0
- flextool/scenario_comparison/plot_settings_seed.py +205 -0
- flextool/schemas/AXIS_CONTRACT.md +71 -0
- flextool/schemas/canonical_databases/howto_aggregate_output.json +6225 -0
- flextool/schemas/canonical_databases/howto_connections.json +5606 -0
- flextool/schemas/canonical_databases/howto_demand.json +5518 -0
- flextool/schemas/canonical_databases/howto_hydro_reservoir.json +6239 -0
- flextool/schemas/canonical_databases/howto_hydro_reservoir_with_pump.json +5933 -0
- flextool/schemas/canonical_databases/howto_non_sync_and_curtailment.json +5794 -0
- flextool/schemas/canonical_databases/howto_ramp_and_start_up.json +5707 -0
- flextool/schemas/canonical_databases/howto_stochastics.json +6032 -0
- flextool/schemas/canonical_databases/templates_examples.json +13532 -0
- flextool/schemas/canonical_databases/templates_time_settings_only.json +5340 -0
- flextool/schemas/comparison_settings_template.json +197 -0
- flextool/schemas/default_plot_settings.yaml +260 -0
- flextool/schemas/default_plots.yaml +2293 -0
- flextool/schemas/flextool_axis_contract.json +303 -0
- flextool/schemas/flextool_axis_contract.schema.json +247 -0
- flextool/schemas/old_flextool_import_template.json +4443 -0
- flextool/schemas/output_info_template.json +48 -0
- flextool/schemas/output_settings_template.json +256 -0
- flextool/schemas/pre_v26/flextool_template_constant_default.json +2105 -0
- flextool/schemas/pre_v26/flextool_template_default_optional_output.json +2152 -0
- flextool/schemas/pre_v26/flextool_template_default_value.json +2094 -0
- flextool/schemas/pre_v26/flextool_template_drop_down.json +2080 -0
- flextool/schemas/pre_v26/flextool_template_lifetime_method.json +1990 -0
- flextool/schemas/pre_v26/flextool_template_optional_outputs.json +2094 -0
- flextool/schemas/pre_v26/flextool_template_output_node_flows.json +2105 -0
- flextool/schemas/pre_v26/flextool_template_results_master.json +493 -0
- flextool/schemas/pre_v26/flextool_template_rolling_start_remove.json +2087 -0
- flextool/schemas/pre_v26/flextool_template_rolling_window.json +2059 -0
- flextool/schemas/pre_v26/flextool_template_storage_binding_defaults.json +46 -0
- flextool/schemas/pre_v26/flextool_template_v2.json +1990 -0
- flextool/schemas/pre_v26/flextool_template_v25.json +3864 -0
- flextool/schemas/spinedb_results_schema.json +581 -0
- flextool/schemas/spinedb_schema.json +4636 -0
- flextool/solver_config/copt.opt.template +18 -0
- flextool/solver_config/cplex.opt.template +25 -0
- flextool/solver_config/gurobi.opt.template +18 -0
- flextool/solver_config/highs.opt.template +18 -0
- flextool/solver_config/xpress.opt.template +26 -0
- flextool/spinedb_backend/__init__.py +26 -0
- flextool/spinedb_backend/_axis_enums.py +1119 -0
- flextool/spinedb_backend/_backend.py +1139 -0
- flextool/update_flextool/__init__.py +12 -0
- flextool/update_flextool/canonical_databases.py +251 -0
- flextool/update_flextool/db_migration.py +7108 -0
- flextool/update_flextool/ensure_settings_db.py +138 -0
- flextool/update_flextool/export_database.py +103 -0
- flextool/update_flextool/extend_tests_fixture.py +772 -0
- flextool/update_flextool/generate_canonical.py +274 -0
- flextool/update_flextool/initialize_database.py +42 -0
- flextool/update_flextool/install_info.py +225 -0
- flextool/update_flextool/self_update.py +464 -0
- flextool/update_flextool/sync_master_json_template.py +125 -0
- flextool/update_flextool/test_fixtures.py +187 -0
- flextool-4.0.0.dist-info/METADATA +217 -0
- flextool-4.0.0.dist-info/RECORD +322 -0
- flextool-4.0.0.dist-info/WHEEL +5 -0
- flextool-4.0.0.dist-info/entry_points.txt +17 -0
- flextool-4.0.0.dist-info/licenses/LICENSE.txt +19 -0
- flextool-4.0.0.dist-info/top_level.txt +1 -0
flextool/gui/__init__.py
ADDED
|
File without changes
|
flextool/gui/__main__.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import signal
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("flextool.gui")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _configure_logging(log_dir: Path) -> None:
|
|
12
|
+
"""Set up logging for the desktop application.
|
|
13
|
+
|
|
14
|
+
Adds a console handler only when a real stream exists -- under the
|
|
15
|
+
windowed launcher on Windows (``pythonw.exe``) ``sys.stdout`` and
|
|
16
|
+
``sys.stderr`` are ``None`` -- and always adds a rotating file handler so
|
|
17
|
+
diagnostics survive even when there is no console to print them to.
|
|
18
|
+
"""
|
|
19
|
+
from logging.handlers import RotatingFileHandler
|
|
20
|
+
|
|
21
|
+
handlers: list[logging.Handler] = []
|
|
22
|
+
|
|
23
|
+
stream = sys.stderr or sys.stdout # both None under pythonw.exe
|
|
24
|
+
if stream is not None:
|
|
25
|
+
handlers.append(logging.StreamHandler(stream))
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
handlers.append(
|
|
30
|
+
RotatingFileHandler(
|
|
31
|
+
log_dir / "flextool_gui.log",
|
|
32
|
+
maxBytes=1_000_000,
|
|
33
|
+
backupCount=3,
|
|
34
|
+
encoding="utf-8",
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
except OSError:
|
|
38
|
+
# A missing log file must never stop the GUI from starting.
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
logging.basicConfig(
|
|
42
|
+
level=logging.WARNING,
|
|
43
|
+
format="%(asctime)s %(name)s %(levelname)s: %(message)s",
|
|
44
|
+
handlers=handlers,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _install_exception_logging() -> None:
|
|
49
|
+
"""Route otherwise-uncaught exceptions to the log.
|
|
50
|
+
|
|
51
|
+
Without a console most users never see (let alone report) a traceback, so
|
|
52
|
+
funnel interpreter-level exceptions into the rotating log file configured
|
|
53
|
+
by :func:`_configure_logging`.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def _hook(exc_type, exc, tb) -> None:
|
|
57
|
+
if issubclass(exc_type, KeyboardInterrupt):
|
|
58
|
+
sys.__excepthook__(exc_type, exc, tb)
|
|
59
|
+
return
|
|
60
|
+
logger.critical("Uncaught exception", exc_info=(exc_type, exc, tb))
|
|
61
|
+
|
|
62
|
+
sys.excepthook = _hook
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def main() -> None:
|
|
66
|
+
# Ensure projects directory exists and load global settings for theme
|
|
67
|
+
from flextool.gui.project_utils import get_projects_dir
|
|
68
|
+
from flextool.gui.settings_io import load_global_settings
|
|
69
|
+
|
|
70
|
+
projects_dir = get_projects_dir()
|
|
71
|
+
projects_dir.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
|
|
73
|
+
# Set up logging (file fallback first, so nothing is lost without a console)
|
|
74
|
+
_configure_logging(projects_dir)
|
|
75
|
+
_install_exception_logging()
|
|
76
|
+
|
|
77
|
+
settings = load_global_settings(projects_dir)
|
|
78
|
+
|
|
79
|
+
# Create and run the main window, passing the initial theme
|
|
80
|
+
from flextool.gui.main_window import MainWindow
|
|
81
|
+
|
|
82
|
+
root = MainWindow(initial_theme=settings.theme)
|
|
83
|
+
|
|
84
|
+
# Tkinter prints callback exceptions to stderr by default, which is absent
|
|
85
|
+
# under the windowed launcher; send them to the log instead.
|
|
86
|
+
root.report_callback_exception = lambda exc_type, exc, tb: logger.error(
|
|
87
|
+
"Unhandled Tk callback exception", exc_info=(exc_type, exc, tb)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Set up signal handlers for clean shutdown (SIGINT = Ctrl+C, SIGTERM = kill)
|
|
91
|
+
def _signal_handler(signum: int, frame: object) -> None:
|
|
92
|
+
"""Handle SIGINT/SIGTERM by cleaning up and exiting."""
|
|
93
|
+
try:
|
|
94
|
+
if hasattr(root, "execution_mgr") and root.execution_mgr:
|
|
95
|
+
root.execution_mgr.cleanup()
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
try:
|
|
99
|
+
root.destroy()
|
|
100
|
+
except Exception:
|
|
101
|
+
pass
|
|
102
|
+
sys.exit(0)
|
|
103
|
+
|
|
104
|
+
signal.signal(signal.SIGINT, _signal_handler)
|
|
105
|
+
signal.signal(signal.SIGTERM, _signal_handler)
|
|
106
|
+
|
|
107
|
+
# Periodically yield control from tkinter's mainloop so Python can
|
|
108
|
+
# process pending signal handlers (tkinter blocks them on some platforms).
|
|
109
|
+
def _check_signals() -> None:
|
|
110
|
+
root.after(500, _check_signals)
|
|
111
|
+
|
|
112
|
+
root.after(500, _check_signals)
|
|
113
|
+
|
|
114
|
+
root.mainloop()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
main()
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Single-source argv builders for the RP-preprocess and calibrate CLIs.
|
|
2
|
+
|
|
3
|
+
These pure functions are the ONE renderer per command. Both the
|
|
4
|
+
ExecutionManager auxiliary-job worker (which runs the subprocess with the
|
|
5
|
+
raw argv list) and the dialog's "Copy CLI command" button (which renders
|
|
6
|
+
the same argv via :func:`command_to_display_string`) go through here, so
|
|
7
|
+
the copied text can never drift from what is actually executed.
|
|
8
|
+
|
|
9
|
+
Targets:
|
|
10
|
+
* ``python -m flextool.representative_periods.preprocess <db_url>
|
|
11
|
+
<scenario> <n_rp> <period_length> [flags]``
|
|
12
|
+
(see ``flextool/representative_periods/preprocess.py::main``)
|
|
13
|
+
* ``python -m flextool.calibrate <db> <scenario> [flags]``
|
|
14
|
+
(see ``flextool/calibrate/_cli.py::build_parser``)
|
|
15
|
+
|
|
16
|
+
Region-scope RP args are intentionally NOT emitted here (out of GUI scope).
|
|
17
|
+
Calibrate's ``--slack-threshold`` is intentionally omitted so the
|
|
18
|
+
calibrator uses its own default.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import shlex
|
|
23
|
+
from collections.abc import Sequence
|
|
24
|
+
|
|
25
|
+
RP_MODULE = "flextool.representative_periods.preprocess"
|
|
26
|
+
CALIBRATE_MODULE = "flextool.calibrate"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def final_write_methods_from_settings(settings) -> list[str]:
|
|
30
|
+
"""Map the project's "File outputs" choices to the calibrator's final
|
|
31
|
+
output formats.
|
|
32
|
+
|
|
33
|
+
The calibration loop always leaves a ``parquet`` result tree, so parquet is
|
|
34
|
+
NOT a choice here; the calibrator regenerates the remaining formats from
|
|
35
|
+
that parquet after the loop (no re-solve). This mirrors the regular-run
|
|
36
|
+
write-method assembly in
|
|
37
|
+
:meth:`flextool.gui.execution_manager.ExecutionManager._build_run_command`
|
|
38
|
+
(same ``auto_generate_*`` flags, same order) minus parquet, so a
|
|
39
|
+
calibration's outputs match exactly what a normal run of the same project
|
|
40
|
+
would produce. An empty list means the operator unchecked every file
|
|
41
|
+
output — the caller emits ``--skip-final-outputs`` to honour that.
|
|
42
|
+
"""
|
|
43
|
+
methods: list[str] = []
|
|
44
|
+
if settings.auto_generate_scen_plots:
|
|
45
|
+
methods.append("plot")
|
|
46
|
+
if settings.auto_generate_scen_excels:
|
|
47
|
+
methods.append("excel")
|
|
48
|
+
if settings.auto_generate_scen_csvs:
|
|
49
|
+
methods.append("csv")
|
|
50
|
+
if settings.auto_generate_comp_spinedb:
|
|
51
|
+
methods.append("spinedb")
|
|
52
|
+
return methods
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def overshoot_pct_to_multiplier(pct: float) -> float:
|
|
56
|
+
"""Convert a planning-safety-margin PERCENT into the CLI ``--overshoot``
|
|
57
|
+
multiplier.
|
|
58
|
+
|
|
59
|
+
The dialog stores ``calib_overshoot_pct`` (0 = off); the calibrator CLI
|
|
60
|
+
consumes a multiplier where ``1.0`` means off. ``20`` percent → ``1.2``.
|
|
61
|
+
"""
|
|
62
|
+
return 1.0 + pct / 100.0
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def build_rp_command(
|
|
66
|
+
python_exe: str,
|
|
67
|
+
db_url: str,
|
|
68
|
+
scenario: str,
|
|
69
|
+
*,
|
|
70
|
+
n_rp: int,
|
|
71
|
+
period_length: int,
|
|
72
|
+
force_sustained: bool,
|
|
73
|
+
force_peak: bool,
|
|
74
|
+
force_window: int | None,
|
|
75
|
+
solves: Sequence[str],
|
|
76
|
+
alternative_name: str | None,
|
|
77
|
+
alternative_description: str | None = None,
|
|
78
|
+
) -> list[str]:
|
|
79
|
+
"""Build the argv for the representative-periods preprocess CLI.
|
|
80
|
+
|
|
81
|
+
``force_sustained`` maps to ``--force-highest-net-load`` ("sustained net
|
|
82
|
+
load") and ``force_peak`` to ``--force-peak-load`` ("instantaneous
|
|
83
|
+
peak"). Boolean flags are emitted only when True; ``--solves`` only when
|
|
84
|
+
the list is non-empty (comma-joined); ``--alternative-name`` only when
|
|
85
|
+
truthy. ``--alternative-description`` is emitted only when a description
|
|
86
|
+
is supplied (single argv element, so ``shlex`` quotes the spaces).
|
|
87
|
+
"""
|
|
88
|
+
argv: list[str] = [
|
|
89
|
+
python_exe,
|
|
90
|
+
"-m",
|
|
91
|
+
RP_MODULE,
|
|
92
|
+
db_url,
|
|
93
|
+
scenario,
|
|
94
|
+
str(n_rp),
|
|
95
|
+
str(period_length),
|
|
96
|
+
]
|
|
97
|
+
if force_sustained:
|
|
98
|
+
argv.append("--force-highest-net-load")
|
|
99
|
+
if force_peak:
|
|
100
|
+
argv.append("--force-peak-load")
|
|
101
|
+
if force_window is not None:
|
|
102
|
+
argv += ["--force-window", str(force_window)]
|
|
103
|
+
if solves:
|
|
104
|
+
argv += ["--solves", ",".join(solves)]
|
|
105
|
+
if alternative_name:
|
|
106
|
+
argv += ["--alternative-name", str(alternative_name)]
|
|
107
|
+
if alternative_description:
|
|
108
|
+
argv += ["--alternative-description", str(alternative_description)]
|
|
109
|
+
return argv
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def build_calibrate_command(
|
|
113
|
+
python_exe: str,
|
|
114
|
+
db_url: str,
|
|
115
|
+
scenario: str,
|
|
116
|
+
*,
|
|
117
|
+
iterations: int,
|
|
118
|
+
sizing: str,
|
|
119
|
+
overshoot: float,
|
|
120
|
+
damping_first: float,
|
|
121
|
+
damping_remaining: float,
|
|
122
|
+
stall_fraction: float,
|
|
123
|
+
warm_start_cache_dir: str | None,
|
|
124
|
+
work_dir: str | None,
|
|
125
|
+
output_location: str | None,
|
|
126
|
+
debug: bool,
|
|
127
|
+
final_write_methods: list[str] | None = None,
|
|
128
|
+
) -> list[str]:
|
|
129
|
+
"""Build the argv for the calibrate CLI.
|
|
130
|
+
|
|
131
|
+
``overshoot`` is the CLI MULTIPLIER (not the dialog percent); the caller
|
|
132
|
+
converts via :func:`overshoot_pct_to_multiplier`. ``--overshoot`` is
|
|
133
|
+
emitted only when the multiplier differs from ``1.0`` (off). ``--debug``
|
|
134
|
+
is emitted only when True.
|
|
135
|
+
|
|
136
|
+
``final_write_methods`` selects which formats the calibrator regenerates
|
|
137
|
+
from the final parquet after the loop (see
|
|
138
|
+
:func:`final_write_methods_from_settings`):
|
|
139
|
+
|
|
140
|
+
* ``None`` — emit no final-output flag; the CLI keeps its own default (csv).
|
|
141
|
+
* ``[]`` — emit ``--skip-final-outputs`` (the operator unchecked every File
|
|
142
|
+
output, so leave the results parquet-only).
|
|
143
|
+
* non-empty — emit ``--final-write-methods <methods…>``.
|
|
144
|
+
"""
|
|
145
|
+
argv: list[str] = [
|
|
146
|
+
python_exe,
|
|
147
|
+
"-m",
|
|
148
|
+
CALIBRATE_MODULE,
|
|
149
|
+
db_url,
|
|
150
|
+
scenario,
|
|
151
|
+
"--iterations",
|
|
152
|
+
str(iterations),
|
|
153
|
+
]
|
|
154
|
+
if sizing:
|
|
155
|
+
argv += ["--sizing", str(sizing)]
|
|
156
|
+
if overshoot != 1.0:
|
|
157
|
+
argv += ["--overshoot", str(overshoot)]
|
|
158
|
+
argv += ["--damping-first-iteration", str(damping_first)]
|
|
159
|
+
argv += ["--damping-remaining-iterations", str(damping_remaining)]
|
|
160
|
+
argv += ["--stall-fraction", str(stall_fraction)]
|
|
161
|
+
if warm_start_cache_dir:
|
|
162
|
+
argv += ["--warm-start-cache-dir", str(warm_start_cache_dir)]
|
|
163
|
+
if work_dir:
|
|
164
|
+
argv += ["--work-dir", str(work_dir)]
|
|
165
|
+
if output_location:
|
|
166
|
+
argv += ["--output-location", str(output_location)]
|
|
167
|
+
if final_write_methods is not None:
|
|
168
|
+
if final_write_methods:
|
|
169
|
+
argv += ["--final-write-methods", *[str(m) for m in final_write_methods]]
|
|
170
|
+
else:
|
|
171
|
+
argv.append("--skip-final-outputs")
|
|
172
|
+
if debug:
|
|
173
|
+
argv.append("--debug")
|
|
174
|
+
return argv
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def command_to_display_string(argv: Sequence[str]) -> str:
|
|
178
|
+
"""Render *argv* as a single copy-pasteable POSIX shell line.
|
|
179
|
+
|
|
180
|
+
Uses :func:`shlex.join`, so ``shlex.split`` on the result reproduces the
|
|
181
|
+
original argv exactly — the copied text and the run argv are the same
|
|
182
|
+
command.
|
|
183
|
+
"""
|
|
184
|
+
return shlex.join(argv)
|