3
3
import logging
4
4
import re
5
5
from collections .abc import Hashable
6
- from typing import TYPE_CHECKING
6
+ from functools import cache
7
+ from typing import TYPE_CHECKING , Literal , Optional
7
8
8
9
import genno
9
10
import pandas as pd
10
- import xarray as xr
11
11
from genno import Key
12
12
from genno .core .key import single_key
13
13
28
28
#: Dimensions of several quantities.
29
29
DIMS = "e n t y UNIT" .split ()
30
30
31
- #: Expression for IAMC ‘variable’ names used in :func:`main`.
32
- EXPR_EMI = r"^Emissions\|(?P<e>[^\|]+)\|Energy\|Demand\|Transportation(?:\|(?P<t>.*))?$"
33
- EXPR_FE = r"^Final Energy\|Transportation\|(?P<c>Liquids\|Oil)$"
31
+ EXPR_EMI = re .compile (
32
+ r"^Emissions\|(?P<e>[^\|]+)\|Energy\|Demand\|(?P<t>(Bunkers|Transportation).*)$"
33
+ )
34
+ EXPR_FE = re .compile (r"^Final Energy\|Transportation\|(?P<c>Liquids\|Oil)$" )
34
35
35
36
#: :class:`.IEA_EWEB` flow codes used in the current file.
36
37
FLOWS = ["AVBUNK" , "DOMESAIR" , "TOTTRANS" ]
@@ -63,27 +64,53 @@ def aviation_share(ref: "TQuantity") -> "TQuantity":
63
64
)
64
65
65
66
66
- def broadcast_t (include_international : bool ) -> "AnyQuantity" :
67
+ def broadcast_t (version : Literal [ 1 , 2 ], include_international : bool ) -> "AnyQuantity" :
67
68
"""Quantity to re-add the |t| dimension.
68
69
69
70
Parameters
70
71
----------
71
- include_international
72
- If :any:`True`, include "Aviation|International" with magnitude 1.0. Otherwise,
73
- omit
72
+ version :
73
+ Version of ‘variable’ names supported by the current module.
74
+ include_international :
75
+ If :any:`True`, include "Transportation|Aviation|International" with magnitude
76
+ 1.0. Otherwise, omit.
74
77
75
78
Return
76
79
------
77
80
genno.Quantity
78
- with dimension "t" and the values:
81
+ with dimension "t".
79
82
80
- - +1.0 for t="Aviation", a label with missing data.
81
- - -1.0 for t="Road Rail and Domestic Shipping", a label with existing data from
82
- which the aviation total should be subtracted.
83
+ If :py:`version=1`, the values include:
84
+
85
+ - +1.0 for t="Transportation|Aviation", a label with missing data.
86
+ - -1.0 for t="Transportation|Road Rail and Domestic Shipping", a label with
87
+ existing data from which the aviation total must be subtracted.
88
+
89
+ If :py:`version=2`, the values include:
90
+
91
+ - +1.0 for t="Bunkers" and t="Bunkers|International Aviation", labels with zeros
92
+ in the input data file.
93
+ - -1.0 for t="Transportation" and t="Transportation|Road Rail and Domestic
94
+ Shipping", labels with existing data from which the aviation total must be
95
+ subtracted.
83
96
"""
84
- value = [1 , - 1 , 1 ]
85
- t = ["Aviation" , "Road Rail and Domestic Shipping" , "Aviation|International" ]
86
- idx = slice (None ) if include_international else slice (- 1 )
97
+ if version == 1 :
98
+ value = [1 , - 1 , 1 ]
99
+ t = [
100
+ "Transportation|Aviation" ,
101
+ "Transportation|Road Rail and Domestic Shipping" ,
102
+ "Transportation|Aviation|International" ,
103
+ ]
104
+ idx = slice (None ) if include_international else slice (- 1 )
105
+ elif version == 2 :
106
+ value = [1 , 1 , - 1 , - 1 ]
107
+ t = [
108
+ "Bunkers" ,
109
+ "Bunkers|International Aviation" ,
110
+ "Transportation" ,
111
+ "Transportation|Road Rail and Domestic Shipping" ,
112
+ ]
113
+ idx = slice (None )
87
114
88
115
return genno .Quantity (value [idx ], coords = {"t" : t [idx ]})
89
116
@@ -119,59 +146,6 @@ def e_UNIT(cl_emission: "sdmx.model.common.Codelist") -> "AnyQuantity":
119
146
)
120
147
121
148
122
- def extract_dims (
123
- qty : "TQuantity" , dim_expr : dict , * , drop : bool = True , fillna : str = "_T"
124
- ) -> "TQuantity" :
125
- """Extract dimensions from IAMC-like ‘variable’ names using regular expressions."""
126
- dims = list (qty .dims )
127
-
128
- dfs = [qty .to_series ().rename ("value" ).reset_index ()]
129
- for dim , expr in dim_expr .items ():
130
- pattern = re .compile (expr )
131
- dfs .append (dfs [0 ][dim ].str .extract (pattern ).fillna (fillna ))
132
- dims .extend (pattern .groupindex )
133
- if drop :
134
- dims .remove (dim )
135
-
136
- return genno .Quantity (pd .concat (dfs , axis = 1 ).set_index (dims )["value" ])
137
-
138
-
139
- def extract_dims1 (qty : "TQuantity" , dim : dict ) -> "TQuantity" : # pragma: no cover
140
- """Extract dimensions from IAMC-like ‘variable’ names expressions.
141
-
142
- .. note:: This incomplete, non-working version of :func:`extract_dims` uses
143
- :mod:`xarray` semantics.
144
- """
145
- from collections import defaultdict
146
-
147
- result = qty
148
- for d0 , expr in dim .items ():
149
- d0_new = f"{ d0 } _new"
150
- pattern = re .compile (expr )
151
-
152
- indexers : dict [Hashable , list [Hashable ]] = {g : [] for g in pattern .groupindex }
153
- indexers [d0_new ] = []
154
-
155
- coords = qty .coords [d0 ].data .astype (str )
156
- for coord in coords :
157
- if match := pattern .match (coord ):
158
- groupdict = match .groupdict ()
159
- coord_new = coord [match .span ()[1 ] :]
160
- else :
161
- groupdict = defaultdict (None )
162
- coord_new = coord
163
-
164
- for g in pattern .groupindex :
165
- indexers [g ].append (groupdict [g ])
166
- indexers [d0_new ].append (coord_new )
167
-
168
- for d1 , labels in indexers .items ():
169
- i2 = {d0 : xr .DataArray (coords , coords = {d1 : labels })}
170
- result = result .sel (i2 )
171
-
172
- return result
173
-
174
-
175
149
def finalize (
176
150
q_all : "TQuantity" , q_update : "TQuantity" , model_name : str , scenario_name : str
177
151
) -> pd .DataFrame :
@@ -210,9 +184,7 @@ def _expand(qty):
210
184
.to_frame ()
211
185
.reset_index ()
212
186
.assign (
213
- Variable = lambda df : (
214
- "Emissions|" + df ["e" ] + "|Energy|Demand|Transportation|" + df ["t" ]
215
- ).str .replace ("|_T" , "" ),
187
+ Variable = lambda df : "Emissions|" + df ["e" ] + "|Energy|Demand|" + df ["t" ]
216
188
)
217
189
.drop (["e" , "t" ], axis = 1 )
218
190
.set_index (s_all .index .names )[0 ]
@@ -239,17 +211,19 @@ def prepare_computer(c: "Computer", k_input: Key, method: str) -> "KeyLike":
239
211
str
240
212
"target". Calling :py:`c.get("target")` triggers the calculation.
241
213
"""
214
+ c .require_compat ("message_ix_models.report.operator" )
215
+
242
216
# Common structure and utility quantities used by prepare_method_[AB]
243
- c .add (f"broadcast:t:{ L } " , broadcast_t , include_international = method == "A" )
217
+ c .add (
218
+ f"broadcast:t:{ L } " , broadcast_t , version = 2 , include_international = method == "A"
219
+ )
244
220
245
- k_emi_in , e_t = Key (L , DIMS , "input" ), tuple ( "et " )
221
+ k_emi_in = Key (L , DIMS , "input" )
246
222
247
223
# Select and transform data matching EXPR_EMI
248
- # Filter on "VARIABLE"
249
- c .add (k_emi_in [0 ] / e_t , select_re , k_input , indexers = {"VARIABLE" : EXPR_EMI })
250
- # Extract the "e" and "t" dimensions from "VARIABLE"
251
- c .add (k_emi_in [1 ], extract_dims , k_emi_in [0 ] / e_t , dim_expr = {"VARIABLE" : EXPR_EMI })
252
- c .add (k_emi_in [2 ], "assign_units" , k_emi_in [1 ], units = "Mt/year" )
224
+ # Filter on "VARIABLE", expand the (e, t) dimensions from "VARIABLE"
225
+ c .add (k_emi_in [0 ], "select_expand" , k_input , dim_cb = {"VARIABLE" : v_to_emi_coords })
226
+ c .add (k_emi_in [1 ], "assign_units" , k_emi_in [0 ], units = "Mt/year" )
253
227
254
228
# Call a function to prepare the remaining calculations
255
229
prepare_func = {"A" : prepare_method_A , "B" : prepare_method_B }[method ]
@@ -318,11 +292,8 @@ def prepare_method_B(
318
292
is, final energy use by aviation.
319
293
6. Load emissions intensity of aviation final energy use from the file
320
294
:ref:`transport-input-emi-intensity`.
321
- 7. Multiply (4) × (5) × (6) to compute the estimate of
322
- ``Emissions|*|Energy|Demand|Transportation|Aviation``.
323
- 8. Estimate an additive adjustment to
324
- ``Emissions|*|Energy|Demand|Transportation|Road Rail and Domestic Shipping`` as
325
- the negative of (7).
295
+ 7. Multiply (4) × (5) × (6) to compute the estimate of aviation emissions.
296
+ 8. Estimate adjustments according to :func:`broadcast_t`.
326
297
9. Adjust `k_emi_in` by adding (7) and (8).
327
298
"""
328
299
from message_ix_models .model .transport import build
@@ -379,14 +350,11 @@ def prepare_method_B(
379
350
380
351
### Prepare data from the input data file: total transport consumption of light oil
381
352
382
- # Filter on "VARIABLE"
383
- c .add (k_fe_in [0 ] / "c" , select_re , k_input , indexers = {"VARIABLE" : EXPR_FE })
384
-
385
- # Extract the "e" dimensions from "VARIABLE"
386
- c .add (k_fe_in [1 ], extract_dims , k_fe_in [0 ] / "c" , dim_expr = {"VARIABLE" : EXPR_FE })
353
+ # Filter on "VARIABLE", extract (e) dimension
354
+ c .add (k_fe_in [0 ], "select_expand" , k_input , dim_cb = {"VARIABLE" : v_to_fe_coords })
387
355
388
356
# Convert "UNIT" dim labels to Quantity.units
389
- c .add (k_fe_in [2 ] / "UNIT" , "unique_units_from_dim" , k_fe_in [1 ], dim = "UNIT" )
357
+ c .add (k_fe_in [1 ] / "UNIT" , "unique_units_from_dim" , k_fe_in [0 ], dim = "UNIT" )
390
358
391
359
# Relabel:
392
360
# - c[ommodity]: 'Liquids|Oil' (IAMC 'variable' component) → 'lightoil'
@@ -395,7 +363,7 @@ def prepare_method_B(
395
363
c = {"Liquids|Oil" : "lightoil" },
396
364
n = {n .id .partition ("_" )[2 ]: n .id for n in get_codelist ("node/R12" )},
397
365
)
398
- c .add (k_fe_in [3 ] / "UNIT" , "relabel" , k_fe_in [2 ] / "UNIT" , labels = labels )
366
+ c .add (k_fe_in [2 ] / "UNIT" , "relabel" , k_fe_in [1 ] / "UNIT" , labels = labels )
399
367
400
368
### Compute estimate of emissions
401
369
# Product of aviation share and FE of total transport → FE of aviation
@@ -500,11 +468,19 @@ def process_file(
500
468
c .get ("target" ).to_csv (path_out , index = False )
501
469
502
470
503
- def select_re (qty : "AnyQuantity" , indexers : dict ) -> "AnyQuantity" :
504
- """Select from `qty` using regular expressions for each dimension."""
505
- new_indexers = dict ()
506
- for dim , expr in indexers .items ():
507
- new_indexers [dim ] = list (
508
- map (str , filter (re .compile (expr ).match , qty .coords [dim ].data .astype (str )))
509
- )
510
- return qty .sel (new_indexers )
471
+ @cache
472
+ def v_to_fe_coords (value : Hashable ) -> Optional [dict [str , str ]]:
473
+ """Match ‘variable’ names used in :func:`main`."""
474
+ if match := EXPR_FE .fullmatch (str (value )):
475
+ return match .groupdict ()
476
+ else :
477
+ return None
478
+
479
+
480
+ @cache
481
+ def v_to_emi_coords (value : Hashable ) -> Optional [dict [str , str ]]:
482
+ """Match ‘variable’ names used in :func:`main`."""
483
+ if match := EXPR_EMI .fullmatch (str (value )):
484
+ return match .groupdict ()
485
+ else :
486
+ return None
0 commit comments