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,208 @@
|
|
|
1
|
+
from types import SimpleNamespace
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Variables: droplevel('solve') only
|
|
7
|
+
_V_DROP = [
|
|
8
|
+
'flow', 'ramp', 'reserve', 'state',
|
|
9
|
+
'online_linear', 'startup_linear', 'shutdown_linear',
|
|
10
|
+
'online_integer', 'startup_integer', 'shutdown_integer',
|
|
11
|
+
'q_state_up', 'q_state_down', 'q_reserve',
|
|
12
|
+
'q_inertia', 'q_non_synchronous', 'q_state_up_group', 'q_capacity_margin',
|
|
13
|
+
'invest', 'divest',
|
|
14
|
+
'dual_invest_connection', 'dual_invest_node', 'dual_invest_unit',
|
|
15
|
+
'dual_maxInvest_period', 'dual_maxCumulative',
|
|
16
|
+
'dual_maxInvestGroup_period', 'dual_maxInvestGroup_total', 'dual_maxInvestGroup_cumulative',
|
|
17
|
+
# Investment-floor (min-side) duals — all keep a ('solve', 'period')
|
|
18
|
+
# index (the min invest-total constraint, unlike max-total, retains its
|
|
19
|
+
# period axis), so every family drops the solve level here rather than
|
|
20
|
+
# collapsing to solve-only.
|
|
21
|
+
'dual_minInvest_period', 'dual_minInvest_total', 'dual_minCumulative',
|
|
22
|
+
'dual_minInvestGroup_period', 'dual_minInvestGroup_total', 'dual_minInvestGroup_cumulative',
|
|
23
|
+
'dual_co2_max_period',
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
# Variables with a solve-only row index — must collapse to a single
|
|
27
|
+
# row (last solve wins) instead of droplevel. Same pattern as
|
|
28
|
+
# ``dual_co2_max_total`` below.
|
|
29
|
+
_V_SOLVE_ONLY = [
|
|
30
|
+
'dual_maxInvest_total',
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# Parameters: droplevel('solve') + deduplicate with keep='last'.
|
|
34
|
+
#
|
|
35
|
+
# Use this bucket for params whose per-roll values represent successive
|
|
36
|
+
# states of the same (entity, period) (e.g. ``entity_all_existing``,
|
|
37
|
+
# which is a chain-sum that gets updated as later rolls realise their
|
|
38
|
+
# investments — the LAST roll covering a given period carries the
|
|
39
|
+
# post-decision value).
|
|
40
|
+
_PAR_DROP = [
|
|
41
|
+
'step_duration', 'timestep_weight', 'flow_min', 'flow_max',
|
|
42
|
+
'process_availability', 'process_source_sink_varCost',
|
|
43
|
+
'process_slope', 'process_section',
|
|
44
|
+
'node_self_discharge_loss', 'node_penalty_up', 'node_penalty_down',
|
|
45
|
+
'node_inflow', 'commodity_price', 'group_co2_price',
|
|
46
|
+
'reserve_upDown_group_reservation', 'profile',
|
|
47
|
+
'entity_annual_discounted', 'entity_annual_divest_discounted',
|
|
48
|
+
'inflation_factor_investment_yearly',
|
|
49
|
+
# inflation_factor_operations_yearly evolves across rolls: each step
|
|
50
|
+
# carries period-d's "active" factor (4.54 ≈ Σ(1+r)^y × p_years[d,y])
|
|
51
|
+
# in its FIRST row and a forward-looking discount (3.73) in its
|
|
52
|
+
# SECOND row. ``keep='last'`` therefore picks the value from the
|
|
53
|
+
# step that COMMITS period d (where d is the realized invest/dispatch
|
|
54
|
+
# period), matching what the LP objective uses. ``keep='first'``
|
|
55
|
+
# would lock in the earlier step's forward-looking discount for every
|
|
56
|
+
# period after the first, leaving costs_discounted off by the
|
|
57
|
+
# active/forward ratio (~1.18 for the multi_year fixture).
|
|
58
|
+
'inflation_factor_operations_yearly',
|
|
59
|
+
'group_penalty_capacity_margin', 'group_capacity_margin',
|
|
60
|
+
# entity_all_existing carries the cumulative existing-capacity
|
|
61
|
+
# chain (existing + Σ prior invest − Σ prior divest). The last roll
|
|
62
|
+
# covering a given period writes the post-decision value; the
|
|
63
|
+
# earlier rolls' rows for the same period carry the pre-decision
|
|
64
|
+
# value. keep='last' picks the post-decision row to match v3.32.0.
|
|
65
|
+
'entity_all_existing',
|
|
66
|
+
# entity_pre_existing is the static baseline before any invest
|
|
67
|
+
# decision. In practice constant across rolls for a given
|
|
68
|
+
# (entity, period), so either keep choice yields the same value —
|
|
69
|
+
# moved here for symmetry with entity_all_existing per the
|
|
70
|
+
# 2026-05-14 investigation into the unit_capacity__d.csv lag.
|
|
71
|
+
'entity_pre_existing',
|
|
72
|
+
# NOTE: complete_period_share_of_year is intentionally NOT in this
|
|
73
|
+
# bucket — it needs an order-INDEPENDENT max-per-period reduction,
|
|
74
|
+
# done explicitly in drop_levels() below. See the comment there.
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# Parameters: droplevel('solve') + deduplicate with keep='first'.
|
|
78
|
+
#
|
|
79
|
+
# Use this bucket ONLY for params that are invariant across rolls (so
|
|
80
|
+
# either keep choice would yield the same result). Do NOT use this
|
|
81
|
+
# bucket for params whose value evolves across rolls — see _PAR_DROP
|
|
82
|
+
# above and the comment there.
|
|
83
|
+
_PAR_DEDUP = [
|
|
84
|
+
'years_from_start_d', 'years_represented_d',
|
|
85
|
+
'entity_max_units',
|
|
86
|
+
'process_startup_cost',
|
|
87
|
+
'entity_fixed_cost', 'entity_lifetime_fixed_cost', 'entity_lifetime_fixed_cost_divest',
|
|
88
|
+
'node_annual_flow',
|
|
89
|
+
'group_penalty_inertia', 'group_penalty_non_synchronous',
|
|
90
|
+
'group_inertia_limit',
|
|
91
|
+
'node_capacity_for_scaling', 'group_capacity_for_scaling',
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _reduce_share_per_period(csoy: pd.Series) -> pd.Series:
|
|
96
|
+
"""Collapse ``complete_period_share_of_year`` to one value per period.
|
|
97
|
+
|
|
98
|
+
``complete_period_share_of_year`` is the per-period annualisation
|
|
99
|
+
denominator for DISPATCH outputs (energy/cost = Σ power·dur ÷ share).
|
|
100
|
+
It is NOT invariant across sub-solves: an invest sub-solve whose
|
|
101
|
+
period samples a representative window (e.g. 3×24 h) carries
|
|
102
|
+
``share = 72/8760 ≈ 0.0082``, while the dispatch sub-solve that
|
|
103
|
+
realises the same period over the full year carries ``share = 1``.
|
|
104
|
+
|
|
105
|
+
BOTH sub-solves persist a row for the realised period, so a plain
|
|
106
|
+
``keep='first'/'last'`` dedup is **order-dependent** — and the multi-
|
|
107
|
+
solve union order is NOT guaranteed: when the ``_solve_order.txt``
|
|
108
|
+
creation manifest is absent, ``union_realized_slice`` falls back to
|
|
109
|
+
filesystem glob order, which can place the invest row last and pick
|
|
110
|
+
the 0.0082 window share, inflating every period total by
|
|
111
|
+
``8760/(sampled hours)`` (≈121.7× for a 3-day window). This was the
|
|
112
|
+
cause of an intermittent multi-solve period/aggregate over-count that
|
|
113
|
+
appeared on some scenarios/runs but not others.
|
|
114
|
+
|
|
115
|
+
The dispatch sub-solve covers the full period, so its share is always
|
|
116
|
+
≥ any sampling sub-solve's for the same period. Reducing by **max per
|
|
117
|
+
period** therefore deterministically selects the realising-dispatch
|
|
118
|
+
share regardless of union order, and is byte-identical to the correct
|
|
119
|
+
(manifest-present) ``keep='last'`` runs. ``sort=False`` preserves the
|
|
120
|
+
first-appearance period order those runs produced.
|
|
121
|
+
"""
|
|
122
|
+
csoy = csoy.droplevel('solve')
|
|
123
|
+
return csoy.groupby(level=0, sort=False).max()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def drop_levels(par: SimpleNamespace, s: SimpleNamespace, v: SimpleNamespace):
|
|
127
|
+
"""Strip the 'solve' level from all time-indexed variables, parameters and sets.
|
|
128
|
+
|
|
129
|
+
Must be called before any post-processing calculations.
|
|
130
|
+
Returns (par, s, v) with the solve level removed in-place.
|
|
131
|
+
"""
|
|
132
|
+
for attr in _V_DROP:
|
|
133
|
+
obj = getattr(v, attr)
|
|
134
|
+
obj = obj.droplevel('solve')
|
|
135
|
+
# For rolling scenarios, keep only last solve's value per (period, time)
|
|
136
|
+
if obj.index.duplicated().any():
|
|
137
|
+
obj = obj[~obj.index.duplicated(keep='last')]
|
|
138
|
+
setattr(v, attr, obj)
|
|
139
|
+
|
|
140
|
+
# Solve-only indexes (``dual_co2_max_total`` historically, plus
|
|
141
|
+
# ``dual_maxInvest_total`` which became solve-only when its reader
|
|
142
|
+
# arity was fixed): collapse to a single row (last solve wins).
|
|
143
|
+
for attr in [*_V_SOLVE_ONLY, 'dual_co2_max_total']:
|
|
144
|
+
obj = getattr(v, attr)
|
|
145
|
+
if not obj.empty:
|
|
146
|
+
last = obj.iloc[[-1]].copy()
|
|
147
|
+
last.index = pd.RangeIndex(1)
|
|
148
|
+
setattr(v, attr, last)
|
|
149
|
+
else:
|
|
150
|
+
setattr(v, attr, obj.reset_index(drop=True))
|
|
151
|
+
|
|
152
|
+
# v_angle: drop solve level only when non-empty (may be empty when no DC PF nodes)
|
|
153
|
+
if not v.angle.empty:
|
|
154
|
+
v.angle = v.angle.droplevel('solve')
|
|
155
|
+
if v.angle.index.duplicated().any():
|
|
156
|
+
v.angle = v.angle[~v.angle.index.duplicated(keep='last')]
|
|
157
|
+
|
|
158
|
+
for attr in _PAR_DROP:
|
|
159
|
+
obj = getattr(par, attr)
|
|
160
|
+
obj = obj.droplevel('solve')
|
|
161
|
+
if obj.index.duplicated().any():
|
|
162
|
+
obj = obj[~obj.index.duplicated(keep='last')]
|
|
163
|
+
setattr(par, attr, obj)
|
|
164
|
+
|
|
165
|
+
for attr in _PAR_DEDUP:
|
|
166
|
+
obj = getattr(par, attr)
|
|
167
|
+
obj = obj.droplevel('solve')
|
|
168
|
+
setattr(par, attr, obj[~obj.index.duplicated(keep='first')])
|
|
169
|
+
|
|
170
|
+
# complete_period_share_of_year — order-independent max-per-period
|
|
171
|
+
# reduction (deterministic across multi-solve union order). See
|
|
172
|
+
# _reduce_share_per_period for the full rationale.
|
|
173
|
+
par.complete_period_share_of_year = _reduce_share_per_period(
|
|
174
|
+
par.complete_period_share_of_year
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Sets have varied special handling so are done individually
|
|
178
|
+
s.solve_period = s.d_realized_period
|
|
179
|
+
# Save per-timestep solve mapping before dropping (for correct re-join in CSV output)
|
|
180
|
+
# For rolling scenarios, keep only the last solve's mapping per (period, time)
|
|
181
|
+
s.solve_period_time = s.dt_realize_dispatch
|
|
182
|
+
if s.solve_period_time.droplevel('solve').duplicated().any():
|
|
183
|
+
s.solve_period_time = s.solve_period_time[~s.solve_period_time.droplevel('solve').duplicated(keep='last')]
|
|
184
|
+
s.period = s.period.droplevel('solve')
|
|
185
|
+
s.period__time_first = s.period__time_first.droplevel('solve')
|
|
186
|
+
s.period_first_of_solve = s.period_first_of_solve.droplevel('solve')
|
|
187
|
+
s.period_in_use = s.period_in_use.droplevel('solve').unique()
|
|
188
|
+
s.d_realize_dispatch_or_invest = s.d_realize_dispatch_or_invest.droplevel('solve').unique()
|
|
189
|
+
s.d_realize_invest = s.d_realize_invest.droplevel('solve').unique()
|
|
190
|
+
s.d_realized_period = s.d_realized_period.droplevel('solve').unique()
|
|
191
|
+
s.dt_realize_dispatch = s.dt_realize_dispatch.droplevel('solve').unique()
|
|
192
|
+
s.dt = s.dt.droplevel('solve').unique()
|
|
193
|
+
s.dt_fix_storage_timesteps = s.dt_fix_storage_timesteps.droplevel('solve')
|
|
194
|
+
# For rolling scenarios, dtt/dtttdt have overlapping timesteps from different solves.
|
|
195
|
+
# Filter to only realized (period, time) pairs, keeping the last solve's mapping.
|
|
196
|
+
s.dtt = s.dtt.droplevel('solve')
|
|
197
|
+
dtt_pt = s.dtt.droplevel('t_previous')
|
|
198
|
+
s.dtt = s.dtt[dtt_pt.isin(s.dt_realize_dispatch) & ~dtt_pt.duplicated(keep='last')]
|
|
199
|
+
s.dtttdt = s.dtttdt.droplevel('solve')
|
|
200
|
+
dtttdt_pt = s.dtttdt.droplevel(['t_previous', 't_previous_within_timeset', 'd_previous', 't_previous_within_solve'])
|
|
201
|
+
s.dtttdt = s.dtttdt[dtttdt_pt.isin(s.dt_realize_dispatch) & ~dtttdt_pt.duplicated(keep='last')]
|
|
202
|
+
s.ed_invest = s.ed_invest.droplevel('solve').unique().join(s.d_realize_invest, how='inner')
|
|
203
|
+
s.edd_invest = s.edd_invest.droplevel('solve').unique()
|
|
204
|
+
s.edd_invest.names = ['entity', 'period_invest', 'period']
|
|
205
|
+
s.edd_invest = s.edd_invest.join(s.d_realize_invest, how='inner')
|
|
206
|
+
s.ed_divest = s.ed_divest.droplevel('solve').unique().join(s.d_realize_invest, how='inner')
|
|
207
|
+
|
|
208
|
+
return par, s, v
|