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/_constant.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wrfrun.core._constant
|
|
3
|
+
#####################
|
|
4
|
+
|
|
5
|
+
.. autosummary::
|
|
6
|
+
:toctree: generated/
|
|
7
|
+
|
|
8
|
+
ConstantMixIn
|
|
9
|
+
|
|
10
|
+
ConstantMixIn
|
|
11
|
+
*************
|
|
12
|
+
|
|
13
|
+
This class provides methods to manage runtime constants of ``wrfrun``.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from os import environ
|
|
17
|
+
from os.path import abspath
|
|
18
|
+
from sys import platform
|
|
19
|
+
|
|
20
|
+
from ..log import logger
|
|
21
|
+
from .error import WRFRunContextError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ConstantMixIn:
|
|
25
|
+
"""
|
|
26
|
+
Define all variables that will be used by other components.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, work_dir: str, *args, **kwargs):
|
|
30
|
+
"""
|
|
31
|
+
Define all variables that will be used by other components.
|
|
32
|
+
|
|
33
|
+
These variables are related to ``wrfrun`` installation environments, configuration files and more.
|
|
34
|
+
They are defined either directly or mapped using URIs to ensure consistent access across all components.
|
|
35
|
+
|
|
36
|
+
:param work_dir: ``wrfrun`` work directory path.
|
|
37
|
+
:type work_dir: str
|
|
38
|
+
"""
|
|
39
|
+
# check system
|
|
40
|
+
if platform != "linux":
|
|
41
|
+
logger.debug("Not Linux system!")
|
|
42
|
+
|
|
43
|
+
if work_dir != "" or platform != "linux":
|
|
44
|
+
# set temporary dir path
|
|
45
|
+
self._WRFRUN_TEMP_PATH = abspath(f"{work_dir}/tmp")
|
|
46
|
+
self._WRFRUN_HOME_PATH = abspath(work_dir)
|
|
47
|
+
|
|
48
|
+
else:
|
|
49
|
+
# the path we may need to store temp files,
|
|
50
|
+
# don't worry, it will be deleted once the system reboots
|
|
51
|
+
self._WRFRUN_TEMP_PATH = "/tmp/wrfrun"
|
|
52
|
+
user_home_path = f"{environ['HOME']}"
|
|
53
|
+
|
|
54
|
+
# WRF may need a large disk space to store output, we can't run wrf in /tmp,
|
|
55
|
+
# so we will create a folder in $HOME/.config to run wrf.
|
|
56
|
+
# we need to check if we're running as a root user
|
|
57
|
+
if user_home_path in ["/", "/root", ""]:
|
|
58
|
+
logger.warning(
|
|
59
|
+
f"User's home path is '{user_home_path}', which means you are running this program as a root user"
|
|
60
|
+
)
|
|
61
|
+
logger.warning("It's not recommended to use wrfrun as a root user")
|
|
62
|
+
logger.warning("Set user_home_path as /root")
|
|
63
|
+
user_home_path = "/root"
|
|
64
|
+
|
|
65
|
+
self._WRFRUN_HOME_PATH = f"{user_home_path}/.config/wrfrun"
|
|
66
|
+
|
|
67
|
+
# workspace root path
|
|
68
|
+
self._WRFRUN_WORKSPACE_ROOT = f"{self._WRFRUN_HOME_PATH}/workspace"
|
|
69
|
+
self._WRFRUN_WORKSPACE_MODEL = f"{self._WRFRUN_WORKSPACE_ROOT}/model"
|
|
70
|
+
self._WRFRUN_WORKSPACE_REPLAY = f"{self._WRFRUN_WORKSPACE_ROOT}/replay"
|
|
71
|
+
|
|
72
|
+
# record WRF progress status
|
|
73
|
+
self._WRFRUN_WORK_STATUS = ""
|
|
74
|
+
|
|
75
|
+
# record context status
|
|
76
|
+
self._WRFRUN_CONTEXT_STATUS = False
|
|
77
|
+
|
|
78
|
+
self._WRFRUN_OUTPUT_PATH = ":WRFRUN_OUTPUT_PATH:"
|
|
79
|
+
self._WRFRUN_RESOURCE_PATH = ":WRFRUN_RESOURCE_PATH:"
|
|
80
|
+
|
|
81
|
+
self.IS_IN_REPLAY: bool = False
|
|
82
|
+
|
|
83
|
+
self.IS_RECORDING: bool = False
|
|
84
|
+
|
|
85
|
+
# in this mode, wrfrun will do all things except call the numerical model.
|
|
86
|
+
# all output rules will also not be executed.
|
|
87
|
+
self.FAKE_SIMULATION_MODE = False
|
|
88
|
+
|
|
89
|
+
super().__init__(*args, **kwargs)
|
|
90
|
+
|
|
91
|
+
def _get_uri_map(self) -> dict[str, str]:
|
|
92
|
+
"""
|
|
93
|
+
Return URIs and their values.
|
|
94
|
+
``wrfrun`` will use this to register uri when initialize config.
|
|
95
|
+
|
|
96
|
+
:return: A dict in which URIs are keys and their values are dictionary values.
|
|
97
|
+
:rtype: dict
|
|
98
|
+
"""
|
|
99
|
+
return {
|
|
100
|
+
self.WRFRUN_TEMP_PATH: self._WRFRUN_TEMP_PATH,
|
|
101
|
+
self.WRFRUN_HOME_PATH: self._WRFRUN_HOME_PATH,
|
|
102
|
+
self.WRFRUN_WORKSPACE_ROOT: self._WRFRUN_WORKSPACE_ROOT,
|
|
103
|
+
self.WRFRUN_WORKSPACE_MODEL: self._WRFRUN_WORKSPACE_MODEL,
|
|
104
|
+
self.WRFRUN_WORKSPACE_REPLAY: self._WRFRUN_WORKSPACE_REPLAY,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def WRFRUN_WORKSPACE_REPLAY(self) -> str:
|
|
109
|
+
"""
|
|
110
|
+
Path (URI) to store related files of ``wrfrun`` replay functionality.
|
|
111
|
+
|
|
112
|
+
:return: URI.
|
|
113
|
+
:rtype: str
|
|
114
|
+
"""
|
|
115
|
+
return ":WRFRUN_WORKSPACE_REPLAY:"
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def WRFRUN_TEMP_PATH(self) -> str:
|
|
119
|
+
"""
|
|
120
|
+
Path to store ``wrfrun`` temporary files.
|
|
121
|
+
|
|
122
|
+
:return: URI
|
|
123
|
+
:rtype: str
|
|
124
|
+
"""
|
|
125
|
+
return ":WRFRUN_TEMP_PATH:"
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def WRFRUN_HOME_PATH(self) -> str:
|
|
129
|
+
"""
|
|
130
|
+
Root path of all others directories. .
|
|
131
|
+
|
|
132
|
+
:return: URI
|
|
133
|
+
:rtype: str
|
|
134
|
+
"""
|
|
135
|
+
return ":WRFRUN_HOME_PATH:"
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def WRFRUN_WORKSPACE_ROOT(self) -> str:
|
|
139
|
+
"""
|
|
140
|
+
Path of the root workspace.
|
|
141
|
+
|
|
142
|
+
:return: URI
|
|
143
|
+
:rtype: str
|
|
144
|
+
"""
|
|
145
|
+
return ":WRFRUN_WORKSPACE_ROOT:"
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def WRFRUN_WORKSPACE_MODEL(self) -> str:
|
|
149
|
+
"""
|
|
150
|
+
Path of the model workspace, in which ``wrfrun`` runs numerical models.
|
|
151
|
+
|
|
152
|
+
:return: URI
|
|
153
|
+
:rtype: str
|
|
154
|
+
"""
|
|
155
|
+
return ":WRFRUN_WORKSPACE_MODEL:"
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def WRFRUN_WORK_STATUS(self) -> str:
|
|
159
|
+
"""
|
|
160
|
+
``wrfrun`` work status.
|
|
161
|
+
|
|
162
|
+
This attribute can be changed by ``Executable`` to reflect the current work progress of ``wrfrun``.
|
|
163
|
+
The returned string is the name of ``Executable``.
|
|
164
|
+
|
|
165
|
+
:return: A string reflect the current work progress.
|
|
166
|
+
:rtype: str
|
|
167
|
+
"""
|
|
168
|
+
return self._WRFRUN_WORK_STATUS
|
|
169
|
+
|
|
170
|
+
@WRFRUN_WORK_STATUS.setter
|
|
171
|
+
def WRFRUN_WORK_STATUS(self, value: str):
|
|
172
|
+
"""
|
|
173
|
+
Set ``wrfrun`` work status.
|
|
174
|
+
|
|
175
|
+
``wrfrun`` recommends ``Executable`` set the status string with their name,
|
|
176
|
+
so to avoid the possible conflicts with other ``Executable``,
|
|
177
|
+
and the user can easily understand the current work progress.
|
|
178
|
+
|
|
179
|
+
:param value: A string represents the work status.
|
|
180
|
+
:type value: str
|
|
181
|
+
"""
|
|
182
|
+
self._WRFRUN_WORK_STATUS = value
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def WRFRUN_OUTPUT_PATH(self) -> str:
|
|
186
|
+
"""
|
|
187
|
+
The root path to store all outputs of the ``wrfrun`` and NWP model.
|
|
188
|
+
|
|
189
|
+
:return: URI
|
|
190
|
+
:rtype: str
|
|
191
|
+
"""
|
|
192
|
+
return self._WRFRUN_OUTPUT_PATH
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def WRFRUN_RESOURCE_PATH(self) -> str:
|
|
196
|
+
"""
|
|
197
|
+
The root path of all ``wrfrun`` resource files.
|
|
198
|
+
|
|
199
|
+
:return: URI
|
|
200
|
+
:rtype: str
|
|
201
|
+
"""
|
|
202
|
+
return self._WRFRUN_RESOURCE_PATH
|
|
203
|
+
|
|
204
|
+
def check_wrfrun_context(self, error=False) -> bool:
|
|
205
|
+
"""
|
|
206
|
+
Check if in WRFRun context or not.
|
|
207
|
+
|
|
208
|
+
:param error: An exception :class:`WRFRunContextError` will be raised
|
|
209
|
+
if ``error==True`` when we are not in WRFRun context.
|
|
210
|
+
:type error: bool
|
|
211
|
+
:return: True or False.
|
|
212
|
+
:rtype: bool
|
|
213
|
+
"""
|
|
214
|
+
if self._WRFRUN_CONTEXT_STATUS:
|
|
215
|
+
return self._WRFRUN_CONTEXT_STATUS
|
|
216
|
+
|
|
217
|
+
if not error:
|
|
218
|
+
logger.warning(
|
|
219
|
+
"You are using wrfrun without entering `WRFRun` context, which may cause some functions don't work."
|
|
220
|
+
)
|
|
221
|
+
return self._WRFRUN_CONTEXT_STATUS
|
|
222
|
+
|
|
223
|
+
logger.error("You need to enter `WRFRun` context to use wrfrun.")
|
|
224
|
+
raise WRFRunContextError("You need to enter `WRFRun` context to use wrfrun.")
|
|
225
|
+
|
|
226
|
+
def set_wrfrun_context(self, status: bool):
|
|
227
|
+
"""
|
|
228
|
+
Change ``WRFRun`` context status to True or False.
|
|
229
|
+
|
|
230
|
+
:param status: ``True`` or ``False``.
|
|
231
|
+
:type status: bool
|
|
232
|
+
"""
|
|
233
|
+
self._WRFRUN_CONTEXT_STATUS = status
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
__all__ = ["ConstantMixIn"]
|
wrfrun/core/_exec_db.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wrfrun.core._exec_db
|
|
3
|
+
####################
|
|
4
|
+
|
|
5
|
+
.. autosummary::
|
|
6
|
+
:toctree: generated/
|
|
7
|
+
|
|
8
|
+
ExecutableDB
|
|
9
|
+
|
|
10
|
+
ExecutableDB
|
|
11
|
+
************
|
|
12
|
+
|
|
13
|
+
To be able to replay simulations, ``wrfrun`` use this class to store ``Executable`` information.
|
|
14
|
+
When replaying simulations, ``wrfrun`` gets the corresponding ``Executable`` and calls it with its name.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Callable
|
|
18
|
+
|
|
19
|
+
from ..log import logger
|
|
20
|
+
from .error import ExecRegisterError, GetExecClassError
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ExecutableDB:
|
|
24
|
+
"""
|
|
25
|
+
This class provides the method to records ``Executable`` class with a unique ``name``.
|
|
26
|
+
Later you can get the class with the ``name``.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self._exec_db = {}
|
|
31
|
+
|
|
32
|
+
def apply_register_func(self, func_list: list[Callable[["ExecutableDB"], None]]):
|
|
33
|
+
"""
|
|
34
|
+
Call register function provided by other submodules.
|
|
35
|
+
|
|
36
|
+
This method allows other submodules register ``Executable`` even after the whole wrfrun has been initialized.
|
|
37
|
+
|
|
38
|
+
:param func_list: A list contains register functions.
|
|
39
|
+
:type func_list: list[Callable[["WRFRunExecutableRegisterCenter"], None]]
|
|
40
|
+
"""
|
|
41
|
+
while len(func_list) != 0:
|
|
42
|
+
_func = func_list.pop(0)
|
|
43
|
+
_func(self)
|
|
44
|
+
|
|
45
|
+
def register_exec(self, name: str, cls: type):
|
|
46
|
+
"""
|
|
47
|
+
Register an ``Executable`` with a unique ``name``.
|
|
48
|
+
|
|
49
|
+
If the ``name`` has been used, :class:`ExecRegisterError <wrfrun.core.error.ExecRegisterError>` will be raised.
|
|
50
|
+
|
|
51
|
+
:param name: Unique name.
|
|
52
|
+
:type name: str
|
|
53
|
+
:param cls: ``Executable`` class.
|
|
54
|
+
:type cls: type
|
|
55
|
+
"""
|
|
56
|
+
if name in self._exec_db:
|
|
57
|
+
logger.error(f"'{name}' has been registered.")
|
|
58
|
+
raise ExecRegisterError(f"'{name}' has been registered.")
|
|
59
|
+
|
|
60
|
+
self._exec_db[name] = cls
|
|
61
|
+
|
|
62
|
+
def unregister_exec(self, name: str):
|
|
63
|
+
"""
|
|
64
|
+
Unregister an ``Executable``.
|
|
65
|
+
|
|
66
|
+
:param name: Unique name.
|
|
67
|
+
:type name: str
|
|
68
|
+
"""
|
|
69
|
+
if name in self._exec_db:
|
|
70
|
+
logger.debug(f"Unregister Executable: '{name}'.")
|
|
71
|
+
self._exec_db.pop(name)
|
|
72
|
+
|
|
73
|
+
def is_registered(self, name: str) -> bool:
|
|
74
|
+
"""
|
|
75
|
+
Check if an ``Executable`` has been registered.
|
|
76
|
+
|
|
77
|
+
:param name: Unique name.
|
|
78
|
+
:type name: str
|
|
79
|
+
:return: True or False.
|
|
80
|
+
:rtype: bool
|
|
81
|
+
"""
|
|
82
|
+
if name in self._exec_db:
|
|
83
|
+
return True
|
|
84
|
+
else:
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
def get_cls(self, name: str) -> type:
|
|
88
|
+
"""
|
|
89
|
+
Get an ``Executable`` class with the ``name``.
|
|
90
|
+
|
|
91
|
+
If the ``name`` can't be found, :class:`ExecRegisterError <wrfrun.core.error.ExecRegisterError>` will be raised.
|
|
92
|
+
|
|
93
|
+
:param name: Unique name.
|
|
94
|
+
:type name: str
|
|
95
|
+
:return: ``Executable`` class.
|
|
96
|
+
:rtype: type
|
|
97
|
+
"""
|
|
98
|
+
if name not in self._exec_db:
|
|
99
|
+
logger.error(f"Executable class '{name}' not found.")
|
|
100
|
+
raise GetExecClassError(f"Executable class '{name}' not found.")
|
|
101
|
+
|
|
102
|
+
return self._exec_db[name]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
__all__ = ["ExecutableDB"]
|
wrfrun/core/_namelist.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wrfrun.core._namelist
|
|
3
|
+
#####################
|
|
4
|
+
|
|
5
|
+
.. autosummary::
|
|
6
|
+
:toctree: generated/
|
|
7
|
+
|
|
8
|
+
NamelistMixIn
|
|
9
|
+
|
|
10
|
+
NamelistMixIn
|
|
11
|
+
*************
|
|
12
|
+
|
|
13
|
+
This class provides methods to manage namelist settings of NWP models.
|
|
14
|
+
It can read namelist files, change namelist values or write values to a namelist file.
|
|
15
|
+
|
|
16
|
+
Namelist values are stored with a unique ``namelist_id``,
|
|
17
|
+
which enables wrfrun to manage multiple namelist settings simultaneously.
|
|
18
|
+
|
|
19
|
+
**Read namelist file**
|
|
20
|
+
|
|
21
|
+
.. code-block:: Python
|
|
22
|
+
:caption: main.py
|
|
23
|
+
|
|
24
|
+
namelist = NamelistMixIn()
|
|
25
|
+
namelist_file_path = "./namelist.wps"
|
|
26
|
+
namelist_id = "wps"
|
|
27
|
+
|
|
28
|
+
# remember to check namelist id first
|
|
29
|
+
if not namelist.check_namelist_id(namelist_id):
|
|
30
|
+
namelist.register_namelist_id(namelist_id)
|
|
31
|
+
|
|
32
|
+
# read namelist file
|
|
33
|
+
namelist.read_namelist(namelist_file_path, namelist_id)
|
|
34
|
+
|
|
35
|
+
**Write namelist file**
|
|
36
|
+
|
|
37
|
+
.. code-block:: Python
|
|
38
|
+
:caption: main.py
|
|
39
|
+
|
|
40
|
+
namelist_file_path = "./namelist.wps"
|
|
41
|
+
namelist_id = "wps"
|
|
42
|
+
|
|
43
|
+
# will overwrite existing file by default.
|
|
44
|
+
namelist.write_namelist(namelist_file_path, namelist_id)
|
|
45
|
+
# doesn't overwrite existing file
|
|
46
|
+
namelist.write_namelist(namelist_file_path, namelist_id, overwrite=False)
|
|
47
|
+
|
|
48
|
+
**Update namelist values**
|
|
49
|
+
|
|
50
|
+
1. You can provide a whole namelist file
|
|
51
|
+
|
|
52
|
+
.. code-block:: Python
|
|
53
|
+
:caption: main.py
|
|
54
|
+
|
|
55
|
+
namelist_file_path = "./namelist.wps"
|
|
56
|
+
namelist_id = "wps"
|
|
57
|
+
|
|
58
|
+
namelist.update_namelist(namelist_file_path, namelist_id)
|
|
59
|
+
|
|
60
|
+
2. Or just some values in a dictionary
|
|
61
|
+
|
|
62
|
+
.. code-block:: Python
|
|
63
|
+
:caption: main.py
|
|
64
|
+
|
|
65
|
+
namelist_value = {"share": {"max_dom": 1}}
|
|
66
|
+
namelist_id = "wps"
|
|
67
|
+
|
|
68
|
+
namelist.update_namelist(namelist_value, namelist_id)
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
from copy import deepcopy
|
|
72
|
+
from os.path import exists
|
|
73
|
+
from typing import Union
|
|
74
|
+
|
|
75
|
+
import f90nml
|
|
76
|
+
|
|
77
|
+
from ..log import logger
|
|
78
|
+
from .error import NamelistError, NamelistIDError
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class NamelistMixIn:
|
|
82
|
+
"""
|
|
83
|
+
Manage namelist settings of NWP models.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(self, *args, **kwargs):
|
|
87
|
+
self._namelist_dict = {}
|
|
88
|
+
self._namelist_id_list: tuple[str, ...] = ("param", "geog_static_data", "wps", "wrf", "wrfda", "palm")
|
|
89
|
+
|
|
90
|
+
super().__init__(*args, **kwargs)
|
|
91
|
+
|
|
92
|
+
def register_namelist_id(self, namelist_id: str) -> bool:
|
|
93
|
+
"""
|
|
94
|
+
Register a unique ``namelist_id`` so you can read, update and write namelist with it later.
|
|
95
|
+
|
|
96
|
+
:param namelist_id: A unique namelist id.
|
|
97
|
+
:type namelist_id: str
|
|
98
|
+
:return: True if register successfully, else False.
|
|
99
|
+
:rtype: bool
|
|
100
|
+
"""
|
|
101
|
+
if namelist_id in self._namelist_id_list:
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
else:
|
|
105
|
+
self._namelist_id_list += (namelist_id,)
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
def unregister_namelist_id(self, namelist_id: str):
|
|
109
|
+
"""
|
|
110
|
+
Unregister a ``namelist_id``.
|
|
111
|
+
If unregister successfully, all values of this namelist will be deleted.
|
|
112
|
+
|
|
113
|
+
:param namelist_id: A unique namelist id.
|
|
114
|
+
:type namelist_id: str
|
|
115
|
+
"""
|
|
116
|
+
if namelist_id not in self._namelist_id_list:
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
self.delete_namelist(namelist_id)
|
|
120
|
+
self._namelist_id_list = tuple(
|
|
121
|
+
set(self._namelist_id_list)
|
|
122
|
+
- {
|
|
123
|
+
namelist_id,
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def check_namelist_id(self, namelist_id: str) -> bool:
|
|
128
|
+
"""
|
|
129
|
+
Check if a ``namelist_id`` is registered.
|
|
130
|
+
|
|
131
|
+
:param namelist_id: A ``namelist_id``.
|
|
132
|
+
:type namelist_id: str
|
|
133
|
+
:return: True if the ``namelist_id`` is registered, else False.
|
|
134
|
+
:rtype: bool
|
|
135
|
+
"""
|
|
136
|
+
if namelist_id in self._namelist_id_list:
|
|
137
|
+
return True
|
|
138
|
+
else:
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
def read_namelist(self, file_path: str, namelist_id: str):
|
|
142
|
+
"""
|
|
143
|
+
Read namelist values from a file and store them with the ``namelist_id``.
|
|
144
|
+
|
|
145
|
+
If ``wrfrun`` can't read the file, :class:`FileNotFoundError` will be raised.
|
|
146
|
+
If ``namelist_id`` isn't registered, :class:`NamelistIDError <wrfrun.core.error.NamelistIDError>` will be raised.
|
|
147
|
+
|
|
148
|
+
:param file_path: Namelist file path.
|
|
149
|
+
:type file_path: str
|
|
150
|
+
:param namelist_id: Registered ``namelist_id``.
|
|
151
|
+
:type namelist_id: str
|
|
152
|
+
"""
|
|
153
|
+
# check the file path
|
|
154
|
+
if not exists(file_path):
|
|
155
|
+
logger.error(f"File not found: {file_path}")
|
|
156
|
+
raise FileNotFoundError
|
|
157
|
+
|
|
158
|
+
if namelist_id not in self._namelist_id_list:
|
|
159
|
+
logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
|
|
160
|
+
raise NamelistIDError(f"Unknown namelist id: {namelist_id}, register it first.")
|
|
161
|
+
|
|
162
|
+
self._namelist_dict[namelist_id] = f90nml.read(file_path).todict()
|
|
163
|
+
|
|
164
|
+
def write_namelist(self, save_path: str, namelist_id: str, overwrite=True):
|
|
165
|
+
"""
|
|
166
|
+
Write namelist values of a ``namelist_id`` to a file.
|
|
167
|
+
|
|
168
|
+
If ``namelist_id`` hasn't been registered, or its namelist value can't be found,
|
|
169
|
+
:class:`NamelistIDError <wrfrun.core.error.NamelistIDError>` will be raised.
|
|
170
|
+
|
|
171
|
+
:param save_path: Target file path.
|
|
172
|
+
:type save_path: str
|
|
173
|
+
:param namelist_id: Registered ``namelist_id``.
|
|
174
|
+
:type namelist_id: str
|
|
175
|
+
:param overwrite: If overwrite the existed file.
|
|
176
|
+
:type overwrite: bool
|
|
177
|
+
"""
|
|
178
|
+
if namelist_id not in self._namelist_id_list:
|
|
179
|
+
logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
|
|
180
|
+
raise NamelistIDError(f"Unknown namelist id: {namelist_id}, register it first.")
|
|
181
|
+
|
|
182
|
+
if namelist_id not in self._namelist_dict:
|
|
183
|
+
logger.error(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
|
|
184
|
+
raise NamelistError(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
|
|
185
|
+
|
|
186
|
+
f90nml.Namelist(self._namelist_dict[namelist_id]).write(save_path, force=overwrite)
|
|
187
|
+
|
|
188
|
+
def update_namelist(self, new_values: Union[str, dict], namelist_id: str):
|
|
189
|
+
"""
|
|
190
|
+
Update namelist values of a ``namelist_id``.
|
|
191
|
+
|
|
192
|
+
You can give the path of a whole namelist file or a file only contains values you want to change.
|
|
193
|
+
|
|
194
|
+
>>> from wrfrun.core import WRFRUN
|
|
195
|
+
>>> namelist_file = "./namelist.wps"
|
|
196
|
+
>>> WRFRUN.config.update_namelist(namelist_file, namelist_id="wps")
|
|
197
|
+
|
|
198
|
+
>>> namelist_file = "./namelist.wrf"
|
|
199
|
+
>>> WRFRUN.config.update_namelist(namelist_file, namelist_id="wrf")
|
|
200
|
+
|
|
201
|
+
You can also give a dictionary contains values you want to change.
|
|
202
|
+
|
|
203
|
+
>>> namelist_values = {"ungrib": {"prefix": "./output/FILE"}}
|
|
204
|
+
>>> WRFRUN.config.update_namelist(namelist_values, namelist_id="wps")
|
|
205
|
+
|
|
206
|
+
>>> namelist_values = {"time_control": {"debug_level": 100}}
|
|
207
|
+
>>> WRFRUN.config.update_namelist(namelist_values, namelist_id="wrf")
|
|
208
|
+
|
|
209
|
+
:param new_values: The path of a namelist file, or a dict contains namelist values.
|
|
210
|
+
:type new_values: str | dict
|
|
211
|
+
:param namelist_id: Registered ``namelist_id``.
|
|
212
|
+
:type namelist_id: str
|
|
213
|
+
"""
|
|
214
|
+
if namelist_id not in self._namelist_id_list:
|
|
215
|
+
logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
|
|
216
|
+
raise NamelistIDError(f"Unknown namelist id: {namelist_id}, register it first.")
|
|
217
|
+
|
|
218
|
+
elif namelist_id not in self._namelist_dict:
|
|
219
|
+
self._namelist_dict[namelist_id] = new_values
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
else:
|
|
223
|
+
reference = self._namelist_dict[namelist_id]
|
|
224
|
+
|
|
225
|
+
if isinstance(new_values, str):
|
|
226
|
+
if not exists(new_values):
|
|
227
|
+
logger.error(f"File not found: {new_values}")
|
|
228
|
+
raise FileNotFoundError(f"File not found: {new_values}")
|
|
229
|
+
new_values = f90nml.read(new_values).todict()
|
|
230
|
+
|
|
231
|
+
for key in new_values:
|
|
232
|
+
if key in reference:
|
|
233
|
+
reference[key].update(new_values[key])
|
|
234
|
+
else:
|
|
235
|
+
reference[key] = new_values[key]
|
|
236
|
+
|
|
237
|
+
def get_namelist(self, namelist_id: str) -> dict:
|
|
238
|
+
"""
|
|
239
|
+
Get namelist values of a ``namelist_id``.
|
|
240
|
+
|
|
241
|
+
:param namelist_id: Registered ``namelist_id``.
|
|
242
|
+
:type namelist_id: str
|
|
243
|
+
:return: A dictionary which contains namelist values.
|
|
244
|
+
:rtype: dict
|
|
245
|
+
"""
|
|
246
|
+
if namelist_id not in self._namelist_id_list:
|
|
247
|
+
logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
|
|
248
|
+
raise NamelistIDError(f"Unknown namelist id: {namelist_id}, register it first.")
|
|
249
|
+
elif namelist_id not in self._namelist_dict:
|
|
250
|
+
logger.error(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
|
|
251
|
+
raise NamelistError(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
|
|
252
|
+
else:
|
|
253
|
+
return deepcopy(self._namelist_dict[namelist_id])
|
|
254
|
+
|
|
255
|
+
def delete_namelist(self, namelist_id: str):
|
|
256
|
+
"""
|
|
257
|
+
Delete namelist values of a ``namelist_id``.
|
|
258
|
+
|
|
259
|
+
:param namelist_id: Registered ``namelist_id``.
|
|
260
|
+
:type namelist_id: str
|
|
261
|
+
"""
|
|
262
|
+
if namelist_id not in self._namelist_id_list:
|
|
263
|
+
logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
|
|
264
|
+
raise ValueError(f"Unknown namelist id: {namelist_id}, register it first.")
|
|
265
|
+
|
|
266
|
+
if namelist_id not in self._namelist_dict:
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
self._namelist_dict.pop(namelist_id)
|
|
270
|
+
|
|
271
|
+
def check_namelist(self, namelist_id: str) -> bool:
|
|
272
|
+
"""
|
|
273
|
+
Check if a namelist has been registered and loaded.
|
|
274
|
+
|
|
275
|
+
:param namelist_id: Registered ``namelist_id``.
|
|
276
|
+
:type namelist_id: str
|
|
277
|
+
:return: ``True`` if it is registered and loaded, else ``False``.
|
|
278
|
+
:rtype: bool
|
|
279
|
+
"""
|
|
280
|
+
if namelist_id in self._namelist_id_list and namelist_id in self._namelist_dict:
|
|
281
|
+
return True
|
|
282
|
+
|
|
283
|
+
else:
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
__all__ = ["NamelistMixIn"]
|