Skip to content

Electrolyser part-load curve: selectable shape, variable breakpoint count, two conditioning flags - #160

Merged
erikfilias merged 2 commits into
mainfrom
feature/electrolyser-pwl-curve
Jul 30, 2026
Merged

Electrolyser part-load curve: selectable shape, variable breakpoint count, two conditioning flags#160
erikfilias merged 2 commits into
mainfrom
feature/electrolyser-pwl-curve

Conversation

@erikfilias

Copy link
Copy Markdown
Contributor

Four new parameters in the investment and input layers. All default to current behaviour, so
existing cases are unchanged.

Parameter Default Effect when set
pParElectrolyserPWLPoints 4 breakpoints per unit curve; the default reproduces the old spacing exactly
pParElectrolyserCurve published ulleberg selects a corrected alkaline curve
pParNormalisePeriodWeight 0 divides the period weight out of the capex row, reapplies it once in the objective
pParFixDisabledCandidates 0 fixes a switched-off candidate's build to zero instead of bounding it at 1e-9

Part-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).

pParNormalisePeriodWeight must not be combined with the decomposition: vTotalICost is a
complicating 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.

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.
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 6 complexity · 0 duplication

Metric Results
Complexity 6
Duplication 0

View in Codacy

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.

@erikfilias
erikfilias merged commit 9901a97 into main Jul 30, 2026
13 of 14 checks passed
@erikfilias
erikfilias deleted the feature/electrolyser-pwl-curve branch July 30, 2026 10:07
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