From abb584de453fccbabc641cf806b065d23b1de164 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 28 Aug 2023 13:31:02 +0200 Subject: [PATCH] add H2 boiler and constraint to avoid existing gas boiler back up --- config/config.perfect.yaml | 13 +++++--- scripts/prepare_perfect_foresight.py | 34 ++++++++++++++++--- scripts/solve_network.py | 50 ++++++++++++++++++++++++++-- 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/config/config.perfect.yaml b/config/config.perfect.yaml index 1787c640..2b625420 100644 --- a/config/config.perfect.yaml +++ b/config/config.perfect.yaml @@ -20,7 +20,7 @@ remote: # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#run run: - name: "test_co2limit" + name: "test_h2_retrofit" disable_progressbar: false shared_resources: true shared_cutouts: true @@ -35,7 +35,7 @@ scenario: - '' ll: - v1.0 - - v1.5 +# - v1.5 clusters: - 37 # - 128 @@ -45,7 +45,9 @@ scenario: opts: - '' sector_opts: - - 4380H-T-H-B-I-A-solar+p3-dist1 + - 1p7-4380H-T-H-B-I-A-solar+p3-dist1 + - 1p5-4380H-T-H-B-I-A-solar+p3-dist1 + - 2p0-4380H-T-H-B-I-A-solar+p3-dist1 planning_horizons: - 2020 - 2030 @@ -69,7 +71,7 @@ enable: retrieve_sector_databundle: true retrieve_cost_data: true build_cutout: false - retrieve_cutout: false + retrieve_cutout: true build_natura_raster: false retrieve_natura_raster: true custom_busmap: false @@ -133,7 +135,7 @@ electricity: # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#atlite atlite: default_cutout: europe-2013-era5 - nprocesses: 4 + nprocesses: 1 show_progress: false cutouts: # use 'base' to determine geographical bounds and time span from config @@ -916,6 +918,7 @@ plotting: H2 for shipping: "#ebaee0" H2: '#bf13a0' hydrogen: '#bf13a0' + retrofitted H2 boiler: '#e5a0d9' SMR: '#870c71' SMR CC: '#4f1745' H2 liquefaction: '#d647bd' diff --git a/scripts/prepare_perfect_foresight.py b/scripts/prepare_perfect_foresight.py index d3ec4966..3bfc2931 100644 --- a/scripts/prepare_perfect_foresight.py +++ b/scripts/prepare_perfect_foresight.py @@ -306,6 +306,29 @@ def adjust_CO2_glc(n): n.df(c).loc[mask, "type"] = "co2_limit" return n + + +def add_H2_boilers(n): + c = "Link" + logger.info("Add H2 boilers.") + # existing gas boilers + mask = n.links.carrier.str.contains("gas boiler") & ~n.links.p_nom_extendable + gas_i = n.links[mask].index + df = n.links.loc[gas_i] + # adjust bus 0 + df["bus0"] = df.bus1.map(n.buses.location) + " H2" + # rename carrier and index + df["carrier"] = df.carrier.apply(lambda x: x.replace("gas boiler", "retrofitted H2 boiler")) + df.rename(index = lambda x: x.replace("gas boiler", "retrofitted H2 boiler"), inplace=True) + # todo, costs for retrofitting + df["capital_costs"] = 100 + # set existing capacity to zero + df["p_nom"] = 0 + df["p_nom_extendable"] = True + # add H2 boilers to network + import_components_from_dataframe(n, df, c) + + # %% if __name__ == "__main__": if "snakemake" not in globals(): @@ -339,14 +362,17 @@ if __name__ == "__main__": n = concat_networks(years) # adjust global constraints lv limit if the same for all years - n = adjust_lvlimit(n) - + n = adjust_lvlimit(n) # adjust global constraints CO2 limit n = adjust_CO2_glc(n) - # set phase outs - set_all_phase_outs(n) # adjust stores to multi period investment n = adjust_stores(n) + + # set phase outs + set_all_phase_outs(n) + + # add H2 boiler + add_H2_boilers(n) # set carbon constraints opts = snakemake.wildcards.sector_opts.split("-") diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 49d99311..38bee124 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -32,7 +32,7 @@ import re import numpy as np import pandas as pd import pypsa -from pypsa.descriptors import nominal_attrs +from pypsa.descriptors import nominal_attrs, get_activity_mask import xarray as xr from _helpers import configure_logging, update_config_with_sector_opts @@ -41,6 +41,7 @@ from vresutils.benchmark import memory_logger logger = logging.getLogger(__name__) pypsa.pf.logger.setLevel(logging.WARNING) from pypsa.descriptors import get_switchable_as_dense as get_as_dense +from pypsa.io import import_components_from_dataframe from linopy.expressions import merge from numpy import isnan @@ -276,7 +277,51 @@ def add_max_growth(n, config): n.carriers.loc[carrier, "max_relative_growth"] = max_r_per_period return n - + + +def add_retrofit_gas_boiler_constraint(n, snapshots): + """Allow retrofitting of existing gas boilers to H2 boilers. + """ + c = "Link" + logger.info("Add constraint for retrofitting gas boilers to H2 boilers.") + # existing gas boilers + mask = n.links.carrier.str.contains("gas boiler") & ~n.links.p_nom_extendable + gas_i = n.links[mask].index + mask = n.links.carrier.str.contains("retrofitted H2 boiler") + h2_i = n.links[mask].index + + + n.links.loc[gas_i, "p_nom_extendable"] = True + p_nom = n.links.loc[gas_i, "p_nom"] + n.links.loc[gas_i, "p_nom"] = 0 + + # heat profile + cols = n.loads_t.p_set.columns[n.loads_t.p_set.columns.str.contains("heat") + & ~n.loads_t.p_set.columns.str.contains("industry") + & ~n.loads_t.p_set.columns.str.contains("agriculture")] + profile = n.loads_t.p_set[cols].div(n.loads_t.p_set[cols].groupby(level=0).max(), level=0) + # to deal if max value is zero + profile.fillna(0, inplace=True) + profile.rename(columns=n.loads.bus.to_dict(), inplace=True) + profile = profile.reindex(columns=n.links.loc[gas_i, "bus1"]) + profile.columns = gas_i + + + rhs = profile.mul(p_nom) + + dispatch = n.model["Link-p"] + active = get_activity_mask(n, c, snapshots, gas_i) + rhs = rhs[active] + p_gas = dispatch.sel(Link=gas_i) + p_h2 = dispatch.sel(Link=h2_i) + + lhs = p_gas + p_h2 + + n.model.add_constraints(lhs == rhs, name="gas_retrofit") + + + + def prepare_network( n, solve_opts=None, @@ -740,6 +785,7 @@ def extra_functionality(n, snapshots): if n._multi_invest: add_carbon_constraint(n, snapshots) add_carbon_budget_constraint(n, snapshots) + add_retrofit_gas_boiler_constraint(n, snapshots) def solve_network(n, config, solving, opts="", **kwargs):