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,164 @@
|
|
|
1
|
+
"""In-memory carrier of state passed between solves.
|
|
2
|
+
|
|
3
|
+
Home of the canonical :class:`SolveHandoff` dataclass — the typed
|
|
4
|
+
record of "what one solve produced that the next solve(s) consume".
|
|
5
|
+
Built post-solve by :func:`flextool.engine_polars.input.build_handoff_from_solution`
|
|
6
|
+
directly from the polar_high ``Solution`` object; consumed by the next
|
|
7
|
+
sub-solve via the orchestrator's iteration-start translator
|
|
8
|
+
(:func:`_provider_translators.translate_handoff_to_provider`) which
|
|
9
|
+
fans each field into the Provider under a ``handoff/<field>`` key.
|
|
10
|
+
|
|
11
|
+
Phase 3 of ``specs/provider_consolidation.md`` retired the legacy
|
|
12
|
+
``capture_post_solve()`` disk-read constructor — the cascade was
|
|
13
|
+
already using ``build_handoff_from_solution`` exclusively; the
|
|
14
|
+
capture-from-disk function was dead code and has been removed,
|
|
15
|
+
together with the three carrier fields it was the only populator
|
|
16
|
+
for (``fix_storage_timesteps``, ``ed_history_realized_first``,
|
|
17
|
+
``edd_history``). Consumers of those fields already fell through
|
|
18
|
+
to Provider/CSV reads when the carrier was ``None`` (the universal
|
|
19
|
+
state in the cascade path).
|
|
20
|
+
|
|
21
|
+
Carrier schemas (each is the on-disk equivalent's columns, renamed
|
|
22
|
+
for in-memory uniformity — string keys + ``value`` for single-value
|
|
23
|
+
carriers, named metric columns for multi-value):
|
|
24
|
+
|
|
25
|
+
realized_invest [entity, period, value]
|
|
26
|
+
realized_existing [entity, period, value]
|
|
27
|
+
divest_cumulative [entity, value]
|
|
28
|
+
roll_end_state [node, value]
|
|
29
|
+
fix_storage_quantity [node, period, step, p_fix_storage_quantity]
|
|
30
|
+
fix_storage_price [node, period, step, p_fix_storage_price]
|
|
31
|
+
fix_storage_usage [node, period, step, p_fix_storage_usage]
|
|
32
|
+
cumulative_co2 [group, period, value]
|
|
33
|
+
cumulative_commodity [commodity, tier, period, p_ladder_cum_realized_mwh]
|
|
34
|
+
cum_sim_hours [period, p_ladder_cum_sim_hours]
|
|
35
|
+
|
|
36
|
+
Phase 4.1a moved ``cumulative_commodity`` and ``cum_sim_hours`` to their
|
|
37
|
+
canonical column names (matching the ``solve_data/`` Provider key
|
|
38
|
+
schemas) so the iteration-start handoff translator can route the
|
|
39
|
+
frames straight through to ``handoff/cumulative_commodity`` /
|
|
40
|
+
``handoff/cum_sim_hours`` without a per-iteration rename.
|
|
41
|
+
|
|
42
|
+
(Δ.1 — ``periods_already_emitted`` was previously listed here; it
|
|
43
|
+
moved to ``_output_writer.OutputWriterState`` since it gates writer-
|
|
44
|
+
side emission and isn't a true solver-handoff carrier.)
|
|
45
|
+
|
|
46
|
+
The three ``fix_storage_*`` narrow carriers replaced the legacy
|
|
47
|
+
single wide ``fix_storage`` field (retired in Phase 4.1l). Each
|
|
48
|
+
metric is independent, so a separate per-metric carrier matches the
|
|
49
|
+
producer/consumer reality without NULL-padding columns.
|
|
50
|
+
|
|
51
|
+
``realized_invest`` and ``realized_existing`` together cover the two
|
|
52
|
+
columns of ``solve_data/p_entity_period_existing_capacity.csv``:
|
|
53
|
+
``realized_invest`` is what was *built this solve*, ``realized_existing``
|
|
54
|
+
is the resolved existing-capacity history (pre-existing decay +
|
|
55
|
+
divest enter this column and aren't reconstructible from
|
|
56
|
+
``realized_invest`` alone).
|
|
57
|
+
"""
|
|
58
|
+
from __future__ import annotations
|
|
59
|
+
|
|
60
|
+
from dataclasses import dataclass
|
|
61
|
+
|
|
62
|
+
import polars as pl
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class SolveHandoff:
|
|
68
|
+
"""Per-solve output → dependent-solve input carrier.
|
|
69
|
+
|
|
70
|
+
Each field is a polars DataFrame in the schema documented in this
|
|
71
|
+
module's docstring, or ``None`` when that carrier kind isn't active
|
|
72
|
+
for this handoff. Solves are identified by full solve name; the
|
|
73
|
+
handoff represents the *output* of one solve becoming the *input*
|
|
74
|
+
to its dependent (child / next-roll) solves.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
# Realized invest *built this solve* in absolute units (post-unitsize).
|
|
78
|
+
# Producer: any solve with v_invest > 0.
|
|
79
|
+
# Consumer: subsequent solves' preprocessing.
|
|
80
|
+
# File equivalent: ``p_entity_period_invested_capacity`` column of
|
|
81
|
+
# solve_data/p_entity_period_existing_capacity.csv.
|
|
82
|
+
realized_invest: pl.DataFrame | None = None
|
|
83
|
+
|
|
84
|
+
# Realized existing-capacity history per (entity, period) — captures
|
|
85
|
+
# pre-existing decay + divest that ``realized_invest`` doesn't.
|
|
86
|
+
# File equivalent: ``p_entity_period_existing_capacity`` column of
|
|
87
|
+
# solve_data/p_entity_period_existing_capacity.csv.
|
|
88
|
+
realized_existing: pl.DataFrame | None = None
|
|
89
|
+
|
|
90
|
+
# Cumulative divested capacity per entity (scalar, not per-period).
|
|
91
|
+
# Carries pre-existing decay forward across solves.
|
|
92
|
+
# File equivalent: solve_data/p_entity_divested.csv.
|
|
93
|
+
divest_cumulative: pl.DataFrame | None = None
|
|
94
|
+
|
|
95
|
+
# End-of-roll storage state for ``bind_forward_only`` carry-over.
|
|
96
|
+
# Producer: prior roll's v_state at its last (d, t).
|
|
97
|
+
# Consumer: next roll's nodeBalance_eq first-timestep term.
|
|
98
|
+
# File equivalent: solve_data/p_roll_continue_state.csv.
|
|
99
|
+
roll_end_state: pl.DataFrame | None = None
|
|
100
|
+
|
|
101
|
+
# Upward feedback carrier: nested dispatch sub-solve's realized
|
|
102
|
+
# end-of-horizon v_state routed UPWARD to its parent storage
|
|
103
|
+
# solve's next roll. When a dispatch sub-solve completes, the
|
|
104
|
+
# parent storage solve's next roll prefers this realized state
|
|
105
|
+
# over its own previously-predicted state — closing the loop
|
|
106
|
+
# between the lower-information storage plan and the higher-
|
|
107
|
+
# information dispatch result. Always-on for any storage→dispatch
|
|
108
|
+
# nesting; no opt-in flag (per specs/feature_fixes.md §1).
|
|
109
|
+
# Schema mirrors ``roll_end_state``: ``[node, value]``.
|
|
110
|
+
# Producer: dispatch sub-solve's roll_end_state (copy at handoff
|
|
111
|
+
# capture; no separate v_state extraction needed).
|
|
112
|
+
# Consumer: parent storage's continuation-roll
|
|
113
|
+
# ``p_roll_continue_state`` (preferred over sequential prior when
|
|
114
|
+
# available).
|
|
115
|
+
# No file equivalent (in-memory only).
|
|
116
|
+
upward_roll_end_state: pl.DataFrame | None = None
|
|
117
|
+
|
|
118
|
+
# Storage quota imposed by parent solve on child solve — narrow
|
|
119
|
+
# per-metric carriers in canonical column schema. The trio is
|
|
120
|
+
# independent (parent may set quantity-only, price-only, or any
|
|
121
|
+
# combination); each metric travels in its own field.
|
|
122
|
+
# Producer: parent solve's v_state + cost duals.
|
|
123
|
+
# Consumer: child's fix_storage_* constraints.
|
|
124
|
+
# File equivalents: solve_data/fix_storage_{quantity,price,usage}.csv
|
|
125
|
+
# Each schema: ``[node, period, step, p_fix_storage_<metric>]``.
|
|
126
|
+
fix_storage_quantity: pl.DataFrame | None = None
|
|
127
|
+
fix_storage_price: pl.DataFrame | None = None
|
|
128
|
+
fix_storage_usage: pl.DataFrame | None = None
|
|
129
|
+
|
|
130
|
+
# Running CO2 totals carried across rolls for cumulative-cap constraint.
|
|
131
|
+
# File equivalent: solve_data/co2_cum_realized_tonnes.csv.
|
|
132
|
+
cumulative_co2: pl.DataFrame | None = None
|
|
133
|
+
|
|
134
|
+
# Running per-tier commodity consumption for cumulative ladder pricing.
|
|
135
|
+
# File equivalent: solve_data/commodity_ladder_cumulative.csv (per-tier mwh).
|
|
136
|
+
cumulative_commodity: pl.DataFrame | None = None
|
|
137
|
+
|
|
138
|
+
# Running simulated-hour total per period. Independent 1-D
|
|
139
|
+
# carrier shared by ladder + CO2-cap constraints.
|
|
140
|
+
# File equivalent: solve_data/ladder_cum_sim_hours.csv.
|
|
141
|
+
cum_sim_hours: pl.DataFrame | None = None
|
|
142
|
+
|
|
143
|
+
_FIELDS = (
|
|
144
|
+
"realized_invest", "realized_existing", "divest_cumulative",
|
|
145
|
+
"roll_end_state", "upward_roll_end_state",
|
|
146
|
+
"fix_storage_quantity", "fix_storage_price", "fix_storage_usage",
|
|
147
|
+
"cumulative_co2", "cumulative_commodity", "cum_sim_hours",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def is_empty(self) -> bool:
|
|
151
|
+
"""True when no carrier is populated."""
|
|
152
|
+
return all(getattr(self, f) is None for f in self._FIELDS)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# Phase 4.1i — ``write_fix_storage_files_from_handoff`` was retired
|
|
156
|
+
# once all readers of ``solve_data/fix_storage_*`` migrated to the
|
|
157
|
+
# per-metric ``handoff/*`` Provider keys seeded by the
|
|
158
|
+
# iteration-start translator (Phases 4.1f–4.1h). The wide → narrow
|
|
159
|
+
# CSV fan-out has no consumers.
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
__all__ = [
|
|
163
|
+
"SolveHandoff",
|
|
164
|
+
]
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Solve-orchestration state types.
|
|
2
|
+
|
|
3
|
+
Foundation module: every downstream orchestration module (timeline,
|
|
4
|
+
recursive solve, stochastic, orchestration loop) needs the exception
|
|
5
|
+
classes, ``ActiveTimeEntry`` namedtuple, ``SolveResult`` dataclass and
|
|
6
|
+
a slim ``RunnerState`` carrier. Runs natively on HiGHS via
|
|
7
|
+
``polar_high``; per-CLI flags live on the CLI wrapper rather than in
|
|
8
|
+
shared state.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Callable, NamedTuple, TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
import polars as pl
|
|
19
|
+
|
|
20
|
+
from flextool.cli._timing import TimingRecorder
|
|
21
|
+
from flextool.engine_polars._solve_config import SolveConfig
|
|
22
|
+
from flextool.engine_polars._solve_handoff import SolveHandoff
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Exception types
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FlexToolError(Exception):
|
|
31
|
+
"""Base exception for FlexTool runner errors."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FlexToolConfigError(FlexToolError):
|
|
35
|
+
"""Raised for configuration / input data errors."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class FlexToolSolveError(FlexToolError):
|
|
39
|
+
"""Raised for solver execution errors."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# Lightweight value types
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ActiveTimeEntry(NamedTuple):
|
|
48
|
+
"""A single timestep in an active time list.
|
|
49
|
+
|
|
50
|
+
Backwards-compatible with the previous ``(timestep, index, duration)``
|
|
51
|
+
tuples — ``entry[0]`` still works, but ``entry.timestep`` is preferred.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
timestep: str
|
|
55
|
+
index: int
|
|
56
|
+
duration: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class SolveResult:
|
|
61
|
+
"""Result container for the recursive solve structure builder.
|
|
62
|
+
|
|
63
|
+
Populated by the recursive solve builder (Γ.8.C) and consumed by the
|
|
64
|
+
orchestration loop (Γ.8.D).
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
solves: list = field(default_factory=list)
|
|
68
|
+
complete_solves: dict = field(default_factory=dict)
|
|
69
|
+
active_time_lists: dict = field(default_factory=dict)
|
|
70
|
+
fix_storage_time_lists: dict = field(default_factory=dict)
|
|
71
|
+
realized_time_lists: dict = field(default_factory=dict)
|
|
72
|
+
parent_roll_lists: dict = field(default_factory=dict)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class PathConfig:
|
|
77
|
+
"""Directory layout for a FlexTool run.
|
|
78
|
+
|
|
79
|
+
Carries the work folder plus the optional ancillary directories
|
|
80
|
+
:class:`FlexToolRunner` resolves (a package data dir, a
|
|
81
|
+
CLI-overrideable ``solver_config/`` for ``highs.opt`` and a project
|
|
82
|
+
root). Native engine_polars callers only populate ``work_folder``;
|
|
83
|
+
the rest are ``None``.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
work_folder: Path
|
|
87
|
+
output_path: Path | None = None
|
|
88
|
+
flextool_dir: Path | None = None
|
|
89
|
+
solver_config_dir: Path | None = None
|
|
90
|
+
root_dir: Path | None = None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class RunnerState:
|
|
95
|
+
"""Cross-cutting state for a native polar_high solve run.
|
|
96
|
+
|
|
97
|
+
Only the fields needed by Γ.8.A are populated. Timeline + handoff
|
|
98
|
+
fields will be filled in Γ.8.B / Γ.8.D as those modules land.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
paths: PathConfig
|
|
102
|
+
solve: "SolveConfig"
|
|
103
|
+
logger: logging.Logger
|
|
104
|
+
# Filled by Γ.8.B (timeline module). Typed as ``object`` so that
|
|
105
|
+
# importing :class:`RunnerState` doesn't pull a non-existent module
|
|
106
|
+
# into the import graph.
|
|
107
|
+
timeline: object | None = None
|
|
108
|
+
# Agent 8 (LP-scaling): opt-in flag — when True the Python
|
|
109
|
+
# ScaleAnalyzer's recommendations are auto-applied. Batch C.10
|
|
110
|
+
# removed the DB-stored ``use_row_scaling`` knob; the per-solve
|
|
111
|
+
# row-scaling toggle is now driven entirely by --scaling CLI +
|
|
112
|
+
# this auto_scale flag (or FLEXTOOL_FORCE_ROW_SCALING test hook).
|
|
113
|
+
# Always-False in the default path preserves pre-Agent-8 behaviour.
|
|
114
|
+
auto_scale: bool = False
|
|
115
|
+
# Roll-loop scratch — set by the orchestration loop just before
|
|
116
|
+
# ``solver.run`` so per-roll diagnostics / handoff capture can find
|
|
117
|
+
# the correct row. ``None`` outside an active solve iteration.
|
|
118
|
+
current_roll_index: int | None = None
|
|
119
|
+
# Agent 18c (LP-scaling): the orchestration loop sets this to the
|
|
120
|
+
# ``ScaleTable`` for the currently-active solve just before calling
|
|
121
|
+
# ``solver.run``. ``_run_highs`` uses it to update bound-scaling
|
|
122
|
+
# diagnostics in the right cache entry even when the roll name
|
|
123
|
+
# differs from the parent (complete) solve name passed to the
|
|
124
|
+
# solver. ``None`` outside an active solve iteration.
|
|
125
|
+
current_scale_solve_name: str | None = None
|
|
126
|
+
# Name of the most-recent solve whose post-solve hook deposited a
|
|
127
|
+
# ``SolveHandoff`` into ``handoffs``. Set by ``orchestration.run_model``
|
|
128
|
+
# after each capture; consulted by post-solve writers (e.g. the
|
|
129
|
+
# cumulative-handoff writers in ``solver_runner._run_highs``) to
|
|
130
|
+
# source prior-roll state from the in-memory dict instead of disk.
|
|
131
|
+
# ``None`` outside an active solve loop and on the first solve of
|
|
132
|
+
# any loop.
|
|
133
|
+
last_captured_solve: str | None = None
|
|
134
|
+
# In-memory solve-to-solve handoff. ``None`` keeps file-based
|
|
135
|
+
# behaviour; opt-in by setting ``state.handoffs = {}``. See
|
|
136
|
+
# ``audit/handoff_csv_retirement.md`` for the migration plan.
|
|
137
|
+
handoffs: "dict[str, SolveHandoff] | None" = None
|
|
138
|
+
# Phase 5b — external override provider. When set, the runner
|
|
139
|
+
# invokes this callable at iteration start (after the sequential
|
|
140
|
+
# + parent handoff translators) and fans the returned dict into
|
|
141
|
+
# the ``override/*`` Provider namespace via
|
|
142
|
+
# :func:`flextool.engine_polars._provider_translators.translate_overrides_to_provider`.
|
|
143
|
+
# The callable is owned by external code wrapping the runner
|
|
144
|
+
# (e.g. file-watch, ZeroMQ bridge); ``None`` means no overrides.
|
|
145
|
+
override_provider: Callable[[], "dict[str, pl.DataFrame]"] | None = None
|
|
146
|
+
# Per-CLI-invocation phase timing recorder. The CLI constructs one
|
|
147
|
+
# very early in ``cmd_run_flextool.main`` and assigns it onto
|
|
148
|
+
# ``state.timing_recorder``; callers using :class:`FlexToolRunner`
|
|
149
|
+
# directly (without going through the CLI) bootstrap their own in
|
|
150
|
+
# ``FlexToolRunner.__init__``. Always non-None inside an active run.
|
|
151
|
+
timing_recorder: "TimingRecorder | None" = None
|
|
152
|
+
# HiGHS thread count (CLI override; solver_runner defaults to 4 when None).
|
|
153
|
+
highs_threads: int | None = None
|
|
154
|
+
# Gates ``data.dump_csvs`` from inside the cascade — set by
|
|
155
|
+
# ``run_orchestration`` from its ``csv_dump`` argument.
|
|
156
|
+
csv_dump: bool = False
|
|
157
|
+
# Seeded cascade-input Provider — set by ``write_input`` / by the
|
|
158
|
+
# orchestration entry point so per-sub-solve Providers can clone
|
|
159
|
+
# the ``input/<class>`` frames. Typed as ``object`` to avoid
|
|
160
|
+
# pulling :class:`FlexDataProvider` into the import graph.
|
|
161
|
+
cascade_input_provider: object | None = None
|
|
162
|
+
# Per-sub-solve Provider currently driving the cascade. Set by
|
|
163
|
+
# ``_native_run_model`` immediately before each ``solver.run``
|
|
164
|
+
# invocation; consumed by post-solve writers that need a Provider
|
|
165
|
+
# handle but were called without one. ``None`` outside an active
|
|
166
|
+
# solve iteration.
|
|
167
|
+
current_provider: object | None = None
|
|
168
|
+
# Per-level Provider cache — keyed by :func:`compute_level_key`.
|
|
169
|
+
# The orchestration loop populates this so sub-solves at the same
|
|
170
|
+
# "level" (matching LP matrix shape) share a :class:`FlexDataProvider`.
|
|
171
|
+
# Typed as ``dict | None``; ``None`` means "not yet initialised".
|
|
172
|
+
_level_providers: dict | None = None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
# Per-level Provider — level-key helper (Design A, step A1).
|
|
177
|
+
# ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def compute_level_key(
|
|
181
|
+
*,
|
|
182
|
+
solve_name: str,
|
|
183
|
+
complete_solve_name: str,
|
|
184
|
+
solve_config,
|
|
185
|
+
timeline_config,
|
|
186
|
+
) -> tuple:
|
|
187
|
+
"""Compute a cheap level identifier for a sub-solve.
|
|
188
|
+
|
|
189
|
+
Two sub-solves with the same level_key share LP matrix shape and
|
|
190
|
+
can share a :class:`FlexDataProvider` (per the user's per-level
|
|
191
|
+
Provider intent). Two sub-solves with different keys must not
|
|
192
|
+
share.
|
|
193
|
+
|
|
194
|
+
Composition (in order):
|
|
195
|
+
|
|
196
|
+
1. Tuple of timesets used by ``complete_solve_name``
|
|
197
|
+
(``solve_config.timesets_used_by_solves[complete_solve_name]``,
|
|
198
|
+
sorted for determinism).
|
|
199
|
+
2. ``timeline_config.new_step_durations.get(complete_solve_name)``
|
|
200
|
+
— the explicit step-size override (1h, 3h, …). ``None`` when
|
|
201
|
+
absent.
|
|
202
|
+
3. ``solve_config.rolling_times.get(complete_solve_name)`` — the
|
|
203
|
+
rolling-window triple ``[jump, horizon, duration]``, coerced to
|
|
204
|
+
a tuple. ``None`` when not rolling.
|
|
205
|
+
4. ``solve_config.solve_modes.get(complete_solve_name)``.
|
|
206
|
+
|
|
207
|
+
Returns a hashable tuple. Same key on consecutive iterations
|
|
208
|
+
means "same level — reuse Provider"; different key means "level
|
|
209
|
+
transition — fresh Provider".
|
|
210
|
+
"""
|
|
211
|
+
timesets = solve_config.timesets_used_by_solves.get(
|
|
212
|
+
complete_solve_name, ()
|
|
213
|
+
)
|
|
214
|
+
timesets = tuple(sorted(timesets)) if timesets else ()
|
|
215
|
+
step_dur = timeline_config.new_step_durations.get(complete_solve_name)
|
|
216
|
+
rolling = solve_config.rolling_times.get(complete_solve_name)
|
|
217
|
+
if rolling is not None:
|
|
218
|
+
rolling = tuple(rolling)
|
|
219
|
+
mode = solve_config.solve_modes.get(complete_solve_name)
|
|
220
|
+
return (timesets, step_dur, rolling, mode)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
__all__ = [
|
|
224
|
+
"FlexToolError",
|
|
225
|
+
"FlexToolConfigError",
|
|
226
|
+
"FlexToolSolveError",
|
|
227
|
+
"ActiveTimeEntry",
|
|
228
|
+
"SolveResult",
|
|
229
|
+
"PathConfig",
|
|
230
|
+
"RunnerState",
|
|
231
|
+
"compute_level_key",
|
|
232
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""SolverRunner shell — base class for the native cascade's solver subclasses.
|
|
2
|
+
|
|
3
|
+
The cascade's ``_PolarHighCascadeSolver`` / ``_NoOpSolver`` in
|
|
4
|
+
:mod:`flextool.engine_polars._orchestration` extend :class:`SolverRunner`
|
|
5
|
+
and override ``run`` to drive the polar-high LP build/solve in-process.
|
|
6
|
+
Direct instantiation + ``run()`` is not supported.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SolverRunner:
|
|
12
|
+
"""Minimal shell that the native cascade's solver subclasses extend.
|
|
13
|
+
|
|
14
|
+
``__init__`` stores ``state`` and ``logger`` for the subclasses,
|
|
15
|
+
matching the contract those subclasses already rely on via
|
|
16
|
+
``super().__init__(runner_state)``. ``run`` is unimplemented by
|
|
17
|
+
design — the cascade subclasses override it.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, state) -> None:
|
|
21
|
+
self.state = state
|
|
22
|
+
self.logger = state.logger
|
|
23
|
+
|
|
24
|
+
def run(self, current_solve: str) -> int: # noqa: ARG002
|
|
25
|
+
"""Unimplemented — subclasses override this method.
|
|
26
|
+
|
|
27
|
+
Direct invocation on the base class is not supported.
|
|
28
|
+
"""
|
|
29
|
+
raise NotImplementedError(
|
|
30
|
+
"SolverRunner.run is unimplemented on the base class. "
|
|
31
|
+
"The cascade subclasses this class and overrides run(); "
|
|
32
|
+
"direct invocation is not supported."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = ["SolverRunner"]
|