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