commit
f59ede8af4
@ -637,6 +637,15 @@ industry:
|
||||
2040: 0.12
|
||||
2045: 0.16
|
||||
2050: 0.20
|
||||
sector_ratios_fraction_future:
|
||||
2020: 0.0
|
||||
2025: 0.1
|
||||
2030: 0.3
|
||||
2035: 0.5
|
||||
2040: 0.7
|
||||
2045: 0.9
|
||||
2050: 1.0
|
||||
basic_chemicals_without_NH3_production_today: 69. #Mt/a, = 86 Mtethylene-equiv - 17 MtNH3
|
||||
HVC_production_today: 52.
|
||||
MWh_elec_per_tHVC_mechanical_recycling: 0.547
|
||||
MWh_elec_per_tHVC_chemical_recycling: 6.9
|
||||
|
@ -17,6 +17,8 @@ HVC_primary_fraction,--,float,The fraction of high value chemicals (HVC) produce
|
||||
HVC_mechanical_recycling _fraction,--,float,The fraction of high value chemicals (HVC) produced using mechanical recycling
|
||||
HVC_chemical_recycling _fraction,--,float,The fraction of high value chemicals (HVC) produced using chemical recycling
|
||||
,,,
|
||||
sector_ratios_fraction_future,--,Dictionary with planning horizons as keys.,The fraction of total progress in fuel and process switching achieved in the industry sector.
|
||||
basic_chemicals_without_NH3_production_today,Mt/a,float,"The amount of basic chemicals produced without ammonia (= 86 Mtethylene-equiv - 17 MtNH3)."
|
||||
HVC_production_today,MtHVC/a,float,"The amount of high value chemicals (HVC) produced. This includes ethylene, propylene and BTX. From `DECHEMA (2017) <https://dechema.de/dechema_media/Downloads/Positionspapiere/Technology_study_Low_carbon_energy_and_feedstock_for_the_European_chemical_industry-p-20002750.pdf>`_, Figure 16, page 107"
|
||||
Mwh_elec_per_tHVC _mechanical_recycling,MWh/tHVC,float,"The energy amount of electricity needed to produce a ton of high value chemical (HVC) using mechanical recycling. From SI of `Meys et al (2020) <https://doi.org/10.1016/j.resconrec.2020.105010>`_, Table S5, for HDPE, PP, PS, PET. LDPE would be 0.756."
|
||||
Mwh_elec_per_tHVC _chemical_recycling,MWh/tHVC,float,"The energy amount of electricity needed to produce a ton of high value chemical (HVC) using chemical recycling. The default value is based on pyrolysis and electric steam cracking. From `Material Economics (2019) <https://materialeconomics.com/latest-updates/industrial-transformation-2050>`_, page 125"
|
||||
|
|
@ -10,6 +10,15 @@ Release Notes
|
||||
Upcoming Release
|
||||
================
|
||||
|
||||
* Improved representation of industry transition pathways. A new script was
|
||||
added to interpolate industry sector ratios from today's status quo to future
|
||||
systems (i.e. specific emissions and demands for energy and feedstocks). For
|
||||
each country we gradually switch industry processes from today's specific
|
||||
energy carrier usage per ton material output to the best-in-class energy
|
||||
consumption of tomorrow. This is done on a per-country basis. The ratio of
|
||||
today to tomorrow's energy consumption is set with the ``industry:
|
||||
sector_ratios_fraction_future:`` parameter.
|
||||
|
||||
* Bugfix: Correct units of subtracted chlorine and methanol demand in
|
||||
:mod:`build_industry_sector_ratios`.
|
||||
|
||||
|
@ -431,6 +431,31 @@ rule build_industry_sector_ratios:
|
||||
"../scripts/build_industry_sector_ratios.py"
|
||||
|
||||
|
||||
rule build_industry_sector_ratios_intermediate:
|
||||
params:
|
||||
industry=config["industry"],
|
||||
input:
|
||||
industry_sector_ratios=RESOURCES + "industry_sector_ratios.csv",
|
||||
industrial_energy_demand_per_country_today=RESOURCES
|
||||
+ "industrial_energy_demand_per_country_today.csv",
|
||||
industrial_production_per_country=RESOURCES
|
||||
+ "industrial_production_per_country.csv",
|
||||
output:
|
||||
industry_sector_ratios=RESOURCES
|
||||
+ "industry_sector_ratios_{planning_horizons}.csv",
|
||||
threads: 1
|
||||
resources:
|
||||
mem_mb=1000,
|
||||
log:
|
||||
LOGS + "build_industry_sector_ratios_{planning_horizons}.log",
|
||||
benchmark:
|
||||
BENCHMARKS + "build_industry_sector_ratios_{planning_horizons}"
|
||||
conda:
|
||||
"../envs/environment.yaml"
|
||||
script:
|
||||
"../scripts/build_industry_sector_ratios_intermediate.py"
|
||||
|
||||
|
||||
rule build_industrial_production_per_country:
|
||||
params:
|
||||
industry=config["industry"],
|
||||
@ -533,7 +558,8 @@ rule build_industrial_production_per_node:
|
||||
|
||||
rule build_industrial_energy_demand_per_node:
|
||||
input:
|
||||
industry_sector_ratios=RESOURCES + "industry_sector_ratios.csv",
|
||||
industry_sector_ratios=RESOURCES
|
||||
+ "industry_sector_ratios_{planning_horizons}.csv",
|
||||
industrial_production_per_node=RESOURCES
|
||||
+ "industrial_production_elec_s{simpl}_{clusters}_{planning_horizons}.csv",
|
||||
industrial_energy_demand_per_node_today=RESOURCES
|
||||
@ -564,7 +590,6 @@ rule build_industrial_energy_demand_per_country_today:
|
||||
industry=config["industry"],
|
||||
input:
|
||||
jrc="data/bundle-sector/jrc-idees-2015",
|
||||
ammonia_production=RESOURCES + "ammonia_production.csv",
|
||||
industrial_production_per_country=RESOURCES
|
||||
+ "industrial_production_per_country.csv",
|
||||
output:
|
||||
|
@ -73,7 +73,7 @@ def industrial_energy_demand_per_country(country, year, jrc_dir):
|
||||
def get_subsector_data(sheet):
|
||||
df = df_dict[sheet][year].groupby(fuels).sum()
|
||||
|
||||
df["ammonia"] = 0.0
|
||||
df["hydrogen"] = 0.0
|
||||
|
||||
df["other"] = df["all"] - df.loc[df.index != "all"].sum()
|
||||
|
||||
@ -94,51 +94,50 @@ def industrial_energy_demand_per_country(country, year, jrc_dir):
|
||||
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
|
||||
def separate_basic_chemicals(demand, production):
|
||||
|
||||
def get_ammonia_by_fuel(x):
|
||||
fuels = {
|
||||
"gas": params["MWh_CH4_per_tNH3_SMR"],
|
||||
"electricity": params["MWh_elec_per_tNH3_SMR"],
|
||||
ammonia = pd.DataFrame(
|
||||
{
|
||||
"hydrogen": production["Ammonia"] * params["MWh_H2_per_tNH3_electrolysis"],
|
||||
"electricity": production["Ammonia"]
|
||||
* params["MWh_elec_per_tNH3_electrolysis"],
|
||||
}
|
||||
|
||||
return pd.Series({k: x * v for k, v in fuels.items()})
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
ammonia = pd.DataFrame({"ammonia": ammonia * params["MWh_NH3_per_tNH3"]}).T
|
||||
).T
|
||||
chlorine = pd.DataFrame(
|
||||
{
|
||||
"hydrogen": production["Chlorine"] * params["MWh_H2_per_tCl"],
|
||||
"electricity": production["Chlorine"] * params["MWh_elec_per_tCl"],
|
||||
}
|
||||
).T
|
||||
methanol = pd.DataFrame(
|
||||
{
|
||||
"gas": production["Methanol"] * params["MWh_CH4_per_tMeOH"],
|
||||
"electricity": production["Methanol"] * params["MWh_elec_per_tMeOH"],
|
||||
}
|
||||
).T
|
||||
|
||||
demand["Ammonia"] = ammonia.unstack().reindex(index=demand.index, fill_value=0.0)
|
||||
demand["Chlorine"] = chlorine.unstack().reindex(index=demand.index, fill_value=0.0)
|
||||
demand["Methanol"] = methanol.unstack().reindex(index=demand.index, fill_value=0.0)
|
||||
|
||||
demand["Basic chemicals (without ammonia)"] = (
|
||||
demand["Basic chemicals"] - ammonia_by_fuel
|
||||
demand["HVC"] = (
|
||||
demand["Basic chemicals"]
|
||||
- demand["Ammonia"]
|
||||
- demand["Methanol"]
|
||||
- demand["Chlorine"]
|
||||
)
|
||||
|
||||
demand["Basic chemicals (without ammonia)"].clip(lower=0, inplace=True)
|
||||
|
||||
demand.drop(columns="Basic chemicals", inplace=True)
|
||||
|
||||
demand["HVC"].clip(lower=0, inplace=True)
|
||||
|
||||
return demand
|
||||
|
||||
|
||||
def add_non_eu28_industrial_energy_demand(countries, demand):
|
||||
def add_non_eu28_industrial_energy_demand(countries, demand, production):
|
||||
non_eu28 = countries.difference(eu28)
|
||||
if non_eu28.empty:
|
||||
return demand
|
||||
# output in MtMaterial/a
|
||||
fn = snakemake.input.industrial_production_per_country
|
||||
production = pd.read_csv(fn, index_col=0) / 1e3
|
||||
|
||||
# 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)
|
||||
|
||||
eu28_production = production.loc[countries.intersection(eu28)].sum()
|
||||
eu28_energy = demand.groupby(level=1).sum()
|
||||
@ -182,9 +181,15 @@ if __name__ == "__main__":
|
||||
|
||||
demand = industrial_energy_demand(countries.intersection(eu28), year)
|
||||
|
||||
demand = add_ammonia_energy_demand(demand)
|
||||
# output in MtMaterial/a
|
||||
production = (
|
||||
pd.read_csv(snakemake.input.industrial_production_per_country, index_col=0)
|
||||
/ 1e3
|
||||
)
|
||||
|
||||
demand = add_non_eu28_industrial_energy_demand(countries, demand)
|
||||
demand = separate_basic_chemicals(demand, production)
|
||||
|
||||
demand = add_non_eu28_industrial_energy_demand(countries, demand, production)
|
||||
|
||||
# for format compatibility
|
||||
demand = demand.stack(dropna=False).unstack(level=[0, 2])
|
||||
|
@ -19,23 +19,31 @@ if __name__ == "__main__":
|
||||
planning_horizons=2030,
|
||||
)
|
||||
|
||||
# import EU ratios df as csv
|
||||
# import ratios
|
||||
fn = snakemake.input.industry_sector_ratios
|
||||
industry_sector_ratios = pd.read_csv(fn, index_col=0)
|
||||
sector_ratios = pd.read_csv(fn, header=[0, 1], index_col=0)
|
||||
|
||||
# material demand per node and industry (kton/a)
|
||||
# material demand per node and industry (Mton/a)
|
||||
fn = snakemake.input.industrial_production_per_node
|
||||
nodal_production = pd.read_csv(fn, index_col=0)
|
||||
nodal_production = pd.read_csv(fn, index_col=0) / 1e3
|
||||
|
||||
# energy demand today to get current electricity
|
||||
fn = snakemake.input.industrial_energy_demand_per_node_today
|
||||
nodal_today = pd.read_csv(fn, index_col=0)
|
||||
|
||||
# final energy consumption per node and industry (TWh/a)
|
||||
nodal_df = nodal_production.dot(industry_sector_ratios.T)
|
||||
nodal_sector_ratios = pd.concat(
|
||||
{node: sector_ratios[node[:2]] for node in nodal_production.index}, axis=1
|
||||
)
|
||||
|
||||
# convert GWh to TWh and ktCO2 to MtCO2
|
||||
nodal_df *= 0.001
|
||||
nodal_production_stacked = nodal_production.stack()
|
||||
nodal_production_stacked.index.names = [None, None]
|
||||
|
||||
# final energy consumption per node and industry (TWh/a)
|
||||
nodal_df = (
|
||||
(nodal_sector_ratios.multiply(nodal_production_stacked))
|
||||
.T.groupby(level=0)
|
||||
.sum()
|
||||
)
|
||||
|
||||
rename_sectors = {
|
||||
"elec": "electricity",
|
||||
|
@ -261,7 +261,11 @@ def separate_basic_chemicals(demand, year):
|
||||
demand["Basic chemicals"].clip(lower=0.0, inplace=True)
|
||||
|
||||
# assume HVC, methanol, chlorine production proportional to non-ammonia basic chemicals
|
||||
distribution_key = demand["Basic chemicals"] / demand["Basic chemicals"].sum()
|
||||
distribution_key = (
|
||||
demand["Basic chemicals"]
|
||||
/ params["basic_chemicals_without_NH3_production_today"]
|
||||
/ 1e3
|
||||
)
|
||||
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
|
||||
|
81
scripts/build_industry_sector_ratios_intermediate.py
Normal file
81
scripts/build_industry_sector_ratios_intermediate.py
Normal file
@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""
|
||||
Build specific energy consumption by carrier and industries and by country,
|
||||
that interpolates between the current average energy consumption (from
|
||||
2015-2020) and the ideal future best-in-class consumption.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
from prepare_sector_network import get
|
||||
|
||||
|
||||
def build_industry_sector_ratios_intermediate():
|
||||
|
||||
# in TWh/a
|
||||
demand = pd.read_csv(
|
||||
snakemake.input.industrial_energy_demand_per_country_today,
|
||||
header=[0, 1],
|
||||
index_col=0,
|
||||
)
|
||||
|
||||
# in Mt/a
|
||||
production = (
|
||||
pd.read_csv(snakemake.input.industrial_production_per_country, index_col=0)
|
||||
/ 1e3
|
||||
).stack()
|
||||
production.index.names = [None, None]
|
||||
|
||||
# in MWh/t
|
||||
future_sector_ratios = pd.read_csv(
|
||||
snakemake.input.industry_sector_ratios, index_col=0
|
||||
)
|
||||
|
||||
today_sector_ratios = demand.div(production, axis=1)
|
||||
|
||||
today_sector_ratios.dropna(how="all", axis=1, inplace=True)
|
||||
|
||||
rename = {
|
||||
"waste": "biomass",
|
||||
"electricity": "elec",
|
||||
"solid": "coke",
|
||||
"gas": "methane",
|
||||
"other": "biomass",
|
||||
"liquid": "naphtha",
|
||||
}
|
||||
today_sector_ratios = today_sector_ratios.rename(rename).groupby(level=0).sum()
|
||||
|
||||
fraction_future = get(params["sector_ratios_fraction_future"], year)
|
||||
|
||||
intermediate_sector_ratios = {}
|
||||
for ct, group in today_sector_ratios.T.groupby(level=0):
|
||||
today_sector_ratios_ct = (
|
||||
group.droplevel(0)
|
||||
.T.reindex_like(future_sector_ratios)
|
||||
.fillna(future_sector_ratios)
|
||||
)
|
||||
intermediate_sector_ratios[ct] = (
|
||||
today_sector_ratios_ct * (1 - fraction_future)
|
||||
+ future_sector_ratios * fraction_future
|
||||
)
|
||||
intermediate_sector_ratios = pd.concat(intermediate_sector_ratios, axis=1)
|
||||
|
||||
intermediate_sector_ratios.to_csv(snakemake.output.industry_sector_ratios)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "snakemake" not in globals():
|
||||
from _helpers import mock_snakemake
|
||||
|
||||
snakemake = mock_snakemake(
|
||||
"build_industry_sector_ratios_intermediate",
|
||||
planning_horizons="2030",
|
||||
)
|
||||
|
||||
year = int(snakemake.wildcards.planning_horizons[-4:])
|
||||
|
||||
params = snakemake.params.industry
|
||||
|
||||
build_industry_sector_ratios_intermediate()
|
Loading…
Reference in New Issue
Block a user