## # This software was developed and / or modified by Raytheon Company, # pursuant to Contract DG133W-05-CQ-1067 with the US Government. # # U.S. EXPORT CONTROLLED TECHNICAL DATA # This software product contains export-restricted data whose # export/transfer/disclosure is restricted by U.S. law. Dissemination # to non-U.S. persons whether in the United States or abroad requires # an export license or other authorization. # # Contractor Name: Raytheon Company # Contractor Address: 6825 Pine Street, Suite 340 # Mail Stop B8 # Omaha, NE 68106 # 402.291.0100 # # See the AWIPS II Master Rights File ("Master Rights File.pdf") for # further licensing information. ## # ---------------------------------------------------------------------------- # This software is in the public domain, furnished "as is", without technical # support, and with no warranty, express or implied, as to its usefulness for # any purpose. # # NDFD_QPF_Checks (was SnowAmtQPFPoPWxCheck) # # Author: Jay Smith, WFO Fairbanks, jay.smith@noaa.gov, 907-458-3721 # SnowAmtQPFPoPWxCheck Incarnation # Version: 1.0.0, 09/14/2006 - Initial version # 1.0.1, 10/12/2006 - Added PoP/QPF check at request of DSPAC # 1.0.2, 10/18/2006 - Changed PoP/QPF check to treat the PoP as # floating. Instead of checking each individual PoP grid # against its corresponding QPF grid, the max of all the # PoP grids overlapping a QPF grid will be checked. # 1.1.0, 01/25/2007 - Added options to choose which checks to run. # Reorganized code so that each check is its own method. # Added a check for QPF and Wx. Added highlighting for the # created temporary grids. # 1.1.1, 02/01/2007 - Changed the SnowAmt/Wx check to return # consistent results for SnowAmt > 0 and Wx grid containing # S, SW, or IP regardless of whether the frozen precip is # mixed with freezing and/or liquid precip. # 1.2.0, 02/13/2007 - Added a configuration option to provide a CWA # edit area to run the procedure over. A bad edit area or no # edit area will result in running over the whole domain. # Modified the SnowAmt/Wx and QPF/Wx checks to handle two # cases. Case 1: The SnowAmt/QPF grid is 6-hr long and starts # at 00, 06, 12, or 18 UTC. Then only one of the corresponding # Wx grids has to meet the consistency rule. Case 2: The # SnowAmt/QPF grid does not meet the case 1 definition. Then # all of the corresponding Wx grids must meet the consistency # rule. # NDFD_QPF_Checks Incarnation # Version: 1.0.0, 04/04/2007 - The program now requires the presence of the # following 3 grids: QPF6hr, SnowAmt6hr, and PoP12hr. The 2 # 6-hr grids are expected to start at 00, 06, 12, or 18 UTC # and be exactly 6 hours long. The PoP12hr grids are expected # to start at 00 or 12 UTC and be exactly 12 hours long. # The procedure still needs the PoP and Wx grids. With these # grid changes, the procedure can now finally perform the last # of the required NDFD checks. This also means there is no # longer the need for two check cases in the two "Wx" checks. # Now, Wx is consistent if any of its grids have the appropriate # type. The procedure can now be run in a "quiet" mode if # called from another procedure. Any messages generated in # "quiet" mode will be of "R" severity so no pop-up messages # are generated but the information is still available from the # GFE status bar area. All error messages are templated in a # dictionary in a separate method. This allows me to put all # the triple-quoted strings, which ignore indentation, in one # location. The code for checking for locked grids was also # templated in its own method, which chopped about 15 lines of # code off the front of each check method. There were a couple # of places where I was applying the "tolerance" values # incorrectly, which have been fixed. I dropped the # "Inconsistent" label from all temporary grid names. I was # making those grid names so long, they didn't actually fit # in the spatial editor window when the grid manager was on # the left. Temporary grid names now are just a concatenation # of the two grids used in the check. # Version: 1.1.0, The logic for the handling of inconsistencies in the # SnowAmt6hr/Wx, QPF6hr/Wx, and PoP12hr/QPF6hr checks could # result in false positive inconsistencies. This is because # I was checking for inconsistencies on each of these grids # individually when I needed to be checking the cumulative # inconsistencies of these grids. With the new logic, if any # of these checks has inconsistencies, the resulting temporary # grid will have the same time range as the "controlling" grid, # which is the first grid listed in each check name. Also, I # have enforced the precision of the SnowAmt6hr and QPF6hr # grids in the methods where they are used. SmartScript has a # method called "around" that does this. I ran into an issue # when this procedure was called from NDFD_QPF_Suite. I am # unable to figure out how to uncache the QPF6hr, SnowAmt6hr, # and PoP12hr grids after they are generated by the # Collaborate_PoP_SnowAmt_QPF procedure. For the QPF6hr and # SnowAmt6hr grids, this means getGrids is getting them from # the cache, which means these float parameters have machine # precision. This is utterly unacceptable. I have to have # ifpServer precision. Now, I think I've ensured this. # 1.1.1, 05/02/2007 - There have been instances of unexpected procedure # performance if the NDFD QPF parameters are not visible in the # grid manager when the procedure is run. The procedure will # now require the existence of a weather element group which # contains just the NDFP QPF parameters. # 1.2.0, 05/03/2007 - Upon further review, the unexpected procedure # performance arises when some of the NDFD QPF parameters are # not present in the grid manager. However, I do not need to # load a weather element group to make the parameters present. # I can use the loadParm command on each element instead. Given # this, the "weGroup" configuration has been removed. Also, # some people believe the lock checking is overly stringent. # To some extent, I agree. For the purposes of this procedure, # other GFE users can have any of the NDFD QPF parmeteres # locked. The user running the procedure, however, cannot have # any of the parameters locked; i.e., that person must save # those elements before running the procedure. # The procedure performs the following checks: # 1. If SnowAmt6hr present and >= 0.5 inches, then corresponding QPF6hr grids # must be >= 0.01 inches. # 2. If SnowAmt6hr >= 0.1 inches then at least one of the corresponding Wx # grids must have S, SW, or IP. # 3. If QPF6hr > 0.0, then at least one of the corresponding PoP grids must # be > 0 # 4. If QPF6hr > 0.0 then at least one of the corresponding Wx grids must have # R, RW, S, SW, RS, IP, L, ZR, ZL. # 5. If PoP12hr >= 50%, then at least one of the corresponding QPF6hr grids # must be >= 0.0. # For all of the checks above, if the initial threshold is not exceeded, then # the two grids are consistent by definition. In other words: # 1. If SnowAmt6hr < 0.5, then SnowAmt6hr and QPF6hr are always consistent. # 2. If SnowAmt6hr < 0.1, then SnowAmt6hr and Wx are always consistent. # 3. If QPF6hr = 0.0, then QPF6hr and PoP are always consistent. # 4. If QPF6hr = 0.0, then QPF6hr and Wx are always consistent. # 5. If PoP12hr < 50%, then PoP12hr and QPF6hr are always consistent. # For the Wx checks above, only the Wx type is considered. # # ****** NOTE NOTE NOTE NOTE ****** # The program checks the PoP12hr, QPF6hr, and SnowAmt6hr grids to make sure # their time constraints are met. For any grid where the time constraint is # violated, those grids are not checked. To reiterate the time constraints: # PoP12hr: starts at either 00 or 12 UTC and is exactly 12 hours duration # QPF6hr: starts at 00, 06, 12, or 18 UTC and is exactly 6 hours duration # SnowAmt6hr: starts at 00, 06, 12 or 18 UTC and is exactly 6 hours duration # ****** NOTE NOTE NOTE NOTE ****** # # If discrepancies are found, then the "bad" grids will be highlighted. # Temporary grids showing where the discrepancies occur will be created and # also highlighted. # # I've written this code such that it's optimized to minimize memory usage # (at least I think I've done that). As a result, it's not particularly # optimized for ifpServer database access. In fact, I retrieve the various # grids from the ifpServer database many times during the procedure's run. # This will have an impact on how fast the procedure runs (it'll run slower # than if I had optimized for ifpServer database access). The choice to favor # memory optimization comes from my belief that there are still "memory leak" # problems in the GFE and that the consequences of those problems will be most # manifest when this procedure is most likely to be run (near the end of the # shift). Funky memory problems are a prime cause of funky application # behavior like application crashes or spontaneous logouts. So, this procedure # basically reads a grid into memory, keeps it as long as it's needed, and # then discards it. # # Finally, this procedure is also intended to provide an example to other # developers of how to write and document code. I have reservations as to how # well I've succeeded at that task. The code is heavily documented, probably # excessively so. Also, it's not as well as organized as it could be. As you # look through the various methods, it should become quickly apparent that # there is a lot of repeated code. I've consciously left some the code this # way in the hopes that it will be easier to understand by more novice # programmers and because the code hasn't quite grown to the point where # updating the repeating code is onerous or overly error-prone. It would be # better to capture the repeating code in separate methods, but keeping track # of the where you are in the code becomes harder the more you have to jump # around from method to method. Anyone who has ever worked with the text # formatters can sympathize with that. As with all things, there are trade- # offs involved. UPDATE: 4/3/2007 - Starting with the first NDFD_QPF_Checks # version, I consolidated quite a bit of the repeating code into separate # methods. So, there's some improvement on that front. # # Acknowledgement: # Many of the Python "tricks" I use in this procedure I learned from # reading/perusing the following book: Python Cookbook, Alex Martelli & # David Ascher, eds., 2002, O'Reilly and Associates # ---------------------------------------------------------------------------- ## # This is an absolute override file, indicating that a higher priority version # of the file will completely replace a lower priority version of the file. ## MenuItems = ["Consistency"] VariableList = [ ('Check_Cleanup', 'Check', 'radio', ['Check', 'Cleanup']), ('Run SnowAmt6hr/QPF6hr Check?', ['Yes'], 'check', ['Yes']), ('Run SnowAmt6hr/Wx Check?', ['Yes'], 'check', ['Yes']), ('Run QPF6hr/PoP Check?', ['Yes'], 'check', ['Yes']), ('Run QPF6hr/Wx Check?', ['Yes'], 'check', ['Yes']), ('Run PoP12hr/QPF6hr Check?', ['Yes'], 'check', ['Yes']), ('If "Cleanup" is selected, then only cleanup actions will run.\nNo checks will be made, regardless of the above settings.', '', 'label'), ] #### Config section # Both the QPF and SnowAmt grids have values which are floating point # numbers. This means comparisons must use a tolerance value. In other # words, 0.5 may be represented in machine numbers as 0.49999999999 or # 0.500000000001. By specifying a tolerance value, we account for the # vagaries of machine representation of floating point numbers while # keeping the precision of the comparisons to acceptable levels. Depending # on the comparison being done, the tolerance value will be added to or # subtracted from the comparison value to allow for machine error in the # floating point number representation. # By default in the GFE, QPF precision is to the nearest one-hundredth while # SnowAmt precision is to the nearest tenth. qpfTol = 0.00001 # 1/100,000 tolerance vs 1/100 precision snowAmtTol = 0.0001 # 1/10,000 tolerance vs 1/10 precision # Inconsistent grid highlight color. One size fits all. To turn off # highlighting, set the variable to the empty string, ''. inconGridColor = 'red' # Temporary grid highlight color. One size fits all. To turn off highlighting, # set the variable to the empty string, ''. tempGridColor = 'orange' # Name of CWA edit area to use instead of running the procedure over the # whole domain. Set to the empty string, '', if you want the procedure to # always run over the whole domain. If the procedure has a problem with the # edit area you provide, it will run over the whole domain. You should probably # choose an edit area that is slightly larger than your entire CWA. It's # possible that when mapping your GFE grids to NDFD grids that the NDFD thinks # some GFE grid cells are in your CWA that the GFE does not think are in your # CWA. Using an edit area slightly larger than the CWA, like the ISC_Send_Area # which is the mask used when sending grids to the NDFD, should eliminate the # possibibilty of the NDFD intermittently flagging CWA border "points" as # inconsistent. Note: running the procedure over a subset of the entire GFE # domain does not really provide any performance gains. Given the way the # underlying array data structure works, calculations are almost always made # at every single grid point first and then a mask is applied to limit the # meaningful results to the edit area. For the purposes of this procedure, the # values outside the edit area are set to the appropriate "consistent" result. # The real benefit of this option is it limits the inconsistent results to the # areas the forecaster really cares about, which should lessen the workload of # using this procedure. Marine Offices: Make sure the edit area provided # includes your marine zones. cwaEditArea = 'ISC_Send_Area' #### Config section end import SmartScript from numpy import * class Procedure (SmartScript.SmartScript): def __init__(self, dbss): SmartScript.SmartScript.__init__(self, dbss) def __cleanup(self, timeRange): # Remove any temporary grids created previously. Turn off any # previous highlighting. for element in ( 'SnowAmt6hrQPF6hr', 'SnowAmt6hrWx', 'QPF6hrPoP', 'QPF6hrWx', 'PoP12hrQPF6hr'): try: # From SmartScript self.unloadWE('Fcst', element, 'SFC') except: # A failure is almost certainly no grids to unload. pass # Turn off any highlights. From SmartScript self.highlightGrids( 'Fcst', 'SnowAmt6hr', 'SFC', timeRange, inconGridColor, on=0) self.highlightGrids( 'Fcst', 'QPF6hr', 'SFC', timeRange, inconGridColor, on=0) self.highlightGrids( 'Fcst', 'Wx', 'SFC', timeRange, inconGridColor, on=0) self.highlightGrids( 'Fcst', 'PoP', 'SFC', timeRange, inconGridColor, on=0) self.highlightGrids( 'Fcst', 'PoP12hr', 'SFC', timeRange, inconGridColor, on=0) return def __checkConfigValueTypes(self): # Make sure the values provided in the configuration section are the # correct type. # Later in the code are two methods devoted to creating error # messages. The error messages here could pop up in quite a large # number of different combinations, which makes capturing them in the # later methods very complex. Rather than do that and considering # that these error messages should never appear once the procedure # is correctly set up, I decided to leave them here. There always has # to be an exception. :-) import types message = '' badValues = False if not type(qpfTol) is float: message = '%sThe "qpfTol" variable is not defined as a floating point value. Please contact your IFPS focal point to fix this.\n' % message badValues = True if not type(snowAmtTol) is float: message = '%sThe "snowAmtTol" variable is not defined as a floating point value. Please contact your IFPS focal point to fix this.\n' % message badValues = True if not type(inconGridColor) is str: message = '%sThe "inconGridColor" variable is not defined as a string value. Please contact your IFPS focal point to fix this.\n' % message badValues = True if not type(tempGridColor) is str: message = '%sThe "tempGridColor" variable is not defined as a string value. Please contact your IFPS focal point to fix this.\n' % message badValues = True if not type(cwaEditArea) is str: message = '%sThe "cwaEditArea" variable is not defined as a string value. Please contact your IFPS focal point to fix this.\n' % message badValues = True if badValues: message = '%sYou will not be able to run the procedure until the problem is corrected.' % message # The next two commands are from SmartScript self.statusBarMsg(message, 'U') self.cancel() return def __checkLockedStatus(self, elementList): # There can be a significant difference between the values stored # in memory and the values returned from the database. This is because # when values are saved, the element's precision (as defined in # serverConfig.py/localConfig.py) is enforced. Values in memory do not # have the element's precision enforced; in fact, they have the # machine precision of the underlying data type. # At the beginning of each check method, a call to this method is # made to make sure the grids are saved. A check method will not run # if the grids it's to check are not saved. This method will return # a list of boolean values indicating if the elements are locked by me # and then if the elements are locked by other. # The lockedByMe and lockedByOther methods are from SmartScript lockedByMe = [] lockedByOther = [] for element in elementList: if self.lockedByMe(element, 'SFC'): lockedByMe.append(True) else: lockedByMe.append(False) ## if self.lockedByOther(element, 'SFC'): ## lockedByOther.append(True) ## else: ## lockedByOther.append(False) lockedByOther.append(False) return lockedByMe + lockedByOther def __getMsgSeverity(self, severity): # For calls to self.statusBarMsg where I intended the severity to be # something other than 'R', this method is now called to determine # what the severity should be. This procedure can be called from # another procedure in such a way as to suppress the pop-up type of # status bar messages. This is done by passing in a varDict with a # 'Quiet' key which evaluates to 'True'. For those situations, the # procedure defers to the calling program and turns any non 'R' # severities into 'R' severity. This allows the message to be # communicated still to the GFE session, but only via the 'Status' # line area of the GFE. When run interactively from the GFE, the # severity this procedure assigns to a message will be used. # This method is actually invoked in the call to statusBarMsg in # place of the severity string. As long as the entry for the severity # in statusBarMsg evaluates to a string type, statusBarMsg will be # 'happy'. if self._quiet: return 'R' return severity def __checkTC(self, element, gridTR): # The QPF6hr, SnowAmt6hr, and PoP12hr grids have specific time # constraints that the respective grids must adhere to. In other # words, it's not acceptable to this procedure for the QPF6hr grid, # for example, to be stretched to 12 hours long. This method makes # sure each of the grids exactly conforms to the time constraint # defintion. The method returns True if good, False if bad. If, for # some reason, the method gets called with some other element, the # method will return True. if element == 'QPF6hr' or element == 'SnowAmt6hr': startHourTup = (0, 6, 12, 18) goodDuration = 6 * 3600 elif element == 'PoP12hr': startHourTup = (0, 12) goodDuration = 12 * 3600 else: return True if gridTR.startTime().hour in startHourTup and \ gridTR.duration() == goodDuration: return True return False def _runSnowAmt6hrQPF6hrCheck(self, timeRange): # This method implements the check that if SnowAmt6hr >= 0.5, then # QPF6hr must be >= 0.01. # If there are locks, post urgent messages and return from the method. snowLockMe, qpfLockMe, snowLockOther, qpfLockOther = \ self.__checkLockedStatus(['SnowAmt6hr', 'QPF6hr']) if snowLockMe or qpfLockMe or snowLockOther or qpfLockOther: # Something's locked, create messages. self._makeLockMsgs( snowLockMe, qpfLockMe, snowLockOther, qpfLockOther, 'SnowAmt6hr', 'QPF6hr', 'snowLockMe', 'qpfLockMe', 'snowLockOther', 'qpfLockOther', 'SnowAmt6hr/QPF6hr') return # Make sure there are actually SnowAmt6hr grids in the time range. # The self.getGridInfo command will return an empty list if there # are no grids in the time range. This is more efficient than using # self.getGrids with mode='First' and noDataError=0. # The getGridInfo method is from SmartScript snowAmtInfoList = self.getGridInfo( 'Fcst', 'SnowAmt6hr', 'SFC', timeRange) if snowAmtInfoList == []: message = self._getMsg( 'noGrids', element='SnowAmt6hr', timeRange=timeRange, method='SnowAmt6hr/QPF6hr') # The statusBarMsg method is from SmartScript self.statusBarMsg(message, self.__getMsgSeverity('U')) # I return instead of aborting because the user may have asked for # other tests that do not have missing grid problems. return # One might ask why I don't just return the result of self.getGrids # to a variable and iterate over that. I'm trying to minimize the # memory footprint of the procedure. Reading all the grids into a # variable could be a fairly large memory hit. I believe the construct # below only reads one SnowAmt6hr grid at a time into memory, the one # that's being checked. (But I can't find the reference that supports # my belief.) By using the cache=0 switch on all the self.getGrids # command, I prevent the GFE from saving the grids into memory for me. # (At least, that's what I think the cache=0 switch does. The # SmartScript documentation is a little vague on this point.) # The Python builtin command enumerate loops over an iterable object # and returns a 2-tuple containing the current index of the # iteration and the object at that index. In cases where I need # both the index and the object, I think this construct is more # elegant than: # for i in xrange(len(iterableObject)): # object = iterableObject[i] for snowAmtIndex, snowAmtGrid in enumerate(self.getGrids( 'Fcst', 'SnowAmt6hr', 'SFC', timeRange, mode='List', cache=0)): gridTR = snowAmtInfoList[snowAmtIndex].gridTime() # Check to make sure the grid meets it's time constraints. if not self.__checkTC('SnowAmt6hr', gridTR): message = self._getMsg( 'badTC', element='SnowAmt6hr', timeRange=gridTR) self.statusBarMsg(message, self.__getMsgSeverity('U')) continue # around is from SmartScript snowAmtGrid = around(snowAmtGrid, 1) # The greater_equal method is from Numeric. For the given array # and threshold, a new array of the same dimensions as the input # array is returned. The new array has the value 1 where the # input array was greater than or equal to the threshold and # has the value 0 elsewhere. # The getGridInfo method is from SmartScript halfInchMask = greater_equal(snowAmtGrid, 0.5 - snowAmtTol) qpfInfoList = self.getGridInfo('Fcst', 'QPF6hr', 'SFC', gridTR) # There should always be more QPF6hr grids than SnowAmt6hr grids, # so if qpfInfoList is empty, then there are missing QPF6hr # grids. Otherwise, qpfInfoList will have length 1 because # SnowAmt6hr and QPF6hr have the same time constrain. However, # the QPF6hr grid that overlaps the SnowAmt6hr grid will still # need to be checked to make sure it hasn't been stretched. if qpfInfoList == []: message = self._getMsg( 'noGrids', element='QPF6hr', timeRange=gridTR, method='SnowAmt6hr/QPF6hr') # The statusBarMsg is from SmartScript self.statusBarMsg(message, self.__getMsgSeverity('U')) continue # I don't need the noDataError=0 in the self.getGrids call # because if there were no grids in the gridTR, the previous # if block would have caught that. # The getGrids method is from SmartScript qpfGrid = self.getGrids( 'Fcst', 'QPF6hr', 'SFC', gridTR, mode='First', cache=0) if not self.__checkTC('QPF6hr', gridTR): message = self._getMsg( 'badTC', element='QPF6hr', timeRange=gridTR) # The statusBarMsg method is from SmartScrtipt self.statusBarMsg(message, self.__getMsgSeverity('U')) continue # If we get here, then we have a SnowAmt6hr grid and a QPF6hr # grid which meet their time constraints and are ready to be # compared. # around is from SmartScript qpfGrid = around(qpfGrid, 2) # The less method is from Numeric. It behaves analogously to # the greater_equal method described above using less than for # the comparison. qpfMask = less(qpfGrid, 0.01 - qpfTol) # The following is the "truth" table for the logical # comparison. # SnowAmt6hr >= 0.5, 1; SnowAmt6hr < 0.5, 0 # QPF6hr < 0.01, 1; QPF6hr >= 0.01, 0 # SnowAmt6hr >= 0.5 (1) and QPF6hr < 0.01 (1) = 1 (Bad result) # SnowAmt6hr >= 0.5 (1) and QPF6hr >= 0.01 (0) = 0 (Good result) # SnowAmt6hr < 0.5 (0) and QPF6hr < 0.01 (1) = 0 (Good result) # SnowAmt6hr < 0.5 (0) and QPF6hr >= 0.01 (0) = 0 (Good result) # The logical_and method is from Numeric. A logical and comparison # results in a "True" value if both compared elements are "True". # Otherwise, the result is "False". consistMask = logical_and(halfInchMask, qpfMask) # Now, apply the CWA mask. There's an assumption here that # all offices will use a mask and provide a valid one, which # means this step does something meaningful. If that assumption # does not hold, then the next statement doesn't actually # change anything, even though each and every grid point has a # comparison check made. # where is from Numeric. The first argument is a mask. # The second argument is/are the value/values to use at the # array points where the mask is one. The third argument # is/are the value/values to use at the array points # where the mask is zero. For this comparison, I want # the values of consistMask where self.cwaMask is one and # I want the "good result", which is zero, where # self.cwaMask is zero. consistMask[logical_not(self.cwaMask)] = 0 # The ravel and sometrue methods are from Numeric. if sometrue(ravel(consistMask)): # The ravel method reduces the rank of the array by one. # Since we had a 2-d array, the ravel function creates a # 1-d array (a vector) such that reading the 2-d array from # left-to-right, top-to-bottom returns the same values as # reading the 1-d array from left-to-right. The sometrue # method performs a logical or on subsequent element pairs # in the 1-d array and returns the final result. If # there are inconsistencies, the result will be 1. # The highlightGrids method is from SmartScript. if inconGridColor: self.highlightGrids( 'Fcst', 'SnowAmt6hr', 'SFC', gridTR, inconGridColor) self.highlightGrids( 'Fcst', 'QPF6hr', 'SFC', gridTR, inconGridColor) # The createGrid method is from SmartScript self.createGrid( 'Fcst', 'SnowAmt6hrQPF6hr', 'SCALAR', consistMask, gridTR, descriptiveName='SnowAmt6hrQPF6hrInconsistent', minAllowedValue=0, maxAllowedValue=1, units='Good/Bad') if tempGridColor: self.highlightGrids( 'Fcst', 'SnowAmt6hrQPF6hr', 'SFC', gridTR, tempGridColor) self.inconsistent = True # While not required, I like to terminate my methods with a return # statement to make it clear this is where the method ends. return def _runSnowAmt6hrWxCheck(self, timeRange): # This implements the check that if SnowAmt6hr >= 0.1, then the Wx grid # must contain S, SW, or IP, regardless of whether or not there is # any freezing or liquid types. Finally, the check does not look at # anything other than the Wx type. In other words, the check will be # okay if SnowAmt != 0 and Wx has Chc:S:- or Def:SW:-- or Lkly:S:+. # If there are locks, post urgent messages and return from the method. snowLockMe, wxLockMe, snowLockOther, wxLockOther = \ self.__checkLockedStatus(['SnowAmt6hr', 'Wx']) if snowLockMe or wxLockMe or snowLockOther or wxLockOther: # Something's locked, create messages. self._makeLockMsgs( snowLockMe, wxLockMe, snowLockOther, wxLockOther, 'SnowAmt6hr', 'Wx', 'snowLockMe', 'wxLockMe', 'snowLockOther', 'wxLockOther', 'SnowAmt6hr/Wx') return # Make sure there are actually SnowAmt6hr grids in the time range. # The getGridInfo method is from SmartScript. snowAmtInfoList = self.getGridInfo( 'Fcst', 'SnowAmt6hr', 'SFC', timeRange) if snowAmtInfoList == []: message = self._getMsg( 'noGrids', element='SnowAmt6hr', timeRange=timeRange, method='SnowAmt6hr/Wx') # The statusBarMsg method is from SmartScript. self.statusBarMsg(message, self.__getMsgSeverity('U')) # I return instead of aborting because the user may have asked for # other tests that do not have missing grid problems. return for snowAmtIndex, snowAmtGrid in enumerate(self.getGrids( 'Fcst', 'SnowAmt6hr', 'SFC', timeRange, mode='List', cache=0)): gridTR = snowAmtInfoList[snowAmtIndex].gridTime() # Make sure the snowAmtGrid meets the time constraints. if not self.__checkTC('SnowAmt6hr', gridTR): message = self._getMsg( 'badTC', element='SnowAmt6hr', timeRange=gridTR) # The statusBarMsg method is from SmartScript. self.statusBarMsg(message, self.__getMsgSeverity('U')) continue # around is from SmartScript snowAmtGrid = around(snowAmtGrid, 1) # The greater_equal method is from Numeric. # The getGridInfo method is from SmartScript. nonZeroMask = greater_equal(snowAmtGrid, 0.1 - snowAmtTol) wxInfoList = self.getGridInfo('Fcst', 'Wx', 'SFC', gridTR) # Check for Wx grid in gridTR if wxInfoList == []: message = self._getMsg( 'noGrids', element='Wx', timeRange=gridTR, method='SnowAmt6hr/Wx') # The statusBarMsg method is from SmartScript self.statusBarMsg(message, self.__getMsgSeverity('U')) continue # Now check the overlapping Wx grids. Initialize a totally # inconsistent grid. # ones is from Numeric inconsistGrid = ones(nonZeroMask.shape, int) for wxIndex, wxGrid in enumerate(self.getGrids( 'Fcst', 'Wx', 'SFC', gridTR, mode='List', cache=0)): # The wxMask method is from SmartScript sMask = self.wxMask(wxGrid, ':S:') swMask = self.wxMask(wxGrid, ':SW:') ipMask = self.wxMask(wxGrid, ':IP:') # The logical_or method is from Numeric. For the two input # arrays, if both values are "False", then the result is # "False". Otherwise, the result is "True". snowMask = logical_or(logical_or(sMask, swMask), ipMask) # I don't need these arrays any longer. Delete them to free # up the memory they use. del (sMask, swMask, ipMask) # The where method is from Numeric wxMask = logical_not(snowMask) # "Truth" table for the logical comparison follows # SnowAmt6hr >= 0.1, 1; SnowAmt6hr < 0.1, 0 # Wx has S, SW, or IP, 0; Wx doesn't have S, SW, or IP, 1 # SnowAmt6hr >= 0.1 (1) and Wx has (0) = 0 (Good result) # SnowAmt6hr >= 0.1 (1) and Wx doesn't have (1) = 1 (Bad result) # SnowAmt6hr < 0.1 (0) and Wx has (0) = 0 (Good result) # SnowAmt6hr < 0.1 (0) and Wx doesn't have (1) = 0 (Good result) # # The logical_and, where, sometrue, and ravel methods are all # from Numeric. consistMask = logical_and(nonZeroMask, wxMask) consistMask[logical_not(self.cwaMask)] = 0 # Update inconsistGrid to be the current state of the # inconsistencies. inconsistGrid = logical_and(inconsistGrid, consistMask) if not sometrue(ravel(inconsistGrid)): # There were no longer any inconsistencies between # SnowAmt6hr and Wx. break else: # This block will only execute if the for loop runs to # completion, i.e., the break statement is not executed. # So, if we get here, we have inconsistencies and need to # highlight the appropriate grids. # The highlightGrids method is from SmartScript. if inconGridColor: self.highlightGrids( 'Fcst', 'SnowAmt6hr', 'SFC', gridTR, inconGridColor) self.highlightGrids( 'Fcst', 'Wx', 'SFC', gridTR, inconGridColor) # The createGrid method is from SmartScript self.createGrid( 'Fcst', 'SnowAmt6hrWx', 'SCALAR', inconsistGrid, gridTR, descriptiveName='SnowAmt6hrWxInconsistent', minAllowedValue=0, maxAllowedValue=1, units='Good/Bad') if tempGridColor: self.highlightGrids( 'Fcst', 'SnowAmt6hrWx', 'SFC', gridTR, tempGridColor) self.inconsistent = True return def _runQPF6hrPoPCheck(self, timeRange): # This method implements the check that if any QPF6hr grid is non zero # then one of the corresponding floating PoP grids must also be non # zero. # If there are locks, post urgent messages and return from the method. qpfLockMe, popLockMe, qpfLockOther, popLockOther = \ self.__checkLockedStatus(['QPF6hr', 'PoP']) if qpfLockMe or popLockMe or qpfLockOther or popLockOther: # Something's locked, create messages. self._makeLockMsgs( qpfLockMe, popLockMe, qpfLockOther, popLockOther, 'QPF6hr', 'PoP', 'qpfLockMe', 'popLockMe', 'qpfLockOther', 'popLockOther', 'QPF6hr/PoP') return # The getGridInfo method is from SmartScript. qpfInfoList = self.getGridInfo('Fcst', 'QPF6hr', 'SFC', timeRange) # Make sure there are actually QPF6hr grids in the time range. if qpfInfoList == []: message = self._getMsg( 'noGrids', element='QPF6hr', timeRange=timeRange, method='QPF6hr/PoP') # The statusBarMsg method is from SmartScript. self.statusBarMsg(message, self.__getMsgSeverity('U')) # I return instead of aborting because the user may have asked for # other tests that do not have missing grid problems. return for qpfIndex, qpfGrid in enumerate(self.getGrids( 'Fcst', 'QPF6hr', 'SFC', timeRange, mode='List', cache=0)): gridTR = qpfInfoList[qpfIndex].gridTime() # Check the QPF6hr grid time constraints if not self.__checkTC('QPF6hr', gridTR): message = self._getMsg( 'badTC', element='QPF6hr', timeRange=gridTR) # The statusBarMsg method is from SmartScript. self.statusBarMsg(message, self.__getMsgSeverity('U')) continue # around is from SmartScript qpfGrid = around(qpfGrid, 2) # The greater_equal method is from Numeric. The getGrids method # is from SmartScript. qpfNonZeroMask = greater_equal(qpfGrid, 0.01 - qpfTol) popGrid = self.getGrids( 'Fcst', 'PoP', 'SFC', gridTR, mode='Max', noDataError=0, cache=0) # Since I don't need to loop over the PoP grids, just get their # max, I don't need to call getGridInfo like in other methods. # With noDataError=0 in the getGrids call, if there are no grids, # then the special Python value None will be returned. So, I can # just check that to see if all the PoP grids are missing. If # there were a gap in the PoP grids, that would not be caught. # But, no one's PoP grids should ever have a gap in them, right? if popGrid is None: message = self._getMsg( 'noGrids', element='PoP', timeRange=gridTR, method='QPF6hr/PoP') # The statusBarMsg method is from SmartScript. self.statusBarMsg(message, self.__getMsgSeverity('U')) continue # The equal method is from Numeric. popZeroMask = equal(popGrid, 0) # popZeroMask = 1 if PoP = 0; popZeroMask = 0 if PoP != 0 # qpfNonZeroMask = 1 if QPF6hr > 0; qpfNonZeroMask = 0 if QPF6hr = 0 # PoP = 0 (1) and QPF6hr = 0 (0) => 0 (Good result) # PoP != 0 (0) and QPF6hr = 0 (0) => 0 (Good result) # PoP != 0 (0) and QPF6hr > 0 (1) => 0 (Good result) # PoP = 0 (1) and QPF6hr > 0 (1) => 1 (Bad result) # # The logical_and, where, sometrue, and ravel methods are all # from Numeric. consistMask = logical_and(qpfNonZeroMask, popZeroMask) consistMask[logical_not(self.cwaMask)] = 0 if sometrue(ravel(consistMask)): # The good result is if the logical_and returns zeros # for every grid point, that is "none true". So, if # the sometrue method evaluates True, there are # inconsistencies. # The createGrid and highlightGrids methods are from # SmartScript. self.createGrid( 'Fcst', 'QPF6hrPoP', 'SCALAR', consistMask, gridTR, descriptiveName='QPF6hrPoPInconsistent', minAllowedValue=0, maxAllowedValue=1, units='Good/Bad') if tempGridColor: self.highlightGrids( 'Fcst', 'QPF6hrPoP', 'SFC', gridTR, tempGridColor) if inconGridColor: self.highlightGrids( 'Fcst', 'QPF6hr', 'SFC', gridTR, inconGridColor) self.highlightGrids( 'Fcst', 'PoP', 'SFC', gridTR, inconGridColor) self.inconsistent = True return def _runQPF6hrWxCheck(self, timeRange): # This method implements the check that if QPF6hr non zero, then the # corresponding Wx grids must contain a precipitable Wx type. Note: # the method only checks the Wx type, no cov/prob, no inten, etc. # If there are locks, post urgent messages and return from the method. qpfLockMe, wxLockMe, qpfLockOther, wxLockOther = \ self.__checkLockedStatus(['QPF6hr', 'Wx']) if qpfLockMe or wxLockMe or qpfLockOther or wxLockOther: # Something's locked, create messages. self._makeLockMsgs( qpfLockMe, wxLockMe, qpfLockOther, wxLockOther, 'QPF6hr', 'Wx', 'qpfLockMe', 'wxLockMe', 'qpfLockOther', 'wxLockOther', 'QPF6hr/Wx') return # The getGridInfo method is from SmartScript. qpfInfoList = self.getGridInfo('Fcst', 'QPF6hr', 'SFC', timeRange) # Make sure there are actually QPF6hr grids in the time range. if qpfInfoList == []: message = self._getMsg( 'noGrids', element='QPF6hr', timeRange=timeRange, method='QPF6hr/Wx') # The statusBarMsg method is from SmartScript. self.statusBarMsg(message, self.__getMsgSeverity('U')) # I return instead of aborting because the user may have asked for # other tests that do not have missing grid problems. return for qpfIndex, qpfGrid in enumerate(self.getGrids( 'Fcst', 'QPF6hr', 'SFC', timeRange, mode='List', noDataError=0, cache=0)): gridTR = qpfInfoList[qpfIndex].gridTime() # Make sure the QPF6hr grid meets the time constraints if not self.__checkTC('QPF6hr', gridTR): message = self._getMsg( 'badTC', element='QPF6hr', timeRange=gridTR) # The statusBarMsg method is from SmartScript. self.statusBarMsg(message, self.__getMsgSeverity('U')) continue # around is from SmartScript qpfGrid = around(qpfGrid, 2) # The greater_equal method is from Numeric. qpfNonZeroMask = greater_equal(qpfGrid, 0.01 - qpfTol) # The getGridInfo method is from SmartScript. wxInfoList = self.getGridInfo('Fcst', 'Wx', 'SFC', gridTR) # Make sure there are Wx grids overlapping the QPF6hr grid if wxInfoList == []: message = self._getMsg( 'noGrids', element='Wx', timeRange=gridTR, method='QPF6hr/Wx') # The statusBarMsg method is from SmartScript. self.statusBarMsg(message, self.__getMsgSeverity('U')) continue # Initialize a totally inconsistent grid and loop over the # overlapping Wx grids. inconsistGrid = ones(qpfNonZeroMask.shape, int) for wxIndex, wxGrid in enumerate(self.getGrids( 'Fcst', 'Wx', 'SFC', gridTR, mode='List', noDataError=0, cache=0)): # The wxMask method is from SmartScript. sMask = self.wxMask(wxGrid, ':S:') swMask = self.wxMask(wxGrid, ':SW:') ipMask = self.wxMask(wxGrid, ':IP:') # The logical_or method is from Numeric. snowMask = logical_or(logical_or(sMask, swMask), ipMask) # I don't need these three grids any longer, so delete them # and free up their memory. del (sMask, swMask, ipMask) rMask = self.wxMask(wxGrid, ':R:') rwMask = self.wxMask(wxGrid, ':RW:') lMask = self.wxMask(wxGrid, ':L:') zlMask = self.wxMask(wxGrid, ':ZL:') zrMask = self.wxMask(wxGrid, ':ZR:') # The logical_or method is from Numeric. rainMask = logical_or( rMask, logical_or( rwMask, logical_or( lMask, logical_or(zlMask, zrMask)))) # Again, I don't need these grids any longer, so delete them # and free up their memory. del (rMask, rwMask, lMask, zlMask, zrMask) precipMask = logical_or(snowMask, rainMask) del (snowMask, rainMask) wxMask = logical_not(precipMask) # QPF6hr >= 0.01, 1; QPF6hr < 0.01, 0 # Wx has precip, 0; Wx doesn't have precip, 1 # QPF6hr >= 0.01 (1) and Wx has (0) = 0 (Good result) # QPF6hr >= 0.01 (1) and Wx doesn't have (1) = 1 (Bad result) # QPF6hr < 0.01 (0) and Wx has (0) = 0 (Good result) # QPF6hr < 0.01 (0) and Wx doesn't have (1) = 0 (Good result) # # The logical_and, where, sometrue, and ravel methods are all # from Numeric. consistMask = logical_and(qpfNonZeroMask, wxMask) consistMask[logical_not(self.cwaMask)] = 0 # Update the inconsistGrid to the current state of the # inconsistencies. inconsistGrid = logical_and(inconsistGrid, consistMask) if not sometrue(ravel(inconsistGrid)): # There were no longer any inconsistencies between the Wx # grids and the QPF6hr grid. break else: # This block will only execute if the for loop runs to # completion, i.e., the break statement is not executed. # So, if we get here, we have inconsistencies and need to # highlight the appropriate grids. # The highlightGrids method is from SmartScript. if inconGridColor: self.highlightGrids( 'Fcst', 'QPF6hr', 'SFC', gridTR, inconGridColor) self.highlightGrids( 'Fcst', 'Wx', 'SFC', gridTR, inconGridColor) # The createGrid method is from SmartScript. self.createGrid( 'Fcst', 'QPF6hrWx', 'SCALAR', inconsistGrid, gridTR, descriptiveName='QPF6hrWxInconsistent', minAllowedValue=0, maxAllowedValue=1, units='Good/Bad') if tempGridColor: self.highlightGrids( 'Fcst', 'QPF6hrWx', 'SFC', gridTR, tempGridColor) self.inconsistent = True return def _runPoP12hrQPF6hrCheck(self, timeRange): # This method implements the check that if any PoP12hr grid # is >= 50%, then at least one of the two corresponding QPF6hr grids # must be non zero. # If there are locks, post urgent messages and return from the method. qpfLockMe, popLockMe, qpfLockOther, popLockOther = \ self.__checkLockedStatus(['QPF6hr', 'PoP12hr']) if qpfLockMe or popLockMe or qpfLockOther or popLockOther: # Something's locked, create messages. self._makeLockMsgs( qpfLockMe, popLockMe, qpfLockOther, popLockOther, 'QPF6hr', 'PoP12hr', 'qpfLockMe', 'popLockMe', 'qpfLockOther', 'popLockOther', 'PoP12hr/QPF6hr') return # The getGridInfo method is from SmartScript. # Make sure there are actually PoP12hr grids in the time range popInfoList = self.getGridInfo('Fcst', 'PoP12hr', 'SFC', timeRange) if popInfoList == []: message = self._getMsg( 'noGrids', element='PoP12hr', timeRange=timeRange, method='PoP12hr/QPF6hr') # The statusBarMsg method is from SmartScript. self.statusBarMsg(message, self.__getMsgSeverity('U')) # I return instead of aborting because the user may have asked for # other tests that do not have missing grid problems. return # This is the one check where it will almost always be the case that # the "controlling" element (PoP12hr) will extend later in time than # the "checked" element (QPF6hr). We don't want a lot of annoying # pop-up messages for missing QPF6hr grids for that case. I will # determine the end time of the last QPF6hr grid, adjust it back to the # nearest 00 or 12 UTC time to align it with the PoP12hr grid, and then # check the end time of the last PoP12hr grid. If the end time for # the QPF6hr grid is earlier, I will adjust the timeRange variable # inside this method to end with the QPF6hr grids. # The getGridInfo method is from SmartScript. qpfInfoList = self.getGridInfo('Fcst', 'QPF6hr', 'SFC', timeRange) # Make sure there are actually QPF6hr grids in the time range if qpfInfoList == []: message = self._getMsg( 'noGrids', element='QPF6hr', timeRange=timeRange, method='PoP12hr/QPF6hr') self.statusBarMsg(message, self.__getMsgSeverity('U')) # I return instead of aborting because the user may have asked for # other tests that do not have missing grid problems. return lastQPFTR = qpfInfoList[-1].gridTime() qpfEndTime = lastQPFTR.endTime().unixTime() qpfEndHr = lastQPFTR.endTime().hour qpfEndTime -= ((qpfEndHr % 12) * 3600) popEndTime = popInfoList[-1].gridTime().endTime().unixTime() if popEndTime > qpfEndTime: # Adjust time range to QPF6hr time range qpfStartTime = qpfInfoList[0].gridTime().startTime().unixTime() qpfDuration = (qpfEndTime - qpfStartTime) // 3600 offset = (qpfStartTime - \ self.timeRange0_1.startTime().unixTime()) // 3600 timeRange = self.createTimeRange( offset, offset+qpfDuration, 'Zulu') message = self._getMsg( 'changeTR', method='PoP12hr/QPF6hr', timeRange=timeRange) self.statusBarMsg(message, 'R') # Because the timeRange has changed, popInfoList needs to be # updated. qpfInfoList will be updated later. # The getGridInfo method is from SmartScript. popInfoList = self.getGridInfo('Fcst', 'PoP12hr', 'SFC', timeRange) # Now, it's possible there were gaps in the PoP12hr grids and the # new time range spans a gap. So, we have to check for grid # existence again. if popInfoList == []: message = self._getMsg( 'noGrids', element='PoP12hr', timeRange=timeRange, method='PoP12hr/QPF6hr') self.statusBarMsg(message, self.__getMsgSeverity('U')) return for popIndex, popGrid in enumerate(self.getGrids( 'Fcst', 'PoP12hr', 'SFC', timeRange, mode='List', cache=0)): gridTR = popInfoList[popIndex].gridTime() qpfInfoList = self.getGridInfo('Fcst', 'QPF6hr', 'SFC', gridTR) # Check for existence of QPF6hr grids in the time range. if qpfInfoList == []: message = self._getMsg( 'noGrids', element='QPF6hr', timeRange=gridTR, method='PoP12hr/QPF6hr') self.statusBarMsg(message, self.__getMsgSeverity('U')) continue # Check the PoP12hr time constraints if not self.__checkTC('PoP12hr', gridTR): message = self._getMsg( 'badTC', element='PoP12hr', timeRange=gridTR) self.statusBarMsg(message, self.__getMsgSeverity('U')) continue # The greater_equal method is from Numeric. pop50Mask = greater_equal(popGrid, 50) # Initialize a totally inconsistent grid. inconsistGrid = ones(pop50Mask.shape, int) for qpfIndex, qpfGrid in enumerate(self.getGrids( 'Fcst', 'QPF6hr', 'SFC', gridTR, mode='List', cache=0)): qpfGridTR = qpfInfoList[qpfIndex].gridTime() # Check the QPF6hr time contraints if not self.__checkTC('QPF6hr', qpfGridTR): message = self._getMsg( 'badTC', element='QPF6hr', timeRange=qpfGridTR) self.statusBarMsg(message, self.__getMsgSeverity('U')) # If one of the QPF6hr grids has a bad time constraint, # then I only have one QPF6hr grid. Time to break. break # around is from SmartScript qpfGrid = around(qpfGrid, 2) # The less method is from Numeric. qpfMask = less(qpfGrid, 0.01 - qpfTol) # The following is the "truth" table for the logical # comparison. # PoP12hr >= 50, 1; PoP12hr < 50, 0 # QPF6hr < 0.01, 1; QPF6hr >= 0.01, 0 # PoP12hr >= 50 (1) and QPF6hr < 0.01 (1) = 1 (Bad result) # PoP12hr >= 50 (1) and QPF6hr >= 0.01 (0) = 0 (Good result) # PoP12hr < 50 (0) and QPF6hr < 0.01 (1) = 0 (Good result) # PoP12hr < 50 (0) and QPF6hr >= 0.01 (0) = 0 (Good result) # logical_and is from Numeric # The logical_and, where, sometrue, and ravel methods are all # from Numeric. consistMask = logical_and(pop50Mask, qpfMask) consistMask[logical_not(self.cwaMask)] = 0 # Update the inconsistentGrid with the state of the # inconsistencies. inconsistGrid = logical_and(inconsistGrid, consistMask) # ravel and sometrue are from Numeric if not sometrue(ravel(inconsistGrid)): # There were no longer any inconsistencies between the # QPF6hr grids and PoP12hr grid. break else: # This else block will only execute if the for loop exits # "naturally", i.e., the above break statement didn't execute. # This means there were inconsistencies. # The highlightGrids method is from SmartScript. ## lin_index = nonzero(ravel(inconsistGrid)) ## sh = list(shape(inconsistGrid)) ## sh.reverse() ## new_index = zeros((len(lin_index), len(sh))) ## mod = zeros(len(lin_index)) ## for j in arange(len(lin_index)): ## count = len(sh) ## for i in sh: ## lin_index[j], mod[j] = divmod(lin_index[j], i) ## count = count - 1 ## new_index[j, count] = mod[j] ## print new_index ## print popGrid[0,0], qpfGrid[0,0] if inconGridColor: self.highlightGrids( 'Fcst', 'PoP12hr', 'SFC', gridTR, inconGridColor) self.highlightGrids( 'Fcst', 'QPF6hr', 'SFC', gridTR, inconGridColor) self.createGrid( 'Fcst', 'PoP12hrQPF6hr', 'SCALAR', inconsistGrid, gridTR, descriptiveName='PoP12hrQPF6hrInconsistent', minAllowedValue=0, maxAllowedValue=1, units='Good/Bad') if tempGridColor: self.highlightGrids( 'Fcst', 'PoP12hrQPF6hr', 'SFC', gridTR, tempGridColor) self.inconsistent = True return def _makeLockMsgs( self, lockMe1, lockMe2, lockOther1, lockOther2, element1, element2, lockMeKey1, lockMeKey2, lockOtherKey1, lockOtherKey2, method): # As I went through the five check methods, I noted that this code # was basically being repeated over and over again. So, here's a # case where I took the repeated code, made it abstract, and turned # it into a callable method. Now where each of the five methods had # about 20 lines of code to do this, they now have only 5, which is # a fairly substantial decrease. The trade-off is in somewhat # lessened code readability because of having to jump from method # to method to track the code. # Below, I assign the call to _getMsg to a temporary variable # called message for readability. Embedding the call to _getMsg # in the call to statusBarMsg makes those calls much harder to # follow. # The statusBarMsg method is from SmartScript. if lockMe1: message = self._getMsg( lockMeKey1, method=method, element=element1) self.statusBarMsg( message, self.__getMsgSeverity('U')) if lockMe2: message = self._getMsg( lockMeKey2, method=method, element=element2) self.statusBarMsg( message, self.__getMsgSeverity('U')) if lockOther1: message = self._getMsg( lockOtherKey1, method=method, element=element1) self.statusBarMsg( message, self.__getMsgSeverity('U')) if lockOther2: message = self._getMsg( lockOtherKey2, method=method, element=element2) self.statusBarMsg( message, self.__getMsgSeverity('U')) return def _msgDict(self): # Since I seem to be incapable of writing concise error messages, # I decided to capture the error messages in a separate method. # Because I tend to be verbose, I like to use triple quoted strings # for messages since this helps minimize (not eliminate) characters # which extend beyond the right margin of the editor window. But triple # quoted strings disrupt the indentation patterns of the code, making # the code harder to read. By capturing all the triple quoted strings # in a separate method, the indentation issue is mitigated. Doing this # does add some complexity to the code, but it's a fair trade-off in my # mind. This method is just a dictionary of all the error messages, # which may contain string formatting code place holders. Another # method, _getMsg, will look up the message boiler plates here and have # the logic to correctly pass the needed variables to the string # formatting codes. return { 'complete': 'NDFD_QPF_Checks complete.', '0_240': '''The NDFD_QPF_Checks procedure did not run over the 0 to 240 hour time period, it ran over %s. This may be what you desired.''', 'cwaMask': '''The procedure was not able to use the CWA edit area, %s, provided in the configuration. You should inform the person responsible for procedures of this problem. The procedure ran over the whole domain.''', 'incon': 'NDFD_QPF_Checks complete. Inconsistencies found!', 'snowLockMe': '''You have the %s grid locked. Please save the %s grid. The %s check was not run.''', 'qpfLockMe': '''You have the %s grid locked. Please save the %s grid. The %s check was not run.''', 'snowLockOther': '''Another user has the %s grid locked. Please have that user save the %s grid. The %s check was not run.''', 'qpfLockOther': '''Another user has the %s grid locked. Please have that user save the %s grid. The %s check was not run.''', 'wxLockMe': '''You have the %s grid locked. Please save the %s grid. The %s check was not run.''', 'wxLockOther': '''Another user has the %s grid locked. Please have that user save the %s grid. The %s check was not run.''', 'popLockMe': '''You have the %s grid locked. Please save the %s grid. The %s check was not run.''', 'popLockOther': '''Another user has the %s grid locked. Please have that user save the %s grid. The %s check was not run.''', 'noGrids': '''There are no %s grids in the time range, %s. The %s Check skipped the time range.''', 'changeTR': '''The time range of the %s check was changed to ensure the PoP12hr grid is not checked beyond the time of the last QPF6hr grid. The time range used was %s.''', 'badTC': '''A %s grid has the following time range: %s, which does not adhere to the time constraint requirement. This %s grid has not been consistency checked at all. Please fix the grid and re-run the procedure.''', } def _getMsg(self, key, timeRange=None, method=None, element=None): # This method looks up the needed error message by passing key to # _msgDict. The other parameters, if provided, are used to expand # the embedded string formatting codes. The resulting message is # passed back to the caller. message = self._msgDict().get(key, '') if key == '0_240': message = message % str(timeRange) return message if key == 'cwaMask': message = message % cwaEditArea return message if key == 'incon': if inconGridColor and tempGridColor: message = '%s Inconsistent grids highlighted %s.\nTemporary grids highlighted %s.' % (message, inconGridColor, tempGridColor) return message if inconGridColor: message = '%s Inconsistent grids highlighted %s.' % ( message, inconGridColor) return message if tempGridColor: message = '%s Temporary grids highlighted %s.' % ( message, tempGridColor) return message return message if key == 'snowLockMe' or key == 'qpfLockMe' or key == 'wxLockMe' or \ key == 'snowLockOther' or key == 'qpfLockOther' or \ key == 'wxLockOther' or key == 'popLockMe' or \ key == 'popLockOther': message = message % (element, element, method) return message if key == 'noGrids': message = message % (element, str(timeRange), method) return message if key == 'changeTR': message = message % (method, str(timeRange)) return message if key == 'badTC': message = message % (element, str(timeRange), element) return message # If for some reason the key look-up failed, then the message # variable will be the empty string, '', and this will be returned. # Since the calling method expects a string to be returned, I must # ensure that this happens. return message def execute(self, timeRange, varDict): # Are we in quiet mode? The variableList above does NOT have a # variable for 'Quiet', by design. When run interactively, some error # conditions will generate pop-up messages. If an office decides to # run this procedure as part of an over-arching check procedure, they # can choose to turn the pop-up messages into routine messages by # passing in a varDict with a 'Quiet' key that evaluates to 'True'. # In Python, you can use the 'get' method on a dictionary to test # for the existence of a key. If the key exists, then 'get' returns # the value. If the key doesn't exist, then the second argument of # the 'get' call is returned. If you don't provide a second argument # to the 'get' call and the key is not found, then None is returned. # As you can see below, my call to 'get' will return False if the key # 'Quiet' is not in varDict. The 'get' method allows you to avoid # constructs like: # try: # self._quiet = varDict['Quiet'] # except KeyError: # self._quiet = False # I don't know for sure, but I'd be willing to bet that the 'get' # method is just a wrapper to the 'try/except' construct very # similar to the one demonstrated. self._quiet = varDict.get('Quiet', False) ## self._quiet = True # Make sure the configuration values are the correct types. self.__checkConfigValueTypes() # createTimeRange is from SmartScript timeRange0_240 = self.createTimeRange(0, 240, 'Zulu') checkCleanup = varDict.get('Check_Cleanup', 'Check') self.__cleanup(timeRange0_240) if checkCleanup == 'Cleanup': self.statusBarMsg(self._getMsg('complete'), 'R') self.cancel() elementList = ( 'PoP', 'QPF', 'Wx', 'SnowAmt', 'QPF6hr', 'SnowAmt6hr', 'PoP12hr') for element in elementList: self.loadParm('Fcst', element, 'SFC') if timeRange.endTime().unixTime() - timeRange.startTime().unixTime() < \ 3600: # No time range selected, use 0 to 240 hour range timeRange = timeRange0_240 # If the user has a time range swept out, send an informational # message. if (timeRange.startTime().unixTime() != \ timeRange0_240.startTime().unixTime()) or \ (timeRange.endTime().unixTime() != \ timeRange0_240.endTime().unixTime()) or \ (timeRange.duration() != timeRange0_240.duration()): # What the incredibly dense expression in the above if statement # does is compare the start and end of the time range to the start # and end of the 0-240 hour time range. If either are different, # then a time range was swept out. self.statusBarMsg( self._getMsg( '0_240', timeRange=timeRange), self.__getMsgSeverity('S')) # I'll need to know the unix time of 00Z so I can determine the # start time of temporary grids later. I'll need this in more than # one of the methods called later, so this will become an instance # variable, i.e., prefixed with "self." I also need an instance # variable that flags whether or not there were inconsistent grids. self.timeRange0_1 = self.createTimeRange(0, 1, 'Zulu') self.inconsistent = False # A CWA edit area can be provided in the configuration section. # Attempt to encode that edit area as a Numeric Python mask so that # the later checks are limited to the edit area. The GFE is not very # friendly if the encoding fails. The GFE will send a nasty message # to the user, but continue executing the procedure. No trappable # error is thrown. As of this writing, the GFE appears to create an # array of shape (0, 0) if the encoding cannot be done, so I will # check for that and, if I find it, then set the edit area to the # domain. # encodeEditArea comes from SmartScript. For the points that are in # the edit area, a value of one is assigned. Otherwise, a value of # zero is assigned. if cwaEditArea: self.cwaMask = self.encodeEditArea(cwaEditArea) if self.cwaMask.shape == (0, 0): # Use the getGridInfo command to get information about the # SnowAmt grid. From this, the grid size can be extracted. I # could use getGridInfo on any valid GFE grid. # getGridInfo is from SmartScript snowAmtInfoList = self.getGridInfo( 'Fcst', 'SnowAmt6hr', 'SFC', timeRange) # I painfully discovered that the array shape is (y, x) gridSize = (snowAmtInfoList[0].gridLocation().gridSize().y, snowAmtInfoList[0].gridLocation().gridSize().x) # ones is from Numeric. It creates an array of the given size # and data type where all values are one. self.cwaMask = ones(gridSize, int) self.statusBarMsg( self._getMsg('cwaMask'), self.__getMsgSeverity('S')) else: snowAmtInfoList = self.getGridInfo( 'Fcst', 'SnowAmt6hr', 'SFC', timeRange) gridSize = (snowAmtInfoList[0].gridLocation().gridSize().y, snowAmtInfoList[0].gridLocation().gridSize().x) self.cwaMask = ones(gridSize, int) # Based on the user's input, run the appropriate checks. # By making each of these options a checkbox with only one option in # the VariableList above, if an option is unchecked then an empty # list, [], will be what's in varDict. If an option is checked then a # list with the value "Yes", ["Yes"], will be what's in varDict. In # Python, a conditional expression can be whether or not a data # structure is empty. In these cases, an empty data structure, # e.g., an empty list, an empty tuple, an empty dictionary, # conditionally test to False while non empty data structures # conditionally test to True. In the if statements below, every varDict # lookup returns a list: either [] or ["Yes"]. I think the constructs # below or more elegant and easier to understand. if varDict['Run SnowAmt6hr/QPF6hr Check?']: # Call the SnowAmt6hr/QPF6hr check method self._runSnowAmt6hrQPF6hrCheck(timeRange) if varDict['Run SnowAmt6hr/Wx Check?']: # Call the SnowAmt6hr/Wx check method self._runSnowAmt6hrWxCheck(timeRange) if varDict['Run QPF6hr/PoP Check?']: # Call the QPF6hr/PoP check method self._runQPF6hrPoPCheck(timeRange) if varDict['Run QPF6hr/Wx Check?']: # Call the QPF6hr/Wx check method self._runQPF6hrWxCheck(timeRange) if varDict['Run PoP12hr/QPF6hr Check?']: # Call the PoP12hr/QPF6hr check method self._runPoP12hrQPF6hrCheck(timeRange) if self.inconsistent: self.statusBarMsg(self._getMsg('incon'), self.__getMsgSeverity('S')) else: self.statusBarMsg(self._getMsg('complete'), 'R')