wrfrun 0.2.0__py3-none-any.whl → 0.3.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.
- wrfrun/__init__.py +8 -3
- wrfrun/cli.py +69 -29
- wrfrun/core/__init__.py +27 -10
- wrfrun/core/_config.py +308 -0
- wrfrun/core/_constant.py +236 -0
- wrfrun/core/_exec_db.py +105 -0
- wrfrun/core/_namelist.py +287 -0
- wrfrun/core/_record.py +178 -0
- wrfrun/core/_resource.py +172 -0
- wrfrun/core/base.py +132 -406
- wrfrun/core/core.py +196 -0
- wrfrun/core/error.py +28 -2
- wrfrun/core/replay.py +10 -96
- wrfrun/core/server.py +52 -27
- wrfrun/core/type.py +171 -0
- wrfrun/data.py +304 -139
- wrfrun/extension/goos_sst/__init__.py +2 -2
- wrfrun/extension/goos_sst/core.py +9 -14
- wrfrun/extension/goos_sst/res/__init__.py +0 -1
- wrfrun/extension/goos_sst/utils.py +50 -44
- wrfrun/extension/littler/core.py +105 -88
- wrfrun/extension/utils.py +4 -3
- wrfrun/log.py +117 -0
- wrfrun/model/__init__.py +11 -7
- wrfrun/model/constants.py +52 -0
- wrfrun/model/palm/__init__.py +30 -0
- wrfrun/model/palm/core.py +145 -0
- wrfrun/model/palm/namelist.py +33 -0
- wrfrun/model/plot.py +99 -119
- wrfrun/model/type.py +116 -0
- wrfrun/model/utils.py +9 -20
- wrfrun/model/wrf/__init__.py +4 -9
- wrfrun/model/wrf/core.py +246 -161
- wrfrun/model/wrf/exec_wrap.py +13 -12
- wrfrun/model/wrf/geodata.py +116 -100
- wrfrun/model/wrf/log.py +103 -0
- wrfrun/model/wrf/namelist.py +90 -73
- wrfrun/model/wrf/plot.py +102 -0
- wrfrun/model/wrf/scheme.py +108 -52
- wrfrun/model/wrf/utils.py +39 -25
- wrfrun/model/wrf/vtable.py +35 -3
- wrfrun/plot/__init__.py +20 -0
- wrfrun/plot/wps.py +90 -73
- wrfrun/res/__init__.py +103 -5
- wrfrun/res/config/config.template.toml +8 -0
- wrfrun/res/config/palm.template.toml +23 -0
- wrfrun/run.py +105 -77
- wrfrun/scheduler/__init__.py +1 -0
- wrfrun/scheduler/lsf.py +3 -2
- wrfrun/scheduler/pbs.py +3 -2
- wrfrun/scheduler/script.py +17 -5
- wrfrun/scheduler/slurm.py +3 -2
- wrfrun/scheduler/utils.py +14 -2
- wrfrun/utils.py +88 -199
- wrfrun/workspace/__init__.py +8 -5
- wrfrun/workspace/core.py +20 -12
- wrfrun/workspace/palm.py +137 -0
- wrfrun/workspace/wrf.py +16 -15
- wrfrun-0.3.0.dist-info/METADATA +240 -0
- wrfrun-0.3.0.dist-info/RECORD +78 -0
- wrfrun/core/config.py +0 -923
- wrfrun/model/base.py +0 -14
- wrfrun-0.2.0.dist-info/METADATA +0 -68
- wrfrun-0.2.0.dist-info/RECORD +0 -62
- {wrfrun-0.2.0.dist-info → wrfrun-0.3.0.dist-info}/WHEEL +0 -0
- {wrfrun-0.2.0.dist-info → wrfrun-0.3.0.dist-info}/entry_points.txt +0 -0
wrfrun/core/core.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wrfrun.core.core
|
|
3
|
+
################
|
|
4
|
+
|
|
5
|
+
.. autosummary::
|
|
6
|
+
:toctree: generated/
|
|
7
|
+
|
|
8
|
+
WRFRUNProxy
|
|
9
|
+
|
|
10
|
+
Global variable "WRFRUN"
|
|
11
|
+
************************
|
|
12
|
+
|
|
13
|
+
``WRFRUN`` is an instance of :class:`WRFRUNProxy`.
|
|
14
|
+
It holds the instance of :class:`WRFRunConfig <wrfrun.core._config.WRFRunConfig>`,
|
|
15
|
+
:class:`ExecutableDB <wrfrun.core._exec_db.ExecutableDB>`,
|
|
16
|
+
and :class:`ExecutableRecorder <wrfrun.core._record.ExecutableRecorder>`.
|
|
17
|
+
Through this global variable, other submodules of wrfrun and users can access attributes and methods of these classes.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Callable, Literal
|
|
21
|
+
|
|
22
|
+
from ..log import logger
|
|
23
|
+
from ._config import WRFRunConfig
|
|
24
|
+
from ._exec_db import ExecutableDB
|
|
25
|
+
from ._record import ExecutableRecorder
|
|
26
|
+
from .error import ConfigError
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class WRFRUNProxy:
|
|
30
|
+
"""
|
|
31
|
+
Proxy class to access :class:`WRFRunConfig <wrfrun.core._config.WRFRunConfig>`,
|
|
32
|
+
:class:`ExecutableDB <wrfrun.core._exec_db.ExecutableDB>`,
|
|
33
|
+
and :class:`ExecutableRecorder <wrfrun.core._record.ExecutableRecorder>`.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
"""
|
|
38
|
+
Proxy class to access :class:`WRFRunConfig <wrfrun.core._config.WRFRunConfig>`,
|
|
39
|
+
:class:`ExecutableDB <wrfrun.core._exec_db.ExecutableDB>`,
|
|
40
|
+
and :class:`ExecutableRecorder <wrfrun.core._record.ExecutableRecorder>`.
|
|
41
|
+
"""
|
|
42
|
+
self._config: WRFRunConfig | None = None
|
|
43
|
+
self._config_initialized = False
|
|
44
|
+
self._exec_db: ExecutableDB | None = None
|
|
45
|
+
self._exec_db_initialized = False
|
|
46
|
+
self._recorder: ExecutableRecorder | None = None
|
|
47
|
+
self._recorder_initialized = False
|
|
48
|
+
|
|
49
|
+
self._config_register_funcs: list[Callable[["WRFRunConfig"], None]] = []
|
|
50
|
+
self._exec_db_register_funcs: list[Callable[["ExecutableDB"], None]] = []
|
|
51
|
+
|
|
52
|
+
self.init_exec_db()
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def config(self) -> WRFRunConfig:
|
|
56
|
+
"""
|
|
57
|
+
Access wrfrun config.
|
|
58
|
+
|
|
59
|
+
:return: wrfrun config.
|
|
60
|
+
:rtype: WRFRunConfig
|
|
61
|
+
"""
|
|
62
|
+
if self._config is None:
|
|
63
|
+
logger.error("You haven't initialize `CONFIG` yet.")
|
|
64
|
+
raise ConfigError("You haven't initialize `CONFIG` yet.")
|
|
65
|
+
return self._config
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def ExecDB(self) -> ExecutableDB:
|
|
69
|
+
"""
|
|
70
|
+
Access Executable DB.
|
|
71
|
+
|
|
72
|
+
:return: Executable DB.
|
|
73
|
+
:rtype: ExecutableDB
|
|
74
|
+
"""
|
|
75
|
+
if self._exec_db is None:
|
|
76
|
+
logger.error("You haven't initialize `ExecDB` yet.")
|
|
77
|
+
raise ConfigError("You haven't initialize `ExecDB` yet.")
|
|
78
|
+
return self._exec_db
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def record(self) -> ExecutableRecorder:
|
|
82
|
+
"""
|
|
83
|
+
Access simulation recorder.
|
|
84
|
+
|
|
85
|
+
:return: Simulation recorder.
|
|
86
|
+
:rtype: ExecutableRecorder
|
|
87
|
+
"""
|
|
88
|
+
if self._recorder is None:
|
|
89
|
+
logger.error("You haven't initialize simulation recorder yet.")
|
|
90
|
+
raise ConfigError("You haven't initialize simulation recorder yet.")
|
|
91
|
+
return self._recorder
|
|
92
|
+
|
|
93
|
+
def set_exec_db(self, exec_db: ExecutableDB):
|
|
94
|
+
"""
|
|
95
|
+
Initialize Executable DB.
|
|
96
|
+
|
|
97
|
+
:param exec_db: Executables DB.
|
|
98
|
+
:type exec_db: ExecutableDB
|
|
99
|
+
"""
|
|
100
|
+
self._exec_db = exec_db
|
|
101
|
+
self._exec_db_initialized = True
|
|
102
|
+
|
|
103
|
+
def set_config_register_func(self, func: Callable[["WRFRunConfig"], None]):
|
|
104
|
+
"""
|
|
105
|
+
Set register function which will be called by wrfrun config.
|
|
106
|
+
This functions should accept a ``WRFRunConfig`` instance.
|
|
107
|
+
|
|
108
|
+
If wrfrun config hasn't been initialized, the function will be stored
|
|
109
|
+
and called in order by the time wrfrun config is initialized.
|
|
110
|
+
|
|
111
|
+
:param func: Register functions.
|
|
112
|
+
:type func: Callable[["WRFRunConfig"], None]
|
|
113
|
+
"""
|
|
114
|
+
if self._config_initialized:
|
|
115
|
+
func(self._config)
|
|
116
|
+
|
|
117
|
+
else:
|
|
118
|
+
if func not in self._config_register_funcs:
|
|
119
|
+
self._config_register_funcs.append(func)
|
|
120
|
+
|
|
121
|
+
def set_exec_db_register_func(self, func: Callable[["ExecutableDB"], None]):
|
|
122
|
+
"""
|
|
123
|
+
Set register function which will be called by executables DB.
|
|
124
|
+
This function should accept a :class:`WRFRunExecutableRegisterCenter` instance.
|
|
125
|
+
|
|
126
|
+
If executables DB hasn't been initialized, the function will be stored
|
|
127
|
+
and called in order by the time executables DB is initialized.
|
|
128
|
+
|
|
129
|
+
:param func: Register functions.
|
|
130
|
+
:type func: Callable[["WRFRunExecutableRegisterCenter"], None]
|
|
131
|
+
"""
|
|
132
|
+
if self._exec_db_initialized:
|
|
133
|
+
func(self._exec_db)
|
|
134
|
+
|
|
135
|
+
else:
|
|
136
|
+
if func not in self._exec_db_register_funcs:
|
|
137
|
+
self._exec_db_register_funcs.append(func)
|
|
138
|
+
|
|
139
|
+
def is_initialized(self, name: Literal["config", "exec_db", "record"]) -> bool:
|
|
140
|
+
"""
|
|
141
|
+
Check if the config has been initialized.
|
|
142
|
+
|
|
143
|
+
:param name: Name of the instance.
|
|
144
|
+
:type name: str
|
|
145
|
+
:return: True or False.
|
|
146
|
+
:rtype: bool
|
|
147
|
+
"""
|
|
148
|
+
flag = False
|
|
149
|
+
|
|
150
|
+
match name:
|
|
151
|
+
case "config":
|
|
152
|
+
flag = self._config_initialized
|
|
153
|
+
|
|
154
|
+
case "exec_db":
|
|
155
|
+
flag = self._exec_db_initialized
|
|
156
|
+
|
|
157
|
+
case "record":
|
|
158
|
+
flag = self._recorder_initialized
|
|
159
|
+
|
|
160
|
+
return flag
|
|
161
|
+
|
|
162
|
+
def init_wrfrun_config(self, config_file: str):
|
|
163
|
+
"""
|
|
164
|
+
Initialize wrfrun config with the given config file.
|
|
165
|
+
|
|
166
|
+
:param config_file: Config file path.
|
|
167
|
+
:type config_file: str
|
|
168
|
+
"""
|
|
169
|
+
logger.info(f"Initialize `WRFRUNConfig` with config: {config_file}")
|
|
170
|
+
self._config = WRFRunConfig.from_config_file(config_file, self._config_register_funcs)
|
|
171
|
+
self._config_initialized = True
|
|
172
|
+
|
|
173
|
+
def init_exec_db(self):
|
|
174
|
+
"""
|
|
175
|
+
Initialize Executable DB.
|
|
176
|
+
"""
|
|
177
|
+
self._exec_db = ExecutableDB()
|
|
178
|
+
self._exec_db.apply_register_func(self._exec_db_register_funcs)
|
|
179
|
+
self._exec_db_initialized = True
|
|
180
|
+
|
|
181
|
+
def init_recorder(self, save_path: str, include_data: bool):
|
|
182
|
+
"""
|
|
183
|
+
Initialize simulation recorder.
|
|
184
|
+
|
|
185
|
+
:param save_path: Save path of the replay file.
|
|
186
|
+
:type save_path: str
|
|
187
|
+
:param include_data: If includes data.
|
|
188
|
+
:type include_data: bool
|
|
189
|
+
"""
|
|
190
|
+
self._recorder = ExecutableRecorder(self._config, save_path, include_data)
|
|
191
|
+
self._recorder_initialized = True
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
WRFRUN = WRFRUNProxy()
|
|
195
|
+
|
|
196
|
+
__all__ = ["WRFRUN", "WRFRUNProxy"]
|
wrfrun/core/error.py
CHANGED
|
@@ -26,6 +26,7 @@ class WRFRunBasicError(Exception):
|
|
|
26
26
|
"""
|
|
27
27
|
Basic exception class of ``wrfrun``. New exception **MUST** inherit this class.
|
|
28
28
|
"""
|
|
29
|
+
|
|
29
30
|
pass
|
|
30
31
|
|
|
31
32
|
|
|
@@ -33,6 +34,7 @@ class ConfigError(WRFRunBasicError):
|
|
|
33
34
|
"""
|
|
34
35
|
Exception indicates the config of ``wrfrun`` or NWP model can't be used.
|
|
35
36
|
"""
|
|
37
|
+
|
|
36
38
|
pass
|
|
37
39
|
|
|
38
40
|
|
|
@@ -40,6 +42,7 @@ class WRFRunContextError(WRFRunBasicError):
|
|
|
40
42
|
"""
|
|
41
43
|
Exception indicates ``wrfrun`` is running out of the ``wrfrun`` context.
|
|
42
44
|
"""
|
|
45
|
+
|
|
43
46
|
pass
|
|
44
47
|
|
|
45
48
|
|
|
@@ -47,6 +50,7 @@ class CommandError(WRFRunBasicError):
|
|
|
47
50
|
"""
|
|
48
51
|
Exception indicates the command of ``Executable`` can't be executed successfully.
|
|
49
52
|
"""
|
|
53
|
+
|
|
50
54
|
pass
|
|
51
55
|
|
|
52
56
|
|
|
@@ -54,6 +58,7 @@ class OutputFileError(WRFRunBasicError):
|
|
|
54
58
|
"""
|
|
55
59
|
Exception indicates ``wrfrun`` can't find any output files with the given rules.
|
|
56
60
|
"""
|
|
61
|
+
|
|
57
62
|
pass
|
|
58
63
|
|
|
59
64
|
|
|
@@ -61,6 +66,7 @@ class ResourceURIError(WRFRunBasicError):
|
|
|
61
66
|
"""
|
|
62
67
|
Exception indicates ``wrfrun`` can't parse the URI.
|
|
63
68
|
"""
|
|
69
|
+
|
|
64
70
|
pass
|
|
65
71
|
|
|
66
72
|
|
|
@@ -68,6 +74,7 @@ class InputFileError(WRFRunBasicError):
|
|
|
68
74
|
"""
|
|
69
75
|
Exception indicates ``wrfrun`` can't find specified input files.
|
|
70
76
|
"""
|
|
77
|
+
|
|
71
78
|
pass
|
|
72
79
|
|
|
73
80
|
|
|
@@ -75,6 +82,7 @@ class NamelistError(WRFRunBasicError):
|
|
|
75
82
|
"""
|
|
76
83
|
Exception indicates ``wrfrun`` can't find the namelist user want to use.
|
|
77
84
|
"""
|
|
85
|
+
|
|
78
86
|
pass
|
|
79
87
|
|
|
80
88
|
|
|
@@ -82,6 +90,7 @@ class NamelistIDError(WRFRunBasicError):
|
|
|
82
90
|
"""
|
|
83
91
|
Exception indicates ``wrfrun`` can't register the specified namelist id.
|
|
84
92
|
"""
|
|
93
|
+
|
|
85
94
|
pass
|
|
86
95
|
|
|
87
96
|
|
|
@@ -89,6 +98,7 @@ class ExecRegisterError(WRFRunBasicError):
|
|
|
89
98
|
"""
|
|
90
99
|
Exception indicates ``wrfrun`` can't register the specified ``Executable``.
|
|
91
100
|
"""
|
|
101
|
+
|
|
92
102
|
pass
|
|
93
103
|
|
|
94
104
|
|
|
@@ -96,6 +106,7 @@ class GetExecClassError(WRFRunBasicError):
|
|
|
96
106
|
"""
|
|
97
107
|
Exception indicates ``wrfrun`` can't find the specified ``Executable``.
|
|
98
108
|
"""
|
|
109
|
+
|
|
99
110
|
pass
|
|
100
111
|
|
|
101
112
|
|
|
@@ -103,6 +114,7 @@ class ModelNameError(WRFRunBasicError):
|
|
|
103
114
|
"""
|
|
104
115
|
Exception indicates ``wrfrun`` can't find config of the specified NWP model in the config file.
|
|
105
116
|
"""
|
|
117
|
+
|
|
106
118
|
pass
|
|
107
119
|
|
|
108
120
|
|
|
@@ -110,8 +122,22 @@ class RecordError(WRFRunBasicError):
|
|
|
110
122
|
"""
|
|
111
123
|
Exception indicates ``wrfrun`` can't record simulations.
|
|
112
124
|
"""
|
|
125
|
+
|
|
113
126
|
pass
|
|
114
127
|
|
|
115
128
|
|
|
116
|
-
__all__ = [
|
|
117
|
-
|
|
129
|
+
__all__ = [
|
|
130
|
+
"WRFRunBasicError",
|
|
131
|
+
"ConfigError",
|
|
132
|
+
"WRFRunContextError",
|
|
133
|
+
"CommandError",
|
|
134
|
+
"OutputFileError",
|
|
135
|
+
"ResourceURIError",
|
|
136
|
+
"InputFileError",
|
|
137
|
+
"NamelistError",
|
|
138
|
+
"ExecRegisterError",
|
|
139
|
+
"GetExecClassError",
|
|
140
|
+
"ModelNameError",
|
|
141
|
+
"NamelistIDError",
|
|
142
|
+
"RecordError",
|
|
143
|
+
]
|
wrfrun/core/replay.py
CHANGED
|
@@ -2,115 +2,29 @@
|
|
|
2
2
|
wrfrun.core.replay
|
|
3
3
|
##################
|
|
4
4
|
|
|
5
|
-
This module provides methods to read
|
|
5
|
+
This module provides methods to read configs from replay file and reproduce simulations.
|
|
6
6
|
|
|
7
7
|
.. autosummary::
|
|
8
8
|
:toctree: generated/
|
|
9
9
|
|
|
10
|
-
WRFRunExecutableRegisterCenter
|
|
11
10
|
replay_config_generator
|
|
12
|
-
|
|
13
|
-
WRFRUNExecDB
|
|
14
|
-
************
|
|
15
|
-
|
|
16
|
-
In order to load ``Executable`` correctly based on the stored ``name`` in ``.replay`` files,
|
|
17
|
-
``wrfrun`` uses ``WRFRUNExecDB``, which is the instance of :class:`WRFRunExecutableRegisterCenter`,
|
|
18
|
-
to records all ``Executable`` classes and corresponding ``name``.
|
|
19
|
-
When ``wrfrun`` replays the simulation, it gets the right ``Executable`` from ``WRFRUNExecDB`` and executes it.
|
|
20
11
|
"""
|
|
21
12
|
|
|
22
13
|
from collections.abc import Generator
|
|
23
14
|
from json import loads
|
|
24
15
|
from os.path import exists
|
|
25
16
|
from shutil import unpack_archive
|
|
26
|
-
from typing import Any
|
|
27
|
-
|
|
28
|
-
from .base import ExecutableBase, ExecutableConfig
|
|
29
|
-
from .config import WRFRUNConfig
|
|
30
|
-
from .error import ExecRegisterError, GetExecClassError
|
|
31
|
-
from ..utils import logger
|
|
32
|
-
|
|
33
|
-
WRFRUN_REPLAY_URI = ":WRFRUN_REPLAY:"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class WRFRunExecutableRegisterCenter:
|
|
37
|
-
"""
|
|
38
|
-
This class provides the method to records ``Executable``'s class with a unique ``name``.
|
|
39
|
-
Later you can get the class with the ``name``.
|
|
40
|
-
"""
|
|
41
|
-
_instance = None
|
|
42
|
-
_initialized = False
|
|
43
|
-
|
|
44
|
-
def __init__(self):
|
|
45
|
-
if self._initialized:
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
self._exec_db = {}
|
|
49
|
-
|
|
50
|
-
self._initialized = True
|
|
51
|
-
|
|
52
|
-
def __new__(cls, *args, **kwargs):
|
|
53
|
-
if cls._instance is None:
|
|
54
|
-
cls._instance = super().__new__(cls)
|
|
55
|
-
|
|
56
|
-
return cls._instance
|
|
57
|
-
|
|
58
|
-
def register_exec(self, name: str, cls: type):
|
|
59
|
-
"""
|
|
60
|
-
Register an ``Executable``'s class with a unique ``name``.
|
|
61
|
-
|
|
62
|
-
If the ``name`` has been used, :class:`ExecRegisterError` will be raised.
|
|
63
|
-
|
|
64
|
-
:param name: ``Executable``'s unique name.
|
|
65
|
-
:type name: str
|
|
66
|
-
:param cls: ``Executable``'s class.
|
|
67
|
-
:type cls: type
|
|
68
|
-
"""
|
|
69
|
-
if name in self._exec_db:
|
|
70
|
-
logger.error(f"'{name}' has been registered.")
|
|
71
|
-
raise ExecRegisterError(f"'{name}' has been registered.")
|
|
72
|
-
|
|
73
|
-
self._exec_db[name] = cls
|
|
74
|
-
|
|
75
|
-
def is_registered(self, name: str) -> bool:
|
|
76
|
-
"""
|
|
77
|
-
Check if an ``Executable``'s class has been registered.
|
|
78
|
-
|
|
79
|
-
:param name: ``Executable``'s unique name.
|
|
80
|
-
:type name: str
|
|
81
|
-
:return: True or False.
|
|
82
|
-
:rtype: bool
|
|
83
|
-
"""
|
|
84
|
-
if name in self._exec_db:
|
|
85
|
-
return True
|
|
86
|
-
else:
|
|
87
|
-
return False
|
|
88
|
-
|
|
89
|
-
def get_cls(self, name: str) -> type:
|
|
90
|
-
"""
|
|
91
|
-
Get an ``Executable``'s class with the ``name``.
|
|
92
|
-
|
|
93
|
-
If the ``name`` can't be found, :class:`GetExecClassError` will be raised.
|
|
94
|
-
|
|
95
|
-
:param name: ``Executable``'s unique name.
|
|
96
|
-
:type name: str
|
|
97
|
-
:return: ``Executable``'s class.
|
|
98
|
-
:rtype: type
|
|
99
|
-
"""
|
|
100
|
-
if name not in self._exec_db:
|
|
101
|
-
logger.error(f"Executable class '{name}' not found.")
|
|
102
|
-
raise GetExecClassError(f"Executable class '{name}' not found.")
|
|
103
|
-
|
|
104
|
-
return self._exec_db[name]
|
|
105
|
-
|
|
106
17
|
|
|
107
|
-
|
|
18
|
+
from ..log import logger
|
|
19
|
+
from .base import ExecutableBase
|
|
20
|
+
from .core import WRFRUN
|
|
21
|
+
from .type import ExecutableConfig
|
|
108
22
|
|
|
109
23
|
|
|
110
|
-
def replay_config_generator(replay_config_file: str) -> Generator[tuple[str, ExecutableBase],
|
|
24
|
+
def replay_config_generator(replay_config_file: str) -> Generator[tuple[str, ExecutableBase], None, None]:
|
|
111
25
|
"""
|
|
112
26
|
This method can read the ``.replay`` file and returns a generator which yields ``Executable`` and their names.
|
|
113
|
-
If this method doesn't find ``config.json`` in the ``.replay`` file,
|
|
27
|
+
If this method doesn't find ``config.json`` in the ``.replay`` file, :class:`FileNotFoundError` will be raised.
|
|
114
28
|
|
|
115
29
|
The ``Executable`` you get from the generator has been initialized so you can execute it directly.
|
|
116
30
|
|
|
@@ -124,7 +38,7 @@ def replay_config_generator(replay_config_file: str) -> Generator[tuple[str, Exe
|
|
|
124
38
|
:rtype: Generator
|
|
125
39
|
"""
|
|
126
40
|
logger.info(f"Loading replay resources from: {replay_config_file}")
|
|
127
|
-
work_path =
|
|
41
|
+
work_path = WRFRUN.config.parse_resource_uri(WRFRUN.config.WRFRUN_WORKSPACE_REPLAY)
|
|
128
42
|
|
|
129
43
|
unpack_archive(replay_config_file, work_path, "zip")
|
|
130
44
|
|
|
@@ -138,9 +52,9 @@ def replay_config_generator(replay_config_file: str) -> Generator[tuple[str, Exe
|
|
|
138
52
|
for _config in replay_config_list:
|
|
139
53
|
args = _config["class_config"]["class_args"]
|
|
140
54
|
kwargs = _config["class_config"]["class_kwargs"]
|
|
141
|
-
executable: ExecutableBase =
|
|
55
|
+
executable: ExecutableBase = WRFRUN.ExecDB.get_cls(_config["name"])(*args, **kwargs)
|
|
142
56
|
executable.load_config(_config)
|
|
143
57
|
yield _config["name"], executable
|
|
144
58
|
|
|
145
59
|
|
|
146
|
-
__all__ = ["
|
|
60
|
+
__all__ = ["replay_config_generator"]
|
wrfrun/core/server.py
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
wrfrun.core.server
|
|
3
3
|
##################
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
In order to report the progress to user, ``wrfrun`` provides :class:`WRFRunServer` to set up a socket server.
|
|
5
|
+
This module defines the socket server to report simulation progress.
|
|
7
6
|
|
|
8
7
|
.. autosummary::
|
|
9
8
|
:toctree: generated/
|
|
@@ -12,6 +11,29 @@ In order to report the progress to user, ``wrfrun`` provides :class:`WRFRunServe
|
|
|
12
11
|
WRFRunServer
|
|
13
12
|
WRFRunServerHandler
|
|
14
13
|
stop_server
|
|
14
|
+
|
|
15
|
+
How does wrfrun get simulation progress?
|
|
16
|
+
****************************************
|
|
17
|
+
|
|
18
|
+
Currently, wrfrun relies on the log parse function provided by :doc:`model </api/model>`.
|
|
19
|
+
The log parse function should accept one parameter ``datetime``,
|
|
20
|
+
and return an integer represents how many seconds the model has simulated.
|
|
21
|
+
For example, the signature of :func:`get_wrf_simulated_seconds <wrfrun.model.wrf.log.get_wrf_simulated_seconds>` is:
|
|
22
|
+
|
|
23
|
+
.. code-block:: Python
|
|
24
|
+
|
|
25
|
+
def get_wrf_simulated_seconds(start_datetime: datetime) -> int:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
Socket server API list
|
|
29
|
+
**********************
|
|
30
|
+
|
|
31
|
+
The socket server accepts a JSON string: ``{"message": "MESSAGE"}``.
|
|
32
|
+
The ``MESSAGE`` can be:
|
|
33
|
+
|
|
34
|
+
* "stop": Stop the socket server.
|
|
35
|
+
* "debug": Get the start date of the simulation, and the total seconds it will simulate: ``{"start_date": "...", "total_seconds": "..."}``.
|
|
36
|
+
* Any string else: Simulation progress: ``{"usage": "...", "status": "...", "progress": "..."}``.
|
|
15
37
|
"""
|
|
16
38
|
|
|
17
39
|
import socket
|
|
@@ -19,19 +41,21 @@ import socketserver
|
|
|
19
41
|
import threading
|
|
20
42
|
from collections.abc import Callable
|
|
21
43
|
from datetime import datetime
|
|
22
|
-
from json import dumps
|
|
44
|
+
from json import dumps, loads
|
|
23
45
|
from time import time
|
|
24
|
-
from typing import Tuple
|
|
46
|
+
from typing import Any, Tuple
|
|
25
47
|
|
|
26
|
-
from
|
|
27
|
-
from
|
|
48
|
+
from ..log import logger
|
|
49
|
+
from .core import WRFRUN
|
|
28
50
|
|
|
29
|
-
|
|
30
|
-
|
|
51
|
+
|
|
52
|
+
def _defaut_log_parser(date: datetime) -> int:
|
|
53
|
+
return -1
|
|
31
54
|
|
|
32
55
|
|
|
33
56
|
SET_LOG_PARSER_LOCK = threading.Lock()
|
|
34
|
-
|
|
57
|
+
# default parser returns -1
|
|
58
|
+
LOG_PARSER: Callable[[datetime], int] = _defaut_log_parser
|
|
35
59
|
|
|
36
60
|
|
|
37
61
|
def set_log_parse_func(func: Callable[[datetime], int]):
|
|
@@ -175,6 +199,7 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
175
199
|
``status`` represents work status,
|
|
176
200
|
``progress`` represents simulation progress of the status in percentage.
|
|
177
201
|
"""
|
|
202
|
+
|
|
178
203
|
def __init__(self, request, client_address, server: WRFRunServer) -> None:
|
|
179
204
|
"""
|
|
180
205
|
:class:`WRFRunServer` handler.
|
|
@@ -189,7 +214,7 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
189
214
|
super().__init__(request, client_address, server)
|
|
190
215
|
|
|
191
216
|
# get server
|
|
192
|
-
self.
|
|
217
|
+
self._server: WRFRunServer = server
|
|
193
218
|
|
|
194
219
|
def calculate_time_usage(self) -> int:
|
|
195
220
|
"""
|
|
@@ -203,7 +228,7 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
203
228
|
current_timestamp = datetime.fromtimestamp(time())
|
|
204
229
|
|
|
205
230
|
# get delta second
|
|
206
|
-
seconds_diff = current_timestamp - self.
|
|
231
|
+
seconds_diff = current_timestamp - self._server.get_start_time()
|
|
207
232
|
seconds_diff = seconds_diff.seconds
|
|
208
233
|
|
|
209
234
|
return seconds_diff
|
|
@@ -216,20 +241,17 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
216
241
|
``progress`` represents simulation progress of the status in percentage.
|
|
217
242
|
:rtype: tuple[str, int]
|
|
218
243
|
"""
|
|
219
|
-
start_date, simulate_seconds = self.
|
|
244
|
+
start_date, simulate_seconds = self._server.get_model_simulate_settings()
|
|
220
245
|
|
|
221
246
|
with SET_LOG_PARSER_LOCK:
|
|
222
|
-
|
|
223
|
-
simulated_seconds = -1
|
|
224
|
-
else:
|
|
225
|
-
simulated_seconds = LOG_PARSER(start_date)
|
|
247
|
+
simulated_seconds = LOG_PARSER(start_date)
|
|
226
248
|
|
|
227
249
|
if simulated_seconds > 0:
|
|
228
250
|
progress = simulated_seconds * 100 // simulate_seconds
|
|
229
251
|
else:
|
|
230
252
|
progress = -1
|
|
231
253
|
|
|
232
|
-
status =
|
|
254
|
+
status = WRFRUN.config.WRFRUN_WORK_STATUS
|
|
233
255
|
|
|
234
256
|
if status == "":
|
|
235
257
|
status = "*"
|
|
@@ -240,20 +262,20 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
240
262
|
"""
|
|
241
263
|
Request handler.
|
|
242
264
|
"""
|
|
243
|
-
|
|
244
265
|
# check if we will to stop server
|
|
245
|
-
|
|
266
|
+
request: dict[str, Any] = loads(self.rfile.readline().decode())
|
|
267
|
+
msg = request["message"]
|
|
246
268
|
|
|
247
269
|
if msg == "stop":
|
|
248
|
-
self.
|
|
249
|
-
self.wfile.write(
|
|
250
|
-
|
|
270
|
+
self._server.shutdown()
|
|
271
|
+
self.wfile.write("Server stop\n".encode())
|
|
272
|
+
|
|
251
273
|
elif msg == "debug":
|
|
252
|
-
start_date, simulate_seconds = self.
|
|
274
|
+
start_date, simulate_seconds = self._server.get_model_simulate_settings()
|
|
253
275
|
start_date = start_date.strftime("%Y-%m-%d %H:%M")
|
|
254
|
-
|
|
276
|
+
|
|
255
277
|
self.wfile.write(dumps({"start_date": start_date, "total_seconds": simulate_seconds}).encode())
|
|
256
|
-
|
|
278
|
+
|
|
257
279
|
else:
|
|
258
280
|
status, progress = self.calculate_progress()
|
|
259
281
|
time_usage = self.calculate_time_usage()
|
|
@@ -275,10 +297,13 @@ def stop_server(socket_ip: str, socket_port: int):
|
|
|
275
297
|
sock.connect((socket_ip, socket_port))
|
|
276
298
|
|
|
277
299
|
# send msg to server
|
|
278
|
-
|
|
300
|
+
# TODO:
|
|
301
|
+
# Need to test the new socket server.
|
|
302
|
+
msg = dumps({"message": "stop"})
|
|
303
|
+
sock.sendall(msg.encode())
|
|
279
304
|
|
|
280
305
|
# receive the message
|
|
281
|
-
msg = sock.recv(1024).decode().split(
|
|
306
|
+
msg = sock.recv(1024).decode().split("\n")[0]
|
|
282
307
|
|
|
283
308
|
logger.info(f"WRFRunServer: {msg}")
|
|
284
309
|
|