diff --git a/config/config.yaml b/config/config.yaml index cb6e905b..4c926a8c 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: 20240807unravelCH4coarse + prefix: 20240809H2_plants_retrofit name: # - CurrentPolicies - KN2045_Bal_v4 @@ -479,6 +479,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' countries: - all @@ -501,6 +507,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: Bus: diff --git a/workflow/Snakefile b/workflow/Snakefile index a6dfdbf8..b63b9dfe 100644 --- a/workflow/Snakefile +++ b/workflow/Snakefile @@ -184,6 +184,7 @@ rule modify_prenetwork: transmission_costs=config_provider("costs", "transmission"), biomass_must_run=config_provider("must_run_biomass"), 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 4c734be9..acd711dc 100644 --- a/workflow/scripts/modify_prenetwork.py +++ b/workflow/scripts/modify_prenetwork.py @@ -679,6 +679,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 @@ -759,4 +864,14 @@ def aladin_mobility_demand(n): snakemake.params.biomass_must_run["regions"], ) + 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)