diff --git a/config/config.default.yaml b/config/config.default.yaml index cecd34e5..3d1292e3 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -448,8 +448,19 @@ sector: 2045: 0.8 2050: 1.0 district_heating_loss: 0.15 - forward_temperature: 90 #C - return_temperature: 50 #C + # check these numbers! + 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_pump_cop_approximation: refrigerant: ammonia diff --git a/doc/configtables/sector.csv b/doc/configtables/sector.csv index 573805bb..88f56abe 100644 --- a/doc/configtables/sector.csv +++ b/doc/configtables/sector.csv @@ -9,8 +9,8 @@ district_heating,--,,`prepare_sector_network.py `__. 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 substituted by the phase-in of sustainable biomass types using the config parameters ``biomass: share_unsustainable_use_retained`` and ``biomass: share_sustainable_potential_available``. diff --git a/rules/build_sector.smk b/rules/build_sector.smk index faad2bb1..598df96e 100644 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -233,9 +233,11 @@ rule build_cop_profiles: "sector", "district_heating", "heat_pump_cop_approximation" ), heat_pump_sources=config_provider("sector", "heat_pump_sources"), + snapshots=config_provider("snapshots"), input: temp_soil_total=resources("temp_soil_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: cop_profiles=resources("cop_profiles_elec_s{simpl}_{clusters}.nc"), resources: diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index f67c38d9..212ae8af 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -22,12 +22,11 @@ from _helpers import ( update_config_from_wildcards, ) 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 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__) cc = coco.CountryConverter() idx = pd.IndexSlice diff --git a/scripts/build_cop_profiles/run.py b/scripts/build_cop_profiles/run.py index 4d57db31..b4ec3e43 100644 --- a/scripts/build_cop_profiles/run.py +++ b/scripts/build_cop_profiles/run.py @@ -2,9 +2,47 @@ # SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors # # 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 `_) and for decentral heating, the approximation is based on Staffell et al. (2012) (c.f. `DecentralHeatingCopApproximator `_). + +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//regions_onshore.geojson`: Onshore regions +- `resources//temp_soil_total`: Ground temperature +- `resources//temp_air_total`: Air temperature + +Outputs +------- +- `resources//cop_profiles.nc`: Heat pump coefficient-of-performance (COP) profiles +""" import sys +import geopandas as gpd import numpy as np import pandas as pd import xarray as xr @@ -14,13 +52,54 @@ from DecentralHeatingCopApproximator import DecentralHeatingCopApproximator 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( heat_system_type: str, heat_source: str, 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: """ Calculate the coefficient of performance (COP) for a heating system. @@ -41,8 +120,8 @@ def get_cop( """ if HeatSystemType(heat_system_type).is_central: return CentralHeatingCopApproximator( - forward_temperature_celsius=snakemake.params.forward_temperature_central_heating, - return_temperature_celsius=snakemake.params.return_temperature_central_heating, + forward_temperature_celsius=forward_temperature_by_node_and_time, + return_temperature_celsius=return_temperature_by_node_and_time, source_inlet_temperature_celsius=source_inlet_temperature_celsius, source_outlet_temperature_celsius=source_inlet_temperature_celsius - snakemake.params.heat_source_cooling_central_heating, @@ -56,6 +135,10 @@ 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 @@ -68,6 +151,23 @@ if __name__ == "__main__": 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 = [] for heat_system_type, heat_sources in snakemake.params.heat_pump_sources.items(): cop_this_system_type = [] @@ -79,6 +179,8 @@ if __name__ == "__main__": heat_system_type=heat_system_type, heat_source=heat_source, 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_all_system_types.append( diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 4f3e3d2b..71b50439 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -30,6 +30,9 @@ from build_energy_totals import ( build_eurostat_co2, ) 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.connectivity.edge_augmentation import k_edge_augmentation 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 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() logger = logging.getLogger(__name__) @@ -2831,8 +2830,11 @@ def add_biomass(n, costs): ) if options["bioH2"]: - name = (pd.Index(spatial.biomass.nodes) + " " - + pd.Index(spatial.h2.nodes.str.replace(" H2", ""))) + name = ( + pd.Index(spatial.biomass.nodes) + + " " + + pd.Index(spatial.h2.nodes.str.replace(" H2", "")) + ) n.madd( "Link", name, @@ -2842,16 +2844,22 @@ def add_biomass(n, costs): bus2=spatial.co2.nodes, bus3="co2 atmosphere", carrier="solid biomass to hydrogen", - efficiency=costs.at['solid biomass to hydrogen', 'efficiency'], - efficiency2=costs.at['solid biomass', 'CO2 intensity'] * options["cc_fraction"], - efficiency3=-costs.at['solid biomass', 'CO2 intensity'] * options["cc_fraction"], + efficiency=costs.at["solid biomass to hydrogen", "efficiency"], + efficiency2=costs.at["solid biomass", "CO2 intensity"] + * options["cc_fraction"], + efficiency3=-costs.at["solid biomass", "CO2 intensity"] + * options["cc_fraction"], p_nom_extendable=True, - capital_cost=costs.at['solid biomass to hydrogen', 'fixed'] * costs.at['solid biomass to hydrogen', 'efficiency'] - + costs.at['biomass CHP capture', 'fixed'] * costs.at['solid biomass', 'CO2 intensity'], - 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., - ) + capital_cost=costs.at["solid biomass to hydrogen", "fixed"] + * costs.at["solid biomass to hydrogen", "efficiency"] + + costs.at["biomass CHP capture", "fixed"] + * costs.at["solid biomass", "CO2 intensity"], + 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):