Merge pull request #1180 from PyPSA/country-specific-dh-forward-temperatures

Add option of country-specific district heating supply temperatures
This commit is contained in:
lisazeyen 2024-08-16 13:43:52 +02:00 committed by GitHub
commit 31c8907370
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 150 additions and 26 deletions

View File

@ -448,8 +448,19 @@ sector:
2045: 0.8 2045: 0.8
2050: 1.0 2050: 1.0
district_heating_loss: 0.15 district_heating_loss: 0.15
forward_temperature: 90 #C # check these numbers!
return_temperature: 50 #C forward_temperature:
default: 90
DK: 70
SE: 70
NO: 70
FI: 70
return_temperature:
default: 50
DK: 40
SE: 40
NO: 40
FI: 40
heat_source_cooling: 6 #K heat_source_cooling: 6 #K
heat_pump_cop_approximation: heat_pump_cop_approximation:
refrigerant: ammonia refrigerant: ammonia

View File

@ -9,8 +9,8 @@ district_heating,--,,`prepare_sector_network.py <https://github.com/PyPSA/pypsa-
-- potential,--,float,maximum fraction of urban demand which can be supplied by district heating -- potential,--,float,maximum fraction of urban demand which can be supplied by district heating
-- progress,--,Dictionary with planning horizons as keys., Increase of today's district heating demand to potential maximum district heating share. Progress = 0 means today's district heating share. Progress = 1 means maximum fraction of urban demand is supplied by district heating -- progress,--,Dictionary with planning horizons as keys., Increase of today's district heating demand to potential maximum district heating share. Progress = 0 means today's district heating share. Progress = 1 means maximum fraction of urban demand is supplied by district heating
-- district_heating_loss,--,float,Share increase in district heat demand in urban central due to heat losses -- district_heating_loss,--,float,Share increase in district heat demand in urban central due to heat losses
-- forward_temperature,°C,float,Forward temperature in district heating -- forward_temperature,°C,Dictionary with country codes as keys. One key must be 'default'.,Forward temperature in district heating
-- return_temperature,°C,float,Return temperature in district heating. Must be lower than forward temperature -- return_temperature,°C,Dictionary with country codes as keys. One key must be 'default'.,Return temperature in district heating. Must be lower than forward temperature
-- heat_source_cooling,K,float,Cooling of heat source for heat pumps -- heat_source_cooling,K,float,Cooling of heat source for heat pumps
-- heat_pump_cop_approximation,,, -- heat_pump_cop_approximation,,,
-- -- refrigerant,--,"{ammonia, isobutane}",Heat pump refrigerant assumed for COP approximation -- -- refrigerant,--,"{ammonia, isobutane}",Heat pump refrigerant assumed for COP approximation

1 Unit Values Description
9 -- potential -- float maximum fraction of urban demand which can be supplied by district heating
10 -- progress -- Dictionary with planning horizons as keys. Increase of today's district heating demand to potential maximum district heating share. Progress = 0 means today's district heating share. Progress = 1 means maximum fraction of urban demand is supplied by district heating
11 -- district_heating_loss -- float Share increase in district heat demand in urban central due to heat losses
12 -- forward_temperature °C float Dictionary with country codes as keys. One key must be 'default'. Forward temperature in district heating
13 -- return_temperature °C float Dictionary with country codes as keys. One key must be 'default'. Return temperature in district heating. Must be lower than forward temperature
14 -- heat_source_cooling K float Cooling of heat source for heat pumps
15 -- heat_pump_cop_approximation
16 -- -- refrigerant -- {ammonia, isobutane} Heat pump refrigerant assumed for COP approximation

View File

@ -20,6 +20,8 @@ Upcoming Release
* Update JRC-IDEES-2015 to `JRC-IDEES-2021 <https://publications.jrc.ec.europa.eu/repository/handle/JRC137809>`__. The reference year is changed from 2015 to 2019. * Update JRC-IDEES-2015 to `JRC-IDEES-2021 <https://publications.jrc.ec.europa.eu/repository/handle/JRC137809>`__. The reference year is changed from 2015 to 2019.
* Added option to use country-specific district heating forward and return temperatures. Defaults to lower temperatures in Scandinavia.
* Added unsustainable biomass potentials for solid, gaseous, and liquid biomass. The potentials can be phased-out and/or * Added unsustainable biomass potentials for solid, gaseous, and liquid biomass. The potentials can be phased-out and/or
substituted by the phase-in of sustainable biomass types using the config parameters substituted by the phase-in of sustainable biomass types using the config parameters
``biomass: share_unsustainable_use_retained`` and ``biomass: share_sustainable_potential_available``. ``biomass: share_unsustainable_use_retained`` and ``biomass: share_sustainable_potential_available``.

