HWRF  trunk@4391
wrf.py
1 """!@brief Create namelists, monitor wrf simulations, generate filenames.
2 
3 @details This module contains classes that manipulate WRF namelists in
4 complex ways, and predict the resulting output and input filenames
5 regardless of whatever crazy timesteps are requested. This module
6 also contains a class, ExternalWRFTask, that can monitor a running WRF
7 simulation, providing a list of output and input files, and check
8 whether the simulation has completed, failed or is still running.
9 
10 @see hwrf.wrfbase"""
11 
12 import fractions,math,re,datetime,os
13 import produtil.fileop
14 
15 from produtil.datastore import COMPLETED,UpstreamFile,UNSTARTED,RUNNING,FAILED
16 from produtil.fileop import isnonempty
17 from produtil.run import bigexe, checkrun, mpirun, mpi, runstr, batchexe
18 
19 from hwrf.hwrftask import HWRFTask
20 from hwrf.numerics import *
21 from hwrf.namelist import *
22 from hwrf.exceptions import *
23 from hwrf.wrfbase import *
24 
25 ## @var __all__
26 # the list of symbols output by "from hwrf.wrf import *"
27 __all__=['default_wrf_outname','WRFDomain','WRFSimulation','ExternalWRFTask']
28 
29 ########################################################################
30 
31 def default_wrf_outname(stream):
32  """!default wrf output filename patterns
33 
34  Generate a reasonable default wrf outname input value for the
35  specified stream. Presently, these match the WRF defaults. These
36  do not have to match the WRF defaults since we always specify the
37  outname for all streams.
38  @param stream the stream
39  @returns the wrf output filename pattern, including <date> and
40  <domain> if relevant"""
41  if stream=='history':
42  return 'wrfout_d<domain>_<date>'
43  elif stream=='anl':
44  return 'wrf%s_d<domain>_<date>' % (stream,)
45  elif stream=='restart':
46  return 'wrfrst_d<domain>_<date>'
47  elif stream=='bdy' or stream=='input':
48  return 'wrf%s_d<domain>' % (stream,)
49  elif stream=='geo_nmm':
50  return '%s_d<domain>' % (stream,)
51  elif stream=='inputout':
52  return 'wrfinput_d<domain>_<date>'
53  elif stream=='bdyout':
54  return 'wrfbdy_d<domain>'
55  else:
56  return '%s_d<domain>_<date>'%(stream,)
57 
58 ########################################################################
59 # Default ordering of WRF namelist sections and namelist variables
60 # when calling WRF.namelist. Note that this can be overridden if
61 # needed. The defaults are set to produce the same order as the
62 # pre-python EMC HWRF.
63 
64 ##@var _wrf_namelist_order
65 # the ordering of WRF namelists
66 _wrf_namelist_order=partial_ordering(['time_control','fdda','domains','physics','dynamics','bdy_control','namelist_quilt','logging'],6)
67 
68 ##@var _wrf_nl_var_order
69 # a mapping of WRF namelist name to a mapping of known variables
70 # within that namelist. This ordering of namelist values is needed by
71 # programs that only know how to read a subset of the namelist, such
72 # as the coupler.
73 _wrf_nl_var_order={
74  'time_control': partial_ordering([
75  'start_year','start_month','start_day','start_hour','start_minute',
76  'start_second','end_year','end_month','end_day','end_hour',
77  'end_minute','end_second','interval_seconds','history_interval',
78  'auxhist1_interval','auxhist2_interval',
79  'auxhist3_interval','history_end','auxhist2_end','auxhist1_outname',
80  'auxhist2_outname','auxhist3_outname','frames_per_outfile',
81  'frames_per_auxhist1','frames_per_auxhist2','frames_per_auxhist3',
82  'analysis','anl_outname','restart','restart_interval',
83  'reset_simulation_start','io_form_input','io_form_history',
84  'io_form_restart','io_form_boundary','io_form_auxinput1',
85  'io_form_auxhist1','io_form_auxhist2','io_form_auxhist3',
86  'auxinput1_inname','debug_level','tg_reset_stream',
87  'override_restart_timers']),
88  'domains': partial_ordering([
89  'time_step','time_step_fract_num','time_step_fract_den',
90  'max_dom','s_we','e_we','s_sn','e_sn','s_vert','e_vert','dx',
91  'dy','grid_id','tile_sz_x','tile_sz_y','numtiles','nproc_x',
92  'nproc_y','parent_id','parent_grid_ratio',
93  'parent_time_step_ratio','i_parent_start','j_parent_start',
94  'feedback','num_moves','num_metgrid_levels','p_top_requested',
95  'ptsgm','eta_levels','use_prep_hybrid',
96  'num_metgrid_soil_levels']),
97  'physics': partial_ordering([
98  'num_soil_layers','mp_physics','ra_lw_physics','ra_sw_physics',
99  'sf_sfclay_physics','sf_surface_physics','bl_pbl_physics',
100  'cu_physics','mommix','var_ric','coef_ric_l','coef_ric_s',
101  'h_diff','gwd_opt',
102  'sfenth','nrads','nradl','nphs','ncnvc','ntrack','gfs_alpha',
103  'sas_pgcon','sas_mass_flux','co2tf','vortex_tracker',
104  'nomove_freq','tg_option','ntornado']),
105  'dynamics': partial_ordering(
106  ['non_hydrostatic','euler_adv','wp','coac','codamp',
107  'terrain_smoothing']),
108  'bdy_control': partial_ordering(['spec_bdy_width','specified']),
109  'namelist_quilt': partial_ordering(
110  ['poll_servers','nio_tasks_per_group','nio_groups']),
111  'logging': partial_ordering(
112  ['compute_tasks_silent','io_servers_silent','stderr_logging'])
113  }
114 
115 ########################################################################
116 
118  """!A domain in a WRF simulation
119 
120  This subclass of WRFDomainsBase adds needed details that let it
121  provide information about a domain in a WRF simulation. It can
122  predict output and input filenames based on timesteps and
123  start/end times. It can do complex manipulations of the WRF
124  namelist. Most functionality should be accessed via the
125  WRFSimulation, after a WRFDomain is created.
126 
127  Note that after you provide a WRFDomain to a WRFSimulation, the
128  WRFSimulation makes its own copy of that WRFDomain. The original
129  is unmodified. That means that if you want details on the
130  namelist and output files added by the WRFSimultion, you must
131  obtain its copy of the WRFDomain like so:
132 
133  @code{.py}
134  moad=WRFDomain(conf,'moad')
135  storm1outer=WRFDomain(conf,'storm1outer')
136  storm1inner=WRFDomain(conf,'storm1inner')
137  wrf=WRFSimulation(conf,'wrf',moad,conf.cycle,
138  conf.cycle+hwrf.numerics.to_timedelta(fcstlen*3600))
139  wrf.add(storm1outer,moad)
140  wrf.add(storm1inner,storm1outer)
141  wrf.add_output('history',step=3600*3,end=9*3600)
142  sim_moad=wrf[moad]
143  sim_storm1outer=wrf[storm1outer]
144  sim_storm1inner=wrf[storm1inner]
145  @endcode
146 
147  In this example, the sim_moad, sim_storm1inner and sim_storm1outer
148  will be new objects, contained within the WRFSimulation named
149  "wrf". They will contain additional information about the
150  WRFDomain that is not in the original moad, storm1inner and
151  storm1outer."""
152 
153  ##@var dx
154  # the resolution in the rotated longitude direction
155 
156  ##@var dy
157  # the resolution in the rotated latitude direction
158 
159  ##@var nestlevel
160  # The WRF domain nesting level
161 
162  ##@var parent
163  # The parent WRFDomain
164 
165  ##@var nocolons
166  # True if colons should be omitted from filenames
167 
168  ##@var nl
169  # The hwrf.namelist.Conf2Namelist for this domain
170 
171  ##@var name
172  # The name of this domain.
173 
174  def __init__(self,conf,section,name=None,copy=None):
175  """!WRFDomain constructor
176 
177  Creates a new WRFDomain based on the information in a section
178  of the HWRFConfig object conf. The domain's name is in
179  "name." The "copy" argument should never be specified: it is
180  used by self.copy for deep copies of a WRFDomain."""
181  self.nestlevel=None
182  self.parent=None
183  self.nocolons=True
184  self._start=None
185  self._end=None
186  self._dt=None
187  self._output={}
188  self.dx=None
189  self.dy=None
190  if copy is not None:
191  self.nl=copy.nl.copy()
192  self.name=str(copy.name)
193  ( self._start,self._end,self._dt,self.nestlevel,self.parent ) =\
194  (copy._start,copy._end,copy._dt,copy.nestlevel,copy.parent )
195  ( self.dx,self.dy,self.nocolons) = \
196  (copy.dx,copy.dy,copy.nocolons)
197  for (n,o) in copy._output.iteritems():
198  self._output[n]=dict(o)
199  else:
200  self.nl=Conf2Namelist(conf,section)
201  self.name=str(section)
202  def add_hifreq(self):
203  self.add_output('hifreq',outname='hifreq_d<domain>.htcf')
204  # The hifreq stream was added to deliver the hifreq_d files
205  # for the multistorm.
206  # But add_output adds the following variable to the time_control
207  # namelist and they shouldn't be there, since wrf executable
208  # doesn't know about those variables, so delete them.
209  self.nl.nl_del('time_control','frames_per_hifreq')
210  self.nl.nl_del('time_control','hifreq_interval')
211  def moad_ratio(self):
212  """!Returns the nesting ratio between this domain and the MOAD."""
213  if self.parent is None:
214  return 1
215  else:
216  return self.parent.moad_ratio()*self.nl.nl_get(
217  'domains','parent_grid_ratio')
218  def getdt(self):
219  """!Returns the timestep for this domain."""
220  return self._dt
221 
222  ##@var dt
223  # read-only property containing the domain timestep
224 
225  dt=property(getdt,None,None,'The timestep for this domain.')
226 
227  def __repr__(self):
228  """!A string description of this domain"""
229  return '<WRFDomain name=%s>'%(str(self.name),)
230  def _nl_subsetter(self,s,v):
231  """!Returns True.
232 
233  This will be used in the future to ensure users do not specify
234  namelist options that should be automatically configured."""
235  #if s=='time_control':
236  # return not re.search('\A(?:start_.*|end_.*|.*_interval|frames_per_.*|analysis|restart.*|io_form.*|reset_simulation_start|.*_inname|override_restart_timers|history_outname|aux.*outname)\Z',s)
237  #elif s=='domains':
238  # return not re.search('\A(?:time_step.*|[se]_.*|dx|dy|grid_id|parent_.*|num_moves|numtiles|tile_sz_x|tile_sz_y)\Z',s)
239  return True
240  def copy(self):
241  """!Returns a deep copy of this object
242 
243  Returns a deep copy of this object. The copy has its own data
244  structures, so modifying the copy will not modify the
245  original."""
246  return WRFDomain(None,None,copy=self)
247  def set_timing(self,start,end,timestep):
248  """!Sets start and end times and timestep
249 
250  Sets this WRFDomain's idea of the simulation start and end
251  times and timestep.
252  @param start the start time of the domain
253  @param end the end time of the domain
254  @param timestep the domain's timestep"""
255  WRFDomainBase.set_timing(self,start,end,timestep)
256  (ts,te,dt)=self._validate_timespan(start,end,timestep)
257  # Set the start/end:
258  for n in ('year','month','day','hour','minute','second'):
259  self.nl.nl_set('time_control','start_'+n,getattr(ts,n))
260  self.nl.nl_set('time_control','end_'+n,getattr(te,n))
261  def get_grid_id(self):
262  """!Returns the WRF grid id."""
263  return self.nl.nl_get('domains','grid_id')
264  def is_moad(self):
265  """!Is this the outermost domain?
266 
267  Returns True if this is the WRF Mother of All Domains (MOAD)
268  and False otherwise. The MOAD is the outermost domain."""
269  gid=self.get_grid_id()
270  gid=int(gid)
271  return gid==1
272  @property
273  def nx(self):
274  """!The number of grid cells the X direction."""
275  return self.nl.nl_get('domains','e_we')
276  @property
277  def ny(self):
278  """!The number of grid cells the Y direction."""
279  return self.nl.nl_get('domains','e_sn')
280  @property
281  def nz(self):
282  """!The number of grid cells the Z direction."""
283  return self.nl.nl_get('domains','e_vert')
284  def init_domain(self,grid_id):
285  """!Internal helper function that initializes variables common to all domains
286 
287  Initializes domain variables that are needed by all domains.
288  This is called as a helper function to the other domain
289  initialization variables."""
290  s=self.nl.nl_set
291  t=self.nl.trait_get
292  s('domains','s_we',1)
293  s('domains','s_sn',1)
294  s('domains','s_vert',1)
295  s('domains','e_we',t('nx'))
296  s('domains','e_sn',t('ny'))
297  s('domains','grid_id',int(grid_id))
298  def init_as_moad(self,simstart,simend,simdt,eta_levels):
299  """!Called by WRFSimulation to initialize this as the outermost domain
300 
301  Do not call this function directly. It is called by the
302  WRFSimulation to initialize the domain as the Mother Of All
303  Domains (MOAD).
304  @param simstart the simulation start time
305  @param simend the simulation end time
306  @param simdt the outermost domain timestep
307  @param eta_levels the NMM eta levels"""
308  WRFDomainBase.init_as_moad(self,simstart,simend,simdt,eta_levels)
309  s=self.nl.nl_set
310  t=self.nl.trait_get
311  self.nestlevel=0
312  (i,n,d)=split_fraction(simdt)
313  nz=t('nz',len(eta_levels))
314  if len(eta_levels)!=nz:
315  raise WRFError('nz and len(eta_levels) mismatch: %d vs %d'
316  %(int(nz),len(eta_levels)))
317  s('domains','e_vert',nz)
318  self.dx=t('dx')
319  s('domains','dx',self.dx)
320  self.dy=t('dy')
321  s('domains','dy',self.dy)
322  s('domains','parent_id',0)
323  s('domains','parent_grid_ratio',1)
324  s('domains','parent_time_step_ratio',1)
325  s('domains','i_parent_start',0)
326  s('domains','j_parent_start',0)
327  assert(self.dy is not None)
328  def init_as_nest(self,parent,grid_id,start,end):
329  """!Called by WRFSimulation to initialize this domain as a nest
330 
331  Do not call this function directly. It is called by the
332  WRFSimulation to initialize the domain as a nest
333  @param parent the parent WRFDomain
334  @param grid_id the integer grid_id
335  @param start the domain start time
336  @param end the domain end time"""
337  WRFDomainBase.init_as_nest(self,parent,grid_id,start,end)
338  if not is_at_timestep(parent._start,start,parent._dt):
340  'Start time %s for domain %d is not at parent %d timestep.'
341  %(parent._start,grid_id,parent.grid_id))
342  s=self.nl.nl_set
343  t=self.nl.trait_get
344  self.nestlevel=parent.nestlevel+1
345  p=parent.nl.nl_get
346  # for n in [ 'restart_begin', 'restart_begin_m',
347  # 'restart_begin_s', 'restart_begin_h',
348  # 'restart_begin_d', 'restart_interval',
349  # 'restart_interval_s', 'restart_interval_m',
350  # 'restart_interval_h', 'restart_interval_d' ]:
351  # if self.nl.nl_have('time_control',n):
352  # self.nl.nl_del('time_control',n)
353  s('domains','parent_id',p('domains','grid_id'))
354  s('domains','parent_time_step_ratio',3)
355  s('domains','parent_grid_ratio',3)
356  self.dx=parent.dx/3.
357  self.dy=parent.dy/3.
358  s('domains','dx',p('domains','dx')/3)
359  s('domains','dy',p('domains','dy')/3)
360  s('domains','e_vert',p('domains','e_vert'))
361  start=str(t('start','auto')).lower()
362  if start=='fixed':
363  s('domains','i_parent_start',int(t('istart')))
364  s('domains','j_parent_start',int(t('jstart')))
365  elif start=='centered':
366  s('domains','i_parent_start','**CENTERED**')
367  s('domains','j_parent_start','**CENTERED**')
368  elif start=='auto':
369  s('domains','i_parent_start','**AUTO**')
370  s('domains','j_parent_start','**AUTO**')
371  else:
372  raise InvalidDomainStart(
373  '%s: Invalid value for start. It must be "fixed" "centered" or "auto",'
374  ' but you gave %s'%(self.name,repr(start)),self.name)
375  assert(self.dy is not None)
376  def make_namelist(self):
377  """!Creates the WRF namelist contents and returns it as a string."""
378  return self.nl.make_namelist()
379  def has_output(self,stream):
380  """!Returns True if the domain will output to the specified
381  stream.
382 
383  Checks the internal data structures maintained by add_output
384  to see if output was requested for the specified stream.
385  @param stream the stream to check
386  @return True if the domain will output to the specified
387  stream, and False if it won't (as far as we know)."""
388  return stream in self._output
389  def get_output_range(self,stream):
390  """!Returns the range of times that has output for the given stream.
391 
392  @return a tuple containing the first output time, last
393  output time and output interval for this domain and the
394  specified stream.
395  @param stream the stream for whom output is requested"""
396  if not self.has_output(stream):
397  raise OutputStreamDisabled(
398  'Stream %s is disabled for domain %s'%(stream,repr(self)))
399  start=self._start
400  if 'start' in self._output[stream] and \
401  self._output[stream]['start'] is not None:
402  start=to_datetime_rel(self._output[stream]['start'],start)
403  end=self._end
404  if 'end' in self._output[stream] and \
405  self._output[stream]['end'] is not None:
406  end=to_datetime_rel(self._output[stream]['end'],start)
407  interval=to_timedelta(self._output[stream]['step'])
408  return (start,end,interval)
409  def hifreq_file(self):
410  """!Returns the hifreq filename for this domain."""
411  return parse_wrf_outname('hifreq_d<domain>.htcf',self.get_grid_id(),
412  self._start,True)
413  def trackpatcf_file(self):
414  """Returns the track patcf file for this domain. """
415  return parse_wrf_outname('track_d<domain>.patcf',self.get_grid_id(),
416  self._start,True)
417  def _get_output_time(self,when):
418  """!Internal function that determines the time output would
419  actually be generated
420 
421  This is an internal implementation function. You should not
422  call it directly. It returns the nearest datetime.datetime to
423  the specified time that lies on a model timestep, without
424  going over. Uses hwrf.numerics.nearest_datetime()
425  @param when the desired time
426  @returns a datetime.datetime for the actual time that will appear"""
427  return nearest_datetime(self._start,when,self._dt)
428  def _get_output(self,stream,outname,when,actual_time=None,logger=None):
429  """!Internal function that generates WRFOutput objects
430 
431  This is an internal implementation function. You should not
432  call it directly. It returns a WRFOutput object for the
433  specified output time (when) and stream. The outname is the
434  WRF output filename syntax, with <domain> and <date> in it.
435  If actual_time is specified, it is used as the output time,
436  otherwise, "when" is passed into self._get_output_time to get
437  the actual time WRF will output it.
438  @param stream the desired stream
439  @param outname the output filename format
440  @param when the desired output time
441  @param actual_time the actual time from _get_output_time. If
442  missing or None, then _get_output_time will be called.
443  @param logger if specified, the logging.Logger to use for log
444  messages"""
445  if actual_time is None:
446  actual_time=self._get_output_time(when)
447  if stream=='hifreq':
448  return WRFOutput(self.get_anl_time(),stream,self,self.hifreq_file(),
449  validtime=self.get_anl_time())
450  path=parse_wrf_outname(outname,self.get_grid_id(),actual_time,
451  self.nocolons)
452  assert(actual_time is not None)
453  return WRFOutput(self.get_anl_time(),stream,self,path,
454  validtime=actual_time)
455  def get_all_outputs(self,time=None):
456  """!Iterate over all output files for a specified time.
457 
458  Iterates over all output files as WRFOutput objects. If a
459  time is specified, then only outputs for that time are
460  yielded.
461  @param time the time of interest, or None (the default)"""
462  for stream in self._output:
463  if time is None:
464  for obj in self.get_outputs(stream):
465  yield obj
466  else:
467  yield self.get_output(stream,to_datetime_rel(time,self._start))
468  def get_outputs(self,stream):
469  """!Iterates over all outputs for a specified stream
470 
471  Iterates over all output files for the specified stream, as
472  WRFOutput objects.
473  @param stream the string name of the output stream"""
474  (start,end,interval)=self.get_output_range(stream)
475  epsilon=to_timedelta('0+1/500')
476  when=start
477  firstwhen=start
478  prevwhen=None
479  outname=self._output[stream]['outname']
480  while when<end+epsilon:
481  actual_time=self._get_output_time(when)
482  if prevwhen is None or prevwhen!=actual_time:
483  obj=self._get_output(stream,outname,when,actual_time)
484  prevwhen=actual_time
485  yield obj
486  when=when+interval
487  if when==firstwhen:
488  raise InvalidTimespan(
489  'Zero output interval %s: somehow %s+%s=%s'%\
490  (repr(interval),repr(when),repr(interval),repr(when)))
491  def get_output(self,stream,time,logger=None):
492  """!Get output for a specified time and stream
493 
494  Returns a WRFOutput object for the output file for the
495  specified stream and time, or None if no such file exists.
496  Will return the first output time not before the given time.
497  @param stream the desired stream
498  @param time the desired time
499  @param logger if specified, a logging.Logger to log to"""
500  (start,end,interval)=self.get_output_range(stream)
501  time=to_datetime_rel(time,self._start)
502  outname=self._output[stream]['outname']
503  near=nearest_datetime(start,time,interval)
504  if(time>end):
505  return None
506  # if(logger is not None):
507  # logger.debug('get_output %s %s %s %s %s %s %s' % (
508  # repr(start),repr(end),repr(interval),repr(outname),
509  # repr(stream),repr(near),repr(time)))
510  result=self._get_output(stream,outname,near,logger=logger)
511  # if(logger is not None):
512  # logger.debug(' got output %s'%(repr(result),))
513  return result
514  def no_output(self,stream):
515  """!Forget that output was requested for a given stream
516 
517  Removes the specified stream from the internal data
518  structures. Its output will revert to the WRF default, and
519  will be unavailable via this WRFDomain.
520  @param stream the string name of the stream of disinterest"""
521  del self._output[stream]
522  def hide_output(self,stream):
523  """!Disable output for a specified stream, if the stream was
524  requested before
525 
526  If output is enabled for the specified stream, moves the
527  output start time to after the end of the simulation. That
528  way any WRF code relying on the output frequency will still
529  work, but no output will be generated. Will not work for
530  restart stream since that is not controlled on a per-domain
531  basis. Note that code that queries the output times will
532  break if this is called.
533  @param stream the string name of the stream of disinterest"""
534  if not self._output[stream]: return
535  forever=999999 # far in the future, in minutes
536  s=self.nl.nl_set
537  d=self.nl.nl_del
538  if stream=='restart' and not self.is_moad():
539  return
540  self._output[stream]['start']=forever
541  self._output[stream]['end']=forever+1
542  self._output[stream]['step']=forever
543  if stream=='inputout':
544  s('time_control','%s_begin_m'%(stream,),forever)
545  s('time_control','%s_end_m'%(stream,),forever+1)
546  else:
547  s('time_control','%s_begin'%(stream,),forever)
548  s('time_control','%s_end'%(stream,),forever+1)
549  d('time_control','%s_begin_s'%(stream,))
550  d('time_control','%s_end_s'%(stream,))
551  del self._output[stream]
552  def add_output(self,stream,start=None,end=None,step=None,outname=None,
553  frames_per_outfile=None,io_form=None,simstart=None):
554  """!Adds output to the specified stream. Other arguments are
555  optional:
556 
557  @param stream the stream: "history" or "auxhistN" for an integer N>0
558  @param start output start time (anything accepted by to_datetime)
559  Default: simulation start time.
560  @param end output end time (anything accepted by to_datetime.
561  Default: simulation end time.
562  @param step output interval, sent into to_out_interval().
563  Default: trait stream+"_interval" or 6hrs
564  @param outname output name or array of output names (one per
565  domain). Can only specify for all domains, not for only
566  one. Default: leave unspecified, and let WRF use its
567  defaults.
568  @param frames_per_outfile how many output times per output file
569  @param io_form WRF IO form. Simply calls self.set_io_form(stream,io_form)
570  @param simstart the simulation start time which must be provided fi start or end times are given"""
571  if io_form is None:
572  iof=self.nl.trait_get('io_form','missing')
573  if iof=='missing':
574  io_form=2
575  else:
576  io_form=int(iof)
577  assert(isinstance(io_form,int))
578  #print 'add output for stream ',stream
579  s=self.nl.nl_set
580  # Check input, convert to usable objects:
581  if outname is None:
582  outname=default_wrf_outname(stream)
583  (dstart,dend,fstep)=(None,None,None)
584  if ( start is not None or end is not None ) and simstart is None:
585  raise TypeError('WRFDomain.add_output: simstart must be provided '
586  'if start or end times are given')
587  if start is not None:
588  start=to_datetime_rel(start,simstart)
589  dstart=start-simstart
590  (smin,ssec,srest) = minutes_seconds_rest(dstart)
591  if srest!=0:
592  raise PrecisionTooHigh(
593  'Output start time must be an integer multiple of a '
594  'second after simulation start time.')
595  if end is not None:
596  startrel=self._start if (start is None) else start
597  dend=to_datetime_rel(end,simstart)
598  fend=dend-simstart
599  (emin,esec,erest) = minutes_seconds_rest(fend)
600  if erest!=0:
601  raise PrecisionTooHigh(
602  'Output end time must be an integer multiple of a second '
603  'after simulation start time.')
604 
605  if step is not None: fstep=to_fraction(step)
606  if fstep is None: fstep=to_fraction(3600*6) # six hours
607  (minutes,seconds,rest)=minutes_seconds_rest(fstep)
608  if rest!=0:
609  raise PrecisionTooHigh(
610  'Output frequency must be an integer multiple of a second.')
611 
612  frames_per='frames_per_outfile'
613  if stream!='history':
614  frames_per='frames_per_%s'%(stream,)
615 
616  # Set namelist values:
617  # no, bad: s('time_control','%s_outname'%(stream,),outname)
618  if stream=='inputout':
619  s('time_control','%s_interval_m'%(stream,),minutes)
620  else:
621  s('time_control','%s_interval'%(stream,),minutes)
622  if seconds!=0:
623  s('time_control','%s_interval_s'%(stream,),seconds)
624  if stream=='restart' and not self.is_moad():
625  pass # print 'NO TIME CONTROL FOR NON-MOAD RESTART'
626  elif start is not None:
627  if stream=='inputout':
628  s('time_control','%s_begin_m'%(stream,),smin)
629  else:
630  s('time_control','%s_begin'%(stream,),smin)
631  if ssec!=0:
632  s('time_control','%s_begin_s'%(stream,),ssec)
633  if end is not None and stream!='restart':
634  if stream=='inputout':
635  s('time_control','%s_end_m'%(stream,),emin)
636  else:
637  s('time_control','%s_end'%(stream,),emin)
638  if esec!=0:
639  s('time_control','%s_end_s'%(stream,),esec)
640  #no, bad: s('time_control','%s_outname'%(stream,),outname)
641  if stream!='inputout' and stream!='restart':
642  if frames_per_outfile is not None:
643  s('time_control',frames_per,int(frames_per_outfile))
644  else:
645  s('time_control',frames_per,1)
646  #no, bad: s('time_control','io_form_%s'%(stream,),io_form)
647  # Store data needed to generate outputs:
648  self._output[stream]={'start':dstart, 'end':dend,
649  'step':fstep, 'outname':outname}
650  self.nl.nl_set_if_unset('time_control','nocolons',True)
651  def interval_for(self,stream):
652  """!Return the output interval for the stream
653 
654  Returns the output interval for the specified stream, or
655  None if the stream is disabled.
656  @param stream the stream of interest"""
657  if stream in self._output:
658  return self._output[stream]['step']
659  return None
660 
661 ########################################################################
662 
664  """!generate and manipulate wrf namelists, predict output filenames
665 
666  The WRFSimulation class is at the core of the HWRF scripting
667  system. It stores information about every aspect of the WRF
668  namelist, and can manipulate it in complex ways. It automatically
669  generates I/O information, and can predict output and input
670  filenames no matter what crazy timesteps and start/end times you
671  select. There are a number of safeguards that will raise
672  exceptions in Python if you try to set up a simulation that is not
673  possible in WRF. """
674  def copy(self):
675  """!Makes a deep copy of this object
676 
677  Returns a deep copy of this object, providing new data
678  structures so modifying the copy will not modify the original.
679  The underlying WRFDomain objects and their data structures are
680  also duplicated."""
681  return WRFSimulation(None,None,None,None,None,None,self)
682 
683  ##@var _tiling
684  # Unused: stores OpenMP tiling information.
685  # Presently, tiling is broken in WRF-NMM, so this variable is unused.
686 
687  def __init__(self,conf,section,moad,simstart,simend,timestep=None,dup=None):
688  """!Creates a new WRFSimulation object:
689 
690  Creates a new WRFSimulation object.
691  @param conf the HWRFConfig to provide configuration information
692  @param section the section to use in that config object
693  @param moad the Mother of All Domains, as a WRFDomain
694  @param simstart,simend - simulation start and end times
695  @param timestep the simulation timestep
696  @param dup do not use. This is used by the self.copy() do do a deep
697  copy of a WRFDomains."""
698  if dup is not None:
699  # copy constructor
700  WRFDomains.__init__(self,None,None,None,None,None,None,dup=dup)
701  self._wps=dup._wps
702  self._tiling=dup._tiling
703  for domain in self: pass
704  return
705 
706  WRFDomains.__init__(self,conf,section,moad,simstart,simend,timestep)
707 
708  self._wps=None
709 
710  nd=self.nl.nl_del
711  ng=self.nl.nl_get
712  s=self.nl.nl_set
713  sh=self.nl.nl_have
714  siu=self.nl.nl_set_if_unset
715  t=self.nl.trait_get
716  th=self.nl.trait_have
717 
718  # Make sure all namelists that we know are required, are present:
719  self.nl.nl_section(
720  'time_control','fdda','domains','physics','dynamics',
721  'bdy_control','namelist_quilt','logging')
722 
723  dt=to_fraction(t('dt'))
724  (i,n,d)=split_fraction(dt)
725  s('domains','time_step',i)
726  s('domains','time_step_fract_num',n)
727  s('domains','time_step_fract_den',d)
728 
729  s('time_control','interval_seconds',t('bdystep'))
730  s('domains','ptsgm',float(t('ptsgm')))
731  s('domains','p_top_requested',float(t('ptop')))
732  s('domains','use_prep_hybrid',bool(t('prep_hybrid')))
733  s('domains','num_metgrid_soil_levels',int(t('metgrid_soil_levels',4)))
734  s('domains','num_metgrid_levels',int(t('metgrid_levels',4)))
735 
736  siu('namelist_quilt','nio_tasks_per_group',[0])
737  siu('namelist_quilt','nio_groups',1)
738  for stream in ['input','boundary','auxinput1','auxhist1','auxhist2',
739  'auxhist3','auxhist4','auxhist5','history','auxhist6',
740  'auxinput2']:
741  n='io_form_'+stream
742  if not sh('time_control',n):
743  if th(n):
744  s('time_control',n,t(n))
745  else:
746  s('time_control',n,self.io_form)
747  siu('time_control','auxinput1_inname',"met_nmm.d<domain>.<date>")
748 
749 
750  # dm_task_split takes precedence over nproc_x and nproc_y
751  if self.nl.nl_have_sect('dm_task_split'):
752  siu('dm_task_split','comm_start',[-1])
753  siu('dm_task_split','nest_pes_x',[-1])
754  siu('dm_task_split','nest_pes_y',[-1])
755  if sh('domains','nproc_x') and sh('domains','nproc_y'):
756  nd('domains','nproc_x')
757  nd('domains','nproc_y')
758  else:
759  siu('domains','nproc_x',-1)
760  siu('domains','nproc_y',-1)
761 
762  # nio_tpg can be a list 4,4,2,4,2...
763  nio_tpg=ng('namelist_quilt','nio_tasks_per_group')
764  nio_g=ng('namelist_quilt','nio_groups')
765 
766  total_nio_tpg=0
767  if isinstance(nio_tpg,list):
768  for num in nio_tpg:
769  total_nio_tpg+=int(num)
770 
771  # jtf I don't think nio_tpg can be a string, but just in case.
772  if isinstance(nio_tpg,basestring):
773  nio_tpg_str=nio_tpg.strip().strip(',').strip()
774  if ',' in nio_tpg_str:
775  nio_tpg_split=nio_tpg_str.split(',')
776  else:
777  nio_tpg_split=nio_tpg_str.split()
778  for num in nio_tpg_split:
779  total_nio_tpg+=int(num)
780 
781  nio=total_nio_tpg * nio_g
782  siu('namelist_quilt','poll_servers',nio > 0)
783 
784  self._tiling=None
785  s('domains','numtiles',1)
786  s('domains','tile_sz_x',0)
787  s('domains','tile_sz_y',0)
788  def set_nprocs(self,nproc_x=-1,nproc_y=-1):
789  """!Sets nproc_x and nproc_y in the namelist
790 
791  Sets the WRF namelist values of nproc_x and nproc_y, which
792  configure task geometry. Default values are -1, which tells
793  WRF to automatically decide the task geometry.
794  @param nproc_x,nproc_y the new values, which default to -1"""
795  nproc_x=int(nproc_x)
796  nproc_y=int(nproc_y)
797  s=self.nl.nl_set
798  s('domains','nproc_x',nproc_x)
799  s('domains','nproc_y',nproc_y)
800  return self
801  def add_hifreq(self,nestlevel):
802  """Adds the WRF hifreq_d<domain>.htcf product to the
803  specified domains of nestlevel in this simulation."""
804  for domain in self:
805  # Only add hifreq to the inner nest, it is
806  # the only domain that has a hifreq product from wrf.
807  if domain.nestlevel == nestlevel:
808  domain.add_hifreq()
809 
810  def set_dm_task_split(self, comm_start,nest_pes_x,nest_pes_y,
811  comm_start_d01=-1, nest_pes_x_d01=-1, nest_pes_y_d01=-1):
812  """Sets the WRF namelist values of comm_start, nest_pes_x and
813  nest_pes_y, which configures task geometry. Default
814  values are -1, which tells WRF to automatically decide the
815  task geometry.
816 
817  The nest_pes_x or nest_pes_y MUST BE either a comma
818  seperated string, list of ints, or a single int.
819  nest_pes_x
820  nest_pes_y"""
821 
822  # Using dm_task_split but let wrf decide. Remove the
823  # variables not the namelist. wrf expects an empty
824  # dm_task_split block.
825  if comm_start_d01==-1 and nest_pes_x_d01==-1 and nest_pes_y_d01==-1:
826  if self.nl.nl_have_sect('dm_task_split'):
827  self.nl.nl_del_sect('dm_task_split')
828  #if self.nl.nl_have('dm_task_split','nest_pes_x'):
829  # self.nl.nl_del('dm_task_split','nest_pes_x')
830  #if self.nl.nl_have('dm_task_split','nest_pes_y'):
831  # self.nl.nl_del('dm_task_split','nest_pes_y')
832  #if self.nl.nl_have('dm_task_split','comm_start'):
833  # self.nl.nl_del('dm_task_split','comm_start')
834  else:
835  if isinstance(comm_start,basestring):
836  if len(comm_start.strip().split(',')) > 1:
837  comm_start_ints=[int(s) for s in comm_start.strip().split(',')]
838  elif len(comm_start.strip().split()) > 1:
839  comm_start_ints=[int(s) for s in comm_start.strip().split()]
840  else:
841  comm_start_ints=[int(comm_start.strip())]
842  else:
843  comm_start_ints=int(comm_start)
844 
845  if isinstance(nest_pes_x,basestring):
846  if len(nest_pes_x.strip().split(',')) > 1:
847  nest_pes_x_ints=[int(s) for s in nest_pes_x.strip().split(',')]
848  elif len(nest_pes_x.strip().split()) > 1:
849  nest_pes_x_ints=[int(s) for s in nest_pes_x.strip().split()]
850  else:
851  nest_pes_x_ints=[int(nest_pes_x.strip())]
852  else:
853  nest_pes_x_ints=int(nest_pes_x)
854 
855  if isinstance(nest_pes_y,basestring):
856  if len(nest_pes_y.strip().split(',')) > 1:
857  nest_pes_y_ints=[int(s) for s in nest_pes_y.strip().split(',')]
858  elif len(nest_pes_y.strip().split()) > 1:
859  nest_pes_y_ints=[int(s) for s in nest_pes_y.strip().split()]
860  else:
861  nest_pes_y_ints=[int(nest_pes_y.strip())]
862  else:
863  nest_pes_y_ints=int(nest_pes_y)
864 
865  self.nl.nl_set('dm_task_split','comm_start',comm_start_ints)
866  self.nl.nl_set('dm_task_split','nest_pes_x',nest_pes_x_ints)
867  self.nl.nl_set('dm_task_split','nest_pes_y',nest_pes_y_ints)
868 
869  return self
870 
871  def has_output(self,stream):
872  """!Does this stream have any outputs?
873 
874  Determines if the specified stream has output.
875  @returns True if the stream if add_output() has been called
876  for this stream, for any domain, and False otherwise.
877  @param stream the string name of the stream (lower-case)."""
878  for domain in self:
879  if domain.has_output(stream):
880  return True
881  return False
882  def set_io_servers(self,tasks_per_group,groups,poll_servers=True):
883  """!Sets the I/O server configuration in WRF.
884 
885  Sets the WRF I/O server configuration in the &namelist_quilt
886  setting.
887  @return self
888  @param tasks_per_group the nio_tasks_per_group setting, which
889  specifies the number of I/O server tasks in each I/O server
890  group.
891  @param groups the nio_groups setting, an integer which
892  specifies the number of I/O server groups.
893  @param poll_servers the poll_servers setting, a logical that
894  specifies whether I/O server polling should be enabled."""
895 
896  # Handles if nio_tasks_per_group is a list 4,4,2,4,2,4,2 or scalar
897  if isinstance(tasks_per_group,basestring):
898  tasks_per_group_str=tasks_per_group.strip().strip(',').strip()
899  if ',' in tasks_per_group_str:
900  tasks_per_group_split=tasks_per_group_str.split(',')
901  else:
902  tasks_per_group_split=tasks_per_group_str.split()
903  if len(tasks_per_group_split) > 1:
904  nio_tpg_ints=[int(s) for s in tasks_per_group_split]
905  else:
906  nio_tpg_ints=[int(tasks_per_group_split)]
907  else:
908  nio_tpg_ints=int(tasks_per_group)
909 
910  groups=int(groups)
911  poll_servers=bool(poll_servers)
912  s=self.nl.nl_set
913  s('namelist_quilt','nio_tasks_per_group',nio_tpg_ints)
914  s('namelist_quilt','nio_groups',groups)
915  s('namelist_quilt','poll_servers',poll_servers)
916  return self
917  @property
919  """!The number of I/O server tasks per group"""
920  iopg=self.nl.nl_get('namelist_quilt','nio_tasks_per_group','1')
921  return iopg
922  @property
923  def nio_groups(self):
924  """!The number of I/O server groups"""
925  ngroups=self.nl.nl_get('namelist_quilt','nio_groups','0')
926  ngroups=int(ngroups)
927  return ngroups
928  def set_bdystep(self,step):
929  """!Sets the boundary input interval (interval_seconds)
930 
931  Sets the interval at which this WRF simulation expects
932  boundary conditions. Accepts anything that can be passed to
933  to_timedelta.
934  @param step boundary input interval. Can be anything accepted
935  by to_timedelta.
936  @return self"""
937  step=to_timedelta(step)
938  step=to_fraction(step)
939  step=int(float(round(step)))
940  self.nl.trait_set('bdystep',step)
941  self.nl.nl_set('time_control','interval_seconds',step)
942  def bdystep(self):
943  """!Returns the boundary input interval (interval_seconds)
944 
945  Computes the interval at which this WRF simulation expects
946  boundary conditions as a datetime.timedelta. This is done
947  using the "bdystep" trait.
948  @return the boundary input interval (interval_seconds) as a
949  datetime.timedelta"""
950  return to_timedelta(self.nl.trait_get('bdystep'))
951  def bdyepsilon(self):
952  """!Returns the epsilon for boundary time equality comparison
953 
954  Returns the largest difference between two times such that
955  they are considered identical. This is used in the context of
956  WRF boundary input times. This is equal to bdystep()/10
957  @return a fractions.Fraction with the suggested epsilon for
958  equality comparisons of boundary output time."""
959  return to_fraction(self.nl.trait_get('bdystep'))/10
960  def bdytimes(self):
961  """!Iterates over boundary times
962 
963  Iterates over times at which this WRF simulation expects
964  boundary conditions. Yields datetime objects for each
965  time."""
966  dt=self.bdystep()
967  now=self.simstart()
968  end=self.simend() + to_timedelta(to_fraction(dt)/10)
969  while now<end:
970  yield now
971  now+=dt
972  def num_tiles(self):
973  """!Returns the number of OpenMP tiles per MPI patch
974 
975  Gets the number of WRF tiles in each WRF patch, returning 1 if
976  tiling is not in use."""
977  if self._tiling is None:
978  return 1
979  else:
980  return self._tiling[0]*self._tiling[1]
981  def set_tiling(self,x,y):
982  """!Sets the OpenMP tiling information.
983 
984  Sets the number of WRF OpenMP tiles in each WRF MPI patch to x
985  by y. Don't use this: OpenMP is not supported by WRF-NMM"""
986  self._tiling=[int(x),int(y)]
987  s('domains','numtiles',int(x)*int(y))
988  s('domains','tile_sz_x',int(x))
989  s('domains','tile_sz_y',int(y))
990  def wrf_namelist(self,section_sorter=None,var_sorters=None):
991  """!Generates a Conf2Namelist for this simulation
992 
993  Generates a Conf2Namelist object for the namelist that
994  should be input to wrf.exe
995  @param section_sorter the section_sorter argument to hwrf.namelist.Conf2Namelist.__init__
996  @param var_sorters the var_sorters argument to hwrf.namelist.Conf2Namelist.__init__
997  @return an hwrf.namelist.Conf2Namelist object that can generate namelists for this simulation"""
998  nl=self.nl.copy()
999  domain_nl_list=[d.nl for d in self]
1000  domain_nl=domain_nl_list[0].join(domain_nl_list[1:])
1001  return domain_nl.copy(other=nl).remove_traits().set_sorters(
1002  _wrf_namelist_order,_wrf_nl_var_order)
1003  def _analysis_setup(self,io_form=None):
1004  """!Internal function for configuring wrfanl file generation
1005 
1006  Sets several namelist settings related to reading and writing
1007  wrfanl files. This does not enable wrfanl reading or writing
1008  though - that is done by analysis_out() and analysis_in().
1009  @param io_form the restart file io_form """
1010  self._domains_done=True
1011  if io_form is None:
1012  io_form=self.nl.trait_get(
1013  'io_form_restart',self.nl.trait_get('io_form',
1014  self._io_form))
1015  self.nl.nl_set('time_control','io_form_restart',io_form)
1016  self.nl.nl_set('time_control','override_restart_timers',True)
1017  self.nl.nl_set('time_control','restart',False)
1018  self.nl.nl_set_if_unset('time_control','restart_interval',36000)
1019  self.nl.nl_set('time_control','reset_simulation_start',False)
1020  def analysis_out(self,io_form=None):
1021  """!Requests that this WRF simulation write analysis files.
1022 
1023  Sets up the namelist settings for writing analysis files
1024  @param io_form the io_form for restart (wrfanl) files
1025  @return self"""
1026  for domain in self:
1027  #print 'domain %s analysis = %s'%(repr(domain),repr(False))
1028  domain.nl.nl_set('time_control','analysis',False)
1029  self._analysis_setup(io_form)
1030  return self
1031  def analysis_in(self,io_form=None):
1032  """!Requests that this WRF simulation read an analysis file.
1033 
1034  Sets up the namelist settings for reading analysis files
1035  @param io_form the io_form for restart (wrfanl) files
1036  @return self"""
1037  self._analysis_setup(io_form)
1038  moad=self.get_moad()
1039  for domain in self:
1040  val = domain!=moad
1041  #print 'domain %s analysis = %s'%(repr(domain),repr(val))
1042  domain.nl.nl_set('time_control','analysis',val)
1043  self._analysis_setup(io_form)
1044  return self
1045  def set_wrfanl_outname(self,pattern):
1046  """!Sets the WRF wrfanl output file pattern for all domains.
1047 
1048  Sets the output file pattern for the wrfanl file. It sets
1049  this for ALL domains.
1050  @param pattern the pattern for all wrfout files. Make sure
1051  you include at least one <domain> in the pattern
1052  @bug this function ignores the pattern argument. It always
1053  sets the pattern to "wrfanl_d<domain>_<date>" """
1054  for domain in self:
1055  domain.nl.nl_set('time_control','anl_outname',
1056  'wrfanl_d<domain>_<date>')
1057  def analysis_name(self,domain):
1058  """!Returns the wrfanl name for the specified domain
1059 
1060  Produces an analysis filename for the specified domain. NOTE:
1061  this function assumes all domains have the same wrfanl
1062  filename format.
1063  @param domain the wrf domain of interest (integer grid_id,
1064  string name or a WRFDomain object)
1065  @return the filename as a string"""
1066  domain=self.get(domain)
1067  domid=self.get(domain).get_grid_id()
1068  pattern=domain.nl.nl_get('time_control','anl_outname',
1069  'wrfanl_d<domain>_<date>')
1070  return parse_wrf_outname(pattern,domid,self.simstart(),
1071  self.get_nocolons())
1072  def restart_in(self,restartsource):
1073  """!Requests reading of a restart file
1074 
1075  Raises NotImplementedError(). This would request that this
1076  WRF simulation read a restart file. This is not implemented
1077  since the restart capability was broken as of the writing of
1078  this function."""
1079  raise NotImplementedError(
1080  'Restart capability is presently broken in HWRF.')
1081  return self
1082  def set_active_io_form_to(self,io_form):
1083  """!Sets the io_form for all active streams.
1084 
1085  Changes the io_form for all streams to the specified io_form
1086  @param io_form the io_form as an integer"""
1087  io_form=int(io_form)
1088  forms=set()
1089  for var,value in self.nl.nl_each('time_control'):
1090  if var.find('io_form')>=0:
1091  forms.add(var)
1092  for var in forms:
1093  self.nl.nl_set('time_control',var,io_form)
1094  def set_io_form(self,stream,io_form=None):
1095  """!Sets the io_form for the given stream
1096 
1097  Changes the io_form for the specified stream
1098  @param stream the string name of the stream, lower-case
1099  @param io_form the io_form to use. If unspecified or None,
1100  then self.io_form_for(stream) is called"""
1101  io_form_stream='io_form_%s'%(stream,)
1102  s=self.nl.nl_set
1103  ts=self.nl.trait_set
1104  t=self.nl.trait_get
1105  if io_form is not None:
1106  s('time_control',io_form_stream,int(io_form))
1107  ts(io_form_stream,int(io_form))
1108  else:
1109  s('time_control',io_form_stream,self.io_form_for(stream))
1110  ts(io_form_stream,self.io_form_for(stream))
1111  def set_timing(self,start=None,end=None,timestep=None):
1112  """!sets the simulation start and tend times, and timestep
1113 
1114  Sets the simulation start and end times, and timestep. The
1115  start may be anything accepted by to_datetime. The end is
1116  passed through to_datetime_rel, relative to start. The
1117  timestep must be accepted by to_fraction.
1118  @param start,end simulation start and end times
1119  @param timestep the outermost domain timestep"""
1120  if start is None: start=self._simstart
1121  if end is None: end=self._simend
1122  if timestep is None: timestep=self._timestep
1123  start=to_datetime(start)
1124  end=to_datetime_rel(end,start)
1125  timestep=to_fraction(timestep)
1126  for domain in self:
1127  domain.set_timing(start,end,timestep/domain.moad_ratio())
1128  self._simstart=start
1129  self._simend=end
1130  self._timestep=timestep
1131  def set_metgrid_levels_from(self,exepath,metgrid_out_file,logger=None):
1132  """!Sets the num_metgrid_levels and num_metgrid_soil_levels
1133 
1134  Overrides the num_metgrid_levels in &domains to equal the
1135  value in the specified metgrid file. Does this by analyzing
1136  the output of metgrid.
1137  @param exepath path to the hwrf_metgrid_levels program
1138  @param metgrid_out_file path to the metgrid out file to read
1139  @param logger optional logging.Logger for logging"""
1140  strexe=str(exepath)
1141  strmet=str(metgrid_out_file)
1142  nummet=runstr(batchexe(strexe)[strmet],logger=logger)
1143  numsm=runstr(batchexe(strexe)[strmet,'num_sm_levels'],logger=logger)
1144  numst=runstr(batchexe(strexe)[strmet,'num_st_levels'],logger=logger)
1145  nummet=int(nummet)
1146  numsm=int(numsm)
1147  numst=int(numst)
1148  numsoil=min(numsm,numst)
1149  if logger is not None:
1150  logger.info('Have %d levels, %d soil levels (m=%d t=%d).'
1151  %(nummet,numsoil,numsm,numst))
1152  self.nl.nl_set('domains','num_metgrid_levels',nummet)
1153  self.nl.nl_set('domains','num_metgrid_soil_levels',numsoil)
1154  def swcorner_dynamic(self,exepath,storminfo,domlat,domlon,logger=None):
1155  """!Suns swcorner_dynamic to set domain start locations
1156 
1157  Runs the swcorner_dynamic program to fill in this
1158  WRFSimulation's domain start locations. Returns the resulting
1159  namelist as a multi-line string.
1160 
1161  @param exepath full path to the hwrf_swcorner_dynamic
1162  @param storminfo an hwrf.storminfo.StormInfo object for the
1163  storm of interest
1164  @param domlat,domlon the outermost domain center lat & lon,
1165  which is also the projection center
1166  @param logger optional logger.Logger object for logging"""
1167  # Create a copy of this WRF so we can fill in junk values for
1168  # the I & J starts:
1169  junkwrf=self.copy()
1170  junkwrf.fill_auto_starts(7) # set auto-start locations to sevens
1171  junknml=junkwrf.wrf_namelist().make_namelist()
1172  with open('fort.12','wt') as f:
1173  f.write(junknml)
1174  # sys.stdout.write(junknml)
1175  with open('domain.center','wt') as f:
1176  f.write('%f\n%f\n'%(float(domlat),float(domlon)))
1177  #f.write(self.confstrinterp("{domlat}\n{domlon}\n"))
1178  with open('storm.center','wt') as f:
1179  f.write('%f\n%f\n'%(storminfo.lat,storminfo.lon))
1180  #f.write(self.confstrinterp("{vit[lat]:f}\n{vit[lon]:f}\n"))
1181  strexe=str(exepath)
1182  checkrun(bigexe(strexe) << str(storminfo.hwrfbasin2),logger=logger)
1183  if not isnonempty('set_nest'):
1184  raise SetNestFailed('%s could not find the nest south-west '
1185  'corner point.'%(strexe,))
1186  try:
1187  (istart, jstart) = (-99,-99)
1188  with open('set_nest','rt') as f:
1189  for line in f:
1190  line=line.upper().rstrip()
1191  m=re.search('([IJ])START=([0-9.+-]+)',line)
1192  if m:
1193  (ij,val)=m.groups()
1194  if ij=='I': istart=int(val)
1195  if ij=='J': jstart=int(val)
1196  elif line and logger:
1197  logger.warning('%s: ignoring unrecognized line %s'
1198  %(strexe,line.rstrip()))
1199  if not (istart>5 and jstart>5):
1200  raise SetNestFailed(
1201  '%s: could not find the south-west corner point '
1202  '(istart=%d jstart=%d)'%(strexe,istart,jstart))
1203  self.fill_auto_starts(istart,jstart)
1204  return self.wrf_namelist().make_namelist()
1205  except Exception as e:
1206  if logger:
1207  logger.warning('%s unexpected exception: %s'
1208  %(strexe,str(e)),exc_info=True)
1209  raise
1210 
1211  # TODO: Refactor, Really only need one method, combine this in to swcorner_dynamic <jtf>
1212  # Just in a hurry, so combine it later.
1213  def swcorner_dynamic_multistorm(self,exepath,all_storminfo,domlat,domlon,logger=None):
1214  """Runs the swcorner_dynamic program to fill in this
1215  WRFSimulation's domain start locations. Returns the resulting
1216  namelist as a multi-line string.
1217 
1218  Inputs:
1219  exepath = full path to the hwrf_swcorner_dynamic
1220 
1221  all_storminfo = a list of hwrf.storminfo.StormInfo objects
1222  for all the real storms in the multistorm run.
1223 
1224 
1225  domlat, domlon = the outermost domain center lat & lon,
1226  which is also the projection center
1227 
1228  logger = optional: a logger.Logger object for logging"""
1229  # Create a copy of this WRF so we can fill in junk values for
1230  # the I & J starts:
1231  junkwrf=self.copy()
1232  junkwrf.fill_auto_starts(7) # set auto-start locations to sevens
1233  junknml=junkwrf.wrf_namelist().make_namelist()
1234  with open('fort.12','wt') as f:
1235  f.write(junknml)
1236  # sys.stdout.write(junknml)
1237 
1238  # This is the domain.center of the MOAD
1239  # So it is the same for all the storms.
1240  with open('domain.center','wt') as f:
1241  f.write('%f\n%f\n'%(float(domlat),float(domlon)))
1242  #f.write(self.confstrinterp("{domlat}\n{domlon}\n"))
1243 
1244  istarts = []
1245  jstarts = []
1246  for index, storminfo in enumerate(all_storminfo):
1247  with open('storm.center','wt') as f:
1248  f.write('%f\n%f\n'%(storminfo.lat,storminfo.lon))
1249  #f.write(self.confstrinterp("{vit[lat]:f}\n{vit[lon]:f}\n"))
1250  strexe=str(exepath)
1251  checkrun(bigexe(strexe) << str(storminfo.hwrfbasin2),logger=logger)
1252  # This is the out file from the fortran program.
1253  # It will get overwritten by each storm, so it will only contain
1254  # the last storm's I and J
1255  # strominfo.hwrfbasin2 is required input but isn't used by the fortran exe.
1256  if not isnonempty('set_nest'):
1257  raise SetNestFailed('%s could not find the nest south-west '
1258  'corner point.'%(strexe,))
1259  try:
1260  (istart, jstart) = (-99,-99)
1261  with open('set_nest','rt') as f:
1262  for line in f:
1263  line=line.upper().rstrip()
1264  m=re.search('([IJ])START=([0-9.+-]+)',line)
1265  if m:
1266  (ij,val)=m.groups()
1267  if ij=='I':
1268  istart=int(val)
1269  istarts.append(istart)
1270  if ij=='J':
1271  jstart=int(val)
1272  jstarts.append(jstart)
1273  elif line and logger:
1274  logger.warning('%s: ignoring unrecognized line %s'
1275  %(strexe,line.rstrip()))
1276  if not (istart>5 and jstart>5):
1277  raise SetNestFailed(
1278  '%s: could not find the south-west corner point '
1279  '(istart=%d jstart=%d)'%(strexe,istart,jstart))
1280  except Exception as e:
1281  if logger:
1282  logger.warning('%s unexpected exception: %s'
1283  %(strexe,str(e)),exc_info=True)
1284  raise
1285  try:
1286  self.fill_auto_starts_multistorm(istarts,jstarts)
1287  return self.wrf_namelist().make_namelist()
1288  except Exception as e:
1289  if logger:
1290  logger.warning('%s unexpected exception: %s'
1291  %(strexe,str(e)),exc_info=True)
1292  raise
1293 
1294 ########################################################################
1295 
1297  """!monitors a running wrf simulation
1298 
1299  This class represents a WRF simulation that is running in an
1300  external workflow. It reads the WRF configuration to internally
1301  generate namelist information, as if it was going to run the WRF
1302  itself. It then monitors the running or completed WRF simulation
1303  for simulation output, making it available as Product objects.
1304  All WRF outputs are available as UpstreamProduct objects.
1305 
1306  The WRFSimulation object is available as the public "wrf" member
1307  variable and is initialized by the ExternalWRFTask constructor
1308  using arguments similar to the WRFSimulation constructor. The
1309  simulation start, end and timestep, if unspecified, are taken from
1310  the specified conf section's variables by the same name."""
1311  def __init__(self,dstore,conf,section,wrf,relocate=False,**kwargs):
1312  """!ExternalWRFTask constructor
1313 
1314  Creates an ExternalWRFTask, as a wrapper around a
1315  WRFSimulation. The conf, section, moad, simstart, simend and
1316  timestep are passed on to the WRFSimulation. If simstart,
1317  simend, or timestep are None or missing, then they are taken
1318  from the configuration section for this task."""
1319  self.__prodcache=dict()
1320  self.__wrf=wrf
1321  if 'skip_parent' not in kwargs or not kwargs['skip_parent']:
1322  HWRFTask.__init__(self,dstore,conf,section,**kwargs)
1323  self.change_location()
1324  if 'outdir' not in self:
1325  if 'outdir' not in kwargs:
1326  self['outdir']=os.path.join(self.getdir('WORKhwrf'),
1327  self.taskname)
1328  else:
1329  self['outdir']=str(kwargs['outdir'])
1330  with self.dstore.transaction() as t:
1331  for p in self.products(relocate=relocate): pass
1332 
1333  ## @var __wrf
1334  # the underlying WRFSimulation object
1335 
1336  ## @var __prodcache
1337  # a mapping from WRFOutput to WRFProduct, used only as a cache
1338 
1339  # This method is used by multistorm.
1340  def change_location(self):
1341  """Allows subclasses to change self.location in the
1342  constructor before product generation."""
1343  def wrf(self):
1344  """!returns the WRFSimulation object that describes the simulation
1345 
1346  Returns the underlying WRFSimulation object that describes the
1347  simulation that is being run."""
1348  return self.__wrf
1349  def unrun(self):
1350  """!marks products as not having been delivered
1351 
1352  Marks all products as not having been delivered. Does not
1353  delete anything."""
1354  for product in self.products():
1355  product.undeliver()
1357  """!clears cached Product objects
1358 
1359  Clears the cache of WRFOutput -> Product mappings, used to
1360  speed up as_product. Calling this will ensure that any later
1361  calls to as_product will generate new Product objects."""
1362  self.__prodcache=dict()
1363  def _get_cache(self,wrfout):
1364  """!Returns the product cached for the specified wrfout or None if not found.
1365  @protected
1366  @param wrfout the hwrf.wrfbase.WRFOutput"""
1367  if self.__prodcache.has_key(wrfout):
1368  return self.__prodcache[wrfout]
1369  return None
1370  def _set_cache(self,wrfout,uf):
1371  """!Sets the cached produtil.datastore.UpstreamFile for the given wrfout
1372  @param wrfout the hwrf.wrfbase.WRFOutput for which uf is the cached product
1373  @param uf the product to return from _get_cache() and as_product()
1374  @returns uf"""
1375  self.__prodcache[wrfout]=uf
1376  return uf
1377  def as_product(self,wrfout,relocate=False):
1378  """!Converts a WRFOutput to a Product.
1379 
1380  Returns a Product for a WRFOutput. The Product is cached in
1381  self.__prodcache and as_product() will return that cached
1382  value if one is available. Otherwise, a new one is created.
1383  Call clear_cached_products() to clear the cache."""
1384  if self.__prodcache.has_key(wrfout):
1385  return self.__prodcache[wrfout]
1386  rel=wrfout.path()
1387  #outdir=os.path.join(self['outdir'],rel)
1388  outdir=self['outdir']
1389  assert(outdir is not None)
1390  loc=os.path.join(outdir,os.path.basename(wrfout.path()))
1391  with self.dstore.transaction() as t:
1392  uf=UpstreamFile(self.dstore,category=self.taskname,
1393  prodname=rel,location=loc)
1394  stream=wrfout.stream()
1395  minsize_def=self.confint('minsize',0)
1396  minsize=self.confint('minsize_'+stream,minsize_def)
1397  minage_def=self.confint('minage',20)
1398  minage=self.confint('minage_'+stream,minage_def)
1399  uf['stream']=stream
1400  uf['location']=loc
1401  uf['minsize']=minsize
1402  uf['minage']=minage
1403  if relocate: uf.location=loc
1404  self.__prodcache[wrfout]=uf
1405  return uf
1406  def products(self,domains=None,stream=None,time=None,relocate=False):
1407  """!Iterate over products
1408 
1409  Iterates over all Products subject to the given constraints,
1410  or all Products if no constraints are given:
1411 
1412  @param domains only these WRFDomains
1413  @param stream only these streams (strings)
1414  @param time only these times. The earliest output time that
1415  is not before the target time is yielded
1416  @param relocate passed to self.as_product. Forces an update
1417  of the product location """
1418  if domains is None:
1419  domlist=[d for d in self.__wrf]
1420  elif isinstance(domains,WRFDomainBase) or isinstance(domains,int) \
1421  or isinstance(domains,basestring):
1422  domlist=[self.__wrf.get(domain)]
1423  else:
1424  domlist=[self.__wrf.get(d) for d in domains]
1425  for domain in domlist:
1426  if stream is None:
1427  for out in domain.get_all_outputs(time):
1428  yield self.as_product(out,relocate=relocate)
1429  elif time is None:
1430  for out in domain.get_outputs(stream):
1431  yield self.as_product(out,relocate=relocate)
1432  else:
1433  yield self.as_product(domain.get_output(stream,time),
1434  relocate=relocate)
1435  def wrf_check(self,product=None,stream=None,time=None):
1436  """!Update file availability information
1437 
1438  Calls produtil.datastore.UpstreamFile.check() to update the
1439  availability information on all products() that match the
1440  given constraints.
1441 
1442  @param product only this product is checked
1443  @param stream only these streams (strings)
1444  @param time only these times. The earliest output time that
1445  is not before the target time is yielded"""
1446  if product is not None:
1447  product.check()
1448  else:
1449  for prod in self.products(stream,time):
1450  product.check()
1451  def update_state(self):
1452  """!Is the WRF running, completed or failed?
1453 
1454  Scans the rsl.out.0000 file to automatically determine the
1455  state of the WRF simulation. Looks for "SUCCESS COMPLETE" and
1456  "FATAL CALLED" to detect successful completion, or calling of
1457  wrf_error_fatal. Sets self.state to
1458  produtil.datastore.COMPLETED, produtil.datastore.FAILED,
1459  produtil.datastore.RUNNING or produtil.datastore.UNSTARTED
1460  based on the contents of rsl.out.0000"""
1461  logger=self.log()
1462  logger.info('Check on status of WRF...')
1463  rsl0=os.path.join(self.location,'rsl.out.0000')
1464  if not produtil.fileop.isnonempty(rsl0):
1465  logger.info('No RSL file here: '+repr(rsl0))
1466  self.state=UNSTARTED
1467  try:
1468  with open(rsl0,'rt') as f:
1469  try:
1470  f.seek(-10000,os.SEEK_END) # seek to 10000 bytes from end
1471  except EnvironmentError as e:
1472  logger.info(
1473  'Cannot seek -10000 bytes. Will read whole file.')
1474  pass # if we cannot seek, then just read from
1475  # start of file
1476  for line in f:
1477  if re.search('SUCCESS COMPLETE',line):
1478  logger.info('WRF is complete: %s'%(line.rstrip(),))
1479  self.state=COMPLETED
1480  return
1481  elif re.search('FATAL CALLED',line):
1482  logger.info('WRF failed: %s'%(line.rstrip(),))
1483  self.state=FAILED
1484  return
1485  except EnvironmentError as e:
1486  logger.warning('Unexpected error checking WRF: %s'%(str(e),),
1487  exc_info=True)
1488  self.state=UNSTARTED
1489  self.state=RUNNING
1490 
Raised when failing to set the parent start location via the set_nest (set_ij_start) program...
Definition: exceptions.py:331
This module provides a set of utility functions to do filesystem operations.
Definition: fileop.py:1
def get_moad(self)
returns the MOAD as a WRFDomain.
Definition: wrfbase.py:625
def get_output
Get output for a specified time and stream.
Definition: wrf.py:491
io_form
the default io_form if none is specified
Definition: wrfbase.py:613
Generates a Fortran namelist entirely from config files.
Definition: namelist.py:411
def set_timing
sets the simulation start and tend times, and timestep
Definition: wrf.py:1111
def swcorner_dynamic_multistorm
Definition: wrf.py:1213
def getdt(self)
Returns the timestep for this domain.
Definition: wrf.py:218
def wrf(self)
returns the WRFSimulation object that describes the simulation
Definition: wrf.py:1343
def get_output_range(self, stream)
Returns the range of times that has output for the given stream.
Definition: wrf.py:389
def set_metgrid_levels_from
Sets the num_metgrid_levels and num_metgrid_soil_levels.
Definition: wrf.py:1131
taskname
Read-only property: the name of this task.
Definition: datastore.py:1134
def nx(self)
The number of grid cells the X direction.
Definition: wrf.py:273
The base class of tasks run by the HWRF system.
Definition: hwrftask.py:25
def moad_ratio(self)
Returns the nesting ratio between this domain and the MOAD.
Definition: wrf.py:211
def update_state(self)
Is the WRF running, completed or failed?
Definition: wrf.py:1451
def analysis_in
Requests that this WRF simulation read an analysis file.
Definition: wrf.py:1031
dstore
Read-only property, an alias for getdatastore(), the Datastore in which this Datum resides...
Definition: datastore.py:557
nl
The hwrf.namelist.Conf2Namelist for this domain.
Definition: wrf.py:191
nocolons
True if colons should be omitted from filenames.
Definition: wrf.py:183
def nio_tasks_per_group(self)
The number of I/O server tasks per group.
Definition: wrf.py:918
def nio_groups(self)
The number of I/O server groups.
Definition: wrf.py:923
def init_as_moad(self, simstart, simend, simdt, eta_levels)
Called by WRFSimulation to initialize this as the outermost domain.
Definition: wrf.py:298
def clear_cached_products(self)
clears cached Product objects
Definition: wrf.py:1356
name
The name of this domain.
Definition: wrf.py:192
def has_output(self, stream)
Returns True if the domain will output to the specified stream.
Definition: wrf.py:379
def set_io_servers
Sets the I/O server configuration in WRF.
Definition: wrf.py:882
def _get_output_time(self, when)
Internal function that determines the time output would actually be generated.
Definition: wrf.py:417
def _analysis_setup
Internal function for configuring wrfanl file generation.
Definition: wrf.py:1003
def bdyepsilon(self)
Returns the epsilon for boundary time equality comparison.
Definition: wrf.py:951
def set_timing(self, start, end, timestep)
Sets start and end times and timestep.
Definition: wrf.py:247
def change_location(self)
Definition: wrf.py:1340
low-level wrf implementation, underlying hwrf.wrf
Definition: wrfbase.py:1
def unrun(self)
marks products as not having been delivered
Definition: wrf.py:1349
_tiling
Unused: stores OpenMP tiling information.
Definition: wrf.py:702
def __init__
Creates a new WRFSimulation object:
Definition: wrf.py:687
def default_wrf_outname(stream)
default wrf output filename patterns
Definition: wrf.py:31
def is_moad(self)
Is this the outermost domain?
Definition: wrf.py:264
def __repr__(self)
A string description of this domain.
Definition: wrf.py:227
def copy(self)
Makes a deep copy of this object.
Definition: wrf.py:674
Base class of tasks run by HWRF.
Definition: hwrftask.py:1
A shell-like syntax for running serial, MPI and OpenMP programs.
Definition: run.py:1
def getdir
Alias for hwrf.config.HWRFConfig.get() for the "dir" section.
Definition: hwrftask.py:396
def set_nprocs
Sets nproc_x and nproc_y in the namelist.
Definition: wrf.py:788
def simstart(self)
Returns the simulation start time as a datetime.datetime.
Definition: wrfbase.py:551
def isnonempty(filename)
Returns True if the filename refers to an existent file that is non-empty, and False otherwise...
Definition: fileop.py:333
Stores products and tasks in an sqlite3 database file.
Definition: datastore.py:1
location
Read-write property, an alias for getlocation() and setlocation().
Definition: datastore.py:563
def set_bdystep(self, step)
Sets the boundary input interval (interval_seconds)
Definition: wrf.py:928
def get_all_outputs
Iterate over all output files for a specified time.
Definition: wrf.py:455
Time manipulation and other numerical routines.
Definition: numerics.py:1
Raised when attempting to obtain information about when a WRF stream outputs, if the stream is disabl...
Definition: exceptions.py:326
def no_output(self, stream)
Forget that output was requested for a given stream.
Definition: wrf.py:514
def add_output
Adds output to the specified stream.
Definition: wrf.py:553
def confint
Alias for self.conf.getint for section self.section.
Definition: hwrftask.py:248
def add_hifreq(self, nestlevel)
Definition: wrf.py:801
def make_namelist(self)
Creates the WRF namelist contents and returns it as a string.
Definition: wrf.py:376
This module provides two different ways to generate Fortran namelist files from HWRFConfig sections: ...
Definition: namelist.py:1
def set_active_io_form_to(self, io_form)
Sets the io_form for all active streams.
Definition: wrf.py:1082
def hifreq_file(self)
Returns the hifreq filename for this domain.
Definition: wrf.py:409
def as_product
Converts a WRFOutput to a Product.
Definition: wrf.py:1377
def interval_for(self, stream)
Return the output interval for the stream.
Definition: wrf.py:651
Raised when a time was requested with higher precision than available.
Definition: exceptions.py:194
def _get_output
Internal function that generates WRFOutput objects.
Definition: wrf.py:428
def log
Obtain a logging domain.
Definition: hwrftask.py:425
monitors a running wrf simulation
Definition: wrf.py:1296
superclass of WRFDomain
Definition: wrfbase.py:144
A class that provides information about WRF output and input files.
Definition: wrfbase.py:46
_io_form
the default io_form
Definition: wrfbase.py:430
abstract base class of WRFSimulation
Definition: wrfbase.py:340
def analysis_out
Requests that this WRF simulation write analysis files.
Definition: wrf.py:1020
Base class of WRF-related errors.
Definition: exceptions.py:302
Sorts a pre-determined list of objects, placing unknown items at a specified location.
Definition: numerics.py:21
Superclass of exceptions relating to groups of one or more distinct times and relationships between t...
Definition: exceptions.py:207
def _get_cache(self, wrfout)
Returns the product cached for the specified wrfout or None if not found.
Definition: wrf.py:1363
dy
the resolution in the rotated latitude direction
Definition: wrf.py:189
def init_as_nest(self, parent, grid_id, start, end)
Called by WRFSimulation to initialize this domain as a nest.
Definition: wrf.py:328
def __init__
WRFDomain constructor.
Definition: wrf.py:174
def simend(self)
Returns the simulation end time as a datetime.datetime.
Definition: wrfbase.py:554
def fill_auto_starts
sets i_parent_start and j_parent_start if needed
Definition: wrfbase.py:449
Raised when a timespan's beginning is not at a timestep.
Definition: exceptions.py:234
def set_tiling(self, x, y)
Sets the OpenMP tiling information.
Definition: wrf.py:981
__wrf
the underlying WRFSimulation object
Definition: wrf.py:1320
def get_nocolons(self)
Force a recheck of whether colons be omitted from filenames.
Definition: wrfbase.py:574
def _validate_timespan(self, start, end, timestep)
checks if a timespan is valid
Definition: wrfbase.py:253
def restart_in(self, restartsource)
Requests reading of a restart file.
Definition: wrf.py:1072
def wrf_namelist
Generates a Conf2Namelist for this simulation.
Definition: wrf.py:990
Exceptions raised by the hwrf package.
Definition: exceptions.py:1
def num_tiles(self)
Returns the number of OpenMP tiles per MPI patch.
Definition: wrf.py:972
generate and manipulate wrf namelists, predict output filenames
Definition: wrf.py:663
parent
The parent WRFDomain.
Definition: wrf.py:182
def get(self, what)
return the specified domain
Definition: wrfbase.py:737
def nz(self)
The number of grid cells the Z direction.
Definition: wrf.py:281
def has_output(self, stream)
Does this stream have any outputs?
Definition: wrf.py:871
def swcorner_dynamic
Suns swcorner_dynamic to set domain start locations.
Definition: wrf.py:1154
def products
Iterate over products.
Definition: wrf.py:1406
def bdystep(self)
Returns the boundary input interval (interval_seconds)
Definition: wrf.py:942
def analysis_name(self, domain)
Returns the wrfanl name for the specified domain.
Definition: wrf.py:1057
def wrf_check
Update file availability information.
Definition: wrf.py:1435
def hide_output(self, stream)
Disable output for a specified stream, if the stream was requested before.
Definition: wrf.py:522
nestlevel
The WRF domain nesting level.
Definition: wrf.py:181
def get_anl_time(self)
returns the analysis time
Definition: wrfbase.py:217
def trackpatcf_file(self)
Definition: wrf.py:413
def copy(self)
Returns a deep copy of this object.
Definition: wrf.py:240
def get_outputs(self, stream)
Iterates over all outputs for a specified stream.
Definition: wrf.py:468
dx
the resolution in the rotated longitude direction
Definition: wrf.py:188
def io_form_for(self, stream)
Returns the io_form for the specified stream.
Definition: wrfbase.py:616
def init_domain(self, grid_id)
Internal helper function that initializes variables common to all domains.
Definition: wrf.py:284
def __init__(self, dstore, conf, section, wrf, relocate=False, kwargs)
ExternalWRFTask constructor.
Definition: wrf.py:1311
def ny(self)
The number of grid cells the Y direction.
Definition: wrf.py:277
Raised when the hwrf.wrf.WRFDomain start type is unrecognized.
Definition: exceptions.py:318
def bdytimes(self)
Iterates over boundary times.
Definition: wrf.py:960
__prodcache
a mapping from WRFOutput to WRFProduct, used only as a cache
Definition: wrf.py:1319
def set_wrfanl_outname(self, pattern)
Sets the WRF wrfanl output file pattern for all domains.
Definition: wrf.py:1045
A domain in a WRF simulation.
Definition: wrf.py:117
def set_io_form
Sets the io_form for the given stream.
Definition: wrf.py:1094
Represents a Product created by an external workflow.
Definition: datastore.py:915
def get_grid_id(self)
Returns the WRF grid id.
Definition: wrf.py:261