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 option to reduce central heating supply temperatures annually (defaults to 1%/a) #1290

Merged
merged 29 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7798e12
feat: add temperature reduction to workflow
amos-schledorn Sep 12, 2024
c252d17
chore: fix name change issues
amos-schledorn Sep 12, 2024
a83909c
feat: update input/output of dependent rules
amos-schledorn Sep 12, 2024
31e6e3a
feat: add {planning _horizons} wildcard to COP input in myopic rules
amos-schledorn Sep 12, 2024
85656c0
fix: exponential temperature reduction
amos-schledorn Sep 12, 2024
881cf01
feat: add {planning_horizons} wildcard to solve_perfect
amos-schledorn Sep 12, 2024
cdfcf23
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 12, 2024
69cad8e
style: "_today" -> "_baseyear"
amos-schledorn Sep 12, 2024
39ae475
doc: update configtables
amos-schledorn Sep 12, 2024
d5c6135
style: "_today" -> "_baseyear" (config)
amos-schledorn Sep 12, 2024
6c112e3
Merge remote-tracking branch 'origin/annual-temperature-reduction' in…
amos-schledorn Sep 12, 2024
a109899
feat: update heat pump efficiency in add_brownfield
amos-schledorn Sep 13, 2024
d218a08
Merge remote-tracking branch 'origin/master' into annual-temperature-…
amos-schledorn Sep 17, 2024
a5be8b4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 17, 2024
7bf57ad
fix: pass planning_horizons to add_brownfield
amos-schledorn Sep 17, 2024
94d628f
Merge branch 'master' into annual-temperature-reduction
amos-schledorn Sep 17, 2024
8871a2d
feat: update COPs for perfect foresight
amos-schledorn Sep 17, 2024
4107e31
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 17, 2024
30f7dce
update release notes
amos-schledorn Sep 17, 2024
bf87e8c
Merge remote-tracking branch 'origin/annual-temperature-reduction' in…
amos-schledorn Sep 17, 2024
635d3d6
Update doc/release_notes.rst
amos-schledorn Sep 30, 2024
f1f0d20
Update scripts/build_central_heating_temperature_profiles/run.py
amos-schledorn Sep 30, 2024
ecbb228
Update scripts/build_central_heating_temperature_profiles/run.py
amos-schledorn Sep 30, 2024
bdf1d7d
Merge branch 'master' into annual-temperature-reduction
amos-schledorn Sep 30, 2024
85a878b
Update build_central_heating_temperature_profiles.run
amos-schledorn Sep 30, 2024
b27227f
style: simplify update_heat_pump_efficiency functions
amos-schledorn Sep 30, 2024
a9853b0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 30, 2024
9950384
Merge branch 'master' into annual-temperature-reduction
amos-schledorn Oct 1, 2024
2e6132c
Merge branch 'master' into annual-temperature-reduction
fneum Oct 10, 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
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
Loading