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

initial dsm implementation for the heating sector #1371

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,9 @@ sector:
rural:
- air
- ground
residential_heat_dsm: false
residential_heat_restriction_value: 0.27
residential_heat_restriction_time: [10, 22] # 9am and 9pm
cluster_heat_buses: true
heat_demand_cutout: default
bev_dsm_restriction_value: 0.75
Expand Down
7 changes: 5 additions & 2 deletions doc/configtables/sector.csv
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ district_heating,--,,`prepare_sector_network.py <https://github.com/PyPSA/pypsa-
-- -- urban central,--,List of heat sources for heat pumps in urban central heating,
-- -- urban decentral,--,List of heat sources for heat pumps in urban decentral heating,
-- -- rural,--,List of heat sources for heat pumps in rural heating,
residential_heat_dsm,--,"{true, false}", Add the option for heat pumps (HPs) to participate in demand-side management (DSM),
residential_heat_restriction_value,--,float,Adds an upper state of charge (SOC) limit for heat pumps (HPs) to manage its own energy demand (DSM). Located in `build_hourly_heat_demand.py <https://github.com/PyPSA/pypsa-eur/blob/master/scripts/build_hourly_heat_demand.py>`_. Set to 0 for full restriction on HP DSM, default value is 0.27,
residential_heat_restriction_time,--,list of int, Time at which SOC of HPs has to be residential_heat_restriction_value. Defaults to [10, 22] corresponding to 9am and 9pm,
cluster_heat_buses,--,"{true, false}",Cluster residential and service heat buses in `prepare_sector_network.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/prepare_sector_network.py>`_ to one to save memory.
,,,
bev_dsm_restriction _value,--,float,Adds a lower state of charge (SOC) limit for battery electric vehicles (BEV) to manage its own energy demand (DSM). Located in `build_transport_demand.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/build_transport_demand.py>`_. Set to 0 for no restriction on BEV DSM
bev_dsm_restriction _time,--,float,Time at which SOC of BEV has to be dsm_restriction_value
bev_dsm_restriction_value,--,float,Adds a lower state of charge (SOC) limit for battery electric vehicles (BEV) to manage its own energy demand (DSM). Located in `build_transport_demand.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/build_transport_demand.py>`_. Set to 0 for no restriction on BEV DSM,
bev_dsm_restriction_time,--,float,Time at which SOC of BEV has to be bev_dsm_restriction_value,
transport_heating _deadband_upper,°C,float,"The maximum temperature in the vehicle. At higher temperatures, the energy required for cooling in the vehicle increases."
transport_heating _deadband_lower,°C,float,"The minimum temperature in the vehicle. At lower temperatures, the energy required for heating in the vehicle increases."
,,,
Expand Down
2 changes: 2 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Upcoming Release

* Bugfix: Bug when multiple DC links are connected to the same DC bus and the DC bus is connected to an AC bus via converter. In this case, the DC links were wrongly simplified, completely dropping the shared DC bus. Bug fixed by adding preceding converter removal. Other functionalities are not impacted.

* Add demand-side-response (DSR) for the heating sector.

PyPSA-Eur 0.13.0 (13th September 2024)
======================================

