Skip to content

Commit 1ce240d

Browse files
macflo8khaeru
authored andcommitted
Add new plastics CO2_Emission calculator
1 parent c5ff035 commit 1ce240d

File tree

3 files changed

+151
-4
lines changed

3 files changed

+151
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# this file provides data about the primary chemicals
2+
# needed to calculate emission factors and embodied carbon
3+
4+
# "plastic use" describes the estimated downstream use in long-lived products (= plastics)
5+
# estimate is based on material flow data from Levi & Cullen chemicals MFA study
6+
# calculations are documented in "HVC plastic shares.xlsx" (currently stored on maczek OneDrive)
7+
# TODO: insert proper literature reference to paper and store xlsx file on a central repo
8+
9+
HVCs:
10+
ethylene:
11+
molar mass: 28
12+
carbon mass: 24
13+
plastics use: 0.879
14+
propylene:
15+
molar mass: 42
16+
carbon mass: 36
17+
plastics use: 0.85
18+
BTX: # values are weighted averages of benzene, toluene and xylene isomers
19+
molar mass: 92
20+
carbon mass: 84
21+
plastics use: 0.855
22+
23+
methanol:
24+
M1:
25+
molar mass: 32
26+
carbon mass: 12
27+
plastics use: 0.273

message_ix_models/model/material/data_util.py

+98-3
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
broadcast,
2121
nodes_ex_world,
2222
package_data_path,
23+
same_node,
2324
)
2425

26+
from .util import read_yaml_file
27+
2528
if TYPE_CHECKING:
2629
from message_ix import Scenario
2730

