feat: unit-committed affine coupling groups (on/off + min-load + no-load base) - #2336
Open
Flix6x wants to merge 256 commits into
Open
feat: unit-committed affine coupling groups (on/off + min-load + no-load base)#2336Flix6x wants to merge 256 commits into
Flix6x wants to merge 256 commits into
Conversation
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
…and map them to the respective group id Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
…chemas Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
…n-gas-and-electricity # Conflicts: # flexmeasures/data/models/planning/tests/test_commitments.py
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
…oving old flex-context fields into the electricity commodity context Signed-off-by: F.N. Claessen <claessen@seita.nl>
…d field) Signed-off-by: F.N. Claessen <claessen@seita.nl>
…-context Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
… gas Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
…DEL, which are named commodity in scheduling.rst Signed-off-by: F.N. Claessen <claessen@seita.nl>
…nstraint analysis #2072, CI/agents #2275) Conflict resolution policy: - Schema layer: main's design wins wholesale (commodities list nesting, commodity default 'electricity', uniqueness + shared-currency validators). - Engine: main's #1946 multi-commodity device_scheduler plus this branch's coupling_groups primitive (coupling_alpha variables and per-device flow-coupling constraints), keeping the branch's group_to_devices mapping which supports overlapping stock-group membership. - storage.py: main's _prepare/_build_soc_schedule structure wins, with the branch's coupling-group construction re-added; main's strict price-presence validation is restored (the branch had it commented out). - Tests: kept both sides' distinct tests (branch: CHP coupling + factory dispatch; main: gas-only flex-model, per-commodity inflexible sensors, directional-capacity fallback). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B
The sign of a coupling coefficient is inferred from directional capacities, and a device with both directions open (or blocked) was silently treated as an input. Reject such flex-models with a validation error instead. Also promote the coupling field descriptions to MetaData constants and document both fields in the storage flex-model table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B
Adapt the CHP converter-port concept to the device inventory: FlexDevice entries carry their coupling name and signed coupling coefficient, and DeviceInventory.coupling_groups replaces Scheduler._build_coupling_groups as the single source of truth for the solver's coupling groups, keyed by canonical device indices. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qxM7UZ5wHTz3ftz1Mf9yy Signed-off-by: F.N. Claessen <felix@seita.nl>
…-chp # Conflicts: # flexmeasures/data/models/planning/linear_optimization.py # flexmeasures/data/models/planning/tests/test_storage.py
…ters Merging main's group-indexed stock recursion (#2282/#2325) rebuilt group_to_devices from a single-valued device_to_group (last assignment wins), which cannot represent a converter device that belongs to more than one stock group (e.g. a steamer bridging a heat node and a steam node). That silently dropped such devices from all but their last group, orphaning coupled outputs (the CHP dispatched to zero in the cheap-gas merit-order scenario). Keep main's namespaced group keys (the stock-id/device-index collision fix) but build group_to_devices directly from the declared stock groups, preserving full overlapping membership; device_to_group still records a single primary group for per-device stock bounds. Restores test_factory_chp_dispatch (all three merit-order scenarios) on top of the new recursive stock formulation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B
Resolve conflicts from PR #2276 (intermediate power constraints via flex-model 'group' field) against the CHP converter-coupling work. All conflicts were additive (both sides added distinct helpers/methods/ docstring notes in the same regions); kept both sides: - devices.py: DeviceRole docstring (converter ports + GROUP), the _is_zero_capacity/_resolve_coupling_coefficient helpers alongside the group-key helpers, and coupling_groups alongside group_to_devices. - storage.py: coupling_groups wiring alongside group entry classification and validation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
The main-merge re-introduced a duplicate `GenericAsset` import (already imported alongside GenericAssetType); flake8 F811. Remove it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
…nk coupling names
Address two review findings on the CHP coupling work:
1. `DBStorageFlexModelSchema` (used to validate persisted flex-models, e.g.
in patch_asset) did not declare `coupling`/`coupling-coefficient`, so
storing a db flex-model containing `coupling` failed with an unknown-field
ValidationError. Add both fields to that schema, mirroring how they are
declared on `StorageFlexModelSchema`.
2. A provided `coupling` name could be an empty/whitespace-only string, which
would become a coupling-group key and silently couple unrelated devices
under an empty group. Reject blank/whitespace-only names via a shared
`_validate_coupling_name` helper wired into a `@validates("coupling")` on
both schemas.
Add tests: a db-stored flex-model with `coupling` validates and round-trips,
and blank `coupling` values (on both schemas) raise a ValidationError.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B
Signed-off-by: F.N. Claessen <claessen@seita.nl>
DBStorageFlexModelSchema gained coupling/coupling-coefficient, but the UI flex-model schema did not, breaking test_ui_flexmodel_schema (which enforces parity between the two). Add the matching UI entries. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
Infer a coupled (commodity-converting) device's flow direction from which directional capacity is given, defaulting the unspecified opposite direction to zero, mirroring how a missing directional site capacity defaults to zero. An input port now needs only a consumption-capacity, an output port only a production-capacity; setting the opposite direction to a fixed 0 remains valid for back-compat. Only genuinely ambiguous cases (both directions flow, or neither) are rejected. Also move the #2218 coupling changelog entry onto this branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
…oad base) Extend the coupling primitive (#2218) so a converter (e.g. a cogeneration unit) can be modelled with an on/off binary, a minimum level when on, and a per-port no-load base gated by that binary. When a coupling group is unit-committed, the solver adds a per-timestep binary u_g[j] and a marginal level alpha_g[j] >= 0 with min_g * u_g <= alpha_g <= max_g * u_g and, for every port d, P[d,j] == coeff_d * alpha_g[j] + base_d * u_g[j]. This turns the LP into a MILP only when a group is unit-committed; purely proportional coupling is unchanged. Schema: add a per-port `coupling-base` (signed power quantity, sign follows the port's flow direction; default 0) and a `coupling-min` power quantity on the group's reference port (|coeff| == 1). Presence of `coupling-min` (or any non-zero `coupling-base`) marks the group unit-committed; max_g comes from the reference port's `power-capacity`. Both are carried through DeviceInventory (coupling_uc, coupling_bases) and storage.py to device_scheduler. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
Documentation build overview
14 files changed ·
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Extends the coupling primitive (#2218) with unit commitment: a coupling group can now have an on/off binary, a minimum level when on, and a per-port no-load base gated by that binary.
What it does
When a coupling group is unit-committed, the solver introduces a per-timestep binary
u_g[j] ∈ {0,1}and a marginal levelalpha_g[j] ≥ 0, with:min_g * u_g[j] <= alpha_g[j] <= max_g * u_g[j]—alphais 0 when off, and at least the minimum level when on;din the group:P[d,j] == coeff_d * alpha_g[j] + base_d * u_g[j],where
coeff_dis the existing signed coupling coefficient andbase_dis the new signed per-port no-load base. This makes the affine cogeneration/CHP model exact, including no-load fuel: in an unprofitable step the whole unit idles (all ports exactly 0, no no-load fuel burned), and when running it stays at or above the minimum level.Purely proportional (non-UC) coupling is unchanged: the problem stays an LP unless some group is unit-committed (a group becomes unit-committed only when
coupling-minis set or a non-zerocoupling-baseis given). Minimum up/down times are out of scope.Schema
coupling-base(per port): a signed power quantity whose direction follows the port's flow direction (likecoupling-coefficient); default 0.coupling-min(on the group's reference port, the port with |coefficient| = 1): a power quantity that sets the group minimum and enables unit commitment.max_gcomes from the reference port'spower-capacity.Both fields are documented in
metadata.py, added to the storage flex-model schemas, and reflected inopenapi-specs.json. They are carried throughDeviceInventory(coupling_uc,coupling_bases) andstorage.pytodevice_scheduler.Tests
Stacked on #2218.
🤖 Generated with Claude Code