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.
- wrfrun/cli.py +128 -0
- wrfrun/core/__init__.py +33 -0
- wrfrun/core/base.py +246 -75
- wrfrun/core/config.py +286 -236
- wrfrun/core/error.py +47 -17
- wrfrun/core/replay.py +65 -32
- wrfrun/core/server.py +139 -79
- wrfrun/data.py +10 -5
- wrfrun/extension/__init__.py +28 -0
- wrfrun/extension/goos_sst/__init__.py +67 -0
- wrfrun/extension/goos_sst/core.py +111 -0
- wrfrun/extension/goos_sst/res/Vtable.ERA_GOOS_SST +7 -0
- wrfrun/extension/goos_sst/res/__init__.py +26 -0
- wrfrun/extension/goos_sst/utils.py +97 -0
- wrfrun/extension/littler/__init__.py +57 -1
- wrfrun/extension/littler/{utils.py → core.py} +326 -40
- wrfrun/extension/utils.py +22 -21
- wrfrun/model/__init__.py +24 -1
- wrfrun/model/plot.py +253 -35
- wrfrun/model/utils.py +17 -8
- wrfrun/model/wrf/__init__.py +41 -0
- wrfrun/model/wrf/core.py +218 -102
- wrfrun/model/wrf/exec_wrap.py +49 -35
- wrfrun/model/wrf/namelist.py +82 -11
- wrfrun/model/wrf/scheme.py +85 -1
- wrfrun/model/wrf/{_metgrid.py → utils.py} +36 -2
- wrfrun/model/wrf/vtable.py +2 -1
- wrfrun/plot/wps.py +66 -58
- wrfrun/res/__init__.py +8 -5
- wrfrun/res/config/config.template.toml +50 -0
- wrfrun/res/{config.toml.template → config/wrf.template.toml} +10 -47
- wrfrun/res/run.template.sh +10 -0
- wrfrun/res/scheduler/lsf.template +5 -0
- wrfrun/res/{job_scheduler → scheduler}/pbs.template +1 -1
- wrfrun/res/{job_scheduler → scheduler}/slurm.template +2 -1
- wrfrun/run.py +19 -23
- wrfrun/scheduler/__init__.py +35 -0
- wrfrun/scheduler/env.py +44 -0
- wrfrun/scheduler/lsf.py +47 -0
- wrfrun/scheduler/pbs.py +48 -0
- wrfrun/scheduler/script.py +70 -0
- wrfrun/scheduler/slurm.py +48 -0
- wrfrun/scheduler/utils.py +14 -0
- wrfrun/utils.py +8 -3
- wrfrun/workspace/__init__.py +38 -0
- wrfrun/workspace/core.py +92 -0
- wrfrun/workspace/wrf.py +121 -0
- {wrfrun-0.1.7.dist-info → wrfrun-0.1.9.dist-info}/METADATA +4 -3
- wrfrun-0.1.9.dist-info/RECORD +62 -0
- wrfrun-0.1.9.dist-info/entry_points.txt +3 -0
- wrfrun/model/wrf/_ndown.py +0 -39
- wrfrun/pbs.py +0 -86
- wrfrun/res/run.sh.template +0 -16
- wrfrun/workspace.py +0 -88
- wrfrun-0.1.7.dist-info/RECORD +0 -46
- {wrfrun-0.1.7.dist-info → wrfrun-0.1.9.dist-info}/WHEEL +0 -0
wrfrun/core/error.py
CHANGED
|
@@ -1,80 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wrfrun.core.error
|
|
3
|
+
#################
|
|
4
|
+
|
|
5
|
+
This module defines all exceptions used in ``wrfrun``.
|
|
6
|
+
|
|
7
|
+
.. autosummary::
|
|
8
|
+
:toctree: generated/
|
|
9
|
+
|
|
10
|
+
WRFRunBasicError
|
|
11
|
+
ConfigError
|
|
12
|
+
WRFRunContextError
|
|
13
|
+
CommandError
|
|
14
|
+
OutputFileError
|
|
15
|
+
ResourceURIError
|
|
16
|
+
InputFileError
|
|
17
|
+
NamelistError
|
|
18
|
+
NamelistIDError
|
|
19
|
+
ExecRegisterError
|
|
20
|
+
GetExecClassError
|
|
21
|
+
ModelNameError
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
1
25
|
class WRFRunBasicError(Exception):
|
|
2
26
|
"""
|
|
3
|
-
Basic
|
|
27
|
+
Basic exception class of ``wrfrun``. New exception **MUST** inherit this class.
|
|
4
28
|
"""
|
|
5
29
|
pass
|
|
6
30
|
|
|
7
31
|
|
|
8
32
|
class ConfigError(WRFRunBasicError):
|
|
9
33
|
"""
|
|
10
|
-
|
|
34
|
+
Exception indicates the config of ``wrfrun`` or NWP model can't be used.
|
|
11
35
|
"""
|
|
12
36
|
pass
|
|
13
37
|
|
|
14
38
|
|
|
15
39
|
class WRFRunContextError(WRFRunBasicError):
|
|
16
40
|
"""
|
|
17
|
-
|
|
41
|
+
Exception indicates ``wrfrun`` is running out of the ``wrfrun`` context.
|
|
18
42
|
"""
|
|
19
43
|
pass
|
|
20
44
|
|
|
21
45
|
|
|
22
46
|
class CommandError(WRFRunBasicError):
|
|
23
47
|
"""
|
|
24
|
-
|
|
48
|
+
Exception indicates the command of ``Executable`` can't be executed successfully.
|
|
25
49
|
"""
|
|
26
50
|
pass
|
|
27
51
|
|
|
28
52
|
|
|
29
|
-
class
|
|
53
|
+
class OutputFileError(WRFRunBasicError):
|
|
30
54
|
"""
|
|
31
|
-
|
|
55
|
+
Exception indicates ``wrfrun`` can't find any output files with the given rules.
|
|
32
56
|
"""
|
|
33
57
|
pass
|
|
34
58
|
|
|
35
59
|
|
|
36
|
-
class
|
|
60
|
+
class ResourceURIError(WRFRunBasicError):
|
|
37
61
|
"""
|
|
38
|
-
|
|
62
|
+
Exception indicates ``wrfrun`` can't parse the URI.
|
|
39
63
|
"""
|
|
40
64
|
pass
|
|
41
65
|
|
|
42
66
|
|
|
43
|
-
class
|
|
67
|
+
class InputFileError(WRFRunBasicError):
|
|
44
68
|
"""
|
|
45
|
-
|
|
69
|
+
Exception indicates ``wrfrun`` can't find specified input files.
|
|
46
70
|
"""
|
|
47
71
|
pass
|
|
48
72
|
|
|
49
73
|
|
|
50
|
-
class
|
|
74
|
+
class NamelistError(WRFRunBasicError):
|
|
51
75
|
"""
|
|
52
|
-
|
|
76
|
+
Exception indicates ``wrfrun`` can't find the namelist user want to use.
|
|
53
77
|
"""
|
|
54
78
|
pass
|
|
55
79
|
|
|
56
80
|
|
|
57
|
-
class
|
|
81
|
+
class NamelistIDError(WRFRunBasicError):
|
|
58
82
|
"""
|
|
59
|
-
|
|
83
|
+
Exception indicates ``wrfrun`` can't register the specified namelist id.
|
|
60
84
|
"""
|
|
61
85
|
pass
|
|
62
86
|
|
|
63
87
|
|
|
64
88
|
class ExecRegisterError(WRFRunBasicError):
|
|
89
|
+
"""
|
|
90
|
+
Exception indicates ``wrfrun`` can't register the specified ``Executable``.
|
|
91
|
+
"""
|
|
65
92
|
pass
|
|
66
93
|
|
|
67
94
|
|
|
68
95
|
class GetExecClassError(WRFRunBasicError):
|
|
96
|
+
"""
|
|
97
|
+
Exception indicates ``wrfrun`` can't find the specified ``Executable``.
|
|
98
|
+
"""
|
|
69
99
|
pass
|
|
70
100
|
|
|
71
101
|
|
|
72
102
|
class ModelNameError(WRFRunBasicError):
|
|
73
103
|
"""
|
|
74
|
-
|
|
104
|
+
Exception indicates ``wrfrun`` can't find config of the specified NWP model in the config file.
|
|
75
105
|
"""
|
|
76
106
|
pass
|
|
77
107
|
|
|
78
108
|
|
|
79
|
-
__all__ = ["WRFRunBasicError", "ConfigError", "WRFRunContextError", "CommandError", "
|
|
80
|
-
"NamelistError", "ExecRegisterError", "GetExecClassError", "ModelNameError"]
|
|
109
|
+
__all__ = ["WRFRunBasicError", "ConfigError", "WRFRunContextError", "CommandError", "OutputFileError", "ResourceURIError", "InputFileError",
|
|
110
|
+
"NamelistError", "ExecRegisterError", "GetExecClassError", "ModelNameError", "NamelistIDError"]
|
wrfrun/core/replay.py
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wrfrun.core.replay
|
|
3
|
+
##################
|
|
4
|
+
|
|
5
|
+
This module provides methods to read config from ``.replay`` file and reproduce the simulation.
|
|
6
|
+
|
|
7
|
+
.. autosummary::
|
|
8
|
+
:toctree: generated/
|
|
9
|
+
|
|
10
|
+
WRFRunExecutableRegisterCenter
|
|
11
|
+
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
|
+
"""
|
|
21
|
+
|
|
22
|
+
from collections.abc import Generator
|
|
1
23
|
from json import loads
|
|
2
24
|
from os.path import exists
|
|
3
25
|
from shutil import unpack_archive
|
|
26
|
+
from typing import Any
|
|
4
27
|
|
|
5
28
|
from .base import ExecutableBase, ExecutableConfig
|
|
6
29
|
from .config import WRFRUNConfig
|
|
@@ -12,7 +35,8 @@ WRFRUN_REPLAY_URI = ":WRFRUN_REPLAY:"
|
|
|
12
35
|
|
|
13
36
|
class WRFRunExecutableRegisterCenter:
|
|
14
37
|
"""
|
|
15
|
-
|
|
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``.
|
|
16
40
|
"""
|
|
17
41
|
_instance = None
|
|
18
42
|
_initialized = False
|
|
@@ -31,67 +55,76 @@ class WRFRunExecutableRegisterCenter:
|
|
|
31
55
|
|
|
32
56
|
return cls._instance
|
|
33
57
|
|
|
34
|
-
def register_exec(self,
|
|
58
|
+
def register_exec(self, name: str, cls: type):
|
|
35
59
|
"""
|
|
36
|
-
Register an
|
|
60
|
+
Register an ``Executable``'s class with a unique ``name``.
|
|
61
|
+
|
|
62
|
+
If the ``name`` has been used, :class:`ExecRegisterError` will be raised.
|
|
37
63
|
|
|
38
|
-
:param
|
|
39
|
-
:type
|
|
40
|
-
:param cls:
|
|
64
|
+
:param name: ``Executable``'s unique name.
|
|
65
|
+
:type name: str
|
|
66
|
+
:param cls: ``Executable``'s class.
|
|
41
67
|
:type cls: type
|
|
42
|
-
:return:
|
|
43
|
-
:rtype:
|
|
44
68
|
"""
|
|
45
|
-
if
|
|
46
|
-
logger.error(f"'{
|
|
47
|
-
raise ExecRegisterError(f"'{
|
|
69
|
+
if name in self._exec_db:
|
|
70
|
+
logger.error(f"'{name}' has been registered.")
|
|
71
|
+
raise ExecRegisterError(f"'{name}' has been registered.")
|
|
48
72
|
|
|
49
|
-
self._exec_db[
|
|
73
|
+
self._exec_db[name] = cls
|
|
50
74
|
|
|
51
|
-
def is_registered(self,
|
|
75
|
+
def is_registered(self, name: str) -> bool:
|
|
52
76
|
"""
|
|
53
|
-
Check if an
|
|
77
|
+
Check if an ``Executable``'s class has been registered.
|
|
54
78
|
|
|
55
|
-
:param
|
|
56
|
-
:type
|
|
79
|
+
:param name: ``Executable``'s unique name.
|
|
80
|
+
:type name: str
|
|
57
81
|
:return: True or False.
|
|
58
82
|
:rtype: bool
|
|
59
83
|
"""
|
|
60
|
-
if
|
|
84
|
+
if name in self._exec_db:
|
|
61
85
|
return True
|
|
62
86
|
else:
|
|
63
87
|
return False
|
|
64
88
|
|
|
65
|
-
def get_cls(self,
|
|
89
|
+
def get_cls(self, name: str) -> type:
|
|
66
90
|
"""
|
|
67
|
-
Get
|
|
91
|
+
Get an ``Executable``'s class with the ``name``.
|
|
92
|
+
|
|
93
|
+
If the ``name`` can't be found, :class:`GetExecClassError` will be raised.
|
|
68
94
|
|
|
69
|
-
:param
|
|
70
|
-
:type
|
|
71
|
-
:return: Executable class.
|
|
95
|
+
:param name: ``Executable``'s unique name.
|
|
96
|
+
:type name: str
|
|
97
|
+
:return: ``Executable``'s class.
|
|
72
98
|
:rtype: type
|
|
73
99
|
"""
|
|
74
|
-
if
|
|
75
|
-
logger.error(f"Executable class '{
|
|
76
|
-
raise GetExecClassError(f"Executable class '{
|
|
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.")
|
|
77
103
|
|
|
78
|
-
return self._exec_db[
|
|
104
|
+
return self._exec_db[name]
|
|
79
105
|
|
|
80
106
|
|
|
81
107
|
WRFRUNExecDB = WRFRunExecutableRegisterCenter()
|
|
82
108
|
|
|
83
109
|
|
|
84
|
-
def replay_config_generator(replay_config_file: str):
|
|
110
|
+
def replay_config_generator(replay_config_file: str) -> Generator[tuple[str, ExecutableBase], Any, None]:
|
|
85
111
|
"""
|
|
86
|
-
|
|
112
|
+
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, ``FileNotFoundError`` will be raised.
|
|
114
|
+
|
|
115
|
+
The ``Executable`` you get from the generator has been initialized so you can execute it directly.
|
|
116
|
+
|
|
117
|
+
>>> replay_file = "./example.replay"
|
|
118
|
+
>>> for name, _exec in replay_config_generator(replay_file):
|
|
119
|
+
>>> _exec.replay()
|
|
87
120
|
|
|
88
|
-
:param replay_config_file:
|
|
121
|
+
:param replay_config_file: Path of the ``.replay`` file.
|
|
89
122
|
:type replay_config_file: str
|
|
90
|
-
:return:
|
|
91
|
-
:rtype:
|
|
123
|
+
:return: A generator that yields: ``(name, Executable)``
|
|
124
|
+
:rtype: Generator
|
|
92
125
|
"""
|
|
93
126
|
logger.info(f"Loading replay resources from: {replay_config_file}")
|
|
94
|
-
work_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.
|
|
127
|
+
work_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_WORKSPACE_REPLAY)
|
|
95
128
|
|
|
96
129
|
unpack_archive(replay_config_file, work_path, "zip")
|
|
97
130
|
|
wrfrun/core/server.py
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wrfrun.core.server
|
|
3
|
+
##################
|
|
4
|
+
|
|
5
|
+
Currently, ``wrfrun`` provides the method to reads log file of WRF and calculates simulation progress.
|
|
6
|
+
In order to report the progress to user, ``wrfrun`` provides :class:`WRFRunServer` to set up a socket server.
|
|
7
|
+
|
|
8
|
+
.. autosummary::
|
|
9
|
+
:toctree: generated/
|
|
10
|
+
|
|
11
|
+
WRFRunServer
|
|
12
|
+
WRFRunServerHandler
|
|
13
|
+
stop_server
|
|
14
|
+
"""
|
|
15
|
+
|
|
1
16
|
import socket
|
|
2
17
|
import socketserver
|
|
3
|
-
import
|
|
18
|
+
from collections.abc import Callable
|
|
4
19
|
from datetime import datetime
|
|
20
|
+
from json import dumps
|
|
5
21
|
from time import time
|
|
6
|
-
from typing import Tuple
|
|
22
|
+
from typing import Tuple
|
|
7
23
|
|
|
8
24
|
from .config import WRFRUNConfig
|
|
9
25
|
from ..utils import logger
|
|
@@ -12,48 +28,53 @@ WRFRUN_SERVER_INSTANCE = None
|
|
|
12
28
|
WRFRUN_SERVER_THREAD = None
|
|
13
29
|
|
|
14
30
|
|
|
15
|
-
|
|
16
|
-
"""
|
|
17
|
-
Get how many seconds wrf has integrated.
|
|
18
|
-
|
|
19
|
-
:param start_datetime: WRF start datetime.
|
|
20
|
-
:type start_datetime: datetime
|
|
21
|
-
:param log_file_path: Absolute path of the log file to be parsed.
|
|
22
|
-
:type log_file_path: str
|
|
23
|
-
:return: Seconds.
|
|
24
|
-
:rtype: int
|
|
31
|
+
class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
|
25
32
|
"""
|
|
26
|
-
|
|
27
|
-
if log_file_path is None:
|
|
28
|
-
log_file_path = WRFRUNConfig.parse_resource_uri(f"{WRFRUNConfig.WRF_WORK_PATH}/rsl.out.0000")
|
|
29
|
-
res = subprocess.run(["tail", "-n", "1", log_file_path], capture_output=True)
|
|
30
|
-
log_text = res.stdout.decode()
|
|
33
|
+
A socket server to report time usage.
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
If you want to use the socket server, you need to give four arguments: ``start_date``,
|
|
36
|
+
``wrf_simulate_seconds``, ``socket_address_port`` and ``WRFRunServerHandler``.
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
>>> start_date = datetime(2021, 3, 25, 8)
|
|
39
|
+
>>> simulate_seconds = 60 * 60 * 72
|
|
40
|
+
>>> socket_address_port = ("0.0.0.0", 54321)
|
|
41
|
+
>>> _wrfrun_server = WRFRunServer(start_date, simulate_seconds, socket_address_port, WRFRunServerHandler)
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
try:
|
|
39
|
-
current_datetime = datetime.strptime(time_string, "%Y-%m-%d_%H:%M:%S")
|
|
40
|
-
# remove timezone info so we can calculate.
|
|
41
|
-
date_delta = current_datetime - start_datetime.replace(tzinfo=None)
|
|
42
|
-
seconds = date_delta.days * 24 * 60 * 60 + date_delta.seconds
|
|
43
|
+
Usually you don't need to create socket server manually, because :class:`WRFRun` will handle this.
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
.. py:attribute:: start_timestamp
|
|
46
|
+
:type: datetime
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
return seconds
|
|
48
|
+
The time the socket server start.
|
|
49
49
|
|
|
50
|
+
.. py:attribute:: start_date
|
|
51
|
+
:type: datetime
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
The simulation's start date.
|
|
54
|
+
|
|
55
|
+
.. py:attribute:: wrf_simulate_seconds
|
|
56
|
+
:type: int
|
|
57
|
+
|
|
58
|
+
The total seconds the simulation will integrate.
|
|
59
|
+
|
|
60
|
+
.. py:attribute:: wrf_log_path
|
|
61
|
+
:type: str
|
|
62
|
+
|
|
63
|
+
Path of the log file the server will read.
|
|
54
64
|
"""
|
|
55
65
|
|
|
56
|
-
def __init__(self, start_date: datetime,
|
|
66
|
+
def __init__(self, start_date: datetime, total_simulate_seconds: int, *args, **kwargs) -> None:
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
:param start_date: The simulation's start date.
|
|
70
|
+
:type start_date: datetime
|
|
71
|
+
:param total_simulate_seconds: The total seconds the simulation will integrate.
|
|
72
|
+
:type total_simulate_seconds: int
|
|
73
|
+
:param args: Other positional arguments passed to parent class.
|
|
74
|
+
:type args:
|
|
75
|
+
:param kwargs: Other keyword arguments passed to parent class.
|
|
76
|
+
:type kwargs:
|
|
77
|
+
"""
|
|
57
78
|
super().__init__(*args, **kwargs)
|
|
58
79
|
|
|
59
80
|
# record the time the server starts
|
|
@@ -63,16 +84,11 @@ class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
|
|
63
84
|
self.start_date = start_date
|
|
64
85
|
|
|
65
86
|
# record how many seconds the wrf will integral
|
|
66
|
-
self.
|
|
67
|
-
|
|
68
|
-
# we need to parse the log file to track the simulation progress.
|
|
69
|
-
self.wrf_log_path = WRFRUNConfig.parse_resource_uri(f"{WRFRUNConfig.WRF_WORK_PATH}/rsl.out.0000")
|
|
70
|
-
logger.debug("WRFRun Server will try to track simulation progress with following log files:")
|
|
71
|
-
logger.debug(f"WRF: {self.wrf_log_path}")
|
|
87
|
+
self.total_simulate_seconds = total_simulate_seconds
|
|
72
88
|
|
|
73
89
|
def server_bind(self):
|
|
74
90
|
"""
|
|
75
|
-
Bind address and port.
|
|
91
|
+
Bind and listen on the address and port.
|
|
76
92
|
"""
|
|
77
93
|
# reuse address and port to prevent the error `Address already in use`
|
|
78
94
|
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
@@ -88,32 +104,84 @@ class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
|
|
88
104
|
"""
|
|
89
105
|
return self.start_timestamp
|
|
90
106
|
|
|
91
|
-
def
|
|
107
|
+
def get_model_simulate_settings(self) -> Tuple[datetime, int]:
|
|
92
108
|
"""
|
|
93
109
|
Get the start date of the case the NWP simulates and the total seconds of the simulation.
|
|
94
110
|
|
|
95
111
|
:return: (start date, simulation seconds)
|
|
96
112
|
:rtype: tuple
|
|
97
113
|
"""
|
|
98
|
-
return self.start_date, self.
|
|
114
|
+
return self.start_date, self.total_simulate_seconds
|
|
99
115
|
|
|
100
116
|
|
|
101
117
|
class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
102
118
|
"""
|
|
103
|
-
|
|
119
|
+
:class:`WRFRunServer` handler.
|
|
120
|
+
|
|
121
|
+
This handler can report time usage and simulation progress of the running model, simulation settings, and stop the server:
|
|
104
122
|
|
|
123
|
+
1. If this handler receives ``"stop"``, it stops the server.
|
|
124
|
+
2. If this handler receives ``"debug"``, it returns the simulation settings in JSON.
|
|
125
|
+
3. It returns time usage and simulation progress in JSON when receiving any other messages.
|
|
126
|
+
|
|
127
|
+
**On receiving "stop"**
|
|
128
|
+
|
|
129
|
+
This handler will stop the server, and return a plain message ``Server stop``.
|
|
130
|
+
|
|
131
|
+
**On receiving "debug"**
|
|
132
|
+
|
|
133
|
+
This handler will return simulation settings in a JSON string like:
|
|
134
|
+
|
|
135
|
+
.. code-block:: json
|
|
136
|
+
|
|
137
|
+
{
|
|
138
|
+
"start_date": "2021-03-25 00:00",
|
|
139
|
+
"total_simulate_seconds": 360000,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
**On receiving any other messages**
|
|
143
|
+
|
|
144
|
+
This handler will return time usage and simulation progress in a JSON string like:
|
|
145
|
+
|
|
146
|
+
.. code-block:: json
|
|
147
|
+
|
|
148
|
+
{
|
|
149
|
+
"usage": 3600,
|
|
150
|
+
"status": "geogrid",
|
|
151
|
+
"progress": 35,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
where ``usage`` represents the seconds ``wrfrun`` has spent running the NWP model,
|
|
155
|
+
``status`` represents work status,
|
|
156
|
+
``progress`` represents simulation progress of the status in percentage.
|
|
105
157
|
"""
|
|
106
|
-
def __init__(self, request, client_address, server: WRFRunServer) -> None:
|
|
158
|
+
def __init__(self, request, client_address, server: WRFRunServer, log_parse_func: Callable[[datetime], int] | None) -> None:
|
|
159
|
+
"""
|
|
160
|
+
:class:`WRFRunServer` handler.
|
|
161
|
+
|
|
162
|
+
:param request:
|
|
163
|
+
:type request:
|
|
164
|
+
:param client_address:
|
|
165
|
+
:type client_address:
|
|
166
|
+
:param server: :class:`WRFRunServer` instance.
|
|
167
|
+
: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
|
+
"""
|
|
107
172
|
super().__init__(request, client_address, server)
|
|
108
173
|
|
|
109
174
|
# get server
|
|
110
175
|
self.server: WRFRunServer = server
|
|
176
|
+
self.log_parse_func = log_parse_func
|
|
111
177
|
|
|
112
|
-
def calculate_time_usage(self) ->
|
|
113
|
-
"""
|
|
178
|
+
def calculate_time_usage(self) -> int:
|
|
179
|
+
"""
|
|
180
|
+
Calculate the duration from the server's start time to the present,
|
|
181
|
+
which represents the time ``wrfrun`` has spent running the NWP model.
|
|
114
182
|
|
|
115
|
-
|
|
116
|
-
|
|
183
|
+
:return: Seconds.
|
|
184
|
+
:rtype: int
|
|
117
185
|
"""
|
|
118
186
|
# get current timestamp
|
|
119
187
|
current_timestamp = datetime.fromtimestamp(time())
|
|
@@ -122,42 +190,34 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
122
190
|
seconds_diff = current_timestamp - self.server.get_start_time()
|
|
123
191
|
seconds_diff = seconds_diff.seconds
|
|
124
192
|
|
|
125
|
-
|
|
126
|
-
seconds = seconds_diff % 60
|
|
127
|
-
minutes = (seconds_diff % 3600) // 60
|
|
128
|
-
hours = seconds_diff // 3600
|
|
129
|
-
|
|
130
|
-
time_usage = ":".join([
|
|
131
|
-
str(hours).rjust(2, '0'),
|
|
132
|
-
str(minutes).rjust(2, '0'),
|
|
133
|
-
str(seconds).rjust(2, '0')
|
|
134
|
-
])
|
|
193
|
+
return seconds_diff
|
|
135
194
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def calculate_progress(self) -> str:
|
|
195
|
+
def calculate_progress(self) -> tuple[str, int]:
|
|
139
196
|
"""
|
|
140
|
-
|
|
197
|
+
Read the log file and calculate the simulation progress.
|
|
141
198
|
|
|
142
|
-
:return:
|
|
143
|
-
|
|
199
|
+
:return: ``(status, progress)``. ``status`` represents work status,
|
|
200
|
+
``progress`` represents simulation progress of the status in percentage.
|
|
201
|
+
:rtype: tuple[str, int]
|
|
144
202
|
"""
|
|
145
|
-
start_date, simulate_seconds = self.server.
|
|
203
|
+
start_date, simulate_seconds = self.server.get_model_simulate_settings()
|
|
146
204
|
|
|
147
|
-
|
|
205
|
+
if self.log_parse_func is None:
|
|
206
|
+
simulated_seconds = -1
|
|
207
|
+
else:
|
|
208
|
+
simulated_seconds = self.log_parse_func(start_date)
|
|
148
209
|
|
|
149
210
|
if simulated_seconds > 0:
|
|
150
211
|
progress = simulated_seconds * 100 // simulate_seconds
|
|
151
|
-
|
|
152
212
|
else:
|
|
153
|
-
progress =
|
|
213
|
+
progress = -1
|
|
154
214
|
|
|
155
215
|
status = WRFRUNConfig.WRFRUN_WORK_STATUS
|
|
156
216
|
|
|
157
217
|
if status == "":
|
|
158
218
|
status = "*"
|
|
159
219
|
|
|
160
|
-
return
|
|
220
|
+
return status, progress
|
|
161
221
|
|
|
162
222
|
def handle(self) -> None:
|
|
163
223
|
"""
|
|
@@ -172,25 +232,25 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
|
|
|
172
232
|
self.wfile.write(f"Server stop\n".encode())
|
|
173
233
|
|
|
174
234
|
elif msg == "debug":
|
|
175
|
-
start_date, simulate_seconds = self.server.
|
|
235
|
+
start_date, simulate_seconds = self.server.get_model_simulate_settings()
|
|
176
236
|
start_date = start_date.strftime("%Y-%m-%d %H:%M")
|
|
177
237
|
|
|
178
|
-
self.wfile.write(
|
|
238
|
+
self.wfile.write(dumps({"start_date": start_date, "total_seconds": simulate_seconds}).encode())
|
|
179
239
|
|
|
180
240
|
else:
|
|
181
|
-
progress = self.calculate_progress()
|
|
241
|
+
status, progress = self.calculate_progress()
|
|
182
242
|
time_usage = self.calculate_time_usage()
|
|
183
243
|
|
|
184
244
|
# send the message
|
|
185
|
-
self.wfile.write(
|
|
245
|
+
self.wfile.write(dumps({"usage": time_usage, "status": status, "progress": progress}).encode())
|
|
186
246
|
|
|
187
247
|
|
|
188
248
|
def stop_server(socket_ip: str, socket_port: int):
|
|
189
|
-
"""
|
|
249
|
+
"""
|
|
250
|
+
Stop the socket server.
|
|
190
251
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
socket_port (int): Server Port.
|
|
252
|
+
:param socket_ip: Server address.
|
|
253
|
+
:param socket_port: Server port.
|
|
194
254
|
"""
|
|
195
255
|
try:
|
|
196
256
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
@@ -209,4 +269,4 @@ def stop_server(socket_ip: str, socket_port: int):
|
|
|
209
269
|
logger.warning("Fail to stop WRFRunServer, maybe it doesn't start at all.")
|
|
210
270
|
|
|
211
271
|
|
|
212
|
-
__all__ = ["WRFRunServer", "WRFRunServerHandler", "
|
|
272
|
+
__all__ = ["WRFRunServer", "WRFRunServerHandler", "stop_server"]
|
wrfrun/data.py
CHANGED
|
@@ -6,12 +6,11 @@ from typing import Union, List, Tuple
|
|
|
6
6
|
from pandas import date_range
|
|
7
7
|
# from seafog import goos_sst_find_data
|
|
8
8
|
|
|
9
|
-
import cdsapi
|
|
10
|
-
|
|
11
9
|
from .core.config import WRFRUNConfig
|
|
12
10
|
from .utils import logger
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
# lazy initialize
|
|
13
|
+
CDS_CLIENT = None
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
class ERA5CONFIG:
|
|
@@ -214,6 +213,8 @@ def find_era5_data(date: Union[List[str], List[datetime]], area: Tuple[int, int,
|
|
|
214
213
|
Returns: data path
|
|
215
214
|
|
|
216
215
|
"""
|
|
216
|
+
global CDS_CLIENT
|
|
217
|
+
|
|
217
218
|
# check variables and datasets
|
|
218
219
|
if not _check_variables_and_datasets(variables, dataset):
|
|
219
220
|
logger.error(
|
|
@@ -281,8 +282,12 @@ def find_era5_data(date: Union[List[str], List[datetime]], area: Tuple[int, int,
|
|
|
281
282
|
exit(1)
|
|
282
283
|
|
|
283
284
|
# download data
|
|
284
|
-
logger.info(
|
|
285
|
-
|
|
285
|
+
logger.info(f"Downloading data to {save_path}, it may take several tens of minutes, please wait...")
|
|
286
|
+
|
|
287
|
+
if CDS_CLIENT is None:
|
|
288
|
+
import cdsapi
|
|
289
|
+
CDS_CLIENT = cdsapi.Client()
|
|
290
|
+
|
|
286
291
|
CDS_CLIENT.retrieve(dataset, params_dict, save_path)
|
|
287
292
|
|
|
288
293
|
return save_path
|
wrfrun/extension/__init__.py
CHANGED
|
@@ -1 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wrfrun.extension
|
|
3
|
+
################
|
|
4
|
+
|
|
5
|
+
Through extensions, developers can add more functionality to ``wrfrun``.
|
|
6
|
+
``wrfrun`` now has the following extensions:
|
|
7
|
+
|
|
8
|
+
========================================= ==========================================
|
|
9
|
+
:doc:`goos_sst </api/extension.goos_sst>` Process NEAR-GOOS SST data.
|
|
10
|
+
:doc:`littler </api/extension.littler>` Process LITTLE_R observation data.
|
|
11
|
+
========================================= ==========================================
|
|
12
|
+
|
|
13
|
+
Other Submodules
|
|
14
|
+
****************
|
|
15
|
+
|
|
16
|
+
=================================== ==========================================
|
|
17
|
+
:doc:`utils </api/extension.utils>` Utility methods can be used by extensions.
|
|
18
|
+
=================================== ==========================================
|
|
19
|
+
|
|
20
|
+
.. toctree::
|
|
21
|
+
:maxdepth: 1
|
|
22
|
+
:hidden:
|
|
23
|
+
|
|
24
|
+
goos_sst <extension.goos_sst>
|
|
25
|
+
littler <extension.littler>
|
|
26
|
+
utils <extension.utils>
|
|
27
|
+
"""
|
|
28
|
+
|
|
1
29
|
from .utils import *
|