jordancaraballo commited on
Commit
c00748e
·
1 Parent(s): a2aa059

Adding production WRF pipeline

Browse files
wildfire_occurrence/model/analysis/__init__.py ADDED
File without changes
wildfire_occurrence/model/analysis/lightning_analysis.py ADDED
File without changes
wildfire_occurrence/model/analysis/wrf_analysis.py ADDED
@@ -0,0 +1,484 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import xwrf
3
+ import netCDF4
4
+ import logging
5
+ import xarray as xr
6
+ from glob import glob
7
+ from rasterio.crs import CRS
8
+ from wrf import getvar, interplevel
9
+
10
+ __all__ = ["xwrf"]
11
+
12
+
13
+ class WRFAnalysis(object):
14
+
15
+ def __init__(self, input_filename_regex):
16
+
17
+ # wrf input_filename_regex
18
+ self.wrf_filename_regex = input_filename_regex
19
+
20
+ # get list of wrf input filenames
21
+ self.wrf_filenames = sorted(glob(self.wrf_filename_regex))
22
+
23
+ # get dataset into xr using xwrf format
24
+ self.wrf_dataset = xr.open_mfdataset(
25
+ self.wrf_filenames,
26
+ engine="netcdf4",
27
+ parallel=True,
28
+ concat_dim="Time",
29
+ combine="nested",
30
+ chunks=None,
31
+ decode_times=False,
32
+ decode_coords="all",
33
+ ).xwrf.postprocess(drop_diagnostic_variable_components=False)
34
+
35
+ # get CRS from xwrf consolidation
36
+ self.crs = CRS.from_string(
37
+ str(self.wrf_dataset['wrf_projection'].values))
38
+
39
+ # assign crs to override crs=None and to be compliant with rioxarray
40
+ self.wrf_dataset.rio.write_crs(self.crs, inplace=True)
41
+
42
+ # get netCDF objects compatible with wrf-python
43
+ # this is needed since wrf-python does not accept xwrf input
44
+ self.wrf_python_dataset = [
45
+ netCDF4.Dataset(f) for f in self.wrf_filenames]
46
+
47
+ # get list of variables, and remove grid_mapping attribute
48
+ # this is needed to be compliant with rioxarray
49
+ vars_list = list(self.wrf_dataset.data_vars)
50
+ for var in vars_list:
51
+ if 'grid_mapping' in self.wrf_dataset[var].attrs:
52
+ del self.wrf_dataset[var].attrs['grid_mapping']
53
+
54
+ def compute_all_and_write(
55
+ self,
56
+ timeidx=0,
57
+ output_variables=["LANDMASK"],
58
+ output_filename=None
59
+ ):
60
+ """
61
+ We use this function to compute lightning specific variables
62
+ and to store them in the same dataset.
63
+ """
64
+
65
+ """
66
+ ['Times', 'LU_INDEX', 'ZS', 'DZS', 'VAR_SSO', 'BATHYMETRY_FLAG',
67
+ 'U', 'V', 'W','PH', 'PHB', 'T', 'THM', 'HFX_FORCE', 'LH_FORCE',
68
+ 'TSK_FORCE', 'HFX_FORCE_TEND','LH_FORCE_TEND', 'TSK_FORCE_TEND',
69
+ 'MU', 'MUB', 'NEST_POS', 'P', 'PB', 'FNM', 'FNP','RDNW', 'RDN',
70
+ 'DNW', 'DN', 'CFN', 'CFN1', 'THIS_IS_AN_IDEAL_RUN', 'P_HYD', 'Q2',
71
+ 'T2', 'TH2', 'PSFC', 'U10', 'V10', 'LPI', 'RDX', 'RDY', 'AREA2D',
72
+ 'DX2D', 'RESM','ZETATOP', 'CF1', 'CF2', 'CF3', 'ITIMESTEP', 'QVAPOR',
73
+ 'QCLOUD', 'QRAIN', 'QICE','QSNOW', 'QGRAUP', 'QNICE', 'QNRAIN',
74
+ 'SHDMAX','SHDMIN', 'SNOALB', 'TSLB', 'SMOIS','SH2O', 'SMCREL',
75
+ 'SEAICE', 'XICEM', 'SFROFF', 'UDROFF', 'IVGTYP', 'ISLTYP', 'VEGFRA',
76
+ 'GRDFLX', 'ACGRDFLX', 'ACSNOM', 'SNOW', 'SNOWH', 'CANWAT', 'SSTSK',
77
+ 'WATER_DEPTH', 'COSZEN', 'LAI', 'U10E', 'V10E', 'DTAUX3D', 'DTAUY3D',
78
+ 'DUSFCG', 'DVSFCG', 'VAR', 'CON', 'OA1', 'OA2', 'OA3', 'OA4', 'OL1',
79
+ 'OL2', 'OL3', 'OL4', 'TKE_PBL', 'EL_PBL', 'O3_GFS_DU', 'MAPFAC_M',
80
+ 'MAPFAC_U', 'MAPFAC_V', 'MAPFAC_MX', 'MAPFAC_MY', 'MAPFAC_UX',
81
+ 'MAPFAC_UY', 'MAPFAC_VX', 'MF_VX_INV', 'MAPFAC_VY', 'F', 'E',
82
+ 'SINALPHA','COSALPHA', 'HGT', 'TSK', 'P_TOP', 'GOT_VAR_SSO', 'T00',
83
+ 'P00', 'TLP','TISO', 'TLP_STRAT', 'P_STRAT', 'MAX_MSFTX', 'MAX_MSFTY',
84
+ 'RAINC','RAINSH', 'RAINNC', 'SNOWNC', 'GRAUPELNC', 'HAILNC',
85
+ 'REFL_10CM','CLDFRA','SWDOWN', 'GLW', 'SWNORM', 'ACSWUPT', 'ACSWUPTC',
86
+ 'ACSWDNT', 'ACSWDNTC','ACSWUPB', 'ACSWUPBC', 'ACSWDNB', 'ACSWDNBC',
87
+ 'ACLWUPT', 'ACLWUPTC', 'ACLWDNT','ACLWDNTC', 'ACLWUPB', 'ACLWUPBC',
88
+ 'ACLWDNB', 'ACLWDNBC', 'SWUPT', 'SWUPTC', 'SWDNT', 'SWDNTC', 'SWUPB',
89
+ 'SWUPBC', 'SWDNB', 'SWDNBC', 'LWUPT', 'LWUPTC', 'LWDNT', 'LWDNTC',
90
+ 'LWUPB', 'LWUPBC', 'LWDNB', 'LWDNBC', 'OLR', 'ALBEDO', 'ALBBCK',
91
+ 'EMISS', 'NOAHRES', 'TMN', 'XLAND', 'UST', 'PBLH', 'HFX', 'QFX',
92
+ 'LH', 'ACHFX', 'ACLHF', 'SNOWC', 'SR', 'SAVE_TOPO_FROM_REAL',
93
+ 'REFD_MAX', 'ISEEDARR_SPPT', 'ISEEDARR_SKEBS', 'ISEEDARR_RAND_PERTURB',
94
+ 'ISEEDARRAY_SPP_CONV', 'ISEEDARRAY_SPP_PBL', 'ISEEDARRAY_SPP_LSM',
95
+ 'C1H', 'C2H', 'C1F', 'C2F', 'C3H', 'C4H', 'C3F', 'C4F', 'PCB', 'PC',
96
+ 'LANDMASK', 'LAKEMASK', 'SST', 'SST_INPUT', 'air_potential_temperature'
97
+ 'air_pressure', 'geopotential', 'geopotential_height', 'wind_east',
98
+ 'wind_north']
99
+ """
100
+
101
+ # create a copy of the dataset with a single time step
102
+ wrf_dataset_single_time = self.wrf_dataset.isel(Time=timeidx)
103
+
104
+ # compute LPI - LPI is already computed by xwrf
105
+
106
+ # compute Helicity
107
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
108
+ Helicity=self.compute_var('helicity', timeidx))
109
+
110
+ # compute LCL
111
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
112
+ LCL=self.compute_var('lcl', timeidx))
113
+
114
+ # compute PW
115
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
116
+ PW=self.compute_var('pw', timeidx))
117
+
118
+ # compute SLP
119
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
120
+ SLP=self.compute_var('slp', timeidx))
121
+
122
+ # compute GPZ levels
123
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
124
+ GPZ500=self.compute_gpz(500, timeidx))
125
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
126
+ GPZ700=self.compute_gpz(700, timeidx))
127
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
128
+ GPZ750=self.compute_gpz(750, timeidx))
129
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
130
+ GPZ850=self.compute_gpz(850, timeidx))
131
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
132
+ GPZ1000=self.compute_gpz(1000, timeidx))
133
+
134
+ # compute DZ levels
135
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
136
+ DZ500_1000=self.compute_dz(
137
+ wrf_dataset_single_time['GPZ500'],
138
+ wrf_dataset_single_time['GPZ1000']
139
+ )
140
+ )
141
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
142
+ DZ850_1000=self.compute_dz(
143
+ wrf_dataset_single_time['GPZ850'],
144
+ wrf_dataset_single_time['GPZ1000']
145
+ )
146
+ )
147
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
148
+ DZ700_850=self.compute_dz(
149
+ wrf_dataset_single_time['GPZ700'],
150
+ wrf_dataset_single_time['GPZ850']
151
+ )
152
+ )
153
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
154
+ DZ700_1000=self.compute_dz(
155
+ wrf_dataset_single_time['GPZ700'],
156
+ wrf_dataset_single_time['GPZ1000']
157
+ )
158
+ )
159
+
160
+ # compute RH2
161
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
162
+ RH2=self.compute_var('rh2', timeidx))
163
+
164
+ # compute RH levels
165
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
166
+ RH500=self.compute_rh(500, timeidx))
167
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
168
+ RH700=self.compute_rh(700, timeidx))
169
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
170
+ RH800=self.compute_rh(800, timeidx))
171
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
172
+ RH850=self.compute_rh(850, timeidx))
173
+
174
+ # compute T2 - T2 is already computed by xwrf
175
+
176
+ # compute Td2
177
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
178
+ TD2=self.compute_var('td2', timeidx))
179
+
180
+ # compute TD levels
181
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
182
+ TD500=self.compute_td(500, timeidx))
183
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
184
+ TD700=self.compute_td(700, timeidx))
185
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
186
+ TD850=self.compute_td(850, timeidx))
187
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
188
+ TD1000=self.compute_td(1000, timeidx))
189
+
190
+ # compute TC levels
191
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
192
+ TC500=self.compute_tc(500, timeidx))
193
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
194
+ TC700=self.compute_tc(700, timeidx))
195
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
196
+ TC850=self.compute_tc(850, timeidx))
197
+
198
+ # compute TP levels, double-check equation for this one
199
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
200
+ TP500=self.compute_tp(500, timeidx))
201
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
202
+ TP850=self.compute_tp(850, timeidx))
203
+
204
+ # compute SHOW
205
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
206
+ SHOW=self.compute_show(
207
+ wrf_dataset_single_time['TC500'],
208
+ wrf_dataset_single_time['TP850']
209
+ )
210
+ )
211
+
212
+ # compute TT
213
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
214
+ TT=self.compute_tt(
215
+ wrf_dataset_single_time['TC850'],
216
+ wrf_dataset_single_time['TD850'],
217
+ wrf_dataset_single_time['TC500']
218
+ )
219
+ )
220
+
221
+ # compute Rain
222
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
223
+ RAINTotal=self.compute_rain(
224
+ wrf_dataset_single_time['RAINNC'],
225
+ wrf_dataset_single_time['RAINC']
226
+ )
227
+ )
228
+
229
+ # compute W levels
230
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
231
+ W500=self.compute_w(500, timeidx))
232
+
233
+ # compute WA levels
234
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
235
+ WA500=self.compute_wa(500, timeidx))
236
+
237
+ # compute cloud frac levels
238
+ cloud_frac_variables = self.compute_cloudfrac(timeidx)
239
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
240
+ CFLow=cloud_frac_variables[0])
241
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
242
+ CFMed=cloud_frac_variables[1])
243
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
244
+ CFHigh=cloud_frac_variables[2])
245
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
246
+ CFTotal=cloud_frac_variables[3])
247
+
248
+ # compute T levels
249
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
250
+ T500=self.compute_t(500, timeidx))
251
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
252
+ T750=self.compute_t(750, timeidx))
253
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
254
+ T850=self.compute_t(850, timeidx))
255
+
256
+ # PLI IS INCORRECT, WE NEED TO FIX THIS
257
+ # compute PLI, might be incorrectly computed, need to double check
258
+ # https://unidata.github.io/MetPy/latest/api/generated/metpy.calc.lifted_index.html
259
+ wrf_dataset_single_time = wrf_dataset_single_time.assign(
260
+ PLI=self.compute_pli(
261
+ wrf_dataset_single_time['T500'],
262
+ wrf_dataset_single_time['TP500']
263
+ )
264
+ )
265
+
266
+ wrf_dataset_single_time[output_variables].rio.write_crs(
267
+ self.crs, inplace=True)
268
+
269
+ wrf_dataset_single_time = wrf_dataset_single_time[
270
+ output_variables].to_array()
271
+ wrf_dataset_single_time.rio.write_nodata(-10001, inplace=True)
272
+ wrf_dataset_single_time.attrs['long_name'] = output_variables
273
+
274
+ if output_filename is not None:
275
+ wrf_dataset_single_time.rio.to_raster(
276
+ output_filename,
277
+ BIGTIFF='IF_SAFER',
278
+ compress='LZW',
279
+ driver='GTiff',
280
+ dtype='float32',
281
+ recalc_transform=False
282
+ )
283
+
284
+ return wrf_dataset_single_time
285
+
286
+ def compute_var(self, var_name: str, timeidx: int = 0):
287
+ var_output = getvar(
288
+ self.wrf_python_dataset, var_name, timeidx=timeidx)
289
+ var_output = var_output.rio.write_crs(self.crs, inplace=True)
290
+ return var_output.rename({'south_north': 'y', 'west_east': 'x'})
291
+
292
+ def compute_gpz(self, pressure_level: int = 500, timeidx: int = 0):
293
+ pressure = getvar(
294
+ self.wrf_python_dataset, 'pressure', timeidx=timeidx)
295
+ gpz = getvar(
296
+ self.wrf_python_dataset, 'geopt', timeidx=timeidx) / 9.81
297
+ var_output = interplevel(gpz, pressure, pressure_level)
298
+ var_output = var_output.rio.write_crs(self.crs, inplace=True)
299
+ return var_output.rename({'south_north': 'y', 'west_east': 'x'})
300
+
301
+ def compute_rh(self, pressure_level: int = 500, timeidx: int = 0):
302
+ pressure = getvar(
303
+ self.wrf_python_dataset, 'pressure', timeidx=timeidx)
304
+ rh = getvar(
305
+ self.wrf_python_dataset, 'rh', timeidx=timeidx)
306
+ var_output = interplevel(rh, pressure, pressure_level)
307
+ var_output = var_output.rio.write_crs(self.crs, inplace=True)
308
+ return var_output.rename({'south_north': 'y', 'west_east': 'x'})
309
+
310
+ def compute_td(self, pressure_level: int = 500, timeidx: int = 0):
311
+ pressure = getvar(
312
+ self.wrf_python_dataset, 'pressure', timeidx=timeidx)
313
+ td = getvar(
314
+ self.wrf_python_dataset, 'td', timeidx=timeidx)
315
+ var_output = interplevel(td, pressure, pressure_level)
316
+ var_output = var_output.rio.write_crs(self.crs, inplace=True)
317
+ return var_output.rename({'south_north': 'y', 'west_east': 'x'})
318
+
319
+ def compute_t(self, pressure_level: int = 500, timeidx: int = 0):
320
+ pressure = getvar(
321
+ self.wrf_python_dataset, 'pressure', timeidx=timeidx)
322
+ t = getvar(
323
+ self.wrf_python_dataset, 'T', timeidx=timeidx)
324
+ var_output = interplevel(t, pressure, pressure_level)
325
+ var_output = var_output.rio.write_crs(self.crs, inplace=True)
326
+ return var_output.rename({'south_north': 'y', 'west_east': 'x'})
327
+
328
+ def compute_tc(self, pressure_level: int = 500, timeidx: int = 0):
329
+ pressure = getvar(
330
+ self.wrf_python_dataset, 'pressure', timeidx=timeidx)
331
+ tc = getvar(
332
+ self.wrf_python_dataset, 'tc', timeidx=timeidx)
333
+ var_output = interplevel(tc, pressure, pressure_level)
334
+ var_output = var_output.rio.write_crs(self.crs, inplace=True)
335
+ return var_output.rename({'south_north': 'y', 'west_east': 'x'})
336
+
337
+ def compute_tp(self, pressure_level: int = 500, timeidx: int = 0):
338
+ # consider removing this extract calculation, try to find it
339
+ # if its already computed
340
+ pressure = getvar(
341
+ self.wrf_python_dataset, 'pressure', timeidx=timeidx)
342
+ tc = getvar(
343
+ self.wrf_python_dataset, 'tc', timeidx=timeidx)
344
+ tc_interpolated = interplevel(tc, pressure, pressure_level)
345
+ var_output = \
346
+ (tc_interpolated + 273.15) * \
347
+ ((500. / pressure_level)**0.286) - 273.15
348
+ # (tc_850 + 273.15)*((500/850)^0.286) - 273.15
349
+ var_output = var_output.rio.write_crs(self.crs, inplace=True)
350
+ return var_output.rename({'south_north': 'y', 'west_east': 'x'})
351
+
352
+ def compute_dz(self, gpz1, gpz2):
353
+ return gpz1 - gpz2
354
+
355
+ def compute_show(self, tc_500, tp_850):
356
+ return tc_500 - tp_850
357
+
358
+ def compute_tt(self, tc_850, td_850, tc_500):
359
+ return tc_850 + td_850 - 2 * tc_500
360
+
361
+ def compute_rain(self, rain_exp, rain_con):
362
+ return rain_exp + rain_con
363
+
364
+ def compute_w(self, pressure_level: int = 500, timeidx: int = 0):
365
+ pressure = getvar(
366
+ self.wrf_python_dataset, 'pressure', timeidx=timeidx)
367
+ w = getvar(
368
+ self.wrf_python_dataset, 'W', timeidx=timeidx)
369
+ w = w[:pressure.shape[0], :, :]
370
+ var_output = interplevel(w, pressure, pressure_level)
371
+ var_output = var_output.rio.write_crs(self.crs, inplace=True)
372
+ return var_output.rename({'south_north': 'y', 'west_east': 'x'})
373
+
374
+ def compute_wa(self, pressure_level: int = 500, timeidx: int = 0):
375
+ pressure = getvar(
376
+ self.wrf_python_dataset, 'pressure', timeidx=timeidx)
377
+ wa = getvar(
378
+ self.wrf_python_dataset, 'wa', timeidx=timeidx)
379
+ var_output = interplevel(wa, pressure, pressure_level)
380
+ var_output = var_output.rio.write_crs(self.crs, inplace=True)
381
+ return var_output.rename({'south_north': 'y', 'west_east': 'x'})
382
+
383
+ def compute_cloudfrac(self, timeidx: int = 0):
384
+ cloudfrac = getvar(
385
+ self.wrf_python_dataset, "cloudfrac", timeidx=timeidx)
386
+ cloudfrac = cloudfrac.rename({'south_north': 'y', 'west_east': 'x'})
387
+ cloudfrac = cloudfrac.rio.write_crs(self.crs, inplace=True)
388
+ low_cloudfrac = cloudfrac[0, :, :]
389
+ mid_cloudfrac = cloudfrac[1, :, :]
390
+ high_cloudfrac = cloudfrac[2, :, :]
391
+ total_cloudfrac = (low_cloudfrac + mid_cloudfrac + high_cloudfrac) / 3
392
+ return low_cloudfrac, mid_cloudfrac, high_cloudfrac, total_cloudfrac
393
+
394
+ def compute_pli(self, t_500, tp_500):
395
+ return t_500 - tp_500
396
+
397
+
398
+ # -----------------------------------------------------------------------------
399
+ # Invoke the main
400
+ # -----------------------------------------------------------------------------
401
+ if __name__ == "__main__":
402
+
403
+ filename_regex = \
404
+ '/explore/nobackup/projects/ilab/projects/LobodaTFO/data/WRF_Data/' + \
405
+ 'WRF_Simulations/2022-07-03/wrfout_d02*'
406
+
407
+ filename_regex = \
408
+ '/explore/nobackup/projects/ilab/projects/LobodaTFO/operations/' + \
409
+ '2023-06-24_2023-07-04/output/wrfout_d02*'
410
+ data_filenames = glob(filename_regex)
411
+
412
+ for filename in data_filenames:
413
+
414
+ # /explore/nobackup/projects/ilab/projects/LobodaTFO/data/WRF_Data/WRF_Simulations/*/wrfout_d02*
415
+ # create WRFAnalysis object, stores wrf_dataset
416
+ wrf_analysis = WRFAnalysis(filename)
417
+
418
+ # output variables for the lightning model
419
+ # these are the variables of importance between both 24 and 48 models
420
+ # output_variables = [
421
+ # 'PLI', 'GPZ500', 'GPZ700', 'TD500', 'CFTotal',
422
+ # 'RH500', 'SLP', 'W500', 'RH700', 'CFLow', 'TD2',
423
+ # 'TT', 'Helicity', 'GPZ850', 'SHOW', 'LCL',
424
+ # 'RH2', 'T850', 'RH850', 'Rain', 'T2', 'DZ700_850',
425
+ # 'RH800', 'T500', 'PW', 'T750'
426
+ # ] # BT missing
427
+
428
+ output_variables = [
429
+ 'CFTotal', 'CFLow', 'CFMed', 'CFHigh',
430
+ 'DZ700_850',
431
+ 'GPZ500', 'GPZ700', 'GPZ750', 'GPZ850',
432
+ 'Helicity',
433
+ 'LCL',
434
+ 'PLI', 'PW',
435
+ 'RAINTotal',
436
+ 'RH2', 'RH500', 'RH700', 'RH800', 'RH850',
437
+ 'SHOW',
438
+ 'SLP',
439
+ 'TD2', 'TD500',
440
+ 'TT', 'T2', 'T500', 'T750', 'T850',
441
+ 'W500', 'WA500'
442
+ ] # BT missing
443
+
444
+ # looks good - Helicity, SLP, 'GPZ500', 'TD500', 'RH500',
445
+ # 'TD2', 'LCL', 'PW', 'RH2', 'RAINTotal'
446
+ # TT, TC500
447
+ # wrong - PLI
448
+ # maybe
449
+ # CFTotoal (wrong, local is 0, ours is higher)
450
+ # CFlow (wrong, local is 0, ours is higher)
451
+ # CFMed (wrong, local is 0, ours is higher)
452
+ # CFHigh (wrong, local is 0, ours is higher)
453
+ # 'GPZ750' no-data problems, kind of similar
454
+ # 'GPZ700' no local data to compare to
455
+ # 'RH700' no local data to compare to
456
+ # 'GPZ850' no local data to compare to
457
+ # 'SHOW' wrong because of no data
458
+ # 'T500' no local data to compare to
459
+ # 'RH800' wrong because of no data
460
+ # 'T2' numbers look far away - our T2 is in Kelvin
461
+ # 'RH850' no local data to compare to
462
+ # 'T850' no local data to compare to
463
+ # 'W500'
464
+ # 'WA500' -looks good
465
+ # 'DZ700_850' - looks good
466
+ # 'BT' missing
467
+
468
+ output_dir = 'output' # os.path.dirname(os.path.abspath(filename))
469
+
470
+ # TODO: make this for loop parallel later
471
+ for t_idx, delta_time in enumerate(wrf_analysis.wrf_dataset.Times.values):
472
+
473
+ logging.info(f'Processing t_idx: {t_idx}, timestamp: {delta_time}')
474
+ output_filename = os.path.join(
475
+ output_dir,
476
+ f"d02_{delta_time.astype(str).replace(':', '-')}.tif")
477
+
478
+ if not os.path.isfile(output_filename):
479
+
480
+ wrf_analysis.compute_all_and_write(
481
+ timeidx=t_idx,
482
+ output_variables=output_variables,
483
+ output_filename=output_filename
484
+ )
wildfire_occurrence/model/config.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Optional
2
  from dataclasses import dataclass, field
3
 
4
 
@@ -34,3 +34,25 @@ class Config:
34
  wrf_config: Optional[dict] = field(
35
  default_factory=lambda: {
36
  'interval_seconds': 10800, 'num_metgrid_levels': 27})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
  from dataclasses import dataclass, field
3
 
4
 
 
34
  wrf_config: Optional[dict] = field(
35
  default_factory=lambda: {
36
  'interval_seconds': 10800, 'num_metgrid_levels': 27})
37
+
38
+ # Output filename from WRF to extract variables from
39
+ wrf_output_filename: Optional[str] = 'wrfout_d02_*_00:00:00'
40
+
41
+ # List for posprocessing of variables
42
+ wrf_output_variables: Optional[List[str]] = field(
43
+ default_factory=lambda: [
44
+ 'CFTotal', 'CFLow', 'CFMed', 'CFHigh',
45
+ 'DZ700_850',
46
+ 'GPZ500', 'GPZ700', 'GPZ750', 'GPZ850',
47
+ 'Helicity',
48
+ 'LCL',
49
+ 'PLI', 'PW',
50
+ 'RAINTotal',
51
+ 'RH2', 'RH500', 'RH700', 'RH800', 'RH850',
52
+ 'SHOW',
53
+ 'SLP',
54
+ 'TD2', 'TD500',
55
+ 'TT', 'T2', 'T500', 'T750', 'T850',
56
+ 'W500', 'WA500'
57
+ ]
58
+ )
wildfire_occurrence/model/data_download/ncep_fnl.py CHANGED
@@ -9,7 +9,15 @@ from datetime import date
9
  from typing import List, Literal
10
  from multiprocessing import Pool, cpu_count
11
 
12
- __data_source__ = 'https://rda.ucar.edu/datasets/ds083.2'
 
 
 
 
 
 
 
 
13
 
14
 
15
  class NCEP_FNL(object):
@@ -20,6 +28,8 @@ class NCEP_FNL(object):
20
  start_date: str = date.today(),
21
  end_date: str = date.today(),
22
  hour_intervals: List = ['00', '06', '12', '18'],
 
 
23
  n_procs: int = cpu_count()
24
  ):
25
 
@@ -37,22 +47,37 @@ class NCEP_FNL(object):
37
  if isinstance(end_date, str):
38
  self.end_date = datetime.datetime.strptime(
39
  end_date, '%Y-%m-%d').date()
 
 
40
  else:
41
  self.end_date = end_date
42
 
43
  # define hour intervals
44
  self.hour_intervals = hour_intervals
45
 
46
- # TODO: IF WE ARE DOWNLOADING INTO THE FUTURE
47
- # THEN WE NEED TO SPECIFY THIS IS FROM THE OTHER
48
- # DATASET AND NOT FROM THE CURRENT GFS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- # make sure we do not download data into the future
51
- # if self.end_date > datetime.datetime.now():
52
- # self.end_date = datetime.datetime.now()
53
- # self.hour_intervals = [
54
- # d for d in self.hour_intervals
55
- # if int(d) <= self.end_date.hour - 6]
56
  logging.info(
57
  f'Downloading data from {self.start_date} to {self.end_date}')
