Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve agg_p_nom_limits configuration #1023

Merged
merged 4 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,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
Expand Down Expand Up @@ -758,6 +757,14 @@ solving:
linearized_unit_commitment: true
horizon: 365

agg_p_nom_limits:
agg_offwind: false
include_existing: false
years:
2030: data/agg_p_nom_minmax.csv
2040: data/agg_p_nom_minmax.csv
2050: data/agg_p_nom_minmax.csv
tgi-climact marked this conversation as resolved.
Show resolved Hide resolved

constraints:
CCL: false
EQ: false
Expand Down
1 change: 0 additions & 1 deletion doc/configtables/electricity.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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``.
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
Expand Down
4 changes: 4 additions & 0 deletions doc/configtables/solving.csv
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ options,,,
-- transmission_losses,int,[0-9],"Add piecewise linear approximation of transmission losses based on n tangents. Defaults to 0, which means losses are ignored."
-- linearized_unit_commitment,bool,"{'true','false'}",Whether to optimise using the linearized unit commitment formulation.
-- horizon,--,int,Number of snapshots to consider in each iteration. Defaults to 100.
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.
-- years,-,Dictionary with planning horizons as key and path as value,Reference to ``.csv`` file for each planning horizon. 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.
Expand Down
10 changes: 10 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,16 @@ Upcoming Release

* Fix custom busmap read in `cluster_network`.

* 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.

PyPSA-Eur 0.10.0 (19th February 2024)
=====================================

Expand Down
39 changes: 35 additions & 4 deletions scripts/solve_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,23 +422,54 @@ 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"]["years"][int(snakemake.wildcards.planning_horizons)], index_col=[0, 1]
)
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(
Expand Down
Loading