Merge pull request #1180 from PyPSA/country-specific-dh-forward-temperatures
Add option of country-specific district heating supply temperatures
This commit is contained in:
commit
31c8907370
@ -448,8 +448,19 @@ sector:
|
|||||||
2045: 0.8
|
2045: 0.8
|
||||||
2050: 1.0
|
2050: 1.0
|
||||||
district_heating_loss: 0.15
|
district_heating_loss: 0.15
|
||||||
forward_temperature: 90 #C
|
# check these numbers!
|
||||||
return_temperature: 50 #C
|
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_source_cooling: 6 #K
|
||||||
heat_pump_cop_approximation:
|
heat_pump_cop_approximation:
|
||||||
refrigerant: ammonia
|
refrigerant: ammonia
|
||||||
|
@ -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
|
-- 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
|
-- 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
|
-- 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
|
-- forward_temperature,°C,Dictionary with country codes as keys. One key must be 'default'.,Forward temperature in district heating
|
||||||
-- return_temperature,°C,float,Return temperature in district heating. Must be lower than forward 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
|
||||||
-- heat_source_cooling,K,float,Cooling of heat source for heat pumps
|
-- heat_source_cooling,K,float,Cooling of heat source for heat pumps
|
||||||
-- heat_pump_cop_approximation,,,
|
-- heat_pump_cop_approximation,,,
|
||||||
-- -- refrigerant,--,"{ammonia, isobutane}",Heat pump refrigerant assumed for COP approximation
|
-- -- refrigerant,--,"{ammonia, isobutane}",Heat pump refrigerant assumed for COP approximation
|
||||||
|
|
@ -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.
|
* 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
|
* 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
|
substituted by the phase-in of sustainable biomass types using the config parameters
|
||||||
``biomass: share_unsustainable_use_retained`` and ``biomass: share_sustainable_potential_available``.
|
``biomass: share_unsustainable_use_retained`` and ``biomass: share_sustainable_potential_available``.
|
||||||
|
@ -233,9 +233,11 @@ rule build_cop_profiles:
|
|||||||
"sector", "district_heating", "heat_pump_cop_approximation"
|
"sector", "district_heating", "heat_pump_cop_approximation"
|
||||||
),
|
),
|
||||||
heat_pump_sources=config_provider("sector", "heat_pump_sources"),
|
heat_pump_sources=config_provider("sector", "heat_pump_sources"),
|
||||||
|
snapshots=config_provider("snapshots"),
|
||||||
input:
|
input:
|
||||||
temp_soil_total=resources("temp_soil_total_elec_s{simpl}_{clusters}.nc"),
|
temp_soil_total=resources("temp_soil_total_elec_s{simpl}_{clusters}.nc"),
|
||||||
temp_air_total=resources("temp_air_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:
|
output:
|
||||||
cop_profiles=resources("cop_profiles_elec_s{simpl}_{clusters}.nc"),
|
cop_profiles=resources("cop_profiles_elec_s{simpl}_{clusters}.nc"),
|
||||||
resources:
|
resources:
|
||||||
|
@ -22,12 +22,11 @@ from _helpers import (
|
|||||||
update_config_from_wildcards,
|
update_config_from_wildcards,
|
||||||
)
|
)
|
||||||
from add_electricity import sanitize_carriers
|
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 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__)
|
logger = logging.getLogger(__name__)
|
||||||
cc = coco.CountryConverter()
|
cc = coco.CountryConverter()
|
||||||
idx = pd.IndexSlice
|
idx = pd.IndexSlice
|
||||||
|
@ -2,9 +2,47 @@
|
|||||||
# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors
|
# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# 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 sys
|
||||||
|
|
||||||
|
import geopandas as gpd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import xarray as xr
|
import xarray as xr
|
||||||
@ -14,13 +52,54 @@ from DecentralHeatingCopApproximator import DecentralHeatingCopApproximator
|
|||||||
|
|
||||||
from scripts.definitions.heat_system_type import HeatSystemType
|
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(
|
def get_cop(
|
||||||
heat_system_type: str,
|
heat_system_type: str,
|
||||||
heat_source: str,
|
heat_source: str,
|
||||||
source_inlet_temperature_celsius: xr.DataArray,
|
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:
|
) -> xr.DataArray:
|
||||||
"""
|
"""
|
||||||
Calculate the coefficient of performance (COP) for a heating system.
|
Calculate the coefficient of performance (COP) for a heating system.
|
||||||
@ -41,8 +120,8 @@ def get_cop(
|
|||||||
"""
|
"""
|
||||||
if HeatSystemType(heat_system_type).is_central:
|
if HeatSystemType(heat_system_type).is_central:
|
||||||
return CentralHeatingCopApproximator(
|
return CentralHeatingCopApproximator(
|
||||||
forward_temperature_celsius=snakemake.params.forward_temperature_central_heating,
|
forward_temperature_celsius=forward_temperature_by_node_and_time,
|
||||||
return_temperature_celsius=snakemake.params.return_temperature_central_heating,
|
return_temperature_celsius=return_temperature_by_node_and_time,
|
||||||
source_inlet_temperature_celsius=source_inlet_temperature_celsius,
|
source_inlet_temperature_celsius=source_inlet_temperature_celsius,
|
||||||
source_outlet_temperature_celsius=source_inlet_temperature_celsius
|
source_outlet_temperature_celsius=source_inlet_temperature_celsius
|
||||||
- snakemake.params.heat_source_cooling_central_heating,
|
- snakemake.params.heat_source_cooling_central_heating,
|
||||||
@ -56,6 +135,10 @@ def get_cop(
|
|||||||
).approximate_cop()
|
).approximate_cop()
|
||||||
|
|
||||||
|
|
||||||
|
def get_country_from_node_name(node_name: str) -> str:
|
||||||
|
return node_name[:2]
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if "snakemake" not in globals():
|
if "snakemake" not in globals():
|
||||||
from _helpers import mock_snakemake
|
from _helpers import mock_snakemake
|
||||||
@ -68,6 +151,23 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
set_scenario_config(snakemake)
|
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 = []
|
cop_all_system_types = []
|
||||||
for heat_system_type, heat_sources in snakemake.params.heat_pump_sources.items():
|
for heat_system_type, heat_sources in snakemake.params.heat_pump_sources.items():
|
||||||
cop_this_system_type = []
|
cop_this_system_type = []
|
||||||
@ -79,6 +179,8 @@ if __name__ == "__main__":
|
|||||||
heat_system_type=heat_system_type,
|
heat_system_type=heat_system_type,
|
||||||
heat_source=heat_source,
|
heat_source=heat_source,
|
||||||
source_inlet_temperature_celsius=source_inlet_temperature_celsius,
|
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_this_system_type.append(cop_da)
|
||||||
cop_all_system_types.append(
|
cop_all_system_types.append(
|
||||||
|
@ -30,6 +30,9 @@ from build_energy_totals import (
|
|||||||
build_eurostat_co2,
|
build_eurostat_co2,
|
||||||
)
|
)
|
||||||
from build_transport_demand import transport_degree_factor
|
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 import complement
|
||||||
from networkx.algorithms.connectivity.edge_augmentation import k_edge_augmentation
|
from networkx.algorithms.connectivity.edge_augmentation import k_edge_augmentation
|
||||||
from prepare_network import maybe_adjust_costs_and_potentials
|
from prepare_network import maybe_adjust_costs_and_potentials
|
||||||
@ -37,10 +40,6 @@ from pypsa.geo import haversine_pts
|
|||||||
from pypsa.io import import_components_from_dataframe
|
from pypsa.io import import_components_from_dataframe
|
||||||
from scipy.stats import beta
|
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()
|
spatial = SimpleNamespace()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -2831,8 +2830,11 @@ def add_biomass(n, costs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if options["bioH2"]:
|
if options["bioH2"]:
|
||||||
name = (pd.Index(spatial.biomass.nodes) + " "
|
name = (
|
||||||
+ pd.Index(spatial.h2.nodes.str.replace(" H2", "")))
|
pd.Index(spatial.biomass.nodes)
|
||||||
|
+ " "
|
||||||
|
+ pd.Index(spatial.h2.nodes.str.replace(" H2", ""))
|
||||||
|
)
|
||||||
n.madd(
|
n.madd(
|
||||||
"Link",
|
"Link",
|
||||||
name,
|
name,
|
||||||
@ -2842,16 +2844,22 @@ def add_biomass(n, costs):
|
|||||||
bus2=spatial.co2.nodes,
|
bus2=spatial.co2.nodes,
|
||||||
bus3="co2 atmosphere",
|
bus3="co2 atmosphere",
|
||||||
carrier="solid biomass to hydrogen",
|
carrier="solid biomass to hydrogen",
|
||||||
efficiency=costs.at['solid biomass to hydrogen', 'efficiency'],
|
efficiency=costs.at["solid biomass to hydrogen", "efficiency"],
|
||||||
efficiency2=costs.at['solid biomass', 'CO2 intensity'] * options["cc_fraction"],
|
efficiency2=costs.at["solid biomass", "CO2 intensity"]
|
||||||
efficiency3=-costs.at['solid biomass', 'CO2 intensity'] * options["cc_fraction"],
|
* options["cc_fraction"],
|
||||||
|
efficiency3=-costs.at["solid biomass", "CO2 intensity"]
|
||||||
|
* options["cc_fraction"],
|
||||||
p_nom_extendable=True,
|
p_nom_extendable=True,
|
||||||
capital_cost=costs.at['solid biomass to hydrogen', 'fixed'] * costs.at['solid biomass to hydrogen', 'efficiency']
|
capital_cost=costs.at["solid biomass to hydrogen", "fixed"]
|
||||||
+ costs.at['biomass CHP capture', 'fixed'] * costs.at['solid biomass', 'CO2 intensity'],
|
* costs.at["solid biomass to hydrogen", "efficiency"]
|
||||||
overnight_cost=costs.at['solid biomass to hydrogen', 'investment'] * costs.at['solid biomass to hydrogen', 'efficiency']
|
+ costs.at["biomass CHP capture", "fixed"]
|
||||||
+ costs.at['biomass CHP capture', 'investment'] * costs.at['solid biomass', 'CO2 intensity'],
|
* costs.at["solid biomass", "CO2 intensity"],
|
||||||
marginal_cost=0.,
|
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):
|
def add_industry(n, costs):
|
||||||
|
Loading…
Reference in New Issue
Block a user