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.
- wrfrun/__init__.py +3 -0
- wrfrun/core/__init__.py +5 -0
- wrfrun/core/base.py +680 -0
- wrfrun/core/config.py +717 -0
- wrfrun/core/error.py +80 -0
- wrfrun/core/replay.py +113 -0
- wrfrun/core/server.py +212 -0
- wrfrun/data.py +418 -0
- wrfrun/extension/__init__.py +1 -0
- wrfrun/extension/littler/__init__.py +1 -0
- wrfrun/extension/littler/utils.py +599 -0
- wrfrun/extension/utils.py +66 -0
- wrfrun/model/__init__.py +7 -0
- wrfrun/model/base.py +14 -0
- wrfrun/model/plot.py +54 -0
- wrfrun/model/utils.py +34 -0
- wrfrun/model/wrf/__init__.py +6 -0
- wrfrun/model/wrf/_metgrid.py +71 -0
- wrfrun/model/wrf/_ndown.py +39 -0
- wrfrun/model/wrf/core.py +805 -0
- wrfrun/model/wrf/exec_wrap.py +101 -0
- wrfrun/model/wrf/geodata.py +301 -0
- wrfrun/model/wrf/namelist.py +377 -0
- wrfrun/model/wrf/scheme.py +311 -0
- wrfrun/model/wrf/vtable.py +65 -0
- wrfrun/pbs.py +86 -0
- wrfrun/plot/__init__.py +1 -0
- wrfrun/plot/wps.py +188 -0
- wrfrun/res/__init__.py +22 -0
- wrfrun/res/config.toml.template +136 -0
- wrfrun/res/extension/plotgrids.ncl +216 -0
- wrfrun/res/job_scheduler/pbs.template +6 -0
- wrfrun/res/job_scheduler/slurm.template +6 -0
- wrfrun/res/namelist/namelist.input.da_wrfvar.template +261 -0
- wrfrun/res/namelist/namelist.input.dfi.template +260 -0
- wrfrun/res/namelist/namelist.input.real.template +256 -0
- wrfrun/res/namelist/namelist.input.wrf.template +256 -0
- wrfrun/res/namelist/namelist.wps.template +44 -0
- wrfrun/res/namelist/parame.in.template +11 -0
- wrfrun/res/run.sh.template +16 -0
- wrfrun/run.py +264 -0
- wrfrun/utils.py +257 -0
- wrfrun/workspace.py +88 -0
- wrfrun-0.1.7.dist-info/METADATA +67 -0
- wrfrun-0.1.7.dist-info/RECORD +46 -0
- 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"]
|