climate-ref-esmvaltool 0.5.4__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/PKG-INFO +5 -4
  2. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/pyproject.toml +7 -7
  3. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/__init__.py +2 -2
  4. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/dataset_registry/data.txt +3 -0
  5. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/diagnostics/__init__.py +5 -2
  6. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/diagnostics/base.py +20 -9
  7. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/diagnostics/ecs.py +24 -18
  8. climate_ref_esmvaltool-0.6.0/src/climate_ref_esmvaltool/diagnostics/enso.py +155 -0
  9. climate_ref_esmvaltool-0.5.4/src/climate_ref_esmvaltool/diagnostics/sea_ice_area_seasonal_cycle.py → climate_ref_esmvaltool-0.6.0/src/climate_ref_esmvaltool/diagnostics/sea_ice_area_basic.py +13 -8
  10. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/diagnostics/tcr.py +19 -16
  11. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/diagnostics/tcre.py +18 -13
  12. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/diagnostics/zec.py +8 -13
  13. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/recipe.py +2 -2
  14. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/recipes.txt +4 -2
  15. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/integration/test_diagnostics.py +1 -0
  16. climate_ref_esmvaltool-0.6.0/tests/unit/diagnostics/input_files_enso_characteristics.json +142 -0
  17. climate_ref_esmvaltool-0.6.0/tests/unit/diagnostics/input_files_enso_climatology.json +177 -0
  18. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/test_base.py +1 -1
  19. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/test_ecs.py +14 -4
  20. climate_ref_esmvaltool-0.6.0/tests/unit/diagnostics/test_enso.py +47 -0
  21. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/test_sea_ice_area.py +3 -3
  22. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/test_tcr.py +10 -3
  23. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/test_tcre.py +1 -1
  24. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/test_zec.py +1 -1
  25. climate_ref_esmvaltool-0.6.0/tests/unit/test_provider.py +18 -0
  26. climate_ref_esmvaltool-0.5.4/tests/unit/test_provider.py +0 -17
  27. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/.gitignore +0 -0
  28. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/LICENCE +0 -0
  29. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/NOTICE +0 -0
  30. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/README.md +0 -0
  31. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/_version.py +0 -0
  32. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/diagnostics/climate_at_global_warming_levels.py +0 -0
  33. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/diagnostics/cloud_radiative_effects.py +0 -0
  34. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/diagnostics/example.py +0 -0
  35. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/py.typed +0 -0
  36. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/requirements/conda-lock.yml +0 -0
  37. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/requirements/environment.yml +0 -0
  38. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/src/climate_ref_esmvaltool/types.py +0 -0
  39. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/input_files_climate_at_global_warming_levels.json +0 -0
  40. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/input_files_cloud_radiative_effects.json +0 -0
  41. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/input_files_ecs.json +0 -0
  42. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/input_files_sea_ice_area.json +0 -0
  43. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/input_files_tcr.json +0 -0
  44. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/input_files_tcre.json +0 -0
  45. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/input_files_zec.json +0 -0
  46. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/test_climate_at_global_warming_levels.py +0 -0
  47. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/diagnostics/test_cloud_radiative_effects.py +0 -0
  48. {climate_ref_esmvaltool-0.5.4 → climate_ref_esmvaltool-0.6.0}/tests/unit/test_metrics.py +0 -0
@@ -1,14 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: climate-ref-esmvaltool
3
- Version: 0.5.4
3
+ Version: 0.6.0
4
4
  Summary: ESMValTool diagnostic provider for the Rapid Evaluation Framework
5
- Author-email: ESMValTool development team <esmvaltool-dev@listserv.dfn.de>
6
- License: Apache-2.0
5
+ Author-email: ESMValTool development team <esmvaltool-dev@listserv.dfn.de>, Jared Lewis <jared.lewis@climate-resource.com>
6
+ License-Expression: Apache-2.0
7
7
  License-File: LICENCE
8
8
  License-File: NOTICE
9
- Classifier: Development Status :: 4 - Beta
9
+ Classifier: Development Status :: 3 - Alpha
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: Apache Software License
12
13
  Classifier: Operating System :: OS Independent
13
14
  Classifier: Programming Language :: Python
14
15
  Classifier: Programming Language :: Python :: 3
@@ -1,16 +1,18 @@
1
1
  [project]