58
 
@@ -84,18 +109,23 @@ class NCEP_FNL(object):
84
  }
85
 
86
  # define data url
87
- self.data_url = 'https://rda.ucar.edu'
88
 
 
89
  if self.start_date.year < 2008:
90
  self.grib_format = 'grib1'
91
  else:
92
  self.grib_format = 'grib2'
93
 
94
- self.dataset_path = f'/data/OS/ds083.2/{self.grib_format}'
95
-
96
  # nnumber of processors to use
97
  self.n_procs = n_procs
98
 
 
 
 
 
 
 
99
  def _authenticate(self, action: Literal["auth", "cleanup"] = "auth"):
100
 
101
  if action == "cleanup":
@@ -167,18 +197,34 @@ class NCEP_FNL(object):
167
  return
168
 
169
  def _get_filenames(self):
 
170
  filenames_list = []
171
- daterange = pd.date_range(self.start_date, self.end_date)
172
- for single_date in daterange:
173
- year = single_date.strftime("%Y")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  for hour in self.hour_intervals:
175
  filename = os.path.join(
176
- self.dataset_path,
177
- f'{year}/{single_date.strftime("%Y.%m")}',
178
- f'fnl_{single_date.strftime("%Y%m%d")}_' +
179
- f'{hour}_00.{self.grib_format}'
180
  )
181
  filenames_list.append(filename)
 
182
  return filenames_list
183
 
184
 
@@ -188,15 +234,16 @@ class NCEP_FNL(object):
188
  if __name__ == "__main__":
189
 
190
  dates = [
191
- '2003-06-23',
192
- '2005-06-11',
193
- '2023-06-04'
 
194
  ]
195
 
196
  for init_date in dates:
197
 
198
  start_date = datetime.datetime.strptime(init_date, "%Y-%m-%d")
199
- end_date = (start_date + datetime.timedelta(days=10))
200
 
201
  downloader = NCEP_FNL(
202
  output_dir='output/NCEP_FNL',
 
9
  from typing import List, Literal
10
  from multiprocessing import Pool, cpu_count
11
 
12
+ __past_data_source__ = 'https://rda.ucar.edu/datasets/ds083.2'
13
+ __future_data_source__ = 'https://rda.ucar.edu/datasets/ds084.1'
14
+ __projection_data_source__ = 'https://rda.ucar.edu/datasets/ds316-1'
15
+
16
+ DATASET_URL = {
17
+ 'prod': 'https://nomads.ncep.noaa.gov/pub/data/nccf/com/gfs/prod',
18
+ 'ds084.1': 'https://data.rda.ucar.edu/ds084.1', # future-short
19
+ 'ds083.2': 'https://stratus.rda.ucar.edu/ds083.2', # past
20
+ }
21
 
22
 
23
  class NCEP_FNL(object):
 
28
  start_date: str = date.today(),
29
  end_date: str = date.today(),
30
  hour_intervals: List = ['00', '06', '12', '18'],
31
+ dataset: str = None,
32
+ resolution: str = '1p00', # 1p00, 0p50, 0p25
33
  n_procs: int = cpu_count()
34
  ):
35
 
 
47
  if isinstance(end_date, str):
48
  self.end_date = datetime.datetime.strptime(
49
  end_date, '%Y-%m-%d').date()
50
+ elif isinstance(end_date, datetime.datetime):
51
+ self.end_date = end_date.date()
52
  else:
53
  self.end_date = end_date
54
 
55
  # define hour intervals
56
  self.hour_intervals = hour_intervals
57
 
58
+ # define resolution to download
59
+ self.resolution = resolution
60
+
61
+ # dataset to download, select based on past vs future
62
+ if dataset is not None:
63
+ # this means the user specified the dataset manually
64
+ self.dataset = dataset
65
+ else:
66
+ # automatically select future dataset
67
+ if self.end_date > datetime.datetime.now().date():
68
+
69
+ # specify NOAA production GFS dataset
70
+ self.dataset = 'prod'
71
+
72
+ # modify the hour interval to match end date
73
+ # 384 is the longest time interval produced by NOAA
74
+ self.hour_intervals = [
75
+ f'{interval:03}' for interval in range(0, 385, 3)]
76
+
77
+ # automatically select past archive dataset
78
+ else:
79
+ self.dataset = 'ds083.2'
80
 
 
 
 
 
 
 
81
  logging.info(
82
  f'Downloading data from {self.start_date} to {self.end_date}')
83
 
 
109
  }
110
 
111
  # define data url
112
+ self.set_data_url(self.dataset)
113
 
114
+ # setup grib format
115
  if self.start_date.year < 2008:
116
  self.grib_format = 'grib1'
117
  else:
118
  self.grib_format = 'grib2'
119
 
 
 
120
  # nnumber of processors to use
121
  self.n_procs = n_procs
122
 
123
+ def set_data_url(self, dataset: str):
124
+ try:
125
+ self.data_url = DATASET_URL[dataset]
126
+ except KeyError:
127
+ sys.exit(f'{dataset} dataset not supported')
128
+
129
  def _authenticate(self, action: Literal["auth", "cleanup"] = "auth"):
130
 
131
  if action == "cleanup":
 
197
  return
198
 
199
  def _get_filenames(self):
200
+ # list to store filenames
201
  filenames_list = []
202
+
203
+ # dataset path for ds083.2, past archive data
204
+ if self.dataset == 'ds083.2':
205
+ daterange = pd.date_range(self.start_date, self.end_date)
206
+ for single_date in daterange:
207
+ year = single_date.strftime("%Y")
208
+ for hour in self.hour_intervals:
209
+ filename = os.path.join(
210
+ f'/{self.grib_format}/',
211
+ f'{year}/{single_date.strftime("%Y.%m")}',
212
+ f'fnl_{single_date.strftime("%Y%m%d")}_' +
213
+ f'{hour}_00.{self.grib_format}'
214
+ )
215
+ filenames_list.append(filename)
216
+
217
+ # dataset path for production
218
+ # https://nomads.ncep.noaa.gov/pub/data/nccf/com/gfs/prod/gfs.20230623/00/atmos/gfs.t00z.pgrb2.1p00.f000
219
+ elif self.dataset == 'prod':
220
  for hour in self.hour_intervals:
221
  filename = os.path.join(
222
+ f'/gfs.{self.start_date.strftime("%Y%m%d")}',
223
+ '00/atmos',
224
+ f'gfs.t00z.pgrb2.{self.resolution}.f{hour}'
 
225
  )
226
  filenames_list.append(filename)
227
+
228
  return filenames_list
229
 
230
 
 
234
  if __name__ == "__main__":
235
 
236
  dates = [
237
+ #'2003-06-23',
238
+ #'2005-06-11',
239
+ #'2023-06-04'
240
+ '2023-06-23'
241
  ]
242
 
243
  for init_date in dates:
244
 
245
  start_date = datetime.datetime.strptime(init_date, "%Y-%m-%d")
246
+ end_date = (start_date + datetime.timedelta(days=2))
247
 
248
  downloader = NCEP_FNL(
249
  output_dir='output/NCEP_FNL',
wildfire_occurrence/model/pipelines/wrf_pipeline.py CHANGED
@@ -5,11 +5,15 @@ import logging
5
  import datetime
6
  from glob import glob
7
  from pathlib import Path
 
 
 
8
  from jinja2 import Environment, PackageLoader, select_autoescape
9
 
10
  from wildfire_occurrence.model.config import Config
11
  from wildfire_occurrence.model.common import read_config
12
  from wildfire_occurrence.model.data_download.ncep_fnl import NCEP_FNL
 
13
 
14
 
15
  class WRFPipeline(object):
@@ -19,6 +23,7 @@ class WRFPipeline(object):
19
  config_filename: str,
20
  start_date: str,
21
  forecast_lenght: str,
 
22
  ):
23
 
24
  # Configuration file intialization
@@ -42,7 +47,7 @@ class WRFPipeline(object):
42
  f'{self.end_date.strftime("%Y-%m-%d")}'
43
  )
44
  os.makedirs(self.simulation_dir, exist_ok=True)
45
- logging.info(f'Created output directory {self.simulation_dir}')
46
 
47
  # Setup data_dir
48
  self.data_dir = os.path.join(self.simulation_dir, 'data')
@@ -55,6 +60,8 @@ class WRFPipeline(object):
55
  # Setup wps directory
56
  self.local_wps_path = os.path.join(self.simulation_dir, 'WPS')
