Skip to content

Commit

Permalink
Merge pull request #66 from nocollier/ilamb3
Browse files Browse the repository at this point in the history
ilamb3 integration
  • Loading branch information
nocollier authored Jan 22, 2025
2 parents 065c88b + 260bc41 commit 37ad1bb
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 5 deletions.
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ mypy: ## run mypy on the codebase
uv run --package cmip_ref mypy packages/ref
uv run --package cmip_ref_metrics_example mypy packages/ref-metrics-example
uv run --package cmip_ref_metrics_esmvaltool mypy packages/ref-metrics-esmvaltool
uv run --package cmip_ref_metrics_ilamb mypy packages/ref-metrics-ilamb

.PHONY: clean
clean: ## clean up temporary files
Expand Down Expand Up @@ -75,14 +76,20 @@ test-metrics-esmvaltool: ## run the tests
pytest packages/ref-metrics-esmvaltool \
-r a -v --doctest-modules --cov=packages/ref-metrics-esmvaltool/src --cov-report=term --cov-append

.PHONY: test-metrics-ilamb
test-metrics-ilamb: ## run the tests
uv run --package cmip_ref_metrics_ilamb \
pytest packages/ref-metrics-ilamb \
-r a -v --doctest-modules --cov=packages/ref-metrics-ilamb/src --cov-report=term --cov-append

.PHONY: test-integration
test-integration: ## run the integration tests
uv run \
pytest tests \
-r a -v

.PHONY: test
test: clean test-core test-ref test-metrics-example test-metrics-esmvaltool test-integration ## run the tests
test: clean test-core test-ref test-metrics-example test-metrics-esmvaltool test-metrics-ilamb test-integration ## run the tests

# Note on code coverage and testing:
# If you want to debug what is going on with coverage, we have found
Expand Down
1 change: 1 addition & 0 deletions changelog/66.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a sample metric to the ilamb metrics package.
5 changes: 5 additions & 0 deletions packages/ref-metrics-ilamb/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# ref-metrics-ilamb