2
2
  name = "climate-ref-esmvaltool"
3
- version = "0.5.4"
3
+ version = "0.6.0"
4
4
  description = "ESMValTool diagnostic provider for the Rapid Evaluation Framework"
5
5
  readme = "README.md"
6
6
  authors = [
7
- { name = "ESMValTool development team", email = "esmvaltool-dev@listserv.dfn.de " }
7
+ { name = "ESMValTool development team", email = "esmvaltool-dev@listserv.dfn.de" },
8
+ { name = "Jared Lewis", email = "jared.lewis@climate-resource.com" },
8
9
  ]
10
+ license = "Apache-2.0"
9
11
  requires-python = ">=3.11"
10
12
  classifiers = [
11
- "Development Status :: 4 - Beta",
12
- "Intended Audience :: Developers",
13
+ "Development Status :: 3 - Alpha",
13
14
  "Operating System :: OS Independent",
15
+ "Intended Audience :: Developers",
14
16
  "Intended Audience :: Science/Research",
15
17
  "Programming Language :: Python",
16
18
  "Programming Language :: Python :: 3",
@@ -18,6 +20,7 @@ classifiers = [
18
20
  "Programming Language :: Python :: 3.12",
19
21
  "Programming Language :: Python :: 3.13",
20
22
  "Topic :: Scientific/Engineering",
23
+ "License :: OSI Approved :: Apache Software License",
21
24
  ]
22
25
  dependencies = [
23
26
  "pooch >= 1.8",
@@ -26,9 +29,6 @@ dependencies = [
26
29
  "xarray >= 2023.3.0",
27
30
  ]
28
31
 
29
- [project.license]
30
- text = "Apache-2.0"
31
-
32
32
  [build-system]
33
33
  requires = ["hatchling"]
34
34
  build-backend = "hatchling.build"
@@ -3,7 +3,7 @@ Rapid evaluating CMIP data with ESMValTool.
3
3
  """
4
4
 
5
5
  import climate_ref_esmvaltool.diagnostics
6
- from climate_ref_core.dataset_registry import dataset_registry_manager
6
+ from climate_ref_core.dataset_registry import DATASET_URL, dataset_registry_manager
7
7
  from climate_ref_core.providers import CondaDiagnosticProvider
8
8
  from climate_ref_esmvaltool._version import __version__
9
9
  from climate_ref_esmvaltool.recipe import _ESMVALTOOL_COMMIT
@@ -24,7 +24,7 @@ for _diagnostic_cls_name in climate_ref_esmvaltool.diagnostics.__all__:
24
24
  # Register OBS, OBS6, and raw data
25
25
  dataset_registry_manager.register(
26
26
  "esmvaltool",
27
- "https://pub-b093171261094c4ea9adffa01f94ee06.r2.dev/",
27
+ base_url=DATASET_URL,
28
28
  package="climate_ref_esmvaltool.dataset_registry",
29
29
  resource="data.txt",
30
30
  )
@@ -1,3 +1,4 @@
1
+ ESMValTool/obs4MIPs/GPCP-V2.3/v20180519/pr_GPCP-SG_L3_v2.3_197901-201710.nc 4dd4678b79ef139446c8406da5aae4fed210abb2f2160ef95f6988bf83e4525b
1
2
  ESMValTool/OBS/Tier2/CERES-EBAF/OBS_CERES-EBAF_sat_Ed4.2_Amon_rlut_200003-202311.nc ede887cf2d83c848a0d71316799232e4d717662bd2f78d5aa1fc166b41d9953b
2
3
  ESMValTool/OBS/Tier2/CERES-EBAF/OBS_CERES-EBAF_sat_Ed4.2_Amon_rlutcs_200003-202311.nc e70e3273092edf01527970693271641fc6474d1974887d7d272e7d656bab83c2
3
4
  ESMValTool/OBS/Tier2/CERES-EBAF/OBS_CERES-EBAF_sat_Ed4.2_Amon_rsut_200003-202311.nc e31e648886c4fa9c09686672a06ab18fbba687ff0d6de2891616d4c8b74e215d
@@ -84,3 +85,5 @@ ESMValTool/OBS/Tier2/OSI-450-sh/OBS_OSI-450-sh_reanaly_v3_OImon_sic_201101-20111
84
85
  ESMValTool/OBS/Tier2/OSI-450-sh/OBS_OSI-450-sh_reanaly_v3_OImon_sic_201201-201212.nc 86187c3d1174053f2cba6dad010af49ceab77d368aa9314bf53c330b5f2217b9
85
86
  ESMValTool/OBS/Tier2/OSI-450-sh/OBS_OSI-450-sh_reanaly_v3_OImon_sic_201301-201312.nc 8820353570884b2ef182caaffb5986ed6268bbe199fd867f61b56e798ca01f1a
86
87
  ESMValTool/OBS/Tier2/OSI-450-sh/OBS_OSI-450-sh_reanaly_v3_OImon_sic_201401-201412.nc 7102d0db3dc02c5b0eb0cfe3535ee50171007ef5b43eb9aae1220ac21b0b98e9
88
+ ESMValTool/OBS/Tier2/TROPFLUX/OBS6_TROPFLUX_reanaly_v1_Amon_tauu_197901-201812.nc bf313e661b42341d5090038b501ed1ff09e58201009c3fccfe45b78e116fdd78
89
+ ESMValTool/OBS/Tier2/TROPFLUX/OBS6_TROPFLUX_reanaly_v1_Omon_tos_197901-201812.nc 5f10a5a2aa47f5d21378ad3178bf8e4b577b0ed72ef8402dc04f5ff6fc99ec07
@@ -3,8 +3,9 @@
3
3
  from climate_ref_esmvaltool.diagnostics.climate_at_global_warming_levels import ClimateAtGlobalWarmingLevels
4
4
  from climate_ref_esmvaltool.diagnostics.cloud_radiative_effects import CloudRadiativeEffects
5
5
  from climate_ref_esmvaltool.diagnostics.ecs import EquilibriumClimateSensitivity
6
+ from climate_ref_esmvaltool.diagnostics.enso import ENSOBasicClimatology, ENSOCharacteristics
6
7
  from climate_ref_esmvaltool.diagnostics.example import GlobalMeanTimeseries
7
- from climate_ref_esmvaltool.diagnostics.sea_ice_area_seasonal_cycle import SeaIceAreaSeasonalCycle
8
+ from climate_ref_esmvaltool.diagnostics.sea_ice_area_basic import SeaIceAreaBasic
8
9
  from climate_ref_esmvaltool.diagnostics.tcr import TransientClimateResponse
9
10
  from climate_ref_esmvaltool.diagnostics.tcre import TransientClimateResponseEmissions
10
11
  from climate_ref_esmvaltool.diagnostics.zec import ZeroEmissionCommitment
@@ -12,9 +13,11 @@ from climate_ref_esmvaltool.diagnostics.zec import ZeroEmissionCommitment
12
13
  __all__ = [
13
14
  "ClimateAtGlobalWarmingLevels",
14
15
  "CloudRadiativeEffects",
16
+ "ENSOBasicClimatology",
17
+ "ENSOCharacteristics",
15
18
  "EquilibriumClimateSensitivity",
16
19
  "GlobalMeanTimeseries",
17
- "SeaIceAreaSeasonalCycle",
20
+ "SeaIceAreaBasic",
18
21
  "TransientClimateResponse",
19
22
  "TransientClimateResponseEmissions",
20
23
  "ZeroEmissionCommitment",
@@ -14,7 +14,7 @@ from climate_ref_core.diagnostics import (
14
14
  ExecutionDefinition,
15
15
  ExecutionResult,
16
16
  )
17
- from climate_ref_core.pycmec.metric import CMECMetric
17
+ from climate_ref_core.pycmec.metric import CMECMetric, MetricCV
18
18
  from climate_ref_core.pycmec.output import CMECOutput, OutputCV
19
19
  from climate_ref_esmvaltool.recipe import load_recipe, prepare_climate_data
20
20
  from climate_ref_esmvaltool.types import MetricBundleArgs, OutputBundleArgs, Recipe
@@ -48,7 +48,7 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
48
48
  execution_dataset: ExecutionDatasetCollection,
49
49
  metric_args: MetricBundleArgs,
50
50
  output_args: OutputBundleArgs,
51
- ) -> tuple[MetricBundleArgs, OutputBundleArgs]:
51
+ ) -> tuple[CMECMetric, CMECOutput]:
52
52
  """
53
53
  Update the arguments needed to create a CMEC diagnostic and output bundle.
54
54
 
@@ -67,7 +67,7 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
67
67
  -------
68
68
  The arguments needed to create a CMEC diagnostic and output bundle.
69
69
  """
70
- return metric_args, output_args
70
+ return CMECMetric.model_validate(metric_args), CMECOutput.model_validate(output_args)
71
71
 
72
72
  def build_cmd(self, definition: ExecutionDefinition) -> Iterable[str]:
73
73
  """
@@ -101,15 +101,17 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
101
101
  config = {
102
102
  "drs": {
103
103
  "CMIP6": "ESGF",
104
+ "obs4MIPs": "ESGF",
104
105
  },
105
106
  "output_dir": str(definition.to_output_path("executions")),
106
107
  "rootpath": {
107
- "default": str(climate_data),
108
+ "CMIP6": str(climate_data),
109
+ "obs4MIPs": str(climate_data),
108
110
  },
109
111
  "search_esgf": "never",
110
112
  }
111
113
 
112
- # Configure the paths to OBS/OBS6/native6 data
114
+ # Configure the paths to OBS/OBS6/native6 and non-compliant obs4MIPs data
113
115
  registry = dataset_registry_manager["esmvaltool"]
114
116
  data_dir = registry.abspath / "ESMValTool" # type: ignore[attr-defined]
115
117
  if not data_dir.exists():
@@ -129,10 +131,14 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
129
131
  config["rootpath"].update( # type: ignore[attr-defined]
130
132
  {
131
133
  "OBS": str(data_dir / "OBS"),
132
- "OBS6": str(data_dir / "OBS6"),
134
+ "OBS6": str(data_dir / "OBS"),
133
135
  "native6": str(data_dir / "RAWOBS"),
134
136
  }
135
137
  )
138
+ config["rootpath"]["obs4MIPs"] = [ # type: ignore[index]
139
+ config["rootpath"]["obs4MIPs"], # type: ignore[index]
140
+ str(data_dir),
141
+ ]
136
142
 
137
143
  config_dir = definition.to_output_path("config")
138
144
  config_dir.mkdir()
@@ -198,15 +204,20 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
198
204
  output_args[OutputCV.PROVENANCE.value][OutputCV.LOG.value] = f"{result_dir}/run/main_log_debug.txt"
199
205
 
200
206
  # Update the diagnostic and output bundle with diagnostic specific executions.
201
- metric_args, output_args = self.format_result(
207
+ metric_bundle, output_bundle = self.format_result(
202
208
  result_dir=result_dir,
203
209
  execution_dataset=definition.datasets,
204
210
  metric_args=metric_args,
205
211
  output_args=output_args,
206
212
  )
207
213
 
214
+ # Add the extra information from the groupby operations
215
+ if len(metric_bundle.DIMENSIONS[MetricCV.JSON_STRUCTURE.value]):
216
+ input_selectors = definition.datasets[SourceDatasetType.CMIP6].selector_dict()
217
+ metric_bundle = metric_bundle.prepend_dimensions(input_selectors)
218
+
208
219
  return ExecutionResult.build_from_output_bundle(
209
220
  definition,
210
- cmec_output_bundle=output_args,
211
- cmec_metric_bundle=metric_args,
221
+ cmec_output_bundle=output_bundle,
222
+ cmec_metric_bundle=metric_bundle,
212
223
  )
@@ -11,7 +11,8 @@ from climate_ref_core.constraints import (
11
11
  )
12
12
  from climate_ref_core.datasets import ExecutionDatasetCollection, FacetFilter, SourceDatasetType
13
13
  from climate_ref_core.diagnostics import DataRequirement
14
- from climate_ref_core.pycmec.metric import MetricCV
14
+ from climate_ref_core.pycmec.metric import CMECMetric, MetricCV
15
+ from climate_ref_core.pycmec.output import CMECOutput
15
16
  from climate_ref_esmvaltool.diagnostics.base import ESMValToolDiagnostic
16
17
  from climate_ref_esmvaltool.recipe import dataframe_to_recipe
17
18
  from climate_ref_esmvaltool.types import MetricBundleArgs, OutputBundleArgs, Recipe
@@ -57,14 +58,14 @@ class EquilibriumClimateSensitivity(ESMValToolDiagnostic):
57
58
  ),
58
59
  ),
59
60
  )
