wrfrun 0.1.7__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 (46) hide show
  1. wrfrun/__init__.py +3 -0
  2. wrfrun/core/__init__.py +5 -0
  3. wrfrun/core/base.py +680 -0
  4. wrfrun/core/config.py +717 -0
  5. wrfrun/core/error.py +80 -0
  6. wrfrun/core/replay.py +113 -0
  7. wrfrun/core/server.py +212 -0
  8. wrfrun/data.py +418 -0
  9. wrfrun/extension/__init__.py +1 -0
  10. wrfrun/extension/littler/__init__.py +1 -0
  11. wrfrun/extension/littler/utils.py +599 -0
  12. wrfrun/extension/utils.py +66 -0
  13. wrfrun/model/__init__.py +7 -0
  14. wrfrun/model/base.py +14 -0
  15. wrfrun/model/plot.py +54 -0
  16. wrfrun/model/utils.py +34 -0
  17. wrfrun/model/wrf/__init__.py +6 -0
  18. wrfrun/model/wrf/_metgrid.py +71 -0
  19. wrfrun/model/wrf/_ndown.py +39 -0
  20. wrfrun/model/wrf/core.py +805 -0
  21. wrfrun/model/wrf/exec_wrap.py +101 -0
  22. wrfrun/model/wrf/geodata.py +301 -0
  23. wrfrun/model/wrf/namelist.py +377 -0
  24. wrfrun/model/wrf/scheme.py +311 -0
  25. wrfrun/model/wrf/vtable.py +65 -0
  26. wrfrun/pbs.py +86 -0
  27. wrfrun/plot/__init__.py +1 -0
  28. wrfrun/plot/wps.py +188 -0
  29. wrfrun/res/__init__.py +22 -0
  30. wrfrun/res/config.toml.template +136 -0
  31. wrfrun/res/extension/plotgrids.ncl +216 -0
  32. wrfrun/res/job_scheduler/pbs.template +6 -0
  33. wrfrun/res/job_scheduler/slurm.template +6 -0
  34. wrfrun/res/namelist/namelist.input.da_wrfvar.template +261 -0
  35. wrfrun/res/namelist/namelist.input.dfi.template +260 -0
  36. wrfrun/res/namelist/namelist.input.real.template +256 -0
  37. wrfrun/res/namelist/namelist.input.wrf.template +256 -0
  38. wrfrun/res/namelist/namelist.wps.template +44 -0
  39. wrfrun/res/namelist/parame.in.template +11 -0
  40. wrfrun/res/run.sh.template +16 -0
  41. wrfrun/run.py +264 -0
  42. wrfrun/utils.py +257 -0
  43. wrfrun/workspace.py +88 -0
  44. wrfrun-0.1.7.dist-info/METADATA +67 -0
  45. wrfrun-0.1.7.dist-info/RECORD +46 -0
  46. wrfrun-0.1.7.dist-info/WHEEL +4 -0
