Merge branch 'feature/enhance-agg-p-nom-limits' of github.com:Climact/pypsa-eur-climact into Climact-feature/enhance-agg-p-nom-limits

This commit is contained in:
Fabian Neumann 2024-05-20 15:49:14 +02:00
commit 65b05e92a1
6 changed files with 88 additions and 38 deletions

View File

@ -94,7 +94,6 @@ electricity:
co2limit_enable: false
co2limit: 7.75e+7
co2base: 1.487e+9
agg_p_nom_limits: data/agg_p_nom_minmax.csv
operational_reserve:
activate: false
@ -805,6 +804,11 @@ solving:
H2 pipeline: 0.3
gas pipeline: 0.3
agg_p_nom_limits:
agg_offwind: false
include_existing: false
file: data/agg_p_nom_minmax.csv
constraints:
CCL: false
EQ: false

View File

@ -1,31 +1,33 @@
country,carrier,min,max
DE,onwind,0.1,
DE,offwind-ac,0.1,
DE,offwind-dc,0.1,
DE,solar,0.2,
LU,onwind,,
LU,solar,,
NL,onwind,,
NL,offwind-ac,,
NL,offwind-dc,,
NL,solar,,
GB,onwind,,
GB,offwind-ac,,
GB,offwind-dc,,
GB,solar,,
IE,onwind,,
IE,offwind-ac,,
IE,offwind-dc,,
IE,solar,,
FR,onwind,,
FR,offwind-ac,,
FR,offwind-dc,,
FR,solar,,
DK,onwind,,
DK,offwind-ac,,
DK,offwind-dc,,
DK,solar,,
BE,onwind,,
BE,offwind-ac,,
BE,offwind-dc,,
BE,solar,,
,,2030,2030,2040,2040,2050,2050
,,min,max,min,max,min,max
country,carrier,,,,,,
DE,onwind,0.1,,0.1,,0.1,
DE,offwind-ac,0.1,,0.1,,0.1,
DE,offwind-dc,0.1,,0.1,,0.1,
DE,solar,0.2,,0.2,,0.2,
LU,onwind,,,,,,
LU,solar,,,,,,
NL,onwind,,,,,,
NL,offwind-ac,,,,,,
NL,offwind-dc,,,,,,
NL,solar,,,,,,
GB,onwind,,,,,,
GB,offwind-ac,,,,,,
GB,offwind-dc,,,,,,
GB,solar,,,,,,
IE,onwind,,,,,,
IE,offwind-ac,,,,,,
IE,offwind-dc,,,,,,
IE,solar,,,,,,
FR,onwind,,,,,,
FR,offwind-ac,,,,,,
FR,offwind-dc,,,,,,
FR,solar,,,,,,
DK,onwind,,,,,,
DK,offwind-ac,,,,,,
DK,offwind-dc,,,,,,
DK,solar,,,,,,
BE,onwind,,,,,,
BE,offwind-ac,,,,,,
BE,offwind-dc,,,,,,
BE,solar,,,,,,
1 country carrier min 2030 max 2030 2040 2040 2050 2050
2 DE onwind 0.1 min max min max min max
3 DE country offwind-ac carrier 0.1
4 DE DE offwind-dc onwind 0.1 0.1 0.1 0.1
5 DE DE solar offwind-ac 0.2 0.1 0.1 0.1
6 LU DE onwind offwind-dc 0.1 0.1 0.1
7 LU DE solar solar 0.2 0.2 0.2
8 NL LU onwind onwind
9 NL LU offwind-ac solar
10 NL NL offwind-dc onwind
11 NL NL solar offwind-ac
12 GB NL onwind offwind-dc
13 GB NL offwind-ac solar
14 GB GB offwind-dc onwind
15 GB GB solar offwind-ac
16 IE GB onwind offwind-dc
17 IE GB offwind-ac solar
18 IE IE offwind-dc onwind
19 IE IE solar offwind-ac
20 FR IE onwind offwind-dc
21 FR IE offwind-ac solar
22 FR FR offwind-dc onwind
23 FR FR solar offwind-ac
24 DK FR onwind offwind-dc
25 DK FR offwind-ac solar
26 DK DK offwind-dc onwind
27 DK DK solar offwind-ac
28 BE DK onwind offwind-dc
29 BE DK offwind-ac solar
30 BE BE offwind-dc onwind
31 BE BE solar offwind-ac
32 BE offwind-dc
33 BE solar

