wrfrun 0.1.9__tar.gz → 0.2.0__tar.gz
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-0.1.9 → wrfrun-0.2.0}/PKG-INFO +1 -1
- {wrfrun-0.1.9 → wrfrun-0.2.0}/meson.build +1 -1
- {wrfrun-0.1.9 → wrfrun-0.2.0}/pyproject.toml +1 -1
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/cli.py +7 -4
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/core/base.py +44 -14
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/core/config.py +184 -28
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/core/error.py +8 -1
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/core/server.py +27 -10
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/data.py +14 -16
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/littler/core.py +3 -3
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/utils.py +3 -2
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/plot.py +7 -2
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/utils.py +4 -3
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/wrf/core.py +49 -37
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/wrf/geodata.py +2 -1
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/wrf/log.py +3 -3
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/wrf/namelist.py +3 -4
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/wrf/utils.py +3 -2
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/wrf/vtable.py +9 -6
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/__init__.py +15 -3
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/config/config.template.toml +7 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/generate_init.py +18 -3
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/run.py +21 -5
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/scheduler/lsf.py +3 -1
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/scheduler/pbs.py +3 -1
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/scheduler/script.py +4 -2
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/scheduler/slurm.py +3 -1
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/scheduler/utils.py +2 -2
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/workspace/core.py +4 -2
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/workspace/wrf.py +50 -6
- {wrfrun-0.1.9 → wrfrun-0.2.0}/.gitattributes +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/.gitignore +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/.python-version +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/LICENSE +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/README.md +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/build.sh +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/uv.lock +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/__init__.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/core/__init__.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/core/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/core/replay.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/__init__.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/goos_sst/README.md +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/goos_sst/__init__.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/goos_sst/core.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/goos_sst/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/goos_sst/res/Vtable.ERA_GOOS_SST +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/goos_sst/res/__init__.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/goos_sst/res/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/goos_sst/utils.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/littler/README.md +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/littler/__init__.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/littler/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/extension/micaps/README.md +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/__init__.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/base.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/wrf/__init__.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/wrf/exec_wrap.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/wrf/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/wrf/plot.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/model/wrf/scheme.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/plot/__init__.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/plot/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/plot/wps.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/README.md +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/config/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/config/name_map.json +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/config/wrf.template.toml +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/extension/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/extension/name_map.json +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/extension/plotgrids.ncl +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/name_map.json +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/namelist/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/namelist/name_map.json +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/namelist/namelist.input.da_wrfvar.template +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/namelist/namelist.input.dfi.template +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/namelist/namelist.input.real.template +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/namelist/namelist.input.wrf.template +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/namelist/namelist.wps.template +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/namelist/parame.in.template +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/run.template.sh +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/scheduler/lsf.template +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/scheduler/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/scheduler/name_map.json +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/scheduler/pbs.template +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/res/scheduler/slurm.template +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/scheduler/__init__.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/scheduler/env.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/scheduler/meson.build +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/utils.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/workspace/__init__.py +0 -0
- {wrfrun-0.1.9 → wrfrun-0.2.0}/wrfrun/workspace/meson.build +0 -0
|
@@ -7,7 +7,7 @@ from shutil import copyfile
|
|
|
7
7
|
import tomli
|
|
8
8
|
import tomli_w
|
|
9
9
|
|
|
10
|
-
from .core import
|
|
10
|
+
from .core import WRFRunConfig
|
|
11
11
|
from .res import CONFIG_MAIN_TOML_TEMPLATE, CONFIG_WRF_TOML_TEMPLATE
|
|
12
12
|
from .utils import logger
|
|
13
13
|
|
|
@@ -16,6 +16,9 @@ MODEL_MAP = {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
wrfrun_config = WRFRunConfig("./.wrfrun")
|
|
20
|
+
|
|
21
|
+
|
|
19
22
|
def _entry_init(args: argparse.Namespace):
|
|
20
23
|
"""
|
|
21
24
|
Initialize a wrfrun project.
|
|
@@ -35,11 +38,11 @@ def _entry_init(args: argparse.Namespace):
|
|
|
35
38
|
makedirs(f"{project_name}/configs")
|
|
36
39
|
makedirs(f"{project_name}/data")
|
|
37
40
|
|
|
38
|
-
copyfile(
|
|
41
|
+
copyfile(wrfrun_config.parse_resource_uri(CONFIG_MAIN_TOML_TEMPLATE), f"{project_name}/config.toml")
|
|
39
42
|
|
|
40
43
|
if models is not None:
|
|
41
44
|
for _model in models:
|
|
42
|
-
src_path =
|
|
45
|
+
src_path = wrfrun_config.parse_resource_uri(MODEL_MAP[_model])
|
|
43
46
|
copyfile(src_path, f"{project_name}/configs/{_model}.toml")
|
|
44
47
|
|
|
45
48
|
logger.info(f"Created project {project_name}.")
|
|
@@ -95,7 +98,7 @@ def _entry_model(args: argparse.Namespace):
|
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
for _new_model in new_models:
|
|
98
|
-
copyfile(
|
|
101
|
+
copyfile(wrfrun_config.parse_resource_uri(model_config_map[_new_model]), f"{config_dir_path}/{_new_model}.toml")
|
|
99
102
|
|
|
100
103
|
with open(config_path, "wb") as f:
|
|
101
104
|
tomli_w.dump(main_config, f)
|
|
@@ -13,7 +13,8 @@ Defines what :class:`ExecutableBase <Executable>` is, how it works and how ``wrf
|
|
|
13
13
|
FileConfigDict
|
|
14
14
|
ExecutableClassConfig
|
|
15
15
|
ExecutableConfig
|
|
16
|
-
|
|
16
|
+
ExecutableConfigRecord
|
|
17
|
+
create_recorder
|
|
17
18
|
ExecutableBase
|
|
18
19
|
|
|
19
20
|
Executable
|
|
@@ -54,7 +55,7 @@ from typing import Optional, TypedDict, Union
|
|
|
54
55
|
import numpy as np
|
|
55
56
|
|
|
56
57
|
from .config import WRFRUNConfig
|
|
57
|
-
from .error import CommandError, ConfigError, OutputFileError
|
|
58
|
+
from .error import CommandError, ConfigError, OutputFileError, RecordError
|
|
58
59
|
from ..utils import check_path, logger
|
|
59
60
|
|
|
60
61
|
|
|
@@ -276,14 +277,14 @@ class ExecutableConfig(TypedDict):
|
|
|
276
277
|
custom_config: Optional[dict]
|
|
277
278
|
|
|
278
279
|
|
|
279
|
-
class
|
|
280
|
+
class ExecutableConfigRecord:
|
|
280
281
|
"""
|
|
281
282
|
A class to helps store configs of various executables and exports them to a file.
|
|
282
283
|
"""
|
|
283
284
|
_instance = None
|
|
284
285
|
_initialized = False
|
|
285
286
|
|
|
286
|
-
def __init__(self, save_path:
|
|
287
|
+
def __init__(self, save_path: str, include_data=False):
|
|
287
288
|
"""
|
|
288
289
|
|
|
289
290
|
:param save_path: Save path of the exported config file.
|
|
@@ -305,7 +306,6 @@ class _ExecutableConfigRecord:
|
|
|
305
306
|
|
|
306
307
|
self.work_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_WORKSPACE_REPLAY)
|
|
307
308
|
self.content_path = f"{self.work_path}/config_and_data"
|
|
308
|
-
check_path(self.content_path)
|
|
309
309
|
|
|
310
310
|
self._recorded_config = []
|
|
311
311
|
self._name_count = {}
|
|
@@ -318,15 +318,20 @@ class _ExecutableConfigRecord:
|
|
|
318
318
|
|
|
319
319
|
return cls._instance
|
|
320
320
|
|
|
321
|
-
|
|
321
|
+
@classmethod
|
|
322
|
+
def reinit(cls, save_path: str, include_data=False):
|
|
322
323
|
"""
|
|
323
324
|
Reinitialize this instance.
|
|
324
325
|
|
|
326
|
+
:param save_path: Save path of the exported config file.
|
|
327
|
+
:type save_path: str
|
|
328
|
+
:param include_data: If includes input data.
|
|
329
|
+
:type include_data: bool
|
|
325
330
|
:return: New instance.
|
|
326
|
-
:rtype:
|
|
331
|
+
:rtype: ExecutableConfigRecord
|
|
327
332
|
"""
|
|
328
|
-
|
|
329
|
-
return
|
|
333
|
+
cls._initialized = False
|
|
334
|
+
return cls(save_path, include_data)
|
|
330
335
|
|
|
331
336
|
def record(self, exported_config: ExecutableConfig):
|
|
332
337
|
"""
|
|
@@ -417,7 +422,25 @@ class _ExecutableConfigRecord:
|
|
|
417
422
|
logger.info(f"Replay config exported to {self.save_path}")
|
|
418
423
|
|
|
419
424
|
|
|
420
|
-
ExecConfigRecorder =
|
|
425
|
+
ExecConfigRecorder: Optional[ExecutableConfigRecord] = None
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def create_recorder(save_path: str, include_data=False) -> ExecutableConfigRecord:
|
|
429
|
+
"""
|
|
430
|
+
Create a recorder to record simulations.
|
|
431
|
+
|
|
432
|
+
:param save_path: Save path of the exported config file.
|
|
433
|
+
:type save_path: str
|
|
434
|
+
:param include_data: If includes input data.
|
|
435
|
+
:type include_data: bool
|
|
436
|
+
:return: Recorder instance.
|
|
437
|
+
:rtype: ExecutableConfigRecord
|
|
438
|
+
"""
|
|
439
|
+
global ExecConfigRecorder
|
|
440
|
+
|
|
441
|
+
ExecConfigRecorder = ExecutableConfigRecord.reinit(save_path, include_data)
|
|
442
|
+
|
|
443
|
+
return ExecConfigRecorder
|
|
421
444
|
|
|
422
445
|
|
|
423
446
|
class ExecutableBase:
|
|
@@ -588,25 +611,27 @@ class ExecutableBase:
|
|
|
588
611
|
|
|
589
612
|
You can give more information with a ``FileConfigDict``.
|
|
590
613
|
|
|
614
|
+
>>> from wrfrun.workspace.wrf import get_wrf_workspace_path
|
|
591
615
|
>>> file_dict: FileConfigDict = {
|
|
592
616
|
... "file_path": "data/custom_file.nc",
|
|
593
|
-
... "save_path":
|
|
617
|
+
... "save_path": get_wrf_workspace_path("wps"),
|
|
594
618
|
... "save_name": "custom_file.nc",
|
|
595
619
|
... "is_data": True,
|
|
596
620
|
... "is_output": False
|
|
597
621
|
... }
|
|
598
622
|
>>> self.add_input_files(file_dict)
|
|
599
623
|
|
|
624
|
+
>>> from wrfrun.workspace.wrf import get_wrf_workspace_path
|
|
600
625
|
>>> file_dict_1: FileConfigDict = {
|
|
601
626
|
... "file_path": "data/custom_file",
|
|
602
|
-
... "save_path": f"{
|
|
627
|
+
... "save_path": f"{get_wrf_workspace_path('wps')}/geogrid",
|
|
603
628
|
... "save_name": "GEOGRID.TBL",
|
|
604
629
|
... "is_data": False,
|
|
605
630
|
... "is_output": False
|
|
606
631
|
... }
|
|
607
632
|
>>> file_dict_2: FileConfigDict = {
|
|
608
633
|
... "file_path": "data/custom_file",
|
|
609
|
-
... "save_path": f"{
|
|
634
|
+
... "save_path": f"{get_wrf_workspace_path('wps')}/outputs",
|
|
610
635
|
... "save_name": "test_file",
|
|
611
636
|
... "is_data": True,
|
|
612
637
|
... "is_output": True
|
|
@@ -845,7 +870,12 @@ class ExecutableBase:
|
|
|
845
870
|
self.after_exec()
|
|
846
871
|
|
|
847
872
|
if not WRFRUNConfig.IS_IN_REPLAY and WRFRUNConfig.IS_RECORDING:
|
|
873
|
+
global ExecConfigRecorder
|
|
874
|
+
if ExecConfigRecorder is None:
|
|
875
|
+
logger.error(f"Trying to record simulation before create the recorder.")
|
|
876
|
+
raise RecordError(f"Trying to record simulation before create the recorder.")
|
|
877
|
+
|
|
848
878
|
ExecConfigRecorder.record(self.export_config())
|
|
849
879
|
|
|
850
880
|
|
|
851
|
-
__all__ = ["ExecutableBase", "FileConfigDict", "InputFileType", "ExecutableConfig", "ExecutableClassConfig", "ExecConfigRecorder"]
|
|
881
|
+
__all__ = ["ExecutableBase", "FileConfigDict", "InputFileType", "ExecutableConfig", "ExecutableClassConfig", "ExecConfigRecorder", "create_recorder"]
|
|
@@ -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,18 +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
34
|
from os.path import abspath, dirname, exists
|
|
26
35
|
from shutil import copyfile
|
|
27
36
|
from sys import platform
|
|
28
|
-
from typing import Optional, Tuple, Union
|
|
37
|
+
from typing import Callable, Optional, Tuple, Union
|
|
29
38
|
|
|
30
39
|
import f90nml
|
|
31
40
|
import tomli
|
|
32
41
|
import tomli_w
|
|
33
42
|
|
|
34
|
-
from .error import ModelNameError, NamelistError, NamelistIDError, ResourceURIError, WRFRunContextError
|
|
43
|
+
from .error import ModelNameError, NamelistError, NamelistIDError, ResourceURIError, WRFRunContextError, ConfigError
|
|
35
44
|
from ..utils import logger
|
|
36
45
|
|
|
37
46
|
|
|
@@ -140,38 +149,42 @@ class _WRFRunConstants:
|
|
|
140
149
|
Define all variables that will be used by other components.
|
|
141
150
|
"""
|
|
142
151
|
|
|
143
|
-
def __init__(self):
|
|
152
|
+
def __init__(self, work_dir: str):
|
|
144
153
|
"""
|
|
145
154
|
Define all variables that will be used by other components.
|
|
146
155
|
|
|
147
156
|
These variables are related to ``wrfrun`` installation environments, configuration files and more.
|
|
148
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
|
|
149
161
|
"""
|
|
150
162
|
# check system
|
|
151
163
|
if platform != "linux":
|
|
152
164
|
logger.debug(f"Not Linux system!")
|
|
153
165
|
|
|
166
|
+
if work_dir != "" or platform != "linux":
|
|
154
167
|
# set temporary dir path
|
|
155
|
-
self._WRFRUN_TEMP_PATH = "
|
|
156
|
-
|
|
168
|
+
self._WRFRUN_TEMP_PATH = abspath(f"{work_dir}/tmp")
|
|
169
|
+
self._WRFRUN_HOME_PATH = abspath(work_dir)
|
|
157
170
|
|
|
158
171
|
else:
|
|
159
|
-
|
|
160
172
|
# the path we may need to store temp files,
|
|
161
173
|
# don't worry, it will be deleted once the system reboots
|
|
162
174
|
self._WRFRUN_TEMP_PATH = "/tmp/wrfrun"
|
|
163
175
|
user_home_path = f"{environ['HOME']}"
|
|
164
176
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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"
|
|
173
187
|
|
|
174
|
-
self._WRFRUN_HOME_PATH = f"{user_home_path}/.config/wrfrun"
|
|
175
188
|
# workspace root path
|
|
176
189
|
self._WRFRUN_WORKSPACE_ROOT = f"{self._WRFRUN_HOME_PATH}/workspace"
|
|
177
190
|
self._WRFRUN_WORKSPACE_MODEL = f"{self._WRFRUN_WORKSPACE_ROOT}/model"
|
|
@@ -534,21 +547,25 @@ class _WRFRunNamelist:
|
|
|
534
547
|
:return: ``True`` if it is registered and loaded, else ``False``.
|
|
535
548
|
:rtype: bool
|
|
536
549
|
"""
|
|
537
|
-
if namelist_id in self._namelist_id_list and self._namelist_dict:
|
|
550
|
+
if namelist_id in self._namelist_id_list and namelist_id in self._namelist_dict:
|
|
538
551
|
return True
|
|
539
552
|
|
|
540
553
|
else:
|
|
541
554
|
return False
|
|
542
555
|
|
|
543
556
|
|
|
557
|
+
_URI_REGISTER_FUNC_LIST: list[Callable[["WRFRunConfig"], None]] = []
|
|
558
|
+
|
|
559
|
+
|
|
544
560
|
class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
|
|
545
561
|
"""
|
|
546
562
|
Class to manage wrfrun config, runtime constants, namelists and resource files.
|
|
547
563
|
"""
|
|
548
564
|
_instance = None
|
|
549
565
|
_initialized = False
|
|
566
|
+
_lock = threading.Lock()
|
|
550
567
|
|
|
551
|
-
def __init__(self):
|
|
568
|
+
def __init__(self, work_dir: str):
|
|
552
569
|
"""
|
|
553
570
|
This class provides various interfaces to access ``wrfrun``'s config, namelist values of NWP models,
|
|
554
571
|
runtime constants and resource files by inheriting from:
|
|
@@ -556,23 +573,32 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
|
|
|
556
573
|
|
|
557
574
|
An instance of this class called ``WRFRUNConfig`` will be created after the user import ``wrfrun``,
|
|
558
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
|
|
559
579
|
"""
|
|
560
580
|
if self._initialized:
|
|
561
581
|
return
|
|
562
582
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
_WRFRunResources.__init__(self)
|
|
583
|
+
with self._lock:
|
|
584
|
+
global _URI_REGISTER_FUNC_LIST
|
|
566
585
|
|
|
567
|
-
|
|
586
|
+
self._initialized = True
|
|
568
587
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
588
|
+
_WRFRunConstants.__init__(self, work_dir)
|
|
589
|
+
_WRFRunNamelist.__init__(self)
|
|
590
|
+
_WRFRunResources.__init__(self)
|
|
591
|
+
|
|
592
|
+
self._config = {}
|
|
593
|
+
|
|
594
|
+
self._config_template_file_path = None
|
|
595
|
+
|
|
596
|
+
self._register_wrfrun_uris()
|
|
572
597
|
|
|
573
|
-
|
|
598
|
+
for _fun in _URI_REGISTER_FUNC_LIST:
|
|
599
|
+
_fun(self)
|
|
574
600
|
|
|
575
|
-
|
|
601
|
+
_URI_REGISTER_FUNC_LIST = []
|
|
576
602
|
|
|
577
603
|
def __new__(cls, *args, **kwargs):
|
|
578
604
|
if cls._instance is None:
|
|
@@ -580,6 +606,39 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
|
|
|
580
606
|
|
|
581
607
|
return cls._instance
|
|
582
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
|
+
|
|
583
642
|
def set_config_template_path(self, file_path: str):
|
|
584
643
|
"""
|
|
585
644
|
Set file path of the config template file.
|
|
@@ -652,6 +711,11 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
|
|
|
652
711
|
output_path = abspath(self["output_path"])
|
|
653
712
|
self.register_resource_uri(self.WRFRUN_OUTPUT_PATH, output_path)
|
|
654
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
|
+
|
|
655
719
|
def save_wrfrun_config(self, save_path: str):
|
|
656
720
|
"""
|
|
657
721
|
Save ``wrfrun``'s config to a file.
|
|
@@ -685,6 +749,18 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
|
|
|
685
749
|
|
|
686
750
|
return deepcopy(self._config[item])
|
|
687
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
|
+
|
|
688
764
|
def get_input_data_path(self) -> str:
|
|
689
765
|
"""
|
|
690
766
|
Get the path of directory in which stores the input data.
|
|
@@ -711,6 +787,23 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
|
|
|
711
787
|
|
|
712
788
|
return deepcopy(self["model"][model_name])
|
|
713
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
|
+
|
|
714
807
|
def get_log_path(self) -> str:
|
|
715
808
|
"""
|
|
716
809
|
Get the directory path to save logs.
|
|
@@ -762,6 +855,69 @@ class WRFRunConfig(_WRFRunConstants, _WRFRunNamelist, _WRFRunResources):
|
|
|
762
855
|
super().write_namelist(save_path, namelist_id, overwrite)
|
|
763
856
|
|
|
764
857
|
|
|
765
|
-
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
|
+
|
|
766
922
|
|
|
767
|
-
__all__ = ["WRFRUNConfig"]
|
|
923
|
+
__all__ = ["WRFRunConfig", "WRFRUNConfig", "init_wrfrun_config", "get_wrfrun_config", "set_register_func"]
|
|
@@ -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"]
|
|
@@ -8,6 +8,7 @@ In order to report the progress to user, ``wrfrun`` provides :class:`WRFRunServe
|
|
|
8
8
|
.. autosummary::
|
|
9
9
|
:toctree: generated/
|
|
10
10
|
|
|
11
|
+
set_log_parse_func
|
|
11
12
|
WRFRunServer
|
|
12
13
|
WRFRunServerHandler
|
|
13
14
|
stop_server
|
|
@@ -15,6 +16,7 @@ In order to report the progress to user, ``wrfrun`` provides :class:`WRFRunServe
|
|
|
15
16
|
|
|
16
17
|
import socket
|
|
17
18
|
import socketserver
|
|
19
|
+
import threading
|
|
18
20
|
from collections.abc import Callable
|
|
19
21
|
from datetime import datetime
|
|
20
22
|
from json import dumps
|
|
@@ -28,6 +30,24 @@ WRFRUN_SERVER_INSTANCE = None
|
|
|
28
30
|
WRFRUN_SERVER_THREAD = None
|
|
29
31
|
|
|
30
32
|
|
|
33
|
+
SET_LOG_PARSER_LOCK = threading.Lock()
|
|
34
|
+
LOG_PARSER: Callable[[datetime], int] | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def set_log_parse_func(func: Callable[[datetime], int]):
|
|
38
|
+
"""
|
|
39
|
+
Set log parse function used by socket server.
|
|
40
|
+
|
|
41
|
+
:param func: Function used to get simulated seconds from model's log file.
|
|
42
|
+
If the function can't parse the simulated seconds, it should return ``-1``.
|
|
43
|
+
:type func: Callable[[datetime], int]
|
|
44
|
+
"""
|
|
45
|
+
global SET_LOG_PARSER_LOCK, LOG_PARSER
|
|
46
|
+
|
|
47
|
+
with SET_LOG_PARSER_LOCK:
|
|
48
|
+
LOG_PARSER = func
|
|
49
|
+
|
|
50
|
+
|
|
31
51
|
class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
|
32
52
|
"""
|
|
33
53
|
A socket server to report time usage.
|
|
@@ -155,7 +175,7 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
155
175
|
``status`` represents work status,
|
|
156
176
|
``progress`` represents simulation progress of the status in percentage.
|
|
157
177
|
"""
|
|
158
|
-
def __init__(self, request, client_address, server: WRFRunServer
|
|
178
|
+
def __init__(self, request, client_address, server: WRFRunServer) -> None:
|
|
159
179
|
"""
|
|
160
180
|
:class:`WRFRunServer` handler.
|
|
161
181
|
|
|
@@ -165,15 +185,11 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
165
185
|
:type client_address:
|
|
166
186
|
:param server: :class:`WRFRunServer` instance.
|
|
167
187
|
:type server: WRFRunServer
|
|
168
|
-
:param log_parse_func: Function used to get simulated seconds from model's log file.
|
|
169
|
-
If the function can't parse the simulated seconds, it should return ``-1``.
|
|
170
|
-
:type log_parse_func: Callable[[datetime], int]
|
|
171
188
|
"""
|
|
172
189
|
super().__init__(request, client_address, server)
|
|
173
190
|
|
|
174
191
|
# get server
|
|
175
192
|
self.server: WRFRunServer = server
|
|
176
|
-
self.log_parse_func = log_parse_func
|
|
177
193
|
|
|
178
194
|
def calculate_time_usage(self) -> int:
|
|
179
195
|
"""
|
|
@@ -202,10 +218,11 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
202
218
|
"""
|
|
203
219
|
start_date, simulate_seconds = self.server.get_model_simulate_settings()
|
|
204
220
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
221
|
+
with SET_LOG_PARSER_LOCK:
|
|
222
|
+
if LOG_PARSER is None:
|
|
223
|
+
simulated_seconds = -1
|
|
224
|
+
else:
|
|
225
|
+
simulated_seconds = LOG_PARSER(start_date)
|
|
209
226
|
|
|
210
227
|
if simulated_seconds > 0:
|
|
211
228
|
progress = simulated_seconds * 100 // simulate_seconds
|
|
@@ -269,4 +286,4 @@ def stop_server(socket_ip: str, socket_port: int):
|
|
|
269
286
|
logger.warning("Fail to stop WRFRunServer, maybe it doesn't start at all.")
|
|
270
287
|
|
|
271
288
|
|
|
272
|
-
__all__ = ["WRFRunServer", "WRFRunServerHandler", "stop_server"]
|
|
289
|
+
__all__ = ["WRFRunServer", "WRFRunServerHandler", "stop_server", "set_log_parse_func"]
|