Skip to content

Add CORDEX-CMIP5 evaluation recipes - #4568

Merged
valeriupredoi merged 2 commits into
mainfrom
cordex-recipes-final
Jul 29, 2026
Merged

Add CORDEX-CMIP5 evaluation recipes#4568
valeriupredoi merged 2 commits into
mainfrom
cordex-recipes-final

Conversation

@ghossh

@ghossh ghossh commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Continuation from #4199

Description

This pull request adds recipes to demonstrate that ESMValTool is now capable of handling CORDEX data.

Use the esmvalcore-dev environment to run the recipes.

examples/recipe_map_CORDEX-CMIP5.yml

recipe_cordex-cmip5.yml plots maps for the CORDEX data and, if an observational dataset is present that is marked with the facet reference_for_monitor_diags: true, computes the bias against it. The maps are useful to visually inspect the data and detect issues in the values or the grid. Some example plots that can be produced are the following (with reference_for_monitor_diags: true on the observational data):

image

and (without reference_for_monitor_diags: true on the observational data):

image

All figures with comparison to CCI data are available at https://esmvaltool.dkrz.de/shared/esmvaltool/cordex/recipe_map_CORDEX-CMIP5_20260713_151914/ and figures with individual plots are available at https://esmvaltool.dkrz.de/shared/esmvaltool/cordex/recipe_map_CORDEX-CMIP5_20260714_084936/

Code to generate the recipe:

Details
"""Script to generate a recipe that plots maps of CORDEX model vs reference data."""

import copy

from esmvalcore._recipe.writer import to_yaml
from esmvalcore.config import CFG
from esmvalcore.dataset import Dataset, datasets_to_recipe


