Skip to content

Commit 94414a2

Browse files
authored
Merge pull request #178 from iiasa/enh/test-report
Test genno vs. legacy reporting
2 parents 2bc42a3 + f943878 commit 94414a2

File tree

15 files changed

+387
-104
lines changed

15 files changed

+387
-104
lines changed

doc/api/report/index.rst

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ On this page:
99
Elsewhere:
1010

1111
- ``global.yaml``, the :doc:`default-config`.
12-
- Documentation for :mod:`genno` (:doc:`genno:index`), :mod:`ixmp.report`, and :mod:`message_ix.report`.
12+
- Documentation for :mod:`genno` (:doc:`genno:index`),
13+
:mod:`ixmp.report`, and
14+
:mod:`message_ix.report`.
1315
- Reporting for specific model variants:
1416

1517
- :mod:`.water.reporting`
@@ -31,41 +33,85 @@ Introduction
3133
See :doc:`the discussion in the MESSAGEix docs <message-ix:reporting>` about the stack.
3234
In short, for instance:
3335

34-
- :mod:`message_ix` **must not** contain reporting code that references :py:`technology="coal_ppl"`, because not every model built on the MESSAGE framework will have a technology with this name.
35-
- Any model in the MESSAGEix-GLOBIOM family—built with :mod:`message_ix_models` and/or :mod:`message_data`—**should**, with few exceptions, have a :py:`technology="coal_ppl"`, since this appears in the common list of :ref:`technology-yaml`.
36-
Reporting specific to this technology ID, *as it is represented* in this model family, should be in :mod:`message_ix_models` or user code.
36+
- :mod:`message_ix` **must not** contain reporting code that references :py:`technology="coal_ppl"`,
37+
because not every model built on the MESSAGE framework will have a technology with this name.
38+
- Any model in the MESSAGEix-GLOBIOM family
39+
—built with :mod:`message_ix_models` and/or :mod:`message_data`—
40+
**should**, with few exceptions, have a :py:`technology="coal_ppl"`,
41+
since this appears in the common list of :ref:`technology-yaml`.
42+
Reporting specific to this technology ID,
43+
*as it is represented* in this model family,
44+
should be in :mod:`message_ix_models` or user code.
3745

3846
The basic **design pattern** of :mod:`message_ix_models.report` is:
3947

40-
- :func:`~.report.prepare_reporter` populates a new :class:`~.message_ix.Reporter` for a given :class:`.Scenario` with many keys to report all quantities of interest in a MESSAGEix-GLOBIOM–family model.
41-
- This function relies on *callbacks* defined in multiple submodules to add keys and tasks for general or tailored reporting calculations and actions.
42-
Additional modules **should** define callback functions and register them with :func:`~report.register` when they are to be used.
48+
- :func:`~.report.prepare_reporter` populates a new :class:`~.message_ix.Reporter`
49+
for a given :class:`.Scenario` with many tasks
50+
to report all quantities of interest in a MESSAGEix-GLOBIOM–family model.
51+
- This function relies on *callbacks* defined in multiple submodules
52+
to add keys and tasks for general or tailored reporting calculations and actions.
53+
Additional modules **should** define callback functions
54+
and register them with :func:`~report.register` when they are to be used.
4355
For example:
4456