wrfrun/core/config.py ADDED
@@ -0,0 +1,717 @@
1
+ """
2
+ This file contains functions to read the config file of wrfrun
3
+ """
4
+
5
+ from copy import deepcopy
6
+ from os import environ, makedirs
7
+ from os.path import abspath, basename, dirname, exists
8
+ from shutil import copyfile
9
+ from typing import Optional, Tuple, Union
10
+
11
+ import f90nml
12
+ import tomli
13
+ import tomli_w
14
+
15
+ from .error import ResourceURIError, WRFRunContextError, ModelNameError
16
+ from ..utils import logger
17
+
18
+
19
+ class _WRFRunResources:
20
+ """
21
+ Manage resource files used by wrfrun components.
22
+ These resources include various configuration files from NWP as well as those provided by wrfrun itself.
23
+ Since their actual file paths may vary depending on the wrfrun installation environment, wrfrun maps them using URIs to ensure consistent access regardless of the environment.
24
+ """
25
+ def __init__(self):
26
+ self._resource_namespace_db = {}
27
+
28
+ def check_resource_uri(self, unique_uri: str) -> bool:
29
+ """
30
+ Check if the uri has been registered.
31
+
32
+ :param unique_uri: Unique URI represents the resource.
33
+ :type unique_uri: str
34
+ :return: True or False.
35
+ :rtype: bool
36
+ """
37
+ if unique_uri in self._resource_namespace_db:
38
+ return True
39
+
40
+ else:
41
+ return False
42
+
43
+ def register_resource_uri(self, unique_uri: str, res_space_path: str):
44
+ """
45
+ This function should only be used by wrfrun functions.
46
+
47
+ Register a unique resource file namespace.
48
+
49
+ :param unique_uri: Unique URI represents the resource. It must start with ``:WRFRUN_`` and end with ``:``. For example, ``":WRFRUN_WORK_PATH:"``.
50
+ :type unique_uri: str
51
+ :param res_space_path: REAL absolute path of your resource path. For example, "$HOME/.config/wrfrun/res".
52
+ :type res_space_path: str
53
+ :return:
54
+ :rtype:
55
+ """
56
+ if not (unique_uri.startswith(":WRFRUN_") and unique_uri.endswith(":")):
57
+ logger.error(f"Can't register resource URI: '{unique_uri}'. It should start with ':WRFRUN_' and end with ':'.")
58
+ raise ResourceURIError(f"Can't register resource URI: '{unique_uri}'. It should start with ':WRFRUN_' and end with ':'.")
59
+
60
+ if unique_uri in self._resource_namespace_db:
61
+ logger.error(f"Resource URI '{unique_uri}' exists.")
62
+ raise ResourceURIError(f"Resource URI '{unique_uri}' exists.")
63
+
64
+
65
+
66
+ logger.debug(f"Register URI '{unique_uri}' to '{res_space_path}'")
67
+ self._resource_namespace_db[unique_uri] = res_space_path
68
+
69
+ def parse_resource_uri(self, file_path: str) -> str:
70
+ """
71
+ Return a real file path by parsing the URI string in it.
72
+
73
+ Normal path will be returned with no change.
74
+
75
+ :param file_path: File path string which may contain URI string.
76
+ :type file_path: str
77
+ :return: Real file path.
78
+ :rtype: str
79
+ """
80
+ if not file_path.startswith(":WRFRUN_"):
81
+ return file_path
82
+
83
+ res_namespace_string = file_path.split(":")[1]
84
+ res_namespace_string = f":{res_namespace_string}:"
85
+
86
+ if res_namespace_string in self._resource_namespace_db:
87
+ file_path = file_path.replace(res_namespace_string, self._resource_namespace_db[res_namespace_string])
88
+
89
+ if not file_path.startswith(":WRFRUN_"):
90
+ return file_path
91
+
92
+ else:
93
+ return self.parse_resource_uri(file_path)
94
+
95
+ else:
96
+ logger.error(f"Unknown resource URI: '{res_namespace_string}'")
97
+ raise ResourceURIError(f"Unknown resource URI: '{res_namespace_string}'")
98
+
99
+
100
+ class _WRFRunConstants:
101
+ """
102
+ Define all variables that will be used by other wrfrun components.
103
+ These variables are related to the wrfrun installation environment and configuration files.
104
+ They are defined either directly or mapped using URIs to ensure consistent access across all components.
105
+ """
106
+ def __init__(self):
107
+ # the path we may need to store temp files,
108
+ # don't worry, it will be deleted once the system reboots
109
+ self._WRFRUN_TEMP_PATH = "/tmp/wrfrun"
110
+
111
+ # WRF may need a large disk space to store output, we can't run wrf in /tmp,
112
+ # so we will create a folder in $HOME/.config to run wrf.
113
+ # we need to check if we're running as a root user
114
+ USER_HOME_PATH = f"{environ['HOME']}"
115
+ if USER_HOME_PATH in ["/", "/root", ""]:
116
+ logger.warning(f"User's home path is '{USER_HOME_PATH}', which means you are running this program as a root user")
117
+ logger.warning("It's not recommended to use wrfrun as a root user")
118
+ logger.warning("Set USER_HOME_PATH as /root")
119
+ USER_HOME_PATH = "/root"
120
+
121
+ self._WRFRUN_HOME_PATH = f"{USER_HOME_PATH}/.config/wrfrun"
122
+ self._WRFRUN_REPLAY_WORK_PATH = f"{self._WRFRUN_HOME_PATH}/replay"
123
+
124
+ # work path to run WPS, WRF and WRFDA
125
+ self._WORK_PATH = f"{self._WRFRUN_HOME_PATH}/workspace"
126
+ self._WPS_WORK_PATH = f"{self._WORK_PATH}/WPS"
127
+ self._WRF_WORK_PATH = f"{self._WORK_PATH}/WRF"
128
+ self._WRFDA_WORK_PATH = f"{self._WORK_PATH}/WRFDA"
129
+
130
+ # record WRF progress status
131
+ self._WORK_STATUS = ""
132
+
133
+ # record context status
134
+ self._WRFRUN_CONTEXT_STATUS = False
135
+
136
+ # WRFDA is not necessary
137
+ self.USE_WRFDA: bool = False
138
+
139
+ # output directory of ungrib
140
+ self._UNGRIB_OUT_DIR = "./outputs"
141
+
142
+ self._WRFRUN_OUTPUT_PATH = ":WRFRUN_OUTPUT_PATH:"
143
+ self._WRFRUN_RESOURCE_PATH = ":WRFRUN_RESOURCE_PATH:"
144
+
145
+ self.IS_IN_REPLAY = False
146
+
147
+ self.IS_RECORDING = False
148
+
149
+ # in this mode, wrfrun will do all things except call the numerical model.
150
+ # all output rules will also not be executed.
151
+ self.FAKE_SIMULATION_MODE = False
152
+
153
+ def _get_uri_map(self) -> dict[str, str]:
154
+ """
155
+ Return uri and its value.
156
+ We will use this to register uri when initialize config.
157
+
158
+ :return:
159
+ :rtype:
160
+ """
161
+ return {
162
+ self.WRFRUN_TEMP_PATH: self._WRFRUN_TEMP_PATH,
163
+ self.WRFRUN_HOME_PATH: self._WRFRUN_HOME_PATH,
164
+ self.WRFRUN_WORKSPACE_PATH: self._WORK_PATH,
165
+ self.WPS_WORK_PATH: self._WPS_WORK_PATH,
166
+ self.WRF_WORK_PATH: self._WRF_WORK_PATH,
167
+ self.WRFDA_WORK_PATH: self._WRFDA_WORK_PATH,
168
+ self.WRFRUN_REPLAY_WORK_PATH: self._WRFRUN_REPLAY_WORK_PATH,
169
+ }
170
+
171
+ @property
172
+ def WRFRUN_REPLAY_WORK_PATH(self) -> str:
173
+ return ":WRFRUN_REPLAY_WORK_PATH:"
174
+
175
+ @property
176
+ def WRFRUN_TEMP_PATH(self) -> str:
177
+ """
178
+ Path of the directory storing temporary files.
179
+
180
+ :return: Path of the directory storing temporary files.
181
+ :rtype: str
182
+ """
183
+ return ":WRFRUN_TEMP_PATH:"
184
+
185
+ @property
186
+ def WRFRUN_HOME_PATH(self) -> str:
187
+ """
188
+ Path of the directory storing wrfrun config files.
189
+
190
+ :return: Path of the directory storing temporary files.
191
+ :rtype: str
192
+ """
193
+ return ":WRFRUN_HOME_PATH:"
194
+
195
+ @property
196
+ def WRFRUN_WORKSPACE_PATH(self) -> str:
197
+ """
198
+ Path of the wrfrun workspace.
199
+
200
+ :return: Path of the wrfrun workspace.
201
+ :rtype: str
202
+ """
203
+ return ":WRFRUN_WORK_PATH:"
204
+
205
+ @property
206
+ def WPS_WORK_PATH(self) -> str:
207
+ """
208
+ Path of the directory to run WPS.
209
+
210
+ :return: Path of the directory to run WPS.
211
+ :rtype: str
212
+ """
213
+ return ":WRFRUN_WPS_WORK_PATH:"
214
+
215
+ @property
216
+ def WRF_WORK_PATH(self) -> str:
217
+ """
218
+ Path of the directory to run WRF.
219
+
220
+ :return: Path of the directory to run WRF.
221
+ :rtype: str
222
+ """
223
+ return ":WRFRUN_WRF_WORK_PATH:"
224
+
225
+ @property
226
+ def WRFDA_WORK_PATH(self) -> str:
227
+ """
228
+ Path of the directory to run WRFDA.
229
+
230
+ :return: Path of the directory to run WRFDA.
231
+ :rtype: str
232
+ """
233
+ return ":WRFRUN_WRFDA_WORK_PATH:"
234
+
235
+ @property
236
+ def WRFRUN_WORK_STATUS(self) -> str:
237
+ """
238
+ wrfrun work status.
239
+
240
+ :return: wrfrun work status.
241
+ :rtype: str
242
+ """
243
+ return self._WORK_STATUS
244
+
245
+ @WRFRUN_WORK_STATUS.setter
246
+ def WRFRUN_WORK_STATUS(self, value: str):
247
+ if not isinstance(value, str):
248
+ value = str(value)
249
+ self._WORK_STATUS = value
250
+
251
+ @property
252
+ def UNGRIB_OUT_DIR(self):
253
+ """
254
+ Output directory path of ungrib.
255
+ """
256
+ return self._UNGRIB_OUT_DIR
257
+
258
+ @UNGRIB_OUT_DIR.setter
259
+ def UNGRIB_OUT_DIR(self, value: str):
260
+ if not isinstance(value, str):
261
+ value = str(value)
262
+ self._UNGRIB_OUT_DIR = value
263
+
264
+ @property
265
+ def WRFRUN_OUTPUT_PATH(self) -> str:
266
+ """
267
+ A marco string represents the save path of output files in wrfrun config.
268
+ """
269
+ return self._WRFRUN_OUTPUT_PATH
270
+
271
+ @property
272
+ def WRFRUN_RESOURCE_PATH(self) -> str:
273
+ """
274
+ Return the preserved string used by wrfrun resource files.
275
+ """
276
+ return self._WRFRUN_RESOURCE_PATH
277
+
278
+ def check_wrfrun_context(self, error=False) -> bool:
279
+ """
280
+ Check if we're in WRFRun context or not.
281
+
282
+ :param error: Raise an error if ``error==True`` when we are not in WRFRun context.
283
+ :type error: bool
284
+ :return: True or False.
285
+ :rtype: bool
286
+ """
287
+ if self._WRFRUN_CONTEXT_STATUS:
288
+ return self._WRFRUN_CONTEXT_STATUS
289
+
290
+ if not error:
291
+ logger.warning("You are using wrfrun without entering `WRFRun` context, which may cause some functions don't work.")
292
+ return self._WRFRUN_CONTEXT_STATUS
293
+
294
+ logger.error("You need to enter `WRFRun` context to use wrfrun.")
295
+ raise WRFRunContextError("You need to enter `WRFRun` context to use wrfrun.")
296
+
297
+ def set_wrfrun_context(self, status: bool):
298
+ """
299
+ Change ``WRFRun`` context status to True or False.
300
+
301
+ :param status: ``WRFRun`` context status.
302
+ :type status: bool
303
+ """
304
+ self._WRFRUN_CONTEXT_STATUS = status
305
+
306
+
307
+ class _WRFRunNamelist:
308
+ """
309
+ A class to manage namelist config.
310
+ """
311
+ def __init__(self):
312
+ self._wps_namelist = {}
313
+ self._wrf_namelist = {}
314
+ self._wrfda_namelist = {}
315
+ self._namelist_dict = {}
316
+ self._namelist_id_list = ("param", "geog_static_data", "wps", "wrf", "wrfda")
317
+
318
+ def register_custom_namelist_id(self, namelist_id: str) -> bool:
319
+ """
320
+ Register a namelist with a unique id so you can read, update and write it later.
321
+
322
+ :param namelist_id: A unique namelist id.
323
+ :type namelist_id: str
324
+ :return: True if register successfully, else False.
325
+ :rtype: bool
326
+ """
327
+ if namelist_id in self._namelist_id_list:
328
+ return False
329
+
330
+ else:
331
+ self._namelist_id_list += (namelist_id,)
332
+ return True
333
+
334
+ def unregister_custom_namelist_id(self, namelist_id: str):
335
+ """
336
+ Unregister a specified namelist id.
337
+ If unregister successfully, all data about this namelist kind will be lost.
338
+
339
+ :param namelist_id: A unique namelist id.
340
+ :type namelist_id: str
341
+ :return:
342
+ :rtype:
343
+ """
344
+ if namelist_id not in self._namelist_id_list:
345
+ return
346
+
347
+ self.delete_namelist(namelist_id)
348
+ self._namelist_id_list = tuple(set(self._namelist_id_list) - {namelist_id, })
349
+
350
+ def check_namelist_id(self, namelist_id: str) -> bool:
351
+ """
352
+ Check if a namelist id is registered.
353
+
354
+ :param namelist_id: Unique namelist ID.
355
+ :type namelist_id: Unique namelist ID.
356
+ :return: True if the ID is registered, else False.
357
+ :rtype: bool
358
+ """
359
+ if namelist_id in self._namelist_id_list:
360
+ return True
361
+ else:
362
+ return False
363
+
364
+ def read_namelist(self, file_path: str, namelist_id: str):
365
+ """
366
+ Read namelist.
367
+
368
+ :param file_path: Namelist file path.
369
+ :type file_path: str
370
+ :param namelist_id: ``"wps"``, ``"wrf"``, ``"wrfda"``, or any other id you have registered.
371
+ :type namelist_id: str
372
+ """
373
+ # check the file path
374
+ if not exists(file_path):
375
+ logger.error(f"File not found: {file_path}")
376
+ raise FileNotFoundError
377
+
378
+ if namelist_id not in self._namelist_id_list:
379
+ logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
380
+ raise ValueError(f"Unknown namelist id: {namelist_id}, register it first.")
381
+
382
+ self._namelist_dict[namelist_id] = f90nml.read(file_path).todict()
383
+
384
+ def write_namelist(self, save_path: str, namelist_id: str, overwrite=True):
385
+ """
386
+ Write namelist to file.
387
+
388
+ :param save_path: Save path.
389
+ :type save_path: str
390
+ :param namelist_id: ``"wps"``, ``"wrf"``, ``"wrfda"``, or any other id you have registered.
391
+ :type namelist_id: str
392
+ :param overwrite: If overwrite the existed file, defaults to ``True``.
393
+ :type overwrite: bool
394
+ """
395
+ if namelist_id not in self._namelist_id_list:
396
+ logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
397
+ raise ValueError(f"Unknown namelist id: {namelist_id}, register it first.")
398
+
399
+ if namelist_id not in self._namelist_dict:
400
+ logger.error(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
401
+ raise KeyError(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
402
+
403
+ f90nml.Namelist(self._namelist_dict[namelist_id]).write(save_path, force=overwrite)
404
+
405
+ def update_namelist(self, new_values: Union[str, dict], namelist_id: str):
406
+ """
407
+ Update value in namelist data.
408
+
409
+ You can give your custom namelist file, a whole namelist or a file only contains values you want to change.
410
+
411
+ >>> namelist_file = "./namelist.wps"
412
+ >>> WRFRUNConfig.update_namelist(namelist_file, namelist_id="wps")
413
+
414
+ >>> namelist_file = "./namelist.wrf"
415
+ >>> WRFRUNConfig.update_namelist(namelist_file, namelist_id="wrf")
416
+
417
+ Or give a Dict object contains values you want to change.
418
+
419
+ >>> namelist_values = {"ungrib": {"prefix": "./output/FILE"}}
420
+ >>> WRFRUNConfig.update_namelist(namelist_values, namelist_id="wps")
421
+
422
+ >>> namelist_values = {"time_control": {"debug_level": 100}}
423
+ >>> WRFRUNConfig.update_namelist(namelist_values, namelist_id="wrf")
424
+
425
+ :param new_values: New values.
426
+ :type new_values: str | dict
427
+ :param namelist_id: ``"wps"``, ``"wrf"``, ``"wrfda"``, or any other id you have registered.
428
+ :type namelist_id: str
429
+ """
430
+ if namelist_id not in self._namelist_id_list:
431
+ logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
432
+ raise ValueError(f"Unknown namelist id: {namelist_id}, register it first.")
433
+
434
+ elif namelist_id not in self._namelist_dict:
435
+ self._namelist_dict[namelist_id] = new_values
436
+ return
437
+
438
+ else:
439
+ reference = self._namelist_dict[namelist_id]
440
+
441
+ if isinstance(new_values, str):
442
+ if not exists(new_values):
443
+ logger.error(f"File not found: {new_values}")
444
+ raise FileNotFoundError(f"File not found: {new_values}")
445
+ new_values = f90nml.read(new_values).todict()
446
+
447
+ for key in new_values:
448
+ if key in reference:
449
+ reference[key].update(new_values[key])
450
+ else:
451
+ reference[key] = new_values[key]
452
+
453
+ def get_namelist(self, namelist_id: str) -> dict:
454
+ """
455
+ Get specific namelist.
456
+
457
+ :param namelist_id: ``"wps"``, ``"wrf"``, ``"wrfda"``, or any other id you have registered.
458
+ :type namelist_id: str
459
+ :return: Namelist.
460
+ :rtype: dict
461
+ """
462
+ if namelist_id not in self._namelist_id_list:
463
+ logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
464
+ raise ValueError(f"Unknown namelist id: {namelist_id}, register it first.")
465
+ elif namelist_id not in self._namelist_dict:
466
+ return {}
467
+ else:
468
+ return deepcopy(self._namelist_dict[namelist_id])
469
+
470
+ def delete_namelist(self, namelist_id: str):
471
+ """
472
+ Delete specified namelist values.
473
+
474
+ :param namelist_id: ``"wps"``, ``"wrf"``, ``"wrfda"``, or any other id you have registered.
475
+ :type namelist_id: str
476
+ :return:
477
+ :rtype:
478
+ """
479
+ if namelist_id not in self._namelist_id_list:
480
+ logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
481
+ raise ValueError(f"Unknown namelist id: {namelist_id}, register it first.")
482
+
483
+ if namelist_id not in self._namelist_dict:
484
+ return
485
+
486
+ self._namelist_dict.pop(namelist_id)
487
+
488
+
489
+ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
490
+ """
491
+ Class to manage wrfrun config.
492
+ """
493
+ _instance = None
494
+ _initialized = False
495
+
496
+ def __init__(self):
497
+ if self._initialized:
498
+ return
499
+
500
+ _WRFRunConstants.__init__(self)
501
+ _WRFRunNamelist.__init__(self)
502
+ _WRFRunResources.__init__(self)
503
+
504
+ self._config = {}
505
+
506
+ # register uri for wrfrun constants
507
+ for key, value in self._get_uri_map().items():
508
+ self.register_resource_uri(key, value)
509
+
510
+ self._config_template_file_path = None
511
+
512
+ self._initialized = True
513
+
514
+ def __new__(cls, *args, **kwargs):
515
+ if cls._instance is None:
516
+ cls._instance = super().__new__(cls)
517
+
518
+ return cls._instance
519
+
520
+ def set_config_template_path(self, file_path: str):
521
+ """
522
+ Set the path of template file.
523
+
524
+ :param file_path: Template file path.
525
+ :type file_path: str
526
+ :return:
527
+ :rtype:
528
+ """
529
+ self._config_template_file_path = file_path
530
+
531
+ def load_wrfrun_config(self, config_path: Optional[str] = None):
532
+ """
533
+ Load wrfrun config.
534
+
535
+ :param config_path: YAML config file. Defaults to None.
536
+ :type config_path: str
537
+ """
538
+ config_template_path = self.parse_resource_uri(self._config_template_file_path)
539
+
540
+ if config_path is not None:
541
+ if not exists(config_path):
542
+ logger.error(f"Config file doesn't exist, copy template config to {config_path}")
543
+ logger.error("Please modify it.")
544
+
545
+ if not exists(dirname(config_path)):
546
+ makedirs(dirname(config_path))
547
+
548
+ copyfile(config_template_path, config_path)
549
+ raise FileNotFoundError(config_path)
550
+ else:
551
+ logger.info("Read config template since you doesn't give config file")
552
+ logger.info("A new config file has been saved to './config.toml', you can change and use it latter")
553
+
554
+ copyfile(config_template_path, "./config.toml")
555
+ config_path = "./config.toml"
556
+
557
+ with open(config_path, "rb") as f:
558
+ self._config = tomli.load(f)
559
+
560
+ # register URI for output directory.
561
+ output_path = abspath(self["output_path"])
562
+ self.register_resource_uri(self.WRFRUN_OUTPUT_PATH, output_path)
563
+
564
+ def save_wrfrun_config(self, save_path: str):
565
+ """
566
+ Save config to a file.
567
+
568
+ :param save_path: Save path of the config.
569
+ :type save_path: str
570
+ """
571
+ save_path = self.parse_resource_uri(save_path)
572
+
573
+ path_dir = dirname(save_path)
574
+ if not exists(path_dir):
575
+ makedirs(path_dir)
576
+
577
+ with open(save_path, "wb") as f:
578
+ tomli_w.dump(self._config, f)
579
+
580
+ def __getitem__(self, item):
581
+ if len(self._config) == 0:
582
+ logger.error("Attempt to read value before load config")
583
+ raise RuntimeError("Attempt to read value before load config")
584
+
585
+ return deepcopy(self._config[item])
586
+
587
+ def get_input_data_path(self) -> list[str]:
588
+ """
589
+ Return the path of input data.
590
+
591
+ :return: Path list.
592
+ :rtype: list
593
+ """
594
+ return deepcopy(self["input_data_path"])
595
+
596
+ def get_model_config(self, model_name: str) -> dict:
597
+ """
598
+ Return the config of a NWP model.
599
+
600
+ :param model_name: Name of the model. For example, "wrf".
601
+ :type model_name: str
602
+ :return: A dict object.
603
+ :rtype: dict
604
+ """
605
+ if model_name not in self["model"]:
606
+ logger.error(f"Config of model '{model_name}' isn't found in your config file.")
607
+ raise ModelNameError(f"Config of model '{model_name}' isn't found in your config file.")
608
+
609
+ return deepcopy(self["model"][model_name])
610
+
611
+ def get_log_path(self) -> str:
612
+ """
613
+ Return the save path of logs.
614
+
615
+ :return: A directory path.
616
+ :rtype: str
617
+ """
618
+ return self["log_path"]
619
+
620
+ def get_socket_server_config(self) -> Tuple[str, int]:
621
+ """
622
+ Return settings of the socket server.
623
+
624
+ :return: ("host", port)
625
+ :rtype: tuple
626
+ """
627
+ return self["server_host"], self["server_port"]
628
+
629
+ def get_job_scheduler_config(self) -> dict:
630
+ """
631
+ Return the config of job scheduler.
632
+
633
+ :return: A dict object.
634
+ :rtype: dict
635
+ """
636
+ return deepcopy(self["job_scheduler"])
637
+
638
+ def get_core_num(self) -> int:
639
+ """
640
+ Return the number of CPU cores.
641
+ :return:
642
+ :rtype:
643
+ """
644
+ return self["core_num"]
645
+
646
+ def get_ungrib_out_dir_path(self) -> str:
647
+ """
648
+ Get the output directory of ungrib output (WRF intermediate file).
649
+
650
+ :return: URI path.
651
+ :rtype: str
652
+ """
653
+ wif_prefix = self.get_namelist("wps")["ungrib"]["prefix"]
654
+ wif_path = f"{self.WPS_WORK_PATH}/{dirname(wif_prefix)}"
655
+
656
+ return wif_path
657
+
658
+ def get_ungrib_out_prefix(self) -> str:
659
+ """
660
+ Get the prefix string of ungrib output (WRF intermediate file).
661
+
662
+ :return: Prefix string of ungrib output (WRF intermediate file).
663
+ :rtype: str
664
+ """
665
+ wif_prefix = self.get_namelist("wps")["ungrib"]["prefix"]
666
+ wif_prefix = basename(wif_prefix)
667
+ return wif_prefix
668
+
669
+ def set_ungrib_out_prefix(self, prefix: str):
670
+ """
671
+ Set the prefix string of ungrib output (WRF intermediate file).
672
+
673
+ :param prefix: Prefix string of ungrib output (WRF intermediate file).
674
+ :type prefix: str
675
+ :return:
676
+ :rtype:
677
+ """
678
+ self.update_namelist({
679
+ "ungrib": {"prefix": f"{self.UNGRIB_OUT_DIR}/{prefix}"}
680
+ }, "wps")
681
+
682
+ def get_metgrid_fg_names(self) -> list[str]:
683
+ """
684
+ Get prefix strings from "fg_name" in namelist "metgrid" section.
685
+
686
+ :return: Prefix strings list.
687
+ :rtype: list
688
+ """
689
+ fg_names = self.get_namelist("wps")["metgrid"]["fg_name"]
690
+ fg_names = [basename(x) for x in fg_names]
691
+ return fg_names
692
+
693
+ def set_metgrid_fg_names(self, prefix: Union[str, list[str]]):
694
+ """
695
+ Set prefix strings from "fg_name" in namelist "metgrid" section.
696
+
697
+ :param prefix: Prefix strings list.
698
+ :type prefix: str | list
699
+ :return:
700
+ :rtype:
701
+ """
702
+ if isinstance(prefix, str):
703
+ prefix = [prefix, ]
704
+ fg_names = [f"{self.UNGRIB_OUT_DIR}/{x}" for x in prefix]
705
+ self.update_namelist({
706
+ "metgrid": {"fg_name": fg_names}
707
+ }, "wps")
708
+
709
+ def write_namelist(self, save_path: str, namelist_id: str, overwrite=True):
710
+ save_path = self.parse_resource_uri(save_path)
711
+ super().write_namelist(save_path, namelist_id, overwrite)
712
+
713
+
714
+ WRFRUNConfig = WRFRunConfig()
715
+
716
+
717
+ __all__ = ["WRFRUNConfig"]