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,377 @@
|
|
|
1
|
+
"""DC power flow input-derivation — BFS reference-node + susceptance.
|
|
2
|
+
|
|
3
|
+
Reads raw input through
|
|
4
|
+
:class:`flextool.spinedb_backend.SpineDBBackend`, performs the
|
|
5
|
+
BFS reference-node selection + susceptance computation, and emits four
|
|
6
|
+
canonical frames into the :class:`FlexDataProvider`:
|
|
7
|
+
|
|
8
|
+
* ``input/node_dc_power_flow`` single-column ``node``
|
|
9
|
+
* ``input/connection_dc_power_flow`` single-column ``process``
|
|
10
|
+
* ``input/node_reference_angle`` single-column ``node``
|
|
11
|
+
* ``input/p_connection_susceptance`` ``process, p_connection_susceptance``
|
|
12
|
+
|
|
13
|
+
Plus a small carrier under ``derived/ct_method_overrides`` consumed by
|
|
14
|
+
the next derivation (:mod:`flextool.input_derivation._process_method`,
|
|
15
|
+
Phase C) — maps connection_name → overridden ct_method (necessary for
|
|
16
|
+
the group-level ``transfer_method`` mechanism that DC PF activates).
|
|
17
|
+
|
|
18
|
+
This module replaces the on-disk emission and the
|
|
19
|
+
``input_writer._write_dc_power_flow_data`` call site. No CSV touches
|
|
20
|
+
disk here; the cascade consumer (:mod:`flextool.engine_polars._dc_power_flow`)
|
|
21
|
+
reads the four frames from the Provider.
|
|
22
|
+
|
|
23
|
+
See ``specs/step_2_5_audit.md`` Section 7 item 9 for the migration
|
|
24
|
+
plan and Section 1 for the input/output contract.
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import logging
|
|
29
|
+
|
|
30
|
+
import polars as pl
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def derive_dc_power_flow(
|
|
34
|
+
backend,
|
|
35
|
+
provider,
|
|
36
|
+
logger: logging.Logger,
|
|
37
|
+
) -> dict[str, str]:
|
|
38
|
+
"""Run the DC power flow derivation.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
backend : SpineDBBackend
|
|
43
|
+
The opened backend. Used for raw EAV reads via
|
|
44
|
+
:meth:`SpineDBBackend.find_entities` and
|
|
45
|
+
:meth:`SpineDBBackend.find_parameter_values`.
|
|
46
|
+
provider : FlexDataProvider
|
|
47
|
+
The cascade-input Provider; receives the four output frames
|
|
48
|
+
plus the ``derived/ct_method_overrides`` carrier.
|
|
49
|
+
logger : logging.Logger
|
|
50
|
+
Routed to the same logger as the legacy helper (DC PF warnings
|
|
51
|
+
about missing reactance, zero existing capacity, and the
|
|
52
|
+
auto-selected reference-node info messages).
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
dict[str, str]
|
|
57
|
+
``ct_method_overrides`` — connection_name → overridden
|
|
58
|
+
ct_method. Returned for the immediate-caller orchestration
|
|
59
|
+
convenience (``write_input`` passes it to
|
|
60
|
+
:func:`_write_process_method` today), but the canonical
|
|
61
|
+
cross-derivation channel is the Provider's
|
|
62
|
+
``derived/ct_method_overrides`` key.
|
|
63
|
+
|
|
64
|
+
Reads via ``backend.find_entities`` / ``backend.find_parameter_values``
|
|
65
|
+
and emits to the Provider via ``provider.put(key, frame)``. The
|
|
66
|
+
four output frames use the canonical CSV column headers as their
|
|
67
|
+
polars columns so downstream byte-parity is preserved when the
|
|
68
|
+
Provider is snapshotted to disk.
|
|
69
|
+
"""
|
|
70
|
+
ct_method_overrides: dict[str, str] = {}
|
|
71
|
+
|
|
72
|
+
# --- Read group transfer_method parameters ---
|
|
73
|
+
group_transfer_methods: dict[str, str] = {}
|
|
74
|
+
for pv in backend.find_parameter_values(
|
|
75
|
+
entity_class_name="group",
|
|
76
|
+
parameter_definition_name="transfer_method",
|
|
77
|
+
):
|
|
78
|
+
if pv["type"] is None:
|
|
79
|
+
continue
|
|
80
|
+
group_name = pv["entity_byname"][0]
|
|
81
|
+
group_transfer_methods[group_name] = str(pv["parsed_value"])
|
|
82
|
+
|
|
83
|
+
# --- Build group -> member_nodes mapping ---
|
|
84
|
+
group_nodes: dict[str, set[str]] = {}
|
|
85
|
+
for entity in backend.find_entities(entity_class_name="group__node"):
|
|
86
|
+
group_name = entity["entity_byname"][0]
|
|
87
|
+
node_name = entity["entity_byname"][1]
|
|
88
|
+
group_nodes.setdefault(group_name, set()).add(node_name)
|
|
89
|
+
|
|
90
|
+
# --- Build connection -> (node1, node2) mapping from connection__node__node ---
|
|
91
|
+
connection_endpoints: dict[str, tuple[str, str]] = {}
|
|
92
|
+
for entity in backend.find_entities(entity_class_name="connection__node__node"):
|
|
93
|
+
conn_name = entity["entity_byname"][0]
|
|
94
|
+
node1 = entity["entity_byname"][1]
|
|
95
|
+
node2 = entity["entity_byname"][2]
|
|
96
|
+
connection_endpoints[conn_name] = (node1, node2)
|
|
97
|
+
|
|
98
|
+
# --- Read is_DC parameter for connections ---
|
|
99
|
+
is_dc_connections: set[str] = set()
|
|
100
|
+
for pv in backend.find_parameter_values(
|
|
101
|
+
entity_class_name="connection",
|
|
102
|
+
parameter_definition_name="is_DC",
|
|
103
|
+
):
|
|
104
|
+
if pv["type"] is None:
|
|
105
|
+
continue
|
|
106
|
+
if str(pv["parsed_value"]) == "yes":
|
|
107
|
+
is_dc_connections.add(pv["entity_byname"][0])
|
|
108
|
+
|
|
109
|
+
# --- Apply group transfer_method overrides for ALL groups ---
|
|
110
|
+
dc_pf_groups: list[str] = []
|
|
111
|
+
for group_name, method in group_transfer_methods.items():
|
|
112
|
+
if method == "use_connection_transfer_methods":
|
|
113
|
+
continue
|
|
114
|
+
nodes_in_group = group_nodes.get(group_name, set())
|
|
115
|
+
if not nodes_in_group:
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
is_dc_pf = method == "dc_power_flow_with_angles"
|
|
119
|
+
if is_dc_pf:
|
|
120
|
+
dc_pf_groups.append(group_name)
|
|
121
|
+
|
|
122
|
+
# Find connections where BOTH endpoints are in the group's node set
|
|
123
|
+
for conn_name, (n1, n2) in connection_endpoints.items():
|
|
124
|
+
if n1 in nodes_in_group and n2 in nodes_in_group:
|
|
125
|
+
# For dc_power_flow_with_angles: exclude is_DC=yes connections
|
|
126
|
+
if is_dc_pf and conn_name in is_dc_connections:
|
|
127
|
+
continue
|
|
128
|
+
if is_dc_pf:
|
|
129
|
+
# DC PF connections use no_losses_no_variable_cost internally
|
|
130
|
+
ct_method_overrides[conn_name] = "no_losses_no_variable_cost"
|
|
131
|
+
else:
|
|
132
|
+
ct_method_overrides[conn_name] = method
|
|
133
|
+
|
|
134
|
+
# --- DC power flow specific processing ---
|
|
135
|
+
dc_pf_nodes: set[str] = set()
|
|
136
|
+
dc_pf_connections: set[str] = set()
|
|
137
|
+
reference_nodes: list[str] = []
|
|
138
|
+
susceptance_map: dict[str, float] = {}
|
|
139
|
+
|
|
140
|
+
for group_name in dc_pf_groups:
|
|
141
|
+
nodes_in_group = group_nodes.get(group_name, set())
|
|
142
|
+
dc_pf_nodes.update(nodes_in_group)
|
|
143
|
+
|
|
144
|
+
# Collect DC PF connections for this group
|
|
145
|
+
group_dc_connections: set[str] = set()
|
|
146
|
+
for conn_name, (n1, n2) in connection_endpoints.items():
|
|
147
|
+
if n1 in nodes_in_group and n2 in nodes_in_group:
|
|
148
|
+
if conn_name not in is_dc_connections:
|
|
149
|
+
group_dc_connections.add(conn_name)
|
|
150
|
+
dc_pf_connections.update(group_dc_connections)
|
|
151
|
+
|
|
152
|
+
# --- Reference bus detection ---
|
|
153
|
+
# Check if reference_node parameter is set on this group
|
|
154
|
+
ref_node: str | None = None
|
|
155
|
+
for pv in backend.find_parameter_values(
|
|
156
|
+
entity_class_name="group",
|
|
157
|
+
parameter_definition_name="reference_node",
|
|
158
|
+
):
|
|
159
|
+
if pv["type"] is None:
|
|
160
|
+
continue
|
|
161
|
+
if pv["entity_byname"][0] == group_name:
|
|
162
|
+
ref_node = str(pv["parsed_value"])
|
|
163
|
+
break
|
|
164
|
+
|
|
165
|
+
if ref_node is not None:
|
|
166
|
+
if ref_node in nodes_in_group:
|
|
167
|
+
reference_nodes.append(ref_node)
|
|
168
|
+
else:
|
|
169
|
+
logger.warning(
|
|
170
|
+
"DC power flow: reference_node '%s' for group '%s' is not a member node. "
|
|
171
|
+
"Falling back to automatic selection.",
|
|
172
|
+
ref_node, group_name,
|
|
173
|
+
)
|
|
174
|
+
ref_node = None
|
|
175
|
+
|
|
176
|
+
if ref_node is None:
|
|
177
|
+
# Automatic selection: build graph, find connected components via BFS,
|
|
178
|
+
# pick node with largest existing capacity in each component.
|
|
179
|
+
adjacency: dict[str, set[str]] = {}
|
|
180
|
+
for conn_name in group_dc_connections:
|
|
181
|
+
n1, n2 = connection_endpoints[conn_name]
|
|
182
|
+
adjacency.setdefault(n1, set()).add(n2)
|
|
183
|
+
adjacency.setdefault(n2, set()).add(n1)
|
|
184
|
+
|
|
185
|
+
# Also include isolated nodes from the group
|
|
186
|
+
for node in nodes_in_group:
|
|
187
|
+
adjacency.setdefault(node, set())
|
|
188
|
+
|
|
189
|
+
# Read existing capacity for nodes
|
|
190
|
+
node_existing: dict[str, float] = {}
|
|
191
|
+
for pv in backend.find_parameter_values(
|
|
192
|
+
entity_class_name="node",
|
|
193
|
+
parameter_definition_name="existing",
|
|
194
|
+
):
|
|
195
|
+
if pv["type"] is None:
|
|
196
|
+
continue
|
|
197
|
+
node_name = pv["entity_byname"][0]
|
|
198
|
+
if node_name in nodes_in_group:
|
|
199
|
+
val = pv["parsed_value"]
|
|
200
|
+
if isinstance(val, (int, float)):
|
|
201
|
+
node_existing[node_name] = float(val)
|
|
202
|
+
|
|
203
|
+
# BFS to find connected components
|
|
204
|
+
visited: set[str] = set()
|
|
205
|
+
for start_node in sorted(adjacency.keys()):
|
|
206
|
+
if start_node in visited:
|
|
207
|
+
continue
|
|
208
|
+
# BFS
|
|
209
|
+
component: list[str] = []
|
|
210
|
+
queue = [start_node]
|
|
211
|
+
visited.add(start_node)
|
|
212
|
+
while queue:
|
|
213
|
+
current = queue.pop(0)
|
|
214
|
+
component.append(current)
|
|
215
|
+
for neighbor in adjacency.get(current, set()):
|
|
216
|
+
if neighbor not in visited:
|
|
217
|
+
visited.add(neighbor)
|
|
218
|
+
queue.append(neighbor)
|
|
219
|
+
|
|
220
|
+
# Pick node with largest existing capacity in this component
|
|
221
|
+
best_node = component[0]
|
|
222
|
+
best_cap = node_existing.get(best_node, 0.0)
|
|
223
|
+
for node in component[1:]:
|
|
224
|
+
cap = node_existing.get(node, 0.0)
|
|
225
|
+
if cap > best_cap:
|
|
226
|
+
best_cap = cap
|
|
227
|
+
best_node = node
|
|
228
|
+
reference_nodes.append(best_node)
|
|
229
|
+
logger.info(
|
|
230
|
+
"DC power flow: auto-selected reference node '%s' (existing=%.1f) "
|
|
231
|
+
"for connected component in group '%s'.",
|
|
232
|
+
best_node, best_cap, group_name,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# --- Susceptance computation ---
|
|
236
|
+
base_mva = 100.0
|
|
237
|
+
for pv in backend.find_parameter_values(
|
|
238
|
+
entity_class_name="group",
|
|
239
|
+
parameter_definition_name="base_MVA",
|
|
240
|
+
):
|
|
241
|
+
if pv["type"] is None:
|
|
242
|
+
continue
|
|
243
|
+
if pv["entity_byname"][0] == group_name:
|
|
244
|
+
base_mva = float(pv["parsed_value"])
|
|
245
|
+
break
|
|
246
|
+
|
|
247
|
+
# Read reactance for DC PF connections
|
|
248
|
+
reactance_map: dict[str, float] = {}
|
|
249
|
+
for pv in backend.find_parameter_values(
|
|
250
|
+
entity_class_name="connection",
|
|
251
|
+
parameter_definition_name="reactance",
|
|
252
|
+
):
|
|
253
|
+
if pv["type"] is None:
|
|
254
|
+
continue
|
|
255
|
+
conn_name = pv["entity_byname"][0]
|
|
256
|
+
if conn_name in group_dc_connections:
|
|
257
|
+
reactance_map[conn_name] = float(pv["parsed_value"])
|
|
258
|
+
|
|
259
|
+
# Compute susceptance (accumulated across groups)
|
|
260
|
+
for conn_name in group_dc_connections:
|
|
261
|
+
if conn_name in reactance_map:
|
|
262
|
+
reactance = reactance_map[conn_name]
|
|
263
|
+
if reactance != 0.0:
|
|
264
|
+
susceptance_map[conn_name] = base_mva / reactance
|
|
265
|
+
else:
|
|
266
|
+
logger.warning(
|
|
267
|
+
"DC power flow: connection '%s' has zero reactance — skipping susceptance computation.",
|
|
268
|
+
conn_name,
|
|
269
|
+
)
|
|
270
|
+
else:
|
|
271
|
+
logger.warning(
|
|
272
|
+
"DC power flow: connection '%s' in group '%s' has no reactance parameter set.",
|
|
273
|
+
conn_name, group_name,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# --- Candidate pre-capacity warning ---
|
|
277
|
+
precapacity = 0.1
|
|
278
|
+
for pv in backend.find_parameter_values(
|
|
279
|
+
entity_class_name="group",
|
|
280
|
+
parameter_definition_name="candidate_precapacity_to_avoid_big_m",
|
|
281
|
+
):
|
|
282
|
+
if pv["type"] is None:
|
|
283
|
+
continue
|
|
284
|
+
if pv["entity_byname"][0] == group_name:
|
|
285
|
+
precapacity = float(pv["parsed_value"])
|
|
286
|
+
break
|
|
287
|
+
|
|
288
|
+
# Read existing capacity for connections
|
|
289
|
+
conn_existing: dict[str, float] = {}
|
|
290
|
+
for pv in backend.find_parameter_values(
|
|
291
|
+
entity_class_name="connection",
|
|
292
|
+
parameter_definition_name="existing",
|
|
293
|
+
):
|
|
294
|
+
if pv["type"] is None:
|
|
295
|
+
continue
|
|
296
|
+
conn_name = pv["entity_byname"][0]
|
|
297
|
+
if conn_name in group_dc_connections:
|
|
298
|
+
val = pv["parsed_value"]
|
|
299
|
+
if isinstance(val, (int, float)):
|
|
300
|
+
conn_existing[conn_name] = float(val)
|
|
301
|
+
|
|
302
|
+
for conn_name in group_dc_connections:
|
|
303
|
+
existing = conn_existing.get(conn_name, 0.0)
|
|
304
|
+
if existing == 0.0:
|
|
305
|
+
logger.warning(
|
|
306
|
+
"DC power flow: connection '%s' in group '%s' has zero existing capacity. "
|
|
307
|
+
"For DC power flow to work without big-M constraints, set a small existing "
|
|
308
|
+
"capacity (e.g. %.3f MW). The candidate_precapacity_to_avoid_big_m "
|
|
309
|
+
"parameter is %.3f MW.",
|
|
310
|
+
conn_name, group_name, precapacity, precapacity,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# --- Build canonical frames and place them on the Provider ---
|
|
314
|
+
# node_dc_power_flow — single-column ``node``, sorted (matches the
|
|
315
|
+
# legacy CSV emission order).
|
|
316
|
+
node_dc_frame = pl.DataFrame(
|
|
317
|
+
{"node": sorted(dc_pf_nodes)},
|
|
318
|
+
schema={"node": pl.Utf8},
|
|
319
|
+
)
|
|
320
|
+
provider.put("input/node_dc_power_flow", node_dc_frame)
|
|
321
|
+
|
|
322
|
+
# connection_dc_power_flow — single-column ``process``, sorted.
|
|
323
|
+
# Note: the canonical CSV column is ``process`` (matches the
|
|
324
|
+
# ``connection_dc_power_flow`` set declaration).
|
|
325
|
+
conn_dc_frame = pl.DataFrame(
|
|
326
|
+
{"process": sorted(dc_pf_connections)},
|
|
327
|
+
schema={"process": pl.Utf8},
|
|
328
|
+
)
|
|
329
|
+
provider.put("input/connection_dc_power_flow", conn_dc_frame)
|
|
330
|
+
|
|
331
|
+
# node_reference_angle — single-column ``node``, preserves
|
|
332
|
+
# reference-selection order (manual entries first, BFS auto-selection
|
|
333
|
+
# second, per the original logic).
|
|
334
|
+
ref_angle_frame = pl.DataFrame(
|
|
335
|
+
{"node": list(reference_nodes)},
|
|
336
|
+
schema={"node": pl.Utf8},
|
|
337
|
+
)
|
|
338
|
+
provider.put("input/node_reference_angle", ref_angle_frame)
|
|
339
|
+
|
|
340
|
+
# p_connection_susceptance — two columns ``process,
|
|
341
|
+
# p_connection_susceptance``, sorted by process. The legacy
|
|
342
|
+
# ``open(..., "w").write(f"{conn},{susceptance_map[conn]}\n")`` emits
|
|
343
|
+
# float values as their Python str repr; preserve byte-parity by
|
|
344
|
+
# casting to Utf8 with the same str() coercion (avoids polars'
|
|
345
|
+
# default float-formatting which adds a trailing decimal).
|
|
346
|
+
sorted_conns = sorted(susceptance_map.keys())
|
|
347
|
+
susceptance_frame = pl.DataFrame(
|
|
348
|
+
{
|
|
349
|
+
"process": sorted_conns,
|
|
350
|
+
"p_connection_susceptance": [
|
|
351
|
+
str(susceptance_map[c]) for c in sorted_conns
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
schema={
|
|
355
|
+
"process": pl.Utf8,
|
|
356
|
+
"p_connection_susceptance": pl.Utf8,
|
|
357
|
+
},
|
|
358
|
+
)
|
|
359
|
+
provider.put("input/p_connection_susceptance", susceptance_frame)
|
|
360
|
+
|
|
361
|
+
# derived/ct_method_overrides — cross-derivation carrier for
|
|
362
|
+
# :func:`flextool.input_derivation._process_method.derive_process_method`.
|
|
363
|
+
# Stored as a two-column (process, ct_method) frame so the Provider
|
|
364
|
+
# holds it under the same in-memory contract as every other frame.
|
|
365
|
+
overrides_frame = pl.DataFrame(
|
|
366
|
+
{
|
|
367
|
+
"process": list(ct_method_overrides.keys()),
|
|
368
|
+
"ct_method": list(ct_method_overrides.values()),
|
|
369
|
+
},
|
|
370
|
+
schema={"process": pl.Utf8, "ct_method": pl.Utf8},
|
|
371
|
+
)
|
|
372
|
+
provider.put("derived/ct_method_overrides", overrides_frame)
|
|
373
|
+
|
|
374
|
+
return ct_method_overrides
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
__all__ = ["derive_dc_power_flow"]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Method-enum subsets — invariants of the FlexTool method taxonomy.
|
|
2
|
+
|
|
3
|
+
These are NOT user-editable; they're invariants of the FlexTool method
|
|
4
|
+
taxonomy. Lifetime / ct / startup / co2 method constants are defined in
|
|
5
|
+
their own preprocessing modules to keep the lookup tight.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# LP variants of online (linear)
|
|
11
|
+
METHOD_LP: frozenset[str] = frozenset((
|
|
12
|
+
"method_1way_1var_LP",
|
|
13
|
+
"method_1way_nvar_LP",
|
|
14
|
+
))
|
|
15
|
+
|
|
16
|
+
# MIP variants (binary online)
|
|
17
|
+
METHOD_MIP: frozenset[str] = frozenset((
|
|
18
|
+
"method_1way_1var_MIP",
|
|
19
|
+
"method_1way_nvar_MIP",
|
|
20
|
+
"method_2way_2var_MIP_exclude",
|
|
21
|
+
))
|
|
22
|
+
|
|
23
|
+
# direct (no efficiency conversion)
|
|
24
|
+
METHOD_DIRECT: frozenset[str] = frozenset((
|
|
25
|
+
"method_1way_1var_off",
|
|
26
|
+
"method_1way_1var_LP",
|
|
27
|
+
"method_1way_1var_MIP",
|
|
28
|
+
"method_2way_1var_off",
|
|
29
|
+
"method_2way_2var_off",
|
|
30
|
+
"method_2way_2var_exclude",
|
|
31
|
+
"method_2way_2var_MIP_exclude",
|
|
32
|
+
))
|
|
33
|
+
|
|
34
|
+
# indirect (efficiency-converting)
|
|
35
|
+
METHOD_INDIRECT: frozenset[str] = frozenset((
|
|
36
|
+
"method_1way_nvar_off",
|
|
37
|
+
"method_1way_nvar_LP",
|
|
38
|
+
"method_1way_nvar_MIP",
|
|
39
|
+
"method_2way_nvar_off",
|
|
40
|
+
))
|
|
41
|
+
|
|
42
|
+
# 2-way 1-variable (one v_flow shared by both directions)
|
|
43
|
+
METHOD_2WAY_1VAR: frozenset[str] = frozenset((
|
|
44
|
+
"method_2way_1var_off",
|
|
45
|
+
))
|
|
46
|
+
|
|
47
|
+
# 2-way 2-variable (separate v_flow per direction)
|
|
48
|
+
METHOD_2WAY_2VAR: frozenset[str] = frozenset((
|
|
49
|
+
"method_2way_2var_off",
|
|
50
|
+
"method_2way_2var_exclude",
|
|
51
|
+
"method_2way_2var_MIP_exclude",
|
|
52
|
+
))
|
|
53
|
+
|
|
54
|
+
# 2-way nvar (n flow variables)
|
|
55
|
+
METHOD_2WAY_NVAR: frozenset[str] = frozenset((
|
|
56
|
+
"method_2way_nvar_off",
|
|
57
|
+
))
|
|
58
|
+
|
|
59
|
+
# 1-way 1-variable
|
|
60
|
+
METHOD_1WAY_1VAR: frozenset[str] = frozenset((
|
|
61
|
+
"method_1way_1var_off",
|
|
62
|
+
"method_1way_1var_LP",
|
|
63
|
+
"method_1way_1var_MIP",
|
|
64
|
+
))
|
|
65
|
+
|
|
66
|
+
# all 1-way methods (1var + nvar variants)
|
|
67
|
+
METHOD_1WAY: frozenset[str] = frozenset((
|
|
68
|
+
"method_1way_1var_off", "method_1way_1var_LP", "method_1way_1var_MIP",
|
|
69
|
+
"method_1way_nvar_off", "method_1way_nvar_LP", "method_1way_nvar_MIP",
|
|
70
|
+
))
|
|
71
|
+
|
|
72
|
+
# universe of ramp methods
|
|
73
|
+
RAMP_METHOD: frozenset[str] = frozenset(("ramp_limit", "ramp_cost", "both"))
|
|
74
|
+
|
|
75
|
+
RAMP_LIMIT_METHOD: frozenset[str] = frozenset(("ramp_limit", "both"))
|
|
76
|
+
|
|
77
|
+
RAMP_COST_METHOD: frozenset[str] = frozenset(("ramp_cost", "both"))
|