45-
1. The module :mod:`message_ix_models.report.plot` defines :func:`.plot.callback` that adds standard plots to the Reporter.
46-
2. The module :mod:`message_data.model.transport.report` defines :func:`~.message_data.model.transport.report.callback` that adds tasks specific to MESSAGEix-Transport.
47-
3. The module :mod:`message_data.projects.navigate.report` defines :func:`~.message_data.projects.navigate.report.callback` that add tasks specific to the ‘NAVIGATE’ research project.
48-
49-
The callback (1) is always registered, because these plots are always applicable and can be expected to function correctly for all models in the family. In contrast, (2) and (3) **should** only be registered and run for the specific model variants for which they are developed/intended.
50-
51-
Modules with tailored reporting configuration **may** also be indicated on the :ref:`command line <report-cli>` by using the :program:`-m/--modules` option: :program:`mix-models report -m model.transport`.
52-
53-
- A file :file:`global.yaml` file (in `YAML <https://en.wikipedia.org/wiki/YAML#Example>`_ format) contains a description of some of the reporting computations needed for a MESSAGE-GLOBIOM model.
54-
:func:`~.report.prepare_reporter` uses the :doc:`configuration handlers <genno:config>` built into :mod:`genno` (and some extensions specific to :mod:`message_ix_models`) to handle the different sections of the file.
57+
1. The module :mod:`message_ix_models.report.plot` defines :func:`.plot.callback`
58+
that adds standard plots to the Reporter.
59+
2. The module :mod:`message_ix_models.model.transport.report` defines :func:`~.transport.report.callback`
60+
that adds tasks specific to the MESSAGEix-Transport model variant.
61+
3. The module :mod:`message_ix_models.project.navigate.report` defines :func:`~.navigate.report.callback`
62+
that add tasks specific to the ‘NAVIGATE’ research project.
63+
64+
The callback (1) is always registered,
65+
because these plots are always applicable and can be expected to function correctly
66+
for all models in the family.
67+
In contrast, (2) and (3) **should** only be registered and run
68+
for the specific model variants for which they are developed/intended.
69+
70+
Modules with tailored reporting configuration **may** also be indicated
71+
on the :ref:`command line <report-cli>`
72+
by using the :program:`-m/--modules` option:
73+
:program:`mix-models report -m model.transport`.
74+
75+
- A file :file:`global.yaml`
76+
(in `YAML <https://en.wikipedia.org/wiki/YAML#Example>`_ format)
77+
contains a description of some of the reporting computations
78+
needed for a MESSAGE-GLOBIOM model.
79+
:func:`~.report.prepare_reporter` uses the :doc:`configuration handlers <genno:config>`
80+
built into :mod:`genno`
81+
(and some extensions specific to :mod:`message_ix_models`)
82+
to handle the different sections of the file.
5583

5684
Features
5785
========
5886

59-
By combining these genno, ixmp, message_ix, and message_ix_models features, the following functionality is provided.
87+
By combining these genno, ixmp, message_ix, and message_ix_models features,
88+
the following functionality is provided.
6089

6190
.. note:: If any of this does not appear to work as advertised, file a bug!
6291

92+
Scope of :mod:`genno`-based vs. :mod:`.legacy` reporting
93+
--------------------------------------------------------
94+
95+
:func:`.prepare_reporter` currently handles only a subset
96+
of the IAMC-structured data produced by :mod:`.report.legacy`.
97+
The module globals :data:`.NOT_IMPLEMENTED_MEASURE` and :mod:`NOT_IMPLEMENTED_IAMC`
98+
express the measures and IAMC ‘variables’ that are either not implemented
99+
or not exactly matching the legacy reporting output.
100+
The test :func:`.test_report.test_compare` compares outputs
101+
from the two forms of reporting using :func:`.iamc.compare`,
102+
ensuring that data match for all entries *except* these exclusions.
103+
104+
See GitHub issues and pull requests with the
105+
`'report' label <https://github.com/iiasa/message-ix-models/issues?q=state%3Aopen%20label%3Areport>`_
106+
for ongoing work to expand the scope of :func:`prepare_reporter`.
107+
63108
Units
64109
-----
65110

66111
- Are read automatically for ixmp parameters.
67112
- Pass through calculations/are derived automatically.
68-
- Are recognized based on the definitions of non-SI units from `IAMconsortium/units <https://github.com/IAMconsortium/units/>`_.
113+
- Are recognized based on the definitions of non-SI units from
114+
`IAMconsortium/units <https://github.com/IAMconsortium/units/>`_.
69115
- Are discarded when inconsistent.
70116
- Can be overridden for entire parameters:
71117

@@ -133,7 +179,9 @@ Operators
133179
remove_ts
134180
share_curtailment
135181

136-
The following functions, defined elsewhere, are exposed through :mod:`.operator` and so can also be referenced by name:
182+
The following functions, defined elsewhere,
183+
are exposed through :mod:`.operator`
184+
and so can also be referenced by name:
137185

