From f3b1685a5232d3ae0821c7d0e74ad61584afb499 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 13 Sep 2023 14:27:41 +0200 Subject: [PATCH 01/56] Add .util.pycountry.iso_3166_alpha_3() --- message_ix_models/util/pycountry.py | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 message_ix_models/util/pycountry.py diff --git a/message_ix_models/util/pycountry.py b/message_ix_models/util/pycountry.py new file mode 100644 index 0000000000..f2b4968f22 --- /dev/null +++ b/message_ix_models/util/pycountry.py @@ -0,0 +1,42 @@ +from functools import lru_cache +from typing import Optional + +from pycountry import countries, historic_countries + +#: Mapping from common, non-standard country names to ISO 3166-1 names. +COUNTRY_NAME = { + "Korea": "Korea, Republic of", + "Republic of Korea": "Korea, Republic of", + "South Korea": "Korea, Republic of", + "Russia": "Russian Federation", +} + + +@lru_cache(maxsize=2**9) +def iso_3166_alpha_3(name: str) -> Optional[str]: + """Return an ISO 3166 alpha-3 code for a country `name`. + + Parameters + ---------- + name : str + Country name. This is looked up in the `pycountry + `_ 'name', + 'official_name', or 'common_name' field. Values in :data:`COUNTRY_NAME` are + supported. + + Returns + ------- + str or None + """ + # Maybe map a known, non-standard value to a standard value + name = COUNTRY_NAME.get(name, name) + + # Use pycountry's built-in, case-insensitive lookup on all fields including name, + # official_name, and common_name + for db in (countries, historic_countries): + try: + return db.lookup(name).alpha_3 + except LookupError: + continue # Not found in `db`, e.g. countries; try again + + return None From 1be7b06d90324e3856558f3df66a88cacfbf8d68 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 13 Sep 2023 14:31:44 +0200 Subject: [PATCH 02/56] Add .exo_data.iamc_like_data_for_query() --- message_ix_models/tools/exo_data.py | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/message_ix_models/tools/exo_data.py b/message_ix_models/tools/exo_data.py index edd17ab55d..6b2bbaa122 100644 --- a/message_ix_models/tools/exo_data.py +++ b/message_ix_models/tools/exo_data.py @@ -1,6 +1,7 @@ """Generic tools for working with exogenous data sources.""" from abc import ABC, abstractmethod from operator import itemgetter +from pathlib import Path from typing import Dict, Mapping, Optional, Type from genno import Computer, Key, Quantity, quote @@ -8,6 +9,7 @@ from message_ix_models import ScenarioInfo from message_ix_models.model.structure import get_codes +from message_ix_models.util import cached __all__ = [ "MEASURES", @@ -220,3 +222,55 @@ def random_data(): v={"v0": "Population", "v1": "GDP"}, y={"y0": 2010, "y1": 2050}, ) + + +@cached +def iamc_like_data_for_query(path: Path, query: str) -> Quantity: + """Load data from `path` in IAMC-like format and transform to :class:`.Quantity`. + + The steps involved are: + + 1. Read the data file; use pyarrow for better performance. + 2. Immediately apply `query` to reduce the data to be handled in subsequent steps. + 3. Assert that Model, Scenario, Variable, and Unit are unique; store the unique + values. This means that `query` **must** result in data with unique values for + these dimensions. + 4. Transform "Region" labels to ISO 3166-1 alpha-3 codes using + :func:`.iso_3166_alpha_3`. + 5. Drop entire time series without such codes; for instance "World". + 6. Transform to a pd.Series with "n" and "y" index levels; ensure the latter are + int. + 7. Transform to :class:`.Quantity` with units. + + The result is :obj:`.cached`. + """ + import pandas as pd + + from message_ix_models.util.pycountry import iso_3166_alpha_3 + + unique = dict() + + def drop_unique(df, names) -> pd.DataFrame: + names_list = names.split() + for name in names_list: + values = df[name].unique() + if len(values) > 1: + raise RuntimeError(f"Not unique {name!r}: {values}") + unique[name] = values[0] + return df.drop(names_list, axis=1) + + tmp = ( + pd.read_csv(path, engine="pyarrow") + .query(query) + .pipe(drop_unique, "Model Scenario Variable Unit") + .assign(n=lambda df: df["Region"].apply(iso_3166_alpha_3)) + .dropna(subset=["n"]) + .drop("Region", axis=1) + .set_index("n") + .rename(columns=lambda y: int(y)) + .rename_axis(columns="y") + .stack() + .dropna() + ) + + return Quantity(tmp, units=unique["Unit"]) From b0a4cf798cb2ab2995fb0c22c190cfd5bbdbfbe4 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 13 Sep 2023 14:37:15 +0200 Subject: [PATCH 03/56] Add .project.ssp.data with a ExoDataSource for SSP data --- message_ix_models/project/ssp/data.py | 47 +++++++++++++++++++++ message_ix_models/tests/project/test_ssp.py | 37 ++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 message_ix_models/project/ssp/data.py diff --git a/message_ix_models/project/ssp/data.py b/message_ix_models/project/ssp/data.py new file mode 100644 index 0000000000..8f4a81ae26 --- /dev/null +++ b/message_ix_models/project/ssp/data.py @@ -0,0 +1,47 @@ +from message_ix_models.tools.exo_data import ( + ExoDataSource, + iamc_like_data_for_query, + register_source, +) +from message_ix_models.util import private_data_path + + +@register_source +class SSPUpdate(ExoDataSource): + """Provider of exogenous data from the SSP Update database.""" + + id = "SSP update" + + def __init__(self, source, source_kw): + s = "ICONICS:SSP(2024)." + if not source.startswith(s): + raise ValueError(source) + + *parts, self.ssp_number = source.partition(s) + + # Map the `measure` keyword to a string appearing in the data + self.measure = { + "GDP": "GDP|PPP", + "POP": "Population", + }[source_kw.pop("measure")] + + # Store the model ID, if any + self.model = source_kw.pop("model", None) + + if len(source_kw): + raise ValueError(source_kw) + + def __call__(self): + # Assemble a query string + query = " and ".join( + [ + f"Scenario == 'SSP{self.ssp_number} - Review Phase 1'", + f"Variable == '{self.measure}'", + f"Model == '{self.model}'" if self.model else "True", + ] + ) + + path = private_data_path("ssp", "SSP-Review-Phase-1.csv.gz") + assert path.exists(), "TODO handle the case where message_data is not insalled" + + return iamc_like_data_for_query(path, query) diff --git a/message_ix_models/tests/project/test_ssp.py b/message_ix_models/tests/project/test_ssp.py index 75b77c1377..35c1fe473e 100644 --- a/message_ix_models/tests/project/test_ssp.py +++ b/message_ix_models/tests/project/test_ssp.py @@ -1,4 +1,5 @@ import pytest +from genno import Computer from message_ix_models.project.ssp import ( SSP, @@ -8,6 +9,8 @@ parse, ssp_field, ) +from message_ix_models.project.ssp.data import SSPUpdate # noqa: F401 +from message_ix_models.tools.exo_data import prepare_computer def test_generate(tmp_path, test_context): @@ -81,3 +84,37 @@ class Foo: def test_cli(mix_models_cli): mix_models_cli.assert_exit_0(["ssp", "gen-structures", "--dry-run"]) + + +class TestSSPUpdate: + @pytest.mark.parametrize( + "source", + ( + "ICONICS:SSP(2024).1", + "ICONICS:SSP(2024).2", + "ICONICS:SSP(2024).3", + "ICONICS:SSP(2024).4", + "ICONICS:SSP(2024).5", + ), + ) + @pytest.mark.parametrize( + "source_kw", + ( + dict(measure="POP"), + dict(measure="GDP", model="IIASA GDP 2023"), + dict(measure="GDP", model="OECD ENV-Growth 2023"), + ), + ) + def test_prepare_computer(self, test_context, source, source_kw): + c = Computer() + keys = prepare_computer(test_context, c, source, source_kw) + + # Preparation of data runs successfully + result = c.get(keys[0]) + + # Data has the expected dimensions + assert ("n", "y") == result.dims + + # Data is complete + assert 14 == len(result.coords["n"]) + assert 14 == len(result.coords["y"]) From 16660b6f53d60dbc50a00d67e78314cdef62af6f Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 13 Sep 2023 15:23:44 +0200 Subject: [PATCH 04/56] Generate synthetic SSP update data for testing --- .gitattributes | 2 +- .../data/test/ssp/SSP-Review-Phase-1.csv.gz | 3 ++ message_ix_models/project/ssp/__init__.py | 32 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 message_ix_models/data/test/ssp/SSP-Review-Phase-1.csv.gz diff --git a/.gitattributes b/.gitattributes index c0edca59f4..db99b4fcec 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,6 @@ # Reduce the number of merge conflicts doc/whatsnew.rst merge=union # Git LFS -*.csv.gz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text *.xlsx filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text diff --git a/message_ix_models/data/test/ssp/SSP-Review-Phase-1.csv.gz b/message_ix_models/data/test/ssp/SSP-Review-Phase-1.csv.gz new file mode 100644 index 0000000000..03e4218d13 --- /dev/null +++ b/message_ix_models/data/test/ssp/SSP-Review-Phase-1.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a82b38c086510e5162a5681d7bfca489c9ec0189864d8f9394255e8af72c53b8 +size 14612126 diff --git a/message_ix_models/project/ssp/__init__.py b/message_ix_models/project/ssp/__init__.py index 5aed2aee31..25a564cecf 100644 --- a/message_ix_models/project/ssp/__init__.py +++ b/message_ix_models/project/ssp/__init__.py @@ -68,3 +68,35 @@ def cli(): def gen_structures(context, **kwargs): """(Re)Generate the SSP data structures in SDMX.""" generate(context) + + +@cli.command("make-test-data") +def make_test_data(): + """Create random data for testing.""" + from pathlib import Path + + import pandas as pd + from numpy import char, random + + from message_ix_models.util import package_data_path, private_data_path + + # Paths + p = Path("ssp", "SSP-Review-Phase-1.csv.gz") + path_in = private_data_path(p) + path_out = package_data_path("test", p) + + # Read the data + df = pd.read_csv(path_in, engine="pyarrow") + + # Determine its numeric columns (2000, 2001, etc.) and shape + cols = list(filter(char.isnumeric, df.columns)) + size = (df.shape[0], len(cols)) + # - Generate random data of this shape. + # - Keep only the elements corresponding to non-NA elements of `df`. + # - Update `df` with these values.* + generator = random.default_rng() + df.update(df.where(df.isna(), pd.DataFrame(generator.random(size), columns=cols))) + + # Write to file, keeping only a few decimal points + path_out.parent.mkdir(parents=True, exist_ok=True) + df.to_csv(path_out, index=False, float_format="%.2f") From ac6aca446d3841cdeb0cf8ebca395980381309a4 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 13 Sep 2023 15:24:40 +0200 Subject: [PATCH 05/56] Fall back to synthetic test data for SSPUpdate --- message_ix_models/project/ssp/data.py | 28 ++++++++++++++++++++------- message_ix_models/util/__init__.py | 2 ++ message_ix_models/util/common.py | 3 +++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/message_ix_models/project/ssp/data.py b/message_ix_models/project/ssp/data.py index 8f4a81ae26..b1fddbaeeb 100644 --- a/message_ix_models/project/ssp/data.py +++ b/message_ix_models/project/ssp/data.py @@ -1,9 +1,18 @@ +import logging +from copy import copy + from message_ix_models.tools.exo_data import ( ExoDataSource, iamc_like_data_for_query, register_source, ) -from message_ix_models.util import private_data_path +from message_ix_models.util import ( + HAS_MESSAGE_DATA, + package_data_path, + private_data_path, +) + +log = logging.getLogger(__name__) @register_source @@ -20,16 +29,17 @@ def __init__(self, source, source_kw): *parts, self.ssp_number = source.partition(s) # Map the `measure` keyword to a string appearing in the data + _kw = copy(source_kw) self.measure = { "GDP": "GDP|PPP", "POP": "Population", - }[source_kw.pop("measure")] + }[_kw.pop("measure")] # Store the model ID, if any - self.model = source_kw.pop("model", None) + self.model = _kw.pop("model", None) - if len(source_kw): - raise ValueError(source_kw) + if len(_kw): + raise ValueError(_kw) def __call__(self): # Assemble a query string @@ -41,7 +51,11 @@ def __call__(self): ] ) - path = private_data_path("ssp", "SSP-Review-Phase-1.csv.gz") - assert path.exists(), "TODO handle the case where message_data is not insalled" + parts = ("ssp", "SSP-Review-Phase-1.csv.gz") + if HAS_MESSAGE_DATA: + path = private_data_path(*parts) + else: + path = package_data_path("test", *parts) + log.warning(f"Reading random data from {path}") return iamc_like_data_for_query(path, query) diff --git a/message_ix_models/util/__init__.py b/message_ix_models/util/__init__.py index 1376bfcfe1..7405a18431 100644 --- a/message_ix_models/util/__init__.py +++ b/message_ix_models/util/__init__.py @@ -10,6 +10,7 @@ from ._convert_units import convert_units, series_of_pint_quantity from .cache import cached from .common import ( + HAS_MESSAGE_DATA, MESSAGE_DATA_PATH, MESSAGE_MODELS_PATH, Adapter, @@ -25,6 +26,7 @@ from .sdmx import CodeLike, as_codes, eval_anno __all__ = [ + "HAS_MESSAGE_DATA", "MESSAGE_DATA_PATH", "MESSAGE_MODELS_PATH", "Adapter", diff --git a/message_ix_models/util/common.py b/message_ix_models/util/common.py index ddae79ea2f..f28f68db59 100644 --- a/message_ix_models/util/common.py +++ b/message_ix_models/util/common.py @@ -14,9 +14,11 @@ except ImportError: log.warning("message_data is not installed or cannot be imported") MESSAGE_DATA_PATH: Optional[Path] = None + HAS_MESSAGE_DATA = False else: # pragma: no cover (needs message_data) # Root directory of the message_data repository. MESSAGE_DATA_PATH = Path(message_data.__file__).parents[1] + HAS_MESSAGE_DATA = True # Directory containing message_ix_models.__init__ MESSAGE_MODELS_PATH = Path(__file__).parents[1] @@ -29,6 +31,7 @@ __all__ = [ + "HAS_MESSAGE_DATA", "Adapter", "MappingAdapter", ] From d03c1edf42a0a230f0fe7c51f74f46dd122dd115 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 13 Sep 2023 15:24:56 +0200 Subject: [PATCH 06/56] Don't package synthetic test data --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 9ec83e970d..9b59d0733c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,2 @@ prune message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline +prune message_ix_models/data/test/ssp From caa9ad74d75ffc380f75d524cef9923ce2077cbb Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 13 Sep 2023 15:52:25 +0200 Subject: [PATCH 07/56] Add strict=True kwarg to exo_data.prepare_computer() --- message_ix_models/report/computations.py | 2 +- message_ix_models/tools/exo_data.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/message_ix_models/report/computations.py b/message_ix_models/report/computations.py index 2d387aca16..430d90f62c 100644 --- a/message_ix_models/report/computations.py +++ b/message_ix_models/report/computations.py @@ -74,7 +74,7 @@ def exogenous_data(): @exogenous_data.helper def add_exogenous_data( func, c: "Computer", *, context=None, source=None, source_kw=None -) -> Tuple["Key"]: +) -> Tuple["Key", ...]: """Prepare `c` to compute exogenous data from `source`.""" from message_ix_models.tools.exo_data import prepare_computer diff --git a/message_ix_models/tools/exo_data.py b/message_ix_models/tools/exo_data.py index 6b2bbaa122..ac793bf8e3 100644 --- a/message_ix_models/tools/exo_data.py +++ b/message_ix_models/tools/exo_data.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from operator import itemgetter from pathlib import Path -from typing import Dict, Mapping, Optional, Type +from typing import Dict, Mapping, Optional, Tuple, Type from genno import Computer, Key, Quantity, quote from genno.core.key import single_key @@ -69,8 +69,13 @@ def __call__(self) -> Quantity: def prepare_computer( - context, c: "Computer", source="test", source_kw: Optional[Mapping] = None -): + context, + c: "Computer", + source="test", + source_kw: Optional[Mapping] = None, + *, + strict: bool = True, +) -> Tuple[Key, ...]: """Prepare `c` to compute GDP, population, or other exogenous data. Returns a tuple of keys. The first, like ``{m}:n-y``, triggers the following @@ -125,10 +130,10 @@ def prepare_computer( c.require_compat("message_ix_models.report.computations") # Retrieve the node codelist - c.add("n::codes", quote(get_codes(f"node/{context.model.regions}")), strict=True) + c.add("n::codes", quote(get_codes(f"node/{context.model.regions}")), strict=strict) # Convert the codelist into a nested dict for aggregate() - c.add("n::groups", "codelist_to_groups", "n::codes", strict=True) + c.add("n::groups", "codelist_to_groups", "n::codes", strict=strict) # Add information about the list of periods if "y" not in c: From f6e89272183f8a236748bcbf58d5fd1cf6437ce0 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 13 Sep 2023 15:52:47 +0200 Subject: [PATCH 08/56] Add pyarrow to dependencies --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 816439aa94..a3e68d5bf4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dependencies = [ "message_ix >= 3.4.0", "pooch", "pyam-iamc >= 0.6", + "pyarrow", "pycountry", "PyYAML", "sdmx1 >= 2.8.0", From 2bb21ca5f4dc26a8b5d9ef4d8f972d96d468bdf5 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 13 Sep 2023 16:20:30 +0200 Subject: [PATCH 09/56] Force regions="R14" in test_ssp --- message_ix_models/tests/project/test_ssp.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/message_ix_models/tests/project/test_ssp.py b/message_ix_models/tests/project/test_ssp.py index 35c1fe473e..c2d7042e42 100644 --- a/message_ix_models/tests/project/test_ssp.py +++ b/message_ix_models/tests/project/test_ssp.py @@ -106,7 +106,12 @@ class TestSSPUpdate: ), ) def test_prepare_computer(self, test_context, source, source_kw): + # FIXME The following should be redundant, but appears mutable on GHA linux and + # Windows runners. + test_context.model.regions = "R14" + c = Computer() + keys = prepare_computer(test_context, c, source, source_kw) # Preparation of data runs successfully From df0c984b04c4a69fcdbb538da73754d89434d469 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Thu, 14 Sep 2023 16:22:44 +0200 Subject: [PATCH 10/56] Use discard_on_error() to avoid locking in report() --- message_ix_models/report/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/message_ix_models/report/__init__.py b/message_ix_models/report/__init__.py index d5ef4eb1de..c613555be5 100644 --- a/message_ix_models/report/__init__.py +++ b/message_ix_models/report/__init__.py @@ -215,6 +215,14 @@ def report(context: Context, *args, **kwargs): - ``report/config`` is set to :file:`report/globa.yaml`, if not set. """ + try: + from ixmp.utils import discard_on_error + except ImportError: + from contextlib import nullcontext + + def discard_on_error(*args): + return nullcontext() + # Handle deprecated usage that appears in: # - .model.cli.new_baseline() # - .model.create.solve() @@ -257,7 +265,8 @@ def report(context: Context, *args, **kwargs): if context.dry_run: return - result = rep.get(key) + with discard_on_error(rep.graph["scenario"]): + result = rep.get(key) # Display information about the result log.info(f"Result:\n\n{result}\n") @@ -475,3 +484,4 @@ def defaults(rep: Reporter, context: Context) -> None: register(defaults) +register("message_ix_models.report.plot") From 4978a711dc7b5fc86bb50eb989d0e477220c3524 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Thu, 14 Sep 2023 16:23:37 +0200 Subject: [PATCH 11/56] Add report function filter_ts() --- message_ix_models/report/computations.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/message_ix_models/report/computations.py b/message_ix_models/report/computations.py index 430d90f62c..309aa91f6e 100644 --- a/message_ix_models/report/computations.py +++ b/message_ix_models/report/computations.py @@ -1,6 +1,7 @@ """Atomic reporting operations for MESSAGEix-GLOBIOM.""" import itertools import logging +import re from typing import TYPE_CHECKING, Any, List, Mapping, Optional, Set, Tuple, Union import ixmp @@ -20,6 +21,10 @@ log = logging.getLogger(__name__) __all__ = [ + "codelist_to_groups", + "compound_growth", + "exogenous_data", + "filter_ts", "from_url", "get_ts", "gwp_factors", @@ -83,6 +88,13 @@ def add_exogenous_data( ) +def filter_ts(df: pd.DataFrame, expr: re.Pattern) -> pd.DataFrame: + """Filter time series data in `df`.""" + return df[df["variable"].str.fullmatch(expr)].assign( + variable=df["variable"].str.replace(expr, r"\1", regex=True) + ) + + def get_ts( scenario: ixmp.Scenario, filters: Optional[dict] = None, From a3ff94478b38d0ef375bbe9c26f98ee4e26b54b0 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Thu, 14 Sep 2023 16:24:10 +0200 Subject: [PATCH 12/56] Use default report config from message_ix_models --- message_ix_models/report/cli.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/message_ix_models/report/cli.py b/message_ix_models/report/cli.py index b822b34b19..bd6d72f673 100644 --- a/message_ix_models/report/cli.py +++ b/message_ix_models/report/cli.py @@ -6,7 +6,6 @@ import yaml from message_ix_models.report import register, report -from message_ix_models.util import local_data_path, private_data_path from message_ix_models.util._logging import mark_time from message_ix_models.util.click import common_params @@ -52,11 +51,13 @@ def cli(context, config_file, legacy, module, output_path, from_file, key, dry_r With --from-file, read multiple Scenario identifiers from FILE, and report each one. In this usage, --output-path may only be a directory. """ + from message_ix_models.util import local_data_path, package_data_path + # --config: use the option value as if it were an absolute path config = Path(config_file) if not config.exists(): # Path doesn't exist; treat it as a stem in the metadata dir - config = private_data_path("report", config_file).with_suffix(".yaml") + config = package_data_path("report", config_file).with_suffix(".yaml") if not config.exists(): # Can't find the file From 061f49e5aea3e05a1f1122f93d1c1a355271df2e Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Thu, 14 Sep 2023 16:24:56 +0200 Subject: [PATCH 13/56] Add .report.plot Loosely based on current message_data.model.transport.plot. --- message_ix_models/report/plot.py | 186 +++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 message_ix_models/report/plot.py diff --git a/message_ix_models/report/plot.py b/message_ix_models/report/plot.py new file mode 100644 index 0000000000..dd11c0844b --- /dev/null +++ b/message_ix_models/report/plot.py @@ -0,0 +1,186 @@ +"""Plots for MESSAGEix-GLOBIOM reporting. + +The current set functions on time series data stored on the scenario by :mod:`.report` +or legacy reporting. +""" +import logging +import re +from datetime import datetime +from typing import TYPE_CHECKING, List, Optional, Sequence + +import genno.compat.plotnine +import pandas as pd +import plotnine as p9 +from genno import Computer, Key + +if TYPE_CHECKING: + from message_ix import Scenario + + from message_ix_models import Context + +log = logging.getLogger(__name__) + + +class Plot(genno.compat.plotnine.Plot): + """Base class for plots.""" + + #: 'Static' geoms: list of plotnine objects that are not dynamic. + static = [p9.theme(figure_size=(11.7, 8.3))] + + #: Fixed plot title string. If not given, the first line of the class docstring is + #: used. + title = None + + #: Units expression for plot title. + unit = None + + #: Scenario URL for plot title. + url: Optional[str] = None + + # NB only here to narrow typing + inputs: Sequence[str] = [] + #: List of regular expressions corresponding to :attr:`inputs`. + inputs_regex: List[re.Pattern] = [] + + def ggtitle(self, value=None) -> p9.ggtitle: + """Return :class:`plotnine.ggtitle` including the current date & time.""" + title_pieces = [ + (self.title or self.__doc__ or "").splitlines()[0].rstrip("."), + f"[{self.unit}]" if self.unit else None, + value, + "\n", + self.url, + f"({datetime.now().isoformat(timespec='minutes')})", + ] + return p9.ggtitle(" ".join(filter(None, title_pieces))) + + def groupby_plot(self, data: pd.DataFrame, *args): + """Combination of groupby and ggplot(). + + Groups by `args` and yields a series of :class:`plotnine.ggplot` objects, one + per group, with :attr:`static` geoms and :func:`ggtitle` appended to each. + """ + for group_key, group_df in data.groupby(*args): + yield group_key, ( + p9.ggplot(group_df) + + self.static + + self.ggtitle( + group_key if isinstance(group_key, str) else repr(group_key) + ) + ) + + +class EmissionsCO2(Plot): + """CO₂ Emissions.""" + + basename = "emission-CO2" + inputs = ["Emissions|CO2::iamc", "scenario"] + + static = Plot.static + [ + p9.aes(x="year", y="value", color="region"), + p9.geom_line(), + p9.geom_point(), + p9.labs(x="Period", y=None, color="Region"), + ] + + def generate(self, data: pd.DataFrame, scenario: "Scenario"): + self.url = scenario.url + self.unit = data["unit"].unique()[0] + + for _, ggplot in self.groupby_plot(data, data.region.str.contains("GLB")): + y_max = max(ggplot.data["value"]) + yield ggplot + p9.expand_limits(y=[0, y_max]) + self.ggtitle("") + + +class FinalEnergy0(EmissionsCO2): + """Final Energy.""" + + basename = "fe0" + inputs = ["Final Energy::iamc", "scenario"] + + +class FinalEnergy1(Plot): + """Final Energy.""" + + basename = "fe1" + inputs = ["fe1-0::iamc", "scenario"] + + _c = [ + "Electricity", + "Gases", + "Geothermal", + "Heat", + "Hydrogen", + "Liquids", + "Solar", + "Solids", + ] + inputs_regex = [re.compile(rf"Final Energy\|({'|'.join(_c)})")] + + static = Plot.static + [ + p9.aes(x="year", y="value", fill="variable"), + p9.geom_bar(stat="identity", size=5.0), # 5.0 is the minimum spacing of "year" + p9.labs(x="Period", y=None, fill="Commodity"), + ] + + def generate(self, data: pd.DataFrame, scenario: "Scenario"): + self.url = scenario.url + self.unit = data["unit"].unique()[0] + + for _, ggplot in self.groupby_plot(data, "region"): + yield ggplot + + +class PrimaryEnergy0(EmissionsCO2): + """Primary Energy.""" + + basename = "pe0" + inputs = ["Primary Energy::iamc", "scenario"] + + +class PrimaryEnergy1(FinalEnergy1): + """Primary Energy.""" + + basename = "pe1" + inputs = ["pe1-0::iamc", "scenario"] + + _omit = ["Fossil", "Non-Biomass Renewables", "Secondary Energy Trade"] + inputs_regex = [re.compile(rf"Primary Energy\|((?!{'|'.join(_omit)})[^\|]*)")] + + +PLOTS = ( + EmissionsCO2, + FinalEnergy0, + FinalEnergy1, + PrimaryEnergy0, + PrimaryEnergy1, +) + + +def callback(c: Computer, context: "Context") -> None: + from copy import copy + from itertools import zip_longest + + all_keys = [] + + # Retrieve all time series data, for advanced filtering + c.add("all::iamc", "get_ts", "scenario") + + for p in PLOTS: + # TODO move these into an override of Plot.add_tasks() + if len(p.inputs_regex): + # Iterate over matched items from `inputs` and `inputs_regex` + for key, expr in zip_longest(p.inputs, p.inputs_regex): + if expr is None: + break + # Filter the data given by `expr` from all::iamc + c.add(key, "filter_ts", "all::iamc", copy(expr)) + else: + for key in map(Key, p.inputs): + # Add a computation to get the time series data for a specific variable + c.add(key, "get_ts", "scenario", dict(variable=key.name)) + + # Add the plot itself + all_keys.append(c.add(f"plot {p.basename}", p)) + + c.add("plot all", all_keys) From 3e1653020470e59e241e371786d988372777225d Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Thu, 14 Sep 2023 16:25:11 +0200 Subject: [PATCH 14/56] Add plotnine to mypy exclusions --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index a3e68d5bf4..91af224338 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,6 +86,7 @@ exclude = ["doc/"] module = [ "colorama", "message_data.*", + "plotnine", "pooch", "pycountry", # Indirectly via message_ix From 05a877f93c99e960783678f382cfe81cd420d7d4 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Fri, 15 Sep 2023 10:18:28 +0200 Subject: [PATCH 15/56] Tolerate mixed case column names in iamc_like_data_for_query() --- message_ix_models/tools/exo_data.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/message_ix_models/tools/exo_data.py b/message_ix_models/tools/exo_data.py index ac793bf8e3..609a8253a2 100644 --- a/message_ix_models/tools/exo_data.py +++ b/message_ix_models/tools/exo_data.py @@ -267,10 +267,11 @@ def drop_unique(df, names) -> pd.DataFrame: tmp = ( pd.read_csv(path, engine="pyarrow") .query(query) - .pipe(drop_unique, "Model Scenario Variable Unit") - .assign(n=lambda df: df["Region"].apply(iso_3166_alpha_3)) + .rename(columns=lambda c: c.upper()) + .pipe(drop_unique, "MODEL SCENARIO VARIABLE UNIT") + .assign(n=lambda df: df["REGION"].apply(iso_3166_alpha_3)) .dropna(subset=["n"]) - .drop("Region", axis=1) + .drop("REGION", axis=1) .set_index("n") .rename(columns=lambda y: int(y)) .rename_axis(columns="y") @@ -278,4 +279,4 @@ def drop_unique(df, names) -> pd.DataFrame: .dropna() ) - return Quantity(tmp, units=unique["Unit"]) + return Quantity(tmp, units=unique["UNIT"]) From 0c7bbc7dc8bd45594919f51f8b86668f9fa20ede Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Fri, 15 Sep 2023 11:31:05 +0200 Subject: [PATCH 16/56] Implement Plot.add_tasks() Collect logic within the class for adding necessary input keys to the graph. --- message_ix_models/report/plot.py | 82 +++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/message_ix_models/report/plot.py b/message_ix_models/report/plot.py index dd11c0844b..9eba19d9e9 100644 --- a/message_ix_models/report/plot.py +++ b/message_ix_models/report/plot.py @@ -14,6 +14,7 @@ from genno import Computer, Key if TYPE_CHECKING: + from genno.core.key import KeyLike from message_ix import Scenario from message_ix_models import Context @@ -22,10 +23,26 @@ class Plot(genno.compat.plotnine.Plot): - """Base class for plots.""" + """Base class for plots based on reported time-series data. + + Subclasses should be used like: + + .. code-block:: python + + class MyPlot(Plot): + ... + + c.add("plot myplot", MyPlot, "scenario") + + …that is, giving "scenario" or another key that points to a |Scenario| object with + stored time series data. See the examples in this file. + """ #: 'Static' geoms: list of plotnine objects that are not dynamic. - static = [p9.theme(figure_size=(11.7, 8.3))] + static = [ + p9.theme(figure_size=(23.4, 16.5)), # A3 paper in landscape [inches] + # p9.theme(figure_size=(11.7, 8.3)), # A4 paper in landscape + ] #: Fixed plot title string. If not given, the first line of the class docstring is #: used. @@ -39,9 +56,42 @@ class Plot(genno.compat.plotnine.Plot): # NB only here to narrow typing inputs: Sequence[str] = [] - #: List of regular expressions corresponding to :attr:`inputs`. + + #: List of regular expressions corresponding to :attr:`inputs`. These are passed as + #: the `expr` argument to :func:`filter_ts` to filter the entire set of time series + #: data. inputs_regex: List[re.Pattern] = [] + @classmethod + def add_tasks( + cls, c: "Computer", key: "KeyLike", *inputs, strict: bool = False + ) -> "KeyLike": + from copy import copy + from itertools import zip_longest + + scenario_key = inputs[0] + + # Retrieve all time series data, for advanced filtering + all_data = Key(scenario_key) + "iamc" + c.add(all_data, "get_ts", scenario_key) + + if len(cls.inputs_regex): + # Iterate over matched items from `inputs` and `inputs_regex` + for k, expr in zip_longest(cls.inputs, cls.inputs_regex): + if expr is None: + break + # Filter the data given by `expr` from all::iamc + c.add(k, "filter_ts", all_data, copy(expr)) + else: + for k in map(Key, cls.inputs): + # Add a computation to get the time series data for a specific variable + c.add(k, "get_ts", scenario_key, dict(variable=k.name)) + + # Add the plot itself + # TODO once the genno class returns the added key, change to "return super().…" + super().add_tasks(c, key, *inputs[1:], strict=strict) + return key + def ggtitle(self, value=None) -> p9.ggtitle: """Return :class:`plotnine.ggtitle` including the current date & time.""" title_pieces = [ @@ -158,29 +208,5 @@ class PrimaryEnergy1(FinalEnergy1): def callback(c: Computer, context: "Context") -> None: - from copy import copy - from itertools import zip_longest - - all_keys = [] - - # Retrieve all time series data, for advanced filtering - c.add("all::iamc", "get_ts", "scenario") - - for p in PLOTS: - # TODO move these into an override of Plot.add_tasks() - if len(p.inputs_regex): - # Iterate over matched items from `inputs` and `inputs_regex` - for key, expr in zip_longest(p.inputs, p.inputs_regex): - if expr is None: - break - # Filter the data given by `expr` from all::iamc - c.add(key, "filter_ts", "all::iamc", copy(expr)) - else: - for key in map(Key, p.inputs): - # Add a computation to get the time series data for a specific variable - c.add(key, "get_ts", "scenario", dict(variable=key.name)) - - # Add the plot itself - all_keys.append(c.add(f"plot {p.basename}", p)) - + all_keys = [c.add(f"plot {p.basename}", p, "scenario") for p in PLOTS] c.add("plot all", all_keys) From 9157f78e469677d34021b7e9ed9e53cf352c4130 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Fri, 15 Sep 2023 11:50:46 +0200 Subject: [PATCH 17/56] Add plotnine to "report" optional dependencies --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 91af224338..35e16c5c4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,9 +59,11 @@ docs = [ "sphinx_rtd_theme", "sphinxcontrib-bibtex", ] +report = ["plotnine"] tests = [ # For nbclient, thus nbformat "ixmp[tests]", + "message_ix_models[report]", "pytest", "pytest-cov", "pytest-xdist", From b68361c9f1b24419c00c43a6b2c17055bad3b42b Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Mon, 18 Sep 2023 14:56:02 +0200 Subject: [PATCH 18/56] Add ScenarioInfo.{path,url}; convert to dataclass --- message_ix_models/util/scenarioinfo.py | 77 ++++++++++++++++++++------ 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/message_ix_models/util/scenarioinfo.py b/message_ix_models/util/scenarioinfo.py index 485a44caeb..f11c41182f 100644 --- a/message_ix_models/util/scenarioinfo.py +++ b/message_ix_models/util/scenarioinfo.py @@ -1,19 +1,25 @@ """:class:`ScenarioInfo` class.""" import logging +import re from collections import defaultdict -from dataclasses import dataclass, field +from dataclasses import InitVar, dataclass, field from itertools import product -from typing import Dict, List, Optional +from typing import TYPE_CHECKING, Dict, List, Optional +import ixmp.utils import pandas as pd import pint import sdmx.model.v21 as sdmx_model from .sdmx import eval_anno +if TYPE_CHECKING: + from message_ix import Scenario + log = logging.getLogger(__name__) +@dataclass(kw_only=True) class ScenarioInfo: """Information about a :class:`~message_ix.Scenario` object. @@ -37,7 +43,7 @@ class ScenarioInfo: Parameters ---------- - scenario : message_ix.Scenario + scenario_obj : message_ix.Scenario If given, :attr:`.set` is initialized from this existing scenario. See also @@ -45,11 +51,17 @@ class ScenarioInfo: .Spec """ + scenario_obj: InitVar[Optional["Scenario"]] = field(default=None, kw_only=False) + + model: Optional[str] = None + scenario: Optional[str] = None + version: Optional[int] = None + #: Elements of :mod:`ixmp`/:mod:`message_ix` sets. - set: Dict[str, List] = {} + set: Dict[str, List] = field(default_factory=lambda: defaultdict(list)) #: Elements of :mod:`ixmp`/:mod:`message_ix` parameters. - par: Dict[str, pd.DataFrame] = {} + par: Dict[str, pd.DataFrame] = field(default_factory=dict) #: First model year, if set, else ``Y[0]``. y0: int = -1 @@ -59,29 +71,34 @@ class ScenarioInfo: _yv_ya: pd.DataFrame = None - def __init__(self, scenario=None): - self.set = defaultdict(list) - self.par = dict() - - if not scenario: + def __post_init__(self, scenario_obj: Optional["Scenario"]): + if not scenario_obj: return - for name in scenario.set_list(): + self.model = scenario_obj.model + self.scenario = scenario_obj.scenario + self.version = ( + None if scenario_obj.version is None else int(scenario_obj.version) + ) + + # Copy structure (set contents) + for name in scenario_obj.set_list(): try: - self.set[name] = scenario.set(name).tolist() + self.set[name] = scenario_obj.set(name).tolist() except AttributeError: continue # pd.DataFrame for ≥2-D set; don't convert + # Copy data for a limited set of parameters for name in ("duration_period",): - self.par[name] = scenario.par(name) + self.par[name] = scenario_obj.par(name) - self.is_message_macro = "PRICE_COMMODITY" in scenario.par_list() + self.is_message_macro = "PRICE_COMMODITY" in scenario_obj.par_list() # Computed once - fmy = scenario.cat("year", "firstmodelyear") + fmy = scenario_obj.cat("year", "firstmodelyear") self.y0 = int(fmy[0]) if len(fmy) else self.set["year"][0] - self._yv_ya = scenario.vintage_and_active_years() + self._yv_ya = scenario_obj.vintage_and_active_years() @property def yv_ya(self): @@ -121,6 +138,30 @@ def Y(self): """Elements of the set 'year' that are >= the first model year.""" return list(filter(lambda y: y >= self.y0, self.set["year"])) + @property + def url(self) -> str: + """Identical to :attr:`.TimeSeries.url`.""" + return f"{self.model}/{self.scenario}#{self.version}" + + @url.setter + def url(self, value): + _, values = ixmp.utils.parse_url(value) + for k in "model", "scenario", "version": + setattr(self, k, values.get(k)) + + _path_re = [ + (re.compile(r"[/<>:\"\\\|\?\*]+"), "_"), + (re.compile("#"), "_v"), + (re.compile("__+"), "_"), + ] + + @property + def path(self) -> str: + """A valid path name similar to :attr:`url`.""" + from functools import reduce + + return reduce(lambda s, e: e[0].sub(e[1], s), self._path_re, self.url) + def update(self, other: "ScenarioInfo"): """Update with the set elements of `other`.""" for name, data in other.set.items(): @@ -129,6 +170,10 @@ def update(self, other: "ScenarioInfo"): for name, data in other.par.items(): raise NotImplementedError("Merging parameter data") + def __iter__(self): + for k in "model", "scenario", "version": + yield (k, getattr(self, k)) + def __repr__(self): return ( f" Date: Mon, 18 Sep 2023 14:57:02 +0200 Subject: [PATCH 19/56] Test ScenarioInfo.{path,url} --- .../tests/util/test_scenarioinfo.py | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/message_ix_models/tests/util/test_scenarioinfo.py b/message_ix_models/tests/util/test_scenarioinfo.py index 8c263ed9d3..ec748f852b 100644 --- a/message_ix_models/tests/util/test_scenarioinfo.py +++ b/message_ix_models/tests/util/test_scenarioinfo.py @@ -77,7 +77,25 @@ def test_units(self, caplog): # level= keyword argument → logged warning assert "level = 'useful' ignored" == caplog.messages[-1] - def test_from_scenario(self, test_context): + def test_iter(self) -> None: + info = ScenarioInfo(model="m", scenario="s") + + # dict() operates on the instance via __iter__ + assert dict(model="m", scenario="s", version=None) == dict(info) + + # Individual attributes are accessible + assert "m" == info.model + assert "s" == info.scenario + assert None is info.version + + def test_url(self) -> None: + info = ScenarioInfo(model="m", scenario="s", version=42) + assert "m/s#42" == info.url + + info.url = "a/b" + assert dict(model="a", scenario="b", version=None) == dict(info) + + def test_from_scenario(self, test_context) -> None: """ScenarioInfo initialized from an existing Scenario.""" mp = test_context.get_platform() scenario = make_dantzig(mp, multi_year=True) @@ -85,6 +103,11 @@ def test_from_scenario(self, test_context): # ScenarioInfo can be initialized from the scenario info = ScenarioInfo(scenario) + # model, scenario, and version attributes are retrieved from `scenario` + assert dict( + model="Canning problem (MESSAGE scheme)", scenario="multi-year", version=1 + ) == dict(info) + # Shorthand properties assert_frame_equal( pd.DataFrame( @@ -114,6 +137,21 @@ def test_from_scenario(self, test_context): assert 1963 == info.y0 assert [1963, 1964, 1965] == info.Y + @pytest.mark.parametrize( + "input, expected", + ( + ( + "Mix-G 1.1-BM-R12 (NAV)/NPi-ref EN_20C_step-3+B#3", + "Mix-G 1.1-BM-R12 (NAV)_NPi-ref EN_20C_step-3+B_v3", + ), + ("foo<>bar/baz|qux*#42", "foo_bar_baz_qux_v42"), + ), + ) + def test_path(self, input, expected) -> None: + si = ScenarioInfo() + si.url = input + assert expected == si.path + def test_repr(self): si = ScenarioInfo() si.set["foo"] = [1, 2, 3] From c44a8d888304b2e59c30f57c769c84f3df57792a Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 20:45:56 +0200 Subject: [PATCH 20/56] Add a configuration class for .report settings --- message_ix_models/report/config.py | 104 +++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 message_ix_models/report/config.py diff --git a/message_ix_models/report/config.py b/message_ix_models/report/config.py new file mode 100644 index 0000000000..8395e912e3 --- /dev/null +++ b/message_ix_models/report/config.py @@ -0,0 +1,104 @@ +import logging +from dataclasses import InitVar, dataclass, field +from pathlib import Path +from typing import TYPE_CHECKING, Dict, Optional, Union + +from message_ix_models.util import local_data_path, package_data_path +from message_ix_models.util.config import ConfigHelper + +if TYPE_CHECKING: + from genno.core.key import KeyLike + +log = logging.getLogger(__name__) + + +@dataclass +class Config(ConfigHelper): + """Settings for :mod:`message_ix_models.report`.""" + + #: Shorthand to call :func:`use_file` on a new instance. + from_file: InitVar[Optional[Path]] = package_data_path("report", "global.yaml") + + #: Shorthand to set :py:`legacy["use"]` on a new instance. + _legacy: InitVar[Optional[bool]] = False + + #: Path to write reporting outputs when invoked from the command line. + cli_output: Optional[Path] = None + + #: Configuration to be handled by :mod:`genno.config`. + genno_config: Dict = field(default_factory=dict) + + #: Key for the Quantity or computation to report. + key: Optional["KeyLike"] = None + + #: Directory for output. + output_dir: Optional[Path] = field( + default_factory=lambda: local_data_path("report") + ) + + #: :data:`True` to use an output directory based on the scenario's model name and + #: name. + use_scenario_path: bool = True + + #: Keyword arguments for the ‘legacy’ reporting entry point + #: (:func:`message_data.tools.post_processing.iamc_report_hackathon.report`), plus + #: the special key "use", which should be :data:`True` if legacy reporting is to + #: be used. + legacy: Dict = field(default_factory=lambda: dict(use=False, merge_hist=True)) + + def __post_init__(self, from_file, _legacy) -> None: + self.use_file(from_file) + self.legacy.update(use=_legacy) + + def set_output_dir(self, arg: Optional[Path]) -> None: + """Set the output directory. + + The value is also passed to :mod:`genno` as the "output_dir" configuration key. + """ + if arg: + self.output_dir = arg.expanduser() + + self.genno_config["output_dir"] = self.output_dir + + def use_file(self, file_path: Union[str, Path, None]) -> None: + """Use genno configuration from a (YAML) file at `file_path`. + + See :mod:`genno.config` for the format of these files. The path is stored at + :py:`.genno_config["path]`, where it is picked up by genno's configuration + mechanism. + + Parameters + ---------- + file_path : PathLike, *optional* + This may be: + + 1. The complete path to any existing file. + 2. A stem like "global" or "other". This is interpreted as referring to a + file named, for instance, :file:`global.yaml`. + 3. A partial path like "project/report.yaml". This or (2) is interpreted + as referring to a file within :file:`MESSAGE_MODELS_PATH/data/report/`; + that is, a file packaged and distributed with :mod:`message_ix_models`. + """ + if file_path is None: + return + try: + path = next( + filter( + Path.exists, + ( + Path(file_path), + # Path doesn't exist; treat it as a stem in the metadata dir + package_data_path("report", file_path).with_suffix(".yaml"), + ), + ) + ) + except StopIteration: + raise FileNotFoundError(f"Reporting configuration in {file_path}") + + # Store for genno to handle + self.genno_config["path"] = path + + def mkdir(self) -> None: + """Ensure the :attr:`output_dir` exists.""" + if self.output_dir: + self.output_dir.mkdir(exist_ok=True, parents=True) From 0d6335c269d18ef54f1a28e50bbb725df5f531cf Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 20:49:28 +0200 Subject: [PATCH 21/56] Add ScenarioInfo.from_url() --- message_ix_models/util/scenarioinfo.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/message_ix_models/util/scenarioinfo.py b/message_ix_models/util/scenarioinfo.py index f11c41182f..c0dc65b3dd 100644 --- a/message_ix_models/util/scenarioinfo.py +++ b/message_ix_models/util/scenarioinfo.py @@ -100,6 +100,13 @@ def __post_init__(self, scenario_obj: Optional["Scenario"]): self._yv_ya = scenario_obj.vintage_and_active_years() + @classmethod + def from_url(cls, url: str) -> "ScenarioInfo": + """Create an instance using only an :attr:`url`.""" + result = cls() + result.url = url + return result + @property def yv_ya(self): """:class:`pandas.DataFrame` with valid ``year_vtg``, ``year_act`` pairs.""" From 8994dc850a1bf254a5d5c1c1f3afc6ae14516859 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 20:50:07 +0200 Subject: [PATCH 22/56] Store ScenarioInfo.platform_name when setting url --- message_ix_models/util/scenarioinfo.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/message_ix_models/util/scenarioinfo.py b/message_ix_models/util/scenarioinfo.py index c0dc65b3dd..7fd2c0650b 100644 --- a/message_ix_models/util/scenarioinfo.py +++ b/message_ix_models/util/scenarioinfo.py @@ -53,6 +53,7 @@ class ScenarioInfo: scenario_obj: InitVar[Optional["Scenario"]] = field(default=None, kw_only=False) + platform_name: Optional[str] = None model: Optional[str] = None scenario: Optional[str] = None version: Optional[int] = None @@ -152,9 +153,10 @@ def url(self) -> str: @url.setter def url(self, value): - _, values = ixmp.utils.parse_url(value) + p, s = ixmp.utils.parse_url(value) + self.platform_name = p["name"] for k in "model", "scenario", "version": - setattr(self, k, values.get(k)) + setattr(self, k, s.get(k)) _path_re = [ (re.compile(r"[/<>:\"\\\|\?\*]+"), "_"), From 6f845cb111aa80ffdbbf10f08d2a55827abf7552 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 20:50:44 +0200 Subject: [PATCH 23/56] =?UTF-8?q?Add=20ScenarioInfo(=E2=80=A6,=20empty=3DT?= =?UTF-8?q?rue)=20for=20fast=20creation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- message_ix_models/util/scenarioinfo.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/message_ix_models/util/scenarioinfo.py b/message_ix_models/util/scenarioinfo.py index 7fd2c0650b..a68646faa4 100644 --- a/message_ix_models/util/scenarioinfo.py +++ b/message_ix_models/util/scenarioinfo.py @@ -52,6 +52,7 @@ class ScenarioInfo: """ scenario_obj: InitVar[Optional["Scenario"]] = field(default=None, kw_only=False) + empty: InitVar[bool] = False platform_name: Optional[str] = None model: Optional[str] = None @@ -72,7 +73,7 @@ class ScenarioInfo: _yv_ya: pd.DataFrame = None - def __post_init__(self, scenario_obj: Optional["Scenario"]): + def __post_init__(self, scenario_obj: Optional["Scenario"], empty: bool): if not scenario_obj: return @@ -82,6 +83,9 @@ def __post_init__(self, scenario_obj: Optional["Scenario"]): None if scenario_obj.version is None else int(scenario_obj.version) ) + if empty: + return + # Copy structure (set contents) for name in scenario_obj.set_list(): try: From 550b6360d5b3ff5290ce68ef8ad1e240e330a810 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 20:52:02 +0200 Subject: [PATCH 24/56] Ensure a .report.Config on every Context instance --- message_ix_models/util/context.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/message_ix_models/util/context.py b/message_ix_models/util/context.py index 98ea03c37d..e1802d36ac 100644 --- a/message_ix_models/util/context.py +++ b/message_ix_models/util/context.py @@ -72,6 +72,7 @@ def only(cls) -> "Context": def __init__(self, *args, **kwargs): from message_ix_models.model import Config as ModelConfig + from message_ix_models.report.config import Config as ReportConfig if len(_CONTEXTS) == 0: log.info("Create root Context") @@ -79,6 +80,7 @@ def __init__(self, *args, **kwargs): # Handle keyword arguments going to known config dataclasses kwargs["core"] = Config(**_dealiased("core", kwargs)) kwargs["model"] = ModelConfig(**_dealiased("model", kwargs)) + kwargs["report"] = ReportConfig() # Store any keyword arguments super().__init__(*args, **kwargs) From 98897df96179cf3be1cd225bcf3fd55eb27d645d Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 20:53:28 +0200 Subject: [PATCH 25/56] Add ConfigHelper.update() --- message_ix_models/util/config.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/message_ix_models/util/config.py b/message_ix_models/util/config.py index 0c48fe467c..776a8cc1b5 100644 --- a/message_ix_models/util/config.py +++ b/message_ix_models/util/config.py @@ -107,6 +107,13 @@ def replace(self, **kwargs): **{k: v for k, v in self._munge_dict(kwargs, "raise", "keyword argument")}, ) + def update(self, **kwargs): + # TODO use _munge_dict(); allow a positional argument + for k, v in kwargs.items(): + if not hasattr(self, k): + raise AttributeError(k) + setattr(self, k, v) + @classmethod def from_dict(cls, data: Mapping): """Construct an instance from `data` with name manipulation.""" From b25ed5da3e5d59aedf1492bda76f90e93c99b954 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 20:54:17 +0200 Subject: [PATCH 26/56] Add .Config.scenarios --- message_ix_models/util/config.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/message_ix_models/util/config.py b/message_ix_models/util/config.py index 776a8cc1b5..f162f14940 100644 --- a/message_ix_models/util/config.py +++ b/message_ix_models/util/config.py @@ -2,10 +2,12 @@ import os from dataclasses import dataclass, field, fields, is_dataclass, replace from pathlib import Path -from typing import Any, Hashable, Mapping, MutableMapping, Optional, Sequence, Set +from typing import Any, Hashable, List, Mapping, MutableMapping, Optional, Sequence, Set import ixmp +from .scenarioinfo import ScenarioInfo + log = logging.getLogger(__name__) ixmp.config.register("message local data", Path, Path.cwd()) @@ -138,6 +140,9 @@ class Config: #: :program:`--scenario` or :program:`--url` CLI options. scenario_info: MutableMapping[str, str] = field(default_factory=dict) + #: Like `scenario_info`, but a list for operations affecting multiple scenarios. + scenarios: List[ScenarioInfo] = field(default_factory=list) + #: Like :attr:`platform_info`, used by e.g. :meth:`.clone_to_dest`. dest_platform: MutableMapping[str, str] = field(default_factory=dict) From 8c3f9d5351a8bb12e54eff91bedc51b74223788e Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 20:55:03 +0200 Subject: [PATCH 27/56] Add --urls-from-file common Click option --- message_ix_models/util/click.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/message_ix_models/util/click.py b/message_ix_models/util/click.py index f95d2db7f3..5602a06491 100644 --- a/message_ix_models/util/click.py +++ b/message_ix_models/util/click.py @@ -4,6 +4,7 @@ """ import logging from datetime import datetime +from pathlib import Path from typing import Optional, Union import click @@ -12,6 +13,8 @@ from message_ix_models import Context from message_ix_models.model.structure import codelists +from .scenarioinfo import ScenarioInfo + log = logging.getLogger(__name__) @@ -83,6 +86,20 @@ def store_context(context: Union[click.Context, Context], param, value): return value +def urls_from_file(context: Union[click.Context, Context], param, value): + """Callback to parse scenario URLs from `value`.""" + si = [] + with click.open_file(value) as f: + for line in f: + si.append(ScenarioInfo.from_url(url=line)) + + # Store on context + mm_context = context.obj if isinstance(context, click.Context) else context + mm_context.core.scenarios = si + + return si + + def unique_id() -> str: """Return a unique ID for a CLI invocation. @@ -178,6 +195,17 @@ def unique_id() -> str: "ssp": Argument( ["ssp"], callback=store_context, type=Choice(["SSP1", "SSP2", "SSP3"]) ), + "urls_from_file": Option( + ["--urls-from-file", "-f"], + type=click.Path( + exists=True, + dir_okay=False, + resolve_path=True, + allow_dash=True, + path_type=Path, + ), + callback=urls_from_file, + ), "verbose": Option( # NB cannot use store_callback here; this is processed in the top-level CLI # before the message_ix_models.Context() object is set up From 4649ba049fcd2cfeabb28d4e607011120343fa09 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 20:59:59 +0200 Subject: [PATCH 28/56] Adjust order in .report.register(); reduce verbosity --- message_ix_models/report/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/message_ix_models/report/__init__.py b/message_ix_models/report/__init__.py index c613555be5..0bb1d8a110 100644 --- a/message_ix_models/report/__init__.py +++ b/message_ix_models/report/__init__.py @@ -146,12 +146,12 @@ def cb(rep: Reporter, ctx: Context): if isinstance(name_or_callback, str): # Resolve a string for name in [ + # As a fully-resolved package/module name + name_or_callback, # As a submodule of message_ix_models f"message_ix_models.{name_or_callback}.report", # As a submodule of message_data f"message_data.{name_or_callback}.report", - # As a fully-resolved package/module name - name_or_callback, ]: try: mod = import_module(name) From 5170e44b3b914426e61377e02d3189386c6c61f6 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 21:04:15 +0200 Subject: [PATCH 29/56] Simplify .report and .report.cli using Config class --- message_ix_models/report/__init__.py | 103 ++++++++------------- message_ix_models/report/cli.py | 130 +++++++++++---------------- 2 files changed, 88 insertions(+), 145 deletions(-) diff --git a/message_ix_models/report/__init__.py b/message_ix_models/report/__init__.py index 0bb1d8a110..6095313c18 100644 --- a/message_ix_models/report/__init__.py +++ b/message_ix_models/report/__init__.py @@ -9,13 +9,11 @@ import genno.config import yaml -from dask.core import literal from genno import Key, KeyExistsError from genno.compat.pyam import iamc as handle_iamc from message_ix import Reporter, Scenario -from message_ix_models import Context -from message_ix_models.util import local_data_path, package_data_path +from message_ix_models import Context, ScenarioInfo from message_ix_models.util._logging import mark_time __all__ = [ @@ -172,7 +170,7 @@ def cb(rep: Reporter, ctx: Context): return name -def log_before(context, rep, key): +def log_before(context, rep, key) -> None: log.info(f"Prepare to report {'(DRY RUN)' if context.dry_run else ''}") log.info(key) log.log( @@ -238,7 +236,7 @@ def discard_on_error(*args): # Transfer args, kwargs to context context.set_scenario(scenario) - context.report["legacy"] = kwargs.pop("legacy") + context.report.legacy = kwargs.pop("legacy") if len(args) + len(set(kwargs.keys()) & {"path"}) != 1: raise TypeError( @@ -249,15 +247,11 @@ def discard_on_error(*args): out_dir = args[0] else: out_dir = kwargs.pop("path") - context.report["legacy"].setdefault("out_dir", out_dir) + context.report.legacy.setdefault("out_dir", out_dir) - if "legacy" in context.report: + if context.report.legacy["use"]: return _invoke_legacy_reporting(context) - # Default arguments for genno-based reporting - context.report.setdefault("key", "default") - context.report.setdefault("config", package_data_path("report", "global.yaml")) - rep, key = prepare_reporter(context) log_before(context, rep, key) @@ -271,7 +265,7 @@ def discard_on_error(*args): # Display information about the result log.info(f"Result:\n\n{result}\n") log.info( - f"File output(s), if any, written under:\n{rep.graph['config']['output_path']}" + f"File output(s), if any, written under:\n{rep.graph['config']['output_dir']}" ) @@ -280,20 +274,14 @@ def _invoke_legacy_reporting(context): from message_data.tools.post_processing import iamc_report_hackathon # Convert "legacy" config to keyword arguments for .iamc_report_hackathon.report() - args = context.report.setdefault("legacy", dict()) - if not isinstance(args, dict): - raise TypeError( - f'Cannot handle Context["report"]["legacy"]={args!r} of type {type(args)}' - ) + kwargs = deepcopy(context.report.legacy) + kwargs.pop("use") - # Read a configuration file and update the arguments - config = context.report.get("config") - if isinstance(config, Path) and config.exists(): - with open(config, "r") as f: - args.update(yaml.safe_load(f)) - - # Default settings - args.setdefault("merge_hist", True) + # Read a legacy reporting configuration file and update the arguments + config_file_path = kwargs.pop("config_file_path", None) + if isinstance(config_file_path, Path) and config_file_path.exists(): + with open(config_file_path, "r") as f: + kwargs.update(yaml.safe_load(f)) # Retrieve the Scenario and Platform scen = context.get_scenario() @@ -301,8 +289,9 @@ def _invoke_legacy_reporting(context): mark_time() - # `context` is passed only for the "dry_run" setting - return iamc_report_hackathon.report(mp=mp, scen=scen, context=context, **args) + # `context` is passed only for the "dry_run" setting; the function receives all its + # other settings via the `kwargs` + return iamc_report_hackathon.report(mp=mp, scen=scen, context=context, **kwargs) def prepare_reporter( @@ -377,6 +366,7 @@ def prepare_reporter( has_solution = True if scenario: log.warning(f"{scenario = } argument ignored") + scenario = rep.graph["scenario"] else: # Retrieve the scenario scenario = scenario or context.get_scenario() @@ -398,65 +388,44 @@ def prepare_reporter( # TODO Remove, once message_data.reporting is removed. genno.config.handles("iamc")(iamc) - # Handle `report/config` setting passed from calling code - context.setdefault("report", dict()) - context.report.setdefault("config", dict()) - if isinstance(context.report["config"], dict): - # Dictionary of existing settings; deepcopy to protect from destructive - # operations - config = deepcopy(context.report["config"]) - else: - # Otherwise, must be Path-like - config = dict(path=Path(context.report["config"])) - - # Check location of the reporting config file - p = config.get("path") - if p and not p.exists() and not p.is_absolute(): - # Try to resolve relative to the data/ directory - p = package_data_path("report", p) - assert p.exists(), p - config.update(path=p) - - # Set defaults - # Directory for reporting output - default_output_dir = local_data_path("report") - config.setdefault( - "output_path", context.report.get("output_path", default_output_dir) + if context.report.use_scenario_path: + # Construct ScenarioInfo + si = ScenarioInfo(scenario, empty=True) + # Use the scenario URL to extend the path + context.report.set_output_dir(context.report.output_dir.joinpath(si.path)) + + # Pass values to genno's configuration; deepcopy to protect from destructive + # operations + rep.configure( + **deepcopy(context.report.genno_config), + fail="raise" if has_solution else logging.NOTSET, ) - # For genno.compat.plot - # FIXME use a consistent set of names - config.setdefault("output_dir", default_output_dir) - - for k in ("output_dir", "output_path"): - config[k] = config[k].expanduser() - config[k].mkdir(exist_ok=True, parents=True) - - # Pass configuration to the reporter - rep.configure(**config, fail="raise" if has_solution else logging.NOTSET) # Apply callbacks for other modules which define additional reporting computations for callback in CALLBACKS: callback(rep, context) - key = context.report.setdefault("key", None) + key = context.report.key if key: # If just a bare name like "ACT" is given, infer the full key if Key.bare_name(key): - msg = f"for {key!r}" inferred = rep.infer_keys(key) if inferred != key: - log.info(f"Infer {key!r} {msg}") + log.info(f"Infer {inferred!r} for {key!r}") key = inferred - if config["output_path"] and not config["output_path"].is_dir(): - # Add a new computation that writes *key* to the specified file + if context.report.cli_output: + # Add a new task that writes `key` to the specified file key = rep.add( - "cli-output", "write_report", key, literal(config["output_path"]) + "cli-output", "write_report", key, path=context.report.cli_output ) else: key = rep.default_key log.info(f"No key given; will use default: {key!r}") + # Create the output directory + context.report.mkdir() + log.info("…done") return rep, key diff --git a/message_ix_models/report/cli.py b/message_ix_models/report/cli.py index bd6d72f673..ae63fda9f9 100644 --- a/message_ix_models/report/cli.py +++ b/message_ix_models/report/cli.py @@ -1,19 +1,24 @@ import logging -from copy import copy from pathlib import Path import click -import yaml -from message_ix_models.report import register, report -from message_ix_models.util._logging import mark_time from message_ix_models.util.click import common_params log = logging.getLogger(__name__) +def _modules_arg(context, param, value): + """--module/-m: load extra reporting config from modules.""" + from . import register + + for m in filter(len, value.split(",")): + name = register(m) + log.info(f"Registered reporting from {name}") + + @click.command(name="report") -@common_params("dry_run") +@common_params("dry_run urls_from_file") @click.option( "--config", "config_file", @@ -21,25 +26,27 @@ show_default=True, help="Path or stem for reporting config file.", ) -@click.option("--legacy", "-L", is_flag=True, help="Invoke legacy reporting.") +@click.option("--legacy", "-L", is_flag=True, help="Invoke 'legacy' reporting.") @click.option( - "--module", "-m", metavar="MODULES", help="Add extra reporting for MODULES." + "--module", + "-m", + "modules", + metavar="MODULES", + default="", + help="Add extra reporting for MODULES.", + callback=_modules_arg, ) @click.option( - "-o", "--output", - "output_path", - type=Path, - help="Write output to file instead of console.", -) -@click.option( - "--from-file", - type=click.Path(exists=True, dir_okay=False), - help="Report multiple Scenarios listed in FILE.", + "-o", + "cli_output", + metavar="PATH", + type=click.Path(writable=True, resolve_path=True, path_type=Path), + help="Write output to PATH instead of console or default locations.", ) @click.argument("key", default="message::default") @click.pass_obj -def cli(context, config_file, legacy, module, output_path, from_file, key, dry_run): +def cli(context, config_file, legacy, cli_output, key, **kwargs): """Postprocess results. KEY defaults to the comprehensive report 'message::default', but may also be the @@ -48,75 +55,42 @@ def cli(context, config_file, legacy, module, output_path, from_file, key, dry_r --config can give either the absolute path to a reporting configuration file, or the stem (i.e. name without .yaml extension) of a file in data/report. - With --from-file, read multiple Scenario identifiers from FILE, and report each one. - In this usage, --output-path may only be a directory. + With --urls-from-file, read multiple Scenario identifiers from FILE, and report each + one. In this usage, --output-path may only be a directory. """ - from message_ix_models.util import local_data_path, package_data_path + from copy import deepcopy - # --config: use the option value as if it were an absolute path - config = Path(config_file) - if not config.exists(): - # Path doesn't exist; treat it as a stem in the metadata dir - config = package_data_path("report", config_file).with_suffix(".yaml") + from message_ix_models.util._logging import mark_time - if not config.exists(): - # Can't find the file - raise FileNotFoundError(f"Reporting configuration --config={config}") + from . import report + from .config import Config - # --output/-o: handle "~" - output_path = output_path.expanduser() if output_path else None + # Update the reporting configuration from command-line parameters + context.report = Config( + from_file=config_file, key=key, cli_output=cli_output, _legacy=legacy + ) - # --module/-m: load extra reporting config from modules - module = module or "" - for m in filter(len, module.split(",")): - name = register(m) - log.info(f"Registered reporting from {name}") - - # Common settings to apply in all contexts - common = dict(config=config, key=key) - - # --legacy/-L: cue report() to invoke the legacy, instead of genno-based reporting - if legacy: - common["legacy"] = dict() - - # Prepare a list of Context objects, each referring to one Scenario + # Prepare a list of Context objects, each referring to one Scenario. contexts = [] - if from_file: - # Multiple URLs - if not output_path.is_dir(): - raise click.BadOptionUsage( - "--output-path must be directory with --from-file" + # - If --urls-from-file was given, then `context.scenarios` will contain a list of + # ScenarioInfo objects that point to the platform and (model, scenario, version) + # identifiers. + # - Otherwise, the user gave identifiers for a single Scenario to the top-level CLI + # (--url/--platform/--model/--scenario/--version) and these are stored in + # `context.platform_info` and `context.scenario_info`. + for si in context.scenarios or [context.scenario_info]: + ctx = deepcopy(context) + ctx.platform_info = dict( + name=getattr( + si, "platform_name", context.platform_info.get("name", "default") ) - - for item in yaml.safe_load(open(from_file)): - # Copy the existing Context to a new object - ctx = copy(context) - - # Update with Scenario info from file - ctx.handle_cli_args(**item) - - # Construct an output path from the parsed info/URL - ctx.output_path = Path( - output_path, - "_".join( - [ - ctx.platform_info["name"], - ctx.scenario_info["model"], - ctx.scenario_info["scenario"], - ] - ), - ).with_suffix(".xlsx") - - contexts.append(ctx) - else: - # Single Scenario; identifiers were supplied to the top-level CLI - context.output_path = output_path - context["report"] = dict(output_dir=local_data_path("report")) - contexts.append(context) + ) + ctx.scenario_info = dict(si) + contexts.append(ctx) for ctx in contexts: - # Update with common settings - context["report"].update(common) - report(ctx) mark_time() + report(ctx) + + mark_time() From 8251ec57d2b73becbc7f05be79add62cacf5fca8 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 21:04:34 +0200 Subject: [PATCH 30/56] Update report docstrings --- message_ix_models/report/__init__.py | 79 +++++++--------------------- 1 file changed, 20 insertions(+), 59 deletions(-) diff --git a/message_ix_models/report/__init__.py b/message_ix_models/report/__init__.py index 6095313c18..4d5bd02bde 100644 --- a/message_ix_models/report/__init__.py +++ b/message_ix_models/report/__init__.py @@ -181,37 +181,23 @@ def log_before(context, rep, key) -> None: def report(context: Context, *args, **kwargs): - """Run complete reporting on a :class:`.message_ix.Scenario`. + """Report (post-process) solution data in a |Scenario| and store time series data. - This function provides a single, common interface to call both the 'new' - (:mod:`.reporting`) and 'legacy' (:mod:`.tools.post_processing`) reporting codes. + This function provides a single, common interface to call both the :mod:`genno` + -based (:mod:`message_ix_models.report`) and ‘legacy’ ( + :mod:`message_data.tools.post_processing`) reporting codes. - The code responds to the following settings on `context`: - - .. list-table:: - :width: 100% - :widths: 25 25 50 - :header-rows: 1 - - * - Setting - - Type - - Description - * - scenario_info - - - - Identifies the (solved) scenario to be reported. - * - report/dry_run - - bool - - Only show what would be done. Default: :data:`False`. - * - report/legacy - - dict or None - - If given, the old-style reporting in :mod:`.iamc_report_hackathon` is used, - with `legacy` as keyword arguments. - - As well: + Parameters + ---------- + context : Context + The code responds to: - - ``report/key`` is set to ``default``, if not set. - - ``report/config`` is set to :file:`report/globa.yaml`, if not set. + - :attr:`.dry_run`: if :obj:`True`, reporting is prepared but nothing is done. + - :attr:`.scenario_info` and :attr:`.platform_info`: used to retrieve the + Scenario to be reported. + - :py:`context.report`, which is an instance of :class:`.report.Config`; see + there for available configuration settings. """ try: from ixmp.utils import discard_on_error @@ -301,39 +287,11 @@ def prepare_reporter( ) -> Tuple[Reporter, Key]: """Return a :class:`message_ix.Reporter` and `key` prepared to report a |Scenario|. - The code responds to the following settings on `context`: - - .. list-table:: - :width: 100% - :widths: 25 25 50 - :header-rows: 1 - - * - Setting - - Type - - Description - * - scenario_info - - - - Identifies the (solved) scenario to be reported. - * - report/key - - str or :class:`ixmp.reporting.Key` - - Quantity or node to compute. The computation is not triggered (i.e. - :meth:`get ` is not called); but the - corresponding, full-resolution Key, if any, is returned. - * - report/config - - dict or Path-like or None - - If :class:`dict`, then this is passed to - :meth:`message_ix.Reporter.configure`. If Path-like, then this is the path to - the reporting configuration file. If not given, defaults to - :file:`report/global.yaml`. - * - report/output_path - - Path-like, *optional* - - Path to write reporting outputs. If given, a computation ``cli-output`` is - added to the Reporter which writes ``report/key`` to this path. - Parameters ---------- context : Context - Containing settings in the ``report/*`` tree. + The code responds to :py:`context.report`, which is an instance of + :class:`.report.Config`. scenario : Scenario, *optional* Scenario to report. If not given, :meth:`.Context.get_scenario` is used to retrieve a Scenario. @@ -346,9 +304,12 @@ def prepare_reporter( .Reporter Reporter prepared with MESSAGEix-GLOBIOM calculations; if `reporter` is given, this is a reference to the same object. + + If :attr:`.cli_output` is given, a task with the key "cli-output" is added that + writes the :attr:`.Config.key` to that path. .Key - Same as ``context.report["key"]`` if any, but in full resolution; else one of - ``default`` or ``cli-output`` according to the other settings. + Same as :attr:`.Config.key` if any, but in full resolution; else either + "default" or "cli-output" according to the other settings. """ from importlib.metadata import version From af7f3521e3dee570a30f476edef015911a571735 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 21:04:50 +0200 Subject: [PATCH 31/56] Update report tests --- message_ix_models/tests/test_report.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/message_ix_models/tests/test_report.py b/message_ix_models/tests/test_report.py index c31d715067..0fc780c1d5 100644 --- a/message_ix_models/tests/test_report.py +++ b/message_ix_models/tests/test_report.py @@ -30,7 +30,7 @@ def test_report_bare_res(request, test_context): scenario = testing.bare_res(request, test_context, solved=True) # Prepare the reporter - test_context.report.update(config="global.yaml", key="message::default") + test_context.report.update(from_file="global.yaml", key="message::default") reporter, key = prepare_reporter(test_context, scenario) # Get the default report @@ -48,13 +48,13 @@ def test_report_legacy(caplog, request, tmp_path, test_context): # Set dry_run = True to not actually perform any calculations or modifications test_context.dry_run = True # Ensure the legacy reporting is used, with default settings - test_context.report = {"legacy": dict()} + test_context.report.legacy["use"] = True # Call succeeds report(test_context) # Dry-run message is logged - assert "DRY RUN" in caplog.messages + assert "DRY RUN" in caplog.messages[-1] caplog.clear() # Other deprecated usage @@ -62,6 +62,7 @@ def test_report_legacy(caplog, request, tmp_path, test_context): # As called in .model.cli.new_baseline() and .model.create.solve(), with path as a # positional argument legacy_arg = dict( + use=True, ref_sol="True", # Must be literal "True" or "False" merge_hist=True, xlsx=test_context.get_local_path("rep_template.xlsx"), @@ -115,7 +116,7 @@ def test_apply_units(request, test_context, regions): config = MIN_CONFIG.copy() # Prepare the reporter - test_context.report.update(config=config, key=qty) + test_context.report.update(genno_config=config, key=qty) reporter, key = prepare_reporter(test_context, bare_res) # Add some data to the scenario @@ -140,14 +141,14 @@ def test_apply_units(request, test_context, regions): assert "dimensionless" == str(reporter.get(key).units) # Update configuration, re-create the reporter - test_context.report["config"]["units"]["apply"] = {"inv_cost": "USD"} + test_context.report.genno_config["units"]["apply"] = {"inv_cost": "USD"} reporter, key = prepare_reporter(test_context, bare_res) # Units are applied assert USD_2005 == reporter.get(key).units # Update configuration, re-create the reporter - test_context.report["config"].update(INV_COST_CONFIG) + test_context.report.genno_config.update(INV_COST_CONFIG) reporter, key = prepare_reporter(test_context, bare_res) # Units are converted From 76b77909ba96aa21950a3bed3a6f990cb3802232 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 21:13:26 +0200 Subject: [PATCH 32/56] Update test of Context.__repr__ --- message_ix_models/tests/util/test_context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/message_ix_models/tests/util/test_context.py b/message_ix_models/tests/util/test_context.py index 5921cb4634..2da18bb4ee 100644 --- a/message_ix_models/tests/util/test_context.py +++ b/message_ix_models/tests/util/test_context.py @@ -237,9 +237,9 @@ def test_handle_cli_args(self): ctx.delete() - def test_repr(self): + def test_repr(self) -> None: c = Context() - assert re.fullmatch("", repr(c)) + assert re.fullmatch("", repr(c)) def test_use_defaults(self, caplog): caplog.set_level(logging.INFO) From a8b923cf03a0e36eed6c9f16d8998c85230934f4 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Sep 2023 21:14:11 +0200 Subject: [PATCH 33/56] Handle no platform info in ScenarioInfo.url --- message_ix_models/util/scenarioinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/message_ix_models/util/scenarioinfo.py b/message_ix_models/util/scenarioinfo.py index a68646faa4..80ddc3f801 100644 --- a/message_ix_models/util/scenarioinfo.py +++ b/message_ix_models/util/scenarioinfo.py @@ -158,7 +158,7 @@ def url(self) -> str: @url.setter def url(self, value): p, s = ixmp.utils.parse_url(value) - self.platform_name = p["name"] + self.platform_name = p.get("name") for k in "model", "scenario", "version": setattr(self, k, s.get(k)) From a0e2c0fc0f7fe2238bf017b971720d557bd11506 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 11 Oct 2023 16:50:36 +0200 Subject: [PATCH 34/56] Add equation and variable data from snapshot 0 for testing Files are generated from the output GDX file using: gdxdump MsgOutput_m_s.gdx Symbols | grep -E "Equ|Var" | \ cut -c5- | cut -d" " -f1 | \ xargs -I{} gdxdump MsgOutput_m_s.gdx CSVAllFields Symb={} \ Format=csv Output={}.csv gzip *.csv --- .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT.csv.gz | 3 +++ .../ACTIVITY_BOUND_ALL_MODES_LO.csv.gz | 3 +++ .../ACTIVITY_BOUND_ALL_MODES_UP.csv.gz | 3 +++ .../ACTIVITY_BOUND_LO.csv.gz | 3 +++ .../ACTIVITY_BOUND_UP.csv.gz | 3 +++ .../ACTIVITY_BY_RATING.csv.gz | 3 +++ .../ACTIVITY_CONSTRAINT_LO.csv.gz | 3 +++ .../ACTIVITY_CONSTRAINT_UP.csv.gz | 3 +++ .../ACTIVITY_RATING_TOTAL.csv.gz | 3 +++ .../ACTIVITY_SOFT_CONSTRAINT_LO.csv.gz | 3 +++ .../ACTIVITY_SOFT_CONSTRAINT_UP.csv.gz | 3 +++ .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_LO.csv.gz | 3 +++ .../ACT_RATING.csv.gz | 3 +++ .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_UP.csv.gz | 3 +++ .../ADDON_ACTIVITY_LO.csv.gz | 3 +++ .../ADDON_ACTIVITY_UP.csv.gz | 3 +++ .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP.csv.gz | 3 +++ .../CAPACITY_CONSTRAINT.csv.gz | 3 +++ .../CAPACITY_MAINTENANCE.csv.gz | 3 +++ .../CAPACITY_MAINTENANCE_HIST.csv.gz | 3 +++ .../CAPACITY_MAINTENANCE_NEW.csv.gz | 3 +++ .../CAP_FIRM.csv.gz | 3 +++ .../CAP_NEW.csv.gz | 3 +++ .../CAP_NEW_LO.csv.gz | 3 +++ .../CAP_NEW_UP.csv.gz | 3 +++ .../COMMODITY_BALANCE_GT.csv.gz | 3 +++ .../COMMODITY_BALANCE_LT.csv.gz | 3 +++ .../COMMODITY_USE.csv.gz | 3 +++ .../COMMODITY_USE_LEVEL.csv.gz | 3 +++ .../COST_ACCOUNTING_NODAL.csv.gz | 3 +++ .../COST_NODAL.csv.gz | 3 +++ .../COST_NODAL_NET.csv.gz | 3 +++ .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DEMAND.csv.gz | 3 +++ .../DYNAMIC_LAND_SCEN_CONSTRAINT_LO.csv.gz | 3 +++ .../DYNAMIC_LAND_SCEN_CONSTRAINT_UP.csv.gz | 3 +++ .../DYNAMIC_LAND_TYPE_CONSTRAINT_LO.csv.gz | 3 +++ .../DYNAMIC_LAND_TYPE_CONSTRAINT_UP.csv.gz | 3 +++ .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISS.csv.gz | 3 +++ .../EMISSION_CONSTRAINT.csv.gz | 3 +++ .../EMISSION_EQUIVALENCE.csv.gz | 3 +++ .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXT.csv.gz | 3 +++ .../EXTRACTION_BOUND_UP.csv.gz | 3 +++ .../EXTRACTION_EQUIVALENCE.csv.gz | 3 +++ .../FIRM_CAPACITY_PROVISION.csv.gz | 3 +++ .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/GDP.csv.gz | 3 +++ .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/LAND.csv.gz | 3 +++ .../LAND_CONSTRAINT.csv.gz | 3 +++ .../MIN_UTILIZATION_CONSTRAINT.csv.gz | 3 +++ .../NEW_CAPACITY_BOUND_LO.csv.gz | 3 +++ .../NEW_CAPACITY_BOUND_UP.csv.gz | 3 +++ .../NEW_CAPACITY_CONSTRAINT_LO.csv.gz | 3 +++ .../NEW_CAPACITY_CONSTRAINT_UP.csv.gz | 3 +++ .../NEW_CAPACITY_SOFT_CONSTRAINT_LO.csv.gz | 3 +++ .../NEW_CAPACITY_SOFT_CONSTRAINT_UP.csv.gz | 3 +++ .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OBJ.csv.gz | 3 +++ .../OBJECTIVE.csv.gz | 3 +++ .../OPERATION_CONSTRAINT.csv.gz | 3 +++ .../PRICE_COMMODITY.csv.gz | 3 +++ .../PRICE_EMISSION.csv.gz | 3 +++ .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/REL.csv.gz | 3 +++ .../RELATION_CONSTRAINT_LO.csv.gz | 3 +++ .../RELATION_CONSTRAINT_UP.csv.gz | 3 +++ .../RELATION_EQUIVALENCE.csv.gz | 3 +++ .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/REN.csv.gz | 3 +++ .../RENEWABLES_CAPACITY_REQUIREMENT.csv.gz | 3 +++ .../RENEWABLES_EQUIVALENCE.csv.gz | 3 +++ .../RENEWABLES_POTENTIAL_CONSTRAINT.csv.gz | 3 +++ .../RESOURCE_CONSTRAINT.csv.gz | 3 +++ .../RESOURCE_HORIZON.csv.gz | 3 +++ .../SHARE_CONSTRAINT_COMMODITY_LO.csv.gz | 3 +++ .../SHARE_CONSTRAINT_COMMODITY_UP.csv.gz | 3 +++ .../SHARE_CONSTRAINT_MODE_LO.csv.gz | 3 +++ .../SHARE_CONSTRAINT_MODE_UP.csv.gz | 3 +++ .../SLACK_ACT_BOUND_LO.csv.gz | 3 +++ .../SLACK_ACT_BOUND_UP.csv.gz | 3 +++ .../SLACK_ACT_DYNAMIC_LO.csv.gz | 3 +++ .../SLACK_ACT_DYNAMIC_UP.csv.gz | 3 +++ .../SLACK_CAP_NEW_BOUND_LO.csv.gz | 3 +++ .../SLACK_CAP_NEW_BOUND_UP.csv.gz | 3 +++ .../SLACK_CAP_NEW_DYNAMIC_LO.csv.gz | 3 +++ .../SLACK_CAP_NEW_DYNAMIC_UP.csv.gz | 3 +++ .../SLACK_CAP_TOTAL_BOUND_LO.csv.gz | 3 +++ .../SLACK_CAP_TOTAL_BOUND_UP.csv.gz | 3 +++ .../SLACK_COMMODITY_EQUIVALENCE_LO.csv.gz | 3 +++ .../SLACK_COMMODITY_EQUIVALENCE_UP.csv.gz | 3 +++ .../SLACK_LAND_SCEN_LO.csv.gz | 3 +++ .../SLACK_LAND_SCEN_UP.csv.gz | 3 +++ .../SLACK_LAND_TYPE_LO.csv.gz | 3 +++ .../SLACK_LAND_TYPE_UP.csv.gz | 3 +++ .../SLACK_RELATION_BOUND_LO.csv.gz | 3 +++ .../SLACK_RELATION_BOUND_UP.csv.gz | 3 +++ .../MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCK.csv.gz | 3 +++ .../STOCKS_BALANCE.csv.gz | 3 +++ .../STOCK_CHG.csv.gz | 3 +++ .../STORAGE.csv.gz | 3 +++ .../STORAGE_BALANCE.csv.gz | 3 +++ .../STORAGE_BALANCE_INIT.csv.gz | 3 +++ .../STORAGE_CHANGE.csv.gz | 3 +++ .../STORAGE_CHARGE.csv.gz | 3 +++ .../STORAGE_INPUT.csv.gz | 3 +++ .../SYSTEM_FLEXIBILITY_CONSTRAINT.csv.gz | 3 +++ .../SYSTEM_RELIABILITY_CONSTRAINT.csv.gz | 3 +++ .../TOTAL_CAPACITY_BOUND_LO.csv.gz | 3 +++ .../TOTAL_CAPACITY_BOUND_UP.csv.gz | 3 +++ 104 files changed, 312 insertions(+) create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_ALL_MODES_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_ALL_MODES_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BY_RATING.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_CONSTRAINT_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_CONSTRAINT_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_RATING_TOTAL.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_SOFT_CONSTRAINT_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_SOFT_CONSTRAINT_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_RATING.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ADDON_ACTIVITY_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ADDON_ACTIVITY_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_CONSTRAINT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_MAINTENANCE.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_MAINTENANCE_HIST.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_MAINTENANCE_NEW.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_FIRM.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_NEW.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_NEW_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_NEW_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_BALANCE_GT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_BALANCE_LT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_USE.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_USE_LEVEL.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COST_ACCOUNTING_NODAL.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COST_NODAL.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COST_NODAL_NET.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DEMAND.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_SCEN_CONSTRAINT_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_SCEN_CONSTRAINT_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_TYPE_CONSTRAINT_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_TYPE_CONSTRAINT_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISS.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISSION_CONSTRAINT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISSION_EQUIVALENCE.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXTRACTION_BOUND_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXTRACTION_EQUIVALENCE.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/FIRM_CAPACITY_PROVISION.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/GDP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/LAND.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/LAND_CONSTRAINT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/MIN_UTILIZATION_CONSTRAINT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_BOUND_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_BOUND_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_CONSTRAINT_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_CONSTRAINT_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_SOFT_CONSTRAINT_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_SOFT_CONSTRAINT_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OBJ.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OBJECTIVE.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OPERATION_CONSTRAINT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/PRICE_COMMODITY.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/PRICE_EMISSION.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/REL.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RELATION_CONSTRAINT_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RELATION_CONSTRAINT_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RELATION_EQUIVALENCE.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/REN.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RENEWABLES_CAPACITY_REQUIREMENT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RENEWABLES_EQUIVALENCE.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RENEWABLES_POTENTIAL_CONSTRAINT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RESOURCE_CONSTRAINT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RESOURCE_HORIZON.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_COMMODITY_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_COMMODITY_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_MODE_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_MODE_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_BOUND_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_BOUND_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_DYNAMIC_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_DYNAMIC_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_BOUND_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_BOUND_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_DYNAMIC_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_DYNAMIC_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_TOTAL_BOUND_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_TOTAL_BOUND_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_COMMODITY_EQUIVALENCE_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_COMMODITY_EQUIVALENCE_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_SCEN_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_SCEN_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_TYPE_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_TYPE_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_RELATION_BOUND_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_RELATION_BOUND_UP.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCK.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCKS_BALANCE.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCK_CHG.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_BALANCE.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_BALANCE_INIT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_CHANGE.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_CHARGE.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_INPUT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SYSTEM_FLEXIBILITY_CONSTRAINT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SYSTEM_RELIABILITY_CONSTRAINT.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/TOTAL_CAPACITY_BOUND_LO.csv.gz create mode 100644 message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/TOTAL_CAPACITY_BOUND_UP.csv.gz diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT.csv.gz new file mode 100644 index 0000000000..d888bbc4a7 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e433c07354b799c820ece98b7138085caf2b8e3d6d8a86539c3da63f89698f4e +size 1119595 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_ALL_MODES_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_ALL_MODES_LO.csv.gz new file mode 100644 index 0000000000..b1858314c0 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_ALL_MODES_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5aa1010c0dddba54aa1d0a801e89c3192d1f4360bdaf578ec45626eb7fdc316 +size 100 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_ALL_MODES_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_ALL_MODES_UP.csv.gz new file mode 100644 index 0000000000..d0c5b68cb8 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_ALL_MODES_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac8693e1c3e9b50551a03d646daf2bdeacc9a38a138a27a11371b0bf01fb6043 +size 100 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_LO.csv.gz new file mode 100644 index 0000000000..62f6d975cf --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edd096c38db7e0a39e26f5a6af481d66c60ff38007a4e8ed73da7d2f4538749c +size 348853 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_UP.csv.gz new file mode 100644 index 0000000000..8f849293d6 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BOUND_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75de3ceae9062278c128cb750542dba94c24b6c0d969b0ce06a3cdafd717df96 +size 83105 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BY_RATING.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BY_RATING.csv.gz new file mode 100644 index 0000000000..fd91267953 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_BY_RATING.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:029b808065ecf6e10c16429b99096c331817617442245354d18e4000626fd143 +size 99 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_CONSTRAINT_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_CONSTRAINT_LO.csv.gz new file mode 100644 index 0000000000..43768bdce8 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_CONSTRAINT_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4a5afeb2f41aaa663d98e5104bf8240b5418929d0cfbbdd5ec4c88c9b3b3974 +size 143041 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_CONSTRAINT_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_CONSTRAINT_UP.csv.gz new file mode 100644 index 0000000000..9dd1392375 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_CONSTRAINT_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df73e159a53f1b727a596c2f2bb7444366adaee489981b4e71274b6e9b53d5fe +size 179283 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_RATING_TOTAL.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_RATING_TOTAL.csv.gz new file mode 100644 index 0000000000..378aa96283 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_RATING_TOTAL.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b85f2517fc01c92f57d2ceed3ff733cb9e807e4b42f515354a621a1bbaf1300 +size 102 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_SOFT_CONSTRAINT_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_SOFT_CONSTRAINT_LO.csv.gz new file mode 100644 index 0000000000..f82bca2578 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_SOFT_CONSTRAINT_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bf71ddbc84d5a71d79b22dfe02f661aef9a3a5b8cf8cf5badea4f13a81d1741 +size 179445 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_SOFT_CONSTRAINT_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_SOFT_CONSTRAINT_UP.csv.gz new file mode 100644 index 0000000000..240b751925 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACTIVITY_SOFT_CONSTRAINT_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6515181be154c86c6157cdf6085ec935c88d0f6836768731e6a78c4858781062 +size 193834 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_LO.csv.gz new file mode 100644 index 0000000000..0ed720e459 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c98deb30aa75efeebe922b4550a924a104b5cde6259bb255fa68f4e5cb6debbd +size 316292 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_RATING.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_RATING.csv.gz new file mode 100644 index 0000000000..37b8c081ed --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_RATING.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a9b8701343252903b66220e11c6c868d3c6b7d1e698dd8bfcb4c1d765454bb8 +size 119 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_UP.csv.gz new file mode 100644 index 0000000000..cb296da422 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ACT_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21dc4e66daef3882bb41998528ac734a8e75403789a6dae1367f79c452f36f2c +size 320194 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ADDON_ACTIVITY_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ADDON_ACTIVITY_LO.csv.gz new file mode 100644 index 0000000000..64300fe29d --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ADDON_ACTIVITY_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af963fb032e65c2e90283a87d8848dbc00626616d0ca44da1db268c6356c80d0 +size 92 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ADDON_ACTIVITY_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ADDON_ACTIVITY_UP.csv.gz new file mode 100644 index 0000000000..61efc1a270 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/ADDON_ACTIVITY_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab312790ecfecebb21e5944eb7c45bb747e134102aae4c4b80ef60b5e356ebf7 +size 92 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP.csv.gz new file mode 100644 index 0000000000..18f83acf8b --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27bf53613717d2780f6343bfb6a0db0ef551d679ec0feb94ab10e24b1599f084 +size 778593 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_CONSTRAINT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_CONSTRAINT.csv.gz new file mode 100644 index 0000000000..70c4a5ff0d --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_CONSTRAINT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17082ce84c47fcabcaef80939de648590492531c13fcb43914cf9f4dfe6aba7d +size 718481 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_MAINTENANCE.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_MAINTENANCE.csv.gz new file mode 100644 index 0000000000..2213fae448 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_MAINTENANCE.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d86fafb476b64dba76621cb333dc3d37ed1387f5f3dacfa142e1dc700c034772 +size 745591 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_MAINTENANCE_HIST.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_MAINTENANCE_HIST.csv.gz new file mode 100644 index 0000000000..c61fd5ad65 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_MAINTENANCE_HIST.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4d2aa3a84b3875d03fb4701c1c061da755e857c338c17ef314f6e88b1ea05f9 +size 69622 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_MAINTENANCE_NEW.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_MAINTENANCE_NEW.csv.gz new file mode 100644 index 0000000000..0696e06170 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAPACITY_MAINTENANCE_NEW.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1f08163b93db37146c07b26ee12b7a4080552f71c855b8d27f757081df112b9 +size 330366 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_FIRM.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_FIRM.csv.gz new file mode 100644 index 0000000000..038f4f8b0e --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_FIRM.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a12ae231e2073edf413cd900c2dc544275a23f64b4621aac315d798ad9d9157 +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_NEW.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_NEW.csv.gz new file mode 100644 index 0000000000..043eee2693 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_NEW.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43ef04e1f32d5c7dc76a8874114dc309e16f1f89d8a664c5fccecfe7aec977b4 +size 179351 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_NEW_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_NEW_LO.csv.gz new file mode 100644 index 0000000000..792a7a05ce --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_NEW_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:891d39e3f37233bd2200112c9c42af2cb0d150e08fe7c28306c501131a5abc55 +size 87 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_NEW_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_NEW_UP.csv.gz new file mode 100644 index 0000000000..630b0696a6 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/CAP_NEW_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba1b62b75ac4843650fdb7dc7653f4b0e10271e7f5f65b368df1fa6cc9ac4206 +size 7283 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_BALANCE_GT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_BALANCE_GT.csv.gz new file mode 100644 index 0000000000..f0072bc847 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_BALANCE_GT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d35dd8ebfdd544dce144373c3c7a0ab00046f51249ebe41e4ec299afbc2a130f +size 84196 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_BALANCE_LT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_BALANCE_LT.csv.gz new file mode 100644 index 0000000000..52ef670fc1 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_BALANCE_LT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00531dc0f1938e8eb5cdc190f78d24dfc9c8e5adbac24057a993b6927130c7df +size 95 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_USE.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_USE.csv.gz new file mode 100644 index 0000000000..8d8dd9f9a3 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_USE.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0956f96a9ef4bfdd2288e8bc3fe860233fbd1ed0335b978a3ff888f6428cd6b +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_USE_LEVEL.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_USE_LEVEL.csv.gz new file mode 100644 index 0000000000..df94bc8548 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COMMODITY_USE_LEVEL.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8275da1b03c128dfb952453f6860ead12701b9314fecb19bf5765ef2506b037 +size 94 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COST_ACCOUNTING_NODAL.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COST_ACCOUNTING_NODAL.csv.gz new file mode 100644 index 0000000000..4dbf876b65 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COST_ACCOUNTING_NODAL.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:824d2f002b4da42a72d0398cfc7b6bfa9c071b38864c3767beec71419603accc +size 879 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COST_NODAL.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COST_NODAL.csv.gz new file mode 100644 index 0000000000..c9f183ca38 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COST_NODAL.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be0b71e31d74852181a8a71c596d19842eea5959e2eba43948e3f547076815fe +size 2448 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COST_NODAL_NET.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COST_NODAL_NET.csv.gz new file mode 100644 index 0000000000..4faac962a7 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/COST_NODAL_NET.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94a339253fea267a1fdfe8b68c395c5380d4fc2d538236114a733cd223ac0fdd +size 2399 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DEMAND.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DEMAND.csv.gz new file mode 100644 index 0000000000..30e855db44 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DEMAND.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3334223695badec19f0a01d9063e77cbb88fcad70090b97be3a34b90e239790d +size 7351 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_SCEN_CONSTRAINT_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_SCEN_CONSTRAINT_LO.csv.gz new file mode 100644 index 0000000000..bbc26a5bd4 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_SCEN_CONSTRAINT_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51ddb8ce9e5d57ad296d8bf6e4f6a42ec7b4dc14a89915a4234b7602e7ce181e +size 53834 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_SCEN_CONSTRAINT_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_SCEN_CONSTRAINT_UP.csv.gz new file mode 100644 index 0000000000..523485e3d1 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_SCEN_CONSTRAINT_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f66490705a08ff2e1df75fe3aec54116f0cb93c0303bf046316e321aa77a34f7 +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_TYPE_CONSTRAINT_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_TYPE_CONSTRAINT_LO.csv.gz new file mode 100644 index 0000000000..4f9e664ced --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_TYPE_CONSTRAINT_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b472c85d718e06d93b1f352fcc96938cb24d53720242b6c3ba1988f3cbdc3a7f +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_TYPE_CONSTRAINT_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_TYPE_CONSTRAINT_UP.csv.gz new file mode 100644 index 0000000000..42b87a15d4 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/DYNAMIC_LAND_TYPE_CONSTRAINT_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba06172def776c57da5c19f25c7fa3898895f85101fc0389854bf114896dc3b3 +size 3557 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISS.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISS.csv.gz new file mode 100644 index 0000000000..89d247e23f --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISS.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be8910fd63cfb04302468f77d4ad6af9b52a2de7defff2b6b91e75948dfd606f +size 326505 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISSION_CONSTRAINT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISSION_CONSTRAINT.csv.gz new file mode 100644 index 0000000000..b7f8d811a8 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISSION_CONSTRAINT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67ee01dca27fba3b186ee7b0f335bf738698c8898142b07e2272154f08082f54 +size 92 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISSION_EQUIVALENCE.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISSION_EQUIVALENCE.csv.gz new file mode 100644 index 0000000000..c76a7d0511 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EMISSION_EQUIVALENCE.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f69facd22f18d5eaca5a46095f7a3b53cdfbe321383ef00e2a79c7058117942 +size 283712 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXT.csv.gz new file mode 100644 index 0000000000..83e66a8e37 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8365f142aea1114519d2c8bf0979e277b0c4c6856b91bd5d18c77ec45e32a593 +size 31593 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXTRACTION_BOUND_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXTRACTION_BOUND_UP.csv.gz new file mode 100644 index 0000000000..6ea0e019d3 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXTRACTION_BOUND_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7109987a35159af0d85b0371cfc0ffb6c4518022f404dd87a7b246b78217349d +size 1227 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXTRACTION_EQUIVALENCE.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXTRACTION_EQUIVALENCE.csv.gz new file mode 100644 index 0000000000..427d76e005 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/EXTRACTION_EQUIVALENCE.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f94beffa45e00f8f32116bd1432f767f8ccb80337d6a6c054d51f00acfe0cf78 +size 27099 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/FIRM_CAPACITY_PROVISION.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/FIRM_CAPACITY_PROVISION.csv.gz new file mode 100644 index 0000000000..fc472346ad --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/FIRM_CAPACITY_PROVISION.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:825fff5f8d5a69c025503d841ae84d26d45bcface9716a39fc4f699d31ccfc6e +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/GDP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/GDP.csv.gz new file mode 100644 index 0000000000..06327cddd3 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/GDP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08727680d1a6d4c99188b4db1710690cfc9222865fd1c6a081599e8f8868722a +size 75 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/LAND.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/LAND.csv.gz new file mode 100644 index 0000000000..b1ed6afa77 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/LAND.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:880a4572c42cb5d0bdf8e836845286729bf311b31024db7dead2bb13c2d411bb +size 146878 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/LAND_CONSTRAINT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/LAND_CONSTRAINT.csv.gz new file mode 100644 index 0000000000..a8530a2741 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/LAND_CONSTRAINT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c11d522defc939b33b81ad1415b2978dec2dc069a4b1ad509f1552c2d395b3df +size 2121 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/MIN_UTILIZATION_CONSTRAINT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/MIN_UTILIZATION_CONSTRAINT.csv.gz new file mode 100644 index 0000000000..054d70bcbe --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/MIN_UTILIZATION_CONSTRAINT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edcd485cc4c93ad535a891506267c8eebb57483b9cc9463e703b163b9bf30f96 +size 99 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_BOUND_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_BOUND_LO.csv.gz new file mode 100644 index 0000000000..a13f082c2d --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_BOUND_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0296de1b644c4d4a388595e9576cd2cb684d698908ed3fb6a3e9792bdf6af967 +size 1681 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_BOUND_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_BOUND_UP.csv.gz new file mode 100644 index 0000000000..5e23f491a1 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_BOUND_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7283ca771db832f55690a1e946af6a243b1c260599d2169e70a662ba429d337b +size 5486 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_CONSTRAINT_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_CONSTRAINT_LO.csv.gz new file mode 100644 index 0000000000..9075e8748c --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_CONSTRAINT_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af32c839021c0d9ffc89b1c0e3010013ba216fbe506572fd2e0b17cd8a15cc45 +size 380 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_CONSTRAINT_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_CONSTRAINT_UP.csv.gz new file mode 100644 index 0000000000..e34f454563 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_CONSTRAINT_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:836fbfa7a70e52ff68edc3f2ce1a0fabb936c3f35194d92f057cc47c3c21d944 +size 24127 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_SOFT_CONSTRAINT_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_SOFT_CONSTRAINT_LO.csv.gz new file mode 100644 index 0000000000..afff7d163d --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_SOFT_CONSTRAINT_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71f5e00d8086605679733ca14fcc4daff7bd401d8e34abdcda658e58d131cb08 +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_SOFT_CONSTRAINT_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_SOFT_CONSTRAINT_UP.csv.gz new file mode 100644 index 0000000000..a030bef853 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/NEW_CAPACITY_SOFT_CONSTRAINT_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d40a2063c1f0e6e7e58aeba1b259764b576a7697f359402062c66e6db0e5b45c +size 5763 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OBJ.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OBJ.csv.gz new file mode 100644 index 0000000000..3422926279 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OBJ.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0bf9752137ca3809e668a7c163785b1088f2a8e788382aefa09cac3050c7d19 +size 89 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OBJECTIVE.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OBJECTIVE.csv.gz new file mode 100644 index 0000000000..69a6163048 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OBJECTIVE.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a259aa6d9c2e3642f4a1125aea5f7dd82f83395af3c5ff2143443ebba069c6b1 +size 75 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OPERATION_CONSTRAINT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OPERATION_CONSTRAINT.csv.gz new file mode 100644 index 0000000000..6a54f4b87f --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/OPERATION_CONSTRAINT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a054afa0bbbb1cbb445974c83c00160b9f2721d34c03347e62c1492290b3dc24 +size 93 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/PRICE_COMMODITY.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/PRICE_COMMODITY.csv.gz new file mode 100644 index 0000000000..8fadc466c7 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/PRICE_COMMODITY.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f8b4f607990bbd14e3f6a427e1ad00fafffb76ddf7b5fb3a3465d5e98f95963 +size 69902 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/PRICE_EMISSION.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/PRICE_EMISSION.csv.gz new file mode 100644 index 0000000000..26d0b47068 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/PRICE_EMISSION.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:849cba6d7ca497aed4b7ce6edf00f587e573c592253beb1cf6da803e0885ba23 +size 106 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/REL.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/REL.csv.gz new file mode 100644 index 0000000000..43cc41c5ae --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/REL.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd06586d4084feb1f6083bd8764ede1afc9ebeef6cdc5ed129cbfe3d6bd7986e +size 238737 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RELATION_CONSTRAINT_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RELATION_CONSTRAINT_LO.csv.gz new file mode 100644 index 0000000000..d68ea6e144 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RELATION_CONSTRAINT_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a87083f2eb1ce3540ddfe6ebe25c7f8f766c68beb47d22ae29e08de4e32f309 +size 115234 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RELATION_CONSTRAINT_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RELATION_CONSTRAINT_UP.csv.gz new file mode 100644 index 0000000000..3469be552a --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RELATION_CONSTRAINT_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03419d2da28b6cce3b7850eac447e3a081a11634779fcf2f425b777fb9a0042 +size 144407 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RELATION_EQUIVALENCE.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RELATION_EQUIVALENCE.csv.gz new file mode 100644 index 0000000000..7dc0d51db7 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RELATION_EQUIVALENCE.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30383bfef3913fa4a9eb95b6d594d7fc93aff0f85218ec1c438f47c19987ab94 +size 176891 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/REN.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/REN.csv.gz new file mode 100644 index 0000000000..bf24a9a51e --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/REN.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b0fb1ffb135d5438408d8562e54cf07391a8a5c665cba52f68aa15963e833d4 +size 100 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RENEWABLES_CAPACITY_REQUIREMENT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RENEWABLES_CAPACITY_REQUIREMENT.csv.gz new file mode 100644 index 0000000000..2d75ac41cb --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RENEWABLES_CAPACITY_REQUIREMENT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0e25cf252c3cb8091142a13ca71cd8f07c80144322487bb32ebc85e79ec5d23 +size 104 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RENEWABLES_EQUIVALENCE.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RENEWABLES_EQUIVALENCE.csv.gz new file mode 100644 index 0000000000..7826eff10b --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RENEWABLES_EQUIVALENCE.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:470c55fb094afe673f6ab349311834838fbfb09d933c31ce40c8fa63d5951c88 +size 97 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RENEWABLES_POTENTIAL_CONSTRAINT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RENEWABLES_POTENTIAL_CONSTRAINT.csv.gz new file mode 100644 index 0000000000..ae47633389 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RENEWABLES_POTENTIAL_CONSTRAINT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5e934da27a5525e5a371359e81b360749f220f7965f77222abcb39cdb3158b5 +size 104 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RESOURCE_CONSTRAINT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RESOURCE_CONSTRAINT.csv.gz new file mode 100644 index 0000000000..9f974fd83d --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RESOURCE_CONSTRAINT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0543af0b6c6478bcc78d20d185c6ef405751498fce96697464b52c32706d5f23 +size 18136 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RESOURCE_HORIZON.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RESOURCE_HORIZON.csv.gz new file mode 100644 index 0000000000..d9e7011bf8 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/RESOURCE_HORIZON.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa24d6fb316eb2d89ae4720858b41ccfeb64bada23f4b63b9eaa0573c2dad2b9 +size 4428 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_COMMODITY_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_COMMODITY_LO.csv.gz new file mode 100644 index 0000000000..5ac0f10152 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_COMMODITY_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7cc21d796366cf7809c83a65390b9f5d43248fd0d86d3c6c43e5e7bda4122b77 +size 102 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_COMMODITY_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_COMMODITY_UP.csv.gz new file mode 100644 index 0000000000..a907e005c2 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_COMMODITY_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7eca51cf2dc8ff606ca94a98a14e589cc4d3482d486558161307cab524a74f1 +size 102 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_MODE_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_MODE_LO.csv.gz new file mode 100644 index 0000000000..7bdc125bc4 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_MODE_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:615fc7b3fbf093700d0ac7262668e92753dc521e3f41e1999ecfa81127c198c0 +size 102 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_MODE_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_MODE_UP.csv.gz new file mode 100644 index 0000000000..92e2d3635e --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SHARE_CONSTRAINT_MODE_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fc3c3acfeecdb0caf0b54da3d570f8e0181b8fef847fe5634823561b541a29c +size 102 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_BOUND_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_BOUND_LO.csv.gz new file mode 100644 index 0000000000..3713151d7d --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_BOUND_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd1aef4b207566c8c86d72651479cd5cb6b163f79ba6e0c42a9dd3f3078bc65b +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_BOUND_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_BOUND_UP.csv.gz new file mode 100644 index 0000000000..288b7ef147 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_BOUND_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb2958a3dd90e4660bca4d7afcc4bee58cef18865ff5688dea89a2d2d2d38a96 +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_DYNAMIC_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_DYNAMIC_LO.csv.gz new file mode 100644 index 0000000000..7ea038637f --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_DYNAMIC_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:218b30b2f8d8572c234974662aa28c6c589b7a468775ad43d6385d6970ba9e18 +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_DYNAMIC_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_DYNAMIC_UP.csv.gz new file mode 100644 index 0000000000..d75c651c38 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_ACT_DYNAMIC_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8ed98c5639b593cd01d65cfd0b52fb77757755391f9d8dae78455b97a7cc99c +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_BOUND_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_BOUND_LO.csv.gz new file mode 100644 index 0000000000..06ebe31365 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_BOUND_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:220b671273ddb13ff930818eb30502f6cf102cfe7e6e4b23d3ec230a0a54401e +size 99 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_BOUND_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_BOUND_UP.csv.gz new file mode 100644 index 0000000000..33d3dc1644 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_BOUND_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27aeba80d88e716cb958f00e090bc1246b189a4fd655d82bad9433e57b0a2d9b +size 99 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_DYNAMIC_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_DYNAMIC_LO.csv.gz new file mode 100644 index 0000000000..dc6ce23c3c --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_DYNAMIC_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09420999b386543c028a616921cd9573c97825551246557c275823b0b59f53cc +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_DYNAMIC_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_DYNAMIC_UP.csv.gz new file mode 100644 index 0000000000..fe9d2eb272 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_NEW_DYNAMIC_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55657401b4fe17b2b01c037a019677b9aa066c9e0b4e8a6af9c927b81398009b +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_TOTAL_BOUND_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_TOTAL_BOUND_LO.csv.gz new file mode 100644 index 0000000000..8c3af142be --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_TOTAL_BOUND_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed09f97168fb57b2bc9a43a3c7436ef3c2f17b9377d0cdfb68b7f7ddad630e45 +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_TOTAL_BOUND_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_TOTAL_BOUND_UP.csv.gz new file mode 100644 index 0000000000..de92da064d --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_CAP_TOTAL_BOUND_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02d264bcc5e00d21af708d7a2b4b5b24a3af1451ea324b41cd2dc841fd1f1625 +size 101 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_COMMODITY_EQUIVALENCE_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_COMMODITY_EQUIVALENCE_LO.csv.gz new file mode 100644 index 0000000000..c964c64f1e --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_COMMODITY_EQUIVALENCE_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:207c23f421c6d1cbb293f4c643f7d790a0f5af311a1a2edbec76aa0dbbfb5382 +size 124 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_COMMODITY_EQUIVALENCE_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_COMMODITY_EQUIVALENCE_UP.csv.gz new file mode 100644 index 0000000000..466b528f25 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_COMMODITY_EQUIVALENCE_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eee76e9e744391ceb4f8d43ecaf79fde17338331cdb22fd5feea7303556392d0 +size 124 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_SCEN_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_SCEN_LO.csv.gz new file mode 100644 index 0000000000..47d7abb6b6 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_SCEN_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d6ab62a9417667f527fe4b01315c75be66c0a18cd29886035ebd7dd23b0bb4b +size 105 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_SCEN_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_SCEN_UP.csv.gz new file mode 100644 index 0000000000..4e82a46460 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_SCEN_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e7125cc74a7f511a180061d2da69e2188ebc236a3be8a7f71db714d528c6cf0 +size 105 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_TYPE_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_TYPE_LO.csv.gz new file mode 100644 index 0000000000..580e46f3c9 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_TYPE_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a93a90461bd8bfab9230192dfc44e142cee2059df3a9be7a5c68d871dcb9f5b +size 100 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_TYPE_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_TYPE_UP.csv.gz new file mode 100644 index 0000000000..2709e78033 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_LAND_TYPE_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47dbd459b84da95cef4d260708ea7a2f6b2a371f6aee73cce4396256f1b74fb0 +size 100 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_RELATION_BOUND_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_RELATION_BOUND_LO.csv.gz new file mode 100644 index 0000000000..777ec98945 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_RELATION_BOUND_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:321a45f905880aab52202062d807ab981c980dc6ba5992a1b32731edd14b5a64 +size 105 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_RELATION_BOUND_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_RELATION_BOUND_UP.csv.gz new file mode 100644 index 0000000000..c10b3cef86 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SLACK_RELATION_BOUND_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10d4d0264169ff676431b3aa7102adef68e6dd6ed5f57dbd151e9c138ff579fe +size 105 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCK.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCK.csv.gz new file mode 100644 index 0000000000..3a7fd52f46 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCK.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aaa482b1c53438ec44c3425b34bac206a4918f9018b4fc8f4f369db20186000f +size 2511 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCKS_BALANCE.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCKS_BALANCE.csv.gz new file mode 100644 index 0000000000..91808833fc --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCKS_BALANCE.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce7b9a8c5f9395d121a1b967c18c5f3242c5995d201d3e9db38896aa0ae62ed2 +size 2185 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCK_CHG.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCK_CHG.csv.gz new file mode 100644 index 0000000000..a4c966da6b --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STOCK_CHG.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe5a0a7a0ddc55dea4c63d6e3c9f600c8cb74e95137cc50e011ce9d7e0ecc07d +size 836 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE.csv.gz new file mode 100644 index 0000000000..ab8527ddf0 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:460b723a702a8392b19a17b82df90965c46f1252e2543c7f9be4490e660a6a65 +size 107 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_BALANCE.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_BALANCE.csv.gz new file mode 100644 index 0000000000..959690754d --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_BALANCE.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df0a23d0094e687e8447b36b1641eae97bebec81c9352775f0b46e2979e3ffa7 +size 98 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_BALANCE_INIT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_BALANCE_INIT.csv.gz new file mode 100644 index 0000000000..85f3c63785 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_BALANCE_INIT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bea5128e4371709310cf3e844b6c76eceb0f873a4569af2af9a1ceb67647d13 +size 103 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_CHANGE.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_CHANGE.csv.gz new file mode 100644 index 0000000000..4774353044 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_CHANGE.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c611fed6c89f09e2386bd65f8d86484389b227ef963a96030b05d5e9058f6470 +size 95 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_CHARGE.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_CHARGE.csv.gz new file mode 100644 index 0000000000..2d8ddf002e --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_CHARGE.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32f2530f3969ccc2b73c8dbfe1f4143d1a83390d960508224d695ad73a4c9648 +size 114 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_INPUT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_INPUT.csv.gz new file mode 100644 index 0000000000..712cc228f3 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/STORAGE_INPUT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1723b15013f140644e2f79606183d570f684abb7a8d5b468ab276df156051953 +size 99 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SYSTEM_FLEXIBILITY_CONSTRAINT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SYSTEM_FLEXIBILITY_CONSTRAINT.csv.gz new file mode 100644 index 0000000000..14299e4c52 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SYSTEM_FLEXIBILITY_CONSTRAINT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69f3eb3aeb2d689231c7ecf213d2ee470cfefd1f510552fd3272f30841439b85 +size 104 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SYSTEM_RELIABILITY_CONSTRAINT.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SYSTEM_RELIABILITY_CONSTRAINT.csv.gz new file mode 100644 index 0000000000..c148af9b2c --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/SYSTEM_RELIABILITY_CONSTRAINT.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9fc24520d97e21232afbf3935c545657313f0fe4a86c98ddf9967dbc77e50b41 +size 104 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/TOTAL_CAPACITY_BOUND_LO.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/TOTAL_CAPACITY_BOUND_LO.csv.gz new file mode 100644 index 0000000000..27fd3f856b --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/TOTAL_CAPACITY_BOUND_LO.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f01dc9d892f36f3abfcb400b9ccc5d049851592421bb2fc881f23dd9374cd0c6 +size 790 diff --git a/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/TOTAL_CAPACITY_BOUND_UP.csv.gz b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/TOTAL_CAPACITY_BOUND_UP.csv.gz new file mode 100644 index 0000000000..872b8f19c0 --- /dev/null +++ b/message_ix_models/data/test/MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline/TOTAL_CAPACITY_BOUND_UP.csv.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09fccc0c30213e4b9a14d22e678d4c795eecbee8cf1fafe15e7d065f06d8decf +size 254 From d40906d483e6182f7d66a83c157b368145e55e07 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 11 Oct 2023 16:53:24 +0200 Subject: [PATCH 35/56] Gitignore output from pytest-profiling --- .gitignore | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index ed451cada3..164685bf4e 100644 --- a/.gitignore +++ b/.gitignore @@ -38,20 +38,21 @@ pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ .benchmarks +.cache .coverage .coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover .hypothesis/ +.nox/ .pytest_cache/ .ruff_cache +.tox/ +*.cover +*.py,cover +coverage.xml +htmlcov/ +nosetests.xml +/prof/ # Translations *.mo From b910d226f2d0bafa28b5968f36358adf658f58e1 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 11 Oct 2023 16:54:35 +0200 Subject: [PATCH 36/56] Test report.sim.add_simulated_solution() --- message_ix_models/tests/test_report.py | 40 +++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/message_ix_models/tests/test_report.py b/message_ix_models/tests/test_report.py index 0fc780c1d5..bfbd022ee2 100644 --- a/message_ix_models/tests/test_report.py +++ b/message_ix_models/tests/test_report.py @@ -1,12 +1,15 @@ """Tests for message_data.reporting.""" from importlib.metadata import version +import numpy as np import pandas as pd import pandas.testing as pdt import pytest -from message_ix_models import testing +from message_ix_models import ScenarioInfo, testing from message_ix_models.report import prepare_reporter, report, util +from message_ix_models.report.sim import add_simulated_solution +from message_ix_models.util import package_data_path # Minimal reporting configuration for testing MIN_CONFIG = { @@ -198,3 +201,38 @@ def test_collapse(input, exp): # collapse() transforms the "variable" column in the expected way pdt.assert_frame_equal(util.collapse(df_in), df_exp) + + +def test_add_simulated_solution(test_context, test_data_path): + from message_ix import Reporter + + rep = Reporter() + + # Simulated solution can be added to an empty Reporter + add_simulated_solution( + rep, + ScenarioInfo(), + path=package_data_path("test", "MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline"), + ) + + # out can be calculated using "output" and "ACT" from files in `path` + result = rep.get("out:*") + + # Has expected dimensions and length + assert tuple("nl t yv ya m nd c l h hd".split()) == result.dims + assert 155461 == len(result) + + # Compare one expected value + value = result.sel( + nl="R11_AFR", + t="biomass_rc", + yv=2020, + ya=2020, + m="M1", + nd="R11_AFR", + c="rc_therm", + l="useful", + h="year", + hd="year", + ) + assert np.isclose(79.76478, value.item()) From b7d4b4678afe344462b905a7c0b66390a5324895 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 11 Oct 2023 17:04:42 +0200 Subject: [PATCH 37/56] Use data from file in add_simulated_solution() --- message_ix_models/report/sim.py | 301 ++++++++++++++++++++++++++------ 1 file changed, 243 insertions(+), 58 deletions(-) diff --git a/message_ix_models/report/sim.py b/message_ix_models/report/sim.py index d77ca88f4a..fc55093632 100644 --- a/message_ix_models/report/sim.py +++ b/message_ix_models/report/sim.py @@ -2,114 +2,287 @@ from collections import ChainMap, defaultdict from collections.abc import Mapping from copy import deepcopy -from typing import Any, Dict, Optional, Tuple +from pathlib import Path +from typing import Any, Dict, Optional, Sequence, Union import pandas as pd from dask.core import quote +from genno import configure from ixmp.reporting import RENAME_DIMS -from message_ix.models import MESSAGE_ITEMS +from message_ix.models import MESSAGE_ITEMS, item from message_ix.reporting import Key, KeyExistsError, Quantity, Reporter from pandas.api.types import is_scalar from message_ix_models import ScenarioInfo -from message_ix_models.util._logging import silence_log +from message_ix_models.util._logging import mark_time, silence_log +log = logging.getLogger(__name__) -# Shorthand for MESSAGE_VARS, below -def item(ix_type, idx_names): - return dict(ix_type=ix_type, idx_names=tuple(idx_names.split())) - - -# Copied from message_ix.models.MESSAGE_ITEMS, where these entries are commented because -# of JDBCBackend limitations. +# Copied and expanded from message_ix.models.MESSAGE_ITEMS, where these entries are +# commented because of JDBCBackend limitations. # TODO read from that location once possible MESSAGE_VARS = { # Activity "ACT": item("var", "nl t yv ya m h"), + # "ACTIVITY_BOUND_ALL_MODES_LO": item("var", ""), + # "ACTIVITY_BOUND_ALL_MODES_UP": item("var", ""), + # "ACTIVITY_BOUND_LO": item("var", ""), + # "ACTIVITY_BOUND_UP": item("var", ""), + # "ACTIVITY_BY_RATING": item("var", ""), + # "ACTIVITY_CONSTRAINT_LO": item("var", ""), + # "ACTIVITY_CONSTRAINT_UP": item("var", ""), + # "ACTIVITY_RATING_TOTAL": item("var", ""), + # "ACTIVITY_SOFT_CONSTRAINT_LO": item("var", ""), + # "ACTIVITY_SOFT_CONSTRAINT_UP": item("var", ""), + # "ACT_LO": item("var", ""), + # "ACT_RATING": item("var", ""), + # "ACT_UP": item("var", ""), + # "ADDON_ACTIVITY_LO": item("var", ""), + # "ADDON_ACTIVITY_UP": item("var", ""), # Maintained capacity "CAP": item("var", "nl t yv ya"), + # "CAPACITY_CONSTRAINT": item("var", ""), + # "CAPACITY_MAINTENANCE": item("var", ""), + # "CAPACITY_MAINTENANCE_HIST": item("var", ""), + # "CAPACITY_MAINTENANCE_NEW": item("var", ""), + # "CAP_FIRM": item("var", ""), # New capacity "CAP_NEW": item("var", "nl t yv"), + # "CAP_NEW_LO": item("var", ""), + # "CAP_NEW_UP": item("var", ""), + # "COMMODITY_BALANCE_GT": item("var", ""), + # "COMMODITY_BALANCE_LT": item("var", ""), + # "COMMODITY_USE": item("var", ""), + # "COMMODITY_USE_LEVEL": item("var", ""), + # "COST_ACCOUNTING_NODAL": item("var", ""), + # "COST_NODAL": item("var", ""), + # "COST_NODAL_NET": item("var", ""), + # "DEMAND": item("var", ""), + # "DYNAMIC_LAND_SCEN_CONSTRAINT_LO": item("var", ""), + # "DYNAMIC_LAND_SCEN_CONSTRAINT_UP": item("var", ""), + # "DYNAMIC_LAND_TYPE_CONSTRAINT_LO": item("var", ""), + # "DYNAMIC_LAND_TYPE_CONSTRAINT_UP": item("var", ""), # Emissions "EMISS": item("var", "n e type_tec y"), + # "EMISSION_CONSTRAINT": item("var", ""), + # "EMISSION_EQUIVALENCE": item("var", ""), # Extraction "EXT": item("var", "n c g y"), + # "EXTRACTION_BOUND_UP": item("var", ""), + # "EXTRACTION_EQUIVALENCE": item("var", ""), + # "FIRM_CAPACITY_PROVISION": item("var", ""), + # "GDP": item("var", ""), # Land scenario share "LAND": item("var", "n land_scenario y"), + # "LAND_CONSTRAINT": item("var", ""), + # "MIN_UTILIZATION_CONSTRAINT": item("var", ""), + # "NEW_CAPACITY_BOUND_LO": item("var", ""), + # "NEW_CAPACITY_BOUND_UP": item("var", ""), + # "NEW_CAPACITY_CONSTRAINT_LO": item("var", ""), + # "NEW_CAPACITY_CONSTRAINT_UP": item("var", ""), + # "NEW_CAPACITY_SOFT_CONSTRAINT_LO": item("var", ""), + # "NEW_CAPACITY_SOFT_CONSTRAINT_UP": item("var", ""), # Objective (scalar) - "OBJ": dict(ix_type="var", idx_names=[]), + "OBJ": dict(ix_type="var", idx_sets=[]), + # "OBJECTIVE": item("var", ""), + # "OPERATION_CONSTRAINT": item("var", ""), # Price of emissions "PRICE_COMMODITY": item("var", "n c l y h"), # Price of emissions "PRICE_EMISSION": item("var", "n e t y"), # Relation (lhs) "REL": item("var", "relation nr yr"), + # "RELATION_CONSTRAINT_LO": item("var", ""), + # "RELATION_CONSTRAINT_UP": item("var", ""), + # "RELATION_EQUIVALENCE": item("var", ""), + # "REN": item("var", ""), + # "RENEWABLES_CAPACITY_REQUIREMENT": item("var", ""), + # "RENEWABLES_EQUIVALENCE": item("var", ""), + # "RENEWABLES_POTENTIAL_CONSTRAINT": item("var", ""), + # "RESOURCE_CONSTRAINT": item("var", ""), + # "RESOURCE_HORIZON": item("var", ""), + # "SHARE_CONSTRAINT_COMMODITY_LO": item("var", ""), + # "SHARE_CONSTRAINT_COMMODITY_UP": item("var", ""), + # "SHARE_CONSTRAINT_MODE_LO": item("var", ""), + # "SHARE_CONSTRAINT_MODE_UP": item("var", ""), + # "SLACK_ACT_BOUND_LO": item("var", ""), + # "SLACK_ACT_BOUND_UP": item("var", ""), + # "SLACK_ACT_DYNAMIC_LO": item("var", ""), + # "SLACK_ACT_DYNAMIC_UP": item("var", ""), + # "SLACK_CAP_NEW_BOUND_LO": item("var", ""), + # "SLACK_CAP_NEW_BOUND_UP": item("var", ""), + # "SLACK_CAP_NEW_DYNAMIC_LO": item("var", ""), + # "SLACK_CAP_NEW_DYNAMIC_UP": item("var", ""), + # "SLACK_CAP_TOTAL_BOUND_LO": item("var", ""), + # "SLACK_CAP_TOTAL_BOUND_UP": item("var", ""), + # "SLACK_COMMODITY_EQUIVALENCE_LO": item("var", ""), + # "SLACK_COMMODITY_EQUIVALENCE_UP": item("var", ""), + # "SLACK_LAND_SCEN_LO": item("var", ""), + # "SLACK_LAND_SCEN_UP": item("var", ""), + # "SLACK_LAND_TYPE_LO": item("var", ""), + # "SLACK_LAND_TYPE_UP": item("var", ""), + # "SLACK_RELATION_BOUND_LO": item("var", ""), + # "SLACK_RELATION_BOUND_UP": item("var", ""), # Stock "STOCK": item("var", "n c l y"), + # "STOCK_CHG": item("var", ""), + # "STOCKS_BALANCE": item("var", ""), + # "STORAGE": item("var", ""), + # "STORAGE_BALANCE": item("var", ""), + # "STORAGE_BALANCE_INIT": item("var", ""), + # "STORAGE_CHANGE": item("var", ""), + # "STORAGE_CHARGE": item("var", ""), + # "STORAGE_INPUT": item("var", ""), + # "SYSTEM_FLEXIBILITY_CONSTRAINT": item("var", ""), + # "SYSTEM_RELIABILITY_CONSTRAINT": item("var", ""), + # "TOTAL_CAPACITY_BOUND_LO": item("var", ""), + # "TOTAL_CAPACITY_BOUND_UP": item("var", ""), } +# Items to included in a simulated solution: MESSAGE sets and parameters; some variables +SIMULATE_ITEMS = deepcopy(MESSAGE_ITEMS) +# Other MESSAGE variables +SIMULATE_ITEMS.update(MESSAGE_VARS) +# MACRO variables +SIMULATE_ITEMS.update( + { + "GDP": item("var", "n y"), + "MERtoPPP": item("var", "n y"), + } +) + +configure( + rename_dims=dict( + node_rel="nr", + year_rel="yr", + ), +) + + +def dims_of(info: dict) -> Dict[str, str]: + """Return a mapping from the full index names to short dimension IDs of `info`.""" + return {d: RENAME_DIMS.get(d, d) for d in info.get("idx_names") or info["idx_sets"]} -def simulate_qty(name: str, item_info: dict, **data_kw: Any) -> Tuple[Key, Quantity]: - """Return simulated data for item `name`.""" - # NB this is code lightly modified from make_df +def simulate_qty( + name: str, info: dict, item_data: Union[dict, pd.DataFrame] +) -> Quantity: + """Return simulated data for item `name`.""" # Dimensions of the resulting quantity - dims = list( - map( - lambda d: RENAME_DIMS.get(d, d), - item_info.get("idx_names", []) or item_info.get("idx_sets", []), - ) - ) + dims = list(dims_of(info).values()) + + if isinstance(item_data, dict): + # NB this is code lightly modified from make_df - # Default values for every column - data: Mapping = ChainMap(data_kw, defaultdict(lambda: None)) + # Default values for every column + data: Mapping = ChainMap(item_data, defaultdict(lambda: None)) - # Arguments for pd.DataFrame constructor - args: Dict[str, Any] = dict(data={}) + # Arguments for pd.DataFrame constructor + args: Dict[str, Any] = dict(data={}) - # Flag if all values in `data` are scalars - all_scalar = True + # Flag if all values in `data` are scalars + all_scalar = True - for column in dims + ["value"]: - # Update flag - all_scalar &= is_scalar(data[column]) - # Store data - args["data"][column] = data[column] + for column in dims + ["value"]: + # Update flag + all_scalar &= is_scalar(data[column]) + # Store data + args["data"][column] = data[column] - if all_scalar: - # All values are scalars, so the constructor requires an index to be passed - # explicitly. - args["index"] = [0] + if all_scalar: + # All values are scalars, so the constructor requires an index to be passed + # explicitly. + args["index"] = [0] + + df = pd.DataFrame(**args) + else: + # Provided complete data frame + df = item_data.rename(columns=RENAME_DIMS) - df = pd.DataFrame(**args) # Data must be entirely empty, or complete assert not df.isna().any().any() or df.isna().all().all(), data assert not df.duplicated().any(), f"Duplicate data for simulated {repr(name)}" - return Key(name, dims), Quantity(df.set_index(dims) if len(dims) else df) + return Quantity(df.set_index(dims)["value"] if len(dims) else df, name=name) + + +def data_from_file(path: Path, *, name: str, dims: Sequence[str]) -> Quantity: + """Read simulated solution data for item `name` from `path`. + + For variables and equations (`name` in upper case), the file **must** have columns + corresponding to `dims` followed by "Val", "Marginal", "Upper", and "Scale". The + "Val" column is returned. + + For parameters, the file **must** have columns corresponding to `dims` followed by + "value" and "unit". The "value" column is returned. + """ + if name.isupper(): + # Construct a list of the columns + # NB Must assign the dimensions directly; they cannot be read from the file, as + # the column headers are the internal GAMS set names (e.g. "year_all") + # instead of the index names from message_ix. + cols = list(dims) + ["Val", "Marginal", "Lower", "Upper", "Scale"] + + return Quantity( + pd.read_csv(path, engine="pyarrow") + .set_axis(cols, axis=1) + .set_index(cols[:-5])["Val"], + name=name, + ) + else: + cols = list(dims) + ["value", "unit"] + tmp = ( + pd.read_csv(path, engine="pyarrow") + .set_axis(cols, axis=1) + .set_index(cols[:-2]) + ) + # TODO pass units if they are unique + return Quantity(tmp["value"], name=name) def add_simulated_solution( - rep: Reporter, info: ScenarioInfo, data: Optional[Dict] = None + rep: Reporter, + info: ScenarioInfo, + data: Optional[Dict] = None, + path: Optional[Path] = None, ): - """Add a simulated model solution to `rep`, given `info` and `data`.""" - # Populate the sets (from `info`, maybe empty) and pars (empty) - to_add = deepcopy(MESSAGE_ITEMS) - # Populate variables - to_add.update(MESSAGE_VARS) - # Populate MACRO items - to_add.update( - { - "GDP": item("var", "n y"), - "MERtoPPP": item("var", "n y"), - } - ) + """Add a simulated model solution to `rep`. + + Parameters + ---------- + data : dict or pandas.DataFrame, *optional* + If given, a mapping from MESSAGE item (set, parameter, or variable) names to + inputs that are passed to :func:`simulate_qty`. + path : Path, *optional* + If given, a path to a directory containing one or more files with names like + :file:`ACT.csv.gz`. These files are taken as containing "simulated" model + solution data for the MESSAGE variable with the same name. See + :func:`data_from_file`. + """ + mark_time() + N = len(rep.graph) + # Add simulated data data = data or dict() + for name, item_info in SIMULATE_ITEMS.items(): + key = Key(name, list(dims_of(item_info).values())) + + # Add a task to load data from a file in `path`, if it exists + try: + # Candidate path + assert path is not None + p = path.joinpath(name).with_suffix(".csv.gz") + assert p.exists() + except AssertionError: + pass # No `path` or no such file + else: + # Add data from file + + rep.add(key, data_from_file, p, name=name, dims=key.dims, sums=True) + continue - for name, item_info in to_add.items(): if item_info["ix_type"] == "set": - # Add the set elements + # Add the set elements from `info` rep.add(RENAME_DIMS.get(name, name), quote(info.set[name])) elif item_info["ix_type"] in ("par", "var"): # Retrieve an existing key for `name` @@ -118,17 +291,26 @@ def add_simulated_solution( except KeyError: full_key = None # Not present in `rep` - # Simulated data for name - item_data = data.get(name, {}) + # Simulate data for name + item_data = data.get(name) if full_key and not item_data: # Don't overwrite existing task with empty data continue - # Store simulated data for this quantity - key, qty = simulate_qty(name, item_info, **item_data) - rep.add(key, qty, sums=True) - # log.debug(f"{key}\n{qty}") + # Add a task to simulate data for this quantity + rep.add( + key, + simulate_qty, + name=name, + info=item_info, + item_data=item_data, + sums=True, + ) + + log.info(f"{len(rep.graph) - N} keys") + N = len(rep.graph) + mark_time() # Prepare the base MESSAGEix computations with silence_log("genno", logging.CRITICAL): @@ -136,3 +318,6 @@ def add_simulated_solution( rep.add_tasks() except KeyExistsError: pass # `rep` was produced with Reporter.from_scenario() + + log.info(f"{len(rep.graph)} total keys") + mark_time() From 790859c6cc4c596488f7166d6aefc116a30e53aa Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 11 Oct 2023 17:19:52 +0200 Subject: [PATCH 38/56] Read only parameter data in snapshot.read_excel() --- message_ix_models/model/snapshot.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/message_ix_models/model/snapshot.py b/message_ix_models/model/snapshot.py index 65ad312dbb..b23e4265a3 100644 --- a/message_ix_models/model/snapshot.py +++ b/message_ix_models/model/snapshot.py @@ -92,9 +92,16 @@ def read_excel(scenario: Scenario, path: Path) -> None: base = unpack(path) scenario.read_excel(base.joinpath("sets.xlsx")) - with scenario.transact(f"Read snapshot from {path}"): + + parameters = set(scenario.par_list()) + + with scenario.transact(f"Read snapshot data from {path}"): for p in base.glob("*.csv.gz"): name = p.name.split(".")[0] + + if name not in parameters: + continue # Variable or equation data: don't read + data = pd.read_csv(p) # Correct units From 35fd0e551bc019becf380d830f36b38041ec541c Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 11 Oct 2023 17:25:18 +0200 Subject: [PATCH 39/56] Handle index sets in sim.dims_of() --- message_ix_models/report/sim.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/message_ix_models/report/sim.py b/message_ix_models/report/sim.py index fc55093632..36a0d4d855 100644 --- a/message_ix_models/report/sim.py +++ b/message_ix_models/report/sim.py @@ -161,7 +161,10 @@ def dims_of(info: dict) -> Dict[str, str]: """Return a mapping from the full index names to short dimension IDs of `info`.""" - return {d: RENAME_DIMS.get(d, d) for d in info.get("idx_names") or info["idx_sets"]} + return { + d: RENAME_DIMS.get(d, d) + for d in (info.get("idx_names") or info.get("idx_sets") or []) + } def simulate_qty( @@ -269,7 +272,6 @@ def add_simulated_solution( # Add a task to load data from a file in `path`, if it exists try: - # Candidate path assert path is not None p = path.joinpath(name).with_suffix(".csv.gz") assert p.exists() @@ -277,7 +279,6 @@ def add_simulated_solution( pass # No `path` or no such file else: # Add data from file - rep.add(key, data_from_file, p, name=name, dims=key.dims, sums=True) continue From 26bab30b24245655273317802324c8a482054c75 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 11 Oct 2023 18:37:08 +0200 Subject: [PATCH 40/56] Add SSPOriginal for 2017 SSP data --- message_ix_models/project/ssp/data.py | 70 +++++++++++++++++++++ message_ix_models/tests/project/test_ssp.py | 38 +++++++++++ 2 files changed, 108 insertions(+) diff --git a/message_ix_models/project/ssp/data.py b/message_ix_models/project/ssp/data.py index b1fddbaeeb..1591d75489 100644 --- a/message_ix_models/project/ssp/data.py +++ b/message_ix_models/project/ssp/data.py @@ -15,6 +15,76 @@ log = logging.getLogger(__name__) +@register_source +class SSPOriginal(ExoDataSource): + """Provider of exogenous data from the original SSP database. + + In the `source_kw` to :func:`.exo_data.prepare_computer`, the following are valid: + + ``model`` + - IIASA POP + - IIASA-WiC POP + - NCAR + - OECD Env-Growth + - PIK GDP-32 + + The measures available differ according to the model; see the source data for + details. + """ + + id = "SSP" + + def __init__(self, source, source_kw): + s = "ICONICS:SSP(2017)." + if not source.startswith(s): + raise ValueError(source) + + *parts, self.ssp_number = source.partition(s) + + # Map the `measure` keyword to a string appearing in the data + _kw = copy(source_kw) + self.measure = { + "GDP": "GDP|PPP", + "POP": "Population", + }[_kw.pop("measure")] + + # Store the model ID, if any + self.model = _kw.pop("model", None) + + # Determine the date based on the model ID. There is a 1:1 correspondence. + self.date = { + "IIASA GDP": "130219", + "IIASA-WiC POP": "130115", + "NCAR": "130115", + "OECD Env-Growth": "130325", + "PIK GDP-32": "130424", + }.get(self.model) + + if len(_kw): + raise ValueError(_kw) + + def __call__(self): + # Assemble a query string + extra = "d" if self.ssp_number == "4" and self.model == "IIASA-WiC POP" else "" + query = " and ".join( + [ + f"SCENARIO == 'SSP{self.ssp_number}{extra}_v9_{self.date}'", + f"VARIABLE == '{self.measure}'", + f"MODEL == '{self.model}'" if self.model else "True", + ] + ) + log.debug(query) + + parts = ("ssp", "SspDb_country_data_2013-06-12.csv.zip") + if HAS_MESSAGE_DATA: + path = private_data_path(*parts) + else: + path = package_data_path("test", *parts) + log.warning(f"Reading random data from {path}") + + return iamc_like_data_for_query(path, query) + + @register_source class SSPUpdate(ExoDataSource): """Provider of exogenous data from the SSP Update database.""" diff --git a/message_ix_models/tests/project/test_ssp.py b/message_ix_models/tests/project/test_ssp.py index c2d7042e42..5b84e95c30 100644 --- a/message_ix_models/tests/project/test_ssp.py +++ b/message_ix_models/tests/project/test_ssp.py @@ -86,6 +86,44 @@ def test_cli(mix_models_cli): mix_models_cli.assert_exit_0(["ssp", "gen-structures", "--dry-run"]) +class TestSSPOriginal: + @pytest.mark.parametrize( + "source", + ( + "ICONICS:SSP(2017).1", + "ICONICS:SSP(2017).2", + "ICONICS:SSP(2017).3", + "ICONICS:SSP(2017).4", + "ICONICS:SSP(2017).5", + ), + ) + @pytest.mark.parametrize( + "source_kw", + ( + dict(measure="POP", model="OECD Env-Growth"), + dict(measure="GDP", model="OECD Env-Growth"), + ), + ) + def test_prepare_computer(self, test_context, source, source_kw): + # FIXME The following should be redundant, but appears mutable on GHA linux and + # Windows runners. + test_context.model.regions = "R14" + + c = Computer() + + keys = prepare_computer(test_context, c, source, source_kw) + + # Preparation of data runs successfully + result = c.get(keys[0]) + + # Data has the expected dimensions + assert ("n", "y") == result.dims + + # Data is complete + assert 14 == len(result.coords["n"]) + assert 14 == len(result.coords["y"]) + + class TestSSPUpdate: @pytest.mark.parametrize( "source", From bd3180a769748e595a4401c4fec577e7ca2ff747 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Wed, 11 Oct 2023 18:38:13 +0200 Subject: [PATCH 41/56] Raise a clearer exception in iamc_like_data_for_query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …in cases where no data matches the query. --- message_ix_models/tools/exo_data.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/message_ix_models/tools/exo_data.py b/message_ix_models/tools/exo_data.py index 609a8253a2..e98971e3a6 100644 --- a/message_ix_models/tools/exo_data.py +++ b/message_ix_models/tools/exo_data.py @@ -256,6 +256,9 @@ def iamc_like_data_for_query(path: Path, query: str) -> Quantity: unique = dict() def drop_unique(df, names) -> pd.DataFrame: + if len(df) == 0: + raise RuntimeError(f"0 rows matching {query!r}") + names_list = names.split() for name in names_list: values = df[name].unique() From 40d2d3adda01f58eca0d68398c21c8de1c66b5c1 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Thu, 12 Oct 2023 01:07:37 +0200 Subject: [PATCH 42/56] Generate synthetic original SSP data for testing --- .../data/test/ssp/SspDb_country_data_2013-06-12.csv.zip | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 message_ix_models/data/test/ssp/SspDb_country_data_2013-06-12.csv.zip diff --git a/message_ix_models/data/test/ssp/SspDb_country_data_2013-06-12.csv.zip b/message_ix_models/data/test/ssp/SspDb_country_data_2013-06-12.csv.zip new file mode 100644 index 0000000000..1cd0174b2b --- /dev/null +++ b/message_ix_models/data/test/ssp/SspDb_country_data_2013-06-12.csv.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1220e19892a27928af159233f087c2abea4337b2cb73af56913612eea6a0bfc +size 6013242 From 0b2abdd29ba011b82719062c5ef263716d97ebec Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Thu, 12 Oct 2023 12:50:30 +0200 Subject: [PATCH 43/56] =?UTF-8?q?Add=20iamc=5Flike=5Fdata=5Ffor=5Fquery(?= =?UTF-8?q?=E2=80=A6,=20replace=3D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- message_ix_models/project/ssp/data.py | 23 +++++++++++++++-------- message_ix_models/tools/exo_data.py | 6 ++++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/message_ix_models/project/ssp/data.py b/message_ix_models/project/ssp/data.py index 1591d75489..2c82ba1b66 100644 --- a/message_ix_models/project/ssp/data.py +++ b/message_ix_models/project/ssp/data.py @@ -34,6 +34,19 @@ class SSPOriginal(ExoDataSource): id = "SSP" + #: One-to-one correspondence between "model" codes and date fragments in scenario + #: codes. + model_date = { + "IIASA GDP": "130219", + "IIASA-WiC POP": "130115", + "NCAR": "130115", + "OECD Env-Growth": "130325", + "PIK GDP-32": "130424", + } + + #: Replacements to apply when loading the data. + replace = {"billion US$2005/yr": "billion USD_2005/yr"} + def __init__(self, source, source_kw): s = "ICONICS:SSP(2017)." if not source.startswith(s): @@ -52,13 +65,7 @@ def __init__(self, source, source_kw): self.model = _kw.pop("model", None) # Determine the date based on the model ID. There is a 1:1 correspondence. - self.date = { - "IIASA GDP": "130219", - "IIASA-WiC POP": "130115", - "NCAR": "130115", - "OECD Env-Growth": "130325", - "PIK GDP-32": "130424", - }.get(self.model) + self.date = self.model_date[self.model] if len(_kw): raise ValueError(_kw) @@ -82,7 +89,7 @@ def __call__(self): path = package_data_path("test", *parts) log.warning(f"Reading random data from {path}") - return iamc_like_data_for_query(path, query) + return iamc_like_data_for_query(path, query, replace=self.replace) @register_source diff --git a/message_ix_models/tools/exo_data.py b/message_ix_models/tools/exo_data.py index e98971e3a6..b146fd88b4 100644 --- a/message_ix_models/tools/exo_data.py +++ b/message_ix_models/tools/exo_data.py @@ -230,7 +230,9 @@ def random_data(): @cached -def iamc_like_data_for_query(path: Path, query: str) -> Quantity: +def iamc_like_data_for_query( + path: Path, query: str, *, replace: Optional[dict] = None +) -> Quantity: """Load data from `path` in IAMC-like format and transform to :class:`.Quantity`. The steps involved are: @@ -270,6 +272,7 @@ def drop_unique(df, names) -> pd.DataFrame: tmp = ( pd.read_csv(path, engine="pyarrow") .query(query) + .replace(replace or {}) .rename(columns=lambda c: c.upper()) .pipe(drop_unique, "MODEL SCENARIO VARIABLE UNIT") .assign(n=lambda df: df["REGION"].apply(iso_3166_alpha_3)) @@ -281,5 +284,4 @@ def drop_unique(df, names) -> pd.DataFrame: .stack() .dropna() ) - return Quantity(tmp, units=unique["UNIT"]) From cb12a8d1b57a00c8b77255957380e9ad49db0da4 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Thu, 12 Oct 2023 12:54:11 +0200 Subject: [PATCH 44/56] Update "mix-models ssp make-test-data" --- message_ix_models/project/ssp/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/message_ix_models/project/ssp/__init__.py b/message_ix_models/project/ssp/__init__.py index 25a564cecf..0186f4fd4a 100644 --- a/message_ix_models/project/ssp/__init__.py +++ b/message_ix_models/project/ssp/__init__.py @@ -71,7 +71,14 @@ def gen_structures(context, **kwargs): @cli.command("make-test-data") -def make_test_data(): +@click.argument( + "filename", + metavar="FILENAME", + type=click.Choice( + ["SSP-Review-Phase-1.csv.gz", "SspDb_country_data_2013-06-12.csv.zip"] + ), +) +def make_test_data(filename): """Create random data for testing.""" from pathlib import Path @@ -81,7 +88,7 @@ def make_test_data(): from message_ix_models.util import package_data_path, private_data_path # Paths - p = Path("ssp", "SSP-Review-Phase-1.csv.gz") + p = Path("ssp", filename) path_in = private_data_path(p) path_out = package_data_path("test", p) From e74f55184bf0ca5bfb46d60fe22b649faccff2a9 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Thu, 12 Oct 2023 13:07:32 +0200 Subject: [PATCH 45/56] =?UTF-8?q?Mark=20add=5Fsimulated=5Fsolution()=20as?= =?UTF-8?q?=20not=20implemented=20for=20message=5Fix=20=E2=89=A4=203.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- message_ix_models/report/sim.py | 8 ++++++++ message_ix_models/tests/test_report.py | 1 + 2 files changed, 9 insertions(+) diff --git a/message_ix_models/report/sim.py b/message_ix_models/report/sim.py index 36a0d4d855..4318410918 100644 --- a/message_ix_models/report/sim.py +++ b/message_ix_models/report/sim.py @@ -262,6 +262,14 @@ def add_simulated_solution( solution data for the MESSAGE variable with the same name. See :func:`data_from_file`. """ + from importlib.metadata import version + + if version("message_ix") < "3.6": + raise NotImplementedError( + "Support for message_ix_models.report.sim.add_simulated_solution() with " + "message_ix <= 3.5.0. Please upgrade to message_ix 3.6 or later." + ) + mark_time() N = len(rep.graph) diff --git a/message_ix_models/tests/test_report.py b/message_ix_models/tests/test_report.py index bfbd022ee2..ea87ea1b8b 100644 --- a/message_ix_models/tests/test_report.py +++ b/message_ix_models/tests/test_report.py @@ -203,6 +203,7 @@ def test_collapse(input, exp): pdt.assert_frame_equal(util.collapse(df_in), df_exp) +@MARK[0] def test_add_simulated_solution(test_context, test_data_path): from message_ix import Reporter From fa1f3d96f278c0970c64c32f0e77f40731147afb Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Thu, 12 Oct 2023 16:26:48 +0200 Subject: [PATCH 46/56] Improve coverage in .report, .util --- message_ix_models/project/ssp/__init__.py | 2 +- message_ix_models/report/__init__.py | 3 ++ message_ix_models/report/config.py | 2 +- message_ix_models/report/plot.py | 1 + message_ix_models/report/sim.py | 3 ++ message_ix_models/tests/project/test_ssp.py | 10 ++++++ .../tests/report/test_computations.py | 19 +++++++++++- message_ix_models/tests/report/test_config.py | 23 ++++++++++++++ message_ix_models/tests/test_report.py | 31 +++++++++++++++++-- message_ix_models/tests/util/test_click.py | 28 +++++++++++++++++ message_ix_models/tests/util/test_config.py | 5 +++ .../tests/util/test_scenarioinfo.py | 6 ++++ message_ix_models/util/click.py | 12 +++++-- message_ix_models/util/config.py | 7 +++++ message_ix_models/util/context.py | 2 +- pyproject.toml | 2 ++ 16 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 message_ix_models/tests/report/test_config.py diff --git a/message_ix_models/project/ssp/__init__.py b/message_ix_models/project/ssp/__init__.py index 0186f4fd4a..3ce98545a4 100644 --- a/message_ix_models/project/ssp/__init__.py +++ b/message_ix_models/project/ssp/__init__.py @@ -78,7 +78,7 @@ def gen_structures(context, **kwargs): ["SSP-Review-Phase-1.csv.gz", "SspDb_country_data_2013-06-12.csv.zip"] ), ) -def make_test_data(filename): +def make_test_data(filename): # pragma: no cover """Create random data for testing.""" from pathlib import Path diff --git a/message_ix_models/report/__init__.py b/message_ix_models/report/__init__.py index 4d5bd02bde..ad4620077e 100644 --- a/message_ix_models/report/__init__.py +++ b/message_ix_models/report/__init__.py @@ -16,7 +16,10 @@ from message_ix_models import Context, ScenarioInfo from message_ix_models.util._logging import mark_time +from .config import Config + __all__ = [ + "Config", "prepare_reporter", "register", "report", diff --git a/message_ix_models/report/config.py b/message_ix_models/report/config.py index 8395e912e3..b813826877 100644 --- a/message_ix_models/report/config.py +++ b/message_ix_models/report/config.py @@ -93,7 +93,7 @@ def use_file(self, file_path: Union[str, Path, None]) -> None: ) ) except StopIteration: - raise FileNotFoundError(f"Reporting configuration in {file_path}") + raise FileNotFoundError(f"Reporting configuration in '{file_path}(.yaml)'") # Store for genno to handle self.genno_config["path"] = path diff --git a/message_ix_models/report/plot.py b/message_ix_models/report/plot.py index 9eba19d9e9..3ca7a03445 100644 --- a/message_ix_models/report/plot.py +++ b/message_ix_models/report/plot.py @@ -210,3 +210,4 @@ class PrimaryEnergy1(FinalEnergy1): def callback(c: Computer, context: "Context") -> None: all_keys = [c.add(f"plot {p.basename}", p, "scenario") for p in PLOTS] c.add("plot all", all_keys) + log.info(f"Add 'plot all' collecting {len(all_keys)} plots") diff --git a/message_ix_models/report/sim.py b/message_ix_models/report/sim.py index 4318410918..55d8280218 100644 --- a/message_ix_models/report/sim.py +++ b/message_ix_models/report/sim.py @@ -273,6 +273,9 @@ def add_simulated_solution( mark_time() N = len(rep.graph) + # Ensure "scenario" is present in the graph + rep.graph.setdefault("scenario", None) + # Add simulated data data = data or dict() for name, item_info in SIMULATE_ITEMS.items(): diff --git a/message_ix_models/tests/project/test_ssp.py b/message_ix_models/tests/project/test_ssp.py index 5b84e95c30..b6f7291268 100644 --- a/message_ix_models/tests/project/test_ssp.py +++ b/message_ix_models/tests/project/test_ssp.py @@ -102,6 +102,11 @@ class TestSSPOriginal: ( dict(measure="POP", model="OECD Env-Growth"), dict(measure="GDP", model="OECD Env-Growth"), + # Excess keyword arguments + pytest.param( + dict(measure="GDP", model="OECD Env-Growth", foo="bar"), + marks=pytest.mark.xfail(raises=ValueError), + ), ), ) def test_prepare_computer(self, test_context, source, source_kw): @@ -141,6 +146,11 @@ class TestSSPUpdate: dict(measure="POP"), dict(measure="GDP", model="IIASA GDP 2023"), dict(measure="GDP", model="OECD ENV-Growth 2023"), + # Excess keyword arguments + pytest.param( + dict(measure="POP", foo="bar"), + marks=pytest.mark.xfail(raises=ValueError), + ), ), ) def test_prepare_computer(self, test_context, source, source_kw): diff --git a/message_ix_models/tests/report/test_computations.py b/message_ix_models/tests/report/test_computations.py index a8735076a9..84c223fa2c 100644 --- a/message_ix_models/tests/report/test_computations.py +++ b/message_ix_models/tests/report/test_computations.py @@ -1,7 +1,10 @@ +import re + +import pandas as pd import xarray as xr from genno import Quantity -from message_ix_models.report.computations import compound_growth +from message_ix_models.report.computations import compound_growth, filter_ts def test_compound_growth(): @@ -28,3 +31,17 @@ def test_compound_growth(): assert all(1.02**5 == r1.sel(t=2035) / r1.sel(t=2030)) assert all(1.0 == result.sel(x="x2")) + + +def test_filter_ts(): + df = pd.DataFrame([["foo"], ["bar"]], columns=["variable"]) + assert 2 == len(df) + + # Operator runs + result = filter_ts(df, re.compile(".(ar)")) + + # Only matching rows are returned + assert 1 == len(result) + + # Only the first match group in `expr` is preserved + assert {"ar"} == set(result.variable.unique()) diff --git a/message_ix_models/tests/report/test_config.py b/message_ix_models/tests/report/test_config.py new file mode 100644 index 0000000000..c84dcfcef8 --- /dev/null +++ b/message_ix_models/tests/report/test_config.py @@ -0,0 +1,23 @@ +import pytest + +from message_ix_models.report.config import Config + + +class TestConfig: + def test_use_file(self, tmp_path): + cfg = Config() + + # No effect + cfg.use_file(None) + + # Passing a missing path raises an exception + with pytest.raises( + FileNotFoundError, match="Reporting configuration in .*missing" + ): + cfg.use_file(tmp_path.joinpath("missing")) + + # Passing a file name that does not exist raises an exception + with pytest.raises( + FileNotFoundError, match=r"Reporting configuration in 'unknown\(.yaml\)'" + ): + cfg.use_file("unknown") diff --git a/message_ix_models/tests/test_report.py b/message_ix_models/tests/test_report.py index ea87ea1b8b..75a69e2943 100644 --- a/message_ix_models/tests/test_report.py +++ b/message_ix_models/tests/test_report.py @@ -159,6 +159,12 @@ def test_apply_units(request, test_context, regions): assert ["EUR_2005"] == df["unit"].unique() +@pytest.mark.xfail(reason="Incomplete") +def test_cli(mix_models_cli): + # TODO complete by providing a Scenario that is reportable (with solution) + mix_models_cli.assert_exit_0(["report"]) + + @pytest.mark.parametrize( "input, exp", ( @@ -203,8 +209,8 @@ def test_collapse(input, exp): pdt.assert_frame_equal(util.collapse(df_in), df_exp) -@MARK[0] -def test_add_simulated_solution(test_context, test_data_path): +def ss_reporter(): + """Reporter with a simulated solution for snapshot 0.""" from message_ix import Reporter rep = Reporter() @@ -216,7 +222,15 @@ def test_add_simulated_solution(test_context, test_data_path): path=package_data_path("test", "MESSAGEix-GLOBIOM_1.1_R11_no-policy_baseline"), ) - # out can be calculated using "output" and "ACT" from files in `path` + return rep + + +@MARK[0] +def test_add_simulated_solution(test_context, test_data_path): + # Simulated solution can be added to an empty Reporter + rep = ss_reporter() + + # "out" can be calculated using "output" and "ACT" from files in `path` result = rep.get("out:*") # Has expected dimensions and length @@ -237,3 +251,14 @@ def test_add_simulated_solution(test_context, test_data_path): hd="year", ) assert np.isclose(79.76478, value.item()) + + +def test_prepare_reporter(test_context): + rep = ss_reporter() + N = len(rep.graph) + + # prepare_reporter() works on the simulated solution + prepare_reporter(test_context, reporter=rep) + + # A number of keys were added + assert 14299 <= len(rep.graph) - N diff --git a/message_ix_models/tests/util/test_click.py b/message_ix_models/tests/util/test_click.py index 801371bc2b..81d9bb19b2 100644 --- a/message_ix_models/tests/util/test_click.py +++ b/message_ix_models/tests/util/test_click.py @@ -58,3 +58,31 @@ def func(ctx, ssp): # The value was stored on, and retrieved from, `ctx` assert "SSP2\n" == result.output + + +def test_urls_from_file(mix_models_cli, tmp_path): + """Test :func:`.urls_from_file` callback.""" + + # Create a hidden command and attach it to the CLI + @click.command(name="_test_store_context", hidden=True) + @common_params("urls_from_file") + @click.pass_obj + def func(ctx, **kwargs): + # Print the value stored on the Context object + print("\n".join([s.url for s in ctx.core.scenarios])) + + # Create a temporary file with some scenario URLs + text = """m/s#3 +foo/bar#5 +baz/qux#123 +""" + p = tmp_path.joinpath("scenarios.txt") + p.write_text(text) + + # Run the command, referring to the temporary file + with temporary_command(main, func): + result = mix_models_cli.assert_exit_0([func.name, f"--urls-from-file={p}"]) + + # Scenario URLs are parsed to ScenarioInfo objects, and then can be reconstructed → + # data is round-tripped + assert text == result.output diff --git a/message_ix_models/tests/util/test_config.py b/message_ix_models/tests/util/test_config.py index 0962a812b7..d48118e1e3 100644 --- a/message_ix_models/tests/util/test_config.py +++ b/message_ix_models/tests/util/test_config.py @@ -131,3 +131,8 @@ def test_replace(self, c): result = c.replace(foo_2="baz") assert result is not c assert "baz" == result.foo_2 + + def test_update(self, c): + """:meth:`.update` raises AttributeError.""" + with pytest.raises(AttributeError): + c.update(foo_4="") diff --git a/message_ix_models/tests/util/test_scenarioinfo.py b/message_ix_models/tests/util/test_scenarioinfo.py index ec748f852b..bb23b5c075 100644 --- a/message_ix_models/tests/util/test_scenarioinfo.py +++ b/message_ix_models/tests/util/test_scenarioinfo.py @@ -137,6 +137,12 @@ def test_from_scenario(self, test_context) -> None: assert 1963 == info.y0 assert [1963, 1964, 1965] == info.Y + def test_from_url(self): + si = ScenarioInfo.from_url("m/s#123") + assert "m" == si.model + assert "s" == si.scenario + assert 123 == si.version + @pytest.mark.parametrize( "input, expected", ( diff --git a/message_ix_models/util/click.py b/message_ix_models/util/click.py index 5602a06491..c6a1004dcf 100644 --- a/message_ix_models/util/click.py +++ b/message_ix_models/util/click.py @@ -5,7 +5,7 @@ import logging from datetime import datetime from pathlib import Path -from typing import Optional, Union +from typing import List, Optional, Union import click from click import Argument, Choice, Option @@ -86,9 +86,15 @@ def store_context(context: Union[click.Context, Context], param, value): return value -def urls_from_file(context: Union[click.Context, Context], param, value): +def urls_from_file( + context: Union[click.Context, Context], param, value +) -> List[ScenarioInfo]: """Callback to parse scenario URLs from `value`.""" - si = [] + si: List[ScenarioInfo] = [] + + if value is None: + return si + with click.open_file(value) as f: for line in f: si.append(ScenarioInfo.from_url(url=line)) diff --git a/message_ix_models/util/config.py b/message_ix_models/util/config.py index f162f14940..e037f42b61 100644 --- a/message_ix_models/util/config.py +++ b/message_ix_models/util/config.py @@ -110,6 +110,13 @@ def replace(self, **kwargs): ) def update(self, **kwargs): + """Update attributes in-place. + + Raises + ------ + AttributeError + Any of the `kwargs` are not fields in the data class. + """ # TODO use _munge_dict(); allow a positional argument for k, v in kwargs.items(): if not hasattr(self, k): diff --git a/message_ix_models/util/context.py b/message_ix_models/util/context.py index e1802d36ac..956b7955c9 100644 --- a/message_ix_models/util/context.py +++ b/message_ix_models/util/context.py @@ -72,7 +72,7 @@ def only(cls) -> "Context": def __init__(self, *args, **kwargs): from message_ix_models.model import Config as ModelConfig - from message_ix_models.report.config import Config as ReportConfig + from message_ix_models.report import Config as ReportConfig if len(_CONTEXTS) == 0: log.info("Create root Context") diff --git a/pyproject.toml b/pyproject.toml index 35e16c5c4b..94699ea3f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,6 +78,8 @@ exclude_also = [ "@(abc\\.)?abstractmethod", # Imports only used by type checkers "if TYPE_CHECKING:", + # Requires message_data + "if HAS_MESSAGE_DATA:", ] [tool.mypy] From 0e42d18831153756653c5861c2332b260327b43c Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Fri, 13 Oct 2023 11:28:15 +0200 Subject: [PATCH 47/56] Document #125; add to whatsnew --- doc/api/project.rst | 44 +++++++++++++- doc/api/report/index.rst | 18 +++++- doc/api/tools.rst | 19 ++++++- doc/api/util.rst | 13 ++++- doc/conf.py | 7 +++ doc/whatsnew.rst | 20 ++++++- message_ix_models/project/ssp/data.py | 63 +++++++++++++++++---- message_ix_models/report/__init__.py | 4 +- message_ix_models/report/computations.py | 14 +++-- message_ix_models/report/config.py | 13 +++-- message_ix_models/report/plot.py | 20 ++++++- message_ix_models/tests/test_report.py | 2 +- message_ix_models/tests/util/test_config.py | 2 +- message_ix_models/tools/exo_data.py | 26 ++++----- message_ix_models/util/context.py | 4 +- message_ix_models/util/scenarioinfo.py | 31 ++++++++-- 16 files changed, 244 insertions(+), 56 deletions(-) diff --git a/doc/api/project.rst b/doc/api/project.rst index c5ff47803e..523c77c3b3 100644 --- a/doc/api/project.rst +++ b/doc/api/project.rst @@ -1,7 +1,11 @@ .. currentmodule:: message_ix_models.project -Specific research projects (:mod:`.project`) -******************************************** +Specific research projects (:mod:`~message_ix_models.project`) +************************************************************** + +.. contents:: + :local: + :backlinks: none .. automodule:: message_ix_models.project :members: @@ -12,5 +16,41 @@ Specific research projects (:mod:`.project`) Shared Socioeconomic Pathways (:mod:`.project.ssp`) =================================================== +Structure +--------- + +The enumerations :obj:`SSP_2017` and :obj:`SSP_2024` contain one member from the corresponding SDMX code lists. +These can be used to uniquely identify both an SSP narrative *and* the set in which it occurs, in applications where this distinction is meaningful: + +.. code-block:: py + + >>> from message_ix_models.project.ssp import SSP_2017, SSP_2024 + >>> x = SSP_2017["2"] + >>> y = SSP_2024["2"] + >>> str(y) + "ICONICS:SSP(2024).2" + >>> x == y + False + .. automodule:: message_ix_models.project.ssp :members: + +Data +---- + +.. automodule:: message_ix_models.project.ssp.data + :members: + + Although free, neither the 2017 or 2024 SSP data can be downloaded automatically. + Both sources require that users first submit personal information to register before being able to retrieve the data. + :mod:`message_ix_models` does not circumvent this requirement. + Thus: + + - A copy of the data are stored in :mod:`message_data`. + - :mod:`message_ix_models` contains only a ‘fuzzed’ version of the data (same structure, random values) for testing purposes. + + .. todo:: Allow users without access to :mod:`message_data` to read a local copy of this data from a :attr:`.Config.local_data` subdirectory. + + .. autosummary:: + SSPOriginal + SSPUpdate diff --git a/doc/api/report/index.rst b/doc/api/report/index.rst index dac83e7272..f6ef88e16c 100644 --- a/doc/api/report/index.rst +++ b/doc/api/report/index.rst @@ -25,7 +25,7 @@ Not public: Introduction ============ -See :doc:`the discussion in the MESSAGEix docs ` about the stack. +See :doc:`the discussion in the MESSAGEix docs ` about the stack. In short, :mod:`message_ix` must not contain reporting code that references ``coal_ppl``, because not every model built on the MESSAGE framework will have a technology with this name. Any reporting specific to ``coal_ppl`` must be in :mod:`message_ix_models`, since all models in the MESSAGEix-GLOBIOM family will have this technology. @@ -95,14 +95,24 @@ API reference .. autosummary:: + Config prepare_reporter register report +.. currentmodule:: message_ix_models.report.plot + +Plots +----- + +.. automodule:: message_ix_models.report.plot + :members: + +.. currentmodule:: message_ix_models.report.computations + Operators --------- -.. currentmodule:: message_ix_models.report.computations .. automodule:: message_ix_models.report.computations :members: @@ -110,6 +120,10 @@ Operators .. autosummary:: + codelist_to_groups + compound_growth + exogenous_data + filter_ts from_url get_ts gwp_factors diff --git a/doc/api/tools.rst b/doc/api/tools.rst index 63da531252..8e9b523c0b 100644 --- a/doc/api/tools.rst +++ b/doc/api/tools.rst @@ -24,7 +24,7 @@ Exogenous data (:mod:`.tools.exo_data`) .. automodule:: message_ix_models.tools.exo_data :members: - :exclude-members: ExoDataSource + :exclude-members: ExoDataSource, prepare_computer .. autosummary:: @@ -32,9 +32,26 @@ Exogenous data (:mod:`.tools.exo_data`) SOURCES DemoSource ExoDataSource + iamc_like_data_for_query prepare_computer register_source +.. autofunction:: prepare_computer + + The first returned key, like ``{measure}:n-y``, triggers the following computations: + + 1. Load data by invoking a :class:`ExoDataSource`. + 2. Aggregate on the |n| (node) dimension according to :attr:`.Config.regions`. + 3. Interpolate on the |y| (year) dimension according to :attr:`.Config.years`. + + Additional key(s) include: + + - ``{measure}:n-y:y0 indexed``: same as ``{measure}:n-y``, indexed to values as of |y0| (the first model year). + + See particular data source classes, like :class:`.SSPOriginal`, for particular examples of usage. + + .. todo:: Extend to also prepare to compute values indexed to a particular |n|. + .. autoclass:: ExoDataSource :members: :special-members: __init__, __call__ diff --git a/doc/api/util.rst b/doc/api/util.rst index 7629a25a81..efb8a1e282 100644 --- a/doc/api/util.rst +++ b/doc/api/util.rst @@ -57,7 +57,6 @@ Commonly used: .. autodata:: message_ix_models.util.cache.SKIP_CACHE - :mod:`.util.click` ================== @@ -66,6 +65,11 @@ Commonly used: .. automodule:: message_ix_models.util.click :members: + :data:`PARAMS` contains, among others: + + - :program:`--urls-from-file=…` Path to a file containing scenario URLs, one per line. + These are parsed and stored on :attr:`.Config.scenarios`. + :mod:`.util.config` =================== @@ -181,6 +185,13 @@ Commonly used: .. automodule:: message_ix_models.util.pooch :members: +:mod:`.util.pycountry` +====================== + +.. currentmodule:: message_ix_models.util.pycountry + +.. automodule:: message_ix_models.util.pycountry + :members: :mod:`.util.scenarioinfo` ========================= diff --git a/doc/conf.py b/doc/conf.py index 3e38e60d4a..b66b306f62 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -53,9 +53,16 @@ } rst_prolog = """ +.. role:: py(code) + :language: python + .. |Code| replace:: :class:`~sdmx.model.common.Code` .. |Platform| replace:: :class:`~ixmp.Platform` .. |Scenario| replace:: :class:`~message_ix.Scenario` + +.. |n| replace:: :math:`n` +.. |y| replace:: :math:`y` +.. |y0| replace:: :math:`y_0` """ diff --git a/doc/whatsnew.rst b/doc/whatsnew.rst index 01556df917..37e26c0f84 100644 --- a/doc/whatsnew.rst +++ b/doc/whatsnew.rst @@ -1,8 +1,24 @@ What's new ********** -.. Next release -.. ============ +Next release +============ + +- New providers for exogenous data from the :class:`.SSPOriginal` and :class:`.SSPUpdate` (:pull:`125`) sources. +- Improved :class:`.ScenarioInfo` (:pull:`125`): + + - New attributes :attr:`~.ScenarioInfo.model`, :attr:`~.ScenarioInfo.scenario`, :attr:`~.ScenarioInfo.version`, and (settable) :attr:`~.ScenarioInfo.url`; class method :meth:`~.ScenarioInfo.from_url` to allow storing |Scenario| identifiers on ScenarioInfo objects. + - New property :attr:`~.ScenarioInfo.path`, giving a valid path name for scenario-specific file I/O. + +- Improvements to :mod:`~message_ix_models.report` (:pull:`125`): + + - New :class:`.report.Config` class collecting recognized settings for the module. + - :py:`context["report"]` always exists as an instance of :class:`.report.Config`. + - New submodule :class:`.report.plot` with base class and 5 plots of time-series data stored on Scenarios (:pull:`125`). + - New operator :func:`.filter_ts` (:pull:`125`). + +- New reusable command-line option :program:`--urls-from-file` in :mod:`.util.click` (:pull:`125`). +- Add `pyarrow `_ to dependencies (:pull:`125`). v2023.9.12 ========== diff --git a/message_ix_models/project/ssp/data.py b/message_ix_models/project/ssp/data.py index 2c82ba1b66..8d58635811 100644 --- a/message_ix_models/project/ssp/data.py +++ b/message_ix_models/project/ssp/data.py @@ -12,6 +12,11 @@ private_data_path, ) +__all__ = [ + "SSPOriginal", + "SSPUpdate", +] + log = logging.getLogger(__name__) @@ -19,17 +24,34 @@ class SSPOriginal(ExoDataSource): """Provider of exogenous data from the original SSP database. - In the `source_kw` to :func:`.exo_data.prepare_computer`, the following are valid: - - ``model`` - - IIASA POP - - IIASA-WiC POP - - NCAR - - OECD Env-Growth - - PIK GDP-32 - - The measures available differ according to the model; see the source data for - details. + To use data from this source, call :func:`.exo_data.prepare_computer` with the + arguments: + + - `source`: Any value from :data:`.SSP_2017` or equivalent string, for instance + "ICONICS:SSP(2017).2". The specific SSP for which data is returned is determined + from the value. + - `source_kw` including: + + - "model": one of: + + - IIASA POP + - IIASA-WiC POP + - NCAR + - OECD Env-Growth + - PIK GDP-32 + + - "measure": The measures available differ according to the model; see the source + data for details. + + Example + ------- + >>> keys = prepare_computer( + ... context, + ... computer, + ... source="ICONICS:SSP(2015).3", + ... source_kw=dict(measure="POP", model="IIASA-WiC POP"), + ... ) + >>> result = computer.get(keys[0]) """ id = "SSP" @@ -94,7 +116,24 @@ def __call__(self): @register_source class SSPUpdate(ExoDataSource): - """Provider of exogenous data from the SSP Update database.""" + """Provider of exogenous data from the SSP Update database. + + To use data from this source, call :func:`.exo_data.prepare_computer` with the + arguments: + + - `source`: Any value from :data:`.SSP_2024` or equivalent string, for instance + "ICONICS:SSP(2024).2". + + Example + ------- + >>> keys = prepare_computer( + ... context, + ... computer, + ... source="ICONICS:SSP(2024).3", + ... source_kw=dict(measure="GDP", model="IIASA GDP 2023"), + ... ) + >>> result = computer.get(keys[0]) + """ id = "SSP update" diff --git a/message_ix_models/report/__init__.py b/message_ix_models/report/__init__.py index ad4620077e..c1f9601517 100644 --- a/message_ix_models/report/__init__.py +++ b/message_ix_models/report/__init__.py @@ -196,8 +196,8 @@ def report(context: Context, *args, **kwargs): The code responds to: - :attr:`.dry_run`: if :obj:`True`, reporting is prepared but nothing is done. - - :attr:`.scenario_info` and :attr:`.platform_info`: used to retrieve the - Scenario to be reported. + - :attr:`~.Config.scenario_info` and :attr:`~.Config.platform_info`: used to + retrieve the Scenario to be reported. - :py:`context.report`, which is an instance of :class:`.report.Config`; see there for available configuration settings. diff --git a/message_ix_models/report/computations.py b/message_ix_models/report/computations.py index 309aa91f6e..2aaf42338b 100644 --- a/message_ix_models/report/computations.py +++ b/message_ix_models/report/computations.py @@ -88,10 +88,16 @@ def add_exogenous_data( ) -def filter_ts(df: pd.DataFrame, expr: re.Pattern) -> pd.DataFrame: - """Filter time series data in `df`.""" - return df[df["variable"].str.fullmatch(expr)].assign( - variable=df["variable"].str.replace(expr, r"\1", regex=True) +def filter_ts(df: pd.DataFrame, expr: re.Pattern, *, column="variable") -> pd.DataFrame: + """Filter time series data in `df`. + + 1. Keep only rows in `df` where `expr` is a full match ( + :meth:`~pandas.Series.str.fullmatch`) for the entry in `column`. + 2. Retain only the first match group ("...(...)...") from `expr` as the `column` + entry. + """ + return df[df[column].str.fullmatch(expr)].assign( + variable=df[column].str.replace(expr, r"\1", regex=True) ) diff --git a/message_ix_models/report/config.py b/message_ix_models/report/config.py index b813826877..cf1c9188d7 100644 --- a/message_ix_models/report/config.py +++ b/message_ix_models/report/config.py @@ -14,7 +14,11 @@ @dataclass class Config(ConfigHelper): - """Settings for :mod:`message_ix_models.report`.""" + """Settings for :mod:`message_ix_models.report`. + + When initializing a new instance, the `from_file` and `_legacy` parameters are + respected. + """ #: Shorthand to call :func:`use_file` on a new instance. from_file: InitVar[Optional[Path]] = package_data_path("report", "global.yaml") @@ -51,9 +55,10 @@ def __post_init__(self, from_file, _legacy) -> None: self.legacy.update(use=_legacy) def set_output_dir(self, arg: Optional[Path]) -> None: - """Set the output directory. + """Set :attr:`output_dir`, the output directory. - The value is also passed to :mod:`genno` as the "output_dir" configuration key. + The value is also stored to be passed to :mod:`genno` as the "output_dir" + configuration key. """ if arg: self.output_dir = arg.expanduser() @@ -64,7 +69,7 @@ def use_file(self, file_path: Union[str, Path, None]) -> None: """Use genno configuration from a (YAML) file at `file_path`. See :mod:`genno.config` for the format of these files. The path is stored at - :py:`.genno_config["path]`, where it is picked up by genno's configuration + :py:`.genno_config["path"]`, where it is picked up by genno's configuration mechanism. Parameters diff --git a/message_ix_models/report/plot.py b/message_ix_models/report/plot.py index 3ca7a03445..586fe6b120 100644 --- a/message_ix_models/report/plot.py +++ b/message_ix_models/report/plot.py @@ -1,7 +1,7 @@ """Plots for MESSAGEix-GLOBIOM reporting. -The current set functions on time series data stored on the scenario by :mod:`.report` -or legacy reporting. +The current set functions on time series data stored on the scenario by +:mod:`message_ix_models.report` or :mod:`message_data` legacy reporting. """ import logging import re @@ -19,6 +19,17 @@ from message_ix_models import Context +__all__ = [ + "PLOTS", + "EmissionsCO2", + "FinalEnergy0", + "FinalEnergy1", + "Plot", + "PrimaryEnergy0", + "PrimaryEnergy1", + "callback", +] + log = logging.getLogger(__name__) @@ -198,6 +209,7 @@ class PrimaryEnergy1(FinalEnergy1): inputs_regex = [re.compile(rf"Primary Energy\|((?!{'|'.join(_omit)})[^\|]*)")] +#: All plot classes. PLOTS = ( EmissionsCO2, FinalEnergy0, @@ -208,6 +220,10 @@ class PrimaryEnergy1(FinalEnergy1): def callback(c: Computer, context: "Context") -> None: + """Add all :data:`PLOTS` to `c`. + + Also add a key "plot all" to triggers the generation of all plots. + """ all_keys = [c.add(f"plot {p.basename}", p, "scenario") for p in PLOTS] c.add("plot all", all_keys) log.info(f"Add 'plot all' collecting {len(all_keys)} plots") diff --git a/message_ix_models/tests/test_report.py b/message_ix_models/tests/test_report.py index 75a69e2943..c4b2d1e70a 100644 --- a/message_ix_models/tests/test_report.py +++ b/message_ix_models/tests/test_report.py @@ -44,7 +44,7 @@ def test_report_bare_res(request, test_context): @pytest.mark.xfail(raises=ModuleNotFoundError, reason="Requires message_data") def test_report_legacy(caplog, request, tmp_path, test_context): - """Legacy reporting can be invoked through :func:`.report()`.""" + """Legacy reporting can be invoked via :func:`message_ix_models.report.report`.""" # Create a target scenario scenario = testing.bare_res(request, test_context, solved=False) test_context.set_scenario(scenario) diff --git a/message_ix_models/tests/util/test_config.py b/message_ix_models/tests/util/test_config.py index d48118e1e3..409d5c1ddc 100644 --- a/message_ix_models/tests/util/test_config.py +++ b/message_ix_models/tests/util/test_config.py @@ -133,6 +133,6 @@ def test_replace(self, c): assert "baz" == result.foo_2 def test_update(self, c): - """:meth:`.update` raises AttributeError.""" + """:meth:`.ConfigHelper.update` raises AttributeError.""" with pytest.raises(AttributeError): c.update(foo_4="") diff --git a/message_ix_models/tools/exo_data.py b/message_ix_models/tools/exo_data.py index b146fd88b4..d36faa0bd7 100644 --- a/message_ix_models/tools/exo_data.py +++ b/message_ix_models/tools/exo_data.py @@ -16,6 +16,7 @@ "SOURCES", "DemoSource", "ExoDataSource", + "iamc_like_data_for_query", "prepare_computer", "register_source", ] @@ -78,19 +79,10 @@ def prepare_computer( ) -> Tuple[Key, ...]: """Prepare `c` to compute GDP, population, or other exogenous data. - Returns a tuple of keys. The first, like ``{m}:n-y``, triggers the following - computations: - - 1. Load data by invoking a :class:`ExoDataSource`. - 2. Aggregate on the ``n`` (``node``) dimension according to :attr:`Config.regions`. - 3. Interpolate on the ``y`` (``year``) dimension according to :attr:`Config.years`. - - Additional key(s) include: - - - ``{m}:n-y:y0 indexed``: same as ``{m}:n-y``, indexed to values as of ``y0``, that - is, the first model year. - - .. todo:: Extend to also prepare to compute values indexed to a particular ``n``. + Check each :class:`ExoDataSource` in :data:`SOURCES` to determine whether it + recognizes and can handle `source` and `source_kw`. If a source is identified, add + tasks to `c` that retrieve and process data into a :class:`.Quantity` with, at + least, dimensions :math:`(n, y)`. Parameters ---------- @@ -103,6 +95,12 @@ def prepare_computer( returned. If the key "measure" is present, it **must** be one of :data:`MEASURES`. + strict : bool, *optional* + Raise an exception if any of the keys to be added already exist. + + Returns + ------- + tuple of .Key """ # Handle arguments source_kw = source_kw or dict() @@ -172,7 +170,7 @@ def prepare_computer( def register_source(cls: Type[ExoDataSource]) -> Type[ExoDataSource]: - """Register `cls` as a source of exogenous data.""" + """Register :class:`.ExoDataSource` `cls` as a source of exogenous data.""" if cls.id in SOURCES: raise ValueError(f"{SOURCES[cls.id]} already registered for id {cls.id!r}") SOURCES[cls.id] = cls diff --git a/message_ix_models/util/context.py b/message_ix_models/util/context.py index 956b7955c9..60c915902e 100644 --- a/message_ix_models/util/context.py +++ b/message_ix_models/util/context.py @@ -349,8 +349,8 @@ def handle_cli_args( ): """Handle command-line arguments. - May update the :attr:`.Config.local_data`, :attr:`.Config.platform_info`, - :attr:`.Config.scenario_info`, and/or :attr:`.url` settings. + May update the :attr:`.Config.local_data`, :attr:`~.Config.platform_info`, + :attr:`~.Config.scenario_info`, and/or :attr:`~.Config.url` settings. """ self.core.verbose = verbose diff --git a/message_ix_models/util/scenarioinfo.py b/message_ix_models/util/scenarioinfo.py index 80ddc3f801..8025ae1c03 100644 --- a/message_ix_models/util/scenarioinfo.py +++ b/message_ix_models/util/scenarioinfo.py @@ -21,18 +21,19 @@ @dataclass(kw_only=True) class ScenarioInfo: - """Information about a :class:`~message_ix.Scenario` object. + """Information about a |Scenario| object. Code that prepares data for a target Scenario can accept a ScenarioInfo instance. - This avoids the need to load a Scenario, which can be slow under some conditions. + This avoids the need to create or load an actual Scenario, which can be slow under + some conditions. - ScenarioInfo objects can also be used (e.g. by :func:`.apply_spec`) to describe the - contents of a Scenario *before* it is created. + ScenarioInfo objects can also be used (for instance, by :func:`.apply_spec`) to + describe the contents of a Scenario *before* it is created. ScenarioInfo objects have the following convenience attributes: .. autosummary:: - set + ~ScenarioInfo.set io_units is_message_macro N @@ -46,17 +47,32 @@ class ScenarioInfo: scenario_obj : message_ix.Scenario If given, :attr:`.set` is initialized from this existing scenario. + Examples + -------- + Iterating over an instance gives "model", "scenario", "version" and the values of + the respective attributes: + >>> si = ScenarioInfo.from_url("model name/scenario name#123") + >>> dict(si) + {'model': 'model name', 'scenario': 'scenario name', 'version': 123} + See also -------- .Spec """ + # Parameters for initialization only scenario_obj: InitVar[Optional["Scenario"]] = field(default=None, kw_only=False) empty: InitVar[bool] = False platform_name: Optional[str] = None + + #: Model name; equivalent to :attr:`.TimeSeries.model`. model: Optional[str] = None + + #: Scenario name; equivalent to :attr:`.TimeSeries.scenario`. scenario: Optional[str] = None + + #: Scenario version; equivalent to :attr:`.TimeSeries.version`. version: Optional[int] = None #: Elements of :mod:`ixmp`/:mod:`message_ix` sets. @@ -170,7 +186,10 @@ def url(self, value): @property def path(self) -> str: - """A valid path name similar to :attr:`url`.""" + """A valid file system path name similar to :attr:`url`. + + Characters invalid in Windows paths are replaced with "_". + """ from functools import reduce return reduce(lambda s, e: e[0].sub(e[1], s), self._path_re, self.url) From 25d6a0b7c6ed3c15f5cf8e904c9a53e959aed971 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Fri, 13 Oct 2023 11:30:25 +0200 Subject: [PATCH 48/56] Make test_cli_techs() insensitive to trailing output --- message_ix_models/tests/model/test_structure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/message_ix_models/tests/model/test_structure.py b/message_ix_models/tests/model/test_structure.py index 8406726859..8c0fef4777 100644 --- a/message_ix_models/tests/model/test_structure.py +++ b/message_ix_models/tests/model/test_structure.py @@ -224,7 +224,7 @@ def test_cli_techs(session_context, mix_models_cli): result = mix_models_cli.assert_exit_0(["techs"]) # Result test - assert result.output.endswith("[5 rows x 8 columns]\n") + assert "\n[5 rows x 8 columns]\n" in result.output # Path to the temporary file written by the command path = Path(re.search("Write to (.*.csv)", result.output)[1]) From 4e4376f03c606f4e82dfcdd60e575c5846934649 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Fri, 13 Oct 2023 11:49:38 +0200 Subject: [PATCH 49/56] Add .report.sim to docs, whatsnew --- doc/api/report/index.rst | 10 +++++++++- doc/whatsnew.rst | 5 +++-- message_ix_models/report/sim.py | 8 ++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/doc/api/report/index.rst b/doc/api/report/index.rst index f6ef88e16c..2773575212 100644 --- a/doc/api/report/index.rst +++ b/doc/api/report/index.rst @@ -172,7 +172,7 @@ Utilities Command-line interface ----------------------- +====================== .. currentmodule:: message_ix_models.report.cli .. automodule:: message_ix_models.report.cli @@ -206,3 +206,11 @@ Command-line interface -o, --output PATH Write output to file instead of console. --from-file FILE Report multiple Scenarios listed in FILE. --help Show this message and exit. + +Testing +======= + + +.. currentmodule:: message_ix_models.report.sim +.. automodule:: message_ix_models.report.sim + :members: diff --git a/doc/whatsnew.rst b/doc/whatsnew.rst index 37e26c0f84..954fe43970 100644 --- a/doc/whatsnew.rst +++ b/doc/whatsnew.rst @@ -14,8 +14,9 @@ Next release - New :class:`.report.Config` class collecting recognized settings for the module. - :py:`context["report"]` always exists as an instance of :class:`.report.Config`. - - New submodule :class:`.report.plot` with base class and 5 plots of time-series data stored on Scenarios (:pull:`125`). - - New operator :func:`.filter_ts` (:pull:`125`). + - New submodule :class:`.report.plot` with base class and 5 plots of time-series data stored on Scenarios. + - Submodule :class:`.report.sim` provides :func:`add_simulated_solution` for testing reporting configuration. + - New operator :func:`.filter_ts`. - New reusable command-line option :program:`--urls-from-file` in :mod:`.util.click` (:pull:`125`). - Add `pyarrow `_ to dependencies (:pull:`125`). diff --git a/message_ix_models/report/sim.py b/message_ix_models/report/sim.py index 55d8280218..1aa678796a 100644 --- a/message_ix_models/report/sim.py +++ b/message_ix_models/report/sim.py @@ -1,3 +1,4 @@ +"""Simulated solution data for testing :mod:`~message_ix_models.report`.""" import logging from collections import ChainMap, defaultdict from collections.abc import Mapping @@ -16,6 +17,13 @@ from message_ix_models import ScenarioInfo from message_ix_models.util._logging import mark_time, silence_log +__all__ = [ + "SIMULATE_ITEMS", + "add_simulated_solution", + "data_from_file", + "simulate_qty", +] + log = logging.getLogger(__name__) # Copied and expanded from message_ix.models.MESSAGE_ITEMS, where these entries are From 41df7a67d0b861ecafa2b6170307446b2873e350 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Fri, 13 Oct 2023 11:53:44 +0200 Subject: [PATCH 50/56] Mark test_prepare_reporter per version compat --- message_ix_models/tests/test_report.py | 1 + 1 file changed, 1 insertion(+) diff --git a/message_ix_models/tests/test_report.py b/message_ix_models/tests/test_report.py index c4b2d1e70a..07fbfd471c 100644 --- a/message_ix_models/tests/test_report.py +++ b/message_ix_models/tests/test_report.py @@ -253,6 +253,7 @@ def test_add_simulated_solution(test_context, test_data_path): assert np.isclose(79.76478, value.item()) +@MARK[0] def test_prepare_reporter(test_context): rep = ss_reporter() N = len(rep.graph) From 80464da86f491c547c9dc17ae9ac923ac1637d10 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Mon, 16 Oct 2023 11:23:46 +0200 Subject: [PATCH 51/56] Work around python/mypy bug with unreleased fix --- .pre-commit-config.yaml | 2 +- message_ix_models/util/config.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1ebb6bc72a..ef90e77295 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: language: python entry: bash -c ". ${PRE_COMMIT_MYPY_VENV:-/dev/null}/bin/activate 2>/dev/null; mypy $0 $@" additional_dependencies: - - "mypy <1.5" + - mypy - pytest - sdmx1 - types-PyYAML diff --git a/message_ix_models/util/config.py b/message_ix_models/util/config.py index e037f42b61..a467736e93 100644 --- a/message_ix_models/util/config.py +++ b/message_ix_models/util/config.py @@ -99,7 +99,9 @@ def read_file(self, path: Path, fail="raise") -> None: # Use name manipulation on the attribute value also value = existing.replace(**value) elif not isinstance(existing, type): - value = replace(existing, **value) + # https://github.com/python/mypy/issues/15843 + # TODO Check that fix is available in mypy 1.7.x; remove + value = replace(existing, **value) # type: ignore [misc] setattr(self, key, value) def replace(self, **kwargs): From 347d25109ef224a218079318386c21b7af1ff4b4 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Mon, 16 Oct 2023 11:24:20 +0200 Subject: [PATCH 52/56] Work around #131 --- message_ix_models/tests/model/test_snapshot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/message_ix_models/tests/model/test_snapshot.py b/message_ix_models/tests/model/test_snapshot.py index dcf80c158d..4c50d3619d 100644 --- a/message_ix_models/tests/model/test_snapshot.py +++ b/message_ix_models/tests/model/test_snapshot.py @@ -35,6 +35,7 @@ def unpacked_snapshot_data(test_context, request): shutil.copytree(snapshot_data_path, dest, dirs_exist_ok=True) +@pytest.mark.xfail(reason="https://github.com/iiasa/message-ix-models/issues/131") @pytest.mark.xfail( condition=version("message_ix") < "3.5", raises=NotImplementedError, From b675bcea75dc2e8d05b30f3efdcf4b317eda5ca6 Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Mon, 16 Oct 2023 11:25:47 +0200 Subject: [PATCH 53/56] Warn, don't raise for unknown exo_data measure --- message_ix_models/tests/tools/test_exo_data.py | 2 +- message_ix_models/tools/exo_data.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/message_ix_models/tests/tools/test_exo_data.py b/message_ix_models/tests/tools/test_exo_data.py index 086bd2003f..bf3d488e86 100644 --- a/message_ix_models/tests/tools/test_exo_data.py +++ b/message_ix_models/tests/tools/test_exo_data.py @@ -45,7 +45,7 @@ def test_prepare_computer(test_context, regions, N_n): def test_prepare_computer_exc(test_context): c = Computer() - with pytest.raises(ValueError, match="must be one of"): + with pytest.raises(ValueError, match="No source found that can handle"): prepare_computer(test_context, c, "test s1", dict(measure="FOO")) with pytest.raises(ValueError, match="No source found that can handle"): diff --git a/message_ix_models/tools/exo_data.py b/message_ix_models/tools/exo_data.py index d36faa0bd7..8635124261 100644 --- a/message_ix_models/tools/exo_data.py +++ b/message_ix_models/tools/exo_data.py @@ -1,4 +1,5 @@ """Generic tools for working with exogenous data sources.""" +import logging from abc import ABC, abstractmethod from operator import itemgetter from pathlib import Path @@ -21,6 +22,8 @@ "register_source", ] +log = logging.getLogger(__name__) + #: Supported measures. #: #: .. todo:: Store this in a separate code list or concept scheme. @@ -52,6 +55,8 @@ def __init__(self, source: str, source_kw: Mapping) -> None: - Transform these into other values, for instance by mapping certain values to others, applying regular expressions, or other operations. - Store those values as instance attributes for use in :meth:`__call__`, below. + - Log messages that give information that may help to debug a + :class:`ValueError` for `source` or `source_kw` that cannot be handled. It **should not** actually load data or perform any time- or memory-intensive operations. @@ -101,12 +106,17 @@ def prepare_computer( Returns ------- tuple of .Key + + Raises + ------ + ValueError + if no source is available which can handle `source` and `source_kw`. """ # Handle arguments source_kw = source_kw or dict() if measure := source_kw.get("measure"): if measure not in MEASURES: - raise ValueError( + log.warning( f"source_kw 'measure' must be one of {MEASURES}; got {measure!r}" ) else: From 0d0ad683c6abe2c13b4805a3ff757f12cc4fd80e Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Mon, 16 Oct 2023 11:53:57 +0200 Subject: [PATCH 54/56] Correct typo in SSPOriginal docstring --- message_ix_models/project/ssp/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/message_ix_models/project/ssp/data.py b/message_ix_models/project/ssp/data.py index 8d58635811..2a585eb53b 100644 --- a/message_ix_models/project/ssp/data.py +++ b/message_ix_models/project/ssp/data.py @@ -34,7 +34,7 @@ class SSPOriginal(ExoDataSource): - "model": one of: - - IIASA POP + - IIASA GDP - IIASA-WiC POP - NCAR - OECD Env-Growth From 4cacde501400fa02d4c4dfffcc11cb14c26ef5ea Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Mon, 16 Oct 2023 12:01:13 +0200 Subject: [PATCH 55/56] Make test_default_path_cb() insensitive to trailing output --- message_ix_models/tests/util/test_click.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/message_ix_models/tests/util/test_click.py b/message_ix_models/tests/util/test_click.py index 81d9bb19b2..f4613df0e7 100644 --- a/message_ix_models/tests/util/test_click.py +++ b/message_ix_models/tests/util/test_click.py @@ -39,7 +39,7 @@ def func(ctx, rep_out_path): result = mix_models_cli.assert_exit_0(cmd) # The value was stored on, and retrieved from, `ctx` - assert f"{expected}\n" == result.output + assert result.output.startswith(f"{expected}\n") def test_store_context(mix_models_cli): From 6a0236d4bb3c05d7081af1814c7dca49ec5db64d Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Mon, 16 Oct 2023 15:40:37 +0200 Subject: [PATCH 56/56] Include parent in codelist_to_groups() --- message_ix_models/report/computations.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/message_ix_models/report/computations.py b/message_ix_models/report/computations.py index 2aaf42338b..e85070d874 100644 --- a/message_ix_models/report/computations.py +++ b/message_ix_models/report/computations.py @@ -43,12 +43,14 @@ def codelist_to_groups( The returned value is suitable for use with :func:`genno.computations.aggregate`. If this is a list of nodes per :func:`.get_codes`, then the mapping is from regions - to the ISO 3166-1 alpha-3 codes of the countries within each region. + to the ISO 3166-1 alpha-3 codes of the countries within each region. The code for + the region itself is also included in the values to be aggregated, so that already- + aggregated data will pass through. """ groups = dict() for code in filter(lambda c: len(c.child), codes): - groups[code.id] = list(map(str, code.child)) + groups[code.id] = [code.id] + list(map(str, code.child)) return {dim: groups}