@@ -421,9 +424,9 @@ def read_sector_data(
421424
list_ef = data_df[["Parameter", "Species", "Mode"]].apply(list, axis=1)
422425

423426
data_df["parameter"] = list_series.str.join("|")
424-
data_df.loc[data_df["Parameter"] == "emission_factor", "parameter"] = (
425-
list_ef.str.join("|")
426-
)
427+
data_df.loc[
428+
data_df["Parameter"] == "emission_factor", "parameter"
429+
] = list_ef.str.join("|")
427430

428431
data_df = data_df.drop(["Parameter", "Level", "Commodity", "Mode"], axis=1)
429432
data_df = data_df.drop(data_df[data_df.Value == ""].index)
@@ -1859,3 +1862,95 @@ def calibrate_for_SSPs(scenario: "Scenario") -> None:
18591862
scenario.commit("increase gro up for loil_i/gas_i/heat_i CHN 2020")
18601863

18611864
return
1865+
1866+
1867+
def gen_plastics_emission_factors(
1868+
info, species: Literal["methanol", "HVCs"]
1869+
) -> pd.DataFrame:
1870+
"""Generate "CO2_Emission" relation parameter that
1871+
represents stored carbon in produced plastics.
1872+
The calculation considers:
1873+
* carbon content of feedstocks,
1874+
* the share that is converted to plastics
1875+
* the end-of-life treatment (i.e. incineration, landfill, etc.)
1876+
1877+
Values are negative since they need to be deducted
1878+
from top-down accounting, which assumes that all extracted
1879+
carbonaceous resources are released as carbon emissions.
1880+
(Which is not correct for carbon used in long-lived products)
1881+
1882+
Parameters
1883+
----------
1884+
species:
1885+
feedstock species to generate relation for
1886+
info: ScenarioInfo
1887+
1888+
Returns
1889+
-------
1890+
pd.DataFrame
1891+
"""
1892+
1893+
if species != "HVCs":
1894+
raise NotImplementedError
1895+
1896+
tec_species_map = {"methanol": NotImplemented, "HVCs": "production_HVC"}
1897+
1898+
# TODO: do same calculation for methanol not used for MTO but other plastics
1899+
carbon_pars = read_yaml_file(
1900+
package_data_path(
1901+
"material", "petrochemicals", "chemicals_carbon_parameters.yaml"
1902+
)
1903+
)
1904+
# TODO: move EOL parameters to a different file to disassociate from methanol model
1905+
end_of_life_pars = pd.read_excel(
1906+
package_data_path("material", "methanol", "methanol_sensitivity_pars.xlsx"),
1907+
sheet_name="Sheet1",
1908+
dtype=object,
1909+
)
1910+
seq_carbon = {
1911+
k: (v["carbon mass"] / v["molar mass"]) * v["plastics use"]
1912+
for k, v in carbon_pars[species].items()
1913+
}
1914+
end_of_life_pars = end_of_life_pars.set_index("par").to_dict()["value"]
1915+
common = {
1916+
"unit": "Mt C",
1917+
"relation": "CO2_Emission",
1918+
"mode": seq_carbon.keys(),
1919+
"technology": tec_species_map[species],
1920+
}
1921+
co2_emi_rel = make_df("relation_activity", **common).drop(columns="value")
1922+
co2_emi_rel = co2_emi_rel.merge(
1923+
pd.Series(seq_carbon, name="value").to_frame().reset_index(),
1924+
left_on="mode",
1925+
right_on="index",
1926+
).drop(columns="index")
1927+
1928+
years = info.Y # [2020, 2025]
1929+
co2_emi_rel = co2_emi_rel.pipe(broadcast, year_act=years)
1930+
co2_emi_rel["year_rel"] = co2_emi_rel["year_act"]
1931+
1932+
co2_emi_rel = co2_emi_rel.pipe(broadcast, node_loc=nodes_ex_world(info.N)).pipe(
1933+
same_node
1934+
)
1935+
1936+
def apply_eol_factor(row, pars):
1937+
if row["year_act"] < pars["incin_trend_end"]:
1938+
share = pars["incin_rate"] + pars["incin_trend"] * (row["year_act"] - 2020)
1939+
else:
1940+
share = 0.5
1941+
return row["value"] * (1 - share)
1942+
1943+
co2_emi_rel["value"] = co2_emi_rel.apply(
1944+
lambda x: apply_eol_factor(x, end_of_life_pars), axis=1
1945+
).mul(-1)
1946+
return co2_emi_rel
1947+
1948+
1949+
if __name__ == "__main__":
1950+
from ixmp import Platform
1951+
from message_ix import Scenario
1952+
1953+
mp = Platform("ixmp_dev")
1954+
scen = Scenario(mp, "MESSAGEix-Materials", "test_c901_refactoring")
1955+
s_info = ScenarioInfo(scen)
1956+
gen_plastics_emission_factors(s_info, "methanol")

message_ix_models/tests/model/material/test_data_util.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import pandas as pd
2+
import pytest
3+
from message_ix.util import make_df
24

3-
from message_ix_models.model.material.data_util import map_iea_db_to_msg_regs
5+
from message_ix_models import ScenarioInfo
6+
from message_ix_models.model.material.data_util import (
7+
gen_plastics_emission_factors,
8+
map_iea_db_to_msg_regs,
9+
)
410

511
DATA = [
612
["ALB", "R12_EEU"],
@@ -120,3 +126,22 @@ def test_map_iea_db_to_msg_regs() -> None:
120126
# - Add a column with True if these two are equal.
121127
# - Assert all are equal.
122128
assert df_out.merge(df, on="COUNTRY").eval("Z = REGION_x == REGION_y").Z.all()
129+
130+
131+
@pytest.mark.parametrize(
132+
"species",
133+
[
134+
"HVCs",
135+
pytest.param("methanol", marks=pytest.mark.xfail(raises=NotImplementedError)),
136+
],
137+
)
138+
def test_gen_plastics_emission_factors(species):
139+
info = ScenarioInfo()
140+
info.set["node"] = ["node0", "node1"]
141+
info.set["year"] = [2020, 2025]
142+
out = gen_plastics_emission_factors(info, "HVCs")
143+
144+
assert not out.isna().any(axis=None) # Completely full
145+
146+
# Data have the expected columns
147+
assert sorted(make_df("relation_activity").columns) == sorted(out.columns)

0 commit comments

Comments
 (0)