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/cli/_timing.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""TimingRecorder — per-CLI-invocation phase timings.
|
|
2
|
+
|
|
3
|
+
Writes a single structured CSV at
|
|
4
|
+
``<work_folder>/solve_data/timings.csv``.
|
|
5
|
+
|
|
6
|
+
Schema (CSV header)::
|
|
7
|
+
|
|
8
|
+
phase,subphase,solve,roll_index,seconds,started_at_iso,cumulative_s
|
|
9
|
+
|
|
10
|
+
One row per phase. ``record(...)`` appends immediately so a crash mid-run
|
|
11
|
+
still leaves usable data. ``section(...)`` is a context-manager helper
|
|
12
|
+
that times an arbitrary block. ``finalize(output_dir)`` copies the file
|
|
13
|
+
to its final per-scenario output destination once the run completes.
|
|
14
|
+
|
|
15
|
+
Always-on; cheap (one stat + one append per phase). Constructed once per
|
|
16
|
+
CLI invocation; tests calling :class:`FlexToolRunner` directly bootstrap
|
|
17
|
+
their own recorder if the CLI did not pass one in.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import csv
|
|
22
|
+
import time
|
|
23
|
+
from contextlib import contextmanager
|
|
24
|
+
from datetime import datetime, timedelta, timezone
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Optional
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_HEADER = (
|
|
30
|
+
"phase",
|
|
31
|
+
"subphase",
|
|
32
|
+
"solve",
|
|
33
|
+
"roll_index",
|
|
34
|
+
"seconds",
|
|
35
|
+
"started_at_iso",
|
|
36
|
+
"cumulative_s",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TimingRecorder:
|
|
41
|
+
"""One per CLI invocation; lives on ``FlexToolRunner.state``.
|
|
42
|
+
|
|
43
|
+
Captures ``time.perf_counter()`` deltas with absolute UTC timestamps
|
|
44
|
+
and cumulative-since-start seconds for every phase of a flextool run.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, work_folder: Path | str, scenario: Optional[str] = None) -> None:
|
|
48
|
+
self.t0 = time.perf_counter()
|
|
49
|
+
self.t0_wall = datetime.now(timezone.utc)
|
|
50
|
+
self.work_folder = Path(work_folder)
|
|
51
|
+
self.scenario = scenario # may be set later if cli didn't have it yet
|
|
52
|
+
self._path = self.work_folder / "solve_data" / "timings.csv"
|
|
53
|
+
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
self._open_for_write()
|
|
55
|
+
|
|
56
|
+
def _open_for_write(self) -> None:
|
|
57
|
+
"""Create the file (truncate any pre-existing one) and write the header."""
|
|
58
|
+
with open(self._path, "w", newline="") as f:
|
|
59
|
+
writer = csv.writer(f)
|
|
60
|
+
writer.writerow(_HEADER)
|
|
61
|
+
|
|
62
|
+
def set_scenario(self, name: str) -> None:
|
|
63
|
+
"""Set/overwrite the scenario name (CLI may not know it at __init__)."""
|
|
64
|
+
self.scenario = name
|
|
65
|
+
|
|
66
|
+
def record(
|
|
67
|
+
self,
|
|
68
|
+
phase: str,
|
|
69
|
+
*,
|
|
70
|
+
subphase: str = "",
|
|
71
|
+
solve: str = "",
|
|
72
|
+
roll_index: int | str = "",
|
|
73
|
+
seconds: float,
|
|
74
|
+
t_start: Optional[float] = None,
|
|
75
|
+
) -> None:
|
|
76
|
+
"""Append one row.
|
|
77
|
+
|
|
78
|
+
``t_start`` is the ``perf_counter()`` value at the START of the
|
|
79
|
+
phase (used to back-compute ``started_at_iso``); if ``None`` we
|
|
80
|
+
assume the phase ended just now (``started_at_iso = now - seconds``).
|
|
81
|
+
"""
|
|
82
|
+
now_perf = time.perf_counter()
|
|
83
|
+
cumulative_s = now_perf - self.t0
|
|
84
|
+
if t_start is not None:
|
|
85
|
+
started_perf = t_start
|
|
86
|
+
else:
|
|
87
|
+
started_perf = now_perf - max(0.0, float(seconds))
|
|
88
|
+
# Map perf_counter → wall time via the recorded t0/t0_wall pair.
|
|
89
|
+
started_wall = self.t0_wall + timedelta(seconds=started_perf - self.t0)
|
|
90
|
+
started_iso = started_wall.isoformat()
|
|
91
|
+
row = (
|
|
92
|
+
str(phase),
|
|
93
|
+
str(subphase),
|
|
94
|
+
str(solve),
|
|
95
|
+
str(roll_index),
|
|
96
|
+
f"{float(seconds):.6f}",
|
|
97
|
+
started_iso,
|
|
98
|
+
f"{cumulative_s:.6f}",
|
|
99
|
+
)
|
|
100
|
+
# Atomic append — open/write/close per row so a crash leaves the
|
|
101
|
+
# file in a parseable state.
|
|
102
|
+
with open(self._path, "a", newline="") as f:
|
|
103
|
+
csv.writer(f).writerow(row)
|
|
104
|
+
|
|
105
|
+
@contextmanager
|
|
106
|
+
def section(
|
|
107
|
+
self,
|
|
108
|
+
phase: str,
|
|
109
|
+
*,
|
|
110
|
+
subphase: str = "",
|
|
111
|
+
solve: str = "",
|
|
112
|
+
roll_index: int | str = "",
|
|
113
|
+
):
|
|
114
|
+
"""Time a block: ``with recorder.section('write_input'): ...``."""
|
|
115
|
+
t_start = time.perf_counter()
|
|
116
|
+
try:
|
|
117
|
+
yield
|
|
118
|
+
finally:
|
|
119
|
+
elapsed = time.perf_counter() - t_start
|
|
120
|
+
self.record(
|
|
121
|
+
phase,
|
|
122
|
+
subphase=subphase,
|
|
123
|
+
solve=solve,
|
|
124
|
+
roll_index=roll_index,
|
|
125
|
+
seconds=elapsed,
|
|
126
|
+
t_start=t_start,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def path(self) -> Path:
|
|
131
|
+
"""Current location of the timings.csv (in the work folder)."""
|
|
132
|
+
return self._path
|
|
133
|
+
|
|
134
|
+
def finalize(self, output_dir: Path | str) -> None:
|
|
135
|
+
"""Copy ``timings.csv`` into the final scenario output dir.
|
|
136
|
+
|
|
137
|
+
Lives alongside ``summary_solve.csv`` so users have one place to
|
|
138
|
+
look. Idempotent: if the source file is missing, this is a no-op.
|
|
139
|
+
"""
|
|
140
|
+
src = self._path
|
|
141
|
+
if not src.exists():
|
|
142
|
+
return
|
|
143
|
+
out = Path(output_dir)
|
|
144
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
145
|
+
dst = out / "timings.csv"
|
|
146
|
+
# Plain byte copy — file is small.
|
|
147
|
+
dst.write_bytes(src.read_bytes())
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
FlexTool Workflow Orchestrator
|
|
4
|
+
|
|
5
|
+
Runs the FlexTool workflow in two phases:
|
|
6
|
+
1. Input preparation (optional): convert tabular data into a FlexTool input
|
|
7
|
+
database. Runs only when --tabular-file-path or --csv-directory-path is
|
|
8
|
+
given; otherwise the existing input database is used as-is.
|
|
9
|
+
2. Model execution + output write: run the optimisation model and write
|
|
10
|
+
results in the requested formats. Parquet is always produced; --write-methods
|
|
11
|
+
selects which additional formats (plot, csv, excel) to generate alongside.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import logging
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from flextool.cli._console import run_tool
|
|
21
|
+
from flextool.update_flextool.ensure_settings_db import ensure_settings_db
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main():
|
|
25
|
+
parser = argparse.ArgumentParser(
|
|
26
|
+
description='Execute complete FlexTool workflow',
|
|
27
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
28
|
+
epilog="""
|
|
29
|
+
Examples:
|
|
30
|
+
# Full workflow with Excel input
|
|
31
|
+
python execute_flextool_workflow.py input.sqlite results.sqlite my_scenario --tabular-file-path input.xlsx
|
|
32
|
+
|
|
33
|
+
# Full workflow with CSV input
|
|
34
|
+
python execute_flextool_workflow.py input.sqlite results.sqlite my_scenario --csv-directory-path input_data/
|
|
35
|
+
|
|
36
|
+
# Use an existing input database (no tabular/csv source -> input prep is skipped)
|
|
37
|
+
python execute_flextool_workflow.py input.sqlite results.sqlite my_scenario
|
|
38
|
+
|
|
39
|
+
# Add plots and CSV alongside the default parquet output
|
|
40
|
+
python execute_flextool_workflow.py input.sqlite results.sqlite my_scenario --write-methods plot parquet csv
|
|
41
|
+
"""
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Required arguments
|
|
45
|
+
parser.add_argument('input_db_url', help='Input database URL (e.g., sqlite:///input.sqlite or input.sqlite)')
|
|
46
|
+
parser.add_argument('output_db_url', help='Output database URL for storing result metadata')
|
|
47
|
+
parser.add_argument('scenario_name', help='Name of the scenario to execute')
|
|
48
|
+
|
|
49
|
+
# Input phase arguments. Presence of either flag triggers Phase 1
|
|
50
|
+
# (input preparation); when neither is given, the existing input
|
|
51
|
+
# database is used as-is.
|
|
52
|
+
input_group = parser.add_mutually_exclusive_group()
|
|
53
|
+
input_group.add_argument('--tabular-file-path', help='Path to Excel/ODS input file')
|
|
54
|
+
input_group.add_argument('--csv-directory-path', help='Path to directory containing CSV input files')
|
|
55
|
+
|
|
56
|
+
# Output controls — forwarded to cmd_run_flextool, which writes
|
|
57
|
+
# outputs inline using the live in-memory flex_data + solution.
|
|
58
|
+
parser.add_argument('--write-methods', nargs='+',
|
|
59
|
+
default=['plot', 'parquet', 'csv'],
|
|
60
|
+
choices=['plot', 'parquet', 'excel', 'csv', 'spinedb'],
|
|
61
|
+
help='Output formats to generate (default: plot parquet csv). '
|
|
62
|
+
'Parquet is the canonical output and is always produced when this flag is omitted.')
|
|
63
|
+
parser.add_argument('--output-subdir', help='Subdirectory for output files (default: scenario_name)')
|
|
64
|
+
parser.add_argument('--output-config', default=None,
|
|
65
|
+
help='Path to output configuration YAML file (default: bundled schemas/default_plots.yaml)')
|
|
66
|
+
|
|
67
|
+
# Additional options
|
|
68
|
+
parser.add_argument('--debug',
|
|
69
|
+
nargs='?',
|
|
70
|
+
const='basic',
|
|
71
|
+
default='off',
|
|
72
|
+
choices=['off', 'basic', 'full'],
|
|
73
|
+
metavar='LEVEL',
|
|
74
|
+
help='Diagnostic verbosity level passed through '
|
|
75
|
+
'to cmd_run_flextool. Bare ``--debug`` '
|
|
76
|
+
'selects ``basic``. See cmd_run_flextool '
|
|
77
|
+
'--help for level semantics.')
|
|
78
|
+
|
|
79
|
+
args = parser.parse_args()
|
|
80
|
+
|
|
81
|
+
# Self-heal missing lightweight settings DBs so fresh clones don't
|
|
82
|
+
# fail opaquely when the user forgot to run `flextool-update`. Only
|
|
83
|
+
# seeds output_info / output_settings / comparison_settings by
|
|
84
|
+
# basename; other paths are left untouched.
|
|
85
|
+
for _candidate in (args.output_db_url,):
|
|
86
|
+
try:
|
|
87
|
+
ensure_settings_db(_candidate)
|
|
88
|
+
except Exception as _exc:
|
|
89
|
+
logging.warning("Failed to auto-seed %s: %s", _candidate, _exc)
|
|
90
|
+
|
|
91
|
+
# Phase 1 runs only when a tabular/csv source is provided; otherwise
|
|
92
|
+
# the existing input database is used as-is.
|
|
93
|
+
run_input_prep = bool(args.tabular_file_path or args.csv_directory_path)
|
|
94
|
+
|
|
95
|
+
# Set default output subdirectory to scenario name if not specified
|
|
96
|
+
output_subdir = args.output_subdir if args.output_subdir else args.scenario_name
|
|
97
|
+
|
|
98
|
+
# Phase 1: Input Preparation
|
|
99
|
+
if run_input_prep:
|
|
100
|
+
print(f"\n{'='*70}")
|
|
101
|
+
print("PHASE 1: PREPARING INPUT DATA")
|
|
102
|
+
print(f"{'='*70}")
|
|
103
|
+
if args.tabular_file_path:
|
|
104
|
+
print(f" Input file: {args.tabular_file_path}")
|
|
105
|
+
else:
|
|
106
|
+
print(f" Input directory: {args.csv_directory_path}")
|
|
107
|
+
print(f" Target DB: {args.input_db_url}")
|
|
108
|
+
print()
|
|
109
|
+
|
|
110
|
+
cmd = [sys.executable, '-m', 'flextool.cli.cmd_read_tabular_input', args.input_db_url]
|
|
111
|
+
if args.tabular_file_path:
|
|
112
|
+
cmd.extend(['--tabular-file-path', args.tabular_file_path])
|
|
113
|
+
else:
|
|
114
|
+
cmd.extend(['--csv-directory-path', args.csv_directory_path])
|
|
115
|
+
|
|
116
|
+
result = subprocess.run(cmd)
|
|
117
|
+
if result.returncode != 0:
|
|
118
|
+
print("\n" + "="*70)
|
|
119
|
+
print("ERROR: Input preparation failed with return code", result.returncode)
|
|
120
|
+
print("="*70)
|
|
121
|
+
sys.exit(result.returncode)
|
|
122
|
+
|
|
123
|
+
print(f"\n{'='*70}")
|
|
124
|
+
print("PHASE 1: COMPLETED SUCCESSFULLY")
|
|
125
|
+
print(f"{'='*70}\n")
|
|
126
|
+
else:
|
|
127
|
+
print(f"\n{'='*70}")
|
|
128
|
+
print("PHASE 1: SKIPPED (using existing input database)")
|
|
129
|
+
print(f"{'='*70}\n")
|
|
130
|
+
|
|
131
|
+
# Phase 2: Model Execution + Output Write
|
|
132
|
+
# cmd_run_flextool writes outputs inline using the live in-memory
|
|
133
|
+
# flex_data + solution from the cascade. There is no separate
|
|
134
|
+
# standalone "write outputs" subprocess — the data is gone once the
|
|
135
|
+
# solver exits.
|
|
136
|
+
print(f"\n{'='*70}")
|
|
137
|
+
print("PHASE 2: RUNNING FLEXTOOL MODEL + WRITING OUTPUTS")
|
|
138
|
+
print(f"{'='*70}")
|
|
139
|
+
print(f" Input DB: {args.input_db_url}")
|
|
140
|
+
print(f" Output DB: {args.output_db_url}")
|
|
141
|
+
print(f" Scenario: {args.scenario_name}")
|
|
142
|
+
print(f" Formats: {', '.join(args.write_methods)}")
|
|
143
|
+
print(f" Subdirectory: {output_subdir}")
|
|
144
|
+
print()
|
|
145
|
+
|
|
146
|
+
# Resolve --output-config: bare basename or unset → bundled
|
|
147
|
+
# default from the package; absolute path → used verbatim.
|
|
148
|
+
from flextool._resources import package_data_path
|
|
149
|
+
output_config = args.output_config
|
|
150
|
+
if not output_config or Path(output_config).name in {
|
|
151
|
+
'default_plots.yaml', 'default_comparison_plots.yaml'
|
|
152
|
+
}:
|
|
153
|
+
output_config = str(package_data_path("schemas/default_plots.yaml"))
|
|
154
|
+
|
|
155
|
+
cmd = [sys.executable, '-m', 'flextool.cli.cmd_run_flextool',
|
|
156
|
+
args.input_db_url, args.output_db_url,
|
|
157
|
+
'--scenario-name', args.scenario_name,
|
|
158
|
+
'--output-config', output_config,
|
|
159
|
+
'--output-subdir', output_subdir,
|
|
160
|
+
'--write-methods'] + args.write_methods
|
|
161
|
+
if args.debug != 'off':
|
|
162
|
+
cmd.append(f'--debug={args.debug}')
|
|
163
|
+
|
|
164
|
+
result = subprocess.run(cmd)
|
|
165
|
+
if result.returncode != 0:
|
|
166
|
+
print("\n" + "="*70)
|
|
167
|
+
print("ERROR: Model execution failed with return code", result.returncode)
|
|
168
|
+
print("="*70)
|
|
169
|
+
sys.exit(result.returncode)
|
|
170
|
+
|
|
171
|
+
print(f"\n{'='*70}")
|
|
172
|
+
print("PHASE 2: COMPLETED SUCCESSFULLY")
|
|
173
|
+
print(f"{'='*70}\n")
|
|
174
|
+
|
|
175
|
+
# Success summary
|
|
176
|
+
print(f"\n{'='*70}")
|
|
177
|
+
print("SUCCESS: COMPLETE WORKFLOW EXECUTED")
|
|
178
|
+
print(f"{'='*70}")
|
|
179
|
+
print(f" Scenario: {args.scenario_name}")
|
|
180
|
+
print(f" Input DB: {args.input_db_url}")
|
|
181
|
+
print(f" Output DB: {args.output_db_url}")
|
|
182
|
+
print(f" Output dir: {output_subdir}/")
|
|
183
|
+
print(f"{'='*70}\n")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
if __name__ == '__main__':
|
|
187
|
+
run_tool(main)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""CLI entry point for exporting a FlexTool Spine DB to Excel (.xlsx) format."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from flextool.export_to_tabular import export_to_excel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
parser = argparse.ArgumentParser(
|
|
10
|
+
description="Export a FlexTool Spine DB to Excel (.xlsx) format."
|
|
11
|
+
)
|
|
12
|
+
parser.add_argument(
|
|
13
|
+
"db_url",
|
|
14
|
+
help="URL to FlexTool input database (e.g. sqlite:///input_data.sqlite)",
|
|
15
|
+
)
|
|
16
|
+
parser.add_argument(
|
|
17
|
+
"output_path",
|
|
18
|
+
help="Output Excel file path (.xlsx)",
|
|
19
|
+
)
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--include-advanced",
|
|
22
|
+
action="store_true",
|
|
23
|
+
help="Include advanced sheets (solve sequences, stochastic data)",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--old-format",
|
|
27
|
+
action="store_true",
|
|
28
|
+
help="Use the old v1 format instead of the self-describing v2 format",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--groups",
|
|
32
|
+
default=None,
|
|
33
|
+
help=(
|
|
34
|
+
"Comma-separated list of parameter_group names to keep "
|
|
35
|
+
"(e.g. 'basics,model,timeline,solve_basics'). Parameters "
|
|
36
|
+
"outside these groups are dropped, and sheets with no surviving "
|
|
37
|
+
"columns are removed unless listed in always_include_with_groups."
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
args = parser.parse_args()
|
|
41
|
+
include_groups = (
|
|
42
|
+
[g.strip() for g in args.groups.split(",") if g.strip()]
|
|
43
|
+
if args.groups
|
|
44
|
+
else None
|
|
45
|
+
)
|
|
46
|
+
export_to_excel(
|
|
47
|
+
args.db_url,
|
|
48
|
+
args.output_path,
|
|
49
|
+
include_advanced=args.include_advanced,
|
|
50
|
+
use_new_format=not args.old_format,
|
|
51
|
+
include_groups=include_groups,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
main()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""CLI entry point for importing FlexTool 2.0 sensitivities into a Spine DB."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from flextool.cli._console import run_tool
|
|
8
|
+
from flextool.process_inputs.read_old_flextool import (
|
|
9
|
+
read_old_flextool,
|
|
10
|
+
read_old_flextool_sensitivities,
|
|
11
|
+
)
|
|
12
|
+
from flextool.process_inputs.write_old_flextool_to_db import write_sensitivities_to_db
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main() -> None:
|
|
18
|
+
parser = argparse.ArgumentParser(
|
|
19
|
+
description="Import FlexTool 2.0 sensitivities into a Spine DB."
|
|
20
|
+
)
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
"master_xlsm",
|
|
23
|
+
help="Path to old FlexTool master .xlsm file (contains Sensitivity definitions sheet)",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"base_xlsm",
|
|
27
|
+
help="Path to the base data .xlsm file (for unit_type mapping and base data)",
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"target_db_url",
|
|
31
|
+
help="Target Spine DB URL (must already contain base import)",
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"--base-alternative",
|
|
35
|
+
default="base",
|
|
36
|
+
help="Name of base alternative in the DB (default: base)",
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--verbose", "-v",
|
|
40
|
+
action="store_true",
|
|
41
|
+
help="Enable verbose (DEBUG) logging",
|
|
42
|
+
)
|
|
43
|
+
args = parser.parse_args()
|
|
44
|
+
|
|
45
|
+
logging.basicConfig(
|
|
46
|
+
level=logging.DEBUG if args.verbose else logging.INFO,
|
|
47
|
+
format="%(levelname)s: %(message)s",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
print(f"Reading base data from: {args.base_xlsm}")
|
|
51
|
+
data = read_old_flextool(args.base_xlsm)
|
|
52
|
+
|
|
53
|
+
print(f"Reading sensitivities from: {args.master_xlsm}")
|
|
54
|
+
sensitivities = read_old_flextool_sensitivities(args.master_xlsm)
|
|
55
|
+
|
|
56
|
+
if not sensitivities:
|
|
57
|
+
print("No sensitivities found. Nothing to import.")
|
|
58
|
+
sys.exit(0)
|
|
59
|
+
|
|
60
|
+
print(f"Found {len(sensitivities)} sensitivity scenario(s).")
|
|
61
|
+
for name, overrides in sensitivities.items():
|
|
62
|
+
print(f" - {name}: {len(overrides)} override(s)")
|
|
63
|
+
|
|
64
|
+
print(f"Writing sensitivities to: {args.target_db_url}")
|
|
65
|
+
write_sensitivities_to_db(
|
|
66
|
+
sensitivities,
|
|
67
|
+
data,
|
|
68
|
+
args.target_db_url,
|
|
69
|
+
base_alternative=args.base_alternative,
|
|
70
|
+
)
|
|
71
|
+
print("Sensitivity import complete!")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
run_tool(main)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from flextool.update_flextool.db_migration import migrate_database
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
parser = argparse.ArgumentParser()
|
|
7
|
+
parser.add_argument('filepath', help="Filepath of the database, absolute or relative to flextool folder")
|
|
8
|
+
args = parser.parse_args()
|
|
9
|
+
migrate_database(args.filepath)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if __name__ == '__main__':
|
|
13
|
+
main()
|