#! /usr/bin/env python ##@namespace run_hwrf # @brief A wrapper around the Rocoto workflow system that knows how to run HWRF in Rocoto # # @anchor run_hwrf_main # This is a Python program, run_hwrf.py, that users run to launch and maintain an # HWRF workflow for one storm, ensemble or basin-scale forecast. # # @code{.sh} # run_hwrf.py [options] [ensids and cycles] 95E case_root [conf] # @endcode # # Arguments: # * 95E --- stormid # * case_root --- HISTORY for retrospective runs or FORECAST for real-time # # Options: # * -f --- Force a run even fiif the *.xml and *.db file already exist # * -w workflow.xml --- specify the Rocoto XML file # * -d workflow.db --- specify the Rocoto database file # * -s site-file.ent --- Specify the site file in the sites/ subdirectory # # Ensids and Cycles: # * 03 --- an ensemble member to run, if the GEFS ensemble is used # * 03-10 --- several ensemble members to run, if the GEFS ensemble is used # * 2014081412 --- one cycle to run # * 2014081400-2014081618 --- a range of cycles to run # * 2014 --- run all cycles for this storm in this year # * H214:2014 --- run whatever cycles H214 ran for this storm # * -t --- include cycles even if they are not in the tcvitals # * -n --- disable renumbering of invests into non-invests # * -W N --- discard invests weaker than N m/s before renumbering # # Conf opitons: # * ../parm/hwrf_3km.conf --- read this configuration file # * config.run_ocean=yes --- specify the value of one configuration option # * prelaunch.basin_overrides=no --- Disasble per-basin configuration overrides # * prelaunch.rsmc_overrides=no --- Disable per-center (RSMC) configuration overrides ##@cond RUN_HWRF_PY import os, sys, re, logging, collections, io, getopt, itertools from os.path import realpath, normpath, dirname def ask(question): sys.stdout.write(question) itry=0 itrytoohard=100 go=True while go: itry+=1 x=sys.stdin.readline() if x.lower()=='y\n': return True elif x.lower()=='n\n': return False elif itry>=itrytoohard: sys.stderr.write('Giving up after %d failed responses.'%itry) sys.exit(2) else: sys.stdout.write('Please answer y or n.') def usage(message=None,logger=None): """!Dumps a usage message and exits with status 2. @param message An extra message to send to stderr after the usage message @param logger Ignored.""" print(''' Usage: run_hwrf.py [options] [ensids and cycles] 95E case_root [conf] Mandatory arguments: 95E -- the storm to run case_root -- FORECAST = real-time mode, HISTORY = retrospective mod Workflow options: -f -- Tells the run_hwrf.py that you already ran it once for this storm, cycle list and experiment. It will use any existing *.xml or *.db file without asking for permission. Critical in crontabs. -w workflow-file.xml -- use this as the output XML file to send into rocotorun (rocotorun's -w option) -d workflow-db.db -- use this as the SQLite3 database file for Rocoto (rocotorun's -d option) Specifying a site: -s site-file -- path to a custom-made site file, rather than using one automatically chosen from sites/*.ent. Do not include any shell or XML metacharacters in the name. PATHS: This script should be run from the rocoto/ subdirectory of the HWRF installation location so it can guess the ush/ and parm/ locations (../ush and ../parm). You can override those guesses by providing the paths in the $USHhwrf and $PARMhwrf environment variables. ENSEMBLE OPTIONS: [ensids] -- one or more ensemble id specification: 03 - run member 03 07-13 - run members 07, 08, 09, 10, 11, 12 and 13 00-20 - run members 00, 01, 02, 03, ..., 20 You can give multiple specifications, so this: 03-05 06 Is the same as giving all four members explicitly: 03 04 05 06 MULTISTORM OPTIONS: -m SIDS -- One or more storm ID's, as a comma separated list. For example: -m 04E,05E,00L -M BASINS -- One or more basin ID's, as a comma separated list. For example: -m L,E,C SPECIFYING CYCLES: -c N -- number of hours between cycles. This ONLY affects cycle specifications after the -c option. [cycles] -- one or more cycle specifications: 2014091312-2014091712 - run this range of cycles 2014091312 - run this cycle 2014 - all cycles from 0Z January 1, 2014 to the end of that year. 2014091312-2014091712 2014091800 - run cycles from 2014091312 through 2014091712 AND run 2014091800 H214:2012 - run whatever cycles H214 ran for this 2012 storm -t -- include cycles even if they are not in the tcvitals. This option is turned on automatically when H214 cycle lists are requested. -n -- disable renumbering of invests to non-invests. This is done automatically when an invest is requested. -W N -- discard invests weaker than N meters per second before renumbering. Default: -W 14 if a non-invest storm is requested, and -W 0 (don't discard) if an invest is requested. Configuration ([conf]): section.option=value -- override conf options on the command line /path/to/file.conf -- additional conf files to parse''', file=sys.stderr) if message is not None: print(str(message).rstrip()+'\n', file=sys.stderr) sys.exit(2) ######################################################################## # Try to guess $USHhwrf and $PARMhwrf. The $HOMEhwrf or current # working directory are used if $USHhwrf and $PARMhwrf are not set in # the environment. We also add the $USHhwrf to the Python library # path. ##@var USHhwrf # The ush/ subdirectory of the HWRF installation directory USHhwrf=None ##@var HOMEhwrf # The HWRF installation directory HOMEhwrf=None ##@var PARMhwrf # The parameter directory PARMhwrf=None if os.environ.get('USHhwrf',''): USHhwrf=os.environ['USHhwrf'] if os.environ.get('PARMhwrf',''): PARMhwrf=os.environ['PARMhwrf'] if os.environ.get('HOMEhwrf',''): HOMEhwrf=os.environ['HOMEhwrf'] if HOMEhwrf is None and (USHhwrf is None or PARMhwrf is None): HOMEhwrf=dirname(os.getcwd()) USHguess=os.path.join(HOMEhwrf,'ush') PARMguess=os.path.join(HOMEhwrf,'parm') if os.path.isdir(USHguess) and os.path.isdir(PARMguess): if USHhwrf is None: USHhwrf=USHguess if PARMhwrf is None: PARMhwrf=PARMguess if HOMEhwrf is not None: if USHhwrf is None: USHhwrf=os.path.join(HOMEhwrf,'ush') if PARMhwrf is None: PARMhwrf=os.path.join(HOMEhwrf,'parm') if USHhwrf is None: print("Cannot guess $USHhwrf. Please set $HOMEhwrf or " \ "$USHhwrf in environment.", file=sys.stderr) sys.exit(2) if PARMhwrf is None: print("Cannot guess $PARMhwrf. Please set $HOMEhwrf or " \ "$PARMhwrf in environment.", file=sys.stderr) sys.exit(2) if HOMEhwrf is None: print("Cannot guess $HOMEhwrf. Please set $HOMEhwrf " \ "in the environment.", file=sys.stderr) sys.exit(2) sys.path.append(USHhwrf) ######################################################################## # Load and set up the produtil package. import produtil.setup, produtil.atparse, produtil.run, produtil.prog, \ produtil.fileop, produtil.batchsystem, produtil.cluster import hwrf.launcher, hwrf.numerics, hwrf.rocoto import hwrf_expt from hwrf.numerics import to_datetime, to_timedelta from hwrf.rocoto import entity_quote from produtil.fileop import remove_file, isnonempty from produtil.run import run, exe, runstr from produtil.prog import shbackslash produtil.batchsystem.set_jobname('run_hwrf') produtil.setup.setup(send_dbn=False) ######################################################################## # Global variables and constants logger=logging.getLogger('run_hwrf') epsilon = to_timedelta(5) # five seconds six_hours = to_timedelta(6*3600) cycling_interval = six_hours cycleset = set() enset = set() mslist = list() mblist = list() benchmarkset = None parse_tcvitals = True renumber = True force = False site_file = '' outxml = '' outdb = '' dateargs = list() iarg = 0 firstarg = 0 weak_invest = None multistorm = False inputonly = False storms_opt = '' basins_opt = '' renumber_opt = '' def okpath(path): return produtil.fileop.norm_expand_path(path,fullnorm=True) ######################################################################## # Parse the options and arguments. short_opts = "c:d:fim:M:ns:tW:w:" long_opts = ["cycling=", "database=", "force", "inputonly", "multistorms=", "multibasins=", "renumber=", "site=", "tcvitals", "weak", "workflow=" ] try: opts, args = getopt.getopt(sys.argv[1:], short_opts, long_opts) except getopt.GetoptError as err: print(str(err)) usage('SCRIPT IS ABORTING DUE TO UNRECOGNIZED ARGUMENT') for k, v in opts: if k in ('-c', '--cycling'): cycling_interval = to_datetime(int(v)*3600) elif k in ('-d', '--database'): outdb = v elif k in ('-f', '--force'): force = True elif k in ('-i', '--inputonly'): inputonly = True elif k in ('-m', '--multistorms'): mslist.extend(v.split(",")) multistorm = True storms_opt=''.join(['-m ', ','.join(mslist)]) elif k in ('-M', '--multibasins'): mblist.extend(v.split(",")) multistorm = True basins_opt=''.join(['-M ', ','.join(mblist)]) elif k in ('-n', '--renumber'): renumber = False renumber_opt='-n' elif k in ('-s', '--site'): site_file = str(v) elif k in ('-t', '--tcvitals'): parse_tcvitals = False elif k in ('-W', '--weak'): weak_invest = int(v) elif k in ('-w', '--workflow'): outxml = v else: assert False, "UNHANDLED OPTION" # Make sure the workflow isn't the database if outxml[-3:]=='.db': usage('When using the -d option, the Rocoto XML filename must ' 'not end with ".db".') # Make sure the database isn't the workflow if outdb[-4:]=='.xml': usage('When using the -d option, the database filename must ' 'not end with ".xml".') for arg in args: if re.match('\A\d\d\Z',arg): logger.info('ensemble id') # Single ensemble ID enset.add('%02d'%int(arg,10)) elif re.match('\A\d\d-\d\d\Z',arg): logger.info('list of ensemble ids') # List of ensemble IDs en1=int(arg[0:2],10) en2=int(arg[3:],10) enset.update([ "%02d"%(x+en1) for x in range(en2-en1+1) ]) elif re.match('\A\d{10}\Z',arg): logger.info('single date/time') # Single date/time cycleset.add(arg) dateargs.append(arg) elif re.match('\A\d{4}\Z',arg): logger.info('year') # Year start=to_datetime(arg+'01010000') end=to_datetime(arg+'12312359') now=start while now[a-zA-Z][a-zA-Z0-9_]*)\.(?P