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,107 @@
1
+ """FlexTool LP autoscaler — public API surface.
2
+
3
+ After Phase R2 the four-range Layer 1 detector and the HiGHS-native
4
+ Layer 3 recommendation live in :mod:`polar_high.autoscale`. FlexTool
5
+ re-exports them here so the rest of the engine_polars code (and any
6
+ external caller) sees a single stable surface. Layer 2 (semantic
7
+ per-quantity scaling, gated on ``ScalingMode.FULL``) stays FlexTool-side
8
+ because it depends on FlexTool's parameter taxonomy.
9
+
10
+ Three layers, run around the polar-high ``Problem``:
11
+
12
+ * **Layer 1 (detect, polar-high):** read the four standard LP
13
+ coefficient ranges (Matrix / Cost / Bound / RHS) and decide whether
14
+ the model needs scaling at all. Detection only — no LP modification.
15
+ * **Layer 2 (semantic per-type scaling, FlexTool):** apply column
16
+ scalers chosen by ``QuantityType`` so each physical quantity (power,
17
+ energy, price, …) lives on its own well-conditioned magnitude. Fires
18
+ only when ``mode == ScalingMode.FULL``.
19
+ * **Layer 3 (HiGHS-native + bound-scale escape, polar-high):** set
20
+ ``user_bound_scale`` / ``user_objective_scale`` / ``simplex_scale_strategy``
21
+ to neutralise residual range pressure inside HiGHS. Honours
22
+ precedence so a caller-set option survives.
23
+ """
24
+ from polar_high.autoscale import (
25
+ USER_SCALE_CLAMP_HI,
26
+ USER_SCALE_CLAMP_LO,
27
+ Layer3Plan,
28
+ RangeReport,
29
+ ScalingConfig,
30
+ ScalingMode,
31
+ apply_scaling,
32
+ detect_ranges,
33
+ get_explicit_option,
34
+ has_explicit_option,
35
+ mode_enables_layer1,
36
+ mode_enables_layer3,
37
+ ranges_from_arrays,
38
+ ranges_from_streamed,
39
+ recommend_scaling,
40
+ )
41
+
42
+ from ._config import (
43
+ USER_BOUND_SCALE_MAX,
44
+ USER_BOUND_SCALE_MIN,
45
+ resolve_scaling_config,
46
+ resolve_user_bound_scale_override,
47
+ )
48
+ from ._layer2 import (
49
+ Layer2Plan,
50
+ apply_layer2,
51
+ apply_layer2_with_exponents,
52
+ bucket_coefficients,
53
+ choose_scale_powers,
54
+ unscale_solution,
55
+ )
56
+ from ._layer2_types import (
57
+ CONSTRAINT_FAMILIES,
58
+ CstrFamily,
59
+ VARIABLE_FAMILIES,
60
+ VarFamily,
61
+ lookup_cstr,
62
+ lookup_var,
63
+ resolve_cstr_rhs_type,
64
+ )
65
+ from ._quantity_types import QuantityType, lookup, resolve_group_capacity_type
66
+ from ._report import format_console_summary, format_nonoptimal_hint, write_report
67
+
68
+ __all__ = [
69
+ "CONSTRAINT_FAMILIES",
70
+ "CstrFamily",
71
+ "Layer2Plan",
72
+ "Layer3Plan",
73
+ "QuantityType",
74
+ "RangeReport",
75
+ "ScalingConfig",
76
+ "ScalingMode",
77
+ "USER_BOUND_SCALE_MAX",
78
+ "USER_BOUND_SCALE_MIN",
79
+ "USER_SCALE_CLAMP_HI",
80
+ "USER_SCALE_CLAMP_LO",
81
+ "VARIABLE_FAMILIES",
82
+ "VarFamily",
83
+ "apply_layer2",
84
+ "apply_layer2_with_exponents",
85
+ "apply_scaling",
86
+ "bucket_coefficients",
87
+ "choose_scale_powers",
88
+ "detect_ranges",
89
+ "format_console_summary",
90
+ "format_nonoptimal_hint",
91
+ "get_explicit_option",
92
+ "has_explicit_option",
93
+ "lookup",
94
+ "lookup_cstr",
95
+ "lookup_var",
96
+ "mode_enables_layer1",
97
+ "mode_enables_layer3",
98
+ "ranges_from_arrays",
99
+ "ranges_from_streamed",
100
+ "recommend_scaling",
101
+ "resolve_cstr_rhs_type",
102
+ "resolve_group_capacity_type",
103
+ "resolve_scaling_config",
104
+ "resolve_user_bound_scale_override",
105
+ "unscale_solution",
106
+ "write_report",
107
+ ]
@@ -0,0 +1,218 @@
1
+ """FlexTool-side configuration for the autoscaler.
2
+
3
+ After Phase R2 the autoscaler's policy primitives (``ScalingMode`` enum,
4
+ ``ScalingConfig`` dataclass, ``mode_enables_layer1`` / ``mode_enables_layer3``
5
+ predicates) live in :mod:`polar_high.autoscale._config`. This module
6
+ keeps the FlexTool-specific resolver — ``resolve_scaling_config`` — which
7
+ honours the ``--scaling`` CLI flag and the ``FLEXTOOL_SCALING`` env var.
8
+
9
+ Layer 2 (semantic per-quantity scaling) stays in FlexTool because it
10
+ needs FlexTool's parameter taxonomy. Layer 2 fires only when
11
+ ``mode == ScalingMode.FULL``.
12
+
13
+ The ``--user-bound-scale N`` CLI override still lives here too: when
14
+ set, it propagates onto ``ScalingConfig.user_bound_scale`` so Layer 3's
15
+ manual-override branch (in polar-high) fires. The legacy DB-side
16
+ ``solve.user_bound_scale`` field is normalised by
17
+ :func:`resolve_user_bound_scale_override`, kept for the cascade's
18
+ ``_baseline_highs_options`` wire-in.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import math
23
+ import os
24
+ from pathlib import Path
25
+ from typing import Any, Optional
26
+
27
+ from polar_high.autoscale import (
28
+ ScalingConfig,
29
+ ScalingMode,
30
+ )
31
+
32
+
33
+ # Clamp range for the HiGHS ``user_bound_scale`` exponent (power of 2). HiGHS
34
+ # itself accepts ``[-30, 30]`` (see ``polar_high.autoscale.USER_SCALE_CLAMP_*``);
35
+ # we cap at ``[-10, 0]`` for the DB-override coercion path because (a) positive
36
+ # scales are nearly always harmful — they expand already-large coefficients,
37
+ # and (b) ``2**-10`` ≈ ``1e-3`` is plenty to neutralise the LP shapes FlexTool
38
+ # produces. Centralised here so both the legacy DB-override coercion and
39
+ # the cascade-level ``user_bound_scale_override`` wire-in use the same bounds.
40
+ USER_BOUND_SCALE_MIN: int = -10
41
+ USER_BOUND_SCALE_MAX: int = 0
42
+
43
+
44
+ def _coerce_env_mode(raw: Optional[str], *, default: ScalingMode) -> ScalingMode:
45
+ """Parse ``FLEXTOOL_SCALING`` env flag.
46
+
47
+ Accepts the four enum values (``off``, ``solver_only``, ``basic``,
48
+ ``full``) case-insensitively. Empty / unrecognised values fall
49
+ through to ``default`` so a misconfigured env doesn't silently flip
50
+ behaviour.
51
+ """
52
+ if raw is None:
53
+ return default
54
+ v = raw.strip().lower()
55
+ for m in ScalingMode:
56
+ if v == m.value:
57
+ return m
58
+ return default
59
+
60
+
61
+ def _coerce_user_bound_scale(raw: Any) -> Optional[int]:
62
+ """Coerce a ``--user-bound-scale`` value to ``int | None``.
63
+
64
+ Accepts already-typed ``int`` / ``None``, as well as decimal strings
65
+ (the form ``cli_args`` typically carries them). Non-numeric strings
66
+ raise ``ValueError`` — the CLI surface should reject them; surfacing
67
+ the error here is louder than silently falling back to ``None``.
68
+ """
69
+ if raw is None:
70
+ return None
71
+ if isinstance(raw, int) and not isinstance(raw, bool):
72
+ return raw
73
+ if isinstance(raw, str):
74
+ s = raw.strip()
75
+ if not s:
76
+ return None
77
+ return int(s)
78
+ raise ValueError(
79
+ f"user_bound_scale must be int, decimal str, or None; got {raw!r}"
80
+ )
81
+
82
+
83
+ def _coerce_cli_scaling(raw: Any) -> Optional[ScalingMode]:
84
+ """Coerce an ``args.scaling`` value to ``ScalingMode | None``.
85
+
86
+ Accepts the four enum-value strings (``"off"`` / ``"solver_only"`` /
87
+ ``"basic"`` / ``"full"``), an already-typed ``ScalingMode``, and
88
+ ``None`` (CLI unset → fall through to env / default). Unrecognised
89
+ values raise so a typo surfaces loudly rather than silently picking
90
+ a different policy.
91
+ """
92
+ if raw is None:
93
+ return None
94
+ if isinstance(raw, ScalingMode):
95
+ return raw
96
+ if isinstance(raw, str):
97
+ v = raw.strip().lower()
98
+ if not v:
99
+ return None
100
+ for m in ScalingMode:
101
+ if v == m.value:
102
+ return m
103
+ raise ValueError(
104
+ f"scaling must be one of {[m.value for m in ScalingMode]!r}, "
105
+ f"ScalingMode, or None; got {raw!r}"
106
+ )
107
+ raise ValueError(
108
+ f"scaling must be a string, ScalingMode, or None; got {raw!r}"
109
+ )
110
+
111
+
112
+ def resolve_scaling_config(cli_args: Any = None) -> ScalingConfig:
113
+ """Build a :class:`ScalingConfig` from CLI args + environment.
114
+
115
+ Precedence for the mode is **CLI > env > default-full**:
116
+
117
+ * ``cli_args.scaling`` (``"off"`` / ``"solver_only"`` / ``"basic"``
118
+ / ``"full"`` / ``ScalingMode`` / ``None``) wins when set. The
119
+ ``cmd_run_flextool`` CLI defines this via ``--scaling`` and, when
120
+ set, also exports ``FLEXTOOL_SCALING`` so subprocess /
121
+ cascade-internal call sites that resolve config with
122
+ ``cli_args=None`` still observe the operator's choice.
123
+ * ``FLEXTOOL_SCALING`` env var (``off`` / ``solver_only`` /
124
+ ``basic`` / ``full``) provides the fallback for callers that
125
+ don't have access to the parsed ``args`` (e.g. cascade-internal
126
+ helpers, tests that pre-set the env).
127
+ * Default ``ScalingMode.FULL`` — the full autoscaler is on.
128
+
129
+ ``user_bound_scale`` follows the same CLI-wins-over-env layering:
130
+
131
+ * ``cli_args.user_bound_scale`` (int / decimal str / ``None``) wins.
132
+ * ``FLEXTOOL_USER_BOUND_SCALE`` env var (set by ``--user-bound-scale``
133
+ CLI) is the fallback.
134
+ * ``cli_args.autoscale_report_yaml`` overrides the default YAML
135
+ report path; otherwise ``_orchestration`` picks
136
+ ``work_folder/solve_data/autoscale_<solve>.yaml``.
137
+
138
+ Returns a frozen :class:`ScalingConfig` (from
139
+ :mod:`polar_high.autoscale`).
140
+ """
141
+ mode = _coerce_env_mode(
142
+ os.environ.get("FLEXTOOL_SCALING"), default=ScalingMode.FULL,
143
+ )
144
+ user_bound_scale_env = _coerce_user_bound_scale(
145
+ os.environ.get("FLEXTOOL_USER_BOUND_SCALE"),
146
+ )
147
+
148
+ user_bound_scale = user_bound_scale_env
149
+ report_yaml_path: Optional[Path] = None
150
+
151
+ if cli_args is not None:
152
+ cli_mode = _coerce_cli_scaling(getattr(cli_args, "scaling", None))
153
+ if cli_mode is not None:
154
+ mode = cli_mode
155
+ cli_ubs = getattr(cli_args, "user_bound_scale", None)
156
+ if cli_ubs is not None:
157
+ user_bound_scale = _coerce_user_bound_scale(cli_ubs)
158
+ cli_yaml = getattr(cli_args, "autoscale_report_yaml", None)
159
+ if cli_yaml is not None:
160
+ report_yaml_path = Path(cli_yaml)
161
+
162
+ return ScalingConfig(
163
+ mode=mode,
164
+ user_bound_scale=user_bound_scale,
165
+ report_yaml_path=report_yaml_path,
166
+ )
167
+
168
+
169
+ def resolve_user_bound_scale_override(
170
+ user_value: Any,
171
+ ) -> Optional[int]:
172
+ """Coerce a raw DB ``user_bound_scale`` value to a clamped int (or None).
173
+
174
+ Accepts ``None``, integers, floats, or numeric strings and returns:
175
+
176
+ * an integer in ``[USER_BOUND_SCALE_MIN, USER_BOUND_SCALE_MAX]`` when
177
+ the user provided a non-zero, finite, parseable value;
178
+ * ``None`` when the user provided no value, ``0``, or anything
179
+ unparseable — caller should then fall back to the heuristic (Layer 3's
180
+ automatic recommendation).
181
+
182
+ Truncates non-integer floats toward zero (e.g. ``-3.7`` → ``-3``) rather
183
+ than rounding, mirroring the conservative direction (smaller |N| is
184
+ gentler scaling).
185
+
186
+ This helper exists alongside :func:`_coerce_user_bound_scale` because the
187
+ two have different contracts: ``_coerce_user_bound_scale`` is for
188
+ CLI / env values where a non-numeric string is a user error worth
189
+ surfacing as ``ValueError``; this helper is for legacy DB values where a
190
+ malformed entry should fall back to the heuristic rather than crash a
191
+ cascade.
192
+ """
193
+ if user_value is None:
194
+ return None
195
+ try:
196
+ as_float = float(user_value)
197
+ except (TypeError, ValueError):
198
+ return None
199
+ if not math.isfinite(as_float):
200
+ return None
201
+ n = int(as_float) # truncates toward zero
202
+ if n == 0:
203
+ return None
204
+ if n > USER_BOUND_SCALE_MAX:
205
+ n = USER_BOUND_SCALE_MAX
206
+ if n < USER_BOUND_SCALE_MIN:
207
+ n = USER_BOUND_SCALE_MIN
208
+ return n
209
+
210
+
211
+ __all__ = [
212
+ "ScalingConfig",
213
+ "ScalingMode",
214
+ "USER_BOUND_SCALE_MAX",
215
+ "USER_BOUND_SCALE_MIN",
216
+ "resolve_scaling_config",
217
+ "resolve_user_bound_scale_override",
218
+ ]