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
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"""Output writer adapter.
|
|
2
|
+
|
|
3
|
+
Bridges the polars-build :class:`polar_high.Solution` to the post-solve
|
|
4
|
+
writers in ``flextool.process_outputs``. Feeds them the live
|
|
5
|
+
``highspy.Highs`` instance the polars LP just produced + the work
|
|
6
|
+
folder so they read the support CSVs (``p_step_duration.csv``,
|
|
7
|
+
``process_block.csv``, …) emitted by ``FlexToolRunner.write_input`` /
|
|
8
|
+
``preprocessing_solve_time``.
|
|
9
|
+
|
|
10
|
+
Variable-name reconciliation: the polars LP carries
|
|
11
|
+
``v_invest_p[<entity>,<period>]`` / ``v_invest_n[…]`` /
|
|
12
|
+
``v_divest_p[…]`` / ``v_divest_n[…]`` whereas the extractor expects
|
|
13
|
+
unified ``v_invest[…]`` / ``v_divest[…]``. We resolve in-place by
|
|
14
|
+
renaming the live HiGHS column names BEFORE delegating;
|
|
15
|
+
``passColName`` accepts a post-solve update. The rename is a no-op
|
|
16
|
+
for variables already named correctly.
|
|
17
|
+
|
|
18
|
+
``v_ramp`` is absent from the polars LP entirely — the extractor
|
|
19
|
+
handles "no matching columns" gracefully by emitting an empty-but-
|
|
20
|
+
well-shaped parquet (see ``read_highs_solution.extract_variable``'s
|
|
21
|
+
``if not seen_cols`` fallback).
|
|
22
|
+
|
|
23
|
+
The carry-forward dependency is the support CSV cluster
|
|
24
|
+
(``p_step_duration.csv``, ``process_block.csv``, ``entity_block.csv``,
|
|
25
|
+
…) read by ``read_highs_solution._apply_block_expand`` /
|
|
26
|
+
``handoff_writers._load_*``. Δ.2-Δ.10 retire those one cluster at a
|
|
27
|
+
time; Δ.1's job is to consume them via the writers, not to replace.
|
|
28
|
+
|
|
29
|
+
``periods_already_emitted`` carrier
|
|
30
|
+
-----------------------------------
|
|
31
|
+
``handoff_writers._bump_period_capacity`` accumulates this set on disk
|
|
32
|
+
into ``solve_data/period_capacity.csv``. Δ.1 removed the in-memory
|
|
33
|
+
mirror that previously lived on :class:`SolveHandoff` and put it
|
|
34
|
+
where it belongs — :class:`OutputWriterState`, this module's
|
|
35
|
+
per-cascade scratch carrier.
|
|
36
|
+
"""
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import logging
|
|
40
|
+
from dataclasses import dataclass, field
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import TYPE_CHECKING
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from polar_high import Solution
|
|
46
|
+
|
|
47
|
+
from flextool.engine_polars._solve_handoff import SolveHandoff
|
|
48
|
+
from flextool.engine_polars.input import FlexData
|
|
49
|
+
|
|
50
|
+
_logger = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# Per-cascade writer state
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class OutputWriterState:
|
|
60
|
+
"""Cross-solve carrier for the output writer.
|
|
61
|
+
|
|
62
|
+
Δ.1 placement: ``periods_already_emitted`` was previously a field on
|
|
63
|
+
:class:`SolveHandoff` (and is still populated there for backward
|
|
64
|
+
compatibility — Δ.2-Δ.10 will retire that mirror). The canonical
|
|
65
|
+
in-memory location for new consumers is this writer-owned state.
|
|
66
|
+
|
|
67
|
+
The set is bumped after each successful adapter call by reading
|
|
68
|
+
back the file ``handoff_writers._bump_period_capacity`` just
|
|
69
|
+
overwrote; that's the same source of truth flextool uses.
|
|
70
|
+
|
|
71
|
+
Other writer-level scratch state (e.g. solve_progress.csv append
|
|
72
|
+
pointers if ever ported) belongs here too.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
# Per-period bare-set; periods accumulated by ``_bump_period_capacity``
|
|
76
|
+
# across the cascade. Empty initially; grows monotonically.
|
|
77
|
+
periods_already_emitted: set[str] = field(default_factory=set)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# Variable-name reconciliation
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Map from polars-LP variable name (in ``Solution._vars``) to the
|
|
86
|
+
# flextool-writer variable name (the prefix the extractor's regex
|
|
87
|
+
# expects). Entries with identical source/target are no-ops; we list
|
|
88
|
+
# them only for clarity.
|
|
89
|
+
_VAR_RENAME: dict[str, str] = {
|
|
90
|
+
"v_invest_p": "v_invest",
|
|
91
|
+
"v_invest_n": "v_invest",
|
|
92
|
+
"v_divest_p": "v_divest",
|
|
93
|
+
"v_divest_n": "v_divest",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _rename_invest_columns(sol: "Solution") -> None:
|
|
98
|
+
"""Rename ``v_invest_p`` / ``v_invest_n`` / ``v_divest_*`` columns
|
|
99
|
+
in-place on the live HiGHS instance to the unified flextool names.
|
|
100
|
+
|
|
101
|
+
The polars LP splits each invest/divest decision into two
|
|
102
|
+
non-negative columns (process-side and node-side); flextool's
|
|
103
|
+
writers expect a single ``v_invest[<entity>,<period>]`` /
|
|
104
|
+
``v_divest[<entity>,<period>]`` family. Since the entity sets for
|
|
105
|
+
``_p`` and ``_n`` are disjoint (one is process-only, the other
|
|
106
|
+
node-only, by construction in
|
|
107
|
+
``flextool/engine_polars/model.py``), the union of the renamed
|
|
108
|
+
columns is itself a well-formed ``v_invest`` family.
|
|
109
|
+
|
|
110
|
+
``passColName`` is invoked per column-id derived from the polars
|
|
111
|
+
Var's ``frame["col_id"]``. This is O(n_cols_to_rename) — typically
|
|
112
|
+
< 1000 for realistic cases. No-op if ``sol.highs is None``.
|
|
113
|
+
"""
|
|
114
|
+
h = getattr(sol, "highs", None)
|
|
115
|
+
if h is None:
|
|
116
|
+
return # Solution synthesized outside a real solve — adapter
|
|
117
|
+
# caller is responsible for ensuring this doesn't happen
|
|
118
|
+
# on the cascade path.
|
|
119
|
+
|
|
120
|
+
for src_name, dst_name in _VAR_RENAME.items():
|
|
121
|
+
if src_name not in sol._vars:
|
|
122
|
+
continue
|
|
123
|
+
v = sol._vars[src_name]
|
|
124
|
+
if not v.dims:
|
|
125
|
+
continue
|
|
126
|
+
# Build the destination col-name strings — same bracket payload,
|
|
127
|
+
# just a different prefix. We reuse the existing col_names list
|
|
128
|
+
# we already computed at solve time (sol.col_names) to avoid
|
|
129
|
+
# re-rendering them; the only diff is the prefix.
|
|
130
|
+
ids = v.frame["col_id"].to_numpy().tolist()
|
|
131
|
+
for cid in ids:
|
|
132
|
+
old_name = sol.col_names[cid]
|
|
133
|
+
# old_name is "v_invest_p[entity,period]" — replace the
|
|
134
|
+
# prefix only, keep the bracketed payload verbatim.
|
|
135
|
+
assert old_name is not None and old_name.startswith(src_name + "[")
|
|
136
|
+
new_name = dst_name + old_name[len(src_name):]
|
|
137
|
+
h.passColName(cid, new_name)
|
|
138
|
+
# Mirror onto the Solution's name array so downstream
|
|
139
|
+
# consumers that look at ``sol.col_names`` (e.g. tests) see
|
|
140
|
+
# the unified name too.
|
|
141
|
+
sol.col_names[cid] = new_name
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _restore_space_mangled_names(sol: "Solution") -> None:
|
|
145
|
+
"""Restore original entity names that HiGHS' solution-file writer
|
|
146
|
+
mangled (spaces → underscores) on the live in-process LP.
|
|
147
|
+
|
|
148
|
+
``writeSolution`` (triggered by ``write_solution_to_file``) rewrites
|
|
149
|
+
every space in the LP's column/row names to an underscore, in place,
|
|
150
|
+
and the output extractors read those mutated names back via
|
|
151
|
+
``allVariableNames()`` / ``getLp().row_names_``. :class:`Solution`
|
|
152
|
+
keeps the un-mangled originals in ``col_names`` / ``row_names``
|
|
153
|
+
(rendered from the polars Var frames, position-aligned with the LP),
|
|
154
|
+
so we re-pass the ones that contain a space.
|
|
155
|
+
|
|
156
|
+
HiGHS mangles *only* the space character — tabs, slashes, commas,
|
|
157
|
+
quotes and brackets survive verbatim — so a ``' ' in name`` test
|
|
158
|
+
covers exactly the affected names. Models with no spaces do zero
|
|
159
|
+
``passColName`` / ``passRowName`` calls, keeping byte-parity for
|
|
160
|
+
every existing fixture. Gated on ``passRowName`` so it runs only for
|
|
161
|
+
a real ``highspy.Highs`` (the cold-path ``_SolHighsShim`` never
|
|
162
|
+
mangles names and exposes no such method).
|
|
163
|
+
"""
|
|
164
|
+
h = getattr(sol, "highs", None)
|
|
165
|
+
if h is None or not hasattr(h, "passRowName"):
|
|
166
|
+
return # cold/subprocess shim (immune) or no live solver
|
|
167
|
+
|
|
168
|
+
col_names = getattr(sol, "col_names", None)
|
|
169
|
+
if col_names is not None:
|
|
170
|
+
for cid, name in enumerate(col_names):
|
|
171
|
+
if name is not None and " " in name:
|
|
172
|
+
h.passColName(cid, name)
|
|
173
|
+
|
|
174
|
+
row_names = getattr(sol, "row_names", None)
|
|
175
|
+
if row_names is not None:
|
|
176
|
+
for rid, name in enumerate(row_names):
|
|
177
|
+
if name is not None and " " in name:
|
|
178
|
+
h.passRowName(rid, name)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
# Main entry point
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def write_outputs_for_solve(
|
|
187
|
+
sol: "Solution",
|
|
188
|
+
*,
|
|
189
|
+
work_folder: Path | str,
|
|
190
|
+
solve_name: str,
|
|
191
|
+
prior_handoff: "SolveHandoff | None" = None,
|
|
192
|
+
writer_state: "OutputWriterState | None" = None,
|
|
193
|
+
flex_data: "FlexData | None" = None,
|
|
194
|
+
is_first_solve: bool | None = None,
|
|
195
|
+
scale_the_objective: float | None = None,
|
|
196
|
+
provider: "object | None" = None,
|
|
197
|
+
csv_dump: bool = False,
|
|
198
|
+
) -> None:
|
|
199
|
+
"""Adapter — emit TIER A artefacts for one cascade sub-solve.
|
|
200
|
+
|
|
201
|
+
Calls flextool's ``process_outputs.read_highs_solution.write_all_variables``
|
|
202
|
+
(~30 variable parquets to ``output_raw/``) and
|
|
203
|
+
``process_outputs.handoff_writers.write_all_handoffs`` (handoff
|
|
204
|
+
CSVs in ``output_raw/``) using the live HiGHS
|
|
205
|
+
instance carried on :class:`Solution`. The writers consume the
|
|
206
|
+
support CSVs in ``solve_data/`` that flextool's preprocessing has
|
|
207
|
+
already produced — Δ.1's carry-forward dependency.
|
|
208
|
+
|
|
209
|
+
``writer_state`` (optional) accumulates cross-solve scratch
|
|
210
|
+
(``periods_already_emitted``). When present, the set is updated
|
|
211
|
+
from the freshly-bumped ``solve_data/period_capacity.csv``.
|
|
212
|
+
|
|
213
|
+
No-ops gracefully when ``sol.highs is None`` (no live solver
|
|
214
|
+
instance available — typically a synthesized Solution in a unit
|
|
215
|
+
test); callers should pass solutions from a real
|
|
216
|
+
:func:`polar_high.Problem.solve` call. Post task #15 every cold
|
|
217
|
+
solve (HiGHS and commercial) goes through
|
|
218
|
+
:func:`flextool.engine_polars._subprocess_solve.solve_via_subprocess`,
|
|
219
|
+
which always returns a Solution with a populated ``highs`` instance
|
|
220
|
+
read back from the MPS — so the None branch is only hit by unit
|
|
221
|
+
tests that synthesize Solutions by hand.
|
|
222
|
+
"""
|
|
223
|
+
h = getattr(sol, "highs", None)
|
|
224
|
+
if h is None:
|
|
225
|
+
_logger.warning(
|
|
226
|
+
"write_outputs_for_solve: Solution carries no live HiGHS "
|
|
227
|
+
"instance (sol.highs is None); skipping output emission for "
|
|
228
|
+
"solve '%s'", solve_name,
|
|
229
|
+
)
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
work_folder = Path(work_folder)
|
|
233
|
+
output_dir = work_folder / "output_raw"
|
|
234
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
235
|
+
|
|
236
|
+
# Variable-name shim — flextool's writers see ``v_invest[…]`` /
|
|
237
|
+
# ``v_divest[…]`` after this call. In-place mutation on the live
|
|
238
|
+
# HiGHS; safe because the Solution is read-only post-solve and the
|
|
239
|
+
# adapter consumes it once.
|
|
240
|
+
_rename_invest_columns(sol)
|
|
241
|
+
|
|
242
|
+
# Undo HiGHS' space→underscore name mangling. When the in-process
|
|
243
|
+
# solve writes a solution file (``write_solution_to_file=true``,
|
|
244
|
+
# highs.opt.template), HiGHS' ``writeSolution`` permanently rewrites
|
|
245
|
+
# spaces to underscores in the live LP's column/row names (it happens
|
|
246
|
+
# for every solution style — the GLPSOL/pretty writers are whitespace-
|
|
247
|
+
# delimited). The output extractors below read ``h.allVariableNames()``
|
|
248
|
+
# / ``h.getLp().row_names_`` from that same mutated LP, so any entity
|
|
249
|
+
# whose name contains a space (e.g. a node "AY. ATHANASIOS") comes back
|
|
250
|
+
# with an underscore and no longer joins against ``flex_data``'s
|
|
251
|
+
# original names — a KeyError in calc_capacity_flows / silently-empty
|
|
252
|
+
# dual columns. ``Solution.col_names`` / ``.row_names`` are rendered
|
|
253
|
+
# from the polars Var frames (never round-tripped through the mangling
|
|
254
|
+
# writer), so they are the authoritative, position-aligned originals;
|
|
255
|
+
# restore them onto the live HiGHS. The cold/subprocess path is immune
|
|
256
|
+
# by construction (generic MPS names, index-mapped back — see
|
|
257
|
+
# _subprocess_solve) and its shim exposes no ``passRowName``, so gate
|
|
258
|
+
# on that to run only for the real in-process ``highspy.Highs``.
|
|
259
|
+
_restore_space_mangled_names(sol)
|
|
260
|
+
|
|
261
|
+
# ``scale_the_objective`` — the polars LP now applies the resolved
|
|
262
|
+
# per-solve ``scale_the_objective`` at LP construction (engine_polars/
|
|
263
|
+
# scaling.py auto-apply, commits 19aca81b / 2682cea1 / 4c3b49ca /
|
|
264
|
+
# 8bac7d70). ``_orchestration._write_scale_csv_and_report`` already
|
|
265
|
+
# writes ``solve_data/scale_the_objective.csv`` with the effective
|
|
266
|
+
# value before this adapter runs; the downstream writers' multiplier
|
|
267
|
+
# (``_resolve_inv_scale_the_objective``) then un-scales objective /
|
|
268
|
+
# dual values back to user-facing units. An earlier shim here
|
|
269
|
+
# forced the CSV to ``value=1.0`` from the era when the polars LP
|
|
270
|
+
# did NOT scale — that override is now destructive (it canceled
|
|
271
|
+
# the un-scale, leaving ``v_obj`` at the LP-internal magnitude
|
|
272
|
+
# ~1e6× too small). Removed: trust the upstream CSV.
|
|
273
|
+
|
|
274
|
+
# Late imports — keep the adapter's import surface narrow for the
|
|
275
|
+
# 99% of callers that never instantiate it.
|
|
276
|
+
from flextool.process_outputs.read_highs_solution import (
|
|
277
|
+
_actual_solve_name,
|
|
278
|
+
write_all_variables,
|
|
279
|
+
)
|
|
280
|
+
from flextool.process_outputs.handoff_writers import write_all_handoffs
|
|
281
|
+
|
|
282
|
+
# Some scenarios use a "complete-solve" name distinct from the
|
|
283
|
+
# roll/solve-current name written into solve_data/ CSVs. Mirror
|
|
284
|
+
# solver_runner._run_highs_or_cplex's resolution.
|
|
285
|
+
roll_name = _actual_solve_name(work_folder, solve_name, provider=provider)
|
|
286
|
+
|
|
287
|
+
# Multi-solve output union — record this sub-solve in the creation-
|
|
288
|
+
# order manifest (``output_raw/_solve_order.txt``). The per-roll
|
|
289
|
+
# variable / param / set parquet readers concat in this order so the
|
|
290
|
+
# ``drop_levels`` ``keep='last'`` dedup picks the same realizing roll
|
|
291
|
+
# the in-memory oracle does. Authoritative on the default
|
|
292
|
+
# ``keep_solutions=False`` flow, where ``solve__p_entity_pre_existing
|
|
293
|
+
# .csv`` is absent (so ``load_solve_order`` would otherwise fall back
|
|
294
|
+
# to filesystem glob order and mis-pick the realizing solve label).
|
|
295
|
+
try:
|
|
296
|
+
from flextool.process_outputs.solve_order import append_solve_order
|
|
297
|
+
|
|
298
|
+
append_solve_order(output_dir, roll_name)
|
|
299
|
+
except Exception as exc: # noqa: BLE001
|
|
300
|
+
_logger.warning(
|
|
301
|
+
"append_solve_order failed for solve '%s': %s", solve_name, exc,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
sd = work_folder / "solve_data"
|
|
305
|
+
realized_dispatch_csv = sd / "realized_dispatch.csv"
|
|
306
|
+
realized_periods_csv = sd / "realized_invest_periods_of_current_solve.csv"
|
|
307
|
+
|
|
308
|
+
# Provider-only existence check. Post-Step-2 the cascade has one
|
|
309
|
+
# data pathway; the on-disk CSVs are debug dumps, never an input.
|
|
310
|
+
def _provider_has_frame(name: str) -> bool:
|
|
311
|
+
return provider is not None and provider.has(name)
|
|
312
|
+
try:
|
|
313
|
+
write_all_variables(
|
|
314
|
+
h,
|
|
315
|
+
solve_name=roll_name,
|
|
316
|
+
output_dir=output_dir,
|
|
317
|
+
realized_dispatch_csv=(
|
|
318
|
+
realized_dispatch_csv
|
|
319
|
+
if _provider_has_frame(
|
|
320
|
+
"solve_data/realized_dispatch",
|
|
321
|
+
) else None
|
|
322
|
+
),
|
|
323
|
+
realized_periods_csv=(
|
|
324
|
+
realized_periods_csv
|
|
325
|
+
if _provider_has_frame(
|
|
326
|
+
"solve_data/realized_invest_periods_of_current_solve",
|
|
327
|
+
) else None
|
|
328
|
+
),
|
|
329
|
+
# Phase G — route in-memory carriers through to the
|
|
330
|
+
# extractor + custom writers so per-iter file reads
|
|
331
|
+
# (_load_canonical_*, _load_inflation_*, _load_row_scaler,
|
|
332
|
+
# scale_the_objective.csv) can short-circuit. CSV fallback
|
|
333
|
+
# preserved.
|
|
334
|
+
flex_data=flex_data,
|
|
335
|
+
scale_the_objective=scale_the_objective,
|
|
336
|
+
provider=provider,
|
|
337
|
+
)
|
|
338
|
+
except Exception as exc: # noqa: BLE001
|
|
339
|
+
_logger.warning(
|
|
340
|
+
"write_all_variables failed for solve '%s': %s", solve_name, exc,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
write_all_handoffs(
|
|
345
|
+
h, solve_name=roll_name, work_folder=work_folder,
|
|
346
|
+
prior_handoff=prior_handoff,
|
|
347
|
+
flex_data=flex_data,
|
|
348
|
+
writer_state=writer_state,
|
|
349
|
+
is_first_solve=is_first_solve,
|
|
350
|
+
scale_the_objective=scale_the_objective,
|
|
351
|
+
provider=provider,
|
|
352
|
+
csv_dump=csv_dump,
|
|
353
|
+
)
|
|
354
|
+
except Exception as exc: # noqa: BLE001
|
|
355
|
+
_logger.warning(
|
|
356
|
+
"write_all_handoffs failed for solve '%s': %s", solve_name, exc,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Multi-solve output union (stage 2a) — persist this roll's realized
|
|
360
|
+
# slice of every per-roll-VARYING parameter to ``output_raw/`` as
|
|
361
|
+
# parquet, mirroring the variable convention
|
|
362
|
+
# (``{attr}__{solve}.parquet``). Runs HERE, while the Solution is
|
|
363
|
+
# live, because ``entity_all_capacity`` needs ``solution.value(
|
|
364
|
+
# "v_invest")`` (nulled on prior steps by memory slimming). The
|
|
365
|
+
# stage-3 reader unions these per-roll parquets into the full
|
|
366
|
+
# output, fixing the last-roll-only collapse. ``flex_data`` carries
|
|
367
|
+
# the realized (period, time) dispatch slice; the realized-invest
|
|
368
|
+
# periods come from the per-solve CSV / provider frame (same source
|
|
369
|
+
# the variable writer's ``realized_periods_csv`` uses).
|
|
370
|
+
if flex_data is not None:
|
|
371
|
+
from flextool.process_outputs.persist_realized_slice import (
|
|
372
|
+
write_all_params_realized_slice,
|
|
373
|
+
write_all_sets_realized_slice,
|
|
374
|
+
)
|
|
375
|
+
from flextool.process_outputs.read_highs_solution import (
|
|
376
|
+
_load_realized_periods,
|
|
377
|
+
)
|
|
378
|
+
try:
|
|
379
|
+
invest_periods = _load_realized_periods(
|
|
380
|
+
realized_periods_csv
|
|
381
|
+
if _provider_has_frame(
|
|
382
|
+
"solve_data/realized_invest_periods_of_current_solve",
|
|
383
|
+
) else None,
|
|
384
|
+
provider=provider,
|
|
385
|
+
)
|
|
386
|
+
except Exception as exc: # noqa: BLE001
|
|
387
|
+
_logger.warning(
|
|
388
|
+
"loading realized invest periods failed for solve '%s': %s",
|
|
389
|
+
solve_name, exc,
|
|
390
|
+
)
|
|
391
|
+
invest_periods = None
|
|
392
|
+
try:
|
|
393
|
+
write_all_params_realized_slice(
|
|
394
|
+
flex_data,
|
|
395
|
+
sol,
|
|
396
|
+
solve_name=roll_name,
|
|
397
|
+
output_dir=output_dir,
|
|
398
|
+
realized_invest_periods=invest_periods,
|
|
399
|
+
)
|
|
400
|
+
except Exception as exc: # noqa: BLE001
|
|
401
|
+
_logger.warning(
|
|
402
|
+
"write_all_params_realized_slice failed for solve '%s': %s",
|
|
403
|
+
solve_name, exc,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# Multi-solve output union (stage 2b) — persist this roll's
|
|
407
|
+
# realized slice of every per-roll-VARYING set (incl. the
|
|
408
|
+
# storage-adjacency sets ``dtt`` / ``dtttdt``) to ``output_raw/``
|
|
409
|
+
# as parquet, mirroring the variable / param convention. Runs
|
|
410
|
+
# HERE, while the Solution is live, alongside the param writer so
|
|
411
|
+
# the per-roll realized window (``flex_data.realized_dispatch``)
|
|
412
|
+
# is available. The stage-3 reader unions these per-roll set
|
|
413
|
+
# parquets, replacing the dead ``read_sets_multi`` source.
|
|
414
|
+
try:
|
|
415
|
+
write_all_sets_realized_slice(
|
|
416
|
+
flex_data,
|
|
417
|
+
sol,
|
|
418
|
+
solve_name=roll_name,
|
|
419
|
+
output_dir=output_dir,
|
|
420
|
+
realized_invest_periods=invest_periods,
|
|
421
|
+
)
|
|
422
|
+
except Exception as exc: # noqa: BLE001
|
|
423
|
+
_logger.warning(
|
|
424
|
+
"write_all_sets_realized_slice failed for solve '%s': %s",
|
|
425
|
+
solve_name, exc,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# Phase G — ``writer_state.periods_already_emitted`` is updated
|
|
429
|
+
# in-place by ``handoff_writers._bump_period_capacity`` when the
|
|
430
|
+
# writer_state is threaded through (above). The previous paranoia
|
|
431
|
+
# re-read of ``solve_data/period_capacity.csv`` was redundant because
|
|
432
|
+
# ``_bump_period_capacity`` is the sole producer and now updates
|
|
433
|
+
# both sinks atomically. No file re-read here.
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
__all__ = [
|
|
437
|
+
"OutputWriterState",
|
|
438
|
+
"write_outputs_for_solve",
|
|
439
|
+
]
|