def main() -> None:
    """Make a recipe with all CORDEX data we can find."""
    # Make sure to configure the data sources before running this script.
    # To configure ESGF as a data source, run:
    # `esmvaltool config copy data-esmvalcore-esgf.yml`
    CFG["search_data"] = "complete"

    # Set up the preprocessors.
    preprocessor = {
        "custom_order": True,
        "monthly_statistics": {
            "operator": "mean",
        },
        "climate_statistics": {
            "period": "full",
            "operator": "mean",
        },
        "mask_landsea": {
            "mask_out": "sea",
        },
        "regrid": {
            "target_grid": "EUR-11",
            "scheme": "linear",
        },
        "convert_units": {
            "units": "degC",
        },
        "multi_model_statistics": {
            "span": "overlap",
            "statistics": ["mean", "median"],
            "exclude": ["reference_dataset"],
        },
    }
    land_preprocessor = copy.deepcopy(preprocessor)
    land_preprocessor["mask_landsea"]["mask_out"] = "sea"
    sea_preprocessor = copy.deepcopy(preprocessor)
    sea_preprocessor["mask_landsea"]["mask_out"] = "land"
    sea_preprocessor["align_metadata"] = {
        "target_project": "CORDEX",
        "target_mip": "day",
        "target_short_name": "ts",
    }
    preprocessor.pop("mask_landsea")
    preprocessor.pop("convert_units")
    sic_preprocessor = copy.deepcopy(preprocessor)
    sic_preprocessor["align_metadata"] = {
        "target_project": "CORDEX",
        "target_mip": "day",
        "target_short_name": "sic",
    }

    documentation = {
        "title": "CORDEX-CMIP5 maps",
        "description": """This is a recipe that generates maps for CORDEX-CMIP5 models. It computes the climatology and plots the bias against an ESA CCI dataset.""",
        "authors": ["loosveldt-tomas_saskia", "andela_bouwe", "ghosh_supriyo"],
        "maintainer": ["ghosh_supriyo", "andela_bouwe"],
    }

    # Define the datasets to include in the recipe.
    reference_datasets = {
        "clivi": Dataset(
            short_name="clivi",
            mip="Amon",
            project="OBS6",
            tier=2,
            dataset="ESACCI-CLOUD",
            version="v3.0-AVHRR-AMPM",
            type="sat",
        ),
        "clt": Dataset(
            short_name="clt",
            mip="day",
            project="OBS6",
            tier=2,
            dataset="ESACCI-CLOUD",
            version="v3.0-AVHRR-AMPM",
            type="sat",
        ),
        "lwp": Dataset(
            short_name="lwp",
            mip="Amon",
            project="OBS6",
            tier=2,
            dataset="ESACCI-CLOUD",
            version="v3.0-AVHRR-AMPM",
            type="sat",
            derive=False,
        ),
        "prw": Dataset(
            short_name="prw",
            mip="Eday",
            project="OBS6",
            tier=3,
            dataset="ESACCI-WATERVAPOUR",
            version="CDR2-L3-COMBI-05deg-fv3.1",
            type="sat",
        ),
        "rlut": Dataset(
            short_name="rlut",
            mip="Amon",
            project="OBS6",
            tier=2,
            dataset="ESACCI-CLOUD",
            version="v3.0-AVHRR-AMPM",
            type="sat",
        ),
        "rsus": Dataset(
            short_name="rsus",
            mip="Amon",
            project="OBS6",
            tier=2,
            dataset="ESACCI-CLOUD",
            type="sat",
            version="v3.0-AVHRR-AMPM",
        ),
        "sic": Dataset(
            short_name="sic",
            mip="SIday",
            project="OBS6",
            tier=2,
            dataset="ESACCI-SEAICE",
            version="L4-SICONC-RE-SSMI-12.5kmEASE2-fv3.0-NH",
            type="sat",
        ),
        "snw": Dataset(
            short_name="snw",
            mip="day",
            project="OBS6",
            tier=2,
            dataset="ESACCI-SNOW",
            version="v2.0",
            type="sat",
        ),
        "lst": Dataset(
            short_name="ts",
            mip="Amon",
            project="OBS",
            tier=2,
            dataset="ESACCI-LST",
            version="1.00",
            type="sat",
        ),
        "sst": Dataset(
            short_name="tos",
            mip="Oday",
            project="OBS6",
            tier=2,
            dataset="ESACCI-SST",
            version="3.0-L4-analysis",
            type="sat",
        ),
    }
    reference_datasets["lst"].add_supplementary(
        short_name="sftlf",
        skip=True,
    )
    reference_datasets["sst"].add_supplementary(
        short_name="sftof",
        skip=True,
    )
    for variable_group, dataset in reference_datasets.items():
        dataset.set_facet("reference_for_monitor_diags", True, persist=True)
        dataset.set_facet("variable_group", variable_group, persist=False)

    datasets = list(reference_datasets.values())
    for short_name in [
        "clivi",
        "clt",
        "clwvi",  # "lwp" is derived from "clwvi" and "clivi"
        "prw",
        "rsus",
        "rlut",
        "sic",
        "snw",
        "ts",
    ]:
        template = Dataset(
            project="CORDEX",
            mip="day",
            short_name=short_name,
            domain="EUR-11",
            dataset="*",
            exp="historical",
            institute="*",
            rcm_version="*",
            driver="*",
            ensemble="*",
        )
        for dataset in template.from_files():
            if dataset.facets["dataset"] == "RegCM4-6":
                continue  # Data no longer available from Cineca ESGF node. -> Some left on Levante, but would need fixes.
            if dataset.facets["short_name"] == "clwvi":
                # "lwp" is derived from "clwvi" and "clivi", but "clwvi" is
                # not required for the recipe.
                dataset.facets["short_name"] = "lwp"
            if dataset.facets["short_name"] == "ts":
                # "ts" needs to be separated into land and sea surface
                # temperature in order to compare with LST and SST reference
                # datasets.
                dataset.add_supplementary(
                    short_name="sftlf",
                    mip="fx",
                    ensemble="*",
                    exp="*",
                    # Not entirely sure if driver and institute should be
                    # wildcards here or if grids differ between drivers and
                    # institutes.
                    driver="*",
                    institute="*",
                )
                result = next(dataset.from_files())
                if result.supplementaries:
                    if (
                        result.supplementaries[0].facets["driver"]
                        == result.facets["driver"]
                    ):
                        # do not include the supplementary in the recipe explicitly, it will be added automatically.
                        result.supplementaries.clear()
                    lst = result.copy(preprocessor="land")
                    lst.set_facet("variable_group", "lst", persist=False)
                    datasets.append(lst)
                    sst = result.copy(preprocessor="sea")
                    sst.set_facet("variable_group", "sst", persist=False)
                    datasets.append(sst)
            else:
                datasets.append(dataset)

    for dataset in datasets:
        dataset.set_facet(
            "diagnostic",
            dataset.facets.get("variable_group", dataset.facets["short_name"]),
            persist=False,
        )

    recipe = datasets_to_recipe(datasets)
    recipe["documentation"] = documentation
    recipe["preprocessors"] = {
        "default": preprocessor,
        "land": land_preprocessor,
        "sea": sea_preprocessor,
        "sic": sic_preprocessor,
    }

    for diagnostic_name, diagnostic in recipe["diagnostics"].items():
        # Configure reference datasets.
        for variable_group, variable in diagnostic["variables"].items():
            if variable_group in reference_datasets:
                variable["reference_dataset"] = reference_datasets[
                    variable_group
                ].facets["dataset"]

        # Move common facets to the variable level.
        for variable_group, variable in diagnostic["variables"].items():
            variable["project"] = "CORDEX"
            variable["mip"] = "day"
            variable["domain"] = "EUR-11"
            variable["exp"] = "historical"
            variable["ensemble"] = "r1i1p1"
            variable["rcm_version"] = "v1"
            variable["timerange"] = "2003/2005"
            if variable_group == "lwp":
                variable["derive"] = True
            if variable_group == "lst":
                variable["preprocessor"] = "land"
                variable["short_name"] = "ts"
            elif variable_group == "sst":
                variable["preprocessor"] = "sea"
                variable["short_name"] = "ts"
            elif variable_group == "sic":
                variable["preprocessor"] = "sic"
            else:
                variable["preprocessor"] = "default"
            for dataset in diagnostic["additional_datasets"]:
                for key in list(dataset.keys()):
                    if dataset[key] == variable.get(key):
                        dataset.pop(key)

        # Set up the diagnostic script.
        diagnostic["scripts"] = {
            "plot": {
                "plot_folder": "{plot_dir}",
                "plot_filename": "{plot_type}_{variable_group}_{alias}_{mip}",
                "facet_used_for_labels": "alias",
                "script": "monitor/multi_datasets.py",
                "plots": {
                    "map": {
                        "common_cbar": False,
                        "fontsize": 6,
                    },
                },
            },
        }

    # Move datasets that are common to all variables to the top level.
    first_datasets = recipe["diagnostics"]["clivi"]["additional_datasets"]
    for dataset in first_datasets:
        if all(
            dataset
            in diagnostic["additional_datasets"]
            for diagnostic in recipe["diagnostics"].values()
        ):
            recipe["datasets"].append(dataset)
            for diagnostic in recipe["diagnostics"].values():
                diagnostic["additional_datasets"].remove(dataset)

    print(to_yaml(recipe))