138186
.. autosummary::
139187

@@ -255,11 +303,20 @@ Testing
255303
Continuous reporting
256304
--------------------
257305

258-
As part of the :ref:`test-suite`, reporting is run on the same events (pushes and daily schedule) on publicly-available :doc:`model snapshots </api/model-snapshot>`.
259-
One goal of these tests *inter alia* is to ensure that adjustments and improvements to the reporting code do not disturb manually-verified model outputs.
260-
261-
As part of the (private) :mod:`message_data` test suite, multiple workflows run on regular schedules; some of these include a combination of :mod:`message_ix_models`-based and :ref:`‘legacy’ reporting <report-legacy>`.
306+
As part of the :ref:`test-suite`, reporting is run on the same events
307+
(pushes and daily schedule)
308+
on publicly-available :doc:`model snapshots </api/model-snapshot>`.
309+
One goal of these tests *inter alia* is to ensure that adjustments
310+
and improvements to the reporting code
311+
do not disturb manually-verified model outputs.
312+
313+
As part of the (private) :mod:`message_data` test suite,
314+
multiple workflows run on regular schedules;
315+
some of these include a combination of :mod:`message_ix_models`-based
316+
and :ref:`‘legacy’ reporting <report-legacy>`.
262317
These workflows:
263318

264319
- Operate on specific scenarios within IIASA databases.
265-
- Create files in CSV, Excel, and/or PDF formats that are that are preserved and made available as 'build artifacts' via the GitHub Actions web interface and API.
320+
- Create files in CSV, Excel, and/or PDF formats
321+
that are that are preserved and made available as 'build artifacts'
322+
via the GitHub Actions web interface and API.

doc/whatsnew.rst

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
What's new
22
**********
33

4-
.. Next release
5-
.. ============
4+
Next release
5+
============
6+
7+
- Improvements to :class:`.report.Config` (:pull:`178`):
8+
9+
- New attribute :attr:`~.report.Config.modules`.
10+
- New method :meth:`~.report.Config.iter_callbacks`.
11+
12+
- New function :func:`.tools.iamc.compare` (:pull:`178`).
13+
- Expand :doc:`api/report/index` documentation (:pull:`178`)
14+
to cover features implemented/not implemented by :mod:`genno`-based reporting.
15+
Module globals :data:`.NOT_IMPLEMENTED_MEASURE` and :data:`.NOT_IMPLEMENTED_IAMC`
16+
record not-yet-implemented measures and IAMC ‘variables’.
617

718
v2025.10.31
819
===========

message_ix_models/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def main(click_ctx, **kwargs):
5757

5858
# Check for a non-trivial execution of the CLI
5959
non_trivial = (
60-
not any(s in sys.argv for s in {"last-log", "--help"})
60+
not any(s in sys.argv for s in {"config", "last-log", "--help"})
6161
and click_ctx.invoked_subcommand != "_test"
6262
and "pytest" not in sys.argv[0]
6363
)

message_ix_models/data/report/global.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ general:
685685
# All other technologies not in out::h2
686686
- key: out:*:se_0
687687
comp: select
688-
inputs: [out]
688+
inputs: [ out ]
689689
args:
690690
indexers:
691691
t: [h2_coal, h2_coal_ccs, h2_smr, h2_smr_ccs, h2_bio, h2_bio_ccs]
@@ -862,12 +862,12 @@ iamc:
862862
# <<: *pe_iamc
863863

864864
- variable: Primary Energy|Hydro
865-
base: out:nl-t-ya-m-c-l:se
865+
base: out:nl-t-ya-m-c-l:se_1+se
866866
select: {l: [secondary], t: [hydro]}
867867
<<: *pe_iamc
868868

869869
- variable: Primary Energy|Nuclear
870-
base: out:nl-t-ya-m-c-l:se
870+
base: out:nl-t-ya-m-c-l:se_1+se
871871
select: {l: [secondary], t: [nuclear]}
872872
<<: *pe_iamc
873873

