Share storage-efficiency per stock group and index solver stock by group#2325
Conversation
`_get_stock_change` expressed a device's stock at step j as a running sum over all earlier steps, so the number of model nonzeros grew quadratically with the scheduling horizon (e.g. ~12k nonzeros at 12h, ~349k at 3 days). That dominated solve time on longer horizons. Introduce an explicit `device_stock` variable coupled by a recursive `device_stock_balance` constraint (stock[j] = a*stock[j-1] + b*change[j], with a, b the per-step loss coefficients that apply_stock_changes_and_losses computes). This is mathematically equivalent -- verified identical objectives across horizons and against the planning test suite -- and keeps the number of nonzeros linear in the horizon. Solve time drops ~10x at the 2-day default horizon and ~23x at 3 days. Found while investigating SeitaBV/ems#172. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqGFrHfGrBHAJyjdAyr3y
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqGFrHfGrBHAJyjdAyr3y
…ce lists Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The recursion formulation lands on a cost-equal alternative vertex (A charges 3, B discharges 1 for free under net site metering). Make device B lossy in both directions so the optimum is unique again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oup (#2324) - storage-efficiency may now be defined on the entry holding a shared stock's SoC parameters (or on exactly one member device) and applies to all members; conflicting definitions fail fast, mirroring how #2321 treats the other SoC parameters. - device_scheduler now models one stock variable and one balance recursion per stock group instead of per device, dropping the redundant secondary recursions, and validates that grouped devices share their storage efficiency and initial stock. - Fix a latent key collision in device_to_group: an ungrouped device whose index equalled a state-of-charge sensor id was silently merged into that stock group, feeding its flows into the wrong stock. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Documentation build overview
|
|
Requirement from the off-tick SoC projection work (#2194): when off-tick SoC constraints auto-enable The tricky case is a shared stock: since #2321, its SoC parameters (incl. Since this PR already treats the stock group as the canonical unit in the solver (group-indexed stock recursion, shared storage-efficiency), it would be good to tackle that mapping here:
With that in place, #2194's relaxation scoping can track stock keys instead of power sensor ids, which also fixes the case where a member device entry carries the SoC parameters of a stock whose first device is a different entry. |
…angelog The buffer's storage-efficiency (previously ignored, with a 'does not work yet' todo) is now honored, so enable it at 99% per 15 minutes and update the expected schedules: the heater covers the loss-induced remainder beyond the maxed-out boiler around the clock, and the boiler tops up the buffer in the final hour of the cheap-electricity window. Also fold the two changelog entries into the existing shared-storage feature entry for v1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Enabler for scoping #2194's SoC-constraint relaxation per stock: - StockCommitment gains a 'stock' attribute holding the stock key; the StorageScheduler stamps it on all soft SoC commitments (soc-minima, soc-maxima, prefer-full). - The solver couples stock-scoped commitments to the stock group as a whole (via the group's first device, whose stock is the group's stock), regardless of which device index the commitment names. - DeviceInventory.stock_constraint_device(stock_key) exposes the device that applies a stock's SoC constraints, complementing stock_groups for deserialization-time scoping logic. - The solver's grouped-device efficiency guard now tolerates a missing efficiency column (the default, lossless case), which direct callers of device_scheduler commonly omit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Done in 239c6da — both parts:
Two observations from the trenches, both out of scope here:
|
|
Follow-up to my earlier comment, from the #2194 work just pushed: besides the relaxation scoping, #2194 now also records at which time the starting state of charge is actually known (when For a shared stock, that timing is a property of the stock (the measurement pertains to the stock's SoC, resolved on the entry holding the SoC parameters), not of any particular member device. So when this PR introduces the canonical
Both currently key on the power sensor id and rely on "the first device of the stock group applies the stock's constraints", which is exactly the positional convention this PR is in a good place to replace. |
It was never registered as a model constraint; all device-level commitments route through grouped_commitment_equalities. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All three follow-ups handled:
|
…fficiency # Conflicts: # flexmeasures/data/models/planning/linear_optimization.py
…fficiency # Conflicts: # flexmeasures/data/models/planning/tests/test_commitments.py
Description
Closes #2324. Originally stacked on #2282; now that #2282 has merged, this PR targets
maindirectly.Since #2321, a shared stock's SoC parameters live on a single flex-model entry and all group members receive the group's initial stock — but
storage-efficiencyremained a per-device field, silently allowing members of one stock group to declare different loss rates for the same physical stock. This PR:storage-efficiencyas a property of the stock: it may be defined on the entry holding the stock's SoC parameters (including stock-only entries, where it was previously ignored) or on exactly one member device, and applies to all members. Conflicting definitions fail fast, mirroring Track devices via a typed device inventory in the StorageScheduler #2321's handling of the other SoC parameters. This resolves the long-standing# todo: move storage-efficiency into a shared parameter…instorage.py.device_schedulervalidates that grouped devices share their storage efficiency and initial stock, so direct callers get a clear error instead of silently ambiguous semantics.Bugfix
While wiring this up, a latent key collision surfaced in
device_to_group: declared stock groups are keyed by state-of-charge sensor id, while ungrouped devices used their own device index as group key. An ungrouped device whose index equalled a SoC sensor id was silently merged into that stock group, feeding its flows into the wrong stock —test_simulation_with_dynamic_consumption_capacityactually exercised this (the building's raw electricity load was counted as heat inflow into the buffer). Group keys are now namespaced.How to test
🤖 Generated with Claude Code