Skip to content

Commit

Permalink
Merge pull request PyPSA#1290 from PyPSA/annual-temperature-reduction
Browse files Browse the repository at this point in the history
Add option to reduce central heating supply temperatures annually (defaults to 1%/a)
  • Loading branch information
amos-schledorn authored Oct 10, 2024
2 parents 6dd41b8 + 2e6132c commit 5402088
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 35 deletions.
7 changes: 4 additions & 3 deletions config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ sector:
2050: 1.0
district_heating_loss: 0.15
supply_temperature_approximation:
max_forward_temperature:
max_forward_temperature_baseyear:
FR: 110
DK: 75
DE: 109
Expand All @@ -465,13 +465,14 @@ sector:
PL: 130
SE: 102
IT: 90
min_forward_temperature:
min_forward_temperature_baseyear:
DE: 82
return_temperature:
return_temperature_baseyear:
DE: 58
lower_threshold_ambient_temperature: 0
upper_threshold_ambient_temperature: 10
rolling_window_ambient_temperature: 72
relative_annual_temperature_reduction: 0.01
heat_source_cooling: 6 #K
heat_pump_cop_approximation:
refrigerant: ammonia
Expand Down
7 changes: 4 additions & 3 deletions doc/configtables/sector.csv
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ district_heating,--,,`prepare_sector_network.py <https://github.com/PyPSA/pypsa-
-- 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
-- supply_temperature_approximation,,,
-- -- max_forward_temperature,°C,Dictionary with country codes as keys. One key must be 'default'., Max. forward temperature in district heating (if ambient temperature lower-or-equal `lower_threshold_ambient_temperature`)
-- -- min_forward_temperature,°C,Dictionary with country codes as keys. One key must be 'default'., Min. forward temperature in district heating (if ambient temperature higher-or-equal `upper_threshold_ambient_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
-- -- max_forward_temperature_baseyear,°C,Dictionary with country codes as keys. One key must be 'default'., Max. forward temperature in district heating in baseyear (if ambient temperature lower-or-equal `lower_threshold_ambient_temperature`)
-- -- min_forward_temperature_baseyear,°C,Dictionary with country codes as keys. One key must be 'default'., Min. forward temperature in district heating in baseyear (if ambient temperature higher-or-equal `upper_threshold_ambient_temperature`)
-- -- return_temperature_baseyear,°C,Dictionary with country codes as keys. One key must be 'default'.,Return temperature in district heating in baseyear . Must be lower than forward temperature
-- -- lower_threshold_ambient_temperature,°C,float, Assume `max_forward_temperature` if ambient temperature is below this threshold
-- -- upper_threshold_ambient_temperature,°C,float, Assume `min_forward_temperature` if ambient temperature is above this threshold
-- -- rolling_window_ambient_temperature, h, int, Rolling window size for averaging ambient temperature when approximating supply temperature
-- -- relative_annual_temperature_reduction,, float, Relative annual reduction of district heating forward and return temperature - defaults to 0.01 (1%)
-- heat_source_cooling,K,float,Cooling of heat source for heat pumps
-- heat_pump_cop_approximation,,,
-- -- refrigerant,--,"{ammonia, isobutane}",Heat pump refrigerant assumed for COP approximation
Expand Down
1 change: 1 addition & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Release Notes
Upcoming Release
================

* Added option to reduce central heating forward temperatures by annual percentage (see rule :mod:`build_central_heating_temperature_profiles`). This makes COP profiles and heat pump efficiencies planning-horizon-dependent. Myopic and perfect foresight modes were adjusted accordingly to update COPs of existing heat pumps in preceding years to adjusted temperatures.

* Rearranged workflow to cluster the electricity network before calculating
renewable profiles and adding further electricity system components.
Expand Down
43 changes: 27 additions & 16 deletions rules/build_sector.smk
Original file line number Diff line number Diff line change
Expand Up @@ -214,23 +214,23 @@ rule build_temperature_profiles:

