Skip to content

Commit

Permalink
Merge pull request #1180 from PyPSA/country-specific-dh-forward-tempe…
Browse files Browse the repository at this point in the history
…ratures

Add option of country-specific district heating supply temperatures
  • Loading branch information
lisazeyen authored Aug 16, 2024
2 parents eb16d74 + 1875905 commit 31c8907
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 26 deletions.
15 changes: 13 additions & 2 deletions config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions doc/configtables/sector.csv
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ district_heating,--,,`prepare_sector_network.py <https://github.com/PyPSA/pypsa-
-- potential,--,float,maximum fraction of urban demand which can be supplied by district heating
-- 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
-- forward_temperature,°C,float,Forward temperature in district heating
-- return_temperature,°C,float,Return temperature in district heating. Must be lower than forward temperature
-- forward_temperature,°C,Dictionary with country codes as keys. One key must be 'default'.,Forward temperature in district heating
-- 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
-- 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
2 changes: 2 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Upcoming Release

* Update JRC-IDEES-2015 to `JRC-IDEES-2021 <https://publications.jrc.ec.europa.eu/repository/handle/JRC137809>`__. 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``.
Expand Down
2 changes: 2 additions & 0 deletions rules/build_sector.smk
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 3 additions & 4 deletions scripts/add_existing_baseyear.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@
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
Expand Down
108 changes: 105 additions & 3 deletions scripts/build_cop_profiles/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <CentralHeatingCopApproximator.py>`_) and for decentral heating, the approximation is based on Staffell et al. (2012) (c.f. `DecentralHeatingCopApproximator <DecentralHeatingCopApproximator.py>`_).
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/<run_name>/regions_onshore.geojson`: Onshore regions
- `resources/<run_name>/temp_soil_total`: Ground temperature
- `resources/<run_name>/temp_air_total`: Air temperature
Outputs
-------
- `resources/<run_name>/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
Expand All @@ -14,13 +52,54 @@

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.
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -68,6 +151,23 @@ def get_cop(

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 = []
Expand All @@ -79,6 +179,8 @@ def get_cop(
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(
Expand Down
38 changes: 23 additions & 15 deletions scripts/prepare_sector_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,16 @@
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
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__)

Expand Down Expand Up @@ -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,
Expand All @@ -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):
Expand Down

0 comments on commit 31c8907

Please sign in to comment.