climate-ref-pmp 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 (29) hide show
  1. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/PKG-INFO +5 -4
  2. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/pyproject.toml +9 -10
  3. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/__init__.py +12 -4
  4. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/diagnostics/__init__.py +2 -0
  5. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/diagnostics/annual_cycle.py +51 -42
  6. climate_ref_pmp-0.6.0/src/climate_ref_pmp/diagnostics/enso.py +245 -0
  7. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/diagnostics/variability_modes.py +6 -7
  8. climate_ref_pmp-0.6.0/src/climate_ref_pmp/drivers/enso_driver.py +458 -0
  9. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/requirements/conda-lock.yml +1809 -2362
  10. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/requirements/environment.yml +1 -0
  11. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/tests/integration/test_diagnostics.py +4 -0
  12. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/tests/unit/conftest.py +1 -1
  13. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/tests/unit/test_annual_cycle.py +143 -21
  14. climate_ref_pmp-0.6.0/tests/unit/test_enso.py +33 -0
  15. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/tests/unit/test_variability_modes.py +1 -12
  16. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/.gitignore +0 -0
  17. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/LICENCE +0 -0
  18. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/NOTICE +0 -0
  19. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/README.md +0 -0
  20. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/conftest.py +0 -0
  21. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/dataset_registry/pmp_climatology.txt +0 -0
  22. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/params/pmp_param_MoV-psl.py +0 -0
  23. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/params/pmp_param_MoV-ts.py +0 -0
  24. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/params/pmp_param_annualcycle_1-clims.py +0 -0
  25. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/params/pmp_param_annualcycle_2-metrics.py +0 -0
  26. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/pmp_driver.py +0 -0
  27. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/src/climate_ref_pmp/py.typed +0 -0
  28. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/tests/unit/test_pmp_driver.py +0 -0
  29. {climate_ref_pmp-0.5.4 → climate_ref_pmp-0.6.0}/tests/unit/test_provider.py +0 -0
@@ -1,14 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: climate-ref-pmp
3
- Version: 0.5.4
3
+ Version: 0.6.0
4
4
  Summary: PMP diagnostic provider for the Rapid Evaluation Framework
5
- Author-email: Jiwoo Lee <jwlee@llnl.gov>
6
- License: Apache-2.0
5
+ Author-email: Jiwoo Lee <jwlee@llnl.gov>, 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-pmp"
3
- version = "0.5.4"
3
+ version = "0.6.0"
4
4
  description = "PMP diagnostic provider for the Rapid Evaluation Framework"
5
5
  readme = "README.md"
6
6
  authors = [
7
- { name = "Jiwoo Lee", email = "jwlee@llnl.gov" }
7
+ { name = "Jiwoo Lee", email = "jwlee@llnl.gov" },
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,17 +20,14 @@ 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
  "climate-ref-core",
24
27
  ]
25
28
 
26
- [project.license]
27
- text = "Apache-2.0"
28
-
29
- [tool.uv]
30
- dev-dependencies = [
31
- ]
29
+ [dependency-groups]
30
+ dev = []
32
31
 
33
32
  [build-system]
34
33
  requires = ["hatchling"]
@@ -4,9 +4,9 @@ Rapid evaluating CMIP data
4
4
 
5
5
  import importlib.metadata
6
6
 
7
- from climate_ref_core.dataset_registry import dataset_registry_manager
7
+ from climate_ref_core.dataset_registry import DATASET_URL, dataset_registry_manager
8
8
  from climate_ref_core.providers import CondaDiagnosticProvider
9
- from climate_ref_pmp.diagnostics import AnnualCycle, ExtratropicalModesOfVariability
9
+ from climate_ref_pmp.diagnostics import ENSO, AnnualCycle, ExtratropicalModesOfVariability
10
10
 
11
11
  __version__ = importlib.metadata.version("climate-ref-pmp")
12
12
 
