Skip to content

Fix extendable thermal handling in the UCBlock expansion path - #56

Merged
AlessandroPampado99 merged 2 commits into
SPSUnipi:mainfrom
dmeoli:fix/ucblock-extendable-thermal
Aug 3, 2026
Merged

Fix extendable thermal handling in the UCBlock expansion path#56
AlessandroPampado99 merged 2 commits into
SPSUnipi:mainfrom
dmeoli:fix/ucblock-extendable-thermal

Conversation

@dmeoli

@dmeoli dmeoli commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Problem

On a pypsa-eur instance with extendable CCGTs, converting with
capacity_expansion_ucblock=True gave an objective +426% above the PyPSA
reference, while the InvestmentBlock path was within 0.06%.

The UCBlock path normalizes an extendable unit's MaxPower to a per-module
value (a design variable is meant to carry the MW), but the design fields
(InvestmentCost / MaxCapacityDesign) are only injected for intermittent
and battery units. Extendable thermals were left normalized with no design
variable
, i.e. modeled as 1 MW plants. UCBlock cannot represent continuous
thermal expansion anyway: the commitment u_t is binary and a continuous
design would multiply it in the max-power constraint (p_t ≤ κ·MaxPower·u_t),
making the model bilinear.

A second, latent issue: StartUpCost and ConstTerm were passed through as
absolute per-generator values while every other scaled field is normalized
per module when p_nom_extendable. Under Scale/InvestmentBlock scaling they
were multiplied by the capacity again (double-counting), affecting both paths.

Fix

  • Extendable thermal in the UCBlock path → raise a ValueError: the
    converter cannot represent it and does not silently alter the model. The
    message asks to either set p_nom_extendable=False on the unit or use the
    InvestmentBlock path (capacity_expansion_ucblock=False).
  • StartUpCost / ConstTerm → normalized per module when
    p_nom_extendable, like the other scaled fields.

Validation

  • pypsa-eur instance, UCBlock path: the conversion now fails fast with the
    error above instead of returning a +426% objective.
  • test_ucblock 15/15 pass (networks without extendable thermals are
    unaffected).

Not covered

QuadTerm (quadratic marginal cost) is structurally incompatible with the
fleet/Scale semantics for thermals (S·q·p² ≠ q·(S·p)²) and cannot be fixed
by normalization; it is 0 in the tested instances. A follow-up could warn when
a nonzero quadratic cost meets an extendable thermal.

UCBlock cannot expand a thermal unit: its commitment u_t is binary and a
continuous design would multiply it in the max-power constraint. Such a unit
was instead normalized to a 1 MW module with no design variable, inflating
the objective (+426% on a pypsa-eur instance). It is now fixed at its nominal
capacity, warned about, and its (constant) capex added back to the reported
objective to match PyPSA.

Also normalize StartUpCost and ConstTerm per module when p_nom_extendable,
like the other scaled fields, so an extendable thermal no longer double-counts
them under Scale.
)


# UCBlock cannot expand a thermal unit: its commitment u_t is a

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Non trovo molto utile tutta questa cosa... Mi sembra come mettere una pezza. Potrebbe essere più utile rompere il loop e dare errore in caso, che dici? Perché tanto non avremmo comunque una corrispondenza in fo e uno user generico non capirebbe

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Aggiungere poi il costo per far tornare i conti in quel modo credo che prima o poi farà rompere qualcosa. Le correzioni in transformation_config vanno bene invece

Replace the fix-at-nominal-capacity fallback (warning + capex constant added
back to the reported objective) with a ValueError: the converter no longer
silently alters the model, it asks to fix the unit or use the InvestmentBlock
path.
@AlessandroPampado99
AlessandroPampado99 merged commit faba2a2 into SPSUnipi:main Aug 3, 2026
3 checks passed
@dmeoli
dmeoli deleted the fix/ucblock-extendable-thermal branch August 3, 2026 18:46
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.

2 participants