Skip to content

Commit a7c26bd

Browse files
authored
Merge pull request #333 from iiasa/project/ssp/transport/2025-04-10
Improve aviation emissions postprocessing
2 parents f908abb + ddc061c commit a7c26bd

File tree

5 files changed

+126
-86
lines changed

5 files changed

+126
-86
lines changed

doc/whatsnew.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ SSP :ref:`ssp-2024`/ScenarioMIP
7070
- Add :func:`~.ssp.transport.process_df` (:pull:`303`);
7171
handle data frames containing :py:`np.NaN` (:pull:`330`).
7272
- Adapt to revised ‘variable’ codes (:pull:`309`, :issue:`304`).
73+
- Add :func:`~.ssp.transport.method_B` and make this the default (:pull:`259`, :pull:`330`).
74+
- Add :func:`~.ssp.transport.method_C` (:issue:`305`, :pull:`325`, :pull:`330`).
75+
- Add :func:`~.ssp.transport.process_df` (:pull:`303`);
76+
handle data frames containing :py:`np.NaN` (:pull:`330`).
77+
- Adapt to revised ‘variable’ codes (:pull:`309`, :issue:`304`).
78+
- Expand covered emission species (:pull:`333`, :issue:`307`)
79+
with values derived from `CEDS <https://www.pnnl.gov/projects/ceds>`_.
7380

7481
Transport
7582
---------

message_ix_models/data/emission.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
# Annotations appearing in this code list:
2+
#
3+
# - report: label used in IAMC 'variable' names. Default: the code ID.
4+
# - unit: mass units only for reporting. Default: "Mt".
5+
# - unit-species: species used in a IAMC 'unit' expression such as "Mt XX/yr".
6+
# Default: the code ID.
7+
18
BCA:
29
name: Black carbon
310
report: BC
11+
unit-species: BC
412

513
CH4:
614
name: Methane
@@ -20,10 +28,12 @@ NH3:
2028

2129
NOx:
2230
name: Nitrogen oxides
31+
# unit-species: NO2
2332

2433
OCA:
2534
name: Organic carbon
2635
report: OC
36+
unit-species: OC
2737

