climate-ref-esmvaltool 0.6.5__py3-none-any.whl → 0.6.6__py3-none-any.whl

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 (27) hide show
  1. climate_ref_esmvaltool/dataset_registry/data.txt +4 -0
  2. climate_ref_esmvaltool/diagnostics/__init__.py +22 -0
  3. climate_ref_esmvaltool/diagnostics/base.py +80 -12
  4. climate_ref_esmvaltool/diagnostics/climate_at_global_warming_levels.py +5 -2
  5. climate_ref_esmvaltool/diagnostics/climate_drivers_for_fire.py +68 -0
  6. climate_ref_esmvaltool/diagnostics/cloud_radiative_effects.py +2 -2
  7. climate_ref_esmvaltool/diagnostics/cloud_scatterplots.py +188 -0
  8. climate_ref_esmvaltool/diagnostics/ecs.py +9 -18
  9. climate_ref_esmvaltool/diagnostics/enso.py +10 -4
  10. climate_ref_esmvaltool/diagnostics/example.py +15 -2
  11. climate_ref_esmvaltool/diagnostics/regional_historical_changes.py +340 -0
  12. climate_ref_esmvaltool/diagnostics/sea_ice_area_basic.py +5 -2
  13. climate_ref_esmvaltool/diagnostics/sea_ice_sensitivity.py +5 -2
  14. climate_ref_esmvaltool/diagnostics/tcr.py +9 -18
  15. climate_ref_esmvaltool/diagnostics/tcre.py +5 -2
  16. climate_ref_esmvaltool/diagnostics/zec.py +5 -2
  17. climate_ref_esmvaltool/recipe.py +46 -5
  18. climate_ref_esmvaltool/recipes.txt +16 -11
  19. climate_ref_esmvaltool/requirements/conda-lock.yml +4081 -3770
  20. climate_ref_esmvaltool/requirements/environment.yml +1 -0
  21. {climate_ref_esmvaltool-0.6.5.dist-info → climate_ref_esmvaltool-0.6.6.dist-info}/METADATA +1 -1
  22. climate_ref_esmvaltool-0.6.6.dist-info/RECORD +30 -0
  23. climate_ref_esmvaltool-0.6.5.dist-info/RECORD +0 -27
  24. {climate_ref_esmvaltool-0.6.5.dist-info → climate_ref_esmvaltool-0.6.6.dist-info}/WHEEL +0 -0
  25. {climate_ref_esmvaltool-0.6.5.dist-info → climate_ref_esmvaltool-0.6.6.dist-info}/entry_points.txt +0 -0
  26. {climate_ref_esmvaltool-0.6.5.dist-info → climate_ref_esmvaltool-0.6.6.dist-info}/licenses/LICENCE +0 -0
  27. {climate_ref_esmvaltool-0.6.5.dist-info → climate_ref_esmvaltool-0.6.6.dist-info}/licenses/NOTICE +0 -0
@@ -73,6 +73,9 @@ ESMValTool/OBS/Tier2/CERES-EBAF/OBS_CERES-EBAF_sat_Ed4.2_Amon_rlut_200003-202311
73
73
  ESMValTool/OBS/Tier2/CERES-EBAF/OBS_CERES-EBAF_sat_Ed4.2_Amon_rlutcs_200003-202311.nc e70e3273092edf01527970693271641fc6474d1974887d7d272e7d656bab83c2
74
74
  ESMValTool/OBS/Tier2/CERES-EBAF/OBS_CERES-EBAF_sat_Ed4.2_Amon_rsut_200003-202311.nc e31e648886c4fa9c09686672a06ab18fbba687ff0d6de2891616d4c8b74e215d
75
75
  ESMValTool/OBS/Tier2/CERES-EBAF/OBS_CERES-EBAF_sat_Ed4.2_Amon_rsutcs_200003-202311.nc eb96edd9274670aa705eab2a6d1ee0cca11e01ac17096706463e032b58e6be47
76
+ ESMValTool/OBS/Tier2/ESACCI-CLOUD/OBS_ESACCI-CLOUD_sat_AVHRR-AMPM-fv3.0_Amon_clivi_198201-201612.nc 13bc6e3a46397386a14a36776fdd6bdbf5c45147c8dc695d4a7387883d449775
77
+ ESMValTool/OBS/Tier2/ESACCI-CLOUD/OBS_ESACCI-CLOUD_sat_AVHRR-AMPM-fv3.0_Amon_clt_198201-201612.nc 4a430d77dbe9164dba2d1fdef4bb89ea9358b6d10023f3fbee50917422446ed0
78
+ ESMValTool/OBS/Tier2/ESACCI-CLOUD/OBS_ESACCI-CLOUD_sat_AVHRR-AMPM-fv3.0_Amon_clwvi_198201-201612.nc e0ffa31369d9552be16b920110b24013d31e116b180ee3f70b3d0aaa5281eff0
76
79
  ESMValTool/OBS/Tier2/ESACCI-CLOUD/OBS_ESACCI-CLOUD_sat_AVHRR-AMPM-fv3.0_Amon_rlut_198201-201612.nc 075144d673a9f2ff49fbe59e701535bf80c04908797a9dca83781000a9b1b7f2
