Cap extendable AT generator p_nom_max values by regional KLIEN study potentials.
Reads pre-processed capacity potential CSVs (in MW) produced by
build_klien_potentials, subtracts already-committed brownfield capacity,
and writes the remaining headroom into p_nom_max for every extendable
AT generator whose carrier appears in klien_potential_limits.technologies.
Only generators on buses whose index starts with "AT" are affected.
Non-AT generators (e.g. DE, CH) are left unchanged. The function skips
silently when klien_potential_limits.technologies is an empty list.
When klien_potential_limits.use_technical_potentials is true, the column
C_technical_potential is used regardless of year, ambition, or
climate_scenario.
The NUTS3-level potential CSVs are always read and aggregated to the
network's clustering resolution via
:func:mods.clustering.utils.combine_regions_by_clustering (AT10 collapses
NUTS3 -> NUTS2; AT35 keeps NUTS3).
Supported carriers and their CSV source:
solar rooftop — nuts3_pv_buildings.csv
solar, solar-hsat — nuts3_pv_ground.csv (shared land area)
onwind — nuts3_wind.csv
Parameters:
| Name |
Type |
Description |
Default |
n
|
pypsa.Network
|
The pre-network to be modified in place.
|
required
|
snakemake
|
snakemake.script.Snakemake
|
Snakemake workflow object; config is read via snakemake.params and
file paths via snakemake.input.
|
required
|
Raises:
| Type |
Description |
ValueError
|
If any entry in technologies is not a recognised carrier, or if
climate_scenario, year, or ambition are unrecognised.
|
KeyError
|
If the requested scenario column is absent from a potential CSV.
|
Notes
Brownfield capacity is estimated via n.statistics.installed_capacity(),
which captures carry-over from previous myopic periods.
Source code in mods/network/potentials.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252 | def apply_klien_potential_limits(n: pypsa.Network, snakemake: Snakemake) -> None:
"""
Cap extendable AT generator ``p_nom_max`` values by regional KLIEN study potentials.
Reads pre-processed capacity potential CSVs (in MW) produced by
``build_klien_potentials``, subtracts already-committed brownfield capacity,
and writes the remaining headroom into ``p_nom_max`` for every extendable
AT generator whose carrier appears in ``klien_potential_limits.technologies``.
Only generators on buses whose index starts with ``"AT"`` are affected.
Non-AT generators (e.g. DE, CH) are left unchanged. The function skips
silently when ``klien_potential_limits.technologies`` is an empty list.
When ``klien_potential_limits.use_technical_potentials`` is true, the column
``C_technical_potential`` is used regardless of ``year``, ``ambition``, or
``climate_scenario``.
The NUTS3-level potential CSVs are always read and aggregated to the
network's clustering resolution via
:func:`mods.clustering.utils.combine_regions_by_clustering` (AT10 collapses
NUTS3 -> NUTS2; AT35 keeps NUTS3).
Supported carriers and their CSV source:
* ``solar rooftop`` — ``nuts3_pv_buildings.csv``
* ``solar``, ``solar-hsat`` — ``nuts3_pv_ground.csv`` (shared land area)
* ``onwind`` — ``nuts3_wind.csv``
Parameters
----------
n
The pre-network to be modified in place.
snakemake
Snakemake workflow object; config is read via ``snakemake.params`` and
file paths via ``snakemake.input``.
Raises
------
ValueError
If any entry in ``technologies`` is not a recognised carrier, or if
``climate_scenario``, ``year``, or ``ambition`` are unrecognised.
KeyError
If the requested scenario column is absent from a potential CSV.
Notes
-----
Brownfield capacity is estimated via ``n.statistics.installed_capacity()``,
which captures carry-over from previous myopic periods.
"""
technologies = snakemake.params["klien_potential_limits_technologies"]
if not technologies:
logger.info("KLIEN potential limits: technologies list is empty — skipping.")
return
unknown = set(technologies) - set(_PYPSA_TO_KLIEN_MAPPING.keys())
if unknown:
raise ValueError(
f"Unknown technologies in klien_potential_limits.technologies: {unknown}. "
f"Valid options: {list(_PYPSA_TO_KLIEN_MAPPING.keys())}."
)
col = _resolve_scenario_column(snakemake)
# Always read the NUTS3-level KLIEN potentials and aggregate them to the
# network's clustering resolution. For AT10 clusterings this collapses
# NUTS3 -> NUTS2 (potentials are additive); for AT35 the NUTS3 regions are
# kept as-is.
clustering = snakemake.config["mods"]["modify_nuts3_shapes"]
paths = {
"buildings": snakemake.input.nuts3_buildings,
"ground": snakemake.input.nuts3_ground,
"wind": snakemake.input.nuts3_wind,
}
# Load each CSV type once; map each requested carrier to its potential dict.
klien_types_needed = {_PYPSA_TO_KLIEN_MAPPING[t] for t in technologies}
carrier_potential = {}
for klien_type in klien_types_needed:
df = pd.read_csv(Path(paths[klien_type]), index_col=0)
potential_dict = combine_regions_by_clustering(df[col], clustering).to_dict()
for tech in _KLIEN_TO_PYPSA_MAPPING[klien_type]:
carrier_potential[tech] = potential_dict
brownfield = n.statistics.installed_capacity(
groupby=["location", "carrier"],
components="Generator",
carrier=list(technologies),
aggregate_across_components=True,
nice_names=False,
drop_zero=False,
)
brownfield_at = brownfield[
brownfield.index.get_level_values("location").str.startswith("AT")
]
for (location, carrier), brownfield_value in brownfield_at.items():
potential = carrier_potential[carrier][location]
mask_ext = (
(n.generators.index.str.startswith(f"{location} "))
& (n.generators["carrier"] == carrier)
& (n.generators["p_nom_extendable"])
)
if not any(mask_ext):
continue
# Make sure that the upper limit can always be reached
new_upper_limit = max(0.0, potential, brownfield_value)
for gen_idx in n.generators.index[mask_ext]:
_set_p_nom_max(n, gen_idx, new_upper_limit)
logger.info(f"AT KLIEN potential limits applied for: {list(technologies)}.")
|