@@ -14,6 +14,15 @@ __version__ = importlib.metadata.version("climate-ref-pmp")
14
14
  # PMP uses a conda environment to run the diagnostics
15
15
  provider = CondaDiagnosticProvider("PMP", __version__)
16
16
 
17
+ # Annual cycle diagnostics and metrics
18
+ provider.register(AnnualCycle())
19
+
20
+ # ENSO diagnostics and metrics
21
+ # provider.register(ENSO("ENSO_perf")) # Assigned to ESMValTool
22
+ provider.register(ENSO("ENSO_tel"))
23
+ provider.register(ENSO("ENSO_proc"))
24
+
25
+ # Extratropical modes of variability diagnostics and metrics
17
26
  provider.register(ExtratropicalModesOfVariability("PDO"))
18
27
  provider.register(ExtratropicalModesOfVariability("NPGO"))
19
28
  provider.register(ExtratropicalModesOfVariability("NAO"))
@@ -21,12 +30,11 @@ provider.register(ExtratropicalModesOfVariability("NAM"))
21
30
  provider.register(ExtratropicalModesOfVariability("PNA"))
22
31
  provider.register(ExtratropicalModesOfVariability("NPO"))
23
32
  provider.register(ExtratropicalModesOfVariability("SAM"))
24
- provider.register(AnnualCycle())
25
33
 
26
34
 
27
35
  dataset_registry_manager.register(
28
36
  "pmp-climatology",
29
- "https://pub-b093171261094c4ea9adffa01f94ee06.r2.dev/",
37
+ base_url=DATASET_URL,
30
38
  package="climate_ref_pmp.dataset_registry",
31
39
  resource="pmp_climatology.txt",
32
40
  )
@@ -1,9 +1,11 @@
1
1
  """PMP diagnostics."""
2
2
 
3
3
  from climate_ref_pmp.diagnostics.annual_cycle import AnnualCycle
4
+ from climate_ref_pmp.diagnostics.enso import ENSO
4
5
  from climate_ref_pmp.diagnostics.variability_modes import ExtratropicalModesOfVariability
5
6
 
6
7
  __all__ = [
8
+ "ENSO",
7
9
  "AnnualCycle",
8
10
  "ExtratropicalModesOfVariability",
9
11
  ]
@@ -15,6 +15,44 @@ from climate_ref_core.pycmec.metric import remove_dimensions
15
15
  from climate_ref_pmp.pmp_driver import build_glob_pattern, build_pmp_command, process_json_result
16
16
 
17
17
 
18
+ def make_data_requirement(variable_id: str, obs_source: str) -> tuple[DataRequirement, DataRequirement]:
19
+ """
20
+ Create a data requirement for the annual cycle diagnostic.
21
+
22
+ Parameters
23
+ ----------
24
+ variable_id : str
25
+ The variable ID to filter the data requirement.
26
+ obs_source : str
27
+ The observation source ID to filter the data requirement.
28
+
29
+ Returns
30
+ -------
31
+ DataRequirement
32
+ A DataRequirement object containing the necessary filters and groupings.
33
+ """
34
+ return (
35
+ DataRequirement(
36
+ source_type=SourceDatasetType.PMPClimatology,
37
+ filters=(FacetFilter(facets={"source_id": (obs_source,), "variable_id": (variable_id,)}),),
38
+ group_by=("variable_id", "source_id"),
39
+ ),
40
+ DataRequirement(
41
+ source_type=SourceDatasetType.CMIP6,
42
+ filters=(
43
+ FacetFilter(
44
+ facets={
45
+ "frequency": "mon",
46
+ "experiment_id": ("amip", "historical", "hist-GHG", "piControl"),
47
+ "variable_id": (variable_id,),
48
+ }
49
+ ),
50
+ ),
51
+ group_by=("variable_id", "source_id", "experiment_id", "member_id", "grid_label"),
52
+ ),
53
+ )
54
+
55
+
18
56
  class AnnualCycle(CommandLineDiagnostic):
