11import logging
22from collections .abc import Mapping
33from functools import lru_cache , partial
4+ from itertools import product
45from operator import itemgetter
56from typing import TYPE_CHECKING , cast
67
8+ from genno import ComputationError , Key , KeyExistsError , Keys , MissingKeyError
79from genno .operator import broadcast_map
8- from ixmp .report import (
9- ComputationError ,
10- Key ,
11- KeyExistsError ,
12- MissingKeyError ,
13- Quantity ,
14- configure ,
15- )
10+ from ixmp .backend import ItemType
11+ from ixmp .model import get_model
12+ from ixmp .report import Quantity , configure
1613from ixmp .report import Reporter as IXMPReporter
1714
1815from message_ix .common import DIMS
1916
2017from .pyam import collapse_message_cols
2118
2219if TYPE_CHECKING :
20+ from message_ix .common import GAMSModel
21+
2322 from .pyam import CollapseMessageColsKw
2423
2524__all__ = [
2625 "ComputationError" ,
2726 "Key" ,
27+ "Keys" ,
2828 "KeyExistsError" ,
2929 "MissingKeyError" ,
3030 "Quantity" ,
6161 ("map_tec" , "map_as_qty" , "cat_tec" , "t" ),
6262 ("map_year" , "map_as_qty" , "cat_year" , "y" ),
6363 #
64+ # Derived set contents
65+ ("current:ya-yv" , "yv_ya_current" , "y" ),
66+ #
6467 # Products
6568 ("out" , "mul" , "output" , "ACT" ),
6669 ("in" , "mul" , "input" , "ACT" ),
6972 ("rel" , "mul" , "relation_activity" , "ACT" ),
7073 ("emi" , "mul" , "emission_factor" , "ACT" ),
7174 ("inv" , "mul" , "inv_cost" , "CAP_NEW" ),
75+ ("inv::historical" , "mul" , "inv_cost" , "historical_activity" ),
76+ ("inv::ref" , "mul" , "inv_cost" , "ref_activity" ),
7277 ("fom" , "mul" , "fix_cost" , "CAP" ),
7378 ("vom" , "mul" , "var_cost" , "ACT" ),
7479 ("land_out" , "mul" , "land_output" , "LAND" ),
154159@lru_cache (1 )
155160def get_tasks () -> list [tuple [tuple , Mapping ]]:
156161 """Return a list of tasks describing MESSAGE reporting calculations."""
162+ from message_ix .message import MESSAGE
163+
157164 # Assemble queue of items to add. Each element is a 2-tuple of (positional, keyword)
158165 # arguments for Reporter.add()
159166 to_add : list [tuple [tuple , Mapping ]] = []
@@ -162,15 +169,46 @@ def get_tasks() -> list[tuple[tuple, Mapping]]:
162169
163170 for t in TASKS0 :
164171 if len (t ) == 2 and isinstance (t [1 ], dict ):
165- # (args, kwargs) → update kwargs with strict
166- t [1 ].update (strict )
167- to_add .append (cast (tuple [tuple , Mapping ], t ))
172+ # (args, kwargs) → use strict unless already set
173+ to_add .append ((t [0 ], strict | t [1 ]))
168174 else :
169175 # args only → use strict as kwargs
170176 to_add .append ((t , strict ))
171177
172- # Conversions to IAMC data structure and pyam objects
178+ # Products using {historical,ref}_activity instead of ACT
179+ for (name , io ), act in product (
180+ [
181+ ("emi" , "emission_factor" ),
182+ ("in" , "input" ),
183+ ("out" , "output" ),
184+ ("vom" , "var_cost" ),
185+ ],
186+ ["historical" , "ref" ],
187+ ):
188+ # Construct some keys
189+ k_io = MESSAGE .items [io ].key
190+ k_act = MESSAGE .items [f"{ act } _activity" ].key
191+ k_share = Key ("share" , k_act .dims , f"{ name } +{ act } " ) * "yv"
192+ k = Key (name , sorted (set (k_io .dims + k_act .dims )), act )
193+ desc = f"share of contribution of each vy to { k_act .name } in each ya"
194+
195+ to_add .extend (
196+ [
197+ ((k ["full" ], "mul" , k_io , k_act ), strict ),
198+ ((k ["current" ], "mul" , k ["full" ], "current:ya-yv" ), strict ),
199+ ((k_share , "missing_data" ), dict (key = k_share , description = desc )),
200+ (
201+ (k ["weighted+full" ], "mul" , k ["full" ], k_share ),
202+ dict (sums = False ) | strict ,
203+ ),
204+ (
205+ (k ["weighted" ] / "yv" , "sum" , k ["weighted+full" ]),
206+ dict (dimensions = ["yv" ], sums = True ) | strict ,
207+ ),
208+ ]
209+ )
173210
211+ # Conversions to IAMC data structure and pyam objects
174212 for qty , collapse_kw in PYAM_CONVERT :
175213 # Column to set as year dimension + standard renames from MESSAGE to IAMC dims
176214 rename = {
@@ -213,9 +251,9 @@ def from_scenario(cls, scenario, **kwargs) -> "Reporter":
213251 .Reporter
214252 A reporter for `scenario`.
215253 """
216- solved = scenario . has_solution ()
254+ import genno
217255
218- if not solved :
256+ if not scenario . has_solution () :
219257 log .warning (
220258 f'Scenario "{ scenario .model } /{ scenario .scenario } " has no solution'
221259 )
@@ -227,6 +265,22 @@ def from_scenario(cls, scenario, **kwargs) -> "Reporter":
227265 # Invoke the ixmp method
228266 rep = cast ("Reporter" , super ().from_scenario (scenario , ** kwargs ))
229267
268+ # Handle missing parameters
269+ missing = set ()
270+ for name , item in cast ("GAMSModel" , get_model (scenario .scheme )).items .items ():
271+ if item .type is not ItemType .PAR or item .key in rep :
272+ continue
273+ rep .add (item .key , lambda : genno .Quantity ())
274+ missing .add (item .name )
275+
276+ if missing :
277+ log .warning (
278+ f"Scenario { scenario .url !r} is missing { len (missing )} parameter(s):"
279+ + "\n - " .join (sorted ({"" } | missing ))
280+ + "\n …possibly added by a newer version of message_ix. These keys will "
281+ "return empty Quantity()."
282+ )
283+
230284 # Add the MESSAGEix calculations
231285 rep .add_tasks (fail_action )
232286
0 commit comments