wrfrun 0.1.7__py3-none-any.whl → 0.1.8__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/core/__init__.py CHANGED
@@ -1,3 +1,36 @@
1
+ """
2
+ wrfrun.core
3
+ ###########
4
+
5
+ The core functionalities of ``wrfrun`` are all implemented in the submodule ``wrfrun.core``,
6
+ such as processing namelist files, managing resource files required during model's running (e.g., VTable files),
7
+ calling the numerical model and process its output and log files, monitoring simulation process via model's log,
8
+ recording and replaying the simulation.
9
+
10
+ The functionalities are split into several submodules, which are listed in the table below.
11
+
12
+ Submodules
13
+ **********
14
+
15
+ ================================ ========================================================
16
+ :doc:`base </api/core.base>` Executable base class and related classes.
17
+ :doc:`config </api/core.config>` ``wrfrun`` config classes.
18
+ :doc:`error </api/core.error>` ``wrfrun`` error classes.
19
+ :doc:`replay </api/core.replay>` Functions and classes to record and replay simulations.
20
+ :doc:`server </api/core.server>` Functions and classes to start socket server.
21
+ ================================ ========================================================
22
+
23
+ .. toctree::
24
+ :maxdepth: 1
25
+ :hidden:
26
+
27
+ base <core.base>
28
+ config <core.config>
29
+ error <core.error>
30
+ replay <core.replay>
31
+ server <core.server>
32
+ """
33
+
1
34
  from .base import *
2
35
  from .config import *
3
36
  from .error import *
wrfrun/core/base.py CHANGED
@@ -1,4 +1,49 @@
1
+ """
2
+ wrfrun.core.base
3
+ ################
4
+
5
+ Defines what :class:`ExecutableBase <Executable>` is, how it works and how ``wrfrun`` records simulations.
6
+
7
+ .. autosummary::
8
+ :toctree: generated/
9
+
10
+ check_subprocess_status
11
+ call_subprocess
12
+ InputFileType
13
+ FileConfigDict
14
+ ExecutableClassConfig
15
+ ExecutableConfig
16
+ _ExecutableConfigRecord
17
+ ExecutableBase
18
+
19
+ Executable
20
+ **********
21
+
22
+ While ``wrfrun`` aims to provide Python interfaces to various Numerical Weather Prediction model,
23
+ it is important to provide a clear standard about how should a external executable file be implemented in ``wrfrun``.
24
+ ``wrfrun`` provides a class called :class:`ExecutableBase`, which is the parent class for all ``Executable`` classes.
25
+ It not only provide the method to execute external programs,
26
+ but also:
27
+
28
+ * Store all the information about the program (e.g., its inputs and outputs, its configuration).
29
+ * Provide the interface to import and export ``Executable``'s config.
30
+ * Support the ``replay`` functionality of ``wrfrun``.
31
+
32
+ If you want to use all the ``wrfrun``'s features, you **HAVE TO** implement your code with :class:`ExecutableBase`.
33
+
34
+ ExecConfigRecorder
35
+ ******************
36
+
37
+ To be able to record the whole simulation, ``wrfrun`` introduces the global variable ``ExecConfigRecorder``,
38
+ which is an instance of class :class:`_ExecutableConfigRecord`.
39
+ ``ExecConfigRecorder`` can store the config of any ``Executable`` in the correct order and export them to a JSON file,
40
+ enabling the precise replay of the simulation. Depending on the internal implementation of the ``Executable``,
41
+ the recorded config may even include the complete namelist values, Vtable config, and more.
42
+ Please check :meth:`ExecutableBase.generate_custom_config` for more details.
43
+ """
44
+
1
45
  import subprocess
46
+ from copy import deepcopy
2
47
  from enum import Enum
3
48
  from json import dumps
4
49
  from os import chdir, getcwd, listdir, makedirs, remove, symlink
@@ -15,7 +60,8 @@ from ..utils import check_path, logger
15
60
 