77
80
  ESMValTool/OBS/Tier2/ESACCI-CLOUD/OBS_ESACCI-CLOUD_sat_AVHRR-AMPM-fv3.0_Amon_rlutcs_198201-201612.nc 21f096ecafff659e5c7e3338060425f7194e5d1b39c9510865496e04ecac3d75
78
81
  ESMValTool/OBS/Tier2/ESACCI-CLOUD/OBS_ESACCI-CLOUD_sat_AVHRR-AMPM-fv3.0_Amon_rsut_198201-201612.nc f2c3f3afcdc2e730df7985c210a3de89b0d4f83b150e0c3846f7ac3c5fa9c54a
@@ -156,5 +159,6 @@ ESMValTool/OBS/Tier2/OSI-450-sh/OBS_OSI-450-sh_reanaly_v3_OImon_sic_201101-20111
156
159
  ESMValTool/OBS/Tier2/OSI-450-sh/OBS_OSI-450-sh_reanaly_v3_OImon_sic_201201-201212.nc 86187c3d1174053f2cba6dad010af49ceab77d368aa9314bf53c330b5f2217b9
157
160
  ESMValTool/OBS/Tier2/OSI-450-sh/OBS_OSI-450-sh_reanaly_v3_OImon_sic_201301-201312.nc 8820353570884b2ef182caaffb5986ed6268bbe199fd867f61b56e798ca01f1a
158
161
  ESMValTool/OBS/Tier2/OSI-450-sh/OBS_OSI-450-sh_reanaly_v3_OImon_sic_201401-201412.nc 7102d0db3dc02c5b0eb0cfe3535ee50171007ef5b43eb9aae1220ac21b0b98e9
162
+ ESMValTool/OBS/Tier3/CALIPSO-ICECLOUD/OBS_CALIPSO-ICECLOUD_sat_1-00_Amon_cli_200701-201512.nc 977824810e8f9dbe7df278c59397ffc3f78491a9eb5a0b70e6b28ac66db8e12d
159
163
  ESMValTool/OBS/Tier2/TROPFLUX/OBS6_TROPFLUX_reanaly_v1_Amon_tauu_197901-201812.nc bf313e661b42341d5090038b501ed1ff09e58201009c3fccfe45b78e116fdd78
160
164
  ESMValTool/OBS/Tier2/TROPFLUX/OBS6_TROPFLUX_reanaly_v1_Omon_tos_197901-201812.nc 5f10a5a2aa47f5d21378ad3178bf8e4b577b0ed72ef8402dc04f5ff6fc99ec07
@@ -1,10 +1,23 @@
1
1
  """ESMValTool diagnostics."""
2
2
 
3
3
  from climate_ref_esmvaltool.diagnostics.climate_at_global_warming_levels import ClimateAtGlobalWarmingLevels
4
+ from climate_ref_esmvaltool.diagnostics.climate_drivers_for_fire import ClimateDriversForFire
4
5
  from climate_ref_esmvaltool.diagnostics.cloud_radiative_effects import CloudRadiativeEffects
6
+ from climate_ref_esmvaltool.diagnostics.cloud_scatterplots import (
7
+ CloudScatterplotCliTa,
8
+ CloudScatterplotCliviLwcre,
9
+ CloudScatterplotCltSwcre,
10
+ CloudScatterplotClwviPr,
11
+ CloudScatterplotsReference,
12
+ )
5
13
  from climate_ref_esmvaltool.diagnostics.ecs import EquilibriumClimateSensitivity
6
14
  from climate_ref_esmvaltool.diagnostics.enso import ENSOBasicClimatology, ENSOCharacteristics
7
15
  from climate_ref_esmvaltool.diagnostics.example import GlobalMeanTimeseries
16
+ from climate_ref_esmvaltool.diagnostics.regional_historical_changes import (
17
+ RegionalHistoricalAnnualCycle,
18
+ RegionalHistoricalTimeSeries,
19
+ RegionalHistoricalTrend,
20
+ )
8
21
  from climate_ref_esmvaltool.diagnostics.sea_ice_area_basic import SeaIceAreaBasic
9
22
  from climate_ref_esmvaltool.diagnostics.sea_ice_sensitivity import SeaIceSensitivity
10
23
  from climate_ref_esmvaltool.diagnostics.tcr import TransientClimateResponse
@@ -13,11 +26,20 @@ from climate_ref_esmvaltool.diagnostics.zec import ZeroEmissionCommitment
13
26
 
14
27
  __all__ = [
15
28
  "ClimateAtGlobalWarmingLevels",
29
+ "ClimateDriversForFire",
16
30
  "CloudRadiativeEffects",
31
+ "CloudScatterplotCliTa",
32
+ "CloudScatterplotCliviLwcre",
33
+ "CloudScatterplotCltSwcre",
34
+ "CloudScatterplotClwviPr",
35
+ "CloudScatterplotsReference",
17
36
  "ENSOBasicClimatology",
18
37
  "ENSOCharacteristics",
19
38
  "EquilibriumClimateSensitivity",
20
39
  "GlobalMeanTimeseries",
40
+ "RegionalHistoricalAnnualCycle",
41
+ "RegionalHistoricalTimeSeries",
42
+ "RegionalHistoricalTrend",
21
43
  "SeaIceAreaBasic",
22
44
  "SeaIceSensitivity",
23
45
  "TransientClimateResponse",
@@ -1,9 +1,11 @@
1
+ import fnmatch
1
2
  from abc import abstractmethod
2
3
  from collections.abc import Iterable
3
4
  from pathlib import Path
4
5
  from typing import ClassVar
5
6
 
6
7
  import pandas
8
+ import xarray as xr
7
9
  import yaml
8
10
  from loguru import logger
9
11
 
@@ -14,6 +16,7 @@ from climate_ref_core.diagnostics import (
14
16
  ExecutionDefinition,
15
17
  ExecutionResult,
16
18
  )
19
+ from climate_ref_core.metric_values.typing import SeriesMetricValue
17
20
  from climate_ref_core.pycmec.metric import CMECMetric, MetricCV
18
21
  from climate_ref_core.pycmec.output import CMECOutput, OutputCV
19
22
  from climate_ref_esmvaltool.recipe import load_recipe, prepare_climate_data
@@ -27,7 +30,10 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
27
30
 
28
31
  @staticmethod
29
32
  @abstractmethod
30
- def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
33
+ def update_recipe(
34
+ recipe: Recipe,
35
+ input_files: dict[SourceDatasetType, pandas.DataFrame],
36
+ ) -> None:
31
37
  """
32
38
  Update the base recipe for the run.
33
39
 
@@ -67,9 +73,9 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
67
73
  """
68
74
  return CMECMetric.model_validate(metric_args), CMECOutput.model_validate(output_args)
69
75
 
70
- def build_cmd(self, definition: ExecutionDefinition) -> Iterable[str]:
76
+ def write_recipe(self, definition: ExecutionDefinition) -> Path:
71
77
  """
72
- Build the command to run an ESMValTool recipe.
78
+ Update the ESMValTool recipe for the diagnostic and write it to file.
73
79
 
74
80
  Parameters
75
81
  ----------
@@ -79,22 +85,42 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
79
85
  Returns
80
86
  -------
81
87
  :
82
- The result of running the diagnostic.
88
+ The path to the written recipe.
83
89
  """
84
- input_files = definition.datasets[SourceDatasetType.CMIP6].datasets
90
+ input_files = {
91
+ project: dataset_collection.datasets
92
+ for project, dataset_collection in definition.datasets.items()
93
+ }
85
94
  recipe = load_recipe(self.base_recipe)
86
95
  self.update_recipe(recipe, input_files)
87
96
 
88
97
  recipe_path = definition.to_output_path("recipe.yml")
89
98
  with recipe_path.open("w", encoding="utf-8") as file:
90
- yaml.dump(recipe, file)
99
+ yaml.safe_dump(recipe, file, sort_keys=False)
100
+ return recipe_path
101
+
102
+ def build_cmd(self, definition: ExecutionDefinition) -> Iterable[str]:
103
+ """
104
+ Build the command to run an ESMValTool recipe.
105
+
106
+ Parameters
107
+ ----------
108
+ definition
109
+ A description of the information needed for this execution of the diagnostic
91
110
 
111
+ Returns
112
+ -------
113
+ :
114
+ The result of running the diagnostic.
115
+ """
116
+ recipe_path = self.write_recipe(definition)
92
117
  climate_data = definition.to_output_path("climate_data")
93
118
 
94
- prepare_climate_data(
95
- definition.datasets[SourceDatasetType.CMIP6].datasets,
96
- climate_data_dir=climate_data,
97
- )
119
+ for metric_dataset in definition.datasets.values():
120
+ prepare_climate_data(
121
+ metric_dataset.datasets,
122
+ climate_data_dir=climate_data,
123
+ )
98
124
 
99
125
  config = {
100
126
  "drs": {
@@ -130,7 +156,7 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
130
156
  {
131
157
  "OBS": str(data_dir / "OBS"),
132
158
  "OBS6": str(data_dir / "OBS"),
133
- "native6": str(data_dir / "RAWOBS"),
159
+ "native6": str(data_dir / "native6"),
134
160
  }
135
161
  )
136
162
  config["rootpath"]["obs4MIPs"] = [ # type: ignore[index]
@@ -141,7 +167,7 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
141
167
  config_dir = definition.to_output_path("config")
142
168
  config_dir.mkdir()
143
169
  with (config_dir / "config.yml").open("w", encoding="utf-8") as file:
144
- yaml.dump(config, file)
170
+ yaml.safe_dump(config, file)
145
171
 
146
172
  return [
147
173
  "esmvaltool",
@@ -173,6 +199,12 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
173
199
  output_args = CMECOutput.create_template()
174
200
 
175
201
  # Add the plots and data files
202
+ variable_attributes = (
203
+ "long_name",
204
+ "standard_name",
205
+ "units",
206
+ )
207
+ series = []
176
208
  plot_suffixes = {".png", ".jpg", ".pdf", ".ps"}
177
209
  for metadata_file in result_dir.glob("run/*/*/diagnostic_provenance.yml"):
178
210
  metadata = yaml.safe_load(metadata_file.read_text(encoding="utf-8"))
@@ -188,6 +220,41 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
188
220
  OutputCV.LONG_NAME.value: caption,
189
221
  OutputCV.DESCRIPTION.value: "",
190
222
  }
223
+ for series_def in definition.diagnostic.series:
224
+ if fnmatch.fnmatch(str(relative_path), f"executions/*/{series_def.file_pattern}"):
225
+ dataset = xr.open_dataset(
226
+ filename, decode_times=xr.coders.CFDatetimeCoder(use_cftime=True)
227
+ )
228
+ dataset = dataset.sel(series_def.sel)
229
+ attributes = {
230
+ attr: dataset.attrs[attr]
231
+ for attr in series_def.attributes
232
+ if attr in dataset.attrs
233
+ }
234
+ attributes["caption"] = caption
235
+ attributes["values_name"] = series_def.values_name
236
+ attributes["index_name"] = series_def.index_name
237
+ for attr in variable_attributes:
238
+ if attr in dataset[series_def.values_name].attrs:
239
+ attributes[f"value_{attr}"] = dataset[series_def.values_name].attrs[attr]
240
+ if attr in dataset[series_def.index_name].attrs:
241
+ attributes[f"index_{attr}"] = dataset[series_def.index_name].attrs[attr]
242
+ index = dataset[series_def.index_name].values.tolist()
243
+ if hasattr(index[0], "calendar"):
244
+ attributes["calendar"] = index[0].calendar
245
+ if hasattr(index[0], "isoformat"):
246
+ # Convert time objects to strings.
247
+ index = [v.isoformat() for v in index]
248
+
249
+ series.append(
250
+ SeriesMetricValue(
251
+ dimensions=series_def.dimensions,
252
+ values=dataset[series_def.values_name].values.tolist(),
253
+ index=index,
254
+ index_name=series_def.index_name,
255
+ attributes=attributes,
256
+ )
257
+ )
191
258
 
192
259
  # Add the index.html file
193
260
  index_html = f"{result_dir}/index.html"
@@ -218,4 +285,5 @@ class ESMValToolDiagnostic(CommandLineDiagnostic):
218
285
  definition,
219
286
  cmec_output_bundle=output_bundle,
220
287
  cmec_metric_bundle=metric_bundle,
288
+ series=series,
221
289
  )
@@ -65,13 +65,16 @@ class ClimateAtGlobalWarmingLevels(ESMValToolDiagnostic):
65
65
  facets = ()
66
66
 
67
67
  @staticmethod
68
- def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
68
+ def update_recipe(
69
+ recipe: Recipe,
70
+ input_files: dict[SourceDatasetType, pandas.DataFrame],
71
+ ) -> None:
69
72
  """Update the recipe."""
70
73
  # Set up the datasets
71
74
  diagnostics = recipe["diagnostics"]
72
75
  for diagnostic in diagnostics.values():
73
76
  diagnostic.pop("additional_datasets")
74
- recipe_variables = dataframe_to_recipe(input_files)
77
+ recipe_variables = dataframe_to_recipe(input_files[SourceDatasetType.CMIP6])
75
78
  datasets = recipe_variables["tas"]["additional_datasets"]
76
79
  datasets = [ds for ds in datasets if ds["exp"] != "historical"]
77
80
  for dataset in datasets:
@@ -0,0 +1,68 @@
1
+ import pandas
2
+
3
+ from climate_ref_core.constraints import (
4
+ AddSupplementaryDataset,
5
+ RequireFacets,
6
+ RequireOverlappingTimerange,
7
+ )
8
+ from climate_ref_core.datasets import FacetFilter, SourceDatasetType
9
+ from climate_ref_core.diagnostics import DataRequirement
10
+ from climate_ref_esmvaltool.diagnostics.base import ESMValToolDiagnostic
11
+ from climate_ref_esmvaltool.recipe import dataframe_to_recipe
12
+ from climate_ref_esmvaltool.types import Recipe
13
+
14
+
15
+ class ClimateDriversForFire(ESMValToolDiagnostic):
16
+ """
17
+ Calculate diagnostics regarding climate drivers for fire.
18
+ """
19
+
20
+ name = "Climate drivers for fire"
21
+ slug = "climate-drivers-for-fire"
22
+ base_recipe = "ref/recipe_ref_fire.yml"
23
+
24
+ variables = (
25
+ "cVeg",
26
+ "hurs",
27
+ "pr",
28
+ "tas",
29
+ "tasmax",
30
+ "treeFrac",
31
+ "vegFrac",
32
+ )
33
+ data_requirements = (
34
+ DataRequirement(
35
+ source_type=SourceDatasetType.CMIP6,
36
+ filters=(
37
+ FacetFilter(
38
+ facets={
39
+ "variable_id": variables,
40
+ "frequency": "mon",
41
+ "experiment_id": "historical",
42
+ }
43
+ ),
44
+ ),
45
+ group_by=("source_id", "member_id", "grid_label"),
46
+ constraints=(
47
+ RequireFacets("variable_id", variables),
48
+ RequireOverlappingTimerange(group_by=("instance_id",)),
49
+ AddSupplementaryDataset.from_defaults("sftlf", SourceDatasetType.CMIP6),
50
+ ),
51
+ ),
52
+ )
53
+ facets = ()
54
+
55
+ @staticmethod
56
+ def update_recipe(
57
+ recipe: Recipe,
58
+ input_files: dict[SourceDatasetType, pandas.DataFrame],
59
+ ) -> None:
60
+ """Update the recipe."""
61
+ recipe_variables = dataframe_to_recipe(input_files[SourceDatasetType.CMIP6])
62
+ dataset = recipe_variables["cVeg"]["additional_datasets"][0]
63
+ dataset.pop("mip")
64
+ dataset.pop("timerange")
65
+ dataset["start_year"] = 2013
66
+ dataset["end_year"] = 2014
67
+ recipe["datasets"] = [dataset]
68
+ recipe["diagnostics"]["fire_evaluation"]["scripts"]["fire_evaluation"]["remove_confire_files"] = True
@@ -53,9 +53,9 @@ class CloudRadiativeEffects(ESMValToolDiagnostic):
53
53
  )
54
54
 
55
55
  @staticmethod
56
- def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
56
+ def update_recipe(recipe: Recipe, input_files: dict[SourceDatasetType, pandas.DataFrame]) -> None:
57
57
  """Update the recipe."""
58
- recipe_variables = dataframe_to_recipe(input_files)
58
+ recipe_variables = dataframe_to_recipe(input_files[SourceDatasetType.CMIP6])
59
59
  recipe_variables = {k: v for k, v in recipe_variables.items() if k != "areacella"}
60
60
 
61
61
  # Select a timerange covered by all datasets.
@@ -0,0 +1,188 @@
1
+ from functools import partial
2
+
3
+ import pandas
4
+
5
+ from climate_ref_core.constraints import (
6
+ AddSupplementaryDataset,
7
+ RequireContiguousTimerange,
8
+ RequireFacets,
9
+ RequireOverlappingTimerange,
10
+ )
11
+ from climate_ref_core.datasets import FacetFilter, SourceDatasetType
12
+ from climate_ref_core.diagnostics import DataRequirement
13
+ from climate_ref_esmvaltool.diagnostics.base import ESMValToolDiagnostic
14
+ from climate_ref_esmvaltool.recipe import dataframe_to_recipe
15
+ from climate_ref_esmvaltool.types import Recipe
16
+
17
+
18
+ def get_cmip6_data_requirements(variables: tuple[str, ...]) -> tuple[DataRequirement, ...]:
19
+ """Create a data requirement for CMIP6 data."""
20
+ return (
21
+ DataRequirement(
22
+ source_type=SourceDatasetType.CMIP6,
23
+ filters=(
24
+ FacetFilter(
25
+ facets={
26
+ "variable_id": variables,
27
+ "experiment_id": "historical",
28
+ },
29
+ ),
30
+ ),
31
+ group_by=("source_id", "experiment_id", "member_id", "frequency", "grid_label"),
32
+ constraints=(
33
+ RequireFacets("variable_id", variables),
34
+ RequireContiguousTimerange(group_by=("instance_id",)),
35
+ RequireOverlappingTimerange(group_by=("instance_id",)),
36
+ # TODO: Add a RequireTimeRange constraint to match reference datasets?
37
+ AddSupplementaryDataset.from_defaults("areacella", SourceDatasetType.CMIP6),
38
+ ),
39
+ ),
40
+ )
41
+
42
+
43
+ def update_recipe(
44
+ recipe: Recipe,
45
+ input_files: dict[SourceDatasetType, pandas.DataFrame],
46
+ var_x: str,
47
+ var_y: str,
48
+ ) -> None:
49
+ """Update the recipe."""
50
+ recipe_variables = dataframe_to_recipe(input_files[SourceDatasetType.CMIP6], equalize_timerange=True)
51
+ diagnostics = recipe["diagnostics"]
52
+ diagnostic_name = f"plot_joint_{var_x}_{var_y}_model"
53
+ diagnostic = diagnostics.pop(diagnostic_name)
54
+ diagnostics.clear()
55
+ diagnostics[diagnostic_name] = diagnostic
56
+ datasets = next(iter(recipe_variables.values()))["additional_datasets"]
57
+ diagnostic["additional_datasets"] = datasets
58
+ suptitle = "CMIP6 {dataset} {ensemble} {grid} {timerange}".format(**datasets[0])
59
+ diagnostic["scripts"]["plot"]["suptitle"] = suptitle
60
+ diagnostic["scripts"]["plot"]["plot_filename"] = (
61
+ f"jointplot_{var_x}_{var_y}_{suptitle.replace(' ', '_').replace('/', '-')}"
62
+ )
63
+
64
+
65
+ class CloudScatterplotCltSwcre(ESMValToolDiagnostic):
66
+ """
67
+ Scatterplot of clt vs swcre.
68
+ """
69
+
70
+ name = "Scatterplots of two cloud-relevant variables (clt vs swcre)"
71
+ slug = "cloud-scatterplots-clt-swcre"
72
+ base_recipe = "ref/recipe_ref_scatterplot.yml"
73
+ facets = ()
74
+ data_requirements = get_cmip6_data_requirements(("clt", "rsut", "rsutcs"))
75
+ update_recipe = partial(update_recipe, var_x="clt", var_y="swcre")
76
+
77
+
78
+ class CloudScatterplotClwviPr(ESMValToolDiagnostic):
79
+ """
80
+ Scatterplot of clwvi vs pr.
81
+ """
82
+
83
+ name = "Scatterplots of two cloud-relevant variables (clwvi vs pr)"
84
+ slug = "cloud-scatterplots-clwvi-pr"
85
+ base_recipe = "ref/recipe_ref_scatterplot.yml"
86
+ facets = ()
87
+ data_requirements = get_cmip6_data_requirements(("clwvi", "pr"))
88
+ update_recipe = partial(update_recipe, var_x="clwvi", var_y="pr")
89
+
90
+
91
+ class CloudScatterplotCliviLwcre(ESMValToolDiagnostic):
92
+ """
93
+ Scatterplot of clivi vs lwcre.
94
+ """
95
+
96
+ name = "Scatterplots of two cloud-relevant variables (clivi vs lwcre)"
97
+ slug = "cloud-scatterplots-clivi-lwcre"
98
+ base_recipe = "ref/recipe_ref_scatterplot.yml"
99
+ facets = ()
100
+ data_requirements = get_cmip6_data_requirements(("clivi", "rlut", "rlutcs"))
101
+ update_recipe = partial(update_recipe, var_x="clivi", var_y="lwcre")
102
+
103
+
104
+ class CloudScatterplotCliTa(ESMValToolDiagnostic):
105
+ """
106
+ Scatterplot of cli vs ta.
107
+ """
108
+
109
+ name = "Scatterplots of two cloud-relevant variables (cli vs ta)"
110
+ slug = "cloud-scatterplots-cli-ta"
111
+ base_recipe = "ref/recipe_ref_scatterplot.yml"
112
+ facets = ()
113
+ data_requirements = get_cmip6_data_requirements(("cli", "ta"))
114
+ update_recipe = partial(update_recipe, var_x="cli", var_y="ta")
115
+
116
+
117
+ class CloudScatterplotsReference(ESMValToolDiagnostic):
118
+ """
119
+ Reference scatterplots of two cloud-relevant variables.
120
+ """
121
+
122
+ name = "Reference scatterplots of two cloud-relevant variables"
123
+ slug = "cloud-scatterplots-reference"
124
+ base_recipe = "ref/recipe_ref_scatterplot.yml"
125
+ facets = ()
126
+ data_requirements = (
127
+ DataRequirement(
128
+ source_type=SourceDatasetType.obs4MIPs,
129
+ filters=(
130
+ FacetFilter(
131
+ facets={
132
+ "source_id": ("ERA-5",),
133
+ "variable_id": ("ta",),
134
+ },
135
+ ),
136
+ ),
137
+ group_by=("instance_id",),
138
+ constraints=(RequireContiguousTimerange(group_by=("instance_id",)),),
139
+ # TODO: Add obs4MIPs datasets once available and working:
140
+ #
141
+ # obs4MIPs datasets with issues:
142
+ # - GPCP-V2.3: pr
143
+ # - CERES-EBAF-4-2: rlut, rlutcs, rsut, rsutcs
144
+ #
145
+ # Unsure if available on obs4MIPs:
146
+ # - AVHRR-AMPM-fv3.0: clivi, clwvi
147
+ # - ESACCI-CLOUD: clt
148
+ # - CALIPSO-ICECLOUD: cli
149
+ #
150
+ # Related issues:
151
+ # - https://github.com/Climate-REF/climate-ref/issues/260
152
+ # - https://github.com/esMValGroup/esMValCore/issues/2712
153
+ # - https://github.com/esMValGroup/esMValCore/issues/2711
154
+ # - https://github.com/sciTools/iris/issues/6411
155
+ ),
156
+ )
157
+
158
+ @staticmethod
159
+ def update_recipe(
160
+ recipe: Recipe,
161
+ input_files: dict[SourceDatasetType, pandas.DataFrame],
162
+ ) -> None:
163
+ """Update the recipe."""
164
+ recipe_variables = dataframe_to_recipe(input_files[SourceDatasetType.obs4MIPs])
165
+ recipe["diagnostics"] = {k: v for k, v in recipe["diagnostics"].items() if k.endswith("_ref")}
166
+
167
+ era5_dataset = recipe_variables["ta"]["additional_datasets"][0]
168
+ era5_dataset["timerange"] = "2007/2015" # Use the same timerange as for the other variable.
169
+ era5_dataset["alias"] = era5_dataset["dataset"]
170
+ diagnostic = recipe["diagnostics"]["plot_joint_cli_ta_ref"]
171
+ diagnostic["variables"]["ta"]["additional_datasets"] = [era5_dataset]
172
+ suptitle = "CALIPSO-ICECLOUD / {dataset} {timerange}".format(**era5_dataset)
173
+ diagnostic["scripts"]["plot"]["suptitle"] = suptitle
174
+ diagnostic["scripts"]["plot"]["plot_filename"] = (
175
+ f"jointplot_cli_ta_{suptitle.replace(' ', '_').replace('/', '-')}"
176
+ )
177
+
178
+ # Use the correct obs4MIPs dataset name for dataset that cannot be ingested
179
+ # https://github.com/Climate-REF/climate-ref/issues/260.
180
+ diagnostic = recipe["diagnostics"]["plot_joint_clwvi_pr_ref"]
181
+ diagnostic["variables"]["pr"]["additional_datasets"] = [
182
+ {
183
+ "dataset": "GPCP-V2.3",
184
+ "project": "obs4MIPs",
185
+ "alias": "GPCP-SG",
186
+ "timerange": "1992/2016",
187
+ }
188
+ ]
@@ -61,7 +61,10 @@ class EquilibriumClimateSensitivity(ESMValToolDiagnostic):
61
61
  facets = ("grid_label", "member_id", "source_id", "region", "metric")
62
62
 
63
63
  @staticmethod
64
- def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
64
+ def update_recipe(
65
+ recipe: Recipe,
66
+ input_files: dict[SourceDatasetType, pandas.DataFrame],
67
+ ) -> None:
65
68
  """Update the recipe."""
66
69
  # Only run the diagnostic that computes ECS for a single model.
67
70
  recipe["diagnostics"] = {
@@ -88,21 +91,11 @@ class EquilibriumClimateSensitivity(ESMValToolDiagnostic):
88
91
  # Prepare updated datasets section in recipe. It contains two
89
92
  # datasets, one for the "abrupt-4xCO2" and one for the "piControl"
90
93
  # experiment.
91
- recipe_variables = dataframe_to_recipe(input_files)
92
- recipe_variables = {k: v for k, v in recipe_variables.items() if k != "areacella"}
93
-
94
- # Select a timerange covered by all datasets.
95
- start_times, end_times = [], []
96
- for variable in recipe_variables.values():
97
- for dataset in variable["additional_datasets"]:
98
- start, end = dataset["timerange"].split("/")
99
- start_times.append(start)
100
- end_times.append(end)
101
- timerange = f"{max(start_times)}/{min(end_times)}"
102
-
103
- datasets = recipe_variables["tas"]["additional_datasets"]
104
- for dataset in datasets:
105
- dataset["timerange"] = timerange
94
+ recipe_variables = dataframe_to_recipe(
95
+ input_files[SourceDatasetType.CMIP6],
96
+ equalize_timerange=True,
97
+ )
98
+ recipe["datasets"] = recipe_variables["tas"]["additional_datasets"]
106
99
 
107
100
  # Remove keys from the recipe that are only used for YAML anchors
108
101
  keys_to_remove = [
@@ -116,8 +109,6 @@ class EquilibriumClimateSensitivity(ESMValToolDiagnostic):
116
109
  for key in keys_to_remove:
117
110
  recipe.pop(key, None)
118
111
 
119
- recipe["datasets"] = datasets
120
-
121
112
  @staticmethod
122
113
  def format_result(
123
114
  result_dir: Path,
@@ -54,9 +54,12 @@ class ENSOBasicClimatology(ESMValToolDiagnostic):
54
54
  facets = ()
55
55
 
56
56
  @staticmethod
57
- def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
57
+ def update_recipe(
58
+ recipe: Recipe,
59
+ input_files: dict[SourceDatasetType, pandas.DataFrame],
60
+ ) -> None:
58
61
  """Update the recipe."""
59
- recipe_variables = dataframe_to_recipe(input_files)
62
+ recipe_variables = dataframe_to_recipe(input_files[SourceDatasetType.CMIP6])
60
63
  recipe.pop("datasets")
61
64
  for diagnostic in recipe["diagnostics"].values():
62
65
  for variable in diagnostic["variables"].values():
@@ -97,9 +100,12 @@ class ENSOCharacteristics(ESMValToolDiagnostic):
97
100
  facets = ("grid_label", "member_id", "source_id", "region", "metric")
98
101
 
99
102
  @staticmethod
100
- def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
103
+ def update_recipe(
104
+ recipe: Recipe,
105
+ input_files: dict[SourceDatasetType, pandas.DataFrame],
106
+ ) -> None:
101
107
  """Update the recipe."""
102
- recipe_variables = dataframe_to_recipe(input_files)
108
+ recipe_variables = dataframe_to_recipe(input_files[SourceDatasetType.CMIP6])
103
109
  recipe["datasets"] = recipe_variables["tos"]["additional_datasets"]
104
110
  # TODO: update the observational data requirement once available on ESGF.
105
111
  # Observations - use only one per run
@@ -3,6 +3,7 @@ import pandas
3
3
  from climate_ref_core.constraints import AddSupplementaryDataset, RequireContiguousTimerange
4
4
  from climate_ref_core.datasets import FacetFilter, SourceDatasetType
5
5
  from climate_ref_core.diagnostics import DataRequirement
6
+ from climate_ref_core.metric_values.typing import SeriesDefinition
6
7
  from climate_ref_esmvaltool.diagnostics.base import ESMValToolDiagnostic
7
8
  from climate_ref_esmvaltool.recipe import dataframe_to_recipe
8
9
  from climate_ref_esmvaltool.types import Recipe
@@ -16,6 +17,15 @@ class GlobalMeanTimeseries(ESMValToolDiagnostic):
16
17
  name = "Global Mean Timeseries"
17
18
  slug = "global-mean-timeseries"
18
19
  base_recipe = "examples/recipe_python.yml"
20
+ series = (
21
+ SeriesDefinition(
22
+ file_pattern="timeseries/script1/*.nc",
23
+ dimensions={},
24
+ values_name="tas",
25
+ index_name="time",
26
+ attributes=[],
27
+ ),
28
+ )
19
29
 
20
30
  data_requirements = (
21
31
  DataRequirement(
@@ -31,7 +41,10 @@ class GlobalMeanTimeseries(ESMValToolDiagnostic):
31
41
  facets = ()
32
42
 
33
43
  @staticmethod
34
- def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
44
+ def update_recipe(
45
+ recipe: Recipe,
46
+ input_files: dict[SourceDatasetType, pandas.DataFrame],
47
+ ) -> None:
35
48
  """Update the recipe."""
36
49
  # Clear unwanted elements from the recipe.
37
50
  recipe["datasets"].clear()
@@ -40,7 +53,7 @@ class GlobalMeanTimeseries(ESMValToolDiagnostic):
40
53
  variables.clear()
41
54
 
42
55
  # Prepare updated variables section in recipe.
43
- recipe_variables = dataframe_to_recipe(input_files)
56
+ recipe_variables = dataframe_to_recipe(input_files[SourceDatasetType.CMIP6])
44
57
  recipe_variables = {k: v for k, v in recipe_variables.items() if k != "areacella"}
45
58
  for variable in recipe_variables.values():
46
59
  variable["preprocessor"] = "annual_mean_global"