From 7ff6ee3446b1ce072403cff05aefa181b03f03df Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 1 Sep 2025 14:13:16 +0200 Subject: [PATCH 01/10] add electrolysis excess heat as a heat source and update related configurations --- config/config.default.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 550cae4653..200ac6e06c 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -549,13 +549,20 @@ sector: geothermal: constant_temperature_celsius: 65 ignore_missing_regions: false + requires_generator: true + electrolysis_excess_heat: + constant_temperature_celsius: 70 + ignore_missing_regions: false + requires_generator: false direct_utilisation_heat_sources: - geothermal + - electrolysis_excess_heat temperature_limited_stores: - ptes heat_pump_sources: urban central: - air + - electrolysis_excess_heat urban decentral: - air rural: @@ -743,7 +750,6 @@ sector: use_methanolisation_waste_heat: 0.25 use_methanation_waste_heat: 0.25 use_fuel_cell_waste_heat: 1 - use_electrolysis_waste_heat: 0.25 electricity_transmission_grid: true electricity_distribution_grid: true electricity_grid_connection: true From 4f6363649ca64a407b6cfdfcb7153a49b869ac9b Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 1 Sep 2025 14:14:09 +0200 Subject: [PATCH 02/10] add electrolysis excess heat and refine generator requirements --- rules/build_sector.smk | 10 ++--- scripts/definitions/heat_system.py | 2 +- scripts/prepare_sector_network.py | 71 ++++++++++++++++-------------- 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 61564eb302..831832b12d 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -1250,7 +1250,9 @@ rule build_egs_potentials: def input_heat_source_power(w): - + limited_heat_sources = config_provider( + "sector", "district_heating", "limited_heat_sources" + )(w) return { heat_source_name: resources( "heat_source_power_" + heat_source_name + "_base_s_{clusters}.csv" @@ -1258,10 +1260,8 @@ def input_heat_source_power(w): for heat_source_name in config_provider( "sector", "heat_pump_sources", "urban central" )(w) - if heat_source_name - in config_provider("sector", "district_heating", "limited_heat_sources")( - w - ).keys() + if heat_source_name in limited_heat_sources.keys() + and limited_heat_sources[heat_source_name]["requires_generator"] } diff --git a/scripts/definitions/heat_system.py b/scripts/definitions/heat_system.py index 7d48303c2d..011d65eb92 100644 --- a/scripts/definitions/heat_system.py +++ b/scripts/definitions/heat_system.py @@ -223,7 +223,7 @@ def heat_pump_costs_name(self, heat_source: str) -> str: str The name for the heat pump costs. """ - if heat_source in ["ptes", "geothermal"]: + if heat_source in ["ptes", "geothermal", "electrolysis_excess_heat"]: return f"{self.central_or_decentral} excess-heat-sourced heat pump" else: return f"{self.central_or_decentral} {heat_source}-sourced heat pump" diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index ac14c5bed1..cdd0f0df74 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -3195,12 +3195,6 @@ def add_heat( ) if heat_source in params.limited_heat_sources: - # get potential - p_max_source = pd.read_csv( - heat_source_profile_files[heat_source], - index_col=0, - ).squeeze()[nodes] - # add resource heat_carrier = f"{heat_system} {heat_source} heat" n.add("Carrier", heat_carrier) @@ -3212,31 +3206,42 @@ def add_heat( carrier=heat_carrier, ) - if heat_source in params.direct_utilisation_heat_sources: - capital_cost = ( - costs.at[ - heat_system.heat_source_costs_name(heat_source), - "capital_cost", + # Check if heat source requires a separate generator + heat_source_config = params.limited_heat_sources[heat_source] + requires_generator = heat_source_config["requires_generator"] + + if requires_generator: + # Standard heat source with potential file and generator + p_max_source = pd.read_csv( + heat_source_profile_files[heat_source], + index_col=0, + ).squeeze()[nodes] + + if heat_source in params.direct_utilisation_heat_sources: + capital_cost = ( + costs.at[ + heat_system.heat_source_costs_name(heat_source), + "capital_cost", + ] + * overdim_factor + ) + lifetime = costs.at[ + heat_system.heat_source_costs_name(heat_source), "lifetime" ] - * overdim_factor + else: + capital_cost = 0.0 + lifetime = np.inf + n.add( + "Generator", + nodes, + suffix=f" {heat_carrier}", + bus=nodes + f" {heat_carrier}", + carrier=heat_carrier, + p_nom_extendable=True, + capital_cost=capital_cost, + lifetime=lifetime, + p_nom_max=p_max_source, ) - lifetime = costs.at[ - heat_system.heat_source_costs_name(heat_source), "lifetime" - ] - else: - capital_cost = 0.0 - lifetime = np.inf - n.add( - "Generator", - nodes, - suffix=f" {heat_carrier}", - bus=nodes + f" {heat_carrier}", - carrier=heat_carrier, - p_nom_extendable=True, - capital_cost=capital_cost, - lifetime=lifetime, - p_nom_max=p_max_source, - ) # add heat pump converting source heat + electricity to urban central heat n.add( @@ -5397,15 +5402,17 @@ def add_waste_heat( # Electrolysis waste heat if ( - options["use_electrolysis_waste_heat"] + "electrolysis_excess_heat" + in options["district_heating"]["heat_pump_sources"]["urban central"] and "H2 Electrolysis" in link_carriers ): + # Connect electrolysis waste heat to electrolysis excess heat bus for heat pump boosting n.links.loc[urban_central + " H2 Electrolysis", "bus2"] = ( - urban_central + " urban central heat" + urban_central + " urban central electrolysis excess heat" ) n.links.loc[urban_central + " H2 Electrolysis", "efficiency2"] = ( 0.84 - n.links.loc[urban_central + " H2 Electrolysis", "efficiency"] - ) * options["use_electrolysis_waste_heat"] + ) # Fuel cell waste heat if options["use_fuel_cell_waste_heat"] and "H2 Fuel Cell" in link_carriers: From 21dd7c215e5851ce3bba178f97138045a5b13a14 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 1 Sep 2025 15:58:03 +0200 Subject: [PATCH 03/10] fix: config access --- scripts/prepare_sector_network.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index cdd0f0df74..f0cad5c3c8 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -5402,8 +5402,7 @@ def add_waste_heat( # Electrolysis waste heat if ( - "electrolysis_excess_heat" - in options["district_heating"]["heat_pump_sources"]["urban central"] + "electrolysis_excess_heat" in options["heat_pump_sources"]["urban central"] and "H2 Electrolysis" in link_carriers ): # Connect electrolysis waste heat to electrolysis excess heat bus for heat pump boosting From de8735fcb2ae605892e2ab2a839ab57e31fc1308 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 1 Sep 2025 18:33:41 +0200 Subject: [PATCH 04/10] refactor: rename electrolysis excess heat to electrolysis excess in configuration and scripts --- config/config.default.yaml | 6 +++--- scripts/definitions/heat_system.py | 2 +- scripts/prepare_sector_network.py | 14 +++++++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 200ac6e06c..762986880c 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -550,19 +550,19 @@ sector: constant_temperature_celsius: 65 ignore_missing_regions: false requires_generator: true - electrolysis_excess_heat: + electrolysis excess: constant_temperature_celsius: 70 ignore_missing_regions: false requires_generator: false direct_utilisation_heat_sources: - geothermal - - electrolysis_excess_heat + - electrolysis excess temperature_limited_stores: - ptes heat_pump_sources: urban central: - air - - electrolysis_excess_heat + - electrolysis excess urban decentral: - air rural: diff --git a/scripts/definitions/heat_system.py b/scripts/definitions/heat_system.py index 011d65eb92..a0a7a88fe9 100644 --- a/scripts/definitions/heat_system.py +++ b/scripts/definitions/heat_system.py @@ -223,7 +223,7 @@ def heat_pump_costs_name(self, heat_source: str) -> str: str The name for the heat pump costs. """ - if heat_source in ["ptes", "geothermal", "electrolysis_excess_heat"]: + if heat_source in ["ptes", "geothermal", "electrolysis excess"]: return f"{self.central_or_decentral} excess-heat-sourced heat pump" else: return f"{self.central_or_decentral} {heat_source}-sourced heat pump" diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index f0cad5c3c8..0b677a3fc9 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -3242,6 +3242,18 @@ def add_heat( lifetime=lifetime, p_nom_max=p_max_source, ) + # else: + # breakpoint() + # # Standard heat source without generator + # heat_carrier = f"{heat_system} {heat_source} heat" + # n.add("Carrier", heat_carrier) + # n.add( + # "Bus", + # nodes, + # location=nodes, + # suffix=f" {heat_carrier}", + # carrier=heat_carrier, + # ) # add heat pump converting source heat + electricity to urban central heat n.add( @@ -5402,7 +5414,7 @@ def add_waste_heat( # Electrolysis waste heat if ( - "electrolysis_excess_heat" in options["heat_pump_sources"]["urban central"] + "electrolysis excess heat" in options["heat_pump_sources"]["urban central"] and "H2 Electrolysis" in link_carriers ): # Connect electrolysis waste heat to electrolysis excess heat bus for heat pump boosting From 65aec309537e1fbfa02b01380ea274e329deec40 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Tue, 2 Sep 2025 09:54:54 +0200 Subject: [PATCH 05/10] fix: update reference from "electrolysis excess heat" to "electrolysis excess" in heat pump sources --- scripts/prepare_sector_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 0b677a3fc9..37be7f37f8 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -5414,7 +5414,7 @@ def add_waste_heat( # Electrolysis waste heat if ( - "electrolysis excess heat" in options["heat_pump_sources"]["urban central"] + "electrolysis excess" in options["heat_pump_sources"]["urban central"] and "H2 Electrolysis" in link_carriers ): # Connect electrolysis waste heat to electrolysis excess heat bus for heat pump boosting From 584ff40eea9721f07cd9e53723ac08d272938526 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Tue, 2 Sep 2025 09:55:15 +0200 Subject: [PATCH 06/10] fix: update heat source references in error messages --- scripts/build_cop_profiles/run.py | 2 +- scripts/build_direct_heat_source_utilisation_profiles.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build_cop_profiles/run.py b/scripts/build_cop_profiles/run.py index 0d32b0a453..300d5af60d 100644 --- a/scripts/build_cop_profiles/run.py +++ b/scripts/build_cop_profiles/run.py @@ -142,7 +142,7 @@ def get_cop( ) else: raise ValueError( - f"Unknown heat source {heat_source}. Must be one of [ground, air] or {snakemake.params.heat_sources.keys()}." + f"Unknown heat source {heat_source}. Must be one of [ground, air, ptes] or {snakemake.params.limited_heat_sources.keys()}." ) cop_da = get_cop( diff --git a/scripts/build_direct_heat_source_utilisation_profiles.py b/scripts/build_direct_heat_source_utilisation_profiles.py index 674672292c..57dc82ad72 100644 --- a/scripts/build_direct_heat_source_utilisation_profiles.py +++ b/scripts/build_direct_heat_source_utilisation_profiles.py @@ -50,7 +50,7 @@ def get_source_temperature(heat_source_key: str): else: raise ValueError( f"Unknown heat source {heat_source_key}. Must be one of " - f"{snakemake.params.heat_sources.keys()}." + f"{snakemake.params.direct_utilisation_heat_sources.keys()}." ) From 9ee5002f866cf75283105022584c7f0f9450bd9c Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Tue, 2 Sep 2025 12:45:31 +0200 Subject: [PATCH 07/10] refactor: remove commented-out code in add_heat function --- scripts/prepare_sector_network.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 37be7f37f8..0c54bfb97d 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -3242,19 +3242,6 @@ def add_heat( lifetime=lifetime, p_nom_max=p_max_source, ) - # else: - # breakpoint() - # # Standard heat source without generator - # heat_carrier = f"{heat_system} {heat_source} heat" - # n.add("Carrier", heat_carrier) - # n.add( - # "Bus", - # nodes, - # location=nodes, - # suffix=f" {heat_carrier}", - # carrier=heat_carrier, - # ) - # add heat pump converting source heat + electricity to urban central heat n.add( "Link", From 85d291e693336801659c160bb3bc464f6d0c1794 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Tue, 2 Sep 2025 12:45:37 +0200 Subject: [PATCH 08/10] feat: add color mappings for electrolysis excess heat and related categories in plotting configuration --- config/plotting.default.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/plotting.default.yaml b/config/plotting.default.yaml index cf921a13a6..58fac0064d 100644 --- a/config/plotting.default.yaml +++ b/config/plotting.default.yaml @@ -498,6 +498,9 @@ plotting: H2 turbine: '#991f83' H2 Electrolysis: '#ff29d9' H2 electrolysis: '#ff29d9' + electrolysis excess heat: '#8c1549' + electrolysis excess heat pump: '#f593e3' + electrolysis excess heat direct utilisation: '#ff29d9' # ammonia NH3: '#46caf0' ammonia: '#46caf0' From a11e8f80bc337156424f5c06ad60bf55603f6221 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Wed, 24 Sep 2025 18:21:35 +0200 Subject: [PATCH 09/10] refactor:integrate technology-data efficiencies --- scripts/prepare_sector_network.py | 44 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 0c54bfb97d..781709b07b 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -5356,9 +5356,9 @@ def add_waste_heat( n.links.loc[urban_central + " Fischer-Tropsch", "bus3"] = ( urban_central + " urban central heat" ) - n.links.loc[urban_central + " Fischer-Tropsch", "efficiency3"] = ( - 0.95 - n.links.loc[urban_central + " Fischer-Tropsch", "efficiency"] - ) * options["use_fischer_tropsch_waste_heat"] + n.links.loc[urban_central + " Fischer-Tropsch", "efficiency3"] = costs.at[ + "Fischer-Tropsch", "efficiency-heat" + ] # Sabatier process waste heat if options["use_methanation_waste_heat"] and "Sabatier" in link_carriers: @@ -5366,25 +5366,19 @@ def add_waste_heat( urban_central + " urban central heat" ) n.links.loc[urban_central + " Sabatier", "efficiency3"] = ( - 0.95 - n.links.loc[urban_central + " Sabatier", "efficiency"] - ) * options["use_methanation_waste_heat"] + 1 + - costs.at["methanation", "heat-losses"] + - n.links.loc[urban_central + " Sabatier", "efficiency"] + ) # Haber-Bosch process waste heat if options["use_haber_bosch_waste_heat"] and "Haber-Bosch" in link_carriers: n.links.loc[urban_central + " Haber-Bosch", "bus3"] = ( urban_central + " urban central heat" ) - total_energy_input = ( - cf_industry["MWh_H2_per_tNH3_electrolysis"] - + cf_industry["MWh_elec_per_tNH3_electrolysis"] - ) / cf_industry["MWh_NH3_per_tNH3"] - electricity_input = ( - cf_industry["MWh_elec_per_tNH3_electrolysis"] - / cf_industry["MWh_NH3_per_tNH3"] - ) - n.links.loc[urban_central + " Haber-Bosch", "efficiency3"] = ( - 0.15 * total_energy_input / electricity_input - ) * options["use_haber_bosch_waste_heat"] + n.links.loc[urban_central + " Haber-Bosch", "efficiency3"] = costs.at[ + "Haber-Bosch", "efficiency-heat" + ] # Methanolisation waste heat if ( @@ -5394,10 +5388,9 @@ def add_waste_heat( n.links.loc[urban_central + " methanolisation", "bus4"] = ( urban_central + " urban central heat" ) - n.links.loc[urban_central + " methanolisation", "efficiency4"] = ( - costs.at["methanolisation", "heat-output"] - / costs.at["methanolisation", "hydrogen-input"] - ) * options["use_methanolisation_waste_heat"] + n.links.loc[urban_central + " methanolisation", "efficiency4"] = costs.at[ + "methanolisation", "efficiency-heat" + ] # Electrolysis waste heat if ( @@ -5408,9 +5401,9 @@ def add_waste_heat( n.links.loc[urban_central + " H2 Electrolysis", "bus2"] = ( urban_central + " urban central electrolysis excess heat" ) - n.links.loc[urban_central + " H2 Electrolysis", "efficiency2"] = ( - 0.84 - n.links.loc[urban_central + " H2 Electrolysis", "efficiency"] - ) + n.links.loc[urban_central + " H2 Electrolysis", "efficiency2"] = costs.at[ + "H2 Electrolysis", "efficiency-heat" + ] # Fuel cell waste heat if options["use_fuel_cell_waste_heat"] and "H2 Fuel Cell" in link_carriers: @@ -5420,6 +5413,11 @@ def add_waste_heat( n.links.loc[urban_central + " H2 Fuel Cell", "efficiency2"] = ( 0.95 - n.links.loc[urban_central + " H2 Fuel Cell", "efficiency"] ) * options["use_fuel_cell_waste_heat"] + n.links.loc[urban_central + " H2 Fuel Cell", "efficiency2"] = ( + 1 + - costs.at["fuel cell", "heat-losses"] + - n.links.loc[urban_central + " H2 Fuel Cell", "efficiency"] + ) def add_agriculture( From c330a2469739da001713d821b61d84d19b73f207 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Wed, 24 Sep 2025 19:05:19 +0200 Subject: [PATCH 10/10] feat:add heat sources to config --- config/config.default.yaml | 28 ++++++++++++++++++++++++++++ scripts/prepare_sector_network.py | 19 ++++++++++++++----- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 762986880c..193274c281 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -554,15 +554,43 @@ sector: constant_temperature_celsius: 70 ignore_missing_regions: false requires_generator: false + fuel cell excess: + constant_temperature_celsius: 70 # assume same as electrolysis + ignore_missing_regions: false + requires_generator: false + Fischer-Tropsch excess: + constant_temperature_celsius: 200 # assuming steam + ignore_missing_regions: false + requires_generator: false + Haber-Bosch excess: + constant_temperature_celsius: 45 # 30-60C acc. to DEA catalogue + ignore_missing_regions: false + requires_generator: false + Sabatier excess: + constant_temperature_celsius: 200 # Agora report states process temperature of catalytic methanation of at least 200C + ignore_missing_regions: false + requires_generator: false + methanolisation excess: + constant_temperature_celsius: 70 # 50-100 + ignore_missing_regions: false + requires_generator: false direct_utilisation_heat_sources: - geothermal - electrolysis excess + - fuel cell excess + - Fischer-Tropsch excess + - Haber-Bosch excess + - Sabatier excess + - methanolisation excess temperature_limited_stores: - ptes heat_pump_sources: urban central: - air - electrolysis excess + - fuel cell excess + - Haber-Bosch excess + - methanolisation excess urban decentral: - air rural: diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 781709b07b..6a2a835c5d 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -5350,7 +5350,7 @@ def add_waste_heat( # Fischer-Tropsch waste heat if ( - options["use_fischer_tropsch_waste_heat"] + "Fischer-Tropsch excess" in options["heat_pump_sources"]["urban central"] and "Fischer-Tropsch" in link_carriers ): n.links.loc[urban_central + " Fischer-Tropsch", "bus3"] = ( @@ -5361,7 +5361,10 @@ def add_waste_heat( ] # Sabatier process waste heat - if options["use_methanation_waste_heat"] and "Sabatier" in link_carriers: + if ( + "Sabatier excess" in options["heat_pump_sources"]["urban central"] + and "Sabatier" in link_carriers + ): n.links.loc[urban_central + " Sabatier", "bus3"] = ( urban_central + " urban central heat" ) @@ -5372,7 +5375,10 @@ def add_waste_heat( ) # Haber-Bosch process waste heat - if options["use_haber_bosch_waste_heat"] and "Haber-Bosch" in link_carriers: + if ( + "Haber-Bosch excess" in options["heat_pump_sources"]["urban central"] + and "Haber-Bosch" in link_carriers + ): n.links.loc[urban_central + " Haber-Bosch", "bus3"] = ( urban_central + " urban central heat" ) @@ -5382,7 +5388,7 @@ def add_waste_heat( # Methanolisation waste heat if ( - options["use_methanolisation_waste_heat"] + "methanolisation excess" in options["heat_pump_sources"]["urban central"] and "methanolisation" in link_carriers ): n.links.loc[urban_central + " methanolisation", "bus4"] = ( @@ -5406,7 +5412,10 @@ def add_waste_heat( ] # Fuel cell waste heat - if options["use_fuel_cell_waste_heat"] and "H2 Fuel Cell" in link_carriers: + if ( + "fuel cell excess" in options["heat_pump_sources"]["urban central"] + and "H2 Fuel Cell" in link_carriers + ): n.links.loc[urban_central + " H2 Fuel Cell", "bus2"] = ( urban_central + " urban central heat" )