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,601 @@
|
|
|
1
|
+
"""Per-roll realized-slice persistence (multi-solve output union, stage 2).
|
|
2
|
+
|
|
3
|
+
Multi-sub-solve runs (rolling / nested / multi-period) used to write
|
|
4
|
+
outputs covering only the LAST sub-solve's realized window: the
|
|
5
|
+
per-roll ``flex_data`` / ``solution`` are nulled by memory slimming on
|
|
6
|
+
prior steps, so by output time only the last roll survives.
|
|
7
|
+
|
|
8
|
+
Variables already avoid this: each roll writes a realized-filtered
|
|
9
|
+
per-solve parquet to ``output_raw/`` (``write_variable_parquet``,
|
|
10
|
+
filename ``{name}__{solve}.parquet``) and the reader concatenates every
|
|
11
|
+
roll. This module extends that exact pattern to **parameters** (stage
|
|
12
|
+
2a) — and, in stage 2b, to **sets** — so the per-roll realized slice is
|
|
13
|
+
persisted off-heap while the data is still live.
|
|
14
|
+
|
|
15
|
+
Design (orchestrator decisions D1/D2/D3):
|
|
16
|
+
|
|
17
|
+
* **D1** — each per-roll attribute slice is written as parquet at
|
|
18
|
+
``output_raw/<attr>__<solve>.parquet``, byte-for-byte mirroring the
|
|
19
|
+
variable convention (``read_highs_solution.write_variable_parquet``).
|
|
20
|
+
No size-based CSV fallback.
|
|
21
|
+
* **D2** — :func:`read_parameters` is re-run once per roll at the
|
|
22
|
+
per-roll write hook (it already calls every slice builder, including
|
|
23
|
+
the live-solution ``entity_all_capacity``); the resulting solve-keyed
|
|
24
|
+
attributes are realized-filtered and persisted. We do NOT factor the
|
|
25
|
+
individual builders out of the 1786-line monolith — re-running it per
|
|
26
|
+
roll is runtime-equivalent to the proven ``keep_solutions`` reference
|
|
27
|
+
(which also calls ``read_parameters`` per step).
|
|
28
|
+
* **D3** — ``entity_all_capacity`` rides the same parquet route via the
|
|
29
|
+
live-solution builder, filtered to the solve's realized periods.
|
|
30
|
+
|
|
31
|
+
Realized filter (per :mod:`...read_parameters` ``read_parameters_multi``
|
|
32
|
+
pre-concat logic, replicated here at persist time so the stage-3 union
|
|
33
|
+
is a clean disjoint concat):
|
|
34
|
+
|
|
35
|
+
* ``(period, time)``-keyed attrs → keep rows whose ``(period, time)`` is
|
|
36
|
+
in the roll's realized dispatch set (``flex_data.realized_dispatch``).
|
|
37
|
+
* ``(period)``-keyed attrs → keep rows whose ``period`` is in the roll's
|
|
38
|
+
realized-invest set.
|
|
39
|
+
* Three attr-specific hacks are preserved verbatim
|
|
40
|
+
(``entity_lifetime_fixed_cost[_divest]``, ``entity_all_existing``,
|
|
41
|
+
and the ``entity_annual_discounted`` emptiness gate) — see
|
|
42
|
+
:func:`_apply_realized_filter`.
|
|
43
|
+
|
|
44
|
+
The 6 output-dead solve-keyed params (``flow_min``, ``flow_max``,
|
|
45
|
+
``years_from_start_d``, ``entity_max_units``, ``node_annual_flow``,
|
|
46
|
+
``group_capacity_margin``) are NOT persisted (spec §2a / Resolved C).
|
|
47
|
+
"""
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
import logging
|
|
51
|
+
from pathlib import Path
|
|
52
|
+
from typing import TYPE_CHECKING
|
|
53
|
+
|
|
54
|
+
import numpy as np
|
|
55
|
+
import pandas as pd
|
|
56
|
+
import polars as pl
|
|
57
|
+
|
|
58
|
+
from flextool.lean_parquet import write_lean_parquet
|
|
59
|
+
|
|
60
|
+
if TYPE_CHECKING:
|
|
61
|
+
from polar_high import Solution
|
|
62
|
+
|
|
63
|
+
from flextool.engine_polars.input import FlexData
|
|
64
|
+
|
|
65
|
+
_logger = logging.getLogger(__name__)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Solve-keyed params that are built but read by no ``out_*`` / ``calc_*``
|
|
69
|
+
# consumer — not persisted (spec §2a / Resolved C).
|
|
70
|
+
_DEAD_PARAMS: frozenset[str] = frozenset(
|
|
71
|
+
{
|
|
72
|
+
"flow_min",
|
|
73
|
+
"flow_max",
|
|
74
|
+
"years_from_start_d",
|
|
75
|
+
"entity_max_units",
|
|
76
|
+
"node_annual_flow",
|
|
77
|
+
"group_capacity_margin",
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Attr-specific realized-filter hack families (preserved from
|
|
82
|
+
# ``read_parameters_multi``).
|
|
83
|
+
_LIFETIME_ATTRS: frozenset[str] = frozenset(
|
|
84
|
+
{"entity_lifetime_fixed_cost", "entity_lifetime_fixed_cost_divest"}
|
|
85
|
+
)
|
|
86
|
+
_EXISTING_ATTRS: frozenset[str] = frozenset({"entity_all_existing"})
|
|
87
|
+
# Emptiness-gate attrs and the FlexData carrier that decides each.
|
|
88
|
+
_ANNUAL_SRC_FIELD: dict[str, str] = {
|
|
89
|
+
"entity_annual_discounted": "ed_entity_annual_discounted",
|
|
90
|
+
"entity_annuity": "ed_entity_annual_discounted",
|
|
91
|
+
"entity_annual_divest_discounted": "ed_entity_annual_divest_discounted",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# Shared parquet writer helper (reused by the stage-2b set writer)
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
_SET_MARKER_COL = "__set_marker__"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def write_realized_slice_parquet(
|
|
104
|
+
frame: "pd.DataFrame | pd.Series | pd.Index | pl.DataFrame",
|
|
105
|
+
*,
|
|
106
|
+
attr: str,
|
|
107
|
+
solve_name: str,
|
|
108
|
+
output_dir: Path | str,
|
|
109
|
+
) -> Path:
|
|
110
|
+
"""Persist one per-roll realized slice to ``output_dir`` as parquet.
|
|
111
|
+
|
|
112
|
+
Filename is ``{attr}__{solve_name}.parquet`` — the same convention
|
|
113
|
+
:func:`...read_highs_solution.write_variable_parquet` uses for
|
|
114
|
+
variables, so the stage-3 reader can union params, sets and
|
|
115
|
+
variables uniformly. The ``solve`` identity lives only in the
|
|
116
|
+
filename (the on-disk frame keeps whatever ``solve`` index level it
|
|
117
|
+
already carries — stage 3 drops it at union time).
|
|
118
|
+
|
|
119
|
+
Accepted inputs:
|
|
120
|
+
|
|
121
|
+
* ``pd.DataFrame`` — written as-is (params, stage 2a).
|
|
122
|
+
* ``pd.Series`` — converted to a one-column DataFrame (its ``name``
|
|
123
|
+
becomes the column label) so the lean-parquet round-trip is well
|
|
124
|
+
defined; the stage-3 reader restores the Series shape.
|
|
125
|
+
* ``pd.Index`` / ``pd.MultiIndex`` — set membership (stage 2b). The
|
|
126
|
+
index carries all the information (no value columns), so we
|
|
127
|
+
materialise it as the row index of a single-column marker frame;
|
|
128
|
+
:func:`write_lean_parquet` records the level names and the stage-3
|
|
129
|
+
reader rebuilds the (Multi)Index by dropping the marker column.
|
|
130
|
+
* ``pl.DataFrame`` — converted to pandas first (defensive: no
|
|
131
|
+
varying set is a polars frame today, but the structural ``solve``
|
|
132
|
+
tests admit DataFrame/polars sets and schema drift could add one).
|
|
133
|
+
|
|
134
|
+
Pandas ``DataFrame`` / ``Series`` behaviour is byte-identical to
|
|
135
|
+
stage 2a — only the new ``Index`` / polars branches are additive.
|
|
136
|
+
"""
|
|
137
|
+
output_dir = Path(output_dir)
|
|
138
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
139
|
+
if isinstance(frame, pl.DataFrame):
|
|
140
|
+
frame = frame.to_pandas()
|
|
141
|
+
if isinstance(frame, pd.Index):
|
|
142
|
+
# A set's identity lives entirely in its (Multi)Index. Make it
|
|
143
|
+
# the row index of a one-column marker frame; the stage-3 reader
|
|
144
|
+
# drops ``_SET_MARKER_COL`` and keeps the reconstructed index.
|
|
145
|
+
df = pd.DataFrame({_SET_MARKER_COL: range(len(frame))}, index=frame)
|
|
146
|
+
elif isinstance(frame, pd.Series):
|
|
147
|
+
df = frame.to_frame()
|
|
148
|
+
else:
|
|
149
|
+
df = frame
|
|
150
|
+
path = output_dir / f"{attr}__{solve_name}.parquet"
|
|
151
|
+
write_lean_parquet(df, path)
|
|
152
|
+
_logger.debug(
|
|
153
|
+
"Wrote realized slice %s for solve '%s' -> %s (shape %s)",
|
|
154
|
+
attr, solve_name, path, df.shape,
|
|
155
|
+
)
|
|
156
|
+
return path
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
# Realized-set extraction from the live FlexData
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _realized_dt_set(flex_data: "FlexData") -> set[tuple[str, str]]:
|
|
165
|
+
"""Return ``{(period, time), …}`` realized by this roll.
|
|
166
|
+
|
|
167
|
+
Source is the in-memory ``flex_data.realized_dispatch`` ``(period,
|
|
168
|
+
step)`` frame — the same carrier the variable writer prefers
|
|
169
|
+
(``read_highs_solution._load_realized_set``). Empty / absent →
|
|
170
|
+
empty set (this roll realized nothing on the dispatch axis).
|
|
171
|
+
"""
|
|
172
|
+
rd = getattr(flex_data, "realized_dispatch", None)
|
|
173
|
+
if rd is None or getattr(rd, "height", 0) == 0:
|
|
174
|
+
return set()
|
|
175
|
+
pdf = rd.select("period", "step").to_pandas()
|
|
176
|
+
return set(
|
|
177
|
+
zip(
|
|
178
|
+
pdf["period"].astype(str).tolist(),
|
|
179
|
+
pdf["step"].astype(str).tolist(),
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _realized_dispatch_periods(flex_data: "FlexData") -> set[str]:
|
|
185
|
+
"""Return ``{period, …}`` appearing in the roll's realized dispatch."""
|
|
186
|
+
rd = getattr(flex_data, "realized_dispatch", None)
|
|
187
|
+
if rd is None or getattr(rd, "height", 0) == 0:
|
|
188
|
+
return set()
|
|
189
|
+
return set(
|
|
190
|
+
rd.select("period").unique().to_pandas()["period"].astype(str).tolist()
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _existing_filter_periods(flex_data: "FlexData") -> set[str]:
|
|
195
|
+
"""``entity_all_existing`` hack periods: realized-dispatch periods ∪
|
|
196
|
+
the ``"d"`` values of ``ed_invest_set`` / ``ed_divest_set``.
|
|
197
|
+
|
|
198
|
+
Replicates ``read_parameters_multi`` (read_parameters.py:1681-1705).
|
|
199
|
+
"""
|
|
200
|
+
periods = _realized_dispatch_periods(flex_data)
|
|
201
|
+
for src_attr in ("ed_invest_set", "ed_divest_set"):
|
|
202
|
+
src = getattr(flex_data, src_attr, None)
|
|
203
|
+
if src is not None and getattr(src, "height", 0) > 0 and "d" in src.columns:
|
|
204
|
+
periods.update(
|
|
205
|
+
src.select("d").unique().to_pandas()["d"].astype(str).tolist()
|
|
206
|
+
)
|
|
207
|
+
return periods
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _annual_carrier_empty(flex_data: "FlexData", src_attr: str) -> bool:
|
|
211
|
+
"""True iff the annual-NPV carrier ``src_attr`` is empty/None — i.e.
|
|
212
|
+
this is a dispatch-only step (read_parameters.py:1707-1752)."""
|
|
213
|
+
src_param = getattr(flex_data, src_attr, None)
|
|
214
|
+
return (
|
|
215
|
+
src_param is None
|
|
216
|
+
or getattr(src_param, "frame", None) is None
|
|
217
|
+
or getattr(src_param.frame, "height", 0) == 0
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
# Per-attribute realized filter
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _index_has_time(obj: "pd.DataFrame | pd.Series") -> bool:
|
|
227
|
+
idx = obj.index
|
|
228
|
+
return isinstance(idx, pd.MultiIndex) and "time" in (idx.names or ())
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _filter_by_dt(
|
|
232
|
+
obj: "pd.DataFrame | pd.Series",
|
|
233
|
+
realized_dt: set[tuple[str, str]],
|
|
234
|
+
) -> "pd.DataFrame | pd.Series":
|
|
235
|
+
"""Keep rows whose ``(period, time)`` is realized."""
|
|
236
|
+
periods = obj.index.get_level_values("period").astype(str)
|
|
237
|
+
times = obj.index.get_level_values("time").astype(str)
|
|
238
|
+
mask = pd.Series(
|
|
239
|
+
[pt in realized_dt for pt in zip(periods, times)],
|
|
240
|
+
index=obj.index,
|
|
241
|
+
)
|
|
242
|
+
return obj[mask.to_numpy()]
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _filter_by_period(
|
|
246
|
+
obj: "pd.DataFrame | pd.Series",
|
|
247
|
+
realized_periods: set[str],
|
|
248
|
+
) -> "pd.DataFrame | pd.Series":
|
|
249
|
+
"""Keep rows whose ``period`` is realized."""
|
|
250
|
+
periods = obj.index.get_level_values("period").astype(str)
|
|
251
|
+
mask = periods.isin(realized_periods)
|
|
252
|
+
return obj[mask]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _apply_realized_filter(
|
|
256
|
+
attr: str,
|
|
257
|
+
obj: "pd.DataFrame | pd.Series",
|
|
258
|
+
flex_data: "FlexData",
|
|
259
|
+
*,
|
|
260
|
+
realized_dt: set[tuple[str, str]],
|
|
261
|
+
realized_invest_periods: set[str],
|
|
262
|
+
realized_dispatch_periods: "set[str] | None" = None,
|
|
263
|
+
) -> "pd.DataFrame | pd.Series":
|
|
264
|
+
"""Return the roll's realized slice of ``obj`` for attribute ``attr``.
|
|
265
|
+
|
|
266
|
+
Dispatches the three preserved attr-specific hacks first, then the
|
|
267
|
+
general realized intersection for every other solve-keyed attr.
|
|
268
|
+
The frame's ``solve`` index level is left intact (stage 3 drops it).
|
|
269
|
+
"""
|
|
270
|
+
# Hack 3 — emptiness gate (NOT a period filter): a dispatch-only
|
|
271
|
+
# step's densified-zero annual frame is cleared so it can't compete
|
|
272
|
+
# with the parent invest step's real values at union time.
|
|
273
|
+
if attr in _ANNUAL_SRC_FIELD:
|
|
274
|
+
if _annual_carrier_empty(flex_data, _ANNUAL_SRC_FIELD[attr]):
|
|
275
|
+
return obj.iloc[0:0]
|
|
276
|
+
return obj
|
|
277
|
+
|
|
278
|
+
# Hack 1 — entity_lifetime_fixed_cost[_divest]: filter by the roll's
|
|
279
|
+
# realized-DISPATCH periods (not realized-invest), so the committing
|
|
280
|
+
# step's active per-period value is the sole contributor.
|
|
281
|
+
if attr in _LIFETIME_ATTRS:
|
|
282
|
+
return _filter_by_period(obj, _realized_dispatch_periods(flex_data))
|
|
283
|
+
|
|
284
|
+
# Hack 2 — entity_all_existing: realized-dispatch periods ∪ the "d"s
|
|
285
|
+
# of ed_invest_set / ed_divest_set.
|
|
286
|
+
if attr in _EXISTING_ATTRS:
|
|
287
|
+
step_periods = _existing_filter_periods(flex_data)
|
|
288
|
+
if not step_periods:
|
|
289
|
+
# Step realizes nothing — legacy behaviour: leave as-is.
|
|
290
|
+
return obj
|
|
291
|
+
return _filter_by_period(obj, step_periods)
|
|
292
|
+
|
|
293
|
+
# General realized intersection for every other solve-keyed attr.
|
|
294
|
+
if _index_has_time(obj):
|
|
295
|
+
return _filter_by_dt(obj, realized_dt)
|
|
296
|
+
# (period)-keyed general params (node/group_capacity_for_scaling,
|
|
297
|
+
# entity_fixed_cost, entity_pre_existing, process_startup_cost, …):
|
|
298
|
+
# keep rows whose ``period`` is in the roll's realized DISPATCH ∪
|
|
299
|
+
# INVEST set. A dispatch-only roll has NO realized-invest period, so
|
|
300
|
+
# filtering on invest-only would drop its single realized-dispatch
|
|
301
|
+
# row (e.g. node_capacity_for_scaling@(roll, p2020)) and the stage-3
|
|
302
|
+
# union would then have no row for that (solve, period) — zeroing
|
|
303
|
+
# ``Loss of load`` in node__dt (``out_node`` multiplies
|
|
304
|
+
# ``v.q_state_up`` by ``node_capacity_for_scaling`` at the realized
|
|
305
|
+
# timestep). ``read_parameters_multi`` concats these raw and lets
|
|
306
|
+
# ``drop_levels`` dedup by period (no realized intersect), so keeping
|
|
307
|
+
# every realized-dispatch period here reproduces that union exactly.
|
|
308
|
+
if realized_dispatch_periods is None:
|
|
309
|
+
realized_dispatch_periods = _realized_dispatch_periods(flex_data)
|
|
310
|
+
return _filter_by_period(
|
|
311
|
+
obj, realized_dispatch_periods | realized_invest_periods
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# ---------------------------------------------------------------------------
|
|
316
|
+
# Top-level per-roll param persistence
|
|
317
|
+
# ---------------------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def write_all_params_realized_slice(
|
|
321
|
+
flex_data: "FlexData",
|
|
322
|
+
solution: "Solution",
|
|
323
|
+
*,
|
|
324
|
+
solve_name: str,
|
|
325
|
+
output_dir: Path | str,
|
|
326
|
+
realized_invest_periods: "set[str] | None" = None,
|
|
327
|
+
) -> list[Path]:
|
|
328
|
+
"""Persist this roll's realized slice of every per-roll-VARYING param.
|
|
329
|
+
|
|
330
|
+
Calls :func:`...read_parameters.read_parameters` once (D2), selects
|
|
331
|
+
the solve-keyed (varying) attributes via the structural
|
|
332
|
+
``_has_solve_level`` test — minus the 6 output-dead params — applies
|
|
333
|
+
the realized filter + the three preserved hacks (incl. the
|
|
334
|
+
live-solution ``entity_all_capacity``, already built inside
|
|
335
|
+
``read_parameters``), and writes each realized slice to parquet.
|
|
336
|
+
|
|
337
|
+
Static (solve-invariant) attrs carry no ``solve`` index level and
|
|
338
|
+
are skipped here — stage 3 takes them once.
|
|
339
|
+
|
|
340
|
+
Parameters
|
|
341
|
+
----------
|
|
342
|
+
realized_invest_periods : set[str] | None
|
|
343
|
+
The roll's realized-invest periods (``period``-keyed attrs).
|
|
344
|
+
When ``None``, falls back to the realized-dispatch periods —
|
|
345
|
+
the same fallback the per-solve realized-invest writer uses
|
|
346
|
+
when no explicit invest set exists.
|
|
347
|
+
|
|
348
|
+
Returns the list of parquet paths written.
|
|
349
|
+
"""
|
|
350
|
+
from flextool.process_outputs.read_parameters import (
|
|
351
|
+
_has_solve_level,
|
|
352
|
+
read_parameters,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
output_dir = Path(output_dir)
|
|
356
|
+
|
|
357
|
+
par = read_parameters(flex_data, solution, solve_name=solve_name)
|
|
358
|
+
|
|
359
|
+
realized_dt = _realized_dt_set(flex_data)
|
|
360
|
+
realized_dispatch_periods = _realized_dispatch_periods(flex_data)
|
|
361
|
+
if realized_invest_periods is None:
|
|
362
|
+
realized_invest_periods = set(realized_dispatch_periods)
|
|
363
|
+
|
|
364
|
+
written: list[Path] = []
|
|
365
|
+
for attr, obj in vars(par).items():
|
|
366
|
+
if attr in _DEAD_PARAMS:
|
|
367
|
+
continue
|
|
368
|
+
if not _has_solve_level(obj):
|
|
369
|
+
# Static / invariant — taken once at union time (stage 3).
|
|
370
|
+
continue
|
|
371
|
+
sliced = _apply_realized_filter(
|
|
372
|
+
attr, obj, flex_data,
|
|
373
|
+
realized_dt=realized_dt,
|
|
374
|
+
realized_invest_periods=set(realized_invest_periods),
|
|
375
|
+
realized_dispatch_periods=realized_dispatch_periods,
|
|
376
|
+
)
|
|
377
|
+
path = write_realized_slice_parquet(
|
|
378
|
+
sliced, attr=attr, solve_name=solve_name, output_dir=output_dir,
|
|
379
|
+
)
|
|
380
|
+
written.append(path)
|
|
381
|
+
|
|
382
|
+
_logger.debug(
|
|
383
|
+
"Persisted %d realized param slices for solve '%s' to %s",
|
|
384
|
+
len(written), solve_name, output_dir,
|
|
385
|
+
)
|
|
386
|
+
return written
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
# ---------------------------------------------------------------------------
|
|
390
|
+
# Per-roll SET realized-slice persistence (stage 2b)
|
|
391
|
+
# ---------------------------------------------------------------------------
|
|
392
|
+
#
|
|
393
|
+
# Sets are persisted with the SAME per-roll realized-slice → parquet →
|
|
394
|
+
# union pattern as variables/params, so the stage-3 reader can union
|
|
395
|
+
# ``v`` / ``par`` / ``s`` uniformly. Only the per-roll-VARYING sets
|
|
396
|
+
# (those carrying a ``solve`` index level) are persisted here; the
|
|
397
|
+
# solve-invariant topology sets (``node``, ``process``, ``upDown``, …)
|
|
398
|
+
# carry no ``solve`` level and are taken once at stage 3.
|
|
399
|
+
#
|
|
400
|
+
# The realized filter REPLICATES ``drop_levels.drop_levels`` (the locus
|
|
401
|
+
# where sets are realized-intersected + deduped + solve-dropped today),
|
|
402
|
+
# applied per-roll so the disjoint union of slices equals what
|
|
403
|
+
# ``drop_levels`` produces over all rolls:
|
|
404
|
+
#
|
|
405
|
+
# * ``(period, time)``-keyed sets (index carries a ``time`` level) →
|
|
406
|
+
# keep rows whose CURRENT-step ``(period, time)`` is in this roll's
|
|
407
|
+
# realized dispatch (``flex_data.realized_dispatch``). For ``dtt`` /
|
|
408
|
+
# ``dtttdt`` the adjacency columns (``t_previous``,
|
|
409
|
+
# ``t_previous_within_timeset``, ``d_previous``,
|
|
410
|
+
# ``t_previous_within_solve``) are kept VERBATIM — the ``t_previous``
|
|
411
|
+
# chain is intentionally broken at each roll's realized start; cross-
|
|
412
|
+
# roll storage continuity is carried by the separate
|
|
413
|
+
# ``p_roll_continue_state`` handoff, NOT by the adjacency set
|
|
414
|
+
# (``calc_storage_vre`` special-cases period-first rows). This is the
|
|
415
|
+
# resolved current-step rule (matches ``drop_levels`` :156-161, which
|
|
416
|
+
# intersects the ``(period, time)`` projection against
|
|
417
|
+
# ``dt_realize_dispatch`` and never touches the ``t_previous*`` /
|
|
418
|
+
# ``d_previous`` levels).
|
|
419
|
+
# * ``ed_invest`` / ``ed_divest`` / ``edd_invest`` ((entity, period[,
|
|
420
|
+
# period_invest])) → keep rows whose ``period`` is in this roll's
|
|
421
|
+
# realized-INVEST periods (mirrors ``drop_levels`` :162-166, the inner
|
|
422
|
+
# join against ``d_realize_invest``).
|
|
423
|
+
# * Every other ``(period)``-keyed set → keep rows whose ``period`` is
|
|
424
|
+
# in this roll's realized DISPATCH ∪ INVEST periods. Sets that
|
|
425
|
+
# ``read_sets`` already builds realized (``d_realized_period``,
|
|
426
|
+
# ``d_realize_invest``, ``d_realize_dispatch_or_invest``,
|
|
427
|
+
# ``dt_realize_dispatch``) are unchanged by this intersection (it is
|
|
428
|
+
# identity for them); ``period`` / ``period_in_use`` — which
|
|
429
|
+
# ``read_sets`` builds from the FULL per-roll ``dt`` incl. foresight —
|
|
430
|
+
# are trimmed to the realized window so the union is disjoint.
|
|
431
|
+
|
|
432
|
+
# Invest-keyed sets — realized filter is on realized-INVEST periods
|
|
433
|
+
# (mirrors the ``d_realize_invest`` inner join in ``drop_levels``).
|
|
434
|
+
_INVEST_SETS: frozenset[str] = frozenset(
|
|
435
|
+
{"ed_invest", "ed_divest", "edd_invest"}
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# dtt / dtttdt — the storage-adjacency sets whose current ``(period,
|
|
439
|
+
# time)`` is the only thing filtered; adjacency columns stay verbatim.
|
|
440
|
+
_ADJACENCY_SETS: frozenset[str] = frozenset({"dtt", "dtttdt"})
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _multiindex_has_level(idx: pd.MultiIndex, name: str) -> bool:
|
|
444
|
+
return isinstance(idx, pd.MultiIndex) and name in (idx.names or ())
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _filter_index_by_dt(
|
|
448
|
+
idx: pd.MultiIndex,
|
|
449
|
+
realized_dt: set[tuple[str, str]],
|
|
450
|
+
) -> pd.MultiIndex:
|
|
451
|
+
"""Keep entries whose current ``(period, time)`` is realized.
|
|
452
|
+
|
|
453
|
+
Filters ONLY on the ``period`` / ``time`` levels — any further
|
|
454
|
+
adjacency levels (``t_previous`` …) are kept verbatim.
|
|
455
|
+
"""
|
|
456
|
+
periods = idx.get_level_values("period").astype(str)
|
|
457
|
+
times = idx.get_level_values("time").astype(str)
|
|
458
|
+
mask = np.fromiter(
|
|
459
|
+
(pt in realized_dt for pt in zip(periods, times)),
|
|
460
|
+
dtype=bool,
|
|
461
|
+
count=len(idx),
|
|
462
|
+
)
|
|
463
|
+
return idx[mask]
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _filter_index_by_period(
|
|
467
|
+
idx: pd.MultiIndex,
|
|
468
|
+
realized_periods: set[str],
|
|
469
|
+
) -> pd.MultiIndex:
|
|
470
|
+
"""Keep entries whose ``period`` level is realized."""
|
|
471
|
+
periods = idx.get_level_values("period").astype(str)
|
|
472
|
+
mask = periods.isin(realized_periods)
|
|
473
|
+
return idx[mask]
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _apply_set_realized_filter(
|
|
477
|
+
attr: str,
|
|
478
|
+
idx: pd.MultiIndex,
|
|
479
|
+
*,
|
|
480
|
+
realized_dt: set[tuple[str, str]],
|
|
481
|
+
realized_invest_periods: set[str],
|
|
482
|
+
realized_dispatch_periods: set[str],
|
|
483
|
+
) -> pd.MultiIndex:
|
|
484
|
+
"""Return the roll's realized slice of varying set ``idx``.
|
|
485
|
+
|
|
486
|
+
Replicates ``drop_levels``' per-set realized intersection (see the
|
|
487
|
+
module-level note above). The ``solve`` level is left intact —
|
|
488
|
+
stage 3 drops it at union time, exactly as it does for params.
|
|
489
|
+
"""
|
|
490
|
+
# (period, time)-keyed sets (incl. dtt / dtttdt) — filter on the
|
|
491
|
+
# current (period, time); adjacency columns ride along verbatim.
|
|
492
|
+
if _multiindex_has_level(idx, "time"):
|
|
493
|
+
return _filter_index_by_dt(idx, realized_dt)
|
|
494
|
+
|
|
495
|
+
# Invest-keyed sets — filter on realized-invest periods.
|
|
496
|
+
if attr in _INVEST_SETS:
|
|
497
|
+
return _filter_index_by_period(idx, realized_invest_periods)
|
|
498
|
+
|
|
499
|
+
# Every other (period)-keyed set — filter on realized dispatch ∪
|
|
500
|
+
# invest periods.
|
|
501
|
+
if _multiindex_has_level(idx, "period"):
|
|
502
|
+
return _filter_index_by_period(
|
|
503
|
+
idx, realized_dispatch_periods | realized_invest_periods,
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# No period/time level to filter on — leave verbatim (shouldn't
|
|
507
|
+
# happen for a solve-keyed set, but be conservative).
|
|
508
|
+
return idx
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def write_all_sets_realized_slice(
|
|
512
|
+
flex_data: "FlexData",
|
|
513
|
+
solution: "Solution",
|
|
514
|
+
*,
|
|
515
|
+
solve_name: str,
|
|
516
|
+
output_dir: Path | str,
|
|
517
|
+
realized_invest_periods: "set[str] | None" = None,
|
|
518
|
+
) -> list[Path]:
|
|
519
|
+
"""Persist this roll's realized slice of every per-roll-VARYING set.
|
|
520
|
+
|
|
521
|
+
Calls :func:`...read_sets.read_sets` once per roll, selects the
|
|
522
|
+
varying sets via the structural ``solve``-level tests
|
|
523
|
+
(``_multi_index_has_solve`` for pandas ``MultiIndex`` sets,
|
|
524
|
+
``_series_or_df_has_solve`` for DataFrame / polars sets with a
|
|
525
|
+
``solve`` column), applies the per-set realized filter (replicating
|
|
526
|
+
``drop_levels``; ``dtt`` / ``dtttdt`` by the resolved current-step
|
|
527
|
+
rule), and writes each realized slice to
|
|
528
|
+
``output_raw/<setname>__<solve>.parquet`` via the shared helper.
|
|
529
|
+
|
|
530
|
+
Solve-invariant topology sets carry no ``solve`` level and are
|
|
531
|
+
skipped here — stage 3 takes them once.
|
|
532
|
+
|
|
533
|
+
Parameters
|
|
534
|
+
----------
|
|
535
|
+
realized_invest_periods : set[str] | None
|
|
536
|
+
The roll's realized-invest periods (``ed_*`` invest sets). When
|
|
537
|
+
``None``, falls back to the realized-dispatch periods — the same
|
|
538
|
+
fallback the per-roll param writer uses.
|
|
539
|
+
|
|
540
|
+
Returns the list of parquet paths written.
|
|
541
|
+
"""
|
|
542
|
+
from flextool.process_outputs.read_sets import (
|
|
543
|
+
_multi_index_has_solve,
|
|
544
|
+
_series_or_df_has_solve,
|
|
545
|
+
read_sets,
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
output_dir = Path(output_dir)
|
|
549
|
+
|
|
550
|
+
s = read_sets(flex_data, solution, solve_name=solve_name)
|
|
551
|
+
|
|
552
|
+
realized_dt = _realized_dt_set(flex_data)
|
|
553
|
+
realized_dispatch_periods = _realized_dispatch_periods(flex_data)
|
|
554
|
+
if realized_invest_periods is None:
|
|
555
|
+
realized_invest_periods = set(realized_dispatch_periods)
|
|
556
|
+
|
|
557
|
+
written: list[Path] = []
|
|
558
|
+
for attr, obj in vars(s).items():
|
|
559
|
+
if isinstance(obj, pd.MultiIndex):
|
|
560
|
+
if not _multi_index_has_solve(obj):
|
|
561
|
+
# Invariant topology — taken once at union time (stage 3).
|
|
562
|
+
continue
|
|
563
|
+
sliced = _apply_set_realized_filter(
|
|
564
|
+
attr, obj,
|
|
565
|
+
realized_dt=realized_dt,
|
|
566
|
+
realized_invest_periods=set(realized_invest_periods),
|
|
567
|
+
realized_dispatch_periods=realized_dispatch_periods,
|
|
568
|
+
)
|
|
569
|
+
elif _series_or_df_has_solve(obj):
|
|
570
|
+
# Defensive: no varying set is a DataFrame/Series with a
|
|
571
|
+
# ``solve`` MultiIndex level today, but the structural test
|
|
572
|
+
# admits one (schema drift). Reuse the param-side
|
|
573
|
+
# (period, time)/(period) filter so such a set is realized-
|
|
574
|
+
# sliced rather than persisted whole. ``dtt`` / ``dtttdt``
|
|
575
|
+
# are MultiIndex (handled above), so this branch never
|
|
576
|
+
# touches the adjacency-column verbatim contract.
|
|
577
|
+
sliced = _apply_realized_filter(
|
|
578
|
+
attr, obj, flex_data,
|
|
579
|
+
realized_dt=realized_dt,
|
|
580
|
+
realized_invest_periods=set(realized_invest_periods),
|
|
581
|
+
)
|
|
582
|
+
else:
|
|
583
|
+
# No ``solve`` level — invariant; taken once at stage 3.
|
|
584
|
+
continue
|
|
585
|
+
path = write_realized_slice_parquet(
|
|
586
|
+
sliced, attr=attr, solve_name=solve_name, output_dir=output_dir,
|
|
587
|
+
)
|
|
588
|
+
written.append(path)
|
|
589
|
+
|
|
590
|
+
_logger.debug(
|
|
591
|
+
"Persisted %d realized set slices for solve '%s' to %s",
|
|
592
|
+
len(written), solve_name, output_dir,
|
|
593
|
+
)
|
|
594
|
+
return written
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
__all__ = [
|
|
598
|
+
"write_all_params_realized_slice",
|
|
599
|
+
"write_all_sets_realized_slice",
|
|
600
|
+
"write_realized_slice_parquet",
|
|
601
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from types import SimpleNamespace
|
|
2
|
+
|
|
3
|
+
from flextool.process_outputs.drop_levels import drop_levels
|
|
4
|
+
from flextool.process_outputs.calc_capacity_flows import compute_capacity_and_flows
|
|
5
|
+
from flextool.process_outputs.calc_connections import compute_connection_flows
|
|
6
|
+
from flextool.process_outputs.calc_storage_vre import compute_storage_and_vre
|
|
7
|
+
from flextool.process_outputs.calc_slacks import compute_slacks
|
|
8
|
+
from flextool.process_outputs.calc_costs import compute_costs
|
|
9
|
+
from flextool.process_outputs.calc_group_flows import compute_group_flows
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def post_process_results(par, s, v):
|
|
13
|
+
"""Calculate post-processing results from variables, parameters, and sets"""
|
|
14
|
+
r = SimpleNamespace()
|
|
15
|
+
|
|
16
|
+
par, s, v = drop_levels(par, s, v)
|
|
17
|
+
compute_capacity_and_flows(par, s, v, r)
|
|
18
|
+
compute_connection_flows(par, s, v, r)
|
|
19
|
+
compute_storage_and_vre(par, s, v, r)
|
|
20
|
+
compute_slacks(par, s, v, r)
|
|
21
|
+
compute_costs(par, s, v, r)
|
|
22
|
+
compute_group_flows(par, s, v, r)
|
|
23
|
+
|
|
24
|
+
return r
|