View File

@ -233,9 +233,11 @@ rule build_cop_profiles:
"sector", "district_heating", "heat_pump_cop_approximation" "sector", "district_heating", "heat_pump_cop_approximation"
), ),
heat_pump_sources=config_provider("sector", "heat_pump_sources"), heat_pump_sources=config_provider("sector", "heat_pump_sources"),
snapshots=config_provider("snapshots"),
input: input:
temp_soil_total=resources("temp_soil_total_elec_s{simpl}_{clusters}.nc"), temp_soil_total=resources("temp_soil_total_elec_s{simpl}_{clusters}.nc"),
temp_air_total=resources("temp_air_total_elec_s{simpl}_{clusters}.nc"), temp_air_total=resources("temp_air_total_elec_s{simpl}_{clusters}.nc"),
regions_onshore=resources("regions_onshore_elec_s{simpl}_{clusters}.geojson"),
output: output:
cop_profiles=resources("cop_profiles_elec_s{simpl}_{clusters}.nc"), cop_profiles=resources("cop_profiles_elec_s{simpl}_{clusters}.nc"),
resources: resources:

View File

@ -22,12 +22,11 @@ from _helpers import (
update_config_from_wildcards, update_config_from_wildcards,
) )
from add_electricity import sanitize_carriers from add_electricity import sanitize_carriers
from definitions.heat_sector import HeatSector
from definitions.heat_system import HeatSystem
from definitions.heat_system_type import HeatSystemType
from prepare_sector_network import cluster_heat_buses, define_spatial, prepare_costs from prepare_sector_network import cluster_heat_buses, define_spatial, prepare_costs
from scripts.definitions.heat_sector import HeatSector
from scripts.definitions.heat_system import HeatSystem
from scripts.definitions.heat_system_type import HeatSystemType
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
cc = coco.CountryConverter() cc = coco.CountryConverter()
idx = pd.IndexSlice idx = pd.IndexSlice

View File

