diff --git a/config/config.default.yaml b/config/config.default.yaml index 550cae4653..193274c281 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -549,13 +549,48 @@ sector: geothermal: constant_temperature_celsius: 65 ignore_missing_regions: false + requires_generator: true + electrolysis excess: + 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: @@ -743,7 +778,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 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' 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/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()}." ) diff --git a/scripts/definitions/heat_system.py b/scripts/definitions/heat_system.py index 7d48303c2d..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"]: + 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 ac14c5bed1..6a2a835c5d 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,32 +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( "Link", @@ -5346,75 +5350,83 @@ 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"] = ( 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: + 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" ) 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: + 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" ) - 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 ( - 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"] = ( 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 ( - options["use_electrolysis_waste_heat"] + "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 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"] + 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: + 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" ) 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(