From db029c82b6c4711964ee94b7c3e79abd744d4262 Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Wed, 26 Mar 2025 23:41:10 +0100 Subject: [PATCH 1/5] Add TCRE metric --- .../metrics/__init__.py | 2 + .../metrics/tcre.py | 111 ++++ .../src/cmip_ref_metrics_esmvaltool/recipe.py | 9 +- .../cmip_ref_metrics_esmvaltool/recipes.txt | 1 + .../tests/unit/metrics/input_files_tcre.json | 496 ++++++++++++++++++ .../tests/unit/metrics/test_tcre.py | 88 ++++ 6 files changed, 704 insertions(+), 3 deletions(-) create mode 100644 packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py create mode 100644 packages/ref-metrics-esmvaltool/tests/unit/metrics/input_files_tcre.json create mode 100644 packages/ref-metrics-esmvaltool/tests/unit/metrics/test_tcre.py diff --git a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/__init__.py b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/__init__.py index 21ada8194..0542baad4 100644 --- a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/__init__.py +++ b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/__init__.py @@ -3,9 +3,11 @@ from cmip_ref_metrics_esmvaltool.metrics.ecs import EquilibriumClimateSensitivity from cmip_ref_metrics_esmvaltool.metrics.example import GlobalMeanTimeseries from cmip_ref_metrics_esmvaltool.metrics.tcr import TransientClimateResponse +from cmip_ref_metrics_esmvaltool.metrics.tcre import TransientClimateResponseEmissions __all__ = [ "EquilibriumClimateSensitivity", "GlobalMeanTimeseries", "TransientClimateResponse", + "TransientClimateResponseEmissions", ] diff --git a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py new file mode 100644 index 000000000..5b2094370 --- /dev/null +++ b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py @@ -0,0 +1,111 @@ +from pathlib import Path + +import pandas +import xarray + +from cmip_ref_core.constraints import ( + AddSupplementaryDataset, + RequireContiguousTimerange, + RequireFacets, + RequireOverlappingTimerange, +) +from cmip_ref_core.datasets import FacetFilter, SourceDatasetType +from cmip_ref_core.metrics import DataRequirement +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 TransientClimateResponseEmissions(ESMValToolMetric): + """ + Calculate the global mean Transient Climate Response to Cumulative CO2 Emissions. + """ + + name = "Transient Climate Response to Cumulative CO2 Emissions" + slug = "esmvaltool-transient-climate-response-emissions" + base_recipe = "recipe_tcre.yml" + + experiments = ( + "esm-1pctCO2", + "esm-piControl", + ) + data_requirements = ( + DataRequirement( + source_type=SourceDatasetType.CMIP6, + filters=( + FacetFilter( + facets={ + "variable_id": ("tas", "fco2antt"), + "frequency": ("mon",), + "experiment_id": experiments, + }, + ), + ), + group_by=("source_id", "member_id", "grid_label"), + constraints=( + RequireFacets("experiment_id", experiments), + RequireContiguousTimerange(group_by=("instance_id",)), + RequireOverlappingTimerange(group_by=("instance_id",)), + AddSupplementaryDataset.from_defaults("areacella", SourceDatasetType.CMIP6), + ), + ), + ) + + @staticmethod + def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None: + """Update the recipe.""" + # Prepare updated datasets section in recipe. It contains three + # datasets, "tas" and "fco2antt" for the "esm-1pctCO2" and just "tas" + # for the "esm-piControl" experiment. + recipe_variables = dataframe_to_recipe(input_files) + tas_esm_1pctCO2 = next( + ds for ds in recipe_variables["tas"]["additional_datasets"] if ds["exp"] == "esm-1pctCO2" + ) + tas_esm_piControl = next( + ds for ds in recipe_variables["tas"]["additional_datasets"] if ds["exp"] == "esm-piControl" + ) + fco2antt = recipe_variables["fco2antt"]["additional_datasets"] + tas_esm_piControl["timerange"] = tas_esm_1pctCO2["timerange"] + + recipe["diagnostics"]["tcre"]["variables"] = { + "tas_esm-1pctCO2": { + "short_name": "tas", + "preprocessor": "global_annual_mean_anomaly", + "additional_datasets": [tas_esm_1pctCO2], + }, + "tas_esm-piControl": { + "short_name": "tas", + "preprocessor": "global_annual_mean_anomaly", + "additional_datasets": [tas_esm_piControl], + }, + "fco2antt": { + "preprocessor": "global_cumulative_sum", + "additional_datasets": fco2antt, + }, + } + recipe["diagnostics"].pop("barplot") + + @staticmethod + def format_result(result_dir: Path) -> OutputBundle: + """Format the result.""" + tcre_file = result_dir / "work/tcre/calculate_tcre/tcre.nc" + tcre = xarray.open_dataset(tcre_file) + + source_id = tcre.dataset.values[0].decode("utf-8") + cmec_output = { + "DIMENSIONS": { + "model": {source_id: {}}, + "region": {"global": {}}, + "metric": {"tcre": {}}, + "json_structure": [ + "model", + "region", + "metric", + ], + }, + "RESULTS": { + source_id: {"global": {"tcre": float(tcre.tcre.values[0])}}, + }, + } + + return cmec_output diff --git a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/recipe.py b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/recipe.py index f53b8fdd8..3d0ba1624 100644 --- a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/recipe.py +++ b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/recipe.py @@ -113,12 +113,15 @@ def dataframe_to_recipe(files: pd.DataFrame) -> dict[str, Any]: return variables -_ESMVALTOOL_VERSION = "2.11.0" +_ESMVALTOOL_VERSION = "2.13.0.dev10+g7883d411e" +_ESMVALTOOL_COMMIT = _ESMVALTOOL_VERSION.split("+")[1][1:] _RECIPES = pooch.create( path=pooch.os_cache("cmip_ref_metrics_esmvaltool"), - base_url="https://raw.githubusercontent.com/ESMValGroup/ESMValTool/refs/tags/v{version}/esmvaltool/recipes/", - version=_ESMVALTOOL_VERSION, + # TODO: use a released version + # base_url="https://raw.githubusercontent.com/ESMValGroup/ESMValTool/refs/tags/v{version}/esmvaltool/recipes/", + # version=_ESMVALTOOL_VERSION, + base_url=f"https://raw.githubusercontent.com/ESMValGroup/ESMValTool/{_ESMVALTOOL_COMMIT}/esmvaltool/recipes/", env="REF_METRICS_ESMVALTOOL_DATA_DIR", ) _RECIPES.load_registry(importlib.resources.open_binary("cmip_ref_metrics_esmvaltool", "recipes.txt")) diff --git a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/recipes.txt b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/recipes.txt index fc91638a5..0b2a76575 100644 --- a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/recipes.txt +++ b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/recipes.txt @@ -1,3 +1,4 @@ examples/recipe_python.yml ab3f06d269bb2c1368f4dc39da9bcb232fb2adb1fa556ba769e6c16294ffb4a3 recipe_ecs.yml 0cc57034fcb64e32015b4ff949ece5df8cdb8c6f493618b50ceded119fb37918 recipe_tcr.yml 35f9ef035a4e71aff5cac5dd26c49da2162fc00291bf3b0bd16b661b7b2f606b +recipe_tcre.yml 4668e357e00c515a8264ac75cb319ce558289689e10189e6f9e982886c414c94 diff --git a/packages/ref-metrics-esmvaltool/tests/unit/metrics/input_files_tcre.json b/packages/ref-metrics-esmvaltool/tests/unit/metrics/input_files_tcre.json new file mode 100644 index 000000000..21ed719e9 --- /dev/null +++ b/packages/ref-metrics-esmvaltool/tests/unit/metrics/input_files_tcre.json @@ -0,0 +1,496 @@ +[ + { + "start_time":"1850-01-16T12:00:00.000", + "end_time":"1869-12-16T12:00:00.000", + "path":"\/home\/bandela\/climate_data\/CMIP6\/CMIP\/MPI-M\/MPI-ESM1-2-LR\/esm-piControl\/r1i1p1f1\/Amon\/tas\/gn\/v20190815\/tas_Amon_MPI-ESM1-2-LR_esm-piControl_r1i1p1f1_gn_185001-186912.nc", + "activity_id":"CMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":146097.0, + "experiment":"pre-industrial control simulation with CO2 concentration calculated", + "experiment_id":"esm-piControl", + "frequency":"mon", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl-spinup", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"Amon", + "variable_id":"tas", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"air_temperature", + "long_name":"Near-Surface Air Temperature", + "units":"K", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.CMIP.MPI-M.MPI-ESM1-2-LR.esm-piControl.r1i1p1f1.Amon.tas.gn.v20190815" + }, + { + "start_time":"1870-01-16T12:00:00.000", + "end_time":"1889-12-16T12:00:00.000", + "path":"\/home\/bandela\/climate_data\/CMIP6\/CMIP\/MPI-M\/MPI-ESM1-2-LR\/esm-piControl\/r1i1p1f1\/Amon\/tas\/gn\/v20190815\/tas_Amon_MPI-ESM1-2-LR_esm-piControl_r1i1p1f1_gn_187001-188912.nc", + "activity_id":"CMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":146097.0, + "experiment":"pre-industrial control simulation with CO2 concentration calculated", + "experiment_id":"esm-piControl", + "frequency":"mon", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl-spinup", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"Amon", + "variable_id":"tas", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"air_temperature", + "long_name":"Near-Surface Air Temperature", + "units":"K", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.CMIP.MPI-M.MPI-ESM1-2-LR.esm-piControl.r1i1p1f1.Amon.tas.gn.v20190815" + }, + { + "start_time":"1890-01-16T12:00:00.000", + "end_time":"1909-12-16T12:00:00.000", + "path":"\/home\/bandela\/climate_data\/CMIP6\/CMIP\/MPI-M\/MPI-ESM1-2-LR\/esm-piControl\/r1i1p1f1\/Amon\/tas\/gn\/v20190815\/tas_Amon_MPI-ESM1-2-LR_esm-piControl_r1i1p1f1_gn_189001-190912.nc", + "activity_id":"CMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":146097.0, + "experiment":"pre-industrial control simulation with CO2 concentration calculated", + "experiment_id":"esm-piControl", + "frequency":"mon", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl-spinup", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"Amon", + "variable_id":"tas", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"air_temperature", + "long_name":"Near-Surface Air Temperature", + "units":"K", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.CMIP.MPI-M.MPI-ESM1-2-LR.esm-piControl.r1i1p1f1.Amon.tas.gn.v20190815" + }, + { + "start_time":"1910-01-16T12:00:00.000", + "end_time":"1929-12-16T12:00:00.000", + "path":"\/home\/bandela\/climate_data\/CMIP6\/CMIP\/MPI-M\/MPI-ESM1-2-LR\/esm-piControl\/r1i1p1f1\/Amon\/tas\/gn\/v20190815\/tas_Amon_MPI-ESM1-2-LR_esm-piControl_r1i1p1f1_gn_191001-192912.nc", + "activity_id":"CMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":146097.0, + "experiment":"pre-industrial control simulation with CO2 concentration calculated", + "experiment_id":"esm-piControl", + "frequency":"mon", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl-spinup", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"Amon", + "variable_id":"tas", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"air_temperature", + "long_name":"Near-Surface Air Temperature", + "units":"K", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.CMIP.MPI-M.MPI-ESM1-2-LR.esm-piControl.r1i1p1f1.Amon.tas.gn.v20190815" + }, + { + "start_time":"1850-01-16T12:00:00.000", + "end_time":"1869-12-16T12:00:00.000", + "path":"\/home\/bandela\/climate_data\/CMIP6\/C4MIP\/MPI-M\/MPI-ESM1-2-LR\/esm-1pctCO2\/r1i1p1f1\/Amon\/fco2antt\/gn\/v20190815\/fco2antt_Amon_MPI-ESM1-2-LR_esm-1pctCO2_r1i1p1f1_gn_185001-186912.nc", + "activity_id":"C4MIP CDRMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":0.0, + "experiment":"emissions driven 1% run", + "experiment_id":"esm-1pctCO2", + "frequency":"mon", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"Amon", + "variable_id":"fco2antt", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"tendency_of_atmosphere_mass_content_of_carbon_dioxide_expressed_as_carbon_due_to_anthropogenic_emission", + "long_name":"Carbon Mass Flux into Atmosphere Due to All Anthropogenic Emissions of CO2", + "units":"kg m-2 s-1", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.C4MIP CDRMIP.MPI-M.MPI-ESM1-2-LR.esm-1pctCO2.r1i1p1f1.Amon.fco2antt.gn.v20190815" + }, + { + "start_time":"1870-01-16T12:00:00.000", + "end_time":"1889-12-16T12:00:00.000", + "path":"\/home\/bandela\/climate_data\/CMIP6\/C4MIP\/MPI-M\/MPI-ESM1-2-LR\/esm-1pctCO2\/r1i1p1f1\/Amon\/fco2antt\/gn\/v20190815\/fco2antt_Amon_MPI-ESM1-2-LR_esm-1pctCO2_r1i1p1f1_gn_187001-188912.nc", + "activity_id":"C4MIP CDRMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":0.0, + "experiment":"emissions driven 1% run", + "experiment_id":"esm-1pctCO2", + "frequency":"mon", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"Amon", + "variable_id":"fco2antt", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"tendency_of_atmosphere_mass_content_of_carbon_dioxide_expressed_as_carbon_due_to_anthropogenic_emission", + "long_name":"Carbon Mass Flux into Atmosphere Due to All Anthropogenic Emissions of CO2", + "units":"kg m-2 s-1", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.C4MIP CDRMIP.MPI-M.MPI-ESM1-2-LR.esm-1pctCO2.r1i1p1f1.Amon.fco2antt.gn.v20190815" + }, + { + "start_time":"1890-01-16T12:00:00.000", + "end_time":"1909-12-16T12:00:00.000", + "path":"\/home\/bandela\/climate_data\/CMIP6\/C4MIP\/MPI-M\/MPI-ESM1-2-LR\/esm-1pctCO2\/r1i1p1f1\/Amon\/fco2antt\/gn\/v20190815\/fco2antt_Amon_MPI-ESM1-2-LR_esm-1pctCO2_r1i1p1f1_gn_189001-190912.nc", + "activity_id":"C4MIP CDRMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":0.0, + "experiment":"emissions driven 1% run", + "experiment_id":"esm-1pctCO2", + "frequency":"mon", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"Amon", + "variable_id":"fco2antt", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"tendency_of_atmosphere_mass_content_of_carbon_dioxide_expressed_as_carbon_due_to_anthropogenic_emission", + "long_name":"Carbon Mass Flux into Atmosphere Due to All Anthropogenic Emissions of CO2", + "units":"kg m-2 s-1", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.C4MIP CDRMIP.MPI-M.MPI-ESM1-2-LR.esm-1pctCO2.r1i1p1f1.Amon.fco2antt.gn.v20190815" + }, + { + "start_time":"1910-01-16T12:00:00.000", + "end_time":"1914-12-16T12:00:00.000", + "path":"\/home\/bandela\/climate_data\/CMIP6\/C4MIP\/MPI-M\/MPI-ESM1-2-LR\/esm-1pctCO2\/r1i1p1f1\/Amon\/fco2antt\/gn\/v20190815\/fco2antt_Amon_MPI-ESM1-2-LR_esm-1pctCO2_r1i1p1f1_gn_191001-191412.nc", + "activity_id":"C4MIP CDRMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":0.0, + "experiment":"emissions driven 1% run", + "experiment_id":"esm-1pctCO2", + "frequency":"mon", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"Amon", + "variable_id":"fco2antt", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"tendency_of_atmosphere_mass_content_of_carbon_dioxide_expressed_as_carbon_due_to_anthropogenic_emission", + "long_name":"Carbon Mass Flux into Atmosphere Due to All Anthropogenic Emissions of CO2", + "units":"kg m-2 s-1", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.C4MIP CDRMIP.MPI-M.MPI-ESM1-2-LR.esm-1pctCO2.r1i1p1f1.Amon.fco2antt.gn.v20190815" + }, + { + "start_time":"1850-01-16T12:00:00.000", + "end_time":"1869-12-16T12:00:00.000", + "path":"\/home\/bandela\/climate_data\/CMIP6\/C4MIP\/MPI-M\/MPI-ESM1-2-LR\/esm-1pctCO2\/r1i1p1f1\/Amon\/tas\/gn\/v20190815\/tas_Amon_MPI-ESM1-2-LR_esm-1pctCO2_r1i1p1f1_gn_185001-186912.nc", + "activity_id":"C4MIP CDRMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":0.0, + "experiment":"emissions driven 1% run", + "experiment_id":"esm-1pctCO2", + "frequency":"mon", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"Amon", + "variable_id":"tas", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"air_temperature", + "long_name":"Near-Surface Air Temperature", + "units":"K", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.C4MIP CDRMIP.MPI-M.MPI-ESM1-2-LR.esm-1pctCO2.r1i1p1f1.Amon.tas.gn.v20190815" + }, + { + "start_time":"1870-01-16T12:00:00.000", + "end_time":"1889-12-16T12:00:00.000", + "path":"\/home\/bandela\/climate_data\/CMIP6\/C4MIP\/MPI-M\/MPI-ESM1-2-LR\/esm-1pctCO2\/r1i1p1f1\/Amon\/tas\/gn\/v20190815\/tas_Amon_MPI-ESM1-2-LR_esm-1pctCO2_r1i1p1f1_gn_187001-188912.nc", + "activity_id":"C4MIP CDRMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":0.0, + "experiment":"emissions driven 1% run", + "experiment_id":"esm-1pctCO2", + "frequency":"mon", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"Amon", + "variable_id":"tas", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"air_temperature", + "long_name":"Near-Surface Air Temperature", + "units":"K", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.C4MIP CDRMIP.MPI-M.MPI-ESM1-2-LR.esm-1pctCO2.r1i1p1f1.Amon.tas.gn.v20190815" + }, + { + "start_time":"1890-01-16T12:00:00.000", + "end_time":"1909-12-16T12:00:00.000", + "path":"\/home\/bandela\/climate_data\/CMIP6\/C4MIP\/MPI-M\/MPI-ESM1-2-LR\/esm-1pctCO2\/r1i1p1f1\/Amon\/tas\/gn\/v20190815\/tas_Amon_MPI-ESM1-2-LR_esm-1pctCO2_r1i1p1f1_gn_189001-190912.nc", + "activity_id":"C4MIP CDRMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":0.0, + "experiment":"emissions driven 1% run", + "experiment_id":"esm-1pctCO2", + "frequency":"mon", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"Amon", + "variable_id":"tas", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"air_temperature", + "long_name":"Near-Surface Air Temperature", + "units":"K", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.C4MIP CDRMIP.MPI-M.MPI-ESM1-2-LR.esm-1pctCO2.r1i1p1f1.Amon.tas.gn.v20190815" + }, + { + "start_time":"1910-01-16T12:00:00.000", + "end_time":"1914-12-16T12:00:00.000", + "path":"\/home\/bandela\/climate_data\/CMIP6\/C4MIP\/MPI-M\/MPI-ESM1-2-LR\/esm-1pctCO2\/r1i1p1f1\/Amon\/tas\/gn\/v20190815\/tas_Amon_MPI-ESM1-2-LR_esm-1pctCO2_r1i1p1f1_gn_191001-191412.nc", + "activity_id":"C4MIP CDRMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":0.0, + "experiment":"emissions driven 1% run", + "experiment_id":"esm-1pctCO2", + "frequency":"mon", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"Amon", + "variable_id":"tas", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"air_temperature", + "long_name":"Near-Surface Air Temperature", + "units":"K", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.C4MIP CDRMIP.MPI-M.MPI-ESM1-2-LR.esm-1pctCO2.r1i1p1f1.Amon.tas.gn.v20190815" + }, + { + "start_time":null, + "end_time":null, + "path":"\/home\/bandela\/climate_data\/CMIP6\/CMIP\/MPI-M\/MPI-ESM1-2-LR\/esm-piControl\/r1i1p1f1\/fx\/areacella\/gn\/v20190815\/areacella_fx_MPI-ESM1-2-LR_esm-piControl_r1i1p1f1_gn.nc", + "activity_id":"CMIP", + "branch_method":"standard", + "branch_time_in_child":0.0, + "branch_time_in_parent":146097.0, + "experiment":"pre-industrial control simulation with CO2 concentration calculated", + "experiment_id":"esm-piControl", + "frequency":"fx", + "grid":"gn", + "grid_label":"gn", + "institution_id":"MPI-M", + "nominal_resolution":"250 km", + "parent_activity_id":"CMIP", + "parent_experiment_id":"esm-piControl-spinup", + "parent_source_id":"MPI-ESM1-2-LR", + "parent_time_units":"days since 1850-1-1 00:00:00", + "parent_variant_label":"r1i1p1f1", + "product":"model-output", + "realm":"atmos", + "source_id":"MPI-ESM1-2-LR", + "source_type":"AOGCM BGC", + "sub_experiment":"none", + "sub_experiment_id":"none", + "table_id":"fx", + "variable_id":"areacella", + "variant_label":"r1i1p1f1", + "member_id":"r1i1p1f1", + "standard_name":"cell_area", + "long_name":"Grid-Cell Area for Atmospheric Grid Variables", + "units":"m2", + "vertical_levels":1, + "init_year":null, + "version":"v20190815", + "instance_id":"CMIP6.CMIP.MPI-M.MPI-ESM1-2-LR.esm-piControl.r1i1p1f1.fx.areacella.gn.v20190815" + } +] diff --git a/packages/ref-metrics-esmvaltool/tests/unit/metrics/test_tcre.py b/packages/ref-metrics-esmvaltool/tests/unit/metrics/test_tcre.py new file mode 100644 index 000000000..6d7bb36d6 --- /dev/null +++ b/packages/ref-metrics-esmvaltool/tests/unit/metrics/test_tcre.py @@ -0,0 +1,88 @@ +from pathlib import Path + +import numpy as np +import pandas +import xarray as xr +from cmip_ref_metrics_esmvaltool.metrics import TransientClimateResponseEmissions +from cmip_ref_metrics_esmvaltool.recipe import load_recipe + + +def test_update_recipe(): + # Insert the following code in ZeroEmissionCommitment.update_recipe to + # save an example input dataframe: + # input_files.to_json(Path("input_files_tcre.json"), orient='records', indent=4, date_format="iso") + input_files = pandas.read_json(Path(__file__).parent / "input_files_tcre.json") + recipe = load_recipe("recipe_tcre.yml") + TransientClimateResponseEmissions().update_recipe(recipe, input_files) + assert recipe["diagnostics"]["tcre"]["variables"] == { + "tas_esm-1pctCO2": { + "short_name": "tas", + "preprocessor": "global_annual_mean_anomaly", + "additional_datasets": [ + { + "project": "CMIP6", + "activity": "C4MIP CDRMIP", + "dataset": "MPI-ESM1-2-LR", + "ensemble": "r1i1p1f1", + "institute": "MPI-M", + "exp": "esm-1pctCO2", + "grid": "gn", + "mip": "Amon", + "timerange": "18500116T120000/19141216T120000", + } + ], + }, + "tas_esm-piControl": { + "short_name": "tas", + "preprocessor": "global_annual_mean_anomaly", + "additional_datasets": [ + { + "project": "CMIP6", + "activity": "CMIP", + "dataset": "MPI-ESM1-2-LR", + "ensemble": "r1i1p1f1", + "institute": "MPI-M", + "exp": "esm-piControl", + "grid": "gn", + "mip": "Amon", + "timerange": "18500116T120000/19141216T120000", + } + ], + }, + "fco2antt": { + "preprocessor": "global_cumulative_sum", + "additional_datasets": [ + { + "project": "CMIP6", + "activity": "C4MIP CDRMIP", + "dataset": "MPI-ESM1-2-LR", + "ensemble": "r1i1p1f1", + "institute": "MPI-M", + "exp": "esm-1pctCO2", + "grid": "gn", + "mip": "Amon", + "timerange": "18500116T120000/19141216T120000", + } + ], + }, + } + + +def test_format_output(tmp_path): + tcr = xr.Dataset( + data_vars={ + "tcre": (["dim0"], np.array([1.0], dtype=np.float32)), + }, + coords={ + "dataset": ("dim0", np.array([b"abc"])), + }, + ) + result_dir = tmp_path + subdir = result_dir / "work" / "tcre" / "calculate_tcre" + subdir.mkdir(parents=True) + tcr.to_netcdf(subdir / "tcre.nc") + + output_bundle = TransientClimateResponseEmissions().format_result(result_dir) + + assert isinstance(output_bundle, dict) + assert output_bundle["RESULTS"]["abc"]["global"]["tcre"] == 1.0 From 5eb9d6cc7ffefd2d0af44917bacfce3754f31f4d Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Wed, 26 Mar 2025 23:49:22 +0100 Subject: [PATCH 2/5] Add changelog --- changelog/208.feature.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/208.feature.md diff --git a/changelog/208.feature.md b/changelog/208.feature.md new file mode 100644 index 000000000..b89816cd6 --- /dev/null +++ b/changelog/208.feature.md @@ -0,0 +1 @@ +Added Transient Climate Response to Cumulative CO2 Emissions (TCRE) metric to the ESMValTool metrics package. From 54ab3cd30a34089e2f1b65c3fb2787eb1b29bef0 Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Thu, 27 Mar 2025 00:06:37 +0100 Subject: [PATCH 3/5] Use fco2antt from the esm-1pctCO2 experiment only --- .../src/cmip_ref_metrics_esmvaltool/metrics/tcre.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py index 5b2094370..a4b3e17b9 100644 --- a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py +++ b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py @@ -61,10 +61,12 @@ def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None: tas_esm_1pctCO2 = next( ds for ds in recipe_variables["tas"]["additional_datasets"] if ds["exp"] == "esm-1pctCO2" ) + fco2antt_esm_1pctCO2 = next( + ds for ds in recipe_variables["fco2antt"]["additional_datasets"] if ds["exp"] == "esm-1pctCO2" + ) tas_esm_piControl = next( ds for ds in recipe_variables["tas"]["additional_datasets"] if ds["exp"] == "esm-piControl" ) - fco2antt = recipe_variables["fco2antt"]["additional_datasets"] tas_esm_piControl["timerange"] = tas_esm_1pctCO2["timerange"] recipe["diagnostics"]["tcre"]["variables"] = { @@ -80,7 +82,7 @@ def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None: }, "fco2antt": { "preprocessor": "global_cumulative_sum", - "additional_datasets": fco2antt, + "additional_datasets": [fco2antt_esm_1pctCO2], }, } recipe["diagnostics"].pop("barplot") From 7f141340c1564a06fd4e24b9eeed71a5262d8e5d Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Thu, 27 Mar 2025 13:31:36 +0100 Subject: [PATCH 4/5] Improve filters --- .../src/cmip_ref_metrics_esmvaltool/metrics/tcre.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py index a4b3e17b9..4afa58b86 100644 --- a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py +++ b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py @@ -36,10 +36,17 @@ class TransientClimateResponseEmissions(ESMValToolMetric): FacetFilter( facets={ "variable_id": ("tas", "fco2antt"), - "frequency": ("mon",), + "frequency": "mon", "experiment_id": experiments, }, ), + FacetFilter( + facets={ + "variable_id": "fco2antt", + "experiment_id": "esm-piControl", + }, + keep=False, + ), ), group_by=("source_id", "member_id", "grid_label"), constraints=( From ba900ea25c31f393b03410bce1554ea1ec24122e Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Thu, 27 Mar 2025 13:38:48 +0100 Subject: [PATCH 5/5] Require both variables to be available --- .../src/cmip_ref_metrics_esmvaltool/metrics/tcre.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py index 4afa58b86..d5e1aa5c9 100644 --- a/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py +++ b/packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/tcre.py @@ -29,13 +29,17 @@ class TransientClimateResponseEmissions(ESMValToolMetric): "esm-1pctCO2", "esm-piControl", ) + variables = ( + "tas", + "fco2antt", + ) data_requirements = ( DataRequirement( source_type=SourceDatasetType.CMIP6, filters=( FacetFilter( facets={ - "variable_id": ("tas", "fco2antt"), + "variable_id": variables, "frequency": "mon", "experiment_id": experiments, }, @@ -51,6 +55,7 @@ class TransientClimateResponseEmissions(ESMValToolMetric): group_by=("source_id", "member_id", "grid_label"), constraints=( RequireFacets("experiment_id", experiments), + RequireFacets("variable_id", variables), RequireContiguousTimerange(group_by=("instance_id",)), RequireOverlappingTimerange(group_by=("instance_id",)), AddSupplementaryDataset.from_defaults("areacella", SourceDatasetType.CMIP6),