climate-ref-pmp 0.5.0__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.
@@ -0,0 +1,32 @@
1
+ """
2
+ Rapid evaluating CMIP data
3
+ """
4
+
5
+ import importlib.metadata
6
+
7
+ from climate_ref_core.dataset_registry import dataset_registry_manager
8
+ from climate_ref_core.providers import CondaDiagnosticProvider
9
+ from climate_ref_pmp.diagnostics import AnnualCycle, ExtratropicalModesOfVariability
10
+
11
+ __version__ = importlib.metadata.version("climate-ref-pmp")
12
+
13
+ # Create the PMP diagnostics provider
14
+ # PMP uses a conda environment to run the diagnostics
15
+ provider = CondaDiagnosticProvider("PMP", __version__)
16
+
17
+ provider.register(ExtratropicalModesOfVariability("PDO"))
18
+ provider.register(ExtratropicalModesOfVariability("NPGO"))
19
+ provider.register(ExtratropicalModesOfVariability("NAO"))
20
+ provider.register(ExtratropicalModesOfVariability("NAM"))
21
+ provider.register(ExtratropicalModesOfVariability("PNA"))
22
+ provider.register(ExtratropicalModesOfVariability("NPO"))
23
+ provider.register(ExtratropicalModesOfVariability("SAM"))
24
+ provider.register(AnnualCycle())
25
+
26
+
27
+ dataset_registry_manager.register(
28
+ "pmp-climatology",
29
+ "https://pub-b093171261094c4ea9adffa01f94ee06.r2.dev/",
30
+ package="climate_ref_pmp.dataset_registry",
31
+ resource="pmp_climatology.txt",
32
+ )
@@ -0,0 +1,25 @@
1
+ PMP_obs4MIPsClims/pr/gr/v20250211/pr_mon_GPCP-Monthly-3-2_RSS_gr_198301-200412_AC_v20250211_interp_2.5x2.5.nc md5:02661bfe30f79b162619a960de08784d
2
+ PMP_obs4MIPsClims/psl/gr/v20250224/psl_mon_ERA-5_PCMDI_gr_198101-200412_AC_v20250224_2.5x2.5.nc md5:098fddaadcc4b5b72771dc493293f68c
3
+ PMP_obs4MIPsClims/rldscs/gr/v20250213/rldscs_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:f6ce612860eed79c04577f6bf53de6b8
4
+ PMP_obs4MIPsClims/rlds/gr/v20250213/rlds_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:fe229671795fffc1209e364cdde6bc58
5
+ PMP_obs4MIPsClims/rltcre/gr/v20250213/rltcre_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:28a2ea1301454f1c65381aaf18dc927f
6
+ PMP_obs4MIPsClims/rlus/gr/v20250213/rlus_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:c29d0182a759aeaf62a741b8b277bd31
7
+ PMP_obs4MIPsClims/rlutcs/gr/v20250213/rlutcs_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:d8255cb36020f616131e6abc3623c0ff
8
+ PMP_obs4MIPsClims/rlut/gr/v20250213/rlut_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:92427d58e789c28e69d4e7ad7f10f78a
9
+ PMP_obs4MIPsClims/rsdscs/gr/v20250213/rsdscs_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:4f7693ee9c739f8891d38430030d5d0f
10
+ PMP_obs4MIPsClims/rsds/gr/v20250213/rsds_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:631b4d9168fd4b558f1db8f3dc7d9fd8
11
+ PMP_obs4MIPsClims/rsdt/gr/v20250213/rsdt_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:6908e55d39dce5c696c2016c7a668759
12
+ PMP_obs4MIPsClims/rstcre/gr/v20250213/rstcre_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:dcb2ec36efcac91fb1060b4f53b9dc3a
13
+ PMP_obs4MIPsClims/rsuscs/gr/v20250213/rsuscs_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:0e06fdbab1f44a14d85774a65d3a97ab
14
+ PMP_obs4MIPsClims/rsus/gr/v20250213/rsus_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:6ff6cf0d715bb12a4cceb5c05531682b
15
+ PMP_obs4MIPsClims/rsutcs/gr/v20250213/rsutcs_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:982914ff7f625f43d6a86f5ab3dbf951
16
+ PMP_obs4MIPsClims/rsut/gr/v20250213/rsut_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:36aee3f4ce60e468e0a5027482960e3b
17
+ PMP_obs4MIPsClims/rt/gr/v20250213/rt_mon_CERES-EBAF-4-2_RSS_gr_200101-200412_AC_v20250213_2.5x2.5.nc md5:8904dfea492b121e7e4252ca340e782f
18
+ PMP_obs4MIPsClims/ta/gr/v20250224/ta_mon_ERA-5_PCMDI_gr_198101-200412_AC_v20250224_2.5x2.5.nc md5:185f9c4382e1585bf4c91f30af371471
19
+ PMP_obs4MIPsClims/tas/gr/v20250224/tas_mon_ERA-5_PCMDI_gr_198101-200412_AC_v20250224_2.5x2.5.nc md5:e9b825a3fcb8d246537dbf7533d0a85d
20
+ PMP_obs4MIPsClims/ts/gr/v20250224/ts_mon_ERA-5_PCMDI_gr_198101-200412_AC_v20250224_2.5x2.5.nc md5:66df004506ddab80d980a37ede4b6298
21
+ PMP_obs4MIPsClims/ua/gr/v20250224/ua_mon_ERA-5_PCMDI_gr_198101-200412_AC_v20250224_2.5x2.5.nc md5:267cf48858a079a500b1b6fc348f6411
22
+ PMP_obs4MIPsClims/uas/gr/v20250224/uas_mon_ERA-5_PCMDI_gr_198101-200412_AC_v20250224_2.5x2.5.nc md5:7a31d4b16710d698805c601af4977065
23
+ PMP_obs4MIPsClims/va/gr/v20250225/va_mon_ERA-5_PCMDI_gr_198101-200412_AC_v20250225_2.5x2.5.nc md5:f8ad5d3d6c7e31f4c76619ea3242f1ff
24
+ PMP_obs4MIPsClims/vas/gr/v20250225/vas_mon_ERA-5_PCMDI_gr_198101-200412_AC_v20250225_2.5x2.5.nc md5:f009b3fd04e65f80e3336f2be8c99e05
25
+ PMP_obs4MIPsClims/zg/gr/v20250225/zg_mon_ERA-5_PCMDI_gr_198101-200412_AC_v20250225_2.5x2.5.nc md5:6d20c4e668cc5c31920162cab0cb5579
@@ -0,0 +1,9 @@
1
+ """PMP diagnostics."""
2
+
3
+ from climate_ref_pmp.diagnostics.annual_cycle import AnnualCycle
4
+ from climate_ref_pmp.diagnostics.variability_modes import ExtratropicalModesOfVariability
5
+
6
+ __all__ = [
7
+ "AnnualCycle",
8
+ "ExtratropicalModesOfVariability",
9
+ ]
@@ -0,0 +1,337 @@
1
+ import datetime
2
+ import json
3
+ from collections.abc import Iterable
4
+ from typing import Any
5
+
6
+ from loguru import logger
7
+
8
+ from climate_ref_core.datasets import FacetFilter, SourceDatasetType
9
+ from climate_ref_core.diagnostics import (
10
+ CommandLineDiagnostic,
11
+ DataRequirement,
12
+ ExecutionDefinition,
13
+ ExecutionResult,
14
+ )
15
+ from climate_ref_pmp.pmp_driver import build_glob_pattern, build_pmp_command, process_json_result
16
+
17
+
18
+ class AnnualCycle(CommandLineDiagnostic):
19
+ """
20
+ Calculate the annual cycle for a dataset
21
+ """
22
+
23
+ name = "Annual Cycle"
24
+ slug = "annual-cycle"
25
+ facets = ("model", "realization", "reference", "mode", "season", "method", "statistic")
26
+ data_requirements = (
27
+ # Surface temperature
28
+ (
29
+ DataRequirement(
30
+ source_type=SourceDatasetType.PMPClimatology,
31
+ filters=(FacetFilter(facets={"source_id": ("ERA-5",), "variable_id": ("ts",)}),),
32
+ group_by=("variable_id", "source_id"),
33
+ ),
34
+ DataRequirement(
35
+ source_type=SourceDatasetType.CMIP6,
36
+ filters=(
37
+ FacetFilter(
38
+ facets={
39
+ "frequency": "mon",
40
+ "experiment_id": ("amip", "historical", "hist-GHG", "piControl"),
41
+ "variable_id": ("ts",),
42
+ }
43
+ ),
44
+ ),
45
+ group_by=("variable_id", "source_id", "experiment_id", "member_id"),
46
+ ),
47
+ ),
48
+ # Precipitation
49
+ (
50
+ DataRequirement(
51
+ source_type=SourceDatasetType.PMPClimatology,
52
+ filters=(FacetFilter(facets={"source_id": ("GPCP-Monthly-3-2",), "variable_id": ("pr",)}),),
53
+ group_by=("variable_id", "source_id"),
54
+ ),
55
+ DataRequirement(
56
+ source_type=SourceDatasetType.CMIP6,
57
+ filters=(
58
+ FacetFilter(
59
+ facets={
60
+ "frequency": "mon",
61
+ "experiment_id": ("amip", "historical", "hist-GHG", "piControl"),
62
+ "variable_id": ("pr",),
63
+ }
64
+ ),
65
+ ),
66
+ group_by=("variable_id", "source_id", "experiment_id", "member_id"),
67
+ ),
68
+ ),
69
+ )
70
+
71
+ def __init__(self) -> None:
72
+ self.parameter_file_1 = "pmp_param_annualcycle_1-clims.py"
73
+ self.parameter_file_2 = "pmp_param_annualcycle_2-metrics.py"
74
+
75
+ def build_cmd(self, definition: ExecutionDefinition) -> Iterable[str]:
76
+ """
77
+ Build the command to run the diagnostic
78
+
79
+ Parameters
80
+ ----------
81
+ definition
82
+ Definition of the diagnostic execution
83
+
84
+ Returns
85
+ -------
86
+ Command arguments to execute in the PMP environment
87
+ """
88
+ raise NotImplementedError("Function not required")
89
+
90
+ def build_cmds(self, definition: ExecutionDefinition) -> list[list[str]]:
91
+ """
92
+ Build the command to run the diagnostic
93
+
94
+ Parameters
95
+ ----------
96
+ definition
97
+ Definition of the diagnostic execution
98
+
99
+ Returns
100
+ -------
101
+ Command arguments to execute in the PMP environment
102
+ """
103
+ input_datasets = definition.datasets[SourceDatasetType.CMIP6]
104
+ source_id = input_datasets["source_id"].unique()[0]
105
+ experiment_id = input_datasets["experiment_id"].unique()[0]
106
+ member_id = input_datasets["member_id"].unique()[0]
107
+ variable_id = input_datasets["variable_id"].unique()[0]
108
+
109
+ logger.debug(f"input_datasets['source_id'].unique(): {input_datasets['source_id'].unique()}")
110
+ logger.debug(f"input_datasets['experiment_id'].unique(): {input_datasets['experiment_id'].unique()}")
111
+ logger.debug(f"input_datasets['member_id'].unique(): {input_datasets['member_id'].unique()}")
112
+ logger.debug(f"input_datasets['variable_id'].unique(): {input_datasets['variable_id'].unique()}")
113
+
114
+ model_files_raw = input_datasets.path.to_list()
115
+ if len(model_files_raw) == 1:
116
+ model_files = model_files_raw[0] # If only one file, use it directly
117
+ elif len(model_files_raw) > 1:
118
+ model_files = build_glob_pattern(model_files_raw) # If multiple files, build a glob pattern
119
+ else:
120
+ raise ValueError("No model files found")
121
+
122
+ logger.debug("build_cmd start")
123
+
124
+ logger.debug(f"input_datasets: {input_datasets}")
125
+ logger.debug(f"input_datasets.keys(): {input_datasets.keys()}")
126
+ logger.debug(f"input_datasets['variable_id']: {input_datasets['variable_id']}")
127
+
128
+ logger.debug(f"source_id: {source_id}")
129
+ logger.debug(f"experiment_id: {experiment_id}")
130
+ logger.debug(f"member_id: {member_id}")
131
+ logger.debug(f"variable_id: {variable_id}")
132
+
133
+ reference_dataset = definition.datasets[SourceDatasetType.PMPClimatology]
134
+ reference_dataset_name = reference_dataset["source_id"].unique()[0]
135
+ reference_dataset_path = reference_dataset.datasets.iloc[0]["path"]
136
+
137
+ logger.debug(f"reference_dataset.datasets: {reference_dataset.datasets}")
138
+ logger.debug(f"reference_dataset['source_id']: {reference_dataset['source_id']}")
139
+ logger.debug(
140
+ f"reference_dataset.datasets.iloc[0]['path']: {reference_dataset.datasets.iloc[0]['path']}"
141
+ )
142
+
143
+ logger.debug(f"reference_dataset_name: {reference_dataset_name}")
144
+ logger.debug(f"reference_dataset_path: {reference_dataset_path}")
145
+
146
+ output_directory_path = str(definition.output_directory)
147
+
148
+ cmds = []
149
+
150
+ # ----------------------------------------------
151
+ # PART 1: Build the command to get climatologies
152
+ # ----------------------------------------------
153
+ # Model
154
+ data_name = f"{source_id}_{experiment_id}_{member_id}"
155
+ data_path = model_files
156
+ params = {
157
+ "driver_file": "mean_climate/pcmdi_compute_climatologies.py",
158
+ "parameter_file": self.parameter_file_1,
159
+ "vars": variable_id,
160
+ "infile": data_path,
161
+ "outfile": f"{output_directory_path}/{variable_id}_{data_name}_clims.nc",
162
+ }
163
+
164
+ cmds.append(build_pmp_command(**params))
165
+
166
+ # ----------------------------------------------
167
+ # PART 2: Build the command to calculate diagnostics
168
+ # ----------------------------------------------
169
+
170
+ # Reference
171
+ obs_dict = {
172
+ variable_id: {
173
+ reference_dataset_name: {
174
+ "template": reference_dataset_path,
175
+ },
176
+ "default": reference_dataset_name,
177
+ }
178
+ }
179
+
180
+ # Generate a JSON file based on the obs_dict
181
+ with open(f"{output_directory_path}/obs_dict.json", "w") as f:
182
+ json.dump(obs_dict, f)
183
+
184
+ date = datetime.datetime.now().strftime("%Y%m%d")
185
+
186
+ params = {
187
+ "driver_file": "mean_climate/mean_climate_driver.py",
188
+ "parameter_file": self.parameter_file_2,
189
+ "vars": variable_id,
190
+ "custom_observations": f"{output_directory_path}/obs_dict.json",
191
+ "test_data_path": output_directory_path,
192
+ "test_data_set": source_id,
193
+ "realization": member_id,
194
+ "filename_template": f"{variable_id}_{data_name}_clims.198101-200512.AC.v{date}.nc",
195
+ "metrics_output_path": output_directory_path,
196
+ "cmec": "",
197
+ }
198
+
199
+ cmds.append(build_pmp_command(**params))
200
+
201
+ return cmds
202
+
203
+ def build_execution_result(self, definition: ExecutionDefinition) -> ExecutionResult:
204
+ """
205
+ Build a diagnostic result from the output of the PMP driver
206
+
207
+ Parameters
208
+ ----------
209
+ definition
210
+ Definition of the diagnostic execution
211
+
212
+ Returns
213
+ -------
214
+ Result of the diagnostic execution
215
+ """
216
+ input_datasets = definition.datasets[SourceDatasetType.CMIP6]
217
+ variable_id = input_datasets["variable_id"].unique()[0]
218
+
219
+ results_directory = definition.output_directory
220
+ png_directory = results_directory / variable_id
221
+ data_directory = results_directory / variable_id
222
+
223
+ logger.debug(f"results_directory: {results_directory}")
224
+ logger.debug(f"png_directory: {png_directory}")
225
+ logger.debug(f"data_directory: {data_directory}")
226
+
227
+ # Find the executions file
228
+ results_files = list(results_directory.glob("*_cmec.json"))
229
+ if len(results_files) != 1: # pragma: no cover
230
+ return ExecutionResult.build_from_failure(definition)
231
+ else:
232
+ results_file = results_files[0]
233
+ logger.debug(f"results_file: {results_file}")
234
+
235
+ # Rewrite executions file for compatibility
236
+ with open(results_file) as f:
237
+ results = json.load(f)
238
+ results_transformed = _transform_results(results)
239
+
240
+ # Get the stem (filename without extension)
241
+ stem = results_file.stem
242
+
243
+ # Create the new filename
244
+ results_file_transformed = results_file.with_name(f"{stem}_transformed.json")
245
+
246
+ with open(results_file_transformed, "w") as f:
247
+ # Write the transformed executions back to the file
248
+ json.dump(results_transformed, f, indent=4)
249
+ logger.debug(f"Transformed executions written to {results_file_transformed}")
250
+
251
+ # Find the other outputs
252
+ png_files = list(png_directory.glob("*.png"))
253
+ data_files = list(data_directory.glob("*.nc"))
254
+
255
+ cmec_output, cmec_metric = process_json_result(results_file_transformed, png_files, data_files)
256
+
257
+ return ExecutionResult.build_from_output_bundle(
258
+ definition,
259
+ cmec_output_bundle=cmec_output,
260
+ cmec_metric_bundle=cmec_metric,
261
+ )
262
+
263
+ def run(self, definition: ExecutionDefinition) -> ExecutionResult:
264
+ """
265
+ Run the diagnostic on the given configuration.
266
+
267
+ Parameters
268
+ ----------
269
+ definition : ExecutionDefinition
270
+ The configuration to run the diagnostic on.
271
+
272
+ Returns
273
+ -------
274
+ :
275
+ The result of running the diagnostic.
276
+ """
277
+ logger.debug("PMP annual cycle run start")
278
+ cmds = self.build_cmds(definition)
279
+
280
+ runs = [self.provider.run(cmd) for cmd in cmds]
281
+ logger.debug(f"runs: {runs}")
282
+
283
+ return self.build_execution_result(definition)
284
+
285
+
286
+ def _transform_results(data: dict[str, Any]) -> dict[str, Any]:
287
+ """
288
+ Transform the executions dictionary to match the expected structure.
289
+
290
+ Parameters
291
+ ----------
292
+ data : dict
293
+ The original executions dictionary.
294
+
295
+ Returns
296
+ -------
297
+ dict
298
+ The transformed executions dictionary.
299
+ """
300
+ # Remove the "CalendarMonths" key from the nested structure
301
+ if "RESULTS" in data:
302
+ models = list(data["RESULTS"].keys())
303
+ for model in models:
304
+ if "default" in data["RESULTS"][model]:
305
+ realizations = list(data["RESULTS"][model]["default"].keys())
306
+ if "attributes" in realizations:
307
+ realizations.remove("attributes")
308
+ for realization in realizations:
309
+ regions = list(data["RESULTS"][model]["default"][realization].keys())
310
+ for region in regions:
311
+ stats = list(data["RESULTS"][model]["default"][realization][region].keys())
312
+ for stat in stats:
313
+ if (
314
+ "CalendarMonths"
315
+ in data["RESULTS"][model]["default"][realization][region][stat]
316
+ ):
317
+ calendar_months = data["RESULTS"][model]["default"][realization][region][
318
+ stat
319
+ ].pop("CalendarMonths")
320
+ for i, value in enumerate(calendar_months):
321
+ key_name = f"CalendarMonth-{i + 1:02d}"
322
+ data["RESULTS"][model]["default"][realization][region][stat][key_name] = (
323
+ value
324
+ )
325
+
326
+ # Remove the "CalendarMonths" key from the nested structure in "DIMENSIONS"
327
+ if (
328
+ "DIMENSIONS" in data
329
+ and "season" in data["DIMENSIONS"]
330
+ and "CalendarMonths" in data["DIMENSIONS"]["season"]
331
+ ):
332
+ calendar_months = data["DIMENSIONS"]["season"].pop("CalendarMonths")
333
+ for i in range(1, 13):
334
+ key_name = f"CalendarMonth-{i:02d}"
335
+ data["DIMENSIONS"]["season"][key_name] = {}
336
+
337
+ return data
@@ -0,0 +1,174 @@
1
+ from collections.abc import Iterable
2
+
3
+ from loguru import logger
4
+
5
+ from climate_ref_core.datasets import FacetFilter, SourceDatasetType
6
+ from climate_ref_core.diagnostics import (
7
+ CommandLineDiagnostic,
8
+ DataRequirement,
9
+ ExecutionDefinition,
10
+ ExecutionResult,
11
+ )
12
+ from climate_ref_pmp.pmp_driver import build_pmp_command, process_json_result
13
+
14
+
15
+ class ExtratropicalModesOfVariability(CommandLineDiagnostic):
16
+ """
17
+ Calculate the extratropical modes of variability for a given area
18
+ """
19
+
20
+ ts_modes = ("PDO", "NPGO", "AMO")
21
+ psl_modes = ("NAO", "NAM", "PNA", "NPO", "SAM")
22
+
23
+ facets = ("model", "realization", "reference", "mode", "season", "method", "statistic")
24
+
25
+ def __init__(self, mode_id: str):
26
+ self.mode_id = mode_id.upper()
27
+ self.name = f"Extratropical modes of variability: {mode_id}"
28
+ self.slug = f"extratropical-modes-of-variability-{mode_id.lower()}"
29
+
30
+ def get_data_requirements(
31
+ obs_source: str,
32
+ obs_variable: str,
33
+ cmip_variable: str,
34
+ extra_experiments: str | tuple[str, ...] | list[str] = (),
35
+ remove_experiments: str | tuple[str, ...] | list[str] = (),
36
+ ) -> tuple[DataRequirement, DataRequirement]:
37
+ filters = [
38
+ FacetFilter(
39
+ facets={
40
+ "frequency": "mon",
41
+ "experiment_id": ("historical", "hist-GHG", "piControl", *extra_experiments),
42
+ "variable_id": cmip_variable,
43
+ }
44
+ )
45
+ ]
46
+
47
+ return (
48
+ DataRequirement(
49
+ source_type=SourceDatasetType.obs4MIPs,
50
+ filters=(
51
+ FacetFilter(facets={"source_id": (obs_source,), "variable_id": (obs_variable,)}),
52
+ ),
53
+ group_by=("source_id", "variable_id"),
54
+ ),
55
+ DataRequirement(
56
+ source_type=SourceDatasetType.CMIP6,
57
+ filters=tuple(filters),
58
+ group_by=("source_id", "experiment_id", "variant_label", "member_id"),
59
+ ),
60
+ )
61
+
62
+ if self.mode_id in self.ts_modes:
63
+ self.parameter_file = "pmp_param_MoV-ts.py"
64
+ self.data_requirements = get_data_requirements("HadISST-1-1", "ts", "ts")
65
+ elif self.mode_id in self.psl_modes:
66
+ self.parameter_file = "pmp_param_MoV-psl.py"
67
+ self.data_requirements = get_data_requirements("20CR", "psl", "psl", extra_experiments=("amip",))
68
+ else:
69
+ raise ValueError(
70
+ f"Unknown mode_id '{self.mode_id}'. Must be one of {self.ts_modes + self.psl_modes}"
71
+ )
72
+
73
+ def build_cmd(self, definition: ExecutionDefinition) -> Iterable[str]:
74
+ """
75
+ Build the command to run the diagnostic
76
+
77
+ Parameters
78
+ ----------
79
+ definition
80
+ Definition of the diagnostic execution
81
+
82
+ Returns
83
+ -------
84
+ Command arguments to execute in the PMP environment
85
+ """
86
+ input_datasets = definition.datasets[SourceDatasetType.CMIP6]
87
+ source_id = input_datasets["source_id"].unique()[0]
88
+ experiment_id = input_datasets["experiment_id"].unique()[0]
89
+ member_id = input_datasets["member_id"].unique()[0]
90
+
91
+ logger.debug(f"input_datasets: {input_datasets}")
92
+ logger.debug(f"source_id: {source_id}")
93
+ logger.debug(f"experiment_id: {experiment_id}")
94
+ logger.debug(f"member_id: {member_id}")
95
+
96
+ reference_dataset = definition.datasets[SourceDatasetType.obs4MIPs]
97
+ reference_dataset_name = reference_dataset["source_id"].unique()[0]
98
+ # reference_dataset_path = reference_dataset.datasets[0]["path"]
99
+ reference_dataset_path = reference_dataset.datasets.iloc[0]["path"]
100
+
101
+ logger.debug(f"reference_dataset: {reference_dataset}")
102
+ logger.debug(f"reference_dataset_name: {reference_dataset_name}")
103
+ logger.debug(f"reference_dataset_path: {reference_dataset_path}")
104
+
105
+ model_files = input_datasets.path.to_list()
106
+
107
+ if len(model_files) != 1:
108
+ # Have some logic to replace the dates in the filename with a wildcard
109
+ raise NotImplementedError("Only one model file is supported at this time.")
110
+
111
+ if isinstance(model_files, list):
112
+ modpath = " ".join([str(p) for p in model_files])
113
+ else:
114
+ modpath = model_files
115
+
116
+ if isinstance(reference_dataset_path, list):
117
+ reference_data_path = " ".join([str(p) for p in reference_dataset_path])
118
+ else:
119
+ reference_data_path = reference_dataset_path
120
+
121
+ # Build the command to run the PMP driver script
122
+ params = {
123
+ "driver_file": "variability_mode/variability_modes_driver.py",
124
+ "parameter_file": self.parameter_file,
125
+ "variability_mode": self.mode_id,
126
+ "modpath": modpath,
127
+ "modpath_lf": "none",
128
+ "exp": experiment_id,
129
+ "realization": member_id,
130
+ "modnames": source_id,
131
+ "reference_data_name": reference_dataset_name,
132
+ "reference_data_path": reference_data_path,
133
+ "results_dir": str(definition.output_directory),
134
+ "cmec": None,
135
+ "no_provenance": None,
136
+ }
137
+
138
+ # Add conditional parameters
139
+ if self.mode_id in ["SAM"]: # pragma: no cover
140
+ params["osyear"] = 1950
141
+ params["oeyear"] = 2005
142
+
143
+ # Pass the parameters using **kwargs
144
+ return build_pmp_command(**params)
145
+
146
+ def build_execution_result(self, definition: ExecutionDefinition) -> ExecutionResult:
147
+ """
148
+ Build a diagnostic result from the output of the PMP driver
149
+
150
+ Parameters
151
+ ----------
152
+ definition
153
+ Definition of the diagnostic execution
154
+
155
+ Returns
156
+ -------
157
+ Result of the diagnostic execution
158
+ """
159
+ results_files = list(definition.output_directory.glob("*_cmec.json"))
160
+ if len(results_files) != 1: # pragma: no cover
161
+ logger.warning(f"A single cmec output file not found: {results_files}")
162
+ return ExecutionResult.build_from_failure(definition)
163
+
164
+ # Find the other outputs
165
+ png_files = [definition.as_relative_path(f) for f in definition.output_directory.glob("*.png")]
166
+ data_files = [definition.as_relative_path(f) for f in definition.output_directory.glob("*.nc")]
167
+
168
+ cmec_output, cmec_metric = process_json_result(results_files[0], png_files, data_files)
169
+
170
+ return ExecutionResult.build_from_output_bundle(
171
+ definition,
172
+ cmec_output_bundle=cmec_output,
173
+ cmec_metric_bundle=cmec_metric,
174
+ )
@@ -0,0 +1,90 @@
1
+ import datetime
2
+ import os
3
+
4
+ # =================================================
5
+ # Background Information
6
+ # -------------------------------------------------
7
+ mip = "cmip6"
8
+ exp = "historical"
9
+ frequency = "mo"
10
+ realm = "atm"
11
+
12
+ # =================================================
13
+ # Analysis Options
14
+ # -------------------------------------------------
15
+ variability_mode = "NAM" # Available domains: NAM, NAO, SAM, PNA, PDO
16
+ seasons = [
17
+ "DJF",
18
+ "MAM",
19
+ "JJA",
20
+ "SON",
21
+ ] # Available seasons: DJF, MAM, JJA, SON, monthly, yearly
22
+
23
+ ConvEOF = True # Calculate conventioanl EOF for model
24
+ CBF = True # Calculate Common Basis Function (CBF) for model
25
+
26
+ # =================================================
27
+ # Miscellaneous
28
+ # -------------------------------------------------
29
+ update_json = False
30
+ debug = False
31
+
32
+ # =================================================
33
+ # Observation
34
+ # -------------------------------------------------
35
+ reference_data_name = "NOAA-CIRES_20CR"
36
+ reference_data_path = os.path.join(
37
+ "/p/user_pub/PCMDIobs/obs4MIPs/NOAA-ESRL-PSD/20CR/mon/psl/gn/latest",
38
+ "psl_mon_20CR_PCMDI_gn_187101-201212.nc",
39
+ )
40
+
41
+ varOBS = "psl"
42
+ ObsUnitsAdjust = (True, "divide", 100.0) # Pa to hPa; or (False, 0, 0)
43
+
44
+ osyear = 1900
45
+ oeyear = 2005
46
+ eofn_obs = 1
47
+
48
+ # =================================================
49
+ # Models
50
+ # -------------------------------------------------
51
+ modpath = os.path.join(
52
+ "/p/css03/cmip5_css02/data/cmip5/output1/CSIRO-BOM/ACCESS1-0/historical/mon/atmos/Amon/r1i1p1/psl/1",
53
+ "psl_Amon_ACCESS1-0_historical_r1i1p1_185001-200512.nc",
54
+ )
55
+
56
+ modnames = ["ACCESS1-0"]
57
+
58
+ realization = "r1i1p1f1"
59
+
60
+ varModel = "psl"
61
+ ModUnitsAdjust = (True, "divide", 100.0) # Pa to hPa
62
+
63
+ msyear = 1900
64
+ meyear = 2005
65
+ eofn_mod = 1
66
+
67
+ # =================================================
68
+ # Output
69
+ # -------------------------------------------------
70
+ case_id = f"{datetime.datetime.now():v%Y%m%d}"
71
+ pmprdir = "/p/user_pub/pmp/pmp_results/"
72
+
73
+ results_dir = os.path.join(
74
+ pmprdir,
75
+ "%(output_type)",
76
+ "variability_modes",
77
+ "%(mip)",
78
+ "%(exp)",
79
+ "%(case_id)",
80
+ "%(variability_mode)",
81
+ "%(reference_data_name)",
82
+ )
83
+
84
+ # Output for obs
85
+ plot_obs = True # Create map graphics
86
+ nc_out_obs = True # Write output in NetCDF
87
+
88
+ # Output for models
89
+ nc_out = True
90
+ plot = True