wrfrun 0.2.0__py3-none-any.whl → 0.3.1__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.
Files changed (66) hide show
  1. wrfrun/__init__.py +8 -3
  2. wrfrun/cli.py +69 -29
  3. wrfrun/core/__init__.py +27 -10
  4. wrfrun/core/_config.py +308 -0
  5. wrfrun/core/_constant.py +236 -0
  6. wrfrun/core/_exec_db.py +105 -0
  7. wrfrun/core/_namelist.py +287 -0
  8. wrfrun/core/_record.py +178 -0
  9. wrfrun/core/_resource.py +172 -0
  10. wrfrun/core/base.py +132 -406
  11. wrfrun/core/core.py +196 -0
  12. wrfrun/core/error.py +28 -2
  13. wrfrun/core/replay.py +10 -96
  14. wrfrun/core/server.py +52 -27
  15. wrfrun/core/type.py +171 -0
  16. wrfrun/data.py +304 -139
  17. wrfrun/extension/goos_sst/__init__.py +2 -2
  18. wrfrun/extension/goos_sst/core.py +9 -14
  19. wrfrun/extension/goos_sst/res/__init__.py +0 -1
  20. wrfrun/extension/goos_sst/utils.py +50 -44
  21. wrfrun/extension/littler/core.py +105 -88
  22. wrfrun/extension/utils.py +4 -3
  23. wrfrun/log.py +117 -0
  24. wrfrun/model/__init__.py +11 -7
  25. wrfrun/model/constants.py +52 -0
  26. wrfrun/model/palm/__init__.py +30 -0
  27. wrfrun/model/palm/core.py +145 -0
  28. wrfrun/model/palm/namelist.py +33 -0
  29. wrfrun/model/plot.py +99 -119
  30. wrfrun/model/type.py +116 -0
  31. wrfrun/model/utils.py +9 -20
  32. wrfrun/model/wrf/__init__.py +4 -9
  33. wrfrun/model/wrf/core.py +246 -161
  34. wrfrun/model/wrf/exec_wrap.py +13 -12
  35. wrfrun/model/wrf/geodata.py +116 -100
  36. wrfrun/model/wrf/log.py +103 -0
  37. wrfrun/model/wrf/namelist.py +90 -73
  38. wrfrun/model/wrf/plot.py +102 -0
  39. wrfrun/model/wrf/scheme.py +108 -52
  40. wrfrun/model/wrf/utils.py +39 -25
  41. wrfrun/model/wrf/vtable.py +35 -3
  42. wrfrun/plot/__init__.py +20 -0
  43. wrfrun/plot/wps.py +96 -73
  44. wrfrun/res/__init__.py +103 -5
  45. wrfrun/res/config/config.template.toml +8 -0
  46. wrfrun/res/config/palm.template.toml +23 -0
  47. wrfrun/run.py +105 -77
  48. wrfrun/scheduler/__init__.py +1 -0
  49. wrfrun/scheduler/lsf.py +3 -2
  50. wrfrun/scheduler/pbs.py +3 -2
  51. wrfrun/scheduler/script.py +17 -5
  52. wrfrun/scheduler/slurm.py +3 -2
  53. wrfrun/scheduler/utils.py +14 -2
  54. wrfrun/utils.py +88 -199
  55. wrfrun/workspace/__init__.py +8 -5
  56. wrfrun/workspace/core.py +20 -12
  57. wrfrun/workspace/palm.py +137 -0
  58. wrfrun/workspace/wrf.py +16 -15
  59. wrfrun-0.3.1.dist-info/METADATA +239 -0
  60. wrfrun-0.3.1.dist-info/RECORD +78 -0
  61. wrfrun/core/config.py +0 -923
  62. wrfrun/model/base.py +0 -14
  63. wrfrun-0.2.0.dist-info/METADATA +0 -68
  64. wrfrun-0.2.0.dist-info/RECORD +0 -62
  65. {wrfrun-0.2.0.dist-info → wrfrun-0.3.1.dist-info}/WHEEL +0 -0
  66. {wrfrun-0.2.0.dist-info → wrfrun-0.3.1.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"]
@@ -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"]