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/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 an 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,35 @@ 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,
158
+ the path it will be copied or moved to, its new name, etc.
159
+ This dict contains following keys:
160
+
161
+ .. py:attribute:: file_path
162
+ :type: str
163
+
164
+ A real file path or a valid URI which can be converted to a file path.
165
+
166
+ .. py:attribute:: save_path
167
+ :type: str
168
+
169
+ Save path of the file.
170
+
171
+ .. py:attribute:: save_name
172
+ :type: str
173
+
174
+ Save name of the file.
175
+
176
+ .. py:attribute:: is_data
177
+ :type: bool
178
+
179
+ If the file is data. If not, ``wrfrun`` will treat it as a config file,
180
+ and always save it to ``.replay`` file when recording the simulation.
181
+
182
+ .. py:attribute:: is_output
183
+ :type: bool
184
+
185
+ If the file is model's output. Output file will never be saved to ``.replay`` file.
102
186
  """
103
187
  file_path: str
104
188
  save_path: str
@@ -109,7 +193,17 @@ class FileConfigDict(TypedDict):
109
193
 
110
194
  class ExecutableClassConfig(TypedDict):
111
195
  """
112
- Executable class initialization config template.
196
+ This dict is used to store arguments of ``Executable``'s ``__init__`` function.
197
+
198
+ .. py:attribute:: class_args
199
+ :type: tuple
200
+
201
+ Positional arguments of the class.
202
+
203
+ .. py:attribute:: class_kwargs
204
+ :type: dict
205
+
206
+ Keyword arguments of the class.
113
207
  """
114
208
  # only list essential config
115
209
  class_args: tuple
@@ -118,7 +212,57 @@ class ExecutableClassConfig(TypedDict):
118
212
 
119
213
  class ExecutableConfig(TypedDict):
120
214
  """
121
- Executable config template.
215
+ This dict is used to store all configs of a :class:`ExecutableBase`.
216
+
217
+ .. py:attribute:: name
218
+ :type: str
219
+
220
+ Name of the executable. Each type of executable has a unique name.
221
+
222
+ .. py:attribute:: cmd
223
+ :type: str | list[str]
224
+
225
+ Command of the executable.
226
+
227
+ .. py:attribute:: work_path
228
+ :type: str | None
229
+
230
+ Work path of the executable.
231
+
232
+ .. py:attribute:: mpi_use
233
+ :type: bool
234
+
235
+ If the executable will use MPI.
236
+
237
+ .. py:attribute:: mpi_cmd
238
+ :type: str | None
239
+
240
+ Command name of the MPI.
241
+
242
+ .. py:attribute:: mpi_core_num
243
+ :type: int | None
244
+
245
+ Number of the CPU core to use with MPI.
246
+
247
+ .. py:attribute:: class_config
248
+ :type: ExecutableClassConfig | None
249
+
250
+ A dict stores arguments of ``Executable``'s ``__init__`` function.
251
+
252
+ .. py:attribute:: input_file_config
253
+ :type: list[FileConfigDict] | None
254
+
255
+ A list stores information about input files of the executable.
256
+
257
+ .. py:attribute:: output_file_config
258
+ :type: list[FileConfigDict] | None
259
+
260
+ A list stores information about output files of the executable.
261
+
262
+ .. py:attribute:: custom_config
263
+ :type: dict | None
264
+
265
+ A dict that can be used by subclass to store other configs.
122
266
  """
123
267
  name: str
124
268
  cmd: Union[str, list[str]]
@@ -134,17 +278,16 @@ class ExecutableConfig(TypedDict):
134
278
 
135
279
  class _ExecutableConfigRecord:
136
280
  """
137
- Record executable configs and export them.
281
+ A class to helps store configs of various executables and exports them to a file.
138
282
  """
139
283
  _instance = None
140
284
  _initialized = False
141
285
 
142
286
  def __init__(self, save_path: Optional[str] = None, include_data=False):
143
287
  """
144
- Record executable configs and export them.
145
288
 
146
289
  :param save_path: Save path of the exported config file.
147
- :type save_path: str
290
+ :type save_path: str | None
148
291
  :param include_data: If includes input data.
149
292
  :type include_data: bool
150
293
  """
@@ -160,7 +303,7 @@ class _ExecutableConfigRecord:
160
303
  self.save_path = save_path
161
304
  self.include_data = include_data
162
305
 
163
- self.work_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_REPLAY_WORK_PATH)
306
+ self.work_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_WORKSPACE_REPLAY)
164
307
  self.content_path = f"{self.work_path}/config_and_data"
165
308
  check_path(self.content_path)
166
309
 
@@ -179,8 +322,8 @@ class _ExecutableConfigRecord:
179
322
  """
180
323
  Reinitialize this instance.
181
324
 
182
- :return:
183
- :rtype:
325
+ :return: New instance.
326
+ :rtype: _ExecutableConfigRecord
184
327
  """
185
328
  self._initialized = False
186
329
  return _ExecutableConfigRecord(save_path, include_data)
@@ -191,8 +334,6 @@ class _ExecutableConfigRecord:
191
334
 
192
335
  :param exported_config: Executable config.
193
336
  :type exported_config: ExecutableConfig
194
- :return:
195
- :rtype:
196
337
  """
197
338
  if not self.include_data:
198
339
  self._recorded_config.append(exported_config)
@@ -210,7 +351,7 @@ class _ExecutableConfigRecord:
210
351
  self._name_count[name] = 1
211
352
  index = 1
212
353
 
213
- data_save_uri = f"{WRFRUNConfig.WRFRUN_REPLAY_WORK_PATH}/{name}/{index}"
354
+ data_save_uri = f"{WRFRUNConfig.WRFRUN_WORKSPACE_REPLAY}/{name}/{index}"
214
355
  data_save_path = f"{self.content_path}/{name}/{index}"
215
356
  makedirs(data_save_path)
216
357
 
@@ -236,19 +377,13 @@ class _ExecutableConfigRecord:
236
377
 
237
378
  def clear_records(self):
238
379
  """
239
- Clean old configs.
240
-
241
- :return:
242
- :rtype:
380
+ Clean recorded configs.
243
381
  """
244
382
  self._recorded_config = []
245
383
 
246
384
  def export_replay_file(self):
247
385
  """
248
- Save replay file to the specific save path.
249
-
250
- :return:
251
- :rtype:
386
+ Save replay file to the save path.
252
387
  """
253
388
  if len(self._recorded_config) == 0:
254
389
  logger.warning("No replay config has been recorded.")
@@ -288,12 +423,37 @@ ExecConfigRecorder = _ExecutableConfigRecord()
288
423
  class ExecutableBase:
289
424
  """
290
425
  Base class for all executables.
426
+
427
+ .. py:attribute:: class_config
428
+ :type: ExecutableClassConfig
429
+ :value: {"class_args": (), "class_kwargs": {}}
430
+
431
+ A dict stores arguments of ``Executable``'s ``__init__`` function.
432
+
433
+ .. py:attribute:: custom_config
434
+ :type: dict
435
+ :value: {}
436
+
437
+ A dict that can be used by subclass to store custom configs.
438
+
439
+ .. py:attribute:: input_file_config
440
+ :type: list[FileConfigDict]
441
+ :value: []
442
+
443
+ A list stores information about input files of the executable.
444
+
445
+ .. py:attribute:: output_file_config
446
+ :type: list[FileConfigDict]
447
+ :value: []
448
+
449
+ A list stores information about output files of the executable.
450
+
291
451
  """
292
452
  _instance = None
293
453
 
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):
454
+ def __init__(self, name: str, cmd: Union[str, list[str]], work_path: str,
455
+ mpi_use=False, mpi_cmd: Optional[str] = None, mpi_core_num: Optional[int] = None):
295
456
  """
296
- Base class for all executables.
297
457
 
298
458
  :param name: Unique name to identify different executables.
299
459
  :type name: str
@@ -338,19 +498,23 @@ class ExecutableBase:
338
498
 
339
499
  def generate_custom_config(self):
340
500
  """
341
- Generate custom configs.
501
+ Generate custom configs. This method should be overwritten in the child class,
502
+ and **MUST STORE THE CUSTOM CONFIG IN THE ATTRIBUTE** :attr:`ExecutableBase.custom_config`,
503
+ or it will do nothing except print a debug log.
342
504
 
343
- :return:
344
- :rtype:
505
+ You can export various configs in this method, like the complete namelist values of a NWP model binary,
506
+ or the path of Vtable file this executable will use.
507
+
508
+ If you overwrite this method to generate custom configs,
509
+ you also have to overwrite :meth:`ExecutableBase.load_custom_config` to load your custom configs.
345
510
  """
346
511
  logger.debug(f"Method 'generate_custom_config' not implemented in '{self.name}'")
347
512
 
348
513
  def load_custom_config(self):
349
514
  """
350
515
  Load custom configs.
351
-
352
- :return:
353
- :rtype:
516
+ This method should be overwritten in the child class to process the custom config stored in :attr:`ExecutableBase.custom_config`,
517
+ or it will do nothing except print a debug log.
354
518
  """
355
519
  logger.debug(f"Method 'load_custom_config' not implemented in '{self.name}'")
356
520
 
@@ -370,20 +534,18 @@ class ExecutableBase:
370
534
  "mpi_use": self.mpi_use,
371
535
  "mpi_cmd": self.mpi_cmd,
372
536
  "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
537
+ "class_config": deepcopy(self.class_config),
538
+ "custom_config": deepcopy(self.custom_config),
539
+ "input_file_config": deepcopy(self.input_file_config),
540
+ "output_file_config": deepcopy(self.output_file_config)
377
541
  }
378
542
 
379
543
  def load_config(self, config: ExecutableConfig):
380
544
  """
381
- Load config from a dict.
545
+ Load executable config from a dict.
382
546
 
383
547
  :param config: Config dict. It must contain some essential keys. Check ``ExecutableConfig`` for details.
384
548
  :type config: ExecutableConfig
385
- :return:
386
- :rtype:
387
549
  """
388
550
  if "name" not in config:
389
551
  logger.error("A valid config is required. Please check ``ExecutableConfig``.")
@@ -398,10 +560,10 @@ class ExecutableBase:
398
560
  self.mpi_use = config["mpi_use"]
399
561
  self.mpi_cmd = config["mpi_cmd"]
400
562
  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"]
563
+ self.class_config = deepcopy(config["class_config"])
564
+ self.custom_config = deepcopy(config["custom_config"])
565
+ self.input_file_config = deepcopy(config["input_file_config"])
566
+ self.output_file_config = deepcopy(config["output_file_config"])
405
567
 
406
568
  self.load_custom_config()
407
569
 
@@ -409,23 +571,22 @@ class ExecutableBase:
409
571
  """
410
572
  This method will be called when replay the simulation.
411
573
  This method should take care every job that will be done when replaying the simulation.
412
-
413
- :return:
414
- :rtype:
574
+ By default, this method will call ``__call__`` method of the instance.
415
575
  """
416
576
  logger.debug(f"Method 'replay' not implemented in '{self.name}', fall back to default action.")
417
577
  self()
418
578
 
419
- def add_input_files(self, input_files: Union[str, list[str], FileConfigDict, list[FileConfigDict]], is_data=True, is_output=True):
579
+ def add_input_files(self, input_files: Union[str, list[str], FileConfigDict, list[FileConfigDict]],
580
+ is_data=True, is_output=True):
420
581
  """
421
- Add input files the extension will use.
582
+ Add input files the executable will use.
422
583
 
423
584
  You can give a single file path or a list contains files' path.
424
585
 
425
586
  >>> self.add_input_files("data/custom_file")
426
587
  >>> self.add_input_files(["data/custom_file_1", "data/custom_file_2"])
427
588
 
428
- You can give more information with a ``FileConfigDict``, like the path and the name to store, and if it is data.
589
+ You can give more information with a ``FileConfigDict``.
429
590
 
430
591
  >>> file_dict: FileConfigDict = {
431
592
  ... "file_path": "data/custom_file.nc",
@@ -452,12 +613,14 @@ class ExecutableBase:
452
613
  ... }
453
614
  >>> self.add_input_files([file_dict_1, file_dict_2])
454
615
 
455
- :param input_files: Custom files' path.
616
+ Please check :class:`FileConfigDict` for more details.
617
+
618
+ :param input_files: Custom files.
456
619
  :type input_files: str | list | dict
457
- :param is_data: If its data. This parameter will be overwritten by the value in ``input_files``.
620
+ :param is_data: If it is a data file. This parameter will be overwritten by the value in ``input_files``.
458
621
  :type is_data: bool