@ -2,9 +2,47 @@
# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors # SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors
# #
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
"""
Approximate heat pump coefficient-of-performance (COP) profiles for different
heat sources and systems.
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>`_).
Relevant Settings
-----------------
.. code:: yaml
sector:
heat_pump_sink_T_decentral_heating:
district_heating:
forward_temperature:
return_temperature:
heat_source_cooling:
heat_pump_cop_approximation:
refrigerant:
heat_exchanger_pinch_point_temperature_difference
isentropic_compressor_efficiency:
heat_loss:
heat_pump_sources:
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
Outputs
-------
- `resources/<run_name>/cop_profiles.nc`: Heat pump coefficient-of-performance (COP) profiles
"""
import sys import sys
import geopandas as gpd
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import xarray as xr import xarray as xr
@ -14,13 +52,54 @@ from DecentralHeatingCopApproximator import DecentralHeatingCopApproximator
from scripts.definitions.heat_system_type import HeatSystemType from scripts.definitions.heat_system_type import HeatSystemType
sys.path.append("..")
def map_temperature_dict_to_onshore_regions(
supply_temperature_by_country: dict,
regions_onshore: pd.Index,
snapshots: pd.DatetimeIndex,
) -> xr.DataArray:
"""
Map dictionary of temperatures to onshore regions.
Parameters:
----------
supply_temperature_by_country : dictionary
Dictionary with temperatures as values and country keys as keys. One key must be named "default"
regions_onshore : pd.Index
Names of onshore regions
snapshots : pd.DatetimeIndex
Time stamps
Returns:
-------
xr.DataArray
The dictionary values mapped to onshore regions with onshore regions as coordinates.
"""
return xr.DataArray(
[
[
(
supply_temperature_by_country[get_country_from_node_name(node_name)]
if get_country_from_node_name(node_name)
in supply_temperature_by_country.keys()
else supply_temperature_by_country["default"]
)
for node_name in regions_onshore.values
]
# pass both nodes and snapshots as dimensions to preserve correct data structure
for _ in snapshots
],
dims=["time", "name"],
coords={"time": snapshots, "name": regions_onshore},
)
def get_cop( def get_cop(
heat_system_type: str, heat_system_type: str,
heat_source: str, heat_source: str,
source_inlet_temperature_celsius: xr.DataArray, source_inlet_temperature_celsius: xr.DataArray,
forward_temperature_by_node_and_time: xr.DataArray = None,
return_temperature_by_node_and_time: xr.DataArray = None,
) -> xr.DataArray: ) -> xr.DataArray:
""" """
Calculate the coefficient of performance (COP) for a heating system. Calculate the coefficient of performance (COP) for a heating system.
@ -41,8 +120,8 @@ def get_cop(
""" """
if HeatSystemType(heat_system_type).is_central: if HeatSystemType(heat_system_type).is_central:
return CentralHeatingCopApproximator( return CentralHeatingCopApproximator(
forward_temperature_celsius=snakemake.params.forward_temperature_central_heating, forward_temperature_celsius=forward_temperature_by_node_and_time,
return_temperature_celsius=snakemake.params.return_temperature_central_heating, return_temperature_celsius=return_temperature_by_node_and_time,
source_inlet_temperature_celsius=source_inlet_temperature_celsius, source_inlet_temperature_celsius=source_inlet_temperature_celsius,
source_outlet_temperature_celsius=source_inlet_temperature_celsius source_outlet_temperature_celsius=source_inlet_temperature_celsius
- snakemake.params.heat_source_cooling_central_heating, - snakemake.params.heat_source_cooling_central_heating,
@ -56,6 +135,10 @@ def get_cop(
).approximate_cop() ).approximate_cop()
def get_country_from_node_name(node_name: str) -> str:
return node_name[:2]
if __name__ == "__main__": if __name__ == "__main__":
if "snakemake" not in globals(): if "snakemake" not in globals():
from _helpers import mock_snakemake from _helpers import mock_snakemake
@ -68,6 +151,23 @@ if __name__ == "__main__":
set_scenario_config(snakemake) 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)
forward_temperature_central_heating_by_node_and_time: xr.DataArray = (
map_temperature_dict_to_onshore_regions(
supply_temperature_by_country=snakemake.params.forward_temperature_central_heating,
regions_onshore=regions_onshore,
snapshots=snapshots,
)
)
return_temperature_central_heating_by_node_and_time: xr.DataArray = (
map_temperature_dict_to_onshore_regions(
supply_temperature_by_country=snakemake.params.return_temperature_central_heating,
regions_onshore=regions_onshore,
snapshots=snapshots,
)
)
cop_all_system_types = [] cop_all_system_types = []
for heat_system_type, heat_sources in snakemake.params.heat_pump_sources.items(): for heat_system_type, heat_sources in snakemake.params.heat_pump_sources.items():
cop_this_system_type = [] cop_this_system_type = []
@ -79,6 +179,8 @@ if __name__ == "__main__":
heat_system_type=heat_system_type, heat_system_type=heat_system_type,
heat_source=heat_source, heat_source=heat_source,
source_inlet_temperature_celsius=source_inlet_temperature_celsius, source_inlet_temperature_celsius=source_inlet_temperature_celsius,
forward_temperature_by_node_and_time=forward_temperature_central_heating_by_node_and_time,
return_temperature_by_node_and_time=return_temperature_central_heating_by_node_and_time,
) )
cop_this_system_type.append(cop_da) cop_this_system_type.append(cop_da)
cop_all_system_types.append( cop_all_system_types.append(

View File

@ -30,6 +30,9 @@ from build_energy_totals import (
build_eurostat_co2, build_eurostat_co2,
) )
from build_transport_demand import transport_degree_factor from build_transport_demand import transport_degree_factor
from definitions.heat_sector import HeatSector
from definitions.heat_system import HeatSystem
from definitions.heat_system_type import HeatSystemType
from networkx.algorithms import complement from networkx.algorithms import complement
from networkx.algorithms.connectivity.edge_augmentation import k_edge_augmentation from networkx.algorithms.connectivity.edge_augmentation import k_edge_augmentation
from prepare_network import maybe_adjust_costs_and_potentials from prepare_network import maybe_adjust_costs_and_potentials
@ -37,10 +40,6 @@ from pypsa.geo import haversine_pts
from pypsa.io import import_components_from_dataframe from pypsa.io import import_components_from_dataframe
from scipy.stats import beta from scipy.stats import beta
from scripts.definitions.heat_sector import HeatSector
from scripts.definitions.heat_system import HeatSystem
from scripts.definitions.heat_system_type import HeatSystemType
spatial = SimpleNamespace() spatial = SimpleNamespace()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -2831,8 +2830,11 @@ def add_biomass(n, costs):
) )
if options["bioH2"]: if options["bioH2"]:
name = (pd.Index(spatial.biomass.nodes) + " " name = (
+ pd.Index(spatial.h2.nodes.str.replace(" H2", ""))) pd.Index(spatial.biomass.nodes)
+ " "
+ pd.Index(spatial.h2.nodes.str.replace(" H2", ""))
)
n.madd( n.madd(
"Link", "Link",
name, name,
@ -2842,16 +2844,22 @@ def add_biomass(n, costs):
bus2=spatial.co2.nodes, bus2=spatial.co2.nodes,
bus3="co2 atmosphere", bus3="co2 atmosphere",
carrier="solid biomass to hydrogen", carrier="solid biomass to hydrogen",
efficiency=costs.at['solid biomass to hydrogen', 'efficiency'], efficiency=costs.at["solid biomass to hydrogen", "efficiency"],
efficiency2=costs.at['solid biomass', 'CO2 intensity'] * options["cc_fraction"], efficiency2=costs.at["solid biomass", "CO2 intensity"]
efficiency3=-costs.at['solid biomass', 'CO2 intensity'] * options["cc_fraction"], * options["cc_fraction"],
efficiency3=-costs.at["solid biomass", "CO2 intensity"]
* options["cc_fraction"],
p_nom_extendable=True, p_nom_extendable=True,
capital_cost=costs.at['solid biomass to hydrogen', 'fixed'] * costs.at['solid biomass to hydrogen', 'efficiency'] capital_cost=costs.at["solid biomass to hydrogen", "fixed"]
+ costs.at['biomass CHP capture', 'fixed'] * costs.at['solid biomass', 'CO2 intensity'], * costs.at["solid biomass to hydrogen", "efficiency"]
overnight_cost=costs.at['solid biomass to hydrogen', 'investment'] * costs.at['solid biomass to hydrogen', 'efficiency'] + costs.at["biomass CHP capture", "fixed"]
+ costs.at['biomass CHP capture', 'investment'] * costs.at['solid biomass', 'CO2 intensity'], * costs.at["solid biomass", "CO2 intensity"],
marginal_cost=0., overnight_cost=costs.at["solid biomass to hydrogen", "investment"]
) * costs.at["solid biomass to hydrogen", "efficiency"]
+ costs.at["biomass CHP capture", "investment"]
* costs.at["solid biomass", "CO2 intensity"],
marginal_cost=0.0,
)
def add_industry(n, costs): def add_industry(n, costs):