wrfrun 0.2.0__py3-none-any.whl → 0.3.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.
Files changed (66) hide show
  1. wrfrun/__init__.py +8 -3
  2. wrfrun/cli.py +69 -29
  3. wrfrun/core/__init__.py +27 -10
  4. wrfrun/core/_config.py +308 -0
  5. wrfrun/core/_constant.py +236 -0
  6. wrfrun/core/_exec_db.py +105 -0
  7. wrfrun/core/_namelist.py +287 -0
  8. wrfrun/core/_record.py +178 -0
  9. wrfrun/core/_resource.py +172 -0
  10. wrfrun/core/base.py +132 -406
  11. wrfrun/core/core.py +196 -0
  12. wrfrun/core/error.py +28 -2
  13. wrfrun/core/replay.py +10 -96
  14. wrfrun/core/server.py +52 -27
  15. wrfrun/core/type.py +171 -0
  16. wrfrun/data.py +304 -139
  17. wrfrun/extension/goos_sst/__init__.py +2 -2
  18. wrfrun/extension/goos_sst/core.py +9 -14
  19. wrfrun/extension/goos_sst/res/__init__.py +0 -1
  20. wrfrun/extension/goos_sst/utils.py +50 -44
  21. wrfrun/extension/littler/core.py +105 -88
  22. wrfrun/extension/utils.py +4 -3
  23. wrfrun/log.py +117 -0
  24. wrfrun/model/__init__.py +11 -7
  25. wrfrun/model/constants.py +52 -0
  26. wrfrun/model/palm/__init__.py +30 -0
  27. wrfrun/model/palm/core.py +145 -0
  28. wrfrun/model/palm/namelist.py +33 -0
  29. wrfrun/model/plot.py +99 -119
  30. wrfrun/model/type.py +116 -0
  31. wrfrun/model/utils.py +9 -20
  32. wrfrun/model/wrf/__init__.py +4 -9
  33. wrfrun/model/wrf/core.py +246 -161
  34. wrfrun/model/wrf/exec_wrap.py +13 -12
  35. wrfrun/model/wrf/geodata.py +116 -100
  36. wrfrun/model/wrf/log.py +103 -0
  37. wrfrun/model/wrf/namelist.py +90 -73
  38. wrfrun/model/wrf/plot.py +102 -0
  39. wrfrun/model/wrf/scheme.py +108 -52
  40. wrfrun/model/wrf/utils.py +39 -25
  41. wrfrun/model/wrf/vtable.py +35 -3
  42. wrfrun/plot/__init__.py +20 -0
  43. wrfrun/plot/wps.py +90 -73
  44. wrfrun/res/__init__.py +103 -5
  45. wrfrun/res/config/config.template.toml +8 -0
  46. wrfrun/res/config/palm.template.toml +23 -0
  47. wrfrun/run.py +105 -77
  48. wrfrun/scheduler/__init__.py +1 -0
  49. wrfrun/scheduler/lsf.py +3 -2
  50. wrfrun/scheduler/pbs.py +3 -2
  51. wrfrun/scheduler/script.py +17 -5
  52. wrfrun/scheduler/slurm.py +3 -2
  53. wrfrun/scheduler/utils.py +14 -2
  54. wrfrun/utils.py +88 -199
  55. wrfrun/workspace/__init__.py +8 -5
  56. wrfrun/workspace/core.py +20 -12
  57. wrfrun/workspace/palm.py +137 -0
  58. wrfrun/workspace/wrf.py +16 -15
  59. wrfrun-0.3.0.dist-info/METADATA +240 -0
  60. wrfrun-0.3.0.dist-info/RECORD +78 -0
  61. wrfrun/core/config.py +0 -923
  62. wrfrun/model/base.py +0 -14
  63. wrfrun-0.2.0.dist-info/METADATA +0 -68
  64. wrfrun-0.2.0.dist-info/RECORD +0 -62
  65. {wrfrun-0.2.0.dist-info → wrfrun-0.3.0.dist-info}/WHEEL +0 -0
  66. {wrfrun-0.2.0.dist-info → wrfrun-0.3.0.dist-info}/entry_points.txt +0 -0
