Electrolyser part-load curve: selectable shape, variable breakpoint count, two conditioning flags - #160
Merged
Merged
Conversation
Two additions, both off by default, so every existing case is unchanged. pParElectrolyserPWLPoints sets how many breakpoints each unit's piecewise curve carries. It defaults to 4 and reproduces the old spacing exactly at that value. Four points cannot represent an efficiency optimum that sits between them, which is why this is needed. pParElectrolyserCurve selects the shape. The default keeps the published curve. Setting it to ulleberg selects a corrected alkaline curve whose optimum sits near half load instead of at 85 per cent of load. The reason for the correction is that the published shape contradicts the source it is cited against. Buttler and Spliethoff report that efficiency falls as current density rises, so specific consumption cannot have its minimum near full load. The replacement is derived from Ulleberg's current-voltage relation and calibrated to the Danish Energy Agency 10 MW alkaline sheet, rather than traced off a published figure. PEM is not corrected. Its reference is paywalled and not yet read, so the published PEM shape is resampled onto the new breakpoint grid unchanged. Related finding, not fixed here. The published curves are not concave: the first slope difference is positive for both technologies. A concave curve is what makes dropping the segment binaries exact, so on these curves the relaxation can reach above the true curve. The new alkaline curve is concave and does not have this problem. PEM still does.
Both come from the scaling review of 4 July, both are off by default, and neither is confirmed by a solve yet. pParNormalisePeriodWeight divides the period weight out of the investment cost row and reapplies it once where that cost enters the objective. The weight is a single constant multiplying the whole bracket, so this is a rescaling of one equality and leaves the feasible set alone. It takes the largest capex coefficient down by the size of the weight, which the review measured at 1.5 to 1.7 orders of magnitude on the widest family in the matrix. Do not combine it with the decomposition. vTotalICost is a complicating variable there, so it would carry the unweighted cost while the master problem expects the weighted one. Neither is used in any campaign, so the two never meet today. pParFixDisabledCandidates fixes a switched-off candidate's build to zero instead of bounding it at 1e-9. A case has to use a tiny positive bound today because an upper bound of exactly 0 is read as "no bound given" and reset to 1. The model now records which candidates arrived with a bound of 0 before that reset runs, so it can fix them properly. Presolve then drops the column rather than carrying a variable squeezed into a range 13 orders of magnitude below the others. Both were called exact in the review, but that is an algebraic argument and nothing has measured it. Before either is used, run one case with the flags off and one with them on and confirm the objective matches to solver tolerance, and repeat for a variant that disables candidates.
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 6 |
| Duplication | 0 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four new parameters in the investment and input layers. All default to current behaviour, so
existing cases are unchanged.
pParElectrolyserPWLPointspParElectrolyserCurveullebergselects a corrected alkaline curvepParNormalisePeriodWeightpParFixDisabledCandidatesPart-load curve
The published alkaline shape puts the efficiency optimum at 85% of load. Buttler and Spliethoff,
the source it is cited against, report efficiency falling as current density rises, so the minimum
cannot sit near full load. The replacement is derived from Ulleberg's current-voltage relation and
calibrated to the DEA "86 AEC 10 MW" 2025 sheet; its optimum comes out near half load. The
derivation lives in the companion repository at
analysis/electrolyser_curve.py.Four breakpoints cannot represent an optimum that sits between them, which is why the count is now
a parameter.
PEM is not corrected. Its reference (Astriani et al. 2024) is paywalled and unread, so the
published PEM shape is resampled onto the new grid unchanged.
Conditioning flags
Both from the scaling review of 2026-07-04, part 2. The capex row carries the period weight on
every coefficient, which reaches about 4.4e3 at a year horizon; the weight is a single constant and
can be applied once instead. Disabled candidates are bounded at 1e-9 because an upper bound of
exactly 0 is read as "no bound given" and reset to 1 — the input layer now records which candidates
arrived with a bound of 0 before that reset, so they can be fixed properly.
Both are argued exact algebraically. Neither has been confirmed by a solve. Before either is used
in a campaign, run one case with the flags off and one with them on and check the objective matches
to solver tolerance, then repeat for a variant that disables candidates (A1, A2, D1 or D2).
pParNormalisePeriodWeightmust not be combined with the decomposition:vTotalICostis acomplicating variable there and would carry the unweighted cost while the master expects the
weighted one. Neither path is used in any campaign today.
Related finding, not fixed here
The piecewise curves the published cases run on are not concave — the first slope difference is
positive for both technologies. Dropping the segment binaries is exact only on a concave curve, so
on these curves the relaxation can reach above the curve. Not yet shown to change any result; the
check needs solved hourly dispatch. The new alkaline curve is concave. PEM is not.