@@ -1043,7 +1043,7 @@ iamc:
10431043
report:
10441044
- key: pe test
10451045
members:
1046-
# - Primary Energy|Biomass::iamc
1046+
# - Primary Energy|Biomass::iamc
10471047
- Primary Energy|Coal::iamc
10481048
- Primary Energy|Gas::iamc
10491049
- Primary Energy|Hydro::iamc

message_ix_models/data/report/legacy/default_units.yaml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,18 @@ conversion_factors:
4141
ZJ: .00003154
4242
km3/yr: 1.
4343
Index (2005 = 1): 1
44+
GWyr/yr:
45+
EJ/yr: 0.03154
46+
GWa: 1.
47+
km3/yr: 1.
4448
EJ/yr:
4549
ZJ: .001
46-
y:
50+
"y":
4751
years: 1.
4852
# New units from unit-revision
4953
"Mt C/GWyr/yr":
5054
Mt CO2/yr: "float(f\"{mu['conv_c2co2']}\")"
51-
Mt CO2-equiv/yr: "float(f\"{mu['conv_c2co2']}\")"
55+
Mt CO2-equiv/yr: "float(f\"{mu['conv_c2co2']}\")"
5256
# Emissions currently have the units ???
5357
-:
5458
Mt CO2/yr: "float(f\"{mu['conv_c2co2']}\")"
@@ -57,7 +61,7 @@ conversion_factors:
5761
# NB this values implies that whatever quantity it is applied to is
5862
# internally [Mt C/yr]
5963
Mt CO2/yr: "float(f\"{mu['conv_c2co2']}\")"
60-
Mt CO2-equiv/yr: "float(f\"{mu['conv_c2co2']}\")"
64+
Mt CO2-equiv/yr: "float(f\"{mu['conv_c2co2']}\")"
6165
# N2O is always left in kt
6266
kt N2O/yr: 1.
6367
# All other units are in kt
@@ -139,7 +143,7 @@ conversion_factors:
139143
Mt C/yr: "float(f\"{mu['conv_co22c']}\")"
140144
Mt C/yr:
141145
Mt CO2eq/yr: "float(f\"{mu['conv_c2co2']}\")"
142-
Mt CO2/yr: "float(f\"{mu['conv_c2co2']}\")"
146+
Mt CO2/yr: "float(f\"{mu['conv_c2co2']}\")"
143147
Mt CO2-equiv/yr: "float(f\"{mu['conv_c2co2']}\")"
144148
Mt CO2/yr:
145149
Mt CO2/yr: 1.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:b096505d79154852870cba8ebe0404a68ac754cd7022d38602444f21156870fc
3+
size 2884451

message_ix_models/report/__init__.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
from .config import Callback
2626