rule build_central_heating_temperature_profiles:
params:
max_forward_temperature_central_heating=config_provider(
max_forward_temperature_central_heating_baseyear=config_provider(
"sector",
"district_heating",
"supply_temperature_approximation",
"max_forward_temperature",
"max_forward_temperature_baseyear",
),
min_forward_temperature_central_heating=config_provider(
min_forward_temperature_central_heating_baseyear=config_provider(
"sector",
"district_heating",
"supply_temperature_approximation",
"min_forward_temperature",
"min_forward_temperature_baseyear",
),
return_temperature_central_heating=config_provider(
return_temperature_central_heating_baseyear=config_provider(
"sector",
"district_heating",
"supply_temperature_approximation",
"return_temperature",
"return_temperature_baseyear",
),
snapshots=config_provider("snapshots"),
lower_threshold_ambient_temperature=config_provider(
Expand All @@ -251,22 +251,33 @@ rule build_central_heating_temperature_profiles:
"supply_temperature_approximation",
"rolling_window_ambient_temperature",
),
relative_annual_temperature_reduction=config_provider(
"sector",
"district_heating",
"supply_temperature_approximation",
"relative_annual_temperature_reduction",
),
energy_totals_year=config_provider("energy", "energy_totals_year"),
input:
temp_air_total=resources("temp_air_total_base_s_{clusters}.nc"),
regions_onshore=resources("regions_onshore_base_s_{clusters}.geojson"),
output:
central_heating_forward_temperature_profiles=resources(
"central_heating_forward_temperature_profiles_base_s_{clusters}.nc"
"central_heating_forward_temperature_profiles_base_s_{clusters}_{planning_horizons}.nc"
),
central_heating_return_temperature_profiles=resources(
"central_heating_return_temperature_profiles_base_s_{clusters}.nc"
"central_heating_return_temperature_profiles_base_s_{clusters}_{planning_horizons}.nc"
),
resources:
mem_mb=20000,
log:
logs("build_central_heating_temperature_profiles_s_{clusters}.log"),
logs(
"build_central_heating_temperature_profiles_s_{clusters}_{planning_horizons}.log"
),
benchmark:
benchmarks("build_central_heating_temperature_profiles/s_{clusters}")
benchmarks(
"build_central_heating_temperature_profiles/s_{clusters}_{planning_horizons}"
)
conda:
"../envs/environment.yaml"
script:
Expand All @@ -288,22 +299,22 @@ rule build_cop_profiles:
snapshots=config_provider("snapshots"),
input:
central_heating_forward_temperature_profiles=resources(
"central_heating_forward_temperature_profiles_base_s_{clusters}.nc"
"central_heating_forward_temperature_profiles_base_s_{clusters}_{planning_horizons}.nc"
),
central_heating_return_temperature_profiles=resources(
"central_heating_return_temperature_profiles_base_s_{clusters}.nc"
"central_heating_return_temperature_profiles_base_s_{clusters}_{planning_horizons}.nc"
),
temp_soil_total=resources("temp_soil_total_base_s_{clusters}.nc"),
temp_air_total=resources("temp_air_total_base_s_{clusters}.nc"),
regions_onshore=resources("regions_onshore_base_s_{clusters}.geojson"),
output:
cop_profiles=resources("cop_profiles_base_s_{clusters}.nc"),
cop_profiles=resources("cop_profiles_base_s_{clusters}_{planning_horizons}.nc"),
resources:
mem_mb=20000,
log:
logs("build_cop_profiles_s_{clusters}.log"),
logs("build_cop_profiles_s_{clusters}_{planning_horizons}.log"),
benchmark:
benchmarks("build_cop_profiles/s_{clusters}")
benchmarks("build_cop_profiles/s_{clusters}_{planning_horizons}")
conda:
"../envs/environment.yaml"
script:
Expand Down Expand Up @@ -1092,7 +1103,7 @@ rule prepare_sector_network:
heating_efficiencies=resources("heating_efficiencies.csv"),
temp_soil_total=resources("temp_soil_total_base_s_{clusters}.nc"),
temp_air_total=resources("temp_air_total_base_s_{clusters}.nc"),
cop_profiles=resources("cop_profiles_base_s_{clusters}.nc"),
cop_profiles=resources("cop_profiles_base_s_{clusters}_{planning_horizons}.nc"),
solar_thermal_total=lambda w: (
resources("solar_thermal_total_base_s_{clusters}.nc")
if config_provider("sector", "solar_thermal")(w)
Expand Down
4 changes: 2 additions & 2 deletions rules/solve_myopic.smk
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ rule add_existing_baseyear:
config_provider("scenario", "planning_horizons", 0)(w)
)
),
cop_profiles=resources("cop_profiles_base_s_{clusters}.nc"),
cop_profiles=resources("cop_profiles_base_s_{clusters}_{planning_horizons}.nc"),
existing_heating_distribution=resources(
"existing_heating_distribution_base_s_{clusters}_{planning_horizons}.csv"
),
Expand Down Expand Up @@ -80,7 +80,7 @@ rule add_brownfield:
+ "prenetworks/base_s_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc",
network_p=solved_previous_horizon, #solved network at previous time step
costs=resources("costs_{planning_horizons}.csv"),
cop_profiles=resources("cop_profiles_base_s_{clusters}.nc"),
cop_profiles=resources("cop_profiles_base_s_{clusters}_{planning_horizons}.nc"),
output:
RESULTS
+ "prenetworks-brownfield/base_s_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc",
Expand Down
2 changes: 1 addition & 1 deletion rules/solve_perfect.smk
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ rule add_existing_baseyear:
config_provider("scenario", "planning_horizons", 0)(w)
)
),
cop_profiles=resources("cop_profiles_base_s_{clusters}.nc"),
cop_profiles=resources("cop_profiles_base_s_{clusters}_{planning_horizons}.nc"),
existing_heating_distribution=resources(
"existing_heating_distribution_base_s_{clusters}_{planning_horizons}.csv"
),
Expand Down
36 changes: 36 additions & 0 deletions scripts/add_brownfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,40 @@ def adjust_renewable_profiles(n, input_profiles, params, year):
n.generators_t.p_max_pu.loc[:, p_max_pu.columns] = p_max_pu


