"""!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 not msids: msids=list() msids=[fake_stid] if fake_stid != msids[0]: multistorm_priority_sid = msids[0] elif len(msids) > 1: multistorm_priority_sid = msids[1] else: #Else, running multistorm with no storm, only the fake storm. multistorm_priority_sid = msids[0] # THIS IS Required: multistorm_all_sids is list of ALL storm ids, which # means it includes the fakestorm. The fakestorm sid MUST be appended # at the end of multistorm_all_sids list. The call in # exhwrf_launch.py:main().fakestorm_conf=hwrf.launcher.launch( # ... moreopts[-1]...) relies on the fake storm being the last in this list. # Ultimately this allows for the creation of a start file of the fakestorm, # in addition to all the realstorms. # This just makes certain the fake storm is at the end of the list. # Also, OK if msids has only the fakestorm in its list. if fake_stid in msids: msids.remove(fake_stid) msids.append(fake_stid) multistorm_all_sids = list(msids) else: 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) # The code block below inserts standard hwrf_multistorm conf files to # the existing list of ordered hwrf conf files and ensures the required # multistorm order of conf files. # # Since multistorm now supports 2km and 3km, we must insert the standard # hwrf_multistorm conf file in to the proper position, not necessarily append. # Specifically, when passing in the additonal 3km conf files when running 3km. # # Required ORDER of conf files for multistorm 2km (default) and 3km (override). # Running multistorm 2km: hwrf_multistorm.conf # Running multistorm 3km: hwrf_multistorm.conf hwrf_3km.conf hwrf_multistorm_3km.conf # # SE Note: # Appending conf files in the logic here results in multistorm conf files being # added after all standard hwrf conf files listed in the parse_launch_args function # AND any additional conf files that have been passed in from the command (such # as when adding additonal conf files to run multistorm 3km, or any user specified # conf files in which they wish to override standard conf file values.) # Determine if we are running multistorm 3km by looking for # hwrf_multistorm_3km.conf in the list of infiles. idx_3km_conf=None idx_system_conf=None for i, storm_args in enumerate(multistorms): (case_root,parm,infiles,stid,moreopt) = \ parse_launch_args(storm_args,logger,usage,PARMhwrf) if any('hwrf_multistorm_3km.conf' in s for s in infiles): for idx,str in enumerate(infiles): if 'hwrf_3km.conf' in str: idx_3km_conf=idx for idx,str in enumerate(infiles): if 'system.conf' in str: idx_system_conf=idx stids.append(stid) moreopts.append(moreopt) for confbn in [ '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)) # Inserts hwrf_multistorm.conf in front of hwrf_3km.conf. # elif inserts hwrf_multstorm.conf after system.conf # Increments the index idx_ counters in case more conf files # are ever added to this list, other than just hwrf_multistorm.conf if idx_3km_conf: infiles.insert(idx_3km_conf,confy) idx_3km_conf += 1 elif idx_system_conf: infiles.insert(idx_system_conf+1,confy) idx_system_conf += 1 else: infiles.append(confy) logger.info('MULTISTORM Conf input ORDER:') for conffile in infiles: logger.info('Conf input: '+repr(conffile)) return (case_root,parm,infiles,stids,fake_stid,multistorm_priority_sid,moreopts) def multistorm_priority(args, basins, logger, usage, PARMhwrf=None, prelaunch=None,renumber=True): 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) print('INFILES: ', infiles) 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) if renumber: rv.renumber() 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) # ADDED BY THIAGO: HRD's new rule for East-pac storms only. # EDIT - GJA - 08/13/2017: Western threshold for EPAC storms is -135 # and Eastern threshold for LANT storms is -25 # Temp fix so relocation does not fail if multistorm: rv.discard_except(lambda v: v.basin1!='E' or (v.basin1=='E' and v.lon>=-140)) 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,'hwrf_input.conf'), os.path.join(parm,'hwrf.conf'), os.path.join(parm,'hwrf_holdvars.conf'), os.path.join(parm,'hwrf_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