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,153 @@
1
+ import logging
2
+ import sys
3
+ import spinedb_api as api
4
+ from spinedb_api import DatabaseMapping
5
+ from pathlib import Path
6
+
7
+ from flextool.engine_polars._db_reader import check_version
8
+ from flextool.input_derivation import run as _input_derivation_run
9
+ from flextool.engine_polars._solve_config import SolveConfig
10
+ from flextool.engine_polars._timeline import TimelineConfig
11
+ from flextool.engine_polars._solve_state import PathConfig, RunnerState, FlexToolError, FlexToolConfigError
12
+ from flextool.cli._timing import TimingRecorder
13
+
14
+
15
+ class FlexToolRunner:
16
+ """Thin coordinator that builds RunnerState for the native cascade.
17
+
18
+ Public API:
19
+ write_input() — reads DB, writes input/ CSV files (delegates to input_writer)
20
+
21
+ See the engine_polars cascade modules for the full input-writing flow.
22
+ """
23
+
24
+ def __init__(self, input_db_url=None, output_path=None, scenario_name=None, flextool_dir=None, solver_config_dir=None, root_dir=None, work_folder=None, highs_threads=None, auto_scale=False, timing_recorder: "TimingRecorder | None" = None):
25
+ try:
26
+ logger = logging.getLogger(__name__)
27
+ # Resolve work_folder: default to cwd for backward compatibility
28
+ resolved_work_folder = Path(work_folder) if work_folder is not None else Path.cwd()
29
+ # delete highs.log from previous run
30
+ highs_log = resolved_work_folder / "HiGHS.log"
31
+ if highs_log.exists():
32
+ highs_log.unlink()
33
+ # make a directory for solve data
34
+ (resolved_work_folder / "solve_data").mkdir(exist_ok=True)
35
+ # Build PathConfig — defaults are PyPI-friendly: the
36
+ # ``flextool`` package directory for static data, CWD for
37
+ # outputs and user-editable ``solver_config/highs.opt``.
38
+ from flextool._resources import package_data_path
39
+ _pkg_dir = package_data_path("")
40
+ paths = PathConfig(
41
+ flextool_dir=Path(flextool_dir) if flextool_dir is not None else _pkg_dir,
42
+ solver_config_dir=Path(solver_config_dir) if solver_config_dir is not None else Path.cwd() / "solver_config",
43
+ root_dir=Path(root_dir) if root_dir is not None else Path.cwd(),
44
+ output_path=Path(output_path) if output_path is not None else Path.cwd(),
45
+ work_folder=resolved_work_folder,
46
+ )
47
+ # read the data in
48
+ # open connection to input db
49
+ if scenario_name:
50
+ scen_config = api.filters.scenario_filter.scenario_filter_config(scenario_name)
51
+ with (DatabaseMapping(input_db_url) as db):
52
+ if scenario_name:
53
+ api.filters.scenario_filter.scenario_filter_from_dict(db, scen_config)
54
+ else:
55
+ scen_names = db.get_scenario_items()
56
+ if len(scen_names) == 0:
57
+ message = "No scenario found"
58
+ logger.error(message)
59
+ raise FlexToolConfigError(message)
60
+ scenario_name=scen_names[0]['name']
61
+ # The Work dir / DB URL / Scenario / Output header is
62
+ # now printed once by ``cmd_run_flextool.main`` before
63
+ # the cascade starts. The previous multi-line INFO
64
+ # here duplicated the Scenario line and split related
65
+ # metadata across the startup log; drop it.
66
+ if len(db.get_scenario_alternative_items(scenario_name=scenario_name)) == 0:
67
+ message = "No alternatives in the scenario, i.e. empty scenario."
68
+ logger.error(message)
69
+ raise FlexToolConfigError(message)
70
+
71
+ # Pre-warm both entity and parameter_value caches so
72
+ # every find_* call downstream (SolveConfig, TimelineConfig,
73
+ # input_writer) hits memory. Adding fetch_all("entity")
74
+ # matches the spinedb-api docs' performance guidance and
75
+ # mirrors the engine_polars _solve_config / _timeline
76
+ # load_from_db_url paths.
77
+ db.fetch_all("entity")
78
+ db.fetch_all("parameter_value")
79
+ check_version(db=db, logger=logger)
80
+ # Solve-level fields — delegated to SolveConfig
81
+ solve = SolveConfig.load_from_db(db=db, logger=logger)
82
+ # Timeline-level fields — delegated to TimelineConfig
83
+ timeline = TimelineConfig.load_from_db(db=db, logger=logger)
84
+
85
+ # Post-DB initialization of timeline
86
+ timeline.create_assumptive_parts(solve)
87
+ timeline.create_timeline_from_timestep_duration(solve)
88
+
89
+ # Assemble RunnerState — the single cross-cutting state container
90
+ self.state = RunnerState(
91
+ paths=paths, solve=solve, timeline=timeline, logger=logger,
92
+ )
93
+ # HiGHS thread count (CLI override; solver_runner defaults to 4 when None).
94
+ self.state.highs_threads = highs_threads
95
+ # Agent 8 (LP-scaling) — opt-in flag that lets the Python
96
+ # ScaleAnalyzer enable row scaling. Batch C.10 removed
97
+ # the DB-stored ``use_row_scaling`` setting; the per-solve
98
+ # row-scaling toggle is now driven by ``--scaling`` CLI +
99
+ # this auto-scale flag (or the FLEXTOOL_FORCE_ROW_SCALING
100
+ # env-var test hook). Analysis itself runs unconditionally
101
+ # (writes JSON); this flag only gates auto-application.
102
+ self.state.auto_scale = auto_scale
103
+ # Phase-timing recorder. The CLI constructs one earlier and
104
+ # passes it in; direct callers (tests) get a fresh recorder
105
+ # bootstrapped here so timings.csv coverage is consistent
106
+ # across both entry points. The recorder always lives on
107
+ # ``state.timing_recorder``.
108
+ if timing_recorder is not None:
109
+ self.state.timing_recorder = timing_recorder
110
+ # Late-bind scenario when the CLI couldn't pass it in.
111
+ if scenario_name and not timing_recorder.scenario:
112
+ timing_recorder.set_scenario(scenario_name)
113
+ elif self.state.timing_recorder is None:
114
+ self.state.timing_recorder = TimingRecorder(
115
+ work_folder=resolved_work_folder,
116
+ scenario=scenario_name,
117
+ )
118
+ except FlexToolError:
119
+ sys.exit(-1)
120
+
121
+ def write_input(
122
+ self,
123
+ input_db_url,
124
+ scenario_name=None,
125
+ precision_digits: int = 0,
126
+ *,
127
+ provider=None,
128
+ ) -> None:
129
+ """Write input/ CSVs to the runner's workdir.
130
+
131
+ Used by the regional-decomposition wrapper and a handful of
132
+ debug callers that want a freshly-staged ``input/`` directory.
133
+ When *provider* is None an ephemeral
134
+ :class:`flextool.engine_polars._flex_data_provider.FlexDataProvider`
135
+ is constructed; the cascade itself constructs its own Provider in
136
+ ``_drive_cascade``.
137
+ """
138
+ if provider is None:
139
+ from flextool.engine_polars._flex_data_provider import FlexDataProvider
140
+ provider = FlexDataProvider()
141
+ _input_derivation_run(
142
+ input_db_url,
143
+ provider,
144
+ self.state.logger,
145
+ scenario_name=scenario_name,
146
+ work_folder=self.state.paths.work_folder,
147
+ precision_digits=precision_digits,
148
+ )
149
+ # Persist the cascade-input Provider on ``self.state`` so the
150
+ # native cascade picks up the seeded ``input/<class>`` frames.
151
+ # Mirrors the same handoff that
152
+ # ``engine_polars._orchestration.run_orchestration`` performs.
153
+ self.state.cascade_input_provider = provider
@@ -0,0 +1,127 @@
1
+ """
2
+ db_reader — Pure functions for reading from spinedb_api.
3
+ No state, no side effects. All functions take explicit parameters.
4
+ """
5
+ import logging
6
+ from collections import defaultdict
7
+ from enum import Enum
8
+
9
+ import spinedb_api as api
10
+
11
+ from flextool.engine_polars._solve_state import FlexToolConfigError
12
+
13
+
14
+ class DictMode(Enum):
15
+ DICT = "dict"
16
+ DEFAULTDICT = "defaultdict"
17
+ LIST = "list"
18
+
19
+
20
+ def check_version(db: api.DatabaseMapping, logger: logging.Logger) -> None:
21
+ """Verify that the input database version is compatible with this tool."""
22
+ db_version_item = db.get_parameter_definition_item(entity_class_name="model", name="version")
23
+ if not db_version_item:
24
+ message = "No version information found in the FlexTool input database, check you have a correct database."
25
+ logger.error(message)
26
+ raise FlexToolConfigError(message)
27
+ database_version = api.from_database(db_version_item["default_value"], db_version_item["default_type"])
28
+ tool_version = 25.0
29
+ if float(database_version) < tool_version:
30
+ message = (
31
+ "The input database is in an older version than the tool.\n"
32
+ "Please migrate the database to the new version:\n"
33
+ "- Make sure FlexTool python environment is activated\n"
34
+ "- cd to flextool directory\n"
35
+ "- run command: python migrate_database.py path_to_database\n"
36
+ "where path_to_database is replaced by the filepath of your current input database"
37
+ )
38
+ logger.error(message)
39
+ raise FlexToolConfigError(message)
40
+
41
+
42
+ def get_single_entities(db: api.DatabaseMapping, entity_class_name: str) -> list[str]:
43
+ """Return a list of entity names for a single-dimension entity class."""
44
+ return [entity["entity_byname"][0] for entity in db.find_entities(entity_class_name=entity_class_name)]
45
+
46
+
47
+ def entities_to_dict(
48
+ db: api.DatabaseMapping, cl: str, mode: DictMode
49
+ ) -> dict | defaultdict:
50
+ """
51
+ Read multi-dimension entities of class *cl* and return as a dict.
52
+
53
+ mode="defaultdict" → defaultdict(list)
54
+ mode="dict" → plain dict
55
+ Each entity maps entity_byname[0] → list of remaining byname elements.
56
+ """
57
+ entities = db.find_entities(entity_class_name=cl)
58
+ if mode == DictMode.DEFAULTDICT:
59
+ result: dict | defaultdict = defaultdict(list)
60
+ else:
61
+ result = dict()
62
+ for entity in entities:
63
+ if len(entity["entity_byname"]) > 1:
64
+ result[entity["entity_byname"][0]] = list(entity["entity_byname"][1:])
65
+ else:
66
+ raise ValueError(
67
+ "Only one dimension in the entity, cannot make into a dict in entities_to_dict"
68
+ )
69
+ return result
70
+
71
+
72
+ def params_to_dict(
73
+ db: api.DatabaseMapping,
74
+ cl: str,
75
+ par: str,
76
+ mode: DictMode,
77
+ str_to_list: bool = False,
78
+ ) -> dict | defaultdict | list:
79
+ """
80
+ Read parameter values of *par* on entity class *cl* and return as a
81
+ dict, defaultdict, or list depending on *mode*.
82
+
83
+ mode="defaultdict" → defaultdict(list)
84
+ mode="dict" → plain dict
85
+ mode="list" → list of [entity_name, value] pairs
86
+ str_to_list → wrap string values in a list (only for dict/defaultdict modes)
87
+ """
88
+ all_params = db.find_parameter_values(
89
+ entity_class_name=cl, parameter_definition_name=par
90
+ )
91
+ if mode == DictMode.DEFAULTDICT:
92
+ result: dict | defaultdict | list = defaultdict(list)
93
+ elif mode == DictMode.DICT:
94
+ result = dict()
95
+ elif mode == DictMode.LIST:
96
+ result = []
97
+ for param in all_params:
98
+ param_value = api.from_database(param["value"], param["type"])
99
+ if mode in (DictMode.DEFAULTDICT, DictMode.DICT):
100
+ if isinstance(param_value, api.Map):
101
+ if isinstance(param_value.values[0], float):
102
+ result[param["entity_name"]] = list(
103
+ zip(list(param_value.indexes), list(map(float, param_value.values)))
104
+ )
105
+ elif isinstance(param_value.values[0], str):
106
+ result[param["entity_name"]] = list(
107
+ zip(list(param_value.indexes), param_value.values)
108
+ )
109
+ elif isinstance(param_value.values[0], api.Map):
110
+ result[param["entity_name"]] = api.convert_map_to_table(param_value)
111
+ else:
112
+ raise TypeError(
113
+ "params_to_dict function does not handle other values than floats and strings"
114
+ )
115
+ elif isinstance(param_value, api.Array):
116
+ result[param["entity_name"]] = param_value.values
117
+ elif isinstance(param_value, float):
118
+ result[param["entity_name"]] = str(param_value)
119
+ elif isinstance(param_value, str):
120
+ if str_to_list:
121
+ result[param["entity_name"]] = [param_value]
122
+ else:
123
+ result[param["entity_name"]] = param_value
124
+ elif mode == DictMode.LIST:
125
+ if isinstance(param_value, (float, str)):
126
+ result.append([param["entity_name"], param_value]) # type: ignore[union-attr]
127
+ return result