if __name__ == "__main__":
    main()

recipe_perfmetrics_CORDEX-CMIP5.yml

Produces the performance metrics plot for several variables in CORDEX and ESA-CCI data, which has been previously examined using the recipe to produce maps to ensure the data is on the correct domain. An attempt has been made to correct issues with the units and to leave out datasets with invalid numbers (e.g. all zeros or of the order of 1e36), but it is not straightforward in all cases what the correct interpretation of the data is. For some models, the data is orders of magnitude removed from the other models and ESA CCI data.

image

The run including provenance and log files is available at https://esmvaltool.dkrz.de/shared/esmvaltool/cordex/recipe_perfmetrics_CORDEX-CMIP5_20260716_083604/

Code to generate the recipe:

Details
"""Script to generate a performance metrics recipe for CORDEX-CMIP5 data."""

import copy
import textwrap

from esmvalcore._recipe.writer import to_yaml
from esmvalcore.config import CFG
from esmvalcore.dataset import Dataset, datasets_to_recipe


def main() -> None:
    """Make a recipe with all CORDEX data we can find."""
    # Make sure to configure the data sources before running this script.
    # To configure ESGF as a data source, run:
    # `esmvaltool config copy data-esmvalcore-esgf.yml`
    CFG["search_data"] = "complete"

    # Set up the documentation and preprocessors.
    preprocessor = {
        "custom_order": True,
        "monthly_statistics": {
            "operator": "mean",
        },
        "climate_statistics": {
            "period": "full",
            "operator": "mean",
        },
        "mask_landsea": {
            "mask_out": "sea",
        },
        "regrid": {
            "target_grid": "EUR-11",
            "scheme": "linear",
        },
        "convert_units": {
            "units": "degC",
        },
        "distance_metric": {
            "metric": "weighted_rmse",
        },
        "multi_model_statistics": {
            "span": "overlap",
            "statistics": [
                {"operator": "mean"},
                {"operator": "median"},
            ],
            "exclude": ["reference_dataset"],
        },
    }
    land_preprocessor = copy.deepcopy(preprocessor)
    land_preprocessor["mask_landsea"]["mask_out"] = "sea"
    sea_preprocessor = copy.deepcopy(preprocessor)
    sea_preprocessor["mask_landsea"]["mask_out"] = "land"
    preprocessor.pop("mask_landsea")
    preprocessor.pop("convert_units")

    documentation = {
        "title": "CORDEX-CMIP5 performance metrics",
        "description": """This is a recipe that computes performance metrics for CORDEX-CMIP5 models.""",
        "authors": ["loosveldt-tomas_saskia", "andela_bouwe", "ghosh_supriyo", "lauer_axel"],
        "maintainer": ["ghosh_supriyo", "andela_bouwe"],
    }

    # Define the datasets to include in the recipe.
    reference_datasets = {
        "clivi": Dataset(
            short_name="clivi",
            mip="Amon",
            project="OBS6",
            tier=2,
            dataset="ESACCI-CLOUD",
            version="v3.0-AVHRR-AMPM",
            type="sat",
        ),
        "clt": Dataset(
            short_name="clt",
            mip="day",
            project="OBS6",
            tier=2,
            dataset="ESACCI-CLOUD",
            version="v3.0-AVHRR-AMPM",
            type="sat",
        ),
        "lwp": Dataset(
            short_name="lwp",
            mip="Amon",
            project="OBS6",
            tier=2,
            dataset="ESACCI-CLOUD",
            version="v3.0-AVHRR-AMPM",
            type="sat",
            derive=False,
        ),
        "prw": Dataset(
            short_name="prw",
            mip="Eday",
            project="OBS6",
            tier=3,
            dataset="ESACCI-WATERVAPOUR",
            version="CDR2-L3-COMBI-05deg-fv3.1",
            type="sat",
        ),
        "rlut": Dataset(
            short_name="rlut",
            mip="Amon",
            project="OBS6",
            tier=2,
            dataset="ESACCI-CLOUD",
            version="v3.0-AVHRR-AMPM",
            type="sat",
        ),
        "rsus": Dataset(
            short_name="rsus",
            mip="Amon",
            project="OBS6",
            tier=2,
            dataset="ESACCI-CLOUD",
            type="sat",
            version="v3.0-AVHRR-AMPM",
        ),
        "sic": Dataset(
            short_name="sic",
            mip="SIday",
            project="OBS6",
            tier=2,
            dataset="ESACCI-SEAICE",
            version="L4-SICONC-RE-SSMI-12.5kmEASE2-fv3.0-NH",
            type="sat",
        ),
        "snw": Dataset(
            short_name="snw",
            mip="day",
            project="OBS6",
            tier=2,
            dataset="ESACCI-SNOW",
            version="v2.0",
            type="sat",
        ),
        "lst": Dataset(
            short_name="ts",
            mip="Amon",
            project="OBS",
            tier=2,
            dataset="ESACCI-LST",
            version="1.00",
            type="sat",
        ),
        "sst": Dataset(
            short_name="tos",
            mip="Oday",
            project="OBS6",
            tier=2,
            dataset="ESACCI-SST",
            version="3.0-L4-analysis",
            type="sat",
        ),
    }
    reference_datasets["lst"].add_supplementary(
        short_name="sftlf",
        skip=True,
    )
    reference_datasets["sst"].add_supplementary(
        short_name="sftof",
        skip=True,
    )
    for variable_group, dataset in reference_datasets.items():
        dataset.set_facet("reference_for_metric", True, persist=True)
        dataset.set_facet("variable_group", variable_group, persist=False)

    datasets = list(reference_datasets.values())
    for short_name in [
        "clivi",
        "clt",
        "clwvi",  # "lwp" is derived from "clwvi" and "clivi"
        "prw",
        "rsus",
        "rlut",
        "sic",
        "snw",
        "ts",
    ]:
        template = Dataset(
            project="CORDEX",
            mip="day",
            short_name=short_name,
            domain="EUR-11",
            dataset="*",
            exp="historical",
            institute="*",
            rcm_version="*",
            driver="*",
            ensemble="*",
        )
        for dataset in template.from_files():
            if dataset.facets["dataset"] == "RegCM4-6":
                continue  # Data no longer available from Cineca ESGF node.
            if (
                dataset.facets["dataset"] == "HIRHAM5"
                and dataset.facets["short_name"] in {"clivi", "prw"}
            ):
                # HIRHAM5 clivi and prw data is much higher than expected.
                continue
            if (
                dataset.facets["dataset"] == "CCLM4-8-17"
                and dataset.facets["short_name"] == "clt"
            ):
                # CCLM4-8-17 clt data is unusable.
                continue
            if (
                dataset.facets["dataset"] == "CCLM4-8-17"
                and dataset.facets["short_name"] == "rsus"
                and dataset.facets["driver"] in {"CNRM-CERFACS-CNRM-CM5", "ICHEC-EC-EARTH", "MOHC-HadGEM2", "MPI-M-MPI-ESM-LR"}
            ):
                # Most CCLM4-8-17 rsus data is unusable.
                continue
            if (
                dataset.facets["dataset"] == "ALADIN63"
                and dataset.facets["short_name"] == "sic"
            ):
                # ALADIN63 sic data is all zeros.
                continue
            if (
                dataset.facets["dataset"] == "RACMO22E"
                and dataset.facets["short_name"] == "snw"
            ):
                # RACMO22E snw data is has a 10 times higher mean than expected.
                continue
            if dataset.facets["short_name"] == "clwvi":
                # "lwp" is derived from "clwvi" and "clivi", but "clwvi" is
                # not required for the recipe.
                dataset.facets["short_name"] = "lwp"
            if dataset.facets["short_name"] == "ts":
                # "ts" needs to be separated into land and sea surface
                # temperature in order to compare with LST and SST reference
                # datasets.
                dataset.add_supplementary(
                    short_name="sftlf",
                    mip="fx",
                    ensemble="*",
                    exp="*",
                    # Not entirely sure if driver and institute should be
                    # wildcards here or if grids differ between drivers and
                    # institutes.
                    driver="*",
                    institute="*",
                )
                result = next(dataset.from_files())
                if result.supplementaries:
                    if (
                        result.supplementaries[0].facets["driver"]
                        == result.facets["driver"]
                    ):
                        # do not include the supplementary in the recipe explicitly, it will be added automatically.
                        result.supplementaries.clear()
                    lst = result.copy(preprocessor="land")
                    lst.set_facet("variable_group", "lst", persist=False)
                    datasets.append(lst)
                    sst = result.copy(preprocessor="sea")
                    sst.set_facet("variable_group", "sst", persist=False)
                    datasets.append(sst)
            else:
                datasets.append(dataset)

    for dataset in datasets:
        dataset.set_facet(
            "diagnostic",
            "diagnostic",
            persist=False,
        )

    recipe = datasets_to_recipe(datasets)
    recipe["documentation"] = documentation
    recipe["preprocessors"] = {
        "default": preprocessor,
        "land": land_preprocessor,
        "sea": sea_preprocessor,
    }

    # Configure reference datasets.
    for variable_group, variable in recipe["diagnostics"]["diagnostic"][
        "variables"
    ].items():
        if variable_group in reference_datasets:
            variable["reference_dataset"] = reference_datasets[
                variable_group
            ].facets["dataset"]

    # Move common facets to the variable level.
    for variable_group, variable in recipe["diagnostics"]["diagnostic"][
        "variables"
    ].items():
        variable["project"] = "CORDEX"
        variable["mip"] = "day"
        variable["domain"] = "EUR-11"
        variable["exp"] = "historical"
        variable["ensemble"] = "r1i1p1"
        variable["rcm_version"] = "v1"
        variable["timerange"] = "2003/2005"
        if variable_group == "lwp":
            variable["derive"] = True
        if variable_group == "lst":
            variable["preprocessor"] = "land"
            variable["short_name"] = "ts"
        elif variable_group == "sst":
            variable["preprocessor"] = "sea"
            variable["short_name"] = "ts"
        else:
            variable["preprocessor"] = "default"
        for dataset in variable["additional_datasets"]:
            for key in list(dataset.keys()):
                if dataset[key] == variable.get(key):
                    dataset.pop(key)

    # Move datasets that are common to all variables to the top level.
    first_datasets = next(
        iter(recipe["diagnostics"]["diagnostic"]["variables"].values())
    )["additional_datasets"]
    for dataset in first_datasets:
        if all(
            dataset
            in recipe["diagnostics"]["diagnostic"]["variables"][var][
                "additional_datasets"
            ]
            for var in recipe["diagnostics"]["diagnostic"]["variables"]
        ):
            recipe["datasets"].append(dataset)
            for var in recipe["diagnostics"]["diagnostic"]["variables"]:
                recipe["diagnostics"]["diagnostic"]["variables"][var][
                    "additional_datasets"
                ].remove(dataset)

    # Set up the diagnostic script.
    recipe["diagnostics"]["diagnostic"]["scripts"] = {
        "portrait": {
            "script": "portrait_plot.py",
            "x_by": "variable_group",
            "y_by": "alias",
            "group_by": "project",
            "figsize": [8, 8],
            "normalize": "centered_median",
            # "default_split": "Ref1",
            "nan_color": "#828282",
            "plot_kwargs": {
                "vmin": -0.5,
                "vmax": +0.5,
            },
            "cbar_kwargs": {
                "label": "Relative RMSE",
                "extend": "both",
                "aspect": 45,
            },
            "matplotlib_rc_params": {
                "ytick.labelsize": 4,
                "xtick.labelsize": 6,
                "axes.labelsize": 12,
                "font.size": 12,
            },
        },
    }

    print(to_yaml(recipe))