459
- :return:
460
- :rtype:
622
+ :param is_output: If it is an output from another executable. This parameter will be overwritten by the value in ``input_files``.
623
+ :type is_output: bool
461
624
  """
462
625
  if isinstance(input_files, str):
463
626
  self.input_file_config.append(
@@ -496,22 +659,39 @@ class ExecutableBase:
496
659
  endswith: Union[None, str, tuple[str, ...]] = None, outputs: Union[None, str, list[str]] = None, no_file_error=True
497
660
  ):
498
661
  """
499
- Add save file rules.
662
+ Find and save model's outputs to the output save path.
663
+ An ``OutputFileError`` exception will be raised if no file can be found and ``no_file_error==True``.
664
+
665
+ You can give the specific path of a file or multiple files.
666
+
667
+ >>> self.add_output_files(outputs="wrfout.d01")
668
+ >>> self.add_output_files(outputs=["wrfout.d01", "wrfout.d02"])
669
+
670
+ If you have too many outputs, but they have the same prefix or postfix, you can use ``startswith`` or ``endswith``.
671
+
672
+ >>> self.add_output_files(startswith="rsl.out.")
673
+ >>> self.add_output_files(endswith="log")
674
+ >>> self.add_output_files(startswith=("rsl", "wrfout"), endswith="log")
675
+
676
+ ``startswith``, ``endswith`` and ``outputs`` can be used together.
500
677
 
501
- :param output_dir: Output dir paths.
678
+ ``output_dir`` specify the search path of outputs, by default it is the work path of the executable.
679
+ You can change its value if the output path of the executable isn't its work path.
680
+
681
+ >>> self.add_output_files(output_dir=f"/absolute/dir/path", outputs=...)
682
+
683
+ :param output_dir: Search path of outputs.
502
684
  :type output_dir: str
503
- :param save_path: Save path.
685
+ :param save_path: New save path of outputs. By default, it is ``f"{WRFRUNConfig.WRFRUN_OUTPUT_PATH}/{self.name}"``.
504
686
  :type save_path: str
505
687
  :param startswith: Prefix string or prefix list of output files.
506
688
  :type startswith: str | list
507
- :param endswith: Postfix string or Postfix list of output files.
689
+ :param endswith: Postfix string or postfix list of output files.
508
690
  :type endswith: str | list
509
691
  :param outputs: Files name list. All files in the list will be saved.
510
692
  :type outputs: str | list
511
- :param no_file_error: If True, an error will be raised with the ``error_message``. Defaults to True.
693
+ :param no_file_error: If True, an OutputFileError will be raised if no output file can be found. Defaults to True.
512
694
  :type no_file_error: bool
513
- :return:
514
- :rtype:
515
695
  """
516
696
  if WRFRUNConfig.FAKE_SIMULATION_MODE:
517
697
  return
@@ -567,10 +747,7 @@ class ExecutableBase:
567
747
 
568
748
  def before_exec(self):
569
749
  """
570
- Prepare input files.
571
-
572
- :return:
573
- :rtype:
750
+ Prepare input files before executing the external program.
574
751
  """
575
752
  if WRFRUNConfig.FAKE_SIMULATION_MODE:
576
753
  logger.info(f"We are in fake simulation mode, skip preparing input files for '{self.name}'")
@@ -603,10 +780,7 @@ class ExecutableBase:
603
780
 
604
781
  def after_exec(self):
605
782
  """
606
- Save outputs and logs.
607
-
608
- :return:
609
- :rtype:
783
+ Save outputs and logs after executing the external program.
610
784
  """
611
785
  if WRFRUNConfig.FAKE_SIMULATION_MODE:
612
786
  logger.info(f"We are in fake simulation mode, skip saving outputs for '{self.name}'")
@@ -639,9 +813,6 @@ class ExecutableBase:
639
813
  def exec(self):
640
814
  """
641
815
  Execute the given command.
642
-
643
- :return:
644
- :rtype:
645
816
  """
646
817
  work_path = WRFRUNConfig.parse_resource_uri(self.work_path)
647
818
 
@@ -664,7 +835,7 @@ class ExecutableBase:
664
835
 
665
836
  def __call__(self):
666
837
  """
667
- Execute the given command.
838
+ Execute the given command by calling ``before_exec``, ``exec`` and ``after_exec``.
668
839
 
669
840
  :return:
670
841
  :rtype: