Skip to content

fix: clamp s/p_nom_max in brownfield to prevent floating-point infeasibility#2115

Merged
lkstrp merged 4 commits intoPyPSA:masterfrom
thogin:fix/brownfield-nom-min-max-clamp
Mar 25, 2026
Merged

fix: clamp s/p_nom_max in brownfield to prevent floating-point infeasibility#2115
lkstrp merged 4 commits intoPyPSA:masterfrom
thogin:fix/brownfield-nom-min-max-clamp

Conversation

@thogin
Copy link
Copy Markdown
Contributor

@thogin thogin commented Mar 6, 2026

Changes proposed in this Pull Request

In myopic optimisation, add_brownfield() sets s_nom_min and p_nom_min for AC lines and DC links to the previous
horizon's s_nom_opt / p_nom_opt. Due to solver floating-point tolerance, the optimised value can slightly exceed
s_nom_max, creating a per-component min > max infeasibility.

I ran into this on a 50-cluster, 6-horizon sector-coupled run where an AC line (DE2 → NL2) ended up with s_nom_min
exceeding s_nom_max by 0.00013 MW on a 33,585 MW line — enough to make the solver declare infeasibility.

PRs #840 and #952 fixed the related aggregate case (total s_nom_min hitting the global transmission expansion
limit). This PR adds the complementary per-component fix: clamping s_nom_max / p_nom_max to be at least
s_nom_min / p_nom_min right after the brownfield assignment.

The pattern is consistent with the existing update_p_nom_max() in _helpers.py and the generator clamp in
add_existing_baseyear.py, both of which already handle the same class of issue for other component types.

Checklist

  • I tested my contribution locally and it works as intended.
  • Code and workflow changes are sufficiently documented.
  • Changed dependencies are added to pixi.toml (using pixi add <dependency-name>).
  • Changes in configuration options are added in config/config.default.yaml.
  • Changes in configuration options are documented in doc/configtables/*.csv.
  • A release note doc/release_notes.rst is added.

…ibility

When brownfield carries forward s_nom_opt from a previous horizon as
s_nom_min, solver floating-point tolerance can make s_nom_min exceed
s_nom_max by a tiny amount (e.g., 0.00013 MW on a 33,585 MW line).
This creates an infeasible constraint that crashes the solver.

Extends the fix from PRs PyPSA#840 and PyPSA#952 (which handle global transmission
constraints) to also cover per-line s_nom_max and DC link p_nom_max.
@fneum fneum enabled auto-merge (squash) March 18, 2026 09:06
When barrier solutions without crossover produce slightly imprecise
p_nom_opt values, the already-retrofitted H2 pipeline capacity can
marginally exceed the original pipe capacity. This makes
remaining_capacity negative, setting p_nom_max < p_nom_min = 0,
which causes infeasibility at the next planning horizon.

Observed on 115-cluster myopic runs with BarConvTol=1e-4:
  H2 pipeline retrofitted ES2 7 -> FR2 1: p_nom_max = -0.000649 MW
  H2 pipeline retrofitted GB1 3 -> GB1 7: p_nom_max = -0.000169 MW

Apply the same clip(lower=0) pattern used for AC lines and DC links.
auto-merge was automatically disabled March 25, 2026 05:27

Head branch was pushed to by a user without write access

@lkstrp lkstrp enabled auto-merge (squash) March 25, 2026 13:03
@lkstrp lkstrp merged commit 192857b into PyPSA:master Mar 25, 2026
8 checks passed
@thogin thogin deleted the fix/brownfield-nom-min-max-clamp branch March 25, 2026 14:17
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.

3 participants