2
2
3
3
from collections import defaultdict
4
4
from functools import partial
5
+ from operator import itemgetter
5
6
from typing import TYPE_CHECKING
6
7
7
- import genno
8
8
import numpy as np
9
9
import pandas as pd
10
+ from genno import Key , literal
10
11
from iam_units import registry
11
12
from message_ix import make_df
12
13
17
18
same_node ,
18
19
same_time ,
19
20
)
21
+ from message_ix_models .util .genno import Collector
20
22
23
+ from .demand import _DEMAND_KW
24
+ from .key import bcast_tcl , bcast_y , exo , fv , fv_cny , gdp_index , gdp_ppp , n , y
21
25
from .util import has_input_commodity , wildcard
22
26
23
27
if TYPE_CHECKING :
28
+ from genno import Computer
24
29
from sdmx .model .common import Code
25
30
26
31
from message_ix_models .model .transport import Config
27
32
33
+ #: Fixed values for some dimensions of some :mod:`message_ix` parameters.
28
34
COMMON = dict (
29
35
mode = "all" ,
30
36
time = "year" ,
31
37
time_dest = "year" ,
32
38
time_origin = "year" ,
33
39
)
40
+
41
+ #: Mapping from :mod:`message_ix` parameter dimensions to source dimensions in some
42
+ #: quantities.
34
43
DIMS = dict (
35
44
node_loc = "n" ,
36
45
node_dest = "n" ,
42
51
level = "l" ,
43
52
)
44
53
45
- #: Shorthand for tags on keys
46
- Fi = "::F+ixmp"
47
-
54
+ NTY = tuple ("nty" )
48
55
49
- def prepare_computer ( c : genno . Computer ):
50
- from genno . core . attrseries import AttrSeries
56
+ #: Target key that collects all data generated in this module.
57
+ TARGET = "transport::F+ixmp"
51
58
52
- from .key import bcast_tcl , bcast_y , n , y
53
59
54
- to_add = [] # Keys for ixmp-structured data to add to the target scenario
55
- k = genno .KeySeq ("F" ) # Sequence of temporary keys for the present function
56
-
57
- ### Produce the full quantity for input efficiency
58
-
59
- # Add a technology dimension with certain labels to the energy intensity of VDT
60
- # NB "energy intensity of VDT" actually has dimension (n,) only
61
- t_F_ROAD = "t::transport F ROAD"
62
- c .add (k [0 ], AttrSeries .expand_dims , "energy intensity of VDT:n-y" , t_F_ROAD )
63
- # Broadcast over dimensions (c, l, y, yv, ya)
64
- prev = c .add (k [1 ], "mul" , k [0 ], bcast_tcl .input , bcast_y .model )
65
- # Convert input to MESSAGE data structure
66
- c .add (k [2 ], "as_message_df" , prev , name = "input" , dims = DIMS , common = COMMON )
67
-
68
- # Convert units
69
- to_add .append (f"input{ Fi } " )
70
- c .add (to_add [- 1 ], convert_units , k [2 ], "transport info" )
71
-
72
- # Create base quantity for "output" parameter
73
- # TODO Combine in a loop with "input", above—similar to .ldv
74
- k_output = genno .KeySeq ("F output" )
75
- nty = tuple ("nty" )
76
- c .add (k_output [0 ] * nty , wildcard (1.0 , "dimensionless" , nty ))
77
- for i , coords in enumerate (["n::ex world" , "t::F" , "y::model" ]):
78
- c .add (
79
- k_output [i + 1 ] * nty ,
80
- "broadcast_wildcard" ,
81
- k_output [i ] * nty ,
82
- coords ,
83
- dim = coords [0 ],
84
- )
85
-
86
- for par_name , base , ks , i in (("output" , k_output [3 ] * nty , k_output , 3 ),):
87
- # Produce the full quantity for input/output efficiency
88
- prev = c .add (ks [i + 1 ], "mul" , ks [i ], getattr (bcast_tcl , par_name ), bcast_y .all )
89
-
90
- # Convert to ixmp/MESSAGEix-structured pd.DataFrame
91
- # NB quote() is necessary with dask 2024.11.0, not with earlier versions
92
- c .add (ks [i + 2 ], "as_message_df" , prev , name = par_name , dims = DIMS , common = COMMON )
93
-
94
- # Convert to target units
95
- to_add .append (f"output{ Fi } " )
96
- c .add (to_add [- 1 ], convert_units , ks [i + 2 ], "transport info" )
97
-
98
- # Extract the 'output' data frame
99
- c .add (k [3 ], lambda d : d ["output" ], to_add [- 1 ])
100
-
101
- # Produce corresponding capacity_factor and technical_lifetime
102
- c .add (
103
- k [4 ],
104
- partial (
105
- make_matched_dfs ,
106
- capacity_factor = registry .Quantity ("1" ),
107
- technical_lifetime = registry ("10 year" ),
108
- ),
109
- k [3 ],
110
- )
111
-
112
- # Convert to target units
113
- c .add (k [5 ], convert_units , k [4 ], "transport info" )
114
-
115
- # Fill values
116
- to_add .append (f"other{ Fi } " )
117
- c .add (to_add [- 1 ], same_node , k [5 ])
118
-
119
- # Base values for conversion technologies
120
- prev = c .add ("F usage output:t:base" , "freight_usage_output" , "context" )
121
- # Broadcast from (t,) to (t, c, l) dimensions
122
- prev = c .add (k [6 ], "mul" , prev , bcast_tcl .output )
123
-
124
- # Broadcast over the (n, yv, ya) dimensions
125
- d = tuple ("tcl" ) + tuple ("ny" )
126
- prev = c .add (k [7 ] * d , "expand_dims" , prev , dim = dict (n = ["*" ], y = ["*" ]))
127
- prev = c .add (k [8 ] * d , "broadcast_wildcard" , prev , "n::ex world" , dim = "n" )
128
- prev = c .add (k [9 ] * d , "broadcast_wildcard" , prev , "y::model" , dim = "y" )
129
- prev = c .add (k [10 ] * (d + ("ya" , "yv" )), "mul" , prev , bcast_y .no_vintage )
130
-
131
- # Convert output to MESSAGE data structure
132
- c .add (k [11 ], "as_message_df" , prev , name = "output" , dims = DIMS , common = COMMON )
133
- to_add .append (f"usage output{ Fi } " )
134
- c .add (to_add [- 1 ], lambda v : same_time (same_node (v )), k [11 ])
135
-
136
- # Create corresponding input values in Gv km
137
- prev = c .add (k [12 ], wildcard (1.0 , "gigavehicle km" , tuple ("nty" )))
138
- for i , coords in enumerate (["n::ex world" , "t::F usage" , "y::model" ], start = 12 ):
139
- prev = c .add (k [i + 1 ], "broadcast_wildcard" , k [i ], coords , dim = coords [0 ])
140
- prev = c .add (k [i + 2 ], "mul" , prev , bcast_tcl .input , bcast_y .no_vintage )
141
- prev = c .add (
142
- k [i + 3 ], "as_message_df" , prev , name = "input" , dims = DIMS , common = COMMON
143
- )
144
- to_add .append (f"usage input{ Fi } " )
145
- c .add (to_add [- 1 ], prev )
146
-
147
- # Constraint data
148
- k_constraint = f"constraints{ Fi } "
149
- to_add .append (k_constraint )
150
- c .add (k_constraint , constraint_data , "t::transport" , n , y , "config" )
151
-
152
- # Merge data to one collection
153
- k_all = f"transport{ Fi } "
154
- c .add (k_all , "merge_data" , * to_add )
155
-
156
- # Append to the "add transport data" key
157
- c .add ("transport_data" , __name__ , key = k_all )
60
+ collect = Collector (TARGET , "{}::F+ixmp" .format )
158
61
159
62
160
63
def constraint_data (
@@ -165,6 +68,7 @@ def constraint_data(
165
68
Responds to the :attr:`.Config.constraint` keys :py:`"non-LDV *"`; see description
166
69
there.
167
70
"""
71
+ # Retrieve transport configuration
168
72
config : "Config" = genno_config ["transport" ]
169
73
170
74
# Freight modes
@@ -225,3 +129,132 @@ def constraint_data(
225
129
assert not any (v .isna ().any (axis = None ) for v in result .values ()), "Missing labels"
226
130
227
131
return result
132
+
133
+
134
+ def demand (c : "Computer" ) -> None :
135
+ """Prepare calculation of freight activity/``demand``."""
136
+ # commented: Base freight activity from IEA EEI
137
+ # c.add("iea_eei_fv", "fv:n-y:historical", quote("tonne-kilometres"), "config")
138
+
139
+ # Base year freight activity from file (n, t), with modes for the 't' dimension
140
+ c .add ("fv:n-t:historical" , "mul" , exo .mode_share_freight , exo .activity_freight )
141
+
142
+ # …indexed to base-year values
143
+ c .add (gdp_index , "index_to" , gdp_ppp , literal ("y" ), "y0" )
144
+ c .add (fv [0 ], "mul" , "fv:n-t:historical" , gdp_index )
145
+
146
+ # (NAVIGATE) Scenario-specific adjustment factor for freight activity
147
+ c .add ("fv factor:n-t-y" , "factor_fv" , n , y , "config" )
148
+
149
+ # Apply the adjustment factor
150
+ c .add (fv [1 ], "mul" , fv [0 ], "fv factor:n-t-y" )
151
+
152
+ # Select only the ROAD data. NB Do not drop so 't' labels can be used for 'c', next.
153
+ c .add (fv [2 ], "select" , fv [1 ], indexers = dict (t = ["RAIL" , "ROAD" ]))
154
+
155
+ # Relabel
156
+ c .add (fv_cny , "relabel2" , fv [2 ], new_dims = {"c" : "transport F {t}" })
157
+
158
+ # Convert to ixmp format
159
+ collect ("demand" , "as_message_df" , fv_cny , ** _DEMAND_KW )
160
+
161
+
162
+ def prepare_computer (c : "Computer" ) -> None :
163
+ """Prepare `c` to calculate and add data for freight transport."""
164
+ # Collect data in `TARGET` and connect to the "add transport data" key
165
+ collect .computer = c
166
+ c .add ("transport_data" , __name__ , key = TARGET )
167
+
168
+ # Call further functions to set up tasks for categories of freight data
169
+ tech_econ (c )
170
+ usage (c )
171
+ demand (c )
172
+
173
+ # Add a task to call constraint_data()
174
+ collect ("constraints" , constraint_data , "t::transport" , n , y , "config" )
175
+
176
+
177
+ def tech_econ (c : "Computer" ) -> None :
178
+ """Prepare calculation of technoeconomic parameters for freight technologies."""
179
+
180
+ ### `input`
181
+ k = Key ("input" , NTY , "F" )
182
+
183
+ # Add a technology dimension with certain labels to the energy intensity of VDT
184
+ # NB "energy intensity of VDT" actually has dimension (n,) only
185
+ t_F_ROAD = "t::transport F ROAD"
186
+ c .add (k [0 ], "expand_dims" , "energy intensity of VDT:n-y" , t_F_ROAD )
187
+ # Broadcast over dimensions (c, l, y, yv, ya)
188
+ prev = c .add (k [1 ], "mul" , k [0 ], bcast_tcl .input , bcast_y .model )
189
+ # Convert MESSAGE data structure
190
+ c .add (k [2 ], "as_message_df" , prev , name = "input" , dims = DIMS , common = COMMON )
191
+ # Convert units; add to `TARGET`
192
+ collect (k .name , convert_units , k [2 ], "transport info" )
193
+
194
+ ### `output`
195
+ k = Key ("output" , NTY , "F" )
196
+
197
+ # Create base quantity
198
+ c .add (k [0 ], wildcard (1.0 , "dimensionless" , NTY ))
199
+ coords = ["n::ex world" , "t::F" , "y::model" ]
200
+ c .add (k [1 ], "broadcast_wildcard" , k [0 ], * coords , dim = NTY )
201
+ # Broadcast over dimensions (c, l, y, yv, ya)
202
+ prev = c .add (k [2 ], "mul" , k [1 ], bcast_tcl .output , bcast_y .all )
203
+ # Convert to MESSAGE data structure
204
+ prev = c .add (k [3 ], "as_message_df" , prev , name = "output" , dims = DIMS , common = COMMON )
205
+ # Convert units; add to `TARGET`
206
+ k_output = collect (k .name , convert_units , prev , "transport info" )
207
+
208
+ ### `capacity_factor` and `technical_lifetime`
209
+ k = Key ("other::F" )
210
+
211
+ # Extract the 'output' data frame
212
+ c .add (k [0 ], itemgetter ("output" ), k_output )
213
+
214
+ # Produce corresponding capacity_factor and technical_lifetime
215
+ c .add (
216
+ k [1 ],
217
+ partial (
218
+ make_matched_dfs ,
219
+ capacity_factor = registry .Quantity ("1" ),
220
+ technical_lifetime = registry ("10 year" ),
221
+ ),
222
+ k [0 ],
223
+ )
224
+ # Convert units
225
+ collect (k .name , convert_units , k [1 ], "transport info" )
226
+
227
+
228
+ def usage (c : "Computer" ) -> None :
229
+ """Prepare calculation of 'usage' pseudo-technologies for freight activity."""
230
+ ### `output`
231
+ k = Key ("F usage output:t" )
232
+
233
+ # Base values
234
+ c .add (k [0 ], "freight_usage_output" , "context" )
235
+ # Broadcast from (t,) → (t, c, l) dimensions
236
+ prev = c .add (k [1 ], "mul" , k [0 ], bcast_tcl .output )
237
+
238
+ # Broadcast over the (n, yv, ya) dimensions
239
+ d = bcast_tcl .output .dims + tuple ("ny" )
240
+ prev = c .add (k [2 ] * d , "expand_dims" , prev , dim = dict (n = ["*" ], y = ["*" ]))
241
+ coords = ["n::ex world" , "y::model" ]
242
+ prev = c .add (k [3 ] * d , "broadcast_wildcard" , prev , * coords , dim = tuple ("ny" ))
243
+ prev = c .add (k [4 ], "mul" , prev , bcast_y .no_vintage )
244
+ # Convert to MESSAGE data structure
245
+ c .add (k [5 ], "as_message_df" , prev , name = "output" , dims = DIMS , common = COMMON )
246
+ # Fill node_dest, time_dest key values
247
+ collect ("usage output" , lambda v : same_time (same_node (v )), k [5 ])
248
+
249
+ ### `input`
250
+ k = Key ("F usage input" , NTY )
251
+
252
+ c .add (k [0 ], wildcard (1.0 , "gigavehicle km" , NTY ))
253
+ coords = ["n::ex world" , "t::F usage" , "y::model" ]
254
+ c .add (k [1 ], "broadcast_wildcard" , k [0 ], * coords , dim = NTY )
255
+ # Broadcast (t,) → (t, c, l) and (y,) → (yv, ya) dimensions
256
+ prev = c .add (k [2 ], "mul" , k [1 ], bcast_tcl .input , bcast_y .no_vintage )
257
+ # Convert to MESSAGE data structure
258
+ collect (
259
+ "usage input" , "as_message_df" , prev , name = "input" , dims = DIMS , common = COMMON
260
+ )
0 commit comments