wrfrun/core/config.py DELETED
@@ -1,923 +0,0 @@
1
- """
2
- wrfrun.core.config
3
- ##################
4
-
5
- All classes in this module is used to manage various configurations of ``wrfrun`` and NWP model.
6
-
7
- .. autosummary::
8
- :toctree: generated/
9
-
10
- WRFRunConfig
11
- _WRFRunConstants
12
- _WRFRunNamelist
13
- _WRFRunResources
14
- init_wrfrun_config
15
- get_wrfrun_config
16
- set_register_func
17
-
18
- WRFRunConfig
19
- ************
20
-
21
- A comprehensive class which provides interfaces to access various configurations and resources.
22
- It inherits from three classes: :class:`_WRFRunResources`, :class:`_WRFRunConstants` and :class:`_WRFRunNamelist`.
23
- Users can use the global variable ``WRFRUNConfig``, which is the instance of this class being created when users import ``wrfrun``.
24
- """
25
-
26
- # TODO:
27
- # 1. NEW FEATURE: Allow reading work directory from config file.
28
- # 2. The first one will be a break change, fix the following errors.
29
- # 3. The structure of wrfrun may need to be changed again.
30
-
31
- import threading
32
- from copy import deepcopy
33
- from os import environ, makedirs
34
- from os.path import abspath, dirname, exists
35
- from shutil import copyfile
36
- from sys import platform
37
- from typing import Callable, Optional, Tuple, Union
38
-
39
- import f90nml
40
- import tomli
41
- import tomli_w
42
-
43
- from .error import ModelNameError, NamelistError, NamelistIDError, ResourceURIError, WRFRunContextError, ConfigError
44
- from ..utils import logger
45
-
46
-
47
- class _WRFRunResources:
48
- """
49
- Manage resource files used by wrfrun components
50
- """
51
-
52
- def __init__(self):
53
- """
54
- This class manage resource files used by wrfrun components.
55
-
56
- These resources include various configuration files from NWP as well as those provided by ``wrfrun`` itself.
57
- Since their actual file paths may vary depending on the installation environment,
58
- ``wrfrun`` maps them using URIs to ensure consistent access regardless of the environment.
59
- The URI always starts with the prefix string ``:WRFRUN_`` and ends with ``:``.
60
-
61
- To register custom URIs, user can use :meth:`_WRFRunResources.register_resource_uri`,
62
- which will check if the provided URI is valid.
63
-
64
- To convert any possible URIs in a string, user can use :meth:`_WRFRunResources.parse_resource_uri`
65
-
66
- For more information about how to use resource files, please see :class:`WRFRunConfig`,
67
- which inherits this class.
68
- """
69
- self._resource_namespace_db = {}
70
-
71
- def check_resource_uri(self, unique_uri: str) -> bool:
72
- """
73
- Check if the URI has been registered.
74
-
75
- ``wrfrun`` uses unique URIs to represent resource files. If you want to register a custom URI, you need to check if it's available.
76
-
77
- :param unique_uri: Unique URI represents the resource.
78
- :type unique_uri: str
79
- :return: True or False.
80
- :rtype: bool
81
- """
82
- if unique_uri in self._resource_namespace_db:
83
- return True
84
-
85
- else:
86
- return False
87
-
88
- def register_resource_uri(self, unique_uri: str, res_space_path: str):
89
- """
90
- Register a resource path with a URI. The URI should start with ``:WRFRUN_`` ,end with ``:`` and hasn't been registered yet,
91
- otherwise an exception :class:`ResourceURIError` will be raised.
92
-
93
- :param unique_uri: Unique URI represents the resource. It must start with ``:WRFRUN_`` and end with ``:``. For example, ``":WRFRUN_WORK_PATH:"``.
94
- :type unique_uri: str
95
- :param res_space_path: REAL absolute path of your resource path. For example, "$HOME/.config/wrfrun/res".
96
- :type res_space_path: str
97
- """
98
- if not (unique_uri.startswith(":WRFRUN_") and unique_uri.endswith(":")):
99
- logger.error(f"Can't register resource URI: '{unique_uri}'. It should start with ':WRFRUN_' and end with ':'.")
100
- raise ResourceURIError(f"Can't register resource URI: '{unique_uri}'. It should start with ':WRFRUN_' and end with ':'.")
101
-
102
- if unique_uri in self._resource_namespace_db:
103
- logger.error(f"Resource URI '{unique_uri}' exists.")
104
- raise ResourceURIError(f"Resource URI '{unique_uri}' exists.")
105
-
106
- logger.debug(f"Register URI '{unique_uri}' to '{res_space_path}'")
107
- self._resource_namespace_db[unique_uri] = res_space_path
108
-
109
- def parse_resource_uri(self, resource_path: str) -> str:
110
- """
111
- Return the converted string by parsing the URI string in it.
112
- Normal path will be returned with no change.
113
-
114
- If the URI hasn't been registered, an exception :class:`ResourceURIError` will be raised.
115
-
116
- :param resource_path: Resource path string which may contain URI string.
117
- :type resource_path: str
118
- :return: Real resource path.
119
- :rtype: str
120
-
121
- For example, you can get the real path of ``wrfrun`` workspace with this method:
122
-
123
- >>> workspace_path = f"{WRFRUNConfig.WRFRUN_WORKSPACE_ROOT}/WPS" # ":WRFRUN_WORKSPACE_PATH:/WPS"
124
- >>> real_path = WRFRUNConfig.parse_resource_uri(workspace_path) # should be a valid path like: "/home/syize/.config/wrfrun/workspace/WPS"
125
-
126
- """
127
- if not resource_path.startswith(":WRFRUN_"):
128
- return resource_path
129
-
130
- res_namespace_string = resource_path.split(":")[1]
131
- res_namespace_string = f":{res_namespace_string}:"
132
-
133
- if res_namespace_string in self._resource_namespace_db:
134
- resource_path = resource_path.replace(res_namespace_string, self._resource_namespace_db[res_namespace_string])
135
-
136
- if not resource_path.startswith(":WRFRUN_"):
137
- return resource_path
138
-
139
- else:
140
- return self.parse_resource_uri(resource_path)
141
-
142
- else:
143
- logger.error(f"Unknown resource URI: '{res_namespace_string}'")
144
- raise ResourceURIError(f"Unknown resource URI: '{res_namespace_string}'")
145
-
146
-
147
- class _WRFRunConstants:
148
- """
149
- Define all variables that will be used by other components.
150
- """
151
-
152
- def __init__(self, work_dir: str):
153
- """
154
- Define all variables that will be used by other components.
155
-
156
- These variables are related to ``wrfrun`` installation environments, configuration files and more.
157
- They are defined either directly or mapped using URIs to ensure consistent access across all components.
158
-
159
- :param work_dir: ``wrfrun`` work directory path.
160
- :type work_dir: str
161
- """
162
- # check system
163
- if platform != "linux":
164
- logger.debug(f"Not Linux system!")
165
-
166
- if work_dir != "" or platform != "linux":
167
- # set temporary dir path
168
- self._WRFRUN_TEMP_PATH = abspath(f"{work_dir}/tmp")
169
- self._WRFRUN_HOME_PATH = abspath(work_dir)
170
-
171
- else:
172
- # the path we may need to store temp files,
173
- # don't worry, it will be deleted once the system reboots
174
- self._WRFRUN_TEMP_PATH = "/tmp/wrfrun"
175
- user_home_path = f"{environ['HOME']}"
176
-
177
- # WRF may need a large disk space to store output, we can't run wrf in /tmp,
178
- # so we will create a folder in $HOME/.config to run wrf.
179
- # we need to check if we're running as a root user
180
- if user_home_path in ["/", "/root", ""]:
181
- logger.warning(f"User's home path is '{user_home_path}', which means you are running this program as a root user")
182
- logger.warning("It's not recommended to use wrfrun as a root user")
183
- logger.warning("Set user_home_path as /root")
184
- user_home_path = "/root"
185
-
186
- self._WRFRUN_HOME_PATH = f"{user_home_path}/.config/wrfrun"
187
-
188
- # workspace root path
189
- self._WRFRUN_WORKSPACE_ROOT = f"{self._WRFRUN_HOME_PATH}/workspace"
190
- self._WRFRUN_WORKSPACE_MODEL = f"{self._WRFRUN_WORKSPACE_ROOT}/model"
191
- self._WRFRUN_WORKSPACE_REPLAY = f"{self._WRFRUN_WORKSPACE_ROOT}/replay"
192
-
193
- # record WRF progress status
194
- self._WRFRUN_WORK_STATUS = ""
195
-
196
- # record context status
197
- self._WRFRUN_CONTEXT_STATUS = False
198
-
199
- self._WRFRUN_OUTPUT_PATH = ":WRFRUN_OUTPUT_PATH:"
200
- self._WRFRUN_RESOURCE_PATH = ":WRFRUN_RESOURCE_PATH:"
201
-
202
- self.IS_IN_REPLAY = False
203
-
204
- self.IS_RECORDING = False
205
-
206
- # in this mode, wrfrun will do all things except call the numerical model.
207
- # all output rules will also not be executed.
208
- self.FAKE_SIMULATION_MODE = False
209
-
210
- def _get_uri_map(self) -> dict[str, str]:
211
- """
212
- Return URIs and their values.
213
- ``wrfrun`` will use this to register uri when initialize config.
214
-
215
- :return: A dict in which URIs are keys and their values are dictionary values.
216
- :rtype: dict
217
- """
218
- return {
219
- self.WRFRUN_TEMP_PATH: self._WRFRUN_TEMP_PATH,
220
- self.WRFRUN_HOME_PATH: self._WRFRUN_HOME_PATH,
221
- self.WRFRUN_WORKSPACE_ROOT: self._WRFRUN_WORKSPACE_ROOT,
222
- self.WRFRUN_WORKSPACE_MODEL: self._WRFRUN_WORKSPACE_MODEL,
223
- self.WRFRUN_WORKSPACE_REPLAY: self._WRFRUN_WORKSPACE_REPLAY,
224
- }
225
-
226
- @property
227
- def WRFRUN_WORKSPACE_REPLAY(self) -> str:
228
- """
229
- Path (URI) to store related files of ``wrfrun`` replay functionality.
230
-
231
- :return: URI.
232
- :rtype: str
233
- """
234
- return ":WRFRUN_WORKSPACE_REPLAY:"
235
-
236
- @property
237
- def WRFRUN_TEMP_PATH(self) -> str:
238
- """
239
- Path to store ``wrfrun`` temporary files.
240
-
241
- :return: URI
242
- :rtype: str
243
- """
244
- return ":WRFRUN_TEMP_PATH:"
245
-
246
- @property
247
- def WRFRUN_HOME_PATH(self) -> str:
248
- """
249
- Root path of all others directories. .
250
-
251
- :return: URI
252
- :rtype: str
253
- """
254
- return ":WRFRUN_HOME_PATH:"
255
-
256
- @property
257
- def WRFRUN_WORKSPACE_ROOT(self) -> str:
258
- """
259
- Path of the root workspace.
260
-
261
- :return: URI
262
- :rtype: str
263
- """
264
- return ":WRFRUN_WORKSPACE_ROOT:"
265
-
266
- @property
267
- def WRFRUN_WORKSPACE_MODEL(self) -> str:
268
- """
269
- Path of the model workspace, in which ``wrfrun`` runs numerical models.
270
-
271
- :return: URI
272
- :rtype: str
273
- """
274
- return ":WRFRUN_WORKSPACE_MODEL:"
275
-
276
- @property
277
- def WRFRUN_WORK_STATUS(self) -> str:
278
- """
279
- ``wrfrun`` work status.
280
-
281
- This attribute can be changed by ``Executable`` to reflect the current work progress of ``wrfrun``.
282
- The returned string is the name of ``Executable``.
283
-
284
- :return: A string reflect the current work progress.
285
- :rtype: str
286
- """
287
- return self._WRFRUN_WORK_STATUS
288
-
289
- @WRFRUN_WORK_STATUS.setter
290
- def WRFRUN_WORK_STATUS(self, value: str):
291
- """
292
- Set ``wrfrun`` work status.
293
-
294
- ``wrfrun`` recommends ``Executable`` set the status string with their name,
295
- so to avoid the possible conflicts with other ``Executable`` and the user can easily understand the current work progress.
296
-
297
- :param value: A string represents the work status.
298
- :type value: str
299
- """
300
- if not isinstance(value, str):
301
- value = str(value)
302
- self._WRFRUN_WORK_STATUS = value
303
-
304
- @property
305
- def WRFRUN_OUTPUT_PATH(self) -> str:
306
- """
307
- The root path to store all outputs of the ``wrfrun`` and NWP model.
308
-
309
- :return: URI
310
- :rtype: str
311
- """
312
- return self._WRFRUN_OUTPUT_PATH
313
-
314
- @property
315
- def WRFRUN_RESOURCE_PATH(self) -> str:
316
- """
317
- The root path of all ``wrfrun`` resource files.
318
-
319
- :return: URI
320
- :rtype: str
321
- """
322
- return self._WRFRUN_RESOURCE_PATH
323
-
324
- def check_wrfrun_context(self, error=False) -> bool:
325
- """
326
- Check if in WRFRun context or not.
327
-
328
- :param error: An exception :class:`WRFRunContextError` will be raised if ``error==True`` when we are not in WRFRun context.
329
- :type error: bool
330
- :return: True or False.
331
- :rtype: bool
332
- """
333
- if self._WRFRUN_CONTEXT_STATUS:
334
- return self._WRFRUN_CONTEXT_STATUS
335
-
336
- if not error:
337
- logger.warning("You are using wrfrun without entering `WRFRun` context, which may cause some functions don't work.")
338
- return self._WRFRUN_CONTEXT_STATUS
339
-
340
- logger.error("You need to enter `WRFRun` context to use wrfrun.")
341
- raise WRFRunContextError("You need to enter `WRFRun` context to use wrfrun.")
342
-
343
- def set_wrfrun_context(self, status: bool):
344
- """
345
- Change ``WRFRun`` context status to True or False.
346
-
347
- :param status: ``True`` or ``False``.
348
- :type status: bool
349
- """
350
- self._WRFRUN_CONTEXT_STATUS = status
351
-
352
-
353
- class _WRFRunNamelist:
354
- """
355
- Manage all namelist configurations of NWP models.
356
- """
357
-
358
- def __init__(self):
359
- """
360
- Manage all namelist configurations of NWP models.
361
-
362
- This class provides interfaces to read and write various namelist values of NWP model.
363
- Namelist values are stored in a dictionary with a unique ``namelist_id`` to avoid being messed up with other namelist.
364
-
365
- If you want to use a new ``namelist_id`` other than the defaults to store namelist,
366
- you can register a new ``namelist_id`` with :meth:`_WRFRunNamelist.register_custom_namelist_id`.
367
- """
368
- self._namelist_dict = {}
369
- self._namelist_id_list = ("param", "geog_static_data", "wps", "wrf", "wrfda")
370
-
371
- def register_custom_namelist_id(self, namelist_id: str) -> bool:
372
- """
373
- Register a unique ``namelist_id`` so you can read, update and write namelist with it later.
374
-
375
- :param namelist_id: A unique namelist id.
376
- :type namelist_id: str
377
- :return: True if register successfully, else False.
378
- :rtype: bool
379
- """
380
- if namelist_id in self._namelist_id_list:
381
- return False
382
-
383
- else:
384
- self._namelist_id_list += (namelist_id,)
385
- return True
386
-
387
- def unregister_custom_namelist_id(self, namelist_id: str):
388
- """
389
- Unregister a ``namelist_id``.
390
- If unregister successfully, all values of this namelist will be deleted.
391
-
392
- :param namelist_id: A unique namelist id.
393
- :type namelist_id: str
394
- """
395
- if namelist_id not in self._namelist_id_list:
396
- return
397
-
398
- self.delete_namelist(namelist_id)
399
- self._namelist_id_list = tuple(set(self._namelist_id_list) - {namelist_id, })
400
-
401
- def check_namelist_id(self, namelist_id: str) -> bool:
402
- """
403
- Check if a ``namelist_id`` is registered.
404
-
405
- :param namelist_id: A ``namelist_id``.
406
- :type namelist_id: str
407
- :return: True if the ``namelist_id`` is registered, else False.
408
- :rtype: bool
409
- """
410
- if namelist_id in self._namelist_id_list:
411
- return True
412
- else:
413
- return False
414
-
415
- def read_namelist(self, file_path: str, namelist_id: str):
416
- """
417
- Read namelist values from a file and store them with the ``namelist_id``.
418
-
419
- If ``wrfrun`` can't read the file, ``FileNotFoundError`` will be raised.
420
- If ``namelist_id`` isn't registered, :class:`NamelistIDError` will be raised.
421
-
422
- :param file_path: Namelist file path.
423
- :type file_path: str
424
- :param namelist_id: Registered ``namelist_id``.
425
- :type namelist_id: str
426
- """
427
- # check the file path
428
- if not exists(file_path):
429
- logger.error(f"File not found: {file_path}")
430
- raise FileNotFoundError
431
-
432
- if namelist_id not in self._namelist_id_list:
433
- logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
434
- raise NamelistIDError(f"Unknown namelist id: {namelist_id}, register it first.")
435
-
436
- self._namelist_dict[namelist_id] = f90nml.read(file_path).todict()
437
-
438
- def write_namelist(self, save_path: str, namelist_id: str, overwrite=True):
439
- """
440
- Write namelist values of a ``namelist_id`` to a file.
441
-
442
- :param save_path: Target file path.
443
- :type save_path: str
444
- :param namelist_id: Registered ``namelist_id``.
445
- :type namelist_id: str
446
- :param overwrite: If overwrite the existed file.
447
- :type overwrite: bool
448
- """
449
- if namelist_id not in self._namelist_id_list:
450
- logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
451
- raise NamelistIDError(f"Unknown namelist id: {namelist_id}, register it first.")
452
-
453
- if namelist_id not in self._namelist_dict:
454
- logger.error(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
455
- raise NamelistError(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
456
-
457
- f90nml.Namelist(self._namelist_dict[namelist_id]).write(save_path, force=overwrite)
458
-
459
- def update_namelist(self, new_values: Union[str, dict], namelist_id: str):
460
- """
461
- Update namelist values of a ``namelist_id``.
462
-
463
- You can give the path of a whole namelist file or a file only contains values you want to change.
464
-
465
- >>> namelist_file = "./namelist.wps"
466
- >>> WRFRUNConfig.update_namelist(namelist_file, namelist_id="wps")
467
-
468
- >>> namelist_file = "./namelist.wrf"
469
- >>> WRFRUNConfig.update_namelist(namelist_file, namelist_id="wrf")
470
-
471
- You can also give a dict contains values you want to change.
472
-
473
- >>> namelist_values = {"ungrib": {"prefix": "./output/FILE"}}
474
- >>> WRFRUNConfig.update_namelist(namelist_values, namelist_id="wps")
475
-
476
- >>> namelist_values = {"time_control": {"debug_level": 100}}
477
- >>> WRFRUNConfig.update_namelist(namelist_values, namelist_id="wrf")
478
-
479
- :param new_values: The path of a namelist file, or a dict contains namelist values.
480
- :type new_values: str | dict
481
- :param namelist_id: Registered ``namelist_id``.
482
- :type namelist_id: str
483
- """
484
- if namelist_id not in self._namelist_id_list:
485
- logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
486
- raise NamelistIDError(f"Unknown namelist id: {namelist_id}, register it first.")
487
-
488
- elif namelist_id not in self._namelist_dict:
489
- self._namelist_dict[namelist_id] = new_values
490
- return
491
-
492
- else:
493
- reference = self._namelist_dict[namelist_id]
494
-
495
- if isinstance(new_values, str):
496
- if not exists(new_values):
497
- logger.error(f"File not found: {new_values}")
498
- raise FileNotFoundError(f"File not found: {new_values}")
499
- new_values = f90nml.read(new_values).todict()
500
-
501
- for key in new_values:
502
- if key in reference:
503
- reference[key].update(new_values[key])
504
- else:
505
- reference[key] = new_values[key]
506
-
507
- def get_namelist(self, namelist_id: str) -> dict:
508
- """
509
- Get namelist values of a ``namelist_id``.
510
-
511
- :param namelist_id: Registered ``namelist_id``.
512
- :type namelist_id: str
513
- :return: A dictionary which contains namelist values.
514
- :rtype: dict
515
- """
516
- if namelist_id not in self._namelist_id_list:
517
- logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
518
- raise NamelistIDError(f"Unknown namelist id: {namelist_id}, register it first.")
519
- elif namelist_id not in self._namelist_dict:
520
- logger.error(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
521
- raise NamelistError(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
522
- else:
523
- return deepcopy(self._namelist_dict[namelist_id])
524
-
525
- def delete_namelist(self, namelist_id: str):
526
- """
527
- Delete namelist values of a ``namelist_id``.
528
-
529
- :param namelist_id: Registered ``namelist_id``.
530
- :type namelist_id: str
531
- """
532
- if namelist_id not in self._namelist_id_list:
533
- logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
534
- raise ValueError(f"Unknown namelist id: {namelist_id}, register it first.")
535
-
536
- if namelist_id not in self._namelist_dict:
537
- return
538
-
539
- self._namelist_dict.pop(namelist_id)
540
-
541
- def check_namelist(self, namelist_id: str) -> bool:
542
- """
543
- Check if a namelist has been registered and loaded.
544
-
545
- :param namelist_id: Registered ``namelist_id``.
546
- :type namelist_id: str
547
- :return: ``True`` if it is registered and loaded, else ``False``.
548
- :rtype: bool
549
- """
550
- if namelist_id in self._namelist_id_list and namelist_id in self._namelist_dict:
551
- return True
552
-
553
- else:
554
- return False
555
-
556
-
557
- _URI_REGISTER_FUNC_LIST: list[Callable[["WRFRunConfig"], None]] = []
558
-
559
-
560
- class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
561
- """
562
- Class to manage wrfrun config, runtime constants, namelists and resource files.
563
- """
564
- _instance = None
565
- _initialized = False
566
- _lock = threading.Lock()
567
-
568
- def __init__(self, work_dir: str):
569
- """
570
- This class provides various interfaces to access ``wrfrun``'s config, namelist values of NWP models,
571
- runtime constants and resource files by inheriting from:
572
- :class:`_WRFRunConstants`, :class:`_WRFRunNamelist` and :class:`_WRFRunResources`.
573
-
574
- An instance of this class called ``WRFRUNConfig`` will be created after the user import ``wrfrun``,
575
- and you should use the instance to access configs or other things instead of creating another instance.
576
-
577
- :param work_dir: ``wrfrun`` work directory path.
578
- :type work_dir: str
579
- """
580
- if self._initialized:
581
- return
582
-
583
- with self._lock:
584
- global _URI_REGISTER_FUNC_LIST
585
-
586
- self._initialized = True
587
-
588
- _WRFRunConstants.__init__(self, work_dir)
589
- _WRFRunNamelist.__init__(self)
590
- _WRFRunResources.__init__(self)
591
-
592
- self._config = {}
593
-
594
- self._config_template_file_path = None
595
-
596
- self._register_wrfrun_uris()
597
-
598
- for _fun in _URI_REGISTER_FUNC_LIST:
599
- _fun(self)
600
-
601
- _URI_REGISTER_FUNC_LIST = []
602
-
603
- def __new__(cls, *args, **kwargs):
604
- if cls._instance is None:
605
- cls._instance = super().__new__(cls)
606
-
607
- return cls._instance
608
-
609
- def __getattribute__(self, item):
610
- if item not in ("_initialized", "_instance", "_lock", "__class__"):
611
- if not object.__getattribute__(self, "_initialized"):
612
- logger.error(f"`WRFRUNConfig` hasn't been initialized.")
613
- logger.error(f"Use `WRFRun` to load config automatically, or use function `init_wrfrun_config` to load config manually.")
614
- raise ConfigError(f"`WRFRUNConfig` hasn't been initialized.")
615
-
616
- return object.__getattribute__(self, item)
617
-
618
- @classmethod
619
- def from_config_file(cls, config_file: str) -> "WRFRunConfig":
620
- """
621
- Read the config file and reinitialize.
622
-
623
- :param config_file: Config file path.
624
- :type config_file: str
625
- :return: New instance
626
- :rtype: WRFRunConfig
627
- """
628
- cls._initialized = False
629
-
630
- with open(config_file, "rb") as f:
631
- config = tomli.load(f)
632
-
633
- instance = cls(work_dir=config["work_dir"])
634
- instance.load_wrfrun_config(config_file)
635
-
636
- return instance
637
-
638
- def _register_wrfrun_uris(self):
639
- for key, value in self._get_uri_map().items():
640
- self.register_resource_uri(key, value)
641
-
642
- def set_config_template_path(self, file_path: str):
643
- """
644
- Set file path of the config template file.
645
-
646
- :param file_path: Template file path.
647
- :type file_path: str
648
- """
649
- self._config_template_file_path = file_path
650
-
651
- def load_wrfrun_config(self, config_path: Optional[str] = None):
652
- """
653
- Load ``wrfrun`` config from a config file.
654
-
655
- If the config path is invalid, ``wrfrun`` will create a new config file at the same place,
656
- and raise ``FileNotFoundError``.
657
-
658
- If you don't give the config path, ``wrfrun`` will create a new config file under the current directory,
659
- and read it.
660
-
661
- :param config_path: TOML config file. Defaults to None.
662
- :type config_path: str
663
- """
664
- config_template_path = self.parse_resource_uri(self._config_template_file_path)
665
-
666
- if config_path is not None:
667
- if not exists(config_path):
668
- logger.error(f"Config file doesn't exist, copy template config to {config_path}")
669
- logger.error("Please modify it.")
670
-
671
- if not exists(dirname(config_path)):
672
- makedirs(dirname(config_path))
673
-
674
- copyfile(config_template_path, config_path)
675
- raise FileNotFoundError(config_path)
676
- else:
677
- logger.info("Read config template since you doesn't give config file")
678
- logger.info("A new config file has been saved to './config.toml', you can change and use it latter")
679
-
680
- copyfile(config_template_path, "./config.toml")
681
- config_path = "./config.toml"
682
-
683
- with open(config_path, "rb") as f:
684
- self._config = tomli.load(f)
685
-
686
- config_dir_path = abspath(dirname(config_path))
687
-
688
- # merge model config.
689
- keys_list = list(self._config["model"].keys())
690
- for model_key in keys_list:
691
-
692
- # skip the key that isn't model.
693
- if model_key == "debug_level":
694
- continue
695
-
696
- if "use" not in self._config["model"][model_key]:
697
- continue
698
-
699
- if self._config["model"][model_key]["use"]:
700
- include_file = self._config["model"][model_key]["include"]
701
- if include_file[0] != "/":
702
- include_file = f"{config_dir_path}/{include_file}"
703
-
704
- with open(include_file, "rb") as f:
705
- self._config["model"][model_key] = tomli.load(f)
706
-
707
- else:
708
- self._config["model"].pop(model_key)
709
-
710
- # register URI for output directory.
711
- output_path = abspath(self["output_path"])
712
- self.register_resource_uri(self.WRFRUN_OUTPUT_PATH, output_path)
713
-
714
- # some additional check
715
- if self._config["input_data_path"] == "":
716
- logger.warning("It seems you forget to set 'input_data_path', set it to 'data'.")
717
- self._config["input_data_path"] = "data"
718
-
719
- def save_wrfrun_config(self, save_path: str):
720
- """
721
- Save ``wrfrun``'s config to a file.
722
-
723
- :param save_path: Save path of the config.
724
- :type save_path: str
725
- """
726
- save_path = self.parse_resource_uri(save_path)
727
-
728
- path_dir = dirname(save_path)
729
- if not exists(path_dir):
730
- makedirs(path_dir)
731
-
732
- with open(save_path, "wb") as f:
733
- tomli_w.dump(self._config, f)
734
-
735
- def __getitem__(self, item: str):
736
- """
737
- You can access ``wrfrun`` config like the way to access values in a dictionary.
738
-
739
- For example
740
-
741
- >>> model_config = WRFRUNConfig["model"] # get all model configs.
742
-
743
- :param item: Keys.
744
- :type item: str
745
- """
746
- if len(self._config) == 0:
747
- logger.error("Attempt to read value before load config")
748
- raise RuntimeError("Attempt to read value before load config")
749
-
750
- return deepcopy(self._config[item])
751
-
752
- def __setitem__(self, key: str, value):
753
- if key == "model":
754
- logger.error(f"Use `update_model_config` to change model configurations.")
755
- raise KeyError(f"Use `update_model_config` to change model configurations.")
756
-
757
- if key in self._config:
758
- self._config[key] = value
759
-
760
- else:
761
- logger.error(f"Can't find key '{key}' in your config.")
762
- raise KeyError(f"Can't find key '{key}' in your config.")
763
-
764
- def get_input_data_path(self) -> str:
765
- """
766
- Get the path of directory in which stores the input data.
767
-
768
- :return: Directory path.
769
- :rtype: str
770
- """
771
- return deepcopy(self["input_data_path"])
772
-
773
- def get_model_config(self, model_name: str) -> dict:
774
- """
775
- Get the config of a NWP model.
776
-
777
- An exception :class:`ModelNameError` will be raised if the config can't be found.
778
-
779
- :param model_name: Name of the model. For example, ``wrf``.
780
- :type model_name: str
781
- :return: A dictionary.
782
- :rtype: dict
783
- """
784
- if model_name not in self["model"]:
785
- logger.error(f"Config of model '{model_name}' isn't found in your config file.")
786
- raise ModelNameError(f"Config of model '{model_name}' isn't found in your config file.")
787
-
788
- return deepcopy(self["model"][model_name])
789
-
790
- def update_model_config(self, model_name: str, value: dict):
791
- """
792
- Update the config of a NWP model.
793
-
794
- An exception :class:`ModelNameError` will be raised if the config can't be found.
795
-
796
- :param model_name: Name of the model. For example, ``wrf``.
797
- :type model_name: str
798
- :param value: Dictionary contains new values.
799
- :type value: dict
800
- """
801
- if model_name not in self["model"]:
802
- logger.error(f"Config of model '{model_name}' isn't found in your config file.")
803
- raise ModelNameError(f"Config of model '{model_name}' isn't found in your config file.")
804
-
805
- self["model"][model_name] = self["model"][model_name] | value
806
-
807
- def get_log_path(self) -> str:
808
- """
809
- Get the directory path to save logs.
810
-
811
- :return: A directory path.
812
- :rtype: str
813
- """
814
- return self["log_path"]
815
-
816
- def get_socket_server_config(self) -> Tuple[str, int]:
817
- """
818
- Get settings of the socket server.
819
-
820
- :return: ("host", port)
821
- :rtype: tuple
822
- """
823
- return self["server_host"], self["server_port"]
824
-
825
- def get_job_scheduler_config(self) -> dict:
826
- """
827
- Get configs of job scheduler.
828
-
829
- :return: A dict object.
830
- :rtype: dict
831
- """
832
- return deepcopy(self["job_scheduler"])
833
-
834
- def get_core_num(self) -> int:
835
- """
836
- Get the number of CPU cores to be used.
837
- :return: Core numbers
838
- :rtype: int
839
- """
840
- return self["core_num"]
841
-
842
- def write_namelist(self, save_path: str, namelist_id: str, overwrite=True):
843
- """
844
- Write namelist values of a ``namelist_id`` to a file.
845
- This method is overwritten to convert URIs in ``save_path`` first.
846
-
847
- :param save_path: Target file path.
848
- :type save_path: str
849
- :param namelist_id: Registered ``namelist_id``.
850
- :type namelist_id: str
851
- :param overwrite: If overwrite the existed file.
852
- :type overwrite: bool
853
- """
854
- save_path = self.parse_resource_uri(save_path)
855
- super().write_namelist(save_path, namelist_id, overwrite)
856
-
857
-
858
- WRFRUNConfig = WRFRunConfig.__new__(WRFRunConfig)
859
-
860
-
861
- def set_register_func(func: Callable[["WRFRunConfig"], None]):
862
- """
863
- Set the function to register URIs.
864
-
865
- These functions should accept ``WRFRUNConfig`` instance,
866
- and will be called when initializing ``WRFRUNConfig``.
867
-
868
- If ``WRFRUNConfig`` has been initialized, ``func`` will be called immediately.
869
-
870
- Normal users should use :meth:`WRFRunConfig.register_resource_uri`,
871
- because ``WRFRUNConfig`` should (and must) be initialized.
872
-
873
- :param func: Functions to register URIs.
874
- :type func: Callable
875
- """
876
- global _URI_REGISTER_FUNC_LIST
877
-
878
- if object.__getattribute__(WRFRUNConfig, "_initialized"):
879
- func(WRFRUNConfig)
880
-
881
- else:
882
- if func not in _URI_REGISTER_FUNC_LIST:
883
- _URI_REGISTER_FUNC_LIST.append(func)
884
-
885
-
886
- def init_wrfrun_config(config_file: str) -> WRFRunConfig:
887
- """
888
- Initialize ``WRFRUNConfig`` with the given config file.
889
-
890
- :param config_file: Config file path.
891
- :type config_file: str
892
- :return: ``WRFRUNConfig`` instance.
893
- :rtype: WRFRunConfig
894
- """
895
- global WRFRUNConfig
896
-
897
- logger.info(f"Initialize `WRFRUNConfig` with config: {config_file}")
898
-
899
- WRFRUNConfig = WRFRunConfig.from_config_file(config_file)
900
-
901
- return WRFRUNConfig
902
-
903
-
904
- def get_wrfrun_config() -> WRFRunConfig:
905
- """
906
- Get ``WRFRUNConfig`` instance.
907
-
908
- An exception :class:`ConfigError` will be raised if you haven't initialized it.
909
-
910
- :return: ``WRFRUNConfig`` instance.
911
- :rtype: WRFRunConfig
912
- """
913
- global WRFRUNConfig
914
-
915
- if WRFRUNConfig is None:
916
- logger.error(f"`WRFRUNConfig` hasn't been initialized.")
917
- logger.error(f"Use `WRFRun` to load config automatically, or use function `init_wrfrun_config` to load config manually.")
918
- raise ConfigError(f"`WRFRUNConfig` hasn't been initialized.")
919
-
920
- return WRFRUNConfig
921
-
922
-
923
- __all__ = ["WRFRunConfig", "WRFRUNConfig", "init_wrfrun_config", "get_wrfrun_config", "set_register_func"]