10
10
import genno
11
11
import pandas as pd
12
12
from genno import Key
13
+ from genno .operator import expand_dims
13
14
14
15
from message_ix_models import Context
16
+ from message_ix_models .model .structure import get_codelist
15
17
from message_ix_models .tools .iamc import iamc_like_data_for_query , to_quantity
16
18
from message_ix_models .util import minimum_version
17
19
from message_ix_models .util .genno import Keys
57
59
#: - :py:`.emi`: computed aviation emissions.
58
60
#: - :py:`.emi_in`: input data for aviation and other transport emissions, to be
59
61
#: adjusted or overwritten.
62
+ #: - :py:`.fe`: computed final energy data.
63
+ #: - :py:`.fe_in`: input data for transport final energy, to be adjusted or overwritten.
60
64
K = Keys (
61
65
bcast = f"broadcast:t:{ L } " ,
62
66
input = f"input:n-y-VARIABLE-UNIT:{ L } " ,
63
67
emi = f"emission:e-n-t-y-UNIT:{ L } " ,
64
- emi_in = f"emission:e-n-t-y-UNIT:{ L } +input" ,
68
+ emi_in = f"emission:e-n-t-y-UNIT:{ L } +in" ,
69
+ fe_in = f"fe:c-n-t-y:{ L } +in" ,
70
+ fe_out = f"fe:c-n-t-y:{ L } +out" ,
65
71
)
66
72
67
73
@@ -100,8 +106,10 @@ def aviation_emi_share(ref: "TQuantity") -> "TQuantity":
100
106
)
101
107
102
108
103
- def broadcast_t (version : Literal [1 , 2 ], include_international : bool ) -> "AnyQuantity" :
104
- """Quantity to re-add the |t| dimension.
109
+ def broadcast_t_emi (
110
+ version : Literal [1 , 2 ], include_international : bool
111
+ ) -> "AnyQuantity" :
112
+ """Quantity to re-add the |t| dimension for emission data.
105
113
106
114
Parameters
107
115
----------
@@ -151,6 +159,22 @@ def broadcast_t(version: Literal[1, 2], include_international: bool) -> "AnyQuan
151
159
return genno .Quantity (value [idx ], coords = {"t" : t [idx ]})
152
160
153
161
162
+ def broadcast_t_fe () -> "AnyQuantity" :
163
+ """Quantity to re-add the |t| dimension for final energy data."""
164
+ return genno .Quantity (
165
+ pd .DataFrame (
166
+ [
167
+ ["lightoil" , "Bunkers" , "" , + 1.0 ],
168
+ ["lightoil" , "Bunkers|International Aviation" , "" , + 1.0 ],
169
+ ["lightoil" , "Bunkers" , "Liquids|Oil" , + 1.0 ],
170
+ ["lightoil" , "Transportation" , "" , - 1.0 ],
171
+ ["lightoil" , "Transportation" , "Liquids|Oil" , - 1.0 ],
172
+ ],
173
+ columns = ["c" , "t" , "c_new" , "value" ],
174
+ ).set_index (["c" , "t" , "c_new" ])["value" ]
175
+ )
176
+
177
+
154
178
def e_UNIT (cl_emission : "sdmx.model.common.Codelist" ) -> "AnyQuantity" :
155
179
"""Return a quantity for broadcasting.
156
180
@@ -185,7 +209,11 @@ def e_UNIT(cl_emission: "sdmx.model.common.Codelist") -> "AnyQuantity":
185
209
186
210
187
211
def finalize (
188
- q_all : "TQuantity" , q_update : "TQuantity" , model_name : str , scenario_name : str
212
+ q_all : "TQuantity" ,
213
+ q_emi_update : "TQuantity" ,
214
+ q_fe_update : "TQuantity" ,
215
+ model_name : str ,
216
+ scenario_name : str ,
189
217
) -> pd .DataFrame :
190
218
"""Finalize output.
191
219
@@ -213,12 +241,12 @@ def _expand(qty):
213
241
# Convert `q_all` to pd.Series
214
242
s_all = q_all .pipe (_expand ).to_series ()
215
243
216
- # - Convert `q_update ` to pd.Series
244
+ # - Convert `q_emi_update ` to pd.Series
217
245
# - Reassemble "Variable" codes.
218
246
# - Drop dimensions (e, t).
219
247
# - Align index with s_all.
220
- s_update = (
221
- q_update .pipe (_expand )
248
+ s_emi_update = (
249
+ q_emi_update .pipe (_expand )
222
250
.to_frame ()
223
251
.reset_index ()
224
252
.assign (
@@ -228,13 +256,37 @@ def _expand(qty):
228
256
.set_index (s_all .index .names )[0 ]
229
257
.rename ("value" )
230
258
)
231
- log .info (f"{ len (s_update )} obs to update" )
232
-
233
- # Update `s_all`. This yields an 'outer join' of the original and s_update indices.
234
- s_all .update (s_update )
259
+ log .info (f'{ len (s_emi_update )} obs to update for Variable="Emission|…"' )
235
260
261
+ # Likewise for q_fe_update
262
+ dim = {"UNIT" : [f"{ q_fe_update .units :~} " .replace ("EJ / a" , "EJ/yr" )]}
263
+ s_fe_update = (
264
+ q_fe_update .pipe (expand_dims , dim = dim )
265
+ .pipe (_expand )
266
+ .to_frame ()
267
+ .reset_index ()
268
+ .assign (
269
+ Variable = lambda df : ("Final Energy|" + df ["t" ] + "|" + df ["c" ]).str .replace (
270
+ r"\|$" , "" , regex = True
271
+ )
272
+ )
273
+ .drop (["c" , "t" ], axis = 1 )
274
+ .set_index (s_all .index .names )[0 ]
275
+ .rename ("value" )
276
+ )
277
+ log .info (f'{ len (s_fe_update )} obs to update for Variable="Final Energy|…"' )
278
+
279
+ # - Concatenate s_all, s_emi_update, and s_fe_update as columns of a data frame.
280
+ # The result has the superset of the indices of the arguments.
281
+ # - Fill along axes. Values from s_*_update end up in the last column.
282
+ # - Select the last column.
283
+ # - Reshape to wide format.
284
+ # - Rename index levels and restore to columns.
236
285
return (
237
- s_all .unstack ("y" )
286
+ pd .concat ([s_all , s_emi_update , s_fe_update ], axis = 1 )
287
+ .ffill (axis = 1 )
288
+ .iloc [:, - 1 ]
289
+ .unstack ("y" )
238
290
.reorder_levels (["Model" , "Scenario" , "Region" , "Variable" , "Unit" ])
239
291
.reset_index ()
240
292
)
@@ -315,32 +367,65 @@ def get_computer(
315
367
log .info (f"method 'C' will use data from { url } " )
316
368
317
369
# Common structure and utility quantities used by method_[ABC]
318
- c .add (K .bcast , broadcast_t , version = 2 , include_international = method == "A" )
370
+ c .add (K .bcast , broadcast_t_emi , version = 2 , include_international = method == "A" )
319
371
320
372
# Placeholder for data-loading task. This is filled in later by process_df() or
321
373
# process_file().
322
374
c .add (K .input , None )
323
375
324
376
# Select and transform data matching EXPR_EMI
325
- # Filter on "VARIABLE", expand the (e, t) dimensions from "VARIABLE"
377
+ # Filter on "VARIABLE", extract the (e, t) dimensions
326
378
c .add (K .emi_in [0 ], "select_expand" , K .input , dim_cb = {"VARIABLE" : v_to_emi_coords })
379
+ # Assign units
327
380
c .add (K .emi_in , "assign_units" , K .emi_in [0 ], units = "Mt/year" )
328
381
382
+ # Select and transform data matching EXPR_FE
383
+ # Filter on "VARIABLE", extract the (c, t) dimensions
384
+ dim_cb = {"VARIABLE" : v_to_fe_coords }
385
+ c .add (K .fe_in [0 ] * "UNITS" , "select_expand" , K .input , dim_cb = dim_cb )
386
+ # Convert "UNIT" dim labels to Quantity.units
387
+ c .add (K .fe_in [1 ], "unique_units_from_dim" , K .fe_in [0 ] * "UNITS" , dim = "UNIT" )
388
+ # Change labels; see get_label()
389
+ c .add (K .fe_in , "relabel" , K .fe_in [1 ], labels = get_labels ())
390
+
329
391
# Call a function to prepare the remaining calculations up to K.emi
330
392
method_func = {METHOD .A : method_A , METHOD .B : method_B , METHOD .C : method_C }[method ]
331
393
method_func (c )
332
394
333
395
# Adjust the original data by adding the (maybe negative) prepared values at K.emi
334
396
c .add (K .emi ["adj" ], "add" , K .emi_in , K .emi )
397
+ c .add (K .fe_out ["adj" ], "add" , K .fe_in [1 ], K .fe_out )
335
398
336
399
# Add a key "target" to:
337
400
# - Collapse to IAMC "VARIABLE" dimension name.
338
401
# - Recombine with other/unaltered original data.
339
- c .add ("target" , finalize , K .input , K .emi ["adj" ], "model name" , "scenario name" )
402
+ c .add (
403
+ "target" ,
404
+ finalize ,
405
+ K .input ,
406
+ K .emi ["adj" ],
407
+ K .fe_out ["adj" ],
408
+ "model name" ,
409
+ "scenario name" ,
410
+ )
340
411
341
412
return c
342
413
343
414
415
+ @cache
416
+ def get_labels ():
417
+ """Return mapper for relabelling input data:
418
+
419
+ - c[ommodity]: 'Liquids|Oil' (IAMC 'variable' component) → 'lightoil'.
420
+ - n[ode]: "AFR" → "R12_AFR" etc. "World" is not changed.
421
+ """
422
+ cl = get_codelist ("node/R12" )
423
+ labels = dict (c = {"Liquids|Oil" : "lightoil" , "" : "_T" }, n = {})
424
+ for n in filter (lambda n : len (n .child ) and n .id != "World" , cl ):
425
+ labels ["n" ][n .id .partition ("_" )[2 ]] = n .id
426
+ return labels
427
+
428
+
344
429
def get_scenario_code (model_name : str , scenario_name : str ) -> "sdmx.model.common.Code" :
345
430
"""Return a specific code from ``CL_TRANSPORT_SCENARIO``.
346
431
@@ -389,6 +474,9 @@ def method_A(c: "Computer") -> None:
389
474
# Rail and Domestic Shipping"
390
475
c .add (K .emi , "mul" , K .emi [0 ] / "t" , k_share , K .bcast )
391
476
477
+ # No change to final energy data
478
+ c .add (K .fe_out , genno .Quantity (0.0 , units = "EJ / a" ))
479
+
392
480
393
481
def method_B (c : "Computer" ) -> None :
394
482
"""Prepare calculations up to :data:`K.emi` using :data:`METHOD.B`.
@@ -467,10 +555,10 @@ def method_BC_common(c: "Computer", k_fe_share: "Key") -> None:
467
555
A key with dimensions either :math:`(c, n)` or :math:`(c, n, y)` giving the
468
556
share of aviation in total transport final energy.
469
557
"""
470
- from message_ix_models . model . structure import get_codelist
558
+
471
559
from message_ix_models .model .transport .key import exo
472
560
473
- # Check dimensions of k_emi_share
561
+ # Check dimensions of k_fe_share
474
562
exp = {frozenset ("cn" ), frozenset ("cny" )}
475
563
if set (k_fe_share .dims ) not in exp : # pragma: no cover
476
564
raise ValueError (f"Dimensions of k_cn={ k_fe_share .dims } are not in { exp } " )
@@ -479,31 +567,17 @@ def method_BC_common(c: "Computer", k_fe_share: "Key") -> None:
479
567
k = Keys (
480
568
ei = exo .emi_intensity , # Dimensions (c, e, t)
481
569
emi0 = Key ("emission" , ("ceny" ), L ),
482
- fe_in = Key ("fe" , ("c" , "n" , "y" , "UNIT" ), "input" ),
483
- fe = Key ("fe" , tuple ("cny" ), L ),
570
+ fe = Key ("fe" , tuple ("cny" ), f"{ L } +BC" ),
484
571
units = Key (f"units:e-UNIT:{ L } " ),
485
572
)
486
573
487
- ### Prepare data from the input data file: total transport consumption of light oil
488
-
489
- # Filter on "VARIABLE", extract (e) dimension
490
- c .add (k .fe_in [0 ], "select_expand" , K .input , dim_cb = {"VARIABLE" : v_to_fe_coords })
491
-
492
- # Convert "UNIT" dim labels to Quantity.units
493
- c .add (k .fe_in [1 ] / "UNIT" , "unique_units_from_dim" , k .fe_in [0 ], dim = "UNIT" )
494
-
495
- # Relabel:
496
- # - c[ommodity]: 'Liquids|Oil' (IAMC 'variable' component) → 'lightoil'
497
- # - n[ode]: "AFR" → "R12_AFR" etc. "World" is not changed.
498
- cl = get_codelist ("node/R12" )
499
- labels = dict (c = {"Liquids|Oil" : "lightoil" }, n = {})
500
- for n in filter (lambda n : len (n .child ) and n .id != "World" , cl ):
501
- labels ["n" ][n .id .partition ("_" )[2 ]] = n .id
502
- c .add (k .fe_in [2 ] / "UNIT" , "relabel" , k .fe_in [1 ] / "UNIT" , labels = labels )
574
+ # Select only total transport consumption of lightoil from K.fe_in
575
+ indexers = {"t" : "Transportation (w/ bunkers)" }
576
+ c .add (k .fe [0 ], "select" , K .fe_in , indexers = indexers , drop = True )
503
577
504
578
### Compute estimate of emissions
505
579
# Product of aviation share and FE of total transport → FE of aviation
506
- c .add (k .fe , "mul" , k .fe_in [ 2 ] / "UNIT" , k_fe_share )
580
+ c .add (k .fe , "mul" , k .fe [ 0 ] , k_fe_share )
507
581
508
582
# Convert exogenous emission intensity data to Mt / EJ
509
583
c .add (k .ei ["units" ], "convert_units" , k .ei , units = "Mt / EJ" )
@@ -524,9 +598,16 @@ def method_BC_common(c: "Computer", k_fe_share: "Key") -> None:
524
598
c .add (K .emi [2 ], "mul" , k .emi0 [1 ], k .units , K .bcast )
525
599
526
600
# Restore labels: "R12_AFR" → "AFR" etc. "World" is not changed.
527
- labels = dict (n = {v : k for k , v in labels ["n" ].items ()})
601
+ labels = dict (n = {v : k for k , v in get_labels () ["n" ].items ()})
528
602
c .add (K .emi , "relabel" , K .emi [2 ], labels = labels )
529
603
604
+ # Re-add the "t" dimension with +ve and -ve sign for certain labels
605
+ c .add (K .fe_out [0 ], "mul" , k .fe , broadcast_t_fe ())
606
+ c .add (K .fe_out [1 ], "drop_vars" , K .fe_out [0 ] * "c_new" , names = "c" )
607
+ c .add (K .fe_out [2 ], "rename_dims" , K .fe_out [1 ], name_dict = {"c_new" : "c" })
608
+ # Restore labels: "R12_AFR" → "AFR" etc. "World" is not changed.
609
+ c .add (K .fe_out , "relabel" , K .fe_out [2 ], labels = labels )
610
+
530
611
531
612
def method_C (c : "Computer" ) -> None :
532
613
"""Prepare calculations up to :data:`K.emi` using :data:`METHOD.C`.
0 commit comments