Skip to content

Update the device scheduler's (MI)LP formulation in the docs - #2394

Open
Flix6x wants to merge 7 commits into
mainfrom
docs/milp-formulation-update
Open

Update the device scheduler's (MI)LP formulation in the docs#2394
Flix6x wants to merge 7 commits into
mainfrom
docs/milp-formulation-update

Conversation

@Flix6x

@Flix6x Flix6x commented Aug 5, 2026

Copy link
Copy Markdown
Member

Why

documentation/concepts/device_scheduler.rst had drifted a long way from the model the scheduler actually builds. It still described a single-stock, single-EMS-constraint LP with per-time-step commitment deviations, while the scheduler has since gained sub-commitments, stock groups, per-commodity EMS constraint groups, device-scoped and stock commitments, flow coupling, balance groups and power bands.

What changed

  • Sub-commitments: deviation variables are indexed by commitment group (Δ_up(c), Δ_down(c)), not by time step, and deviation prices are per sub-commitment. Added a section explaining the group → sub-commitment split.
  • Stock groups: stock is a variable per stock group, coupled by a recursive balance (which keeps the model's nonzeros linear in the horizon), and device stock bounds are expressed relative to the initial stock.
  • Efficiency variants: dropped perfect/left/right. The scheduler only models the linear treatment (loss_coefficients); the other treatments live on in apply_stock_changes_and_losses, which is noted.
  • EMS constraint groups: site capacity is enforced per device group (the StorageScheduler uses one per commodity), not over one global sum.
  • Commitment bound sides: which deviation prices a sub-commitment carries decides whether its constraint is an equality or a one-sided inequality — now spelled out in a table.
  • New sections: device-scoped and stock commitments (the (c,j,g) constraint family), hard flow coupling (P = γ·α), balance groups for internal commodity nodes, and power bands (S2 operation modes).
  • Big-Ms: Md and Mc instead of one M, plus the commitment-sign binaries, which are only added when the cost curve is non-convex.
  • Retitled to "Device scheduler: mixed-integer linear model" and noted that the Pyomo and direct-HiGHS backends build the same model from the same prepared problem. The storage_device_scheduler label is unchanged, so the existing :ref:s still resolve.

How to test

sphinx-build over documentation/ completes without warnings.

Flix6x and others added 2 commits August 5, 2026 10:33
The formulation had drifted from the model the scheduler actually builds.
It now covers sub-commitments (deviation variables indexed by commitment
group rather than by time step), stock groups and the recursive stock
balance, per-commodity EMS constraint groups, device-scoped and stock
commitments, hard flow coupling, balance groups for internal commodity
nodes, and power bands (S2 operation modes), plus the binary variables
that make this a mixed-integer program.

Also drops the perfect/left/right efficiency variants, which the
scheduler no longer models (it always uses the linear treatment; the
other variants live on in apply_stock_changes_and_losses).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKx19DmqHiTBS7iEeFTijs
Signed-off-by: F.N. Claessen <felix@seita.nl>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKx19DmqHiTBS7iEeFTijs
Signed-off-by: F.N. Claessen <felix@seita.nl>
@read-the-docs-community

read-the-docs-community Bot commented Aug 5, 2026

Copy link
Copy Markdown

Documentation build overview

📚 flexmeasures | 🛠️ Build #33924184 | 📁 Comparing c96af4b against latest (65d2a70)

  🔍 Preview build  

7 files changed · ± 7 modified

± Modified

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the device scheduler documentation to match the current mixed-integer linear model the scheduler actually constructs, and adds a changelog entry to surface the documentation refresh to end users/plugin developers.

Changes:

  • Rewrite documentation/concepts/device_scheduler.rst to describe the current (MI)LP formulation: sub-commitments, stock groups, EMS constraint groups, device/stock-scoped commitments, coupling/balance groups, and power bands.
  • Add explanatory material about when binaries are introduced (non-convex deviation costs, power sign, and band choice) and clarify backend parity (Pyomo vs direct HiGHS).
  • Add a main changelog entry announcing the documentation update.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
documentation/concepts/device_scheduler.rst Major rewrite of the scheduler model documentation to reflect current formulation and concepts
documentation/changelog.rst Adds a changelog entry noting the documentation update
Suppressed comments (2)

documentation/concepts/device_scheduler.rst:46

  • Several symbols used later in the equations are not introduced in the Notation section (e.g. s(d), E(e), G(c,g), N(n), and \mathcal{D}(c)). Adding a short definition here would prevent readers from having to infer these mappings/sets from context.
.. note::
  The time index :math:`j` has two interpretations: a time period or an instantaneous moment at the end of time period :math:`j`.
  For example, :math:`j` in flow constraints correspond to time periods, whereas :math:`j` used in a stock constraint refers to the end of time period :math:`j`.

documentation/concepts/device_scheduler.rst:141

  • In the stock recursion, the initial condition uses Stock_0(s), but the parameters table defines Stock_0(d) (initial stock per device, shared within a stock group). This mismatch makes the notation internally inconsistent.
with :math:`Stock(s, -1) = Stock_0(s)`, and with the loss coefficients

Comment on lines +20 to +22
The model below is built by ``flexmeasures.data.models.planning.linear_optimization.device_scheduler`` using Pyomo.
A second backend (``highspy_optimization``) builds the same model directly with the HiGHS Python API, which is much faster to construct.
Both are fed by the same input preparation step (``scheduling_problem.prepare_scheduling_problem``), and the symbols below refer to its output.
…module paths

Adds a "Sets and mappings" table introducing s(d), S(s), E(e), D(c), G(c,g)
and N(n), which the constraints sum over but which were only inferable from
context. The stock recursion's initial condition now uses Stock_0(d), as the
parameters table defines it, rather than an undefined per-group Stock_0(s).
Also spells out the HiGHS backend and problem-preparation entrypoints in full.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKx19DmqHiTBS7iEeFTijs
Signed-off-by: F.N. Claessen <felix@seita.nl>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

documentation/concepts/device_scheduler.rst:234

  • The text says the commitment-deviation sign binaries are only added when the cost curve is non-convex, but in the Pyomo backend commitment_sign is declared unconditionally and only the constraints are gated (see flexmeasures/data/models/planning/linear_optimization.py:500 and the if not convex_cost_curve block at ~653). Consider adjusting the wording to avoid claiming the binaries themselves are conditionally added.
These two constraints (and their binary variables) are only added when the summed deviation prices do not describe a convex cost curve.
For a convex cost curve, deviating in both directions is never optimal anyway, and leaving the binaries out keeps the problem a pure LP.

documentation/concepts/device_scheduler.rst:77

  • Stock_{min}(d,j) / Stock_{max}(d,j) are described here as bounds on the absolute stock at the end of the period, but later (and in the implementation) they bound the stock change relative to the initial stock. Updating these parameter descriptions would avoid that mismatch.

This issue also appears on line 233 of the same file.

:math:`Stock_{min}(d,j)`                    device_min                         Minimum quantity for the stock of device :math:`d` at the end of time period :math:`j`.
:math:`Stock_{max}(d,j)`                    device_max                         Maximum quantity for the stock of device :math:`d` at the end of time period :math:`j`.

…the sign binary

The parameters table described device_min/device_max as absolute stock
bounds, while both the constraint below it and the implementation bound
the stock change since the start of the schedule.

Also corrects the claim that the commitment-sign binaries are only added
for a non-convex cost curve: only the constraints are gated in the Pyomo
backend, which declares the variable either way. The direct HiGHS backend
does skip the variable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKx19DmqHiTBS7iEeFTijs
Signed-off-by: F.N. Claessen <felix@seita.nl>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

documentation/concepts/device_scheduler.rst:35

  • The index description for :math:c implies sub-commitments are always split by deviation direction. In the implementation, commitments are split by deviation direction only when a group spans multiple time steps; single-row groups remain a single sub-commitment carrying both prices. Updating this wording here would better match the explanation below and the scheduler implementation.
:math:`c`  c                     Sub-commitments: one per commitment group and per deviation direction (see `Commitments and sub-commitments`_).

…irection

The index table claimed sub-commitments are always split per deviation
direction, while the split only happens for groups spanning several time
steps, as the section below already explained.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKx19DmqHiTBS7iEeFTijs
Signed-off-by: F.N. Claessen <felix@seita.nl>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

documentation/concepts/device_scheduler.rst:85

  • In the Parameters table, the “Variable in the Code” entry for the coupling coefficient \u03b3(k,d) is listed as “coupling coefficient”, but the prepared-problem field is coupling_device_specs (a list of (group, device, coefficient) triples). Using the actual identifier here makes it easier to find in the codebase.
:math:`\gamma(k,d)`                         coupling coefficient               Fixed proportion of device :math:`d` within coupling group :math:`k`. Positive for inputs (consuming), negative for outputs (producing).

documentation/concepts/device_scheduler.rst:107

  • The Variables table no longer lists the auxiliary \u0394Stock(s,j) symbol, even though it is introduced and used later in the “State dynamics” section. Adding it back (as documentation-only) makes the notation section self-contained.
========================  ==============================  =====================================================================================================================
Symbol                    Variable in the Code            Description
========================  ==============================  =====================================================================================================================
:math:`\Delta_{up}(c)`    commitment_upwards_deviation    Upwards deviation from sub-commitment :math:`c` (:math:`\geq 0`). One variable per sub-commitment, not per time step.
:math:`\Delta_{down}(c)`  commitment_downwards_deviation  Downwards deviation from sub-commitment :math:`c` (:math:`\leq 0`).
:math:`\sigma_c(c)`       commitment_sign                 Binary. Upwards deviation allowed if :math:`\sigma_c(c)=1`, downwards deviation otherwise.
:math:`P_{up}(d,j)`       device_power_up                 Upwards (consuming) power of device :math:`d` during time period :math:`j` (:math:`\geq 0`).
:math:`P_{down}(d,j)`     device_power_down               Downwards (producing) power of device :math:`d` during time period :math:`j` (:math:`\leq 0`).
:math:`P^{ems}(d,j)`      ems_power                       Net flow of device :math:`d` during time period :math:`j`.
:math:`\sigma(d,j)`       device_power_sign               Binary. Upwards power activation if :math:`\sigma(d,j)=1`, downwards power activation otherwise.
:math:`Stock(s,j)`        group_stock                     Stock of stock group :math:`s` at the end of time period :math:`j`.
:math:`\alpha(k,j)`       coupling_alpha                  Common normalised flow level of coupling group :math:`k` during time period :math:`j`.
:math:`y(d,b,j)`          device_band                     Binary. Device :math:`d` operates in power band :math:`b` during time period :math:`j`.
========================  ==============================  =====================================================================================================================

…ock change

Points the coupling coefficient at the prepared-problem field that
actually holds it, and puts the documentation-only Delta Stock(s,j)
symbol back in the variables table, so the notation section covers
everything the equations below use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKx19DmqHiTBS7iEeFTijs
Signed-off-by: F.N. Claessen <felix@seita.nl>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

documentation/concepts/device_scheduler.rst:112

  • To support the new ``:ref:`commitments_and_sub_commitments``` reference (and to avoid Sphinx 'unknown target' warnings), add an explicit label for this section.
Commitments and sub-commitments
--------------------------------

documentation/concepts/device_scheduler.rst:35

  • The reference (see `Commitments and sub-commitments`_) uses a named link target that isn’t defined anywhere in the document, which will typically raise a Sphinx warning (unknown target). Prefer an explicit Sphinx section reference via :ref: (with a label defined at the section).

This issue also appears on line 110 of the same file.

:math:`c`  c                     Sub-commitments: one per commitment group, and one per deviation direction where a group spans several time steps (see `Commitments and sub-commitments`_).

@Flix6x

Flix6x commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

Not acting on the latest review round: the two remaining suggestions are based on a false premise.

They claim that `Commitments and sub-commitments`_ is an undefined target that will raise a Sphinx "unknown target" warning, and that an explicit :ref: label is needed. It isn't: a section title creates an implicit hyperlink target in docutils, which resolves within the document. (The review also refers to a :ref:commitments_and_sub_commitments`` that this PR never introduces.)

Verified on the built HTML:

$ grep -o 'id="commitments-and-sub-commitments"' device_scheduler.html
id="commitments-and-sub-commitments"
$ grep -o 'href="#commitments-and-sub-commitments"[^<]*<' device_scheduler.html
href="#commitments-and-sub-commitments">Commitments and sub-commitments<

sphinx-build over documentation/ emits no warnings, and the Read the Docs build for this PR is green.

The four earlier rounds were all valid and are addressed.

@Flix6x Flix6x self-assigned this Aug 5, 2026
@Flix6x Flix6x added Scheduling documentation Improvements or additions to documentation labels Aug 5, 2026
@Flix6x Flix6x added this to the 1.0.0 milestone Aug 5, 2026
Comment on lines +20 to +23
The model below is built by ``flexmeasures.data.models.planning.linear_optimization.device_scheduler`` using Pyomo.
A second backend, ``flexmeasures.data.models.planning.highspy_optimization.device_scheduler_highspy``,
builds the same model directly with the HiGHS Python API, which is much faster to construct.
Both are fed by the same input preparation step, ``flexmeasures.data.models.planning.scheduling_problem.prepare_scheduling_problem``,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's link these to the actual place in the _autosummary docs and shorten the text to just the module, so the text alignment / wrapping doesn't leave so much white space (e.g. between "second" and "backend").

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in c96af4b. They are now :func:/:mod: roles with the ~ prefix, so they link into the _autosummary pages — down to the function anchor — while rendering as just device_scheduler(), highspy_optimization, prepare_scheduling_problem() and apply_stock_changes_and_losses(). That also takes the long unbreakable literals out of the paragraph flow.

Verified in a local build (with the branch's source on PYTHONPATH, so autosummary picks up highspy_optimization and scheduling_problem, which are newer than my installed checkout):

href="../_autosummary/...linear_optimization.html#...linear_optimization.device_scheduler"
href="../_autosummary/...highspy_optimization.html#module-...highspy_optimization"
href="../_autosummary/...scheduling_problem.html#...scheduling_problem.prepare_scheduling_problem"
href="../_autosummary/flexmeasures.utils.calculations.html#...apply_stock_changes_and_losses"

I applied the same treatment to the apply_stock_changes_and_losses reference further down, which had the same problem.

Replaces the fully qualified literals with :func:/:mod: roles, which
resolve into the _autosummary pages (down to the function anchor) while
rendering as just device_scheduler(), highspy_optimization, and so on,
so the paragraphs no longer wrap around long unbreakable literals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKx19DmqHiTBS7iEeFTijs
Signed-off-by: F.N. Claessen <felix@seita.nl>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation Scheduling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants