flextool 4.0.1__tar.gz → 4.0.4__tar.gz

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 (401) hide show
  1. {flextool-4.0.1 → flextool-4.0.4}/PKG-INFO +2 -2
  2. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_derived_params.py +73 -6
  3. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_solve_writers.py +7 -2
  4. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_solve_context.py +54 -0
  5. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_stochastic.py +58 -13
  6. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_timeline.py +85 -71
  7. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/calc_storage_vre.py +22 -1
  8. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/out_costs.py +12 -2
  9. {flextool-4.0.1 → flextool-4.0.4}/flextool/spinedb_backend/_axis_enums.py +44 -0
  10. {flextool-4.0.1 → flextool-4.0.4}/flextool/update_flextool/self_update.py +3 -4
  11. {flextool-4.0.1 → flextool-4.0.4}/flextool.egg-info/PKG-INFO +2 -2
  12. {flextool-4.0.1 → flextool-4.0.4}/flextool.egg-info/requires.txt +1 -1
  13. {flextool-4.0.1 → flextool-4.0.4}/pyproject.toml +10 -5
  14. {flextool-4.0.1 → flextool-4.0.4}/LICENSE.txt +0 -0
  15. {flextool-4.0.1 → flextool-4.0.4}/README.md +0 -0
  16. {flextool-4.0.1 → flextool-4.0.4}/flextool/__init__.py +0 -0
  17. {flextool-4.0.1 → flextool-4.0.4}/flextool/_mem_sampler.py +0 -0
  18. {flextool-4.0.1 → flextool-4.0.4}/flextool/_resources.py +0 -0
  19. {flextool-4.0.1 → flextool-4.0.4}/flextool/calibrate/__init__.py +0 -0
  20. {flextool-4.0.1 → flextool-4.0.4}/flextool/calibrate/__main__.py +0 -0
  21. {flextool-4.0.1 → flextool-4.0.4}/flextool/calibrate/_cli.py +0 -0
  22. {flextool-4.0.1 → flextool-4.0.4}/flextool/calibrate/_db_alt.py +0 -0
  23. {flextool-4.0.1 → flextool-4.0.4}/flextool/calibrate/_final_outputs.py +0 -0
  24. {flextool-4.0.1 → flextool-4.0.4}/flextool/calibrate/_guard.py +0 -0
  25. {flextool-4.0.1 → flextool-4.0.4}/flextool/calibrate/_loop.py +0 -0
  26. {flextool-4.0.1 → flextool-4.0.4}/flextool/calibrate/_readers.py +0 -0
  27. {flextool-4.0.1 → flextool-4.0.4}/flextool/calibrate/_report.py +0 -0
  28. {flextool-4.0.1 → flextool-4.0.4}/flextool/calibrate/_sizing.py +0 -0
  29. {flextool-4.0.1 → flextool-4.0.4}/flextool/calibrate/_solve.py +0 -0
  30. {flextool-4.0.1 → flextool-4.0.4}/flextool/calibrate/_solve_status.py +0 -0
  31. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/__init__.py +0 -0
  32. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/_console.py +0 -0
  33. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/_timing.py +0 -0
  34. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_execute_flextool_workflow.py +0 -0
  35. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_export_to_tabular.py +0 -0
  36. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_import_sensitivities.py +0 -0
  37. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_migrate_database.py +0 -0
  38. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_open_results_db.py +0 -0
  39. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_read_matpower.py +0 -0
  40. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_read_old_flextool.py +0 -0
  41. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_read_self_describing_tabular_input.py +0 -0
  42. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_read_tabular_input.py +0 -0
  43. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_run_flextool.py +0 -0
  44. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_scenario_results.py +0 -0
  45. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_solve_mps.py +0 -0
  46. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_update_flextool.py +0 -0
  47. {flextool-4.0.1 → flextool-4.0.4}/flextool/cli/cmd_write_outputs.py +0 -0
  48. {flextool-4.0.1 → flextool-4.0.4}/flextool/common_utils/__init__.py +0 -0
  49. {flextool-4.0.1 → flextool-4.0.4}/flextool/common_utils/plot_mem_shape.py +0 -0
  50. {flextool-4.0.1 → flextool-4.0.4}/flextool/common_utils/precision.py +0 -0
  51. {flextool-4.0.1 → flextool-4.0.4}/flextool/decomposition/__init__.py +0 -0
  52. {flextool-4.0.1 → flextool-4.0.4}/flextool/decomposition/region_decomposition.py +0 -0
  53. {flextool-4.0.1 → flextool-4.0.4}/flextool/decomposition/region_filter.py +0 -0
  54. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/__init__.py +0 -0
  55. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_axis_enums.py +0 -0
  56. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_benders.py +0 -0
  57. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_block_layout.py +0 -0
  58. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_blocks.py +0 -0
  59. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_commodity_ladder.py +0 -0
  60. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_cumulative_invest.py +0 -0
  61. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_db_loader.py +0 -0
  62. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_db_reader.py +0 -0
  63. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_dc_power_flow.py +0 -0
  64. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_delay.py +0 -0
  65. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_derived_arithmetic.py +0 -0
  66. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_derived_block.py +0 -0
  67. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_derived_branch.py +0 -0
  68. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_derived_existing.py +0 -0
  69. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_derived_npv.py +0 -0
  70. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_derived_profile.py +0 -0
  71. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_derived_walks.py +0 -0
  72. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_determinism.py +0 -0
  73. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_direct_params.py +0 -0
  74. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_dump_csvs.py +0 -0
  75. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_arc_unions.py +0 -0
  76. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_calc_params.py +0 -0
  77. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_chain_params.py +0 -0
  78. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_co2_accumulators.py +0 -0
  79. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_dispatchers.py +0 -0
  80. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_energy_margin.py +0 -0
  81. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_energy_margin_adder.py +0 -0
  82. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_entity_annual.py +0 -0
  83. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_inflow_scaling.py +0 -0
  84. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_leaf_sets.py +0 -0
  85. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_lp_scaling.py +0 -0
  86. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_mid_sets.py +0 -0
  87. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_pdt_params.py +0 -0
  88. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_per_solve.py +0 -0
  89. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_period_calc.py +0 -0
  90. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_period_params.py +0 -0
  91. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_provider_io.py +0 -0
  92. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_reserve.py +0 -0
  93. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_emit_solve_time.py +0 -0
  94. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_flex_data_accumulator.py +0 -0
  95. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_flex_data_provider.py +0 -0
  96. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_group_slack.py +0 -0
  97. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_inmemory_reader.py +0 -0
  98. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_input_source.py +0 -0
  99. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_invest_seeds.py +0 -0
  100. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_native_input_writer.py +0 -0
  101. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_native_run_model.py +0 -0
  102. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_orchestration.py +0 -0
  103. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_output_writer.py +0 -0
  104. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_param_shapes.py +0 -0
  105. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_parquet_bundle.py +0 -0
  106. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_pdt_join.py +0 -0
  107. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_pdt_lookup.py +0 -0
  108. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_per_solve_sets.py +0 -0
  109. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_projection_params.py +0 -0
  110. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_provider_keys.py +0 -0
  111. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_provider_translators.py +0 -0
  112. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_recursive_solve.py +0 -0
  113. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_region_filter.py +0 -0
  114. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_reserve.py +0 -0
  115. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_solve_acceptance.py +0 -0
  116. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_solve_config.py +0 -0
  117. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_solve_handoff.py +0 -0
  118. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_solve_state.py +0 -0
  119. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_solver_base.py +0 -0
  120. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_solver_dispatch.py +0 -0
  121. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_spinedb_reader.py +0 -0
  122. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_subprocess_solve.py +0 -0
  123. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_vectorize.py +0 -0
  124. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/_warm.py +0 -0
  125. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/autoscale/__init__.py +0 -0
  126. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/autoscale/_config.py +0 -0
  127. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/autoscale/_layer2.py +0 -0
  128. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/autoscale/_layer2_types.py +0 -0
  129. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/autoscale/_quantity_types.py +0 -0
  130. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/autoscale/_report.py +0 -0
  131. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/chain.py +0 -0
  132. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/input.py +0 -0
  133. {flextool-4.0.1 → flextool-4.0.4}/flextool/engine_polars/model.py +0 -0
  134. {flextool-4.0.1 → flextool-4.0.4}/flextool/env_check.py +0 -0
  135. {flextool-4.0.1 → flextool-4.0.4}/flextool/export_to_tabular/__init__.py +0 -0
  136. {flextool-4.0.1 → flextool-4.0.4}/flextool/export_to_tabular/db_reader.py +0 -0
  137. {flextool-4.0.1 → flextool-4.0.4}/flextool/export_to_tabular/excel_writer.py +0 -0
  138. {flextool-4.0.1 → flextool-4.0.4}/flextool/export_to_tabular/export_settings.yaml +0 -0
  139. {flextool-4.0.1 → flextool-4.0.4}/flextool/export_to_tabular/export_to_excel.py +0 -0
  140. {flextool-4.0.1 → flextool-4.0.4}/flextool/export_to_tabular/formatting.py +0 -0
  141. {flextool-4.0.1 → flextool-4.0.4}/flextool/export_to_tabular/sheet_config.py +0 -0
  142. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/__init__.py +0 -0
  143. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/__main__.py +0 -0
  144. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/calibrate_commands.py +0 -0
  145. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/calibrate_jobs.py +0 -0
  146. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/check_tree.py +0 -0
  147. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/cli_format.py +0 -0
  148. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/config_parser.py +0 -0
  149. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/data_models.py +0 -0
  150. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/db_editor_integration.py +0 -0
  151. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/db_version_check.py +0 -0
  152. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/dialogs/__init__.py +0 -0
  153. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/dialogs/add_dialog.py +0 -0
  154. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/dialogs/calibrate_dialog.py +0 -0
  155. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/dialogs/file_picker.py +0 -0
  156. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/dialogs/group_picker.py +0 -0
  157. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/dialogs/migration_consent_dialog.py +0 -0
  158. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/dialogs/migration_progress_dialog.py +0 -0
  159. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/dialogs/plot_dialog.py +0 -0
  160. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/dialogs/plot_settings_picker.py +0 -0
  161. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/dialogs/project_dialog.py +0 -0
  162. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/dialogs/update_dialog.py +0 -0
  163. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/downsampling.py +0 -0
  164. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/error_handling.py +0 -0
  165. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/execution_manager.py +0 -0
  166. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/execution_window.py +0 -0
  167. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/hover_tooltip.py +0 -0
  168. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/input_sources.py +0 -0
  169. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/main_window.py +0 -0
  170. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/network_graph.py +0 -0
  171. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/output_actions.py +0 -0
  172. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/output_log_window.py +0 -0
  173. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/platform_utils.py +0 -0
  174. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/plot_cache.py +0 -0
  175. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/plot_canvas.py +0 -0
  176. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/plot_config_reader.py +0 -0
  177. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/project_utils.py +0 -0
  178. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/result_viewer.py +0 -0
  179. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/scenario_key.py +0 -0
  180. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/scenario_lists.py +0 -0
  181. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/settings_io.py +0 -0
  182. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/solve_reader.py +0 -0
  183. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/tree_reorder.py +0 -0
  184. {flextool-4.0.1 → flextool-4.0.4}/flextool/gui/ui_metrics.py +0 -0
  185. {flextool-4.0.1 → flextool-4.0.4}/flextool/input_derivation/__init__.py +0 -0
  186. {flextool-4.0.1 → flextool-4.0.4}/flextool/input_derivation/_commodity_ladder.py +0 -0
  187. {flextool-4.0.1 → flextool-4.0.4}/flextool/input_derivation/_commodity_ladder_sets.py +0 -0
  188. {flextool-4.0.1 → flextool-4.0.4}/flextool/input_derivation/_dc_power_flow.py +0 -0
  189. {flextool-4.0.1 → flextool-4.0.4}/flextool/input_derivation/_method_constants.py +0 -0
  190. {flextool-4.0.1 → flextool-4.0.4}/flextool/input_derivation/_process_method.py +0 -0
  191. {flextool-4.0.1 → flextool-4.0.4}/flextool/input_derivation/_specs.py +0 -0
  192. {flextool-4.0.1 → flextool-4.0.4}/flextool/input_derivation/_validators.py +0 -0
  193. {flextool-4.0.1 → flextool-4.0.4}/flextool/lean_parquet.py +0 -0
  194. {flextool-4.0.1 → flextool-4.0.4}/flextool/model_builder/__init__.py +0 -0
  195. {flextool-4.0.1 → flextool-4.0.4}/flextool/model_builder/build_model.py +0 -0
  196. {flextool-4.0.1 → flextool-4.0.4}/flextool/model_builder/encoding.py +0 -0
  197. {flextool-4.0.1 → flextool-4.0.4}/flextool/model_builder/names.py +0 -0
  198. {flextool-4.0.1 → flextool-4.0.4}/flextool/model_builder/profiles.py +0 -0
  199. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/__init__.py +0 -0
  200. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/axis_helpers.py +0 -0
  201. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/color_template.py +0 -0
  202. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/config.py +0 -0
  203. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/format_helpers.py +0 -0
  204. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/legend_helpers.py +0 -0
  205. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/orchestrator.py +0 -0
  206. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/perf.py +0 -0
  207. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/plan.py +0 -0
  208. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/plot_bars.py +0 -0
  209. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/plot_bars_detail.py +0 -0
  210. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/plot_lines.py +0 -0
  211. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/shared_manifest.py +0 -0
  212. {flextool-4.0.1 → flextool-4.0.4}/flextool/plot_outputs/subplot_helpers.py +0 -0
  213. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_inputs/__init__.py +0 -0
  214. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_inputs/import_old_excel_input.json +0 -0
  215. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_inputs/read_matpower.py +0 -0
  216. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_inputs/read_old_flextool.py +0 -0
  217. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_inputs/read_self_describing_excel.py +0 -0
  218. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_inputs/read_tabular_with_specification.py +0 -0
  219. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_inputs/write_old_flextool_to_db.py +0 -0
  220. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_inputs/write_self_describing_to_db.py +0 -0
  221. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_inputs/write_to_input_db.py +0 -0
  222. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/__init__.py +0 -0
  223. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/_annualize.py +0 -0
  224. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/_inmemory_helpers.py +0 -0
  225. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/_output_meta.py +0 -0
  226. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/calc_capacity_flows.py +0 -0
  227. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/calc_connections.py +0 -0
  228. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/calc_costs.py +0 -0
  229. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/calc_group_flows.py +0 -0
  230. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/calc_slacks.py +0 -0
  231. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/drop_levels.py +0 -0
  232. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/handoff_writers.py +0 -0
  233. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/out_ancillary.py +0 -0
  234. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/out_capacity.py +0 -0
  235. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/out_flowgroup.py +0 -0
  236. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/out_flows.py +0 -0
  237. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/out_group.py +0 -0
  238. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/out_node.py +0 -0
  239. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/persist_realized_slice.py +0 -0
  240. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/process_results.py +0 -0
  241. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/read_highs_solution.py +0 -0
  242. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/read_parameters.py +0 -0
  243. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/read_sets.py +0 -0
  244. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/read_variables.py +0 -0
  245. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/solve_order.py +0 -0
  246. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/spinedb_replay.py +0 -0
  247. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/union_realized_slice.py +0 -0
  248. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/write_outputs.py +0 -0
  249. {flextool-4.0.1 → flextool-4.0.4}/flextool/process_outputs/write_spinedb.py +0 -0
  250. {flextool-4.0.1 → flextool-4.0.4}/flextool/representative_periods/__init__.py +0 -0
  251. {flextool-4.0.1 → flextool-4.0.4}/flextool/representative_periods/clustering.py +0 -0
  252. {flextool-4.0.1 → flextool-4.0.4}/flextool/representative_periods/force_include.py +0 -0
  253. {flextool-4.0.1 → flextool-4.0.4}/flextool/representative_periods/netload.py +0 -0
  254. {flextool-4.0.1 → flextool-4.0.4}/flextool/representative_periods/netload_inputs.py +0 -0
  255. {flextool-4.0.1 → flextool-4.0.4}/flextool/representative_periods/netload_iterate.py +0 -0
  256. {flextool-4.0.1 → flextool-4.0.4}/flextool/representative_periods/preprocess.py +0 -0
  257. {flextool-4.0.1 → flextool-4.0.4}/flextool/representative_periods/scenario_stack.py +0 -0
  258. {flextool-4.0.1 → flextool-4.0.4}/flextool/representative_periods/weights.py +0 -0
  259. {flextool-4.0.1 → flextool-4.0.4}/flextool/scenario_comparison/__init__.py +0 -0
  260. {flextool-4.0.1 → flextool-4.0.4}/flextool/scenario_comparison/config_builder.py +0 -0
  261. {flextool-4.0.1 → flextool-4.0.4}/flextool/scenario_comparison/constants.py +0 -0
  262. {flextool-4.0.1 → flextool-4.0.4}/flextool/scenario_comparison/data_models.py +0 -0
  263. {flextool-4.0.1 → flextool-4.0.4}/flextool/scenario_comparison/db_reader.py +0 -0
  264. {flextool-4.0.1 → flextool-4.0.4}/flextool/scenario_comparison/dispatch_data.py +0 -0
  265. {flextool-4.0.1 → flextool-4.0.4}/flextool/scenario_comparison/dispatch_mappings.py +0 -0
  266. {flextool-4.0.1 → flextool-4.0.4}/flextool/scenario_comparison/dispatch_plots.py +0 -0
  267. {flextool-4.0.1 → flextool-4.0.4}/flextool/scenario_comparison/input_entity_colors.py +0 -0
  268. {flextool-4.0.1 → flextool-4.0.4}/flextool/scenario_comparison/orchestrator.py +0 -0
  269. {flextool-4.0.1 → flextool-4.0.4}/flextool/scenario_comparison/plan_union.py +0 -0
  270. {flextool-4.0.1 → flextool-4.0.4}/flextool/scenario_comparison/plot_settings_seed.py +0 -0
  271. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/AXIS_CONTRACT.md +0 -0
  272. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/canonical_databases/howto_aggregate_output.json +0 -0
  273. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/canonical_databases/howto_connections.json +0 -0
  274. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/canonical_databases/howto_demand.json +0 -0
  275. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/canonical_databases/howto_hydro_reservoir.json +0 -0
  276. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/canonical_databases/howto_hydro_reservoir_with_pump.json +0 -0
  277. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/canonical_databases/howto_non_sync_and_curtailment.json +0 -0
  278. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/canonical_databases/howto_ramp_and_start_up.json +0 -0
  279. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/canonical_databases/howto_stochastics.json +0 -0
  280. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/canonical_databases/templates_examples.json +0 -0
  281. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/canonical_databases/templates_time_settings_only.json +0 -0
  282. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/comparison_settings_template.json +0 -0
  283. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/default_plot_settings.yaml +0 -0
  284. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/default_plots.yaml +0 -0
  285. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/flextool_axis_contract.json +0 -0
  286. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/flextool_axis_contract.schema.json +0 -0
  287. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/old_flextool_import_template.json +0 -0
  288. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/output_info_template.json +0 -0
  289. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/output_settings_template.json +0 -0
  290. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_constant_default.json +0 -0
  291. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_default_optional_output.json +0 -0
  292. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_default_value.json +0 -0
  293. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_drop_down.json +0 -0
  294. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_lifetime_method.json +0 -0
  295. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_optional_outputs.json +0 -0
  296. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_output_node_flows.json +0 -0
  297. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_results_master.json +0 -0
  298. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_rolling_start_remove.json +0 -0
  299. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_rolling_window.json +0 -0
  300. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_storage_binding_defaults.json +0 -0
  301. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_v2.json +0 -0
  302. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/pre_v26/flextool_template_v25.json +0 -0
  303. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/spinedb_results_schema.json +0 -0
  304. {flextool-4.0.1 → flextool-4.0.4}/flextool/schemas/spinedb_schema.json +0 -0
  305. {flextool-4.0.1 → flextool-4.0.4}/flextool/solver_config/copt.opt.template +0 -0
  306. {flextool-4.0.1 → flextool-4.0.4}/flextool/solver_config/cplex.opt.template +0 -0
  307. {flextool-4.0.1 → flextool-4.0.4}/flextool/solver_config/gurobi.opt.template +0 -0
  308. {flextool-4.0.1 → flextool-4.0.4}/flextool/solver_config/highs.opt.template +0 -0
  309. {flextool-4.0.1 → flextool-4.0.4}/flextool/solver_config/xpress.opt.template +0 -0
  310. {flextool-4.0.1 → flextool-4.0.4}/flextool/spinedb_backend/__init__.py +0 -0
  311. {flextool-4.0.1 → flextool-4.0.4}/flextool/spinedb_backend/_backend.py +0 -0
  312. {flextool-4.0.1 → flextool-4.0.4}/flextool/update_flextool/__init__.py +0 -0
  313. {flextool-4.0.1 → flextool-4.0.4}/flextool/update_flextool/canonical_databases.py +0 -0
  314. {flextool-4.0.1 → flextool-4.0.4}/flextool/update_flextool/db_migration.py +0 -0
  315. {flextool-4.0.1 → flextool-4.0.4}/flextool/update_flextool/ensure_settings_db.py +0 -0
  316. {flextool-4.0.1 → flextool-4.0.4}/flextool/update_flextool/export_database.py +0 -0
  317. {flextool-4.0.1 → flextool-4.0.4}/flextool/update_flextool/extend_tests_fixture.py +0 -0
  318. {flextool-4.0.1 → flextool-4.0.4}/flextool/update_flextool/generate_canonical.py +0 -0
  319. {flextool-4.0.1 → flextool-4.0.4}/flextool/update_flextool/initialize_database.py +0 -0
  320. {flextool-4.0.1 → flextool-4.0.4}/flextool/update_flextool/install_info.py +0 -0
  321. {flextool-4.0.1 → flextool-4.0.4}/flextool/update_flextool/sync_master_json_template.py +0 -0
  322. {flextool-4.0.1 → flextool-4.0.4}/flextool/update_flextool/test_fixtures.py +0 -0
  323. {flextool-4.0.1 → flextool-4.0.4}/flextool.egg-info/SOURCES.txt +0 -0
  324. {flextool-4.0.1 → flextool-4.0.4}/flextool.egg-info/dependency_links.txt +0 -0
  325. {flextool-4.0.1 → flextool-4.0.4}/flextool.egg-info/entry_points.txt +0 -0
  326. {flextool-4.0.1 → flextool-4.0.4}/flextool.egg-info/top_level.txt +0 -0
  327. {flextool-4.0.1 → flextool-4.0.4}/setup.cfg +0 -0
  328. {flextool-4.0.1 → flextool-4.0.4}/tests/test_bar_layout.py +0 -0
  329. {flextool-4.0.1 → flextool-4.0.4}/tests/test_bar_sub_pixel_labels.py +0 -0
  330. {flextool-4.0.1 → flextool-4.0.4}/tests/test_blocks.py +0 -0
  331. {flextool-4.0.1 → flextool-4.0.4}/tests/test_branch_weights.py +0 -0
  332. {flextool-4.0.1 → flextool-4.0.4}/tests/test_color_template.py +0 -0
  333. {flextool-4.0.1 → flextool-4.0.4}/tests/test_commodity_ladder_rolling.py +0 -0
  334. {flextool-4.0.1 → flextool-4.0.4}/tests/test_comparison_excel.py +0 -0
  335. {flextool-4.0.1 → flextool-4.0.4}/tests/test_comparison_project_root.py +0 -0
  336. {flextool-4.0.1 → flextool-4.0.4}/tests/test_complete_period_share_reduction.py +0 -0
  337. {flextool-4.0.1 → flextool-4.0.4}/tests/test_connection_node_warnings.py +0 -0
  338. {flextool-4.0.1 → flextool-4.0.4}/tests/test_console_run_tool.py +0 -0
  339. {flextool-4.0.1 → flextool-4.0.4}/tests/test_cost_aggregation_semantics.py +0 -0
  340. {flextool-4.0.1 → flextool-4.0.4}/tests/test_cost_breakdown_by_entity.py +0 -0
  341. {flextool-4.0.1 → flextool-4.0.4}/tests/test_csv_dump_roundtrip.py +0 -0
  342. {flextool-4.0.1 → flextool-4.0.4}/tests/test_dc_power_flow.py +0 -0
  343. {flextool-4.0.1 → flextool-4.0.4}/tests/test_dispatch_consumer_and_connection_signs.py +0 -0
  344. {flextool-4.0.1 → flextool-4.0.4}/tests/test_dispatch_node_debug_and_tree.py +0 -0
  345. {flextool-4.0.1 → flextool-4.0.4}/tests/test_dispatch_order_specials.py +0 -0
  346. {flextool-4.0.1 → flextool-4.0.4}/tests/test_dispatch_scenario_tag.py +0 -0
  347. {flextool-4.0.1 → flextool-4.0.4}/tests/test_ensure_settings_db.py +0 -0
  348. {flextool-4.0.1 → flextool-4.0.4}/tests/test_entity_unitsize_invariant.py +0 -0
  349. {flextool-4.0.1 → flextool-4.0.4}/tests/test_entity_universe_carrier.py +0 -0
  350. {flextool-4.0.1 → flextool-4.0.4}/tests/test_env_check.py +0 -0
  351. {flextool-4.0.1 → flextool-4.0.4}/tests/test_export_to_tabular.py +0 -0
  352. {flextool-4.0.1 → flextool-4.0.4}/tests/test_extend_tests_fixture.py +0 -0
  353. {flextool-4.0.1 → flextool-4.0.4}/tests/test_flowgroup_indicators.py +0 -0
  354. {flextool-4.0.1 → flextool-4.0.4}/tests/test_force_include.py +0 -0
  355. {flextool-4.0.1 → flextool-4.0.4}/tests/test_group_output_warnings.py +0 -0
  356. {flextool-4.0.1 → flextool-4.0.4}/tests/test_handoff_writers.py +0 -0
  357. {flextool-4.0.1 → flextool-4.0.4}/tests/test_input_entity_colors.py +0 -0
  358. {flextool-4.0.1 → flextool-4.0.4}/tests/test_lh2_three_region.py +0 -0
  359. {flextool-4.0.1 → flextool-4.0.4}/tests/test_min_load_rolling.py +0 -0
  360. {flextool-4.0.1 → flextool-4.0.4}/tests/test_non_anticipativity.py +0 -0
  361. {flextool-4.0.1 → flextool-4.0.4}/tests/test_old_flextool_importer_schema_drift.py +0 -0
  362. {flextool-4.0.1 → flextool-4.0.4}/tests/test_open_results_db.py +0 -0
  363. {flextool-4.0.1 → flextool-4.0.4}/tests/test_output_column_metadata.py +0 -0
  364. {flextool-4.0.1 → flextool-4.0.4}/tests/test_output_metadata_renderers.py +0 -0
  365. {flextool-4.0.1 → flextool-4.0.4}/tests/test_parameter_group_coverage.py +0 -0
  366. {flextool-4.0.1 → flextool-4.0.4}/tests/test_plan_union.py +0 -0
  367. {flextool-4.0.1 → flextool-4.0.4}/tests/test_plot_draw_order.py +0 -0
  368. {flextool-4.0.1 → flextool-4.0.4}/tests/test_plot_pipeline.py +0 -0
  369. {flextool-4.0.1 → flextool-4.0.4}/tests/test_plot_plan_chunk_reconstruction.py +0 -0
  370. {flextool-4.0.1 → flextool-4.0.4}/tests/test_plot_plan_time_labels_roundtrip.py +0 -0
  371. {flextool-4.0.1 → flextool-4.0.4}/tests/test_plot_settings_seed.py +0 -0
  372. {flextool-4.0.1 → flextool-4.0.4}/tests/test_precision.py +0 -0
  373. {flextool-4.0.1 → flextool-4.0.4}/tests/test_read_highs_solution.py +0 -0
  374. {flextool-4.0.1 → flextool-4.0.4}/tests/test_regional_filter.py +0 -0
  375. {flextool-4.0.1 → flextool-4.0.4}/tests/test_representative_periods.py +0 -0
  376. {flextool-4.0.1 → flextool-4.0.4}/tests/test_rp_alternative_name.py +0 -0
  377. {flextool-4.0.1 → flextool-4.0.4}/tests/test_rp_force_include_wiring.py +0 -0
  378. {flextool-4.0.1 → flextool-4.0.4}/tests/test_rp_solves_filter.py +0 -0
  379. {flextool-4.0.1 → flextool-4.0.4}/tests/test_scenario_colors.py +0 -0
  380. {flextool-4.0.1 → flextool-4.0.4}/tests/test_scenario_stack.py +0 -0
  381. {flextool-4.0.1 → flextool-4.0.4}/tests/test_scenarios.py +0 -0
  382. {flextool-4.0.1 → flextool-4.0.4}/tests/test_self_describing_reader.py +0 -0
  383. {flextool-4.0.1 → flextool-4.0.4}/tests/test_shared_axis_manifest.py +0 -0
  384. {flextool-4.0.1 → flextool-4.0.4}/tests/test_simple_bar_entity_color.py +0 -0
  385. {flextool-4.0.1 → flextool-4.0.4}/tests/test_solve_config.py +0 -0
  386. {flextool-4.0.1 → flextool-4.0.4}/tests/test_solve_reader.py +0 -0
  387. {flextool-4.0.1 → flextool-4.0.4}/tests/test_synthesize_invest_dual.py +0 -0
  388. {flextool-4.0.1 → flextool-4.0.4}/tests/test_timings_csv.py +0 -0
  389. {flextool-4.0.1 → flextool-4.0.4}/tests/test_unidirectional_connection.py +0 -0
  390. {flextool-4.0.1 → flextool-4.0.4}/tests/test_unit_online_dt_columns.py +0 -0
  391. {flextool-4.0.1 → flextool-4.0.4}/tests/test_v52_migration.py +0 -0
  392. {flextool-4.0.1 → flextool-4.0.4}/tests/test_v63_migration.py +0 -0
  393. {flextool-4.0.1 → flextool-4.0.4}/tests/test_v64_migration.py +0 -0
  394. {flextool-4.0.1 → flextool-4.0.4}/tests/test_v65_migration.py +0 -0
  395. {flextool-4.0.1 → flextool-4.0.4}/tests/test_v66_migration.py +0 -0
  396. {flextool-4.0.1 → flextool-4.0.4}/tests/test_v67_migration.py +0 -0
  397. {flextool-4.0.1 → flextool-4.0.4}/tests/test_v68_migration.py +0 -0
  398. {flextool-4.0.1 → flextool-4.0.4}/tests/test_v69_migration.py +0 -0
  399. {flextool-4.0.1 → flextool-4.0.4}/tests/test_workdir_empty_default.py +0 -0
  400. {flextool-4.0.1 → flextool-4.0.4}/tests/test_xlsx_workflow.py +0 -0
  401. {flextool-4.0.1 → flextool-4.0.4}/tests/test_years_represented.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flextool
3
- Version: 4.0.1
3
+ Version: 4.0.4
4
4
  Summary: IRENA FlexTool is an energy and power systems model for understanding the role of variable power generation in future energy systems.
5
5
  Author: IRENA FlexTool contributors
6
6
  License: © International Renewable Energy Agency 2018-2022
@@ -39,7 +39,7 @@ Requires-Python: >=3.11
39
39
  Description-Content-Type: text/markdown
40
40
  License-File: LICENSE.txt
41
41
  Requires-Dist: polar-high>=3.7.0
42
- Requires-Dist: highspy<=1.14.0
42
+ Requires-Dist: highspy<=1.14.0,>=1.14.0
43
43
  Requires-Dist: polars>=1.40
44
44
  Requires-Dist: pandas
45
45
  Requires-Dist: spinedb-api
@@ -6572,12 +6572,23 @@ def dtttdt_from_source(source: "InputSource",
6572
6572
  # period__branch.csv (workdir-aware) to discover which periods are
6573
6573
  # branches off a non-self anchor.
6574
6574
  branch_anchor: dict[str, str] = {}
6575
+ anchors_with_self: set[str] = set()
6575
6576
  pbf: pl.DataFrame | None = None
6576
6577
  period_col, branch_col = "period", "branch"
6577
6578
  # Path B Cat B: prefer ctx.period_branch (schema [d_anchor, b]).
6579
+ # Null-guard: the accessor casts against the live axis enums, and
6580
+ # the ``d_anchor`` enum vocabulary is empty by contract ("built at
6581
+ # Phase 3 handoff") — under enum activation every anchor value
6582
+ # casts to null, which would poison the anchors_with_self /
6583
+ # branch_anchor classification below (a null==null self-row turns
6584
+ # EVERY period into a "branch"). Only trust the ctx frame when
6585
+ # both columns are fully populated; otherwise fall through to the
6586
+ # provider/workdir CSV (raw Utf8 columns).
6578
6587
  if ctx is not None:
6579
6588
  pb_ctx = ctx.period_branch
6580
- if pb_ctx.height > 0:
6589
+ if (pb_ctx.height > 0
6590
+ and pb_ctx["d_anchor"].null_count() == 0
6591
+ and pb_ctx["b"].null_count() == 0):
6581
6592
  pbf = pb_ctx
6582
6593
  period_col, branch_col = "d_anchor", "b"
6583
6594
  if pbf is None and workdir is not None:
@@ -6590,13 +6601,52 @@ def dtttdt_from_source(source: "InputSource",
6590
6601
  if pbf is not None and pbf.height > 0:
6591
6602
  pb_rows = list(zip(pbf[period_col].to_list(),
6592
6603
  pbf[branch_col].to_list()))
6593
- anchors_with_self: set[str] = set()
6594
6604
  for a, b in pb_rows:
6595
6605
  if a == b:
6596
6606
  anchors_with_self.add(a)
6597
6607
  for a, b in pb_rows:
6598
6608
  if b != a and a in anchors_with_self:
6599
6609
  branch_anchor[b] = a
6610
+ # Continuation fan-out: the time-branch map (period -> time_branch,
6611
+ # from solve_branch__time_branch.csv) lets a continuation branch
6612
+ # period wrap to the nearest earlier period of the SAME time-branch
6613
+ # instead of self-wrapping, mirroring ``make_step_jump``. Ctx-first
6614
+ # (typed accessor), provider/workdir fallback.
6615
+ time_branch_of: dict[str, str] = {}
6616
+ if ctx is not None:
6617
+ sbtb_ctx = ctx.solve_branch_time_branch
6618
+ if sbtb_ctx.height > 0:
6619
+ time_branch_of = dict(zip(sbtb_ctx["d"].to_list(),
6620
+ sbtb_ctx["time_branch"].to_list()))
6621
+ if not time_branch_of and workdir is not None:
6622
+ sbtb_path = (Path(workdir) / "solve_data"
6623
+ / "solve_branch__time_branch.csv")
6624
+ if _provider_has_key(provider, sbtb_path): # Phase E-j — seed-aware
6625
+ try:
6626
+ sbtb_df = _provider_read(provider, sbtb_path)
6627
+ except Exception:
6628
+ sbtb_df = None
6629
+ if (sbtb_df is not None and sbtb_df.height > 0
6630
+ and "period" in sbtb_df.columns
6631
+ and "branch" in sbtb_df.columns):
6632
+ time_branch_of = dict(zip(sbtb_df["period"].to_list(),
6633
+ sbtb_df["branch"].to_list()))
6634
+ if branch_anchor and not time_branch_of:
6635
+ # Failure semantics (do NOT fall back to self-wrap): a
6636
+ # period__branch with branch rows but no time-branch map means
6637
+ # the per-solve preprocessing artefacts are inconsistent. A
6638
+ # silent self-wrap fallback would sever continuation branch
6639
+ # periods from their own branch chain — the exact silent
6640
+ # truncation bug this walk repairs — so raise instead.
6641
+ raise ValueError(
6642
+ "dtttdt_from_source: period__branch declares branch periods "
6643
+ f"({sorted(branch_anchor)}) but solve_branch__time_branch is "
6644
+ "missing or empty — cannot resolve cross-period wrap "
6645
+ "predecessors for branch periods. The per-solve emitter "
6646
+ "(emit_branch_weights_and_map) writes this frame for every "
6647
+ "solve; its absence indicates an inconsistent workdir/"
6648
+ "Provider state."
6649
+ )
6600
6650
  out_rows: list[tuple[str, str, str, str, str, str]] = []
6601
6651
  len(period_order)
6602
6652
  # Find within-solve predecessor for first-of-period rows.
@@ -6606,19 +6656,36 @@ def dtttdt_from_source(source: "InputSource",
6606
6656
  continue
6607
6657
  # Determine the predecessor period for the j=0 wrap row.
6608
6658
  # Mirrors ``timeline_config.make_step_jump``:
6609
- # * branch (non-anchor) period → self-wrap;
6659
+ # * branch (non-anchor) period → nearest earlier period with
6660
+ # the SAME time-branch (continuation linkage); self-wrap
6661
+ # when none exists (the branching period's own fan);
6610
6662
  # * first anchor period (== first_period_name in flextool's
6611
6663
  # reversed walk) → wrap to last_period_name (last in
6612
- # active_time_list iteration order, == period_order[-1]);
6613
- # * non-first anchor period → wrap to the period at
6614
- # period_order[pi - 1].
6664
+ # active_time_list iteration order, == period_order[-1] —
6665
+ # retained quirk);
6666
+ # * non-first anchor period → nearest earlier ANCHOR period
6667
+ # (skips branch periods so a realized continuation period
6668
+ # wraps to the previous realized period); positional
6669
+ # ``period_order[pi - 1]`` fallback when there is no
6670
+ # period__branch data (deterministic path, where
6671
+ # positional ≡ previous-anchor anyway).
6615
6672
  if period in branch_anchor:
6616
6673
  wrap_period = period
6674
+ tb = time_branch_of.get(period)
6675
+ if tb is not None:
6676
+ for qi in range(pi - 1, -1, -1):
6677
+ if time_branch_of.get(period_order[qi]) == tb:
6678
+ wrap_period = period_order[qi]
6679
+ break
6617
6680
  else:
6618
6681
  if pi == 0:
6619
6682
  wrap_period = period_order[-1]
6620
6683
  else:
6621
6684
  wrap_period = period_order[pi - 1]
6685
+ for qi in range(pi - 1, -1, -1):
6686
+ if period_order[qi] in anchors_with_self:
6687
+ wrap_period = period_order[qi]
6688
+ break
6622
6689
  prev_solve_steps = per_period.get(wrap_period) or []
6623
6690
  prev_solve_last_t = prev_solve_steps[-1][0] if prev_solve_steps \
6624
6691
  else steps[-1][0]
@@ -241,7 +241,10 @@ def derive_years_represented(
241
241
  str(years_to_cover_within_year),
242
242
  ))
243
243
  for pd_pair in period__branch:
244
- if pd_pair[0] in period__years[0] and pd_pair[0] != pd_pair[1]:
244
+ # Equality, not substring containment (``in``): with
245
+ # prefix-overlapping period names (p1 / p10), ``in``
246
+ # would copy p10's year rows to p1's branches.
247
+ if pd_pair[0] == period__years[0] and pd_pair[0] != pd_pair[1]:
245
248
  rows.append((
246
249
  pd_pair[1], str(year_count), str(year_count),
247
250
  str(years_to_cover_within_year),
@@ -278,7 +281,9 @@ def derive_period_years(
278
281
  for period__year in years_represented:
279
282
  rows.append((period__year[0], str(year_count)))
280
283
  for pd_pair in stochastic_branches:
281
- if pd_pair[0] in period__year[0] and pd_pair[0] != pd_pair[1]:
284
+ # Equality, not substring containment — see
285
+ # derive_years_represented.
286
+ if pd_pair[0] == period__year[0] and pd_pair[0] != pd_pair[1]:
282
287
  rows.append((pd_pair[1], str(year_count)))
283
288
  year_count += float(period__year[1])
284
289
  return _to_utf8_frame(("period", "param"), rows)
@@ -192,6 +192,13 @@ class SolveContext:
192
192
  ),
193
193
  repr=False,
194
194
  )
195
+ _solve_branch_time_branch_loaded: bool = field(default=False, repr=False)
196
+ _solve_branch_time_branch: pl.DataFrame = field(
197
+ default_factory=lambda: pl.DataFrame(
198
+ schema={"d": pl.Utf8, "time_branch": pl.Utf8}
199
+ ),
200
+ repr=False,
201
+ )
195
202
 
196
203
  # Internal: ad-hoc CSV cache. Keyed by ``str(path)`` (no syscall).
197
204
  # Values are ``None`` when the file is absent so we don't re-stat
@@ -452,6 +459,27 @@ class SolveContext:
452
459
  self._solve_branch_weight_loaded = True
453
460
  return self._solve_branch_weight
454
461
 
462
+ @property
463
+ def solve_branch_time_branch(self) -> pl.DataFrame:
464
+ """``[d, time_branch]`` from ``solve_branch__time_branch.csv``
465
+ (rename ``period`` → ``d``, ``branch`` → ``time_branch``).
466
+ Empty when the file is missing.
467
+
468
+ Continuation fan-out: ``dtttdt_from_source`` walks this map so
469
+ a continuation branch period wraps to the nearest earlier
470
+ period sharing the same time-branch instead of self-wrapping.
471
+ Columns stay ``Utf8`` — the ``time_branch`` vocabulary (DB
472
+ branch names like ``rlz`` / ``low``) is not a canonical axis
473
+ enum, and consumers use the frame as a plain dict.
474
+ """
475
+ if not self._solve_branch_time_branch_loaded:
476
+ self._solve_branch_time_branch = _load_solve_branch_time_branch(
477
+ self.solve_data_dir / "solve_branch__time_branch.csv",
478
+ provider=self.provider,
479
+ )
480
+ self._solve_branch_time_branch_loaded = True
481
+ return self._solve_branch_time_branch
482
+
455
483
  # ------------------------------------------------------------------
456
484
  # Typed-field convenience accessors
457
485
  # ------------------------------------------------------------------
@@ -856,6 +884,32 @@ def _load_period_share(solve_data_dir: Path,
856
884
  return empty
857
885
 
858
886
 
887
+ def _load_solve_branch_time_branch(path: Path,
888
+ *, provider: "object | None" = None,
889
+ ) -> pl.DataFrame:
890
+ """Load ``solve_branch__time_branch.csv`` as ``[d, time_branch]``.
891
+
892
+ Plain ``Utf8`` columns — the time-branch vocabulary is not a
893
+ canonical axis enum (see :pyattr:`SolveContext.solve_branch_time_branch`).
894
+ """
895
+ empty = pl.DataFrame(schema={"d": pl.Utf8, "time_branch": pl.Utf8})
896
+ df = _read_frame(path, provider=provider,
897
+ consumer="SolveContext.solve_branch_time_branch")
898
+ if df is None:
899
+ return empty
900
+ rename = {}
901
+ if "period" in df.columns:
902
+ rename["period"] = "d"
903
+ if "branch" in df.columns:
904
+ rename["branch"] = "time_branch"
905
+ df = df.rename(rename)
906
+ cols = [c for c in ("d", "time_branch") if c in df.columns]
907
+ if len(cols) < 2:
908
+ return empty
909
+ return (df.select(pl.col(c).cast(pl.Utf8) for c in cols)
910
+ .unique(maintain_order=True))
911
+
912
+
859
913
  def _load_solve_branch_weight(path: Path,
860
914
  *, provider: "object | None" = None) -> pl.DataFrame:
861
915
  """Load ``solve_branch_weight.csv`` and rename to canonical
@@ -30,10 +30,15 @@ Behavioural quirks
30
30
  ``branch != period`` AND the realized flag is not ``"yes"``. Subtle
31
31
  three-way condition.
32
32
 
33
- * **Continuation-after-branching**: when a roll's jump exceeds a
34
- period length, branches continue into subsequent periods using the
35
- same ``period + "_" + branch`` naming. The naming is "sticky"
36
- across periods.
33
+ * **Continuation-after-branching**: every period after the branching
34
+ period continues the fan. The realized branch keeps the REAL period
35
+ name (with its active / realized / fix-storage windows); each
36
+ non-realized nonzero-weight branch gets a synthetic
37
+ ``period + "_" + branch`` copy of the period's active time (LP
38
+ variables, never committed). The naming is "sticky" across periods.
39
+ ``period__branch`` mirrors the branching period's metadata rows
40
+ (``(period, period)`` self-row plus one row per fan member,
41
+ realized and zero-weight included).
37
42
 
38
43
  * **Single realized branch per period**: each period must have
39
44
  exactly one ``realized: yes`` row (or zero before any branching has
@@ -378,6 +383,17 @@ class StochasticSolver:
378
383
  but NOT to ``realized_time_lists`` / ``fix_storage_time_lists``;
379
384
  branches are future scenarios that aren't committed.
380
385
 
386
+ After the branching period, the fan extends through every
387
+ remaining period of the solve: the realized branch keeps the
388
+ REAL period names (and their realized / fix-storage windows),
389
+ while each non-realized nonzero-weight branch ``b`` gets one
390
+ synthetic ``period + "_" + b`` period per continuation period
391
+ carrying a full copy of that period's active time.
392
+ ``period__branch`` mirrors the branching period's metadata rows
393
+ (self-row plus one row per fan member including the realized
394
+ and zero-weight branches); ``solve_branch__time_branch`` gains
395
+ rows only for branches that received active time.
396
+
381
397
  IMPORTANT: branches do NOT enter ``invest_periods`` either.
382
398
  Per ``audit/a6_b_dim_alternative.md`` (R-O6), ``v_invest`` is
383
399
  realized-only — recourse investment is a future capability.
@@ -418,6 +434,10 @@ class StochasticSolver:
418
434
 
419
435
  branched = False
420
436
  branches: list[str] = []
437
+ # Branches that received active time at the branching period
438
+ # (nonzero weight, != period, not realized). Only these are
439
+ # extended with active time into continuation periods.
440
+ active_branches: list[str] = []
421
441
  branch_start_time_lists[solve] = None
422
442
 
423
443
  # First step of the solve (used for validation below).
@@ -446,9 +466,14 @@ class StochasticSolver:
446
466
  raise FlexToolConfigError(message)
447
467
 
448
468
  # Walk periods — at the first branch trigger, fan out into
449
- # branches; subsequent periods continue the same branches
450
- # (continuation-after-branching at lines 329-340 of the
451
- # reference).
469
+ # branches; subsequent periods continue the same branches.
470
+ # NB: the reference implementation's continuation block
471
+ # (flextoolrunner.py:1745-1752) was dead code — it never
472
+ # wrote active/realized/fix-storage time for continuation
473
+ # periods, silently truncating the horizon. This module
474
+ # intentionally diverges by actually emitting the
475
+ # continuation timelines (realized branch keeps real period
476
+ # names; active branches get synthetic copies).
452
477
  for period, active_time in active_time_list.items():
453
478
  if not branched:
454
479
  period__branch_lists[solve].append((period, period))
@@ -491,6 +516,7 @@ class StochasticSolver:
491
516
  and branch != period
492
517
  and branch__weight__real[2] != "yes"
493
518
  ):
519
+ active_branches.append(branch)
494
520
  new_active_time_list[solve_branch] = active_time[0:]
495
521
  solve_branch__time_branch_lists[solve].append(
496
522
  (solve_branch, branch)
@@ -506,15 +532,34 @@ class StochasticSolver:
506
532
  )
507
533
  break
508
534
  else:
509
- # Continuation-after-branching: extend each branch
510
- # into the next period using the same branch-name
511
- # prefix.
535
+ # Continuation-after-branching: the realized branch
536
+ # keeps the REAL period name; each active branch is
537
+ # extended into this period as a synthetic
538
+ # '<period>_<branch>' copy. Append order mirrors
539
+ # the branching period: the (period, period)
540
+ # self-row first, then fan rows in DB order. The
541
+ # self-row is load-bearing for the branch-weight
542
+ # normalization (the realized member of each cohort
543
+ # is detected via its (d, d) row) — see
544
+ # derive_solve_branch_weight.
545
+ period__branch_lists[solve].append((period, period))
546
+ new_active_time_list[period] = active_time_list[period]
547
+ if period in realized_time_list:
548
+ new_realized_time_list[period] = realized_time_list[period]
549
+ if period in fix_storage_time_list:
550
+ new_fix_storage_time_list[period] = (
551
+ fix_storage_time_list[period]
552
+ )
512
553
  for branch in branches:
513
554
  solve_branch = period + "_" + branch
514
555
  period__branch_lists[solve].append((period, solve_branch))
515
- solve_branch__time_branch_lists[solve].append(
516
- (solve_branch, branch)
517
- )
556
+ if branch in active_branches:
557
+ new_active_time_list[solve_branch] = (
558
+ active_time_list[period][0:]
559
+ )
560
+ solve_branch__time_branch_lists[solve].append(
561
+ (solve_branch, branch)
562
+ )
518
563
  for i in active_time_list[period]:
519
564
  self.state.timeline.stochastic_timesteps[solve].append(
520
565
  (solve_branch, i.timestep)
@@ -561,9 +561,18 @@ class TimelineConfig:
561
561
  solve_config.realized_periods[solve]
562
562
  + solve_config.invest_periods[solve]
563
563
  )
564
- all_periods = {item for tup in all_periods_tuples for item in tup}
565
- for period in all_periods:
566
- if solve not in solve_config.solve_period_years_represented:
564
+ # Ordered de-dup (first occurrence) — a set here would make
565
+ # the seeded row order vary with PYTHONHASHSEED.
566
+ all_periods = list(
567
+ dict.fromkeys(
568
+ item for tup in all_periods_tuples for item in tup
569
+ )
570
+ )
571
+ # Membership check OUTSIDE the loop: the first append below
572
+ # inserts the solve key into the defaultdict, so an in-loop
573
+ # check would stop after seeding a single period.
574
+ if solve not in solve_config.solve_period_years_represented:
575
+ for period in all_periods:
567
576
  solve_config.solve_period_years_represented[solve].append(
568
577
  [period, 1.0]
569
578
  )
@@ -1097,14 +1106,29 @@ def make_step_jump(
1097
1106
  """Build a list of step-jump entries for the solver.
1098
1107
 
1099
1108
  Each entry describes the jump from one simulation step to the
1100
- next, including cross-period jumps. Stochastic branches use the
1101
- last-realized-step on a sibling branch as the predecessor.
1109
+ next, including cross-period jumps.
1110
+
1111
+ Cross-period linkage for the first step of a period:
1112
+
1113
+ * **anchor periods** (``(period, period) ∈ period__branch``): the
1114
+ nearest previous ANCHOR key in ``active_time_list`` order (skips
1115
+ branch periods, so a realized continuation period links to the
1116
+ previous realized period). The solve's FIRST period keeps the
1117
+ cyclic wrap to the last dict key (a branch period in stochastic
1118
+ solves — retained quirk, consumed only by cyclic storage-binding
1119
+ paths).
1120
+ * **branch periods**: the nearest earlier period sharing the same
1121
+ time-branch in ``solve_branch__time_branch_list`` (a continuation
1122
+ branch period continues from the same branch's previous period);
1123
+ when no such period exists (the branching period's own fan) the
1124
+ period self-cycles on its own last step.
1102
1125
  """
1103
1126
  step_lengths: list[tuple] = []
1104
1127
  period_start_pos = 0
1105
1128
  period_counter = -1
1106
- first_period_name = list(active_time_list)[0]
1107
- last_period_name = list(active_time_list)[-1]
1129
+ period_names = list(active_time_list)
1130
+ first_period_name = period_names[0]
1131
+ last_period_name = period_names[-1]
1108
1132
  for period, active_time in reversed(active_time_list.items()):
1109
1133
  period_counter -= 1
1110
1134
  period_last = len(active_time)
@@ -1112,7 +1136,18 @@ def make_step_jump(
1112
1136
  if period == first_period_name:
1113
1137
  previous_period_name = last_period_name
1114
1138
  else:
1115
- previous_period_name = list(active_time_list)[period_counter]
1139
+ previous_period_name = period_names[period_counter]
1140
+ if (period, period) in period__branch:
1141
+ # Anchor period: skip branch periods — link to the
1142
+ # nearest previous key that is itself an anchor.
1143
+ # Deterministic solves (every key an anchor) reduce to
1144
+ # the positional previous; when no earlier anchor
1145
+ # exists the positional previous is kept as fallback.
1146
+ pos = period_names.index(period)
1147
+ for k in range(pos - 1, -1, -1):
1148
+ if (period_names[k], period_names[k]) in period__branch:
1149
+ previous_period_name = period_names[k]
1150
+ break
1116
1151
  for i, step in enumerate(reversed(active_time)):
1117
1152
  j = period_last - i - 1
1118
1153
  if j > 0:
@@ -1146,18 +1181,45 @@ def make_step_jump(
1146
1181
  )
1147
1182
  else:
1148
1183
  if (period, period) not in period__branch:
1149
- original_period = None
1150
- for pb in period__branch:
1151
- if pb[1] == period:
1152
- original_period = pb[0]
1153
- if (
1154
- original_period is not None
1155
- and (original_period, original_period)
1156
- in period__branch
1157
- and original_period in active_time_list
1184
+ # Branch period: find the nearest EARLIER period
1185
+ # sharing this period's time-branch — the
1186
+ # continuation linkage (a continuation branch
1187
+ # period continues from the same branch's previous
1188
+ # period). When none exists (the branching
1189
+ # period's own fan: earlier periods carry only the
1190
+ # realized time-branch), fall back to the
1191
+ # self-cycle row on the period's own last step.
1192
+ time_branch = None
1193
+ for sb_tb in solve_branch__time_branch_list:
1194
+ if sb_tb[0] == period:
1195
+ time_branch = sb_tb[1]
1196
+ past = False
1197
+ found = False
1198
+ previous_period_with_branch = None
1199
+ for solve_period, _a_t in reversed(
1200
+ active_time_list.items()
1158
1201
  ):
1202
+ if past:
1203
+ for sb_tb in solve_branch__time_branch_list:
1204
+ if (
1205
+ sb_tb[0] == solve_period
1206
+ and sb_tb[1] == time_branch
1207
+ ):
1208
+ previous_period_with_branch = (
1209
+ solve_period
1210
+ )
1211
+ found = True
1212
+ if found:
1213
+ break
1214
+ else:
1215
+ if solve_period == period:
1216
+ past = True
1217
+ if found:
1159
1218
  jump = (
1160
- active_time[j].index - active_time[-1].index
1219
+ active_time[j].index
1220
+ - active_time_list[
1221
+ previous_period_with_branch
1222
+ ][-1].index
1161
1223
  )
1162
1224
  step_lengths.insert(
1163
1225
  period_start_pos,
@@ -1166,16 +1228,14 @@ def make_step_jump(
1166
1228
  step.timestep,
1167
1229
  active_time[j - 1].timestep,
1168
1230
  active_time[block_last].timestep,
1169
- period,
1170
- active_time_list[period][-1].timestep,
1231
+ previous_period_with_branch,
1232
+ active_time_list[
1233
+ previous_period_with_branch
1234
+ ][-1].timestep,
1171
1235
  jump,
1172
1236
  ),
1173
1237
  )
1174
- elif (
1175
- original_period is not None
1176
- and (original_period, original_period)
1177
- in period__branch
1178
- ):
1238
+ else:
1179
1239
  jump = (
1180
1240
  active_time[j].index - active_time[-1].index
1181
1241
  )
@@ -1191,52 +1251,6 @@ def make_step_jump(
1191
1251
  jump,
1192
1252
  ),
1193
1253
  )
1194
- else:
1195
- time_branch = None
1196
- for sb_tb in solve_branch__time_branch_list:
1197
- if sb_tb[0] == period:
1198
- time_branch = sb_tb[1]
1199
- past = False
1200
- found = False
1201
- previous_period_with_branch = None
1202
- for solve_period, _a_t in reversed(
1203
- active_time_list.items()
1204
- ):
1205
- if past:
1206
- for sb_tb in solve_branch__time_branch_list:
1207
- if (
1208
- sb_tb[0] == solve_period
1209
- and sb_tb[1] == time_branch
1210
- ):
1211
- previous_period_with_branch = (
1212
- solve_period
1213
- )
1214
- found = True
1215
- if found:
1216
- break
1217
- else:
1218
- if solve_period == period:
1219
- past = True
1220
- jump = (
1221
- active_time[j].index
1222
- - active_time_list[
1223
- previous_period_with_branch
1224
- ][-1].index
1225
- )
1226
- step_lengths.insert(
1227
- period_start_pos,
1228
- (
1229
- period,
1230
- step.timestep,
1231
- active_time[j - 1].timestep,
1232
- active_time[block_last].timestep,
1233
- previous_period_with_branch,
1234
- active_time_list[
1235
- previous_period_with_branch
1236
- ][-1].timestep,
1237
- jump,
1238
- ),
1239
- )
1240
1254
  else:
1241
1255
  jump = (
1242
1256
  active_time[j].index
@@ -126,12 +126,33 @@ def compute_storage_and_vre(par, s, v, r) -> None:
126
126
  profile_level_of_vre_node_profile_upper = vre_node_profile_upper.get_level_values('profile')
127
127
  vre_processes_in_use = vre_profiles_in_use[profile_level_of_vre_node_profile_upper]
128
128
  vre_processes_in_use.columns = vre_node_profile_upper
129
+ # par.process_availability only carries processes that authored
130
+ # unit/connection.availability. A profile-driven VRE process typically
131
+ # leaves the availability *parameter* at its 1.0 default and is therefore
132
+ # absent from the frame. A plain ``.mul`` aligns on the shared 'process'
133
+ # level and drops those columns to NaN, silently zeroing VRE potential /
134
+ # curtailment. The engine applies availability=1.0 to every unauthored
135
+ # process (model.py: left-join + fill_null(1.0)); reproduce that here by
136
+ # densifying availability to 1.0 over the VRE process set before the mul.
137
+ vre_procs = vre_processes_in_use.columns.get_level_values('process').unique()
138
+ availability_dt = par.process_availability.reindex(columns=vre_procs).fillna(1.0)
129
139
  r.potentialVREgen_dt = (
130
140
  vre_processes_in_use
131
- .mul(par.process_availability)
141
+ .mul(availability_dt, level='process')
132
142
  .mul(r.entity_all_capacity, axis=1, level=0)
133
143
  .droplevel(axis=1, level=['profile', 'profile_method'])
134
144
  )
145
+ # A (process, node) flow may carry more than one ``upper_limit`` profile
146
+ # (e.g. an availability profile plus a scheduling upper bound under a
147
+ # ``free_schedule`` alternative). The engine enforces flow ≤ profile ×
148
+ # capacity for each, so the achievable potential is the tightest (min)
149
+ # cap; collapsing to the min also keeps curtailment = potential − actual
150
+ # ≥ 0. Only rewrite when duplicates exist so single-profile models stay
151
+ # byte-identical (min-collapse preserves first-seen column order).
152
+ if r.potentialVREgen_dt.columns.duplicated().any():
153
+ r.potentialVREgen_dt = (
154
+ r.potentialVREgen_dt.T.groupby(level=['process', 'node'], sort=False).min().T
155
+ )
135
156
  # potentialVREgen_dt is MW (profile × availability × capacity); multiply
136
157
  # by step_duration to get MWh per step before summing to MWh per period.
137
158
  r.potentialVREgen_d = (
@@ -41,8 +41,18 @@ def cost_summaries(par, s, v, r, debug):
41
41
  costs_dt['co2'] = r.cost_co2_dt
42
42
  costs_dt['other operational'] = r.cost_process_other_operational_cost_dt.sum(axis=1)
43
43
  costs_dt['starts'] = r.cost_startup_dt.sum(axis=1)
44
- costs_dt['upward slack penalty'] = r.costPenalty_node_state_upDown_dt.xs('up', level='upDown', axis=1).sum(axis=1)
45
- costs_dt['downward slack penalty'] = r.costPenalty_node_state_upDown_dt.xs('down', level='upDown', axis=1).sum(axis=1)
44
+ # With penalty_method=off on every node, v.q_state_up/down have no
45
+ # columns, so costPenalty_node_state_upDown_dt is empty and the 'up'/
46
+ # 'down' keys never attach to the upDown level (KeyError on .xs). Guard
47
+ # like the reserve slack terms below and the per-entity path (line ~265).
48
+ try:
49
+ costs_dt['upward slack penalty'] = r.costPenalty_node_state_upDown_dt.xs('up', level='upDown', axis=1).sum(axis=1)
50
+ except KeyError:
51
+ costs_dt['upward slack penalty'] = 0
52
+ try:
53
+ costs_dt['downward slack penalty'] = r.costPenalty_node_state_upDown_dt.xs('down', level='upDown', axis=1).sum(axis=1)
54
+ except KeyError:
55
+ costs_dt['downward slack penalty'] = 0
46
56
  costs_dt['inertia slack penalty'] = r.costPenalty_inertia_dt.sum(axis=1)
47
57
  costs_dt['non-synchronous slack penalty'] = r.costPenalty_non_synchronous_dt.sum(axis=1)
48
58
  try: