HWRF  trunk@4391
hwrf_generate_vitals.py
1 #! /usr/bin/env python
2 
3 ##@namespace ush.hwrf_generate_vitals
4 # A utility script for tcvitals manipulation, a wrapper around hwrf.revital
5 #
6 # This script is a wrappar around the hwrf.storminfo and hwrf.revital
7 # modules. It can read in tcvitals files from known locations and
8 # manipulate them in various ways. Call like so:
9 # @code{.sh}
10 # hwrf_generate_vitals.py [options] stormid years [LOG renumberlog [/path/to/syndat/ ]]
11 # @endcode
12 #
13 # Where:
14 # * stormid --- the three character storm ID (ie.: 12L is Katrina)
15 # * years --- a single argument with a space-separated list of years.
16 # Typically only one year is provided but multiple is allowed.
17 # * LOG renumberlog --- The renumberlog is an output file with information
18 # about how renumbering was done. It must be preceeded by a non-empty
19 # argument (such as the word "LOG").
20 # * /path/to/syndat/ --- a directory with tcvitals data. If this is not
21 # specified, the script will try to guess where the data resides.
22 #
23 # Options:
24 # * -v --- verbosity
25 # * -n --- disable renumbering of invest cycles
26 # * -H --- enable HHS output format. Do not use.
27 #
28 # Environment Variables
29 # * $PARAFLAG=YES/NO --- set to NO if you are NCEP Central Operations, or YES if you are not
30 # * $CASE_ROOT=HISTORY --- retrospective runs
31 # * $CASE_ROOT=FORECAST --- real-time runs
32 # * $COMINARCH --- if you are NCO, this is the path to the tcvitals
33 # * $envir --- if you are NCO, this is the run environment (prod, para, test)
34 # * $mesagdir --- if you are NCO, this is where the message files reside
35 
36 import logging, sys, os, getopt
38 import hwrf.revital
39 import hwrf.rocoto
40 
41 ##@var logger
42 # Logging domain used for this script
43 logger=None
44 
45 def setup():
46  """!Sets up the used parts of the produtil package. This does NOT
47  call produtil.setup.setup(). Instead, it installs the signal
48  handlers and sets up the Python logging module directly. No other
49  parts of the produtil package are initialized."""
50  # Install SIGTERM, SIGINT, etc. handlers. Needed to handle batch job
51  # kills correctly.
52  global logger
54  global handler, logger
55  # Set up logging. We customize things to look nice.
56  handler=logging.StreamHandler(sys.stderr)
57  handler.setFormatter(logging.Formatter(
58  'hwrf_revital:%(levelname)s:%(asctime)s: %(message)s',
59  '%Y%m%d.%H%M%S'))
60  logger=logging.getLogger()
61  logger.setLevel(logging.INFO)
62  logger.addHandler(handler)
63 
64 ##@var forecast
65 # Unused.
66 forecast=False
67 
68 ##@var inputs
69 # List of input tcvitals and message files to read in.
70 inputs=[]
71 
72 ##@var tcvlocs
73 # List of possible tcvitals locations.
74 tcvlocs=[]
75 
76 ##@var messagedir
77 # Directory with message files
78 messagedir=[]
79 
80 ##@var PARA
81 # True = we are not NCEP Central Operations ($PARAFLAG=YES in environment)
82 PARA = ( 'YES' == os.environ.get('PARAFLAG','YES') )
83 
84 ########################################################################
85 # THIS SECTION NEEDS HARD-CODED PATHS FOR EMC ##########################
86 # HARD-CODED PATHS ARE PROTECTED BY PARAFLAG=YES BLOCK BELOW ###########
87 def set_para_paths():
88  """!Sets tcvitals and message file locations for non-NCO runs."""
89  global tcvlocs, messagedir, inputs
90  tcvlocs=[
91  "/scratch3/NCEPDEV/hwrf/noscrub/input/SYNDAT-PLUS",
92  "/scratch3/NCEPDEV/hwrf/noscrub/input/SYNDAT",
93  "/scratch1/portfolios/NCEPDEV/hwrf/noscrub/input/SYNDAT-PLUS",
94  "/scratch1/portfolios/NCEPDEV/hwrf/noscrub/input/SYNDAT",
95  "/lfs3/projects/hwrf-data/hwrf-input/SYNDAT-PLUS",
96  "/lfs3/projects/hwrf-data/hwrf-input/SYNDAT",
97  "/hwrf/noscrub/input/SYNDAT-PLUS",
98  "/hwrf/noscrub/input/SYNDAT",
99  "/gpfs/gp1/nco/ops/com/arch/prod/syndat/",
100  "/gpfs/tp1/nco/ops/com/arch/prod/syndat/",
101  ]
102  messagedir=[
103  "/scratch3/NCEPDEV/hwrf/noscrub/input/MESSAGES",
104  "/scratch1/portfolios/NCEPDEV/hwrf/noscrub/input/MESSAGES",
105  "/lfs1/projects/hwrf-vd/hwrf-input/MESSAGES",
106  "/com/hur/prod/inpdata"
107  ]
108  if 'CASE_ROOT' in os.environ and os.environ['CASE_ROOT']=='FORECAST':
109  tcvlocs=['/com/arch/prod/syndat']
110  else:
111  tcvlocs.append('/com/arch/prod/syndat')
112 # END OF SECTION WITH HARD-CODED PATHS #################################
113 ########################################################################
114 
115 usage_message='''hwrf_generate_vitals.py version 5.7.1
116 SYNOPSIS:
117  hwrf_generate_vitals.py 11L 2015
118 
119  Generates cycle lists and other information from TCVitals data.
120 
121 SYNTAX:
122 
123  hwrf_generate_vitals.py [options] STORMID YEAR
124 
125 The STORMID must be a capital, three-character storm identifier such
126 as 11L or 18E or 04S. The only valid basin letters are the ones found
127 in the tcvitals database.
128 
129 The year must be four digits.
130 
131 OPTIONS:
132 
133  -v => Be verbose.
134  -W 14 => Set the "weak storm" threshold to 14 knots.
135  -n => Disable renumbering of invests to non-invests.
136  -N => Enable renaming of storms to last name seen.
137  -u => Unrenumber and unrename after renumbering and
138  renaming, discarding unrelated cycles
139  -R => Output data in Rocoto <cycledef> tags.
140  -H => Do not use. Special output format for HHS.
141 '''
142 
143 def usage(why=None):
144  """!Prints a usage message on stderr and exits with status 1."""
145  sys.stderr.write(usage_message)
146  if why:
147  sys.stderr.write('\nSCRIPT IS ABORTING DUE TO ERROR: %s\n'%(why,))
148  sys.exit(1)
149  else:
150  sys.exit(0)
151 
152 def main():
153  """!Main program. Parses arguments, reads inputs, writes outputs."""
154  # PARSE ARGUMENTS
155  global logger, inputs, tcvlocs, messagedir, forecast, PARA
156  setup()
157  renumber=True
158  unrenumber=False
159  rename=False
160  format='tcvitals'
161  threshold=14
162  try:
163  (optlist,args) = getopt.getopt(sys.argv[1:],'HvnW:NuR')
164  for opt,val in optlist:
165  if opt=='-v':
166  logger.setLevel(logging.DEBUG)
167  logger.debug('Verbosity enabled.')
168  elif opt=='-W':
169  threshold=int(val)
170  logger.debug('Weak storm threshold is now %d'%(threshold,))
171  elif opt=='-n':
172  renumber=False
173  logger.info('Disabling renumbering due to -n')
174  elif opt=='-N':
175  rename=True
176  logger.info('Enabling renaming.')
177  elif opt=='-u':
178  unrenumber=True
179  logger.info('Enabling un-renumbering and un-renaming.')
180  elif opt=='-H': format='HHS'
181  elif opt=='-R': format='rocoto'
182  else:
183  logger.error('Invalid option %s'%(opt,))
184  sys.exit(1)
185  except (getopt.GetoptError,ValueError,TypeError) as e:
186  usage(str(e))
187  sys.exit(1)
188 
189  if unrenumber and format=='tcvitals':
190  logger.info('Switching to "renumbering" format output.')
191  format='renumbering'
192 
193  ########################################################################
194  # DECIDE VITALS LOCATIONS
195  # THIS PARA BLOCK PROTECTS AGAINST NCO REACHING HARD-CODED PATHS:
196  if PARA:
197  set_para_paths()
198  else:
199  # Find tcvitals:
200  if 'COMINARCH' in os.environ:
201  tcvlocs = [ os.environ['COMINARCH'], ]
202  elif 'envir' in os.environ:
203  tcvlocs = [ '/com/arch/'+envir+'/syndat', ]
204  else:
205  fail('ERROR: Both $COMINARCH and $envir are unset in '
206  '$PARAFLAG=NO mode. Cannot find tcvitals.')
207  # Find message files
208  if 'mesagdir' in os.environ:
209  messagedir = [ os.environ['mesagdir'], ]
210  elif 'envir' in os.environ:
211  messagedir = [ '/com/hur/'+envir+'/inpdata', ]
212  else:
213  fail('ERROR: Both $mesagdir and $envir are unset in '
214  '$PARAFLAG=NO mode. Cannot find tcvitals.')
215  ########################################################################
216 
217  if 'CASE_ROOT' in os.environ and os.environ['CASE_ROOT']=='FORECAST':
218  for d in messagedir:
219  if os.path.isdir(d):
220  inputs.extend([os.path.join(d,'message%d'%(1+x,)) \
221  for x in xrange(5)])
222  break
223 
224  if len(args)<2:
225  print>>sys.stderr,'ERROR: Script requires at least two '\
226  'arguments: stormid and year'
227  sys.exit(1)
228 
229  stormid=str(args[0]).upper()
230  if stormid=='ALL':
231  stormid='00X'
232  stormnum=0
233  else:
234  stormnum=int(stormid[0:2])
235  tcvyears_in=[ int(x) for x in str(args[1]).split()]
236  tcvyears=list()
237  xset=set()
238 
239  def check_test_vitals(vl):
240  """This is a replacement for hwrf.storminfo.name_number_okay for
241  use with TEST storms and internal stormids. It allows through
242  only the storm numbers matching stormnum, regardless of the
243  storm name (usually TEST and UNKNOWN would be dropped)."""
244  logger.info('Keeping only storm number %d in vitals'%(stormnum,))
245  for vital in vl:
246  if vital.stnum==stormnum:
247  yield vital
248  elif getattr(vital,'old_stnum','XX')==stormnum:
249  yield vital
250 
251  for tcvyear in tcvyears_in:
252  if tcvyear not in xset:
253  xset.add(tcvyear)
254  tcvyears.append(tcvyear)
255 
256  if len(args)>2 and args[2]!='':
257  renumberlog=open(str(sys.argv[3]),'wt')
258  else:
259  renumberlog=None
260 
261  if len(args)>3:
262  for tcvyear in tcvyears:
263  tcvfile=os.path.join(str(args[3]),'syndat_tcvitals.%04d'%(tcvyear,))
264  if not os.path.isdir(tcvfile):
265  logger.error('%s: syndat file does not exist'%(tcvfile,))
266  sys.exit(1)
267  inputs.append(tcvfile)
268  else:
269  for tcvyear in tcvyears:
270  for thatdir in tcvlocs:
271  thatfile=os.path.join(thatdir,'syndat_tcvitals.%04d'%(tcvyear,))
272  if produtil.fileop.isnonempty(thatfile):
273  inputs.append(thatfile)
274  break
275  else:
276  logger.debug('%s: empty or non-existent'%(thatfile,))
277 
278  try:
279  revital=hwrf.revital.Revital(logger=logger)
280  logger.info('List of input files: %s'%( repr(inputs), ))
281  logger.info('Read input files...')
282  revital.readfiles(inputs,raise_all=False)
283  if not renumber:
284  logger.info(
285  'Not renumbering because renumbering is disabled via -n')
286  logger.info('Cleaning up vitals instead.')
287  revital.clean_up_vitals()
288  elif stormnum<50:
289  logger.info('Renumber invests with weak storm threshold %d...'
290  %(threshold,))
291  revital.renumber(threshold=threshold,
292  discard_duplicates=unrenumber)
293  elif stormnum>=90:
294  logger.info('Not renumbering invests when storm of '
295  'interest is 90-99.')
296  logger.info('Cleaning up vitals instead.')
297  revital.clean_up_vitals()
298  else:
299  logger.info('Fake stormid requested. Running limited clean-up.')
300  revital.clean_up_vitals(name_number_checker=check_test_vitals)
301  if rename and stormnum<50:
302  logger.info('Renaming storms.')
303  revital.rename()
304  elif rename:
305  logger.info('Not renaming storms because storm id is >=50')
306 
307  if unrenumber:
308  logger.info('Unrenumbering and unrenaming storms.')
309  revital.swap_numbers()
310  revital.swap_names()
311 
312  logger.info('Reformat vitals...')
313  if format=='rocoto' and stormid=='00X':
314  cycleset=set([ vit.YMDH for vit in revital ])
315  print hwrf.rocoto.cycles_as_entity(cycleset)
316  elif format=='rocoto':
317  # An iterator that iterates over YMDH values for vitals
318  # with the proper stormid:
319  def okcycles(revital):
320  for vit in revital:
321  if vit.stormid3==stormid:
322  yield vit.YMDH
323  cycleset=set([ ymdh for ymdh in okcycles(revital) ])
324  print hwrf.rocoto.cycles_as_entity(cycleset)
325  elif stormid=='00X':
326  revital.print_vitals(sys.stdout,renumberlog=renumberlog,
327  format=format,old=True)
328  else:
329  revital.print_vitals(sys.stdout,renumberlog=renumberlog,
330  stormid=stormid,format=format,old=True)
331  except Exception as e:
332  logger.info(str(e),exc_info=True)
333  logger.critical('ERROR: %s'%(str(e),))
334 
335 if __name__=='__main__': main()
This module provides a set of utility functions to do filesystem operations.
Definition: fileop.py:1
def install_handlers
Installs signal handlers that will raise exceptions.
Definition: sigsafety.py:153
Sets up signal handlers to ensure a clean exit.
Definition: sigsafety.py:1
def cycles_as_entity(cycleset)
Returns a set of Rocoto XML tags to add to an XML file.
Definition: rocoto.py:162
Defines the Revital class which manipulates tcvitals files.
Definition: revital.py:1
def isnonempty(filename)
Returns True if the filename refers to an existent file that is non-empty, and False otherwise...
Definition: fileop.py:333
This module contains utilities for plugging HWRF into the Rocoto workflow manager.
Definition: rocoto.py:1
This class reads one or more tcvitals files and rewrites them as requested.
Definition: revital.py:38