-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
209 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
# -*- coding: utf-8 -*- | ||
# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors | ||
# | ||
# SPDX-License-Identifier: MIT | ||
""" | ||
Builds table of existing heat generation capacities for initial planning | ||
horizon. | ||
Existing heat generation capacities are distributed to nodes based on population. | ||
Within the nodes, the capacities are distributed to sectors (residential and services) based on sectoral consumption and urban/rural based population distribution. | ||
Inputs: | ||
------- | ||
- Existing heating generators: `data/existing_heating_raw.csv` per country | ||
- Population layout: `resources/{run_name}/pop_layout_s<simpl>_<clusters>.csv`. Output of `scripts/build_clustered_population_layout.py` | ||
- Population layout with energy demands: `resources/<run_name>/pop_weighted_energy_totals_s<simpl>_<clusters>.csv` | ||
- District heating share: `resources/<run_name>/district_heat_share_elec_s<simpl>_<clusters>_<planning_horizons>.csv` | ||
Outputs: | ||
-------- | ||
- Existing heat generation capacities distributed to nodes: `resources/{run_name}/existing_heating_distribution_elec_s{simpl}_{clusters}_{planning_horizons}.csv` | ||
Relevant settings: | ||
------------------ | ||
.. code:: yaml | ||
scenario: | ||
planning_horizons | ||
sector: | ||
existing_capacities: | ||
Notes: | ||
------ | ||
- Data for Albania, Montenegro and Macedonia is not included in input database and assumed 0. | ||
- Coal and oil boilers are assimilated to oil boilers. | ||
- All ground-source heat pumps are assumed in rural areas and all air-source heat pumps are assumed to be in urban areas. | ||
References: | ||
----------- | ||
- "Mapping and analyses of the current and future (2020 - 2030) heating/cooling fuel deployment (fossil/renewables)" (https://energy.ec.europa.eu/publications/mapping-and-analyses-current-and-future-2020-2030-heatingcooling-fuel-deployment-fossilrenewables-1_en) | ||
""" | ||
import logging | ||
import os | ||
|
||
import country_converter as coco | ||
import numpy as np | ||
import pandas as pd | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
cc = coco.CountryConverter() | ||
|
||
|
||
def build_existing_heating(): | ||
# retrieve existing heating capacities | ||
|
||
# Add existing heating capacities, data comes from the study | ||
# "Mapping and analyses of the current and future (2020 - 2030) | ||
# heating/cooling fuel deployment (fossil/renewables) " | ||
# https://energy.ec.europa.eu/publications/mapping-and-analyses-current-and-future-2020-2030-heatingcooling-fuel-deployment-fossilrenewables-1_en | ||
# file: "WP2_DataAnnex_1_BuildingTechs_ForPublication_201603.xls" -> "existing_heating_raw.csv". | ||
# data is for buildings only (i.e. NOT district heating) and represents the year 2012 | ||
# TODO start from original file | ||
|
||
existing_heating = pd.read_csv( | ||
snakemake.input.existing_heating, index_col=0, header=0 | ||
) | ||
|
||
# data for Albania, Montenegro and Macedonia not included in database | ||
existing_heating.loc["Albania"] = np.nan | ||
existing_heating.loc["Montenegro"] = np.nan | ||
existing_heating.loc["Macedonia"] = np.nan | ||
|
||
existing_heating.fillna(0.0, inplace=True) | ||
|
||
fillvalue_missing = existing_heating.loc["DEFAULT"] | ||
|
||
# convert GW to MW | ||
existing_heating *= 1e3 | ||
|
||
existing_heating.index = cc.convert(existing_heating.index, to="iso2") | ||
|
||
# coal and oil boilers are assimilated to oil boilers | ||
existing_heating["oil boiler"] = ( | ||
existing_heating["oil boiler"] + existing_heating["coal boiler"] | ||
) | ||
existing_heating.drop(["coal boiler"], axis=1, inplace=True) | ||
|
||
# distribute technologies to nodes by population | ||
pop_layout = pd.read_csv(snakemake.input.clustered_pop_layout, index_col=0) | ||
|
||
# fill missing rows | ||
missing_countries = list(set(pop_layout.ct.unique()) - set(existing_heating.index)) | ||
if len(missing_countries) > 0: | ||
logger.warning( | ||
f"Missing population data for countries: {missing_countries}. Filling with DEFAULT values." | ||
) | ||
for country in missing_countries: | ||
existing_heating.loc[country] = fillvalue_missing | ||
|
||
nodal_heating = existing_heating.loc[pop_layout.ct] | ||
nodal_heating.index = pop_layout.index | ||
nodal_heating = nodal_heating.multiply(pop_layout.fraction, axis=0) | ||
|
||
district_heat_info = pd.read_csv(snakemake.input.district_heat_share, index_col=0) | ||
urban_fraction = pop_layout["fraction"] | ||
|
||
energy_layout = pd.read_csv( | ||
snakemake.input.clustered_pop_energy_layout, index_col=0 | ||
) | ||
|
||
uses = ["space", "water"] | ||
sectors = ["residential", "services"] | ||
|
||
nodal_sectoral_totals = pd.DataFrame(dtype=float) | ||
|
||
for sector in sectors: | ||
nodal_sectoral_totals[sector] = energy_layout[ | ||
[f"total {sector} {use}" for use in uses] | ||
].sum(axis=1) | ||
|
||
nodal_sectoral_fraction = nodal_sectoral_totals.div( | ||
nodal_sectoral_totals.sum(axis=1), axis=0 | ||
) | ||
|
||
nodal_heat_name_fraction = pd.DataFrame(index=district_heat_info.index, dtype=float) | ||
|
||
nodal_heat_name_fraction["urban central"] = 0.0 | ||
|
||
for sector in sectors: | ||
nodal_heat_name_fraction[f"{sector} rural"] = nodal_sectoral_fraction[ | ||
sector | ||
] * (1 - urban_fraction) | ||
nodal_heat_name_fraction[f"{sector} urban decentral"] = ( | ||
nodal_sectoral_fraction[sector] * urban_fraction | ||
) | ||
|
||
nodal_heat_name_tech = pd.concat( | ||
{ | ||
name: nodal_heating.multiply(nodal_heat_name_fraction[name], axis=0) | ||
for name in nodal_heat_name_fraction.columns | ||
}, | ||
axis=1, | ||
names=["heat name", "technology"], | ||
) | ||
|
||
# move all ground HPs to rural, all air to urban | ||
|
||
for sector in sectors: | ||
nodal_heat_name_tech[(f"{sector} rural", "ground heat pump")] += ( | ||
nodal_heat_name_tech[("urban central", "ground heat pump")] | ||
* nodal_sectoral_fraction[sector] | ||
+ nodal_heat_name_tech[(f"{sector} urban decentral", "ground heat pump")] | ||
) | ||
nodal_heat_name_tech[(f"{sector} urban decentral", "ground heat pump")] = 0.0 | ||
|
||
nodal_heat_name_tech[ | ||
(f"{sector} urban decentral", "air heat pump") | ||
] += nodal_heat_name_tech[(f"{sector} rural", "air heat pump")] | ||
nodal_heat_name_tech[(f"{sector} rural", "air heat pump")] = 0.0 | ||
|
||
nodal_heat_name_tech[("urban central", "ground heat pump")] = 0.0 | ||
|
||
nodal_heat_name_tech.to_csv(snakemake.output.existing_heating_distribution) | ||
|
||
|
||
if __name__ == "__main__": | ||
if "snakemake" not in globals(): | ||
from helpers import mock_snakemake | ||
|
||
os.chdir(os.path.dirname(os.path.abspath(__file__))) | ||
|
||
snakemake = mock_snakemake( | ||
"build_existing_heating_distribution", | ||
simpl="", | ||
clusters=4, | ||
planning_horizons=2030, | ||
demand="DF", | ||
) | ||
|
||
build_existing_heating() |