Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test-platform fixture for ixmp4 tests #832

Merged
merged 10 commits into from
Mar 8, 2024
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Bumped minimum version of pandas and numpy to fit **ixmp4**'s requirement.

## Individual updates

- [#832](https://github.com/IAMconsortium/pyam/pull/832) Improve the test-suite for the ixmp4 integration
- [#827](https://github.com/IAMconsortium/pyam/pull/827) Migrate to poetry for project management
- [#830](https://github.com/IAMconsortium/pyam/pull/830) Implement more consistent logging behavior with **ixmp4**
- [#829](https://github.com/IAMconsortium/pyam/pull/829) Add a `pyam.iiasa.platforms()` function for a list of available platforms
Expand Down
21 changes: 18 additions & 3 deletions pyam/ixmp4.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import logging

import ixmp4
import pandas as pd
from ixmp4.core.region import RegionModel
from ixmp4.core.unit import UnitModel

logger = logging.getLogger(__name__)


def read_ixmp4(platform: ixmp4.Platform | str, default_only: bool = True):
"""Read scenario runs from an ixmp4 platform database instance
Expand Down Expand Up @@ -50,12 +54,12 @@ def write_to_ixmp4(platform: ixmp4.Platform | str, df):
The IamDataFrame instance with scenario data
"""
if df.time_domain != "year":
raise NotImplementedError("Only time_domain='year' is supported for now")
raise NotImplementedError("Only time_domain='year' is supported for now.")

if not isinstance(platform, ixmp4.Platform):
platform = ixmp4.Platform(platform)

# TODO: implement a try-except to roll back changes if any error writing to platform
# TODO: implement try-except to roll back changes if any error writing to platform
# depends on https://github.com/iiasa/ixmp4/issues/29
# quickfix: ensure that units and regions exist before writing
for dimension, values, model in [
Expand All @@ -70,10 +74,21 @@ def write_to_ixmp4(platform: ixmp4.Platform | str, df):
f"{dimension}."
)

# The "version" meta-indicator, added when reading from an ixmp4 platform,
# should not be written to the platform
if "version" in df.meta.columns:
logger.warning(
"The `meta.version` column was dropped when writing to the ixmp4 platform."
)
meta = df.meta.drop(columns="version")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this result in an empty data frame except for the model and scenario index?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, added a test-variant for this case

else:
meta = df.meta.copy()
phackstock marked this conversation as resolved.
Show resolved Hide resolved

# Create runs and add IAMC timeseries data and meta indicators
for model, scenario in df.index:
_df = df.filter(model=model, scenario=scenario)

run = platform.runs.create(model=model, scenario=scenario)
run.iamc.add(_df.data)
run.meta = dict(_df.meta.iloc[0])
run.meta = dict(meta.loc[(model, scenario)])
run.set_as_default()
10 changes: 10 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import pandas as pd
import pytest
from httpx import ConnectError
from ixmp4.core import Platform
from ixmp4.data.backend import SqliteTestBackend

from pyam import IamDataFrame, iiasa
from pyam.utils import IAMC_IDX, META_IDX
Expand Down Expand Up @@ -263,6 +265,14 @@ def plot_stackplot_df():
yield df


@pytest.fixture(scope="function")
def test_platform():
platform = Platform(_backend=SqliteTestBackend())
platform.regions.create(name="World", hierarchy="common")
platform.units.create(name="EJ/yr")
yield platform


@pytest.fixture(scope="session")
def conn():
if not IIASA_UNAVAILABLE:
Expand Down
53 changes: 29 additions & 24 deletions tests/test_ixmp4.py
Original file line number Diff line number Diff line change
@@ -1,59 +1,64 @@
import pytest
from ixmp4.core import Platform
from ixmp4.core.region import RegionModel
from ixmp4.core.unit import UnitModel
from ixmp4.data.backend import SqliteTestBackend

import pyam
from pyam import read_ixmp4


def test_to_ixmp4_missing_region_raises(test_df_year):
def test_to_ixmp4_missing_region_raises(test_platform, test_df_year):
"""Writing to platform raises if region not defined"""
platform = Platform(_backend=SqliteTestBackend())
with pytest.raises(RegionModel.NotFound, match="World. Use `Platform.regions."):
test_df_year.to_ixmp4(platform=platform)
test_df_year.rename(region={"World": "foo"}, inplace=True)
with pytest.raises(RegionModel.NotFound, match="foo. Use `Platform.regions."):
test_df_year.to_ixmp4(platform=test_platform)


def test_to_ixmp4_missing_unit_raises(test_df_year):
def test_to_ixmp4_missing_unit_raises(test_platform, test_df_year):
"""Writing to platform raises if unit not defined"""
platform = Platform(_backend=SqliteTestBackend())
platform.regions.create(name="World", hierarchy="common")
with pytest.raises(UnitModel.NotFound, match="EJ/yr. Use `Platform.units."):
test_df_year.to_ixmp4(platform=platform)
test_df_year.rename(unit={"EJ/yr": "foo"}, inplace=True)
with pytest.raises(UnitModel.NotFound, match="foo. Use `Platform.units."):
test_df_year.to_ixmp4(platform=test_platform)


def test_ixmp4_time_not_implemented(test_df):
def test_ixmp4_time_not_implemented(test_platform, test_df):
"""Writing an IamDataFrame with datetime-data is not implemented"""
platform = Platform(_backend=SqliteTestBackend())
platform.regions.create(name="World", hierarchy="common")
platform.units.create(name="EJ/yr")

if test_df.time_domain != "year":
with pytest.raises(NotImplementedError):
test_df.to_ixmp4(platform=platform)
test_df.to_ixmp4(platform=test_platform)


def test_ixmp4_integration(test_df_year):
def test_ixmp4_integration(test_platform, test_df_year):
"""Write an IamDataFrame to the platform"""
platform = Platform(_backend=SqliteTestBackend())
platform.regions.create(name="World", hierarchy="common")
platform.units.create(name="EJ/yr")

# test writing to platform
test_df_year.to_ixmp4(platform=platform)
test_df_year.to_ixmp4(platform=test_platform)

# read only default scenarios (runs) - version number added as meta indicator
obs = read_ixmp4(platform=platform)
obs = read_ixmp4(platform=test_platform)
exp = test_df_year.copy()
exp.set_meta(1, "version") # add version number added from ixmp4
pyam.assert_iamframe_equal(exp, obs)

# make one scenario a non-default scenario, make sure that it is not included
test_platform.runs.get("model_a", "scen_b").unset_as_default()
obs = read_ixmp4(platform=test_platform)
pyam.assert_iamframe_equal(exp.filter(scenario="scen_a"), obs)

# read all scenarios (runs) - version number used as additional index dimension
obs = read_ixmp4(platform=platform, default_only=False)
obs = read_ixmp4(platform=test_platform, default_only=False)
data = test_df_year.data
data["version"] = 1
meta = test_df_year.meta.reset_index()
meta["version"] = 1
exp = pyam.IamDataFrame(data, meta=meta, index=["model", "scenario", "version"])
pyam.assert_iamframe_equal(exp, obs)


def test_ixmp4_reserved_columns(test_platform, test_df_year):
"""Make sure that a 'version' column in `meta` is not written to the platform"""

# test writing to platform with a version-number as meta indicator
test_df_year.set_meta(1, "version") # add version number added from ixmp4
test_df_year.to_ixmp4(platform=test_platform)

assert "version" not in test_platform.runs.get("model_a", "scen_a").meta
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the meta here just yield nothing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meta yields an empty dictionary here, added a test for it