wrfrun 0.1.8__py3-none-any.whl → 0.2.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 (53) hide show
  1. wrfrun/cli.py +131 -0
  2. wrfrun/core/base.py +52 -19
  3. wrfrun/core/config.py +257 -170
  4. wrfrun/core/error.py +8 -1
  5. wrfrun/core/replay.py +1 -1
  6. wrfrun/core/server.py +91 -71
  7. wrfrun/data.py +14 -16
  8. wrfrun/extension/goos_sst/__init__.py +5 -5
  9. wrfrun/extension/goos_sst/core.py +4 -1
  10. wrfrun/extension/goos_sst/res/Vtable.ERA_GOOS_SST +1 -1
  11. wrfrun/extension/goos_sst/res/__init__.py +17 -0
  12. wrfrun/extension/goos_sst/utils.py +21 -5
  13. wrfrun/extension/littler/__init__.py +57 -1
  14. wrfrun/extension/littler/{utils.py → core.py} +329 -43
  15. wrfrun/extension/utils.py +24 -22
  16. wrfrun/model/__init__.py +24 -1
  17. wrfrun/model/plot.py +259 -36
  18. wrfrun/model/utils.py +19 -9
  19. wrfrun/model/wrf/__init__.py +41 -0
  20. wrfrun/model/wrf/core.py +229 -101
  21. wrfrun/model/wrf/exec_wrap.py +49 -35
  22. wrfrun/model/wrf/geodata.py +2 -1
  23. wrfrun/model/wrf/namelist.py +78 -4
  24. wrfrun/model/wrf/{_metgrid.py → utils.py} +38 -3
  25. wrfrun/model/wrf/vtable.py +9 -5
  26. wrfrun/res/__init__.py +22 -7
  27. wrfrun/res/config/config.template.toml +57 -0
  28. wrfrun/res/{config.toml.template → config/wrf.template.toml} +7 -46
  29. wrfrun/res/run.template.sh +10 -0
  30. wrfrun/res/scheduler/lsf.template +5 -0
  31. wrfrun/res/{job_scheduler → scheduler}/pbs.template +1 -1
  32. wrfrun/res/{job_scheduler → scheduler}/slurm.template +2 -1
  33. wrfrun/run.py +39 -27
  34. wrfrun/scheduler/__init__.py +35 -0
  35. wrfrun/scheduler/env.py +44 -0
  36. wrfrun/scheduler/lsf.py +49 -0
  37. wrfrun/scheduler/pbs.py +50 -0
  38. wrfrun/scheduler/script.py +72 -0
  39. wrfrun/scheduler/slurm.py +50 -0
  40. wrfrun/scheduler/utils.py +14 -0
  41. wrfrun/utils.py +8 -3
  42. wrfrun/workspace/__init__.py +38 -0
  43. wrfrun/workspace/core.py +94 -0
  44. wrfrun/workspace/wrf.py +165 -0
  45. {wrfrun-0.1.8.dist-info → wrfrun-0.2.0.dist-info}/METADATA +3 -2
  46. wrfrun-0.2.0.dist-info/RECORD +62 -0
  47. wrfrun-0.2.0.dist-info/entry_points.txt +3 -0
  48. wrfrun/model/wrf/_ndown.py +0 -39
  49. wrfrun/pbs.py +0 -86
  50. wrfrun/res/run.sh.template +0 -16
  51. wrfrun/workspace.py +0 -88
  52. wrfrun-0.1.8.dist-info/RECORD +0 -51
  53. {wrfrun-0.1.8.dist-info → wrfrun-0.2.0.dist-info}/WHEEL +0 -0
wrfrun/core/config.py CHANGED
@@ -11,6 +11,9 @@ All classes in this module is used to manage various configurations of ``wrfrun`
11
11
  _WRFRunConstants
12
12
  _WRFRunNamelist
13
13
  _WRFRunResources
14
+ init_wrfrun_config
15
+ get_wrfrun_config
16
+ set_register_func
14
17
 
15
18
  WRFRunConfig
16
19
  ************
@@ -20,17 +23,24 @@ It inherits from three classes: :class:`_WRFRunResources`, :class:`_WRFRunConsta
20
23
  Users can use the global variable ``WRFRUNConfig``, which is the instance of this class being created when users import ``wrfrun``.
21
24
  """
22
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
23
32
  from copy import deepcopy
24
33
  from os import environ, makedirs
25
- from os.path import abspath, basename, dirname, exists
34
+ from os.path import abspath, dirname, exists
26
35
  from shutil import copyfile
27
- from typing import Optional, Tuple, Union
36
+ from sys import platform
37
+ from typing import Callable, Optional, Tuple, Union
28
38
 
29
39
  import f90nml
30
40
  import tomli
31
41
  import tomli_w
32
42
 
33
- from .error import ResourceURIError, WRFRunContextError, ModelNameError, NamelistIDError, NamelistError
43
+ from .error import ModelNameError, NamelistError, NamelistIDError, ResourceURIError, WRFRunContextError, ConfigError
34
44
  from ..utils import logger
35
45
 
36
46
 
@@ -110,7 +120,7 @@ class _WRFRunResources:
110
120
 
111
121
  For example, you can get the real path of ``wrfrun`` workspace with this method:
112
122
 
113
- >>> workspace_path = f"{WRFRUNConfig.WRFRUN_WORKSPACE_PATH}/WPS" # ":WRFRUN_WORKSPACE_PATH:/WPS"
123
+ >>> workspace_path = f"{WRFRUNConfig.WRFRUN_WORKSPACE_ROOT}/WPS" # ":WRFRUN_WORKSPACE_PATH:/WPS"
114
124
  >>> real_path = WRFRUNConfig.parse_resource_uri(workspace_path) # should be a valid path like: "/home/syize/.config/wrfrun/workspace/WPS"
115
125
 
116
126
  """
@@ -139,48 +149,53 @@ class _WRFRunConstants:
139
149
  Define all variables that will be used by other components.
140
150
  """
141
151
 
142
- def __init__(self):
152
+ def __init__(self, work_dir: str):
143
153
  """
144
154
  Define all variables that will be used by other components.
145
155
 
146
156
  These variables are related to ``wrfrun`` installation environments, configuration files and more.
147
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
148
161
  """
149
- # the path we may need to store temp files,
150
- # don't worry, it will be deleted once the system reboots
151
- self._WRFRUN_TEMP_PATH = "/tmp/wrfrun"
152
-
153
- # WRF may need a large disk space to store output, we can't run wrf in /tmp,
154
- # so we will create a folder in $HOME/.config to run wrf.
155
- # we need to check if we're running as a root user
156
- USER_HOME_PATH = f"{environ['HOME']}"
157
- if USER_HOME_PATH in ["/", "/root", ""]:
158
- logger.warning(f"User's home path is '{USER_HOME_PATH}', which means you are running this program as a root user")
159
- logger.warning("It's not recommended to use wrfrun as a root user")
160
- logger.warning("Set USER_HOME_PATH as /root")
161
- USER_HOME_PATH = "/root"
162
-
163
- self._WRFRUN_HOME_PATH = f"{USER_HOME_PATH}/.config/wrfrun"
164
- self._WRFRUN_REPLAY_WORK_PATH = f"{self._WRFRUN_HOME_PATH}/replay"
165
-
166
- # work path to run WPS, WRF and WRFDA
167
- self._WORK_PATH = f"{self._WRFRUN_HOME_PATH}/workspace"
168
- self._WPS_WORK_PATH = f"{self._WORK_PATH}/WPS"
169
- self._WRF_WORK_PATH = f"{self._WORK_PATH}/WRF"
170
- self._WRFDA_WORK_PATH = f"{self._WORK_PATH}/WRFDA"
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"
171
192
 
172
193
  # record WRF progress status
173
- self._WORK_STATUS = ""
194
+ self._WRFRUN_WORK_STATUS = ""
174
195
 
175
196
  # record context status
176
197
  self._WRFRUN_CONTEXT_STATUS = False
177
198
 
178
- # WRFDA is not necessary
179
- self.USE_WRFDA: bool = False
180
-
181
- # output directory of ungrib
182
- self._UNGRIB_OUT_DIR = "./outputs"
183
-
184
199
  self._WRFRUN_OUTPUT_PATH = ":WRFRUN_OUTPUT_PATH:"
185
200
  self._WRFRUN_RESOURCE_PATH = ":WRFRUN_RESOURCE_PATH:"
186
201
 
@@ -203,22 +218,20 @@ class _WRFRunConstants:
203
218
  return {
204
219
  self.WRFRUN_TEMP_PATH: self._WRFRUN_TEMP_PATH,
205
220
  self.WRFRUN_HOME_PATH: self._WRFRUN_HOME_PATH,
206
- self.WRFRUN_WORKSPACE_PATH: self._WORK_PATH,
207
- self.WPS_WORK_PATH: self._WPS_WORK_PATH,
208
- self.WRF_WORK_PATH: self._WRF_WORK_PATH,
209
- self.WRFDA_WORK_PATH: self._WRFDA_WORK_PATH,
210
- self.WRFRUN_REPLAY_WORK_PATH: self._WRFRUN_REPLAY_WORK_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,
211
224
  }
212
225
 
213
226
  @property
214
- def WRFRUN_REPLAY_WORK_PATH(self) -> str:
227
+ def WRFRUN_WORKSPACE_REPLAY(self) -> str:
215
228
  """