60
- facets = ("source_id", "region", "metric")
61
+ facets = ("grid_label", "member_id", "source_id", "region", "metric")
61
62
 
62
63
  @staticmethod
63
64
  def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
64
65
  """Update the recipe."""
65
66
  # Only run the diagnostic that computes ECS for a single model.
66
67
  recipe["diagnostics"] = {
67
- "cmip6": {
68
+ "ecs": {
68
69
  "description": "Calculate ECS.",
69
70
  "variables": {
70
71
  "tas": {
@@ -76,7 +77,7 @@ class EquilibriumClimateSensitivity(ESMValToolDiagnostic):
76
77
  },
77
78
  },
78
79
  "scripts": {
79
- "ecs": {
80
+ "calculate": {
80
81
  "script": "climate_metrics/ecs.py",
81
82
  "calculate_mmm": False,
82
83
  },
@@ -103,6 +104,18 @@ class EquilibriumClimateSensitivity(ESMValToolDiagnostic):
103
104
  for dataset in datasets:
104
105
  dataset["timerange"] = timerange
105
106
 
107
+ # Remove keys from the recipe that are only used for YAML anchors
108
+ keys_to_remove = [
109
+ "CMIP5_RTMT",
110
+ "CMIP6_RTMT",
111
+ "CMIP5_RTNT",
112
+ "CMIP6_RTNT",
113
+ "ECS_SCRIPT",
114
+ "SCATTERPLOT",
115
+ ]
116
+ for key in keys_to_remove:
117
+ recipe.pop(key, None)
118
+
106
119
  recipe["datasets"] = datasets
107
120
 
108
121
  @staticmethod
@@ -111,34 +124,27 @@ class EquilibriumClimateSensitivity(ESMValToolDiagnostic):
111
124
  execution_dataset: ExecutionDatasetCollection,
112
125
  metric_args: MetricBundleArgs,
113
126
  output_args: OutputBundleArgs,
114
- ) -> tuple[MetricBundleArgs, OutputBundleArgs]:
127
+ ) -> tuple[CMECMetric, CMECOutput]:
115
128
  """Format the result."""
116
- input_files = next(c.datasets for _, c in execution_dataset.items())
117
- source_id = input_files.iloc[0].source_id
118
-
119
- ecs_ds = xarray.open_dataset(result_dir / "work" / "cmip6" / "ecs" / "ecs.nc")
129
+ ecs_ds = xarray.open_dataset(result_dir / "work" / "ecs" / "calculate" / "ecs.nc")
120
130
  ecs = float(ecs_ds["ecs"].values[0])
121
- lambda_ds = xarray.open_dataset(result_dir / "work" / "cmip6" / "ecs" / "lambda.nc")
131
+ lambda_ds = xarray.open_dataset(result_dir / "work" / "ecs" / "calculate" / "lambda.nc")
122
132
  lambda_ = float(lambda_ds["lambda"].values[0])
123
133
 
124
134
  # Update the diagnostic bundle arguments with the computed diagnostics.
125
135
  metric_args[MetricCV.DIMENSIONS.value] = {
126
136
  MetricCV.JSON_STRUCTURE.value: [
127
- "source_id",
128
137
  "region",
129
138
  "metric",
130
139
  ],
131
- "source_id": {source_id: {}},
132
140
  "region": {"global": {}},
133
141
  "metric": {"ecs": {}, "lambda": {}},
134
142
  }
135
143
  metric_args[MetricCV.RESULTS.value] = {
136
- source_id: {
137
- "global": {
138
- "ecs": ecs,
139
- "lambda": lambda_,
140
- },
144
+ "global": {
145
+ "ecs": ecs,
146
+ "lambda": lambda_,
141
147
  },
142
148
  }
143
149
 
144
- return metric_args, output_args
150
+ return CMECMetric.model_validate(metric_args), CMECOutput.model_validate(output_args)
@@ -0,0 +1,155 @@
1
+ from pathlib import Path
2
+
3
+ import pandas
4
+ import pandas as pd
5
+
6
+ from climate_ref_core.constraints import (
7
+ AddSupplementaryDataset,
8
+ RequireContiguousTimerange,
9
+ RequireFacets,
10
+ RequireOverlappingTimerange,
11
+ )
12
+ from climate_ref_core.datasets import ExecutionDatasetCollection, FacetFilter, SourceDatasetType
13
+ from climate_ref_core.diagnostics import DataRequirement
14
+ from climate_ref_core.pycmec.metric import CMECMetric, MetricCV
15
+ from climate_ref_core.pycmec.output import CMECOutput
16
+ from climate_ref_esmvaltool.diagnostics.base import ESMValToolDiagnostic
17
+ from climate_ref_esmvaltool.recipe import dataframe_to_recipe
18
+ from climate_ref_esmvaltool.types import MetricBundleArgs, OutputBundleArgs, Recipe
19
+
20
+
21
+ class ENSOBasicClimatology(ESMValToolDiagnostic):
22
+ """
23
+ Calculate the ENSO CLIVAR metrics - background climatology.
24
+ """
25
+
26
+ name = "ENSO Basic Climatology"
27
+ slug = "enso-basic-climatology"
28
+ base_recipe = "ref/recipe_enso_basicclimatology.yml"
29
+
30
+ variables = (
31
+ "pr",
32
+ "tos",
33
+ "tauu",
34
+ )
35
+ data_requirements = (
36
+ DataRequirement(
37
+ source_type=SourceDatasetType.CMIP6,
38
+ filters=(
39
+ FacetFilter(
40
+ facets={
41
+ "variable_id": variables,
42
+ "experiment_id": "historical",
43
+ },
44
+ ),
45
+ ),
46
+ group_by=("source_id", "member_id", "grid_label"),
47
+ constraints=(
48
+ RequireFacets("variable_id", variables),
49
+ RequireContiguousTimerange(group_by=("instance_id",)),
50
+ RequireOverlappingTimerange(group_by=("instance_id",)),
51
+ ),
52
+ ),
53
+ )
54
+ facets = ()
55
+
56
+ @staticmethod
57
+ def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
58
+ """Update the recipe."""
59
+ recipe_variables = dataframe_to_recipe(input_files)
60
+ recipe.pop("datasets")
61
+ for diagnostic in recipe["diagnostics"].values():
62
+ for variable in diagnostic["variables"].values():
63
+ variable["additional_datasets"].extend(
64
+ recipe_variables[variable["short_name"]]["additional_datasets"]
65
+ )
66
+
67
+
68
+ class ENSOCharacteristics(ESMValToolDiagnostic):
69
+ """
70
+ Calculate the ENSO CLIVAR metrics - basic ENSO characteristics.
71
+ """
72
+
73
+ name = "ENSO Characteristics"
74
+ slug = "enso-characteristics"
75
+ base_recipe = "ref/recipe_enso_characteristics.yml"
76
+
77
+ data_requirements = (
78
+ DataRequirement(
79
+ source_type=SourceDatasetType.CMIP6,
80
+ filters=(
81
+ FacetFilter(
82
+ facets={
83
+ "variable_id": "tos",
84
+ "experiment_id": "historical",
85
+ },
86
+ ),
87
+ ),
88
+ group_by=("source_id", "member_id", "grid_label"),
89
+ constraints=(
90
+ RequireFacets("variable_id", ("tos",)),
91
+ RequireContiguousTimerange(group_by=("instance_id",)),
92
+ RequireOverlappingTimerange(group_by=("instance_id",)),
93
+ AddSupplementaryDataset.from_defaults("areacello", SourceDatasetType.CMIP6),
94
+ ),
95
+ ),
96
+ )
97
+ facets = ("grid_label", "member_id", "source_id", "region", "metric")
98
+
99
+ @staticmethod
100
+ def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
101
+ """Update the recipe."""
102
+ recipe_variables = dataframe_to_recipe(input_files)
103
+ recipe["datasets"] = recipe_variables["tos"]["additional_datasets"]
104
+ # TODO: update the observational data requirement once available on ESGF.
105
+ # Observations - use only one per run
106
+ recipe["datasets"].append(
107
+ # {
108
+ # "dataset": "NOAA-ERSSTv5",
109
+ # "version": "v5",
110
+ # "project": "OBS6",
111
+ # "type": "reanaly",
112
+ # "tier": 2,
113
+ # }
114
+ {
115
+ "dataset": "TROPFLUX",
116
+ "version": "v1",
117
+ "project": "OBS6",
118
+ "type": "reanaly",
119
+ "tier": 2,
120
+ }
121
+ )
122
+
123
+ @staticmethod
124
+ def format_result(
125
+ result_dir: Path,
126
+ execution_dataset: ExecutionDatasetCollection,
127
+ metric_args: MetricBundleArgs,
128
+ output_args: OutputBundleArgs,
129
+ ) -> tuple[CMECMetric, CMECOutput]:
130
+ """Format the result."""
131
+ metrics = pd.read_csv(
132
+ result_dir / "work" / "diagnostic_metrics" / "plot_script" / "matrix.csv",
133
+ names=["dataset", "metric_name", "metric_value"],
134
+ )
135
+
136
+ # Update the diagnostic bundle arguments with the computed diagnostics.
137
+ metric_args[MetricCV.DIMENSIONS.value] = {
138
+ "json_structure": [
139
+ "region",
140
+ "metric",
141
+ ],
142
+ "region": {"global": {}},
143
+ "metric": {metric: {} for metric in metrics.metric_name},
144
+ }
145
+ metric_args[MetricCV.RESULTS.value] = {
146
+ "global": {
147
+ metric_name: metric_value
148
+ for metric_name, metric_value in zip(
149
+ metrics.metric_name,
150
+ metrics.metric_value,
151
+ )
152
+ },
153
+ }
154
+
155
+ return CMECMetric.model_validate(metric_args), CMECOutput.model_validate(output_args)
@@ -11,13 +11,13 @@ from climate_ref_esmvaltool.recipe import dataframe_to_recipe
11
11
  from climate_ref_esmvaltool.types import Recipe
12
12
 
13
13
 
14
- class SeaIceAreaSeasonalCycle(ESMValToolDiagnostic):
14
+ class SeaIceAreaBasic(ESMValToolDiagnostic):
15
15
  """
16
16
  Calculate seasonal cycle and time series of NH and SH sea ice area.
17
17
  """
18
18
 
19
- name = "Sea ice area seasonal cycle"
20
- slug = "sea-ice-area-seasonal-cycle"
19
+ name = "Sea ice area basic"
20
+ slug = "sea-ice-area-basic"
21
21
  base_recipe = "ref/recipe_ref_sea_ice_area_basic.yml"
22
22
 
23
23
  data_requirements = (
@@ -44,14 +44,13 @@ class SeaIceAreaSeasonalCycle(ESMValToolDiagnostic):
44
44
  @staticmethod
45
45
  def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
46
46
  """Update the recipe."""
47
- # Overlap between observations and historical experiment.
48
- timerange = "1995/2014"
49
-
50
47
  # Update datasets
51
48
  recipe_variables = dataframe_to_recipe(input_files)
52
49
  recipe["datasets"] = recipe_variables["siconc"]["additional_datasets"]
50
+
51
+ # Use the timerange from the recipe, as defined in the variable.
53
52
  for dataset in recipe["datasets"]:
54
- dataset["timerange"] = timerange
53
+ dataset.pop("timerange")
55
54
 
56
55
  # Update observational datasets
57
56
  nh_obs = {
@@ -65,7 +64,6 @@ class SeaIceAreaSeasonalCycle(ESMValToolDiagnostic):
65
64
  },
66
65
  ],
67
66
  "tier": 2,
68
- "timerange": timerange,
69
67
  "type": "reanaly",
70
68
  "version": "v3",
71
69
  }
@@ -76,3 +74,10 @@ class SeaIceAreaSeasonalCycle(ESMValToolDiagnostic):
76
74
  diagnostics["siarea_min"]["variables"]["sea_ice_area_sh_feb"]["additional_datasets"] = [sh_obs]
77
75
  diagnostics["siarea_seas"]["variables"]["sea_ice_area_nh"]["additional_datasets"] = [nh_obs]
78
76
  diagnostics["siarea_seas"]["variables"]["sea_ice_area_sh"]["additional_datasets"] = [sh_obs]
77
+
78
+ # Update the captions.
79
+ dataset = "{dataset}.{ensemble}.{grid}".format(**recipe["datasets"][0])
80
+ for diagnostic in diagnostics.values():
81
+ for script_settings in diagnostic["scripts"].values():
82
+ for plot_settings in script_settings["plots"].values():
83
+ plot_settings["caption"] = plot_settings["caption"].replace("[dataset]", dataset)
@@ -11,7 +11,8 @@ from climate_ref_core.constraints import (
11
11
  )
12
12
  from climate_ref_core.datasets import ExecutionDatasetCollection, FacetFilter, SourceDatasetType
13
13
  from climate_ref_core.diagnostics import DataRequirement
14
- from climate_ref_core.pycmec.metric import MetricCV
14
+ from climate_ref_core.pycmec.metric import CMECMetric, MetricCV
15
+ from climate_ref_core.pycmec.output import CMECOutput
15
16
  from climate_ref_esmvaltool.diagnostics.base import ESMValToolDiagnostic
16
17
  from climate_ref_esmvaltool.recipe import dataframe_to_recipe
17
18
  from climate_ref_esmvaltool.types import MetricBundleArgs, OutputBundleArgs, Recipe
@@ -50,14 +51,14 @@ class TransientClimateResponse(ESMValToolDiagnostic):
50
51
  ),
51
52
  ),
52
53
  )