2838
SO2:
2939
name: Sulfur dioxide
Lines changed: 76 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,82 @@
11
# Emissions intensity of energy use
22
#
3-
# Sources
4-
# - Group 1: e-mail text from Shaohui Zhang 2024-10-09
5-
# Originally supplied with units [g / kg].
6-
# - Group 2: e-mail attachment from Shaohui Zhang 2024-10-09
7-
# "EMEP_EEA Emission factor.xlsx". These all have the following
8-
# dimensions/metadata in other columns:
9-
# - NFR: 1.A.3.a.ii.(i)
10-
# - Sector: Civil aviation (domestic, LTO)
11-
# → technology=AIR
12-
# - Table: Table_3-3
13-
# - Type: Tier 1 Emission Factor
14-
# - Technology: NA
15-
# - Fuel: Jet Gasoline and Aviation Gasoline
16-
# - Abatement: (empty)
17-
# - Region: NA
18-
# - Pollutant → emission
19-
# - CI_lower, CI_upper, Reference → preserved as comments
20-
# - Unit: kg/tonne fuel, which is equivalent to g / kg fuel.
21-
# - Group 3: e-mail from Lena Höglund-Isaksson 2024-10-06.
22-
# The units for these are kt/petajoule gasoline with [=] g / MJ.
3+
# Sources:
4+
#
5+
# 1. e-mail text from Shaohui Zhang 2024-10-09
6+
# Originally supplied with units [g / kg].
7+
# 2. e-mail attachment from Shaohui Zhang 2024-10-09
8+
# "EMEP_EEA Emission factor.xlsx". These all have the following
9+
# dimensions/metadata in other columns:
10+
# - NFR: 1.A.3.a.ii.(i)
11+
# - Sector: Civil aviation (domestic, LTO)
12+
# → technology=AIR
13+
# - Table: Table_3-3
14+
# - Type: Tier 1 Emission Factor
15+
# - Technology: NA
16+
# - Fuel: Jet Gasoline and Aviation Gasoline
17+
# - Abatement: (empty)
18+
# - Region: NA
19+
# - Pollutant → emission
20+
# - CI_lower, CI_upper, Reference → preserved as comments
21+
# - Unit: kg/tonne fuel, which is equivalent to g / kg fuel.
22+
# 3. e-mail from Lena Höglund-Isaksson 2024-10-06.
23+
# The units for these are kt/petajoule gasoline with [=] g / MJ.
24+
# 4. Estimated as the ratio of CEDS data for y=2019 and MESSAGEix-Transport
25+
# final energy use. The CEDS data use:
26+
# - the code “Sulfur” with units “Mt SO2/yr”.
27+
# - the code “NOx” with units “Mt NO2/yr”.
28+
#
29+
# See https://iiasa-ece.slack.com/archives/CCFHDNA6P/p1744277852653529
2330
#
2431
# Units: g / MJ
2532
#
2633
technology, commodity, emission, value
27-
#
28-
# Group 1
29-
#
30-
AIR, lightoil, SO2, 0.0279 # 1.2 g/kg
31-
# AIR, lightoil, NOx, 0.3284 # 14.12 g/kg, lower end of confidence interval
32-
# AIR, lightoil, NOx, 0.3521 # 15.14 g/kg, upper end of confidence interval
33-
#
34-
# Group 2
35-
#
36-
# “Calculated using Tier 2 method”
37-
# - These values are used because the above are too large and produce negative totals.
38-
#
39-
# AIR, lightoil, NOx, 0.0465 # 2 g/kg
40-
AIR, lightoil, NOx, 0.0930 # 4 g/kg
41-
# AIR, lightoil, NOx, 0.1860 # 8 g/kg
42-
#
43-
# “Calculated using Tier 2 method”
44-
# - The low end of the range is used because the midpoint value produces negative totals.
45-
#
46-
AIR, lightoil, CO, 0.5 # Placeholder value for debugging
47-
# AIR, lightoil, CO, 13.95 # 600 g/kg
48-
# AIR, lightoil, CO, 27.90 # 1200 g/kg
49-
# AIR, lightoil, CO, 55.81 # 2400 g/kg
50-
#
51-
# “Calculated using Tier 2 method”
52-
# - These data were provided with the code 'NMVOC', but we use the label 'VOC' to
53-
# align with MESSAGEix-GLOBIOM, even though these are not strictly the same.
54-
# - The low end of the range is used because the midpoint value produces negative totals.
55-
#
56-
AIR, lightoil, VOC, 0.1 # Placeholder value for debugging
57-
# AIR, lightoil, VOC, 0.2209 # 9.5 g/kg
58-
# AIR, lightoil, VOC, 0.4419 # 19 g/kg
59-
# AIR, lightoil, VOC, 0.8837 # 38 g/kg
60-
#
61-
# “Assuming 0.05% S by mass”
62-
#
63-
# AIR, lightoil, SOx, 0.0116 # 0.5 g/kg
64-
AIR, lightoil, SOx, 0.0233 # 1 g/kg
65-
# AIR, lightoil, SOx, 0.0465 # 2 g/kg
66-
#
67-
# Group 3
68-
#
69-
AIR, lightoil, CH4, 0.0005
70-
AIR, lightoil, N2O, 0.0031
34+
35+
AIR, lightoil, BC, 0.001430 # (4)
36+
37+
# (3) is used because the (4) is (nearly) zero.
38+
AIR, lightoil, CH4, 0.0005 # (3)
39+
# AIR, lightoil, CH4, 0.000000 # (4)
40+
41+
# (4) is used because (2) values are too large and yield negative totals.
42+
#
43+
# AIR, lightoil, CO, 13.95 # (2) 600 g/kg “Calculated using Tier 2 method”
44+
# AIR, lightoil, CO, 27.90 # (2) 1200 g/kg “Calculated using Tier 2 method”
45+
# AIR, lightoil, CO, 55.81 # (2) 2400 g/kg “Calculated using Tier 2 method”
46+
AIR, lightoil, CO, 0.088798 # (4)
47+
48+
AIR, lightoil, CO2, 111.053342 # (4)
49+
50+
AIR, lightoil, N2O, 0.0031 # (3)
51+
# AIR, lightoil, N2O, 3.635365 # (4)
52+
53+
AIR, lightoil, NH3, 0.005857 # (4)
54+
55+
# NOx:
56+
# - (2) is used because (1) and (4) values are too large and yield negative totals.
57+
#
58+
# AIR, lightoil, NOx, 0.3284 # (1) 14.12 g/kg, lower end of confidence interval
59+
# AIR, lightoil, NOx, 0.3521 # (1) 15.14 g/kg, upper end of confidence interval
60+
# AIR, lightoil, NOx, 0.0465 # (2) 2 g/kg “Calculated using Tier 2 method”
61+
AIR, lightoil, NOx, 0.0930 # (2) 4 g/kg “Calculated using Tier 2 method”
62+
# AIR, lightoil, NOx, 0.1860 # (2) 8 g/kg “Calculated using Tier 2 method”
63+
# AIR, lightoil, NOx, 0.456715 # (4)
64+
65+
AIR, lightoil, OC, 0.000709 # (4)
66+
67+
AIR, lightoil, Sulfur, 0.0279 # (1) 1.2 g/kg
68+
# AIR, lightoil, Sulfur, 0.040058 # (4) “Sulfur” in upstream data with “SO2” in units
69+
70+
# AIR, lightoil, SOx, 0.0116 # (2) 0.5 g/kg “Assuming 0.05% S by mass”
71+
AIR, lightoil, SOx, 0.0233 # (2) 1 g/kg “Assuming 0.05% S by mass”
72+
# AIR, lightoil, SOx, 0.0465 # (2) 2 g/kg “Assuming 0.05% S by mass”
73+
74+
# VOC:
75+
# - (2) were provided with the code 'NMVOC', but we use the label 'VOC' to align with
76+
# MESSAGEix-GLOBIOM, even though these are not strictly the same.
77+
# - (4) is used because (2) values are too large and yield negative totals.
78+
#
79+
# AIR, lightoil, VOC, 0.2209 # (2) 9.5 g/kg “Calculated using Tier 2 method”
80+
# AIR, lightoil, VOC, 0.4419 # (2) 19 g/kg “Calculated using Tier 2 method”
81+
# AIR, lightoil, VOC, 0.8837 # (2) 38 g/kg “Calculated using Tier 2 method”
82+
AIR, lightoil, VOC, 0.013246 # (4)

