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 to_ixmp4() method #797

Merged
merged 22 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b5c72e3
Add `to_ixmp4()` method
danielhuppmann Nov 3, 2023
53f0da9
Move ixmp4-methods to own module
danielhuppmann Nov 15, 2023
44db2d5
Check if regions and units exist before writing to platform
danielhuppmann Nov 15, 2023
4bb15dc
Add missing items and and hint to error message
danielhuppmann Nov 15, 2023
391aee6
Add a smoke test
danielhuppmann Nov 15, 2023
e4c9ab7
Merge validation of dimensions
danielhuppmann Nov 15, 2023
db8b88e
Quickfix for numpy-int
danielhuppmann Nov 15, 2023
401207e
Make black
danielhuppmann Nov 15, 2023
08d9151
Add to-do for reading from ixmp4 platform
danielhuppmann Nov 15, 2023
c363e15
Add to release notes
danielhuppmann Nov 15, 2023
d8b65ac
Remove "manual" casting to simple types
danielhuppmann Nov 17, 2023
fa106b8
Clean-up
danielhuppmann Nov 24, 2023
483d7f7
Use new runs API
danielhuppmann Dec 12, 2023
846fb3a
Move release notes to next-release section
danielhuppmann Jan 5, 2024
7c30d97
Bump the dependency (due to the new `runs` API)
danielhuppmann Jan 5, 2024
4d40ba8
Change arg order
danielhuppmann Jan 9, 2024
271476f
Fix the signature
danielhuppmann Jan 9, 2024
77b1821
Merge branch 'main' into ixmp4/to-db
danielhuppmann Feb 22, 2024
5cfd196
Allow string as platform name
danielhuppmann Feb 22, 2024
fae481f
Add explicit `NotImplementedError` for datetime-format
danielhuppmann Feb 22, 2024
afc3574
Use set notation per suggestion by @phackstock
danielhuppmann Feb 22, 2024
0da2f88
Use `getattr()` per suggestion by @phackstock
danielhuppmann Feb 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Next Release
# Next release

- [#813](https://github.com/IAMconsortium/pyam/pull/813) Fix a corner case in region-aggregation with missing data
- [#797](https://github.com/IAMconsortium/pyam/pull/797) Add `to_ixmp4()` method to write to an **ixmp4** platform

# Release v2.1.0

Expand Down
13 changes: 13 additions & 0 deletions pyam/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
from pathlib import Path
from tempfile import TemporaryDirectory

import ixmp4

from pyam.ixmp4 import write_to_ixmp4
from pyam.slice import IamSlice
from pyam.filter import filter_by_time_domain, filter_by_year, filter_by_dt_arg

Expand Down Expand Up @@ -2333,6 +2336,16 @@ def diff(self, mapping, periods=1, append=False):
# append to `self` or return as `IamDataFrame`
return self._finalize(_value, append=append)

def to_ixmp4(self, platform: ixmp4.Platform):
"""Save all scenarios as new default runs in an ixmp4 platform database instance

Parameters
----------
platform : :class:`ixmp4.Platform` or str
The ixmp4 platform database instance to which the scenario data is saved
"""
write_to_ixmp4(platform, self)

def _to_file_format(self, iamc_index):
"""Return a dataframe suitable for writing to a file"""
df = self.timeseries(iamc_index=iamc_index).reset_index()
Expand Down
42 changes: 42 additions & 0 deletions pyam/ixmp4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import ixmp4
from ixmp4.core.region import RegionModel
from ixmp4.core.unit import UnitModel


def write_to_ixmp4(platform: ixmp4.Platform | str, df):
"""Save all scenarios as new default runs in an ixmp4 platform database instance

Parameters
----------
platform : :class:`ixmp4.Platform` or str
The ixmp4 platform database instance to which the scenario data is saved
df : pyam.IamDataFrame
The IamDataFrame instance with scenario data
"""
if df.time_domain != "year":
raise NotImplementedError("Only time_domain='year' is supported for now")

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

Check warning on line 20 in pyam/ixmp4.py

View check run for this annotation

Codecov / codecov/patch

pyam/ixmp4.py#L20

Added line #L20 was not covered by tests

# TODO: implement a 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 [
("regions", df.region, RegionModel),
("units", df.unit, UnitModel),
]:
platform_values = getattr(platform, dimension).tabulate().name.values
if missing := set(values).difference(platform_values):
raise model.NotFound(
", ".join(missing)
+ f". Use `Platform.{dimension}.create()` to add the missing {dimension}."
)

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.set_as_default()
2 changes: 1 addition & 1 deletion pyam/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def _intuit_column_groups(df, index, include_index=False):
elif isinstance(df, pd.DataFrame):
existing_cols = existing_cols.union(df.columns)

# check that there is no column in the timeseries data with reserved names
# check that there is no unnamed column in the timeseries data
if None in existing_cols:
raise ValueError("Unnamed column in timeseries data: None")

Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ python_requires = >=3.10, <3.12
# Please also add a section "Dependency changes" to the release notes
install_requires =
iam-units >= 2020.4.21
ixmp4 >= 0.4.0
ixmp4 >= 0.6.0
numpy >= 1.23.0, < 1.24
# requests included via ixmp4
# httpx[http2] included via ixmp4
Expand Down
35 changes: 35 additions & 0 deletions tests/test_ixmp4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import pytest
from ixmp4.core import Platform
from ixmp4.data.backend import SqliteTestBackend
from ixmp4.core.region import RegionModel
from ixmp4.core.unit import UnitModel


def test_to_ixmp4_missing_region_raises(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)


def test_to_ixmp4_missing_unit_raises(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)


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

if test_df.time_domain == "year":
test_df.to_ixmp4(platform=platform)
phackstock marked this conversation as resolved.
Show resolved Hide resolved
else:
with pytest.raises(NotImplementedError):
test_df.to_ixmp4(platform=platform)

# TODO add test for reading data from ixmp4 platform
danielhuppmann marked this conversation as resolved.
Show resolved Hide resolved
Loading