fpdiff Namespace Reference

Functions

def gettype (a)
 
def stuff (string, symbol, number)
 
def read_file (filename)
 
def fpdiff_helper (filename1, filename2, relative_error, small, outstream, details_stream)
 
def fpdiff (filename1, filename2, relative_error=0.1, small=1e-14, outstream=sys.stdout, details_stream=sys.stdout)
 
def run_as_script (argv)
 

Function Documentation

◆ fpdiff()

def fpdiff.fpdiff (   filename1,
  filename2,
  relative_error = 0.1,
  small = 1e-14,
  outstream = sys.stdout,
  details_stream = sys.stdout 
)
Wrapper for using fpdiff inside python. Has default args and returns a
bool.
276  outstream=sys.stdout, details_stream=sys.stdout):
277  """Wrapper for using fpdiff inside python. Has default args and returns a
278  bool."""
279  ok, max_rel_diff, max_wrong_entry = \
280  fpdiff_helper(filename1, filename2, relative_error, small,
281  outstream, details_stream)
282 
283  return (ok == 0), max_rel_diff, max_wrong_entry
284 
285 
def fpdiff_helper(filename1, filename2, relative_error, small, outstream, details_stream)
Definition: Configuration/fpdiff.py:60

References fpdiff_helper().

◆ fpdiff_helper()

def fpdiff.fpdiff_helper (   filename1,
  filename2,
  relative_error,
  small,
  outstream,
  details_stream 
)
 Calculate the floating-point difference between two data files.
    The idea is to use a looser tolerance than the UNIX diff command,
    so that if two entries have a relative error less than the argument
    relative_error, they are counted as the same.

    Note that the relative error is percentage!
    
    Information on pass/failure is written to outstream. Details on which
    lines failed are written to details_stream. Warning: if run on
    large files the details_stream may be overwhelmingly long.
    
    First return value: 0 if the two files are the same, 1 if they are
    different or 5 if the files cannot be opened.

    Second return value: the maximum relative error.

    Third return value: the largest entry that caused an error
    (i.e. what "small" would need to be set as for there to be no
    differences).