message_ix_models/project/ssp/transport.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,17 +157,19 @@ def e_UNIT(cl_emission: "sdmx.model.common.Codelist") -> "AnyQuantity":
157157
Values are everywhere 1.0, except for species such as ``N2O`` that must be
158158
reported in kt rather than Mt.
159159
"""
160+
# Iterate over codes in the codelist
160161
data = []
161162
for e in cl_emission:
162-
try:
163-
label = str(e.get_annotation(id="report").text)
164-
except KeyError:
165-
label = e.id
166-
try:
167-
unit = str(e.get_annotation(id="units").text)
168-
except KeyError:
169-
unit = "Mt"
170-
data.append([e.id, f"{unit} {label}/yr", 1.0 if unit == "Mt" else 1e3])
163+
# Retrieve info from annotations
164+
i = {}
165+
for k, default in {"report": e.id, "unit-species": e.id, "units": "Mt"}.items():
166+
try:
167+
i[k] = str(e.get_annotation(id=k).text)
168+
except KeyError:
169+
i[k] = default
170+
171+
scale_factor = 1.0 if i["units"] == "Mt" else 1e3
172+
data.append([i["report"], f"{i['units']} {i['unit-species']}/yr", scale_factor])
171173

172174
dims = "e UNIT value".split()
173175
return genno.Quantity(
@@ -285,9 +287,22 @@ def get_computer(
285287
# - Update it using the `sc`.
286288
# - Retrieve a 'label' used to construct a target scenario URL.
287289
label_full = TransportConfig.from_context(context).use_scenario_code(sc)[1]
288-
# - Construct the target scenario URL.
289-
# - Use it to update context.core.scenario_info.
290+
# Construct the target scenario URL
290291
url = workflow.scenario_url(context, label_full)
292+
# Optionally apply a regex substitution
293+
URL_SUB = {
294+
"LED-SSP1": ("$", "#102"), # Point to a specific version
295+
"LED-SSP2": ("$", "#108"),
296+
"SSP1": ("$", "#652"),
297+
"SSP2": ("$", "#695"),
298+
"SSP3": ("$", "#569"),
299+
"SSP4": ("$", "#525"),
300+
"SSP5": ("$", "#522"), # Other scenario name
301+
# "SSP5": ("(SSP_2024.5) baseline$", r"\1 baseline#525"), # Other scenario name
302+
}
303+
if pattern_repl := URL_SUB.get(sc.id):
304+
url = re.sub(pattern_repl[0], pattern_repl[1], url)
305+
# Use the URL to update context.core.scenario_info
291306
context.handle_cli_args(url=url)
292307

293308
log.info(f"method 'C' will use data from {url}")

message_ix_models/tests/project/ssp/test_transport.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections.abc import Callable, Hashable
2+
from functools import cache
23
from typing import TYPE_CHECKING
34

45
import numpy as np
@@ -60,13 +61,7 @@ def input_xlsx_path(tmp_path_factory, input_csv_path) -> "pathlib.Path":
6061
SPECIES = {"CH4", "BC", "CO", "CO2", "N2O", "NH3", "NOx", "OC", "Sulfur", "VOC"}
6162

6263
#: Species for which no aviation-specific emission factor values are available.
63-
SPECIES_WITHOUT_EF = {
64-
"BC", # No emissions factor data for e=BCA
65-
"CO2",
66-
"NH3",
67-
"OC", # No emissions factor data for e=OCA
68-
"Sulfur", # No emissions factor data for e=SO2; only SOx
69-
}
64+
SPECIES_WITHOUT_EF: set[str] = set()
7065

7166

7267
def check(df_in: pd.DataFrame, df_out: pd.DataFrame, method: METHOD) -> None:
@@ -123,10 +118,10 @@ def _to_long(df):
123118
N_exp = {
124119
(METHOD.A, False): 10280,
125120
(METHOD.A, True): 10280,
126-
(METHOD.B, False): 5060,
127-
(METHOD.B, True): 3860,
128-
(METHOD.C, False): 3500,
129-
(METHOD.C, True): 3500,
121+
(METHOD.B, False): 10120,
122+
(METHOD.B, True): 7720,
123+
(METHOD.C, False): 7000,
124+
(METHOD.C, True): 7000,
130125
}[(method, iea_eweb_test_data)]
131126

132127
if N_exp != len(df):
@@ -145,6 +140,7 @@ def _to_long(df):
145140
assert iea_eweb_test_data, msg # Negative values → fail if NOT using test data
146141

147142

143+
@cache
148144
def expected_variables(flag: int, method: METHOD) -> set[str]:
149145
"""Set of expected ‘Variable’ codes according to `flag` and `method`."""
150146
# Shorthand

0 commit comments

Comments
 (0)