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

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

cleaned up glob snow variable detection; extra code for map file generation

Line 
1"""A set of classes running checks and providing utilities to support checks"""
2import string, re, os, sys, traceback, ctypes
3
4def strmm3( mm ):
5  return string.join( map( lambda x: '%s="%s" [correct: "%s"]' % x, mm ), '; ' )
6
7from fcc_utils import mipTableScan
8from xceptions import *
9
10class timeInt(object):
11
12   vc = {'gregorian':0, 'standard':0, 'proleptic_gregorian':0, 'noleap':1, '365_day':1, 'all_leap':2, '366_day':2, '360_day':3, 'julian':0, 'none':None}
13   mnmx = [ (365,366),(365,365),(366,366),(360,360) ]
14   mmnmmx = [ (28,31),(28,31),(29,31),(30,30) ]
15   def __init__(self,cal='proleptic_gregorian',dpymn=None, dpymx=None,dpmmn=None,dpmmx=None,tol=1.e-6):
16     self.tol = tol
17     if not self.vc.has_key(cal) or cal == None:
18       assert dpymx != None and dpymn != None, 'If standard calendar is not use, dpymn and dpymx must be set'
19       assert dpmmx != None and dpmmn != None, 'If standard calendar is not use, dpmmn and dpmmx must be set'
20       self.dpymn = dpymn - tol
21       self.dpymx = dpymx + tol
22       self.dpmmn = dpmmn - tol
23       self.dpmmx = dpmmx + tol
24     else:
25       self.dpymn = self.mnmx[self.vc[cal]][0] - tol
26       self.dpymx = self.mnmx[self.vc[cal]][1] + tol
27       self.dpmmn = self.mmnmmx[self.vc[cal]][0] - tol
28       self.dpmmx = self.mmnmmx[self.vc[cal]][1] + tol
29     self.map = { 'yr':'P1Y','monClim':'P1M','mon':'P1M','day':'P1D','6hr':'P6H','3hr':'P3H'}
30     self.nd = { 'x':'y' }
31
32   def setUnit(self,u):
33     if u not in ['days','months','years']:
34         print  'Time unit %s not supported' % u
35         self.u = None
36     else:
37         self.u = u
38
39   def chk(self,v,u,f):
40      if not self.map.has_key(f):
41         return (0,'No frequency check available for f = %s' % f )
42      if u not in ['days']:
43         return (0,'No frequency check available for units = %s' % u )
44      x = self.map(f)
45      if x == 'P1Y':
46        return (v > self.dpymn) and (v < self.dpymx)
47      elif x == 'P1M':
48        return (v > self.dpmmn) and (v < self.dpmmx)
49      elif x == 'P1D':
50        return (v > 1.-self.tol) and (v < 1.+self.tol)
51      elif x == 'P6H':
52        return (v > 0.25-self.tol) and (v < 0.25+self.tol)
53      elif x == 'P3H':
54        return (v > 0.125-self.tol) and (v < 0.125+self.tol)
55     
56
57class reportSection(object):
58
59  def __init__(self,id,cls,parent=None, description=None):
60    self.id = id
61    self.cls = cls
62    self.parent = parent
63    self.description = description
64    self.records = []
65    self.subsections = []
66    self.closed = False
67    self.npass = 0
68    self.fail = 0
69    self.auditDone = True
70
71  def addSubSection( self, id, cls, description=None):
72    assert not self.closed, 'Attempt to add sub-section to closed report section'
73    self.subsections.append( reportSection(id, cls, parent=self, description=description )  )
74    self.auditDone = False
75    return self.subsections[-1]
76
77  def addRecord( self, id, cls, res, msg ):
78    assert not self.closed, 'Attempt to add record to closed report section'
79    self.records.append( (id, cls, res, msg) )
80    self.auditDone = False
81
82  def close(self):
83    self.closed = True
84
85  def reopen(self):
86    self.closed = False
87
88  def audit(self):
89    if self.auditDone:
90      return
91    self.closed = True
92    self.fail = 0
93    self.npass = 0
94    for ss in self.subsections:
95      ss.audit()
96      self.fail += ss.fail
97      self.npass += ss.npass
98
99    for r in self.records:
100      if r[2]:
101        self.npass += 1
102      else:
103        self.fail += 1
104
105class checkSeq(object):
106  def __init__(self):
107    pass
108
109  def check(self,x):
110    d = map( lambda i: x[i+1] - x[i], range(len(x)-1) )
111    self.delt = sum(d)/len(d)
112    self.dmx = max(d)
113    self.dmn = min(d)
114    return self.dmx - self.dmn < abs(self.delt)*1.e-4
115
116cs = checkSeq()
117
118class checkBase(object):
119  """Base class for checks, containing a set of standard methods for managing operation of checks and logging of results"""
120
121  def  __init__(self,cls="CORDEX",reportPass=True,parent=None,monitor=None):
122    """Creat class instance: set defaults, link arguments to instance, create a range of compiled regular expressions"""
123    self.cls = cls
124    self.project = cls
125    self.abortMessageCount = parent.abortMessageCount
126    self.monitor = monitor
127    self.re_isInt = re.compile( '[0-9]+' )
128    self.errorCount = 0
129    self.passCount = 0
130    self.missingValue = 1.e20
131    self.missingValue = ctypes.c_float(1.00000002004e+20).value
132    from file_utils import ncLib
133    if ncLib == 'netCDF4':
134      import numpy
135      self.missingValue = numpy.float32(self.missingValue)
136    self.parent = parent
137    self.reportPass=reportPass
138    self.pcfg = parent.pcfg
139################################
140    self.requiredGlobalAttributes = self.pcfg.requiredGlobalAttributes
141    self.controlledGlobalAttributes = self.pcfg.controlledGlobalAttributes
142    self.globalAttributesInFn = self.pcfg.globalAttributesInFn
143    self.requiredVarAttributes = self.pcfg.requiredVarAttributes
144    self.drsMappings = self.pcfg.drsMappings
145#######################################
146    self.checks = ()
147    self.messageCount = 0
148    self.init()
149    if not hasattr( self.parent, 'amapListDraft' ):
150      self.parent.amapListDraft = []
151
152  def isInt(self,x):
153    """Check that a string is a representation of an integer"""
154    return self.re_isInt.match( x ) != None
155
156  def logMessage(self, msg, error=False ):
157    """Log messages and count messages"""
158    self.messageCount += 1
159    assert self.abortMessageCount < 0 or self.abortMessageCount > self.messageCount, 'Raising error [TESTX01], perhaps for testing'
160    if self.parent != None and self.parent.log != None:
161       if error:
162         self.parent.log.error( msg )
163       else:
164         self.parent.log.info( msg )
165    else:
166       print msg
167
168    doThis = True
169    if self.appendLogfile[0] != None and doThis:
170      if self.monitor != None:
171         nofh0 = self.monitor.get_open_fds()
172      xlog = self.c4i.getFileLog( self.appendLogfile[1], flf=self.appendLogfile[0] )
173      if error:
174         xlog.error( msg )
175      else:
176         xlog.info( msg )
177      self.c4i.closeFileLog()
178      if self.monitor != None:
179         nofh9 = self.monitor.get_open_fds()
180         if nofh9 > nofh0:
181           print 'Leaking file handles [1]: %s --- %s' % (nofh0, nofh9)
182
183  def log_exception( self, msg):
184    """Logging of exceptions -- putting trace information in log files"""
185    if self.parent != None and self.parent.log != None:
186        self.parent.log.error("Exception has occured" ,exc_info=1)
187    else:
188        traceback.print_exc(file=sys.stdout)
189
190  def log_error( self, msg ):
191    """Create an error log message and call logMessage; count errors;"""
192    self.lastError = msg
193    self.errorCount += 1
194    self.logMessage( '%s.%s: FAILED:: %s' % (self.id,self.getCheckId(),msg), error=True )
195
196  def log_pass( self ):
197    """Create a pass log message and call logMessage; count passes;"""
198    self.passCount = True
199    if self.reportPass:
200      self.logMessage(  '%s.%s: OK' % (self.id,self.getCheckId()) )
201
202  def log_abort( self ):
203    self.completed = False
204    self.logMessage(   '%s.%s: ABORTED:: Errors too severe to complete further checks in this module' % (self.id,'xxx') )
205    raise abortChecks
206
207  def status(self):
208    return '%s.%s' % (self.id,self.getCheckId())
209
210  def getCheckId(self,full=True):
211    if type( self.checkId ) == type( 'x' ):
212      return self.checkId
213    else:
214      if full:
215        return '%s: [%s]' % self.checkId
216      else:
217        return self.checkId[0]
218
219  def test(self,res,msg,abort=False,part=False,appendLogfile=(None,None)):
220    """Handle test results.
221      :param res: [True/False] result of test;
222      :param msg: Message describing the test;
223      :param abort: {optional} Set True if checks should be aborted when test fails;
224      :param part: {optional} Set True if this is a component of a test (logging of pass suppressed);
225      :param appendLogfile: {optional} Allows results to be appended to pre-existing log file;
226    """
227    self.appendLogfile = appendLogfile
228    if res:
229      if not part:
230         self.log_pass()
231    else:
232      self.log_error(msg)
233      if abort:
234        self.log_abort()
235    return res
236
237  def runChecks(self):
238    """Run all the checks registered in this instance (in self.checks) and handle exceptions"""
239
240    try:
241      for c in self.checks:
242        c()  # run check
243      self.completed = True
244    except abortChecks:
245      ## error logging done before raising this exception
246      return
247    except:
248      self.log_exception( 'Exception caught by runChecks' )
249      raise loggedException
250   
251class checkFileName(checkBase):
252  """Check basic syntax of file names (i.e. checks properties of the text string, it does not attempt to access the file).
253Inherits :class:`checkBase` class. Checks are run by the :meth:`check` method."""
254
255  def init(self):
256    self.id = 'C4.001'
257    self.checkId = 'unset'
258    self.isFixed = False
259    self.step = 'Initialised'
260    self.checks = (self.do_check_fn,self.do_check_fnextra)
261    self.re_c1 = re.compile( '^[0-9]*$' )
262    self.fnDict = {}
263####
264
265  def check(self,fn):
266    """Initiate checks: manage arguments and then call *runChecks* (inherited from checkBase class).
267  Arguments: fn: file name: the file name to be checked."""
268    self.errorCount = 0
269    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)
270    self.fn = fn
271    self.fnsep = self.pcfg.fNameSep
272
273    self.runChecks()
274    self.parent.fnDict = self.fnDict
275###
276  def do_check_fn(self):
277    """Basic file name checks:
278       (1) Check suffix;
279       (1b) [for ESA-CCI files] check presence of "ESACCI" and identify file naming convention;
280       (2) Split file name into components and check number of such components;
281       (3) Additional specialist checks for ESA-CCI, CORDEX, CMIP-type (for the time range).
282    """
283    fn = self.fn
284    self.errorCount = 0
285    self.completed = False
286
287## check basic parsing of file name
288    self.checkId = ('001','parse_filename')
289    self.test( fn[-3:] == '.nc', 'File name ending ".nc" expected', abort=True, part=True )
290    bits = string.split( fn[:-3], self.fnsep )
291
292    self.fnParts = bits[:]
293    if self.pcfg.domainIndex != None:
294      self.domain = self.fnParts[self.pcfg.domainIndex]
295    else:
296      self.domain = None
297
298
299    if self.pcfg.projectV.id in ['ESA-CCI']:
300      self.test( 'ESACCI' in bits[:2] or 'ESA' == bits[0], 'File name not a valid ESA-CCI file name: %s' % fn, abort=True )
301      if bits[0] == 'ESA':
302        self.esaFnId = 2
303      elif bits[0] == 'ESACCI':
304        self.esaFnId = 1
305      else:
306        self.esaFnId = 0
307        bb = string.split( bits[2], '_' )
308        self.test( bits[2][0] == 'L' and len(bb) == 2, 'Cannot parse ESA-CCI file name: %s' % fn, abort=True )
309        bits = bits[:2] + bb + bits[3:]
310        self.fnParts = bits[:]
311       
312      self.pcfg.setEsaCciFNType(self.esaFnId)
313    self.test( len(bits) in self.pcfg.fnPartsOkLen, 'File name not parsed in %s elements [%s]' % (str(self.pcfg.fnPartsOkLen),str(bits)), abort=True )
314
315    self.fnDict = {}
316    if self.pcfg.projectV.id in ['ESA-CCI']:
317      l0 = {0:6, 1:5, 2:5}[self.esaFnId] 
318      for i in range(l0):
319        x = self.pcfg.globalAttributesInFn[i]
320        if x != None and x[0] == '*':
321          self.fnDict[x[1:]] = bits[i]
322      self.fnDict['version'] = bits[-1]
323      self.fnDict['gdsv'] = 'na'
324      if self.esaFnId == 0:
325        if len(bits) == 9:
326          self.fnDict['additional'] = bits[-3]
327          self.fnDict['gdsv'] = bits[-2]
328        elif len(bits) == 8:
329          if bits[-2][0] == 'v':
330            self.fnDict['gdsv'] = bits[-2]
331          else:
332            self.fnDict['additional'] = bits[-2]
333      elif self.esaFnId in [1,2]:
334        if len(bits) == 8:
335          self.fnDict['additional'] = bits[-3]
336
337         
338   
339    if self.pcfg.groupIndex != None:
340      self.group = self.fnParts[self.pcfg.groupIndex]
341    else:
342      self.group = None
343
344    if self.pcfg.freqIndex != None:
345      self.freq = self.fnParts[self.pcfg.freqIndex]
346    elif self.group in ['fx','fixed']:
347      self.freq = 'fx'
348    else:
349      self.freq = None
350
351    ##if self.cls == 'CORDEX':
352      ##self.freq = self.fnParts[7]
353    ##elif self.cls == 'SPECS':
354      ##self.freq = self.fnParts[1]
355
356    self.var = self.fnParts[self.pcfg.varIndex]
357    if self.pcfg.projectV.id in ['ESA-CCIxxx']:
358      if  self.fnDict['project'] == 'GlobSnow':
359        if self.fnDict['additional'] == 'weekly':
360          self.var += 'weekly'
361        elif self.fnDict['additional'] == 'monthly' and self.var == 'SWE':
362          self.var += 'weekly'
363
364    if self.pcfg.fnvdict != None:
365      if self.pcfg.fnvdict.has_key( self.var ):
366        self.var = self.pcfg.fnvdict.get( self.var )['v']
367      else:
368        addi = self.fnDict.get('additional','xxxx')
369        thiskey = '%s:%s' % (self.var,addi)
370        if self.pcfg.fnvdict.has_key( thiskey ):
371          self.var = self.pcfg.fnvdict.get( thiskey )['v']
372
373    self.isFixed = self.freq in ['fx','fixed']
374    self.parent.fileIsFixed = True
375    if self.isFixed:
376      self.test( len(self.fnParts) in self.pcfg.fnPartsOkFixedLen, 'Number of file name elements not acceptable for fixed data' )
377
378    self.checkId = ('002','parse_filename_timerange')
379    if not self.isFixed:
380
381## test time segment
382      if self.pcfg.trangeType == 'CMIP':
383        bits = string.split( self.fnParts[-1], '-' )
384        self.test( len(bits) == 2, 'File time segment [%s] will not parse into 2 elements' % (self.fnParts[-1] ), abort=True, part=True )
385
386        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  )
387        self.test(  int(bits[0]) <= int(bits[1]), 'Start and end time specified in file name [%s] in wrong order' % (self.fnParts[-1] ), abort=True, part=True  )
388
389        for b in bits:
390          self.test( self.isInt(b), 'Time segment in filename [%s] contains non integer characters' % (self.fnParts[-1] ),  abort=True, part=True  )
391        self.log_pass()
392        self.fnTimeParts = bits[:]
393      elif self.pcfg.trangeType == 'ESA-CCI':
394        self.pcfg.checkTrangeLen = False
395        tt = self.fnParts[self.pcfg.trangeIndex] 
396        if self.test( len(tt) in [4,6,8,10,12,14] and self.re_c1.match(tt) != None, 'Length of indicative date/time not consistent with YYYY[MM[DD[HH[MM[SS]]]]] specification: %s' % self.fnParts[-1], part=True  ):
397          ll = [tt[:4],]
398          tt = tt[4:]
399          for j in range(5):
400            if len(tt) > 0:
401              ll.append( tt[:2] )
402              tt = tt[2:]
403            elif j in [1,2]:
404              ll.append( '01' )
405            else:
406              ll.append( '00' )
407          indDateTime = map( int, ll )
408          self.test( indDateTime[1] in range(1,13), 'Invalid Month in indicative date time %s' % str(ll), part=True )
409          self.test( indDateTime[2] in range(1,32), 'Invalid Day in indicative date time %s' % str(ll), part=True )
410          self.test( indDateTime[3] in range(25), 'Invalid hour in indicative date time %s' % str(ll), part=True )
411          self.test( indDateTime[4] in range(60), 'Invalid minute in indicative date time %s' % str(ll), part=True )
412          self.test( indDateTime[5] in range(60), 'Invalid second in indicative date time %s' % str(ll), part=True )
413
414    self.checkId = '003'
415
416    self.checkId, ok = (('004','filename_timerange_length'),True)
417    if (not self.isFixed) and self.pcfg.checkTrangeLen:
418      ltr = { 'mon':6, 'sem':6, 'day':8, '3hr':[10,12], '6hr':10 }
419      ok &=self.test( self.freq in ltr.keys(), 'Frequency [%s] not recognised' % self.freq, part=True )
420      if ok:
421        if type( ltr[self.freq] ) == type(0):
422          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)
423          ok &= self.test( len(self.fnTimeParts[0]) == ltr[self.freq], msg, part=True )
424        elif type( ltr[self.freq] ) in [type([]),type( () )]:
425          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)
426          ok &= self.test( len(self.fnTimeParts[0]) in ltr[self.freq], msg, part=True )
427
428      if ok:
429        self.log_pass()
430
431  def do_check_fnextra(self):
432    """Check whether file name components match constraints -- but only if those constraints are not implicitly verified through comparison with global attributes in later checks"""
433    self.checkId = ('004','file_name_extra' )
434    vocabs = self.pcfg.vocabs
435    m = []
436    for a in self.pcfg.controlledFnParts:
437      if self.fnDict.has_key(a):
438        try:
439          if not vocabs[a].check( str(self.fnDict[a]) ):
440            m.append( (a,self.fnDict[a],vocabs[a].note) )
441        except:
442          print 'failed trying to check file name component %s' % a
443          raise baseException( 'failed trying to check file name component %s' % a )
444
445    self.test( len(m)  == 0, 'File name components do not match constraints: %s' % str(m) )
446
447
448class checkGlobalAttributes(checkBase):
449  """Check global and variable attributes, using tables of valid values"""
450
451  def init(self):
452    self.id = 'C4.002'
453    self.checkId = 'unset'
454    self.step = 'Initialised'
455    self.checks = (self.do_check_ga,)
456    self.fileId = None
457
458  def check(self,globalAts, varAts,varName,varGroup, vocabs, fnParts):
459    self.errorCount = 0
460    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)
461    self.var = varName
462    self.globalAts = globalAts
463    self.varAts = varAts
464    self.varGroup = varGroup
465    self.vocabs = vocabs
466    self.fnParts = fnParts
467    self.runChecks()
468
469  def getId(self):
470    if self.fileId == None:
471      id = self.globalAts['id']
472      if id != '':
473        self.fileId = '%s.%s' % (self.globalAts['naming_authority'],id)
474        if self.globalAts['naming_authority'] == 'uk.ac.pml':
475          i0 = string.find(self.globalAts['id'],'OC4v6_QAA')
476          if i0 != -1:
477            self.fileId = '%s.%s' % (self.globalAts['naming_authority'],self.globalAts['id'][:i0+9])
478      else:
479        self.fileId = '%s.:%s' % (self.globalAts['naming_authority'],self.globalAts['title'])
480
481  def getDrs( self ):
482    assert self.completed, 'method getDrs should not be called if checks have not been completed successfully'
483    ee = {}
484    drsDefaults = { 'convention_version':'n/a'}
485    if not self.globalAts.has_key('product'):
486        self.globalAts['product'] = 'output'
487    for k in self.drsMappings:
488      if self.drsMappings[k] == '@var':
489        ee[k] = self.var
490      elif self.drsMappings[k][0] == '=':
491        ee[k] = self.drsMappings[k][1:]
492      elif self.drsMappings[k] == '@ensemble':
493        ee[k] = "r%si%sp%s" % (self.globalAts["realization"],self.globalAts["initialization_method"],self.globalAts["physics_version"])
494      elif self.drsMappings[k] == '@forecast_reference_time':
495        x = self.globalAts.get("forecast_reference_time",'yyyy-mm-dd Thh:mm:ssZ' )
496        ee[k] = "%s%s%s" % (x[:4],x[5:7],x[8:10])
497      elif self.drsMappings[k] == '@mip_id':
498        ee[k] = string.split( self.globalAts["table_id"] )[1]
499      elif self.drsMappings[k] == '@ecv':
500        ee[k] = self.pcfg.ecvMappings[ self.parent.fnDict['project'] ]
501      elif self.drsMappings[k][0] == '$':
502        self.pcfg.getExtraAtts()
503        self.getId()
504        if string.find(self.drsMappings[k],':') != -1:
505          k2,dflt = string.split( self.drsMappings[k][1:],':')
506          ee[k] = self.pcfg.extraAtts[self.fileId].get( k2, dflt )
507        else:
508          ee[k] = self.pcfg.extraAtts[self.fileId][self.drsMappings[k][1:]]
509      elif self.drsMappings[k][0] == '*':
510        thisk = self.drsMappings[k][1:]
511        ee[k] = self.varAts[self.var][thisk]
512      elif self.drsMappings[k][0] == '#':
513        thisk = self.drsMappings[k][1:]
514        if drsDefaults.has_key( thisk ):
515          ee[k] = self.parent.fnDict.get(thisk, drsDefaults[thisk] )
516        else:
517          ee[k] = self.parent.fnDict[thisk]
518      else:
519        ee[k] = self.globalAts[ self.drsMappings[k] ]
520
521    for k in ['creation_date','tracking_id']:
522      if k in self.globalAts.keys():
523        ee[k] = self.globalAts[k]
524
525    return ee
526
527  def do_check_ga(self):
528    varName = self.var
529    globalAts = self.globalAts
530    varAts = self.varAts
531    varGroup = self.varGroup
532    vocabs = self.vocabs
533    fnParts = self.fnParts
534
535    self.completed = False
536    self.checkId = ('001','global_ncattribute_present')
537    m = []
538    for k in self.requiredGlobalAttributes:
539      if not globalAts.has_key(k):
540         m.append(k)
541         self.globalAts[k] = '__errorReported__'
542
543    if not self.test( len(m)  == 0, 'Required global attributes missing: %s' % str(m) ):
544      gaerr = True
545      for k in m:
546        self.parent.amapListDraft.append( '#@;%s=%s|%s=%s' % (k,'__absent__',k,'<insert attribute value and uncomment>') )
547
548    self.checkId = ('002','variable_in_group')
549
550    self.test( varAts.has_key( varName ), 'Expected variable [%s] not present' % varName, abort=True, part=True )
551    msg = 'Variable %s not in table %s' % (varName,varGroup)
552    self.test( vocabs['variable'].isInTable( varName, varGroup ), msg, abort=True, part=True )
553
554    if self.pcfg.checkVarType:
555      self.checkId = ('003','variable_type')
556
557      mipType = vocabs['variable'].getAttr( varName, varGroup, 'type' )
558      thisType = {'real':'float32', 'integer':'int32', 'float':'float32', 'double':'float64' }.get( mipType, mipType )
559      self.test( mipType == None or varAts[varName]['_type'] == thisType, 'Variable [%s/%s] not of type %s [%s]' % (varName,varGroup,str(thisType),varAts[varName]['_type']) )
560    else:
561      mipType = None
562
563    self.checkId = ('004','variable_ncattribute_present')
564    m = []
565    reqAts = self.requiredVarAttributes[:]
566    if (not self.parent.fileIsFixed) and self.pcfg.projectV.id in ['CORDEX']:
567      reqAts.append( 'cell_methods' )
568    for k in reqAts + vocabs['variable'].lists(varName, 'addRequiredAttributes'):
569      if not varAts[varName].has_key(k):
570         m.append(k)
571    if not self.test( len(m)  == 0, 'Required variable attributes missing: %s' % str(m) ):
572      vaerr = True
573      for k in m:
574        self.parent.amapListDraft.append( '#@var=%s;%s=%s|%s=%s' % (varName,k,'__absent__',k,'<insert attribute value and uncomment>') )
575        ## print self.parent.amapListDraft[-1]
576
577## need to insert a check that variable is present
578    self.checkId = ('005','variable_ncattribute_mipvalues')
579    ok = True
580    hm = varAts[varName].get( 'missing_value', None ) != None
581    hf = varAts[varName].has_key( '_FillValue' )
582    if hm or hf:
583      if self.pcfg.varTables=='CMIP':
584        ok &= self.test( hm, 'missing_value must be present if _FillValue is [%s]' % varName )
585        ok &= self.test( hf, '_FillValue must be present if missing_value is [%s]' % varName )
586      else:
587        ok = True
588      if mipType == 'real':
589        if varAts[varName].has_key( 'missing_value' ):
590           msg = 'Variable [%s] has incorrect attribute missing_value=%s [correct: %s]' % (varName,varAts[varName]['missing_value'],self.missingValue)
591### need to use ctypes here when using ncq3 to read files -- appears OK for other libraries.
592           ok &= self.test( ctypes.c_float(varAts[varName]['missing_value']).value == ctypes.c_float(self.missingValue).value, msg, part=True )
593        if varAts[varName].has_key( '_FillValue' ):
594           msg = 'Variable [%s] has incorrect attribute _FillValue=%s [correct: %s]' % (varName,varAts[varName]['_FillValue'],self.missingValue)
595           ok &= self.test( varAts[varName]['_FillValue'] == self.missingValue, msg, part=True )
596
597    mm = []
598   
599    if self.pcfg.varTables=='CMIP':
600      contAts = ['long_name', 'standard_name', 'units']
601      if not self.parent.fileIsFixed:
602      ##if varGroup not in ['fx','fixed']:
603        contAts.append( 'cell_methods' )
604    else:
605      contAts = ['standard_name']
606    hcm = varAts[varName].has_key( "cell_methods" )
607    for k in contAts + vocabs['variable'].lists(varName,'addControlledAttributes'):
608      targ = varAts[varName].get( k, 'Attribute not present' )
609      val = vocabs['variable'].getAttr( varName, varGroup, k )
610
611      if k == "cell_methods":
612        if val != None:
613          parenthesies1 = []
614          targ0 = targ[:]
615          while string.find( targ, '(' ) != -1:
616            i0 = targ.index( '(' )
617            i1 = targ.index( ')' )
618            parenthesies1.append( targ[i0:i1+1] )
619            targ = targ[:i0-1] + targ[i1+1:]
620          parenthesies2 = []
621          val0 = val[:]
622          while string.find( val, '(' ) != -1:
623            i0 = val.index( '(' )
624            i1 = val.index( ')' )
625            parenthesies2.append( val[i0:i1+1] )
626            val = val[:i0-1] + val[i1+1:]
627          for p in parenthesies2:
628            if p not in parenthesies1:
629              mm.append( (k,parenthesies1,p) )
630          if string.find( targ, val):
631             mm.append( (k,targ,val) )
632      elif targ != 'Attribute not present' and targ != val:
633        mm.append( (k,targ,val) )
634
635    ok &= self.test( len(mm)  == 0, 'Variable [%s] has incorrect attributes: %s' % (varName, strmm3(mm)), part=True )
636    if len( mm  ) != 0:
637      if self.parent.amapListDraft == None:
638        self.parent.amapListDraft = []
639      for m in mm:
640          if m[2] == None:
641            self.parent.amapListDraft.append( '## @var=%s;%s=%s|@delete=%s -- not supported yet' % (varName,m[0],m[1],m[0]) )
642          else:
643            self.parent.amapListDraft.append( '@var=%s;%s=%s|%s=%s' % (varName,m[0],m[1],m[0],m[2]) )
644
645    if ok:
646       self.log_pass()
647
648    if (not self.parent.fileIsFixed) and hcm:
649    ## if (varGroup not in ['fx','fixed']) and hcm:
650      self.isInstantaneous = string.find( varAts[varName]['cell_methods'], 'time: point' ) != -1
651    else:
652      self.isInstantaneous = True
653
654    self.checkId = ('006','global_ncattribute_cv' )
655    m = []
656    for a in self.controlledGlobalAttributes:
657      if globalAts.has_key(a):
658        try:
659          if not vocabs[a].check( str(globalAts[a]) ):
660            m.append( (a,globalAts[a],vocabs[a].note) )
661        except:
662          print 'failed trying to check global attribute %s' % a
663          raise baseException( 'failed trying to check global attribute %s' % a )
664
665    if not self.test( len(m)  == 0, 'Global attributes do not match constraints: %s' % str(m) ):
666      for t in m:
667        self.parent.amapListDraft.append( '#@;%s=%s|%s=%s' % (t[0],str(t[1]),t[0],'<insert attribute value and uncomment>' + str(t[2]) ) )
668
669    self.checkId = ('007','filename_filemetadata_consistency')
670    m = []
671    for i in range(len(self.globalAttributesInFn)):
672       gaif = self.globalAttributesInFn[i]
673       if gaif != None and gaif[0] != '*':
674         if gaif[-1] == ':':
675           bits = string.split(gaif,':')
676           gaif0 = gaif
677           gaif = bits[0]
678           ix = int(bits[1])
679         else:
680           ix = i
681
682         targVal = fnParts[ix]
683         if gaif[0] == "@":
684           if gaif[1:] == "mip_id":
685               bits = string.split( globalAts[ "table_id" ] ) 
686               if len( bits ) > 2 and bits[0] == "Table":
687                 thisVal = bits[1]
688               else:
689                 thisVal = globalAts[ "table_id" ]
690                 self.test( False, 'Global attribute table_id does not conform to CMOR pattern ["Table ......"]: %s' % thisVal, part=True)
691           elif gaif[1:] == "ensemble":
692               thisVal = "r%si%sp%s" % (globalAts["realization"],globalAts["initialization_method"],globalAts["physics_version"])
693## following mappings are depricated -- introduced for SPECS and withdrawn ---
694           elif gaif[1:] == "experiment_family":
695               thisVal = globalAts["experiment_id"][:-4]
696           elif gaif[1:] == "forecast_reference_time":
697               x = self.globalAts.get("forecast_reference_time",'yyyy-mm-dd Thh:mm:ssZ' )
698               thisVal = "S%s%s%s" % (x[:4],x[5:7],x[8:10])
699           elif gaif[1:] == "series":
700               thisVal = 'series%s' % globalAts["series"]
701           else:
702               assert False, "Not coded to deal with this configuration: globalAttributesInFn[%s]=%s" % (i,self.globalAttributesInFn[i])
703         
704         else:
705             thisVal = globalAts[gaif]
706
707         if thisVal not in [targVal,'__errorReported__']:
708             m.append( (i,self.globalAttributesInFn[i]) )
709
710    self.test( len(m)  == 0,'File name segments do not match corresponding global attributes: %s' % str(m) )
711
712    self.completed = True
713       
714class checkStandardDims(checkBase):
715  """Check the dimensions which are defined in the specifications"""
716
717  def init(self):
718    self.id = 'C4.003'
719    self.checkId = 'unset'
720    self.step = 'Initialised'
721    self.checks = (self.do_check,)
722    self.plevRequired = self.pcfg.plevRequired
723    self.plevValues = self.pcfg.plevValues
724    self.heightRequired = self.pcfg.heightRequired
725    self.heightValues = self.pcfg.heightValues
726    self.heightRange = self.pcfg.heightRange
727
728  def check(self,varName,varGroup, da, va, isInsta,vocabs):
729    self.errorCount = 0
730    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)
731    self.var = varName
732    self.varGroup = varGroup
733    self.da = da
734    self.va = va
735    self.isInsta = isInsta
736    self.vocabs = vocabs
737    self.runChecks()
738
739  def do_check(self):
740    varName = self.var
741    varGroup = self.varGroup
742    da = self.da
743    va = self.va
744    isInsta = self.isInsta
745
746    self.errorCount = 0
747    self.completed = False
748    self.checkId = ('001','time_attributes')
749    self.calendar = 'None'
750    if not self.parent.fileIsFixed:
751    ## if varGroup not in ['fx','fixed']:
752      ok = True
753      self.test( 'time' in da.keys(), 'Time dimension not found' , abort=True, part=True )
754      if self.pcfg.varTables=='CMIP':
755        if not isInsta:
756          ok &= self.test(  da['time'].get( 'bounds', 'xxx' ) == 'time_bnds', 'Required bounds attribute not present or not correct value', part=True )
757
758## is time zone designator needed?
759        tunits = da['time'].get( 'units', 'xxx' )
760        if self.project  == 'CORDEX':
761          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"],
762               'Time units [%s] attribute not set correctly to "days since 1949-12-01 00:00:00Z"' % tunits, part=True )
763        else:
764          ok &= self.test( tunits[:10] == "days since", 'time units [%s] attribute not set correctly to "days since ....."' % tunits, part=True )
765
766        ok &= self.test(  da['time'].has_key( 'calendar' ), 'Time: required attribute calendar missing', part=True )
767
768        ok &= self.test( da['time']['_type'] in ["float64","double"], 'Time: data type not float64 [%s]' % da['time']['_type'], part=True )
769       
770        if ok:
771          self.log_pass()
772        self.calendar = da['time'].get( 'calendar', 'None' )
773
774    self.checkId = ('002','pressure_levels')
775    if varName in self.plevRequired:
776      ok = True
777      self.test( 'plev' in va.keys(), 'plev coordinate not found %s' % str(va.keys()), abort=True, part=True )
778
779      ok &= self.test( int( va['plev']['_data'][0] ) == self.plevValues[varName],  \
780                  'plev value [%s] does not match required [%s]' % (va['plev']['_data'],self.plevValues[varName] ), part=True )
781     
782      plevAtDict = {'standard_name':"air_pressure", \
783                    'long_name':"pressure", \
784                    'units':"Pa", \
785                    'positive':"down", \
786                    'axis':"Z" }
787
788      if varName in ['clh','clm','cll']:
789        plevAtDict['bounds']= "plev_bnds"
790
791      for k in plevAtDict.keys():
792        ok &= self.test( va['plev'].get( k, None ) == plevAtDict[k], 
793                     'plev attribute %s absent or wrong value (should be %s)' % (k,plevAtDict[k]), part=True )
794
795      if varName in ['clh','clm','cll']:
796         self.test( "plev_bnds" in va.keys(), 'plev_bnds variable not found %s' % str(va.keys()), abort=True, part=True )
797         mm = []
798         for k in plevAtDict.keys():
799            if k != 'bounds' and k in va['plev_bnds'].keys():
800               if va['plev_bnds'][k] != va['plev'][k]:
801                 mm.append(k)
802         ok &= self.test( len(mm) == 0, 'Attributes of plev_bnds do not match those of plev: %s' % str(mm), part=True )
803
804         bndsVals = {'clh':[44000, 0], 'clm':[68000, 44000], 'cll':[100000, 68000] }
805         res = self.test( len( va['plev_bnds']['_data'] ) == 2, 'plev_bnds array is of wrong length', part=True )
806         ok &= res
807         if res:
808            kk = 0
809            for i in [0,1]:
810               if int(va['plev_bnds']['_data'][i]) != bndsVals[varName][i]:
811                  kk+=1
812            ok &= self.test( kk == 0, 'plev_bnds values not correct: should be %s' % str(bndsVals[varName]), part=True )
813
814      if ok:
815        self.log_pass()
816
817    self.checkId = ('003','height_levels')
818    hreq = varName in self.heightRequired
819    if self.parent.experimental:
820      print 'utils_c4: ', varName, self.vocabs['variable'].varcons[varGroup][varName].get( '_dimension',[])
821      hreq = "height2m" in self.vocabs['variable'].varcons[varGroup][varName].get( '_dimension',[])
822      if hreq:
823        print 'testing height, var=%s' % varName
824    if hreq:
825      heightAtDict = {'long_name':"height", 'standard_name':"height", 'units':"m", 'positive':"up", 'axis':"Z" }
826      ok = True
827      ok &= self.test( 'height' in va.keys(), 'height coordinate not found %s' % str(va.keys()), abort=True, part=True )
828      ##ok &= self.test( abs( va['height']['_data'] - self.heightValues[varName]) < 0.001, \
829                ##'height value [%s] does not match required [%s]' % (va['height']['_data'],self.heightValues[varName] ), part=True )
830
831      ok1 = self.test( len( va['height']['_data'] ) == 1, 'More height values (%s) than expected (1)' % (len( va['height']['_data'])), part=True )
832      if ok1:
833        r = self.heightRange[varName]
834        ok1 &= self.test( r[0] <= va['height']['_data'][0] <= r[1], \
835                'height value [%s] not in specified range [%s]' % (va['height']['_data'], (self.heightRange[varName] ) ), part=True )
836
837      ok &= ok1
838     
839      for k in heightAtDict.keys():
840        val =  va['height'].get( k, "none" )
841        if not self.test( val == heightAtDict[k], \
842                         'height attribute %s absent or wrong value (should be %s)' % (k,heightAtDict[k]), part=True ):
843          self.parent.amapListDraft.append( '@var=%s;%s=%s|%s=%s' % ('height',k,val,k,heightAtDict[k]) )
844          ok = False
845
846      if ok:
847        self.log_pass()
848
849    self.completed = True
850
851class checkGrids(checkBase):
852
853  def init(self):
854    self.id = 'C4.004'
855    self.checkId = 'unset'
856    self.step = 'Initialised'
857    self.checks = (self.do_check_rp,self.do_check_intd)
858
859  def check(self,varName, domain, da, va):
860    self.errorCount = 0
861    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)
862    self.var = varName
863    self.domain = domain
864    self.da = da
865    self.va = va
866
867    self.runChecks()
868    ##for c in self.checks:
869      ##c()
870    ##self.do_check_rp()
871    ##self.do_check_intd()
872
873  def do_check_rp(self):
874    varName = self.var
875    domain = self.domain
876    da = self.da
877    va = self.va
878    if va[varName].get( 'grid_mapping', None ) == "rotated_pole":
879      self.checkId = ('001','grid_mapping')
880      atDict = { 'grid_mapping_name':'rotated_latitude_longitude' }
881      atDict['grid_north_pole_latitude'] = self.pcfg.rotatedPoleGrids[domain]['grid_np_lat']
882      if self.pcfg.rotatedPoleGrids[domain]['grid_np_lon'] != 'N/A':
883        atDict['grid_north_pole_longitude'] = self.pcfg.rotatedPoleGrids[domain]['grid_np_lon']
884
885      self.checkId = ('002','rotated_latlon_attributes')
886      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 )
887
888      atDict = {'rlat':{'long_name':"rotated latitude", 'standard_name':"grid_latitude", 'units':"degrees", 'axis':"Y", '_type':'float64'},
889                'rlon':{'long_name':"rotated longitude", 'standard_name':"grid_longitude", 'units':"degrees", 'axis':"X", '_type':'float64'} }
890      mm = []
891      for k in ['rlat','rlon']:
892        for k2 in atDict[k].keys():
893          if atDict[k][k2] != da[k].get(k2, None ):
894            mm.append( (k,k2) )
895            record = '#@ax=%s;%s=%s|%s=%s <uncomment if correct>' % (k,k2,da[k].get(k2, '__missing__'),k2,atDict[k][k2]   )
896            self.parent.amapListDraft.append( record )
897      self.test( len(mm) == 0, 'Required attributes of grid coordinate arrays not correct: %s' % str(mm) )
898
899      self.checkId = ('003','rotated_latlon_domain')
900      ok = True
901      for k in ['rlat','rlon']:
902        res = len(da[k]['_data']) == self.pcfg.rotatedPoleGrids[domain][ {'rlat':'nlat','rlon':'nlon' }[k] ]
903        if not res:
904          self.test( res, 'Size of %s dimension does not match specification (%s,%s)' % (k,a,b), part=True  )
905          ok = False
906
907      a = ( da['rlat']['_data'][0], da['rlat']['_data'][-1], da['rlon']['_data'][0], da['rlon']['_data'][-1] )
908      b = map( lambda x: self.pcfg.rotatedPoleGrids[domain][x], ['s','n','w','e'] )
909      mm = []
910      for i in range(4):
911        if abs(a[i] - b[i]) > self.pcfg.gridSpecTol:
912          mm.append( (a[i],b[i]) )
913
914      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 )
915
916      for k in ['rlat','rlon']:
917        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 )
918
919      if ok:
920        self.log_pass()
921
922  def do_check_intd(self):
923    varName = self.var
924    domain = self.domain
925    da = self.da
926    va = self.va
927    if domain[-1] == 'i':
928      self.checkId = ('004','regular_grid_attributes')
929      self.test( 'lat' in da.keys() and 'lon' in da.keys(), 'lat and lon not found (required for interpolated data)', abort=True, part=True )
930
931      atDict = {'lat':{'long_name':"latitude", 'standard_name':"latitude", 'units':"degrees_north", '_type':'float64'},
932                'lon':{'long_name':"longitude", 'standard_name':"longitude", 'units':"degrees_east", '_type':'float64'} }
933      mm = []
934      for k in ['lat','lon']:
935        for k2 in atDict[k].keys():
936          if atDict[k][k2] != da[k].get(k2, None ):
937            mm.append( (k,k2) )
938            record = '#@ax=%s;%s=%s|%s=%s <uncomment if correct>' % (k,k2,da[k].get(k2, '__missing__'),k2,atDict[k][k2]   )
939            self.parent.amapListDraft.append( record )
940
941      self.test( len(mm) == 0,  'Required attributes of grid coordinate arrays not correct: %s' % str(mm), part=True )
942
943      ok = True
944      self.checkId = ('005','regular_grid_domain')
945      for k in ['lat','lon']:
946        res = len(da[k]['_data']) >= self.pcfg.interpolatedGrids[domain][ {'lat':'nlat','lon':'nlon' }[k] ]
947        if not res:
948          a,b =  len(da[k]['_data']), self.pcfg.interpolatedGrids[domain][ {'lat':'nlat','lon':'nlon' }[k] ]
949          self.test( res, 'Size of %s dimension does not match specification (%s,%s)' % (k,a,b), part=True )
950          ok = False
951
952      a = ( da['lat']['_data'][0], da['lat']['_data'][-1], da['lon']['_data'][0], da['lon']['_data'][-1] )
953      b = map( lambda x: self.pcfg.interpolatedGrids[domain][x], ['s','n','w','e'] )
954      rs = self.pcfg.interpolatedGrids[domain]['res']
955      c = [-rs,rs,-rs,rs]
956      mm = []
957      for i in range(4):
958        if a[i] != b[i]:
959          x = (a[i]-b[i])/c[i]
960          if x < 0 or abs( x - int(x) ) > 0.001:
961             skipThis = False
962             if self.project  == 'CORDEX':
963               if domain[:3] == 'ANT':
964                 if i == 2 and abs( a[i] - 0.25 ) < 0.001:
965                    skipThis = True
966                 elif i == 3 and abs( a[i] - 359.75 ) < 0.001:
967                    skipThis = True
968             if not skipThis:
969               mm.append( (a[i],b[i]) )
970
971      ok &= self.test( len(mm) == 0, 'Interpolated grid boundary error: File %s; Req. %s' % (str(a),str(b)), part=True )
972
973      for k in ['lat','lon']:
974        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 )
975      if ok:
976        self.log_pass()
977
978class mipVocab(object):
979
980  def __init__(self,pcfg,dummy=False):
981     self.pcfg = pcfg
982     if dummy:
983       self.dummyMipTable()
984     elif pcfg.varTables=='CMIP':
985       self.ingestMipTables()
986     elif pcfg.varTables=='FLAT':
987       self.flatTable()
988   
989  def ingestMipTables(self):
990     dir, tl, vgmap, fnpat = self.pcfg.mipVocabPars
991     ms = mipTableScan()
992     self.varInfo = {}
993     self.varcons = {}
994     for f in tl:
995        vg = vgmap.get( f, f )
996        self.varcons[vg] = {}
997        fn = fnpat % f
998        ll = open( '%s%s' % (dir,fn) ).readlines()
999        ee = ms.scan_table(ll,None,asDict=True)
1000        for v in ee.keys():
1001## set global default: type float
1002          eeee = { 'type':self.pcfg.defaults.get( 'variableDataType', 'float' ) }
1003          eeee['_dimension'] = ee[v][0]
1004          ar = []
1005          ac = []
1006          for a in ee[v][1].keys():
1007            eeee[a] = ee[v][1][a]
1008          ##if 'positive' in eeee.keys():
1009            ##ar.append( 'positive' )
1010            ##ac.append( 'positive' )
1011          self.varInfo[v] = {'ar':ar, 'ac':ac }
1012          self.varcons[vg][v] = eeee
1013           
1014  def dummyMipTable(self):
1015     self.varInfo = {}
1016     self.varcons = {}
1017     ee = { 'standard_name':'sn%s', 'long_name':'n%s', 'units':'1' }
1018     dir, tl, vgmap, fnpat = self.pcfg.mipVocabPars
1019     for f in tl:
1020        vg = vgmap.get( f, f )
1021        self.varcons[vg] = {}
1022        for i in range(12):
1023          v = 'v%s' % i
1024          eeee = {}
1025          eeee['standard_name'] = ee['standard_name'] % i
1026          eeee['long_name'] = ee['long_name'] % i
1027          eeee['cell_methods'] = 'time: point'
1028          eeee['units'] = ee['units']
1029          eeee['type'] = 'float'
1030          ar = []
1031          ac = []
1032          self.varInfo[v] = {'ar':ar, 'ac':ac }
1033          self.varcons[vg][v] = eeee
1034
1035  def flatTable(self):
1036     self.varInfo = {}
1037     self.varcons = {}
1038     dir, tl, vgm, fn = self.pcfg.mipVocabPars
1039     vg = vgm.keys()[0]
1040     ee = { 'standard_name':'sn%s', 'long_name':'n%s', 'units':'1' }
1041     ll = open( '%s%s' % (dir,fn) ).readlines()
1042     self.varcons[vg] = {}
1043     for l in ll:
1044       if l[0] != '#':
1045          bits = string.split( string.strip(l), '|' )
1046          if len(bits) == 2:
1047            p1,p2 = bits
1048          else:
1049            p1 = l
1050            p2 = None
1051          dt, v, sn = string.split( string.strip(p1) )
1052          if p2 != None:
1053            bits = string.split( string.strip(p2), '=' )
1054            eex = { bits[0]:bits[1] }
1055          else:
1056            eex = None
1057          self.pcfg.fnvdict[dt] = { 'v':v, 'sn':sn, 'ex':eex }
1058          ar = []
1059          ac = []
1060          self.varInfo[v] = {'ar':ar, 'ac':ac }
1061          self.varcons[vg][v] = {'standard_name':sn, 'type':'float' }
1062
1063  def lists( self, k, k2 ):
1064     if k2 == 'addRequiredAttributes':
1065       return self.varInfo[k]['ar']
1066     elif k2 == 'addControlledAttributes':
1067       return self.varInfo[k]['ac']
1068     else:
1069       raise baseException( 'mipVocab.lists called with bad list specifier %s' % k2 )
1070
1071  def isInTable( self, v, vg1 ):
1072    vg = vg1
1073    if vg == 'ESA':
1074      vg = 'ESACCI'
1075    assert vg in self.varcons.keys(), '%s not found in  self.varcons.keys() [%s]' % (vg,str(self.varcons.keys()) )
1076    return (v in self.varcons[vg].keys())
1077     
1078  def getAttr( self, v, vg1, a ):
1079    vg = vg1
1080    if vg == 'ESA':
1081      vg = 'ESACCI'
1082    assert vg in self.varcons.keys(), '%s not found in  self.varcons.keys()'
1083    assert v in self.varcons[vg].keys(), '%s not found in self.varcons[%s].keys()' % (v,vg)
1084     
1085    return self.varcons[vg][v][a]
1086     
1087class patternControl(object):
1088
1089  def __init__(self,tag,pattern,list=None,cls=None,examples=None,badExamples=None,runTest=True):
1090    if cls != None:
1091      assert cls in ['ISO'], 'value of cls [%s] not recognised' % cls
1092      if cls == 'ISO':
1093        assert pattern in ['ISO8601 duration'], 'value of pattern [%s] for ISO constraint not recognised' % pattern
1094        if pattern == 'ISO8601 duration':
1095          thispat = '^(P([0-9]+Y){0,1}([0-9]+M){0,1}([0-9]+D){0,1}(T([0-9]+H){0,1}([0-9]+M){0,1}([0-9]+(.[0-9]+){0,1}S){0,1}){0,1})$|^(P[0-9]+W)$'
1096        self.re_pat = re.compile( thispat )
1097        self.pattern = thispat
1098        self.pattern_src = pattern
1099    else:
1100      try:
1101        self.re_pat = re.compile( pattern )
1102      except:
1103        print "Failed to compile pattern >>%s<< (%s)" % (pattern, tag)
1104      self.pattern = pattern
1105   
1106    self.examples = examples
1107    self.badExamples = badExamples
1108    self.list = list
1109    self.cls = cls
1110
1111    if runTest:
1112      if examples != None:
1113        for e in examples:
1114          assert self.check(e), 'Internal check failed: example %s does not fit pattern %s' % (e,self.pattern)
1115
1116  def check(self,val):
1117    self.note = '-'
1118    m = self.re_pat.match( val )
1119    if self.list == None:
1120      self.note = "simple test"
1121      return m != None
1122    else:
1123      if m == None:
1124        self.note = "no match %s::%s" % (val,self.pattern)
1125        return False
1126      if not m.groupdict().has_key("val"):
1127        self.note = "no 'val' in match"
1128        return False
1129      self.note = "val=%s" % m.groupdict()["val"]
1130      return m.groupdict()["val"] in self.list
1131   
1132class listControl(object):
1133  def __init__(self,tag,list,split=False,splitVal=None,enumeration=False):
1134    self.list = list
1135    self.tag = tag
1136    self.split = split
1137    self.splitVal = splitVal
1138    self.enumeration = enumeration
1139    self.etest = re.compile( '(.*)<([0-9]+(,[0-9]+)*)>' )
1140    self.essplit = re.compile(r'(?:[^\s,<]|<(?:\\.|[^>])*>)+')
1141
1142  def check(self,val):
1143    self.note = '-'
1144    if len(self.list) < 4:
1145      self.note = str( self.list )
1146    else:
1147      self.note = str( self.list[:4] )
1148    if self.split:
1149      if self.splitVal == None:
1150        vs = string.split( val )
1151      elif self.enumeration:
1152        vs = map( string.strip, self.essplit.findall( val ) )
1153      else:
1154        vs = map( string.strip, string.split( val, self.splitVal ) )
1155    else:
1156      vs = [val,]
1157    if self.enumeration:
1158      vs2 = []
1159      for v in vs:
1160        m = self.etest.findall( v )
1161        if m in [None,[]]:
1162          vs2.append( v )
1163        else:
1164          opts = string.split( m[0][1], ',' )
1165          for o in opts:
1166            vs2.append( '%s%s' % (m[0][0],o) )
1167      vs = vs2[:]
1168       
1169    return all( map( lambda x: x in self.list, vs ) )
1170
1171
1172class checkByVar(checkBase):
1173  """Run some checks on groups of files with a common variable. Checks for continuity of time in group"""
1174
1175  def init(self,fileNameSeparator='_'):
1176    self.id = 'C5.001'
1177    self.checkId = 'unset'
1178    self.step = 'Initialised'
1179    self.checks = (self.checkTrange,)
1180    self.fnsep = fileNameSeparator
1181
1182  def setLogDict( self,fLogDict ):
1183    self.fLogDict = fLogDict
1184
1185  def impt(self,flist):
1186    """Scan a list of files and identify groups which a common variable and extract time ranges into a dictionary of lists, keyed on group identifiers.
1187     :param flist: List of file names.
1188
1189 This routine has rather obscure nested logical tests used to identify the group to which a file belongs. The complexity arises from the fact that the identification of the files that should form a continuous time series from the file names alone is not a standardised feature of the file names."""
1190    ee = {}
1191    elist = []
1192    for f in flist:
1193      fn = string.split(f, '/' )[-1]
1194      fnParts = string.split( fn[:-3], self.fnsep )
1195     
1196      try:
1197        if self.pcfg.freqIndex != None:
1198          freq = fnParts[self.pcfg.freqIndex]
1199        else:
1200          freq = None
1201
1202        group = fnParts[ self.pcfg.groupIndex ]
1203
1204        if self.parent.fileIsFixed:
1205          trange = None
1206        else:
1207          trange = string.split( fnParts[-1], '-' )
1208        var = fnParts[self.pcfg.varIndex]
1209        thisKey = string.join( fnParts[:-1], '.' )
1210        if group not in ee.keys():
1211          ee[group] = {}
1212        if thisKey not in ee[group].keys():
1213          ee[group][thisKey] = []
1214        ee[group][thisKey].append( (f,fn,group,trange) )
1215      except:
1216        print 'Cannot parse file name: %s' % (f) 
1217        elist.append(f)
1218## this ee entry is not used, except in bookkeeping check below.
1219## parsing of file name is repeated later, and a error log entry is created at that stage -- this could be improved.
1220## in order to improve, need to clarify flow of program: the list here is used to provide preliminary info before log files etc are set up.
1221        group = '__error__'
1222        thisKey = fn
1223        if group not in ee.keys():
1224          ee[group] = {}
1225        if thisKey not in ee[group].keys():
1226          ee[group][thisKey] = []
1227        ee[group][thisKey].append( (f,fn,group) )
1228
1229    nn = len(flist)
1230    n2 = 0
1231    for k in ee.keys():
1232      for k2 in ee[k].keys():
1233        n2 += len( ee[k][k2] )
1234
1235    assert nn==n2, 'some file lost!!!!!!'
1236    if len(elist) == 0:
1237      self.info =  '%s files, %s' % (nn,str(ee.keys()) )
1238    else:
1239      self.info =  '%s files, %s frequencies, severe errors in file names: %s' % (nn,len(ee.keys()),len(elist) )
1240      for e in elist:
1241        self.info += '\n%s' % e
1242    self.ee = ee
1243
1244  def check(self, recorder=None,calendar='None',norun=False):
1245    self.errorCount = 0
1246    self.recorder=recorder
1247    self.calendar=calendar
1248    if calendar == '360-day':
1249      self.enddec = 30
1250    else:
1251      self.enddec = 31
1252    mm = { 'enddec':self.enddec }
1253    self.pats = {'mon':('(?P<d>[0-9]{3})101','(?P<e>[0-9]{3})012'), \
1254            'sem':('(?P<d>[0-9]{3})(012|101)','(?P<e>[0-9]{3})(011|010)'), \
1255            'day':('(?P<d>[0-9]{3}[16])0101','(?P<e>[0-9]{3}[50])12%(enddec)s' % mm), \
1256            '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 ), \
1257            'subd2':('(?P<d>[0-9]{4})0101(?P<h1>[0-9]{2})', '(?P<e>[0-9]{4})010100' ) }
1258
1259    if not norun:
1260      self.runChecks()
1261
1262  def checkTrange(self):
1263    """Manage time range checks: loop over groups of files identified by :meth:`impt`"""
1264    keys = self.ee.keys()
1265    keys.sort()
1266    for k in keys:
1267      if k not in ['fx','fixed']:
1268        keys2 = self.ee[k].keys()
1269        keys2.sort()
1270        for k2 in keys2:
1271          self.checkThisTrange( self.ee[k][k2], k )
1272
1273  def checkThisTrange( self, tt, group):
1274    """Check consistency across a list of time ranges"""
1275
1276    if group in ['3hr','6hr']:
1277       kg = 'subd'
1278    else:
1279       kg = group
1280    ps = self.pats[kg]
1281    rere = (re.compile( ps[0] ), re.compile( ps[1] ) )
1282
1283    n = len(tt)
1284    self.checkId = ('001','filename_timerange_value')
1285    for j in range(n):
1286      if self.monitor != None:
1287         nofh0 = self.monitor.get_open_fds()
1288      t = tt[j]
1289      fn = t[1]
1290      isFirst = j == 0
1291      isLast = j == n-1
1292      lok = True
1293      for i in [0,1]:
1294        if not (i==0 and isFirst or i==1 and isLast):
1295          x = rere[i].match( t[3][i] )
1296          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) )
1297        if not lok:
1298          if self.recorder != None:
1299            self.recorder.modify( t[1], 'ERROR: time range' )
1300      if self.monitor != None:
1301         nofh9 = self.monitor.get_open_fds()
1302         if nofh9 > nofh0:
1303           print 'Open file handles: %s --- %s [%s]' % (nofh0, nofh9, j )
1304
1305### http://stackoverflow.com/questions/2023608/check-what-files-are-open-in-python
1306class sysMonitor(object):
1307
1308  def __init__(self):
1309    self.fhCountMax = 0
1310
1311  def get_open_fds(self):
1312    '''
1313    return the number of open file descriptors for current process
1314    .. warning: will only work on UNIX-like os-es.
1315    '''
1316    import subprocess
1317    import os
1318
1319    pid = os.getpid()
1320    self.procs = subprocess.check_output( 
1321        [ "lsof", '-w', '-Ff', "-p", str( pid ) ] )
1322
1323    self.ps = filter( 
1324            lambda s: s and s[ 0 ] == 'f' and s[1: ].isdigit(),
1325            self.procs.split( '\n' ) )
1326    self.fhCountMax = max( self.fhCountMax, len(self.ps) )
1327    return len( self.ps )
Note: See TracBrowser for help on using the repository browser.