source: trunk/misc/tools/cmpsww/cmpsww.py @ 8523

Last change on this file since 8523 was 8427, checked in by davies, 12 years ago

Adding the trapezoidal channel validation test, and editing the ANUGA manual

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