diff --git a/config/config.default.yaml b/config/config.default.yaml index d9ed959e..63cdcabf 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -451,21 +451,18 @@ sector: district_heating_loss: 0.15 supply_temperature_approximation: max_forward_temperature: - default: 90 - DK: 70 - SE: 70 - NO: 70 + FR: 110 + DK: 75 + DE: 109 + CZ: 130 + FI: 115 + PL: 130 + SE: 102 + IT: 90 min_forward_temperature: - default: 68 - DK: 54 - SE: 54 - NO: 54 + DE: 82 return_temperature: - default: 50 - DK: 40 - SE: 40 - NO: 40 - FI: 40 + DE: 58 lower_threshold_ambient_temperature: 0 upper_threshold_ambient_temperature: 10 rolling_window_ambient_temperature: 72 diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 32c9eed7..97db4baf 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -9,8 +9,8 @@ Release Notes ########################################## .. Upcoming Release -.. ================ +* Updated district heating supply temperatures based on `Euroheat's DHC Market Outlook 2024`__ and `AGFW-Hauptbericht 2022 `__. `min_forward_temperature` and `return_temperature` (not given by Euroheat) are extrapolated based on German values. * Made the overdimensioning factor for heating systems specific for central/decentral heating, defaults to no overdimensionining for central heating and no changes to decentral heating compared to previous version. * bugfix: The carrier of stores was silently overwritten by their bus_carrier as a side effect when building the co2 constraints diff --git a/scripts/build_central_heating_temperature_profiles/central_heating_temperature_approximator.py b/scripts/build_central_heating_temperature_profiles/central_heating_temperature_approximator.py index 5b467824..67f2c019 100644 --- a/scripts/build_central_heating_temperature_profiles/central_heating_temperature_approximator.py +++ b/scripts/build_central_heating_temperature_profiles/central_heating_temperature_approximator.py @@ -58,6 +58,15 @@ class CentralHeatingTemperatureApproximator: rolling_window_ambient_temperature : int Rolling window size for averaging ambient temperature. """ + + if any(max_forward_temperature < min_forward_temperature): + raise ValueError( + "max_forward_temperature must be greater than min_forward_temperature" + ) + if any(min_forward_temperature < fixed_return_temperature): + raise ValueError( + "min_forward_temperature must be greater than fixed_return_temperature" + ) self._ambient_temperature = ambient_temperature self.max_forward_temperature = max_forward_temperature self.min_forward_temperature = min_forward_temperature diff --git a/scripts/build_central_heating_temperature_profiles/run.py b/scripts/build_central_heating_temperature_profiles/run.py index 115293e4..feb4ab4f 100644 --- a/scripts/build_central_heating_temperature_profiles/run.py +++ b/scripts/build_central_heating_temperature_profiles/run.py @@ -9,8 +9,8 @@ al. 2019, where for ambient temperatures below 0C, the highest possible forward temperature is assumed and vice versa for temperatures above 10C. Between these threshold levels, forward temperatures are linearly interpolated. -By default, temperature levels are increased for non-Scandinavian countries. -The default ratios between min. and max. forward temperatures is based on AGFW-Hauptbericht 2022. +By default, `max_forward_temperature` from Euroheat DHC Market Outlook 2024 is used; `min_forward_temperature` and `return_temperature` for Germany is used from AGFW-Hauptbericht 2022. +`min_forward_temperature` and `return_temperature` for other countries are extrapolated based on the ratio between `max_forward_temperature` and `min_forward_temperature` and `return_temperature` for those countries not missing (by default only Germany). Relevant Settings ----------------- @@ -47,26 +47,68 @@ from central_heating_temperature_approximator import ( ) +def extrapolate_missing_supply_temperatures_by_country( + extrapolate_from: dict, extrapolate_to: dict +) -> xr.DataArray: + """ + Extrapolates missing supply temperatures by country. + + Parameters: + extrapolate_from (dict): A dictionary containing supply temperatures to extrapolate from. Should contain all countries. + extrapolate_to (dict): A dictionary containing supply temperatures to extrapolate to. Where `country` is present, average ratio between `extrapolate_to[country]` and `extrapolate_from[country]` is applied to all countries for which `country` is not present in `extrapolate_from.keys()` to infer ratio for extrapolation. + + Returns: + xr.DataArray: A DataArray containing the extrapolated supply temperatures. + """ + + if not all([key in extrapolate_from.keys() for key in extrapolate_to.keys()]): + raise ValueError( + "Not all countries in extrapolate_to are present in extrapolate_from." + ) + # average ratio between extrapolate_from and extrapolate_to for those countries that are in both dictionaries + extrapolation_ratio = np.mean( + [extrapolate_to[key] / extrapolate_from[key] for key in extrapolate_to.keys()] + ) + + # apply extrapolation ratio to all keys missing in extrapolate_to + return { + key: ( + extrapolate_to[key] + if key in extrapolate_to.keys() + else extrapolate_from[key] * extrapolation_ratio + ) + for key in extrapolate_from.keys() + } + + def get_country_from_node_name(node_name: str) -> str: + """ + Extracts the country code from a given node name. + + Parameters: + node_name (str): The name of the node. + + Returns: + str: The country code extracted from the node name. + """ return node_name[:2] 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. + Missing values are replaced by the mean of all values. + Parameters: ---------- supply_temperature_by_country : dictionary - Dictionary with temperatures as values and country keys as keys. One key must be named "default" + Dictionary with temperatures as values and country keys as keys. regions_onshore : pd.Index Names of onshore regions - snapshots : pd.DatetimeIndex - Time stamps Returns: ------- @@ -75,20 +117,16 @@ def map_temperature_dict_to_onshore_regions( """ 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 + ( + 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 np.mean(list(supply_temperature_by_country.values())) + ) + for node_name in regions_onshore.values ], - dims=["time", "name"], - coords={"time": snapshots, "name": regions_onshore}, + dims=["name"], + coords={"name": regions_onshore}, ) @@ -104,28 +142,35 @@ if __name__ == "__main__": set_scenario_config(snakemake) + max_forward_temperature = snakemake.params.max_forward_temperature_central_heating + min_forward_temperature = extrapolate_missing_supply_temperatures_by_country( + extrapolate_from=max_forward_temperature, + extrapolate_to=snakemake.params.min_forward_temperature_central_heating, + ) + return_temperature = extrapolate_missing_supply_temperatures_by_country( + extrapolate_from=max_forward_temperature, + extrapolate_to=snakemake.params.return_temperature_central_heating, + ) + # 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) max_forward_temperature_central_heating_by_node_and_time: xr.DataArray = ( map_temperature_dict_to_onshore_regions( - supply_temperature_by_country=snakemake.params.max_forward_temperature_central_heating, + supply_temperature_by_country=max_forward_temperature, regions_onshore=regions_onshore, - snapshots=snapshots, ) ) min_forward_temperature_central_heating_by_node_and_time: xr.DataArray = ( map_temperature_dict_to_onshore_regions( - supply_temperature_by_country=snakemake.params.min_forward_temperature_central_heating, + supply_temperature_by_country=min_forward_temperature, 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, + supply_temperature_by_country=return_temperature, regions_onshore=regions_onshore, - snapshots=snapshots, ) )