wrfrun 0.1.7__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 (46) hide show
  1. wrfrun/__init__.py +3 -0
  2. wrfrun/core/__init__.py +5 -0
  3. wrfrun/core/base.py +680 -0
  4. wrfrun/core/config.py +717 -0
  5. wrfrun/core/error.py +80 -0
  6. wrfrun/core/replay.py +113 -0
  7. wrfrun/core/server.py +212 -0
  8. wrfrun/data.py +418 -0
  9. wrfrun/extension/__init__.py +1 -0
  10. wrfrun/extension/littler/__init__.py +1 -0
  11. wrfrun/extension/littler/utils.py +599 -0
  12. wrfrun/extension/utils.py +66 -0
  13. wrfrun/model/__init__.py +7 -0
  14. wrfrun/model/base.py +14 -0
  15. wrfrun/model/plot.py +54 -0
  16. wrfrun/model/utils.py +34 -0
  17. wrfrun/model/wrf/__init__.py +6 -0
  18. wrfrun/model/wrf/_metgrid.py +71 -0
  19. wrfrun/model/wrf/_ndown.py +39 -0
  20. wrfrun/model/wrf/core.py +805 -0
  21. wrfrun/model/wrf/exec_wrap.py +101 -0
  22. wrfrun/model/wrf/geodata.py +301 -0
  23. wrfrun/model/wrf/namelist.py +377 -0
  24. wrfrun/model/wrf/scheme.py +311 -0
  25. wrfrun/model/wrf/vtable.py +65 -0
  26. wrfrun/pbs.py +86 -0
  27. wrfrun/plot/__init__.py +1 -0
  28. wrfrun/plot/wps.py +188 -0
  29. wrfrun/res/__init__.py +22 -0
  30. wrfrun/res/config.toml.template +136 -0
  31. wrfrun/res/extension/plotgrids.ncl +216 -0
  32. wrfrun/res/job_scheduler/pbs.template +6 -0
  33. wrfrun/res/job_scheduler/slurm.template +6 -0
  34. wrfrun/res/namelist/namelist.input.da_wrfvar.template +261 -0
  35. wrfrun/res/namelist/namelist.input.dfi.template +260 -0
  36. wrfrun/res/namelist/namelist.input.real.template +256 -0
  37. wrfrun/res/namelist/namelist.input.wrf.template +256 -0
  38. wrfrun/res/namelist/namelist.wps.template +44 -0
  39. wrfrun/res/namelist/parame.in.template +11 -0
  40. wrfrun/res/run.sh.template +16 -0
  41. wrfrun/run.py +264 -0
  42. wrfrun/utils.py +257 -0
  43. wrfrun/workspace.py +88 -0
  44. wrfrun-0.1.7.dist-info/METADATA +67 -0
  45. wrfrun-0.1.7.dist-info/RECORD +46 -0
  46. wrfrun-0.1.7.dist-info/WHEEL +4 -0
