source: CCCC/tags/0.1/utils_c4.py @ 165

Subversion URL: http://proj.badc.rl.ac.uk/svn/exarch/CCCC/tags/0.1/utils_c4.py@283
Revision 165, 37.0 KB checked in by astephen, 6 years ago (diff)

updated to allow for absent 'product' attribute in SPECS data

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