source: CCCC/trunk/ceda_cc/utils_c4.py @ 193

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

brought branches-mnj back to trunk

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