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
"""
2024-06-05 13:02:44 +00:00
This rule builds the historical industrial production per country .
Relevant Settings
- - - - - - - - - - - - - - - - -
. . code : : yaml
countries :
. .
Inputs
- - - - - - -
- ` ` resources / ammonia_production . csv ` `
2024-07-29 13:39:01 +00:00
- ` ` data / bundle - sector / jrc - idees - 2021 ` `
2024-06-05 13:02:44 +00:00
- ` ` data / eurostat ` `
Outputs
- - - - - - -
- ` ` resources / industrial_production_per_country . csv ` `
Description
- - - - - - -
The industrial production is taken from the ` JRC - IDEES < https : / / joint - research - centre . ec . europa . eu / potencia - policy - oriented - tool - energy - and - climate - change - impact - assessment / jrc - idees_en ) > ` .
This dataset provides detailed information about the consumption of energy for various processes .
If the country is not part of the EU28 , the energy consumption in the industrial sectors is taken from the ` Eurostat < https : / / ec . europa . eu / eurostat / de / data / database > ` dataset . The industrial production is calculated for the year specified in the config [ " industry " ] [ " reference_year " ] .
2024-08-07 09:49:33 +00:00
The ammonia production is provided by the rule ` build_ammonia_production < https : / / pypsa - eur . readthedocs . io / en / latest / sector . html #module-build_ammonia_production>`. Since Switzerland is not part of the EU28 nor reported by eurostat, the energy consumption in the industrial sectors is taken from the `BFE <https://pubdb.bfe.admin.ch/de/publication/download/11817> dataset.
2024-06-05 13:02:44 +00:00
After the industrial production is calculated , the basic chemicals are separated into ammonia , chlorine , methanol and HVC . The production of these chemicals is assumed to be proportional to the production of basic chemicals without ammonia .
The following subcategories [ kton / a ] are considered :
- Electric arc
- Integrated steelworks
- Other chemicals
- Pharmaceutical products etc .
- Cement
- Ceramics & other NMM
- Glass production
- Pulp production
- Paper production
- Printing and media reproduction
- Food , beverages and tobacco
- Alumina production
- Aluminium - primary production
- Aluminium - secondary production
- Other non - ferrous metals
2024-07-29 13:39:01 +00:00
- Transport equipment
- Machinery equipment
2024-06-05 13:02:44 +00:00
- Textiles and leather
- Wood and wood products
2024-07-29 13:39:01 +00:00
- Other industrial sectors
2024-06-05 13:02:44 +00:00
- Ammonia
- HVC
- Chlorine
- Methanol
2021-07-01 18:09:04 +00:00
"""
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:04:41 +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:53:20 +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 " ,
2024-07-29 13:39:01 +00:00
" Chemical industry " : " CHI " ,
2021-07-01 18:09:04 +00:00
" Non-metallic mineral products " : " NMM " ,
" Pulp, paper and printing " : " PPA " ,
" Food, beverages and tobacco " : " FBT " ,
" Non Ferrous Metals " : " NFM " ,
2024-07-29 13:39:01 +00:00
" Transport equipment " : " TRE " ,
" Machinery equipment " : " MAE " ,
2021-07-01 18:09:04 +00:00
" Textiles and leather " : " TEL " ,
" Wood and wood products " : " WWP " ,
2024-07-29 13:39:01 +00:00
" Other industrial sectors " : " OIS " ,
2021-07-01 18:09:04 +00:00
}
2023-03-06 08:27:45 +00:00
2024-07-29 13:39:01 +00:00
eu27 = cc . EU27as ( " 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 " ] ,
2024-07-29 13:39:01 +00:00
" Chemical industry " : [
2019-07-18 09:40:38 +00:00
" 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 " ,
] ,
2024-07-29 13:39:01 +00:00
" Transport equipment " : [ " Transport equipment " ] ,
" Machinery equipment " : [ " Machinery equipment " ] ,
2019-07-18 09:40:38 +00:00
" Textiles and leather " : [ " Textiles and leather " ] ,
2021-07-01 18:09:04 +00:00
" Wood and wood products " : [ " Wood and wood products " ] ,
2024-07-29 13:39:01 +00:00
" Other industrial sectors " : [ " Other industrial sectors " ] ,
2021-07-01 18:09:04 +00:00
}
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.) " ,
2024-07-29 13:39:01 +00:00
" Transport equipment " : " Physical output (index) " ,
" Machinery equipment " : " Physical output (index) " ,
2019-07-18 09:40:38 +00:00
" Textiles and leather " : " Physical output (index) " ,
" Wood and wood products " : " Physical output (index) " ,
2024-07-29 13:39:01 +00:00
" Other industrial sectors " : " Physical output (index) " ,
2019-07-18 09:40:38 +00:00
}
2023-03-06 08:27:45 +00:00
2021-07-01 18:09:04 +00:00
eb_sectors = {
2024-03-05 17:40:06 +00:00
" Iron & steel " : " Iron and steel " ,
2024-07-29 13:39:01 +00:00
" Chemical & petrochemical " : " Chemical industry " ,
2024-03-05 17:40:06 +00:00
" Non-ferrous metals " : " Non-metallic mineral products " ,
" Paper, pulp & printing " : " Pulp, paper and printing " ,
" Food, beverages & tobacco " : " Food, beverages and tobacco " ,
" Non-metallic minerals " : " Non Ferrous Metals " ,
2024-07-29 13:39:01 +00:00
" Transport equipment " : " Transport equipment " ,
" Machinery " : " Machinery equipment " ,
2024-03-05 17:40:06 +00:00
" Textile & leather " : " Textiles and leather " ,
" Wood & wood products " : " Wood and wood products " ,
2024-07-29 13:39:01 +00:00
" Not elsewhere specified (industry) " : " Other industrial sectors " ,
2021-07-01 18:09:04 +00:00
}
2024-07-29 13:39:01 +00:00
2024-08-07 09:49:33 +00:00
ch_mapping = {
" Nahrung " : " Food, beverages and tobacco " ,
" Textil / Leder " : " Textiles and leather " ,
" Papier / Druck " : " Pulp, paper and printing " ,
" Chemie / Pharma " : " Chemical industry " ,
2024-08-07 09:59:06 +00:00
" Zement / Beton " : " Non-metallic mineral products " ,
2024-08-07 09:49:33 +00:00
" Andere NE-Mineralien " : " Other non-ferrous metals " ,
" Metall / Eisen " : " Iron and steel " ,
" NE-Metall " : " Non Ferrous Metals " ,
2024-08-07 09:59:06 +00:00
" Metall / Geräte " : " Transport equipment " ,
2024-08-07 09:49:33 +00:00
" Maschinen " : " Machinery equipment " ,
2024-08-07 09:59:06 +00:00
" Andere Industrien " : " Other industrial sectors " ,
}
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 " :
2024-08-07 09:49:33 +00:00
# data ranges between 2014-2023
2024-08-07 09:59:06 +00:00
e_country = pd . read_csv (
snakemake . input . ch_industrial_production , index_col = 0
) . dropna ( )
2024-08-07 09:49:33 +00:00
e_country = e_country . rename ( index = ch_mapping ) . groupby ( level = 0 ) . sum ( )
e_country = e_country [ str ( min ( 2019 , year ) ) ]
2024-08-07 09:59:06 +00:00
e_country * = tj_to_ktoe
2021-07-01 18:09:04 +00:00
else :
2024-07-31 14:10:09 +00:00
ct_eurostat = country . replace ( " GB " , " UK " )
2021-07-01 18:09:04 +00:00
# estimate physical output, energy consumption in the sector and country
2024-07-29 13:39:01 +00:00
fn = f " { eurostat_dir } / { ct_eurostat } -Energy-balance-sheets-April-2023-edition.xlsb "
2024-03-05 17:40:06 +00:00
df = pd . read_excel (
fn ,
2024-08-07 08:33:47 +00:00
sheet_name = str ( min ( 2019 , year ) ) ,
2024-03-05 17:40:06 +00:00
index_col = 2 ,
header = 0 ,
skiprows = 4 ,
)
e_country = df . loc [ eb_sectors . keys ( ) , " Total " ] . rename ( eb_sectors )
2021-07-01 18:09:04 +00:00
2024-07-29 13:39:01 +00:00
fn = f " { jrc_dir } /EU27/JRC-IDEES-2021_Industry_EU27.xlsx "
2021-07-01 18:09:04 +00:00
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
2024-07-29 13:39:01 +00:00
assert df . index [ 49 ] == " by sector "
2021-07-01 18:09:04 +00:00
year_i = df . columns . get_loc ( year )
2024-07-29 13:39:01 +00:00
e_eu27 = df . iloc [ 50 : 77 , year_i ]
e_eu27 . index = e_eu27 . index . str . lstrip ( )
2021-07-01 18:09:04 +00:00
2024-07-29 13:39:01 +00:00
e_ratio = e_country / e_eu27
2021-07-01 18:09:04 +00:00
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 )
2024-07-29 13:39:01 +00:00
fn = f " { jrc_dir } / { jrc_country } /JRC-IDEES-2021_Industry_ { jrc_country } .xlsx "
2021-07-01 18:09:04 +00:00
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
2024-07-29 13:39:01 +00:00
ct = " EU27 " if country not in eu27 else country
2023-03-09 10:04:41 +00:00
demand = pd . concat ( [ get_sector_data ( s , ct ) for s in sect2sub ] )
2021-07-01 18:09:04 +00:00
2024-07-29 13:39:01 +00:00
if country not in eu27 :
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:35:21 +00:00
func = partial (
2023-03-07 11:20:22 +00:00
industry_production_per_country ,
year = year ,
eurostat_dir = eurostat_dir ,
jrc_dir = jrc_dir ,
2023-03-07 09:35:21 +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 .
"""
2024-08-07 08:33:47 +00:00
# ammonia data from 2018-2022
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
2024-07-31 14:10:09 +00:00
2024-07-29 14:09:38 +00:00
year_to_use = min ( max ( year , 2018 ) , 2022 )
2024-07-29 14:00:37 +00:00
if year_to_use != year :
2024-08-07 09:59:06 +00:00
logger . info (
f " Year { year } outside data range. Using data from { year_to_use } for ammonia production. "
)
2024-07-29 14:00:37 +00:00
demand . loc [ there , " Ammonia " ] = ammonia . loc [ there , str ( year_to_use ) ]
2021-07-01 18:09:04 +00:00
demand [ " Basic chemicals " ] - = demand [ " Ammonia " ]
# EE, HR and LT got negative demand through subtraction - poor data
2024-07-08 06:29:16 +00:00
col = " Basic chemicals "
demand [ col ] = demand [ col ] . clip ( lower = 0.0 )
2021-07-01 18:09:04 +00:00
2021-09-24 11:00:58 +00:00
# assume HVC, methanol, chlorine production proportional to non-ammonia basic chemicals
2024-02-14 09:33:50 +00:00
distribution_key = (
demand [ " Basic chemicals " ]
/ params [ " basic_chemicals_without_NH3_production_today " ]
/ 1e3
2024-02-14 17:31:48 +00:00
)
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
2024-08-30 13:36:03 +00:00
demand . fillna ( 0.0 , inplace = True )
2021-07-01 18:09:04 +00:00
fn = snakemake . output . industrial_production_per_country
demand . to_csv ( fn , float_format = " %.2f " )