19
57
  """
20
58
  Calculate the annual cycle for a dataset
@@ -32,49 +70,20 @@ class AnnualCycle(CommandLineDiagnostic):
32
70
  "statistic",
33
71
  "season",
34
72
  )
73
+
35
74
  data_requirements = (
36
- # Surface temperature
37
- (
38
- DataRequirement(
39
- source_type=SourceDatasetType.PMPClimatology,
40
- filters=(FacetFilter(facets={"source_id": ("ERA-5",), "variable_id": ("ts",)}),),
41
- group_by=("variable_id", "source_id"),
42
- ),
43
- DataRequirement(
44
- source_type=SourceDatasetType.CMIP6,
45
- filters=(
46
- FacetFilter(
47
- facets={
48
- "frequency": "mon",
49
- "experiment_id": ("amip", "historical", "hist-GHG", "piControl"),
50
- "variable_id": ("ts",),
51
- }
52
- ),
53
- ),
54
- group_by=("variable_id", "source_id", "experiment_id", "member_id"),
55
- ),
56
- ),
57
- # Precipitation
58
- (
59
- DataRequirement(
60
- source_type=SourceDatasetType.PMPClimatology,
61
- filters=(FacetFilter(facets={"source_id": ("GPCP-Monthly-3-2",), "variable_id": ("pr",)}),),
62
- group_by=("variable_id", "source_id"),
63
- ),
64
- DataRequirement(
65
- source_type=SourceDatasetType.CMIP6,
66
- filters=(
67
- FacetFilter(
68
- facets={
69
- "frequency": "mon",
70
- "experiment_id": ("amip", "historical", "hist-GHG", "piControl"),
71
- "variable_id": ("pr",),
72
- }
73
- ),
74
- ),
75
- group_by=("variable_id", "source_id", "experiment_id", "member_id"),
76
- ),
77
- ),
75
+ make_data_requirement("ts", "ERA-5"),
76
+ make_data_requirement("uas", "ERA-5"),
77
+ make_data_requirement("vas", "ERA-5"),
78
+ make_data_requirement("psl", "ERA-5"),
79
+ make_data_requirement("pr", "GPCP-Monthly-3-2"),
80
+ make_data_requirement("rlds", "CERES-EBAF-4-2"),
81
+ make_data_requirement("rlus", "CERES-EBAF-4-2"),
82
+ make_data_requirement("rlut", "CERES-EBAF-4-2"),
83
+ make_data_requirement("rsds", "CERES-EBAF-4-2"),
84
+ make_data_requirement("rsdt", "CERES-EBAF-4-2"),
85
+ make_data_requirement("rsus", "CERES-EBAF-4-2"),
86
+ make_data_requirement("rsut", "CERES-EBAF-4-2"),
78
87
  )
79
88
 
80
89
  def __init__(self) -> None:
@@ -0,0 +1,245 @@
1
+ import json
2
+ import os
3
+ from collections.abc import Collection, Iterable
4
+ from typing import Any
5
+
6
+ from loguru import logger
7
+
8
+ from climate_ref_core.constraints import AddSupplementaryDataset
9
+ from climate_ref_core.datasets import DatasetCollection, FacetFilter, SourceDatasetType
10
+ from climate_ref_core.diagnostics import (
11
+ CommandLineDiagnostic,
12
+ DataRequirement,
13
+ ExecutionDefinition,
14
+ ExecutionResult,
15
+ )
16
+ from climate_ref_pmp.pmp_driver import _get_resource, process_json_result
17
+
18
+
19
+ class ENSO(CommandLineDiagnostic):
20
+ """
21
+ Calculate the ENSO performance metrics for a dataset
22
+ """
23
+
24
+ facets = ("source_id", "member_id", "grid_label", "experiment_id", "metric", "reference_datasets")
25
+
26
+ def __init__(self, metrics_collection: str, experiments: Collection[str] = ("historical",)) -> None:
27
+ self.name = metrics_collection
28
+ self.slug = metrics_collection.lower()
29
+ self.metrics_collection = metrics_collection
30
+ self.parameter_file = "pmp_param_enso.py"
31
+ self.obs_sources: tuple[str, ...]
32
+ self.model_variables: tuple[str, ...]
33
+
34
+ if metrics_collection == "ENSO_perf": # pragma: no cover
35
+ self.model_variables = ("pr", "ts", "tauu")
36
+ self.obs_sources = ("GPCP-Monthly-3-2", "TropFlux-1-0", "HadISST-1-1")
37
+ elif metrics_collection == "ENSO_tel":
38
+ self.model_variables = ("pr", "ts")
39
+ self.obs_sources = ("GPCP-Monthly-3-2", "TropFlux-1-0", "HadISST-1-1")
40
+ elif metrics_collection == "ENSO_proc":
41
+ self.model_variables = ("ts", "tauu", "hfls", "hfss", "rlds", "rlus", "rsds", "rsus")
42
+ self.obs_sources = (
43
+ "GPCP-Monthly-3-2",
44
+ "TropFlux-1-0",
45
+ "HadISST-1-1",
46
+ "CERES-EBAF-4-2",
47
+ )
48
+ else:
49
+ raise ValueError(
50
+ f"Unknown metrics collection: {metrics_collection}. "
51
+ "Valid options are: ENSO_perf, ENSO_tel, ENSO_proc"
52
+ )
53
+
54
+ self.data_requirements = self._get_data_requirements(experiments)
55
+
56
+ def _get_data_requirements(
57
+ self,
58
+ experiments: Collection[str] = ("historical",),
59
+ ) -> tuple[DataRequirement, DataRequirement]:
60
+ filters = [
61
+ FacetFilter(
62
+ facets={
63
+ "frequency": "mon",
64
+ "experiment_id": tuple(experiments),
65
+ "variable_id": self.model_variables,
66
+ }
67
+ )
68
+ ]
69
+
70
+ return (
71
+ DataRequirement(
72
+ source_type=SourceDatasetType.obs4MIPs,
73
+ filters=(
74
+ FacetFilter(facets={"source_id": self.obs_sources, "variable_id": self.model_variables}),
75
+ ),
76
+ group_by=("activity_id",),
77
+ ),
78
+ DataRequirement(
79
+ source_type=SourceDatasetType.CMIP6,
80
+ filters=tuple(filters),
81
+ group_by=("source_id", "experiment_id", "member_id", "grid_label"),
82
+ constraints=(
83
+ AddSupplementaryDataset.from_defaults("areacella", SourceDatasetType.CMIP6),
84
+ AddSupplementaryDataset.from_defaults("sftlf", SourceDatasetType.CMIP6),
85
+ ),
86
+ ),
87
+ )
88
+
89
+ def build_cmd(self, definition: ExecutionDefinition) -> Iterable[str]:
90
+ """
91
+ Run the diagnostic on the given configuration.
92
+
93
+ Parameters
94
+ ----------
95
+ definition : ExecutionDefinition
96
+ The configuration to run the diagnostic on.
97
+
98
+ Returns
99
+ -------
100
+ :
101
+ The result of running the diagnostic.
102
+ """
103
+ mc_name = self.metrics_collection
104
+
105
+ # ------------------------------------------------
106
+ # Get the input datasets information for the model
107
+ # ------------------------------------------------
108
+ input_datasets = definition.datasets[SourceDatasetType.CMIP6]
109
+ input_selectors = input_datasets.selector_dict()
110
+ source_id = input_selectors["source_id"]
111
+ member_id = input_selectors["member_id"]
112
+ experiment_id = input_selectors["experiment_id"]
113
+ variable_ids = set(input_datasets["variable_id"].unique()) - {"areacella", "sftlf"}
114
+ mod_run = f"{source_id}_{member_id}"
115
+
116
+ # We only need one entry for the model run
117
+ dict_mod: dict[str, dict[str, Any]] = {mod_run: {}}
118
+
119
+ def extract_variable(dc: DatasetCollection, variable: str) -> list[str]:
120
+ return dc.datasets[input_datasets["variable_id"] == variable]["path"].to_list() # type: ignore
121
+
122
+ # TO DO: Get the path to the files per variable
123
+ for variable in variable_ids:
124
+ list_files = extract_variable(input_datasets, variable)
125
+ list_areacella = extract_variable(input_datasets, "areacella")
126
+ list_sftlf = extract_variable(input_datasets, "sftlf")
127
+
128
+ if len(list_files) > 0:
129
+ dict_mod[mod_run][variable] = {
130
+ "path + filename": list_files,
131
+ "varname": variable,
132
+ "path + filename_area": list_areacella,
133
+ "areaname": "areacella",
134
+ "path + filename_landmask": list_sftlf,
135
+ "landmaskname": "sftlf",
136
+ }
137
+
138
+ # -------------------------------------------------------
139
+ # Get the input datasets information for the observations
140
+ # -------------------------------------------------------
141
+ reference_dataset = definition.datasets[SourceDatasetType.obs4MIPs]
142
+ reference_dataset_names = reference_dataset["source_id"].unique()
143
+
144
+ dict_obs: dict[str, dict[str, Any]] = {}
145
+
146
+ # TO DO: Get the path to the files per variable and per source
147
+ for obs_name in reference_dataset_names:
148
+ dict_obs[obs_name] = {}
149
+ for variable in variable_ids:
150
+ # Get the list of files for the current variable and observation source
151
+ list_files = reference_dataset.datasets[
152
+ (reference_dataset["variable_id"] == variable)
153
+ & (reference_dataset["source_id"] == obs_name)
154
+ ]["path"].to_list()
155
+ # If the list is not empty, add it to the dictionary
156
+ if len(list_files) > 0:
157
+ dict_obs[obs_name][variable] = {
158
+ "path + filename": list_files,
159
+ "varname": variable,
160
+ }
161
+
162
+ # Create input directory
163
+ dict_datasets = {
164
+ "model": dict_mod,
165
+ "observations": dict_obs,
166
+ "metricsCollection": mc_name,
167
+ "experiment_id": experiment_id,
168
+ }
169
+
170
+ # Create JSON file for dictDatasets
171
+ json_file = os.path.join(
172
+ definition.output_directory, f"input_{mc_name}_{source_id}_{experiment_id}_{member_id}.json"
173
+ )
174
+ with open(json_file, "w") as f:
175
+ json.dump(dict_datasets, f, indent=4)
176
+ logger.debug(f"JSON file created: {json_file}")
177
+
178
+ driver_file = _get_resource("climate_ref_pmp.drivers", "enso_driver.py", use_resources=True)
179
+ return [
180
+ "python",
181
+ driver_file,
182
+ "--metrics_collection",
183
+ mc_name,
184
+ "--experiment_id",
185
+ experiment_id,
186
+ "--input_json_path",
187
+ json_file,
188
+ "--output_directory",
189
+ str(definition.output_directory),
190
+ ]
191
+
192
+ def build_execution_result(self, definition: ExecutionDefinition) -> ExecutionResult:
193
+ """
194
+ Build a diagnostic result from the output of the PMP driver
195
+
196
+ Parameters
197
+ ----------
198
+ definition
199
+ Definition of the diagnostic execution
200
+
201
+ Returns
202
+ -------
203
+ Result of the diagnostic execution
204
+ """
205
+ input_datasets = definition.datasets[SourceDatasetType.CMIP6]
206
+ source_id = input_datasets["source_id"].unique()[0]
207
+ experiment_id = input_datasets["experiment_id"].unique()[0]
208
+ member_id = input_datasets["member_id"].unique()[0]
209
+ mc_name = self.metrics_collection
210
+ pattern = f"{mc_name}_{source_id}_{experiment_id}_{member_id}"
211
+
212
+ # Find the results files
213
+ results_files = list(definition.output_directory.glob(f"{pattern}_cmec.json"))
214
+ logger.debug(f"Results files: {results_files}")
215
+
216
+ if len(results_files) != 1: # pragma: no cover
217
+ logger.warning(f"A single cmec output file not found: {results_files}")
218
+ return ExecutionResult.build_from_failure(definition)
219
+
220
+ # Find the other outputs
221
+ png_files = [definition.as_relative_path(f) for f in definition.output_directory.glob("*.png")]
222
+ data_files = [definition.as_relative_path(f) for f in definition.output_directory.glob("*.nc")]
223
+
224
+ cmec_output, cmec_metric = process_json_result(results_files[0], png_files, data_files)
225
+
226
+ input_selectors = definition.datasets[SourceDatasetType.CMIP6].selector_dict()
227
+ cmec_metric_bundle = cmec_metric.remove_dimensions(
228
+ [
229
+ "model",
230
+ "realization",
231
+ ],
232
+ ).prepend_dimensions(
233
+ {
234
+ "source_id": input_selectors["source_id"],
235
+ "member_id": input_selectors["member_id"],
236
+ "grid_label": input_selectors["grid_label"],
237
+ "experiment_id": input_selectors["experiment_id"],
238
+ }
239
+ )
240
+
241
+ return ExecutionResult.build_from_output_bundle(
242
+ definition,
243
+ cmec_output_bundle=cmec_output,
244
+ cmec_metric_bundle=cmec_metric_bundle,
245
+ )
@@ -37,10 +37,10 @@ class ExtratropicalModesOfVariability(CommandLineDiagnostic):
37
37
  self.name = f"Extratropical modes of variability: {mode_id}"
38
38
  self.slug = f"extratropical-modes-of-variability-{mode_id.lower()}"
39
39
 
40
- def get_data_requirements(
40
+ def _get_data_requirements(
41
41
  obs_source: str,
42
42
  obs_variable: str,
43
- cmip_variable: str,
43
+ model_variable: str,
44
44
  extra_experiments: str | tuple[str, ...] | list[str] = (),
45
45
  ) -> tuple[DataRequirement, DataRequirement]:
46
46
  filters = [
@@ -48,7 +48,7 @@ class ExtratropicalModesOfVariability(CommandLineDiagnostic):
48
48
  facets={
49
49
  "frequency": "mon",
50
50
  "experiment_id": ("historical", "hist-GHG", "piControl", *extra_experiments),
51
- "variable_id": cmip_variable,
51
+ "variable_id": model_variable,
52
52
  }
53
53
  )
54
54
  ]
@@ -64,17 +64,16 @@ class ExtratropicalModesOfVariability(CommandLineDiagnostic):
64
64
  DataRequirement(
65
65
  source_type=SourceDatasetType.CMIP6,
66
66
  filters=tuple(filters),
67
- # TODO: remove unneeded variant_label
68
- group_by=("source_id", "experiment_id", "variant_label", "member_id"),
67
+ group_by=("source_id", "experiment_id", "member_id", "grid_label"),
69
68
  ),
70
69
  )
71
70
 
72
71
  if self.mode_id in self.ts_modes:
73
72
  self.parameter_file = "pmp_param_MoV-ts.py"
74
- self.data_requirements = get_data_requirements("HadISST-1-1", "ts", "ts")
73
+ self.data_requirements = _get_data_requirements("HadISST-1-1", "ts", "ts")
75
74
  elif self.mode_id in self.psl_modes:
76
75
  self.parameter_file = "pmp_param_MoV-psl.py"
77
- self.data_requirements = get_data_requirements("20CR", "psl", "psl", extra_experiments=("amip",))
76
+ self.data_requirements = _get_data_requirements("20CR", "psl", "psl", extra_experiments=("amip",))
78
77
  else:
79
78
  raise ValueError(
80
79
  f"Unknown mode_id '{self.mode_id}'. Must be one of {self.ts_modes + self.psl_modes}"