Skip to content

Relax soc-targets along with the other SoC constraints - #2390

Open
Flix6x wants to merge 1 commit into
mainfrom
feat/relax-soc-targets
Open

Relax soc-targets along with the other SoC constraints#2390
Flix6x wants to merge 1 commit into
mainfrom
feat/relax-soc-targets

Conversation

@Flix6x

@Flix6x Flix6x commented Aug 4, 2026

Copy link
Copy Markdown
Member

Description

  • Relax soc-targets as a two-sided stock commitment instead of always enforcing them as hard equals constraints
  • Price the two directions with the existing breach prices: falling short of a target is a shortage (priced like a soc-minima breach), overshooting is a surplus (priced like a soc-maxima breach)
  • Report missed targets among the unresolved constraints, as the absolute deviation in either direction
  • Update the tests that relied on targets being hard, so they opt out of relaxation explicitly
  • Docs + changelog

Context

soc-targets was the last SoC constraint that constraint relaxation did not cover. An unreachable target made the whole scheduling problem infeasible, so one bad target cost the user their entire schedule — the outcome relax-constraints exists to prevent, and the same failure mode #2252 addresses by retiring the fallback scheduler.

Reusing soc-minima-breach-price and soc-maxima-breach-price means targets need no new flex-context field and follow relax-soc-constraints / relax-constraints automatically. Targets are relaxed only when both prices are available, which is the case whenever SoC relaxation is on, since the two defaults are filled in as a pair.

This is a piece of the wider physics-vs-preference discussion (a target is a preference, not a physical bound); by that reading it should never have been the one unconditionally hard constraint in the flex-model.

Impact

Breaking behaviour change. Under the default relax-constraints: True, an unreachable soc-targets entry now yields a schedule that gets as close as the hard constraints allow and pays the breach, instead of failing the job with "The input data yields an infeasible problem.".

To keep targets hard, set relax-constraints: False — and note that relax-soc-constraints: False alone does not achieve this, because relax-constraints defaults to True and still fills in the default SoC breach prices (see "Discovered along the way" below).

Three existing tests encoded the old behaviour and were updated to opt out of relaxation explicitly, rather than being weakened:

  • test_infeasible_problem_error — now runs with relax-constraints: False, and gains a sibling test_unreachable_soc_target_is_relaxed_by_default asserting the new path
  • test_numerical_errors — is about numerical error in the hard equals constraint, so it opts out
  • test_add_storage_constraint_from_sensor — inspects the equals column directly, so it opts out
  • test_get_job_status_failed_infeasible_schedule_includes_exc_info — used an unreachable target as its infeasibility source, so it opts out
  • test_off_tick_soc_target_extends_schedule_end_to_next_tick — needs both flags off; see below

Discovered along the way (not fixed here)

relax-soc-constraints: False does not actually keep SoC constraints hard while relax-constraints is True. In flexmeasures/data/schemas/scheduling/__init__.py, the guard reads

if (
    data["relax_soc_constraints"]
    or data["relax_constraints"]
    and not data.get("soc_minima_breach_price")
    and not data.get("soc_maxima_breach_price")
):

which parses as A or (B and C and D), so the not ... already set conditions only guard the relax_constraints branch. Two consequences, both pre-existing:

  1. relax-soc-constraints: False + default relax-constraints still fills in the default SoC breach prices.
  2. relax-soc-constraints: True overwrites explicitly-provided breach prices with the defaults, since set_default_breach_prices assigns unconditionally.

The same shape appears in the capacity and site-capacity blocks. This PR does not change it — fixing it would alter behaviour for soc-minima/soc-maxima and the capacity constraints too, which deserves its own PR. It is called out because this PR makes consequence 1 more visible: targets were previously the one constraint that stayed hard regardless.

How to test

pytest \
  flexmeasures/data/models/planning/tests/test_solver.py::test_unreachable_soc_target_is_relaxed_by_default \
  flexmeasures/data/models/planning/tests/test_solver.py::test_infeasible_problem_error \
  flexmeasures/data/models/planning/tests/test_storage.py::test_unresolved_targets_soc_targets \
  flexmeasures/data/models/planning/tests/ \
  flexmeasures/api/v3_0/tests/test_jobs_api.py \
  -v

Related Items


Sign-off

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on code under GPL or other license that is incompatible with FlexMeasures
  • Added changelog item in documentation/changelog.rst

🤖 Generated with Claude Code

https://claude.ai/code/session_014qPe5bv41RDTuBVtpdxjWH

An unreachable soc-target made the whole scheduling problem infeasible, so a
single bad target cost the user their entire schedule. Targets were the last
SoC constraint that constraint relaxation did not cover.

A target is a two-sided constraint, so relax it as a stock commitment priced in
both directions: falling short is a shortage, priced like a soc-minima breach,
and overshooting is a surplus, priced like a soc-maxima breach. Reusing the two
existing breach prices means targets need no new flex-context field and follow
relax-soc-constraints/relax-constraints automatically. Targets are relaxed only
when both prices are available, which is the case whenever SoC relaxation is on,
since the defaults are filled in as a pair.

Missed targets are now reported among the unresolved constraints, as the
absolute deviation in either direction. Since a met target leaves no headroom,
targets never produce a "resolved" entry.

Tests that relied on targets being hard now opt out of relaxation explicitly.

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>
@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 flexmeasures | 🛠️ Build #33913572 | 📁 Comparing a8cbf72 against latest (65d2a70)

  🔍 Preview build  

6 files changed · ± 6 modified

± Modified

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant