43 import os, sys, re, logging, collections, StringIO, getopt, itertools
44 from os.path
import realpath, normpath, dirname
47 sys.stdout.write(question)
53 x=sys.stdin.readline()
56 elif x.lower()==
'n\n':
58 elif itry>=itrytoohard:
59 sys.stderr.write(
'Giving up after %d failed responses.'%itry)
62 sys.stdout.write(
'Please answer y or n.')
64 def usage(message=None,logger=None):
65 """!Dumps a usage message and exits with status 2.
66 @param message An extra message to send to stderr after the usage message
67 @param logger Ignored."""
68 print>>sys.stderr,
'''
69 Usage: run_hwrf.py [options] [ensids and cycles] 95E case_root [conf]
72 95E -- the storm to run
73 case_root -- FORECAST = real-time mode, HISTORY = retrospective mod
76 -f -- Tells the run_hwrf.py that you already ran it once for this
77 storm, cycle list and experiment. It will use any existing
78 *.xml or *.db file without asking for permission. Critical
80 -w workflow-file.xml -- use this as the output XML file to send
81 into rocotorun (rocotorun's -w option)
82 -d workflow-db.db -- use this as the SQLite3 database file for
83 Rocoto (rocotorun's -d option)
86 -s site-file -- path to a custom-made site file, rather than using
87 one automatically chosen from sites/*.ent. Do not include any
88 shell or XML metacharacters in the name.
91 This script should be run from the rocoto/ subdirectory of the HWRF
92 installation location so it can guess the ush/ and parm/ locations
93 (../ush and ../parm). You can override those guesses by providing
94 the paths in the $USHhwrf and $PARMhwrf environment variables.
97 [ensids] -- one or more ensemble id specification:
99 07-13 - run members 07, 08, 09, 10, 11, 12 and 13
100 00-20 - run members 00, 01, 02, 03, ..., 20
101 You can give multiple specifications, so this:
103 Is the same as giving all four members explicitly:
107 -m SIDS -- One or more storm ID's, as a comma separated list.
108 For example: -m 04E,05E,00L
109 -M BASINS -- One or more basin ID's, as a comma separated list.
110 For example: -m L,E,C
114 -c N -- number of hours between cycles. This ONLY affects cycle
115 specifications after the -c option.
117 [cycles] -- one or more cycle specifications:
118 2014091312-2014091712 - run this range of cycles
119 2014091312 - run this cycle
120 2014 - all cycles from 0Z January 1, 2014 to
121 the end of that year.
122 2014091312-2014091712 2014091800 - run cycles from 2014091312
123 through 2014091712 AND run 2014091800
125 H214:2012 - run whatever cycles H214 ran for this 2012 storm
127 -t -- include cycles even if they are not in the tcvitals. This
128 option is turned on automatically when H214 cycle lists are
131 -n -- disable renumbering of invests to non-invests. This is done
132 automatically when an invest is requested.
134 -W N -- discard invests weaker than N meters per second before
135 renumbering. Default: -W 14 if a non-invest storm is
136 requested, and -W 0 (don't discard) if an invest is requested.
138 Configuration ([conf]):
139 section.option=value -- override conf options on the command line
140 /path/to/file.conf -- additional conf files to parse'''
141 if message
is not None:
142 print>>sys.stderr,str(message).rstrip()+
'\n'
163 if os.environ.get(
'USHhwrf',
''): USHhwrf=os.environ[
'USHhwrf']
164 if os.environ.get(
'PARMhwrf',
''): PARMhwrf=os.environ[
'PARMhwrf']
165 if os.environ.get(
'HOMEhwrf',
''): HOMEhwrf=os.environ[
'HOMEhwrf']
167 if HOMEhwrf
is None and (USHhwrf
is None or PARMhwrf
is None):
168 HOMEhwrf=dirname(os.getcwd())
169 USHguess=os.path.join(HOMEhwrf,
'ush')
170 PARMguess=os.path.join(HOMEhwrf,
'parm')
171 if os.path.isdir(USHguess)
and os.path.isdir(PARMguess):
172 if USHhwrf
is None: USHhwrf=USHguess
173 if PARMhwrf
is None: PARMhwrf=PARMguess
175 if HOMEhwrf
is not None:
176 if USHhwrf
is None: USHhwrf=os.path.join(HOMEhwrf,
'ush')
177 if PARMhwrf
is None: PARMhwrf=os.path.join(HOMEhwrf,
'parm')
180 print>>sys.stderr,
"Cannot guess $USHhwrf. Please set $HOMEhwrf or " \
181 "$USHhwrf in environment."
185 print>>sys.stderr,
"Cannot guess $PARMhwrf. Please set $HOMEhwrf or " \
186 "$PARMhwrf in environment."
190 print>>sys.stderr,
"Cannot guess $HOMEhwrf. Please set $HOMEhwrf " \
191 "in the environment."
194 sys.path.append(USHhwrf)
217 logger=logging.getLogger(
'run_hwrf')
219 epsilon = to_timedelta(5)
220 six_hours = to_timedelta(6*3600)
221 cycling_interval = six_hours
227 parse_tcvitals =
True
244 short_opts =
"c:d:fm:M:n:s:tWw:"
245 long_opts = [
"cycling=",
257 opts, args = getopt.getopt(sys.argv[1:], short_opts, long_opts)
258 except getopt.GetoptError
as err:
260 usage(
'SCRIPT IS ABORTING DUE TO UNRECOGNIZED ARGUMENT')
263 if k
in (
'-c',
'--cycling'):
264 cycling_interval = to_datetime(int(v)*3600)
265 elif k
in (
'-d',
'--database'):
267 elif k
in (
'-f',
'--force'):
269 elif k
in (
'-m',
'--multistorms'):
270 mslist.extend(v.split(
","))
271 elif k
in (
'-M',
'--multibasins'):
272 mblist.extend(v.split(
","))
273 elif k
in (
'-n',
'--renumber'):
275 elif k
in (
'-s',
'--site'):
277 elif k
in (
'-t',
'--tcvitals'):
278 parse_tcvitals =
False
279 elif k
in (
'-W',
'--weak'):
281 elif k
in (
'-w',
'--workflow'):
284 assert False,
"UNHANDLED OPTION"
288 if outxml[-3:]==
'.db':
289 usage(
'When using the -d option, the Rocoto XML filename must '
290 'not end with ".db".')
292 if outdb[-4:]==
'.xml':
293 usage(
'When using the -d option, the database filename must '
294 'not end with ".xml".')
297 if re.match(
'\A\d\d\Z',arg):
298 logger.info(
'ensemble id')
300 enset.add(
'%02d'%int(arg,10))
301 elif re.match(
'\A\d\d-\d\d\Z',arg):
302 logger.info(
'list of ensemble ids')
306 enset.update([
"%02d"%(x+en1)
for x
in range(en2-en1+1) ])
307 elif re.match(
'\A\d{10}\Z',arg):
308 logger.info(
'single date/time')
312 elif re.match(
'\A\d{4}\Z',arg):
315 start=to_datetime(arg+
'01010000')
316 end=to_datetime(arg+
'12312359')
318 while now<end+epsilon:
319 cycleset.add(now.strftime(
'%Y%m%d%H'))
320 now+=cycling_interval
322 elif re.match(
'\A\d{10}-\d{10}\Z',arg):
323 logger.info(
'range of cycles')
325 start=to_datetime(arg[0:10])
326 end=to_datetime(arg[11:])
328 while now<end+epsilon:
329 cycleset.add(now.strftime(
'%Y%m%d%H'))
330 now+=cycling_interval
331 dateargs.append(start)
332 elif re.match(
'\A\d\d[A-Z]\Z',arg.upper()):
333 logger.info(
'storm id')
339 logger.info(
'Disabling renumbering for invest storm '+arg)
342 logger.info(
'Disabling renumbering for test storm '+arg)
345 elif re.match(
'\AH214:\d\d\d\d\Z',arg.upper()):
347 logger.info(
'H214 - use the H214 benchmark cycles')
349 benchmarkset=arg.upper()
354 if benchmarkset
and cycleset:
355 usage(
'SCRIPT IS ABORTING: YOU CANNOT SPECIFY CYCLES AND '
356 'USE A BENCHMARK SET')
358 if enset==set([
'99']):
364 s_opts.append(dateargs[0])
366 pir_vits = runstr(exe(shbackslash(USHhwrf) +
'/hwrf_multistorm_sort.py')
367 [s_opts], logger=logger)
368 mslist.extend([line.split()[1]
for line
in pir_vits.splitlines()])
378 if item
in ms_seen:
continue
380 ms_unique.append(item)
381 ms_basins.append(item[-1:])
383 os.environ[
'MULTISTORM_SIDS'] =
' '.join(mslist)
385 for item
in itertools.chain(ms_basins, mblist):
387 if basin
in mb_seen:
continue
389 mb_unique.append(basin)
391 logger.info(
'BASINS: ' +repr(mblist))
392 os.environ[
'BASINS'] =
' '.join(mblist)
395 print 'firstarg',firstarg
396 print 'argsfirstarg..',args[firstarg:]
398 m=re.match(
'''(?x)(?P<section>[a-zA-Z][a-zA-Z0-9_]*)\.(?P<option>[^=]+)=(?P<value>.*)$''',s)
400 return os.path.abspath(s)
407 if firstarg+2<len(args):
408 confargs=args[(firstarg+2):]
409 more_launch_vars=
' '.join(
410 entity_quote(shbackslash(fullify(str(x))))
415 logger.info(
'MORE_LAUNCH_VARS='+repr(more_launch_vars))
423 logger.info(
'MSLIST: ' +repr(mslist))
426 mslist,args[firstarg:],logger,usage,PARMhwrf=PARMhwrf)
428 logger=logging.getLogger(
'run_hwrf_'+str(fakestid))
430 fakestorm_conf =
hwrf.launcher.launch(infiles,
None,fakestid,moreopts[stids.index(pstid)],case_root,
431 init_dirs=
False,prelaunch=hwrf_expt.prelaunch,
434 for i,storm
in reversed(list(enumerate(stids))):
435 if storm != fakestid:
436 if(weak_invest
is None):
437 if(str(storm)[0]==
'9'):
438 logger.info(
'Invest requested, and no -w given. Not discarding '
442 logger.info(
'Non-Invest requested, and no -w given. Will start '
443 'cycling off of last Invest <14m/s.')
449 init_dirs=
False,prelaunch=hwrf_expt.prelaunch,
450 fakestorm_conf=fakestorm_conf, storm_num=global_storm_num)
451 global_storm_num += 1
454 args[firstarg:],logger,usage,PARMhwrf=PARMhwrf)
456 logger=logging.getLogger(
'run_hwrf_'+str(stid))
458 if(weak_invest
is None):
459 if(str(stid)[0]==
'9'):
460 logger.info(
'Invest requested, and no -w given. Not discarding '
464 logger.info(
'Non-Invest requested, and no -w given. Will start '
465 'cycling off of last Invest <14m/s.')
471 init_dirs=
False,prelaunch=hwrf_expt.prelaunch)
473 logger.info(
'Run sanity checks.')
475 conf.timeless_sanity_check(enset,logger)
476 except Exception
as e:
479 logger.info(
"I think I'm sane.")
482 loghere=conf.getloc(
'jlogfile',
'')
485 loghere=os.path.join(
486 conf.getloc(
'CDSCRUB'),conf.getstr(
'config',
'SUBEXPT'),
488 except KeyError
as ke:
491 print 'Sending jlogfile messages to %s'%(loghere,)
497 def check_test_vitals(vl):
498 """!This is a replacement for hwrf.storminfo.name_number_okay for
499 use with TEST storms and internal stormids. It allows through
500 only the storm numbers matching stormnum, regardless of the
501 storm name (usually TEST and UNKNOWN would be dropped)."""
502 logger.info(
'Keeping only storm number %d in vitals'%(stid,))
504 if vital.stnum.upper()==stid.upper():
508 logger.info(
'Getting list of tcvitals files.')
509 syndatdir=conf.getdir(
'syndat')
510 vitpattern=conf.getstr(
'config',
'vitpattern',
'syndat_tcvitals.%Y')
512 for cycle
in cycleset:
513 when=to_datetime(cycle)
514 vitfile=os.path.join(syndatdir,when.strftime(vitpattern))
517 logger.info(
'List of files to scan: '+(
','.join(fileset)))
518 revit.readfiles(fileset,raise_all=
False)
520 logger.info(
'Renumber invest cycles.')
522 revit.renumber(threshold=int(weak_invest))
526 logger.info(
'Fake stormid requested. Running limited clean-up.')
527 revit.clean_up_vitals(name_number_checker=check_test_vitals)
529 logger.info(
'Not renumbering invest cycles. Will just clean.')
530 revit.clean_up_vitals()
535 tcvset.update([ vit.when.strftime(
'%Y%m%d%H')
for vit
in revit.each(ms_id) ])
537 tcvset.update([ vit.when.strftime(
'%Y%m%d%H')
for vit
in revit.each(stid) ])
538 notok = cycleset - tcvset
539 okset = cycleset - notok
544 logger.debug(
'NOTOK = '+(
','.join(listed) ))
546 listed=list(cycleset)
550 produtil.log.jlogger.info(
551 '%s %s: no cycles to run. Exiting.'
552 %(str(stid),
' '.join(dateargs)))
555 logger.info(
'I will ONLY run these cycles, since they have vitals:'
562 VARS=dict(os.environ)
565 for line
in VARS[
'CYCLE_LIST'].splitlines():
566 logger.info(
'Rocoto cycles: %s'%(line.rstrip(),))
567 cyclelist=list(cycleset)
569 firstcycle=to_datetime(cyclelist[0])
570 cycledesc=firstcycle.strftime(
'%Y%m%d%H')
572 assert(isinstance(benchmarkset,basestring))
573 year=int(benchmarkset[5:])
575 basin1=sid[2].upper()
576 (ibasin2,basin2,basin1,longinfo) = \
578 cycledesc=
'&%s%s%s;'%(basin2,number,year)
579 VARS[
'CYCLE_LIST']=cycledesc
581 fgatstr=conf.getint(
'fgat',
'FGATSTR',-3)
582 fgatend=conf.getint(
'fgat',
'FGATEND',3)
583 fgatinv=conf.getint(
'fgat',
'FGATINV',3)
585 if fgatend > fgatstr:
586 fhrlist=
' '.join([
'%d'%(i+6)
for i
in xrange(fgatstr,fgatend+1,fgatinv) ])
587 initmodel=
' '.join([
'GDAS1' for i
in xrange(fgatstr,fgatend+1,fgatinv) ])
588 initparts=
' '.join([
'3DVAR' for i
in xrange(fgatstr,fgatend+1,fgatinv) ])
589 VARS.update(INIT_FHR=fhrlist)
590 VARS.update(INIT_MODEL=initmodel)
591 VARS.update(INIT_PARTS=initparts)
596 VARS.update(ENSIDS=
' '.join(enlist),
599 VARS.update(ENSIDS=
'99',ENSEMBLE=
'NO')
601 if conf.getbool(
'config',
'run_ensemble_da'):
602 VARS.update(DA_ENSEMBLE=
'YES',ENSDA_ENS_MEMBER=
'99')
603 esize=conf.getint(
'hwrf_da_ens',
'ensda_size',40)
605 ensdalist=
' '.join([
'%03d'%(i+1)
for i
in xrange(esize) ])
606 VARS.update(ENSDA_IDS=ensdalist)
608 VARS.update(DA_ENSEMBLE=
'NO',ENSDA_ENS_MEMBER=
'99',ENSDA_IDS=
'001')
611 VARS.update(MULTISTORM=
'YES',
612 BASINS=
' '.join(mblist),
613 PRIORITY_SIDS=
' '.join(mslist),
614 FAKE_SID=fakestid.upper())
616 VARS.update(MULTISTORM=
'NO')
618 stormlabel=conf.get(
'config',
'stormlabel',
'storm1')
623 return 'YES' if(b)
else 'NO'
625 VARS.update(SID=stid.upper(), stormlabel=str(stormlabel),
626 WHERE_AM_I=conf.get(
'holdvars',
'WHERE_AM_I'),
627 WHICH_JET=conf.get(
'holdvars',
'WHICH_JET',
'none'),
628 MORE_LAUNCH_VARS=more_launch_vars,
631 FETCH_INPUT=yesno(conf.get(
'config',
'input_catalog')==
'hwrfdata'),
632 ARCHIVE_WRFOUT=yesno(conf.getraw(
'archive',
'wrfout',
'')),
633 ARCHIVE_HYCOMAB=yesno(conf.getraw(
'archive',
'hycomab',
''))
636 for (key,val)
in conf.items(
'rocotostr'):
638 for (key,val)
in conf.items(
'rocotobool'):
639 VARS[key]=yesno(conf.getbool(
'rocotobool',key))
643 for k,v
in VARS.iteritems():
644 if not isinstance(v,basestring):
645 logger.error(
'%s: value is not a string. '
646 'It is type %s with value %s'%(
647 str(k),type(v).__name__,repr(v)))
654 rocotoxml=StringIO.StringIO()
657 parser.parse_file(
'hwrf_multistorm_workflow.xml.in')
659 parser.parse_file(
'hwrf_workflow.xml.in')
662 outbase=
'hwrf-%s-%s-%s'%(
663 conf.get(
'config',
'SUBEXPT'),
667 if not outxml: outxml=okpath(outbase+
'.xml')
668 if not outdb: outdb=okpath(outbase+
'.db')
669 havexml=isnonempty(outxml)
673 not ask(
'ALERT! %s: XML file exists. Overwrite (y/n)?'%(outxml,)):
674 logger.error(
'%s: file exists, user does not want to overwrite.'
678 logger.warning(
'%s: overwriting pre-existing XML file.'%(outxml,))
680 havedb=isnonempty(outdb)
684 'ALERT! %s: database for old configuration exists. Use it (y/n)?'
686 logger.warning(
'%s: not deleting database for old configuration.'
688 elif ask(
'%s: Delete database for old configuration (y/n)?'%(outdb,)):
689 logger.warning(
'%s: deleting database for old configuration.'
693 logger.error(
'%s: database exists, user does not want to delete '
694 'or use it. Aborting.')
697 with open(outxml,
'wt')
as outf:
698 outf.write(rocotoxml.getvalue())
705 if clustername
in (
'tide',
'gyre'):
707 elif clustername
in (
'luna',
'surge'):
708 WHERE_AM_I=
'wcosscray'
710 WHERE_AM_I=clustername
713 '--login',
'-c',
'. %s/hwrf_pre_job.sh.inc ; which ruby ; which rocotorun ; rocotorun --verbose=5 -d %s -w %s'
714 %( shbackslash(USHhwrf), shbackslash(outdb),
715 shbackslash(outxml) ) ] .env(QUIET_PRE_JOB=
'YES',
717 WHERE_AM_I=WHERE_AM_I) \
719 result=run(cmd,logger=logger)
722 produtil.jlogger.critical(
'rocotorun failed')
726 logger.info(
'Making a backup copy of .db file here: %s'%(bakdb,))
728 logger.info(
'Success. Rejoice: hurrah!')
This module provides a set of utility functions to do filesystem operations.
def deliver_file
This moves or copies the file "infile" to "outfile" in a unit operation; outfile will never be seen i...
def expand_basin
Converts basin identifiers.
def set_jlogfile(filename)
Tells the jlogger to log to the specified file instead of the current jlogfile.
Contains setup(), which initializes the produtil package.
def cycles_as_entity(cycleset)
Returns a set of Rocoto XML tags to add to an XML file.
Implements the produtil.run: provides the object tree for representing shell commands.
A shell-like syntax for running serial, MPI and OpenMP programs.
def postmsg(message)
Sends the message to the jlogfile logging stream at level INFO.
def setup(ignore_hup=False, dbnalert_logger=None, jobname=None, cluster=None, send_dbn=None, thread_logger=False, thread_stack=2 **24, kwargs)
Initializes the produtil package.
def parse_launch_args
Parsed arguments to scripts that launch the HWRF system.
def launch
Initializes the directory structure for a new HWRF workflow.
Time manipulation and other numerical routines.
def set_jobname(jobname)
Sets the value that jobname() should return.
Provides information about the cluster on which this job is running.
def norm_expand_path
Normalizes path and expand home directories.
def sanity_check_failed(logger, ex)
Logs information about a failure of a sanity check routine.
Creates the initial HWRF directory structure, loads information into each job.
Takes input files or other data, and replaces certain strings with variables or functions.
Provides information about the batch system.
This module contains utilities for plugging HWRF into the Rocoto workflow manager.
def name()
Synonym for here.name.
def multistorm_parse_args
ATParser is a text parser that replaces strings with variables and function output.
This class reads one or more tcvitals files and rewrites them as requested.