216
- Path (URI) to store related files of ``wrfrun``'s replay functionality.
229
+ Path (URI) to store related files of ``wrfrun`` replay functionality.
217
230
 
218
231
  :return: URI.
219
232
  :rtype: str
220
233
  """
221
- return ":WRFRUN_REPLAY_WORK_PATH:"
234
+ return ":WRFRUN_WORKSPACE_REPLAY:"
222
235
 
223
236
  @property
224
237
  def WRFRUN_TEMP_PATH(self) -> str:
@@ -241,44 +254,24 @@ class _WRFRunConstants:
241
254
  return ":WRFRUN_HOME_PATH:"
242
255
 
243
256
  @property
244
- def WRFRUN_WORKSPACE_PATH(self) -> str:
245
- """
246
- Path of the workspace, in which ``wrfrun`` runs NWP models.
247
-
248
- :return: URI
249
- :rtype: str
250
- """
251
- return ":WRFRUN_WORKSPACE_PATH:"
252
-
253
- @property
254
- def WPS_WORK_PATH(self) -> str:
255
- """
256
- Workspace in which ``wrfrun`` runs WPS.
257
-
258
- :return: URI
259
- :rtype: str
260
- """
261
- return ":WRFRUN_WPS_WORK_PATH:"
262
-
263
- @property
264
- def WRF_WORK_PATH(self) -> str:
257
+ def WRFRUN_WORKSPACE_ROOT(self) -> str:
265
258
  """
266
- Workspace in which ``wrfrun`` runs WRF.
259
+ Path of the root workspace.
267
260
 
268
261
  :return: URI
269
262
  :rtype: str
270
263
  """
271
- return ":WRFRUN_WRF_WORK_PATH:"
264
+ return ":WRFRUN_WORKSPACE_ROOT:"
272
265
 
273
266
  @property
274
- def WRFDA_WORK_PATH(self) -> str:
267
+ def WRFRUN_WORKSPACE_MODEL(self) -> str:
275
268
  """
276
- Workspace in which ``wrfrun`` runs WRFDA.
269
+ Path of the model workspace, in which ``wrfrun`` runs numerical models.
277
270
 
278
271
  :return: URI
279
272
  :rtype: str
280
273
  """
281
- return ":WRFRUN_WRFDA_WORK_PATH:"
274
+ return ":WRFRUN_WORKSPACE_MODEL:"
282
275
 
283
276
  @property
284
277
  def WRFRUN_WORK_STATUS(self) -> str:
@@ -291,7 +284,7 @@ class _WRFRunConstants:
291
284
  :return: A string reflect the current work progress.
292
285
  :rtype: str
293
286
  """
294
- return self._WORK_STATUS
287
+ return self._WRFRUN_WORK_STATUS
295
288
 
296
289
  @WRFRUN_WORK_STATUS.setter
297
290
  def WRFRUN_WORK_STATUS(self, value: str):
