ehratm APIs
wrf.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 # *******************************************************
4 # * International Data Centre *
5 # * Comprehensive Nuclear Test Ban Treaty Organization *
6 # * Vienna *
7 # * Austria *
8 # *
9 # * Don Morton (DM) *
10 # * Boreal Scientific Computing *
11 # * Fairbanks, Alaska USA *
12 # *******************************************************
13 
14 
15 
16 #-----------------------------------------------------------------------------
17 
18 #import argparse
19 import datetime
20 import logging
21 import os
22 import uuid
23 
24 import f90nml
25 
26 import ehratm.defaults
27 import ehratm.mylogger
28 
29 #import nwpservice.wps.namelistwps
30 import nwpservice.wrf.wrf
31 
33 
35 
36 class WrfWorkflow(object):
37 
38 
39  '''Class for managing preparation and execution of wrf components
40  '''
41 
42 
43 
44  def __init__(self,
45  wpswrf_distro_path=None,
46  working_rootdir=None,
47  start_time_dt=None,
48  stop_time_dt=None,
49  hours_intvl=None,
50  num_nests=None,
51  nests_defn_dict=None,
52  real_data_dict=None,
53  num_mpi_tasks=None,
54  log_level=None,
55  bypass_namelist_input=None,
56  ):
57 
58  '''Check and initialise the class
59 
60 
61  Parameters
62  ----------
63  wpswrf_distro_path : str
64  (Optional) Full path to the WPS/WRF distribution to be used.
65  This is assumed to have been installed in a way that is compatible
66  with the nwpservice module. If arg is not present, uses a default
67  value
68  working_rootdir : str
69  (Optional) Full path to a directory (assumed to have already
70  been created), to be used as scratch space for setting up and
71  running this real instance. If arg is not present, uses a
72  default value
73  start_time_dt : datetime
74  Time of first ungribbed file(s)
75  stop_time_dt : datetime
76  Time of last ungribbed file(s)
77  hours_intvl : int
78  Interval (in hours) between ungribbed files
79  num_nests : int
80  Number of domain nests represented in geogrid data, and in
81  the metgrid data that will be produced
82  nests_defn_dict : dict
83  Dict of nest definitions (of use if generating namelist.input)
84  num_mpi_tasks : int
85  (Optional) Number of MPI tasks to use for running this instance
86  of metgrid. If arg is not present, non-MPI execution is assumed.
87  real_data_dict : dict
88  Necessary information on the real files. We expect dict with
89  entries {'path' : <path>, 'filelist' : [list of real files]}.
90  This implies that the real files are all in the same src
91  directory
92  log_level : int
93  Python logging level (e.g. logging.INFO)
94  bypass_namelist_input : str
95  This is an assumed-correct namelist.input to be used for
96  execution of real. Its presence bypasses the normal process
97  of creating a namelist.input, and puts complete trust in the
98  correctness of the provided namelist.input. If this argument
99  is present, then any values for start_time_dt, stop_time_dt,
100  hours_intvl and num_nests are ignored. This is meant primarily
101  for devtest operations, and probably shouldn't be used for
102  normal workflows.
103 
104 
105  Example
106  -------
107  These are examples of the real_data_dict
108 
109  ::
110 
111  real_data_dict = {
112  'path' : '/path/to/dir/with/real/files'
113  'filelist' : ['wrfbdy_d01',
114  'wrfinput_d01', ,
115  'wrfinput_d02', ,
116  ]
117  }
118  '''
119 
120  if log_level:
121  self._logging_level = log_level
122  else:
123  self._logging_level = DEFAULTS.log_level()
124  LOGGER.setLevel(self._logging_level)
125 
126  # Now I can use the logger
127  LOGGER.debug('started')
128 
129  # Do some checks (need to expand on these over the years)
130  # Essentially, would be good to do a controlled bail here if there's
131  # a problem as opposed to doing it further down
132 
133 
134  # Check bypass_namelist_input arg. If present, verify good
135  # path. And, if present, some of the following checks will be
136  # ignored
137  self._bypass_namelist_input = None
138  if bypass_namelist_input:
139  if not os.path.isfile(bypass_namelist_input):
140  raise FileNotFoundError('bypass_namelist_input not found: %s' %
141  bypass_namelist_input)
142  self._bypass_namelist_input = bypass_namelist_input
143  LOGGER.debug('bypass_namelist_input: %s' %
145 
146  if not wpswrf_distro_path:
147  wpswrf_distro_path = DEFAULTS.wpswrf_distro_path()
148  self._wpswrf_distro_path = wpswrf_distro_path
149  LOGGER.debug('wpswrf_distro_path: %s' % self._wpswrf_distro_path)
150  if not os.path.isdir(self._wpswrf_distro_path):
151  raise FileNotFoundError('wpswrf_distro_path not found: %s' %
152  self._wpswrf_distro_path)
153 
154  if not working_rootdir:
155  working_rootdir = DEFAULTS.working_scratch_rootdir()
156  self._working_rootdir = working_rootdir
157  if not os.path.isdir(self._working_rootdir):
158  raise FileNotFoundError('working_rootdir not found: %s' %
159  self._working_rootdir)
160  LOGGER.debug('working_rootdir: %s' % self._working_rootdir)
161 
162 
163 
164 
165  # Check that expected real files are available (use real_data_dict)
166  real_dir = real_data_dict['path_to_files']
167  real_files = real_data_dict['files_list']
168  for fname in real_files:
169  fpath = os.path.join(real_dir, fname)
170  if not os.path.isfile(fpath):
171  raise FileNotFoundError('fpath: %s' % fpath)
172 
173 
174  #----- Don't deal with these possibly empty variables if we're
175  #----- using the bypass_namelist_input
176  if not self._bypass_namelist_input:
177  # Basic test of num_nests
178  if not num_nests:
179  raise ValueError("Missing num_nests arg")
180  if num_nests <= 0:
181  raise ValueError("num_nests: %d" % num_nests)
182  self._num_nests = num_nests
183 
184  # Check that we have expected nests. We're going to assume
185  # that there's only one outer nest for wrfbdy, and we're
186  # going to assume there's one wrfinput for each nest. This
187  # may not always be the case!
188  LOGGER.debug('NUM_NESTS: %d, real_files: %s' % (self._num_nests, real_files))
189  for i in range(self._num_nests):
190  expected_fname = 'wrfinput_d%02d' % (i+1)
191  if expected_fname not in real_files:
192  raise ValueError('Did not find nest: %s' % expected_fname)
193 
194  # Check that we have the nest definitions
195  if nests_defn_dict:
196  self._nests_defn_dict = nests_defn_dict
197  else:
198  raise ValueError('Did not find nests_defn_dict...')
199 
200  # Use specified number of MPI tasks, or default to 0, which will imply
201  # non-MPI execution
202  self._mpirunpath = None # Just give it a namespace for now
203  if num_mpi_tasks:
204  # Ensure valid number, otherwise raise exception. If the value
205  # looks good, check that the default mpirun executable is
206  if 1 <= num_mpi_tasks <= DEFAULTS.max_mpi_tasks():
207  # Go ahead and check the mpirun path
208  mpirunpath = DEFAULTS.mpirun_path()
209  if os.path.isfile(mpirunpath) and \
210  os.access(mpirunpath, os.X_OK):
211  self._num_mpi_tasks = num_mpi_tasks
212  self._mpirunpath = mpirunpath
213  else:
214  raise FileNotFoundError('mpirun not executable: %s' %
215  mpirunpath)
216  else:
217  raise ValueError('Bad num_mpi_tasks value: %d' % num_mpi_tasks)
218  else:
219  # This default value of 0 will denote non-MPI execution when
220  # invoking the geogrid service
221  self._num_mpi_tasks = 0
222  LOGGER.debug('num_mpi_tasks: %d' % self._num_mpi_tasks)
223 
224 
225  # If we got through all this, save the time info for the run_wrf()
226  # method
227 
228 
229  if self._bypass_namelist_input:
230  # We won't use these if bypass_namelist_input is being used, so
231  # set them to None so that other code won't mistakenly try to use
232  # them
233  self._start_time_dt = None
234  self._stop_time_dt = None
235  self._hours_intvl = None
236  self._num_nests = None
237 
238  else:
239  self._start_time_dt = start_time_dt
240  self._stop_time_dt = stop_time_dt
241  self._hours_intvl = hours_intvl
242 
243 
244  self._real_data_dict = real_data_dict
245 
246 
247  def run_wrf(self, wrf_output_stagedir=None):
248 
249 
250  '''Set up and run, via nwpservice module, instance of wrf
251 
252  Parameters
253  ----------
254  wrf_output_stagedir : str
255  (Optional) Full path to dir where wrf output files will be staged.
256  If not specified, no staging will be done. If specified, we
257  assume the dir already exists.
258 
259  Returns
260  -------
261  return_dict : dict
262  Dictionary with manifest of the wrf output files and the location
263  (if applicable) of staged files
264  '''
265 
266 
267  LOGGER.debug('Starting run_wrf()...')
268 
269  # Check the output stagedir
270 
271  # If we're using the bypass_namelist_input, then there's
272  # just a little prep necessary. Otherwise, we will need to
273  # create the namelist based on a number of parameters, including
274  # the domain_defn and the dates/times, etc.
275  if self._bypass_namelist_input:
276  # Test for its presence
277  if not os.path.isfile(self._bypass_namelist_input):
278  raise FileNotFoundError(self._bypass_namelist_input)
279  namelist_input_path = self._bypass_namelist_input
280 
281  # Just in case they're needed later, get the num_nests and
282  # hours_intvl values from the namelist (They were set to None
283  # in __init__() for the bypass namelist scenario)
284  nml = f90nml.read(self._bypass_namelist_input)
285  self._hours_intvl = int(
286  nml['time_control']['interval_seconds'] / 3600)
287  self._num_nests = nml['domains']['max_dom']
288 
289  else:
290  # This is where eventually we'll add in the namelist.input
291  # creation
292  namelist_input_path = None
293  raise NotImplementedError(
294  'namelist.input creation not yet supported')
295 
296 
297  # Create nwpservice object, then setup and run
298  domainpath = os.path.join(self._working_rootdir, 'wrf_rundir')
299  LOGGER.debug('domainpath: %s' % domainpath)
300 
301 
302  nwpwrf_obj = nwpservice.wrf.wrf.Wrf(
303  wpswrf_distro_path=self._wpswrf_distro_path,
304  wpswrf_rundir=domainpath,
305  realdatadir=self._real_data_dict['path_to_files'],
306  namelist_input=namelist_input_path,
307  output_dir=wrf_output_stagedir,
308  numpes=self._num_mpi_tasks,
309  mpirun_path=self._mpirunpath,
310  log_level=self._logging_level
311  )
312 
313  nwpwrf_obj.setup()
314  output_manifest = nwpwrf_obj.run()
315  LOGGER.debug('output_manifest: %s' % output_manifest)
316 
317  # If this wasn't successful (even if an output staging dir was
318  # never specified), it should return false
319  stage_success = nwpwrf_obj.stage_output(auxfiles=True)
320  if stage_success:
321  staging_dir = wrf_output_stagedir
322  else:
323  staging_dir = None
324 
325 
326 
327 
328  return_dict = {
329  'wrf_output_manifest' : output_manifest,
330  'staging_dir' : staging_dir,
331  'hours_intvl' : self._hours_intvl
332  }
333  LOGGER.debug('return_dict: %s' % return_dict)
334 
335 
336  return return_dict
ehratm.wrf.wrf.WrfWorkflow._mpirunpath
_mpirunpath
Definition: wrf.py:190
ehratm.wrf.wrf.WrfWorkflow._bypass_namelist_input
_bypass_namelist_input
Definition: wrf.py:125
ehratm.wrf.wrf.WrfWorkflow.__init__
def __init__(self, wpswrf_distro_path=None, working_rootdir=None, start_time_dt=None, stop_time_dt=None, hours_intvl=None, num_nests=None, nests_defn_dict=None, real_data_dict=None, num_mpi_tasks=None, log_level=None, bypass_namelist_input=None)
Definition: wrf.py:44
ehratm.defaults
Definition: defaults.py:1
ehratm.wrf.wrf.WrfWorkflow._num_nests
_num_nests
Definition: wrf.py:170
ehratm.wrf.wrf.WrfWorkflow._num_mpi_tasks
_num_mpi_tasks
Definition: wrf.py:199
ehratm.wrf.wrf.WrfWorkflow._real_data_dict
_real_data_dict
Definition: wrf.py:232
ehratm.wrf.wrf.WrfWorkflow
Definition: wrf.py:36
ehratm.wrf.wrf.WrfWorkflow._hours_intvl
_hours_intvl
Definition: wrf.py:223
ehratm.wrf.wrf.WrfWorkflow._working_rootdir
_working_rootdir
Definition: wrf.py:144
ehratm.wrf.wrf.WrfWorkflow._stop_time_dt
_stop_time_dt
Definition: wrf.py:222
ehratm.wrf.wrf.WrfWorkflow._nests_defn_dict
_nests_defn_dict
Definition: wrf.py:184
ehratm.defaults.Defaults
Definition: defaults.py:9
ehratm.wrf.wrf.WrfWorkflow._start_time_dt
_start_time_dt
Definition: wrf.py:221
ehratm.wrf.wrf.WrfWorkflow._wpswrf_distro_path
_wpswrf_distro_path
Definition: wrf.py:136
ehratm.mylogger.getlogger
def getlogger()
Definition: mylogger.py:11
ehratm.mylogger
Definition: mylogger.py:1
ehratm.wrf.wrf.WrfWorkflow._logging_level
_logging_level
Definition: wrf.py:109
ehratm.wrf.wrf.WrfWorkflow.run_wrf
def run_wrf(self, wrf_output_stagedir=None)
Definition: wrf.py:247