wrfrun 0.1.9__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 +74 -31
- 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 +136 -380
- wrfrun/core/core.py +196 -0
- wrfrun/core/error.py +35 -2
- wrfrun/core/replay.py +10 -96
- wrfrun/core/server.py +74 -32
- wrfrun/core/type.py +171 -0
- wrfrun/data.py +312 -149
- 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 +5 -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 -114
- wrfrun/model/type.py +116 -0
- wrfrun/model/utils.py +9 -19
- wrfrun/model/wrf/__init__.py +4 -9
- wrfrun/model/wrf/core.py +262 -165
- wrfrun/model/wrf/exec_wrap.py +13 -12
- wrfrun/model/wrf/geodata.py +116 -99
- wrfrun/model/wrf/log.py +103 -0
- wrfrun/model/wrf/namelist.py +92 -76
- wrfrun/model/wrf/plot.py +102 -0
- wrfrun/model/wrf/scheme.py +108 -52
- wrfrun/model/wrf/utils.py +39 -24
- wrfrun/model/wrf/vtable.py +42 -7
- wrfrun/plot/__init__.py +20 -0
- wrfrun/plot/wps.py +90 -73
- wrfrun/res/__init__.py +115 -5
- wrfrun/res/config/config.template.toml +15 -0
- wrfrun/res/config/palm.template.toml +23 -0
- wrfrun/run.py +121 -77
- wrfrun/scheduler/__init__.py +1 -0
- wrfrun/scheduler/lsf.py +4 -1
- wrfrun/scheduler/pbs.py +4 -1
- wrfrun/scheduler/script.py +19 -5
- wrfrun/scheduler/slurm.py +4 -1
- wrfrun/scheduler/utils.py +14 -2
- wrfrun/utils.py +88 -199
- wrfrun/workspace/__init__.py +8 -5
- wrfrun/workspace/core.py +21 -11
- wrfrun/workspace/palm.py +137 -0
- wrfrun/workspace/wrf.py +59 -14
- wrfrun-0.3.0.dist-info/METADATA +240 -0
- wrfrun-0.3.0.dist-info/RECORD +78 -0
- wrfrun/core/config.py +0 -767
- wrfrun/model/base.py +0 -14
- wrfrun-0.1.9.dist-info/METADATA +0 -68
- wrfrun-0.1.9.dist-info/RECORD +0 -62
- {wrfrun-0.1.9.dist-info → wrfrun-0.3.0.dist-info}/WHEEL +0 -0
- {wrfrun-0.1.9.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,8 +114,30 @@ 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
|
+
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class RecordError(WRFRunBasicError):
|
|
122
|
+
"""
|
|
123
|
+
Exception indicates ``wrfrun`` can't record simulations.
|
|
124
|
+
"""
|
|
125
|
+
|
|
106
126
|
pass
|
|
107
127
|
|
|
108
128
|
|
|
109
|
-
__all__ = [
|
|
110
|
-
|
|
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,30 +2,74 @@
|
|
|
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/
|
|
10
9
|
|
|
10
|
+
set_log_parse_func
|
|
11
11
|
WRFRunServer
|
|
12
12
|
WRFRunServerHandler
|
|
13
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": "..."}``.
|
|
14
37
|
"""
|
|
15
38
|
|
|
16
39
|
import socket
|
|
17
40
|
import socketserver
|
|
41
|
+
import threading
|
|
18
42
|
from collections.abc import Callable
|
|
19
43
|
from datetime import datetime
|
|
20
|
-
from json import dumps
|
|
44
|
+
from json import dumps, loads
|
|
21
45
|
from time import time
|
|
22
|
-
from typing import Tuple
|
|
46
|
+
from typing import Any, Tuple
|
|
47
|
+
|
|
48
|
+
from ..log import logger
|
|
49
|
+
from .core import WRFRUN
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _defaut_log_parser(date: datetime) -> int:
|
|
53
|
+
return -1
|
|
54
|
+
|
|
23
55
|
|
|
24
|
-
|
|
25
|
-
|
|
56
|
+
SET_LOG_PARSER_LOCK = threading.Lock()
|
|
57
|
+
# default parser returns -1
|
|
58
|
+
LOG_PARSER: Callable[[datetime], int] = _defaut_log_parser
|
|
26
59
|
|
|
27
|
-
|
|
28
|
-
|
|
60
|
+
|
|
61
|
+
def set_log_parse_func(func: Callable[[datetime], int]):
|
|
62
|
+
"""
|
|
63
|
+
Set log parse function used by socket server.
|
|
64
|
+
|
|
65
|
+
:param func: Function used to get simulated seconds from model's log file.
|
|
66
|
+
If the function can't parse the simulated seconds, it should return ``-1``.
|
|
67
|
+
:type func: Callable[[datetime], int]
|
|
68
|
+
"""
|
|
69
|
+
global SET_LOG_PARSER_LOCK, LOG_PARSER
|
|
70
|
+
|
|
71
|
+
with SET_LOG_PARSER_LOCK:
|
|
72
|
+
LOG_PARSER = func
|
|
29
73
|
|
|
30
74
|
|
|
31
75
|
class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
|
@@ -155,7 +199,8 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
155
199
|
``status`` represents work status,
|
|
156
200
|
``progress`` represents simulation progress of the status in percentage.
|
|
157
201
|
"""
|
|
158
|
-
|
|
202
|
+
|
|
203
|
+
def __init__(self, request, client_address, server: WRFRunServer) -> None:
|
|
159
204
|
"""
|
|
160
205
|
:class:`WRFRunServer` handler.
|
|
161
206
|
|
|
@@ -165,15 +210,11 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
165
210
|
:type client_address:
|
|
166
211
|
:param server: :class:`WRFRunServer` instance.
|
|
167
212
|
: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
213
|
"""
|
|
172
214
|
super().__init__(request, client_address, server)
|
|
173
215
|
|
|
174
216
|
# get server
|
|
175
|
-
self.
|
|
176
|
-
self.log_parse_func = log_parse_func
|
|
217
|
+
self._server: WRFRunServer = server
|
|
177
218
|
|
|
178
219
|
def calculate_time_usage(self) -> int:
|
|
179
220
|
"""
|
|
@@ -187,7 +228,7 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
187
228
|
current_timestamp = datetime.fromtimestamp(time())
|
|
188
229
|
|
|
189
230
|
# get delta second
|
|
190
|
-
seconds_diff = current_timestamp - self.
|
|
231
|
+
seconds_diff = current_timestamp - self._server.get_start_time()
|
|
191
232
|
seconds_diff = seconds_diff.seconds
|
|
192
233
|
|
|
193
234
|
return seconds_diff
|
|
@@ -200,19 +241,17 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
200
241
|
``progress`` represents simulation progress of the status in percentage.
|
|
201
242
|
:rtype: tuple[str, int]
|
|
202
243
|
"""
|
|
203
|
-
start_date, simulate_seconds = self.
|
|
244
|
+
start_date, simulate_seconds = self._server.get_model_simulate_settings()
|
|
204
245
|
|
|
205
|
-
|
|
206
|
-
simulated_seconds =
|
|
207
|
-
else:
|
|
208
|
-
simulated_seconds = self.log_parse_func(start_date)
|
|
246
|
+
with SET_LOG_PARSER_LOCK:
|
|
247
|
+
simulated_seconds = LOG_PARSER(start_date)
|
|
209
248
|
|
|
210
249
|
if simulated_seconds > 0:
|
|
211
250
|
progress = simulated_seconds * 100 // simulate_seconds
|
|
212
251
|
else:
|
|
213
252
|
progress = -1
|
|
214
253
|
|
|
215
|
-
status =
|
|
254
|
+
status = WRFRUN.config.WRFRUN_WORK_STATUS
|
|
216
255
|
|
|
217
256
|
if status == "":
|
|
218
257
|
status = "*"
|
|
@@ -223,20 +262,20 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
223
262
|
"""
|
|
224
263
|
Request handler.
|
|
225
264
|
"""
|
|
226
|
-
|
|
227
265
|
# check if we will to stop server
|
|
228
|
-
|
|
266
|
+
request: dict[str, Any] = loads(self.rfile.readline().decode())
|
|
267
|
+
msg = request["message"]
|
|
229
268
|
|
|
230
269
|
if msg == "stop":
|
|
231
|
-
self.
|
|
232
|
-
self.wfile.write(
|
|
233
|
-
|
|
270
|
+
self._server.shutdown()
|
|
271
|
+
self.wfile.write("Server stop\n".encode())
|
|
272
|
+
|
|
234
273
|
elif msg == "debug":
|
|
235
|
-
start_date, simulate_seconds = self.
|
|
274
|
+
start_date, simulate_seconds = self._server.get_model_simulate_settings()
|
|
236
275
|
start_date = start_date.strftime("%Y-%m-%d %H:%M")
|
|
237
|
-
|
|
276
|
+
|
|
238
277
|
self.wfile.write(dumps({"start_date": start_date, "total_seconds": simulate_seconds}).encode())
|
|
239
|
-
|
|
278
|
+
|
|
240
279
|
else:
|
|
241
280
|
status, progress = self.calculate_progress()
|
|
242
281
|
time_usage = self.calculate_time_usage()
|
|
@@ -258,10 +297,13 @@ def stop_server(socket_ip: str, socket_port: int):
|
|
|
258
297
|
sock.connect((socket_ip, socket_port))
|
|
259
298
|
|
|
260
299
|
# send msg to server
|
|
261
|
-
|
|
300
|
+
# TODO:
|
|
301
|
+
# Need to test the new socket server.
|
|
302
|
+
msg = dumps({"message": "stop"})
|
|
303
|
+
sock.sendall(msg.encode())
|
|
262
304
|
|
|
263
305
|
# receive the message
|
|
264
|
-
msg = sock.recv(1024).decode().split(
|
|
306
|
+
msg = sock.recv(1024).decode().split("\n")[0]
|
|
265
307
|
|
|
266
308
|
logger.info(f"WRFRunServer: {msg}")
|
|
267
309
|
|
|
@@ -269,4 +311,4 @@ def stop_server(socket_ip: str, socket_port: int):
|
|
|
269
311
|
logger.warning("Fail to stop WRFRunServer, maybe it doesn't start at all.")
|
|
270
312
|
|
|
271
313
|
|
|
272
|
-
__all__ = ["WRFRunServer", "WRFRunServerHandler", "stop_server"]
|
|
314
|
+
__all__ = ["WRFRunServer", "WRFRunServerHandler", "stop_server", "set_log_parse_func"]
|