source: branches/numpy_anuga_validation/automated_validation_tests/patong_beach_validation/cmpsww.py @ 6919

Last change on this file since 6919 was 6919, checked in by rwilson, 15 years ago

Back-merged changes to cmpsww.py.

  • Property svn:executable set to *
File size: 12.8 KB
Line 
1#!/usr/bin/env python
2
3'''
4A program to compare two SWW  files for "equality".
5
6This program makes lots of assumptions about the structure of the SWW files,
7so if that structure changes, this program must change.
8'''
9
10import sys
11import os
12import os.path
13import getopt
14from Scientific.IO.NetCDF import NetCDFFile
15import numpy as num
16from anuga.config import netcdf_mode_r
17
18
19#####
20# Various constants.
21#####
22
23# allowable 'slop' when testing two float values
24epsilon = 1.0e-9
25
26# Global attributes that should exist and be same in both files
27# Don't have to have all of these, and we don't care about others.
28expect_global_attributes = ['smoothing', 'vertices_are_stored_uniquely',
29                            'order', 'starttime',
30                            'xllcorner', 'yllcorner',
31                            'zone', 'false_easting', 'false_northing',
32                            'datum', 'projection', 'units']
33
34# dimensions expected, with expected values (None means unknown)
35expected_dimensions = {'number_of_volumes': None,
36                       'number_of_vertices': 3,
37                       'numbers_in_range': 2,
38                       'number_of_points': None,
39                       'number_of_timesteps': None}
40
41# Variables expected, with expected dimensions.
42# Don't have to have all of these, and we don't care about others.
43expected_variables = {'x': ('number_of_points',),
44                      'y': ('number_of_points',),
45                      'elevation': ('number_of_points',),
46                      'elevation_range': ('numbers_in_range',),
47                      'z': ('number_of_points',),
48                      'volumes': ('number_of_volumes', 'number of vertices'),
49                      'time': ('number_of_timesteps',),
50                      'stage': ('number_of_timesteps', 'numbers_of_points',),
51                      'stage_range': ('numbers_in_range',),
52                      'xmomentum': ('number_of_timesteps', 'number_of_points'),
53                      'xmomentum_range': ('numbers_in_range'),
54                      'ymomentum': ('number_of_timesteps', 'number_of_points'),
55                      'ymomentum_range': ('numbers_in_range')}
56
57##
58# @brief An exception to inform user of usage problems.
59class Usage(Exception):
60    def __init__(self, msg):
61        self.msg = msg
62
63
64##
65# @brief Compare two SWW files.
66# @param files A tuple of two filenames.
67# @param globals A list of global attribute names to compare.
68# @param timesteps A list of timesteps to compare at.
69# @param variables A list of variable names to compare.
70# @return Returns if files 'equal', else raises RuntimeError.
71def files_are_the_same(files, globals=None, timesteps=None, variables=None):
72    # split out the filenames and check they exist
73    (file1, file2) = files
74    filename1 = os.path.basename(file1)
75    filename2 = os.path.basename(file2)
76    width = max(len(filename1), len(filename2))
77    filename1 = filename1.rjust(width)
78    filename2 = filename2.rjust(width)
79
80    error = False
81    error_msg = ''
82
83    try:
84        fid1 = NetCDFFile(file1, netcdf_mode_r)
85    except:
86        error_msg += "\nFile '%s' can't be opened?\n" % file1
87        error = True
88
89    try:
90        fid2 = NetCDFFile(file2, netcdf_mode_r)
91    except:
92        error_msg += "\nFile '%s' can't be opened?\n" % file2
93        error = True
94        fid1.close()
95
96    if error:
97        raise RuntimeError, error_msg
98
99    if globals is None:
100        globals = expect_global_attributes
101
102    #####
103    # First, check that files have the required structure
104    #####
105
106    # dimensions - only check expected dimensions
107    for key in expected_dimensions:
108        if key not in fid1.dimensions.keys():
109            error_msg += ("\nFile %s doesn't contain dimension '%s'\n"
110                          % (filename1, key))
111            error = True
112        if key not in fid2.dimensions.keys():
113            error_msg += ("\nFile %s doesn't contain dimension '%s'\n"
114                          % (filename2, key))
115            error = True
116
117    # now check that dimensions are the same length
118    # NOTE: DOESN'T CHECK 'UNLIMITED' DIMENSIONS YET! (get None at the moment)
119    for dim in expected_dimensions:
120        dim1_shape = fid1.dimensions.get(dim, None)
121        dim2_shape = fid2.dimensions.get(dim, None)
122        if dim1_shape != dim2_shape:
123            error_msg += ("\nFile %s has '%s' dimension of size %s,\n"
124                          "file %s has that dimension of size %s\n"
125                          % (filename1, dim, str(dim1_shape),
126                             filename2, str(dim2_shape)))
127            error = True
128
129    # check that we have the required globals
130    if globals:
131        for glob in globals:
132            if glob not in dir(fid1):
133                error_msg += ("\nGlobal attribute '%s' isn't in file %s\n"
134                              % (glob, filename1))
135                error = True
136            if glob not in dir(fid2):
137                error_msg += ("\nGlobal attribute '%s' isn't in file %s\n"
138                              % (glob, filename2))
139                error = True
140    else:
141        # get list of global attributes
142        glob_vars1 = []
143        glob_vars2 = []
144        for glob in expect_global_attributes:
145            if glob in dir(fid1):
146                glob_vars1.append(glob)
147            if glob in dir(fid2):
148                glob_vars2.append(glob)
149
150        # now check attribute lists are same
151        if glob_vars1 != glob_vars2:
152            error_msg = ('\nFiles differ in global attributes:\n'
153                         '%s: %s,\n'
154                         '%s: %s\n' % (filename1, str(glob_vars1),
155                                     filename2, str(glob_vars2)))
156            error = True
157
158    # get variables to test
159    if variables:
160        for var in variables:
161            if var not in fid1.variables.keys():
162                error_msg += ("\nVariable '%s' isn't in file %s\n"
163                              % (var, filename1))
164                error = True
165            if var not in fid2.variables.keys():
166                error_msg += ("\nVariable '%s' isn't in file %s\n"
167                              % (var, filename2))
168                error = True
169    else:
170        # check that variables are as expected in both files
171        var_names1 = []
172        var_names2 = []
173        for var_name in expected_variables:
174            if fid1.variables.has_key(var_name):
175                var_names1.append(var_name)
176            if fid2.variables.has_key(var_name):
177                var_names2.append(var_name)
178   
179        if var_names1 != var_names2:
180            error_msg += ('\nVariables are not the same between files:\n'
181                          '%s variables= %s,\n'
182                          '%s variables = %s\n'
183                          % (filename1, str(var_names1), filename2, str(var_names2)))
184            error = True
185        variables = var_names1
186
187    # get size of time dimension
188    num_timesteps1 = fid1.variables['time'].shape
189    num_timesteps2 = fid2.variables['time'].shape
190    if num_timesteps1 != num_timesteps2:
191        error_msg += ('Files have different number of timesteps:\n'
192                      '%s=%d,\n'
193                      '%s=%d\n'
194                      % (filename1, num_timesteps1, filename2, num_timesteps2))
195        error = True
196
197    num_timesteps = num_timesteps1[0]
198
199    # variable shapes same?
200    for var_name in variables:
201        var1 = fid1.variables[var_name]
202        var2 = fid2.variables[var_name]
203        var1_shape = var1.shape
204        var2_shape = var2.shape
205        if var1_shape != var2_shape:
206            error_msg += ('Files differ in variable %s shape:\n'
207                          '%s: %s,\n'
208                          '%s: %s\n'
209                          % (var_name, filename1, str(var1_shape),
210                             filename2, str(var2_shape)))
211            error = True
212
213    if error:
214        fid1.close()
215        fid2.close()
216        raise RuntimeError, error_msg
217
218    #####
219    # Now check that actual data values are the same
220    #####
221
222    # check values of global attributes
223    for glob_name in globals:
224        if getattr(fid1, glob_name) != getattr(fid2, glob_name):
225            error_msg += ("\nFiles differ in global '%s':\n"
226                          "%s: '%s',\n"
227                          "%s: '%s'\n"
228                          % (glob_name, filename1, str(g1), filename2, str(g2)))
229            error = True
230
231    # check data variables, be clever with time series data
232    for var_name in variables:
233        var_dims = expected_variables[var_name]
234        if (len(var_dims) > 1) and (var_dims[0] == 'number_of_timesteps'):
235            # time series, check by timestep block
236            for t in xrange(num_timesteps):
237                var1 = num.array(fid1.variables[var_name][t,:])
238                var2 = num.array(fid2.variables[var_name][t,:])
239                if var1 != var2:
240                    for i in xrange(len(var1)):
241                        if var1[i] != var2[i]:
242                            error_msg += ('\nFiles differ in variable '
243                                          '%s[%d,%d]:\n'
244                                          '%s: %.14f\n'
245                                          '%s: %.14f\n'
246                                          % (var_name, t, i,
247                                             filename1, var1[i],
248                                             filename2, var2[i]))
249                            break
250                    error = True
251        else:
252            # simple data, check whole thing at once
253            var1 = num.array(fid1.variables[var_name][:])
254            var2 = num.array(fid2.variables[var_name][:])
255            if not num.allclose(var1, var2):
256                for j in xrange(len(var1)):
257                    if abs(var1[j] - var2[j]) > epsilon:
258                        error_msg += ('\nFiles differ in variable '
259                                      '%s[%d]:\n'
260                                      '%s: %.14f\n'
261                                      '%s: %.14f\n'
262                                       % (var_name, j, 
263                                          filename1, var1[j],
264                                          filename2, var2[j]))
265                        break
266                error = True
267
268    #####
269    # close files and signal OK or ERROR
270    #####
271
272    fid1.close()
273    fid2.close()
274
275    if error:
276        raise RuntimeError, error_msg
277
278    return
279
280
281##
282# @brief Return a usage string.
283def usage():
284    result = []
285    a = result.append
286    a('Usage: %s <options> <file1> <file2>\n' % ProgName)
287    a('where <options> is zero or more of:\n')
288    a('                   -h        print this help\n')
289    a("                   -a <val>  set absolute threshold of 'equivalent'\n")
290    a("                   -r <val>  set relative threshold of 'equivalent'\n")
291    a('                   -g <arg>  check only global attributes specified\n')
292    a('                             <arg> has the form <globname>[,<globname>[,...]]\n')
293    a('                   -t <arg>  check only timesteps specified\n')
294    a('                             <arg> has the form <starttime>[,<stoptime>[,<step>]]\n')
295    a('                   -v <arg>  check only the named variables\n')
296    a('                             <arg> has the form <varname>[,<varname>[,...]]\n')
297    a('and <file1> and <file2> are two SWW files to compare.\n')
298    a('\n')
299    a('The program exit status is one of:\n')
300    a('   0    the two files are equivalent\n')
301    a('   else the files are not equivalent.')
302    return ''.join(result)
303
304##
305# @brief Print a message to stderr.
306def warn(msg):
307    print >>sys.stderr, msg
308
309
310##
311# @brief
312# @param argv
313# @return The status code the program will exit with.
314def main(argv=None):
315    if argv is None:
316        argv = sys.argv
317
318    try:
319        try:
320            opts, args = getopt.getopt(argv[1:], 'hg:t:v:',
321                                       ['help', 'globals',
322                                        'variables', 'timesteps'])
323        except getopt.error, msg:
324            raise Usage(msg)
325    except Usage, err:
326        print >>sys.stderr, err.msg
327        print >>sys.stderr, "for help use --help"
328        return 2
329
330    # process options
331    globals = None
332    timesteps = None
333    variables = None
334    for opt, arg in opts:
335        if opt in ('-h', '--help'):
336            print usage()
337            sys.exit(0)
338        elif opt in ('-g', '--globals'):
339            globals = arg.split(',')
340        elif opt in ('-t', '--timesteps'):
341            timesteps = arg.split(',')
342        elif opt in ('-v', '--variables'):
343            variables = arg.split(',')
344
345    # process arguments
346    if len(args) != 2:
347        msg = usage()
348        print 'msg=%s' % msg
349        raise Usage(msg)
350
351    try:
352        files_are_the_same(args, globals=globals,
353                           timesteps=timesteps, variables=variables)
354    except RuntimeError, msg:
355         print msg
356         return 10
357
358
359if __name__ == "__main__":
360    global ProgName
361
362    ProgName = os.path.basename(sys.argv[0])
363
364    sys.exit(main())
365
Note: See TracBrowser for help on using the repository browser.