From b105a9f5a86658d3959b386c398a4cfb982c1ce3 Mon Sep 17 00:00:00 2001 From: Toni Seibold <153275395+toniseibold@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:08:00 +0200 Subject: [PATCH] Hydrogen Gas Turbines and Retrofitting of Gas Turbines (#151) * first stab at enabling H2 and H2 retrofit gas plants * forcing gas turbines from one year on * fixing efficiency of hydrogen gas turbines * bug fixing params * dropping gas plants after forcing them to retrofit * make unravelling of oil bus consistent with new refineries I.e. separate DE oil primary from DE oil. * update exporter * ci: add validator * select images and add repo owner path * Update PULL_REQUEST_TEMPLATE.md * Update Changelog.md * update submodule * Update .pre-commit-config.yaml * remove reuse check * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Using config_provider for additional functionality exclusively (#154) * using exclusively config_provider * moving bc to solving config * style changes * minor style changes * cosmetic changes * add back default values of constraints * remove unused function arguments * small fix for exporter * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Michael Lindner Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Create .git-blame-ignore-revs (#158) * minor changes for running single node network (#160) * minor changes for running single node network * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Fix hydrogen import boundary condition (#159) * include retrofitted and Kernnetz pipelines for hydrogen import boundary condition * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * bugfixing mock_snakemake and fullfilling prerequisites for review --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * first stab at enabling H2 and H2 retrofit gas plants * forcing gas turbines from one year on * fix merge bugs * change exporter to report hydrogen plants * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * change name of retrofitted H2 * updating exporter * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix bus0 auf H2 plants * finish rename of h2 retrofit * default efficiency = 1 --------- Co-authored-by: Tom Brown Co-authored-by: Michael Lindner Co-authored-by: lkstrp Co-authored-by: Micha Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Julian Geis --- config/config.yaml | 12 ++ workflow/Snakefile | 1 + workflow/scripts/export_ariadne_variables.py | 51 ++++++++- workflow/scripts/modify_prenetwork.py | 114 +++++++++++++++++++ 4 files changed, 172 insertions(+), 6 deletions(-) diff --git a/config/config.yaml b/config/config.yaml index e45f7726..2991c680 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -604,6 +604,12 @@ plotting: load: "#111100" H2 pipeline (Kernnetz): '#6b3161' renewable oil: '#c9c9c9' + urban central H2 retrofit CHP: '#ff0000' + H2 retrofit OCGT: '#ff0000' + H2 retrofit CCGT: '#ff0000' + H2 OCGT: '#ff0000' + H2 CCGT: '#ff0000' + urban central H2 CHP: '#ff0000' renewable gas: '#e05b09' hbi: '#858d99' DE industry methanol: '#f2b134' @@ -628,6 +634,12 @@ electricity: custom_file: resources/german_chp.csv estimate_renewable_capacities: year: 2019 + H2_plants_DE: + enable: true + start: 2030 # should be < force + force: 2035 + cost_factor: 0.15 # repurposing cost of OCGT gas to H2 in % investment cost in EUR/MW source: Christidis et al (2023) - H2-Ready-Gaskraftwerke, Table 3 https://reiner-lemoine-institut.de/wp-content/uploads/2023/11/RLI-Studie-H2-ready_DE.pdf + efficiency_loss: 0.05 pypsa_eur: Generator: diff --git a/workflow/Snakefile b/workflow/Snakefile index fdbc2ed3..d48facd1 100644 --- a/workflow/Snakefile +++ b/workflow/Snakefile @@ -185,6 +185,7 @@ rule modify_prenetwork: biomass_must_run=config_provider("must_run_biomass"), sector=config_provider("sector"), clustering=config_provider("clustering", "temporal", "resolution_sector"), + H2_plants=config_provider("electricity", "H2_plants_DE"), land_transport_electric_share=config_provider( "sector", "land_transport_electric_share" ), diff --git a/workflow/scripts/export_ariadne_variables.py b/workflow/scripts/export_ariadne_variables.py index 59f0bef1..055c23ec 100644 --- a/workflow/scripts/export_ariadne_variables.py +++ b/workflow/scripts/export_ariadne_variables.py @@ -567,7 +567,17 @@ def _get_capacities(n, region, cap_func, cap_string="Capacity|", costs=None): # Q: What about retrofitted gas power plants? -> Lisa var[cap_string + "Electricity|Hydrogen"] = var[ cap_string + "Electricity|Hydrogen|FC" - ] = capacities_electricity.get("H2 Fuel Cell", 0) + ] = capacities_electricity.reindex( + [ + "H2 Fuel Cell", + "H2 OCGT", + "H2 CCGT", + "urban central H2 CHP", + "H2 retrofit OCGT", + "H2 retrofit CCGT", + "urban central H2 retrofit CHP", + ] + ).sum() var[cap_string + "Electricity|Nuclear"] = capacities_electricity.get("nuclear", 0) @@ -796,6 +806,10 @@ def _get_capacities(n, region, cap_func, cap_string="Capacity|", costs=None): } ).sum() + var[cap_string + "Heat|Hydrogen"] = capacities_central_heat.reindex( + ["urban central H2 CHP", "urban central H2 retrofit CHP"] + ).sum() + # !!! Missing in the Ariadne database var[cap_string + "Heat|Gas"] = ( @@ -1362,9 +1376,17 @@ def get_secondary_energy(n, region, _industry_demand): + var["Secondary Energy|Electricity|Wind"] ) - var["Secondary Energy|Electricity|Hydrogen"] = electricity_supply.get( - "H2 Fuel Cell", 0 - ) + var["Secondary Energy|Electricity|Hydrogen"] = electricity_supply.reindex( + [ + "H2 Fuel Cell", + "H2 OCGT", + "H2 CCGT", + "urban central H2 CHP", + "H2 retrofit OCGT", + "H2 retrofit CCGT", + "urban central H2 retrofit CHP", + ] + ).sum() # ! Add H2 Turbines if they get implemented var["Secondary Energy|Electricity|Waste"] = electricity_supply.filter( @@ -1470,6 +1492,9 @@ def get_secondary_energy(n, region, _industry_demand): # var["Secondary Energy|Heat|Nuclear"] = \ # var["Secondary Energy|Heat|Other"] = \ # ! Not implemented + var["Secondary Energy|Heat|Hydrogen"] = heat_supply.filter( + like="urban central H2" + ).sum() var["Secondary Energy|Heat|Oil"] = heat_supply.filter( like="urban central oil" @@ -1508,6 +1533,7 @@ def get_secondary_energy(n, region, _industry_demand): + var["Secondary Energy|Heat|Other"] + var["Secondary Energy|Heat|Coal"] + var["Secondary Energy|Heat|Waste"] + + var["Secondary Energy|Heat|Hydrogen"] ) assert isclose( var["Secondary Energy|Heat"], @@ -1671,10 +1697,23 @@ def get_secondary_energy(n, region, _industry_demand): .multiply(MWh2PJ) ) - var["Secondary Energy Input|Hydrogen|Electricity"] = hydrogen_withdrawal.get( - "H2 Fuel Cell", 0 + H2_CHP_E_usage, H2_CHP_H_usage = get_CHP_E_and_H_usage(n, "H2", region) + + var["Secondary Energy Input|Hydrogen|Electricity"] = ( + hydrogen_withdrawal.reindex( + [ + "H2 Fuel Cell", + "H2 OCGT", + "H2 CCGT", + "H2 retrofit OCGT", + "H2 retrofit CCGT", + ] + ).sum() + + H2_CHP_E_usage ) + var["Secondary Energy Input|Hydrogen|Heat"] = H2_CHP_H_usage + var["Secondary Energy Input|Hydrogen|Gases"] = hydrogen_withdrawal.get( "Sabatier", 0 ) diff --git a/workflow/scripts/modify_prenetwork.py b/workflow/scripts/modify_prenetwork.py index 4c5314e3..aaf9c4a6 100644 --- a/workflow/scripts/modify_prenetwork.py +++ b/workflow/scripts/modify_prenetwork.py @@ -901,6 +901,111 @@ def aladin_mobility_demand(n): n.stores.loc[dsm_i].e_nom *= pd.Series(factor.values, index=dsm_i) +def add_hydrogen_turbines(n): + """ + This adds links that instead of a gas turbine use a hydrogen turbine. + + It is assumed that the efficiency stays the same. This function is + only applied to German nodes. + """ + logger.info("Adding hydrogen turbine technologies for Germany.") + + gas_carrier = ["OCGT", "CCGT"] + for carrier in gas_carrier: + gas_plants = n.links[ + (n.links.carrier == carrier) + & (n.links.index.str[:2] == "DE") + & (n.links.p_nom_extendable) + ].index + if gas_plants.empty: + continue + h2_plants = n.links.loc[gas_plants].copy() + h2_plants.carrier = h2_plants.carrier.str.replace(carrier, "H2 " + carrier) + h2_plants.index = h2_plants.index.str.replace(carrier, "H2 " + carrier) + h2_plants.bus0 = h2_plants.bus1 + " H2" + h2_plants.bus2 = "" + h2_plants.efficiency2 = 1 + # add the new links + n.import_components_from_dataframe(h2_plants, "Link") + + # special handling of CHPs + gas_plants = n.links[ + (n.links.carrier == "urban central gas CHP") + & (n.links.index.str[:2] == "DE") + & (n.links.p_nom_extendable) + ].index + h2_plants = n.links.loc[gas_plants].copy() + h2_plants.carrier = h2_plants.carrier.str.replace("gas", "H2") + h2_plants.index = h2_plants.index.str.replace("gas", "H2") + h2_plants.bus0 = h2_plants.bus1 + " H2" + h2_plants.bus3 = "" + h2_plants.efficiency3 = 1 + n.import_components_from_dataframe(h2_plants, "Link") + + +def force_retrofit(n, params): + """ + This function forces the retrofit of gas turbines from params["force"] on. + + Extendable gas links are deleted. + """ + + logger.info("Forcing retrofit of gas turbines to hydrogen turbines.") + + gas_carrier = ["OCGT", "CCGT", "urban central gas CHP"] + # deleting extendable gas turbine plants + to_drop = n.links[ + (n.links.carrier.isin(gas_carrier)) + & (n.links.p_nom_extendable) + & (n.links.index.str[:2] == "DE") + ].index + n.links.drop(to_drop, inplace=True) + + # forcing retrofit + for carrier in ["OCGT", "CCGT"]: + gas_plants = n.links[ + (n.links.carrier == carrier) + & (n.links.index.str[:2] == "DE") + & (n.links.build_year >= params["start"]) + ].index + if gas_plants.empty: + continue + + h2_plants = n.links.loc[gas_plants].copy() + h2_plants.carrier = h2_plants.carrier.str.replace( + carrier, "H2 retrofit " + carrier + ) + h2_plants.index = h2_plants.index.str.replace(carrier, "H2 retrofit " + carrier) + h2_plants.bus0 = h2_plants.bus1 + " H2" + h2_plants.bus2 = "" + h2_plants.efficiency -= params["efficiency_loss"] + h2_plants.efficiency2 = 1 # default value + h2_plants.capital_cost *= 1 + params["cost_factor"] + # add the new links + n.import_components_from_dataframe(h2_plants, "Link") + n.links.drop(gas_plants, inplace=True) + + # special handling of CHPs + gas_plants = n.links[ + (n.links.carrier == "urban central gas CHP") + & (n.links.index.str[:2] == "DE") + & (n.links.build_year >= params["start"]) + ].index + if gas_plants.empty: + return + + h2_plants = n.links.loc[gas_plants].copy() + h2_plants.carrier = h2_plants.carrier.str.replace("gas", "H2 retrofit") + h2_plants.index = h2_plants.index.str.replace("gas", "H2 retrofit") + h2_plants.bus0 = h2_plants.bus1 + " H2" + h2_plants.bus3 = "" + h2_plants.efficiency -= params["efficiency_loss"] + h2_plants.efficiency3 = 1 # default value + h2_plants.capital_cost *= 1 + params["cost_factor"] + n.import_components_from_dataframe(h2_plants, "Link") + n.links.drop(gas_plants, inplace=True) + + if __name__ == "__main__": if "snakemake" not in globals(): import os @@ -992,5 +1097,14 @@ def aladin_mobility_demand(n): n.links.loc[electrolysis, "capital_cost"] = costs.at["electrolysis", "fixed"] * 1.1 else: raise ValueError("Invalid electrolysis costs option. Must be 'low', 'high' or false.") + if snakemake.params.H2_plants["enable"]: + if snakemake.params.H2_plants["start"] <= int( + snakemake.wildcards.planning_horizons + ): + add_hydrogen_turbines(n) + if snakemake.params.H2_plants["force"] <= int( + snakemake.wildcards.planning_horizons + ): + force_retrofit(n, snakemake.params.H2_plants) n.export_to_netcdf(snakemake.output.network)