2023-03-06 08:27:45 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2023-03-06 17:49:23 +00:00
|
|
|
# SPDX-FileCopyrightText: : 2020-2023 The PyPSA-Eur Authors
|
|
|
|
#
|
|
|
|
# SPDX-License-Identifier: MIT
|
2021-07-01 18:09:04 +00:00
|
|
|
"""
|
|
|
|
Build industrial energy demand per country.
|
|
|
|
"""
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
import multiprocessing as mp
|
2023-03-07 07:58:07 +00:00
|
|
|
from functools import partial
|
2023-03-06 08:27:45 +00:00
|
|
|
|
2023-03-09 10:04:41 +00:00
|
|
|
import country_converter as coco
|
2023-03-09 10:39:57 +00:00
|
|
|
import pandas as pd
|
2021-07-01 18:09:04 +00:00
|
|
|
from tqdm import tqdm
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2023-03-09 10:04:41 +00:00
|
|
|
cc = coco.CountryConverter()
|
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
ktoe_to_twh = 0.011630
|
2020-09-07 14:48:06 +00:00
|
|
|
|
|
|
|
# name in JRC-IDEES Energy Balances
|
2021-07-01 18:09:04 +00:00
|
|
|
sector_sheets = {
|
|
|
|
"Integrated steelworks": "cisb",
|
|
|
|
"Electric arc": "cise",
|
|
|
|
"Alumina production": "cnfa",
|
|
|
|
"Aluminium - primary production": "cnfp",
|
|
|
|
"Aluminium - secondary production": "cnfs",
|
|
|
|
"Other non-ferrous metals": "cnfo",
|
|
|
|
"Basic chemicals": "cbch",
|
|
|
|
"Other chemicals": "coch",
|
|
|
|
"Pharmaceutical products etc.": "cpha",
|
|
|
|
"Basic chemicals feedstock": "cpch",
|
|
|
|
"Cement": "ccem",
|
|
|
|
"Ceramics & other NMM": "ccer",
|
|
|
|
"Glass production": "cgla",
|
|
|
|
"Pulp production": "cpul",
|
|
|
|
"Paper production": "cpap",
|
|
|
|
"Printing and media reproduction": "cprp",
|
|
|
|
"Food, beverages and tobacco": "cfbt",
|
|
|
|
"Transport Equipment": "ctre",
|
|
|
|
"Machinery Equipment": "cmae",
|
|
|
|
"Textiles and leather": "ctel",
|
|
|
|
"Wood and wood products": "cwwp",
|
|
|
|
"Mining and quarrying": "cmiq",
|
|
|
|
"Construction": "ccon",
|
2020-09-07 14:48:06 +00:00
|
|
|
"Non-specified": "cnsi",
|
2021-07-01 18:09:04 +00:00
|
|
|
}
|
2023-03-06 08:27:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
fuels = {
|
2021-07-01 18:09:04 +00:00
|
|
|
"All Products": "all",
|
|
|
|
"Solid Fuels": "solid",
|
|
|
|
"Total petroleum products (without biofuels)": "liquid",
|
2023-03-06 08:27:45 +00:00
|
|
|
"Gases": "gas",
|
2021-07-01 18:09:04 +00:00
|
|
|
"Nuclear heat": "heat",
|
|
|
|
"Derived heat": "heat",
|
|
|
|
"Biomass and Renewable wastes": "biomass",
|
|
|
|
"Wastes (non-renewable)": "waste",
|
|
|
|
"Electricity": "electricity",
|
2023-03-06 08:27:45 +00:00
|
|
|
}
|
|
|
|
|
2023-03-09 10:39:57 +00:00
|
|
|
eu28 = cc.EU28as("ISO2").ISO2.tolist()
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
jrc_names = {"GR": "EL", "GB": "UK"}
|
|
|
|
|
|
|
|
|
2023-03-07 11:20:22 +00:00
|
|
|
def industrial_energy_demand_per_country(country, year, jrc_dir):
|
2021-07-01 18:09:04 +00:00
|
|
|
jrc_country = jrc_names.get(country, country)
|
|
|
|
fn = f"{jrc_dir}/JRC-IDEES-2015_EnergyBalance_{jrc_country}.xlsx"
|
|
|
|
|
|
|
|
sheets = list(sector_sheets.values())
|
|
|
|
df_dict = pd.read_excel(fn, sheet_name=sheets, index_col=0)
|
|
|
|
|
|
|
|
def get_subsector_data(sheet):
|
|
|
|
df = df_dict[sheet][year].groupby(fuels).sum()
|
|
|
|
|
2022-06-10 12:44:36 +00:00
|
|
|
df["ammonia"] = 0.0
|
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
df["other"] = df["all"] - df.loc[df.index != "all"].sum()
|
|
|
|
|
|
|
|
return df
|
|
|
|
|
|
|
|
df = pd.concat(
|
|
|
|
{sub: get_subsector_data(sheet) for sub, sheet in sector_sheets.items()}, axis=1
|
2023-03-06 08:27:45 +00:00
|
|
|
)
|
2021-07-01 18:09:04 +00:00
|
|
|
|
|
|
|
sel = ["Mining and quarrying", "Construction", "Non-specified"]
|
|
|
|
df["Other Industrial Sectors"] = df[sel].sum(axis=1)
|
|
|
|
df["Basic chemicals"] += df["Basic chemicals feedstock"]
|
|
|
|
|
|
|
|
df.drop(columns=sel + ["Basic chemicals feedstock"], index="all", inplace=True)
|
|
|
|
|
|
|
|
df *= ktoe_to_twh
|
|
|
|
|
|
|
|
return df
|
|
|
|
|
|
|
|
|
|
|
|
def add_ammonia_energy_demand(demand):
|
|
|
|
# MtNH3/a
|
|
|
|
fn = snakemake.input.ammonia_production
|
|
|
|
ammonia = pd.read_csv(fn, index_col=0)[str(year)] / 1e3
|
|
|
|
|
2022-06-10 12:44:36 +00:00
|
|
|
def get_ammonia_by_fuel(x):
|
2023-03-06 08:27:45 +00:00
|
|
|
fuels = {
|
2023-05-17 17:25:45 +00:00
|
|
|
"gas": params["MWh_CH4_per_tNH3_SMR"],
|
|
|
|
"electricity": params["MWh_elec_per_tNH3_SMR"],
|
2023-03-06 08:27:45 +00:00
|
|
|
}
|
2021-07-01 18:09:04 +00:00
|
|
|
|
|
|
|
return pd.Series({k: x * v for k, v in fuels.items()})
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2022-06-10 12:44:36 +00:00
|
|
|
ammonia_by_fuel = ammonia.apply(get_ammonia_by_fuel).T
|
|
|
|
ammonia_by_fuel = ammonia_by_fuel.unstack().reindex(
|
|
|
|
index=demand.index, fill_value=0.0
|
|
|
|
)
|
|
|
|
|
2023-05-17 17:25:45 +00:00
|
|
|
ammonia = pd.DataFrame({"ammonia": ammonia * params["MWh_NH3_per_tNH3"]}).T
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
demand["Ammonia"] = ammonia.unstack().reindex(index=demand.index, fill_value=0.0)
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2022-06-10 12:44:36 +00:00
|
|
|
demand["Basic chemicals (without ammonia)"] = (
|
|
|
|
demand["Basic chemicals"] - ammonia_by_fuel
|
2023-03-06 08:27:45 +00:00
|
|
|
)
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
demand["Basic chemicals (without ammonia)"].clip(lower=0, inplace=True)
|
2021-09-28 15:08:03 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
demand.drop(columns="Basic chemicals", inplace=True)
|
2020-09-07 16:41:07 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
return demand
|
2020-09-07 16:41:07 +00:00
|
|
|
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2023-03-09 10:04:41 +00:00
|
|
|
def add_non_eu28_industrial_energy_demand(countries, demand):
|
|
|
|
non_eu28 = countries.difference(eu28)
|
|
|
|
if non_eu28.empty:
|
|
|
|
return demand
|
2021-07-01 18:09:04 +00:00
|
|
|
# output in MtMaterial/a
|
|
|
|
fn = snakemake.input.industrial_production_per_country
|
|
|
|
production = pd.read_csv(fn, index_col=0) / 1e3
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2021-09-24 11:00:58 +00:00
|
|
|
# recombine HVC, Chlorine and Methanol to Basic chemicals (without ammonia)
|
|
|
|
chemicals = ["HVC", "Chlorine", "Methanol"]
|
|
|
|
production["Basic chemicals (without ammonia)"] = production[chemicals].sum(axis=1)
|
|
|
|
production.drop(columns=chemicals, inplace=True)
|
|
|
|
|
2023-03-09 10:04:41 +00:00
|
|
|
eu28_production = production.loc[countries.intersection(eu28)].sum()
|
2021-07-01 18:09:04 +00:00
|
|
|
eu28_energy = demand.groupby(level=1).sum()
|
|
|
|
eu28_averages = eu28_energy / eu28_production
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
demand_non_eu28 = pd.concat(
|
|
|
|
{k: v * eu28_averages for k, v in production.loc[non_eu28].iterrows()}
|
|
|
|
)
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
return pd.concat([demand, demand_non_eu28])
|
2020-09-07 14:48:06 +00:00
|
|
|
|
|
|
|
|
2023-03-07 11:20:22 +00:00
|
|
|
def industrial_energy_demand(countries, year):
|
2021-07-01 18:09:04 +00:00
|
|
|
nprocesses = snakemake.threads
|
2023-05-17 16:43:30 +00:00
|
|
|
disable_progress = snakemake.config["run"].get("disable_progressbar", False)
|
2023-03-07 11:22:06 +00:00
|
|
|
func = partial(
|
|
|
|
industrial_energy_demand_per_country, year=year, jrc_dir=snakemake.input.jrc
|
|
|
|
)
|
2021-07-01 18:09:04 +00:00
|
|
|
tqdm_kwargs = dict(
|
|
|
|
ascii=False,
|
|
|
|
unit=" country",
|
|
|
|
total=len(countries),
|
|
|
|
desc="Build industrial energy demand",
|
2023-03-07 19:37:47 +00:00
|
|
|
disable=disable_progress,
|
2021-07-01 18:09:04 +00:00
|
|
|
)
|
|
|
|
with mp.Pool(processes=nprocesses) as pool:
|
|
|
|
demand_l = list(tqdm(pool.imap(func, countries), **tqdm_kwargs))
|
2020-09-07 16:41:07 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
demand = pd.concat(demand_l, keys=countries)
|
2020-09-07 16:41:07 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
return demand
|
2020-09-07 16:41:07 +00:00
|
|
|
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
if "snakemake" not in globals():
|
2023-03-06 18:09:45 +00:00
|
|
|
from _helpers import mock_snakemake
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
snakemake = mock_snakemake("build_industrial_energy_demand_per_country_today")
|
2023-03-06 08:27:45 +00:00
|
|
|
|
2023-06-15 16:52:25 +00:00
|
|
|
params = snakemake.params.industry
|
2023-05-17 17:25:45 +00:00
|
|
|
year = params.get("reference_year", 2015)
|
2023-06-15 16:52:25 +00:00
|
|
|
countries = pd.Index(snakemake.params.countries)
|
2020-09-07 17:12:47 +00:00
|
|
|
|
2023-03-09 10:04:41 +00:00
|
|
|
demand = industrial_energy_demand(countries.intersection(eu28), year)
|
2020-09-07 17:12:47 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
demand = add_ammonia_energy_demand(demand)
|
2020-09-07 17:12:47 +00:00
|
|
|
|
2023-03-09 10:04:41 +00:00
|
|
|
demand = add_non_eu28_industrial_energy_demand(countries, demand)
|
2020-09-07 17:12:47 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
# for format compatibility
|
|
|
|
demand = demand.stack(dropna=False).unstack(level=[0, 2])
|
2020-09-07 17:12:47 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
# style and annotation
|
|
|
|
demand.index.name = "TWh/a"
|
|
|
|
demand.sort_index(axis=1, inplace=True)
|
2020-09-07 14:48:06 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
fn = snakemake.output.industrial_energy_demand_per_country_today
|
|
|
|
demand.to_csv(fn)
|