Skip to content

Commit 4128f96

Browse files
committed
Add .transport.plot.Kind enumeration
- Add .plot.prepare_computer(…, kind=…, target=…) keyword arguments. - Filter plots to add using `kind`.
1 parent fbde5be commit 4128f96

File tree

2 files changed

+57
-36
lines changed

2 files changed

+57
-36
lines changed

message_ix_models/model/transport/key.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
#: Keys for :mod:`.transport.report`.
115115
report = SimpleNamespace(
116116
all="transport all",
117+
plot="transport plot",
117118
sdmx=Key("transport::sdmx"),
118119
)
119120

message_ix_models/model/transport/plot.py

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import logging
44
from datetime import datetime
5+
from enum import Enum, auto
56
from pathlib import Path
67
from typing import TYPE_CHECKING
78

@@ -11,6 +12,8 @@
1112
from genno import Computer
1213
from iam_units import registry
1314

15+
from message_ix_models.util.genno import append
16+
1417
from .key import gdp_cap, pdt_nyt
1518
from .key import report as k_report
1619

@@ -20,7 +23,6 @@
2023
# NB The following is itself within an if TYPE_CHECKING block; mypy can't find it
2124
from plotnine.typing import PlotAddable # type: ignore [attr-defined]
2225

23-
from .config import Config
2426

2527
log = logging.getLogger(__name__)
2628

@@ -30,6 +32,27 @@
3032
logging.getLogger("matplotlib.font_manager").setLevel(logging.INFO + 1)
3133

3234

35+
class Kind(Enum):
36+
"""Kinds of transport :class:`Plots <.Plot>`."""
37+
38+
#: Plots for :func:`.transport.build.get_computer`, that is, during the process of
39+
#: building a single scenario. These are used within a :class:`~.genno.Computer`,
40+
#: without a built or solved Scenario.
41+
BUILD = auto()
42+
43+
#: Plots for pre-solve or build-process analysis of multiple scenarios. These are
44+
#: added by :func:`.transport.build.debug_multi`.
45+
BUILD_MULTI = auto()
46+
47+
#: Plots for post-solve reporting of single MESSAGEix-Transport scenarios. These are
48+
#: added to a :class:`.Reporter` via :func:`.transport.report.callback`.
49+
REPORT = auto()
50+
51+
#: Plots of post-solve reporting of multiple MESSAGEix-Transport scenarios, or
52+
#: of data reported from these. These are added by :class:`.transport.report.multi`.
53+
REPORT_MULTI = auto()
54+
55+
3356
class LabelFirst:
3457
"""Labeller that labels the first item using a format string.
3558
@@ -54,6 +77,9 @@ class Plot(genno.compat.plotnine.Plot):
5477
This class extends :class:`genno.compat.plotnine.Plot` with extra features.
5578
"""
5679

