Skip to content

Commit 7198591

Browse files
committed
apply power limit to gross imports/exports
1 parent df36b86 commit 7198591

File tree

4 files changed

+123
-27
lines changed

4 files changed

+123
-27
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ cutouts
8585

8686
# custom local files
8787
local
88+
figures
8889
# private dev folder
8990
dev/*
9091

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Changelog
2+
- Bugfix: Enforce stricter power import limit to avoid that import from one country compensate from exports to another
23
- Bugfix: Enforce stricter H2 derivative import limit to avoid that exports of one type of derivative compensate for imports of another
34
- Added an option to source mobility demand from UBA MWMS (Projektionsbericht 2025) for the years 2025-2035
45
- Renamed functions and script for exogenous mobility demand

config/config.de.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#run
66
run:
7-
prefix: 20250901_h2_deriv_fix
7+
prefix: 20250926_improve_power_limits
88
name:
99
# - ExPol
1010
- KN2045_Mix

scripts/pypsa-de/additional_functionality.py

Lines changed: 120 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -106,56 +106,150 @@ def add_power_limits(n, investment_year, limits_power_max):
106106
if investment_year not in limits_power_max[ct].keys():
107107
continue
108108

109-
limit = 1e3 * limits_power_max[ct][investment_year] / 10
109+
lim = 1e3 * limits_power_max[ct][investment_year] # in MW
110110

111111
logger.info(
112-
f"Adding constraint on electricity import/export from/to {ct} to be < {limit} MW"
112+
f"Adding constraint on electricity import/export from/to {ct} to be < {lim} MW"
113113
)
114-
incoming_line = n.lines.index[
114+
# identify interconnectors
115+
116+
incoming_lines = n.lines[
115117
(n.lines.carrier == "AC")
116118
& (n.lines.bus0.str[:2] != ct)
117119
& (n.lines.bus1.str[:2] == ct)
120+
& n.lines.active
118121
]
119-
outgoing_line = n.lines.index[
122+
outgoing_lines = n.lines[
120123
(n.lines.carrier == "AC")
121124
& (n.lines.bus0.str[:2] == ct)
122125
& (n.lines.bus1.str[:2] != ct)
126+
& n.lines.active
123127
]
124-
125-
incoming_link = n.links.index[
128+
incoming_links = n.links[
126129
(n.links.carrier == "DC")
127130
& (n.links.bus0.str[:2] != ct)
128131
& (n.links.bus1.str[:2] == ct)
132+
& n.links.active
129133
]
130-
outgoing_link = n.links.index[
134+
outgoing_links = n.links[
131135
(n.links.carrier == "DC")
132136
& (n.links.bus0.str[:2] == ct)
133137
& (n.links.bus1.str[:2] != ct)
138+
& n.links.active
134139
]
135140

136-
# iterate over snapshots - otherwise exporting of postnetwork fails since
137-
# the constraints are time dependent
138141
for t in n.snapshots:
139-
incoming_line_p = n.model["Line-s"].loc[t, incoming_line]
140-
outgoing_line_p = n.model["Line-s"].loc[t, outgoing_line]
141-
incoming_link_p = n.model["Link-p"].loc[t, incoming_link]
142-
outgoing_link_p = n.model["Link-p"].loc[t, outgoing_link]
143-
144-
lhs = (
145-
incoming_link_p.sum()
146-
- outgoing_link_p.sum()
147-
+ incoming_line_p.sum()
148-
- outgoing_line_p.sum()
149-
) / 10
150-
# divide by 10 to avoid numerical issues
142+
# For incoming flows s > 0 means imports, s < 0 exports
143+
# For outgoing flows s > 0 means exports, s < 0 imports
144+
# to get the positive and negative parts separately, we use auxiliary variables
145+
incoming_lines_var = n.model["Line-s"].loc[t, incoming_lines.index]
146+
n.model.add_variables(
147+
coords=[incoming_lines.index],
148+
name=f"Line-s-incoming-{ct}-aux-pos-{t}",
149+
lower=0,
150+
upper=incoming_lines.s_nom_max,
151+
)
152+
n.model.add_variables(
153+
coords=[incoming_lines.index],
154+
name=f"Line-s-incoming-{ct}-aux-neg-{t}",
155+
lower=-incoming_lines.s_nom_max,
156+
upper=0,
157+
)
158+
n.model.add_constraints(
159+
n.model[f"Line-s-incoming-{ct}-aux-pos-{t}"] >= incoming_lines_var,
160+
name=f"Line-s-incoming-{ct}-aux-pos-constr-{t}",
161+
)
162+
n.model.add_constraints(
163+
n.model[f"Line-s-incoming-{ct}-aux-neg-{t}"] <= incoming_lines_var,
164+
name=f"Line-s-incoming-{ct}-aux-neg-constr-{t}",
165+
)
151166

152-
cname_upper = f"Power-import-limit-{ct}-{t}"
153-
cname_lower = f"Power-export-limit-{ct}-{t}"
167+
outgoing_lines_var = n.model["Line-s"].loc[t, outgoing_lines.index]
168+
n.model.add_variables(
169+
coords=[outgoing_lines.index],
170+
name=f"Line-s-outgoing-{ct}-aux-pos-{t}",
171+
lower=0,
172+
upper=outgoing_lines.s_nom_max,
173+
)
174+
n.model.add_variables(
175+
coords=[outgoing_lines.index],
176+
name=f"Line-s-outgoing-{ct}-aux-neg-{t}",
177+
lower=-outgoing_lines.s_nom_max,
178+
upper=0,
179+
)
180+
n.model.add_constraints(
181+
n.model[f"Line-s-outgoing-{ct}-aux-pos-{t}"] >= outgoing_lines_var,
182+
name=f"Line-s-outgoing-{ct}-aux-pos-constr-{t}",
183+
)
184+
n.model.add_constraints(
185+
n.model[f"Line-s-outgoing-{ct}-aux-neg-{t}"] <= outgoing_lines_var,
186+
name=f"Line-s-outgoing-{ct}-aux-neg-constr-{t}",
187+
)
154188

155-
n.model.add_constraints(lhs <= limit, name=cname_upper)
156-
n.model.add_constraints(lhs >= -limit, name=cname_lower)
189+
incoming_links_var = n.model["Link-p"].loc[t, incoming_links.index]
190+
n.model.add_variables(
191+
coords=[incoming_links.index],
192+
name=f"Link-p-incoming-{ct}-aux-pos-{t}",
193+
lower=0,
194+
upper=incoming_links.p_nom_max,
195+
)
196+
n.model.add_variables(
197+
coords=[incoming_links.index],
198+
name=f"Link-p-incoming-{ct}-aux-neg-{t}",
199+
lower=-incoming_links.p_nom_max,
200+
upper=0,
201+
)
202+
n.model.add_constraints(
203+
n.model[f"Link-p-incoming-{ct}-aux-pos-{t}"] >= incoming_links_var,
204+
name=f"Link-p-incoming-{ct}-aux-pos-constr-{t}",
205+
)
206+
n.model.add_constraints(
207+
n.model[f"Link-p-incoming-{ct}-aux-neg-{t}"] <= incoming_links_var,
208+
name=f"Link-p-incoming-{ct}-aux-neg-constr-{t}",
209+
)
157210

158-
# not adding to network as the shadow prices are not needed
211+
outgoing_links_var = n.model["Link-p"].loc[t, outgoing_links.index]
212+
n.model.add_variables(
213+
coords=[outgoing_links.index],
214+
name=f"Link-p-outgoing-{ct}-aux-pos-{t}",
215+
lower=0,
216+
upper=outgoing_links.p_nom_max,
217+
)
218+
n.model.add_variables(
219+
coords=[outgoing_links.index],
220+
name=f"Link-p-outgoing-{ct}-aux-neg-{t}",
221+
lower=-outgoing_links.p_nom_max,
222+
upper=0,
223+
)
224+
n.model.add_constraints(
225+
n.model[f"Link-p-outgoing-{ct}-aux-pos-{t}"] >= outgoing_links_var,
226+
name=f"Link-p-outgoing-{ct}-aux-pos-constr-{t}",
227+
)
228+
n.model.add_constraints(
229+
n.model[f"Link-p-outgoing-{ct}-aux-neg-{t}"] <= outgoing_links_var,
230+
name=f"Link-p-outgoing-{ct}-aux-neg-constr-{t}",
231+
)
232+
# To constraint the absolute values of imports and exports, we have to sum the
233+
# corresponding positive and negative flows separately, using auxiliary variables
234+
import_lhs = (
235+
n.model[f"Link-p-incoming-{ct}-aux-pos-{t}"].sum()
236+
+ n.model[f"Line-s-incoming-{ct}-aux-pos-{t}"].sum()
237+
- n.model[f"Link-p-outgoing-{ct}-aux-neg-{t}"].sum()
238+
- n.model[f"Line-s-outgoing-{ct}-aux-neg-{t}"].sum()
239+
) / 10 # divide by 10 to improve numerical stability
240+
export_lhs = (
241+
n.model[f"Link-p-outgoing-{ct}-aux-pos-{t}"].sum()
242+
+ n.model[f"Line-s-outgoing-{ct}-aux-pos-{t}"].sum()
243+
- n.model[f"Link-p-incoming-{ct}-aux-neg-{t}"].sum()
244+
- n.model[f"Line-s-incoming-{ct}-aux-neg-{t}"].sum()
245+
) / 10
246+
247+
n.model.add_constraints(
248+
import_lhs <= lim / 10, name=f"Power-import-limit-{ct}-{t}"
249+
)
250+
n.model.add_constraints(
251+
export_lhs <= lim / 10, name=f"Power-export-limit-{ct}-{t}"
252+
)
159253

160254

161255
def h2_import_limits(n, investment_year, limits_volume_max):

0 commit comments

Comments
 (0)