Small penalties to prevent volatile asset utilisation - #1478
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1478 +/- ##
==========================================
+ Coverage 89.78% 89.91% +0.13%
==========================================
Files 60 60
Lines 8445 8556 +111
Branches 8445 8556 +111
==========================================
+ Hits 7582 7693 +111
+ Misses 545 543 -2
- Partials 318 320 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
93a1eb9 to
ea81668
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces optional seasonal and annual utilisation “peak” penalties in the dispatch optimisation objective to encourage smoother intra-season and inter-season activity profiles, helping break ties when seasonal/annual commodity balances otherwise leave dispatch underdetermined.
Changes:
- Added auxiliary variables and constraints to represent seasonal peak capacity requirements (and optionally annual peak) per asset, and included them as small weighted terms in the optimisation objective.
- Added two new model parameters (
seasonal_utilisation_penalty,annual_utilisation_penalty) with defaults and schema support. - Documented the mathematical formulation of the new penalties in the dispatch optimisation docs.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/simulation/optimisation/constraints.rs | Adds seasonal/annual peak variables, constraints, and objective coefficients for utilisation equalisation. |
| src/model/parameters.rs | Introduces new penalty parameters and defaults in ModelParameters. |
| schemas/input/model.yaml | Exposes the new parameters in the input schema (types, defaults, notes). |
| docs/model/dispatch_optimisation.md | Documents the new penalties and their mathematical formulation. |
Suppressed comments (1)
src/simulation/optimisation/constraints.rs:174
- This comment says the variable represents "seasonal peak activity", but the variable is actually the peak capacity required (as used in the constraints and objective).
// Create a variable for each (asset, season) pair, representing the seasonal peak activity
let mut seasonal_peak_vars = SeasonalPeakVariableMap::new();
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| seasonal_utilisation_penalty: MoneyPerCapacityPerYear(1e-6), | ||
| annual_utilisation_penalty: MoneyPerCapacityPerYear(1e-6), |
9e118c3 to
8daa78d
Compare
ahawkes
left a comment
There was a problem hiding this comment.
LGTM. Would be good to be able to roll back though, in case we find issues when running more complex models.
It can be turned off by setting the penalties to zero (in this case the variables/constraints wouldn't be added). The code is also pretty self-contained, so would be easy to delete if we ever wanted to. |
dc2917
left a comment
There was a problem hiding this comment.
Implementation looks perfectly reasonable to me
Description
Adds a small penalty on peak seasonal/annual utilisation to promote even spreading of utilisation, rather than loading all production onto a single timeslice/season. This is especially useful for seasonally/annually balanced commodities, which may otherwise have no incentive for one activity profile over the other. The rationale for this is that there may be costs involved with having to maintain a high level of capacity to support peak use, and/or costs associated with switching between high and low activity. The penalties should be kept very small so they don't dominate the primary objective and only serve to break ties between otherwise equally good solutions.
The main reason for this is to avoid arbitrary activity profiles as a result of the solver picking one amongst many equally good solutions. We can see in the results that there are fewer zeros in the flows files, which previously often arose when assets choose to serve full seasonal demands in one or two timeslices, which in the real world wouldn't happen.
This also helps with #1399 and #1174, although to fully close both of these we also need additional constraints to spread utilisation evenly among equally good assets (i.e. removing the ability to chose arbitrarily among equally good assets). That's coming in #1477
Screenshot from documentation
Performance discussion
If both penalties are active (>0), this adds:
i.e. scales linearly with the number of assets and temporal granularity. The performance cost for the example models is about 5-10%. For the
simple_modifiedexample, which has many more assets, it's more like 30%. I think this is probably acceptable, and since the number of added variables/constraints increases linearly with the problem size, I wouldn't expect the performance cost to vary much in % terms even for much larger models.Fixes # (issue)
Type of change
Key checklist
$ cargo test$ cargo docpresent in the previous release
Further checks