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