Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add geothermal-sourced central heat pumps #1359

Merged
merged 50 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
686cf58
feat: add utilisation potential retrieval
amos-schledorn Oct 14, 2024
7268b76
feat: add build_heat_source_potentials module
amos-schledorn Oct 14, 2024
bb0067d
feat: update config
amos-schledorn Oct 14, 2024
70a0303
feat: extend retrieve.smk
amos-schledorn Oct 14, 2024
4cabd85
feat: pass potential to update prepare_sector_network
amos-schledorn Oct 14, 2024
5a85311
feat: update cop profile module
amos-schledorn Oct 14, 2024
b50ea04
feat: update build_sector.smk accordingly
amos-schledorn Oct 14, 2024
3bb2e59
fix: use generator + links for heat source
amos-schledorn Oct 16, 2024
c31109c
clean up run.py
amos-schledorn Oct 17, 2024
e60a908
Merge remote-tracking branch 'origin/master' into add-geothermal-sour…
amos-schledorn Oct 24, 2024
6310bad
feat: generalise 'geothermal' to any heat source in Fraunhofer data
amos-schledorn Oct 24, 2024
5f0b94a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 24, 2024
e614637
chor: simplify indexing
amos-schledorn Nov 4, 2024
e23d64c
style: rename potentials var
amos-schledorn Nov 4, 2024
fa50c39
Merge remote-tracking branch 'origin/master' into add-geothermal-sour…
amos-schledorn Nov 4, 2024
cdcae94
fix: return 0 for COP calculation when source inlet temperature excee…
amos-schledorn Nov 5, 2024
4f87d58
refactor: remove unused function and clean up code in build_cop_profi…
amos-schledorn Nov 5, 2024
854af80
style: clean-up comments in OnshoreRegionData.py and prepare_sector_n…
amos-schledorn Nov 5, 2024
fcd43c0
feat: add direct heat source utilisation when source temp > forward temp
amos-schledorn Nov 5, 2024
1fac259
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 5, 2024
ec729fc
feat: add costs for geothermal heat source
amos-schledorn Nov 11, 2024
028c6d7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 11, 2024
f2db6f9
style: pre-commit, add dev to gitignore
amos-schledorn Nov 11, 2024
fd9414e
Merge remote-tracking branch 'origin/add-geothermal-sourced-heat-pump…
amos-schledorn Nov 11, 2024
a7d4b06
feat: replace access to config with unpack where possible
amos-schledorn Nov 14, 2024
6d064bf
feat: replace dict access to config in snakemake
amos-schledorn Nov 15, 2024
2421bcd
Merge remote-tracking branch 'origin/master' into add-geothermal-sour…
amos-schledorn Nov 20, 2024
2df8fc4
Merge remote-tracking branch 'origin/master' into add-geothermal-sour…
amos-schledorn Nov 22, 2024
1073059
Merge remote-tracking branch 'origin/master' into add-geothermal-sour…
amos-schledorn Nov 28, 2024
df80e55
doc: add docstrings, clean up code
amos-schledorn Nov 28, 2024
0447adc
docs: update release notes
amos-schledorn Nov 28, 2024
f866887
doc: update configtables
amos-schledorn Nov 28, 2024
ab64002
doc: update data sourcs
amos-schledorn Nov 28, 2024
3bc61f2
update docs
amos-schledorn Nov 28, 2024
336786e
Update config/config.default.yaml
amos-schledorn Nov 29, 2024
e1986da
docs: fix data-retrieval docs
amos-schledorn Dec 2, 2024
8defb22
Merge branch 'master' into add-geothermal-sourced-heat-pumps
amos-schledorn Dec 2, 2024
69cf11b
feat: turn off geothermal DH heat by default
amos-schledorn Dec 2, 2024
2c755fd
style: remove "fraunhofer_" in naming
amos-schledorn Dec 2, 2024
ab97c53
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 2, 2024
2673e7d
style: use snakemake.wildcards.heat_sources rather than additional param
amos-schledorn Dec 2, 2024
0e68827
Merge remote-tracking branch 'origin/add-geothermal-sourced-heat-pump…
amos-schledorn Dec 2, 2024
b214672
Update doc/configtables/sector.csv
amos-schledorn Dec 2, 2024
4a64f4a
Update doc/configtables/sector.csv
amos-schledorn Dec 2, 2024
32acf4b
docs: update sector.csv
amos-schledorn Dec 2, 2024
102cd8d
refactor: refactor scaling factor calculation in run.py
amos-schledorn Dec 2, 2024
18c3588
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 2, 2024
a033b83
docs: add direct_utilisation_heat_sources to configtables
amos-schledorn Dec 2, 2024
3e3868c
Merge remote-tracking branch 'origin/add-geothermal-sourced-heat-pump…
amos-schledorn Dec 2, 2024
0d8f4ad
udpate release_notes
amos-schledorn Dec 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,5 @@ d1gam3xoknrgr2.cloudfront.net/
merger-todos.md

*.html
# private dev folder
dev/*
19 changes: 17 additions & 2 deletions config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,18 @@ sector:
heat_exchanger_pinch_point_temperature_difference: 5 #K
isentropic_compressor_efficiency: 0.8
heat_loss: 0.0
fraunhofer_heat_utilisation_potentials:
amos-schledorn marked this conversation as resolved.
Show resolved Hide resolved
geothermal:
# activate for 85C hydrothermal
# key: hydrothermal_85
# constant_temperature_celsius: 85
key: hydrothermal_65
constant_temperature_celsius: 65
column_name: Energy_TWh
unit: TWh
full_load_hours: 4000
direct_utilisation_heat_sources:
amos-schledorn marked this conversation as resolved.
Show resolved Hide resolved
- geothermal
heat_pump_sources:
urban central:
- air
Expand Down Expand Up @@ -827,7 +839,7 @@ industry:
# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#costs
costs:
year: 2030
version: v0.9.2
version: v0.10.0
social_discountrate: 0.02
fill_values:
FOM: 0
Expand Down Expand Up @@ -1228,8 +1240,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: '#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'
Expand Down
7 changes: 7 additions & 0 deletions doc/configtables/sector.csv
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ district_heating,--,,`prepare_sector_network.py <https://github.com/PyPSA/pypsa-
-- -- heat_exchanger_pinch_point_temperature_difference,K,float,Heat pump pinch point temperature difference in heat exchangers assumed for approximation.
-- -- isentropic_compressor_efficiency,--,float,Isentropic efficiency of heat pump compressor assumed for approximation. Must be between 0 and 1.
-- -- heat_loss,--,float,Heat pump heat loss assumed for approximation. Must be between 0 and 1.
-- fraunhofer_heat_utilisation_potentials,--,Dictionary with names of heat sources for which data by Fraunhofer ISI (Manz et al. 2024) should be used,
amos-schledorn marked this conversation as resolved.
Show resolved Hide resolved
-- -- geothermal,-,Name of the heat source. Must be the same as in `heat_pump_sources,
amos-schledorn marked this conversation as resolved.
Show resolved Hide resolved
-- -- -- key,-,string used to complete URL for data download,
amos-schledorn marked this conversation as resolved.
Show resolved Hide resolved
-- -- -- constant_temperature_celsius,°C,heat source temperature,
-- -- -- column_name,-,name of the data column in retrieved geoDataFrame,
amos-schledorn marked this conversation as resolved.
Show resolved Hide resolved
-- -- -- unit,-,unit of heat source potential must be in (K/M/G/T)Wh,
-- -- -- full_load_hours,h,assumed full-load hours in Manz et al. (used to scale from utilisation to technical potential),
-- heat_pump_sources,--,,
-- -- urban central,--,List of heat sources for heat pumps in urban central heating,
-- -- urban decentral,--,List of heat sources for heat pumps in urban decentral heating,
Expand Down
9 changes: 9 additions & 0 deletions doc/data-retrieval.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ 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://fordatis.fraunhofer.de/handle/fordatis/341.3?mode=simple
- **License:** `CC BY 4.0 <https://creativecommons.org/licenses/by/4.0/>`__
- **Description:** Utilisation potentials for different heat sources across Europe, based on Manz et al. 2024<https://doi.org/10.1016/j.renene.2024.120111>.


``data/nuts``

- **Source:** GISCO
Expand Down
2 changes: 2 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
amos-schledorn marked this conversation as resolved.
Show resolved Hide resolved

* 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`.
Expand Down
83 changes: 83 additions & 0 deletions rules/build_sector.smk
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,29 @@ rule build_central_heating_temperature_profiles:
"../scripts/build_central_heating_temperature_profiles/run.py"


rule build_heat_source_potentials:
params:
fraunhofer_heat_sources=config_provider(
"sector", "district_heating", "fraunhofer_heat_utilisation_potentials"
),
heat_source="{heat_source}",
amos-schledorn marked this conversation as resolved.
Show resolved Hide resolved
input:
utilisation_potential="data/fraunhofer_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"),
resources:
mem_mb=2000,
log:
logs("build_heat_source_potentials_{heat_source}_s_{clusters}.log"),
benchmark:
benchmarks("build_heat_source_potentials/{heat_source}_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(
Expand All @@ -296,6 +319,9 @@ 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"
),
snapshots=config_provider("snapshots"),
input:
central_heating_forward_temperature_profiles=resources(
Expand All @@ -321,6 +347,39 @@ rule build_cop_profiles:
"../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)
if c == "default":
Expand Down Expand Up @@ -1004,6 +1063,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"),
Expand All @@ -1028,8 +1101,15 @@ 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"
),
direct_utilisation_heat_sources=config_provider(
"sector", "district_heating", "direct_utilisation_heat_sources"
),
input:
unpack(input_profile_offwind),
unpack(input_heat_source_potentials),
**rules.cluster_gas_network.output,
**rules.build_gas_input_locations.output,
snapshot_weightings=resources(
Expand Down Expand Up @@ -1119,6 +1199,9 @@ rule prepare_sector_network:
if config_provider("sector", "enhanced_geothermal", "enable")(w)
else []
),
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",
Expand Down
18 changes: 18 additions & 0 deletions rules/retrieve.smk
Original file line number Diff line number Diff line change
Expand Up @@ -631,3 +631,21 @@ if config["enable"]["retrieve"] and (
"data/osm-raw/{country}/substations_relation.json",
country=config_provider("countries"),
),


if config["enable"]["retrieve"]:

rule retrieve_fraunhofer_heat_source_utilisation_potentials:
params:
heat_source="{heat_source}",
fraunhofer_heat_utilisation_potentials=config_provider(
"sector", "district_heating", "fraunhofer_heat_utilisation_potentials"
),
log:
"logs/retrieve_fraunhofer_heat_source_potentials_{heat_source}.log",
resources:
mem_mb=500,
output:
"data/fraunhofer_heat_source_utilisation_potentials/{heat_source}.gpkg",
script:
"../scripts/retrieve_fraunhofer_heat_source_utilisation_potentials.py"
10 changes: 8 additions & 2 deletions scripts/build_cop_profiles/CentralHeatingCopApproximator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
* (
(
Expand All @@ -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
Expand Down
35 changes: 21 additions & 14 deletions scripts/build_cop_profiles/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <CentralHeatingCopApproximator.py>`_) and for decentral heating, the approximation is based on Staffell et al. (2012) (c.f. `DecentralHeatingCopApproximator <DecentralHeatingCopApproximator.py>`_).

Expand All @@ -27,11 +27,8 @@
urban central:
urban decentral:
rural:
snapshots:

Inputs
------
- `resources/<run_name>/regions_onshore.geojson`: Onshore regions
- `resources/<run_name>/temp_soil_total`: Ground temperature
- `resources/<run_name>/temp_air_total`: Air temperature

Expand Down Expand Up @@ -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
Expand All @@ -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
)
Expand All @@ -123,9 +113,26 @@ 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.fraunhofer_heat_utilisation_potentials.keys()
):
source_inlet_temperature_celsius = (
snakemake.params.fraunhofer_heat_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()}."
)

cop_da = get_cop(
heat_system_type=heat_system_type,
heat_source=heat_source,
Expand Down
Loading
Loading