@@ -306,29 +299,7 @@ class _WRFRunConstants:
306
299
  """
307
300
  if not isinstance(value, str):
308
301
  value = str(value)
309
- self._WORK_STATUS = value
310
-
311
- @property
312
- def UNGRIB_OUT_DIR(self) -> str:
313
- """
314
- Output directory path of ``ungrib.exe``.
315
-
316
- :return: URI
317
- :rtype: str
318
- """
319
- return self._UNGRIB_OUT_DIR
320
-
321
- @UNGRIB_OUT_DIR.setter
322
- def UNGRIB_OUT_DIR(self, value: str):
323
- """
324
- Set the output directory path of ``ungrib.exe``.
325
-
326
- :param value: A real path or a URI represents the directory path of ``ungrib.exe``'s output.
327
- :type value: str
328
- """
329
- if not isinstance(value, str):
330
- value = str(value)
331
- self._UNGRIB_OUT_DIR = value
302
+ self._WRFRUN_WORK_STATUS = value
332
303
 
333
304
  @property
334
305
  def WRFRUN_OUTPUT_PATH(self) -> str:
@@ -394,9 +365,6 @@ class _WRFRunNamelist:
394
365
  If you want to use a new ``namelist_id`` other than the defaults to store namelist,
395
366
  you can register a new ``namelist_id`` with :meth:`_WRFRunNamelist.register_custom_namelist_id`.
396
367
  """
397
- self._wps_namelist = {}
398
- self._wrf_namelist = {}
399
- self._wrfda_namelist = {}
400
368
  self._namelist_dict = {}
401
369
  self._namelist_id_list = ("param", "geog_static_data", "wps", "wrf", "wrfda")
402
370
 
@@ -570,6 +538,24 @@ class _WRFRunNamelist:
570
538
 
571
539
  self._namelist_dict.pop(namelist_id)
572
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
+
573
559
 
574
560
  class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
575
561
  """
@@ -577,8 +563,9 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
577
563
  """
578
564
  _instance = None
579
565
  _initialized = False
566
+ _lock = threading.Lock()
580
567
 
581
- def __init__(self):
568
+ def __init__(self, work_dir: str):
582
569
  """
583
570
  This class provides various interfaces to access ``wrfrun``'s config, namelist values of NWP models,
584
571
  runtime constants and resource files by inheriting from:
@@ -586,23 +573,32 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
586
573
 
587
574
  An instance of this class called ``WRFRUNConfig`` will be created after the user import ``wrfrun``,
588
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
589
579
  """
590
580
  if self._initialized:
591
581
  return
592
582
 
593
- _WRFRunConstants.__init__(self)
594
- _WRFRunNamelist.__init__(self)
595
- _WRFRunResources.__init__(self)
583
+ with self._lock:
584
+ global _URI_REGISTER_FUNC_LIST
596
585
 
597
- self._config = {}
586
+ self._initialized = True
598
587
 
599
- # register uri for wrfrun constants
600
- for key, value in self._get_uri_map().items():
601
- self.register_resource_uri(key, value)
588
+ _WRFRunConstants.__init__(self, work_dir)
589
+ _WRFRunNamelist.__init__(self)
590
+ _WRFRunResources.__init__(self)
591
+
592
+ self._config = {}
602
593
 
603
- self._config_template_file_path = None
594
+ self._config_template_file_path = None
604
595
 
605
- self._initialized = True
596
+ self._register_wrfrun_uris()
597
+
598
+ for _fun in _URI_REGISTER_FUNC_LIST:
599
+ _fun(self)
600
+
601
+ _URI_REGISTER_FUNC_LIST = []
606
602
 
607
603
  def __new__(cls, *args, **kwargs):
608
604
  if cls._instance is None:
@@ -610,6 +606,39 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
610
606
 
611
607
  return cls._instance
612
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
+
613
642
  def set_config_template_path(self, file_path: str):
614
643
  """
615
644
  Set file path of the config template file.
@@ -654,10 +683,39 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
654
683
  with open(config_path, "rb") as f:
655
684
  self._config = tomli.load(f)
656
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
+
657
710
  # register URI for output directory.
658
711
  output_path = abspath(self["output_path"])
659
712
  self.register_resource_uri(self.WRFRUN_OUTPUT_PATH, output_path)
660
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
+
661
719
  def save_wrfrun_config(self, save_path: str):
662
720
  """
663
721
  Save ``wrfrun``'s config to a file.
@@ -691,6 +749,18 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
691
749
 
692
750
  return deepcopy(self._config[item])
693
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
+
694
764
  def get_input_data_path(self) -> str:
695
765
  """
696
766
  Get the path of directory in which stores the input data.
@@ -717,6 +787,23 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
717
787
 
718
788
  return deepcopy(self["model"][model_name])
719
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
+
720
807
  def get_log_path(self) -> str:
721
808
  """
722
809
  Get the directory path to save logs.