@@ -0,0 +1,101 @@
1
+ from os.path import basename
2
+ from typing import Optional, Union
3
+
4
+ from wrfrun import WRFRUNConfig
5
+ from .core import DFI, GeoGrid, MetGrid, Real, UnGrib, WRF, NDown
6
+
7
+
8
+ def geogrid(geogrid_tbl_file: Union[str, None] = None):
9
+ """
10
+ Interface to execute geogrid.exe.
11
+
12
+ :param geogrid_tbl_file: Custom GEOGRID.TBL file path. Defaults to None.
13
+ """
14
+ GeoGrid(geogrid_tbl_file, WRFRUNConfig.get_core_num())()
15
+
16
+
17
+ def ungrib(vtable_file: Union[str, None] = None, input_data_path: Optional[str] = None, prefix="FILE"):
18
+ """
19
+ Interface to execute ungrib.exe.
20
+
21
+ :param vtable_file: Vtable file used to run ungrib. Defaults to None.
22
+ :type vtable_file: str
23
+ :param input_data_path: Directory path of the input data. If None, ``wrfrun`` will read its value from the config file.
24
+ :type input_data_path: str
25
+ :param prefix: Prefix of ungrib output.
26
+ :type prefix: str
27
+ """
28
+ prefix = basename(prefix)
29
+ WRFRUNConfig.set_ungrib_out_prefix(prefix)
30
+
31
+ UnGrib(vtable_file, input_data_path)()
32
+
33
+
34
+ def metgrid(geogrid_data_path: Optional[str] = None, ungrib_data_path: Optional[str] = None, fg_names: Union[str, list[str]] = "FILE"):
35
+ """
36
+ Interface to execute metgrid.exe.
37
+
38
+ :param geogrid_data_path: Directory path of outputs from geogrid.exe. If None, tries to use the output path specified by config file.
39
+ :type geogrid_data_path: str
40
+ :param ungrib_data_path: Directory path of outputs from ungrib.exe. If None, tries to use the output path specified by config file.
41
+ :type ungrib_data_path: str
42
+ :param fg_names: Set ``fg_name`` of metgrid, a single prefix string or a string list.
43
+ :type fg_names: str | list
44
+ """
45
+ if isinstance(fg_names, str):
46
+ fg_names = [fg_names, ]
47
+ fg_names = [basename(x) for x in fg_names]
48
+ WRFRUNConfig.set_metgrid_fg_names(fg_names)
49
+
50
+ MetGrid(geogrid_data_path, ungrib_data_path, WRFRUNConfig.get_core_num())()
51
+
52
+
53
+ def real(metgrid_data_path: Union[str, None] = None):
54
+ """
55
+ Interface to execute real.exe.
56
+
57
+ :param metgrid_data_path: The path store output from metgrid.exe. If it is None, the default output path will be used.
58
+ """
59
+ Real(metgrid_data_path, WRFRUNConfig.get_core_num())()
60
+
61
+
62
+ def wrf(input_file_dir_path: Union[str, None] = None, restart_file_dir_path: Optional[str] = None, save_restarts=False):
63
+ """
64
+ Interface to execute wrf.exe.
65
+
66
+ :param input_file_dir_path: The path store input data which will be feed into wrf.exe. Defaults to None.
67
+ :param restart_file_dir_path: The path store WRF restart files. This parameter will be ignored if ``restart=False`` in your config.
68
+ :param save_restarts: Also save restart files to the output directory.
69
+ """
70
+ WRF(input_file_dir_path, restart_file_dir_path, save_restarts, WRFRUNConfig.get_core_num())()
71
+
72
+
73
+ def dfi(input_file_dir_path: Optional[str] = None, update_real_output=True):
74
+ """
75
+ Execute "wrf.exe" to run DFI.
76
+
77
+ :param input_file_dir_path: Path of the directory that stores input data for "wrf.exe".
78
+ :type input_file_dir_path: str
79
+ :param update_real_output: If update the corresponding file in real.exe output directory.
80
+ :type update_real_output: bool
81
+ """
82
+ DFI(input_file_dir_path, update_real_output, WRFRUNConfig.get_core_num())()
83
+
84
+
85
+ def ndown(wrfout_file_path: str, real_output_dir_path: Optional[str] = None, update_namelist=True):
86
+ """
87
+ Execute "ndown.exe".
88
+
89
+ :param wrfout_file_path: wrfout file path.
90
+ :type wrfout_file_path: str
91
+ :param real_output_dir_path: Path of the directory that contains output of "real.exe".
92
+ :type real_output_dir_path: str
93
+ :param update_namelist: If update wrf's namelist for the final integral.
94
+ :type update_namelist: bool
95
+ :return:
96
+ :rtype:
97
+ """
98
+ NDown(wrfout_file_path, real_output_dir_path, update_namelist, WRFRUNConfig.get_core_num())()
99
+
100
+
101
+ __all__ = ["geogrid", "ungrib", "metgrid", "real", "wrf", "dfi", "ndown"]
@@ -0,0 +1,301 @@
1
+ from os import listdir
2
+ from os.path import exists
3
+ from typing import OrderedDict, Union
4
+
5
+ import numpy as np
6
+ from xarray import DataArray
7
+
8
+ from wrfrun.core import WRFRUNConfig
9
+ from wrfrun.utils import logger
10
+
11
+ # for example: 00001-00200.00201-00400
12
+ DATA_NAME_TEMPLATE = "{}-{}.{}-{}"
13
+
14
+
15
+ def _get_data_type(wordsize: int) -> type:
16
+ """Get data type based on wordsize value in index file.
17
+
18
+ Args:
19
+ wordsize (int): Wordsize in index file.
20
+
21
+ Returns:
22
+ type: NumPy value type.
23
+ """
24
+ # define map dict
25
+ map_dict = {
26
+ 1: np.int8,
27
+ 2: np.int16,
28
+ 4: np.int32
29
+ }
30
+
31
+ return map_dict[wordsize]
32
+
33
+
34
+ def _get_clip_area(index_area: tuple[int, int, int, int], row_num: int, col_num: int, tile_x: int, tile_y: int) -> tuple[int, int, int, int]:
35
+ """Get clip area.
36
+
37
+ Args:
38
+ index_area (tuple[int, int, int, int]): Full area index.
39
+ row_num (int): Row number of the tile.
40
+ col_num (int): Column number of the file.
41
+ tile_x (int): X size of the tile.
42
+ tile_y (int): Y size of the tile.
43
+
44
+ Returns:
45
+ tuple[int, int, int, int]: Clip area.
46
+ """
47
+ # calculate tile area
48
+ tile_area = (
49
+ col_num * tile_x + 1,
50
+ col_num * tile_x + tile_x,
51
+ row_num * tile_y + 1,
52
+ row_num * tile_y + tile_y,
53
+ )
54
+
55
+ # generate clip area
56
+ clip_area = (
57
+ 0 if index_area[0] <= tile_area[0] else index_area[0] % tile_x - 1,
58
+ tile_x if index_area[1] >= tile_area[1] else index_area[1] % tile_x - 1,
59
+ 0 if index_area[2] <= tile_area[2] else index_area[2] % tile_y - 1,
60
+ tile_y if index_area[3] >= tile_area[3] else index_area[3] % tile_y - 1,
61
+ )
62
+
63
+ return clip_area
64
+
65
+
66
+ def parse_geographical_data_index(index_path: str) -> OrderedDict:
67
+ """Read geographical data index file.
68
+
69
+ Args:
70
+ index_path (str): Index file path.
71
+
72
+ Returns:
73
+ dict: Info stored in dict.
74
+ """
75
+ # since the index file is very similar to fortran namelist file,
76
+ # we can manually add "&index" and "/" and parse it as a namelist
77
+ # temp file store path
78
+ temp_file = f"{WRFRUNConfig.WRFRUN_TEMP_PATH}/geogrid_data.index"
79
+ temp_file = WRFRUNConfig.parse_resource_uri(temp_file)
80
+
81
+ # open file and add header and tail
82
+ with open(index_path, "r") as _index_file:
83
+ with open(temp_file, "w") as _temp_file:
84
+
85
+ _temp_file.write("&index\n")
86
+ _temp_file.write(_index_file.read())
87
+ _temp_file.write("/")
88
+
89
+ # read index
90
+ WRFRUNConfig.read_namelist(temp_file, "geog_static_data")
91
+
92
+ return WRFRUNConfig.get_namelist("geog_static_data")["index"]
93
+
94
+
95
+ def parse_geographical_data_file(file_path: str, wordsize: int, endian: str, tile_shape: tuple[int, ...],
96
+ area: Union[tuple[int, ...], None] = None, miss_value: Union[int, float, None] = None) -> np.ndarray:
97
+ """Read geographical data file.
98
+
99
+ Args:
100
+ file_path (str): File path.
101
+ wordsize (int): How many bytes are used to store value in data file.
102
+ endian (str): "big" or "little".
103
+ tile_shape (tuple[int, ...]): The raw shape of the tile. Can be 2D or 3D.
104
+ area (Union[tuple[int, ...], None], optional): The range (x_start, x_stop, y_start, y_stop, ...) of data you want to read. Defaults to None.
105
+ miss_value (Union[int, float, None], optional): The value which represents NaN. Defaults to None.
106
+
107
+ Returns:
108
+ np.ndarray: NumPy array object.
109
+ """
110
+ # get data type
111
+ data_type = _get_data_type(wordsize)
112
+
113
+ # read data
114
+ data = np.fromfile(file_path, dtype=data_type)
115
+
116
+ # swap byte if need
117
+ if endian == "big":
118
+ data = data.byteswap()
119
+
120
+ # reshape
121
+ data = data.reshape(tile_shape)
122
+
123
+ # clip
124
+ if area:
125
+ # check area
126
+ if len(area) % 2 != 0:
127
+ logger.error(
128
+ f"The length of `area` must be even, but is {len(area)}")
129
+ exit(1)
130
+
131
+ area_array = np.asarray(area).reshape(-1, 2)
132
+ slice_index = tuple((slice(i[0], i[1]) for i in area_array))
133
+
134
+ if len(slice_index) == 2:
135
+ slice_index += (slice(None), )
136
+
137
+ data = data[slice_index[::-1]] # type: ignore
138
+
139
+ # fill nan
140
+ if miss_value:
141
+ data[data == miss_value] = np.nan
142
+
143
+ return data
144
+
145
+
146
+ def read_geographical_static_data(geog_data_folder_path: str, name: str, area: Union[tuple[float, float, float, float], None] = None) -> DataArray:
147
+ """Read WPS geographical static data
148
+
149
+ Args:
150
+ geog_data_folder_path (str): Data folder path.
151
+ name (str): Name that will be used to create DataArray.
152
+ area (Union[tuple[float, float, float, float], None]): Longitude and latitude area (lon_start, lon_stop, lat_start, lat_stop). Defaults to None.
153
+
154
+ Returns:
155
+ DataArray: DataArray object.
156
+ """
157
+ # check if folder exists
158
+ if not exists(geog_data_folder_path):
159
+ logger.error(f"Can't find folder {geog_data_folder_path}")
160
+ exit(1)
161
+
162
+ # parse index file first
163
+ index_path = f"{geog_data_folder_path}/index"
164
+ index_data = parse_geographical_data_index(index_path)
165
+
166
+ # extract info to read data
167
+ # # check essential key
168
+ if "wordsize" not in index_data:
169
+ logger.error(
170
+ f"Can't find key `wordsize` in index file, maybe it is corrupted.")
171
+ exit(1)
172
+ # # extract info
173
+ wordsize = index_data["wordsize"]
174
+ endian = "little" if "endian" not in index_data else index_data["endian"]
175
+ miss_value = None if "missing_value" not in index_data else index_data["missing_value"]
176
+ tile_shape = []
177
+ for key in ["tile_z", "tile_y", "tile_x"]:
178
+ if key in index_data:
179
+ tile_shape.append(index_data[key])
180
+ tile_shape = tuple(tile_shape)
181
+ known_lat = index_data["known_lat"]
182
+ known_lon = index_data["known_lon"]
183
+ dx = index_data["dx"]
184
+ dy = index_data["dy"]
185
+
186
+ # calculate area
187
+ if area:
188
+ # read resolution, latitude and longitude
189
+ index_area = (
190
+ int((area[0] - known_lon) // dx),
191
+ int((area[1] - known_lon) // dx),
192
+ int((area[2] - known_lat) // dy),
193
+ int((area[3] - known_lat) // dy),
194
+ )
195
+ # check if negative value exists
196
+ if (
197
+ index_area[0] < 0 or
198
+ index_area[2] < 0
199
+ ):
200
+ logger.warning(f"Part of your area has exceeded data's area")
201
+ # set negative value to 0
202
+ index_area = tuple((i if i >= 0 else 0 for i in index_area))
203
+ else:
204
+ logger.warning(f"You want to read all data, which may be very large")
205
+ index_area = None
206
+
207
+ # find the file we need to read
208
+ if index_area:
209
+ # # calculate tile index number
210
+ tile_index_num = (
211
+ index_area[0] // tile_shape[-1],
212
+ index_area[1] // tile_shape[-1],
213
+ index_area[2] // tile_shape[-2],
214
+ index_area[3] // tile_shape[-2],
215
+ )
216
+
217
+ filenames = []
218
+ # # generate filenames and clip area
219
+ for row_num in range(tile_index_num[2], tile_index_num[3] + 1):
220
+
221
+ _names = []
222
+ for col_num in range(tile_index_num[0], tile_index_num[1] + 1):
223
+
224
+ _names.append(
225
+ [
226
+ DATA_NAME_TEMPLATE.format(
227
+ str(col_num * tile_shape[-1] + 1).rjust(5, '0'),
228
+ str((col_num + 1) * tile_shape[-1]).rjust(5, '0'),
229
+ str(row_num * tile_shape[-2] + 1).rjust(5, '0'),
230
+ str((row_num + 1) * tile_shape[-2]).rjust(5, '0'),
231
+ ), _get_clip_area(index_area, row_num, col_num, tile_shape[-1], tile_shape[-2]) # type: ignore
232
+ ]
233
+ )
234
+
235
+ filenames.append(_names)
236
+ else:
237
+ raw_filenames = [x for x in listdir(
238
+ geog_data_folder_path) if x != "index"]
239
+ raw_filenames.sort()
240
+
241
+ # parse the last file to get row number and column number
242
+ _last_filename = raw_filenames[-1]
243
+ total_col_num = int(_last_filename.split(
244
+ ".")[0].split("-")[1]) // tile_shape[-1]
245
+ total_row_num = int(_last_filename.split(
246
+ ".")[1].split("-")[1]) // tile_shape[-2]
247
+
248
+ filenames = []
249
+
250
+ for row_num in range(total_row_num):
251
+
252
+ _names = []
253
+ for col_num in range(total_col_num):
254
+
255
+ _names.append(
256
+ [
257
+ raw_filenames[row_num * total_col_num + col_num], None
258
+ ]
259
+ )
260
+
261
+ filenames.append(_names)
262
+
263
+ # read and concatenate
264
+ array = []
265
+ for _row in filenames:
266
+
267
+ _array = []
268
+ for _col in _row:
269
+
270
+ _array.append(parse_geographical_data_file(
271
+ f"{geog_data_folder_path}/{_col[0]}", wordsize, endian, tile_shape, _col[1], miss_value))
272
+
273
+ # concatenate _array
274
+ array.append(np.concatenate(_array, axis=-1))
275
+
276
+ array = np.concatenate(array, axis=-2)
277
+
278
+ # get the longitude and latitude of the start point
279
+ if index_area:
280
+ longitude = known_lon + dx * index_area[0]
281
+ latitude = known_lat + dy * index_area[2]
282
+ else:
283
+ longitude = known_lon
284
+ latitude = known_lat
285
+
286
+ longitude = np.arange(array.shape[-1]) * dx + longitude
287
+ latitude = np.arange(array.shape[-2]) * dy + latitude
288
+ levels = np.arange(array.shape[-3])
289
+
290
+ return DataArray(
291
+ name=name, data=array,
292
+ dims=["levels", "latitude", "longitude"],
293
+ coords={
294
+ "longitude": longitude,
295
+ "latitude": latitude,
296
+ "levels": levels
297
+ }, attrs=index_data
298
+ )
299
+
300
+
301
+ __all__ = ["parse_geographical_data_index", "parse_geographical_data_file", "read_geographical_static_data"]