57
  self.local_wrf_path = os.path.join(self.simulation_dir, 'em_real')
 
 
58
 
59
  # Setup configuration filenames
60
  self.wps_conf_filename = os.path.join(self.conf_dir, 'namelist.wps')
@@ -65,6 +72,9 @@ class WRFPipeline(object):
65
  self.local_wrf_conf = os.path.join(
66
  self.local_wrf_path, 'namelist.input')
67
 
 
 
 
68
  # -------------------------------------------------------------------------
69
  # setup
70
  # -------------------------------------------------------------------------
@@ -120,15 +130,15 @@ class WRFPipeline(object):
120
  if not self.conf.multi_node:
121
  geodrid_cmd = \
122
  'singularity exec -B /explore/nobackup/projects/ilab,' + \
123
- '$NOBACKUP,/lscratch,/panfs/ccds02/nobackup/projects/ilab ' + \
124
  f'{self.conf.container_path} ' + \
125
- 'mpirun -np 40 --oversubscribe ./geogrid.exe'
126
  else:
127
- geodrid_cmd = \
128
- 'srun --mpi=pmix -N 2 -n 80 singularity exec -B /explore/nobackup/projects/ilab,' + \
129
- '$NOBACKUP,/lscratch,/panfs/ccds02/nobackup/projects/ilab ' + \
130
- f'{self.conf.container_path} ' + \
131
- './geogrid.exe'
132
 
133
  # run geogrid command
134
  os.system(geodrid_cmd)
@@ -160,22 +170,24 @@ class WRFPipeline(object):
160
  os.chdir(self.local_wps_path)
161
  logging.info(f'Changed working directory to {self.local_wps_path}')
162
 
 
 
 
 
163
  # run link_grib
164
- os.system(
165
- f'./link_grib.csh {self.data_dir}/{str(self.start_date.year)}/' +
166
- f'fnl_{str(self.start_date.year)}')
167
  logging.info('Done with link_grib.csh')
168
 
169
  # setup ungrib command
170
  if not self.conf.multi_node:
171
  ungrib_cmd = \
172
  'singularity exec -B /explore/nobackup/projects/ilab,' + \
173
- '$NOBACKUP,/lscratch,/panfs/ccds02/nobackup/projects/ilab ' + \
174
- f'{self.conf.container_path} mpirun ./ungrib.exe'
175
  else:
176
  ungrib_cmd = \
177
  'srun --mpi=pmix -N 1 -n 1 singularity exec -B /explore/nobackup/projects/ilab,' + \
178
- '$NOBACKUP,/lscratch,/panfs/ccds02/nobackup/projects/ilab ' + \
179
  f'{self.conf.container_path} ' + \
180
  './ungrib.exe'
181
 
@@ -203,13 +215,13 @@ class WRFPipeline(object):
203
  if not self.conf.multi_node:
204
  metgrid_cmd = \
205
  'singularity exec -B /explore/nobackup/projects/ilab,' + \
206
- '$NOBACKUP,/lscratch,/panfs/ccds02/nobackup/projects/ilab ' + \
207
  f'{self.conf.container_path} ' + \
208
- 'mpirun -np 40 --oversubscribe ./metgrid.exe'
209
  else:
210
  metgrid_cmd = \
211
- 'srun --mpi=pmix -N 1 -n 40 singularity exec -B /explore/nobackup/projects/ilab,' + \
212
- '$NOBACKUP,/lscratch,/panfs/ccds02/nobackup/projects/ilab ' + \
213
  f'{self.conf.container_path} ' + \
214
  './metgrid.exe'
215
 
@@ -250,13 +262,13 @@ class WRFPipeline(object):
250
  if not self.conf.multi_node:
251
  real_cmd = \
252
  'singularity exec -B /explore/nobackup/projects/ilab,' + \
253
- '$NOBACKUP,/lscratch,/panfs/ccds02/nobackup/projects/ilab ' + \
254
  f'{self.conf.container_path} ' + \
255
- 'mpirun -np 40 --oversubscribe ./real.exe'
256
  else:
257
  real_cmd = \
258
  'srun --mpi=pmix -N 2 -n 80 singularity exec -B /explore/nobackup/projects/ilab,' + \
259
- '$NOBACKUP,/lscratch,/panfs/ccds02/nobackup/projects/ilab ' + \
260
  f'{self.conf.container_path} ' + \
261
  './real.exe'
262
 
@@ -284,13 +296,13 @@ class WRFPipeline(object):
284
  if not self.conf.multi_node:
285
  wrf_cmd = \
286
  'singularity exec -B /explore/nobackup/projects/ilab,' + \
287
- '$NOBACKUP,/lscratch,/panfs/ccds02/nobackup/projects/ilab ' + \
288
  f'{self.conf.container_path} ' + \
289
- 'mpirun -np 40 --oversubscribe ./wrf.exe'
290
  else:
291
  wrf_cmd = \
292
  'srun --mpi=pmix -N 2 -n 80 singularity exec -B /explore/nobackup/projects/ilab,' + \
293
- '$NOBACKUP,/lscratch,/panfs/ccds02/nobackup/projects/ilab ' + \
294
  f'{self.conf.container_path} ' + \
295
  './wrf.exe'
296
 
@@ -302,6 +314,114 @@ class WRFPipeline(object):
302
 
303
  return
304
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  # -------------------------------------------------------------------------
306
  # setup_wps_config
307
  # -------------------------------------------------------------------------
 
5
  import datetime
6
  from glob import glob
7
  from pathlib import Path
8
+ from itertools import repeat
9
+ from omegaconf import OmegaConf
10
+ from multiprocessing import Pool, cpu_count
11
  from jinja2 import Environment, PackageLoader, select_autoescape
12
 
13
  from wildfire_occurrence.model.config import Config
14
  from wildfire_occurrence.model.common import read_config
15
  from wildfire_occurrence.model.data_download.ncep_fnl import NCEP_FNL
16
+ from wildfire_occurrence.model.analysis.wrf_analysis import WRFAnalysis
17
 
18
 
19
  class WRFPipeline(object):
 
23
  config_filename: str,
24
  start_date: str,
25
  forecast_lenght: str,
26
+ multi_node: bool = False
27
  ):
28
 
29
  # Configuration file intialization
 
47
  f'{self.end_date.strftime("%Y-%m-%d")}'
48
  )
49
  os.makedirs(self.simulation_dir, exist_ok=True)
50
+ logging.info(f'Created model output directory {self.simulation_dir}')
51
 
52
  # Setup data_dir
53
  self.data_dir = os.path.join(self.simulation_dir, 'data')
 
60
  # Setup wps directory
61
  self.local_wps_path = os.path.join(self.simulation_dir, 'WPS')
62
  self.local_wrf_path = os.path.join(self.simulation_dir, 'em_real')
63
+ self.local_wrf_output = os.path.join(self.simulation_dir, 'output')
64
+ self.local_wrf_output_vars = os.path.join(self.simulation_dir, 'variables')
65
 
66
  # Setup configuration filenames
67
  self.wps_conf_filename = os.path.join(self.conf_dir, 'namelist.wps')
 
72
  self.local_wrf_conf = os.path.join(
73
  self.local_wrf_path, 'namelist.input')
74
 
75
+ # setup multi_node variable
76
+ self.conf.multi_node = multi_node
77
+
78
  # -------------------------------------------------------------------------
79
  # setup
80
  # -------------------------------------------------------------------------
 
130
  if not self.conf.multi_node:
131
  geodrid_cmd = \
132
  'singularity exec -B /explore/nobackup/projects/ilab,' + \
133
+ '$NOBACKUP,/panfs/ccds02/nobackup/projects/ilab ' + \
134
  f'{self.conf.container_path} ' + \
135
+ f'mpirun -np {cpu_count()} --oversubscribe ./geogrid.exe'
136
  else:
137
+ geodrid_cmd = 'bash /panfs/ccds02/nobackup/projects/ilab/projects/LobodaTFO/operations/2015-07-23_2015-08-02/WPS/run_geogrid.sh'
138
+ #'mpirun -np 40 --host gpu016 --oversubscribe singularity exec -B /explore/nobackup/projects/ilab,' + \
139
+ #'$NOBACKUP,/lscratch,/panfs/ccds02/nobackup/projects/ilab ' + \
140
+ #f'{self.conf.container_path} ' + \
141
+ #'bash /panfs/ccds02/nobackup/projects/ilab/projects/LobodaTFO/operations/2015-07-23_2015-08-02/WPS/run_geogrid.sh'
142
 
143
  # run geogrid command
144
  os.system(geodrid_cmd)
 
170
  os.chdir(self.local_wps_path)
171
  logging.info(f'Changed working directory to {self.local_wps_path}')
172
 
173
+ # find all files in directory, and extract common prefix
174
+ common_prefix = os.path.commonprefix(
175
+ glob(f'{self.data_dir}/{str(self.start_date.year)}/*'))
176
+
177
  # run link_grib
178
+ os.system(f'./link_grib.csh {common_prefix}')
 
 
179
  logging.info('Done with link_grib.csh')
180
 
181
  # setup ungrib command
182
  if not self.conf.multi_node:
183
  ungrib_cmd = \
184
  'singularity exec -B /explore/nobackup/projects/ilab,' + \
185
+ '$NOBACKUP,/panfs/ccds02/nobackup/projects/ilab ' + \
186
+ f'{self.conf.container_path} ./ungrib.exe'
187
  else:
188
  ungrib_cmd = \
189
  'srun --mpi=pmix -N 1 -n 1 singularity exec -B /explore/nobackup/projects/ilab,' + \
190
+ '$NOBACKUP,/panfs/ccds02/nobackup/projects/ilab ' + \
191
  f'{self.conf.container_path} ' + \
192
  './ungrib.exe'
193
 
 
215
  if not self.conf.multi_node:
216
  metgrid_cmd = \
217
  'singularity exec -B /explore/nobackup/projects/ilab,' + \
218
+ '$NOBACKUP,/panfs/ccds02/nobackup/projects/ilab ' + \
219
  f'{self.conf.container_path} ' + \
220
+ f'mpirun -np {cpu_count()} --oversubscribe ./metgrid.exe'
221
  else:
222
  metgrid_cmd = \
223
+ f'srun --mpi=pmix -N 1 -n {cpu_count()} singularity exec -B /explore/nobackup/projects/ilab,' + \
224
+ '$NOBACKUP,/panfs/ccds02/nobackup/projects/ilab ' + \
225
  f'{self.conf.container_path} ' + \
226
  './metgrid.exe'
227
 
 
262
  if not self.conf.multi_node:
263
  real_cmd = \
264
  'singularity exec -B /explore/nobackup/projects/ilab,' + \
265
+ '$NOBACKUP,/panfs/ccds02/nobackup/projects/ilab ' + \
266
  f'{self.conf.container_path} ' + \
267
+ f'mpirun -np {cpu_count()} --oversubscribe ./real.exe'
268
  else:
269
  real_cmd = \
270
  'srun --mpi=pmix -N 2 -n 80 singularity exec -B /explore/nobackup/projects/ilab,' + \
271
+ '$NOBACKUP,/panfs/ccds02/nobackup/projects/ilab ' + \
272
  f'{self.conf.container_path} ' + \
273
  './real.exe'
274
 
 
296
  if not self.conf.multi_node:
297
  wrf_cmd = \
298
  'singularity exec -B /explore/nobackup/projects/ilab,' + \
299
+ '$NOBACKUP,/panfs/ccds02/nobackup/projects/ilab ' + \
300
  f'{self.conf.container_path} ' + \
301
+ f'mpirun -np {cpu_count()} --oversubscribe ./wrf.exe'
302
  else:
303
  wrf_cmd = \
304
  'srun --mpi=pmix -N 2 -n 80 singularity exec -B /explore/nobackup/projects/ilab,' + \
305
+ '$NOBACKUP,/panfs/ccds02/nobackup/projects/ilab ' + \
306
  f'{self.conf.container_path} ' + \
307
  './wrf.exe'
308
 
 
314
 
315
  return
316
 
317
+ # -------------------------------------------------------------------------
318
+ # postprocess
319
+ # -------------------------------------------------------------------------
320
+ def postprocess(self) -> None:
321
+
322
+ logging.info('Preparing to postprocess and extract variables')
323
+
324
+ # create output directory
325
+ os.makedirs(self.local_wrf_output, exist_ok=True)
326
+ logging.info(f'Created WRF output directory {self.local_wrf_output}')
327
+
328
+ # if the files have not been moved, move them to the output dir
329
+ if len(os.listdir(self.local_wrf_output)) == 0:
330
+
331
+ # get filenames, make sure they exist
332
+ wrf_output_filenames = \
333
+ glob(os.path.join(self.local_wrf_path, 'auxhist24_d0*')) + \
334
+ glob(os.path.join(self.local_wrf_path, 'wrfout_d0*'))
335
+ assert len(wrf_output_filenames) > 0, \
336
+ 'WRF output (auxhist24_d0*, wrfout_d0*) not found. Re-run WRF.'
337
+
338
+ # move output to clean directory
339
+ for filename in wrf_output_filenames:
340
+ shutil.move(filename, self.local_wrf_output)
341
+ logging.info(f'Moved WRF output to {self.local_wrf_output}')
342
+
343
+ # Get WRF output filename
344
+ wrf_output_filename = glob(
345
+ os.path.join(self.local_wrf_output, self.conf.wrf_output_filename))
346
+ assert len(wrf_output_filename) == 1, \
347
+ f'WRF output filename not found under {self.local_wrf_output}.'
348
+
349
+ # Get first item from the list
350
+ wrf_output_filename = wrf_output_filename[0]
351
+ logging.info(f'Loading {wrf_output_filename}')
352
+
353
+ # create WRFAnalysis object, stores wrf_dataset
354
+ wrf_analysis = WRFAnalysis(wrf_output_filename)
355
+
356
+ # variables output dir
357
+ os.makedirs(self.local_wrf_output_vars, exist_ok=True)
358
+ logging.info(
359
+ f'Created WRF vars output directory {self.local_wrf_output_vars}')
360
+
361
+ """
362
+ # parallel extraction of variables
363
+ p = Pool(processes=cpu_count())
364
+ p.starmap(
365
+ self._compute_variables,
366
+ zip(
367
+ range(len(wrf_analysis.wrf_dataset.Times.values)),
368
+ wrf_analysis.wrf_dataset.Times.values,
369
+ repeat(wrf_output_filename)
370
+ )
371
+ )
372
+ """
373
+
374
+ # serial extraction of variables
375
+ for t_idx, delta_time in \
376
+ enumerate(wrf_analysis.wrf_dataset.Times.values):
377
+
378
+ logging.info(f'Processing t_idx: {t_idx}, timestamp: {delta_time}')
379
+
380
+ # setup output filename
381
+ output_filename = os.path.join(
382
+ self.local_wrf_output_vars,
383
+ f"d02_{delta_time.astype(str).replace(':', '-')}.tif")
384
+
385
+ # if the imagery does not exist
386
+ if not os.path.isfile(output_filename):
387
+
388
+ # compute WRF variables and output to disk
389
+ wrf_analysis.compute_all_and_write(
390
+ timeidx=t_idx,
391
+ output_variables=OmegaConf.to_object(
392
+ self.conf.wrf_output_variables),
393
+ output_filename=output_filename
394
+ )
395
+
396
+ # -------------------------------------------------------------------------
397
+ # _compute_variables
398
+ # -------------------------------------------------------------------------
399
+ def _compute_variables(self, t_idx, delta_time, wrf_output_filename):
400
+
401
+ logging.info(f'Processing t_idx: {t_idx}, timestamp: {delta_time}')
402
+
403
+ # setup output filename
404
+ output_filename = os.path.join(
405
+ self.local_wrf_output_vars,
406
+ f"d02_{delta_time.astype(str).replace(':', '-')}.tif")
407
+
408
+ # if the imagery does not exist
409
+ if not os.path.isfile(output_filename):
410
+
411
+ # unfortunately, netCDF object is not pickable, thus we need
412
+ # to redifine it on every single process, hopefully there
413
+ # will be an implementation on it at some point by them
414
+ wrf_analysis = WRFAnalysis(wrf_output_filename)
415
+
416
+ # compute WRF variables and output to disk
417
+ wrf_analysis.compute_all_and_write(
418
+ timeidx=t_idx,
419
+ output_variables=OmegaConf.to_object(
420
+ self.conf.wrf_output_variables),
421
+ output_filename=output_filename
422
+ )
423
+ return
424
+
425
  # -------------------------------------------------------------------------
426
  # setup_wps_config
427
  # -------------------------------------------------------------------------
wildfire_occurrence/templates/config.yaml CHANGED
@@ -12,13 +12,46 @@ wps_config:
12
 
13
  wrf_config:
14
  interval_seconds: 10800
15
- num_metgrid_levels: 27
16
 
17
  # Directories to mount inside the container
18
  container_mounts:
19
  - /explore/nobackup/projects/ilab'
20
  - '$NOBACKUP'
21
- - '/lscratch'
22
  - '/panfs/ccds02/nobackup/projects/ilab'
23
 
24
- multi_node: False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  wrf_config:
14
  interval_seconds: 10800
15
+ num_metgrid_levels: 34
16
 
17
  # Directories to mount inside the container
18
  container_mounts:
19
  - /explore/nobackup/projects/ilab'
20
  - '$NOBACKUP'
 
21
  - '/panfs/ccds02/nobackup/projects/ilab'
22
 
23
+ multi_node: False
24
+
25
+ wrf_output_filename: 'wrfout_d02_*_00:00:00'
26
+
27
+ wrf_output_variables:
28
+ - 'CFTotal'
29
+ # - 'CFLow'
30
+ # - 'CFMed'
31
+ # - 'CFHigh'
32
+ # - 'DZ700_850'
33
+ # - 'GPZ500'
34
+ # - 'GPZ700'
35
+ # - 'GPZ750'
36
+ # - 'GPZ850'
37
+ # - 'Helicity'
38
+ # - 'LCL'
39
+ # - 'PLI'
40
+ # - 'PW'
41
+ # - 'RAINTotal'
42
+ # - 'RH2'
43
+ # - 'RH500'
44
+ # - 'RH700'
45
+ # - 'RH800'
46
+ # - 'RH850'
47
+ # - 'SHOW'
48
+ # - 'SLP'
49
+ # - 'TD2'
50
+ # - 'TD500'
51
+ # - 'TT'
52
+ # - 'T2'
53
+ # - 'T500'
54
+ # - 'T750'
55
+ # - 'T850'
56
+ # - 'W500'
57
+ # - 'WA500'
wildfire_occurrence/view/wrf_pipeline_cli.py CHANGED
@@ -42,6 +42,14 @@ def main():
42
  dest='forecast_lenght',
43
  help='Lenght of WRF forecast')
44
 
 
 
 
 
 
 
 
 
45
  parser.add_argument(
46
  '-s',
47
  '--pipeline-step',
@@ -52,10 +60,10 @@ def main():
52
  help='Pipeline step to perform',
53
  default=[
54
  'setup', 'geogrid', 'ungrib', 'metgrid',
55
- 'real', 'wrf', 'all'],
56
  choices=[
57
  'setup', 'geogrid', 'ungrib', 'metgrid',
58
- 'real', 'wrf', 'all'])
59
 
60
  args = parser.parse_args()
61
 
@@ -75,7 +83,9 @@ def main():
75
 
76
  # Initialize pipeline object
77
  pipeline = WRFPipeline(
78
- args.config_file, args.start_date, args.forecast_lenght)
 
 
79
 
80
  # WRF pipeline steps
81
  if "setup" in args.pipeline_step or "all" in args.pipeline_step:
@@ -90,6 +100,8 @@ def main():
90
  pipeline.real()
91
  if "wrf" in args.pipeline_step or "all" in args.pipeline_step:
92
  pipeline.wrf()
 
 
93
 
94
  logging.info(f'Took {(time.time()-timer)/60.0:.2f} min.')
95
 
 
42
  dest='forecast_lenght',
43
  help='Lenght of WRF forecast')
44
 
45
+ parser.add_argument('-mn',
46
+ '--multi-node',
47
+ type=bool,
48
+ required=False,
49
+ default=False,
50
+ dest='multi_node',
51
+ help='Multiple nodes for WRF forecast')
52
+
53
  parser.add_argument(
54
  '-s',
55
  '--pipeline-step',
 
60
  help='Pipeline step to perform',
61
  default=[
62
  'setup', 'geogrid', 'ungrib', 'metgrid',
63
+ 'real', 'wrf', 'postprocess', 'all'],
64
  choices=[
65
  'setup', 'geogrid', 'ungrib', 'metgrid',
66
+ 'real', 'wrf', 'postprocess', 'all'])
67
 
68
  args = parser.parse_args()
69
 
 
83
 
84
  # Initialize pipeline object
85
  pipeline = WRFPipeline(
86
+ args.config_file, args.start_date,
87
+ args.forecast_lenght, args.multi_node
88
+ )
89
 
90
  # WRF pipeline steps
91
  if "setup" in args.pipeline_step or "all" in args.pipeline_step:
 
100
  pipeline.real()
101
  if "wrf" in args.pipeline_step or "all" in args.pipeline_step:
102
  pipeline.wrf()
103
+ if "postprocess" in args.pipeline_step or "all" in args.pipeline_step:
104
+ pipeline.postprocess()
105
 
106
  logging.info(f'Took {(time.time()-timer)/60.0:.2f} min.')
107