Keep device capacities hard unless relaxation is asked for - #2398
Conversation
Since relax-constraints started defaulting to True, every scheduling request got default device breach prices, which turned a directional device capacity into a soft constraint. That is wrong for the many cases where the capacity states a physical impossibility rather than an economic preference: a heat pump with production-capacity 0 was scheduled to produce, because breaching the device was cheaper than breaching the site. Restore the opt-in that held before the default flip: fill in default device breach prices only when relax-capacity-constraints is set, or when relax-constraints is passed explicitly rather than defaulted. The SoC and site capacity constraints keep following the default, where softening was the point. Also stop overwriting explicitly given device breach prices. The guard's own comment says "unless already set explicitly", but operator precedence tied that condition to one branch only, and set_default_breach_prices assigns unconditionally. This does not fix the case where relaxation was asked for and a capacity of zero is still meant as a physical statement; see issue #2395. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qPe5bv41RDTuBVtpdxjWH Signed-off-by: F.N. Claessen <claessen@seita.nl>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: F.N. Claessen <claessen@seita.nl>
Documentation build overview
5 files changed ·
|
There was a problem hiding this comment.
Pull request overview
This PR adjusts flex-context price-defaulting so that device directional capacities (consumption-capacity / production-capacity) remain hard constraints unless the caller explicitly opts into relaxing them, preventing physically impossible schedules caused by default breach pricing.
Changes:
- Update
FlexContextSchema.check_pricesto only auto-fill default device breach prices whenrelax-capacity-constraintsis set, or whenrelax-constraintswas explicitly provided (not merely defaulted). - Add regression tests covering opt-in device relaxation and ensuring explicitly provided device breach prices aren’t overwritten.
- Update API/OpenAPI/docs/changelog text to document the revised relaxation semantics.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| flexmeasures/data/schemas/scheduling/init.py | Changes defaulting logic so default relax-constraints no longer softens device directional capacities |
| flexmeasures/data/schemas/tests/test_scheduling.py | Adds tests for opt-in device capacity relaxation and non-overwriting of explicit breach prices |
| flexmeasures/data/schemas/scheduling/metadata.py | Updates relax-constraints field description to reflect new semantics |
| flexmeasures/ui/static/openapi-specs.json | Updates OpenAPI description for relax-constraints accordingly |
| documentation/api/introduction.rst | Notes that device directional capacities stay hard unless explicitly relaxed |
| documentation/changelog.rst | Adds changelog entry describing the behavior change and explicit-price preservation |
Suppressed comments (4)
flexmeasures/data/schemas/tests/test_scheduling.py:2007
- This comment wraps mid-phrase (“as it did” → next line), which violates the repo’s rule that comments only break lines after punctuation. Please reflow the comment.
# An explicitly passed relax-constraints still softens everything, as it did
# before relax-constraints defaulted to True.
flexmeasures/data/schemas/tests/test_scheduling.py:2029
- This docstring wraps mid-phrase (“…that cannot” → next line), which violates the repo’s rule that docstrings only break lines after punctuation. Please reflow without mid-phrase breaks.
A directional capacity can state a physical impossibility (a heat pump that cannot
produce), so making it breachable at a price has to be asked for, either through
relax-capacity-constraints or through an explicitly passed relax-constraints.
flexmeasures/data/schemas/tests/test_scheduling.py:2045
- This docstring wraps mid-phrase (“…keep it” → next line), which violates the repo’s rule that docstrings only break lines after punctuation. Please reflow without mid-phrase breaks.
``set_default_breach_prices`` assigns unconditionally, so the guard has to keep it
from running at all when the caller already priced a breach themselves.
flexmeasures/data/schemas/tests/test_scheduling.py:2056
- This comment wraps mid-phrase between lines (“…puts” → next line), which violates the repo’s rule that comments only break lines after punctuation. Please reflow it.
# The opposite direction is left alone too: pricing one direction explicitly puts
# the caller in charge of both, rather than mixing their price with our default.
| # Note that a *defaulted* 'relax-constraints' does not soften device capacities. | ||
| # A directional capacity can state a physical impossibility (a heat pump that | ||
| # cannot produce), and a default should not make that breachable at a price. | ||
| # Softening them therefore stays an opt-in, through 'relax-capacity-constraints' | ||
| # or through an explicitly passed 'relax-constraints'. |
| # Nothing given: relax-constraints defaults to True, which softens the SoC and | ||
| # site capacity constraints, but no longer the device directional capacities. |
| The fallback schedule mechanism activates when the main scheduler encounters an infeasible problem (i.e. when constraints cannot be satisfied). | ||
| This is less likely to happen when ``"relax-constraints": true`` is set in the ``flex-context``, as constraint relaxation softens most infeasibility-causing constraints. | ||
| The hard constraints that remain even after constraint relaxation are ``soc-min``, ``soc-max``, ``soc-targets`` and ``power-capacity`` in the ``flex-model``, and ``site-power-capacity`` in the ``flex-context``. | ||
| The device ``consumption-capacity`` and ``production-capacity`` also stay hard unless you ask for them to be relaxed, by passing ``relax-constraints`` explicitly or by setting ``relax-capacity-constraints``. |
| * Floor off-clock API datetimes to a non-instantaneous sensor's resolution by default when ingesting sensor data, uploading sensor data, and handling scheduler flex-model timed events; configurable with the ``floor_datetimes_to_resolution`` sensor attribute [see `PR #2146 <https://www.github.com/FlexMeasures/flexmeasures/pull/2146>`_ and `PR #2194 <https://www.github.com/FlexMeasures/flexmeasures/pull/2194>`_] | ||
| * In the UI, asset and sensor charts now render with Apache ECharts (canvas) by default, for much faster drawing and interaction on dense time series, while staying visually and functionally equivalent to the previous Vega-Lite charts, which remain available as a fallback via a toggle [see `PR #2234 <https://www.github.com/FlexMeasures/flexmeasures/pull/2234>`_] | ||
| * Breaking behaviour change: the top-level flex-context's ``relax-constraints`` field now defaults to ``True`` (matching the default already used within each ``commodities`` entry), so constraint violations are softly penalized by default instead of being hard constraints, unless explicitly set to ``False`` [see `PR #2172 <https://www.github.com/FlexMeasures/flexmeasures/pull/2172>`_] | ||
| * That default does not extend to the device ``consumption-capacity`` and ``production-capacity``, which stay hard unless relaxation is asked for, either by passing ``relax-constraints`` explicitly or by setting ``relax-capacity-constraints``. A directional device capacity may state a physical impossibility (a heat pump that cannot produce) rather than an economic preference, and a default should not make that breachable at a price. Explicitly given device breach prices are now also respected instead of being overwritten by the defaults [see `PR #2398 <https://www.github.com/FlexMeasures/flexmeasures/pull/2398>`_] |
There was a problem hiding this comment.
Consider merging with the previous changelog entry, referencing the two PRs.
Description
relax-capacity-constraints, or through arelax-constraintsthat was passed explicitly rather than defaultedrelax-constraintsfield description, API introduction note) + changelogContext
Fixes the default-path half of #2323, and is step 1 of the plan in #2395.
Since
relax-constraintsbegan defaulting toTrue(#2172, unreleased), every scheduling request gets default device breach prices, which turns a directional device capacity into a soft constraint for everybody. That is wrong wherever the capacity states a physical impossibility rather than an economic preference — in #2323 a heat pump withproduction-capacity: 0 kWwas scheduled to produce −1.764 kW, because breaching the device (100 EUR/kW) was cheaper than breaching the site (10 000 EUR/kW). The LP was right, given what we told it.Before the default flip, softening only ever happened when the user explicitly set
consumption-breach-price/production-breach-price(shipped in v0.26.0, PR #1405). "You asked for a breach price, so we softened your capacity" is a defensible reading of an opt-in. This PR restores that opt-in without taking anything away: an explicitrelax-constraints: truestill relaxes everything, exactly as it did before.Impact
Behaviour change, on top of an unreleased behaviour change, so most users never saw the intermediate state.
relax-constraints: truerelax-capacity-constraints: truerelax-constraints: falseAnyone who wants the current behaviour adds one field. Anyone who was relying on the pre-#2172 opt-in is unaffected.
Second change: explicit device breach prices are no longer overwritten
The guard's own comment says "unless already set explicitly", but
A or B and C and Dparses asA or (B and C and D), so the "already set" conditions only ever guarded therelax_constraintsbranch — andset_default_breach_pricesassigns unconditionally. Settingrelax-capacity-constraints: truetogether withconsumption-breach-price: 7 EUR/kWtherefore silently replaced the 7 with the 100 EUR/kW default. It now keeps the caller's price.The same precedence shape remains in the SoC and site-capacity blocks. Fixing those changes behaviour for
soc-minima/soc-maximaand the site capacities too, and the SoC one needs the relax flags to become tri-state (relax_soc_constraintshasload_default=False, so "unset" and "explicitly False" are indistinguishable). Left for its own PR; tracked in #2395.What this does not fix
#2329, which passes
'relax-capacity-constraints': trueexplicitly. That is an opt-in, so the device breach prices are still filled and its EVSE'sproduction-capacity: 0 Wis still soft. Closing that needs a way to tell a physical zero from a preferential one — see #2395, and PR #2345 for the interim rule.How to test
Related Items
production-capacity: 0means two different things, and the rule that separates them is unwritten #2395Sign-off
documentation/changelog.rst🤖 Generated with Claude Code
https://claude.ai/code/session_014qPe5bv41RDTuBVtpdxjWH