Skip to content

Commit

Permalink
Add ESMValTool Equilibrium Climate Sensitivity recipe (#51)
Browse files Browse the repository at this point in the history
Co-authored-by: Jared Lewis <jared@jared.kiwi.nz>
  • Loading branch information
bouweandela and lewisjared authored Jan 15, 2025
1 parent b0d2c90 commit 6dd6995
Show file tree
Hide file tree
Showing 15 changed files with 719 additions and 133 deletions.
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,13 +2,11 @@
Rapid evaluating CMIP data with ESMValTool.
"""

import importlib.metadata

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

__version__ = importlib.metadata.version("cmip_ref_metrics_esmvaltool")
from cmip_ref_metrics_esmvaltool._version import __version__
from cmip_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("cmip_ref_metrics_esmvaltool")

This file was deleted.

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

from cmip_ref_metrics_esmvaltool.metrics.ecs import EquilibriumClimateSensitivity
from cmip_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 cmip_ref_core.datasets import SourceDatasetType
from cmip_ref_core.metrics import Metric, MetricExecutionDefinition, MetricResult
from cmip_ref_metrics_esmvaltool.recipe import load_recipe, run_recipe
from cmip_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 cmip_ref_core.datasets import FacetFilter, SourceDatasetType
from cmip_ref_core.metrics import DataRequirement
from cmip_ref_metrics_esmvaltool._version import __version__
from cmip_ref_metrics_esmvaltool.metrics.base import ESMValToolMetric
from cmip_ref_metrics_esmvaltool.recipe import dataframe_to_recipe
from cmip_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": "cmip_ref_metrics_esmvaltool",
"version": __version__,
},
"RESULTS": {
source_id: {"global": {"ecs": ecs.ecs.values[0]}},
},
}

return cmec_output
Loading

0 comments on commit 6dd6995

Please sign in to comment.