53
- facets = ("source_id", "region", "metric")
54
+ facets = ("grid_label", "member_id", "source_id", "region", "metric")
54
55
 
55
56
  @staticmethod
56
57
  def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
57
58
  """Update the recipe."""
58
59
  # Only run the diagnostic that computes TCR for a single model.
59
60
  recipe["diagnostics"] = {
60
- "cmip6": {
61
+ "tcr": {
61
62
  "description": "Calculate TCR.",
62
63
  "variables": {
63
64
  "tas": {
@@ -65,7 +66,7 @@ class TransientClimateResponse(ESMValToolDiagnostic):
65
66
  },
66
67
  },
67
68
  "scripts": {
68
- "tcr": {
69
+ "calculate": {
69
70
  "script": "climate_metrics/tcr.py",
70
71
  "calculate_mmm": False,
71
72
  },
@@ -92,6 +93,15 @@ class TransientClimateResponse(ESMValToolDiagnostic):
92
93
  for dataset in datasets:
93
94
  dataset["timerange"] = timerange
94
95
 
96
+ # Remove keys from the recipe that are only used for YAML anchors
97
+ keys_to_remove = [
98
+ "TCR",
99
+ "SCATTERPLOT",
100
+ "VAR_SETTING",
101
+ ]
102
+ for key in keys_to_remove:
103
+ recipe.pop(key, None)
104
+
95
105
  recipe["datasets"] = datasets
96
106
 
97
107
  @staticmethod
@@ -100,31 +110,24 @@ class TransientClimateResponse(ESMValToolDiagnostic):
100
110
  execution_dataset: ExecutionDatasetCollection,
101
111
  metric_args: MetricBundleArgs,
102
112
  output_args: OutputBundleArgs,
103
- ) -> tuple[MetricBundleArgs, OutputBundleArgs]:
113
+ ) -> tuple[CMECMetric, CMECOutput]:
104
114
  """Format the result."""
105
- input_files = next(c.datasets for _, c in execution_dataset.items())
106
- source_id = input_files.iloc[0].source_id
107
-
108
- tcr_ds = xarray.open_dataset(result_dir / "work" / "cmip6" / "tcr" / "tcr.nc")
115
+ tcr_ds = xarray.open_dataset(result_dir / "work" / "tcr" / "calculate" / "tcr.nc")
109
116
  tcr = float(tcr_ds["tcr"].values[0])
110
117
 
111
118
  # Update the diagnostic bundle arguments with the computed diagnostics.
112
119
  metric_args[MetricCV.DIMENSIONS.value] = {
113
120
  "json_structure": [
114
- "source_id",
115
121
  "region",
116
122
  "metric",
117
123
  ],
118
- "source_id": {source_id: {}},
119
124
  "region": {"global": {}},
120
125
  "metric": {"tcr": {}},
121
126
  }
122
127
  metric_args[MetricCV.RESULTS.value] = {
123
- source_id: {
124
- "global": {
125
- "tcr": tcr,
126
- },
128
+ "global": {
129
+ "tcr": tcr,
127
130
  },
128
131
  }
129
132
 
130
- return metric_args, output_args
133
+ return CMECMetric.model_validate(metric_args), CMECOutput.model_validate(output_args)