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 ESMValTool Equilibrium Climate Sensitivity recipe #51

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions changelog/51.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added Equilibrium Climate Sensitivity (ECS) to the ESMValTool metrics package.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@
Rapid evaluating CMIP data with ESMValTool.
"""

import importlib.metadata

from ref_core.providers import MetricsProvider

from ref_metrics_esmvaltool.example import GlobalMeanTimeseries

__version__ = importlib.metadata.version("ref_metrics_esmvaltool")
__core_version__ = importlib.metadata.version("ref_core")
from ref_metrics_esmvaltool._version import __version__
from ref_metrics_esmvaltool.metrics import EquilibriumClimateSensitivity, GlobalMeanTimeseries

# Initialise the metrics manager and register the example metric
provider = MetricsProvider("ESMValTool", __version__)
provider.register(GlobalMeanTimeseries())
provider.register(EquilibriumClimateSensitivity())
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import importlib

__version__ = importlib.metadata.version("ref_metrics_esmvaltool")
110 changes: 0 additions & 110 deletions packages/ref-metrics-esmvaltool/src/ref_metrics_esmvaltool/example.py

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""ESMValTool metrics."""

from ref_metrics_esmvaltool.metrics.ecs import EquilibriumClimateSensitivity
from ref_metrics_esmvaltool.metrics.example import GlobalMeanTimeseries

__all__ = [
"EquilibriumClimateSensitivity",
"GlobalMeanTimeseries",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from abc import abstractmethod
from pathlib import Path
from typing import ClassVar

import pandas
from ref_core.datasets import SourceDatasetType
from ref_core.metrics import Metric, MetricExecutionDefinition, MetricResult

from ref_metrics_esmvaltool.recipe import load_recipe, run_recipe
from ref_metrics_esmvaltool.types import OutputBundle, Recipe


class ESMValToolMetric(Metric):
"""ESMValTool Metric base class."""

base_recipe: ClassVar

@staticmethod
@abstractmethod
def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
"""
Update the base recipe for the run.

Parameters
----------
recipe:
The base recipe to update.
input_files:
The dataframe describing the input files.

"""

@staticmethod
@abstractmethod
def format_result(result_dir: Path) -> OutputBundle:
"""
Create a CMEC output bundle for the results.

Parameters
----------
result_dir
Directory containing results from an ESMValTool run.

Returns
-------
A CMEC output bundle.
"""

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_files = definition.metric_dataset[SourceDatasetType.CMIP6].datasets
recipe = load_recipe(self.base_recipe)
self.update_recipe(recipe, input_files)
result_dir = run_recipe(recipe, definition)
output_bundle = self.format_result(result_dir)
return MetricResult.build_from_output_bundle(definition, output_bundle)
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from pathlib import Path

import pandas
import xarray
from ref_core.datasets import FacetFilter, SourceDatasetType
from ref_core.metrics import DataRequirement

from ref_metrics_esmvaltool._version import __version__
from ref_metrics_esmvaltool.metrics.base import ESMValToolMetric
from ref_metrics_esmvaltool.recipe import dataframe_to_recipe
from ref_metrics_esmvaltool.types import OutputBundle, Recipe


class EquilibriumClimateSensitivity(ESMValToolMetric):
"""
Calculate the global mean equilibrium climate sensitivity for a dataset.
"""

name = "Equilibrium Climate Sensitivity"
slug = "esmvaltool-equilibrium-climate-sensitivity"
base_recipe = "recipe_ecs.yml"

data_requirements = (
DataRequirement(
source_type=SourceDatasetType.CMIP6,
filters=(
FacetFilter(
facets={
"variable_id": (
"rlut",
"rsdt",
"rsut",
"tas",
),
"experiment_id": (
"abrupt-4xCO2",
"piControl",
),
},
),
),
# TODO: Select only datasets that have both experiments and all four variables
# TODO: Select only datasets that have a contiguous, shared timerange
# TODO: Add cell areas to the groups
# constraints=(AddCellAreas(),),
group_by=("source_id", "variant_label"),
),
)

@staticmethod
def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
"""Update the recipe."""
# Only run the diagnostic that computes ECS for a single model.
recipe["diagnostics"] = {
"cmip6": {
"description": "Calculate ECS.",
"variables": {
"tas": {
"preprocessor": "spatial_mean",
},
"rtnt": {
"preprocessor": "spatial_mean",
"derive": True,
},
},
"scripts": {
"ecs": {
"script": "climate_metrics/ecs.py",
"calculate_mmm": False,
},
},
},
}

# Prepare updated datasets section in recipe. It contains two
# datasets, one for the "abrupt-4xCO2" and one for the "piControl"
# experiment.
recipe_variables = dataframe_to_recipe(input_files)

# Select a timerange covered by all datasets.
start_times, end_times = [], []
for variable in recipe_variables.values():
for dataset in variable["additional_datasets"]:
start, end = dataset["timerange"].split("/")
start_times.append(start)
end_times.append(end)
timerange = f"{max(start_times)}/{min(end_times)}"

datasets = recipe_variables["tas"]["additional_datasets"]
for dataset in datasets:
dataset["timerange"] = timerange

recipe["datasets"] = datasets

@staticmethod
def format_result(result_dir: Path) -> OutputBundle:
"""Format the result."""
ecs_file = result_dir / "work/cmip6/ecs/ecs.nc"
ecs = xarray.open_dataset(ecs_file)

source_id = ecs.dataset.values[0].decode("utf-8")
cmec_output = {
"DIMENSIONS": {
"dimensions": {
"source_id": {source_id: {}},
"region": {"global": {}},
"variable": {"ecs": {}},
},
"json_structure": [
"model",
"region",
"statistic",
],
},
# Is the schema tracked?
"SCHEMA": {
"name": "CMEC-REF",
"package": "ref_metrics_esmvaltool",
"version": __version__,
},
"RESULTS": {
source_id: {"global": {"ecs": ecs.ecs.values[0]}},
},
}

return cmec_output
Loading
Loading