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.
- wrfrun/__init__.py +3 -0
- wrfrun/core/__init__.py +5 -0
- wrfrun/core/base.py +680 -0
- wrfrun/core/config.py +717 -0
- wrfrun/core/error.py +80 -0
- wrfrun/core/replay.py +113 -0
- wrfrun/core/server.py +212 -0
- wrfrun/data.py +418 -0
- wrfrun/extension/__init__.py +1 -0
- wrfrun/extension/littler/__init__.py +1 -0
- wrfrun/extension/littler/utils.py +599 -0
- wrfrun/extension/utils.py +66 -0
- wrfrun/model/__init__.py +7 -0
- wrfrun/model/base.py +14 -0
- wrfrun/model/plot.py +54 -0
- wrfrun/model/utils.py +34 -0
- wrfrun/model/wrf/__init__.py +6 -0
- wrfrun/model/wrf/_metgrid.py +71 -0
- wrfrun/model/wrf/_ndown.py +39 -0
- wrfrun/model/wrf/core.py +805 -0
- wrfrun/model/wrf/exec_wrap.py +101 -0
- wrfrun/model/wrf/geodata.py +301 -0
- wrfrun/model/wrf/namelist.py +377 -0
- wrfrun/model/wrf/scheme.py +311 -0
- wrfrun/model/wrf/vtable.py +65 -0
- wrfrun/pbs.py +86 -0
- wrfrun/plot/__init__.py +1 -0
- wrfrun/plot/wps.py +188 -0
- wrfrun/res/__init__.py +22 -0
- wrfrun/res/config.toml.template +136 -0
- wrfrun/res/extension/plotgrids.ncl +216 -0
- wrfrun/res/job_scheduler/pbs.template +6 -0
- wrfrun/res/job_scheduler/slurm.template +6 -0
- wrfrun/res/namelist/namelist.input.da_wrfvar.template +261 -0
- wrfrun/res/namelist/namelist.input.dfi.template +260 -0
- wrfrun/res/namelist/namelist.input.real.template +256 -0
- wrfrun/res/namelist/namelist.input.wrf.template +256 -0
- wrfrun/res/namelist/namelist.wps.template +44 -0
- wrfrun/res/namelist/parame.in.template +11 -0
- wrfrun/res/run.sh.template +16 -0
- wrfrun/run.py +264 -0
- wrfrun/utils.py +257 -0
- wrfrun/workspace.py +88 -0
- wrfrun-0.1.7.dist-info/METADATA +67 -0
- wrfrun-0.1.7.dist-info/RECORD +46 -0
- 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"]
|
wrfrun/model/__init__.py
ADDED