From 686cf58db36bc92f7d484fea384767a63c74b07c Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 14 Oct 2024 16:49:00 +0200 Subject: [PATCH 01/41] feat: add utilisation potential retrieval --- ...ieve_heat_source_utilisation_potentials.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 scripts/retrieve_heat_source_utilisation_potentials.py diff --git a/scripts/retrieve_heat_source_utilisation_potentials.py b/scripts/retrieve_heat_source_utilisation_potentials.py new file mode 100644 index 000000000..4f07f1458 --- /dev/null +++ b/scripts/retrieve_heat_source_utilisation_potentials.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: : 2024 The PyPSA-Eur Authors +# +# SPDX-License-Identifier: MIT +""" +""" + +import logging +from pathlib import Path + +from _helpers import configure_logging, progress_retrieve, set_scenario_config + +logger = logging.getLogger(__name__) + +if __name__ == "__main__": + if "snakemake" not in globals(): + from _helpers import mock_snakemake + + snakemake = mock_snakemake("retrieve_heat_source_utilisation_potentials") + rootpath = ".." + else: + rootpath = "." + configure_logging(snakemake) + set_scenario_config(snakemake) + + # license: https://creativecommons.org/licenses/by/4.0/ + for ( + heat_source, + heat_source_features, + ) in snakemake.params.heat_source_utilisation_potentials.items(): + url = snakemake.input[heat_source] + filepath = Path(snakemake.output[heat_source]) + + to_fn = Path(rootpath) / filepath + + logger.info( + f"Downloading heat source utilisation potential data for {heat_source} from '{url}'." + ) + disable_progress = snakemake.config["run"].get("disable_progressbar", False) + progress_retrieve(url, to_fn, disable=disable_progress) + + logger.info(f"Data available at at {to_fn}") From 7268b76eb90b00e0c8f4759aaa43931272e1c336 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 14 Oct 2024 16:49:24 +0200 Subject: [PATCH 02/41] feat: add build_heat_source_potentials module --- .../OnshoreRegionData.py | 56 ++++++++++++++ scripts/build_heat_source_potentials/run.py | 77 +++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100755 scripts/build_heat_source_potentials/OnshoreRegionData.py create mode 100644 scripts/build_heat_source_potentials/run.py diff --git a/scripts/build_heat_source_potentials/OnshoreRegionData.py b/scripts/build_heat_source_potentials/OnshoreRegionData.py new file mode 100755 index 000000000..eda307d73 --- /dev/null +++ b/scripts/build_heat_source_potentials/OnshoreRegionData.py @@ -0,0 +1,56 @@ +import geopandas as gpd +from typing import List + + +class OnshoreRegionData: + + def __init__( + self, + onshore_regions: gpd.GeoDataFrame, + data: gpd.GeoDataFrame, + column_name: str, + scaling_factor: float = 1.0, + ) -> None: + + self.onshore_regions = onshore_regions + self.column_name = column_name + self.scaling_factor = scaling_factor + self.data = data.to_crs(onshore_regions.crs) + + self._mapped = False + + @property + def data_in_regions_scaled(self) -> gpd.GeoDataFrame: + if self._mapped: + return self._scaled_data_in_regions + else: + self._data_in_regions = self._map_to_onshore_regions() + self._mapped = True + return self._scaled_data_in_regions + + def _map_to_onshore_regions(self): + """ + This function maps the heat potentials to the onshore regions + + + """ + data_in_regions = gpd.sjoin(self.data, self.onshore_regions, how="right") + + # Initialize an empty list to store the merged GeoDataFrames + ret_val = self.onshore_regions.copy() + ret_val[self.column_name] = ( + data_in_regions.groupby("name")[self.column_name] + .sum() + .reset_index(drop=True) + ) + ret_val = ret_val.set_index("name", drop=True).rename_axis("name")[ + self.column_name + ] + + return ret_val + + @property + def _scaled_data_in_regions(self): + # scaled_data_in_regions = self._data_in_regions.copy() + # scaled_data_in_regions[self.column_name] *= self.scaling_factor + return self._data_in_regions * self.scaling_factor diff --git a/scripts/build_heat_source_potentials/run.py b/scripts/build_heat_source_potentials/run.py new file mode 100644 index 000000000..d7934eab2 --- /dev/null +++ b/scripts/build_heat_source_potentials/run.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors +# +# SPDX-License-Identifier: MIT +""" +""" + +import geopandas as gpd +import xarray as xr +from OnshoreRegionData import OnshoreRegionData +from _helpers import set_scenario_config + + +def get_unit_conversion_factor( + input_unit: str, + output_unit: str, + unit_scaling: dict = {"Wh": 1, "kWh": 1e3, "MWh": 1e6, "GWh": 1e9, "TWh": 1e12}, +) -> float: + + if input_unit not in unit_scaling.keys(): + raise ValueError( + f"Input unit {input_unit} not allowed. Must be one of {unit_scaling.keys()}" + ) + elif output_unit not in unit_scaling.keys(): + raise ValueError( + f"Output unit {output_unit} not allowed. Must be one of {unit_scaling.keys()}" + ) + + return unit_scaling[input_unit] / unit_scaling[output_unit] + + +if __name__ == "__main__": + + if "snakemake" not in globals(): + from _helpers import mock_snakemake + + snakemake = mock_snakemake( + "build_heat_source_potentials", + clusters=48, + ) + + set_scenario_config(snakemake) + + regions_onshore = gpd.read_file(snakemake.input.regions_onshore) + + heat_source_technical_potential = {} + for heat_source, heat_source_features in snakemake.params.heat_sources.items(): + + # move this to retrieve later + heat_source_utilisation_potential = gpd.read_file( + snakemake.input[f"heat_source_utilisation_potential_{heat_source}"] + ) + + heat_source_technical_potential[heat_source] = OnshoreRegionData( + onshore_regions=regions_onshore, + data=heat_source_utilisation_potential, + column_name=heat_source_features["column_name"], + scaling_factor=get_unit_conversion_factor( + input_unit=heat_source_features["unit"], output_unit="MWh" + ) + / heat_source_features["full_load_hours"], + ).data_in_regions_scaled + + heat_source_technical_potential[heat_source].to_csv( + snakemake.output[f"heat_source_technical_potential_{heat_source}"] + ) + + # import ipdb + + # ipdb.set_trace() + # xr.concat( + # [ + # val.expand_dims(dim=key) + # for key, val in heat_source_technical_potential.items() + # ], + # dim=list(heat_source_technical_potential.keys()), + # ).to_netcdf(snakemake.output.heat_source_technical_potentials) From bb0067d748b545a057af38f6692f880962e044d7 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 14 Oct 2024 16:55:40 +0200 Subject: [PATCH 03/41] feat: update config --- config/config.default.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 1cb19dbbf..75bbd063b 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -478,9 +478,19 @@ sector: heat_exchanger_pinch_point_temperature_difference: 5 #K isentropic_compressor_efficiency: 0.8 heat_loss: 0.0 + heat_source_utilisation_potentials: + geothermal: + key: hydrothermal_65 + # activate for 85C hydrothermal + # key: hydrothermal_85 + constant_temperature_celsius: 65 + column_name: Energy_TWh + unit: TWh + full_load_hours: 4000 heat_pump_sources: urban central: - air + - geothermal urban decentral: - air rural: @@ -821,7 +831,7 @@ industry: # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#costs costs: year: 2030 - version: v0.9.2 + version: master social_discountrate: 0.02 fill_values: FOM: 0 @@ -1222,6 +1232,7 @@ plotting: services urban decentral air heat pump: '#5af95d' services rural air heat pump: '#5af95d' urban central air heat pump: '#6cfb6b' + urban central geothermal heat pump: '#48f74f' ground heat pump: '#2fb537' residential rural ground heat pump: '#48f74f' residential rural air heat pump: '#48f74f' From 70a0303337371574b2a94948a94bd972b9743716 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 14 Oct 2024 16:56:29 +0200 Subject: [PATCH 04/41] feat: extend retrieve.smk --- rules/retrieve.smk | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/rules/retrieve.smk b/rules/retrieve.smk index 9d2d3a5b8..0a7363983 100755 --- a/rules/retrieve.smk +++ b/rules/retrieve.smk @@ -630,3 +630,38 @@ if config["enable"]["retrieve"] and ( "data/osm-raw/{country}/substations_relation.json", country=config_provider("countries"), ), + + +if config["enable"]["retrieve"]: + + rule retrieve_heat_source_utilisation_potentials: + # params: + # heat_source_utilisation_potentials=config_provider( + # "sector", "district_heating", "heat_source_utilisation_potentials" + # ), + input: + geothermal=storage( + f"https://fordatis.fraunhofer.de/bitstream/fordatis/341.3/10/{config["sector"]["district_heating"]["heat_source_utilisation_potentials"]["geothermal"]["key"]}.gpkg", + keep_local=True, + ), + output: + geothermal="data/heat_source_utilisation_potentials/geothermal.gpkg", + # **{ + # f"{heat_source}_utilisation_potential": f"data/heat_source_utilisation_potentials/{heat_source}.gpkg" + # for heat_source in config_provider( + # "sector", "district_heating", "heat_source_utilisation_potentials" + # ).keywords.keys() + # }, + log: + "logs/retrieve_heat_source_utilisation_potentials.log", + resources: + mem_mb=500, + # conda: + # "../envs/retrieve.yaml" + # script: + # "../scripts/retrieve_heat_source_utilisation_potentials.py" + retries: 2 + run: + for key in input.keys(): + move(input[key], output[key]) + From 4cabd8533a3a577e51e0894fc52c694198f48f51 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 14 Oct 2024 16:56:55 +0200 Subject: [PATCH 05/41] feat: pass potential to update prepare_sector_network --- scripts/prepare_sector_network.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index f65e2d7b3..125f2b57d 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2172,6 +2172,17 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): else costs.at[costs_name, "efficiency"] ) + # todo: make this more generic + if heat_source in ["geothermal"]: + p_max_source = pd.read_csv( + snakemake.input.heat_source_technical_potential_geothermal, + index_col=0, + ).squeeze() + p_max_source.index = nodes + p_nom_max_electric = p_max_source / (efficiency.max() - 1) + else: + p_nom_max_electric = np.inf + n.madd( "Link", nodes, @@ -2184,6 +2195,7 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): * costs.at[costs_name, "fixed"] * overdim_factor, p_nom_extendable=True, + p_nom_max=p_nom_max_electric, lifetime=costs.at[costs_name, "lifetime"], ) From 5a853114e3e03624b2f4c1d1e1229a59011c101e Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 14 Oct 2024 16:57:07 +0200 Subject: [PATCH 06/41] feat: update cop profile module --- scripts/build_cop_profiles/run.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/scripts/build_cop_profiles/run.py b/scripts/build_cop_profiles/run.py index b93f7df69..2796db93a 100644 --- a/scripts/build_cop_profiles/run.py +++ b/scripts/build_cop_profiles/run.py @@ -123,9 +123,24 @@ def get_country_from_node_name(node_name: str) -> str: for heat_system_type, heat_sources in snakemake.params.heat_pump_sources.items(): cop_this_system_type = [] for heat_source in heat_sources: - source_inlet_temperature_celsius = xr.open_dataarray( - snakemake.input[f"temp_{heat_source.replace('ground', 'soil')}_total"] - ) + if heat_source in ["ground", "air"]: + source_inlet_temperature_celsius = xr.open_dataarray( + snakemake.input[ + f"temp_{heat_source.replace('ground', 'soil')}_total" + ] + ) + elif ( + heat_source + in snakemake.params.heat_source_utilisation_potentials.keys() + ): + source_inlet_temperature_celsius = snakemake.params.heat_sources[ + heat_source + ]["constant_temperature_celsius"] + else: + raise ValueError( + f"Unknown heat source {heat_source}. Must be one of [ground, air] or {snakemake.params.heat_sources.keys()}." + ) + cop_da = get_cop( heat_system_type=heat_system_type, heat_source=heat_source, From b50ea0403bd6c73f64e3f54866077fcfeeebcba4 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 14 Oct 2024 16:57:21 +0200 Subject: [PATCH 07/41] feat: update build_sector.smk accordingly --- rules/build_sector.smk | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/rules/build_sector.smk b/rules/build_sector.smk index f5e395643..d8a94bbd7 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -273,6 +273,30 @@ rule build_central_heating_temperature_profiles: "../scripts/build_central_heating_temperature_profiles/run.py" +rule build_heat_source_potentials: + params: + heat_sources=config_provider( + "sector", "district_heating", "heat_source_utilisation_potentials" + ), + input: + heat_source_utilisation_potential_geothermal="data/heat_source_utilisation_potentials/geothermal.gpkg", + regions_onshore=resources("regions_onshore_base_s_{clusters}.geojson"), + output: + heat_source_technical_potential_geothermal=resources( + "heat_source_technical_potential_geothermal_base_s_{clusters}.csv" + ), + resources: + mem_mb=2000, + log: + logs("build_heat_source_potentials_s_{clusters}.log"), + benchmark: + benchmarks("build_heat_source_potentials/s_{clusters}") + conda: + "../envs/environment.yaml" + script: + "../scripts/build_heat_source_potentials/run.py" + + rule build_cop_profiles: params: heat_pump_sink_T_decentral_heating=config_provider( @@ -285,6 +309,9 @@ rule build_cop_profiles: "sector", "district_heating", "heat_pump_cop_approximation" ), heat_pump_sources=config_provider("sector", "heat_pump_sources"), + heat_source_utilisation_potentials=config_provider( + "sector", "district_heating", "heat_source_utilisation_potentials" + ), snapshots=config_provider("snapshots"), input: central_heating_forward_temperature_profiles=resources( @@ -1113,6 +1140,9 @@ rule prepare_sector_network: if config_provider("sector", "enhanced_geothermal", "enable")(w) else [] ), + heat_source_technical_potential_geothermal=resources( + "heat_source_technical_potential_geothermal_base_s_{clusters}.csv" + ), output: RESULTS + "prenetworks/base_s_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc", From 3bb2e59239eb8ae6c71c0d96234d812e5f11f31b Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Wed, 16 Oct 2024 17:14:07 +0200 Subject: [PATCH 08/41] fix: use generator + links for heat source --- config/config.default.yaml | 1 + scripts/build_cop_profiles/run.py | 2 +- scripts/prepare_sector_network.py | 71 +++++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 18 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 75bbd063b..19fd200a4 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -1233,6 +1233,7 @@ plotting: services rural air heat pump: '#5af95d' urban central air heat pump: '#6cfb6b' urban central geothermal heat pump: '#48f74f' + geothermal heat pump: '#2fb537' ground heat pump: '#2fb537' residential rural ground heat pump: '#48f74f' residential rural air heat pump: '#48f74f' diff --git a/scripts/build_cop_profiles/run.py b/scripts/build_cop_profiles/run.py index 2796db93a..69098e2d4 100644 --- a/scripts/build_cop_profiles/run.py +++ b/scripts/build_cop_profiles/run.py @@ -133,7 +133,7 @@ def get_country_from_node_name(node_name: str) -> str: heat_source in snakemake.params.heat_source_utilisation_potentials.keys() ): - source_inlet_temperature_celsius = snakemake.params.heat_sources[ + source_inlet_temperature_celsius = snakemake.params.heat_source_utilisation_potentials[ heat_source ]["constant_temperature_celsius"] else: diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 125f2b57d..826b75bfe 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2174,30 +2174,67 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): # todo: make this more generic if heat_source in ["geothermal"]: + # get potential p_max_source = pd.read_csv( snakemake.input.heat_source_technical_potential_geothermal, index_col=0, ).squeeze() p_max_source.index = nodes - p_nom_max_electric = p_max_source / (efficiency.max() - 1) + + # add resource + geothermal_heat_carrier = f"{heat_system} geothermal heat" + n.add("Carrier", geothermal_heat_carrier) + n.madd( + "Bus", + nodes, + suffix=f" {geothermal_heat_carrier}", + carrier=geothermal_heat_carrier, + ) + + n.madd( + "Generator", + nodes, + suffix=f" {geothermal_heat_carrier}", + bus=nodes + f" {geothermal_heat_carrier}", + carrier=geothermal_heat_carrier, + p_nom_extendable=True, + capital_cost=0.0, # TODO: update later to underground part + p_nom_max=p_max_source + ) + + # add heat pump converting geothermal heat + electricity to urban central heat + n.madd( + "Link", + nodes, + suffix=f" {heat_system} {heat_source} heat pump", + bus0=nodes, + bus1=nodes + f" {geothermal_heat_carrier}", + bus2=nodes + f" {heat_system} heat", + carrier=f"{heat_system} {heat_source} heat pump", + efficiency=-(efficiency - 1), + efficiency2=efficiency, + capital_cost=costs.at[costs_name, "efficiency"] + * costs.at[costs_name, "fixed"] + * overdim_factor, # TODO: update later to heat pump part + p_nom_extendable=True, + lifetime=costs.at[costs_name, "lifetime"], + ) else: - p_nom_max_electric = np.inf - n.madd( - "Link", - nodes, - suffix=f" {heat_system} {heat_source} heat pump", - bus0=nodes, - bus1=nodes + f" {heat_system} heat", - carrier=f"{heat_system} {heat_source} heat pump", - efficiency=efficiency, - capital_cost=costs.at[costs_name, "efficiency"] - * costs.at[costs_name, "fixed"] - * overdim_factor, - p_nom_extendable=True, - p_nom_max=p_nom_max_electric, - lifetime=costs.at[costs_name, "lifetime"], - ) + n.madd( + "Link", + nodes, + suffix=f" {heat_system} {heat_source} heat pump", + bus0=nodes, + bus1=nodes + f" {heat_system} heat", + carrier=f"{heat_system} {heat_source} heat pump", + efficiency=efficiency, + capital_cost=costs.at[costs_name, "efficiency"] + * costs.at[costs_name, "fixed"] + * overdim_factor, + p_nom_extendable=True, + lifetime=costs.at[costs_name, "lifetime"], + ) if options["tes"]: n.add("Carrier", f"{heat_system} water tanks") From c31109cb4e1ee172a3b08d8bdf68f4fa47f06721 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Thu, 17 Oct 2024 12:09:24 +0200 Subject: [PATCH 09/41] clean up run.py --- scripts/build_heat_source_potentials/run.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/scripts/build_heat_source_potentials/run.py b/scripts/build_heat_source_potentials/run.py index d7934eab2..6e5a88b93 100644 --- a/scripts/build_heat_source_potentials/run.py +++ b/scripts/build_heat_source_potentials/run.py @@ -46,7 +46,6 @@ def get_unit_conversion_factor( heat_source_technical_potential = {} for heat_source, heat_source_features in snakemake.params.heat_sources.items(): - # move this to retrieve later heat_source_utilisation_potential = gpd.read_file( snakemake.input[f"heat_source_utilisation_potential_{heat_source}"] ) @@ -65,13 +64,3 @@ def get_unit_conversion_factor( snakemake.output[f"heat_source_technical_potential_{heat_source}"] ) - # import ipdb - - # ipdb.set_trace() - # xr.concat( - # [ - # val.expand_dims(dim=key) - # for key, val in heat_source_technical_potential.items() - # ], - # dim=list(heat_source_technical_potential.keys()), - # ).to_netcdf(snakemake.output.heat_source_technical_potentials) From 6310bad8f049887f0a38378250ac57861b22c6d6 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Thu, 24 Oct 2024 14:25:29 +0200 Subject: [PATCH 10/41] feat: generalise 'geothermal' to any heat source in Fraunhofer data --- config/config.default.yaml | 2 +- rules/build_sector.smk | 49 ++++++++++++++++----- rules/retrieve.smk | 41 ++++++++--------- scripts/build_heat_source_potentials/run.py | 16 ++++--- scripts/prepare_sector_network.py | 20 ++++----- 5 files changed, 79 insertions(+), 49 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 5170ab0ea..ba99365f6 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -479,7 +479,7 @@ sector: heat_exchanger_pinch_point_temperature_difference: 5 #K isentropic_compressor_efficiency: 0.8 heat_loss: 0.0 - heat_source_utilisation_potentials: + fraunhofer_heat_utilisation_potentials: geothermal: key: hydrothermal_65 # activate for 85C hydrothermal diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 51a89f38b..650daa70c 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -286,16 +286,31 @@ rule build_central_heating_temperature_profiles: rule build_heat_source_potentials: params: - heat_sources=config_provider( - "sector", "district_heating", "heat_source_utilisation_potentials" + fraunhofer_heat_sources=config_provider( + "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" ), input: - heat_source_utilisation_potential_geothermal="data/heat_source_utilisation_potentials/geothermal.gpkg", + # TODO: accessing `config` as a dictionary might not work with scenario management!! + **{ + heat_source: f"data/fraunhofer_heat_utilisation_potentials/{heat_source}.gpkg" + for heat_source in config["sector"]["district_heating"][ + "fraunhofer_heat_utilisation_potentials" + ].keys() + if heat_source in config["sector"]["heat_pump_sources"]["urban central"] + }, regions_onshore=resources("regions_onshore_base_s_{clusters}.geojson"), + # dict comprehension gives those Fraunhofer utilisation potentials that are present in sector/district_heating/heat_pump_sources/urban central as input output: - heat_source_technical_potential_geothermal=resources( - "heat_source_technical_potential_geothermal_base_s_{clusters}.csv" - ), + # TODO: accessing `config` as a dictionary might not work with scenario management!! + **{ + heat_source: resources( + "heat_source_potential_" + heat_source + "_base_s_{clusters}.csv" + ) + for heat_source in config["sector"]["district_heating"][ + "fraunhofer_heat_utilisation_potentials" + ].keys() + if heat_source in config["sector"]["heat_pump_sources"]["urban central"] + }, resources: mem_mb=2000, log: @@ -320,8 +335,8 @@ rule build_cop_profiles: "sector", "district_heating", "heat_pump_cop_approximation" ), heat_pump_sources=config_provider("sector", "heat_pump_sources"), - heat_source_utilisation_potentials=config_provider( - "sector", "district_heating", "heat_source_utilisation_potentials" + fraunhofer_heat_utilisation_potentials=config_provider( + "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" ), snapshots=config_provider("snapshots"), input: @@ -1055,6 +1070,9 @@ rule prepare_sector_network: heat_pump_sources=config_provider("sector", "heat_pump_sources"), heat_systems=config_provider("sector", "heat_systems"), energy_totals_year=config_provider("energy", "energy_totals_year"), + fraunhofer_heat_sources=config_provider( + "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + ) input: unpack(input_profile_offwind), **rules.cluster_gas_network.output, @@ -1151,9 +1169,18 @@ rule prepare_sector_network: if config_provider("sector", "enhanced_geothermal", "enable")(w) else [] ), - heat_source_technical_potential_geothermal=resources( - "heat_source_technical_potential_geothermal_base_s_{clusters}.csv" - ), + # heat_source_technical_potential_geothermal=resources( + # "heat_source_technical_potential_geothermal_base_s_{clusters}.csv" + # ), + **{ + heat_source: resources( + "heat_source_potential_" + heat_source + "_base_s_{clusters}.csv" + ) + for heat_source in config["sector"]["district_heating"][ + "fraunhofer_heat_utilisation_potentials" + ].keys() + if heat_source in config["sector"]["heat_pump_sources"]["urban central"] + } output: RESULTS + "prenetworks/base_s_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc", diff --git a/rules/retrieve.smk b/rules/retrieve.smk index 0a7363983..dba7ef505 100755 --- a/rules/retrieve.smk +++ b/rules/retrieve.smk @@ -635,33 +635,34 @@ if config["enable"]["retrieve"] and ( if config["enable"]["retrieve"]: rule retrieve_heat_source_utilisation_potentials: - # params: - # heat_source_utilisation_potentials=config_provider( - # "sector", "district_heating", "heat_source_utilisation_potentials" - # ), input: - geothermal=storage( - f"https://fordatis.fraunhofer.de/bitstream/fordatis/341.3/10/{config["sector"]["district_heating"]["heat_source_utilisation_potentials"]["geothermal"]["key"]}.gpkg", - keep_local=True, - ), + # TODO: accessing `config` as a dictionary might not work with scenario management!! + **{ + heat_source_name: storage( + f"https://fordatis.fraunhofer.de/bitstream/fordatis/341.3/10/{heat_source_features["key"]}.gpkg", + keep_local=True, + ) + for heat_source_name, heat_source_features in config["sector"][ + "district_heating" + ]["fraunhofer_heat_utilisation_potentials"].items() + if heat_source_name + in config["sector"]["heat_pump_sources"]["urban central"] + }, output: - geothermal="data/heat_source_utilisation_potentials/geothermal.gpkg", - # **{ - # f"{heat_source}_utilisation_potential": f"data/heat_source_utilisation_potentials/{heat_source}.gpkg" - # for heat_source in config_provider( - # "sector", "district_heating", "heat_source_utilisation_potentials" - # ).keywords.keys() - # }, + # TODO: accessing `config` as a dictionary might not work with scenario management!! + **{ + heat_source_name: f"data/fraunhofer_heat_utilisation_potentials/{heat_source_name}.gpkg" + for heat_source_name, heat_source_features in config["sector"][ + "district_heating" + ]["fraunhofer_heat_utilisation_potentials"].items() + if heat_source_name + in config["sector"]["heat_pump_sources"]["urban central"] + }, log: "logs/retrieve_heat_source_utilisation_potentials.log", resources: mem_mb=500, - # conda: - # "../envs/retrieve.yaml" - # script: - # "../scripts/retrieve_heat_source_utilisation_potentials.py" retries: 2 run: for key in input.keys(): move(input[key], output[key]) - diff --git a/scripts/build_heat_source_potentials/run.py b/scripts/build_heat_source_potentials/run.py index 6e5a88b93..4c291102d 100644 --- a/scripts/build_heat_source_potentials/run.py +++ b/scripts/build_heat_source_potentials/run.py @@ -14,16 +14,19 @@ def get_unit_conversion_factor( input_unit: str, output_unit: str, - unit_scaling: dict = {"Wh": 1, "kWh": 1e3, "MWh": 1e6, "GWh": 1e9, "TWh": 1e12}, + unit_scaling: dict = {"Wh": 1, "kWh": 1e3, + "MWh": 1e6, "GWh": 1e9, "TWh": 1e12}, ) -> float: if input_unit not in unit_scaling.keys(): raise ValueError( - f"Input unit {input_unit} not allowed. Must be one of {unit_scaling.keys()}" + f"Input unit {input_unit} not allowed. Must be one of { + unit_scaling.keys()}" ) elif output_unit not in unit_scaling.keys(): raise ValueError( - f"Output unit {output_unit} not allowed. Must be one of {unit_scaling.keys()}" + f"Output unit {output_unit} not allowed. Must be one of { + unit_scaling.keys()}" ) return unit_scaling[input_unit] / unit_scaling[output_unit] @@ -44,10 +47,10 @@ def get_unit_conversion_factor( regions_onshore = gpd.read_file(snakemake.input.regions_onshore) heat_source_technical_potential = {} - for heat_source, heat_source_features in snakemake.params.heat_sources.items(): + for heat_source, heat_source_features in snakemake.params.fraunhofer_heat_sources.items(): heat_source_utilisation_potential = gpd.read_file( - snakemake.input[f"heat_source_utilisation_potential_{heat_source}"] + snakemake.input[heat_source] ) heat_source_technical_potential[heat_source] = OnshoreRegionData( @@ -61,6 +64,5 @@ def get_unit_conversion_factor( ).data_in_regions_scaled heat_source_technical_potential[heat_source].to_csv( - snakemake.output[f"heat_source_technical_potential_{heat_source}"] + snakemake.output[heat_source] ) - diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index e6be8c42b..1238b996c 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2161,30 +2161,30 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): ) # todo: make this more generic - if heat_source in ["geothermal"]: + if heat_source in snakemake.params.fraunhofer_heat_sources: # get potential p_max_source = pd.read_csv( - snakemake.input.heat_source_technical_potential_geothermal, + snakemake.input[heat_source], index_col=0, ).squeeze() p_max_source.index = nodes # add resource - geothermal_heat_carrier = f"{heat_system} geothermal heat" - n.add("Carrier", geothermal_heat_carrier) + heat_carrier = f"{heat_system} {heat_source} heat" + n.add("Carrier", heat_carrier) n.madd( "Bus", nodes, - suffix=f" {geothermal_heat_carrier}", - carrier=geothermal_heat_carrier, + suffix=f" {heat_carrier}", + carrier=heat_carrier, ) n.madd( "Generator", nodes, - suffix=f" {geothermal_heat_carrier}", - bus=nodes + f" {geothermal_heat_carrier}", - carrier=geothermal_heat_carrier, + suffix=f" {heat_carrier}", + bus=nodes + f" {heat_carrier}", + carrier=heat_carrier, p_nom_extendable=True, capital_cost=0.0, # TODO: update later to underground part p_nom_max=p_max_source @@ -2196,7 +2196,7 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): nodes, suffix=f" {heat_system} {heat_source} heat pump", bus0=nodes, - bus1=nodes + f" {geothermal_heat_carrier}", + bus1=nodes + f" {heat_carrier}", bus2=nodes + f" {heat_system} heat", carrier=f"{heat_system} {heat_source} heat pump", efficiency=-(efficiency - 1), From 5f0b94aff54f2130547a97f5be23971f880bc072 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:26:19 +0000 Subject: [PATCH 11/41] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- rules/build_sector.smk | 24 +++++++++---------- scripts/build_cop_profiles/run.py | 8 ++++--- .../OnshoreRegionData.py | 4 +++- scripts/build_heat_source_potentials/run.py | 14 +++++------ scripts/prepare_sector_network.py | 14 +++++------ 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 650daa70c..0f8ad8d8c 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -290,7 +290,7 @@ rule build_heat_source_potentials: "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" ), input: - # TODO: accessing `config` as a dictionary might not work with scenario management!! + # TODO: accessing `config` as a dictionary might not work with scenario management!! **{ heat_source: f"data/fraunhofer_heat_utilisation_potentials/{heat_source}.gpkg" for heat_source in config["sector"]["district_heating"][ @@ -301,7 +301,7 @@ rule build_heat_source_potentials: regions_onshore=resources("regions_onshore_base_s_{clusters}.geojson"), # dict comprehension gives those Fraunhofer utilisation potentials that are present in sector/district_heating/heat_pump_sources/urban central as input output: - # TODO: accessing `config` as a dictionary might not work with scenario management!! + # TODO: accessing `config` as a dictionary might not work with scenario management!! **{ heat_source: resources( "heat_source_potential_" + heat_source + "_base_s_{clusters}.csv" @@ -1072,11 +1072,20 @@ rule prepare_sector_network: energy_totals_year=config_provider("energy", "energy_totals_year"), fraunhofer_heat_sources=config_provider( "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" - ) + ), input: unpack(input_profile_offwind), **rules.cluster_gas_network.output, **rules.build_gas_input_locations.output, + **{ + heat_source: resources( + "heat_source_potential_" + heat_source + "_base_s_{clusters}.csv" + ) + for heat_source in config["sector"]["district_heating"][ + "fraunhofer_heat_utilisation_potentials" + ].keys() + if heat_source in config["sector"]["heat_pump_sources"]["urban central"] + }, snapshot_weightings=resources( "snapshot_weightings_base_s_{clusters}_elec_l{ll}_{opts}_{sector_opts}.csv" ), @@ -1172,15 +1181,6 @@ rule prepare_sector_network: # heat_source_technical_potential_geothermal=resources( # "heat_source_technical_potential_geothermal_base_s_{clusters}.csv" # ), - **{ - heat_source: resources( - "heat_source_potential_" + heat_source + "_base_s_{clusters}.csv" - ) - for heat_source in config["sector"]["district_heating"][ - "fraunhofer_heat_utilisation_potentials" - ].keys() - if heat_source in config["sector"]["heat_pump_sources"]["urban central"] - } output: RESULTS + "prenetworks/base_s_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc", diff --git a/scripts/build_cop_profiles/run.py b/scripts/build_cop_profiles/run.py index 69098e2d4..2968ff5c9 100644 --- a/scripts/build_cop_profiles/run.py +++ b/scripts/build_cop_profiles/run.py @@ -133,9 +133,11 @@ def get_country_from_node_name(node_name: str) -> str: heat_source in snakemake.params.heat_source_utilisation_potentials.keys() ): - source_inlet_temperature_celsius = snakemake.params.heat_source_utilisation_potentials[ - heat_source - ]["constant_temperature_celsius"] + source_inlet_temperature_celsius = ( + snakemake.params.heat_source_utilisation_potentials[heat_source][ + "constant_temperature_celsius" + ] + ) else: raise ValueError( f"Unknown heat source {heat_source}. Must be one of [ground, air] or {snakemake.params.heat_sources.keys()}." diff --git a/scripts/build_heat_source_potentials/OnshoreRegionData.py b/scripts/build_heat_source_potentials/OnshoreRegionData.py index eda307d73..7d18edf37 100755 --- a/scripts/build_heat_source_potentials/OnshoreRegionData.py +++ b/scripts/build_heat_source_potentials/OnshoreRegionData.py @@ -1,6 +1,8 @@ -import geopandas as gpd +# -*- coding: utf-8 -*- from typing import List +import geopandas as gpd + class OnshoreRegionData: diff --git a/scripts/build_heat_source_potentials/run.py b/scripts/build_heat_source_potentials/run.py index 4c291102d..d3459ca45 100644 --- a/scripts/build_heat_source_potentials/run.py +++ b/scripts/build_heat_source_potentials/run.py @@ -7,15 +7,14 @@ import geopandas as gpd import xarray as xr -from OnshoreRegionData import OnshoreRegionData from _helpers import set_scenario_config +from OnshoreRegionData import OnshoreRegionData def get_unit_conversion_factor( input_unit: str, output_unit: str, - unit_scaling: dict = {"Wh": 1, "kWh": 1e3, - "MWh": 1e6, "GWh": 1e9, "TWh": 1e12}, + unit_scaling: dict = {"Wh": 1, "kWh": 1e3, "MWh": 1e6, "GWh": 1e9, "TWh": 1e12}, ) -> float: if input_unit not in unit_scaling.keys(): @@ -47,11 +46,12 @@ def get_unit_conversion_factor( regions_onshore = gpd.read_file(snakemake.input.regions_onshore) heat_source_technical_potential = {} - for heat_source, heat_source_features in snakemake.params.fraunhofer_heat_sources.items(): + for ( + heat_source, + heat_source_features, + ) in snakemake.params.fraunhofer_heat_sources.items(): - heat_source_utilisation_potential = gpd.read_file( - snakemake.input[heat_source] - ) + heat_source_utilisation_potential = gpd.read_file(snakemake.input[heat_source]) heat_source_technical_potential[heat_source] = OnshoreRegionData( onshore_regions=regions_onshore, diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 1238b996c..7bcb2f9db 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2173,12 +2173,12 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): heat_carrier = f"{heat_system} {heat_source} heat" n.add("Carrier", heat_carrier) n.madd( - "Bus", - nodes, + "Bus", + nodes, suffix=f" {heat_carrier}", carrier=heat_carrier, - ) - + ) + n.madd( "Generator", nodes, @@ -2186,8 +2186,8 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): bus=nodes + f" {heat_carrier}", carrier=heat_carrier, p_nom_extendable=True, - capital_cost=0.0, # TODO: update later to underground part - p_nom_max=p_max_source + capital_cost=0.0, # TODO: update later to underground part + p_nom_max=p_max_source, ) # add heat pump converting geothermal heat + electricity to urban central heat @@ -2203,7 +2203,7 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): efficiency2=efficiency, capital_cost=costs.at[costs_name, "efficiency"] * costs.at[costs_name, "fixed"] - * overdim_factor, # TODO: update later to heat pump part + * overdim_factor, # TODO: update later to heat pump part p_nom_extendable=True, lifetime=costs.at[costs_name, "lifetime"], ) From e614637f4f76d56e6eb52d6c8ca0730a790228c2 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 4 Nov 2024 16:15:06 +0100 Subject: [PATCH 12/41] chor: simplify indexing --- 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 7bcb2f9db..80512f570 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2166,8 +2166,7 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): p_max_source = pd.read_csv( snakemake.input[heat_source], index_col=0, - ).squeeze() - p_max_source.index = nodes + ).squeeze()[nodes] # add resource heat_carrier = f"{heat_system} {heat_source} heat" From e23d64c835c43e412dbe491e88068f411b21a0b0 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 4 Nov 2024 16:15:20 +0100 Subject: [PATCH 13/41] style: rename potentials var --- scripts/build_cop_profiles/run.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build_cop_profiles/run.py b/scripts/build_cop_profiles/run.py index 2968ff5c9..d84feaba7 100644 --- a/scripts/build_cop_profiles/run.py +++ b/scripts/build_cop_profiles/run.py @@ -131,10 +131,10 @@ def get_country_from_node_name(node_name: str) -> str: ) elif ( heat_source - in snakemake.params.heat_source_utilisation_potentials.keys() + in snakemake.params.fraunhofer_heat_utilisation_potentials.keys() ): source_inlet_temperature_celsius = ( - snakemake.params.heat_source_utilisation_potentials[heat_source][ + snakemake.params.fraunhofer_heat_utilisation_potentials[heat_source][ "constant_temperature_celsius" ] ) From cdcae9400429260e691768ff44298431cce00aa7 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Tue, 5 Nov 2024 15:07:13 +0100 Subject: [PATCH 14/41] fix: return 0 for COP calculation when source inlet temperature exceeds sink outlet temperature --- .../CentralHeatingCopApproximator.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/build_cop_profiles/CentralHeatingCopApproximator.py b/scripts/build_cop_profiles/CentralHeatingCopApproximator.py index 08dd6a1a8..30f84356b 100644 --- a/scripts/build_cop_profiles/CentralHeatingCopApproximator.py +++ b/scripts/build_cop_profiles/CentralHeatingCopApproximator.py @@ -144,11 +144,17 @@ def approximate_cop(self) -> Union[xr.DataArray, np.array]: """ Calculate the coefficient of performance (COP) for the system. + Notes: + ------ + Returns 0 where the source inlet temperature is greater than the sink outlet temperature. + Returns: -------- Union[xr.DataArray, np.array]: The calculated COP values. """ - return ( + return xr.where( + self.t_source_in_kelvin > self.t_sink_out_kelvin, + 0, self.ideal_lorenz_cop * ( ( @@ -170,7 +176,7 @@ def approximate_cop(self) -> Union[xr.DataArray, np.array]: * (1 - self.ratio_evaporation_compression_work) + 1 - self.isentropic_efficiency_compressor_kelvin - - self.heat_loss + - self.heat_loss, ) @property From 4f87d58968851e7c9d5c31899010662357a25f4f Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Tue, 5 Nov 2024 15:34:03 +0100 Subject: [PATCH 15/41] refactor: remove unused function and clean up code in build_cop_profiles.run.py --- scripts/build_cop_profiles/run.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/scripts/build_cop_profiles/run.py b/scripts/build_cop_profiles/run.py index d84feaba7..da90d9185 100644 --- a/scripts/build_cop_profiles/run.py +++ b/scripts/build_cop_profiles/run.py @@ -27,11 +27,8 @@ urban central: urban decentral: rural: - snapshots: - Inputs ------ -- `resources//regions_onshore.geojson`: Onshore regions - `resources//temp_soil_total`: Ground temperature - `resources//temp_air_total`: Air temperature @@ -94,10 +91,6 @@ def get_cop( ).approximate_cop() -def get_country_from_node_name(node_name: str) -> str: - return node_name[:2] - - if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake @@ -109,9 +102,6 @@ def get_country_from_node_name(node_name: str) -> str: set_scenario_config(snakemake) - # map forward and return temperatures specified on country-level to onshore regions - regions_onshore = gpd.read_file(snakemake.input.regions_onshore)["name"] - snapshots = pd.date_range(freq="h", **snakemake.params.snapshots) central_heating_forward_temperature: xr.DataArray = xr.open_dataarray( snakemake.input.central_heating_forward_temperature_profiles ) From 854af804a7d9ec32e6d7af9f9068212861f1398d Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Tue, 5 Nov 2024 15:42:07 +0100 Subject: [PATCH 16/41] style: clean-up comments in OnshoreRegionData.py and prepare_sector_network.py --- scripts/build_heat_source_potentials/OnshoreRegionData.py | 8 ++++++-- scripts/prepare_sector_network.py | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/build_heat_source_potentials/OnshoreRegionData.py b/scripts/build_heat_source_potentials/OnshoreRegionData.py index 7d18edf37..f6dec2620 100755 --- a/scripts/build_heat_source_potentials/OnshoreRegionData.py +++ b/scripts/build_heat_source_potentials/OnshoreRegionData.py @@ -1,4 +1,10 @@ # -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors +# +# SPDX-License-Identifier: MIT +""" +""" + from typing import List import geopandas as gpd @@ -53,6 +59,4 @@ def _map_to_onshore_regions(self): @property def _scaled_data_in_regions(self): - # scaled_data_in_regions = self._data_in_regions.copy() - # scaled_data_in_regions[self.column_name] *= self.scaling_factor return self._data_in_regions * self.scaling_factor diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 1f821b7e8..38bdc1609 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2160,7 +2160,6 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): else costs.at[costs_name, "efficiency"] ) - # todo: make this more generic if heat_source in snakemake.params.fraunhofer_heat_sources: # get potential p_max_source = pd.read_csv( @@ -2189,7 +2188,7 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): p_nom_max=p_max_source, ) - # add heat pump converting geothermal heat + electricity to urban central heat + # add heat pump converting source heat + electricity to urban central heat n.madd( "Link", nodes, From fcd43c0e4261ad1d8eee8ddf53e48dd75561409c Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Tue, 5 Nov 2024 17:39:06 +0100 Subject: [PATCH 17/41] feat: add direct heat source utilisation when source temp > forward temp --- config/config.default.yaml | 5 +- rules/build_sector.smk | 37 +++++++++- ...direct_heat_source_utilisation_profiles.py | 74 +++++++++++++++++++ scripts/build_heat_source_potentials/run.py | 1 - scripts/prepare_sector_network.py | 34 ++++++++- 5 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 scripts/build_direct_heat_source_utilisation_profiles.py diff --git a/config/config.default.yaml b/config/config.default.yaml index ba99365f6..3cb51c2f3 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -488,6 +488,8 @@ sector: column_name: Energy_TWh unit: TWh full_load_hours: 4000 + direct_utilisation_heat_sources: + - geothermal heat_pump_sources: urban central: - air @@ -1234,7 +1236,8 @@ plotting: services rural air heat pump: '#5af95d' urban central air heat pump: '#6cfb6b' urban central geothermal heat pump: '#48f74f' - geothermal heat pump: '#2fb537' + geothermal heat pump: '#48f74f' + geothermal direct utilisation: '#2fb541' ground heat pump: '#2fb537' residential rural ground heat pump: '#48f74f' residential rural air heat pump: '#48f74f' diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 161368a8f..1aaaadab3 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -362,6 +362,34 @@ rule build_cop_profiles: script: "../scripts/build_cop_profiles/run.py" +rule build_direct_heat_source_utilisation_profiles: + params: + direct_utilisation_heat_sources=config_provider( + "sector", "district_heating", "direct_utilisation_heat_sources" + ), + fraunhofer_heat_utilisation_potentials=config_provider( + "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + ), + snapshots=config_provider("snapshots"), + input: + central_heating_forward_temperature_profiles=resources( + "central_heating_forward_temperature_profiles_base_s_{clusters}_{planning_horizons}.nc" + ), + output: + direct_heat_source_utilisation_profiles=resources("direct_heat_source_utilisation_profiles_base_s_{clusters}_{planning_horizons}.nc"), + resources: + mem_mb=20000, + log: + logs("build_direct_heat_source_utilisation_profiles_s_{clusters}_{planning_horizons}.log"), + benchmark: + benchmarks("build_direct_heat_source_utilisation_profiles/s_{clusters}_{planning_horizons}") + conda: + "../envs/environment.yaml" + script: + "../scripts/build_direct_heat_source_utilisation_profiles.py" + + + def solar_thermal_cutout(wildcards): c = config_provider("solar_thermal", "cutout")(wildcards) @@ -1073,6 +1101,9 @@ rule prepare_sector_network: fraunhofer_heat_sources=config_provider( "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" ), + direct_utilisation_heat_sources=config_provider( + "sector", "district_heating", "direct_utilisation_heat_sources" + ), input: unpack(input_profile_offwind), **rules.cluster_gas_network.output, @@ -1173,9 +1204,9 @@ rule prepare_sector_network: if config_provider("sector", "enhanced_geothermal", "enable")(w) else [] ), - # heat_source_technical_potential_geothermal=resources( - # "heat_source_technical_potential_geothermal_base_s_{clusters}.csv" - # ), + direct_heat_source_utilisation_profiles=resources( + "direct_heat_source_utilisation_profiles_base_s_{clusters}_{planning_horizons}.nc" + ), output: RESULTS + "prenetworks/base_s_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc", diff --git a/scripts/build_direct_heat_source_utilisation_profiles.py b/scripts/build_direct_heat_source_utilisation_profiles.py new file mode 100644 index 000000000..42edc8d9c --- /dev/null +++ b/scripts/build_direct_heat_source_utilisation_profiles.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors +# +# SPDX-License-Identifier: MIT +""" + +""" + +import sys + +import geopandas as gpd +import numpy as np +import pandas as pd +import xarray as xr +from typing import List + + +from _helpers import set_scenario_config + + +def get_source_temperature(heat_source_key: str): + + if ( + heat_source_key + in snakemake.params.fraunhofer_heat_utilisation_potentials.keys() + ): + return snakemake.params.fraunhofer_heat_utilisation_potentials[heat_source_key][ + "constant_temperature_celsius" + ] + else: + raise ValueError( + f"Unknown heat source {heat_source_key}. Must be one of { + snakemake.params.heat_sources.keys()}." + ) + + +def get_profile( + source_temperature: float | xr.DataArray, forward_temperature: xr.DataArray +): + + return xr.where(source_temperature >= forward_temperature, 1.0, 0.0) + + +if __name__ == "__main__": + if "snakemake" not in globals(): + from _helpers import mock_snakemake + + snakemake = mock_snakemake( + "build_cop_profiles", + clusters=48, + ) + + set_scenario_config(snakemake) + + direct_utilisation_heat_sources: List[str] = ( + snakemake.params.direct_utilisation_heat_sources + ) + + central_heating_forward_temperature: xr.DataArray = xr.open_dataarray( + snakemake.input.central_heating_forward_temperature_profiles + ) + + xr.concat( + [ + get_profile( + source_temperature=get_source_temperature(heat_source_key), + forward_temperature=central_heating_forward_temperature, + ).assign_coords( + heat_source=heat_source_key + ) + for heat_source_key in direct_utilisation_heat_sources + ], + dim="heat_source", + ).to_netcdf(snakemake.output.direct_heat_source_utilisation_profiles) diff --git a/scripts/build_heat_source_potentials/run.py b/scripts/build_heat_source_potentials/run.py index d3459ca45..ca865121a 100644 --- a/scripts/build_heat_source_potentials/run.py +++ b/scripts/build_heat_source_potentials/run.py @@ -6,7 +6,6 @@ """ import geopandas as gpd -import xarray as xr from _helpers import set_scenario_config from OnshoreRegionData import OnshoreRegionData diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 38bdc1609..ac84d5fb7 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2030,7 +2030,7 @@ def build_heat_demand(n): return heat_demand -def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): +def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray, direct_heat_source_utilisation_profile: xr.DataArray): """ Add heat sector to the network. @@ -2205,8 +2205,29 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): p_nom_extendable=True, lifetime=costs.at[costs_name, "lifetime"], ) - else: + if heat_source in snakemake.params.direct_utilisation_heat_sources: + # todo: add efficiency + efficiency = ( + direct_heat_source_utilisation_profile.sel( + heat_source=heat_source, + name=nodes, + ) + .to_pandas() + .reindex(index=n.snapshots) + ) + # add link for direct usage of heat source when source temperature exceeds forward temperature + n.madd( + "Link", + nodes, + suffix=f" {heat_system} {heat_source} direct utilisation", + bus0=nodes + f" {heat_carrier}", + bus1=nodes + f" {heat_system} heat", + efficiency=efficiency, + carrier=f"{heat_system} {heat_source} direct utilisation", + p_nom_extendable=True + ) + else: n.madd( "Link", nodes, @@ -4629,7 +4650,14 @@ def add_enhanced_geothermal(n, egs_potentials, egs_overlap, costs): add_land_transport(n, costs) if options["heating"]: - add_heat(n=n, costs=costs, cop=xr.open_dataarray(snakemake.input.cop_profiles)) + add_heat( + n=n, + costs=costs, + cop=xr.open_dataarray(snakemake.input.cop_profiles), + direct_heat_source_utilisation_profile=xr.open_dataarray( + snakemake.input.direct_heat_source_utilisation_profiles + ), + ) if options["biomass"]: add_biomass(n, costs) From 1fac259a2b8ee453d688793b95a5c9d857d7d1fe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:39:32 +0000 Subject: [PATCH 18/41] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- config/config.default.yaml | 2 +- rules/build_sector.smk | 15 ++++++++++----- scripts/build_cop_profiles/run.py | 6 +++--- ...ild_direct_heat_source_utilisation_profiles.py | 8 ++------ scripts/prepare_sector_network.py | 9 +++++++-- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 3cb51c2f3..911b14c22 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -489,7 +489,7 @@ sector: unit: TWh full_load_hours: 4000 direct_utilisation_heat_sources: - - geothermal + - geothermal heat_pump_sources: urban central: - air diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 1aaaadab3..87dd501cc 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -362,6 +362,7 @@ rule build_cop_profiles: script: "../scripts/build_cop_profiles/run.py" + rule build_direct_heat_source_utilisation_profiles: params: direct_utilisation_heat_sources=config_provider( @@ -376,21 +377,25 @@ rule build_direct_heat_source_utilisation_profiles: "central_heating_forward_temperature_profiles_base_s_{clusters}_{planning_horizons}.nc" ), output: - direct_heat_source_utilisation_profiles=resources("direct_heat_source_utilisation_profiles_base_s_{clusters}_{planning_horizons}.nc"), + direct_heat_source_utilisation_profiles=resources( + "direct_heat_source_utilisation_profiles_base_s_{clusters}_{planning_horizons}.nc" + ), resources: mem_mb=20000, log: - logs("build_direct_heat_source_utilisation_profiles_s_{clusters}_{planning_horizons}.log"), + logs( + "build_direct_heat_source_utilisation_profiles_s_{clusters}_{planning_horizons}.log" + ), benchmark: - benchmarks("build_direct_heat_source_utilisation_profiles/s_{clusters}_{planning_horizons}") + benchmarks( + "build_direct_heat_source_utilisation_profiles/s_{clusters}_{planning_horizons}" + ) conda: "../envs/environment.yaml" script: "../scripts/build_direct_heat_source_utilisation_profiles.py" - - def solar_thermal_cutout(wildcards): c = config_provider("solar_thermal", "cutout")(wildcards) if c == "default": diff --git a/scripts/build_cop_profiles/run.py b/scripts/build_cop_profiles/run.py index da90d9185..4ce2b9026 100644 --- a/scripts/build_cop_profiles/run.py +++ b/scripts/build_cop_profiles/run.py @@ -124,9 +124,9 @@ def get_cop( in snakemake.params.fraunhofer_heat_utilisation_potentials.keys() ): source_inlet_temperature_celsius = ( - snakemake.params.fraunhofer_heat_utilisation_potentials[heat_source][ - "constant_temperature_celsius" - ] + snakemake.params.fraunhofer_heat_utilisation_potentials[ + heat_source + ]["constant_temperature_celsius"] ) else: raise ValueError( diff --git a/scripts/build_direct_heat_source_utilisation_profiles.py b/scripts/build_direct_heat_source_utilisation_profiles.py index 42edc8d9c..d53cd7af6 100644 --- a/scripts/build_direct_heat_source_utilisation_profiles.py +++ b/scripts/build_direct_heat_source_utilisation_profiles.py @@ -7,14 +7,12 @@ """ import sys +from typing import List import geopandas as gpd import numpy as np import pandas as pd import xarray as xr -from typing import List - - from _helpers import set_scenario_config @@ -65,9 +63,7 @@ def get_profile( get_profile( source_temperature=get_source_temperature(heat_source_key), forward_temperature=central_heating_forward_temperature, - ).assign_coords( - heat_source=heat_source_key - ) + ).assign_coords(heat_source=heat_source_key) for heat_source_key in direct_utilisation_heat_sources ], dim="heat_source", diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index ac84d5fb7..382862d38 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2030,7 +2030,12 @@ def build_heat_demand(n): return heat_demand -def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray, direct_heat_source_utilisation_profile: xr.DataArray): +def add_heat( + n: pypsa.Network, + costs: pd.DataFrame, + cop: xr.DataArray, + direct_heat_source_utilisation_profile: xr.DataArray, +): """ Add heat sector to the network. @@ -2225,7 +2230,7 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray, direct_he bus1=nodes + f" {heat_system} heat", efficiency=efficiency, carrier=f"{heat_system} {heat_source} direct utilisation", - p_nom_extendable=True + p_nom_extendable=True, ) else: n.madd( From ec729fcefad8b05df9781a2bfbcb696d74f19553 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 11 Nov 2024 14:13:18 +0100 Subject: [PATCH 19/41] feat: add costs for geothermal heat source --- config/config.default.yaml | 11 +++---- scripts/definitions/heat_system.py | 19 ++++++++++++ scripts/prepare_sector_network.py | 46 ++++++++++++++++++------------ 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 911b14c22..c1a94ba6c 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -481,9 +481,10 @@ sector: heat_loss: 0.0 fraunhofer_heat_utilisation_potentials: geothermal: - key: hydrothermal_65 # activate for 85C hydrothermal # key: hydrothermal_85 + # constant_temperature_celsius: 85 + key: hydrothermal_65 constant_temperature_celsius: 65 column_name: Energy_TWh unit: TWh @@ -1235,11 +1236,11 @@ plotting: services urban decentral air heat pump: '#5af95d' services rural air heat pump: '#5af95d' urban central air heat pump: '#6cfb6b' - urban central geothermal heat pump: '#48f74f' - geothermal heat pump: '#48f74f' - geothermal direct utilisation: '#2fb541' + urban central geothermal heat pump: '#4f2144' + geothermal heat pump: '#4f2144' + geothermal heat direct utilisation: '#ba91b1' ground heat pump: '#2fb537' - residential rural ground heat pump: '#48f74f' + residential rural ground heat pump: '#4f2144' residential rural air heat pump: '#48f74f' services rural ground heat pump: '#5af95d' Ambient: '#98eb9d' diff --git a/scripts/definitions/heat_system.py b/scripts/definitions/heat_system.py index 2806f6bf4..6e18af825 100644 --- a/scripts/definitions/heat_system.py +++ b/scripts/definitions/heat_system.py @@ -226,6 +226,25 @@ def heat_pump_costs_name(self, heat_source: str) -> str: """ return f"{self.central_or_decentral} {heat_source}-sourced heat pump" + def heat_source_costs_name(self, heat_source: str) -> str: + """ + Generates the name for direct source utilisation costs based on the heat source and + system. + Used to retrieve data from `technology-data `. + + Parameters + ---------- + heat_source : str + The heat source. + + Returns + ------- + str + The name for the technology-data costs. + """ + return f"{self.central_or_decentral} {heat_source} heat source" + + @property def resistive_heater_costs_name(self) -> str: """ diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 382862d38..c9af9c794 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2152,8 +2152,8 @@ def add_heat( for heat_source in snakemake.params.heat_pump_sources[ heat_system.system_type.value ]: - costs_name = heat_system.heat_pump_costs_name(heat_source) - efficiency = ( + costs_name_heat_pump = heat_system.heat_pump_costs_name(heat_source) + cop_heat_pump = ( cop.sel( heat_system=heat_system.system_type.value, heat_source=heat_source, @@ -2162,7 +2162,7 @@ def add_heat( .to_pandas() .reindex(index=n.snapshots) if options["time_dep_hp_cop"] - else costs.at[costs_name, "efficiency"] + else costs.at[costs_name_heat_pump, "efficiency"] ) if heat_source in snakemake.params.fraunhofer_heat_sources: @@ -2182,6 +2182,13 @@ def add_heat( carrier=heat_carrier, ) + costs_name_heat_source = heat_system.heat_source_costs_name(heat_source) + if heat_source in snakemake.params.direct_utilisation_heat_sources: + capital_cost = costs.at[heat_system.heat_source_costs_name(heat_source), "fixed"] * overdim_factor + lifetime = costs.at[heat_system.heat_source_costs_name(heat_source), "lifetime"] + else: + capital_cost = 0.0 + lifetime = np.inf n.madd( "Generator", nodes, @@ -2189,7 +2196,8 @@ def add_heat( bus=nodes + f" {heat_carrier}", carrier=heat_carrier, p_nom_extendable=True, - capital_cost=0.0, # TODO: update later to underground part + capital_cost=capital_cost, + lifetime=lifetime, p_nom_max=p_max_source, ) @@ -2202,18 +2210,18 @@ def add_heat( bus1=nodes + f" {heat_carrier}", bus2=nodes + f" {heat_system} heat", carrier=f"{heat_system} {heat_source} heat pump", - efficiency=-(efficiency - 1), - efficiency2=efficiency, - capital_cost=costs.at[costs_name, "efficiency"] - * costs.at[costs_name, "fixed"] - * overdim_factor, # TODO: update later to heat pump part + efficiency=-(cop_heat_pump - 1), + efficiency2=cop_heat_pump, + capital_cost=costs.at[costs_name_heat_pump, "efficiency"] + * costs.at[costs_name_heat_pump, "fixed"] + * overdim_factor, p_nom_extendable=True, - lifetime=costs.at[costs_name, "lifetime"], + lifetime=costs.at[costs_name_heat_pump, "lifetime"], ) if heat_source in snakemake.params.direct_utilisation_heat_sources: - # todo: add efficiency - efficiency = ( + # 1 if source temperature exceeds forward temperature, 0 otherwise: + efficiency_direct_utilisation = ( direct_heat_source_utilisation_profile.sel( heat_source=heat_source, name=nodes, @@ -2225,11 +2233,11 @@ def add_heat( n.madd( "Link", nodes, - suffix=f" {heat_system} {heat_source} direct utilisation", + suffix=f" {heat_system} {heat_source} heat direct utilisation", bus0=nodes + f" {heat_carrier}", bus1=nodes + f" {heat_system} heat", - efficiency=efficiency, - carrier=f"{heat_system} {heat_source} direct utilisation", + efficiency=efficiency_direct_utilisation, + carrier=f"{heat_system} {heat_source} heat direct utilisation", p_nom_extendable=True, ) else: @@ -2240,12 +2248,12 @@ def add_heat( bus0=nodes, bus1=nodes + f" {heat_system} heat", carrier=f"{heat_system} {heat_source} heat pump", - efficiency=efficiency, - capital_cost=costs.at[costs_name, "efficiency"] - * costs.at[costs_name, "fixed"] + efficiency=cop_heat_pump, + capital_cost=costs.at[costs_name_heat_pump, "efficiency"] + * costs.at[costs_name_heat_pump, "fixed"] * overdim_factor, p_nom_extendable=True, - lifetime=costs.at[costs_name, "lifetime"], + lifetime=costs.at[costs_name_heat_pump, "lifetime"], ) if options["tes"]: From 028c6d78d04aefbb477f9349eccd9e88c132d74b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:28:20 +0000 Subject: [PATCH 20/41] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/definitions/heat_system.py | 1 - scripts/prepare_sector_network.py | 15 +++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/definitions/heat_system.py b/scripts/definitions/heat_system.py index 6e18af825..127a991b6 100644 --- a/scripts/definitions/heat_system.py +++ b/scripts/definitions/heat_system.py @@ -244,7 +244,6 @@ def heat_source_costs_name(self, heat_source: str) -> str: """ return f"{self.central_or_decentral} {heat_source} heat source" - @property def resistive_heater_costs_name(self) -> str: """ diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index c9af9c794..67a3bf6a6 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2184,8 +2184,15 @@ def add_heat( costs_name_heat_source = heat_system.heat_source_costs_name(heat_source) if heat_source in snakemake.params.direct_utilisation_heat_sources: - capital_cost = costs.at[heat_system.heat_source_costs_name(heat_source), "fixed"] * overdim_factor - lifetime = costs.at[heat_system.heat_source_costs_name(heat_source), "lifetime"] + capital_cost = ( + costs.at[ + heat_system.heat_source_costs_name(heat_source), "fixed" + ] + * overdim_factor + ) + lifetime = costs.at[ + heat_system.heat_source_costs_name(heat_source), "lifetime" + ] else: capital_cost = 0.0 lifetime = np.inf @@ -2196,7 +2203,7 @@ def add_heat( bus=nodes + f" {heat_carrier}", carrier=heat_carrier, p_nom_extendable=True, - capital_cost=capital_cost, + capital_cost=capital_cost, lifetime=lifetime, p_nom_max=p_max_source, ) @@ -2214,7 +2221,7 @@ def add_heat( efficiency2=cop_heat_pump, capital_cost=costs.at[costs_name_heat_pump, "efficiency"] * costs.at[costs_name_heat_pump, "fixed"] - * overdim_factor, + * overdim_factor, p_nom_extendable=True, lifetime=costs.at[costs_name_heat_pump, "lifetime"], ) From f2db6f90e4340c0ea409763e52c66393db44680a Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Mon, 11 Nov 2024 14:37:13 +0100 Subject: [PATCH 21/41] style: pre-commit, add dev to gitignore --- .gitignore | 2 ++ scripts/definitions/heat_system.py | 1 - scripts/prepare_sector_network.py | 15 +++++++++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 27a2fb182..83c1397e6 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,5 @@ d1gam3xoknrgr2.cloudfront.net/ merger-todos.md *.html +# private dev folder +dev/* diff --git a/scripts/definitions/heat_system.py b/scripts/definitions/heat_system.py index 6e18af825..127a991b6 100644 --- a/scripts/definitions/heat_system.py +++ b/scripts/definitions/heat_system.py @@ -244,7 +244,6 @@ def heat_source_costs_name(self, heat_source: str) -> str: """ return f"{self.central_or_decentral} {heat_source} heat source" - @property def resistive_heater_costs_name(self) -> str: """ diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index c9af9c794..67a3bf6a6 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2184,8 +2184,15 @@ def add_heat( costs_name_heat_source = heat_system.heat_source_costs_name(heat_source) if heat_source in snakemake.params.direct_utilisation_heat_sources: - capital_cost = costs.at[heat_system.heat_source_costs_name(heat_source), "fixed"] * overdim_factor - lifetime = costs.at[heat_system.heat_source_costs_name(heat_source), "lifetime"] + capital_cost = ( + costs.at[ + heat_system.heat_source_costs_name(heat_source), "fixed" + ] + * overdim_factor + ) + lifetime = costs.at[ + heat_system.heat_source_costs_name(heat_source), "lifetime" + ] else: capital_cost = 0.0 lifetime = np.inf @@ -2196,7 +2203,7 @@ def add_heat( bus=nodes + f" {heat_carrier}", carrier=heat_carrier, p_nom_extendable=True, - capital_cost=capital_cost, + capital_cost=capital_cost, lifetime=lifetime, p_nom_max=p_max_source, ) @@ -2214,7 +2221,7 @@ def add_heat( efficiency2=cop_heat_pump, capital_cost=costs.at[costs_name_heat_pump, "efficiency"] * costs.at[costs_name_heat_pump, "fixed"] - * overdim_factor, + * overdim_factor, p_nom_extendable=True, lifetime=costs.at[costs_name_heat_pump, "lifetime"], ) From a7d4b0643513833facc375b6cc338a6f7f2ab917 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Thu, 14 Nov 2024 14:58:58 +0100 Subject: [PATCH 22/41] feat: replace access to config with unpack where possible Note: unpack() doesn't work in snakemake output, so geothermal is specified explicitly as only heat source! --- rules/build_sector.smk | 67 +++++++++++-------- rules/retrieve.smk | 55 ++++++++------- ...fer_heat_source_utilisation_potentials.py} | 21 +++--- 3 files changed, 80 insertions(+), 63 deletions(-) rename scripts/{retrieve_heat_source_utilisation_potentials.py => retrieve_fraunhofer_heat_source_utilisation_potentials.py} (66%) diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 87dd501cc..787f9fc41 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -284,33 +284,29 @@ rule build_central_heating_temperature_profiles: "../scripts/build_central_heating_temperature_profiles/run.py" +def input_heat_source_potentials(w): + + return { + heat_source_name: f"data/fraunhofer_heat_utilisation_potentials/{heat_source_name}.gpkg" + for heat_source_name in config_provider( + "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + )(w).keys() + if heat_source_name + in config_provider("sector", "heat_pump_sources", "urban central")(w) + } + + rule build_heat_source_potentials: params: fraunhofer_heat_sources=config_provider( "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" ), input: - # TODO: accessing `config` as a dictionary might not work with scenario management!! - **{ - heat_source: f"data/fraunhofer_heat_utilisation_potentials/{heat_source}.gpkg" - for heat_source in config["sector"]["district_heating"][ - "fraunhofer_heat_utilisation_potentials" - ].keys() - if heat_source in config["sector"]["heat_pump_sources"]["urban central"] - }, + unpack(input_heat_source_potentials), regions_onshore=resources("regions_onshore_base_s_{clusters}.geojson"), - # dict comprehension gives those Fraunhofer utilisation potentials that are present in sector/district_heating/heat_pump_sources/urban central as input output: - # TODO: accessing `config` as a dictionary might not work with scenario management!! - **{ - heat_source: resources( - "heat_source_potential_" + heat_source + "_base_s_{clusters}.csv" - ) - for heat_source in config["sector"]["district_heating"][ - "fraunhofer_heat_utilisation_potentials" - ].keys() - if heat_source in config["sector"]["heat_pump_sources"]["urban central"] - }, + # TODO: this is a workaround since unpacked functions don't work in output + geothermal=resources("heat_source_potential_geothermal_base_s_{clusters}.csv"), resources: mem_mb=2000, log: @@ -1079,6 +1075,20 @@ rule build_egs_potentials: "../scripts/build_egs_potentials.py" +def input_heat_source_potentials(w): + + return { + heat_source_name: resources( + "heat_source_potential_" + heat_source_name + "_base_s_{clusters}.csv" + ) + for heat_source_name in config_provider( + "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + )(w).keys() + if heat_source_name + in config_provider("sector", "heat_pump_sources", "urban central")(w) + } + + rule prepare_sector_network: params: time_resolution=config_provider("clustering", "temporal", "resolution_sector"), @@ -1111,17 +1121,18 @@ rule prepare_sector_network: ), input: unpack(input_profile_offwind), + unpack(input_heat_source_potentials), **rules.cluster_gas_network.output, **rules.build_gas_input_locations.output, - **{ - heat_source: resources( - "heat_source_potential_" + heat_source + "_base_s_{clusters}.csv" - ) - for heat_source in config["sector"]["district_heating"][ - "fraunhofer_heat_utilisation_potentials" - ].keys() - if heat_source in config["sector"]["heat_pump_sources"]["urban central"] - }, + # **{ + # heat_source: resources( + # "heat_source_potential_" + heat_source + "_base_s_{clusters}.csv" + # ) + # for heat_source in config["sector"]["district_heating"][ + # "fraunhofer_heat_utilisation_potentials" + # ].keys() + # if heat_source in config["sector"]["heat_pump_sources"]["urban central"] + # }, snapshot_weightings=resources( "snapshot_weightings_base_s_{clusters}_elec_l{ll}_{opts}_{sector_opts}.csv" ), diff --git a/rules/retrieve.smk b/rules/retrieve.smk index dba7ef505..cf4e305c2 100755 --- a/rules/retrieve.smk +++ b/rules/retrieve.smk @@ -634,35 +634,42 @@ if config["enable"]["retrieve"] and ( if config["enable"]["retrieve"]: - rule retrieve_heat_source_utilisation_potentials: + def input_heat_source_potentials(w): + + return { + heat_source_name: storage( + f"https://fordatis.fraunhofer.de/bitstream/fordatis/341.3/10/{heat_source_features["key"]}.gpkg", + keep_local=True, + ) + for heat_source_name, heat_source_features in config_provider( + "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + )(w).items() + if heat_source_name + in config_provider("sector", "heat_pump_sources", "urban central")(w) + } + + def output_heat_source_potentials(w): + + return { + heat_source_name: f"data/fraunhofer_heat_utilisation_potentials/{heat_source_name}.gpkg" + for heat_source_name in config_provider( + "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + )(w).keys() + if heat_source_name + in config_provider("sector", "heat_pump_sources", "urban central")(w) + } + + rule retrieve_fraunhofer_heat_source_utilisation_potentials: input: - # TODO: accessing `config` as a dictionary might not work with scenario management!! - **{ - heat_source_name: storage( - f"https://fordatis.fraunhofer.de/bitstream/fordatis/341.3/10/{heat_source_features["key"]}.gpkg", - keep_local=True, - ) - for heat_source_name, heat_source_features in config["sector"][ - "district_heating" - ]["fraunhofer_heat_utilisation_potentials"].items() - if heat_source_name - in config["sector"]["heat_pump_sources"]["urban central"] - }, - output: - # TODO: accessing `config` as a dictionary might not work with scenario management!! - **{ - heat_source_name: f"data/fraunhofer_heat_utilisation_potentials/{heat_source_name}.gpkg" - for heat_source_name, heat_source_features in config["sector"][ - "district_heating" - ]["fraunhofer_heat_utilisation_potentials"].items() - if heat_source_name - in config["sector"]["heat_pump_sources"]["urban central"] - }, + unpack(input_heat_source_potentials), log: "logs/retrieve_heat_source_utilisation_potentials.log", resources: mem_mb=500, - retries: 2 + output: + geothermal="data/fraunhofer_heat_utilisation_potentials/geothermal.gpkg", run: for key in input.keys(): + output_dir = Path(output[key]).parent + output_dir.mkdir(parents=True, exist_ok=True) move(input[key], output[key]) diff --git a/scripts/retrieve_heat_source_utilisation_potentials.py b/scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py similarity index 66% rename from scripts/retrieve_heat_source_utilisation_potentials.py rename to scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py index 4f07f1458..f27f1478f 100644 --- a/scripts/retrieve_heat_source_utilisation_potentials.py +++ b/scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py @@ -8,6 +8,7 @@ import logging from pathlib import Path +import requests from _helpers import configure_logging, progress_retrieve, set_scenario_config logger = logging.getLogger(__name__) @@ -22,21 +23,19 @@ rootpath = "." configure_logging(snakemake) set_scenario_config(snakemake) + breakpoint() # license: https://creativecommons.org/licenses/by/4.0/ - for ( - heat_source, - heat_source_features, - ) in snakemake.params.heat_source_utilisation_potentials.items(): - url = snakemake.input[heat_source] - filepath = Path(snakemake.output[heat_source]) - - to_fn = Path(rootpath) / filepath + for heat_source_name, url in snakemake.input.items(): + # download the data in url + filepath = Path( + f"data/fraunhofer_heat_utilisation_potentials/{heat_source_name}.gpkg" + ) logger.info( - f"Downloading heat source utilisation potential data for {heat_source} from '{url}'." + f"Downloading heat source utilisation potential data for {heat_source_name} from '{url}'." ) disable_progress = snakemake.config["run"].get("disable_progressbar", False) - progress_retrieve(url, to_fn, disable=disable_progress) + progress_retrieve(url, filepath, disable=disable_progress) - logger.info(f"Data available at at {to_fn}") + logger.info(f"Data available at at {filepath}") From 6d064bf5865fc27252fcdc340d43bd7585d0e4a8 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Fri, 15 Nov 2024 15:15:00 +0100 Subject: [PATCH 23/41] feat: replace dict access to config in snakemake --- rules/build_sector.smk | 31 +++---------- rules/retrieve.smk | 43 ++++--------------- scripts/build_heat_source_potentials/run.py | 40 ++++++++--------- ...ofer_heat_source_utilisation_potentials.py | 29 +++++++------ 4 files changed, 49 insertions(+), 94 deletions(-) diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 787f9fc41..1c84712fe 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -284,35 +284,23 @@ rule build_central_heating_temperature_profiles: "../scripts/build_central_heating_temperature_profiles/run.py" -def input_heat_source_potentials(w): - - return { - heat_source_name: f"data/fraunhofer_heat_utilisation_potentials/{heat_source_name}.gpkg" - for heat_source_name in config_provider( - "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" - )(w).keys() - if heat_source_name - in config_provider("sector", "heat_pump_sources", "urban central")(w) - } - - rule build_heat_source_potentials: params: fraunhofer_heat_sources=config_provider( "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" ), + heat_source="{heat_source}", input: - unpack(input_heat_source_potentials), + utilisation_potential="data/fraunhofer_heat_source_utilisation_potentials/{heat_source}.gpkg", regions_onshore=resources("regions_onshore_base_s_{clusters}.geojson"), output: - # TODO: this is a workaround since unpacked functions don't work in output - geothermal=resources("heat_source_potential_geothermal_base_s_{clusters}.csv"), + resources("heat_source_potential_{heat_source}_base_s_{clusters}.csv"), resources: mem_mb=2000, log: - logs("build_heat_source_potentials_s_{clusters}.log"), + logs("build_heat_source_potentials_{heat_source}_s_{clusters}.log"), benchmark: - benchmarks("build_heat_source_potentials/s_{clusters}") + benchmarks("build_heat_source_potentials/{heat_source}_s_{clusters}") conda: "../envs/environment.yaml" script: @@ -1124,15 +1112,6 @@ rule prepare_sector_network: unpack(input_heat_source_potentials), **rules.cluster_gas_network.output, **rules.build_gas_input_locations.output, - # **{ - # heat_source: resources( - # "heat_source_potential_" + heat_source + "_base_s_{clusters}.csv" - # ) - # for heat_source in config["sector"]["district_heating"][ - # "fraunhofer_heat_utilisation_potentials" - # ].keys() - # if heat_source in config["sector"]["heat_pump_sources"]["urban central"] - # }, snapshot_weightings=resources( "snapshot_weightings_base_s_{clusters}_elec_l{ll}_{opts}_{sector_opts}.csv" ), diff --git a/rules/retrieve.smk b/rules/retrieve.smk index cf4e305c2..ca88ec972 100755 --- a/rules/retrieve.smk +++ b/rules/retrieve.smk @@ -634,42 +634,17 @@ if config["enable"]["retrieve"] and ( if config["enable"]["retrieve"]: - def input_heat_source_potentials(w): - - return { - heat_source_name: storage( - f"https://fordatis.fraunhofer.de/bitstream/fordatis/341.3/10/{heat_source_features["key"]}.gpkg", - keep_local=True, - ) - for heat_source_name, heat_source_features in config_provider( - "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" - )(w).items() - if heat_source_name - in config_provider("sector", "heat_pump_sources", "urban central")(w) - } - - def output_heat_source_potentials(w): - - return { - heat_source_name: f"data/fraunhofer_heat_utilisation_potentials/{heat_source_name}.gpkg" - for heat_source_name in config_provider( - "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" - )(w).keys() - if heat_source_name - in config_provider("sector", "heat_pump_sources", "urban central")(w) - } - rule retrieve_fraunhofer_heat_source_utilisation_potentials: - input: - unpack(input_heat_source_potentials), + params: + heat_source="{heat_source}", + fraunhofer_heat_utilisation_potentials=config_provider( + "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + ), log: - "logs/retrieve_heat_source_utilisation_potentials.log", + "logs/retrieve_fraunhofer_heat_source_potentials_{heat_source}.log", resources: mem_mb=500, output: - geothermal="data/fraunhofer_heat_utilisation_potentials/geothermal.gpkg", - run: - for key in input.keys(): - output_dir = Path(output[key]).parent - output_dir.mkdir(parents=True, exist_ok=True) - move(input[key], output[key]) + "data/fraunhofer_heat_source_utilisation_potentials/{heat_source}.gpkg", + script: + "../scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py" diff --git a/scripts/build_heat_source_potentials/run.py b/scripts/build_heat_source_potentials/run.py index ca865121a..0da18cd74 100644 --- a/scripts/build_heat_source_potentials/run.py +++ b/scripts/build_heat_source_potentials/run.py @@ -43,25 +43,25 @@ def get_unit_conversion_factor( set_scenario_config(snakemake) regions_onshore = gpd.read_file(snakemake.input.regions_onshore) + heat_source_utilisation_potential = gpd.read_file( + snakemake.input.utilisation_potential + ) - heat_source_technical_potential = {} - for ( - heat_source, - heat_source_features, - ) in snakemake.params.fraunhofer_heat_sources.items(): - - heat_source_utilisation_potential = gpd.read_file(snakemake.input[heat_source]) - - heat_source_technical_potential[heat_source] = OnshoreRegionData( - onshore_regions=regions_onshore, - data=heat_source_utilisation_potential, - column_name=heat_source_features["column_name"], - scaling_factor=get_unit_conversion_factor( - input_unit=heat_source_features["unit"], output_unit="MWh" - ) - / heat_source_features["full_load_hours"], - ).data_in_regions_scaled - - heat_source_technical_potential[heat_source].to_csv( - snakemake.output[heat_source] + heat_source_technical_potential = OnshoreRegionData( + onshore_regions=regions_onshore, + data=heat_source_utilisation_potential, + column_name=snakemake.params.fraunhofer_heat_sources[ + snakemake.params.heat_source + ]["column_name"], + scaling_factor=get_unit_conversion_factor( + input_unit=snakemake.params.fraunhofer_heat_sources[ + snakemake.params.heat_source + ]["unit"], + output_unit="MWh", ) + / snakemake.params.fraunhofer_heat_sources[snakemake.params.heat_source][ + "full_load_hours" + ], + ).data_in_regions_scaled + + heat_source_technical_potential.to_csv(snakemake.output[0]) diff --git a/scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py b/scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py index f27f1478f..450988bf8 100644 --- a/scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py +++ b/scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py @@ -23,19 +23,20 @@ rootpath = "." configure_logging(snakemake) set_scenario_config(snakemake) - breakpoint() # license: https://creativecommons.org/licenses/by/4.0/ - for heat_source_name, url in snakemake.input.items(): - # download the data in url - filepath = Path( - f"data/fraunhofer_heat_utilisation_potentials/{heat_source_name}.gpkg" - ) - - logger.info( - f"Downloading heat source utilisation potential data for {heat_source_name} from '{url}'." - ) - disable_progress = snakemake.config["run"].get("disable_progressbar", False) - progress_retrieve(url, filepath, disable=disable_progress) - - logger.info(f"Data available at at {filepath}") + # download the data in url + heat_source = snakemake.params["heat_source"] + filepath = Path(snakemake.output[0]) + if not filepath.parent.exists(): + filepath.parent.mkdir(parents=True) + + url = f"https://fordatis.fraunhofer.de/bitstream/fordatis/341.3/10/{snakemake.params.fraunhofer_heat_utilisation_potentials[heat_source]['key']}.gpkg" + + logger.info( + f"Downloading heat source utilisation potential data for {heat_source} from '{url}'." + ) + disable_progress = snakemake.config["run"].get("disable_progressbar", False) + progress_retrieve(url, filepath, disable=disable_progress) + + logger.info(f"Data available at at {filepath}") From df80e55811298bf707e5e4fd38a9d6a294428482 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Thu, 28 Nov 2024 15:31:54 +0100 Subject: [PATCH 24/41] doc: add docstrings, clean up code --- scripts/build_cop_profiles/run.py | 2 +- ...direct_heat_source_utilisation_profiles.py | 62 ++++++++++++++++--- .../OnshoreRegionData.py | 48 +++++++++++--- ...ofer_heat_source_utilisation_potentials.py | 17 ++++- 4 files changed, 113 insertions(+), 16 deletions(-) diff --git a/scripts/build_cop_profiles/run.py b/scripts/build_cop_profiles/run.py index 4ce2b9026..a29bf260d 100644 --- a/scripts/build_cop_profiles/run.py +++ b/scripts/build_cop_profiles/run.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: MIT """ Approximate heat pump coefficient-of-performance (COP) profiles for different -heat sources and systems. +heat sources and systems. Returns zero where source temperature higher than sink temperature. For central heating, this is based on Jensen et al. (2018) (c.f. `CentralHeatingCopApproximator `_) and for decentral heating, the approximation is based on Staffell et al. (2012) (c.f. `DecentralHeatingCopApproximator `_). diff --git a/scripts/build_direct_heat_source_utilisation_profiles.py b/scripts/build_direct_heat_source_utilisation_profiles.py index d53cd7af6..7db06e56f 100644 --- a/scripts/build_direct_heat_source_utilisation_profiles.py +++ b/scripts/build_direct_heat_source_utilisation_profiles.py @@ -3,20 +3,53 @@ # # SPDX-License-Identifier: MIT """ - +Build availability profiles for direct heat source utilisation (1 in regions and time steps where heat source can be utilised, 0 otherwise). +When direct utilisation is possible, heat pump COPs are set to zero (c.f. `build_cop_profiles`). + +Relevant Settings +----------------- + +.. code:: yaml + sector: + district_heating: + fraunhofer_heat_utilisation_potentials: + direct_utilisation_heat_sources: + snapshots: + +Inputs +------ +- `resources//central_heating_forward_temperatures_base_s_{clusters}_{planning_horizons}.nc`: Central heating forward temperature profiles + +Outputs +------- +- `resources//direct_heat_source_utilisation_profiles_base_s_{clusters}_{planning_horizons}.nc`: Direct heat source utilisation profiles """ -import sys from typing import List -import geopandas as gpd -import numpy as np -import pandas as pd import xarray as xr from _helpers import set_scenario_config def get_source_temperature(heat_source_key: str): + """ + Get the constant temperature of a heat source. + + Args: + ----- + heat_source_key: str + The key (name) of the heat source. + + Returns: + -------- + float + The constant temperature of the heat source in degrees Celsius. + + Raises: + ------- + ValueError + If the heat source is unknown (not in `config`). + """ if ( heat_source_key @@ -34,8 +67,23 @@ def get_source_temperature(heat_source_key: str): def get_profile( source_temperature: float | xr.DataArray, forward_temperature: xr.DataArray -): - +) -> xr.DataArray | float: + """ + Get the direct heat source utilisation profile. + + Args: + ----- + source_temperature: float | xr.DataArray + The constant temperature of the heat source in degrees Celsius. If `xarray`, indexed by `time` and `region`. If a float, it is broadcasted to the shape of `forward_temperature`. + forward_temperature: xr.DataArray + The central heating forward temperature profiles. If `xarray`, indexed by `time` and `region`. If a float, it is broadcasted to the shape of `return_temperature`. + + Returns: + -------- + xr.DataArray | float + The direct heat source utilisation profile. + + """ return xr.where(source_temperature >= forward_temperature, 1.0, 0.0) diff --git a/scripts/build_heat_source_potentials/OnshoreRegionData.py b/scripts/build_heat_source_potentials/OnshoreRegionData.py index f6dec2620..16e766bff 100755 --- a/scripts/build_heat_source_potentials/OnshoreRegionData.py +++ b/scripts/build_heat_source_potentials/OnshoreRegionData.py @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: MIT """ + """ from typing import List @@ -11,6 +12,18 @@ class OnshoreRegionData: + """ + This class is used to map heat potentials to onshore regions. + + Attributes + ---------- + onshore_regions : gpd.GeoDataFrame + GeoDataFrame containing the onshore regions + data : gpd.GeoDataFrame + GeoDataFrame containing the heat potentials + scaling_factor : float + Scaling factor for the heat potentials + """ def __init__( self, @@ -19,16 +32,36 @@ def __init__( column_name: str, scaling_factor: float = 1.0, ) -> None: + """ + Parameters + ---------- + onshore_regions : gpd.GeoDataFrame + GeoDataFrame containing the onshore regions + data : gpd.GeoDataFrame + GeoDataFrame containing the heat potentials + column_name : str + Column name of the heat potential data in `data` + scaling_factor : float, optional + Scaling factor for the heat potentials, by default 1.0 + """ self.onshore_regions = onshore_regions - self.column_name = column_name self.scaling_factor = scaling_factor self.data = data.to_crs(onshore_regions.crs) + self._column_name = column_name self._mapped = False @property def data_in_regions_scaled(self) -> gpd.GeoDataFrame: + """ + Scale the heat potentials and map them to the onshore regions. + + Returns + ------- + gpd.GeoDataFrame + GeoDataFrame containing the scaled heat potentials in the onshore regions + """ if self._mapped: return self._scaled_data_in_regions else: @@ -38,25 +71,26 @@ def data_in_regions_scaled(self) -> gpd.GeoDataFrame: def _map_to_onshore_regions(self): """ - This function maps the heat potentials to the onshore regions - - + Map the heat potentials to the onshore regions """ data_in_regions = gpd.sjoin(self.data, self.onshore_regions, how="right") # Initialize an empty list to store the merged GeoDataFrames ret_val = self.onshore_regions.copy() - ret_val[self.column_name] = ( - data_in_regions.groupby("name")[self.column_name] + ret_val[self._column_name] = ( + data_in_regions.groupby("name")[self._column_name] .sum() .reset_index(drop=True) ) ret_val = ret_val.set_index("name", drop=True).rename_axis("name")[ - self.column_name + self._column_name ] return ret_val @property def _scaled_data_in_regions(self): + """ + Scale the heat potentials in the onshore regions + """ return self._data_in_regions * self.scaling_factor diff --git a/scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py b/scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py index 450988bf8..c044d8417 100644 --- a/scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py +++ b/scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py @@ -3,12 +3,27 @@ # # SPDX-License-Identifier: MIT """ +Retrieve heat source utilisation potentials from Fraunhofer Fordatis. + +Source +------ +Manz et al. 2024: "Spatial analysis of renewable and excess heat potentials for climate-neutral district heating in Europe", Renewable Energy, vol. 224, no. 120111, https://doi.org/10.1016/j.renene.2024.120111 + +Relevant Settings +----------------- +.. code:: yaml + sector: + district_heating: + fraunhofer_heat_utilisation_potentials: + +Outputs +------ +- `resources//heat_source_utilisation_potentials/.gpkg` """ import logging from pathlib import Path -import requests from _helpers import configure_logging, progress_retrieve, set_scenario_config logger = logging.getLogger(__name__) From 0447adc2cfc28a784c083aa0cc0ff637535c5e19 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Thu, 28 Nov 2024 15:48:58 +0100 Subject: [PATCH 25/41] docs: update release notes --- doc/release_notes.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index daefe6854..e90de868c 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -11,6 +11,8 @@ Release Notes Upcoming Release ================ +* Feature: Introduce geothermal district heating (direct utilisation and heat pumps). Potentials are based on Manz et al. 2024: Spatial analysis of renewable and excess heat potentials for climate-neutral district heating in Europe + * Feature: Allow CHPs to use different fuel sources such as gas, oil, coal, and methanol. Note that the cost assumptions are based on a gas CHP (except for solid biomass-fired CHP). * Improve `sanitize_carrier`` function by filling in colors of missing carriers with colors mapped after using the function `rename_techs`. From f866887a4ecd23918ee62da1e28653d60d36f213 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Thu, 28 Nov 2024 15:57:03 +0100 Subject: [PATCH 26/41] doc: update configtables --- doc/configtables/sector.csv | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/configtables/sector.csv b/doc/configtables/sector.csv index 13246b27f..5c54794b7 100644 --- a/doc/configtables/sector.csv +++ b/doc/configtables/sector.csv @@ -23,6 +23,13 @@ district_heating,--,,`prepare_sector_network.py Date: Thu, 28 Nov 2024 16:01:00 +0100 Subject: [PATCH 27/41] doc: update data sourcs --- doc/data-retrieval.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/data-retrieval.rst b/doc/data-retrieval.rst index 7509c8b57..aee47464c 100644 --- a/doc/data-retrieval.rst +++ b/doc/data-retrieval.rst @@ -9,12 +9,21 @@ Specific retrieval rules Data in this section is retrieved and extracted in rules specified in ``rules/retrieve.smk``. + +``data/fraunhofer_heat_source_utilisation_potentials`` + +- **Source:** Fraunhofer Fordatis +- **Link:** https://gisco-services.ec.europa.eu/distribution/v2/nuts/download/https://fordatis.fraunhofer.de/handle/fordatis/341.3?mode=simple +- **License:** `custom `__ +- **Description:** Europe's NUTS administrative regions. + + ``data/nuts`` - **Source:** GISCO - **Link:** https://gisco-services.ec.europa.eu/distribution/v2/nuts/download/ -- **License:** `custom `__ -- **Description:** Europe's NUTS administrative regions. +- **License:** `CC BY 4.0 `__ +- **Description:** Utilisation potentials for different heat sources across Europe, based on Manz et al. 2024. ``data/ENSPRESO_BIOMASS.xlsx`` From 3bc61f227d8c7bb697d879a3a2df63fd7cbe6577 Mon Sep 17 00:00:00 2001 From: Amos Schledorn Date: Thu, 28 Nov 2024 16:24:32 +0100 Subject: [PATCH 28/41] update docs --- ...reRegionData.py => onshore_region_data.py} | 2 +- scripts/build_heat_source_potentials/run.py | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) rename scripts/build_heat_source_potentials/{OnshoreRegionData.py => onshore_region_data.py} (97%) diff --git a/scripts/build_heat_source_potentials/OnshoreRegionData.py b/scripts/build_heat_source_potentials/onshore_region_data.py similarity index 97% rename from scripts/build_heat_source_potentials/OnshoreRegionData.py rename to scripts/build_heat_source_potentials/onshore_region_data.py index 16e766bff..1dfcbf42d 100755 --- a/scripts/build_heat_source_potentials/OnshoreRegionData.py +++ b/scripts/build_heat_source_potentials/onshore_region_data.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: MIT """ - +Helper class for matching heat source potentials to onshore regions. """ from typing import List diff --git a/scripts/build_heat_source_potentials/run.py b/scripts/build_heat_source_potentials/run.py index 0da18cd74..b8204ebc0 100644 --- a/scripts/build_heat_source_potentials/run.py +++ b/scripts/build_heat_source_potentials/run.py @@ -3,11 +3,35 @@ # # SPDX-License-Identifier: MIT """ +Build heat source potentials for a given heat source. + +This script maps and aggregates heat source potentials per heat source to `onshore_regions` using `OnshoreRegionData`. +It scales the heat source utilisation potentials to technical potentials by dividing the utilisation potentials by the full load hours of the heat source, also taking into account the energy unit set for the respective source in the config. + + +Relevant Settings +----------------- +.. code:: yaml + sector: + district_heating: + fraunhofer_heat_utilisation_potentials: + {heat_source} + + +Inputs +------ +- `resources//regions_onshore.geojson` +- `resources//heat_source_utilisation_potentials/.gpkg` + +Outputs +------- +- `resources//heat_source_technical_potential_{heat_source}_base_s_{clusters}.csv` """ import geopandas as gpd from _helpers import set_scenario_config -from OnshoreRegionData import OnshoreRegionData + +from scripts.build_heat_source_potentials.onshore_region_data import OnshoreRegionData def get_unit_conversion_factor( From 336786e8d20851779af3aac99679dbc72c632dad Mon Sep 17 00:00:00 2001 From: Amos Schledorn <60692940+amos-schledorn@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:24:43 +0100 Subject: [PATCH 29/41] Update config/config.default.yaml Co-authored-by: Lukas Trippe --- config/config.default.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 609018266..e2a68e70d 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -840,7 +840,7 @@ industry: # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#costs costs: year: 2030 - version: master + version: v0.10.0 social_discountrate: 0.02 fill_values: FOM: 0 From e1986da7c4eea2300ceccff0ae78bd3551400fd4 Mon Sep 17 00:00:00 2001 From: amos-schledorn Date: Mon, 2 Dec 2024 11:19:34 +0100 Subject: [PATCH 30/41] docs: fix data-retrieval docs --- doc/data-retrieval.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/data-retrieval.rst b/doc/data-retrieval.rst index aee47464c..3186157e7 100644 --- a/doc/data-retrieval.rst +++ b/doc/data-retrieval.rst @@ -13,17 +13,17 @@ Data in this section is retrieved and extracted in rules specified in ``rules/re ``data/fraunhofer_heat_source_utilisation_potentials`` - **Source:** Fraunhofer Fordatis -- **Link:** https://gisco-services.ec.europa.eu/distribution/v2/nuts/download/https://fordatis.fraunhofer.de/handle/fordatis/341.3?mode=simple -- **License:** `custom `__ -- **Description:** Europe's NUTS administrative regions. +- **Link:** https://fordatis.fraunhofer.de/handle/fordatis/341.3?mode=simple +- **License:** `CC BY 4.0 `__ +- **Description:** Utilisation potentials for different heat sources across Europe, based on Manz et al. 2024. ``data/nuts`` - **Source:** GISCO - **Link:** https://gisco-services.ec.europa.eu/distribution/v2/nuts/download/ -- **License:** `CC BY 4.0 `__ -- **Description:** Utilisation potentials for different heat sources across Europe, based on Manz et al. 2024. +- **License:** `custom `__ +- **Description:** Europe's NUTS administrative regions. ``data/ENSPRESO_BIOMASS.xlsx`` From 69cf11beade92d3b5233d2e00cfc3bdfea5b85a0 Mon Sep 17 00:00:00 2001 From: amos-schledorn Date: Mon, 2 Dec 2024 12:03:13 +0100 Subject: [PATCH 31/41] feat: turn off geothermal DH heat by default --- config/config.default.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index e2a68e70d..918457dbd 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -495,7 +495,6 @@ sector: heat_pump_sources: urban central: - air - - geothermal urban decentral: - air rural: From 2c755fd2bd68d9f6672e5b742bbeb1fb3f95b8b5 Mon Sep 17 00:00:00 2001 From: amos-schledorn Date: Mon, 2 Dec 2024 16:01:20 +0100 Subject: [PATCH 32/41] style: remove "fraunhofer_" in naming --- config/config.default.yaml | 2 +- rules/build_sector.smk | 20 +++++++++---------- rules/retrieve.smk | 12 +++++------ scripts/build_cop_profiles/run.py | 4 ++-- ...direct_heat_source_utilisation_profiles.py | 6 +++--- scripts/build_heat_source_potentials/run.py | 8 ++++---- scripts/prepare_sector_network.py | 2 +- ...eve_heat_source_utilisation_potentials.py} | 4 ++-- 8 files changed, 29 insertions(+), 29 deletions(-) rename scripts/{retrieve_fraunhofer_heat_source_utilisation_potentials.py => retrieve_heat_source_utilisation_potentials.py} (92%) diff --git a/config/config.default.yaml b/config/config.default.yaml index 918457dbd..15af59344 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -480,7 +480,7 @@ sector: heat_exchanger_pinch_point_temperature_difference: 5 #K isentropic_compressor_efficiency: 0.8 heat_loss: 0.0 - fraunhofer_heat_utilisation_potentials: + heat_utilisation_potentials: geothermal: # activate for 85C hydrothermal # key: hydrothermal_85 diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 1c84712fe..07a6dc6be 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -286,12 +286,12 @@ rule build_central_heating_temperature_profiles: rule build_heat_source_potentials: params: - fraunhofer_heat_sources=config_provider( - "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + heat_utilisation_potentials=config_provider( + "sector", "district_heating", "heat_utilisation_potentials" ), heat_source="{heat_source}", input: - utilisation_potential="data/fraunhofer_heat_source_utilisation_potentials/{heat_source}.gpkg", + utilisation_potential="data/heat_source_utilisation_potentials/{heat_source}.gpkg", regions_onshore=resources("regions_onshore_base_s_{clusters}.geojson"), output: resources("heat_source_potential_{heat_source}_base_s_{clusters}.csv"), @@ -319,8 +319,8 @@ rule build_cop_profiles: "sector", "district_heating", "heat_pump_cop_approximation" ), heat_pump_sources=config_provider("sector", "heat_pump_sources"), - fraunhofer_heat_utilisation_potentials=config_provider( - "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + heat_utilisation_potentials=config_provider( + "sector", "district_heating", "heat_utilisation_potentials" ), snapshots=config_provider("snapshots"), input: @@ -352,8 +352,8 @@ rule build_direct_heat_source_utilisation_profiles: direct_utilisation_heat_sources=config_provider( "sector", "district_heating", "direct_utilisation_heat_sources" ), - fraunhofer_heat_utilisation_potentials=config_provider( - "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + heat_utilisation_potentials=config_provider( + "sector", "district_heating", "heat_utilisation_potentials" ), snapshots=config_provider("snapshots"), input: @@ -1070,7 +1070,7 @@ def input_heat_source_potentials(w): "heat_source_potential_" + heat_source_name + "_base_s_{clusters}.csv" ) for heat_source_name in config_provider( - "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + "sector", "district_heating", "heat_utilisation_potentials" )(w).keys() if heat_source_name in config_provider("sector", "heat_pump_sources", "urban central")(w) @@ -1101,8 +1101,8 @@ rule prepare_sector_network: heat_pump_sources=config_provider("sector", "heat_pump_sources"), heat_systems=config_provider("sector", "heat_systems"), energy_totals_year=config_provider("energy", "energy_totals_year"), - fraunhofer_heat_sources=config_provider( - "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + heat_utilisation_potentials=config_provider( + "sector", "district_heating", "heat_utilisation_potentials" ), direct_utilisation_heat_sources=config_provider( "sector", "district_heating", "direct_utilisation_heat_sources" diff --git a/rules/retrieve.smk b/rules/retrieve.smk index 5ce78af64..9154386b1 100755 --- a/rules/retrieve.smk +++ b/rules/retrieve.smk @@ -635,17 +635,17 @@ if config["enable"]["retrieve"] and ( if config["enable"]["retrieve"]: - rule retrieve_fraunhofer_heat_source_utilisation_potentials: + rule retrieve_heat_source_utilisation_potentials: params: heat_source="{heat_source}", - fraunhofer_heat_utilisation_potentials=config_provider( - "sector", "district_heating", "fraunhofer_heat_utilisation_potentials" + heat_utilisation_potentials=config_provider( + "sector", "district_heating", "heat_utilisation_potentials" ), log: - "logs/retrieve_fraunhofer_heat_source_potentials_{heat_source}.log", + "logs/retrieve_heat_source_potentials_{heat_source}.log", resources: mem_mb=500, output: - "data/fraunhofer_heat_source_utilisation_potentials/{heat_source}.gpkg", + "data/heat_source_utilisation_potentials/{heat_source}.gpkg", script: - "../scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py" + "../scripts/retrieve_heat_source_utilisation_potentials.py" diff --git a/scripts/build_cop_profiles/run.py b/scripts/build_cop_profiles/run.py index a29bf260d..27ece8d89 100644 --- a/scripts/build_cop_profiles/run.py +++ b/scripts/build_cop_profiles/run.py @@ -121,10 +121,10 @@ def get_cop( ) elif ( heat_source - in snakemake.params.fraunhofer_heat_utilisation_potentials.keys() + in snakemake.params.heat_utilisation_potentials.keys() ): source_inlet_temperature_celsius = ( - snakemake.params.fraunhofer_heat_utilisation_potentials[ + snakemake.params.heat_utilisation_potentials[ heat_source ]["constant_temperature_celsius"] ) diff --git a/scripts/build_direct_heat_source_utilisation_profiles.py b/scripts/build_direct_heat_source_utilisation_profiles.py index 7db06e56f..ffbc405a5 100644 --- a/scripts/build_direct_heat_source_utilisation_profiles.py +++ b/scripts/build_direct_heat_source_utilisation_profiles.py @@ -12,7 +12,7 @@ .. code:: yaml sector: district_heating: - fraunhofer_heat_utilisation_potentials: + heat_utilisation_potentials: direct_utilisation_heat_sources: snapshots: @@ -53,9 +53,9 @@ def get_source_temperature(heat_source_key: str): if ( heat_source_key - in snakemake.params.fraunhofer_heat_utilisation_potentials.keys() + in snakemake.params.heat_utilisation_potentials.keys() ): - return snakemake.params.fraunhofer_heat_utilisation_potentials[heat_source_key][ + return snakemake.params.heat_utilisation_potentials[heat_source_key][ "constant_temperature_celsius" ] else: diff --git a/scripts/build_heat_source_potentials/run.py b/scripts/build_heat_source_potentials/run.py index b8204ebc0..e3ae02799 100644 --- a/scripts/build_heat_source_potentials/run.py +++ b/scripts/build_heat_source_potentials/run.py @@ -14,7 +14,7 @@ .. code:: yaml sector: district_heating: - fraunhofer_heat_utilisation_potentials: + heat_utilisation_potentials: {heat_source} @@ -74,16 +74,16 @@ def get_unit_conversion_factor( heat_source_technical_potential = OnshoreRegionData( onshore_regions=regions_onshore, data=heat_source_utilisation_potential, - column_name=snakemake.params.fraunhofer_heat_sources[ + column_name=snakemake.params.heat_utilisation_potentials[ snakemake.params.heat_source ]["column_name"], scaling_factor=get_unit_conversion_factor( - input_unit=snakemake.params.fraunhofer_heat_sources[ + input_unit=snakemake.params.heat_utilisation_potentials[ snakemake.params.heat_source ]["unit"], output_unit="MWh", ) - / snakemake.params.fraunhofer_heat_sources[snakemake.params.heat_source][ + / snakemake.params.heat_utilisation_potentials[snakemake.params.heat_source][ "full_load_hours" ], ).data_in_regions_scaled diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 4cbbea384..1709e8201 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2165,7 +2165,7 @@ def add_heat( else costs.at[costs_name_heat_pump, "efficiency"] ) - if heat_source in snakemake.params.fraunhofer_heat_sources: + if heat_source in snakemake.params.heat_utilisation_potentials: # get potential p_max_source = pd.read_csv( snakemake.input[heat_source], diff --git a/scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py b/scripts/retrieve_heat_source_utilisation_potentials.py similarity index 92% rename from scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py rename to scripts/retrieve_heat_source_utilisation_potentials.py index c044d8417..59e662d8b 100644 --- a/scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py +++ b/scripts/retrieve_heat_source_utilisation_potentials.py @@ -14,7 +14,7 @@ .. code:: yaml sector: district_heating: - fraunhofer_heat_utilisation_potentials: + heat_utilisation_potentials: Outputs ------ @@ -46,7 +46,7 @@ if not filepath.parent.exists(): filepath.parent.mkdir(parents=True) - url = f"https://fordatis.fraunhofer.de/bitstream/fordatis/341.3/10/{snakemake.params.fraunhofer_heat_utilisation_potentials[heat_source]['key']}.gpkg" + url = f"https://fordatis.fraunhofer.de/bitstream/fordatis/341.3/10/{snakemake.params.heat_utilisation_potentials[heat_source]['key']}.gpkg" logger.info( f"Downloading heat source utilisation potential data for {heat_source} from '{url}'." From ab97c536198994ee5c2d8f618127597a02142ac3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:02:39 +0000 Subject: [PATCH 33/41] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/build_cop_profiles/run.py | 11 ++++------- .../build_direct_heat_source_utilisation_profiles.py | 5 +---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/scripts/build_cop_profiles/run.py b/scripts/build_cop_profiles/run.py index 27ece8d89..28bbf1a10 100644 --- a/scripts/build_cop_profiles/run.py +++ b/scripts/build_cop_profiles/run.py @@ -119,14 +119,11 @@ def get_cop( f"temp_{heat_source.replace('ground', 'soil')}_total" ] ) - elif ( - heat_source - in snakemake.params.heat_utilisation_potentials.keys() - ): + elif heat_source in snakemake.params.heat_utilisation_potentials.keys(): source_inlet_temperature_celsius = ( - snakemake.params.heat_utilisation_potentials[ - heat_source - ]["constant_temperature_celsius"] + snakemake.params.heat_utilisation_potentials[heat_source][ + "constant_temperature_celsius" + ] ) else: raise ValueError( diff --git a/scripts/build_direct_heat_source_utilisation_profiles.py b/scripts/build_direct_heat_source_utilisation_profiles.py index ffbc405a5..72a9e4646 100644 --- a/scripts/build_direct_heat_source_utilisation_profiles.py +++ b/scripts/build_direct_heat_source_utilisation_profiles.py @@ -51,10 +51,7 @@ def get_source_temperature(heat_source_key: str): If the heat source is unknown (not in `config`). """ - if ( - heat_source_key - in snakemake.params.heat_utilisation_potentials.keys() - ): + if heat_source_key in snakemake.params.heat_utilisation_potentials.keys(): return snakemake.params.heat_utilisation_potentials[heat_source_key][ "constant_temperature_celsius" ] From 2673e7d19c824bbe50d7b89a6394366432d7bac3 Mon Sep 17 00:00:00 2001 From: amos-schledorn Date: Mon, 2 Dec 2024 16:06:13 +0100 Subject: [PATCH 34/41] style: use snakemake.wildcards.heat_sources rather than additional param --- rules/build_sector.smk | 1 - scripts/build_heat_source_potentials/run.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 07a6dc6be..04f5c3e8e 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -289,7 +289,6 @@ rule build_heat_source_potentials: heat_utilisation_potentials=config_provider( "sector", "district_heating", "heat_utilisation_potentials" ), - heat_source="{heat_source}", input: utilisation_potential="data/heat_source_utilisation_potentials/{heat_source}.gpkg", regions_onshore=resources("regions_onshore_base_s_{clusters}.geojson"), diff --git a/scripts/build_heat_source_potentials/run.py b/scripts/build_heat_source_potentials/run.py index e3ae02799..56185f1e2 100644 --- a/scripts/build_heat_source_potentials/run.py +++ b/scripts/build_heat_source_potentials/run.py @@ -75,15 +75,15 @@ def get_unit_conversion_factor( onshore_regions=regions_onshore, data=heat_source_utilisation_potential, column_name=snakemake.params.heat_utilisation_potentials[ - snakemake.params.heat_source + snakemake.wildcards.heat_source ]["column_name"], scaling_factor=get_unit_conversion_factor( input_unit=snakemake.params.heat_utilisation_potentials[ - snakemake.params.heat_source + snakemake.wildcards.heat_source ]["unit"], output_unit="MWh", ) - / snakemake.params.heat_utilisation_potentials[snakemake.params.heat_source][ + / snakemake.params.heat_utilisation_potentials[snakemake.wildcards.heat_source][ "full_load_hours" ], ).data_in_regions_scaled From b2146720aa468def044a94997d8ecda2e8f0092a Mon Sep 17 00:00:00 2001 From: Amos Schledorn <60692940+amos-schledorn@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:07:16 +0100 Subject: [PATCH 35/41] Update doc/configtables/sector.csv Co-authored-by: Fabian Neumann --- doc/configtables/sector.csv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/configtables/sector.csv b/doc/configtables/sector.csv index 5c54794b7..def9d1681 100644 --- a/doc/configtables/sector.csv +++ b/doc/configtables/sector.csv @@ -24,7 +24,8 @@ district_heating,--,,`prepare_sector_network.py Date: Mon, 2 Dec 2024 16:07:27 +0100 Subject: [PATCH 36/41] Update doc/configtables/sector.csv Co-authored-by: Fabian Neumann --- doc/configtables/sector.csv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/configtables/sector.csv b/doc/configtables/sector.csv index def9d1681..4f6313171 100644 --- a/doc/configtables/sector.csv +++ b/doc/configtables/sector.csv @@ -28,7 +28,8 @@ district_heating,--,,`prepare_sector_network.py Date: Mon, 2 Dec 2024 16:19:15 +0100 Subject: [PATCH 37/41] docs: update sector.csv --- doc/configtables/sector.csv | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/configtables/sector.csv b/doc/configtables/sector.csv index 4f6313171..821f06715 100644 --- a/doc/configtables/sector.csv +++ b/doc/configtables/sector.csv @@ -23,10 +23,9 @@ district_heating,--,,`prepare_sector_network.py ) should be used, -- -- geothermal,-,Name of the heat source. Must be the same as in ``heat_pump_sources``, - --- -- -- key,-,string used to complete URL for data download, +-- -- -- key,-,string used to complete URL for data download - e.g. `geothermal_65` or `geothermal_85`","i.e file names in `Fordatis `, -- -- -- constant_temperature_celsius,°C,heat source temperature, -- -- -- column_name,-,name of the data column in retrieved GeoDataFrame, From 102cd8d14601feb98fca699296fa24384f79faa8 Mon Sep 17 00:00:00 2001 From: amos-schledorn Date: Mon, 2 Dec 2024 16:21:16 +0100 Subject: [PATCH 38/41] refactor: refactor scaling factor calculation in run.py --- scripts/build_heat_source_potentials/run.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/scripts/build_heat_source_potentials/run.py b/scripts/build_heat_source_potentials/run.py index 56185f1e2..b266d1836 100644 --- a/scripts/build_heat_source_potentials/run.py +++ b/scripts/build_heat_source_potentials/run.py @@ -70,6 +70,17 @@ def get_unit_conversion_factor( heat_source_utilisation_potential = gpd.read_file( snakemake.input.utilisation_potential ) + + + unit_conversion_factor=get_unit_conversion_factor( + input_unit=snakemake.params.heat_utilisation_potentials[ + snakemake.wildcards.heat_source + ]["unit"], + output_unit="MWh", + ) + scaling_factor = unit_conversion_factor / snakemake.params.heat_utilisation_potentials[snakemake.wildcards.heat_source][ + "full_load_hours" + ] heat_source_technical_potential = OnshoreRegionData( onshore_regions=regions_onshore, @@ -77,15 +88,7 @@ def get_unit_conversion_factor( column_name=snakemake.params.heat_utilisation_potentials[ snakemake.wildcards.heat_source ]["column_name"], - scaling_factor=get_unit_conversion_factor( - input_unit=snakemake.params.heat_utilisation_potentials[ - snakemake.wildcards.heat_source - ]["unit"], - output_unit="MWh", - ) - / snakemake.params.heat_utilisation_potentials[snakemake.wildcards.heat_source][ - "full_load_hours" - ], + scaling_factor=scaling_factor, ).data_in_regions_scaled heat_source_technical_potential.to_csv(snakemake.output[0]) From 18c35888a396bc9290c2bf7b63fb361a125caf71 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:21:38 +0000 Subject: [PATCH 39/41] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/build_heat_source_potentials/run.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/scripts/build_heat_source_potentials/run.py b/scripts/build_heat_source_potentials/run.py index b266d1836..b824b613f 100644 --- a/scripts/build_heat_source_potentials/run.py +++ b/scripts/build_heat_source_potentials/run.py @@ -70,17 +70,19 @@ def get_unit_conversion_factor( heat_source_utilisation_potential = gpd.read_file( snakemake.input.utilisation_potential ) - - - unit_conversion_factor=get_unit_conversion_factor( - input_unit=snakemake.params.heat_utilisation_potentials[ - snakemake.wildcards.heat_source - ]["unit"], - output_unit="MWh", - ) - scaling_factor = unit_conversion_factor / snakemake.params.heat_utilisation_potentials[snakemake.wildcards.heat_source][ + + unit_conversion_factor = get_unit_conversion_factor( + input_unit=snakemake.params.heat_utilisation_potentials[ + snakemake.wildcards.heat_source + ]["unit"], + output_unit="MWh", + ) + scaling_factor = ( + unit_conversion_factor + / snakemake.params.heat_utilisation_potentials[snakemake.wildcards.heat_source][ "full_load_hours" ] + ) heat_source_technical_potential = OnshoreRegionData( onshore_regions=regions_onshore, From a033b83bc06f603f844bb70deee315e6a4298f3a Mon Sep 17 00:00:00 2001 From: amos-schledorn Date: Mon, 2 Dec 2024 16:23:51 +0100 Subject: [PATCH 40/41] docs: add direct_utilisation_heat_sources to configtables --- doc/configtables/sector.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/configtables/sector.csv b/doc/configtables/sector.csv index 821f06715..ba98adee0 100644 --- a/doc/configtables/sector.csv +++ b/doc/configtables/sector.csv @@ -31,6 +31,7 @@ district_heating,--,,`prepare_sector_network.py Date: Mon, 2 Dec 2024 16:24:57 +0100 Subject: [PATCH 41/41] udpate release_notes --- doc/release_notes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index e90de868c..604537fdd 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -11,7 +11,7 @@ Release Notes Upcoming Release ================ -* Feature: Introduce geothermal district heating (direct utilisation and heat pumps). Potentials are based on Manz et al. 2024: Spatial analysis of renewable and excess heat potentials for climate-neutral district heating in Europe +* Feature: Introduce geothermal district heating (direct utilisation and heat pumps). Potentials are based on `Manz et al. 2024: Spatial analysis of renewable and excess heat potentials for climate-neutral district heating in Europe `. * Feature: Allow CHPs to use different fuel sources such as gas, oil, coal, and methanol. Note that the cost assumptions are based on a gas CHP (except for solid biomass-fired CHP).