Skip to content

Commit

Permalink
Limit German CO2 sequestration in each investment year (#170)
Browse files Browse the repository at this point in the history
* add_func: use same function for add_max/min_capacity_limits

to avoid boilerplate code repetition

* add_func: stores in add_{min/max}_limits; limit DE co2 sequestration

Extend the functions add_{min/max}_limits in
additional_functionality.py to allow stores.

Add limits to DE CO2 sequestration based on feasibility constraints.

E.g. since EU target for 2030 is 50 MtCO2/a sequestration in Net Zero
Industry Act, limit DE to 10 MtCO2/a, then relax with subsequent years.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* check if constraint exists inside loop

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Michael Lindner <michaellindner@posteo.de>
Co-authored-by: Micha <michaellindner@pik-potsdam.de>
  • Loading branch information
4 people authored Aug 15, 2024
1 parent 0bd5246 commit d87f4af
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 89 deletions.
13 changes: 10 additions & 3 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#run
run:

prefix: 20240814_districtheatingshares

prefix: 20240814limitseq
name:
# - CurrentPolicies
- KN2045_Bal_v4
Expand Down Expand Up @@ -378,6 +376,15 @@ solving:
2035: 400
2040: 400
2045: 400
Store:
co2 sequestered:
DE:
2020: 0
2025: 0
2030: 10000
2035: 20000
2040: 50000
2045: 80000
limits_capacity_min:
Generator:
onwind:
Expand Down
136 changes: 50 additions & 86 deletions workflow/scripts/additional_functionality.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,24 @@
logger = logging.getLogger(__name__)


def add_min_capacity_limits(n, investment_year, limits_capacity_min):
def add_capacity_limits(n, investment_year, limits_capacity, sense="maximum"):

for c in n.iterate_components(limits_capacity_min):
logger.info(f"Adding minimum constraints for {c.list_name}")
for c in n.iterate_components(limits_capacity):
logger.info(f"Adding {sense} constraints for {c.list_name}")

for carrier in limits_capacity_min[c.name]:
attr = "e" if c.name == "Store" else "p"
units = "MWh or tCO2" if c.name == "Store" else "MW"

for ct in limits_capacity_min[c.name][carrier]:
# check if the limit is defined for the investement year
if (
investment_year
not in limits_capacity_min[c.name][carrier][ct].keys()
):
for carrier in limits_capacity[c.name]:

for ct in limits_capacity[c.name][carrier]:
if investment_year not in limits_capacity[c.name][carrier][ct].keys():
continue
limit = 1e3 * limits_capacity_min[c.name][carrier][ct][investment_year]

limit = 1e3 * limits_capacity[c.name][carrier][ct][investment_year]

logger.info(
f"Adding constraint on {c.name} {carrier} capacity in {ct} to be greater than {limit} MW"
f"Adding constraint on {c.name} {carrier} capacity in {ct} to be {sense} {limit} {units}"
)

valid_components = (
Expand All @@ -35,92 +35,52 @@ def add_min_capacity_limits(n, investment_year, limits_capacity_min):
& ~c.df.carrier.str.contains("thermal")
) # exclude solar thermal

existing_index = c.df.index[valid_components & ~c.df.p_nom_extendable]
extendable_index = c.df.index[valid_components & c.df.p_nom_extendable]
existing_index = c.df.index[
valid_components & ~c.df[attr + "_nom_extendable"]
]
extendable_index = c.df.index[
valid_components & c.df[attr + "_nom_extendable"]
]

existing_capacity = c.df.loc[existing_index, "p_nom"].sum()
existing_capacity = c.df.loc[existing_index, attr + "_nom"].sum()

logger.info(
f"Existing {c.name} {carrier} capacity in {ct}: {existing_capacity} MW"
f"Existing {c.name} {carrier} capacity in {ct}: {existing_capacity} {units}"
)

p_nom = n.model[c.name + "-p_nom"].loc[extendable_index]
nom = n.model[c.name + "-" + attr + "_nom"].loc[extendable_index]

lhs = p_nom.sum()
lhs = nom.sum()

cname = f"capacity_minimum-{ct}-{c.name}-{carrier.replace(' ','-')}"
cname = f"capacity_{sense}-{ct}-{c.name}-{carrier.replace(' ','-')}"

n.model.add_constraints(
lhs >= limit - existing_capacity, name=f"GlobalConstraint-{cname}"
)
if cname not in n.global_constraints.index:
n.add(
"GlobalConstraint",
cname,
constant=limit,
sense=">=",
type="",
carrier_attribute="",
if cname in n.global_constraints.index:
logger.warning(
f"Global constraint {cname} already exists. Skipping."
)


def add_max_capacity_limits(n, investment_year, limits_capacity_max):

for c in n.iterate_components(limits_capacity_max):
logger.info(f"Adding maximum constraints for {c.list_name}")

for carrier in limits_capacity_max[c.name]:

for ct in limits_capacity_max[c.name][carrier]:
if (
investment_year
not in limits_capacity_max[c.name][carrier][ct].keys()
):
continue
limit = 1e3 * limits_capacity_max[c.name][carrier][ct][investment_year]

valid_components = (
(c.df.index.str[:2] == ct)
& (c.df.carrier.str[: len(carrier)] == carrier)
& ~c.df.carrier.str.contains("thermal")
) # exclude solar thermal

existing_index = c.df.index[valid_components & ~c.df.p_nom_extendable]
extendable_index = c.df.index[valid_components & c.df.p_nom_extendable]

existing_capacity = c.df.loc[existing_index, "p_nom"].sum()

logger.info(
f"Existing {c.name} {carrier} capacity in {ct}: {existing_capacity} MW"
)
logger.info(
f"Adding constraint on {c.name} {carrier} capacity in {ct} to be smaller than {limit} MW"
)

p_nom = n.model[c.name + "-p_nom"].loc[extendable_index]

lhs = p_nom.sum()

cname = f"capacity_maximum-{ct}-{c.name}-{carrier.replace(' ','-')}"
if limit - existing_capacity <= 0:
n.model.add_constraints(lhs <= 0, name=f"GlobalConstraint-{cname}")
logger.warning(
f"Existing capacity in {ct} for carrier {carrier} already exceeds the limit of {limit} MW. Limiting capacity expansion for this investment period to 0."
)
else:
if sense == "maximum":
if limit - existing_capacity <= 0:
n.model.add_constraints(
lhs <= 0, name=f"GlobalConstraint-{cname}"
)
logger.warning(
f"Existing capacity in {ct} for carrier {carrier} already exceeds the limit of {limit} MW. Limiting capacity expansion for this investment period to 0."
)
else:
n.model.add_constraints(
lhs <= limit - existing_capacity,
name=f"GlobalConstraint-{cname}",
)
elif sense == "minimum":
n.model.add_constraints(
lhs <= limit - existing_capacity,
lhs >= limit - existing_capacity,
name=f"GlobalConstraint-{cname}",
)
if cname not in n.global_constraints.index:
n.add(
"GlobalConstraint",
cname,
constant=limit,
sense="<=",
type="",
carrier_attribute="",
)
else:
logger.error("sense {sense} not recognised")
sys.exit()


def h2_import_limits(n, investment_year, limits_volume_max):
Expand Down Expand Up @@ -571,9 +531,13 @@ def additional_functionality(n, snapshots, snakemake):
investment_year = int(snakemake.wildcards.planning_horizons[-4:])
constraints = snakemake.params.solving["constraints"]

add_min_capacity_limits(n, investment_year, constraints["limits_capacity_min"])
add_capacity_limits(
n, investment_year, constraints["limits_capacity_min"], "minimum"
)

add_max_capacity_limits(n, investment_year, constraints["limits_capacity_max"])
add_capacity_limits(
n, investment_year, constraints["limits_capacity_max"], "maximum"
)

if int(snakemake.wildcards.clusters) != 1:
h2_import_limits(n, investment_year, constraints["limits_volume_max"])
Expand Down

0 comments on commit d87f4af

Please sign in to comment.