60  outstream, details_stream):
61  """ Calculate the floating-point difference between two data files.
62  The idea is to use a looser tolerance than the UNIX diff command,
63  so that if two entries have a relative error less than the argument
64  relative_error, they are counted as the same.
65 
66  Note that the relative error is percentage!
67 
68  Information on pass/failure is written to outstream. Details on which
69  lines failed are written to details_stream. Warning: if run on
70  large files the details_stream may be overwhelmingly long.
71 
72  First return value: 0 if the two files are the same, 1 if they are
73  different or 5 if the files cannot be opened.
74 
75  Second return value: the maximum relative error.
76 
77  Third return value: the largest entry that caused an error
78  (i.e. what "small" would need to be set as for there to be no
79  differences).
80  """
81 
82  import math
83 
84  # Storage for worst case error sizes
85  max_rel_diff = 0
86  max_wrong_entry = 0
87 
88  # Open the files (if run as a script then open failures are handled higher
89  # up by catching the error, otherwise it is the parent program's job to
90  # handle the error and so we shouldn't do anything weird here).
91  tmpfile1 = read_file(filename1)
92  tmpfile2 = read_file(filename2)
93  # this line cateches the error when an empty selftest gold data file is provided
94  # and will return FAIL (e.g. wrong filename is passed for th eselftest, etc.)
95  for file_length, filename in [(len(tmpfile1), filename1), (len(tmpfile2), filename2)]:
96  if file_length == 0:
97  details_stream.write(f"\nWarning: file {filename} is empty and the test will therefore be treated as a FAIL\n")
98  return 1
99 
100  #Find the number of lines in each file
101  n1 = len(tmpfile1)
102  n2 = len(tmpfile2)
103 
104  #If file1 has more lines than file2, keep order the same
105  if n1 >= n2:
106  file1 = tmpfile1; file2 = tmpfile2; n = n2
107  #Otherwise swap the order of the files
108  else:
109  file1 = tmpfile2; file2 = tmpfile1; n = n1
110 
111  #Counter for the number of errors
112  nerr = 0
113  #Counter for the number of lines
114  count = -1
115  #Counter for the number of lines with errors
116  nline_error = 0
117 
118  #Loop over the lines in file1 (the file with the most lines!)
119  for line1 in file1:
120  #Increase the counter
121  count += 1
122  #If we've run over the end of the file2, issue a warning and end the loop
123  if count >= n:
124  details_stream.write("\nWarning: files have different numbers of lines")
125  details_stream.write("\nResults are for first %d lines of both files\n" % count)
126  nerr += 1
127  break
128  #Read the next line from file2
129  line2 = file2[count]
130 
131  #If the lines are the same, we're done
132  if(line1 == line2):
133  continue
134  #If not need to do more work
135  else:
136  #Split each line into its separate fields
137  fields1 = line1.split(); fields2 = line2.split()
138  #Find the number of fields in each line
139  nfields1 = len(fields1); nfields2 = len(fields2)
140 
141  #If the number of fields is not the same, report it as an error
142  if nfields1 != nfields2:
143  details_stream.write("\n =====> line %d: different number of fields\n" \
144  % (count+1))
145  details_stream.write("%s fields: %s" % (nfields1, line1))
146  details_stream.write("%s fields: %s" % (nfields2, line2))
147  nerr += 1
148  continue
149 
150  #Otherwise, we now compare field by field
151  else:
152  #Flag to indicate whether there has been a problem in the field
153  problem = 0
154  #Strings that will hold the output data
155  outputline1 = ""; outputline2 = ""; outputline3 = ""
156 
157  #Loop over the fields
158  for i in range(nfields1):
159  #Start by loading the fields into the outputlines (plus whitespace)
160  outputline1 += fields1[i] + " "; outputline3 += fields2[i] + " "
161 
162  #Find the lengths of the fields
163  length1 = len(fields1[i]); length2 = len(fields2[i])
164 
165  #Pad the shortest field so the lengths are the same
166  if length1 < length2:
167  fieldlength = length2
168  for j in range(length2-length1):
169  outputline1 += " "
170  else:
171  fieldlength = length1
172  for j in range(length1 - length2):
173  outputline3 += " "
174 
175  #If the fields are identical, we are fine
176  if fields1[i] == fields2[i]:
177  #Put spaces into the error line
178  outputline2 = stuff(outputline2," ",fieldlength)
179  #Otherwise time for yet more analysis
180  else:
181  #Find the type (numeric or string) of each field
182  type1 = gettype(fields1[i]); type2 = gettype(fields2[i])
183 
184  #If the data-types aren't the same issue an error
185  if type1 != type2:
186  problem = 1
187  nerr += 1
188  #Put the appropriate symbol into the error line
189  outputline2 = stuff(outputline2,"*",fieldlength)
190  #Otherwise more analysis
191  #If the types are both strings then report the error
192  elif type1 == 2:
193  problem = 1
194  nerr += 1
195  #Put the appropriate symbol into the error line
196  outputline2 = stuff(outputline2,"%",fieldlength)
197  else:
198  #Convert strings to floating point number
199  x1 = float(fields1[i].lower().replace("d","e"))
200  x2 = float(fields2[i].lower().replace("d","e"))
201 
202  #If both numbers are very small, that's fine
203  if math.fabs(x1) <= small and math.fabs(x2) <= small:
204  #Put spaces into the error line
205  outputline2 = stuff(outputline2," ",fieldlength)
206  else:
207  #Find the relative difference based on the largest number
208  #Note that this "minimises" the relative error (in some sense)
209  #but means that I don't have to separately trap the cases
210  #when x1, x2 are zero
211  if math.fabs(x1) > math.fabs(x2) :
212  diff = 100.0*(math.fabs(x1 - x2) / math.fabs(x1))
213  else:
214  diff = 100.0*(math.fabs(x1 - x2) / math.fabs(x2))
215 
216  #If the relative error is smaller than the tolerance, that's fine
217  if diff <= relative_error:
218  #Put spaces into the error line
219  outputline2 = stuff(outputline2," ",fieldlength)
220  #Otherwise issue an error
221  else:
222  problem = 1
223  nerr += 1
224  #Put the appropriate symbols into the error line
225  outputline2 = stuff(outputline2,"-",fieldlength)
226 
227  # Record any changes in the worst case values
228  if diff > max_rel_diff:
229  max_rel_diff = diff
230 
231  if math.fabs(x1) > max_wrong_entry:
232  max_wrong_entry = math.fabs(x1)
233  elif math.fabs(x2) > max_wrong_entry:
234  max_wrong_entry = math.fabs(x2)
235 
236  #If there has been any sort of error, print it
237  if problem == 1:
238  nline_error += 1
239  details_stream.write("\n =====> line %d\n" % (count+1))
240  details_stream.write("%s\n%s\n%s\n" % (outputline1, outputline2, outputline3))
241 
242  #Final print out, once loop over lines is complete
243  if nerr > 0:
244  outstream.write("\n In files %s %s" % (filename1, filename2))
245  outstream.write("\n number of lines processed: %d" % count)
246  outstream.write("\n number of lines containing errors: %d" % nline_error)
247  outstream.write("\n number of errors: %d " % nerr)
248  outstream.write("\n largest relative error: %g " % max_rel_diff)
249  outstream.write("\n largest abs value of an entry which caused an error: %g "
250  % max_wrong_entry)
251  outstream.write("\n========================================================")
252  outstream.write("\n Parameters used:")
253  outstream.write("\n threshold for numerical zero : %g" % small)
254  outstream.write("\n maximum rel. difference [percent] : %g" % relative_error)
255  outstream.write("\n Legend: ")
256  outstream.write("\n ******* means differences in data type (string vs number)")
257  outstream.write("\n ------- means real data exceeded the relative difference maximum")
258  outstream.write("\n %%%%%%% means that two strings are different")
259  outstream.write("\n========================================================")
260  outstream.write("\n\n [FAILED]\n")
261  # Return failure
262  return 2, max_rel_diff, max_wrong_entry
263 
264  else:
265  outstream.write("\n\n In files %s %s" % (filename1, filename2))
266  outstream.write(\
267  "\n [OK] for fpdiff.py parameters: - max. rel. error = %g " % relative_error)
268  outstream.write("%")
269  outstream.write(\
270  "\n - numerical zero = %g\n" % small)
271  # Return success
272  return 0, max_rel_diff, max_wrong_entry
273 
274 
if(UPLO(*uplo)==INVALID) info
Definition: level3_impl.h:428
def stuff(string, symbol, number)
Definition: Configuration/fpdiff.py:26
def gettype(a)
Definition: Configuration/fpdiff.py:13
def read_file(filename)
Definition: Configuration/fpdiff.py:36
std::string lower(std::string s)
returns the input string after converting upper-case characters to lower case
Definition: StringHelpers.cc:11

