2023-03-06 08:27:45 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2024-02-19 15:21:48 +00:00
|
|
|
# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors
|
2023-03-06 17:49:23 +00:00
|
|
|
#
|
|
|
|
# SPDX-License-Identifier: MIT
|
2021-07-01 18:09:04 +00:00
|
|
|
"""
|
|
|
|
Build industrial production per country.
|
|
|
|
"""
|
2019-07-18 09:40:38 +00:00
|
|
|
|
2023-02-23 09:30:32 +00:00
|
|
|
import logging
|
2021-07-01 18:09:04 +00:00
|
|
|
import multiprocessing as mp
|
2024-01-19 09:47:58 +00:00
|
|
|
from functools import partial
|
2023-03-06 08:27:45 +00:00
|
|
|
|
2023-03-09 10:39:57 +00:00
|
|
|
import country_converter as coco
|
2021-07-01 18:09:04 +00:00
|
|
|
import numpy as np
|
2019-07-18 09:40:38 +00:00
|
|
|
import pandas as pd
|
2024-02-12 10:54:13 +00:00
|
|
|
from _helpers import configure_logging, mute_print, set_scenario_config
|
2021-07-01 18:09:04 +00:00
|
|
|
from tqdm import tqdm
|
2019-07-18 09:40:38 +00:00
|
|
|
|
2024-01-19 09:47:58 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2023-03-09 10:04:41 +00:00
|
|
|
cc = coco.CountryConverter()
|
|
|
|
|
2019-07-18 09:40:38 +00:00
|
|
|
tj_to_ktoe = 0.0238845
|
|
|
|
ktoe_to_twh = 0.01163
|
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
sub_sheet_name_dict = {
|
|
|
|
"Iron and steel": "ISI",
|
|
|
|
"Chemicals Industry": "CHI",
|
|
|
|
"Non-metallic mineral products": "NMM",
|
|
|
|
"Pulp, paper and printing": "PPA",
|
|
|
|
"Food, beverages and tobacco": "FBT",
|
|
|
|
"Non Ferrous Metals": "NFM",
|
|
|
|
"Transport Equipment": "TRE",
|
|
|
|
"Machinery Equipment": "MAE",
|
|
|
|
"Textiles and leather": "TEL",
|
|
|
|
"Wood and wood products": "WWP",
|
|
|
|
"Other Industrial Sectors": "OIS",
|
|
|
|
}
|
2023-03-06 08:27:45 +00:00
|
|
|
|
2023-03-09 10:39:57 +00:00
|
|
|
eu28 = cc.EU28as("ISO2").ISO2.values
|
2019-07-18 09:40:38 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
jrc_names = {"GR": "EL", "GB": "UK"}
|
2019-07-18 09:40:38 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
sect2sub = {
|
|
|
|
"Iron and steel": ["Electric arc", "Integrated steelworks"],
|
2019-07-18 09:40:38 +00:00
|
|
|
"Chemicals Industry": [
|
|
|
|
"Basic chemicals",
|
|
|
|
"Other chemicals",
|
|
|
|
"Pharmaceutical products etc.",
|
2021-07-01 18:09:04 +00:00
|
|
|
],
|
|
|
|
"Non-metallic mineral products": [
|
|
|
|
"Cement",
|
|
|
|
"Ceramics & other NMM",
|
|
|
|
"Glass production",
|
|
|
|
],
|
|
|
|
"Pulp, paper and printing": [
|
|
|
|
"Pulp production",
|
|
|
|
"Paper production",
|
|
|
|
"Printing and media reproduction",
|
2023-03-06 08:27:45 +00:00
|
|
|
],
|
2019-07-18 09:40:38 +00:00
|
|
|
"Food, beverages and tobacco": ["Food, beverages and tobacco"],
|
|
|
|
"Non Ferrous Metals": [
|
|
|
|
"Alumina production",
|
|
|
|
"Aluminium - primary production",
|
|
|
|
"Aluminium - secondary production",
|
|
|
|
"Other non-ferrous metals",
|
|
|
|
],
|
|
|
|
"Transport Equipment": ["Transport Equipment"],
|
|
|
|
"Machinery Equipment": ["Machinery Equipment"],
|
|
|
|
"Textiles and leather": ["Textiles and leather"],
|
2021-07-01 18:09:04 +00:00
|
|
|
"Wood and wood products": ["Wood and wood products"],
|
|
|
|
"Other Industrial Sectors": ["Other Industrial Sectors"],
|
|
|
|
}
|
2019-07-18 09:40:38 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
sub2sect = {v: k for k, vv in sect2sub.items() for v in vv}
|
2020-08-26 10:06:01 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
fields = {
|
|
|
|
"Electric arc": "Electric arc",
|
2019-07-18 09:40:38 +00:00
|
|
|
"Integrated steelworks": "Integrated steelworks",
|
|
|
|
"Basic chemicals": "Basic chemicals (kt ethylene eq.)",
|
2021-07-01 18:09:04 +00:00
|
|
|
"Other chemicals": "Other chemicals (kt ethylene eq.)",
|
|
|
|
"Pharmaceutical products etc.": "Pharmaceutical products etc. (kt ethylene eq.)",
|
|
|
|
"Cement": "Cement (kt)",
|
|
|
|
"Ceramics & other NMM": "Ceramics & other NMM (kt bricks eq.)",
|
|
|
|
"Glass production": "Glass production (kt)",
|
|
|
|
"Pulp production": "Pulp production (kt)",
|
|
|
|
"Paper production": "Paper production (kt)",
|
|
|
|
"Printing and media reproduction": "Printing and media reproduction (kt paper eq.)",
|
2019-07-18 09:40:38 +00:00
|
|
|
"Food, beverages and tobacco": "Physical output (index)",
|
2021-07-01 18:09:04 +00:00
|
|
|
"Alumina production": "Alumina production (kt)",
|
2019-07-18 09:40:38 +00:00
|
|
|
"Aluminium - primary production": "Aluminium - primary production",
|
|
|
|
"Aluminium - secondary production": "Aluminium - secondary production",
|
2021-07-01 18:09:04 +00:00
|
|
|
"Other non-ferrous metals": "Other non-ferrous metals (kt lead eq.)",
|
2019-07-18 09:40:38 +00:00
|
|
|
"Transport Equipment": "Physical output (index)",
|
|
|
|
"Machinery Equipment": "Physical output (index)",
|
|
|
|
"Textiles and leather": "Physical output (index)",
|
|
|
|
"Wood and wood products": "Physical output (index)",
|
|
|
|
"Other Industrial Sectors": "Physical output (index)",
|
|
|
|
}
|
2023-03-06 08:27:45 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
eb_names = {
|
|
|
|
"NO": "Norway",
|
|
|
|
"AL": "Albania",
|
|
|
|
"BA": "Bosnia and Herzegovina",
|
|
|
|
"MK": "FYR of Macedonia",
|
|
|
|
"GE": "Georgia",
|
|
|
|
"IS": "Iceland",
|
|
|
|
"KO": "Kosovo",
|
|
|
|
"MD": "Moldova",
|
|
|
|
"ME": "Montenegro",
|
|
|
|
"RS": "Serbia",
|
|
|
|
"UA": "Ukraine",
|
|
|
|
"TR": "Turkey",
|
|
|
|
}
|
2023-03-06 08:27:45 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
eb_sectors = {
|
|
|
|
"Iron & steel industry": "Iron and steel",
|
|
|
|
"Chemical and Petrochemical industry": "Chemicals Industry",
|
|
|
|
"Non-ferrous metal industry": "Non-metallic mineral products",
|
|
|
|
"Paper, Pulp and Print": "Pulp, paper and printing",
|
|
|
|
"Food and Tabacco": "Food, beverages and tobacco",
|
|
|
|
"Non-metallic Minerals (Glass, pottery & building mat. Industry)": "Non Ferrous Metals",
|
|
|
|
"Transport Equipment": "Transport Equipment",
|
|
|
|
"Machinery": "Machinery Equipment",
|
|
|
|
"Textile and Leather": "Textiles and leather",
|
|
|
|
"Wood and Wood Products": "Wood and wood products",
|
|
|
|
"Non-specified (Industry)": "Other Industrial Sectors",
|
|
|
|
}
|
|
|
|
|
|
|
|
# TODO: this should go in a csv in `data`
|
|
|
|
# Annual energy consumption in Switzerland by sector in 2015 (in TJ)
|
|
|
|
# From: Energieverbrauch in der Industrie und im Dienstleistungssektor, Der Bundesrat
|
|
|
|
# http://www.bfe.admin.ch/themen/00526/00541/00543/index.html?lang=de&dossier_id=00775
|
|
|
|
e_switzerland = pd.Series(
|
2023-03-06 08:27:45 +00:00
|
|
|
{
|
2021-07-01 18:09:04 +00:00
|
|
|
"Iron and steel": 7889.0,
|
|
|
|
"Chemicals Industry": 26871.0,
|
|
|
|
"Non-metallic mineral products": 15513.0 + 3820.0,
|
|
|
|
"Pulp, paper and printing": 12004.0,
|
|
|
|
"Food, beverages and tobacco": 17728.0,
|
|
|
|
"Non Ferrous Metals": 3037.0,
|
|
|
|
"Transport Equipment": 14993.0,
|
|
|
|
"Machinery Equipment": 4724.0,
|
|
|
|
"Textiles and leather": 1742.0,
|
|
|
|
"Wood and wood products": 0.0,
|
|
|
|
"Other Industrial Sectors": 10825.0,
|
|
|
|
"current electricity": 53760.0,
|
2023-03-06 08:27:45 +00:00
|
|
|
}
|
2021-07-01 18:09:04 +00:00
|
|
|
)
|
2023-03-06 08:27:45 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
|
|
|
|
def find_physical_output(df):
|
|
|
|
start = np.where(df.index.str.contains("Physical output", na=""))[0][0]
|
|
|
|
empty_row = np.where(df.index.isnull())[0]
|
|
|
|
end = empty_row[np.argmax(empty_row > start)]
|
|
|
|
return slice(start, end)
|
|
|
|
|
|
|
|
|
2023-03-07 11:20:22 +00:00
|
|
|
def get_energy_ratio(country, eurostat_dir, jrc_dir, year):
|
2021-07-01 18:09:04 +00:00
|
|
|
if country == "CH":
|
|
|
|
e_country = e_switzerland * tj_to_ktoe
|
|
|
|
else:
|
|
|
|
# estimate physical output, energy consumption in the sector and country
|
|
|
|
fn = f"{eurostat_dir}/{eb_names[country]}.XLSX"
|
2023-02-21 21:36:49 +00:00
|
|
|
with mute_print():
|
|
|
|
df = pd.read_excel(
|
|
|
|
fn, sheet_name="2016", index_col=2, header=0, skiprows=1
|
|
|
|
).squeeze("columns")
|
2021-07-01 18:09:04 +00:00
|
|
|
e_country = df.loc[eb_sectors.keys(), "Total all products"].rename(eb_sectors)
|
|
|
|
|
|
|
|
fn = f"{jrc_dir}/JRC-IDEES-2015_Industry_EU28.xlsx"
|
|
|
|
|
2023-02-21 21:36:49 +00:00
|
|
|
with mute_print():
|
|
|
|
df = pd.read_excel(fn, sheet_name="Ind_Summary", index_col=0, header=0).squeeze(
|
|
|
|
"columns"
|
|
|
|
)
|
2021-07-01 18:09:04 +00:00
|
|
|
|
|
|
|
assert df.index[48] == "by sector"
|
|
|
|
year_i = df.columns.get_loc(year)
|
|
|
|
e_eu28 = df.iloc[49:76, year_i]
|
|
|
|
e_eu28.index = e_eu28.index.str.lstrip()
|
|
|
|
|
|
|
|
e_ratio = e_country / e_eu28
|
|
|
|
|
|
|
|
return pd.Series({k: e_ratio[v] for k, v in sub2sect.items()})
|
|
|
|
|
|
|
|
|
2023-03-07 11:20:22 +00:00
|
|
|
def industry_production_per_country(country, year, eurostat_dir, jrc_dir):
|
2021-07-01 18:09:04 +00:00
|
|
|
def get_sector_data(sector, country):
|
|
|
|
jrc_country = jrc_names.get(country, country)
|
|
|
|
fn = f"{jrc_dir}/JRC-IDEES-2015_Industry_{jrc_country}.xlsx"
|
|
|
|
sheet = sub_sheet_name_dict[sector]
|
2023-02-21 21:36:49 +00:00
|
|
|
with mute_print():
|
|
|
|
df = pd.read_excel(fn, sheet_name=sheet, index_col=0, header=0).squeeze(
|
|
|
|
"columns"
|
|
|
|
)
|
2021-07-01 18:09:04 +00:00
|
|
|
|
|
|
|
year_i = df.columns.get_loc(year)
|
|
|
|
df = df.iloc[find_physical_output(df), year_i]
|
|
|
|
|
|
|
|
df = df.loc[map(fields.get, sect2sub[sector])]
|
|
|
|
df.index = sect2sub[sector]
|
|
|
|
|
|
|
|
return df
|
|
|
|
|
2023-03-09 10:04:41 +00:00
|
|
|
ct = "EU28" if country not in eu28 else country
|
|
|
|
demand = pd.concat([get_sector_data(s, ct) for s in sect2sub])
|
2021-07-01 18:09:04 +00:00
|
|
|
|
2023-03-09 10:04:41 +00:00
|
|
|
if country not in eu28:
|
2023-03-07 11:20:22 +00:00
|
|
|
demand *= get_energy_ratio(country, eurostat_dir, jrc_dir, year)
|
2021-07-01 18:09:04 +00:00
|
|
|
|
|
|
|
demand.name = country
|
|
|
|
|
|
|
|
return demand
|
|
|
|
|
|
|
|
|
2023-03-07 11:20:22 +00:00
|
|
|
def industry_production(countries, year, eurostat_dir, jrc_dir):
|
2023-03-07 19:37:47 +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 19:37:47 +00:00
|
|
|
|
2023-03-07 09:36:24 +00:00
|
|
|
func = partial(
|
2023-03-07 11:22:06 +00:00
|
|
|
industry_production_per_country,
|
|
|
|
year=year,
|
|
|
|
eurostat_dir=eurostat_dir,
|
|
|
|
jrc_dir=jrc_dir,
|
2023-03-07 09:36:24 +00:00
|
|
|
)
|
2021-07-01 18:09:04 +00:00
|
|
|
tqdm_kwargs = dict(
|
|
|
|
ascii=False,
|
|
|
|
unit=" country",
|
|
|
|
total=len(countries),
|
|
|
|
desc="Build industry production",
|
2023-03-07 19:37:47 +00:00
|
|
|
disable=disable_progress,
|
2021-07-01 18:09:04 +00:00
|
|
|
)
|
2023-02-21 21:36:49 +00:00
|
|
|
with mp.Pool(processes=nprocesses) as pool:
|
2021-07-01 18:09:04 +00:00
|
|
|
demand_l = list(tqdm(pool.imap(func, countries), **tqdm_kwargs))
|
|
|
|
|
|
|
|
demand = pd.concat(demand_l, axis=1).T
|
|
|
|
|
|
|
|
demand.index.name = "kton/a"
|
|
|
|
|
|
|
|
return demand
|
|
|
|
|
|
|
|
|
2023-03-07 11:20:22 +00:00
|
|
|
def separate_basic_chemicals(demand, year):
|
2021-09-28 15:08:03 +00:00
|
|
|
"""
|
|
|
|
Separate basic chemicals into ammonia, chlorine, methanol and HVC.
|
|
|
|
"""
|
2021-07-01 18:09:04 +00:00
|
|
|
ammonia = pd.read_csv(snakemake.input.ammonia_production, index_col=0)
|
|
|
|
|
|
|
|
there = ammonia.index.intersection(demand.index)
|
|
|
|
missing = demand.index.symmetric_difference(there)
|
|
|
|
|
2023-02-23 09:30:32 +00:00
|
|
|
logger.info(f"Following countries have no ammonia demand: {missing.tolist()}")
|
2021-07-01 18:09:04 +00:00
|
|
|
|
2021-09-28 15:08:03 +00:00
|
|
|
demand["Ammonia"] = 0.0
|
2021-07-01 18:09:04 +00:00
|
|
|
|
|
|
|
demand.loc[there, "Ammonia"] = ammonia.loc[there, str(year)]
|
|
|
|
|
|
|
|
demand["Basic chemicals"] -= demand["Ammonia"]
|
|
|
|
|
|
|
|
# EE, HR and LT got negative demand through subtraction - poor data
|
|
|
|
demand["Basic chemicals"].clip(lower=0.0, inplace=True)
|
|
|
|
|
2021-09-24 11:00:58 +00:00
|
|
|
# assume HVC, methanol, chlorine production proportional to non-ammonia basic chemicals
|
2024-02-14 17:31:48 +00:00
|
|
|
distribution_key = (
|
|
|
|
demand["Basic chemicals"]
|
|
|
|
/ params["basic_chemicals_without_NH3_production_today"]
|
|
|
|
/ 1e3
|
|
|
|
)
|
2023-05-17 17:25:45 +00:00
|
|
|
demand["HVC"] = params["HVC_production_today"] * 1e3 * distribution_key
|
|
|
|
demand["Chlorine"] = params["chlorine_production_today"] * 1e3 * distribution_key
|
|
|
|
demand["Methanol"] = params["methanol_production_today"] * 1e3 * distribution_key
|
2021-09-24 11:00:58 +00:00
|
|
|
|
|
|
|
demand.drop(columns=["Basic chemicals"], inplace=True)
|
2021-07-01 18:09:04 +00:00
|
|
|
|
2023-03-06 08:27:45 +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
|
2021-07-01 18:09:04 +00:00
|
|
|
|
|
|
|
snakemake = mock_snakemake("build_industrial_production_per_country")
|
2024-02-12 10:53:20 +00:00
|
|
|
configure_logging(snakemake)
|
|
|
|
set_scenario_config(snakemake)
|
2023-02-23 09:30:32 +00:00
|
|
|
|
2023-06-15 16:52:25 +00:00
|
|
|
countries = snakemake.params.countries
|
2021-07-01 18:09:04 +00:00
|
|
|
|
2023-06-15 16:52:25 +00:00
|
|
|
year = snakemake.params.industry["reference_year"]
|
2021-07-01 18:09:04 +00:00
|
|
|
|
2023-06-15 16:52:25 +00:00
|
|
|
params = snakemake.params.industry
|
2021-09-24 11:00:58 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
jrc_dir = snakemake.input.jrc
|
|
|
|
eurostat_dir = snakemake.input.eurostat
|
|
|
|
|
2023-03-07 11:20:22 +00:00
|
|
|
demand = industry_production(countries, year, eurostat_dir, jrc_dir)
|
2020-08-28 17:13:18 +00:00
|
|
|
|
2023-03-07 11:20:22 +00:00
|
|
|
separate_basic_chemicals(demand, year)
|
2019-07-18 09:40:38 +00:00
|
|
|
|
2021-07-01 18:09:04 +00:00
|
|
|
fn = snakemake.output.industrial_production_per_country
|
|
|
|
demand.to_csv(fn, float_format="%.2f")
|