Spaces:
Build error
Build error
jordancaraballo
commited on
Commit
·
c00748e
1
Parent(s):
a2aa059
Adding production WRF pipeline
Browse files- wildfire_occurrence/model/analysis/__init__.py +0 -0
- wildfire_occurrence/model/analysis/lightning_analysis.py +0 -0
- wildfire_occurrence/model/analysis/wrf_analysis.py +484 -0
- wildfire_occurrence/model/config.py +23 -1
- wildfire_occurrence/model/data_download/ncep_fnl.py +71 -24
- wildfire_occurrence/model/pipelines/wrf_pipeline.py +144 -24
- wildfire_occurrence/templates/config.yaml +36 -3
- wildfire_occurrence/view/wrf_pipeline_cli.py +15 -3
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
#
|
47 |
-
|
48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
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 |
-
|
172 |
-
for
|
173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
for hour in self.hour_intervals:
|
175 |
filename = os.path.join(
|
176 |
-
self.
|
177 |
-
|
178 |
-
f'
|
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=
|
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,/
|
124 |
f'{self.conf.container_path} ' + \
|
125 |
-
'mpirun -np
|
126 |
else:
|
127 |
-
geodrid_cmd =
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
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,/
|
174 |
-
f'{self.conf.container_path}
|
175 |
else:
|
176 |
ungrib_cmd = \
|
177 |
'srun --mpi=pmix -N 1 -n 1 singularity exec -B /explore/nobackup/projects/ilab,' + \
|
178 |
-
'$NOBACKUP,/
|
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,/
|
207 |
f'{self.conf.container_path} ' + \
|
208 |
-
'mpirun -np
|
209 |
else:
|
210 |
metgrid_cmd = \
|
211 |
-
'srun --mpi=pmix -N 1 -n
|
212 |
-
'$NOBACKUP,/
|
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,/
|
254 |
f'{self.conf.container_path} ' + \
|
255 |
-
'mpirun -np
|
256 |
else:
|
257 |
real_cmd = \
|
258 |
'srun --mpi=pmix -N 2 -n 80 singularity exec -B /explore/nobackup/projects/ilab,' + \
|
259 |
-
'$NOBACKUP,/
|
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,/
|
288 |
f'{self.conf.container_path} ' + \
|
289 |
-
'mpirun -np
|
290 |
else:
|
291 |
wrf_cmd = \
|
292 |
'srun --mpi=pmix -N 2 -n 80 singularity exec -B /explore/nobackup/projects/ilab,' + \
|
293 |
-
'$NOBACKUP,/
|
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:
|
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,
|
|
|
|
|
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 |
|