if __name__ == "__main__":
    main()

Closes #4198

Link to documentation:
https://esmvaltool--4568.org.readthedocs.build/en/4568/recipes/recipe_perfmetrics.html
https://esmvaltool--4568.org.readthedocs.build/en/4568/input.html


Before you get started

Checklist

It is the responsibility of the author to make sure the pull request is ready to review. The icons indicate whether the item will be subject to the 🛠 Technical or 🧪 Scientific review.

New or updated recipe/diagnostic


To help with the number of pull requests:

@valeriupredoi valeriupredoi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @ghossh - just to add that this contains work initially done by @sloosvel with reviews from both @bouweandela and @axel-lauer 🍺

The work was transferred from #4199 that was fully approved by Bouwe and Axel, so that we can transfer ownership from Saskia to Supriyo; Saskia is no longer a developer at ESMValTool, so we think this is the best approach to ensure further continuation of work on this.

@valeriupredoi
valeriupredoi merged commit 568fe39 into main Jul 29, 2026
8 checks passed
@valeriupredoi
valeriupredoi deleted the cordex-recipes-final branch July 29, 2026 11:15
ehogan added a commit that referenced this pull request Aug 7, 2026
* main: (60 commits)
  Bump actions/stale from 10.4.0 to 11.0.0 (#4581)
  Bump pypa/gh-action-pypi-publish from 1.14.1 to 1.14.2 (#4580)
  Bump mamba-org/setup-micromamba from 3.0.0 to 3.1.0 (#4579)
  Update pixi lockfile (#4578)
  Add provenance information to figures written by ``diag_scripts/landcover/landcover.py`` (#4576)
  Bump actions/setup-python from 6.3.0 to 7.0.0 (#4574)
  Bump actions/checkout from 7.0.0 to 7.0.1 (#4573)
  Bump zizmorcore/zizmor-action from 0.6.0 to 0.6.1 (#4572)
  Bump pypa/gh-action-pypi-publish from 1.14.0 to 1.14.1 (#4571)
  Run install from conda Github Action test also with Python 3.14 (#4569)
  Add CORDEX-CMIP5 evaluation recipes (#4568)
  Update release schedule following 2.15 release (#4567)
  release date
  Add to broken recipes from v2.15.0 (#4559)
  Add to broken recipes from v2.15.0 (#4559)
  Bump zizmorcore/zizmor-action from 0.5.7 to 0.6.0 (#4562)
  core 2.15.0 release
  Pass Python scalars instead of Numpy arrays to functions that expect scalars due to new Numpy version in emergent constraints diagnostic (#4545)
  Fix ocean/diagnostic_seaice type error (#4556)
  Fix typo monthly for day frequency ESACCI-AEROSOL recipe_check_obs.yml (#4553)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ESO4clima CORDEX CMIP5 recipe

2 participants