Skip to content

Commit

Permalink
add build_heating_distribution
Browse files Browse the repository at this point in the history
  • Loading branch information
davide-f committed Jul 10, 2024
1 parent d4a6e5b commit f1fdcee
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 7 deletions.
33 changes: 28 additions & 5 deletions Snakefile
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,31 @@ rule build_industry_demand: #default data
"scripts/build_industry_demand.py"


rule build_existing_heating_distribution:
params:
baseyear=config["scenario"]["planning_horizons"][0],
sector=config["sector"],
existing_capacities=config["existing_capacities"],
input:
existing_heating="data/existing_infrastructure/existing_heating_raw.csv",
clustered_pop_layout="resources/population_shares/pop_layout_elec_s{simpl}_{clusters}.csv",
clustered_pop_energy_layout="resources/demand/heat/nodal_energy_heat_totals_{demand}_s{simpl}_{clusters}_{planning_horizons}.csv", #"resources/population_shares/pop_weighted_energy_totals_s{simpl}_{clusters}.csv",
district_heat_share="resources/demand/heat/district_heat_share_{demand}_s{simpl}_{clusters}_{planning_horizons}.csv",
output:
existing_heating_distribution="resources/heating/existing_heating_distribution_{demand}_s{simpl}_{clusters}_{planning_horizons}.csv",
threads: 1
resources:
mem_mb=2000,
log:
RDIR
+ "/logs/build_existing_heating_distribution_{demand}_s{simpl}_{clusters}_{planning_horizons}.log",
benchmark:
RDIR
+"/benchmarks/build_existing_heating_distribution/{demand}_s{simpl}_{clusters}_{planning_horizons}"
script:
"scripts/build_existing_heating_distribution.py"


if config["foresight"] == "myopic":

rule add_existing_baseyear:
Expand All @@ -715,7 +740,7 @@ if config["foresight"] == "myopic":
+ "costs_{}.csv".format(config["scenario"]["planning_horizons"][0]),
cop_soil_total="resources/cops/cop_soil_total_elec_s{simpl}_{clusters}.nc",
cop_air_total="resources/cops/cop_air_total_elec_s{simpl}_{clusters}.nc",
existing_heating_distribution="data/existing_infrastructure/existing_heating_raw.csv",
existing_heating_distribution="resources/heating/existing_heating_distribution_{demand}_s{simpl}_{clusters}_{planning_horizons}.csv",
output:
RDIR
+ "/prenetworks-brownfield/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sopts}_{planning_horizons}_{discountrate}_{demand}_{h2export}export.nc",
Expand All @@ -731,10 +756,8 @@ if config["foresight"] == "myopic":
RDIR
+ "/logs/add_existing_baseyear_elec_s{simpl}_{clusters}_ec_l{ll}_{opts}_{sopts}_{planning_horizons}_{discountrate}_{demand}_{h2export}export.log",
benchmark:
(
RDIR
+ "/benchmarks/add_existing_baseyear/elec_s{simpl}_{clusters}_ec_l{ll}_{opts}_{sopts}_{planning_horizons}_{discountrate}_{demand}_{h2export}export"
)
RDIR
+"/benchmarks/add_existing_baseyear/elec_s{simpl}_{clusters}_ec_l{ll}_{opts}_{sopts}_{planning_horizons}_{discountrate}_{demand}_{h2export}export"
script:
"scripts/add_existing_baseyear.py"

Expand Down
3 changes: 1 addition & 2 deletions data/existing_infrastructure/existing_heating_raw.csv
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,4 @@ Norway,,,,,2.91,0.334
Switzerland,,,,,1,0.849
Serbia,,,,,,
Bosnia Herzegovina,,,,,,
Nigeria,,,,,,
Benin,,,,,,
DEFAULT,,,,,,
180 changes: 180 additions & 0 deletions scripts/build_existing_heating_distribution.py
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()

0 comments on commit f1fdcee

Please sign in to comment.