Feat/offtick soc projection#2194
Conversation
…ataFrames 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: F.N. Claessen <felix@seita.nl>
# Conflicts: # flexmeasures/api/common/schemas/sensor_data.py
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
…nt-start Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com> # Conflicts: # flexmeasures/api/v3_0/tests/test_sensor_schedules.py
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Mohamed Belhsan Hmida <149331360+BelhsanHmida@users.noreply.github.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Agent-Logs-Url: https://github.com/FlexMeasures/flexmeasures/sessions/be65737b-e479-49f5-bd8b-5c62a1b8b340 Co-authored-by: BelhsanHmida <149331360+BelhsanHmida@users.noreply.github.com>
Agent-Logs-Url: https://github.com/FlexMeasures/flexmeasures/sessions/be65737b-e479-49f5-bd8b-5c62a1b8b340 Co-authored-by: BelhsanHmida <149331360+BelhsanHmida@users.noreply.github.com>
Agent-Logs-Url: https://github.com/FlexMeasures/flexmeasures/sessions/be65737b-e479-49f5-bd8b-5c62a1b8b340 Co-authored-by: BelhsanHmida <149331360+BelhsanHmida@users.noreply.github.com>
Agent-Logs-Url: https://github.com/FlexMeasures/flexmeasures/sessions/be65737b-e479-49f5-bd8b-5c62a1b8b340 Co-authored-by: BelhsanHmida <149331360+BelhsanHmida@users.noreply.github.com>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
…dule_floors_flex_model_datetimes 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: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <149331360+BelhsanHmida@users.noreply.github.com>
…nt-start Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com> # Conflicts: # flexmeasures/api/common/schemas/sensor_data.py # flexmeasures/api/v3_0/tests/test_sensor_data.py # flexmeasures/data/schemas/sensors.py
Pure code move: extract the projection helpers, rule table and project_off_tick_soc_constraints() from planning/storage.py into planning/soc_projection.py, and update imports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
…ions The SocProjectionRule dataclass, policy table and five decoder helpers abstracted just six fixed projection rules; writing the projections out explicitly is shorter and easier to audit. Also simplify the reachable-energy computation: the projection periods always lie within a single scheduling tick, so no tick-walking loop is needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
…ommitments Previously the projection ran only in the hard-constraint path, after soc-minima/soc-maxima with breach prices had already been converted to soft StockCommitments (and nulled). Since off-tick SoC constraints auto-enable relax-soc-constraints, off-tick minima/maxima would in practice reach the commitment builder unprojected, while bounds projected from off-tick targets were applied as hard constraints despite relaxation being enabled. Now the projection runs right after the device capacities are known and before the soft/hard split, so both paths consume on-tick events: projected bounds are softened by breach prices exactly like user-given bounds, and only fall back to hard constraints when no breach price is set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
…lity The reachable energy used to project off-tick SoC constraints now converts grid power to stock change: charging at power P moves the stock at rate P * charging_efficiency (which can exceed 1, e.g. a heat pump's COP), and discharging at power P moves the stock at rate P / discharging_efficiency. To that end, compute the (dis)charging efficiency series before the SoC constraint handling in the device loop and pass them to the projection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
Off-tick soc-target datetimes are no longer floored during deserialization, so a target that drives the schedule extension could set an off-tick schedule end. Its projection onto the next scheduling tick would then fall beyond the schedule end and be silently disregarded. Ceiling the extension keeps the projected target within the schedule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
… constraints Auto-enabling SoC constraint relaxation used to silently override a user's explicit choice to keep SoC constraints hard. Now the explicit False is respected (with a logged warning that projection may add infeasible bounds), and auto-enabling is logged as well. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
… dropped Bounds projected from off-tick SoC events can only be merged into list-based (or missing) soc-minima/soc-maxima specifications. When the counterpart field is given as a sensor, series or fixed quantity, the projected bounds are dropped; that is now logged instead of silent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
…alization # Conflicts: # documentation/changelog.rst
Assigning new instance-level soc fields in __init__ never had any effect: marshmallow resolves fields from the class-level declared fields, so the flooring resolution (and timezone) passed to those instance fields was dead code, contradicting this PR's preservation of off-tick SoC event datetimes. Remove it, and instead realize the intended per-instance overrides (timezone and default soc-unit) by setting attributes on the bound fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
…ction When off-tick projection is disabled (floor_datetimes_to_resolution=False), point-like SoC events between scheduling ticks match no entry in the constraint series and were silently dropped. Log a warning instead, and document the attribute's role as the projection opt-out, as well as the fact that an explicit relax-soc-constraints: False is respected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
…chema The schema's pre_load detection used a guessed resolution (falling back to the sensor's event resolution or 15 minutes even when the scheduler would resolve a different scheduling resolution) and kept mutable per-load state on the schema instance. Detect off-tick SoC events on the serialized flex-model in the scheduler instead, using the scheduler's actual resolution, so the relax decision matches what the projection will actually do. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
…he soft path - Pure test that reachable energy uses charging efficiency (incl. COP > 1) and discharging efficiency. - A target beyond the schedule end extends it to the tick carrying the projected target (ceiled), instead of the target being disregarded. - With a soc-minima breach price, projected off-tick minima feed the soft stock commitments rather than becoming hard constraints. - Pin relax-constraints/relax-soc-constraints to False in the hard-path projection tests, since projected bounds are now softened by default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
…ints When relaxation is enabled purely because off-tick SoC constraints require projection (i.e. the user's flex-context did not already soften SoC constraints via relax flags or explicit breach prices), the softening now applies only to the devices that actually use off-tick SoC constraints, tracked by their power sensor. Other devices in a multi-device flex-model keep their hard SoC constraints. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
Two devices via the serialized multi-sensor flex-model path: the off-tick device's soc-minima are projected and softened into stock commitments, while the on-tick device's soc-minima remain hard constraints. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
Following the original recipe in issue #10: when soc-at-start is resolved from the state-of-charge field (an instantaneous sensor or time series) and the underlying measurement was taken at an off-tick time within the first scheduling interval, the SoC is assumed to hold from the schedule start until that time, and the next scheduling tick receives an upper bound (value plus chargeable energy since then) and a lower bound (value minus dischargeable energy), efficiency-aware and clamped to the global soc-min/soc-max. The measurement time is recorded (per power sensor) when resolving the state-of-charge field, in both single-sensor and multi-device (shared stock) modes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
A pure test for the next-tick bounds (efficiency-aware, no-op on ticks), and an end-to-end test resolving the starting SoC from an off-tick state-of-charge time series entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
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>
|
Thanks @BelhsanHmida — the projection policy is sound and well-tested, and the target projection faithfully generalizes the original recipe in #10 (comment). I reviewed the branch in depth and, rather than a long list of change requests, pushed fixes for what I found directly onto your branch (17 commits, Correctness issues found in review
Refinements
One policy note for future discussion (not changed on the branch): the capacity-loosened bounds for user-given Please review the pushed commits — happy to adjust anything you disagree with. |
Both trackers previously keyed on the device's power sensor id and relied on the positional convention that the first device of a stock group applies the stock's constraints: - off_tick_soc_sensor_ids becomes off_tick_stock_keys: entries are tracked by the stock key resolved from their serialized state-of-charge field, so scoping covers all devices sharing the stock (entries without a resolvable stock key fall back to a namespaced power-sensor key). - soc_at_start_datetimes is keyed by stock key, threaded through the soc-at-start resolution chain (exact stock key in multi-device mode; a None key covers the single-sensor time-series case). Adds a regression test for the shared-stock case: off-tick soc-minima on a stock-only entry soften the whole group's constraints while an unrelated device keeps its hard on-tick minima. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed 1a0b144 implementing the stock-key rekey requested on #2325:
Full planning suite passes on this branch (210 passed). Note: no dependency on #2325 — the rekey only uses #2321-level APIs, so the branches merge in either order (trivial textual overlap in |
…able Reinstate a policy table, now minimal: with soc-at-start added there are eight bound rules following one symmetric pattern, and each rule only needs to state which bound lands on which surrounding tick. The capacity period, charge-vs-discharge direction, efficiency treatment and loosening sign all derive from those two choices (SocProjectionRule.uses_charging / .sign). A small _SocProjection working state bundles the device's capacities, efficiencies and SoC limits, and accumulates the projected bounds, removing the four near-identical projection blocks and the long parameter threading. Public signatures and behavior are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
On Python 3.10, module-level generic aliases such as list[dict[...]] pass inspect.isclass (bpo-46080 changed this in 3.11) and then make issubclass raise a TypeError in test_all_custom_fields_call_super_deserialize. The new type aliases in soc_projection.py triggered exactly that. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YNPESudGKciPZq88nGJn3C Signed-off-by: F.N. Claessen <claessen@seita.nl>
…oup (#2325) * Speed up device_scheduler with a recursive stock balance `_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 * Reference PR #2282 in the changelog entry Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqGFrHfGrBHAJyjdAyr3y * Address review: apply changelog suggestion and cache stock-group device lists Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix degenerate optimum in alignment regression test after merging main 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> * Share storage-efficiency per stock group and index solver stock by group (#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> * Add changelog entries for PR #2325 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Enable storage losses in the dynamic-capacity test and consolidate changelog 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> * Attach soft SoC commitments to their stock, not to a device index 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> * Remove dead device_stock_commitment_equalities 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> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…alization # Conflicts: # flexmeasures/data/models/planning/storage.py
Description
soc-targets,soc-minima, andsoc-maxima.relax-soc-constraintswhen off-tick SoC constraints are submitted, so generated/adjusted constraints can be softened safely.How to test
Manual test:
soc-target, e.g.17:12.0.04 MWcharging capacity and target1 MWh.17:00, but projects it onto the scheduling ticks as an adjusted previous-tick bound and a next-tick target.relax-soc-constraints.Projection policy
The scheduler can enforce SoC constraints only on its scheduling ticks. For point-like off-tick SoC events:
soc-targetsare projected to an exact target on the next scheduling tick, with capacity-adjusted lower and upper bounds on the previous tick.soc-minimaare projected as lower bounds on the surrounding ticks, adjusted by reachable charge/discharge capacity.soc-maximaare projected as upper bounds on the surrounding ticks, adjusted by reachable charge/discharge capacity.Related Items
Follow-up to PR #2146 review discussion around off-tick storage SoC constraints.
Sign-off