fpdiff.cc
Go to the documentation of this file.
1// LIC// ====================================================================
2// LIC// This file forms part of oomph-lib, the object-oriented,
3// LIC// multi-physics finite-element library, available
4// LIC// at http://www.oomph-lib.org.
5// LIC//
6// LIC// Copyright (C) 2006-2022 Matthias Heil and Andrew Hazel
7// LIC//
8// LIC// This library is free software; you can redistribute it and/or
9// LIC// modify it under the terms of the GNU Lesser General Public
10// LIC// License as published by the Free Software Foundation; either
11// LIC// version 2.1 of the License, or (at your option) any later version.
12// LIC//
13// LIC// This library is distributed in the hope that it will be useful,
14// LIC// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// LIC// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16// LIC// Lesser General Public License for more details.
17// LIC//
18// LIC// You should have received a copy of the GNU Lesser General Public
19// LIC// License along with this library; if not, write to the Free Software
20// LIC// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21// LIC// 02110-1301 USA.
22// LIC//
23// LIC// The authors may be contacted at oomph-lib@maths.man.ac.uk.
24// LIC//
25// LIC//====================================================================
26
27#include "fpdiff.h"
28#include "gzip_reader.h"
29#include "oomph_definitions.h"
30
31#include <cstdlib>
32#include <cmath>
33#include <fstream>
34#include <sstream>
35#include <string>
36#include <regex>
37#include <vector>
38
39#define CHUNKSIZE 8192
40
41namespace oomph
42{
43 /************************************************************************************
44 * @brief
45 *
46 ************************************************************************************/
48 {
49 Passed = 0,
50 Failed = 1
51 };
52
53 /************************************************************************************
54 * @brief
55 *
56 ************************************************************************************/
57 enum class Type
58 {
59 Number,
60 String
61 };
62
63 /************************************************************************************
64 * @brief
65 *
66 * @param value
67 * @param ending
68 * @return true
69 * @return false
70 ************************************************************************************/
71 inline bool ends_with(std::string const& value, std::string const& ending)
72 {
73 if (ending.size() > value.size())
74 {
75 return false;
76 }
77 return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
78 } // End of ends_with
79
80 /************************************************************************************
81 * @brief Add number copies of the symbol to the input string.
82 *
83 * @param string
84 * @param symbol
85 * @param number
86 * @return std::string
87 ************************************************************************************/
88 inline std::string modify_string_inplace(std::string& text,
89 const std::string& symbol,
90 const unsigned& number)
91 {
92 for (unsigned i = 0; i < number; i++)
93 {
94 text += symbol;
95 }
96 text += " ";
97 return text;
98 } // End of modify_string_inplace
99
100 /************************************************************************************
101 * @brief
102 *
103 * @param str
104 ************************************************************************************/
105 inline std::vector<std::string> split_string(const std::string& text)
106 {
107 std::stringstream text_stream{text};
108 std::string word;
109 std::vector<std::string> words;
110 while (std::getline(text_stream, word, ' '))
111 {
112 words.push_back(word);
113 }
114 return words;
115 } // End of split_string
116
117 /************************************************************************************
118 * @brief
119 *
120 * @param text
121 * @return std::string
122 ************************************************************************************/
123 inline std::string lower(const std::string& text)
124 {
125 std::string text_copy = text;
126 std::transform(text_copy.begin(),
127 text_copy.end(),
128 text_copy.begin(),
129 [](unsigned char c) { return std::tolower(c); });
130 return text_copy;
131 }
132
133 /************************************************************************************
134 * @brief Distinguish between a number and a string:
135 *
136 * Returns integer 1 if the argument is a number,
137 * 2 if the argument is a string.
138 *
139 * @param text
140 * @return true
141 * @return false
142 ************************************************************************************/
143 Type get_type(const std::string& text)
144 {
145 std::regex is_number{
146 "(^[+-]?[0-9]*[.]?[0-9]+$)|(^[+-]?[0-9]+[.]?[0-9]*$)|(^[+-]?[0-9]?"
147 "[.]?[0-9]+[EeDd][+-][0-9]+$)"};
148 if (std::regex_match(text, is_number))
149 {
150 return Type::Number;
151 }
152 return Type::String;
153 } // End of get_type
154
155 /************************************************************************************
156 * @brief
157 *
158 * @param filename
159 * @return std::vector<std::string>
160 ************************************************************************************/
161 std::vector<std::string> gzip_load(const std::string& filename)
162 {
163 if (!ends_with(filename, ".gz"))
164 {
165 throw OomphLibError("File:\n\t" + filename + "\nis not a gzip file!",
168 }
169 return GZipReader(filename).read_all();
170 } // End of gzip_load
171
172
173 /************************************************************************************
174 * @brief
175 *
176 * @param filename
177 * @return std::vector<std::string>
178 ************************************************************************************/
179 std::vector<std::string> load_file(const std::string& filename)
180 {
181 std::string line;
182 std::vector<std::string> file_data;
183
184 if (ends_with(filename, ".gz"))
185 {
186 file_data = std::move(gzip_load(filename));
187 }
188 else
189 {
190 std::ifstream file(filename);
191 while (std::getline(file, line))
192 {
193 file_data.push_back(line);
194 }
195 file.close();
196 }
197 return file_data;
198 } // End of load_file
199
200 /************************************************************************************
201 * @brief
202 *
203 * @param filename1
204 * @param filename2
205 * @param relative_error
206 * @param small
207 * @param outstream
208 * @return int
209 ************************************************************************************/
210 int fpdiff(const std::string& filename1,
211 const std::string& filename2,
212 std::ostream& outstream,
213 const double& relative_error,
214 const double& small)
215 {
216 // Storage for worst case error sizes
217 double max_rel_diff = 0.0;
218 double max_wrong_entry = 0.0;
219
220 // Open the files (if run as a script then open Faileds are handled higher
221 // up by catching the error, otherwise it is the parent program's job to
222 // handle the error and so we shouldn't do anything weird here).
223 auto file1 = load_file(filename1);
224 auto file2 = load_file(filename2);
225
226 // Find the number of lines in each file
227 auto n1 = file1.size();
228 auto n2 = file2.size();
229 auto min_lines = std::min(n1, n2);
230
231 if (n1 < n2)
232 {
233 std::vector<std::string> temp(std::move(file1));
234 file1 = std::move(file2);
235 file2 = std::move(temp);
236 }
237
238 // Counter for the number of errors
239 unsigned long long nerr = 0;
240
241 // Counter for the number of lines
242 unsigned long long count = -1;
243
244 // Counter for the number of lines with errors
245 unsigned long long nline_error = 0;
246
247 // Loop over the lines in file1 (the file with the most lines!)
248 for (const auto& line1 : file1)
249 {
250 // Increase the counter
251 count++;
252
253 // If we've run over the end of the file2, issue a warning and end the
254 // loop
255 if (count >= min_lines)
256 {
257 outstream << "\nWarning: files have different numbers of lines\n"
258 << "\nResults are for first " << count
259 << " lines of both files\n"
260 << std::endl;
261 nerr++;
262 break;
263 }
264
265 // Read the next line from file2
266 auto line2 = file2[count];
267
268 // If the lines are the same, we're done
269 if (line1 == line2) continue;
270
271 // We need to do more work
272
273 // Split each line into its separate fields
274 auto fields1 = split_string(line1);
275 auto fields2 = split_string(line2);
276
277 // Find the number of fields in each line
278 auto n_field1 = fields1.size();
279 auto n_field2 = fields2.size();
280
281 if (n_field1 != n_field2)
282 {
283 outstream << "\n =====> line " << (count + 1)
284 << ": different number of fields\n"
285 << n_field1 << " fields: " << line1 << "\n"
286 << n_field2 << " fields: " << line2 << std::endl;
287 nerr++;
288 continue;
289 }
290
291 // Flag to indicate whether there has been a problem in the field
292 bool encountered_problem = false;
293
294 // Strings that will hold the output data
295 std::string outputline1 = "";
296 std::string outputline2 = "";
297 std::string outputline3 = "";
298
299 // Loop over the fields
300 for (unsigned i = 0; i < n_field1; i++)
301 {
302 // Start by loading the fields into the outputlines (plus whitespace)
303 outputline1 += (fields1[i] + " ");
304 outputline3 += (fields2[i] + " ");
305
306 // Find the lengths of the fields
307 auto length1 = fields1[i].length();
308 auto length2 = fields2[i].length();
309
310 // Pad the shortest field so the lengths are the same
311 auto field_length = length2;
312 if (length1 < length2)
313 {
314 for (unsigned j = 0; j < (length2 - length1); j++)
315 {
316 outputline1 += " ";
317 }
318 }
319 else
320 {
322 for (unsigned j = 0; j < (length1 - length2); j++)
323 {
324 outputline3 += " ";
325 }
326 } // if (length1 < length2)
327
328 // If the fields are identical, we are fine
329 if (fields1[i] == fields2[i])
330 {
331 // Put spaces into the error line
333 continue;
334 }
335
336 // Find the type (numeric or string) of each field
339
340 // If the data-types aren't the same issue an error
341 if ((type1 != type2) || (type1 == Type::String))
342 {
343 std::string symbol = (type1 != type2 ? "*" : "%");
344 encountered_problem = true;
345 nerr++;
346
347 // Put the appropriate symbol into the error line
349 continue;
350 }
351
352 // Convert strings to floating point number
353 auto string_to_double = [](const std::string& text) {
354 return std::stod(
355 std::regex_replace(lower(text), std::regex("d"), "e"));
356 };
357 double x1 = string_to_double(fields1[i]);
358 double x2 = string_to_double(fields2[i]);
359
360 // If both numbers are very small, that's fine
361 if ((std::fabs(x1) <= small) && (std::fabs(x2) <= small))
362 {
363 // Put spaces into the error line
365 }
366 else
367 {
368 // Find the relative difference based on the largest number
369 // Note that this "minimises" the relative error (in some sense)
370 // but means that I don't have to separately trap the cases
371 // when x1, x2 are zero
372 double diff = 100.0 * (std::fabs(x1 - x2) /
373 std::max(std::fabs(x1), std::fabs(x2)));
374
375 // If the relative error is smaller than the tolerance, that's fine
376 if (diff <= relative_error)
377 {
378 // Put spaces into the error line
380 }
381 // Otherwise issue an error
382 else
383 {
384 encountered_problem = true;
385 nerr++;
386
387 // Put the appropriate symbols into the error line
389
390 // Record any changes in the worst case values
391 max_rel_diff = std::max(max_rel_diff, diff);
392 // max_wrong_entry =
393 // std::max({max_wrong_entry, std::fabs(x1), std::fabs(x2)});
394 if (std::fabs(x1) > max_wrong_entry)
395 {
396 max_wrong_entry = std::fabs(x1);
397 }
398 else if (std::fabs(x2) > max_wrong_entry)
399 {
400 max_wrong_entry = std::fabs(x2);
401 }
402 }
403 } // if ((std::fabs(x1) <= small) && (std::fabs(x2) <= small))
404 } // for (unsigned i = 0; i < n_field1; i++)
405
406 // If there has been any sort of error, print it
408 {
409 nline_error++;
410 outstream << "\n =====> line " << (count + 1) << "\n"
411 << outputline1 << "\n"
412 << outputline2 << "\n"
413 << outputline3 << std::endl;
414 }
415 } // for (const auto& line1 : file1)
416
417 std::string border(80, '*');
418 std::cout << border << "\nfpdiff() compared files:"
419 << "\n * " << filename1 << "\n * " << filename2 << std::endl;
420
421 if (nerr > 0)
422 {
423 outstream << "\n In files " << filename1 << " " << filename2
424 << "\n number of lines processed: " << count
425 << "\n number of lines containing errors: " << nline_error
426 << "\n number of errors: " << nerr << " "
427 << "\n largest relative error: " << max_rel_diff << " "
428 << "\n largest abs value of an entry which caused an error: "
429 << max_wrong_entry << " "
430 << "\n========================================================"
431 << "\n Parameters used:"
432 << "\n threshold for numerical zero : " << small
433 << "\n maximum rel. difference [percent] : "
434 << relative_error << "\n Legend: "
435 << "\n ******* means differences in data type "
436 "(string vs number)"
437 << "\n ------- means real data exceeded the relative "
438 "difference maximum"
439 << "\n %%%%%%% means that two strings are different"
440 << "\n========================================================"
441 << "\n\n [FAILED]" << std::endl;
442 std::cout << "Test failed!\n" << border << std::endl;
443 return EXIT_FAILURE;
444 }
445 else
446 {
447 outstream << "\n\n In files " << filename1 << " " << filename2
448 << "\n [OK] for fpdiff.py parameters: - max. rel. error = "
449 << relative_error << " %"
450 << "\n - numerical zero = "
451 << small << std::endl;
452 std::cout << "Test passed!\n" << border << std::endl;
453 return EXIT_SUCCESS;
454 }
455 } // End of fpdiff
456
457 /************************************************************************************
458 * @brief
459 *
460 * @param filename1:
461 * @param filename2:
462 * @param log_file:
463 * @param relative_error:
464 * @param small:
465 * @return int:
466 ************************************************************************************/
467 int fpdiff(const std::string& filename1,
468 const std::string& filename2,
469 const std::string& log_file,
470 const double& relative_error,
471 const double& small)
472 {
473 // File to output fpdiff output to
474 std::ofstream log_stream{log_file};
475
476 // Run test
478
479 // Close file
480 log_stream.close();
481
482 // Return test result
483 return test_status;
484 } // End of fpdiff
485} // namespace oomph
cstr elem_len * i
Definition cfortran.h:603
double size() const
Calculate the size of the element (length, area, volume,...) in Eulerian computational coordinates....
Definition elements.cc:4320
std::vector< std::string > read_all()
An OomphLibError object which should be thrown when an run-time error is encountered....
TAdvectionDiffusionReactionElement<NREAGENT,DIM,NNODE_1D> elements are isoparametric triangular DIM-d...
DRAIG: Change all instances of (SPATIAL_DIM) to (DIM-1).
std::string modify_string_inplace(std::string &text, const std::string &symbol, const unsigned &number)
Definition fpdiff.cc:88
bool ends_with(std::string const &value, std::string const &ending)
Definition fpdiff.cc:71
std::vector< std::string > gzip_load(const std::string &filename)
Definition fpdiff.cc:161
std::vector< std::string > load_file(const std::string &filename)
Definition fpdiff.cc:179
std::string lower(const std::string &text)
Definition fpdiff.cc:123
TestStatus
Definition fpdiff.cc:48
@ Failed
Definition fpdiff.cc:50
@ Passed
Definition fpdiff.cc:49
int fpdiff(const std::string &filename1, const std::string &filename2, std::ostream &outstream, const double &relative_error, const double &small)
Definition fpdiff.cc:210
Type get_type(const std::string &text)
Definition fpdiff.cc:143
std::vector< std::string > split_string(const std::string &text)
Definition fpdiff.cc:105