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