Use [ILAMB](https://github.com/rubisco-sfa/ilamb3) as a REF metrics provider. See [ilamb.org](https://www.ilamb.org/) for more information on project goals and resources.

See [running-metrics-locally](https://cmip-ref.readthedocs.io/en/latest/how-to-guides/running-metrics-locally/) for usage instructions.
40 changes: 40 additions & 0 deletions packages/ref-metrics-ilamb/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[project]
name = "cmip_ref_metrics_ilamb"
version = "0.1.5"
description = "ILAMB metrics provider for the CMIP Rapid Evaluation Framework"
readme = "README.md"
authors = [
{ name = "Nathan Collier", email = "nathaniel.collier@gmail.com" }
]
requires-python = ">=3.10"
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Intended Audience :: Science/Research",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering",
]
dependencies = [
"cmip_ref_core",
"ilamb3"
]

[tool.uv.sources]
ilamb3 = { git = "https://github.com/rubisco-sfa/ilamb3" }

[project.license]
text = "Apache-2.0"

[tool.uv]
dev-dependencies = [
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
14 changes: 14 additions & 0 deletions packages/ref-metrics-ilamb/src/cmip_ref_metrics_ilamb/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
Rapid evaluating CMIP data
"""

import importlib.metadata

from cmip_ref_core.providers import MetricsProvider
from cmip_ref_metrics_ilamb.example import GlobalMeanTimeseries

__version__ = importlib.metadata.version("cmip_ref_metrics_ilamb")

# Initialise the metrics manager and register the example metric
provider = MetricsProvider("ILAMB", __version__)
provider.register(GlobalMeanTimeseries())
107 changes: 107 additions & 0 deletions packages/ref-metrics-ilamb/src/cmip_ref_metrics_ilamb/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from pathlib import Path
from typing import Any

import xarray as xr
from ilamb3.dataset import integrate_space # type: ignore

from cmip_ref_core.datasets import FacetFilter, SourceDatasetType
from cmip_ref_core.metrics import DataRequirement, Metric, MetricExecutionDefinition, MetricResult


def calculate_global_mean_timeseries(input_files: list[Path]) -> xr.Dataset:
"""
Calculate the global mean timeseries for a dataset.
Parameters
----------
input_files
List of input files to calculate the annual mean timeseries.
Returns
-------
:
The annual mean timeseries of the dataset
"""
ds = xr.open_mfdataset(input_files, combine="by_coords", chunks=None, use_cftime=True)
mean: xr.Dataset = integrate_space(ds, "tas", mean=True).to_dataset()
return mean


def format_cmec_output_bundle(dataset: xr.Dataset) -> dict[str, Any]:
"""
Create a simple CMEC output bundle for the dataset.
Parameters
----------
dataset
Processed dataset
Returns
-------
A CMEC output bundle ready to be written to disk
"""
cmec_output = {
"DIMENSIONS": {
"dimensions": {
"source_id": {dataset.attrs["source_id"]: {}},
"region": {"global": {}},
"variable": {"tas": {}},
},
"json_structure": [
"model",
"region",
"statistic",
],
},
"SCHEMA": {
"name": "CMEC-REF",
"package": "example",
"version": "v1",
},
"RESULTS": {
dataset.attrs["source_id"]: {"global": {"tas": 0}},
},
}

return cmec_output


class GlobalMeanTimeseries(Metric):
"""
Calculate the global mean timeseries for a dataset
"""

name = "Global Mean Timeseries"
slug = "global-mean-timeseries"

data_requirements = (
DataRequirement(
source_type=SourceDatasetType.CMIP6,
filters=(
FacetFilter(facets={"variable_id": ("tas", "rsut")}),
FacetFilter(facets={"experiment_id": ("1pctCO2-*", "hist-*")}, keep=False),
),
group_by=("source_id", "variable_id", "experiment_id", "variant_label"),
),
)

def run(self, definition: MetricExecutionDefinition) -> MetricResult:
"""
Run a metric
Parameters
----------
definition
A description of the information needed for this execution of the metric
Returns
-------
:
The result of running the metric.
"""
input_datasets = definition.metric_dataset[SourceDatasetType.CMIP6]
global_mean_timeseries = calculate_global_mean_timeseries(input_files=input_datasets.path.to_list())

return MetricResult.build_from_output_bundle(
definition, format_cmec_output_bundle(global_mean_timeseries)
)
Empty file.
60 changes: 60 additions & 0 deletions packages/ref-metrics-ilamb/tests/unit/test_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import pathlib

import pytest
from cmip_ref_metrics_ilamb.example import GlobalMeanTimeseries, calculate_global_mean_timeseries

from cmip_ref_core.datasets import DatasetCollection, MetricDataset, SourceDatasetType
from cmip_ref_core.metrics import MetricExecutionDefinition


@pytest.fixture
def metric_dataset(cmip6_data_catalog) -> MetricDataset:
selected_dataset = cmip6_data_catalog[
cmip6_data_catalog["instance_id"]
== "CMIP6.ScenarioMIP.CSIRO.ACCESS-ESM1-5.ssp126.r1i1p1f1.Amon.tas.gn.v20210318"
]
return MetricDataset(
{
SourceDatasetType.CMIP6: DatasetCollection(
selected_dataset,
"instance_id",
)
}
)


def test_annual_mean(metric_dataset):
annual_mean = calculate_global_mean_timeseries(metric_dataset["cmip6"].path.to_list())
assert annual_mean.time.size == 572


def test_example_metric(tmp_path, cmip6_data_catalog, mocker):
metric = GlobalMeanTimeseries()
ds = cmip6_data_catalog.groupby("instance_id").first()
output_directory = tmp_path / "output"

mock_calc = mocker.patch("cmip_ref_metrics_ilamb.example.calculate_global_mean_timeseries")
mock_calc.return_value.attrs.__getitem__.return_value = "ABC"

definition = MetricExecutionDefinition(
output_directory=output_directory,
output_fragment=pathlib.Path(metric.slug),
key="global_mean_timeseries",
metric_dataset=MetricDataset(
{
SourceDatasetType.CMIP6: DatasetCollection(ds, "instance_id"),
}
),
)

result = metric.run(definition)

assert mock_calc.call_count == 1

assert str(result.bundle_filename) == "output.json"

output_bundle_path = definition.output_directory / definition.output_fragment / result.bundle_filename

assert result.successful
assert output_bundle_path.exists()
assert output_bundle_path.is_file()
9 changes: 9 additions & 0 deletions packages/ref-metrics-ilamb/tests/unit/test_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from cmip_ref_metrics_ilamb import __version__, provider


def test_provider():
assert provider.name == "ILAMB"
assert provider.slug == "ilamb"
assert provider.version == __version__

assert len(provider) == 1
Loading

0 comments on commit 37ad1bb

Please sign in to comment.