12
12
from genno import Key
13
13
14
14
from message_ix_models import Context
15
+ from message_ix_models .model .structure import get_codelist
15
16
from message_ix_models .tools .iamc import iamc_like_data_for_query , to_quantity
16
17
from message_ix_models .util import minimum_version
17
18
from message_ix_models .util .genno import Keys
58
59
#: - :py:`.emi`: computed aviation emissions.
59
60
#: - :py:`.emi_in`: input data for aviation and other transport emissions, to be
60
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.
61
64
K = Keys (
62
65
bcast = f"broadcast:t:{ L } " ,
63
66
input = f"input:n-y-VARIABLE-UNIT:{ L } " ,
64
67
emi = f"emission:e-n-t-y-UNIT:{ L } " ,
65
- 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" ,
66
71
)
67
72
68
73
@@ -101,8 +106,10 @@ def aviation_emi_share(ref: "TQuantity") -> "TQuantity":
101
106
)
102
107
103
108
104
- def broadcast_t (version : Literal [1 , 2 ], include_international : bool ) -> "AnyQuantity" :
105
- """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.
106
113
107
114
Parameters
108
115
----------
@@ -152,6 +159,22 @@ def broadcast_t(version: Literal[1, 2], include_international: bool) -> "AnyQuan
152
159
return genno .Quantity (value [idx ], coords = {"t" : t [idx ]})
153
160
154
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
+
155
178
def e_UNIT (cl_emission : "sdmx.model.common.Codelist" ) -> "AnyQuantity" :
156
179
"""Return a quantity for broadcasting.
157
180
@@ -186,7 +209,11 @@ def e_UNIT(cl_emission: "sdmx.model.common.Codelist") -> "AnyQuantity":
186
209
187
210
188
211
def finalize (
189
- 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 ,
190
217
) -> pd .DataFrame :
191
218
"""Finalize output.
192
219
@@ -214,12 +241,12 @@ def _expand(qty):
214
241
# Convert `q_all` to pd.Series
215
242
s_all = q_all .pipe (_expand ).to_series ()
216
243
217
- # - Convert `q_update ` to pd.Series
244
+ # - Convert `q_emi_update ` to pd.Series
218
245
# - Reassemble "Variable" codes.
219
246
# - Drop dimensions (e, t).
220
247
# - Align index with s_all.
221
- s_update = (
222
- q_update .pipe (_expand )
248
+ s_emi_update = (
249
+ q_emi_update .pipe (_expand )
223
250
.to_frame ()
224
251
.reset_index ()
225
252
.assign (
@@ -229,13 +256,37 @@ def _expand(qty):
229
256
.set_index (s_all .index .names )[0 ]
230
257
.rename ("value" )
231
258
)
232
- log .info (f"{ len (s_update )} obs to update" )
233
-
234
- # Update `s_all`. This yields an 'outer join' of the original and s_update indices.
235
- s_all .update (s_update )
259
+ log .info (f'{ len (s_emi_update )} obs to update for Variable="Emission|…"' )
236
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 .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.
237
285
return (
238
- 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" )
239
290
.reorder_levels (["Model" , "Scenario" , "Region" , "Variable" , "Unit" ])
240
291
.reset_index ()
241
292
)
@@ -316,32 +367,65 @@ def get_computer(
316
367
log .info (f"method 'C' will use data from { url } " )
317
368
318
369
# Common structure and utility quantities used by method_[ABC]
319
- 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" )
320
371
321
372
# Placeholder for data-loading task. This is filled in later by process_df() or
322
373
# process_file().
323
374
c .add (K .input , None )
324
375
325
376
# Select and transform data matching EXPR_EMI
326
- # Filter on "VARIABLE", expand the (e, t) dimensions from "VARIABLE"
377
+ # Filter on "VARIABLE", extract the (e, t) dimensions
327
378
c .add (K .emi_in [0 ], "select_expand" , K .input , dim_cb = {"VARIABLE" : v_to_emi_coords })
379
+ # Assign units
328
380
c .add (K .emi_in , "assign_units" , K .emi_in [0 ], units = "Mt/year" )
329
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
+
330
391
# Call a function to prepare the remaining calculations up to K.emi
331
392
method_func = {METHOD .A : method_A , METHOD .B : method_B , METHOD .C : method_C }[method ]
332
393
method_func (c )
333
394
334
395
# Adjust the original data by adding the (maybe negative) prepared values at K.emi
335
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 )
336
398
337
399
# Add a key "target" to:
338
400
# - Collapse to IAMC "VARIABLE" dimension name.
339
401
# - Recombine with other/unaltered original data.
340
- 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
+ )
341
411
342
412
return c
343
413
344
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
+
345
429
def get_scenario_code (model_name : str , scenario_name : str ) -> "sdmx.model.common.Code" :
346
430
"""Return a specific code from ``CL_TRANSPORT_SCENARIO``.
347
431
@@ -390,6 +474,9 @@ def method_A(c: "Computer") -> None:
390
474
# Rail and Domestic Shipping"
391
475
c .add (K .emi , "mul" , K .emi [0 ] / "t" , k_share , K .bcast )
392
476
477
+ # No change to final energy data
478
+ c .add (K .fe_out , genno .Quantity (0.0 , units = "EJ / a" ))
479
+
393
480
394
481
def method_B (c : "Computer" ) -> None :
395
482
"""Prepare calculations up to :data:`K.emi` using :data:`METHOD.B`.
@@ -468,10 +555,10 @@ def method_BC_common(c: "Computer", k_fe_share: "Key") -> None:
468
555
A key with dimensions either :math:`(c, n)` or :math:`(c, n, y)` giving the
469
556
share of aviation in total transport final energy.
470
557
"""
471
- from message_ix_models . model . structure import get_codelist
558
+
472
559
from message_ix_models .model .transport .key import exo
473
560
474
- # Check dimensions of k_emi_share
561
+ # Check dimensions of k_fe_share
475
562
exp = {frozenset ("cn" ), frozenset ("cny" )}
476
563
if set (k_fe_share .dims ) not in exp : # pragma: no cover
477
564
raise ValueError (f"Dimensions of k_cn={ k_fe_share .dims } are not in { exp } " )
@@ -480,31 +567,17 @@ def method_BC_common(c: "Computer", k_fe_share: "Key") -> None:
480
567
k = Keys (
481
568
ei = exo .emi_intensity , # Dimensions (c, e, t)
482
569
emi0 = Key ("emission" , ("ceny" ), L ),
483
- fe_in = Key ("fe" , ("c" , "n" , "y" , "UNIT" ), "input" ),
484
- fe = Key ("fe" , tuple ("cny" ), L ),
570
+ fe = Key ("fe" , tuple ("cny" ), f"{ L } +BC" ),
485
571
units = Key (f"units:e-UNIT:{ L } " ),
486
572
)
487
573
488
- ### Prepare data from the input data file: total transport consumption of light oil
489
-
490
- # Filter on "VARIABLE", extract (e) dimension
491
- c .add (k .fe_in [0 ], "select_expand" , K .input , dim_cb = {"VARIABLE" : v_to_fe_coords })
492
-
493
- # Convert "UNIT" dim labels to Quantity.units
494
- c .add (k .fe_in [1 ] / "UNIT" , "unique_units_from_dim" , k .fe_in [0 ], dim = "UNIT" )
495
-
496
- # Relabel:
497
- # - c[ommodity]: 'Liquids|Oil' (IAMC 'variable' component) → 'lightoil'
498
- # - n[ode]: "AFR" → "R12_AFR" etc. "World" is not changed.
499
- cl = get_codelist ("node/R12" )
500
- labels = dict (c = {"Liquids|Oil" : "lightoil" }, n = {})
501
- for n in filter (lambda n : len (n .child ) and n .id != "World" , cl ):
502
- labels ["n" ][n .id .partition ("_" )[2 ]] = n .id
503
- 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 )
504
577
505
578
### Compute estimate of emissions
506
579
# Product of aviation share and FE of total transport → FE of aviation
507
- 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 )
508
581
509
582
# Convert exogenous emission intensity data to Mt / EJ
510
583
c .add (k .ei ["units" ], "convert_units" , k .ei , units = "Mt / EJ" )
@@ -525,9 +598,16 @@ def method_BC_common(c: "Computer", k_fe_share: "Key") -> None:
525
598
c .add (K .emi [2 ], "mul" , k .emi0 [1 ], k .units , K .bcast )
526
599
527
600
# Restore labels: "R12_AFR" → "AFR" etc. "World" is not changed.
528
- 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 ()})
529
602
c .add (K .emi , "relabel" , K .emi [2 ], labels = labels )
530
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
+
531
611
532
612
def method_C (c : "Computer" ) -> None :
533
613
"""Prepare calculations up to :data:`K.emi` using :data:`METHOD.C`.
0 commit comments