View File

@ -5,7 +5,6 @@ gaslimit,MWhth,float or false,Global gas usage limit
co2limit_enable,bool,true or false,Add an overall absolute carbon-dioxide emissions limit configured in ``electricity: co2limit`` in :mod:`prepare_network`. **Warning:** This option should currently only be used with electricity-only networks, not for sector-coupled networks..
co2limit,:math:`t_{CO_2-eq}/a`,float,Cap on total annual system carbon dioxide emissions
co2base,:math:`t_{CO_2-eq}/a`,float,Reference value of total annual system carbon dioxide emissions if relative emission reduction target is specified in ``{opts}`` wildcard.
agg_p_nom_limits,file,path,Reference to ``.csv`` file specifying per carrier generator nominal capacity constraints for individual countries if ``'CCL'`` is in ``{opts}`` wildcard. Defaults to ``data/agg_p_nom_minmax.csv``.
operational_reserve,,,Settings for reserve requirements following `GenX <https://genxproject.github.io/GenX/dev/core/#Reserves>`_
,,,
-- activate,bool,true or false,Whether to take operational reserve requirements into account during optimisation

Can't render this file because it has a wrong number of fields in line 5.

View File

@ -22,6 +22,10 @@ options,,,
-- -- -- {carrier},,,
-- -- link_threshold,,float,The threshold relative to the discrete link unit size beyond which to round up to the next unit by carrier (given in dictionary style).
-- -- -- {carrier},,,
agg_p_nom_limits,,,Configure per carrier generator nominal capacity constraints for individual countries if ``'CCL'`` is in ``{opts}`` wildcard.
-- agg_offwind,bool,"{'true','false'}",Aggregate together all the types of offwind when writing the constraint. Default is false.
-- include_existing,bool,"{'true','false'}",Take existing capacities into account when writing the constraint. Default is false.
-- file,file,path,Reference to ``.csv`` file specifying per carrier generator nominal capacity constraints for individual countries and planning horizons. Defaults to ``data/agg_p_nom_minmax.csv``.
constraints ,,,
-- CCL,bool,"{'true','false'}",Add minimum and maximum levels of generator nominal capacity per carrier for individual countries. These can be specified in the file linked at ``electricity: agg_p_nom_limits`` in the configuration. File defaults to ``data/agg_p_nom_minmax.csv``.
-- EQ,bool/string,"{'false',`n(c| )``; i.e. ``0.5``-``0.7c``}",Require each country or node to on average produce a minimal share of its total consumption itself. Example: ``EQ0.5c`` demands each country to produce on average at least 50% of its consumption; ``EQ0.5`` demands each node to produce on average at least 50% of its consumption.

