#!/usr/bin/env python """!Creates the initial HWRF directory structure, loads information into each job. This module is used to create the initial HWRF conf file in the first HWRF job via the hwrf.launcher.launch(). The hwrf.launcher.load() then reloads that configuration. The launch() function does more than just create the conf file though. It parses the tcvitals, creates several initial files and directories and runs a sanity check on the whole setup. The HWRFLauncher class is used in place of an hwrf.config.HWRFConfig throughout the HWRF system. It can be used as a drop-in replacement for an hwrf.config.HWRFConfig, but has additional features needed to support sanity checks, initial creation of the HWRF system and tcvitals generation.""" ##@var __all__ # All symbols exported by "from hwrf.launcher import *" __all__=['load','launch','HWRFLauncher','parse_launch_args','multistorm_parse_args'] import os, re, sys, collections, random import produtil.fileop, produtil.run, produtil.log import hwrf.revital, hwrf.storminfo, hwrf.numerics, hwrf.config, hwrf.input from random import Random from produtil.fileop import isnonempty from produtil.run import run, exe from produtil.log import jlogger from hwrf.numerics import to_datetime_rel, to_datetime from hwrf.config import HWRFConfig from hwrf.exceptions import HWRFDirInsane,HWRFStormInsane,HWRFCycleInsane, \ HWRFVariableInsane,HWRFInputInsane,HWRFScriptInsane,HWRFExecutableInsane,\ HWRFFixInsane,HWRFArchiveInsane,HWRFConfigInsane def multistorm_parse_args(msids, args, logger, usage, PARMhwrf=None, wrapper=False): """This is the multistorm argument parser. It is really just a wrapper around parse_launch_args(). The last Element of the returned list is the launch args for the Fake storm. From the original arguments, returns a new list of launch args for all the storms in a multistorm run. The SID and optional config.startfile from the original sys.argv[1:] list are replaced with a storm id and a config.startfile (if present) from the MULTISTORM_SIDS. The following multistorm conf options are also added to each storm. config.fakestormid=, config.multistorm_sids=,config.multistorm_priority_sid=, config.multistorm_sids=, General structure of the returned list. [[storm1, arg1, ..argN], ..[stormN, arg1, ..argN], [storm00, arg1, ..argN]] INPUT: args -- a copy of the initial command line args, excluding sys.argv[0] RETURNS: case_root,parm,infiles,stids,fake_stid,multistorm_priority_sid,moreopts[] """ # See if the optional config.startfile argument is present and get its index. # startfile_idx is a list of indexes in the args_multistorm list that have # a 'config.startfile' substring. There should only be one or none. # if there are none, then startfile_idx = [], an empty list. startfile_idx = [args.index(arg) for arg in args if 'config.startfile' in arg] if len(startfile_idx) > 1: logger.error('Exiting, More than 1 config.startfile= parameter in the argument list.') sys.exit(2) # MULTISTORM Requirement-The fakestorm will be defined as "00L". fake_stid = '00L' assert(msids is not None) # Best guess at priority storm id if fake_stid != msids[0]: multistorm_priority_sid = msids[0] elif len(multistorm_sids) > 1: multistorm_priority_sid = msids[1] else: #Else, running multistorm with no storm, only the fake storm. multistorm_priority_sid = msids[0] if fake_stid in msids: msids.remove(fake_stid) multistorm_all_sids = list(msids) multistorm_all_sids.append(fake_stid) args.append('config.fakestormid=' + fake_stid) args.append('config.multistorm_priority_sid=' + multistorm_priority_sid) args.append('config.multistorm_sids=' + ' '.join(msids)) logger.info('Setting up hwrf to run as a multi storm with sids: %s' %(msids)) logger.info('HWRF multistorm: The priority sid is: %s'%(multistorm_priority_sid)) logger.info('HWRF multistorm: The multistorm fake storm id is: %s' %(fake_stid)) # Setup arguments for each storm, as if this script was called individually for each storm. # Update the storm id and startfile arguments for each storm. # [[storm1, arg1, ..argN], ..[stormN, arg1, ..argN], [storm00, arg1, ..argN]] multistorms = [] stids = [] moreopts = [] # Used to build the start files for a multistorm when called from the wrappers. # ie. if "00L." passed in, it is replace in the startfile name in the loop below # for each storm. sid_passedin = args[0] for i, stormid in enumerate(multistorm_all_sids): multistorms.append(args[:]) multistorms[i][0] = stormid if startfile_idx: if sid_passedin in multistorms[i][startfile_idx[0]]: multistorms[i][startfile_idx[0]]= \ args[startfile_idx[0]].replace(sid_passedin,stormid) else: multistorms[i][startfile_idx[0]]= args[startfile_idx[0]] + str(stormid) for i, storm_args in enumerate(multistorms): (case_root,parm,infiles,stid,moreopt) = \ parse_launch_args(storm_args,logger,usage,PARMhwrf) stids.append(stid) moreopts.append(moreopt) for confbn in [ 'hwrf_3km.conf', 'hwrf_multistorm.conf' ]: confy= os.path.join(parm, confbn) if not os.path.exists(confy): logger.error(confy+': conf file does not exist.') sys.exit(2) elif not os.path.isfile(confy): logger.error(confy+': conf file is not a regular file.') sys.exit(2) elif not produtil.fileop.isnonempty(confy): logger.warning( confy+': conf file is empty. Will continue anyway.') logger.info('Conf input: '+repr(confy)) infiles.append(confy) return (case_root,parm,infiles,stids,fake_stid,multistorm_priority_sid,moreopts) def multistorm_priority(args, basins, logger, usage, PARMhwrf=None, prelaunch=None): storms = list() strcycle=args[0] cyc=hwrf.numerics.to_datetime(strcycle) YMDH=cyc.strftime('%Y%m%d%H') (case_root,parm,infiles,stid,moreopt) = \ parse_launch_args(args[1:],logger,usage,PARMhwrf) conf = launch(infiles,cyc,stid,moreopt,case_root, init_dirs=False,prelaunch=prelaunch, fakestorm=True) syndatdir=conf.getdir('syndat') vitpattern=conf.getstr('config','vitpattern','syndat_tcvitals.%Y') vitfile=os.path.join(syndatdir,cyc.strftime(vitpattern)) multistorm=conf.getbool('config','run_multistorm',False) #ADDED BY THIAGO TO DETERMINE IF "run_multistorm=true". rv=hwrf.revital.Revital(logger=logger) rv.readfiles(vitfile, raise_all=False) rv.delete_invest_duplicates() rv.clean_up_vitals() rv.discard_except(lambda v: v.YMDH==YMDH) rv.discard_except(lambda v: v.basin1 in basins) if multistorm: rv.discard_except(lambda v: v.basin1!='E' or (v.basin1=='E' and v.lon>=-140)) #ADDED BY THIAGO: HRD's new rule for East-pac storms only. rv.clean_up_vitals() rv.sort_by_function(rv.hrd_multistorm_sorter) for v in rv: sid = v.as_tcvitals().split()[1] storms.append(sid) if len(storms) == 0: logger.info('No storms for cycle: '+cyc.strftime('%Y%m%d%H')) produtil.fileop.touch(os.path.join(conf.getdir('com'), 'no_storms.txt')) return(storms) def parse_launch_args(args,logger,usage,PARMhwrf=None): """!Parsed arguments to scripts that launch the HWRF system. This is the argument parser for the exhwrf_launch.py and hwrf_driver.py scripts. It parses the storm ID and later arguments (in args). Earlier arguments are parsed by the scripts themselves. If something goes wrong, this function calls sys.exit(1) or sys.exit(2). The arguments depend on if PARMhwrf=None or not. @code{.py} If PARMhwrf is None: StormID CASE_ROOT /path/to/parm [options] Otherwise: StormID CASE_ROOT [options] @endcode * StormID --- three character storm identifier (ie.: 12L for Katrina) * CASE_ROOT -- HISTORY or FORECAST * /path/to/parm - path to the parm directory, which contains the default conf files. Options: * section.variable=value --- set this value in this section, no matter what * /path/to/file.conf --- read this conf file after the default conf files. Later conf files override earlier ones. The conf files read in are: * parm/hwrf_input.conf * parm/hwrf.conf * parm/hwrf_holdvars.conf * parm/hwrf_basic.conf * parm/system.conf @param args the script arguments, after script-specific ones are removed @param logger a logging.Logger for log messages @param usage a function called to provide a usage message @param PARMhwrf the directory with *.conf files""" if len(args)<2 or ( PARMhwrf is None and len(args)<3): usage(logger=logger) sys.exit(2) # Get the storm ID: stid=args[0].upper() if not re.match('^[0-9][0-9][ABCELPQSW]$',stid): logger.error('%s: invalid storm id. Must be a three character ' 'storm ID such as 90L or 13W'%(stid,)) sys.exit(2) logger.info('Running Storm ID is '+repr(stid)) # Get the case root (real-time vs. retrospective): case_root=args[1].upper() if case_root=='HISTORY': real_time=False elif case_root=='FORECAST': real_time=True else: logger.error('%s: invalid case root. Must be HISTORY for ' 'retrospective runs or FORECAST for real-time runs.' %(case_root,)) sys.exit(2) logger.info('Case root is '+repr(case_root)) # Find the parm directory if PARMhwrf is None: parm=args[2] if not os.path.exists(parm): logger.error(parm+': parm directory does not exist') sys.exit(2) elif not os.path.isdir(parm): logger.error(parm+': parm directory is not a directory') sys.exit(2) logger.info('Scan %d optional arguments.'%(len(args)-3)) args=args[3:] else: parm=PARMhwrf logger.info('Scan %d optional arguments.'%(len(args)-1)) args=args[2:] parm=os.path.realpath(parm) # Standard conf files: infiles=[ os.path.join(parm,'hmon_input.conf'), os.path.join(parm,'hmon_2.conf'), os.path.join(parm,'hmon_holdvars.conf'), os.path.join(parm,'hmon_basic.conf'), os.path.join(parm,'system.conf') ] # Now look for any option and conf file arguments: bad=False moreopt=collections.defaultdict(dict) for iarg in range(len(args)): logger.info(args[iarg]) m=re.match('''(?x) (?P
[a-zA-Z][a-zA-Z0-9_]*) \.(?P