@@ -752,69 +839,6 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
752
839
  """
753
840
  return self["core_num"]
754
841
 
755
- def get_ungrib_out_dir_path(self) -> str:
756
- """
757
- Get the output directory of ungrib output (WRF intermediate file).
758
-
759
- :return: URI path.
760
- :rtype: str
761
- """
762
- wif_prefix = self.get_namelist("wps")["ungrib"]["prefix"]
763
- wif_path = f"{self.WPS_WORK_PATH}/{dirname(wif_prefix)}"
764
-
765
- return wif_path
766
-
767
- def get_ungrib_out_prefix(self) -> str:
768
- """
769
- Get the prefix string of ungrib output (WRF intermediate file).
770
-
771
- :return: Prefix string of ungrib output (WRF intermediate file).
772
- :rtype: str
773
- """
774
- wif_prefix = self.get_namelist("wps")["ungrib"]["prefix"]
775
- wif_prefix = basename(wif_prefix)
776
- return wif_prefix
777
-
778
- def set_ungrib_out_prefix(self, prefix: str):
779
- """
780
- Set the prefix string of ungrib output (WRF intermediate file).
781
-
782
- :param prefix: Prefix string of ungrib output (WRF intermediate file).
783
- :type prefix: str
784
- """
785
- self.update_namelist(
786
- {
787
- "ungrib": {"prefix": f"{self.UNGRIB_OUT_DIR}/{prefix}"}
788
- }, "wps"
789
- )
790
-
791
- def get_metgrid_fg_names(self) -> list[str]:
792
- """
793
- Get prefix strings from "fg_name" in namelist "metgrid" section.
794
-
795
- :return: Prefix strings list.
796
- :rtype: list
797
- """
798
- fg_names = self.get_namelist("wps")["metgrid"]["fg_name"]
799
- fg_names = [basename(x) for x in fg_names]
800
- return fg_names
801
-
802
- def set_metgrid_fg_names(self, prefix: Union[str, list[str]]):
803
- """
804
- Set prefix strings of "fg_name" in namelist "metgrid" section.
805
-
806
- :param prefix: Prefix strings list.
807
- :type prefix: str | list
808
- """
809
- if isinstance(prefix, str):
810
- prefix = [prefix, ]
811
- fg_names = [f"{self.UNGRIB_OUT_DIR}/{x}" for x in prefix]
812
- self.update_namelist(
813
- {
814
- "metgrid": {"fg_name": fg_names}
815
- }, "wps"
816
- )
817
-
818
842
  def write_namelist(self, save_path: str, namelist_id: str, overwrite=True):
819
843
  """
820
844
  Write namelist values of a ``namelist_id`` to a file.
@@ -831,6 +855,69 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
831
855
  super().write_namelist(save_path, namelist_id, overwrite)
832
856
 
833
857
 
834
- WRFRUNConfig = WRFRunConfig()
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
+
835
922
 
836
- __all__ = ["WRFRUNConfig"]
923
+ __all__ = ["WRFRunConfig", "WRFRUNConfig", "init_wrfrun_config", "get_wrfrun_config", "set_register_func"]
wrfrun/core/error.py CHANGED
@@ -106,5 +106,12 @@ class ModelNameError(WRFRunBasicError):
106
106
  pass
107
107
 
108
108
 
109
+ class RecordError(WRFRunBasicError):
110
+ """
111
+ Exception indicates ``wrfrun`` can't record simulations.
112
+ """
113
+ pass
114
+
115
+
109
116
  __all__ = ["WRFRunBasicError", "ConfigError", "WRFRunContextError", "CommandError", "OutputFileError", "ResourceURIError", "InputFileError",
110
- "NamelistError", "ExecRegisterError", "GetExecClassError", "ModelNameError", "NamelistIDError"]
117
+ "NamelistError", "ExecRegisterError", "GetExecClassError", "ModelNameError", "NamelistIDError", "RecordError"]
wrfrun/core/replay.py CHANGED
@@ -124,7 +124,7 @@ def replay_config_generator(replay_config_file: str) -> Generator[tuple[str, Exe
124
124
  :rtype: Generator
125
125
  """
126
126
  logger.info(f"Loading replay resources from: {replay_config_file}")
127
- work_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_REPLAY_WORK_PATH)
127
+ work_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_WORKSPACE_REPLAY)
128
128
 
129
129
  unpack_archive(replay_config_file, work_path, "zip")
130
130