Expand Down
7 changes: 7 additions & 0 deletions rules/build_sector.smk
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,15 @@ rule build_hourly_heat_demand:
params:
snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"),
sector=config_provider("sector"),
input:
heat_profile="data/heat_load_profile_BDEW.csv",
heat_demand=resources("daily_heat_demand_total_base_s_{clusters}.nc"),
output:
heat_demand=resources("hourly_heat_demand_total_base_s_{clusters}.nc"),
heat_dsm_profile=resources(
"residential_heat_dsm_profile_total_base_s_{clusters}.csv"
),
resources:
mem_mb=2000,
threads: 8
Expand Down Expand Up @@ -1069,6 +1073,9 @@ rule prepare_sector_network:
transport_data=resources("transport_data_s_{clusters}.csv"),
avail_profile=resources("avail_profile_s_{clusters}.csv"),
dsm_profile=resources("dsm_profile_s_{clusters}.csv"),
heat_dsm_profile=resources(
"residential_heat_dsm_profile_total_base_s_{clusters}.csv"
),
co2_totals_name=resources("co2_totals.csv"),
co2="data/bundle/eea/UNFCCC_v23.csv",
biomass_potentials=resources(
Expand Down
26 changes: 26 additions & 0 deletions scripts/build_hourly_heat_demand.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,27 @@

from itertools import product

import numpy as np
import pandas as pd
import xarray as xr
from _helpers import generate_periodic_profiles, get_snapshots, set_scenario_config


def heat_dsm_profile(nodes, options):

weekly_profile = np.ones((24 * 7))
for i in options["residential_heat_restriction_time"]:
weekly_profile[(np.arange(0, 7, 1) * 24 + int(i))] = 0

dsm_profile = generate_periodic_profiles(
dt_index=pd.date_range(freq="h", **snakemake.params.snapshots, tz="UTC"),
nodes=nodes,
weekly_profile=weekly_profile,
)

return dsm_profile


if __name__ == "__main__":
if "snakemake" not in globals():
from _helpers import mock_snakemake
Expand All @@ -51,6 +68,8 @@
snakemake.params.snapshots, snakemake.params.drop_leap_day
)

options = snakemake.params.sector

daily_space_heat_demand = (
xr.open_dataarray(snakemake.input.heat_demand)
.to_pandas()
Expand All @@ -63,6 +82,7 @@
uses = ["water", "space"]

heat_demand = {}
dsm_profile = {}
for sector, use in product(sectors, uses):
weekday = list(intraday_profiles[f"{sector} {use} weekday"])
weekend = list(intraday_profiles[f"{sector} {use} weekend"])
Expand All @@ -77,13 +97,19 @@
heat_demand[f"{sector} {use}"] = (
daily_space_heat_demand * intraday_year_profile
)
if sector == "residential":
dsm_profile[f"{sector} {use}"] = heat_dsm_profile(
daily_space_heat_demand.columns, options
)
else:
heat_demand[f"{sector} {use}"] = intraday_year_profile

heat_demand = pd.concat(heat_demand, axis=1, names=["sector use", "node"])
dsm_profile = pd.concat(dsm_profile, axis=1, names=["sector use", "node"])

heat_demand.index.name = "snapshots"

ds = heat_demand.stack(future_stack=True).to_xarray()

ds.to_netcdf(snakemake.output.heat_demand)
dsm_profile.to_csv(snakemake.output.heat_dsm_profile)
44 changes: 44 additions & 0 deletions scripts/prepare_sector_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -2143,6 +2143,50 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray):
p_set=heat_load.loc[n.snapshots],
)

if options["residential_heat_dsm"] and heat_system in [
HeatSystem.RESIDENTIAL_RURAL,
HeatSystem.RESIDENTIAL_URBAN_DECENTRAL,
HeatSystem.URBAN_CENTRAL,
]:
factor = heat_system.heat_demand_weighting(
urban_fraction=urban_fraction[nodes], dist_fraction=dist_fraction[nodes]
)

heat_dsm_profile = pd.read_csv(
snakemake.input.heat_dsm_profile, header=[1], index_col=[0]
)[nodes]
heat_dsm_profile.index = n.snapshots

e_nom = (
heat_demand[["residential space"]]
.T.groupby(level=1)
.sum()
.T[nodes]
.multiply(factor)
)

heat_dsm_profile = (
heat_dsm_profile * options["residential_heat_restriction_value"]
)
e_nom = e_nom.max()

# Thermal (standing) losses of buildings assumed to be the same as decentralized water tanks
tes_time_constant_days = options["tes_tau"]["decentral"]

n.madd(
"Store",
nodes,
suffix=f" {heat_system} heat flexibility",
bus=nodes + f" {heat_system} heat",
carrier="residential heating flexibility",
standing_loss=1 - np.exp(-1 / 24 / tes_time_constant_days),
e_cyclic=True,
e_nom=e_nom,
e_max_pu=heat_dsm_profile,
)

logger.info(f"adding heat dsm in {heat_system} heating.")

## Add heat pumps
for heat_source in snakemake.params.heat_pump_sources[
heat_system.system_type.value
Expand Down
Loading