From 58bdd19760a85fd3246b077c0f3c9c707db5cacc Mon Sep 17 00:00:00 2001 From: Tom Brown Date: Wed, 14 Aug 2024 15:24:20 +0200 Subject: [PATCH 1/4] add_func: use same function for add_max/min_capacity_limits to avoid boilerplate code repetition --- workflow/scripts/additional_functionality.py | 104 ++++++------------- 1 file changed, 31 insertions(+), 73 deletions(-) diff --git a/workflow/scripts/additional_functionality.py b/workflow/scripts/additional_functionality.py index 37afc6e9..b3273717 100644 --- a/workflow/scripts/additional_functionality.py +++ b/workflow/scripts/additional_functionality.py @@ -9,76 +9,26 @@ 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]: + for carrier in limits_capacity[c.name]: - for ct in limits_capacity_min[c.name][carrier]: - # check if the limit is defined for the investement year + for ct in limits_capacity[c.name][carrier]: if ( investment_year - not in limits_capacity_min[c.name][carrier][ct].keys() + not in limits_capacity[c.name][carrier][ct].keys() ): continue - limit = 1e3 * limits_capacity_min[c.name][carrier][ct][investment_year] - logger.info( - f"Adding constraint on {c.name} {carrier} capacity in {ct} to be greater than {limit} MW" - ) - - 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() + limit = 1e3 * limits_capacity[c.name][carrier][ct][investment_year] logger.info( - f"Existing {c.name} {carrier} capacity in {ct}: {existing_capacity} MW" + f"Adding constraint on {c.name} {carrier} capacity in {ct} to be {sense} {limit} MW" ) - p_nom = n.model[c.name + "-p_nom"].loc[extendable_index] - - lhs = p_nom.sum() - - cname = f"capacity_minimum-{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="", - ) - - -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) @@ -93,36 +43,44 @@ def add_max_capacity_limits(n, investment_year, limits_capacity_max): 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: + cname = f"capacity_{sense}-{ct}-{c.name}-{carrier.replace(' ','-')}" + + 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, - name=f"GlobalConstraint-{cname}", + lhs >= limit - existing_capacity, name=f"GlobalConstraint-{cname}" ) + else: + logger.error("sense {sense} not recognised") + sys.exit() + if cname not in n.global_constraints.index: n.add( "GlobalConstraint", cname, constant=limit, - sense="<=", + sense="<=" if sense == "maximum" else ">=", type="", carrier_attribute="", ) + def h2_import_limits(n, investment_year, limits_volume_max): for ct in limits_volume_max["h2_import"]: @@ -571,9 +529,9 @@ 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"]) From 692701e9365ac3be6a0853d977affe4eb6d2ee84 Mon Sep 17 00:00:00 2001 From: Tom Brown Date: Wed, 14 Aug 2024 15:43:14 +0200 Subject: [PATCH 2/4] 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. --- config/config.yaml | 11 ++++++++++- workflow/scripts/additional_functionality.py | 17 ++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/config/config.yaml b/config/config.yaml index b59b6e53..619f308a 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -4,7 +4,7 @@ # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#run run: - prefix: 20240809withSpain + prefix: 20240814limitseq name: # - CurrentPolicies - KN2045_Bal_v4 @@ -376,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: diff --git a/workflow/scripts/additional_functionality.py b/workflow/scripts/additional_functionality.py index b3273717..04a12f8c 100644 --- a/workflow/scripts/additional_functionality.py +++ b/workflow/scripts/additional_functionality.py @@ -14,6 +14,9 @@ def add_capacity_limits(n, investment_year, limits_capacity, sense="maximum"): for c in n.iterate_components(limits_capacity): logger.info(f"Adding {sense} constraints for {c.list_name}") + attr = "e" if c.name == "Store" else "p" + units = "MWh or tCO2" if c.name == "Store" else "MW" + for carrier in limits_capacity[c.name]: for ct in limits_capacity[c.name][carrier]: @@ -26,7 +29,7 @@ def add_capacity_limits(n, investment_year, limits_capacity, sense="maximum"): limit = 1e3 * limits_capacity[c.name][carrier][ct][investment_year] logger.info( - f"Adding constraint on {c.name} {carrier} capacity in {ct} to be {sense} {limit} MW" + f"Adding constraint on {c.name} {carrier} capacity in {ct} to be {sense} {limit} {units}" ) valid_components = ( @@ -35,18 +38,18 @@ def add_capacity_limits(n, investment_year, limits_capacity, sense="maximum"): & ~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_{sense}-{ct}-{c.name}-{carrier.replace(' ','-')}" From b71804041f162871ec1be6968e0b30d70b757279 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:47:50 +0000 Subject: [PATCH 3/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- workflow/scripts/additional_functionality.py | 29 ++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/workflow/scripts/additional_functionality.py b/workflow/scripts/additional_functionality.py index 04a12f8c..e9766715 100644 --- a/workflow/scripts/additional_functionality.py +++ b/workflow/scripts/additional_functionality.py @@ -20,10 +20,7 @@ def add_capacity_limits(n, investment_year, limits_capacity, sense="maximum"): 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() - ): + if investment_year not in limits_capacity[c.name][carrier][ct].keys(): continue limit = 1e3 * limits_capacity[c.name][carrier][ct][investment_year] @@ -38,8 +35,12 @@ def add_capacity_limits(n, investment_year, limits_capacity, sense="maximum"): & ~c.df.carrier.str.contains("thermal") ) # exclude solar thermal - 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_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, attr + "_nom"].sum() @@ -55,7 +56,9 @@ def add_capacity_limits(n, investment_year, limits_capacity, sense="maximum"): if sense == "maximum": if limit - existing_capacity <= 0: - n.model.add_constraints(lhs <= 0, name=f"GlobalConstraint-{cname}") + 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." ) @@ -66,7 +69,8 @@ def add_capacity_limits(n, investment_year, limits_capacity, sense="maximum"): ) elif sense == "minimum": n.model.add_constraints( - lhs >= limit - existing_capacity, name=f"GlobalConstraint-{cname}" + lhs >= limit - existing_capacity, + name=f"GlobalConstraint-{cname}", ) else: logger.error("sense {sense} not recognised") @@ -83,7 +87,6 @@ def add_capacity_limits(n, investment_year, limits_capacity, sense="maximum"): ) - def h2_import_limits(n, investment_year, limits_volume_max): for ct in limits_volume_max["h2_import"]: @@ -532,9 +535,13 @@ def additional_functionality(n, snapshots, snakemake): investment_year = int(snakemake.wildcards.planning_horizons[-4:]) constraints = snakemake.params.solving["constraints"] - add_capacity_limits(n, investment_year, constraints["limits_capacity_min"], "minimum") + add_capacity_limits( + n, investment_year, constraints["limits_capacity_min"], "minimum" + ) - add_capacity_limits(n, investment_year, constraints["limits_capacity_max"], "maximum") + 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"]) From 351c8213801806609402dbda225d11645a10b09b Mon Sep 17 00:00:00 2001 From: Michael Lindner Date: Thu, 15 Aug 2024 10:08:17 +0200 Subject: [PATCH 4/4] check if constraint exists inside loop --- workflow/scripts/additional_functionality.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/workflow/scripts/additional_functionality.py b/workflow/scripts/additional_functionality.py index e9766715..3d8ddc10 100644 --- a/workflow/scripts/additional_functionality.py +++ b/workflow/scripts/additional_functionality.py @@ -54,6 +54,12 @@ def add_capacity_limits(n, investment_year, limits_capacity, sense="maximum"): cname = f"capacity_{sense}-{ct}-{c.name}-{carrier.replace(' ','-')}" + if cname in n.global_constraints.index: + logger.warning( + f"Global constraint {cname} already exists. Skipping." + ) + continue + if sense == "maximum": if limit - existing_capacity <= 0: n.model.add_constraints( @@ -76,16 +82,6 @@ def add_capacity_limits(n, investment_year, limits_capacity, sense="maximum"): logger.error("sense {sense} not recognised") sys.exit() - if cname not in n.global_constraints.index: - n.add( - "GlobalConstraint", - cname, - constant=limit, - sense="<=" if sense == "maximum" else ">=", - type="", - carrier_attribute="", - ) - def h2_import_limits(n, investment_year, limits_volume_max):