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,164 @@
1
+ """In-memory carrier of state passed between solves.
2
+
3
+ Home of the canonical :class:`SolveHandoff` dataclass — the typed
4
+ record of "what one solve produced that the next solve(s) consume".
5
+ Built post-solve by :func:`flextool.engine_polars.input.build_handoff_from_solution`
6
+ directly from the polar_high ``Solution`` object; consumed by the next
7
+ sub-solve via the orchestrator's iteration-start translator
8
+ (:func:`_provider_translators.translate_handoff_to_provider`) which
9
+ fans each field into the Provider under a ``handoff/<field>`` key.
10
+
11
+ Phase 3 of ``specs/provider_consolidation.md`` retired the legacy
12
+ ``capture_post_solve()`` disk-read constructor — the cascade was
13
+ already using ``build_handoff_from_solution`` exclusively; the
14
+ capture-from-disk function was dead code and has been removed,
15
+ together with the three carrier fields it was the only populator
16
+ for (``fix_storage_timesteps``, ``ed_history_realized_first``,
17
+ ``edd_history``). Consumers of those fields already fell through
18
+ to Provider/CSV reads when the carrier was ``None`` (the universal
19
+ state in the cascade path).
20
+
21
+ Carrier schemas (each is the on-disk equivalent's columns, renamed
22
+ for in-memory uniformity — string keys + ``value`` for single-value
23
+ carriers, named metric columns for multi-value):
24
+
25
+ realized_invest [entity, period, value]
26
+ realized_existing [entity, period, value]
27
+ divest_cumulative [entity, value]
28
+ roll_end_state [node, value]
29
+ fix_storage_quantity [node, period, step, p_fix_storage_quantity]
30
+ fix_storage_price [node, period, step, p_fix_storage_price]
31
+ fix_storage_usage [node, period, step, p_fix_storage_usage]
32
+ cumulative_co2 [group, period, value]
33
+ cumulative_commodity [commodity, tier, period, p_ladder_cum_realized_mwh]
34
+ cum_sim_hours [period, p_ladder_cum_sim_hours]
35
+
36
+ Phase 4.1a moved ``cumulative_commodity`` and ``cum_sim_hours`` to their
37
+ canonical column names (matching the ``solve_data/`` Provider key
38
+ schemas) so the iteration-start handoff translator can route the
39
+ frames straight through to ``handoff/cumulative_commodity`` /
40
+ ``handoff/cum_sim_hours`` without a per-iteration rename.
41
+
42
+ (Δ.1 — ``periods_already_emitted`` was previously listed here; it
43
+ moved to ``_output_writer.OutputWriterState`` since it gates writer-
44
+ side emission and isn't a true solver-handoff carrier.)
45
+
46
+ The three ``fix_storage_*`` narrow carriers replaced the legacy
47
+ single wide ``fix_storage`` field (retired in Phase 4.1l). Each
48
+ metric is independent, so a separate per-metric carrier matches the
49
+ producer/consumer reality without NULL-padding columns.
50
+
51
+ ``realized_invest`` and ``realized_existing`` together cover the two
52
+ columns of ``solve_data/p_entity_period_existing_capacity.csv``:
53
+ ``realized_invest`` is what was *built this solve*, ``realized_existing``
54
+ is the resolved existing-capacity history (pre-existing decay +
55
+ divest enter this column and aren't reconstructible from
56
+ ``realized_invest`` alone).
57
+ """
58
+ from __future__ import annotations
59
+
60
+ from dataclasses import dataclass
61
+
62
+ import polars as pl
63
+
64
+
65
+
66
+ @dataclass
67
+ class SolveHandoff:
68
+ """Per-solve output → dependent-solve input carrier.
69
+
70
+ Each field is a polars DataFrame in the schema documented in this
71
+ module's docstring, or ``None`` when that carrier kind isn't active
72
+ for this handoff. Solves are identified by full solve name; the
73
+ handoff represents the *output* of one solve becoming the *input*
74
+ to its dependent (child / next-roll) solves.
75
+ """
76
+
77
+ # Realized invest *built this solve* in absolute units (post-unitsize).
78
+ # Producer: any solve with v_invest > 0.
79
+ # Consumer: subsequent solves' preprocessing.
80
+ # File equivalent: ``p_entity_period_invested_capacity`` column of
81
+ # solve_data/p_entity_period_existing_capacity.csv.
82
+ realized_invest: pl.DataFrame | None = None
83
+
84
+ # Realized existing-capacity history per (entity, period) — captures
85
+ # pre-existing decay + divest that ``realized_invest`` doesn't.
86
+ # File equivalent: ``p_entity_period_existing_capacity`` column of
87
+ # solve_data/p_entity_period_existing_capacity.csv.
88
+ realized_existing: pl.DataFrame | None = None
89
+
90
+ # Cumulative divested capacity per entity (scalar, not per-period).
91
+ # Carries pre-existing decay forward across solves.
92
+ # File equivalent: solve_data/p_entity_divested.csv.
93
+ divest_cumulative: pl.DataFrame | None = None
94
+
95
+ # End-of-roll storage state for ``bind_forward_only`` carry-over.
96
+ # Producer: prior roll's v_state at its last (d, t).
97
+ # Consumer: next roll's nodeBalance_eq first-timestep term.
98
+ # File equivalent: solve_data/p_roll_continue_state.csv.
99
+ roll_end_state: pl.DataFrame | None = None
100
+
101
+ # Upward feedback carrier: nested dispatch sub-solve's realized
102
+ # end-of-horizon v_state routed UPWARD to its parent storage
103
+ # solve's next roll. When a dispatch sub-solve completes, the
104
+ # parent storage solve's next roll prefers this realized state
105
+ # over its own previously-predicted state — closing the loop
106
+ # between the lower-information storage plan and the higher-
107
+ # information dispatch result. Always-on for any storage→dispatch
108
+ # nesting; no opt-in flag (per specs/feature_fixes.md §1).
109
+ # Schema mirrors ``roll_end_state``: ``[node, value]``.
110
+ # Producer: dispatch sub-solve's roll_end_state (copy at handoff
111
+ # capture; no separate v_state extraction needed).
112
+ # Consumer: parent storage's continuation-roll
113
+ # ``p_roll_continue_state`` (preferred over sequential prior when
114
+ # available).
115
+ # No file equivalent (in-memory only).
116
+ upward_roll_end_state: pl.DataFrame | None = None
117
+
118
+ # Storage quota imposed by parent solve on child solve — narrow
119
+ # per-metric carriers in canonical column schema. The trio is
120
+ # independent (parent may set quantity-only, price-only, or any
121
+ # combination); each metric travels in its own field.
122
+ # Producer: parent solve's v_state + cost duals.
123
+ # Consumer: child's fix_storage_* constraints.
124
+ # File equivalents: solve_data/fix_storage_{quantity,price,usage}.csv
125
+ # Each schema: ``[node, period, step, p_fix_storage_<metric>]``.
126
+ fix_storage_quantity: pl.DataFrame | None = None
127
+ fix_storage_price: pl.DataFrame | None = None
128
+ fix_storage_usage: pl.DataFrame | None = None
129
+
130
+ # Running CO2 totals carried across rolls for cumulative-cap constraint.
131
+ # File equivalent: solve_data/co2_cum_realized_tonnes.csv.
132
+ cumulative_co2: pl.DataFrame | None = None
133
+
134
+ # Running per-tier commodity consumption for cumulative ladder pricing.
135
+ # File equivalent: solve_data/commodity_ladder_cumulative.csv (per-tier mwh).
136
+ cumulative_commodity: pl.DataFrame | None = None
137
+
138
+ # Running simulated-hour total per period. Independent 1-D
139
+ # carrier shared by ladder + CO2-cap constraints.
140
+ # File equivalent: solve_data/ladder_cum_sim_hours.csv.
141
+ cum_sim_hours: pl.DataFrame | None = None
142
+
143
+ _FIELDS = (
144
+ "realized_invest", "realized_existing", "divest_cumulative",
145
+ "roll_end_state", "upward_roll_end_state",
146
+ "fix_storage_quantity", "fix_storage_price", "fix_storage_usage",
147
+ "cumulative_co2", "cumulative_commodity", "cum_sim_hours",
148
+ )
149
+
150
+ def is_empty(self) -> bool:
151
+ """True when no carrier is populated."""
152
+ return all(getattr(self, f) is None for f in self._FIELDS)
153
+
154
+
155
+ # Phase 4.1i — ``write_fix_storage_files_from_handoff`` was retired
156
+ # once all readers of ``solve_data/fix_storage_*`` migrated to the
157
+ # per-metric ``handoff/*`` Provider keys seeded by the
158
+ # iteration-start translator (Phases 4.1f–4.1h). The wide → narrow
159
+ # CSV fan-out has no consumers.
160
+
161
+
162
+ __all__ = [
163
+ "SolveHandoff",
164
+ ]
@@ -0,0 +1,232 @@
1
+ """Solve-orchestration state types.
2
+
3
+ Foundation module: every downstream orchestration module (timeline,
4
+ recursive solve, stochastic, orchestration loop) needs the exception
5
+ classes, ``ActiveTimeEntry`` namedtuple, ``SolveResult`` dataclass and
6
+ a slim ``RunnerState`` carrier. Runs natively on HiGHS via
7
+ ``polar_high``; per-CLI flags live on the CLI wrapper rather than in
8
+ shared state.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path
15
+ from typing import Callable, NamedTuple, TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ import polars as pl
19
+
20
+ from flextool.cli._timing import TimingRecorder
21
+ from flextool.engine_polars._solve_config import SolveConfig
22
+ from flextool.engine_polars._solve_handoff import SolveHandoff
23
+
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Exception types
27
+ # ---------------------------------------------------------------------------
28
+
29
+
30
+ class FlexToolError(Exception):
31
+ """Base exception for FlexTool runner errors."""
32
+
33
+
34
+ class FlexToolConfigError(FlexToolError):
35
+ """Raised for configuration / input data errors."""
36
+
37
+
38
+ class FlexToolSolveError(FlexToolError):
39
+ """Raised for solver execution errors."""
40
+
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # Lightweight value types
44
+ # ---------------------------------------------------------------------------
45
+
46
+
47
+ class ActiveTimeEntry(NamedTuple):
48
+ """A single timestep in an active time list.
49
+
50
+ Backwards-compatible with the previous ``(timestep, index, duration)``
51
+ tuples — ``entry[0]`` still works, but ``entry.timestep`` is preferred.
52
+ """
53
+
54
+ timestep: str
55
+ index: int
56
+ duration: str
57
+
58
+
59
+ @dataclass
60
+ class SolveResult:
61
+ """Result container for the recursive solve structure builder.
62
+
63
+ Populated by the recursive solve builder (Γ.8.C) and consumed by the
64
+ orchestration loop (Γ.8.D).
65
+ """
66
+
67
+ solves: list = field(default_factory=list)
68
+ complete_solves: dict = field(default_factory=dict)
69
+ active_time_lists: dict = field(default_factory=dict)
70
+ fix_storage_time_lists: dict = field(default_factory=dict)
71
+ realized_time_lists: dict = field(default_factory=dict)
72
+ parent_roll_lists: dict = field(default_factory=dict)
73
+
74
+
75
+ @dataclass
76
+ class PathConfig:
77
+ """Directory layout for a FlexTool run.
78
+
79
+ Carries the work folder plus the optional ancillary directories
80
+ :class:`FlexToolRunner` resolves (a package data dir, a
81
+ CLI-overrideable ``solver_config/`` for ``highs.opt`` and a project
82
+ root). Native engine_polars callers only populate ``work_folder``;
83
+ the rest are ``None``.
84
+ """
85
+
86
+ work_folder: Path
87
+ output_path: Path | None = None
88
+ flextool_dir: Path | None = None
89
+ solver_config_dir: Path | None = None
90
+ root_dir: Path | None = None
91
+
92
+
93
+ @dataclass
94
+ class RunnerState:
95
+ """Cross-cutting state for a native polar_high solve run.
96
+
97
+ Only the fields needed by Γ.8.A are populated. Timeline + handoff
98
+ fields will be filled in Γ.8.B / Γ.8.D as those modules land.
99
+ """
100
+
101
+ paths: PathConfig
102
+ solve: "SolveConfig"
103
+ logger: logging.Logger
104
+ # Filled by Γ.8.B (timeline module). Typed as ``object`` so that
105
+ # importing :class:`RunnerState` doesn't pull a non-existent module
106
+ # into the import graph.
107
+ timeline: object | None = None
108
+ # Agent 8 (LP-scaling): opt-in flag — when True the Python
109
+ # ScaleAnalyzer's recommendations are auto-applied. Batch C.10
110
+ # removed the DB-stored ``use_row_scaling`` knob; the per-solve
111
+ # row-scaling toggle is now driven entirely by --scaling CLI +
112
+ # this auto_scale flag (or FLEXTOOL_FORCE_ROW_SCALING test hook).
113
+ # Always-False in the default path preserves pre-Agent-8 behaviour.
114
+ auto_scale: bool = False
115
+ # Roll-loop scratch — set by the orchestration loop just before
116
+ # ``solver.run`` so per-roll diagnostics / handoff capture can find
117
+ # the correct row. ``None`` outside an active solve iteration.
118
+ current_roll_index: int | None = None
119
+ # Agent 18c (LP-scaling): the orchestration loop sets this to the
120
+ # ``ScaleTable`` for the currently-active solve just before calling
121
+ # ``solver.run``. ``_run_highs`` uses it to update bound-scaling
122
+ # diagnostics in the right cache entry even when the roll name
123
+ # differs from the parent (complete) solve name passed to the
124
+ # solver. ``None`` outside an active solve iteration.
125
+ current_scale_solve_name: str | None = None
126
+ # Name of the most-recent solve whose post-solve hook deposited a
127
+ # ``SolveHandoff`` into ``handoffs``. Set by ``orchestration.run_model``
128
+ # after each capture; consulted by post-solve writers (e.g. the
129
+ # cumulative-handoff writers in ``solver_runner._run_highs``) to
130
+ # source prior-roll state from the in-memory dict instead of disk.
131
+ # ``None`` outside an active solve loop and on the first solve of
132
+ # any loop.
133
+ last_captured_solve: str | None = None
134
+ # In-memory solve-to-solve handoff. ``None`` keeps file-based
135
+ # behaviour; opt-in by setting ``state.handoffs = {}``. See
136
+ # ``audit/handoff_csv_retirement.md`` for the migration plan.
137
+ handoffs: "dict[str, SolveHandoff] | None" = None
138
+ # Phase 5b — external override provider. When set, the runner
139
+ # invokes this callable at iteration start (after the sequential
140
+ # + parent handoff translators) and fans the returned dict into
141
+ # the ``override/*`` Provider namespace via
142
+ # :func:`flextool.engine_polars._provider_translators.translate_overrides_to_provider`.
143
+ # The callable is owned by external code wrapping the runner
144
+ # (e.g. file-watch, ZeroMQ bridge); ``None`` means no overrides.
145
+ override_provider: Callable[[], "dict[str, pl.DataFrame]"] | None = None
146
+ # Per-CLI-invocation phase timing recorder. The CLI constructs one
147
+ # very early in ``cmd_run_flextool.main`` and assigns it onto
148
+ # ``state.timing_recorder``; callers using :class:`FlexToolRunner`
149
+ # directly (without going through the CLI) bootstrap their own in
150
+ # ``FlexToolRunner.__init__``. Always non-None inside an active run.
151
+ timing_recorder: "TimingRecorder | None" = None
152
+ # HiGHS thread count (CLI override; solver_runner defaults to 4 when None).
153
+ highs_threads: int | None = None
154
+ # Gates ``data.dump_csvs`` from inside the cascade — set by
155
+ # ``run_orchestration`` from its ``csv_dump`` argument.
156
+ csv_dump: bool = False
157
+ # Seeded cascade-input Provider — set by ``write_input`` / by the
158
+ # orchestration entry point so per-sub-solve Providers can clone
159
+ # the ``input/<class>`` frames. Typed as ``object`` to avoid
160
+ # pulling :class:`FlexDataProvider` into the import graph.
161
+ cascade_input_provider: object | None = None
162
+ # Per-sub-solve Provider currently driving the cascade. Set by
163
+ # ``_native_run_model`` immediately before each ``solver.run``
164
+ # invocation; consumed by post-solve writers that need a Provider
165
+ # handle but were called without one. ``None`` outside an active
166
+ # solve iteration.
167
+ current_provider: object | None = None
168
+ # Per-level Provider cache — keyed by :func:`compute_level_key`.
169
+ # The orchestration loop populates this so sub-solves at the same
170
+ # "level" (matching LP matrix shape) share a :class:`FlexDataProvider`.
171
+ # Typed as ``dict | None``; ``None`` means "not yet initialised".
172
+ _level_providers: dict | None = None
173
+
174
+
175
+ # ---------------------------------------------------------------------------
176
+ # Per-level Provider — level-key helper (Design A, step A1).
177
+ # ---------------------------------------------------------------------------
178
+
179
+
180
+ def compute_level_key(
181
+ *,
182
+ solve_name: str,
183
+ complete_solve_name: str,
184
+ solve_config,
185
+ timeline_config,
186
+ ) -> tuple:
187
+ """Compute a cheap level identifier for a sub-solve.
188
+
189
+ Two sub-solves with the same level_key share LP matrix shape and
190
+ can share a :class:`FlexDataProvider` (per the user's per-level
191
+ Provider intent). Two sub-solves with different keys must not
192
+ share.
193
+
194
+ Composition (in order):
195
+
196
+ 1. Tuple of timesets used by ``complete_solve_name``
197
+ (``solve_config.timesets_used_by_solves[complete_solve_name]``,
198
+ sorted for determinism).
199
+ 2. ``timeline_config.new_step_durations.get(complete_solve_name)``
200
+ — the explicit step-size override (1h, 3h, …). ``None`` when
201
+ absent.
202
+ 3. ``solve_config.rolling_times.get(complete_solve_name)`` — the
203
+ rolling-window triple ``[jump, horizon, duration]``, coerced to
204
+ a tuple. ``None`` when not rolling.
205
+ 4. ``solve_config.solve_modes.get(complete_solve_name)``.
206
+
207
+ Returns a hashable tuple. Same key on consecutive iterations
208
+ means "same level — reuse Provider"; different key means "level
209
+ transition — fresh Provider".
210
+ """
211
+ timesets = solve_config.timesets_used_by_solves.get(
212
+ complete_solve_name, ()
213
+ )
214
+ timesets = tuple(sorted(timesets)) if timesets else ()
215
+ step_dur = timeline_config.new_step_durations.get(complete_solve_name)
216
+ rolling = solve_config.rolling_times.get(complete_solve_name)
217
+ if rolling is not None:
218
+ rolling = tuple(rolling)
219
+ mode = solve_config.solve_modes.get(complete_solve_name)
220
+ return (timesets, step_dur, rolling, mode)
221
+
222
+
223
+ __all__ = [
224
+ "FlexToolError",
225
+ "FlexToolConfigError",
226
+ "FlexToolSolveError",
227
+ "ActiveTimeEntry",
228
+ "SolveResult",
229
+ "PathConfig",
230
+ "RunnerState",
231
+ "compute_level_key",
232
+ ]
@@ -0,0 +1,36 @@
1
+ """SolverRunner shell — base class for the native cascade's solver subclasses.
2
+
3
+ The cascade's ``_PolarHighCascadeSolver`` / ``_NoOpSolver`` in
4
+ :mod:`flextool.engine_polars._orchestration` extend :class:`SolverRunner`
5
+ and override ``run`` to drive the polar-high LP build/solve in-process.
6
+ Direct instantiation + ``run()`` is not supported.
7
+ """
8
+ from __future__ import annotations
9
+
10
+
11
+ class SolverRunner:
12
+ """Minimal shell that the native cascade's solver subclasses extend.
13
+
14
+ ``__init__`` stores ``state`` and ``logger`` for the subclasses,
15
+ matching the contract those subclasses already rely on via
16
+ ``super().__init__(runner_state)``. ``run`` is unimplemented by
17
+ design — the cascade subclasses override it.
18
+ """
19
+
20
+ def __init__(self, state) -> None:
21
+ self.state = state
22
+ self.logger = state.logger
23
+
24
+ def run(self, current_solve: str) -> int: # noqa: ARG002
25
+ """Unimplemented — subclasses override this method.
26
+
27
+ Direct invocation on the base class is not supported.
28
+ """
29
+ raise NotImplementedError(
30
+ "SolverRunner.run is unimplemented on the base class. "
31
+ "The cascade subclasses this class and overrides run(); "
32
+ "direct invocation is not supported."
33
+ )
34
+
35
+
36
+ __all__ = ["SolverRunner"]