1 Unit Values Description
22 -- -- -- {carrier}
23 -- -- link_threshold float The threshold relative to the discrete link unit size beyond which to round up to the next unit by carrier (given in dictionary style).
24 -- -- -- {carrier}
25 agg_p_nom_limits Configure per carrier generator nominal capacity constraints for individual countries if ``'CCL'`` is in ``{opts}`` wildcard.
26 -- agg_offwind bool {'true','false'} Aggregate together all the types of offwind when writing the constraint. Default is false.
27 -- include_existing bool {'true','false'} Take existing capacities into account when writing the constraint. Default is false.
28 -- file file path Reference to ``.csv`` file specifying per carrier generator nominal capacity constraints for individual countries and planning horizons. Defaults to ``data/agg_p_nom_minmax.csv``.
29 constraints
30 -- CCL bool {'true','false'} Add minimum and maximum levels of generator nominal capacity per carrier for individual countries. These can be specified in the file linked at ``electricity: agg_p_nom_limits`` in the configuration. File defaults to ``data/agg_p_nom_minmax.csv``.
31 -- EQ bool/string {'false',`n(c| )``; i.e. ``0.5``-``0.7c``} Require each country or node to on average produce a minimal share of its total consumption itself. Example: ``EQ0.5c`` demands each country to produce on average at least 50% of its consumption; ``EQ0.5`` demands each node to produce on average at least 50% of its consumption.

View File

@ -281,6 +281,15 @@ Upcoming Release
* Data on existing renewable capacities is now consistently taken from powerplantmatching (instead of being retrieved separately); the dataset has also been updated to include 2023 values.
* Added shapes to .nc file for different stages of the network object in `base_network`, `simplify_network`, and `cluster_network`; the `build_bus_regions` rule is now integrated into the `base_network` rule.
* Improved the behaviour of `agg_p_nom_limits`:
- Moved the associated configuration to `solving`. This allows *Snakemake* to correctly decide which rules to run when the configuration changes.
- Added the ability to enable aggregation of all *offwind* types (*offwind-ac* and *offwind-dc*) when writing the constraint.
- Added the possibility to take existing capacities into account when writing the constraint.
- Added the possibility to have a different file for each planning horizon.
* Fix p_nom_min of renewables generators for myopic approach and add check of existing capacities in `add_land_use_constraint_m`.

View File

@ -531,23 +531,55 @@ def add_CCL_constraints(n, config):
agg_p_nom_limits: data/agg_p_nom_minmax.csv
"""
agg_p_nom_minmax = pd.read_csv(
config["electricity"]["agg_p_nom_limits"], index_col=[0, 1]
)
config["solving"]["agg_p_nom_limits"]["file"],
index_col=[0, 1], header=[0, 1]
)[snakemake.wildcards.planning_horizons]
logger.info("Adding generation capacity constraints per carrier and country")
p_nom = n.model["Generator-p_nom"]
gens = n.generators.query("p_nom_extendable").rename_axis(index="Generator-ext")
grouper = pd.concat([gens.bus.map(n.buses.country), gens.carrier])
if config["solving"]["agg_p_nom_limits"]["agg_offwind"]:
rename_offwind = {"offwind-ac": "offwind-all", "offwind-dc": "offwind-all", "offwind": "offwind-all"}
gens = gens.replace(rename_offwind)
grouper = pd.concat([gens.bus.map(n.buses.country), gens.carrier], axis=1)
lhs = p_nom.groupby(grouper).sum().rename(bus="country")
minimum = xr.DataArray(agg_p_nom_minmax["min"].dropna()).rename(dim_0="group")
if config["solving"]["agg_p_nom_limits"]["include_existing"]:
gens_cst = n.generators.query("~p_nom_extendable").rename_axis(index="Generator-cst")
gens_cst = gens_cst[
(gens_cst["build_year"] + gens_cst["lifetime"]) >= int(snakemake.wildcards.planning_horizons)]
if config["solving"]["agg_p_nom_limits"]["agg_offwind"]:
gens_cst = gens_cst.replace(rename_offwind)
rhs_cst = (
pd.concat([gens_cst.bus.map(n.buses.country), gens_cst[["carrier", "p_nom"]]], axis=1)
.groupby(["bus", "carrier"]).sum()
)
rhs_cst.index = rhs_cst.index.rename({"bus": "country"})
rhs_min = agg_p_nom_minmax["min"].dropna()
idx_min = rhs_min.index.join(rhs_cst.index, how="left")
rhs_min = rhs_min.reindex(idx_min).fillna(0)
rhs = (rhs_min - rhs_cst.reindex(idx_min).fillna(0).p_nom).dropna()
rhs[rhs < 0] = 0
minimum = xr.DataArray(rhs).rename(dim_0="group")
else:
minimum = xr.DataArray(agg_p_nom_minmax["min"].dropna()).rename(dim_0="group")
index = minimum.indexes["group"].intersection(lhs.indexes["group"])
if not index.empty:
n.model.add_constraints(
lhs.sel(group=index) >= minimum.loc[index], name="agg_p_nom_min"
)
maximum = xr.DataArray(agg_p_nom_minmax["max"].dropna()).rename(dim_0="group")
if config["solving"]["agg_p_nom_limits"]["include_existing"]:
rhs_max = agg_p_nom_minmax["max"].dropna()
idx_max = rhs_max.index.join(rhs_cst.index, how="left")
rhs_max = rhs_max.reindex(idx_max).fillna(0)
rhs = (rhs_max - rhs_cst.reindex(idx_max).fillna(0).p_nom).dropna()
rhs[rhs < 0] = 0
maximum = xr.DataArray(rhs).rename(dim_0="group")
else:
maximum = xr.DataArray(agg_p_nom_minmax["max"].dropna()).rename(dim_0="group")
index = maximum.indexes["group"].intersection(lhs.indexes["group"])
if not index.empty:
n.model.add_constraints(