source: CCCC/trunk/utils_c4.py @ 100

Subversion URL: http://proj.badc.rl.ac.uk/svn/exarch/CCCC/trunk/utils_c4.py@100
Revision 100, 29.6 KB checked in by mjuckes, 7 years ago (diff)

fixed leaking of log file handles

Line 
1
2import string, re, os, sys, traceback
3
4from fcc_utils import mipTableScan
5
6class reportSection:
7
8  def __init__(self,id,cls,parent=None, description=None):
9    self.id = id
10    self.cls = cls
11    self.parent = parent
12    self.description = description
13    self.records = []
14    self.subsections = []
15    self.closed = False
16    self.npass = 0
17    self.fail = 0
18    self.auditDone = True
19
20  def addSubSection( self, id, cls, description=None):
21    assert not self.closed, 'Attempt to add sub-section to closed report section'
22    self.subsections.append( reportSection(id, cls, parent=self, description=description )  )
23    self.auditDone = False
24    return self.subsections[-1]
25
26  def addRecord( self, id, cls, res, msg ):
27    assert not self.closed, 'Attempt to add record to closed report section'
28    self.records.append( (id, cls, res, msg) )
29    self.auditDone = False
30
31  def close(self):
32    self.closed = True
33
34  def reopen(self):
35    self.closed = False
36
37  def audit(self):
38    if self.auditDone:
39      return
40    self.closed = True
41    self.fail = 0
42    self.npass = 0
43    for ss in self.subsections:
44      ss.audit()
45      self.fail += ss.fail
46      self.npass += ss.npass
47
48    for r in self.records:
49      if r[2]:
50        self.npass += 1
51      else:
52        self.fail += 1
53
54class abortChecks(Exception):
55  pass
56class loggedException(Exception):
57  pass
58class baseException(Exception):
59 
60  def __init__(self,msg):
61    self.msg = 'utils_c4:: %s' % msg
62
63  def __str__(self):
64        return unicode(self).encode('utf-8')
65
66  def __unicode__(self):
67        return self.msg % tuple([force_unicode(p, errors='replace')
68                                 for p in self.params])
69class checkSeq:
70  def __init__(self):
71    pass
72
73  def check(self,x):
74    d = map( lambda i: x[i+1] - x[i], range(len(x)-1) )
75    self.delt = sum(d)/len(d)
76    self.dmx = max(d)
77    self.dmn = min(d)
78    return self.dmx - self.dmn < abs(self.delt)*1.e-4
79
80cs = checkSeq()
81
82class checkBase:
83
84  def  __init__(self,cls="CORDEX",reportPass=True,parent=None,monitor=None):
85    self.cls = cls
86    self.project = cls
87    self.monitor = monitor
88    ## check done earlier
89    ## assert cls in ['CORDEX','SPECS'],'This version of the checker only supports CORDEX, SPECS'
90    self.re_isInt = re.compile( '[0-9]+' )
91    self.errorCount = 0
92    self.passCount = 0
93    self.missingValue = 1.e20
94    self.parent = parent
95    self.reportPass=reportPass
96    self.pcfg = parent.pcfg
97################################
98    self.requiredGlobalAttributes = self.pcfg.requiredGlobalAttributes
99    self.controlledGlobalAttributes = self.pcfg.controlledGlobalAttributes
100    self.globalAttributesInFn = self.pcfg.globalAttributesInFn
101    self.requiredVarAttributes = self.pcfg.requiredVarAttributes
102    self.drsMappings = self.pcfg.drsMappings
103#######################################
104    self.checks = ()
105    self.init()
106
107  def isInt(self,x):
108    return self.re_isInt.match( x ) != None
109
110  def logMessage(self, msg, error=False ):
111    if self.parent != None and self.parent.log != None:
112       if error:
113         self.parent.log.error( msg )
114       else:
115         self.parent.log.info( msg )
116    else:
117       print msg
118
119    doThis = True
120    if self.appendLogFile[0] != None and doThis:
121      if self.monitor != None:
122         nofh0 = self.monitor.get_open_fds()
123      xlog = self.c4i.getFileLog( self.appendLogFile[1], flf=self.appendLogFile[0] )
124      if error:
125         xlog.error( msg )
126      else:
127         xlog.info( msg )
128      self.c4i.closeFileLog()
129      if self.monitor != None:
130         nofh9 = self.monitor.get_open_fds()
131         if nofh9 > nofh0:
132           print 'Leaking file handles [1]: %s --- %s' % (nofh0, nofh9)
133
134  def log_exception( self, msg):
135    if self.parent != None and self.parent.log != None:
136        self.parent.log.error("Exception has occured" ,exc_info=1)
137    else:
138        traceback.print_exc(file=sys.stdout)
139
140  def log_error( self, msg ):
141    self.lastError = msg
142    self.errorCount += 1
143    self.logMessage( '%s.%s: FAILED:: %s' % (self.id,self.checkId,msg), error=True )
144
145  def log_pass( self ):
146    self.passCount = True
147    if self.reportPass:
148      self.logMessage(  '%s.%s: OK' % (self.id,self.checkId) )
149
150  def log_abort( self ):
151    self.completed = False
152    self.logMessage(   '%s.%s: ABORTED:: Errors too severe to complete further checks in this module' % (self.id,'xxx') )
153    raise abortChecks
154
155  def status(self):
156    return '%s.%s' % (self.id,self.checkId)
157
158  def test(self,res,msg,abort=False,part=False,appendLogFile=(None,None)):
159    self.appendLogFile = appendLogFile
160    if res:
161      if not part:
162         self.log_pass()
163    else:
164      self.log_error(msg)
165      if abort:
166        self.log_abort()
167    return res
168
169  def runChecks(self):
170
171    try:
172      for c in self.checks:
173        c()  # run check
174      self.completed = True
175    except abortChecks:
176      ## error logging done before raising this exception
177      return
178    except:
179      self.log_exception( 'Exception caught by runChecks' )
180      raise loggedException
181      ##traceback.print_exc(file=open("errlog.txt","a"))
182      ##logger.error("Exception has occured" ,exc_info=1)
183   
184class checkFileName(checkBase):
185
186  def init(self):
187    self.id = 'C4.001'
188    self.checkId = 'unset'
189    self.step = 'Initialised'
190    self.checks = (self.do_check_fn,)
191####
192
193  def check(self,fn):
194    self.errorCount = 0
195    assert type(fn) in [type('x'),type(u'x')], '1st argument to "check" method of checkGrids shound be a string variable name (not %s)' % type(fn)
196    self.fn = fn
197
198    self.runChecks()
199###
200  def do_check_fn(self):
201    fn = self.fn
202    self.errorCount = 0
203    self.completed = False
204
205## check basic parsing of file name
206    self.checkId = '001'
207    self.test( fn[-3:] == '.nc', 'File name ending ".nc" expected', abort=True, part=True )
208    bits = string.split( fn[:-3], '_' )
209    self.fnParts = bits[:]
210
211    if self.pcfg.domainIndex != None:
212      self.domain = self.fnParts[self.pcfg.domainIndex]
213    else:
214      self.domain = None
215    ##if self.cls == 'CORDEX':
216      ##self.fnPartsOkLen = [8,9]
217      ##self.fnPartsOkFixedLen = [8,]
218      ##self.fnPartsOkUnfixedLen = [9,]
219      ##checkTrangeLen = True
220    ##elif self.cls == 'SPECS':
221      ##self.fnPartsOkLen = [6,7]
222      ##self.fnPartsOkFixedLen = [6,]
223      ##self.fnPartsOkUnfixedLen = [7,]
224      ##checkTrangeLen = False
225
226    self.test( len(bits) in self.pcfg.fnPartsOkLen, 'File name not parsed in %s elements [%s]' % (str(self.pcfg.fnPartsOkLen),str(bits)), abort=True )
227
228    if self.pcfg.freqIndex != None:
229      self.freq = self.fnParts[self.pcfg.freqIndex]
230    else:
231      self.freq = None
232    ##if self.cls == 'CORDEX':
233      ##self.freq = self.fnParts[7]
234    ##elif self.cls == 'SPECS':
235      ##self.freq = self.fnParts[1]
236
237    self.var = self.fnParts[0]
238
239    self.isFixed = self.freq == 'fx'
240
241    self.checkId = '002'
242    if not self.isFixed:
243
244## test time segment
245      bits = string.split( self.fnParts[-1], '-' )
246      self.test( len(bits) == 2, 'File time segment [%s] will not parse into 2 elements' % (self.fnParts[-1] ), abort=True, part=True )
247
248      self.test(  len(bits[0]) == len(bits[1]), 'Start and end time specified in file name [%s] of unequal length' % (self.fnParts[-1] ), abort=True, part=True  )
249
250      for b in bits:
251        self.test( self.isInt(b), 'Time segment in filename [%s] contains non integer characters' % (self.fnParts[-1] ),  abort=True, part=True  )
252      self.log_pass()
253      self.fnTimeParts = bits[:]
254
255    self.checkId = '003'
256    if self.isFixed:
257      self.test( len(self.fnParts) in self.pcfg.fnPartsOkFixedLen, 'Number of file name elements not acceptable for fixed data' )
258
259    self.checkId, ok = ('004',True)
260    if len(self.fnParts) == 9 and self.pcfg.checkTrangeLen:
261      ltr = { 'mon':6, 'sem':6, 'day':8, '3hr':[10,12], '6hr':10 }
262      ok &=self.test( self.freq in ltr.keys(), 'Frequency [%s] not recognised' % self.freq, part=True )
263      if ok:
264        if type( ltr[self.freq] ) == type(0):
265          msg = 'Length of time range parts [%s,%s] not equal to required length [%s] for frequency %s' % (self.fnTimeParts[0],self.fnTimeParts[1],ltr[self.freq],self.freq)
266          ok &= self.test( len(self.fnTimeParts[0]) == ltr[self.freq], msg, part=True )
267        elif type( ltr[self.freq] ) in [type([]),type( () )]:
268          msg = 'Length of time range parts [%s,%s] not in acceptable list [%s] for frequency %s' % (self.fnTimeParts[0],self.fnTimeParts[1],str(ltr[self.freq]),self.freq)
269          ok &= self.test( len(self.fnTimeParts[0]) in ltr[self.freq], msg, part=True )
270
271      if ok:
272        self.log_pass()
273    self.completed = True
274
275class checkGlobalAttributes(checkBase):
276
277  def init(self):
278    self.id = 'C4.002'
279    self.checkId = 'unset'
280    self.step = 'Initialised'
281    self.checks = (self.do_check_ga,)
282
283  def check(self,globalAts, varAts,varName,varGroup, vocabs, fnParts):
284    self.errorCount = 0
285    assert type(varName) in [type('x'),type(u'x')], '1st argument to "check" method of checkGrids shound be a string variable name (not %s)' % type(varName)
286    self.var = varName
287    self.globalAts = globalAts
288    self.varAts = varAts
289    self.varGroup = varGroup
290    self.vocabs = vocabs
291    self.fnParts = fnParts
292    self.runChecks()
293
294  def getDrs( self ):
295    assert self.completed, 'method getDrs should not be called if checks have not been completed successfully'
296    ee = {}
297    for k in self.drsMappings:
298      if self.drsMappings[k] == '@var':
299        ee[k] = self.var
300      else:
301        ee[k] = self.globalAts[ self.drsMappings[k] ]
302
303    for k in ['creation_date','tracking_id']:
304      if k in self.globalAts.keys():
305        ee[k] = self.globalAts[k]
306
307    return ee
308
309  def do_check_ga(self):
310    varName = self.var
311    globalAts = self.globalAts
312    varAts = self.varAts
313    varGroup = self.varGroup
314    vocabs = self.vocabs
315    fnParts = self.fnParts
316
317    self.completed = False
318    self.checkId = '001'
319    m = []
320    for k in self.requiredGlobalAttributes:
321      if not globalAts.has_key(k):
322         m.append(k)
323
324    gaerr = not self.test( len(m)  == 0, 'Required global attributes missing: %s' % str(m) )
325
326    self.checkId = '002'
327
328    self.test( varAts.has_key( varName ), 'Expected variable [%s] not present' % varName, abort=True, part=True )
329    self.test( vocabs['variable'].isInTable( varName, varGroup ), 'Variable %s not in table %s' % (varName,varGroup), abort=True, part=True )
330
331    self.checkId = '003'
332
333    self.test( varAts[varName]['_type'] == "float32", 'Variable [%s] not of type float' % varName )
334
335    self.checkId = '004'
336    m = []
337    reqAts = self.requiredVarAttributes[:]
338    if varGroup != 'fx':
339      reqAts.append( 'cell_methods' )
340    for k in reqAts + vocabs['variable'].lists(varName, 'addRequiredAttributes'):
341      if not varAts[varName].has_key(k):
342         m.append(k)
343    vaerr = not self.test( len(m)  == 0, 'Required variable attributes missing: %s' % str(m) )
344
345    if vaerr or gaerr:
346      self.log_abort()
347
348## need to insert a check that variable is present
349    self.checkId = '005'
350    ok = True
351    if varAts[varName].has_key( 'missing_value' ) or varAts[varName].has_key( '_FillValue' ):
352      ok &= self.test( varAts[varName].has_key( 'missing_value' ) and varAts[varName].has_key( '_FillValue' ), \
353                'missing_value and _FillValue must both be present if one is [%s]' % varName )
354      if varAts[varName].has_key( 'missing_value' ):
355         msg = 'Variable [%s] has incorrect missing_value attribute' % varName
356         ok &= self.test( varAts[varName]['missing_value'] == self.missingValue, msg, part=True )
357      if varAts[varName].has_key( '_FillValue' ):
358         msg = 'Variable [%s] has incorrect _FillValue attribute' % varName
359         ok &= self.test( varAts[varName]['_FillValue'] == self.missingValue, msg, part=True )
360
361    mm = []
362   
363    contAts = ['long_name', 'standard_name', 'units']
364    if varGroup != 'fx':
365      contAts.append( 'cell_methods' )
366    for k in contAts + vocabs['variable'].lists(varName,'addControlledAttributes'):
367      if varAts[varName][k] != vocabs['variable'].getAttr( varName, varGroup, k ):
368        mm.append( k )
369
370    ok &= self.test( len(mm)  == 0, 'Variable [%s] has incorrect attributes: %s' % (varName, str(mm)), part=True )
371    if ok:
372       self.log_pass()
373
374    if varGroup != 'fx':
375      self.isInstantaneous = string.find( varAts[varName]['cell_methods'], 'time: point' ) != -1
376    else:
377      self.isInstantaneous = True
378
379    self.checkId = '006'
380    m = []
381    for a in self.controlledGlobalAttributes:
382       if not vocabs[a].check( str(globalAts[a]) ):
383          m.append( (a,globalAts[a]) )
384
385    self.test( len(m)  == 0, 'Global attributes do not match constraints: %s' % str(m) )
386
387    self.checkId = '007'
388    m = []
389    for i in range(len(self.globalAttributesInFn)):
390       if self.globalAttributesInFn[i] != None:
391         if globalAts[self.globalAttributesInFn[i]] != fnParts[i]:
392           m.append( (i,self.globalAttributesInFn[i]) )
393
394    self.test( len(m)  == 0,'File name segments do not match corresponding global attributes: %s' % str(m) )
395
396    self.completed = True
397       
398class checkStandardDims(checkBase):
399
400  def init(self):
401    self.id = 'C4.003'
402    self.checkId = 'unset'
403    self.step = 'Initialised'
404    self.checks = (self.do_check,)
405    self.plevRequired = self.pcfg.plevRequired
406    self.plevValues = self.pcfg.plevValues
407    self.heightRequired = self.pcfg.heightRequired
408    self.heightValues = self.pcfg.heightValues
409    self.heightRange = self.pcfg.heightRange
410
411  def check(self,varName,varGroup, da, va, isInsta):
412    self.errorCount = 0
413    assert type(varName) in [type('x'),type(u'x')], '1st argument to "check" method of checkGrids shound be a string variable name (not %s)' % type(varName)
414    self.var = varName
415    self.varGroup = varGroup
416    self.da = da
417    self.va = va
418    self.isInsta = isInsta
419    self.runChecks()
420
421  def do_check(self):
422    varName = self.var
423    varGroup = self.varGroup
424    da = self.da
425    va = self.va
426    isInsta = self.isInsta
427
428    self.errorCount = 0
429    self.completed = False
430    self.checkId = '001'
431    if varGroup != 'fx':
432      ok = True
433      self.test( 'time' in da.keys(), 'Time dimension not found' , abort=True, part=True )
434      if not isInsta:
435         ok &= self.test(  da['time'].get( 'bounds', 'xxx' ) == 'time_bnds', 'Required bounds attribute not present or not correct value', part=True )
436
437## is time zone designator needed?
438      ok &= self.test( da['time'].get( 'units', 'xxx' ) in ["days since 1949-12-01 00:00:00Z", "days since 1949-12-01 00:00:00", "days since 1949-12-01"],
439                       'Time units [%s] attribute not set correctly to "days since 1949-12-01 00:00:00Z"' % da['time'].get( 'units', 'xxx' ), part=True )
440
441      ok &= self.test(  da['time'].has_key( 'calendar' ), 'Time: required attibute calendar missing', part=True )
442
443      ok &= self.test( da['time']['_type'] == "float64", 'Time: data type not float64', part=True )
444       
445      if ok:
446        self.log_pass()
447      self.calendar = da['time'].get( 'calendar', 'None' )
448    else:
449      self.calendar = 'None'
450    self.checkId = '002'
451    if varName in self.plevRequired:
452      ok = True
453      self.test( 'plev' in va.keys(), 'plev coordinate not found %s' % str(va.keys()), abort=True, part=True )
454
455      ok &= self.test( int( va['plev']['_data'] ) == self.plevValues[varName],  \
456                  'plev value [%s] does not match required [%s]' % (va['plev']['_data'],self.plevValues[varName] ), part=True )
457     
458      plevAtDict = {'standard_name':"air_pressure", \
459                    'long_name':"pressure", \
460                    'units':"Pa", \
461                    'positive':"down", \
462                    'axis':"Z" }
463
464      if varName in ['clh','clm','cll']:
465        plevAtDict['bounds']= "plev_bnds"
466
467      for k in plevAtDict.keys():
468        ok &= self.test( va['plev'].get( k, None ) == plevAtDict[k], 
469                     'plev attribute %s absent or wrong value (should be %s)' % (k,plevAtDict[k]), part=True )
470
471      if varName in ['clh','clm','cll']:
472         self.test( "plev_bnds" in va.keys(), 'plev_bnds variable not found %s' % str(va.keys()), abort=True, part=True )
473         mm = []
474         for k in plevAtDict.keys():
475            if k != 'bounds' and k in va['plev_bnds'].keys():
476               if va['plev_bnds'][k] != va['plev'][k]:
477                 mm.append(k)
478         ok &= self.test( len(mm) == 0, 'Attributes of plev_bnds do not match those of plev: %s' % str(mm), part=True )
479
480         bndsVals = {'clh':[44000, 0], 'clm':[68000, 44000], 'cll':[100000, 68000] }
481         res = self.test( len( va['plev_bnds']['_data'] ) == 2, 'plev_bnds array is of wrong length', part=True )
482         ok &= res
483         if res:
484            kk = 0
485            for i in [0,1]:
486               if int(va['plev_bnds']['_data'][i]) != bndsVals[varName][i]:
487                  kk+=1
488            ok &= self.test( kk == 0, 'plev_bnds values not correct: should be %s' % str(bndsVals[varName]), part=True )
489
490      if ok:
491        self.log_pass()
492
493    self.checkId = '003'
494    if varName in self.heightRequired:
495      heightAtDict = {'long_name':"height", 'standard_name':"height", 'units':"m", 'positive':"up", 'axis':"Z" }
496      ok = True
497      ok &= self.test( 'height' in va.keys(), 'height coordinate not found %s' % str(va.keys()), abort=True, part=True )
498      ##ok &= self.test( abs( va['height']['_data'] - self.heightValues[varName]) < 0.001, \
499                ##'height value [%s] does not match required [%s]' % (va['height']['_data'],self.heightValues[varName] ), part=True )
500
501      r = self.heightRange[varName]
502      ok &= self.test( r[0] <= va['height']['_data'] <= r[1], \
503                'height value [%s] not in specified range [%s]' % (va['height']['_data'], (self.heightRange[varName] ) ), part=True )
504     
505      for k in heightAtDict.keys():
506        ok &= self.test( va['height'].get( k, None ) == heightAtDict[k], \
507                         'height attribute %s absent or wrong value (should be %s)' % (k,heightAtDict[k]), part=True )
508
509      if ok:
510        self.log_pass()
511
512    self.completed = True
513
514class checkGrids(checkBase):
515
516  def init(self):
517    self.id = 'C4.004'
518    self.checkId = 'unset'
519    self.step = 'Initialised'
520    self.checks = (self.do_check_rp,self.do_check_intd)
521
522  def check(self,varName, domain, da, va):
523    self.errorCount = 0
524    assert type(varName) in [type('x'),type(u'x')], '1st argument to "check" method of checkGrids shound be a string variable name (not %s)' % type(varName)
525    self.var = varName
526    self.domain = domain
527    self.da = da
528    self.va = va
529
530    self.runChecks()
531    ##for c in self.checks:
532      ##c()
533    ##self.do_check_rp()
534    ##self.do_check_intd()
535
536  def do_check_rp(self):
537    varName = self.var
538    domain = self.domain
539    da = self.da
540    va = self.va
541    if va[varName].get( 'grid_mapping', None ) == "rotated_pole":
542      self.checkId = '001'
543      atDict = { 'grid_mapping_name':'rotated_latitude_longitude' }
544      atDict['grid_north_pole_latitude'] = self.pcfg.rotatedPoleGrids[domain]['grid_np_lat']
545      if self.pcfg.rotatedPoleGrids[domain]['grid_np_lon'] != 'N/A':
546        atDict['grid_north_pole_longitude'] = self.pcfg.rotatedPoleGrids[domain]['grid_np_lon']
547
548      self.checkId = '002'
549      self.test( 'rlat' in da.keys() and 'rlon' in da.keys(), 'rlat and rlon not found (required for grid_mapping = rotated_pole )', abort=True, part=True )
550
551      atDict = {'rlat':{'long_name':"rotated latitude", 'standard_name':"grid_latitude", 'units':"degrees", 'axis':"Y", '_type':'float64'},
552                'rlon':{'long_name':"rotated longitude", 'standard_name':"grid_longitude", 'units':"degrees", 'axis':"X", '_type':'float64'} }
553      mm = []
554      for k in ['rlat','rlon']:
555        for k2 in atDict[k].keys():
556          if atDict[k][k2] != da[k].get(k2, None ):
557            mm.append( (k,k2) )
558      self.test( len(mm) == 0, 'Required attributes of grid coordinate arrays not correct: %s' % str(mm) )
559
560      self.checkId = '003'
561      ok = True
562      for k in ['rlat','rlon']:
563        res = len(da[k]['_data']) == self.pcfg.rotatedPoleGrids[domain][ {'rlat':'nlat','rlon':'nlon' }[k] ]
564        if not res:
565          self.test( res, 'Size of %s dimension does not match specification (%s,%s)' % (k,a,b), part=True  )
566          ok = False
567
568      a = ( da['rlat']['_data'][0], da['rlat']['_data'][-1], da['rlon']['_data'][0], da['rlon']['_data'][-1] )
569      b = map( lambda x: self.pcfg.rotatedPoleGrids[domain][x], ['s','n','w','e'] )
570      mm = []
571      for i in range(4):
572        if a[i] != b[i]:
573          mm.append( (a[i],b[i]) )
574
575      ok &= self.test( len(mm) == 0, 'Domain boundaries for rotated pole grid do not match %s' % str(mm), part=True )
576
577      for k in ['rlat','rlon']:
578        ok &= self.test( cs.check( da[k]['_data'] ), '%s values not evenly spaced -- min/max delta = %s, %s' % (k,cs.dmn,cs.dmx), part=True )
579
580      if ok:
581        self.log_pass()
582
583  def do_check_intd(self):
584    varName = self.var
585    domain = self.domain
586    da = self.da
587    va = self.va
588    if domain[-1] == 'i':
589      self.checkId = '002'
590      self.test( 'lat' in da.keys() and 'lon' in da.keys(), 'lat and lon not found (required for interpolated data)', abort=True, part=True )
591
592      atDict = {'lat':{'long_name':"latitude", 'standard_name':"latitude", 'units':"degrees_north", '_type':'float64'},
593                'lon':{'long_name':"longitude", 'standard_name':"longitude", 'units':"degrees_east", '_type':'float64'} }
594      mm = []
595      for k in ['lat','lon']:
596        for k2 in atDict[k].keys():
597          if atDict[k][k2] != da[k].get(k2, None ):
598            mm.append( (k,k2) )
599
600      self.test( len(mm) == 0,  'Required attributes of grid coordinate arrays not correct: %s' % str(mm), part=True )
601
602      ok = True
603      for k in ['lat','lon']:
604        res = len(da[k]['_data']) >= self.pcfg.interpolatedGrids[domain][ {'lat':'nlat','lon':'nlon' }[k] ]
605        if not res:
606          a,b =  len(da[k]['_data']), self.pcfg.interpolatedGrids[domain][ {'lat':'nlat','lon':'nlon' }[k] ]
607          self.test( res, 'Size of %s dimension does not match specification (%s,%s)' % (k,a,b), part=True )
608          ok = False
609
610      a = ( da['lat']['_data'][0], da['lat']['_data'][-1], da['lon']['_data'][0], da['lon']['_data'][-1] )
611      b = map( lambda x: self.pcfg.interpolatedGrids[domain][x], ['s','n','w','e'] )
612      rs = self.pcfg.interpolatedGrids[domain]['res']
613      c = [-rs,rs,-rs,rs]
614      mm = []
615      for i in range(4):
616        if a[i] != b[i]:
617          x = (a[i]-b[i])/c[i]
618          if x < 0 or abs( x - int(x) ) > 0.001:
619             skipThis = False
620             if self.project  == 'CORDEX':
621               if domain[:3] == 'ANT':
622                 if i == 2 and abs( a[i] - 0.25 ) < 0.001:
623                    skipThis = True
624                 elif i == 3 and abs( a[i] - 359.75 ) < 0.001:
625                    skipThis = True
626             if not skipThis:
627               mm.append( (a[i],b[i]) )
628
629      ok &= self.test( len(mm) == 0, 'Domain boundaries for interpolated grid do not match %s' % str(mm), part=True )
630
631      for k in ['lat','lon']:
632        ok &= self.test( cs.check( da[k]['_data'] ), '%s values not evenly spaced -- min/max delta = %s, %s' % (k,cs.dmn,cs.dmx), part=True )
633      if ok:
634        self.log_pass()
635
636class mipVocab:
637
638  def __init__(self,pcfg,dummy=False):
639     project = pcfg.project
640     if dummy:
641       self.pcfg = pcfg
642       return self.dummyMipTable()
643     assert project in ['CORDEX','SPECS'],'Project %s not recognised' % project
644     ##if project == 'CORDEX':
645       ##dir = 'cordex_vocabs/mip/'
646       ##tl = ['fx','sem','mon','day','6h','3h']
647       ##vgmap = {'6h':'6hr','3h':'3hr'}
648       ##fnpat = 'CORDEX_%s'
649     ##elif project == 'SPECS':
650       ##dir = 'specs_vocabs/mip/'
651       ##tl = ['fx','Omon','Amon','Lmon','OImon','day','6hrLev']
652       ##vgmap = {}
653       ##fnpat = 'SPECS_%s'
654     dir, tl, vgmap, fnpat = pcfg.mipVocabPars
655     ms = mipTableScan()
656     self.varInfo = {}
657     self.varcons = {}
658     for f in tl:
659        vg = vgmap.get( f, f )
660        self.varcons[vg] = {}
661        fn = fnpat % f
662        ll = open( '%s%s' % (dir,fn) ).readlines()
663        ee = ms.scan_table(ll,None,asDict=True)
664        for v in ee.keys():
665          eeee = {}
666          ar = []
667          ac = []
668          for a in ee[v][1].keys():
669            eeee[a] = ee[v][1][a]
670          ##if 'positive' in eeee.keys():
671            ##ar.append( 'positive' )
672            ##ac.append( 'positive' )
673          self.varInfo[v] = {'ar':ar, 'ac':ac }
674          self.varcons[vg][v] = eeee
675           
676  def dummyMipTable(self):
677     self.varInfo = {}
678     self.varcons = {}
679     ee = { 'standard_name':'sn%s', 'name':'n%s', 'units':'1' }
680     dir, tl, vgmap, fnpat = self.pcfg.mipVocabPars
681     for f in tl:
682        vg = vgmap.get( f, f )
683        self.varcons[vg] = {}
684        for i in range(12):
685          v = 'v%s' % i
686          eeee = {}
687          eeee['standard_name'] = ee['standard_name'] % i
688          eeee['name'] = ee['name'] % i
689          eeee['units'] = ee['units']
690          ar = []
691          ac = []
692          self.varInfo[v] = {'ar':ar, 'ac':ac }
693          self.varcons[vg][v] = eeee
694
695  def lists( self, k, k2 ):
696     if k2 == 'addRequiredAttributes':
697       return self.varInfo[k]['ar']
698     elif k2 == 'addControlledAttributes':
699       return self.varInfo[k]['ac']
700     else:
701       raise 'mipVocab.lists called with bad list specifier %s' % k2
702
703  def isInTable( self, v, vg ):
704    assert vg in self.varcons.keys(), '%s not found in  self.varcons.keys()' % vg
705    return (v in self.varcons[vg].keys())
706     
707  def getAttr( self, v, vg, a ):
708    assert vg in self.varcons.keys(), '%s not found in  self.varcons.keys()'
709    assert v in self.varcons[vg].keys(), '%s not found in self.varcons[%s].keys()' % (v,vg)
710     
711    return self.varcons[vg][v][a]
712     
713class patternControl:
714
715  def __init__(self,tag,pattern):
716    try:
717      self.re_pat = re.compile( pattern )
718    except:
719      print "Failed to compile pattern >>%s<< (%s)" % (pattern, tag)
720    self.pattern = pattern
721
722  def check(self,val):
723    return self.re_pat.match( val ) != None
724   
725class listControl:
726  def __init__(self,tag,list):
727    self.list = list
728    self.tag = tag
729
730  def check(self,val):
731    return val in self.list
732
733
734class checkByVar(checkBase):
735
736  def init(self):
737    self.id = 'C5.001'
738    self.checkId = 'unset'
739    self.step = 'Initialised'
740    self.checks = (self.checkTrange,)
741
742  def setLogDict( self,fLogDict ):
743    self.fLogDict = fLogDict
744
745  def impt(self,flist):
746    ee = {}
747    for f in flist:
748      fn = string.split(f, '/' )[-1]
749      fnParts = string.split( fn[:-3], '_' )
750      ##if self.cls == 'CORDEX':
751        ##isFixed = fnParts[7] == 'fx'
752        ##group = fnParts[7]
753      ##elif self.cls == 'SPECS':
754        ##isFixed = fnParts[1] == 'fx'
755        ##group = fnParts[1]
756
757      if self.pcfg.freqIndex != None:
758        freq = fnParts[self.pcfg.freqIndex]
759      else:
760        freq = None
761
762      isFixed = freq == 'fx'
763      group = fnParts[ self.pcfg.groupIndex ]
764
765      if isFixed:
766        trange = None
767      else:
768        trange = string.split( fnParts[-1], '-' )
769      var = fnParts[0]
770      if group not in ee.keys():
771        ee[group] = {}
772      if var not in ee[group].keys():
773        ee[group][var] = []
774      ee[group][var].append( (f,fn,group,trange) )
775
776    nn = len(flist)
777    n2 = 0
778    for k in ee.keys():
779      for k2 in ee[k].keys():
780        n2 += len( ee[k][k2] )
781
782    assert nn==n2, 'some file lost!!!!!!'
783    print '%s files, %s frequencies' % (nn,len(ee.keys()) )
784    self.ee = ee
785
786  def check(self, recorder=None,calendar='None',norun=False):
787    self.errorCount = 0
788    self.recorder=recorder
789    self.calendar=calendar
790    if calendar == '360-day':
791      self.enddec = 30
792    else:
793      self.enddec = 31
794    mm = { 'enddec':self.enddec }
795    self.pats = {'mon':('(?P<d>[0-9]{3})101','(?P<e>[0-9]{3})012'), \
796            'sem':('(?P<d>[0-9]{3})(012|101)','(?P<e>[0-9]{3})(011|010)'), \
797            'day':('(?P<d>[0-9]{3}[16])0101','(?P<e>[0-9]{3}[50])12%(enddec)s' % mm), \
798            'subd':('(?P<d>[0-9]{4})0101(?P<h1>[0-9]{2})(?P<mm>[30]0){0,1}$', '(?P<e>[0-9]{4})12%(enddec)s(?P<h2>[0-9]{2})([30]0){0,1}$' % mm ), \
799            'subd2':('(?P<d>[0-9]{4})0101(?P<h1>[0-9]{2})', '(?P<e>[0-9]{4})010100' ) }
800
801    if not norun:
802      self.runChecks()
803
804  def checkTrange(self):
805    keys = self.ee.keys()
806    keys.sort()
807    for k in keys:
808      if k != 'fx':
809        keys2 = self.ee[k].keys()
810        keys2.sort()
811        for k2 in keys2:
812          self.checkThisTrange( self.ee[k][k2], k, k2 )
813
814  def checkThisTrange( self, tt, group, var):
815
816    if group in ['3hr','6hr']:
817       kg = 'subd'
818    else:
819       kg = group
820    ps = self.pats[kg]
821    rere = (re.compile( ps[0] ), re.compile( ps[1] ) )
822
823    n = len(tt)
824    for j in range(n):
825      if self.monitor != None:
826         nofh0 = self.monitor.get_open_fds()
827      t = tt[j]
828      fn = t[1]
829      isFirst = j == 0
830      isLast = j == n-1
831      lok = True
832      for i in [0,1]:
833        if not (i==0 and isFirst or i==1 and isLast):
834          x = rere[i].match( t[3][i] )
835          lok &= self.test( x != None, 'Cannot match time range %s: %s' % (i,fn), part=True, appendLogFile=(self.fLogDict.get(fn,None),fn) )
836        if not lok:
837          ### print 'Cannot match time range %s:' % t[1]
838          if self.recorder != None:
839            self.recorder.modify( t[1], 'ERROR: time range' )
840      if self.monitor != None:
841         nofh9 = self.monitor.get_open_fds()
842         if nofh9 > nofh0:
843           print 'Open file handles: %s --- %s [%s]' % (nofh0, nofh9, j )
844
845### http://stackoverflow.com/questions/2023608/check-what-files-are-open-in-python
846class sysMonitor:
847
848  def __init__(self):
849    pass
850
851  def get_open_fds(self):
852    '''
853    return the number of open file descriptors for current process
854    .. warning: will only work on UNIX-like os-es.
855    '''
856    import subprocess
857    import os
858
859    pid = os.getpid()
860    self.procs = subprocess.check_output( 
861        [ "lsof", '-w', '-Ff', "-p", str( pid ) ] )
862
863    self.ps = filter( 
864            lambda s: s and s[ 0 ] == 'f' and s[1: ].isdigit(),
865            self.procs.split( '\n' ) )
866    return len( self.ps )
Note: See TracBrowser for help on using the repository browser.