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.
- flextool/__init__.py +41 -0
- flextool/_mem_sampler.py +193 -0
- flextool/_resources.py +43 -0
- flextool/calibrate/__init__.py +51 -0
- flextool/calibrate/__main__.py +11 -0
- flextool/calibrate/_cli.py +316 -0
- flextool/calibrate/_db_alt.py +166 -0
- flextool/calibrate/_final_outputs.py +110 -0
- flextool/calibrate/_guard.py +151 -0
- flextool/calibrate/_loop.py +558 -0
- flextool/calibrate/_readers.py +223 -0
- flextool/calibrate/_report.py +263 -0
- flextool/calibrate/_sizing.py +699 -0
- flextool/calibrate/_solve.py +134 -0
- flextool/calibrate/_solve_status.py +495 -0
- flextool/cli/__init__.py +9 -0
- flextool/cli/_console.py +51 -0
- flextool/cli/_timing.py +147 -0
- flextool/cli/cmd_execute_flextool_workflow.py +187 -0
- flextool/cli/cmd_export_to_tabular.py +56 -0
- flextool/cli/cmd_import_sensitivities.py +75 -0
- flextool/cli/cmd_migrate_database.py +13 -0
- flextool/cli/cmd_open_results_db.py +269 -0
- flextool/cli/cmd_read_matpower.py +66 -0
- flextool/cli/cmd_read_old_flextool.py +63 -0
- flextool/cli/cmd_read_self_describing_tabular_input.py +50 -0
- flextool/cli/cmd_read_tabular_input.py +81 -0
- flextool/cli/cmd_run_flextool.py +1095 -0
- flextool/cli/cmd_scenario_results.py +284 -0
- flextool/cli/cmd_solve_mps.py +169 -0
- flextool/cli/cmd_update_flextool.py +17 -0
- flextool/cli/cmd_write_outputs.py +125 -0
- flextool/common_utils/__init__.py +1 -0
- flextool/common_utils/plot_mem_shape.py +77 -0
- flextool/common_utils/precision.py +451 -0
- flextool/decomposition/__init__.py +0 -0
- flextool/decomposition/region_decomposition.py +128 -0
- flextool/decomposition/region_filter.py +1261 -0
- flextool/engine_polars/__init__.py +110 -0
- flextool/engine_polars/_axis_enums.py +742 -0
- flextool/engine_polars/_benders.py +3462 -0
- flextool/engine_polars/_block_layout.py +1479 -0
- flextool/engine_polars/_blocks.py +1515 -0
- flextool/engine_polars/_commodity_ladder.py +660 -0
- flextool/engine_polars/_cumulative_invest.py +1165 -0
- flextool/engine_polars/_db_loader.py +153 -0
- flextool/engine_polars/_db_reader.py +127 -0
- flextool/engine_polars/_dc_power_flow.py +445 -0
- flextool/engine_polars/_delay.py +442 -0
- flextool/engine_polars/_derived_arithmetic.py +432 -0
- flextool/engine_polars/_derived_block.py +990 -0
- flextool/engine_polars/_derived_branch.py +769 -0
- flextool/engine_polars/_derived_existing.py +1353 -0
- flextool/engine_polars/_derived_npv.py +1297 -0
- flextool/engine_polars/_derived_params.py +9850 -0
- flextool/engine_polars/_derived_profile.py +881 -0
- flextool/engine_polars/_derived_walks.py +276 -0
- flextool/engine_polars/_determinism.py +70 -0
- flextool/engine_polars/_direct_params.py +2186 -0
- flextool/engine_polars/_dump_csvs.py +1009 -0
- flextool/engine_polars/_emit_arc_unions.py +1631 -0
- flextool/engine_polars/_emit_calc_params.py +729 -0
- flextool/engine_polars/_emit_chain_params.py +709 -0
- flextool/engine_polars/_emit_co2_accumulators.py +400 -0
- flextool/engine_polars/_emit_dispatchers.py +690 -0
- flextool/engine_polars/_emit_energy_margin.py +125 -0
- flextool/engine_polars/_emit_energy_margin_adder.py +290 -0
- flextool/engine_polars/_emit_entity_annual.py +428 -0
- flextool/engine_polars/_emit_inflow_scaling.py +1420 -0
- flextool/engine_polars/_emit_leaf_sets.py +550 -0
- flextool/engine_polars/_emit_lp_scaling.py +665 -0
- flextool/engine_polars/_emit_mid_sets.py +859 -0
- flextool/engine_polars/_emit_pdt_params.py +759 -0
- flextool/engine_polars/_emit_per_solve.py +774 -0
- flextool/engine_polars/_emit_period_calc.py +504 -0
- flextool/engine_polars/_emit_period_params.py +2398 -0
- flextool/engine_polars/_emit_provider_io.py +141 -0
- flextool/engine_polars/_emit_reserve.py +574 -0
- flextool/engine_polars/_emit_solve_time.py +311 -0
- flextool/engine_polars/_emit_solve_writers.py +1249 -0
- flextool/engine_polars/_flex_data_accumulator.py +388 -0
- flextool/engine_polars/_flex_data_provider.py +478 -0
- flextool/engine_polars/_group_slack.py +1253 -0
- flextool/engine_polars/_inmemory_reader.py +140 -0
- flextool/engine_polars/_input_source.py +336 -0
- flextool/engine_polars/_invest_seeds.py +191 -0
- flextool/engine_polars/_native_input_writer.py +100 -0
- flextool/engine_polars/_native_run_model.py +1348 -0
- flextool/engine_polars/_orchestration.py +4314 -0
- flextool/engine_polars/_output_writer.py +439 -0
- flextool/engine_polars/_param_shapes.py +1595 -0
- flextool/engine_polars/_parquet_bundle.py +723 -0
- flextool/engine_polars/_pdt_join.py +167 -0
- flextool/engine_polars/_pdt_lookup.py +547 -0
- flextool/engine_polars/_per_solve_sets.py +335 -0
- flextool/engine_polars/_projection_params.py +2056 -0
- flextool/engine_polars/_provider_keys.py +173 -0
- flextool/engine_polars/_provider_translators.py +225 -0
- flextool/engine_polars/_recursive_solve.py +703 -0
- flextool/engine_polars/_region_filter.py +2508 -0
- flextool/engine_polars/_reserve.py +649 -0
- flextool/engine_polars/_solve_acceptance.py +331 -0
- flextool/engine_polars/_solve_config.py +1001 -0
- flextool/engine_polars/_solve_context.py +885 -0
- flextool/engine_polars/_solve_handoff.py +164 -0
- flextool/engine_polars/_solve_state.py +232 -0
- flextool/engine_polars/_solver_base.py +36 -0
- flextool/engine_polars/_solver_dispatch.py +511 -0
- flextool/engine_polars/_spinedb_reader.py +1165 -0
- flextool/engine_polars/_stochastic.py +593 -0
- flextool/engine_polars/_subprocess_solve.py +1838 -0
- flextool/engine_polars/_timeline.py +1416 -0
- flextool/engine_polars/_vectorize.py +438 -0
- flextool/engine_polars/_warm.py +858 -0
- flextool/engine_polars/autoscale/__init__.py +107 -0
- flextool/engine_polars/autoscale/_config.py +218 -0
- flextool/engine_polars/autoscale/_layer2.py +1253 -0
- flextool/engine_polars/autoscale/_layer2_types.py +584 -0
- flextool/engine_polars/autoscale/_quantity_types.py +621 -0
- flextool/engine_polars/autoscale/_report.py +336 -0
- flextool/engine_polars/chain.py +259 -0
- flextool/engine_polars/input.py +6638 -0
- flextool/engine_polars/model.py +4754 -0
- flextool/env_check.py +388 -0
- flextool/export_to_tabular/__init__.py +5 -0
- flextool/export_to_tabular/db_reader.py +224 -0
- flextool/export_to_tabular/excel_writer.py +3559 -0
- flextool/export_to_tabular/export_settings.yaml +377 -0
- flextool/export_to_tabular/export_to_excel.py +227 -0
- flextool/export_to_tabular/formatting.py +543 -0
- flextool/export_to_tabular/sheet_config.py +876 -0
- flextool/gui/__init__.py +0 -0
- flextool/gui/__main__.py +118 -0
- flextool/gui/calibrate_commands.py +184 -0
- flextool/gui/calibrate_jobs.py +424 -0
- flextool/gui/check_tree.py +142 -0
- flextool/gui/cli_format.py +83 -0
- flextool/gui/config_parser.py +68 -0
- flextool/gui/data_models.py +362 -0
- flextool/gui/db_editor_integration.py +202 -0
- flextool/gui/db_version_check.py +269 -0
- flextool/gui/dialogs/__init__.py +0 -0
- flextool/gui/dialogs/add_dialog.py +1098 -0
- flextool/gui/dialogs/calibrate_dialog.py +1259 -0
- flextool/gui/dialogs/file_picker.py +473 -0
- flextool/gui/dialogs/group_picker.py +299 -0
- flextool/gui/dialogs/migration_consent_dialog.py +106 -0
- flextool/gui/dialogs/migration_progress_dialog.py +237 -0
- flextool/gui/dialogs/plot_dialog.py +459 -0
- flextool/gui/dialogs/plot_settings_picker.py +2184 -0
- flextool/gui/dialogs/project_dialog.py +426 -0
- flextool/gui/dialogs/update_dialog.py +212 -0
- flextool/gui/downsampling.py +88 -0
- flextool/gui/error_handling.py +50 -0
- flextool/gui/execution_manager.py +1715 -0
- flextool/gui/execution_window.py +1377 -0
- flextool/gui/hover_tooltip.py +111 -0
- flextool/gui/input_sources.py +730 -0
- flextool/gui/main_window.py +6181 -0
- flextool/gui/network_graph.py +215 -0
- flextool/gui/output_actions.py +393 -0
- flextool/gui/output_log_window.py +159 -0
- flextool/gui/platform_utils.py +421 -0
- flextool/gui/plot_cache.py +88 -0
- flextool/gui/plot_canvas.py +543 -0
- flextool/gui/plot_config_reader.py +272 -0
- flextool/gui/project_utils.py +100 -0
- flextool/gui/result_viewer.py +4394 -0
- flextool/gui/scenario_key.py +162 -0
- flextool/gui/scenario_lists.py +516 -0
- flextool/gui/settings_io.py +360 -0
- flextool/gui/solve_reader.py +103 -0
- flextool/gui/tree_reorder.py +88 -0
- flextool/gui/ui_metrics.py +420 -0
- flextool/input_derivation/__init__.py +281 -0
- flextool/input_derivation/_commodity_ladder.py +375 -0
- flextool/input_derivation/_commodity_ladder_sets.py +70 -0
- flextool/input_derivation/_dc_power_flow.py +377 -0
- flextool/input_derivation/_method_constants.py +77 -0
- flextool/input_derivation/_process_method.py +258 -0
- flextool/input_derivation/_specs.py +1026 -0
- flextool/input_derivation/_validators.py +321 -0
- flextool/lean_parquet.py +159 -0
- flextool/model_builder/__init__.py +5 -0
- flextool/model_builder/build_model.py +589 -0
- flextool/model_builder/encoding.py +67 -0
- flextool/model_builder/names.py +34 -0
- flextool/model_builder/profiles.py +129 -0
- flextool/plot_outputs/__init__.py +14 -0
- flextool/plot_outputs/axis_helpers.py +355 -0
- flextool/plot_outputs/color_template.py +888 -0
- flextool/plot_outputs/config.py +171 -0
- flextool/plot_outputs/format_helpers.py +345 -0
- flextool/plot_outputs/legend_helpers.py +143 -0
- flextool/plot_outputs/orchestrator.py +1141 -0
- flextool/plot_outputs/perf.py +37 -0
- flextool/plot_outputs/plan.py +1787 -0
- flextool/plot_outputs/plot_bars.py +1510 -0
- flextool/plot_outputs/plot_bars_detail.py +753 -0
- flextool/plot_outputs/plot_lines.py +951 -0
- flextool/plot_outputs/shared_manifest.py +564 -0
- flextool/plot_outputs/subplot_helpers.py +137 -0
- flextool/process_inputs/__init__.py +188 -0
- flextool/process_inputs/import_old_excel_input.json +4159 -0
- flextool/process_inputs/read_matpower.py +451 -0
- flextool/process_inputs/read_old_flextool.py +1288 -0
- flextool/process_inputs/read_self_describing_excel.py +1423 -0
- flextool/process_inputs/read_tabular_with_specification.py +1114 -0
- flextool/process_inputs/write_old_flextool_to_db.py +3077 -0
- flextool/process_inputs/write_self_describing_to_db.py +977 -0
- flextool/process_inputs/write_to_input_db.py +269 -0
- flextool/process_outputs/__init__.py +7 -0
- flextool/process_outputs/_annualize.py +55 -0
- flextool/process_outputs/_inmemory_helpers.py +292 -0
- flextool/process_outputs/_output_meta.py +672 -0
- flextool/process_outputs/calc_capacity_flows.py +107 -0
- flextool/process_outputs/calc_connections.py +136 -0
- flextool/process_outputs/calc_costs.py +260 -0
- flextool/process_outputs/calc_group_flows.py +192 -0
- flextool/process_outputs/calc_slacks.py +103 -0
- flextool/process_outputs/calc_storage_vre.py +160 -0
- flextool/process_outputs/drop_levels.py +208 -0
- flextool/process_outputs/handoff_writers.py +1315 -0
- flextool/process_outputs/out_ancillary.py +544 -0
- flextool/process_outputs/out_capacity.py +179 -0
- flextool/process_outputs/out_costs.py +334 -0
- flextool/process_outputs/out_flowgroup.py +189 -0
- flextool/process_outputs/out_flows.py +301 -0
- flextool/process_outputs/out_group.py +475 -0
- flextool/process_outputs/out_node.py +190 -0
- flextool/process_outputs/persist_realized_slice.py +601 -0
- flextool/process_outputs/process_results.py +24 -0
- flextool/process_outputs/read_highs_solution.py +2256 -0
- flextool/process_outputs/read_parameters.py +1799 -0
- flextool/process_outputs/read_sets.py +1095 -0
- flextool/process_outputs/read_variables.py +553 -0
- flextool/process_outputs/solve_order.py +81 -0
- flextool/process_outputs/spinedb_replay.py +412 -0
- flextool/process_outputs/union_realized_slice.py +224 -0
- flextool/process_outputs/write_outputs.py +1286 -0
- flextool/process_outputs/write_spinedb.py +1267 -0
- flextool/representative_periods/__init__.py +5 -0
- flextool/representative_periods/clustering.py +165 -0
- flextool/representative_periods/force_include.py +563 -0
- flextool/representative_periods/netload.py +365 -0
- flextool/representative_periods/netload_inputs.py +345 -0
- flextool/representative_periods/netload_iterate.py +722 -0
- flextool/representative_periods/preprocess.py +948 -0
- flextool/representative_periods/scenario_stack.py +195 -0
- flextool/representative_periods/weights.py +124 -0
- flextool/scenario_comparison/__init__.py +13 -0
- flextool/scenario_comparison/config_builder.py +158 -0
- flextool/scenario_comparison/constants.py +20 -0
- flextool/scenario_comparison/data_models.py +222 -0
- flextool/scenario_comparison/db_reader.py +399 -0
- flextool/scenario_comparison/dispatch_data.py +1002 -0
- flextool/scenario_comparison/dispatch_mappings.py +205 -0
- flextool/scenario_comparison/dispatch_plots.py +691 -0
- flextool/scenario_comparison/input_entity_colors.py +319 -0
- flextool/scenario_comparison/orchestrator.py +453 -0
- flextool/scenario_comparison/plan_union.py +244 -0
- flextool/scenario_comparison/plot_settings_seed.py +205 -0
- flextool/schemas/AXIS_CONTRACT.md +71 -0
- flextool/schemas/canonical_databases/howto_aggregate_output.json +6225 -0
- flextool/schemas/canonical_databases/howto_connections.json +5606 -0
- flextool/schemas/canonical_databases/howto_demand.json +5518 -0
- flextool/schemas/canonical_databases/howto_hydro_reservoir.json +6239 -0
- flextool/schemas/canonical_databases/howto_hydro_reservoir_with_pump.json +5933 -0
- flextool/schemas/canonical_databases/howto_non_sync_and_curtailment.json +5794 -0
- flextool/schemas/canonical_databases/howto_ramp_and_start_up.json +5707 -0
- flextool/schemas/canonical_databases/howto_stochastics.json +6032 -0
- flextool/schemas/canonical_databases/templates_examples.json +13532 -0
- flextool/schemas/canonical_databases/templates_time_settings_only.json +5340 -0
- flextool/schemas/comparison_settings_template.json +197 -0
- flextool/schemas/default_plot_settings.yaml +260 -0
- flextool/schemas/default_plots.yaml +2293 -0
- flextool/schemas/flextool_axis_contract.json +303 -0
- flextool/schemas/flextool_axis_contract.schema.json +247 -0
- flextool/schemas/old_flextool_import_template.json +4443 -0
- flextool/schemas/output_info_template.json +48 -0
- flextool/schemas/output_settings_template.json +256 -0
- flextool/schemas/pre_v26/flextool_template_constant_default.json +2105 -0
- flextool/schemas/pre_v26/flextool_template_default_optional_output.json +2152 -0
- flextool/schemas/pre_v26/flextool_template_default_value.json +2094 -0
- flextool/schemas/pre_v26/flextool_template_drop_down.json +2080 -0
- flextool/schemas/pre_v26/flextool_template_lifetime_method.json +1990 -0
- flextool/schemas/pre_v26/flextool_template_optional_outputs.json +2094 -0
- flextool/schemas/pre_v26/flextool_template_output_node_flows.json +2105 -0
- flextool/schemas/pre_v26/flextool_template_results_master.json +493 -0
- flextool/schemas/pre_v26/flextool_template_rolling_start_remove.json +2087 -0
- flextool/schemas/pre_v26/flextool_template_rolling_window.json +2059 -0
- flextool/schemas/pre_v26/flextool_template_storage_binding_defaults.json +46 -0
- flextool/schemas/pre_v26/flextool_template_v2.json +1990 -0
- flextool/schemas/pre_v26/flextool_template_v25.json +3864 -0
- flextool/schemas/spinedb_results_schema.json +581 -0
- flextool/schemas/spinedb_schema.json +4636 -0
- flextool/solver_config/copt.opt.template +18 -0
- flextool/solver_config/cplex.opt.template +25 -0
- flextool/solver_config/gurobi.opt.template +18 -0
- flextool/solver_config/highs.opt.template +18 -0
- flextool/solver_config/xpress.opt.template +26 -0
- flextool/spinedb_backend/__init__.py +26 -0
- flextool/spinedb_backend/_axis_enums.py +1119 -0
- flextool/spinedb_backend/_backend.py +1139 -0
- flextool/update_flextool/__init__.py +12 -0
- flextool/update_flextool/canonical_databases.py +251 -0
- flextool/update_flextool/db_migration.py +7108 -0
- flextool/update_flextool/ensure_settings_db.py +138 -0
- flextool/update_flextool/export_database.py +103 -0
- flextool/update_flextool/extend_tests_fixture.py +772 -0
- flextool/update_flextool/generate_canonical.py +274 -0
- flextool/update_flextool/initialize_database.py +42 -0
- flextool/update_flextool/install_info.py +225 -0
- flextool/update_flextool/self_update.py +464 -0
- flextool/update_flextool/sync_master_json_template.py +125 -0
- flextool/update_flextool/test_fixtures.py +187 -0
- flextool-4.0.0.dist-info/METADATA +217 -0
- flextool-4.0.0.dist-info/RECORD +322 -0
- flextool-4.0.0.dist-info/WHEEL +5 -0
- flextool-4.0.0.dist-info/entry_points.txt +17 -0
- flextool-4.0.0.dist-info/licenses/LICENSE.txt +19 -0
- flextool-4.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
"""Layer 2 (semantic per-type scaling) family registries.
|
|
2
|
+
|
|
3
|
+
This module declares, for every variable and constraint family FlexTool
|
|
4
|
+
emits, the :class:`QuantityType` that determines its Layer-2 scaling
|
|
5
|
+
group.
|
|
6
|
+
|
|
7
|
+
The registry is populated by walking every ``add_var(...)`` and
|
|
8
|
+
``add_cstr(...)`` call site in ``flextool/engine_polars/`` (see comments
|
|
9
|
+
on each entry). An unregistered name raises :class:`KeyError` at
|
|
10
|
+
lookup time — Layer 2 refuses to silently default, on principle (see
|
|
11
|
+
``feedback_no_shortcuts`` user memory).
|
|
12
|
+
|
|
13
|
+
Per :class:`CstrFamily` semantics:
|
|
14
|
+
|
|
15
|
+
* ``rhs_type=None`` is a sentinel for *user-supplied composite-LHS
|
|
16
|
+
constraints* (the ``process_constraint_*`` / ``node_balance_fix_*``
|
|
17
|
+
family) and the small number of structural rows whose RHS is
|
|
18
|
+
identically zero (``dc_flow_eq``, ``fix_v_invest_no_investment_eq``)
|
|
19
|
+
— Layer 2 has no per-row scaling to apply there. The column scaler
|
|
20
|
+
on each LHS variable still propagates through these rows.
|
|
21
|
+
* ``member_class_resolver="group_capacity"`` is the trigger for
|
|
22
|
+
:func:`resolve_group_capacity_type` — the constraint name itself is
|
|
23
|
+
``[MW or MWh]`` ambiguous (group invest / divest / cumulative
|
|
24
|
+
capacity); apply-time inspects the constraint suffix to pick POWER
|
|
25
|
+
vs ENERGY.
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
from typing import Optional
|
|
31
|
+
|
|
32
|
+
from ._quantity_types import QuantityType
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class VarFamily:
|
|
37
|
+
"""Per-variable Layer-2 metadata.
|
|
38
|
+
|
|
39
|
+
Attributes
|
|
40
|
+
----------
|
|
41
|
+
column_type:
|
|
42
|
+
:class:`QuantityType` of one unit of the variable as it appears
|
|
43
|
+
in the LP **column** (before any per-row coefficient
|
|
44
|
+
multiplication). Layer 2 uses this to pick the column scaler.
|
|
45
|
+
multiplier_param:
|
|
46
|
+
Name of the parameter (e.g. ``"p_unitsize"``) whose magnitude
|
|
47
|
+
the column scaler is *expected* to absorb so the matrix entry
|
|
48
|
+
``A_{ij} = multiplier * coef`` lands near 1. ``None`` if no
|
|
49
|
+
such single dominant multiplier applies. Informational; the
|
|
50
|
+
Layer-2 bucketing in :func:`_layer2.bucket_coefficients`
|
|
51
|
+
derives factors from observed magnitudes, not from this hint.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
column_type: QuantityType
|
|
55
|
+
multiplier_param: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class CstrFamily:
|
|
60
|
+
"""Per-constraint Layer-2 metadata.
|
|
61
|
+
|
|
62
|
+
Attributes
|
|
63
|
+
----------
|
|
64
|
+
rhs_type:
|
|
65
|
+
:class:`QuantityType` of the RHS / row-bound vector for this
|
|
66
|
+
family. Layer 2 uses this to pick the row scaler. ``None``
|
|
67
|
+
explicitly opts out of per-row scaling (user-defined
|
|
68
|
+
composite-LHS constraints; zero-RHS structural rows).
|
|
69
|
+
member_class_resolver:
|
|
70
|
+
When set to ``"group_capacity"``, Layer 2 ignores ``rhs_type``
|
|
71
|
+
and calls :func:`resolve_group_capacity_type` with the
|
|
72
|
+
constraint's suffix (``_p`` / ``_n``) to choose POWER vs ENERGY
|
|
73
|
+
at apply time. Used by the
|
|
74
|
+
``maxInvestGroup_entity_*`` / ``maxDivestGroup_entity_*`` /
|
|
75
|
+
``maxInvest_entity_total*`` / ``maxDivest_entity_total*`` /
|
|
76
|
+
``maxCumulative_capacity`` / ``minCumulative_capacity`` /
|
|
77
|
+
``minInvest_entity_total*`` / ``minDivest_entity_total*``
|
|
78
|
+
families that carry ``[MW or MWh]`` group-capacity RHS params.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
rhs_type: Optional[QuantityType]
|
|
82
|
+
member_class_resolver: Optional[str] = None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
# Variable families.
|
|
87
|
+
#
|
|
88
|
+
# The Layer-2 ``column_type`` is the type of one unit of the LP column
|
|
89
|
+
# variable itself. Matrix-entry physical units are
|
|
90
|
+
# ``column_type × multiplier_param`` (e.g. v_flow's column is
|
|
91
|
+
# DIMENSIONLESS; its row coefficient is ``p_unitsize`` → POWER on the
|
|
92
|
+
# row). Bucketing in Layer 2 uses the *effective* type per (row, col)
|
|
93
|
+
# pair, not the column type alone.
|
|
94
|
+
|
|
95
|
+
VARIABLE_FAMILIES: dict[str, VarFamily] = {
|
|
96
|
+
# ── Process / connection flow (dispatched per timestep).
|
|
97
|
+
# flextool/engine_polars/model.py:482
|
|
98
|
+
"v_flow": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
99
|
+
|
|
100
|
+
# ── Storage / nodeBalance slacks. Carry ENERGY directly (no
|
|
101
|
+
# unitsize multiplier on the column).
|
|
102
|
+
# flextool/engine_polars/model.py:484
|
|
103
|
+
"vq_state_up": VarFamily(QuantityType.ENERGY),
|
|
104
|
+
# flextool/engine_polars/model.py:485
|
|
105
|
+
"vq_state_down": VarFamily(QuantityType.ENERGY),
|
|
106
|
+
|
|
107
|
+
# ── Storage state (per-step, per-block). ENERGY column.
|
|
108
|
+
# flextool/engine_polars/model.py:490
|
|
109
|
+
"v_state": VarFamily(QuantityType.ENERGY),
|
|
110
|
+
# flextool/engine_polars/model.py:502
|
|
111
|
+
"v_state_inter": VarFamily(QuantityType.ENERGY),
|
|
112
|
+
# flextool/engine_polars/model.py:505
|
|
113
|
+
"v_state_rp_start": VarFamily(QuantityType.ENERGY),
|
|
114
|
+
|
|
115
|
+
# ── Unit-commitment families. Column counts a number of units
|
|
116
|
+
# online / starting / shutting; DIMENSIONLESS. The matrix coef
|
|
117
|
+
# ``p_unitsize`` (or ``v_unitsize`` from process_existing_count)
|
|
118
|
+
# turns the row into POWER as needed. Integer variants are
|
|
119
|
+
# mathematically the same column type.
|
|
120
|
+
# flextool/engine_polars/model.py:511
|
|
121
|
+
"v_online_linear": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
122
|
+
# flextool/engine_polars/model.py:512
|
|
123
|
+
"v_startup_linear": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
124
|
+
# flextool/engine_polars/model.py:513
|
|
125
|
+
"v_shutdown_linear": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
126
|
+
# flextool/engine_polars/model.py:518
|
|
127
|
+
"v_online_integer": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
128
|
+
# flextool/engine_polars/model.py:520
|
|
129
|
+
"v_startup_integer": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
130
|
+
# flextool/engine_polars/model.py:521
|
|
131
|
+
"v_shutdown_integer": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
132
|
+
|
|
133
|
+
# ── Investment / divestment counts. DIMENSIONLESS column whose
|
|
134
|
+
# matrix coefficient ``p_unitsize`` (process / connection) or
|
|
135
|
+
# ``p_state_unitsize`` (node) sets the row's effective POWER /
|
|
136
|
+
# ENERGY type.
|
|
137
|
+
# flextool/engine_polars/model.py:523
|
|
138
|
+
"v_invest_p": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
139
|
+
# flextool/engine_polars/model.py:525
|
|
140
|
+
"v_divest_p": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
141
|
+
# flextool/engine_polars/model.py:527
|
|
142
|
+
"v_invest_n": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_state_unitsize"),
|
|
143
|
+
# flextool/engine_polars/model.py:529
|
|
144
|
+
"v_divest_n": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_state_unitsize"),
|
|
145
|
+
|
|
146
|
+
# ── Reserve. DIMENSIONLESS column; ``p_unitsize`` multiplier
|
|
147
|
+
# carries the per-row POWER coefficient.
|
|
148
|
+
# flextool/engine_polars/_reserve.py:275
|
|
149
|
+
"v_reserve": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
150
|
+
# flextool/engine_polars/_reserve.py:282
|
|
151
|
+
"vq_reserve": VarFamily(QuantityType.POWER),
|
|
152
|
+
|
|
153
|
+
# ── Commodity ladder. v_trade is DIMENSIONLESS; row coef
|
|
154
|
+
# ``unitsize`` turns it into ENERGY for the ladder caps and the
|
|
155
|
+
# balance row.
|
|
156
|
+
# flextool/engine_polars/_commodity_ladder.py:399
|
|
157
|
+
"v_trade": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="unitsize"),
|
|
158
|
+
|
|
159
|
+
# ── Group-level slacks. Direct physical units.
|
|
160
|
+
# flextool/engine_polars/_group_slack.py:522
|
|
161
|
+
"vq_capacity_margin": VarFamily(QuantityType.POWER),
|
|
162
|
+
# flextool/engine_polars/_group_slack.py:902
|
|
163
|
+
"vq_inertia": VarFamily(QuantityType.INERTIA),
|
|
164
|
+
# flextool/engine_polars/_group_slack.py:1045
|
|
165
|
+
"vq_non_synchronous": VarFamily(QuantityType.POWER),
|
|
166
|
+
|
|
167
|
+
# ── DC power flow.
|
|
168
|
+
# ``v_angle`` (radians). Dimensionless in our taxonomy.
|
|
169
|
+
# flextool/engine_polars/_dc_power_flow.py:250
|
|
170
|
+
"v_angle": VarFamily(QuantityType.DIMENSIONLESS),
|
|
171
|
+
# ``v_flow_back`` mirrors v_flow on the reverse direction.
|
|
172
|
+
# flextool/engine_polars/_dc_power_flow.py:296
|
|
173
|
+
"v_flow_back": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
174
|
+
|
|
175
|
+
# ── Benders (Option C) hand-built master families. The hand master
|
|
176
|
+
# (``master="hand"``) is built AUTOSCALE-OFF, so Layer 2 never scales
|
|
177
|
+
# these columns; registering them keeps the registry-coverage invariant
|
|
178
|
+
# complete (CLAUDE.md #1).
|
|
179
|
+
# ``f`` = cross-region trade flow, in the SAME unitsize-normalised units
|
|
180
|
+
# as the region half-flow ``v_flow`` ⇒ same family as v_flow.
|
|
181
|
+
# flextool/engine_polars/_benders.py:483
|
|
182
|
+
"f": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
183
|
+
# ``C`` = invested trade-connection capacity (normalised) ⇒ same family
|
|
184
|
+
# as v_invest_p.
|
|
185
|
+
# flextool/engine_polars/_benders.py:473
|
|
186
|
+
"C": VarFamily(QuantityType.DIMENSIONLESS, multiplier_param="p_unitsize"),
|
|
187
|
+
# ``eta`` = per-region recourse cost (enters the objective at coef 1.0
|
|
188
|
+
# carrying the region's currency cost) ⇒ CURRENCY column.
|
|
189
|
+
# flextool/engine_polars/_benders.py:489
|
|
190
|
+
"eta": VarFamily(QuantityType.CURRENCY),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
# Constraint families.
|
|
196
|
+
#
|
|
197
|
+
# The key is either the full constraint name (exact match) or a prefix
|
|
198
|
+
# (the constraint name's ``startswith(key + "_")``). Suffixed UC
|
|
199
|
+
# families (``maxOnline_linear`` / ``maxOnline_integer``) are matched
|
|
200
|
+
# by prefix; the per-family Layer-2 type is the same for both because
|
|
201
|
+
# they carry the same RHS structure.
|
|
202
|
+
#
|
|
203
|
+
# ``rhs_type=None`` ⇒ Layer 2 does NOT apply a per-row factor to that
|
|
204
|
+
# family (the rows participate in column scaling only).
|
|
205
|
+
|
|
206
|
+
# Suffix-resolved (`_p` ⇒ POWER, `_n` ⇒ ENERGY) group capacity rows
|
|
207
|
+
# are flagged via member_class_resolver="group_capacity".
|
|
208
|
+
|
|
209
|
+
CONSTRAINT_FAMILIES: dict[str, CstrFamily] = {
|
|
210
|
+
# ── Node balance (the central energy equation) — ENERGY.
|
|
211
|
+
# flextool/engine_polars/model.py:1336
|
|
212
|
+
"nodeBalance_eq": CstrFamily(QuantityType.ENERGY),
|
|
213
|
+
# Per-period energy balance for balance_within_period nodes — ENERGY.
|
|
214
|
+
# flextool/engine_polars/model.py:nodeBalancePeriod_eq
|
|
215
|
+
"nodeBalancePeriod_eq": CstrFamily(QuantityType.ENERGY),
|
|
216
|
+
# flextool/engine_polars/model.py:1792
|
|
217
|
+
"nodeBalanceBlock_eq": CstrFamily(QuantityType.ENERGY),
|
|
218
|
+
|
|
219
|
+
# ── Storage state max / state ladder. ENERGY.
|
|
220
|
+
# flextool/engine_polars/model.py:2788
|
|
221
|
+
"maxState": CstrFamily(QuantityType.ENERGY),
|
|
222
|
+
# flextool/engine_polars/model.py:1159
|
|
223
|
+
"maxState_rp_start": CstrFamily(QuantityType.ENERGY),
|
|
224
|
+
# flextool/engine_polars/model.py:1570
|
|
225
|
+
"stateConstantWithinBlock_eq": CstrFamily(QuantityType.ENERGY),
|
|
226
|
+
# flextool/engine_polars/model.py:905
|
|
227
|
+
"rp_inter_period_balance": CstrFamily(QuantityType.ENERGY),
|
|
228
|
+
# flextool/engine_polars/model.py:1039
|
|
229
|
+
"rp_inter_period_cyclic": CstrFamily(QuantityType.ENERGY),
|
|
230
|
+
# flextool/engine_polars/model.py:1144
|
|
231
|
+
"rp_inter_period_max_state": CstrFamily(QuantityType.ENERGY),
|
|
232
|
+
# flextool/engine_polars/model.py:2985
|
|
233
|
+
"storage_state_start_binding": CstrFamily(QuantityType.ENERGY),
|
|
234
|
+
# flextool/engine_polars/model.py:3072
|
|
235
|
+
"storage_state_start_binding_cyclic_period": CstrFamily(QuantityType.ENERGY),
|
|
236
|
+
# flextool/engine_polars/model.py:3124
|
|
237
|
+
"storage_state_solve_horizon_reference_value": CstrFamily(QuantityType.ENERGY),
|
|
238
|
+
|
|
239
|
+
# ── Flow caps from process side. DIMENSIONLESS (capacity coefficient).
|
|
240
|
+
# The cap is a fraction × existing_count; the row's effective type
|
|
241
|
+
# lives in the coefficient, not the RHS.
|
|
242
|
+
# flextool/engine_polars/model.py:2045
|
|
243
|
+
"maxFlow": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
244
|
+
# flextool/engine_polars/model.py:2075
|
|
245
|
+
"maxFlow_negCap": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
246
|
+
# Reverse-flow capacity cap on v_flow_back for method_2way_1var_off
|
|
247
|
+
# arcs (DC + non-DC). flextool/engine_polars/model.py (maxFlow_back).
|
|
248
|
+
"maxFlow_back": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
249
|
+
# Legacy name for the DC-only back-flow cap; the cap is now emitted as
|
|
250
|
+
# ``maxFlow_back`` over the full arc set. Retained for back-compat.
|
|
251
|
+
"maxToSink_back": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
252
|
+
# Ramp constraints, prefix-matched: ramp_<side>_<dir>_constraint.
|
|
253
|
+
# flextool/engine_polars/model.py:2164
|
|
254
|
+
"ramp": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
255
|
+
|
|
256
|
+
# ── Investment caps — entity scope.
|
|
257
|
+
# flextool/engine_polars/model.py:2189
|
|
258
|
+
"maxInvest_var_bound": CstrFamily(QuantityType.POWER),
|
|
259
|
+
# flextool/engine_polars/model.py:2197
|
|
260
|
+
"maxDivest_var_bound": CstrFamily(QuantityType.POWER),
|
|
261
|
+
# flextool/engine_polars/model.py:2217
|
|
262
|
+
"maxInvest_var_bound_n": CstrFamily(QuantityType.ENERGY),
|
|
263
|
+
# flextool/engine_polars/model.py:2225
|
|
264
|
+
"maxDivest_var_bound_n": CstrFamily(QuantityType.ENERGY),
|
|
265
|
+
# flextool/engine_polars/model.py:2257
|
|
266
|
+
"maxInvest_entity_period_p": CstrFamily(QuantityType.POWER),
|
|
267
|
+
# flextool/engine_polars/model.py:2274
|
|
268
|
+
"maxInvest_entity_period_n": CstrFamily(QuantityType.ENERGY),
|
|
269
|
+
# flextool/engine_polars/model.py:2292
|
|
270
|
+
"maxDivest_entity_period_p": CstrFamily(QuantityType.POWER),
|
|
271
|
+
# flextool/engine_polars/model.py:2309
|
|
272
|
+
"maxDivest_entity_period_n": CstrFamily(QuantityType.ENERGY),
|
|
273
|
+
# flextool/engine_polars/model.py:2380
|
|
274
|
+
"maxInvest_entity_total": CstrFamily(QuantityType.POWER),
|
|
275
|
+
# flextool/engine_polars/model.py:2432
|
|
276
|
+
"maxInvest_entity_total_n": CstrFamily(QuantityType.ENERGY),
|
|
277
|
+
# flextool/engine_polars/model.py:2405
|
|
278
|
+
"maxDivest_entity_total": CstrFamily(QuantityType.POWER),
|
|
279
|
+
# flextool/engine_polars/model.py:2457
|
|
280
|
+
"maxDivest_entity_total_n": CstrFamily(QuantityType.ENERGY),
|
|
281
|
+
|
|
282
|
+
# min-invest / min-divest entity totals — group_capacity resolved
|
|
283
|
+
# via _p / _n suffix in _cumulative_invest.py.
|
|
284
|
+
# flextool/engine_polars/_cumulative_invest.py:456 (minInvest_entity_total_p)
|
|
285
|
+
# flextool/engine_polars/_cumulative_invest.py:496 (minInvest_entity_total_n)
|
|
286
|
+
"minInvest_entity_total": CstrFamily(
|
|
287
|
+
None, member_class_resolver="group_capacity"
|
|
288
|
+
),
|
|
289
|
+
# flextool/engine_polars/_cumulative_invest.py:464 (minDivest_entity_total_p)
|
|
290
|
+
# flextool/engine_polars/_cumulative_invest.py:504 (minDivest_entity_total_n)
|
|
291
|
+
"minDivest_entity_total": CstrFamily(
|
|
292
|
+
None, member_class_resolver="group_capacity"
|
|
293
|
+
),
|
|
294
|
+
|
|
295
|
+
# min-invest / min-divest entity period (prefix-matched _p/_n) —
|
|
296
|
+
# POWER for _p, ENERGY for _n.
|
|
297
|
+
# flextool/engine_polars/_cumulative_invest.py:381 (minInvest_entity_period_p)
|
|
298
|
+
# flextool/engine_polars/_cumulative_invest.py:395 (minInvest_entity_period_n)
|
|
299
|
+
"minInvest_entity_period": CstrFamily(
|
|
300
|
+
None, member_class_resolver="group_capacity"
|
|
301
|
+
),
|
|
302
|
+
"minDivest_entity_period": CstrFamily(
|
|
303
|
+
None, member_class_resolver="group_capacity"
|
|
304
|
+
),
|
|
305
|
+
|
|
306
|
+
# Group invest / divest variants — `[MW or MWh]` resolved at
|
|
307
|
+
# apply time.
|
|
308
|
+
# flextool/engine_polars/_cumulative_invest.py:681 (maxInvestGroup_entity_period_p)
|
|
309
|
+
# flextool/engine_polars/_cumulative_invest.py:701 (maxInvestGroup_entity_period_n)
|
|
310
|
+
"maxInvestGroup_entity_period": CstrFamily(
|
|
311
|
+
None, member_class_resolver="group_capacity"
|
|
312
|
+
),
|
|
313
|
+
"minInvestGroup_entity_period": CstrFamily(
|
|
314
|
+
None, member_class_resolver="group_capacity"
|
|
315
|
+
),
|
|
316
|
+
"maxDivestGroup_entity_period": CstrFamily(
|
|
317
|
+
None, member_class_resolver="group_capacity"
|
|
318
|
+
),
|
|
319
|
+
"minDivestGroup_entity_period": CstrFamily(
|
|
320
|
+
None, member_class_resolver="group_capacity"
|
|
321
|
+
),
|
|
322
|
+
# flextool/engine_polars/_cumulative_invest.py:786 (maxInvestGroup_entity_total_p)
|
|
323
|
+
# flextool/engine_polars/_cumulative_invest.py:819 (maxInvestGroup_entity_total_n)
|
|
324
|
+
"maxInvestGroup_entity_total": CstrFamily(
|
|
325
|
+
None, member_class_resolver="group_capacity"
|
|
326
|
+
),
|
|
327
|
+
"minInvestGroup_entity_total": CstrFamily(
|
|
328
|
+
None, member_class_resolver="group_capacity"
|
|
329
|
+
),
|
|
330
|
+
"maxDivestGroup_entity_total": CstrFamily(
|
|
331
|
+
None, member_class_resolver="group_capacity"
|
|
332
|
+
),
|
|
333
|
+
"minDivestGroup_entity_total": CstrFamily(
|
|
334
|
+
None, member_class_resolver="group_capacity"
|
|
335
|
+
),
|
|
336
|
+
# flextool/engine_polars/_cumulative_invest.py:845 (maxInvestGroup_entity_cumulative_p)
|
|
337
|
+
# flextool/engine_polars/_cumulative_invest.py:869 (maxInvestGroup_entity_cumulative_n)
|
|
338
|
+
"maxInvestGroup_entity_cumulative": CstrFamily(
|
|
339
|
+
None, member_class_resolver="group_capacity"
|
|
340
|
+
),
|
|
341
|
+
"minInvestGroup_entity_cumulative": CstrFamily(
|
|
342
|
+
None, member_class_resolver="group_capacity"
|
|
343
|
+
),
|
|
344
|
+
|
|
345
|
+
# Cumulative capacity per-entity — `_p` / `_n` suffix.
|
|
346
|
+
# flextool/engine_polars/_cumulative_invest.py:588 (maxCumulative_capacity_p)
|
|
347
|
+
# flextool/engine_polars/_cumulative_invest.py:632 (maxCumulative_capacity_n)
|
|
348
|
+
"maxCumulative_capacity": CstrFamily(
|
|
349
|
+
None, member_class_resolver="group_capacity"
|
|
350
|
+
),
|
|
351
|
+
"minCumulative_capacity": CstrFamily(
|
|
352
|
+
None, member_class_resolver="group_capacity"
|
|
353
|
+
),
|
|
354
|
+
|
|
355
|
+
# Group-level cumulative / period / instant flow — POWER.
|
|
356
|
+
# flextool/engine_polars/_cumulative_invest.py:1050 (maxCumulative_flow_solve)
|
|
357
|
+
# flextool/engine_polars/_cumulative_invest.py:1085 (maxCumulative_flow_period)
|
|
358
|
+
"maxCumulative_flow_solve": CstrFamily(QuantityType.POWER),
|
|
359
|
+
"minCumulative_flow_solve": CstrFamily(QuantityType.POWER),
|
|
360
|
+
"maxCumulative_flow_period": CstrFamily(QuantityType.POWER),
|
|
361
|
+
"minCumulative_flow_period": CstrFamily(QuantityType.POWER),
|
|
362
|
+
# flextool/engine_polars/_cumulative_invest.py:1111 (maxInstant_flow / minInstant_flow)
|
|
363
|
+
"maxInstant_flow": CstrFamily(QuantityType.POWER),
|
|
364
|
+
"minInstant_flow": CstrFamily(QuantityType.POWER),
|
|
365
|
+
|
|
366
|
+
# ── User-defined constraints: composite LHS, RHS in caller's
|
|
367
|
+
# coordinates. Layer 2 skips per-row scaling.
|
|
368
|
+
# flextool/engine_polars/model.py:2740 (process_constraint_equal / _less_than / _greater_than)
|
|
369
|
+
"process_constraint_equal": CstrFamily(None),
|
|
370
|
+
"process_constraint_less_than": CstrFamily(None),
|
|
371
|
+
"process_constraint_greater_than": CstrFamily(None),
|
|
372
|
+
|
|
373
|
+
# ── Node-balance fix (used by manual demand patching). Zero RHS
|
|
374
|
+
# in the most common case; treat as DIMENSIONLESS so the row
|
|
375
|
+
# coefficient is the rescaled quantity.
|
|
376
|
+
# flextool/engine_polars/model.py:1392
|
|
377
|
+
"node_balance_fix_quantity_eq_lower": CstrFamily(None),
|
|
378
|
+
# flextool/engine_polars/model.py:1539
|
|
379
|
+
"node_storage_usage_fix_le": CstrFamily(None),
|
|
380
|
+
|
|
381
|
+
# ── Conversion (efficiency-balanced flow row).
|
|
382
|
+
# flextool/engine_polars/model.py:2509
|
|
383
|
+
"conversion_indirect": CstrFamily(QuantityType.ENERGY),
|
|
384
|
+
|
|
385
|
+
# ── CO2 caps — EMISSION_MASS.
|
|
386
|
+
# flextool/engine_polars/model.py:2542
|
|
387
|
+
"co2_max_period": CstrFamily(QuantityType.EMISSION_MASS),
|
|
388
|
+
# flextool/engine_polars/model.py:2577
|
|
389
|
+
"co2_max_total": CstrFamily(QuantityType.EMISSION_MASS),
|
|
390
|
+
|
|
391
|
+
# ── Unit-commitment (online / startup / shutdown / minimum times).
|
|
392
|
+
# All DIMENSIONLESS — they bound counts.
|
|
393
|
+
# flextool/engine_polars/model.py:3732 (maxOnline_<sfx>)
|
|
394
|
+
"maxOnline": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
395
|
+
# flextool/engine_polars/model.py:3735 (maxStartup_<sfx>)
|
|
396
|
+
"maxStartup": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
397
|
+
# flextool/engine_polars/model.py:3738 (maxShutdown_<sfx>)
|
|
398
|
+
"maxShutdown": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
399
|
+
# flextool/engine_polars/model.py:3744 (online__startup_<sfx>)
|
|
400
|
+
"online__startup": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
401
|
+
# flextool/engine_polars/model.py:3748 (online__shutdown_<sfx>)
|
|
402
|
+
"online__shutdown": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
403
|
+
# flextool/engine_polars/model.py:3770 (maxFlow_online_<sfx>)
|
|
404
|
+
"maxFlow_online": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
405
|
+
# flextool/engine_polars/model.py:3785 (minFlow_minload_<sfx>)
|
|
406
|
+
"minFlow_minload": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
407
|
+
# flextool/engine_polars/model.py:3814 (minimum_uptime_<sfx>)
|
|
408
|
+
"minimum_uptime": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
409
|
+
# flextool/engine_polars/model.py:3853 (minimum_downtime_<sfx>)
|
|
410
|
+
"minimum_downtime": CstrFamily(QuantityType.DIMENSIONLESS),
|
|
411
|
+
|
|
412
|
+
# ── DC PF.
|
|
413
|
+
# flextool/engine_polars/_dc_power_flow.py:397 — RHS=0, skip.
|
|
414
|
+
"dc_flow_eq": CstrFamily(None),
|
|
415
|
+
# flextool/engine_polars/_dc_power_flow.py:340 — fixed-zero angle
|
|
416
|
+
# at the reference node; RHS=0, skip per-row scaling.
|
|
417
|
+
"dc_reference_angle_eq": CstrFamily(None),
|
|
418
|
+
|
|
419
|
+
# ── Reserve.
|
|
420
|
+
# flextool/engine_polars/_reserve.py:373
|
|
421
|
+
"reserveBalance_timeseries_eq": CstrFamily(QuantityType.POWER),
|
|
422
|
+
# flextool/engine_polars/_reserve.py:449
|
|
423
|
+
"reserveBalance_dynamic_eq": CstrFamily(QuantityType.POWER),
|
|
424
|
+
# flextool/engine_polars/_reserve.py:508 (reserveBalance_up_n_1_eq / _down_n_1_eq)
|
|
425
|
+
"reserveBalance_up_n_1_eq": CstrFamily(QuantityType.POWER),
|
|
426
|
+
"reserveBalance_down_n_1_eq": CstrFamily(QuantityType.POWER),
|
|
427
|
+
# flextool/engine_polars/_reserve.py:576 (reserve_process_upward / _downward)
|
|
428
|
+
"reserve_process_upward": CstrFamily(QuantityType.POWER),
|
|
429
|
+
"reserve_process_downward": CstrFamily(QuantityType.POWER),
|
|
430
|
+
|
|
431
|
+
# ── Group slack constraints.
|
|
432
|
+
# flextool/engine_polars/_group_slack.py:859
|
|
433
|
+
"capacityMargin": CstrFamily(QuantityType.POWER),
|
|
434
|
+
# flextool/engine_polars/_group_slack.py:984
|
|
435
|
+
"inertia_constraint": CstrFamily(QuantityType.INERTIA),
|
|
436
|
+
# flextool/engine_polars/_group_slack.py:1157
|
|
437
|
+
"non_sync_constraint": CstrFamily(QuantityType.POWER),
|
|
438
|
+
|
|
439
|
+
# ── Commodity ladder. ENERGY (MWh).
|
|
440
|
+
# flextool/engine_polars/_commodity_ladder.py:521
|
|
441
|
+
"commodity_ladder_balance": CstrFamily(QuantityType.ENERGY),
|
|
442
|
+
# flextool/engine_polars/_commodity_ladder.py:561 ← the H2_trade trigger
|
|
443
|
+
"ladder_tier_cap_annual_roll": CstrFamily(QuantityType.ENERGY),
|
|
444
|
+
# flextool/engine_polars/_commodity_ladder.py:607
|
|
445
|
+
"ladder_tier_cap_cumulative_roll": CstrFamily(QuantityType.ENERGY),
|
|
446
|
+
|
|
447
|
+
# ── Forbid-invest rows. RHS = 0 by construction; skip per-row.
|
|
448
|
+
# flextool/engine_polars/_cumulative_invest.py:284, 298
|
|
449
|
+
"fix_v_invest_no_investment_eq_p": CstrFamily(None),
|
|
450
|
+
"fix_v_invest_no_investment_eq_n": CstrFamily(None),
|
|
451
|
+
|
|
452
|
+
# ── Non-anticipativity (cross-branch equality). RHS = 0; skip.
|
|
453
|
+
# flextool/engine_polars/model.py:273, 306, 336, 364
|
|
454
|
+
"non_anticipativity_storage_use": CstrFamily(None),
|
|
455
|
+
"non_anticipativity_online_integer": CstrFamily(None),
|
|
456
|
+
"non_anticipativity_online_linear": CstrFamily(None),
|
|
457
|
+
"non_anticipativity_reserve": CstrFamily(None),
|
|
458
|
+
|
|
459
|
+
# ── Profile constraints (per timestep).
|
|
460
|
+
# Node profiles emit at model.py:2879 with names
|
|
461
|
+
# ``profile_state_upper_limit`` / ``_lower_limit`` / ``_fixed``;
|
|
462
|
+
# process profiles at model.py:3914 with ``profile_flow_*``. Per
|
|
463
|
+
# the autoscaler handoff, profile state is POWER for unit /
|
|
464
|
+
# connection, ENERGY for node. Node-side rows go through one
|
|
465
|
+
# call site (``_add_node_profile_cstr``) so we register the names
|
|
466
|
+
# here without a member_class_resolver — they are exclusively
|
|
467
|
+
# ENERGY-typed. Process-side ``profile_flow_*`` rows are POWER.
|
|
468
|
+
"profile_state_upper_limit": CstrFamily(QuantityType.ENERGY),
|
|
469
|
+
"profile_state_lower_limit": CstrFamily(QuantityType.ENERGY),
|
|
470
|
+
"profile_state_fixed": CstrFamily(QuantityType.ENERGY),
|
|
471
|
+
"profile_flow_upper_limit": CstrFamily(QuantityType.POWER),
|
|
472
|
+
"profile_flow_lower_limit": CstrFamily(QuantityType.POWER),
|
|
473
|
+
"profile_flow_fixed": CstrFamily(QuantityType.POWER),
|
|
474
|
+
|
|
475
|
+
# ── Benders (Option C) hand-built master constraints. The hand master
|
|
476
|
+
# is built AUTOSCALE-OFF, so Layer 2 never scales these rows; registering
|
|
477
|
+
# them keeps the registry-coverage invariant complete (CLAUDE.md #1).
|
|
478
|
+
# ``maxInvest``: C[conn] <= max_units — capacity bound, mirrors the
|
|
479
|
+
# entity-scope ``maxInvest_var_bound`` (POWER RHS).
|
|
480
|
+
# flextool/engine_polars/_benders.py:520
|
|
481
|
+
"maxInvest": CstrFamily(QuantityType.POWER),
|
|
482
|
+
# ``trade_capacity``: C[conn] - f[arc,d,t] >= 0 — the RHS is identically
|
|
483
|
+
# zero (no per-row scaling), so CstrFamily(None) like the other zero-RHS
|
|
484
|
+
# structural rows; the column scalers on C / f still propagate.
|
|
485
|
+
# flextool/engine_polars/_benders.py:504
|
|
486
|
+
"trade_capacity": CstrFamily(None),
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def lookup_var(name: str) -> VarFamily:
|
|
491
|
+
"""Return the :class:`VarFamily` registered for ``name``.
|
|
492
|
+
|
|
493
|
+
Raises :class:`KeyError` for any unregistered variable; Layer 2
|
|
494
|
+
must refuse to silently treat unknown variables as DIMENSIONLESS,
|
|
495
|
+
which would hide drift between this registry and the LP build.
|
|
496
|
+
"""
|
|
497
|
+
return VARIABLE_FAMILIES[name]
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def lookup_cstr(name: str) -> CstrFamily:
|
|
501
|
+
"""Return the :class:`CstrFamily` registered for ``name``.
|
|
502
|
+
|
|
503
|
+
Exact match wins. Otherwise we strip a trailing ``_linear``,
|
|
504
|
+
``_integer``, ``_p``, ``_n`` suffix and retry (covers
|
|
505
|
+
``maxOnline_linear`` → ``maxOnline``, ``maxInvest_entity_total_n``
|
|
506
|
+
is already exact, ``minInvest_entity_total_p`` →
|
|
507
|
+
``minInvest_entity_total``). Suffix stripping is conservative:
|
|
508
|
+
only the known UC and member-class suffixes are removed.
|
|
509
|
+
|
|
510
|
+
Raises :class:`KeyError` if no registration matches.
|
|
511
|
+
"""
|
|
512
|
+
if name in CONSTRAINT_FAMILIES:
|
|
513
|
+
return CONSTRAINT_FAMILIES[name]
|
|
514
|
+
# Strip UC suffixes first.
|
|
515
|
+
for sfx in ("_linear", "_integer"):
|
|
516
|
+
if name.endswith(sfx):
|
|
517
|
+
base = name[: -len(sfx)]
|
|
518
|
+
if base in CONSTRAINT_FAMILIES:
|
|
519
|
+
return CONSTRAINT_FAMILIES[base]
|
|
520
|
+
# Then member-class suffix (_p / _n). This is the prefix that
|
|
521
|
+
# member_class_resolver entries cover. We only strip when the
|
|
522
|
+
# *unsuffixed* name matches an entry whose resolver is set, so a
|
|
523
|
+
# row that happens to end with ``_p`` but is registered with an
|
|
524
|
+
# explicit suffix (e.g. ``maxInvest_entity_period_p``) stays on
|
|
525
|
+
# the exact-match path.
|
|
526
|
+
for sfx in ("_p", "_n"):
|
|
527
|
+
if name.endswith(sfx):
|
|
528
|
+
base = name[: -len(sfx)]
|
|
529
|
+
if (base in CONSTRAINT_FAMILIES
|
|
530
|
+
and CONSTRAINT_FAMILIES[base].member_class_resolver):
|
|
531
|
+
return CONSTRAINT_FAMILIES[base]
|
|
532
|
+
# Prefix dispatch (the contract documented in this module's header and
|
|
533
|
+
# in test_registry_coverage): a registry key ``K`` matches a dynamic
|
|
534
|
+
# constraint name when ``name.startswith(K + "_")``. This is what the
|
|
535
|
+
# ``"ramp"`` entry relies on — the ramp family
|
|
536
|
+
# (``ramp_{side}_{dir}_constraint``, model.py:2328) carries middle and
|
|
537
|
+
# suffix tokens (``_sink_up_constraint``) that neither the exact-match
|
|
538
|
+
# nor the UC / member-class suffix-strip paths above can resolve.
|
|
539
|
+
# Longest matching key wins so a more specific registration is never
|
|
540
|
+
# shadowed by a shorter prefix. Runs last, after exact + suffix-strip,
|
|
541
|
+
# so it can only resolve names those paths leave unresolved — never
|
|
542
|
+
# change an existing resolution.
|
|
543
|
+
prefix_match: Optional[str] = None
|
|
544
|
+
for key in CONSTRAINT_FAMILIES:
|
|
545
|
+
if name.startswith(key + "_") and (
|
|
546
|
+
prefix_match is None or len(key) > len(prefix_match)):
|
|
547
|
+
prefix_match = key
|
|
548
|
+
if prefix_match is not None:
|
|
549
|
+
return CONSTRAINT_FAMILIES[prefix_match]
|
|
550
|
+
raise KeyError(name)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def resolve_cstr_rhs_type(name: str) -> Optional[QuantityType]:
|
|
554
|
+
"""Final per-name rhs_type after suffix resolution.
|
|
555
|
+
|
|
556
|
+
For ``member_class_resolver="group_capacity"`` families, the
|
|
557
|
+
constraint name's ``_p`` / ``_n`` suffix selects POWER vs ENERGY
|
|
558
|
+
(via :func:`resolve_group_capacity_type`). Returns ``None`` for
|
|
559
|
+
families that opt out of per-row scaling.
|
|
560
|
+
"""
|
|
561
|
+
fam = lookup_cstr(name)
|
|
562
|
+
if fam.member_class_resolver == "group_capacity":
|
|
563
|
+
if name.endswith("_p"):
|
|
564
|
+
return QuantityType.POWER
|
|
565
|
+
if name.endswith("_n"):
|
|
566
|
+
return QuantityType.ENERGY
|
|
567
|
+
# Unsuffixed (shouldn't happen for group-capacity registered
|
|
568
|
+
# entries) — fall through. Surface as KeyError so we don't
|
|
569
|
+
# silently misclassify.
|
|
570
|
+
raise KeyError(
|
|
571
|
+
f"{name!r}: group_capacity resolver requires _p / _n suffix"
|
|
572
|
+
)
|
|
573
|
+
return fam.rhs_type
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
__all__ = [
|
|
577
|
+
"VarFamily",
|
|
578
|
+
"CstrFamily",
|
|
579
|
+
"VARIABLE_FAMILIES",
|
|
580
|
+
"CONSTRAINT_FAMILIES",
|
|
581
|
+
"lookup_var",
|
|
582
|
+
"lookup_cstr",
|
|
583
|
+
"resolve_cstr_rhs_type",
|
|
584
|
+
]
|