16
61
  def check_subprocess_status(status: subprocess.CompletedProcess):
17
62
  """
18
- Check subprocess return code and print log if ``return_code != 0``.
63
+ Check subprocess return code.
64
+ An ``RuntimeError`` exception will be raised if ``return_code != 0``, and the ``stdout`` and ``stderr`` of the subprocess will be logged.
19
65
 
20
66
  :param status: Status from subprocess.
21
67
  :type status: CompletedProcess
@@ -39,7 +85,7 @@ def check_subprocess_status(status: subprocess.CompletedProcess):
39
85
 
40
86
  def call_subprocess(command: list[str], work_path: Optional[str] = None, print_output=False):
41
87
  """
42
- Execute the given command in system shell.
88
+ Execute the given command in the system shell.
43
89
 
44
90
  :param command: A list contains the command and parameters to be executed.
45
91
  :type command: list
@@ -48,8 +94,6 @@ def call_subprocess(command: list[str], work_path: Optional[str] = None, print_o
48
94
  :type work_path: str | None
49
95
  :param print_output: If print standard output and error in the logger.
50
96
  :type print_output: bool
51
- :return:
52
- :rtype:
53
97
  """
54
98
  if work_path is not None:
55
99
  origin_path = getcwd()
@@ -88,9 +132,21 @@ def _json_default(obj):
88
132
 
89
133
  class InputFileType(Enum):
90
134
  """
91
- Input file type.
92
- ``WRFRUN_RES`` means the input file is from the NWP or wrfrun package.
93
- ``CUSTOM_RES`` means the input file is from the user, which may be a customized file.
135
+ This class is an ``Enum`` class, providing the following values:
136
+
137
+ .. py:attribute:: WRFRUN_RES
138
+ :type: int
139
+ :value: 1
140
+
141
+ Indicating resource files are from the model or ``wrfrun``.
142
+ ``wrfrun`` won't save these files when recording the simulation.
143
+
144
+ .. py:attribute:: CUSTOM_RES
145
+ :type: int
146
+ :value: 2
147
+
148
+ Indicating resource files are provided by the user.
149
+ ``wrfrun`` will also save these files to ``.replay`` file when recording the simulation to ensure the simulation is replayable.
94
150
  """
95
151
  WRFRUN_RES = 1
96
152
  CUSTOM_RES = 2
@@ -98,7 +154,32 @@ class InputFileType(Enum):
98
154
 
99
155
  class FileConfigDict(TypedDict):
100
156
  """
101
- Dict class to give information to process files.
157
+ This dict is used to store information about the file, including its path, the path it will be copied or moved to, its new name, etc. This dict contains following keys:
158
+
159
+ .. py:attribute:: file_path
160
+ :type: str
161
+
162
+ A real file path or a valid URI which can be converted to a file path.
163
+
164
+ .. py:attribute:: save_path
165
+ :type: str
166
+
167
+ Save path of the file.
168
+
169
+ .. py:attribute:: save_name
170
+ :type: str
171
+
172
+ Save name of the file.
173
+
174
+ .. py:attribute:: is_data
175
+ :type: bool
176
+
177
+ If the file is data. If not, ``wrfrun`` will treat it as a config file, and always save it to ``.replay`` file when recording the simulation.
178
+
179
+ .. py:attribute:: is_output
180
+ :type: bool
181
+
182
+ If the file is model's output. Output file will never be saved to ``.replay`` file.
102
183
  """
103
184
  file_path: str
104
185
  save_path: str
@@ -109,7 +190,17 @@ class FileConfigDict(TypedDict):
109
190
 
110
191
  class ExecutableClassConfig(TypedDict):
111
192
  """
112
- Executable class initialization config template.
193
+ This dict is used to store arguments of ``Executable``'s ``__init__`` function.
194
+
195
+ .. py:attribute:: class_args
196
+ :type: tuple
197
+
198
+ Positional arguments of the class.
199
+
200
+ .. py:attribute:: class_kwargs
201
+ :type: dict
202
+
203
+ Keyword arguments of the class.
113
204
  """
114
205
  # only list essential config
115
206
  class_args: tuple
@@ -118,7 +209,57 @@ class ExecutableClassConfig(TypedDict):
118
209
 
119
210
  class ExecutableConfig(TypedDict):
120
211
  """
121
- Executable config template.
212
+ This dict is used to store all configs of a :class:`ExecutableBase`.
213
+
214
+ .. py:attribute:: name
215
+ :type: str
216
+
217
+ Name of the executable. Each type of executable has a unique name.
218
+
219
+ .. py:attribute:: cmd
220
+ :type: str | list[str]
221
+
222
+ Command of the executable.
223
+
224
+ .. py:attribute:: work_path
225
+ :type: str | None
226
+
227
+ Work path of the executable.
228
+
229
+ .. py:attribute:: mpi_use
230
+ :type: bool
231
+
232
+ If the executable will use MPI.
233
+
234
+ .. py:attribute:: mpi_cmd
235
+ :type: str | None
236
+
237
+ Command name of the MPI.
238
+
239
+ .. py:attribute:: mpi_core_num
240
+ :type: int | None
241
+
242
+ Number of the CPU core to use with MPI.
243
+
244
+ .. py:attribute:: class_config
245
+ :type: ExecutableClassConfig | None
246
+
247
+ A dict stores arguments of ``Executable``'s ``__init__`` function.
248
+
249
+ .. py:attribute:: input_file_config
250
+ :type: list[FileConfigDict] | None
251
+
252
+ A list stores information about input files of the executable.
253
+
254
+ .. py:attribute:: output_file_config
255
+ :type: list[FileConfigDict] | None
256
+
257
+ A list stores information about output files of the executable.
258
+
259
+ .. py:attribute:: custom_config
260
+ :type: dict | None
261
+
262
+ A dict that can be used by subclass to store other configs.
122
263
  """
123
264
  name: str
124
265
  cmd: Union[str, list[str]]
@@ -134,17 +275,16 @@ class ExecutableConfig(TypedDict):
134
275
 
135
276
  class _ExecutableConfigRecord:
136
277
  """
137
- Record executable configs and export them.
278
+ A class to helps store configs of various executables and exports them to a file.
138
279
  """
139
280
  _instance = None
140
281
  _initialized = False
141
282
 
142
283
  def __init__(self, save_path: Optional[str] = None, include_data=False):
143
284
  """
144
- Record executable configs and export them.
145
285
 
146
286
  :param save_path: Save path of the exported config file.
147
- :type save_path: str
287
+ :type save_path: str | None
148
288
  :param include_data: If includes input data.
149
289
  :type include_data: bool
150
290
  """
@@ -179,8 +319,8 @@ class _ExecutableConfigRecord:
179
319
  """
180
320
  Reinitialize this instance.
181
321
 
182
- :return:
183
- :rtype:
322
+ :return: New instance.
323
+ :rtype: _ExecutableConfigRecord
184
324
  """
185
325
  self._initialized = False
186
326
  return _ExecutableConfigRecord(save_path, include_data)
@@ -191,8 +331,6 @@ class _ExecutableConfigRecord:
191
331
 
192
332
  :param exported_config: Executable config.
193
333
  :type exported_config: ExecutableConfig
194
- :return:
195
- :rtype:
196
334
  """
197
335
  if not self.include_data:
198
336
  self._recorded_config.append(exported_config)
@@ -236,19 +374,13 @@ class _ExecutableConfigRecord:
236
374
 
237
375
  def clear_records(self):
238
376
  """
239
- Clean old configs.
240
-
241
- :return:
242
- :rtype:
377
+ Clean recorded configs.
243
378
  """
244
379
  self._recorded_config = []
245
380
 
246
381
  def export_replay_file(self):
247
382
  """
248
- Save replay file to the specific save path.
249
-
250
- :return:
251
- :rtype:
383
+ Save replay file to the save path.
252
384
  """
253
385
  if len(self._recorded_config) == 0:
254
386
  logger.warning("No replay config has been recorded.")
@@ -288,12 +420,37 @@ ExecConfigRecorder = _ExecutableConfigRecord()
288
420
  class ExecutableBase:
289
421
  """
290
422
  Base class for all executables.
423
+
424
+ .. py:attribute:: class_config
425
+ :type: ExecutableClassConfig
426
+ :value: {"class_args": (), "class_kwargs": {}}
427
+
428
+ A dict stores arguments of ``Executable``'s ``__init__`` function.
429
+
430
+ .. py:attribute:: custom_config
431
+ :type: dict
432
+ :value: {}
433
+
434
+ A dict that can be used by subclass to store custom configs.
435
+
436
+ .. py:attribute:: input_file_config
437
+ :type: list[FileConfigDict]
438
+ :value: []
439
+
440
+ A list stores information about input files of the executable.
441
+
442
+ .. py:attribute:: output_file_config
443
+ :type: list[FileConfigDict]
444
+ :value: []
445
+
446
+ A list stores information about output files of the executable.
447
+
291
448
  """
292
449
  _instance = None
293
450
 
294
- def __init__(self, name: str, cmd: Union[str, list[str]], work_path: str, mpi_use=False, mpi_cmd: Optional[str] = None, mpi_core_num: Optional[int] = None):
451
+ def __init__(self, name: str, cmd: Union[str, list[str]], work_path: str,
452
+ mpi_use=False, mpi_cmd: Optional[str] = None, mpi_core_num: Optional[int] = None):
295
453
  """
296
- Base class for all executables.
297
454
 
298
455
  :param name: Unique name to identify different executables.
299
456
  :type name: str
@@ -338,19 +495,23 @@ class ExecutableBase:
338
495
 
339
496
  def generate_custom_config(self):
340
497
  """
341
- Generate custom configs.
498
+ Generate custom configs. This method should be overwritten in the child class,
499
+ and **MUST STORE THE CUSTOM CONFIG IN THE ATTRIBUTE** :attr:`ExecutableBase.custom_config`,
500
+ or it will do nothing except print a debug log.
342
501
 
343
- :return:
344
- :rtype:
502
+ You can export various configs in this method, like the complete namelist values of a NWP model binary,
503
+ or the path of Vtable file this executable will use.
504
+
505
+ If you overwrite this method to generate custom configs,
506
+ you also have to overwrite :meth:`ExecutableBase.load_custom_config` to load your custom configs.
345
507
  """
346
508
  logger.debug(f"Method 'generate_custom_config' not implemented in '{self.name}'")
347
509
 
348
510
  def load_custom_config(self):
349
511
  """
350
512
  Load custom configs.
351
-
352
- :return:
353
- :rtype:
513
+ This method should be overwritten in the child class to process the custom config stored in :attr:`ExecutableBase.custom_config`,
514
+ or it will do nothing except print a debug log.
354
515
  """
355
516
  logger.debug(f"Method 'load_custom_config' not implemented in '{self.name}'")
356
517
 
@@ -370,20 +531,18 @@ class ExecutableBase:
370
531
  "mpi_use": self.mpi_use,
371
532
  "mpi_cmd": self.mpi_cmd,
372
533
  "mpi_core_num": self.mpi_core_num,
373
- "class_config": self.class_config,
374
- "custom_config": self.custom_config,
375
- "input_file_config": self.input_file_config,
376
- "output_file_config": self.output_file_config
534
+ "class_config": deepcopy(self.class_config),
535
+ "custom_config": deepcopy(self.custom_config),
536
+ "input_file_config": deepcopy(self.input_file_config),
537
+ "output_file_config": deepcopy(self.output_file_config)
377
538
  }
378
539
 
379
540
  def load_config(self, config: ExecutableConfig):
380
541
  """
381
- Load config from a dict.
542
+ Load executable config from a dict.
382
543
 
383
544
  :param config: Config dict. It must contain some essential keys. Check ``ExecutableConfig`` for details.
384
545
  :type config: ExecutableConfig
385
- :return:
386
- :rtype:
387
546
  """
388
547
  if "name" not in config:
389
548
  logger.error("A valid config is required. Please check ``ExecutableConfig``.")
@@ -398,10 +557,10 @@ class ExecutableBase:
398
557
  self.mpi_use = config["mpi_use"]
399
558
  self.mpi_cmd = config["mpi_cmd"]
400
559
  self.mpi_core_num = config["mpi_core_num"]
401
- self.class_config = config["class_config"]
402
- self.custom_config = config["custom_config"]
403
- self.input_file_config = config["input_file_config"]
404
- self.output_file_config = config["output_file_config"]
560
+ self.class_config = deepcopy(config["class_config"])
561
+ self.custom_config = deepcopy(config["custom_config"])
562
+ self.input_file_config = deepcopy(config["input_file_config"])
563
+ self.output_file_config = deepcopy(config["output_file_config"])
405
564
 
406
565
  self.load_custom_config()
407
566
 
@@ -409,23 +568,22 @@ class ExecutableBase:
409
568
  """
410
569
  This method will be called when replay the simulation.
411
570
  This method should take care every job that will be done when replaying the simulation.
412
-
413
- :return:
414
- :rtype:
571
+ By default, this method will call ``__call__`` method of the instance.
415
572
  """
416
573
  logger.debug(f"Method 'replay' not implemented in '{self.name}', fall back to default action.")
417
574
  self()
418
575
 
419
- def add_input_files(self, input_files: Union[str, list[str], FileConfigDict, list[FileConfigDict]], is_data=True, is_output=True):
576
+ def add_input_files(self, input_files: Union[str, list[str], FileConfigDict, list[FileConfigDict]],
577
+ is_data=True, is_output=True):
420
578
  """
421
- Add input files the extension will use.
579
+ Add input files the executable will use.
422
580
 
423
581
  You can give a single file path or a list contains files' path.
424
582
 
425
583
  >>> self.add_input_files("data/custom_file")
426
584
  >>> self.add_input_files(["data/custom_file_1", "data/custom_file_2"])
427
585
 
428
- You can give more information with a ``FileConfigDict``, like the path and the name to store, and if it is data.
586
+ You can give more information with a ``FileConfigDict``.
429
587
 
430
588
  >>> file_dict: FileConfigDict = {
431
589
  ... "file_path": "data/custom_file.nc",
@@ -452,12 +610,14 @@ class ExecutableBase:
452
610
  ... }
453
611
  >>> self.add_input_files([file_dict_1, file_dict_2])
454
612
 
455
- :param input_files: Custom files' path.
613
+ Please check :class:`FileConfigDict` for more details.
614
+
615
+ :param input_files: Custom files.
456
616
  :type input_files: str | list | dict
457
- :param is_data: If its data. This parameter will be overwritten by the value in ``input_files``.
617
+ :param is_data: If it is a data file. This parameter will be overwritten by the value in ``input_files``.
458
618
  :type is_data: bool
459
- :return:
460
- :rtype:
619
+ :param is_output: If it is an output from another executable. This parameter will be overwritten by the value in ``input_files``.
620
+ :type is_output: bool
461
621
  """
462
622
  if isinstance(input_files, str):
463
623
  self.input_file_config.append(
@@ -496,22 +656,39 @@ class ExecutableBase:
496
656
  endswith: Union[None, str, tuple[str, ...]] = None, outputs: Union[None, str, list[str]] = None, no_file_error=True
497
657
  ):
498
658
  """
499
- Add save file rules.
659
+ Find and save model's outputs to the output save path.
660
+ An ``OutputFileError`` exception will be raised if no file can be found and ``no_file_error==True``.
661
+
662
+ You can give the specific path of a file or multiple files.
663
+
664
+ >>> self.add_output_files(outputs="wrfout.d01")
665
+ >>> self.add_output_files(outputs=["wrfout.d01", "wrfout.d02"])
666
+
667
+ If you have too many outputs, but they have the same prefix or postfix, you can use ``startswith`` or ``endswith``.
668
+
669
+ >>> self.add_output_files(startswith="rsl.out.")
670
+ >>> self.add_output_files(endswith="log")
671
+ >>> self.add_output_files(startswith=("rsl", "wrfout"), endswith="log")
672
+
673
+ ``startswith``, ``endswith`` and ``outputs`` can be used together.
500
674
 
501
- :param output_dir: Output dir paths.
675
+ ``output_dir`` specify the search path of outputs, by default it is the work path of the executable.
676
+ You can change its value if the output path of the executable isn't its work path.
677
+
678
+ >>> self.add_output_files(output_dir=f"/absolute/dir/path", outputs=...)
679
+
680
+ :param output_dir: Search path of outputs.
502
681
  :type output_dir: str
503
- :param save_path: Save path.
682
+ :param save_path: New save path of outputs. By default, it is ``f"{WRFRUNConfig.WRFRUN_OUTPUT_PATH}/{self.name}"``.
504
683
  :type save_path: str
505
684
  :param startswith: Prefix string or prefix list of output files.
506
685
  :type startswith: str | list
507
- :param endswith: Postfix string or Postfix list of output files.
686
+ :param endswith: Postfix string or postfix list of output files.
508
687
  :type endswith: str | list
509
688
  :param outputs: Files name list. All files in the list will be saved.
510
689
  :type outputs: str | list
511
- :param no_file_error: If True, an error will be raised with the ``error_message``. Defaults to True.
690
+ :param no_file_error: If True, an OutputFileError will be raised if no output file can be found. Defaults to True.
512
691
  :type no_file_error: bool
513
- :return:
514
- :rtype:
515
692
  """
516
693
  if WRFRUNConfig.FAKE_SIMULATION_MODE:
517
694
  return
@@ -567,10 +744,7 @@ class ExecutableBase:
567
744
 
568
745
  def before_exec(self):
569
746
  """
570
- Prepare input files.
571
-
572
- :return:
573
- :rtype:
747
+ Prepare input files before executing the external program.
574
748
  """
575
749
  if WRFRUNConfig.FAKE_SIMULATION_MODE:
576
750
  logger.info(f"We are in fake simulation mode, skip preparing input files for '{self.name}'")
@@ -603,10 +777,7 @@ class ExecutableBase:
603
777
 
604
778
  def after_exec(self):
605
779
  """
606
- Save outputs and logs.
607
-
608
- :return:
609
- :rtype:
780
+ Save outputs and logs after executing the external program.
610
781
  """
611
782
  if WRFRUNConfig.FAKE_SIMULATION_MODE:
612
783
  logger.info(f"We are in fake simulation mode, skip saving outputs for '{self.name}'")
@@ -639,9 +810,6 @@ class ExecutableBase:
639
810
  def exec(self):
640
811
  """
641
812
  Execute the given command.
642
-
643
- :return:
644
- :rtype:
645
813
  """
646
814
  work_path = WRFRUNConfig.parse_resource_uri(self.work_path)
647
815
 
@@ -664,7 +832,7 @@ class ExecutableBase:
664
832
 
665
833
  def __call__(self):
666
834
  """
667
- Execute the given command.
835
+ Execute the given command by calling ``before_exec``, ``exec`` and ``after_exec``.
668
836
 
669
837
  :return:
670
838
  :rtype: