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.
Files changed (322) hide show
  1. flextool/__init__.py +41 -0
  2. flextool/_mem_sampler.py +193 -0
  3. flextool/_resources.py +43 -0
  4. flextool/calibrate/__init__.py +51 -0
  5. flextool/calibrate/__main__.py +11 -0
  6. flextool/calibrate/_cli.py +316 -0
  7. flextool/calibrate/_db_alt.py +166 -0
  8. flextool/calibrate/_final_outputs.py +110 -0
  9. flextool/calibrate/_guard.py +151 -0
  10. flextool/calibrate/_loop.py +558 -0
  11. flextool/calibrate/_readers.py +223 -0
  12. flextool/calibrate/_report.py +263 -0
  13. flextool/calibrate/_sizing.py +699 -0
  14. flextool/calibrate/_solve.py +134 -0
  15. flextool/calibrate/_solve_status.py +495 -0
  16. flextool/cli/__init__.py +9 -0
  17. flextool/cli/_console.py +51 -0
  18. flextool/cli/_timing.py +147 -0
  19. flextool/cli/cmd_execute_flextool_workflow.py +187 -0
  20. flextool/cli/cmd_export_to_tabular.py +56 -0
  21. flextool/cli/cmd_import_sensitivities.py +75 -0
  22. flextool/cli/cmd_migrate_database.py +13 -0
  23. flextool/cli/cmd_open_results_db.py +269 -0
  24. flextool/cli/cmd_read_matpower.py +66 -0
  25. flextool/cli/cmd_read_old_flextool.py +63 -0
  26. flextool/cli/cmd_read_self_describing_tabular_input.py +50 -0
  27. flextool/cli/cmd_read_tabular_input.py +81 -0
  28. flextool/cli/cmd_run_flextool.py +1095 -0
  29. flextool/cli/cmd_scenario_results.py +284 -0
  30. flextool/cli/cmd_solve_mps.py +169 -0
  31. flextool/cli/cmd_update_flextool.py +17 -0
  32. flextool/cli/cmd_write_outputs.py +125 -0
  33. flextool/common_utils/__init__.py +1 -0
  34. flextool/common_utils/plot_mem_shape.py +77 -0
  35. flextool/common_utils/precision.py +451 -0
  36. flextool/decomposition/__init__.py +0 -0
  37. flextool/decomposition/region_decomposition.py +128 -0
  38. flextool/decomposition/region_filter.py +1261 -0
  39. flextool/engine_polars/__init__.py +110 -0
  40. flextool/engine_polars/_axis_enums.py +742 -0
  41. flextool/engine_polars/_benders.py +3462 -0
  42. flextool/engine_polars/_block_layout.py +1479 -0
  43. flextool/engine_polars/_blocks.py +1515 -0
  44. flextool/engine_polars/_commodity_ladder.py +660 -0
  45. flextool/engine_polars/_cumulative_invest.py +1165 -0
  46. flextool/engine_polars/_db_loader.py +153 -0
  47. flextool/engine_polars/_db_reader.py +127 -0
  48. flextool/engine_polars/_dc_power_flow.py +445 -0
  49. flextool/engine_polars/_delay.py +442 -0
  50. flextool/engine_polars/_derived_arithmetic.py +432 -0
  51. flextool/engine_polars/_derived_block.py +990 -0
  52. flextool/engine_polars/_derived_branch.py +769 -0
  53. flextool/engine_polars/_derived_existing.py +1353 -0
  54. flextool/engine_polars/_derived_npv.py +1297 -0
  55. flextool/engine_polars/_derived_params.py +9850 -0
  56. flextool/engine_polars/_derived_profile.py +881 -0
  57. flextool/engine_polars/_derived_walks.py +276 -0
  58. flextool/engine_polars/_determinism.py +70 -0
  59. flextool/engine_polars/_direct_params.py +2186 -0
  60. flextool/engine_polars/_dump_csvs.py +1009 -0
  61. flextool/engine_polars/_emit_arc_unions.py +1631 -0
  62. flextool/engine_polars/_emit_calc_params.py +729 -0
  63. flextool/engine_polars/_emit_chain_params.py +709 -0
  64. flextool/engine_polars/_emit_co2_accumulators.py +400 -0
  65. flextool/engine_polars/_emit_dispatchers.py +690 -0
  66. flextool/engine_polars/_emit_energy_margin.py +125 -0
  67. flextool/engine_polars/_emit_energy_margin_adder.py +290 -0
  68. flextool/engine_polars/_emit_entity_annual.py +428 -0
  69. flextool/engine_polars/_emit_inflow_scaling.py +1420 -0
  70. flextool/engine_polars/_emit_leaf_sets.py +550 -0
  71. flextool/engine_polars/_emit_lp_scaling.py +665 -0
  72. flextool/engine_polars/_emit_mid_sets.py +859 -0
  73. flextool/engine_polars/_emit_pdt_params.py +759 -0
  74. flextool/engine_polars/_emit_per_solve.py +774 -0
  75. flextool/engine_polars/_emit_period_calc.py +504 -0
  76. flextool/engine_polars/_emit_period_params.py +2398 -0
  77. flextool/engine_polars/_emit_provider_io.py +141 -0
  78. flextool/engine_polars/_emit_reserve.py +574 -0
  79. flextool/engine_polars/_emit_solve_time.py +311 -0
  80. flextool/engine_polars/_emit_solve_writers.py +1249 -0
  81. flextool/engine_polars/_flex_data_accumulator.py +388 -0
  82. flextool/engine_polars/_flex_data_provider.py +478 -0
  83. flextool/engine_polars/_group_slack.py +1253 -0
  84. flextool/engine_polars/_inmemory_reader.py +140 -0
  85. flextool/engine_polars/_input_source.py +336 -0
  86. flextool/engine_polars/_invest_seeds.py +191 -0
  87. flextool/engine_polars/_native_input_writer.py +100 -0
  88. flextool/engine_polars/_native_run_model.py +1348 -0
  89. flextool/engine_polars/_orchestration.py +4314 -0
  90. flextool/engine_polars/_output_writer.py +439 -0
  91. flextool/engine_polars/_param_shapes.py +1595 -0
  92. flextool/engine_polars/_parquet_bundle.py +723 -0
  93. flextool/engine_polars/_pdt_join.py +167 -0
  94. flextool/engine_polars/_pdt_lookup.py +547 -0
  95. flextool/engine_polars/_per_solve_sets.py +335 -0
  96. flextool/engine_polars/_projection_params.py +2056 -0
  97. flextool/engine_polars/_provider_keys.py +173 -0
  98. flextool/engine_polars/_provider_translators.py +225 -0
  99. flextool/engine_polars/_recursive_solve.py +703 -0
  100. flextool/engine_polars/_region_filter.py +2508 -0
  101. flextool/engine_polars/_reserve.py +649 -0
  102. flextool/engine_polars/_solve_acceptance.py +331 -0
  103. flextool/engine_polars/_solve_config.py +1001 -0
  104. flextool/engine_polars/_solve_context.py +885 -0
  105. flextool/engine_polars/_solve_handoff.py +164 -0
  106. flextool/engine_polars/_solve_state.py +232 -0
  107. flextool/engine_polars/_solver_base.py +36 -0
  108. flextool/engine_polars/_solver_dispatch.py +511 -0
  109. flextool/engine_polars/_spinedb_reader.py +1165 -0
  110. flextool/engine_polars/_stochastic.py +593 -0
  111. flextool/engine_polars/_subprocess_solve.py +1838 -0
  112. flextool/engine_polars/_timeline.py +1416 -0
  113. flextool/engine_polars/_vectorize.py +438 -0
  114. flextool/engine_polars/_warm.py +858 -0
  115. flextool/engine_polars/autoscale/__init__.py +107 -0
  116. flextool/engine_polars/autoscale/_config.py +218 -0
  117. flextool/engine_polars/autoscale/_layer2.py +1253 -0
  118. flextool/engine_polars/autoscale/_layer2_types.py +584 -0
  119. flextool/engine_polars/autoscale/_quantity_types.py +621 -0
  120. flextool/engine_polars/autoscale/_report.py +336 -0
  121. flextool/engine_polars/chain.py +259 -0
  122. flextool/engine_polars/input.py +6638 -0
  123. flextool/engine_polars/model.py +4754 -0
  124. flextool/env_check.py +388 -0
  125. flextool/export_to_tabular/__init__.py +5 -0
  126. flextool/export_to_tabular/db_reader.py +224 -0
  127. flextool/export_to_tabular/excel_writer.py +3559 -0
  128. flextool/export_to_tabular/export_settings.yaml +377 -0
  129. flextool/export_to_tabular/export_to_excel.py +227 -0
  130. flextool/export_to_tabular/formatting.py +543 -0
  131. flextool/export_to_tabular/sheet_config.py +876 -0
  132. flextool/gui/__init__.py +0 -0
  133. flextool/gui/__main__.py +118 -0
  134. flextool/gui/calibrate_commands.py +184 -0
  135. flextool/gui/calibrate_jobs.py +424 -0
  136. flextool/gui/check_tree.py +142 -0
  137. flextool/gui/cli_format.py +83 -0
  138. flextool/gui/config_parser.py +68 -0
  139. flextool/gui/data_models.py +362 -0
  140. flextool/gui/db_editor_integration.py +202 -0
  141. flextool/gui/db_version_check.py +269 -0
  142. flextool/gui/dialogs/__init__.py +0 -0
  143. flextool/gui/dialogs/add_dialog.py +1098 -0
  144. flextool/gui/dialogs/calibrate_dialog.py +1259 -0
  145. flextool/gui/dialogs/file_picker.py +473 -0
  146. flextool/gui/dialogs/group_picker.py +299 -0
  147. flextool/gui/dialogs/migration_consent_dialog.py +106 -0
  148. flextool/gui/dialogs/migration_progress_dialog.py +237 -0
  149. flextool/gui/dialogs/plot_dialog.py +459 -0
  150. flextool/gui/dialogs/plot_settings_picker.py +2184 -0
  151. flextool/gui/dialogs/project_dialog.py +426 -0
  152. flextool/gui/dialogs/update_dialog.py +212 -0
  153. flextool/gui/downsampling.py +88 -0
  154. flextool/gui/error_handling.py +50 -0
  155. flextool/gui/execution_manager.py +1715 -0
  156. flextool/gui/execution_window.py +1377 -0
  157. flextool/gui/hover_tooltip.py +111 -0
  158. flextool/gui/input_sources.py +730 -0
  159. flextool/gui/main_window.py +6181 -0
  160. flextool/gui/network_graph.py +215 -0
  161. flextool/gui/output_actions.py +393 -0
  162. flextool/gui/output_log_window.py +159 -0
  163. flextool/gui/platform_utils.py +421 -0
  164. flextool/gui/plot_cache.py +88 -0
  165. flextool/gui/plot_canvas.py +543 -0
  166. flextool/gui/plot_config_reader.py +272 -0
  167. flextool/gui/project_utils.py +100 -0
  168. flextool/gui/result_viewer.py +4394 -0
  169. flextool/gui/scenario_key.py +162 -0
  170. flextool/gui/scenario_lists.py +516 -0
  171. flextool/gui/settings_io.py +360 -0
  172. flextool/gui/solve_reader.py +103 -0
  173. flextool/gui/tree_reorder.py +88 -0
  174. flextool/gui/ui_metrics.py +420 -0
  175. flextool/input_derivation/__init__.py +281 -0
  176. flextool/input_derivation/_commodity_ladder.py +375 -0
  177. flextool/input_derivation/_commodity_ladder_sets.py +70 -0
  178. flextool/input_derivation/_dc_power_flow.py +377 -0
  179. flextool/input_derivation/_method_constants.py +77 -0
  180. flextool/input_derivation/_process_method.py +258 -0
  181. flextool/input_derivation/_specs.py +1026 -0
  182. flextool/input_derivation/_validators.py +321 -0
  183. flextool/lean_parquet.py +159 -0
  184. flextool/model_builder/__init__.py +5 -0
  185. flextool/model_builder/build_model.py +589 -0
  186. flextool/model_builder/encoding.py +67 -0
  187. flextool/model_builder/names.py +34 -0
  188. flextool/model_builder/profiles.py +129 -0
  189. flextool/plot_outputs/__init__.py +14 -0
  190. flextool/plot_outputs/axis_helpers.py +355 -0
  191. flextool/plot_outputs/color_template.py +888 -0
  192. flextool/plot_outputs/config.py +171 -0
  193. flextool/plot_outputs/format_helpers.py +345 -0
  194. flextool/plot_outputs/legend_helpers.py +143 -0
  195. flextool/plot_outputs/orchestrator.py +1141 -0
  196. flextool/plot_outputs/perf.py +37 -0
  197. flextool/plot_outputs/plan.py +1787 -0
  198. flextool/plot_outputs/plot_bars.py +1510 -0
  199. flextool/plot_outputs/plot_bars_detail.py +753 -0
  200. flextool/plot_outputs/plot_lines.py +951 -0
  201. flextool/plot_outputs/shared_manifest.py +564 -0
  202. flextool/plot_outputs/subplot_helpers.py +137 -0
  203. flextool/process_inputs/__init__.py +188 -0
  204. flextool/process_inputs/import_old_excel_input.json +4159 -0
  205. flextool/process_inputs/read_matpower.py +451 -0
  206. flextool/process_inputs/read_old_flextool.py +1288 -0
  207. flextool/process_inputs/read_self_describing_excel.py +1423 -0
  208. flextool/process_inputs/read_tabular_with_specification.py +1114 -0
  209. flextool/process_inputs/write_old_flextool_to_db.py +3077 -0
  210. flextool/process_inputs/write_self_describing_to_db.py +977 -0
  211. flextool/process_inputs/write_to_input_db.py +269 -0
  212. flextool/process_outputs/__init__.py +7 -0
  213. flextool/process_outputs/_annualize.py +55 -0
  214. flextool/process_outputs/_inmemory_helpers.py +292 -0
  215. flextool/process_outputs/_output_meta.py +672 -0
  216. flextool/process_outputs/calc_capacity_flows.py +107 -0
  217. flextool/process_outputs/calc_connections.py +136 -0
  218. flextool/process_outputs/calc_costs.py +260 -0
  219. flextool/process_outputs/calc_group_flows.py +192 -0
  220. flextool/process_outputs/calc_slacks.py +103 -0
  221. flextool/process_outputs/calc_storage_vre.py +160 -0
  222. flextool/process_outputs/drop_levels.py +208 -0
  223. flextool/process_outputs/handoff_writers.py +1315 -0
  224. flextool/process_outputs/out_ancillary.py +544 -0
  225. flextool/process_outputs/out_capacity.py +179 -0
  226. flextool/process_outputs/out_costs.py +334 -0
  227. flextool/process_outputs/out_flowgroup.py +189 -0
  228. flextool/process_outputs/out_flows.py +301 -0
  229. flextool/process_outputs/out_group.py +475 -0
  230. flextool/process_outputs/out_node.py +190 -0
  231. flextool/process_outputs/persist_realized_slice.py +601 -0
  232. flextool/process_outputs/process_results.py +24 -0
  233. flextool/process_outputs/read_highs_solution.py +2256 -0
  234. flextool/process_outputs/read_parameters.py +1799 -0
  235. flextool/process_outputs/read_sets.py +1095 -0
  236. flextool/process_outputs/read_variables.py +553 -0
  237. flextool/process_outputs/solve_order.py +81 -0
  238. flextool/process_outputs/spinedb_replay.py +412 -0
  239. flextool/process_outputs/union_realized_slice.py +224 -0
  240. flextool/process_outputs/write_outputs.py +1286 -0
  241. flextool/process_outputs/write_spinedb.py +1267 -0
  242. flextool/representative_periods/__init__.py +5 -0
  243. flextool/representative_periods/clustering.py +165 -0
  244. flextool/representative_periods/force_include.py +563 -0
  245. flextool/representative_periods/netload.py +365 -0
  246. flextool/representative_periods/netload_inputs.py +345 -0
  247. flextool/representative_periods/netload_iterate.py +722 -0
  248. flextool/representative_periods/preprocess.py +948 -0
  249. flextool/representative_periods/scenario_stack.py +195 -0
  250. flextool/representative_periods/weights.py +124 -0
  251. flextool/scenario_comparison/__init__.py +13 -0
  252. flextool/scenario_comparison/config_builder.py +158 -0
  253. flextool/scenario_comparison/constants.py +20 -0
  254. flextool/scenario_comparison/data_models.py +222 -0
  255. flextool/scenario_comparison/db_reader.py +399 -0
  256. flextool/scenario_comparison/dispatch_data.py +1002 -0
  257. flextool/scenario_comparison/dispatch_mappings.py +205 -0
  258. flextool/scenario_comparison/dispatch_plots.py +691 -0
  259. flextool/scenario_comparison/input_entity_colors.py +319 -0
  260. flextool/scenario_comparison/orchestrator.py +453 -0
  261. flextool/scenario_comparison/plan_union.py +244 -0
  262. flextool/scenario_comparison/plot_settings_seed.py +205 -0
  263. flextool/schemas/AXIS_CONTRACT.md +71 -0
  264. flextool/schemas/canonical_databases/howto_aggregate_output.json +6225 -0
  265. flextool/schemas/canonical_databases/howto_connections.json +5606 -0
  266. flextool/schemas/canonical_databases/howto_demand.json +5518 -0
  267. flextool/schemas/canonical_databases/howto_hydro_reservoir.json +6239 -0
  268. flextool/schemas/canonical_databases/howto_hydro_reservoir_with_pump.json +5933 -0
  269. flextool/schemas/canonical_databases/howto_non_sync_and_curtailment.json +5794 -0
  270. flextool/schemas/canonical_databases/howto_ramp_and_start_up.json +5707 -0
  271. flextool/schemas/canonical_databases/howto_stochastics.json +6032 -0
  272. flextool/schemas/canonical_databases/templates_examples.json +13532 -0
  273. flextool/schemas/canonical_databases/templates_time_settings_only.json +5340 -0
  274. flextool/schemas/comparison_settings_template.json +197 -0
  275. flextool/schemas/default_plot_settings.yaml +260 -0
  276. flextool/schemas/default_plots.yaml +2293 -0
  277. flextool/schemas/flextool_axis_contract.json +303 -0
  278. flextool/schemas/flextool_axis_contract.schema.json +247 -0
  279. flextool/schemas/old_flextool_import_template.json +4443 -0
  280. flextool/schemas/output_info_template.json +48 -0
  281. flextool/schemas/output_settings_template.json +256 -0
  282. flextool/schemas/pre_v26/flextool_template_constant_default.json +2105 -0
  283. flextool/schemas/pre_v26/flextool_template_default_optional_output.json +2152 -0
  284. flextool/schemas/pre_v26/flextool_template_default_value.json +2094 -0
  285. flextool/schemas/pre_v26/flextool_template_drop_down.json +2080 -0
  286. flextool/schemas/pre_v26/flextool_template_lifetime_method.json +1990 -0
  287. flextool/schemas/pre_v26/flextool_template_optional_outputs.json +2094 -0
  288. flextool/schemas/pre_v26/flextool_template_output_node_flows.json +2105 -0
  289. flextool/schemas/pre_v26/flextool_template_results_master.json +493 -0
  290. flextool/schemas/pre_v26/flextool_template_rolling_start_remove.json +2087 -0
  291. flextool/schemas/pre_v26/flextool_template_rolling_window.json +2059 -0
  292. flextool/schemas/pre_v26/flextool_template_storage_binding_defaults.json +46 -0
  293. flextool/schemas/pre_v26/flextool_template_v2.json +1990 -0
  294. flextool/schemas/pre_v26/flextool_template_v25.json +3864 -0
  295. flextool/schemas/spinedb_results_schema.json +581 -0
  296. flextool/schemas/spinedb_schema.json +4636 -0
  297. flextool/solver_config/copt.opt.template +18 -0
  298. flextool/solver_config/cplex.opt.template +25 -0
  299. flextool/solver_config/gurobi.opt.template +18 -0
  300. flextool/solver_config/highs.opt.template +18 -0
  301. flextool/solver_config/xpress.opt.template +26 -0
  302. flextool/spinedb_backend/__init__.py +26 -0
  303. flextool/spinedb_backend/_axis_enums.py +1119 -0
  304. flextool/spinedb_backend/_backend.py +1139 -0
  305. flextool/update_flextool/__init__.py +12 -0
  306. flextool/update_flextool/canonical_databases.py +251 -0
  307. flextool/update_flextool/db_migration.py +7108 -0
  308. flextool/update_flextool/ensure_settings_db.py +138 -0
  309. flextool/update_flextool/export_database.py +103 -0
  310. flextool/update_flextool/extend_tests_fixture.py +772 -0
  311. flextool/update_flextool/generate_canonical.py +274 -0
  312. flextool/update_flextool/initialize_database.py +42 -0
  313. flextool/update_flextool/install_info.py +225 -0
  314. flextool/update_flextool/self_update.py +464 -0
  315. flextool/update_flextool/sync_master_json_template.py +125 -0
  316. flextool/update_flextool/test_fixtures.py +187 -0
  317. flextool-4.0.0.dist-info/METADATA +217 -0
  318. flextool-4.0.0.dist-info/RECORD +322 -0
  319. flextool-4.0.0.dist-info/WHEEL +5 -0
  320. flextool-4.0.0.dist-info/entry_points.txt +17 -0
  321. flextool-4.0.0.dist-info/licenses/LICENSE.txt +19 -0
  322. 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"))