def update_heat_pump_efficiency(n: pypsa.Network, n_p: pypsa.Network, year: int):
"""
Update the efficiency of heat pumps from previous years to current year
(e.g. 2030 heat pumps receive 2040 heat pump COPs in 2030).
Parameters
----------
n : pypsa.Network
The original network.
n_p : pypsa.Network
The network with the updated parameters.
year : int
The year for which the efficiency is being updated.
Returns
-------
None
This function updates the efficiency in place and does not return a value.
"""

# get names of heat pumps in previous iteration
heat_pump_idx_previous_iteration = n_p.links.index[
n_p.links.index.str.contains("heat pump")
]
# construct names of same-technology heat pumps in the current iteration
corresponding_idx_this_iteration = heat_pump_idx_previous_iteration.str[:-4] + str(
year
)
# update efficiency of heat pumps in previous iteration in-place to efficiency in this iteration
n_p.links_t["efficiency"].loc[:, heat_pump_idx_previous_iteration] = (
n.links_t["efficiency"].loc[:, corresponding_idx_this_iteration].values
)


if __name__ == "__main__":
if "snakemake" not in globals():
from _helpers import mock_snakemake
Expand Down Expand Up @@ -251,6 +285,8 @@ def adjust_renewable_profiles(n, input_profiles, params, year):

n_p = pypsa.Network(snakemake.input.network_p)

update_heat_pump_efficiency(n, n_p, year)

add_brownfield(n, n_p, year)

disable_grid_expansion_if_limit_hit(n)
Expand Down
103 changes: 93 additions & 10 deletions scripts/build_central_heating_temperature_profiles/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,45 +130,128 @@ def map_temperature_dict_to_onshore_regions(
)