2727
__all__ = [
28+
"NOT_IMPLEMENTED_IAMC",
29+
"NOT_IMPLEMENTED_MEASURE",
2830
"Config",
2931
"prepare_reporter",
3032
"register",
@@ -46,6 +48,66 @@ def _(c: Reporter, info):
4648
pass
4749

4850

51+
PE0 = r"Primary Energy\|(Coal|Gas|Hydro|Nuclear|Solar|Wind)"
52+
PE1 = r"Primary Energy\|(Coal|Gas|Solar|Wind)"
53+
E = (
54+
r"Emissions\|CO2\|Energy\|Demand\|Transportation\|Road Rail and Domestic "
55+
"Shipping"
56+
)
57+
58+
#: Measures for which reporting is not implemented. See :data:`NOT_IMPLEMENTED_IAMC`.
59+
NOT_IMPLEMENTED_MEASURE = {
60+
"(Agricultural|Forestry) (Demand|Production)",
61+
"Capacity Additions",
62+
"Capacity",
63+
"Capital Cost",
64+
"Carbon Sequestration",
65+
"Consumption",
66+
r"Cost\|Cost Nodal Net",
67+
"Cumulative Capacity",
68+
"Efficiency",
69+
r"Emissions\|(BC|CF4|CH4|CO|CO2|F-Gases|HFC|Kyoto Gases|N2O|NH3|NOx|OC|SF6|Sulfur)",
70+
r"Emissions\|(VOC)",
71+
"Fertilizer Use",
72+
"Final Energy",
73+
"Food Demand",
74+
"GDP",
75+
"GLOBIOM",
76+
"Investment",
77+
"Land Cover",
78+
"Lifetime",
79+
"OM Cost",
80+
"Population",
81+
"Price",
82+
r"Resource\|(Cumulative )?Extraction",
83+
"Secondary Energy",
84+
"Trade",
85+
"Useful Energy",
86+
"Water Consumption",
87+
"Water Withdrawal",
88+
"Yield",
89+
}
90+
91+
92+
#: Expressions for IAMC variable names not implemented by :func:`prepare_reporter`.
93+
NOT_IMPLEMENTED_IAMC = [
94+
# Other 'variable' codes are missing from `obs`
95+
rf"variable='({'|'.join(NOT_IMPLEMENTED_MEASURE)})(\|.*)?': no right data",
96+
# 'variable' codes with further parts are missing from `obs`
97+
f"variable='{PE0}.*': no right data",
98+
# For `pe1` (NB: not Hydro or Solar) units and most values differ
99+
f"variable='{PE1}.*': units mismatch .*EJ/yr.*'', nan",
100+
r"variable='Primary Energy|Coal': 220 of 240 values with \|diff",
101+
r"variable='Primary Energy|Gas': 234 of 240 values with \|diff",
102+
r"variable='Primary Energy|Solar': 191 of 240 values with \|diff",
103+
r"variable='Primary Energy|Wind': 179 of 240 values with \|diff",
104+
# For `e` units and most values differ
105+
f"variable='{E}': units mismatch: .*Mt CO2/yr.*Mt / a",
106+
rf"variable='{E}': 20 missing right entries",
107+
rf"variable='{E}': 220 of 240 values with \|diff",
108+
]
109+
110+
49111
@genno.config.handles("iamc")
50112
def iamc(c: Reporter, info):
51113
"""Handle one entry from the ``iamc:`` config section.
@@ -74,6 +136,11 @@ def iamc(c: Reporter, info):
74136
# Common
75137
base_key = Key(info["base"])
76138

139+
# First part of the 'Variable' name
140+
name = info.pop("variable", base_key.name)
141+
# Parts (string literals or dimension names) to concatenate into variable name
142+
var_parts = info.pop("var", [name])
143+
77144
# Use message_ix_models custom collapse() method
78145
info.setdefault("collapse", {})
79146

@@ -96,17 +163,15 @@ def iamc(c: Reporter, info):
96163
# TODO allow iterable of str
97164
dims = dims.split("-")
98165

99-
label = f"{info['variable']} {'-'.join(dims) or 'full'}"
166+
label = f"{name} {'-'.join(dims) or 'full'}"
100167

101168
# Modified copy of `info` for this invocation
102169
_info = info.copy()
103170
# Base key: use the partial sum over any `dims`. Use a distinct variable name.
104171
_info.update(base=base_key.drop(*dims), variable=label)
105172
# Exclude any summed dimensions from the IAMC Variable to be constructed
106173
_info["collapse"].update(
107-
callback=partial(
108-
collapse, var=list(filter(lambda v: v not in dims, info.get("var", [])))
109-
)
174+
callback=partial(collapse, var=[v for v in var_parts if v not in dims])
110175
)
111176

112177
# Invoke the genno built-in handler
@@ -115,7 +180,7 @@ def iamc(c: Reporter, info):
115180
keys.append(f"{label}::iamc")
116181

117182
# Concatenate together the multiple tables
118-
c.add("concat", f"{info['variable']}::iamc", *keys)
183+
c.add("concat", f"{name}::iamc", *keys)
119184

120185

121186
def register(name_or_callback: "Callback | str") -> str | None:
@@ -324,8 +389,11 @@ def prepare_reporter(
324389
)
325390
rep.configure(model=deepcopy(context.model))
326391

392+
# Add a placeholder task to concatenate IAMC-structured data
393+
rep.add("all::iamc", "concat")
394+
327395
# Apply callbacks for other modules which define additional reporting computations
328-
for callback in context.report.callback:
396+
for callback in context.report.iter_callbacks():
329397
callback(rep, context)
330398

331399
key = context.report.key

0 commit comments

Comments
 (0)