80+
#: Kind of plot.
81+
kind: Kind = Kind.REPORT
82+
5783
#: 'Static' geoms: list of plotnine objects that are not dynamic
5884
static: list["PlotAddable"] = [
5985
p9.theme(figure_size=(11.7, 8.3)),
@@ -66,9 +92,6 @@ class Plot(genno.compat.plotnine.Plot):
6692
#: Units expression for plot title.
6793
unit: str | None = None
6894

69-
#: :obj:`False` for plots not intended to be run on a solved scenario.
70-
runs_on_solved_scenario: bool = True
71-
7295
def ggtitle(self, extra: str | None = None):
7396
"""Return :class:`plotnine.ggtitle` including the current date & time."""
7497
title_parts = [
@@ -119,7 +142,7 @@ def add_tasks(
119142
# Output path for this parameter
120143
k_path = f"plot {cls.basename} path"
121144
filename = f"{cls.basename}{cls.suffix}"
122-
if cls.runs_on_solved_scenario:
145+
if cls.kind is Kind.REPORT:
123146
# Make a path including the Scenario URL
124147
c.add(k_path, "make_output_path", "config", name=filename)
125148
else:
@@ -212,7 +235,7 @@ class ComparePDT(Plot):
212235
213236
This plot is used in :func:`.transport.build.debug_multi`, not in ordinary
214237
reporting. Rather than receiving data from computed quantities already in the graph,
215-
it reads them from files named :file:`pdt.csv` (per :attr:`.kind`) in the
238+
it reads them from files named :file:`pdt.csv` (per :attr:`.measure`) in the
216239
directories generated by the workflow steps like "SSP1 debug build" (
217240
:func:`.transport.build.debug`).
218241
@@ -221,7 +244,8 @@ class ComparePDT(Plot):
221244
- One line with points per scenario, coloured by scenario.
222245
"""
223246

224-
runs_on_solved_scenario = False
247+
kind = Kind.BUILD_MULTI
248+
225249
basename = "compare-pdt"
226250

227251
static = Plot.static + [
@@ -234,15 +258,15 @@ class ComparePDT(Plot):
234258
]
235259

236260
#: Base name for source data files, for instance :file:`pdt.csv`.
237-
kind = "pdt"
261+
measure = "pdt"
238262

239263
#: Units of input files
240264
unit = "km/a"
241265
#: Unit adjustment factor.
242266
factor = 1e6
243267

244268
def generate(self, *paths: Path):
245-
data = read_csvs(self.kind, *paths).eval("value = value / @self.factor")
269+
data = read_csvs(self.measure, *paths).eval("value = value / @self.factor")
246270

247271
# Add factor to the unit expression
248272
if self.factor != 1.0:
@@ -259,7 +283,7 @@ class ComparePDTCap0(ComparePDT):
259283
"""
260284

261285
basename = "compare-pdt-cap"
262-
kind = "pdt-cap"
286+
measure = "pdt-cap"
263287
factor = 1e3
264288

265289

@@ -284,7 +308,7 @@ class ComparePDTCap1(Plot):
284308
- One line with points per |t| (=transport mode), coloured by mode.
285309
"""
286310

287-
runs_on_solved_scenario = False
311+
kind = Kind.BUILD_MULTI
288312
basename = "compare-pdt-capita-gdp"
289313
static = PDT_CAP_GDP_STATIC + [
290314
p9.facet_wrap("scenario", ncol=5),
@@ -526,7 +550,7 @@ class DemandCap(Plot):
526550

527551
def generate(self, data, commodities, cg):
528552
# Convert and select data
529-
data = data.query(f"c in {repr(list(map(str, commodities)))}").pipe(c_group, cg)
553+
data = data.query(f"c in {list(map(str, commodities))!r}").pipe(c_group, cg)
530554
for _, ggplot in self.groupby_plot(data, "n"):
531555
yield ggplot
532556

@@ -544,7 +568,7 @@ def _reduce_units(df: pd.DataFrame, target_units) -> tuple[pd.DataFrame, str]:
544568
class DemandExo(Plot):
545569
"""Passenger transport activity."""
546570

547-
runs_on_solved_scenario = False
571+
kind = Kind.BUILD
548572
basename = "demand-exo"
549573
inputs = [pdt_nyt]
550574
static = Plot.static + [
@@ -566,7 +590,7 @@ def generate(self, data):
566590
class DemandExoCap0(Plot):
567591
"""Passenger transport activity per person."""
568592

569-
runs_on_solved_scenario = False
593+
kind = Kind.BUILD
570594
basename = "demand-exo-capita"
571595
inputs = [pdt_nyt + "capita+post"]
572596
static = Plot.static + [
@@ -591,7 +615,7 @@ class DemandExoCap1(DemandExoCap0):
591615
Unlike :class:`DemandExoCap0`, this uses GDP per capita as the abscissa/x-aesthetic.
592616
"""
593617

594-
runs_on_solved_scenario = False
618+
kind = Kind.BUILD
595619
basename = "demand-exo-capita-gdp"
596620
inputs = [pdt_nyt + "capita+post", gdp_cap]
597621
static = PDT_CAP_GDP_STATIC
@@ -663,10 +687,10 @@ def generate(self, y0: int, data):
663687

664688

665689
class MultiStock(Plot):
666-
"""Stock, LDV technologies, SSP × policy."""
690+
"""LDV technology stock."""
667691

692+
kind = Kind.REPORT_MULTI
668693
basename = "multi-stock"
669-
runs_on_solved_scenario = False
670694

671695
static = Plot.static + [
672696
p9.aes(x="y", y="value", color="v"),
@@ -692,7 +716,7 @@ class Scale1Diff(Plot):
692716

693717
basename = "scale-1-diff"
694718

695-
runs_on_solved_scenario = False
719+
kind = Kind.BUILD
696720
inputs = ["scale-1:nl-t-c-l-h:a", "scale-1:nl-t-c-l-h:b"]
697721

698722
_s, _v, _y = "scenario", "value", "t + ' ' + c"
@@ -776,40 +800,36 @@ def generate(self, data):
776800
yield ggplot + p9.expand_limits(y=[0, y_max])
777801

778802

779-
def prepare_computer(c: Computer):
803+
def prepare_computer(
804+
c: Computer, *, kind: Kind = Kind.REPORT, target: "KeyLike" = k_report.all
805+
) -> None:
780806
"""Add :data:`.PLOTS` to `c`.
781807
782808
Adds:
783809
784810
- 1 key like **"plot inv-cost"** corresponding to the :attr:`~.Plot.basename` of
785-
each :class:`.Plot` subclass defined in this module.
811+
each :class:`.Plot` subclass defined in this module with a matching `kind`.
786812
- The key **"transport plots"** that triggers writing all the plots to file.
787813
"""
814+
# Force matplotlib to use a non-interactive backend for plotting
788815
import matplotlib
789816

790-
# Force matplotlib to use a non-interactive backend for plotting
791817
matplotlib.use("pdf")
792818

793-
keys = []
794-
795-
config: "Config" = c.graph["config"]["transport"]
796-
797819
# Iterate over the Plot subclasses defined in the current module
820+
keys = []
798821
for plot in filter(
799-
lambda cls: isinstance(cls, type) and issubclass(cls, Plot) and cls is not Plot,
822+
lambda cls: isinstance(cls, type)
823+
and issubclass(cls, Plot)
824+
and cls is not Plot
825+
and cls.kind is kind,
800826
globals().values(),
801827
):
802-
if (not plot.runs_on_solved_scenario and config.with_solution) or (
803-
False # Use True here or uncomment below to skip some or all plots
804-
# "stock" not in plot.basename
805-
):
806-
log.info(f"Skip {plot}")
807-
continue
808828
keys.append(f"plot {plot.basename}")
809829
c.add(keys[-1], plot)
810830

811-
key = "transport plots"
812-
log.info(f"Add {repr(key)} collecting {len(keys)} plots")
813-
c.add(key, keys)
831+
log.info(f"Add {k_report.plot!r} collecting {len(keys)} plots")
832+
c.add(k_report.plot, "summarize", keys)
814833

815-
c.graph[k_report.all].append(key)
834+
# Extend the task at `target` with k_report.plot
835+
append(c, target, k_report.plot)

0 commit comments

Comments
 (0)