def scale_temperature_to_investment_year(
temperature_baseyear: dict,
relative_annual_temperature_reduction: float,
investment_year: int,
current_year: int,
) -> dict:
"""
Scale temperature for each country to investment year by constant
exponential factor.
Parameters
----------
temperature_baseyear : dict
Dictionary with the current temperatures as values and country keys as keys.
relative_annual_temperature_reduction : float
The annual reduction rate of the temperature, must be between 0 and 1.
investment_year : int
The year in which the investment is planned.
current_year : int
The current year.
Returns
-------
dict
A dictionary with the temperature for each country in the investment year.
Raises
------
ValueError
If the investment year is before the current year.
If the relative annual temperature reduction is not between 0 and 1.
"""

if investment_year < current_year:
raise ValueError(
f"Error: Investment year {investment_year} is before current year {current_year}."
)
if (
relative_annual_temperature_reduction < 0
or relative_annual_temperature_reduction > 1
):
raise ValueError(
f"Error: Relative annual temperature reduction must be between 0 and 1, but is {relative_annual_temperature_reduction}."
)

return {
key: temperature_baseyear[key]
* (
(1 - relative_annual_temperature_reduction)
** (investment_year - current_year)
)
for key in temperature_baseyear.keys()
}


if __name__ == "__main__":
if "snakemake" not in globals():
from _helpers import mock_snakemake

snakemake = mock_snakemake(
"build_cop_profiles",
clusters=48,
planning_horizons="2050",
)

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,
# reduce temperatures to investment years by constant exponential factor
max_forward_temperature_investment_year = scale_temperature_to_investment_year(
temperature_baseyear=snakemake.params.max_forward_temperature_central_heating_baseyear,
relative_annual_temperature_reduction=snakemake.params.relative_annual_temperature_reduction,
investment_year=int(snakemake.wildcards.planning_horizons),
current_year=int(snakemake.params.energy_totals_year),
)

min_forward_temperature_investment_year = scale_temperature_to_investment_year(
temperature_baseyear=snakemake.params.min_forward_temperature_central_heating_baseyear,
relative_annual_temperature_reduction=snakemake.params.relative_annual_temperature_reduction,
investment_year=int(snakemake.wildcards.planning_horizons),
current_year=int(snakemake.params.energy_totals_year),
)
return_temperature = extrapolate_missing_supply_temperatures_by_country(
extrapolate_from=max_forward_temperature,
extrapolate_to=snakemake.params.return_temperature_central_heating,

return_temperature_investment_year = scale_temperature_to_investment_year(
temperature_baseyear=snakemake.params.return_temperature_central_heating_baseyear,
relative_annual_temperature_reduction=snakemake.params.relative_annual_temperature_reduction,
investment_year=int(snakemake.wildcards.planning_horizons),
current_year=int(snakemake.params.energy_totals_year),
)

# min_forward_temperature and return_temperature contain only values for Germany by default
# extrapolate missing values based on ratio between max_forward_temperature and min_forward_temperature / return_temperature for Germany (or other countries provided in min_forward_temperature_baseyear and return_temperature_baseyear)
min_forward_temperature_investment_year = (
extrapolate_missing_supply_temperatures_by_country(
extrapolate_from=max_forward_temperature_investment_year,
extrapolate_to=min_forward_temperature_investment_year,
)
)
return_temperature_investment_year = (
extrapolate_missing_supply_temperatures_by_country(
extrapolate_from=max_forward_temperature_investment_year,
extrapolate_to=return_temperature_investment_year,
)
)

# 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=max_forward_temperature,
supply_temperature_by_country=max_forward_temperature_investment_year,
regions_onshore=regions_onshore,
)
)
min_forward_temperature_central_heating_by_node_and_time: xr.DataArray = (
map_temperature_dict_to_onshore_regions(
supply_temperature_by_country=min_forward_temperature,
supply_temperature_by_country=min_forward_temperature_investment_year,
regions_onshore=regions_onshore,
)
)
return_temperature_central_heating_by_node_and_time: xr.DataArray = (
map_temperature_dict_to_onshore_regions(
supply_temperature_by_country=return_temperature,
supply_temperature_by_country=return_temperature_investment_year,
regions_onshore=regions_onshore,
)
)
Expand Down
Loading

0 comments on commit 5402088

Please sign in to comment.