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/_record.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wrfrun.core._record
|
|
3
|
+
###################
|
|
4
|
+
|
|
5
|
+
.. autosummary::
|
|
6
|
+
:toctree: generated/
|
|
7
|
+
|
|
8
|
+
ExecutableRecorder
|
|
9
|
+
|
|
10
|
+
ExecutableRecorder
|
|
11
|
+
******************
|
|
12
|
+
|
|
13
|
+
This class provides methods to record simulations.
|
|
14
|
+
It will save configs and resources of ``Executable``, and input data optionally.
|
|
15
|
+
A file ends with ``.replay`` will be generated after finishing recording,
|
|
16
|
+
and users can reproduce the simulation with the ``.replay`` file.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from json import dumps
|
|
20
|
+
from os import makedirs, remove
|
|
21
|
+
from os.path import basename, dirname, exists, isdir
|
|
22
|
+
from shutil import copyfile, make_archive, move
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
|
|
26
|
+
from ..log import check_path, logger
|
|
27
|
+
from ._config import WRFRunConfig
|
|
28
|
+
from .type import ExecutableConfig
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _json_default(obj):
|
|
32
|
+
"""
|
|
33
|
+
Used for json.dumps.
|
|
34
|
+
|
|
35
|
+
:param obj:
|
|
36
|
+
:type obj:
|
|
37
|
+
:return:
|
|
38
|
+
:rtype:
|
|
39
|
+
"""
|
|
40
|
+
if isinstance(obj, np.integer):
|
|
41
|
+
return int(obj)
|
|
42
|
+
elif isinstance(obj, np.floating):
|
|
43
|
+
return float(obj)
|
|
44
|
+
else:
|
|
45
|
+
raise TypeError(f"Object of type {type(obj)} is not JSON serializable.")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ExecutableRecorder:
|
|
49
|
+
"""
|
|
50
|
+
This class provides methods to record simulations.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, wrfrun_config: WRFRunConfig, save_path="./wrfrun.replay", include_data=False):
|
|
54
|
+
"""
|
|
55
|
+
:param wrfrun_config: `WRFRunConfig` instance.
|
|
56
|
+
:type wrfrun_config: WRFRunConfig
|
|
57
|
+
:param save_path: Save path of the replay file, defaults to "./wrfrun.replay"
|
|
58
|
+
:type save_path: str, optional
|
|
59
|
+
:param include_data: If includes data files, defaults to False
|
|
60
|
+
:type include_data: bool, optional
|
|
61
|
+
"""
|
|
62
|
+
self._wrfrun_config = wrfrun_config
|
|
63
|
+
|
|
64
|
+
self.save_path = save_path
|
|
65
|
+
self.include_data = include_data
|
|
66
|
+
|
|
67
|
+
self.work_path = self._wrfrun_config.parse_resource_uri(self._wrfrun_config.WRFRUN_WORKSPACE_REPLAY)
|
|
68
|
+
self.content_path = f"{self.work_path}/config_and_data"
|
|
69
|
+
|
|
70
|
+
self._recorded_config = []
|
|
71
|
+
self._name_count = {}
|
|
72
|
+
|
|
73
|
+
def record(self, exported_config: ExecutableConfig):
|
|
74
|
+
"""
|
|
75
|
+
Record exported config for replay.
|
|
76
|
+
|
|
77
|
+
:param exported_config: Executable config.
|
|
78
|
+
:type exported_config: ExecutableConfig
|
|
79
|
+
"""
|
|
80
|
+
if not self.include_data:
|
|
81
|
+
self._recorded_config.append(exported_config)
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
check_path(self.content_path)
|
|
85
|
+
|
|
86
|
+
# process exported config so we can also include data.
|
|
87
|
+
# create directory to place data
|
|
88
|
+
name = exported_config["name"]
|
|
89
|
+
if name in self._name_count:
|
|
90
|
+
self._name_count[name] += 1
|
|
91
|
+
index = self._name_count[name]
|
|
92
|
+
else:
|
|
93
|
+
self._name_count[name] = 1
|
|
94
|
+
index = 1
|
|
95
|
+
|
|
96
|
+
data_save_uri = f"{self._wrfrun_config.WRFRUN_WORKSPACE_REPLAY}/{name}/{index}"
|
|
97
|
+
data_save_path = f"{self.content_path}/{name}/{index}"
|
|
98
|
+
makedirs(data_save_path)
|
|
99
|
+
|
|
100
|
+
input_file_config = exported_config["input_file_config"]
|
|
101
|
+
|
|
102
|
+
for _config_index, _config in enumerate(input_file_config):
|
|
103
|
+
if not _config["is_data"]:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
if _config["is_output"]:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
file_path = _config["file_path"]
|
|
110
|
+
file_path = self._wrfrun_config.parse_resource_uri(file_path)
|
|
111
|
+
filename = basename(file_path)
|
|
112
|
+
copyfile(file_path, f"{data_save_path}/{filename}")
|
|
113
|
+
|
|
114
|
+
_config["file_path"] = f"{data_save_uri}/{filename}"
|
|
115
|
+
input_file_config[_config_index] = _config
|
|
116
|
+
|
|
117
|
+
exported_config["input_file_config"] = input_file_config
|
|
118
|
+
self._recorded_config.append(exported_config)
|
|
119
|
+
|
|
120
|
+
def clear_records(self):
|
|
121
|
+
"""
|
|
122
|
+
Clean recorded configs.
|
|
123
|
+
"""
|
|
124
|
+
self._recorded_config = []
|
|
125
|
+
|
|
126
|
+
def set_recorder(self, save_path: str | None, include_data: bool | None):
|
|
127
|
+
"""
|
|
128
|
+
Change recorder settings.
|
|
129
|
+
|
|
130
|
+
:param save_path: Save path of the exported config file.
|
|
131
|
+
:type save_path: str | None
|
|
132
|
+
:param include_data: If includes input data.
|
|
133
|
+
:type include_data: bool | None
|
|
134
|
+
"""
|
|
135
|
+
if save_path is not None:
|
|
136
|
+
logger.debug(f"Change save path to: {save_path}")
|
|
137
|
+
self.save_path = save_path
|
|
138
|
+
|
|
139
|
+
if include_data is not None:
|
|
140
|
+
self.include_data = include_data
|
|
141
|
+
|
|
142
|
+
def export_replay_file(self):
|
|
143
|
+
"""
|
|
144
|
+
Save replay file to the save path.
|
|
145
|
+
"""
|
|
146
|
+
if len(self._recorded_config) == 0:
|
|
147
|
+
logger.warning("No replay config has been recorded.")
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
logger.info("Exporting replay config... It may take a few minutes if you include data.")
|
|
151
|
+
|
|
152
|
+
check_path(self.content_path)
|
|
153
|
+
|
|
154
|
+
with open(f"{self.content_path}/config.json", "w") as f:
|
|
155
|
+
f.write(dumps(self._recorded_config, indent=4, default=_json_default))
|
|
156
|
+
|
|
157
|
+
if exists(self.save_path):
|
|
158
|
+
if isdir(self.save_path):
|
|
159
|
+
self.save_path = f"{self.save_path}/wrfrun.replay"
|
|
160
|
+
else:
|
|
161
|
+
if not self.save_path.endswith(".replay"):
|
|
162
|
+
self.save_path = f"{self.save_path}.replay"
|
|
163
|
+
|
|
164
|
+
if exists(self.save_path):
|
|
165
|
+
logger.warning(f"Found existed replay file with the same name '{basename(self.save_path)}', overwrite it")
|
|
166
|
+
remove(self.save_path)
|
|
167
|
+
|
|
168
|
+
if not exists(dirname(self.save_path)):
|
|
169
|
+
makedirs(dirname(self.save_path))
|
|
170
|
+
|
|
171
|
+
temp_file = f"{self.work_path}/config_and_data"
|
|
172
|
+
make_archive(temp_file, "zip", self.content_path)
|
|
173
|
+
move(f"{temp_file}.zip", self.save_path)
|
|
174
|
+
|
|
175
|
+
logger.info(f"Replay config exported to {self.save_path}")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
__all__ = ["ExecutableRecorder"]
|
wrfrun/core/_resource.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wrfrun.core._resource
|
|
3
|
+
#####################
|
|
4
|
+
|
|
5
|
+
.. autosummary::
|
|
6
|
+
:toctree: generated/
|
|
7
|
+
|
|
8
|
+
ResourceMixIn
|
|
9
|
+
|
|
10
|
+
ResourceMixIn
|
|
11
|
+
*************
|
|
12
|
+
|
|
13
|
+
This class provides methods to manage resources in wrfrun.
|
|
14
|
+
|
|
15
|
+
URIs in wrfrun
|
|
16
|
+
==============
|
|
17
|
+
|
|
18
|
+
``wrfrun`` uses URI (Uniform Resource Identifier) to represents real resource files,
|
|
19
|
+
to make sure the same code works on different machines.
|
|
20
|
+
|
|
21
|
+
There are some additional restrictions about URIs used in wrfrun:
|
|
22
|
+
|
|
23
|
+
* Must start with ``:WRFRUN_``
|
|
24
|
+
* Must end with ``:``
|
|
25
|
+
|
|
26
|
+
Register URIs
|
|
27
|
+
=============
|
|
28
|
+
|
|
29
|
+
There are two ways to register URI:
|
|
30
|
+
|
|
31
|
+
**Register directly**
|
|
32
|
+
|
|
33
|
+
The most convenient way to register resource uri is using :meth:`register_resource_uri <ResourceMixIn.register_resource_uri>`.
|
|
34
|
+
|
|
35
|
+
.. code-block:: Python
|
|
36
|
+
:caption: main.py
|
|
37
|
+
|
|
38
|
+
resource = ResourceMixIn()
|
|
39
|
+
resource_uri = ":WRFRUN_TEST_URI:"
|
|
40
|
+
|
|
41
|
+
# remember to check if it has been registered.
|
|
42
|
+
if not resource.check_resource_uri(resource_uri):
|
|
43
|
+
resource.register_resource_uri(resource_uri)
|
|
44
|
+
|
|
45
|
+
**Use register function**
|
|
46
|
+
|
|
47
|
+
Please see documentation about :class:`WRFRUN <wrfrun.core.core._WRFRUNProxy>`.
|
|
48
|
+
|
|
49
|
+
Parse URIs
|
|
50
|
+
==========
|
|
51
|
+
|
|
52
|
+
You can get the real file path using :meth:`parse_resource_uri <ResourceMixIn.parse_resource_uri>`,
|
|
53
|
+
it will parse all the URIs in the string.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
from ..log import logger
|
|
57
|
+
from .error import ResourceURIError
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ResourceMixIn:
|
|
61
|
+
"""
|
|
62
|
+
This class provides methods to manage resources in wrfrun.
|
|
63
|
+
|
|
64
|
+
These resources may include various configuration files from NWP as well as those provided by wrfrun itself.
|
|
65
|
+
Since their actual file paths may vary depending on the installation environment,
|
|
66
|
+
wrfrun maps them using URIs to ensure consistent access regardless of the environment.
|
|
67
|
+
|
|
68
|
+
The URI always starts with the prefix string ``:WRFRUN_`` and ends with ``:``.
|
|
69
|
+
|
|
70
|
+
To register URIs, user can use :meth:`ResourceMixIn.register_resource_uri`.
|
|
71
|
+
|
|
72
|
+
To convert any possible URIs in a string, user can use :meth:`ResourceMixIn.parse_resource_uri`.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, *args, **kwargs):
|
|
76
|
+
self._resource_namespace_db = {}
|
|
77
|
+
|
|
78
|
+
super().__init__(*args, **kwargs)
|
|
79
|
+
|
|
80
|
+
def check_resource_uri(self, unique_uri: str) -> bool:
|
|
81
|
+
"""
|
|
82
|
+
Check if the URI has been registered.
|
|
83
|
+
|
|
84
|
+
``wrfrun`` uses unique URIs to represent resource files.
|
|
85
|
+
If you want to register a URI, you need to check if it's available.
|
|
86
|
+
|
|
87
|
+
:param unique_uri: Unique URI represents the resource.
|
|
88
|
+
:type unique_uri: str
|
|
89
|
+
:return: True or False.
|
|
90
|
+
:rtype: bool
|
|
91
|
+
"""
|
|
92
|
+
if unique_uri in self._resource_namespace_db:
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
else:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
def register_resource_uri(self, unique_uri: str, res_space_path: str):
|
|
99
|
+
"""
|
|
100
|
+
Register a resource path with a URI.
|
|
101
|
+
The URI should start with ``:WRFRUN_`` ,end with ``:`` and hasn't been registered yet,
|
|
102
|
+
otherwise an exception :class:`ResourceURIError <wrfrun.core.error.ResourceURIError>` will be raised.
|
|
103
|
+
|
|
104
|
+
:param unique_uri: Unique URI represents the resource.
|
|
105
|
+
It must start with ``:WRFRUN_`` and end with ``:``. For example, ``":WRFRUN_WORK_PATH:"``.
|
|
106
|
+
:type unique_uri: str
|
|
107
|
+
:param res_space_path: REAL absolute path of your resource path. For example, "$HOME/.config/wrfrun/res".
|
|
108
|
+
:type res_space_path: str
|
|
109
|
+
"""
|
|
110
|
+
if not (unique_uri.startswith(":WRFRUN_") and unique_uri.endswith(":")):
|
|
111
|
+
logger.error(f"Can't register resource URI: '{unique_uri}'. It should start with ':WRFRUN_' and end with ':'.")
|
|
112
|
+
raise ResourceURIError(
|
|
113
|
+
f"Can't register resource URI: '{unique_uri}'. It should start with ':WRFRUN_' and end with ':'."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if unique_uri in self._resource_namespace_db:
|
|
117
|
+
logger.error(f"Resource URI '{unique_uri}' exists.")
|
|
118
|
+
raise ResourceURIError(f"Resource URI '{unique_uri}' exists.")
|
|
119
|
+
|
|
120
|
+
logger.debug(f"Register URI '{unique_uri}' to '{res_space_path}'")
|
|
121
|
+
self._resource_namespace_db[unique_uri] = res_space_path
|
|
122
|
+
|
|
123
|
+
def unregister_resource_uri(self, unique_uri: str):
|
|
124
|
+
"""
|
|
125
|
+
Unregister a resource URI.
|
|
126
|
+
|
|
127
|
+
:param unique_uri: Unique URI represents the resource.
|
|
128
|
+
:type unique_uri: str
|
|
129
|
+
"""
|
|
130
|
+
if unique_uri in self._resource_namespace_db:
|
|
131
|
+
self._resource_namespace_db.pop(unique_uri)
|
|
132
|
+
|
|
133
|
+
def parse_resource_uri(self, resource_path: str) -> str:
|
|
134
|
+
"""
|
|
135
|
+
Return the converted string by parsing the URI string in it.
|
|
136
|
+
Normal path will be returned with no change.
|
|
137
|
+
|
|
138
|
+
If the URI hasn't been registered, an exception :class:`ResourceURIError` will be raised.
|
|
139
|
+
|
|
140
|
+
For example, you can get the real path of ``wrfrun`` workspace with this method:
|
|
141
|
+
|
|
142
|
+
>>> from wrfrun.core import WRFRUN
|
|
143
|
+
>>> workspace_path = f"{WRFRUN.config.WRFRUN_WORKSPACE_ROOT}/WPS" # ":WRFRUN_WORKSPACE_PATH:/WPS"
|
|
144
|
+
>>> # real_path should be a valid path like: "/home/syize/.config/wrfrun/workspace/WPS"
|
|
145
|
+
>>> real_path = WRFRUN.config.parse_resource_uri(workspace_path)
|
|
146
|
+
|
|
147
|
+
:param resource_path: Resource path string which may contain URI string.
|
|
148
|
+
:type resource_path: str
|
|
149
|
+
:return: Real resource path.
|
|
150
|
+
:rtype: str
|
|
151
|
+
"""
|
|
152
|
+
if not resource_path.startswith(":WRFRUN_"):
|
|
153
|
+
return resource_path
|
|
154
|
+
|
|
155
|
+
res_namespace_string = resource_path.split(":")[1]
|
|
156
|
+
res_namespace_string = f":{res_namespace_string}:"
|
|
157
|
+
|
|
158
|
+
if res_namespace_string in self._resource_namespace_db:
|
|
159
|
+
resource_path = resource_path.replace(res_namespace_string, self._resource_namespace_db[res_namespace_string])
|
|
160
|
+
|
|
161
|
+
if not resource_path.startswith(":WRFRUN_"):
|
|
162
|
+
return resource_path
|
|
163
|
+
|
|
164
|
+
else:
|
|
165
|
+
return self.parse_resource_uri(resource_path)
|
|
166
|
+
|
|
167
|
+
else:
|
|
168
|
+
logger.error(f"Unknown resource URI: '{res_namespace_string}'")
|
|
169
|
+
raise ResourceURIError(f"Unknown resource URI: '{res_namespace_string}'")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
__all__ = ["ResourceMixIn"]
|