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,599 @@
1
+ from json import loads, dumps
2
+ from typing import Union, Tuple
3
+ from zipfile import ZipFile
4
+
5
+ from pandas import DataFrame, read_csv
6
+ import numpy as np
7
+
8
+ from wrfrun.core import WRFRUNConfig
9
+ from wrfrun.utils import logger
10
+
11
+
12
+ def to_fstring(var: Union[int, float, bool, str], length: Union[int, Tuple[int, int]]) -> str:
13
+ """
14
+ Convert a basic variable to a string following the Fortran standard.
15
+
16
+ :param var: Basic variable that can be one of the ``[int, float, bool, str]``.
17
+ :type var: Union[int, float, bool, str]
18
+ :param length: The length of the output string. If the type of ``var`` is ``float``, the length must contain two parameters ``(total length, decimal length)``.
19
+ :type length: Union[int, Tuple[int, int]]
20
+ :return: Converted string.
21
+ :rtype: str
22
+ """
23
+ if isinstance(var, float):
24
+ if not isinstance(length, tuple):
25
+ logger.error(
26
+ "`length` must be a tuple contain two values `(total length, decimal length)` when `var` is `float`"
27
+ )
28
+ raise ValueError(
29
+ "`length` must be a tuple contain two values `(total length, decimal length)` when `var` is `float`"
30
+ )
31
+
32
+ res = f"{var:{length[0]}.{length[1]}}"
33
+
34
+ else:
35
+ if not isinstance(length, int):
36
+ logger.error(
37
+ "`length` must be an int value when `var` is not `float`"
38
+ )
39
+ raise ValueError(
40
+ "`length` must be an int value when `var` is not `float`"
41
+ )
42
+
43
+ if isinstance(var, bool):
44
+ res = "T" if var else "F"
45
+ res = res.rjust(length, " ")
46
+
47
+ else:
48
+ res = f"{var:{length}}"
49
+
50
+ return res
51
+
52
+
53
+ class LittleRHead(dict):
54
+ """
55
+ Head info class for LittleR format data.
56
+ """
57
+ def __init__(
58
+ self,
59
+ longitude: float,
60
+ latitude: float,
61
+ fm: str,
62
+ elevation: float,
63
+ is_bogus: bool,
64
+ date: str,
65
+ ID="-----",
66
+ name="wrfrun",
67
+ source="Created by wrfrun package",
68
+ num_sequence=0,
69
+ sea_level_pressure=-888888,
70
+ reference_pressure=-888888,
71
+ surface_pressure=-888888,
72
+ cloud_cover=-888888,
73
+ precipitable_water=-888888,
74
+ quality_control: Union[dict, int] = 0,
75
+ **kwargs
76
+ ) -> None:
77
+ """Head info of little_r obs data.
78
+
79
+ :param longitude: Longitude.
80
+ :type longitude: float
81
+ :param latitude: Latitude.
82
+ :type latitude: float
83
+ :param fm: Platform code (FM-Code).
84
+ :type fm: str
85
+ :param elevation: Elevation.
86
+ :type elevation: float
87
+ :param is_bogus: Is bogus?
88
+ :type is_bogus: bool
89
+ :param date: Date string in format ``%Y%m%d%H%M%S``, UTC time.
90
+ :type date: str
91
+ :param ID: ID, defaults to "-----".
92
+ :type ID: str
93
+ :param name: Name, defaults to "wrfrun".
94
+ :type name: str
95
+ :param source: Data source, defaults to "Created by wrfrun package".
96
+ :type source: str
97
+ :param num_sequence: Sequence number which is used to merge multiple record data, the less the num is, the newer the data is. Defaults to 0.
98
+ :type num_sequence: int
99
+ :param sea_level_pressure: Sea level pressure. Defaults to -888,888.
100
+ :type sea_level_pressure: int
101
+ :param reference_pressure: Reference pressure. Defaults to -888,888.
102
+ :type reference_pressure: int
103
+ :param surface_pressure: Surface pressure. Defaults to -888,888.
104
+ :type surface_pressure: int
105
+ :param cloud_cover: Cloud cover. Defaults to -888,888.
106
+ :type cloud_cover: int
107
+ :param precipitable_water: Precipitable water. Defaults to -888,888.
108
+ :type precipitable_water: int
109
+ :param quality_control: Quality control flag for observation data that can be specified per data in a dict. Defaults to 0.
110
+ :type quality_control: Union[dict, int]
111
+ """
112
+ super().__init__(
113
+ longitude=longitude,
114
+ latitude=latitude,
115
+ fm=fm,
116
+ elevation=elevation,
117
+ is_bogus=is_bogus,
118
+ date=date,
119
+ ID=ID,
120
+ name=name,
121
+ source=source,
122
+ num_sequence=num_sequence,
123
+ sea_level_pressure=sea_level_pressure,
124
+ reference_pressure=reference_pressure,
125
+ surface_pressure=surface_pressure,
126
+ cloud_cover=cloud_cover,
127
+ precipitable_water=precipitable_water,
128
+ quality_control=quality_control,
129
+ num_valid_field=256,
130
+ num_error=-888888,
131
+ num_warning=-888888,
132
+ num_duplicate=-888888,
133
+ is_sounding=True,
134
+ discard=False,
135
+ time=-888888,
136
+ julian_day=-888888,
137
+ ground_temperature=-888888,
138
+ sst=-888888,
139
+ precipitation=-888888,
140
+ temp_daily_max=-888888,
141
+ temp_daily_min=-888888,
142
+ temp_night_min=-888888,
143
+ pressure_delta_3h=-888888,
144
+ pressure_delta_24h=-888888,
145
+ ceiling=-888888,
146
+ )
147
+ self.longitude = longitude
148
+ self.latitude = latitude
149
+ self.fm = fm
150
+ self.elevation = elevation
151
+ self.is_bogus = is_bogus
152
+ self.date = date
153
+ self.ID = ID
154
+ self.name = name
155
+ self.source = source
156
+ self.num_sequence = num_sequence
157
+ self.sea_level_pressure = sea_level_pressure
158
+ self.reference_pressure = reference_pressure
159
+ self.surface_pressure = surface_pressure
160
+ self.cloud_cover = cloud_cover
161
+ self.precipitable_water = precipitable_water
162
+ self.quality_control = quality_control
163
+
164
+ # other unused field
165
+ self.num_valid_field = 256
166
+ self.num_error = -888888
167
+ self.num_warning = -888888
168
+ self.num_duplicate = -888888
169
+ self.is_sounding = True
170
+ self.discard = False
171
+ self.time = -888888
172
+ self.julian_day = -888888
173
+ self.ground_temperature = -888888
174
+ self.sst = -888888
175
+ self.precipitation = -888888
176
+ self.temp_daily_max = -888888
177
+ self.temp_daily_min = -888888
178
+ self.temp_night_min = -888888
179
+ self.pressure_delta_3h = -888888
180
+ self.pressure_delta_24h = -888888
181
+ self.ceiling = -888888
182
+
183
+ _ = kwargs
184
+
185
+ def __str__(self) -> str:
186
+ return self._convert_to_fstring()
187
+
188
+ # def __repr__(self) -> str:
189
+ # return self._convert_to_fstring()
190
+
191
+ def _convert_to_fstring(self) -> str:
192
+ return f"{self.latitude:20.5f}" \
193
+ f"{self.longitude:20.5f}" \
194
+ f"{self.ID.rjust(40, ' ')}" \
195
+ f"{self.name.rjust(40, ' ')}" \
196
+ f"{self.fm.rjust(40, ' ')}" \
197
+ f"{self.source.rjust(40, ' ')}" \
198
+ f"{self.elevation:20.5f}" \
199
+ f"{self.num_valid_field:10d}" \
200
+ f"{self.num_error:10d}" \
201
+ f"{self.num_warning:10d}" \
202
+ f"{self.num_sequence:10d}" \
203
+ f"{self.num_duplicate:10d}" \
204
+ f"{to_fstring(self.is_sounding, 10)}" \
205
+ f"{to_fstring(self.is_bogus, 10)}" \
206
+ f"{to_fstring(self.discard, 10)}" \
207
+ f"{self.time:10d}" \
208
+ f"{self.julian_day:10d}" \
209
+ f"{self.date.rjust(20, ' ')}" + self._generate_data_qc()
210
+
211
+ def _generate_data_qc(self) -> str:
212
+ field = [
213
+ "sea_level_pressure",
214
+ "reference_pressure",
215
+ "ground_temperature",
216
+ "sst",
217
+ "surface_pressure",
218
+ "precipitation",
219
+ "temp_daily_max",
220
+ "temp_daily_min",
221
+ "temp_night_min",
222
+ "pressure_delta_3h",
223
+ "pressure_delta_24h",
224
+ "cloud_cover",
225
+ "ceiling",
226
+ "precipitable_water",
227
+ ]
228
+
229
+ if isinstance(self.quality_control, int):
230
+ res = ""
231
+ for key in field:
232
+ res += f"{getattr(self, key):13.5f}{self.quality_control:7d}"
233
+ else:
234
+ res = ""
235
+ for key in field:
236
+ res += f"{getattr(self, key):13.5f}{self.quality_control[key]:7d}"
237
+
238
+ return res
239
+
240
+
241
+ LITTLE_R_DATA_FIELD = [
242
+ "pressure", "pressure_qc",
243
+ "height", "height_qc",
244
+ "temperature", "temperature_qc",
245
+ "dew_point", "dew_point_qc",
246
+ "wind_speed", "wind_speed_qc",
247
+ "wind_direction", "wind_direction_qc",
248
+ "wind_u", "wind_u_qc",
249
+ "wind_v", "wind_v_qc",
250
+ "relative_humidity", "relative_humidity_qc",
251
+ "thickness", "thickness_qc",
252
+ ]
253
+
254
+
255
+ class LittleRData(DataFrame):
256
+ def __init__(
257
+ self,
258
+ data=None,
259
+ index=None,
260
+ columns=None,
261
+ pressure: Union[np.ndarray, float] = 100000.,
262
+ height: Union[np.ndarray, float] = -888888.,
263
+ temperature: Union[np.ndarray, float] = 264.,
264
+ dew_point: Union[np.ndarray, float] = 263.,
265
+ wind_speed: Union[np.ndarray, float] = -888888.,
266
+ wind_direction: Union[np.ndarray, float] = -888888.,
267
+ wind_u: Union[np.ndarray, float] = -888888.,
268
+ wind_v: Union[np.ndarray, float] = -888888.,
269
+ relative_humidity: Union[np.ndarray, float] = -888888.,
270
+ thickness: Union[np.ndarray, float] = -888888.,
271
+ quality_control_flag: Union[np.ndarray, dict, int] = 0,
272
+ **kwargs
273
+ ) -> None:
274
+ # check data type
275
+ if data is not None:
276
+ super().__init__(data=data, index=index, columns=columns, **kwargs) # type: ignore
277
+
278
+ else:
279
+ if isinstance(pressure, float):
280
+ pressure = np.array([pressure]).astype(float)
281
+ height = np.array([height]).astype(float)
282
+ temperature = np.array([temperature]).astype(float)
283
+ dew_point = np.array([dew_point]).astype(float)
284
+ wind_speed = np.array([wind_speed]).astype(float)
285
+ wind_direction = np.array([wind_direction]).astype(float)
286
+ wind_u = np.array([wind_u]).astype(float)
287
+ wind_v = np.array([wind_v]).astype(float)
288
+ relative_humidity = np.array([relative_humidity]).astype(float)
289
+ thickness = np.array([thickness]).astype(float)
290
+ quality_control_flag = np.array(
291
+ [quality_control_flag]
292
+ ).astype(int)
293
+ else:
294
+ pressure = np.asarray(pressure).astype(float)
295
+ height = np.asarray(height).astype(float)
296
+ temperature = np.asarray(temperature).astype(float)
297
+ dew_point = np.asarray(dew_point).astype(float)
298
+ wind_speed = np.asarray(wind_speed).astype(float)
299
+ wind_direction = np.asarray(wind_direction).astype(float)
300
+ wind_u = np.asarray(wind_u).astype(float)
301
+ wind_v = np.asarray(wind_v).astype(float)
302
+ relative_humidity = np.asarray(relative_humidity).astype(float)
303
+ thickness = np.asarray(thickness).astype(float)
304
+ quality_control_flag = np.asarray(
305
+ quality_control_flag
306
+ ).astype(int)
307
+
308
+ # construct data
309
+ if isinstance(quality_control_flag, dict):
310
+ data = {
311
+ "pressure": pressure,
312
+ "pressure_qc": quality_control_flag["pressure"],
313
+ "height": height,
314
+ "height_qc": quality_control_flag["height"],
315
+ "temperature": temperature,
316
+ "temperature_qc": quality_control_flag["temperature"],
317
+ "dew_point": dew_point,
318
+ "dew_point_qc": quality_control_flag["dew_point"],
319
+ "wind_speed": wind_speed,
320
+ "wind_speed_qc": quality_control_flag["wind_speed"],
321
+ "wind_direction": wind_direction,
322
+ "wind_direction_qc": quality_control_flag["wind_direction"],
323
+ "wind_u": wind_u,
324
+ "wind_u_qc": quality_control_flag["wind_u"],
325
+ "wind_v": wind_v,
326
+ "wind_v_qc": quality_control_flag["wind_v"],
327
+ "relative_humidity": relative_humidity,
328
+ "relative_humidity_qc": quality_control_flag["relative_humidity"],
329
+ "thickness": thickness,
330
+ "thickness_qc": quality_control_flag["thickness"],
331
+ }
332
+ else:
333
+ data = {
334
+ "pressure": pressure,
335
+ "pressure_qc": quality_control_flag,
336
+ "height": height,
337
+ "height_qc": quality_control_flag,
338
+ "temperature": temperature,
339
+ "temperature_qc": quality_control_flag,
340
+ "dew_point": dew_point,
341
+ "dew_point_qc": quality_control_flag,
342
+ "wind_speed": wind_speed,
343
+ "wind_speed_qc": quality_control_flag,
344
+ "wind_direction": wind_direction,
345
+ "wind_direction_qc": quality_control_flag,
346
+ "wind_u": wind_u,
347
+ "wind_u_qc": quality_control_flag,
348
+ "wind_v": wind_v,
349
+ "wind_v_qc": quality_control_flag,
350
+ "relative_humidity": relative_humidity,
351
+ "relative_humidity_qc": quality_control_flag,
352
+ "thickness": thickness,
353
+ "thickness_qc": quality_control_flag,
354
+ }
355
+
356
+ super().__init__(data=data) # type: ignore
357
+
358
+ @classmethod
359
+ def from_csv(cls, csv_path):
360
+ """
361
+ Read saved LittleR data from a CSV file.
362
+
363
+ :param csv_path: CSV file path.
364
+ :type csv_path: str
365
+ :return:
366
+ :rtype:
367
+ """
368
+ data_dict = read_csv(csv_path).to_dict()
369
+ return cls.from_dict(data_dict)
370
+
371
+ @classmethod
372
+ def from_dict(cls, data: dict, orient='columns', dtype=None, columns=None):
373
+ # check fields
374
+ data_key = next(iter(data))
375
+ temp_data = data[data_key]
376
+ for field in LITTLE_R_DATA_FIELD:
377
+ if field not in data:
378
+ if field.endswith("_qc"):
379
+ data[field] = np.zeros_like(temp_data).astype(int)
380
+ else:
381
+ data[field] = np.zeros_like(temp_data) - 888888.
382
+
383
+ return super().from_dict(data, orient, dtype, columns) # type: ignore
384
+
385
+ def __str__(self) -> str:
386
+ return self._convert_to_fstring()
387
+
388
+ def _convert_to_fstring(self) -> str:
389
+ fields = [
390
+ "pressure",
391
+ "height",
392
+ "temperature",
393
+ "dew_point",
394
+ "wind_speed",
395
+ "wind_direction",
396
+ "wind_u",
397
+ "wind_v",
398
+ "relative_humidity",
399
+ "thickness",
400
+ ]
401
+ qc_fields = [
402
+ "pressure_qc",
403
+ "height_qc",
404
+ "temperature_qc",
405
+ "dew_point_qc",
406
+ "wind_speed_qc",
407
+ "wind_direction_qc",
408
+ "wind_u_qc",
409
+ "wind_v_qc",
410
+ "relative_humidity_qc",
411
+ "thickness_qc",
412
+ ]
413
+
414
+ res = ""
415
+ valid_field_num = 0
416
+ for row in self.index:
417
+ for (key, qc_key) in zip(fields, qc_fields):
418
+ _field = self.loc[row, key]
419
+ _field_qc = self.loc[row, qc_key]
420
+
421
+ if _field > -100: # type: ignore
422
+ valid_field_num += 1
423
+
424
+ res += f"{_field:13.5f}{_field_qc:7d}"
425
+ res += "\n"
426
+
427
+ # add ending record
428
+ for (_, _) in zip(fields, qc_fields):
429
+ res += f"{-777777:13.5f}{0:7d}"
430
+ res += "\n"
431
+
432
+ # add tail integers
433
+ res += f"{valid_field_num:7d}{0:7d}{0:7d}"
434
+
435
+ return res
436
+
437
+
438
+ class LittleR(LittleRData):
439
+
440
+ _metadata = ["little_r_head"]
441
+
442
+ def __init__(
443
+ self,
444
+ data=None,
445
+ index=None,
446
+ columns=None,
447
+ data_header: Union[dict, None] = None,
448
+ pressure: Union[np.ndarray, float] = 100000.,
449
+ height: Union[np.ndarray, float] = -888888.,
450
+ temperature: Union[np.ndarray, float] = 264.,
451
+ dew_point: Union[np.ndarray, float] = 263.,
452
+ wind_speed: Union[np.ndarray, float] = -888888.,
453
+ wind_direction: Union[np.ndarray, float] = -888888.,
454
+ wind_u: Union[np.ndarray, float] = -888888.,
455
+ wind_v: Union[np.ndarray, float] = -888888.,
456
+ relative_humidity: Union[np.ndarray, float] = -888888.,
457
+ thickness: Union[np.ndarray, float] = -888888.,
458
+ quality_control_flag: Union[np.ndarray, dict, int] = 0,
459
+ **kwargs
460
+ ) -> None:
461
+ super().__init__(
462
+ data=data,
463
+ pressure=pressure,
464
+ height=height,
465
+ temperature=temperature,
466
+ dew_point=dew_point,
467
+ wind_speed=wind_speed,
468
+ wind_direction=wind_direction,
469
+ wind_u=wind_u,
470
+ wind_v=wind_v,
471
+ relative_humidity=relative_humidity,
472
+ thickness=thickness,
473
+ quality_control_flag=quality_control_flag,
474
+ index=index,
475
+ columns=columns,
476
+ **kwargs
477
+ )
478
+
479
+ if data_header is None:
480
+ self.little_r_head = None
481
+ else:
482
+ self.little_r_head = LittleRHead(**data_header)
483
+
484
+ def set_header(
485
+ self,
486
+ longitude: float,
487
+ latitude: float,
488
+ fm: str,
489
+ elevation: float,
490
+ is_bogus: bool,
491
+ date: str,
492
+ ID="-----",
493
+ name="wrfrun",
494
+ source="Created by wrfrun package",
495
+ num_sequence=0,
496
+ sea_level_pressure=-888888,
497
+ reference_pressure=-888888,
498
+ surface_pressure=-888888,
499
+ cloud_cover=-888888,
500
+ precipitable_water=-888888,
501
+ quality_control: Union[dict, int] = 0,
502
+ **kwargs
503
+ ):
504
+ """Head info of little_r obs data.
505
+
506
+ :param longitude: Longitude.
507
+ :type longitude: float
508
+ :param latitude: Latitude.
509
+ :type latitude: float
510
+ :param fm: Platform code (FM-Code).
511
+ :type fm: str
512
+ :param elevation: Elevation.
513
+ :type elevation: float
514
+ :param is_bogus: Is bogus?
515
+ :type is_bogus: bool
516
+ :param date: Date string in format ``%Y%m%d%H%M%S``, UTC time.
517
+ :type date: str
518
+ :param ID: ID, defaults to "-----".
519
+ :type ID: str, optional
520
+ :param name: Name, defaults to "wrfrun".
521
+ :type name: str, optional
522
+ :param source: Data source, defaults to "Created by wrfrun package".
523
+ :type source: str, optional
524
+ :param num_sequence: Sequence number, used to merge multiple record data, the less the num is, the newer the data is, defaults to 0
525
+ :type num_sequence: int, optional
526
+ :param sea_level_pressure: Sea level pressure, defaults to -888888
527
+ :type sea_level_pressure: int, optional
528
+ :param reference_pressure: Reference pressure, defaults to -888888
529
+ :type reference_pressure: int, optional
530
+ :param surface_pressure: Surface pressure, defaults to -888888
531
+ :type surface_pressure: int, optional
532
+ :param cloud_cover: Cloud cover, defaults to -888888
533
+ :type cloud_cover: int, optional
534
+ :param precipitable_water: Precipitable water, defaults to -888888
535
+ :type precipitable_water: int, optional
536
+ :param quality_control: Quality control flag for observation data, can be specified per data in a dict, defaults to 0
537
+ :type quality_control: Union[dict, int], optional
538
+ """
539
+ self.little_r_head = LittleRHead(
540
+ longitude, latitude, fm, elevation, is_bogus, date, ID,
541
+ name=name, source=source, num_sequence=num_sequence, sea_level_pressure=sea_level_pressure, reference_pressure=reference_pressure,
542
+ surface_pressure=surface_pressure, cloud_cover=cloud_cover, precipitable_water=precipitable_water, quality_control=quality_control,
543
+ **kwargs
544
+ )
545
+
546
+ def __str__(self) -> str:
547
+ data_str = super().__str__()
548
+ return f"{str(self.little_r_head)}\n{data_str}"
549
+
550
+ @classmethod
551
+ def from_csv(cls, csv_path):
552
+ return super().from_csv(csv_path)
553
+
554
+ def to_zlr(self, file_path: str):
555
+ """
556
+ Save all LittleR data to a ".zlr" file.
557
+
558
+ :param file_path: The save path.
559
+ :type file_path: str
560
+ :return:
561
+ :rtype:
562
+ """
563
+ if not file_path.endswith(".zlr"):
564
+ file_path = f"{file_path}.zlr"
565
+
566
+ file_path = WRFRUNConfig.parse_resource_uri(file_path)
567
+
568
+ with ZipFile(file_path, "w") as zip_file:
569
+ with zip_file.open("header", "w") as header_file:
570
+ header_file.write(dumps(self.little_r_head).encode())
571
+
572
+ with zip_file.open("data", "w") as data_file:
573
+ self.to_csv(data_file, index=False)
574
+
575
+ @classmethod
576
+ def from_zlr(cls, file_path: str):
577
+ """
578
+ Read data from a ".zlr" file.
579
+
580
+ :param file_path: The file path.
581
+ :type file_path: str
582
+ :return:
583
+ :rtype:
584
+ """
585
+ file_path = WRFRUNConfig.parse_resource_uri(file_path)
586
+
587
+ with ZipFile(file_path, "r") as zip_file:
588
+ with zip_file.open("header", "r") as header_file:
589
+ header = loads(header_file.read().decode())
590
+
591
+ with zip_file.open("data", "r") as data_file:
592
+ little_r = cls.from_csv(data_file) # type: ignore
593
+
594
+ little_r.set_header(**header) # type: ignore
595
+
596
+ return little_r
597
+
598
+
599
+ __all__ = ["LittleRHead", "LittleRData", "LittleR"]
@@ -0,0 +1,66 @@
1
+ from os import listdir
2
+ from os.path import exists
3
+ from shutil import copyfile
4
+ from typing import List, Optional
5
+
6
+ from wrfrun.core import WRFRUNConfig
7
+ from wrfrun.utils import check_path, logger
8
+
9
+
10
+ def extension_postprocess(output_dir: str, extension_id: str, outputs: Optional[List[str]] = None):
11
+ """
12
+ Apply postprocess after running an extension.
13
+ This function can help you save all files and logs in ``output_dir`` to the ``output_path/extension_id`` and ``output_path/extension_id/logs``,
14
+ ``output_path`` is defined in ``config.yaml``.
15
+ Files end with ``.log`` are treated as log files, while others are treated as outputs.
16
+ You can specify outputs by giving their names through parameter ``outputs``.
17
+
18
+ Save all outputs of ungrib.
19
+
20
+ >>> from wrfrun.extension.utils import extension_postprocess
21
+ >>> output_dir_path = "/WPS/outputs"
22
+ >>> extension_postprocess(output_dir_path, "ungrib")
23
+
24
+ Save outputs start with ``SST_FILE`` of ungrib.
25
+
26
+ >>> from os import listdir
27
+ >>> from wrfrun.extension.utils import extension_postprocess
28
+ >>> output_dir_path = "/WPS/outputs"
29
+ >>> outputs_name = [x for x in listdir(output_dir_path) if x.startswith("SST_FILE")] # type: ignore
30
+ >>> extension_postprocess(output_dir_path, "ungrib", outputs=outputs_name)
31
+
32
+ :param output_dir: Absolute path of output directory.
33
+ :param extension_id: A unique id to distinguish different extensions.
34
+ And all files and logs will be saved to ``output_path/extension_id`` and ``output_path/extension_id/logs``,
35
+ ``output_path`` is defined in ``config.yaml``.
36
+ :param outputs: A list contains multiple filenames. Files in this will be treated as outputs.
37
+ :return:
38
+ """
39
+ output_path = WRFRUNConfig.WRFRUN_OUTPUT_PATH
40
+ output_save_path = f"{output_path}/{extension_id}"
41
+ log_save_path = f"{output_path}/{extension_id}/logs"
42
+
43
+ output_save_path = WRFRUNConfig.parse_resource_uri(output_save_path)
44
+ log_save_path = WRFRUNConfig.parse_resource_uri(log_save_path)
45
+ output_dir = WRFRUNConfig.parse_resource_uri(output_dir)
46
+
47
+ filenames = listdir(output_dir)
48
+ logs = [x for x in filenames if x.endswith(".log")]
49
+ if outputs is None:
50
+ outputs = list(set(filenames) - set(logs))
51
+
52
+ check_path(output_save_path, log_save_path)
53
+
54
+ logger.info(f"Saving outputs and logs to {output_save_path}")
55
+
56
+ for _file in outputs:
57
+ if exists(f"{output_dir}/{_file}"):
58
+ copyfile(f"{output_dir}/{_file}", f"{output_save_path}/{_file}")
59
+ else:
60
+ logger.warning(f"Output {_file} not found")
61
+
62
+ for _log in logs:
63
+ copyfile(f"{output_dir}/{_log}", f"{log_save_path}/{_log}")
64
+
65
+
66
+ __all__ = ["extension_postprocess"]
@@ -0,0 +1,7 @@
1
+ from .base import *
2
+ from .plot import *
3
+ from .utils import *
4
+
5
+ # just to register executables
6
+ from . import wrf
7
+ del wrf
wrfrun/model/base.py ADDED
@@ -0,0 +1,14 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class NamelistName:
6
+ """
7
+ Namelist file names.
8
+ """
9
+ WPS = "namelist.wps"
10
+ WRF = "namelist.input"
11
+ WRFDA = "namelist.input"
12
+
13
+
14
+ __all__ = ["NamelistName"]