References gettype(), if(), helpers.lower(), read_file(), and stuff().

Referenced by fpdiff(), and run_as_script().

◆ gettype()

def fpdiff.gettype (   a)
 Distinguish between a number and a string:
    
    Returns integer 1 if the argument is a number,
                    2 if the argument is a string.
13 def gettype(a):
14  """ Distinguish between a number and a string:
15 
16  Returns integer 1 if the argument is a number,
17  2 if the argument is a string.
18  """
19  import re
20  type1 = re.compile("(^[+-]?[0-9]*[.]?[0-9]+$)|(^[+-]?[0-9]+[.]?[0-9]*$)|(^[+-]?[0-9]?[.]?[0-9]+[EeDd][+-][0-9]+$)")
21  if type1.match(a):
22  return 1
23  else:
24  return 2
25 

Referenced by fpdiff_helper().

◆ read_file()

def fpdiff.read_file (   filename)
 Read file into a list of strings, uncluding direct reading of ".gz" files
Different operations required for gzip files in Python 3 because they are
read as binary (rather than text) files
36 def read_file(filename):
37  """ Read file into a list of strings, uncluding direct reading of ".gz" files
38  Different operations required for gzip files in Python 3 because they are
39  read as binary (rather than text) files
40  """
41  import gzip
42 
43  #If the first file is a gzipped file, open it via the gzip module
44  if(filename.find(".gz") != -1):
45  F=gzip.open(filename)
46  filedata = [l.decode() for l in F.readlines() ]
47  F.close()
48  #Otherwise open as normal
49  else:
50  F=open(filename);
51  filedata = F.readlines();
52  F.close()
53 
54  if ".restart" in filename:
55  filedata = filedata[1:]
56 
57  return filedata
58 

References if().

Referenced by fpdiff_helper().

◆ run_as_script()

def fpdiff.run_as_script (   argv)
Run fpdiff as a script (handles argument parsing, output as error codes
   and some helpful messages).
286 def run_as_script(argv):
287  """Run fpdiff as a script (handles argument parsing, output as error codes
288  and some helpful messages).
289  """
290  # Note that we shouldn't just put this code this under 'if __name__ ==
291  # "__main__":' because variables created there are global. This resulted
292  # in some bugs before.
293 
294  #Set the defaults
295  maxreld = 1.0e-1 # max relative difference in percent
296  small = 1.0e-14 # small number -- essentially round-off error
297 
298  #Remove the program name from the front of the argument list
299  argv.pop(0)
300 
301  #Let's find the number of command line arguments
302  narg = len(argv)
303 
304  #If we're out of range, issue a usage message
305  if narg < 2 or narg > 4:
306  sys.stdout.write("\n ********* ERROR **********\n")
307  sys.stdout.write("\nMust specify 2, 3 or 4 keywords on the command line. ")
308  sys.stdout.write("You have specified %d" % narg)
309  sys.stdout.write("\n Proper usage: ")
310  sys.stdout.write("\n fpdiff file1 file2 [max_rel_diff_percent] [small]\n")
311  sys.stdout.write("\n ********* PROGRAM TERMINATING ***********")
312  sys.stdout.write("\n [FAILED] \n")
313  sys.exit(4)
314 
315  #Read any optional arguments
316  if narg >= 3:
317  maxreld = float(argv[2])
318  if narg == 4:
319  small = float(argv[3])
320 
321  # Run the diff
322  try:
323  error_code, _, _ = fpdiff_helper(argv[0], argv[1], maxreld, small,
324  sys.stdout, sys.stdout)
325 
326  # If there has been an IO error then fail with a useful message
327  except IOError:
328 
329  # Get the exception that was raised
330  _, err, _ = sys.exc_info()
331  # We have to get exceptions manually using this function instead of the
332  # usual `except IOError as err:` or `except IOError, err:` for
333  # compatibility with both the ancient version of python on the wulfling
334  # and python3+.
335 
336  # Write the message
337  sys.stderr.write("\n [FAILED] I/O error(%d): %s \"%s\"\n"
338  % (err.errno, err.strerror, err.filename))
339 
340  return 5
341 
342  return error_code
343 
344 
345 
346 
347 # What to do if this is run as a script, rather than loaded as a module
def run_as_script(argv)
Definition: Configuration/fpdiff.py:286

References fpdiff_helper().

◆ stuff()

def fpdiff.stuff (   string,
  symbol,
  number 
)
 Add number copies of symbol to  string

    Returns modified string.
26 def stuff(string,symbol,number):
27  """ Add number copies of symbol to string
28 
29  Returns modified string.
30  """
31  for i in range(number):
32  string += symbol
33  string += " "
34  return string
35 

Referenced by fpdiff_helper().