Chebyshev
Unit testing for scientific software
Loading...
Searching...
No Matches
prec.h
Go to the documentation of this file.
1
5
6#ifndef CHEBYSHEV_PREC_H
7#define CHEBYSHEV_PREC_H
8
9#include <string>
10#include <vector>
11#include <map>
12#include <iostream>
13#include <memory>
14#include <mutex>
15#include <thread>
16
18#include "./core/output.h"
19#include "./core/random.h"
20#include "./prec/estimator.h"
21#include "./prec/fail.h"
22
23
24namespace chebyshev {
25
27namespace prec {
28
29
33
35 std::string moduleName = "unknown";
36
39
42
45
47 std::vector<std::string> outputFiles {};
48
50 std::vector<std::string> estimateColumns = {
51 "name", "meanErr", "rmsErr", "maxErr", "failed"
52 };
53
56 std::vector<std::string> estimateOutputFiles {};
57
59 std::vector<std::string> equationColumns = {
60 "name", "difference", "tolerance", "failed"
61 };
62
65 std::vector<std::string> equationOutputFiles {};
66
70 std::map<std::string, bool> pickedTests {};
71
77 bool multithreading = true;
78
79 };
80
81
85 private:
86
88 std::map<std::string, std::vector<estimate_result>> estimateResults {};
89
91 std::mutex estimateMutex;
92
94 std::vector<std::thread> estimateThreads;
95
97 std::map<std::string, std::vector<equation_result>> equationResults {};
98
100 bool wasTerminated {false};
101
102 public:
103
106
109 std::shared_ptr<output::output_context> output;
110
113 std::shared_ptr<random::random_context> random;
114
115
121 inline void setup(
122 const std::string& moduleName,
123 int argc = 0, const char** argv = nullptr) {
124
125 // Initialize other modules
127 output = std::make_shared<output::output_context>();
128 random = std::make_shared<random::random_context>();
129
130 // Initialize list of picked tests
131 if(argc && argv)
132 for (int i = 1; i < argc; ++i)
133 settings.pickedTests[argv[i]] = true;
134
135 output->info("Starting precision testing of the " + moduleName + " module ...");
136
137 settings.moduleName = moduleName;
138 wasTerminated = false;
139 }
140
141
145 inline void terminate(bool exit = false) {
146
147 // Ensure all test cases have been executed
148 this->wait_results();
149 // std::lock_guard<std::mutex> lock(estimateMutex);
150
151 unsigned int totalTests = 0;
152 unsigned int failedTests = 0;
153
154 for (const auto& pair : estimateResults) {
155 for (const auto& testCase : pair.second) {
156 totalTests++;
157 failedTests += testCase.failed ? 1 : 0;
158 }
159 }
160
161 for (const auto& pair : equationResults) {
162 for (const auto& testCase : pair.second) {
163 totalTests++;
164 failedTests += testCase.failed ? 1 : 0;
165 }
166 }
167
168
169 // Ensure that an output file is specified
170 if( output->settings.outputToFile &&
171 !output->settings.outputFiles.size() &&
174 !settings.outputFiles.size()) {
175
176 settings.outputFiles = { settings.moduleName + "_results" };
177 }
178
179
180 // Print test results
181 std::vector<std::string> outputFiles;
182
183 // Print estimate results
184 outputFiles = settings.outputFiles;
185 outputFiles.insert(
186 outputFiles.end(),
189 );
190
191 output->print_results(
192 estimateResults, settings.estimateColumns, outputFiles
193 );
194
195
196 // Print equation results
197 outputFiles = settings.outputFiles;
198 outputFiles.insert(
199 outputFiles.end(),
202 );
203
204 output->print_results(
205 equationResults, settings.equationColumns, outputFiles
206 );
207
208
209 // Print overall test results
210 double percent = totalTests > 0 ? (failedTests / (double) totalTests) * 100 : 0;
211
212 output->info("Finished testing " + settings.moduleName);
213 output->info(
214 std::to_string(totalTests) + " total tests, " + std::to_string(failedTests) +
215 " failed (" + std::to_string(percent).substr(0, 4) + "%)"
216 );
217
218 if (totalTests == 0)
219 output->warn("No tests were executed!");
220
221 if(exit) {
222 output->terminate();
223 std::exit(failedTests);
224 }
225
226 wasTerminated = true;
227 }
228
229
234 prec_context(const std::string& moduleName, int argc, const char** argv) {
235 setup(moduleName, argc, argv);
236 }
237
238
241 if (!wasTerminated)
242 terminate();
243 }
244
245
248
249 std::lock_guard<std::mutex> lock(estimateMutex);
250 estimateResults = other.estimateResults;
251 equationResults = other.equationResults;
252 settings = other.settings;
253 output = other.output;
254 random = other.random;
255 }
256
257
260
261 std::lock_guard<std::mutex> lock(estimateMutex);
262 estimateResults = other.estimateResults;
263 equationResults = other.equationResults;
264 settings = other.settings;
265 output = other.output;
266 random = other.random;
267
268 return *this;
269 }
270
271
280 template <
281 typename R, typename ...Args,
282 typename Function1 = std::function<R(Args...)>,
283 typename Function2 = Function1
284 >
285 inline void estimate(
286 const std::string& name,
290
291 // Skip the test case if any tests have been picked
292 // and this one was not picked.
293 if(settings.pickedTests.size())
294 if(settings.pickedTests.find(name) == settings.pickedTests.end())
295 return;
296
297 output->debug("Running estimate case: " + name);
298
299 auto task = [this, name, funcApprox, funcExpected, opt]() {
300
301 // Use the estimator to estimate error integrals.
302 auto res = opt.estimator(funcApprox, funcExpected, opt);
303
304 res.name = name;
305 res.domain = opt.domain;
306 res.tolerance = opt.tolerance;
307 res.quiet = opt.quiet;
308 res.iterations = opt.iterations;
309
310 // Use the fail function to determine whether the test failed.
311 res.failed = opt.fail(res);
312
313 std::lock_guard<std::mutex> lock(estimateMutex);
314 estimateResults[name].push_back(res);
315 };
316
318 estimateThreads.emplace_back(task) : task();
319 }
320
321
335 template <
336 typename R, typename ...Args,
337 typename Function1 = std::function<R(Args...)>,
338 typename Function2 = Function1
339 >
340 inline void estimate(
341 const std::string& name,
344 std::vector<interval> domain,
345 prec_t tolerance, unsigned int iterations,
346 FailFunction fail,
347 Estimator<R, Args...> estimator,
348 bool quiet = false) {
349
350 estimate_options<R, Args...> opt {};
351 opt.domain = domain;
352 opt.tolerance = tolerance;
353 opt.iterations = iterations;
354 opt.fail = fail;
355 opt.estimator = estimator;
356 opt.quiet = quiet;
357
359 }
360
361
376 inline void estimate(
377 const std::string& name,
380 interval domain,
381 prec_t tolerance = get_nan(),
382 unsigned int iterations = 0,
384 Estimator<double, double> estimator = estimator::quadrature1D<double>(),
385 bool quiet = false) {
386
387 if (tolerance != tolerance)
388 tolerance = settings.defaultTolerance;
389
390 if (iterations == 0)
391 iterations = settings.defaultIterations;
392
394 opt.domain = { domain };
395 opt.tolerance = tolerance;
396 opt.iterations = iterations;
397 opt.fail = fail;
398 opt.estimator = estimator;
399 opt.quiet = quiet;
400
402 }
403
404
411 template <
412 typename Type, typename Identity = EndoFunction<Type>
413 >
414 inline void identity(
415 const std::string& name,
416 Identity id,
418
419 // Apply the identity function
421 return id(x);
422 };
423
424 // And compare it to the identity
426 return x;
427 };
428
430 }
431
432
440 template <
441 typename Type, typename Involution = EndoFunction<Type>
442 >
443 inline void involution(
444 const std::string& name,
447
448 // Apply the involution two times
450 return invol(invol(x));
451 };
452
453 // And compare it to the identity
455 return x;
456 };
457
459 }
460
461
469 template <
470 typename Type, typename Involution = EndoFunction<Type>
471 >
472 inline void idempotence(
473 const std::string& name,
476
477 // Apply the idem two times
479 return idem(idem(x));
480 };
481
482 // And compare it to the identity
484 return idem(x);
485 };
486
488 }
489
490
502 template <
503 typename OutputType, typename InputType = OutputType,
504 typename Homogeneous = std::function<OutputType(InputType)>
505 >
506 inline void homogeneous(
507 const std::string& name,
511
512 // Apply the homogeneous function
513 std::function<OutputType(InputType)> funcApprox =
514 [=](InputType x) -> OutputType {
515 return hom(x);
516 };
517
518 // And compare it to the zero element
519 std::function<OutputType(InputType)> funcExpected =
520 [=](InputType x) -> OutputType {
521 return zero_element;
522 };
523
525 }
526
527
535 template<typename Type = double>
536 inline void equals(
537 const std::string& name,
538 const Type& evaluated, const Type& expected,
540
541 // Skip the test case if any tests have been picked
542 // and this one was not picked.
543 if(settings.pickedTests.size())
544 if(settings.pickedTests.find(name) == settings.pickedTests.end())
545 return;
546
547 output->debug("Running equation case: " + name);
548
550 prec_t diff = opt.distance(evaluated, expected);
551
552 // Mark the test as failed if the
553 // distance between the two values
554 // is bigger than the tolerance.
555 res.failed = (diff > opt.tolerance) || (diff != diff);
556
557 res.name = name;
558 res.difference = diff;
559 res.tolerance = opt.tolerance;
560 res.quiet = opt.quiet;
561
562 // Register the result of the equation by name
563 equationResults[name].push_back(res);
564 }
565
566
576 template<typename Type = double>
577 inline void equals(
578 const std::string& name,
579 const Type& evaluated, const Type& expected,
580 prec_t tolerance,
581 DistanceFunction<Type> distance,
582 bool quiet = false) {
583
585 opt.tolerance = tolerance;
586 opt.distance = distance;
587 opt.quiet = quiet;
588
589 equals(name, evaluated, expected, opt);
590 }
591
592
601 inline void equals(
602 const std::string& name,
603 prec_t evaluated, prec_t expected,
604 prec_t tolerance = get_nan(),
605 bool quiet = false) {
606
607 if (tolerance != tolerance)
608 tolerance = settings.defaultTolerance;
609
610 // Skip the test case if any tests have been picked
611 // and this one was not picked.
612 if(settings.pickedTests.size())
613 if(settings.pickedTests.find(name) == settings.pickedTests.end())
614 return;
615
616 output->debug("Running equation case: " + name);
617
619 prec_t diff = distance::absolute(evaluated, expected);
620
621 // Mark the test as failed if the
622 // distance between the two values
623 // is bigger than the tolerance or is NaN.
624 res.failed = (diff > tolerance) || (diff != diff);
625
626 res.name = name;
627 res.difference = diff;
628 res.tolerance = tolerance;
629 res.quiet = quiet;
630
631 res.evaluated = evaluated;
632 res.expected = expected;
633
634 // Register the result of the equation by name
635 equationResults[name].push_back(res);
636 }
637
638
646 template<typename Type>
647 inline void equals(
648 const std::string& name,
649 std::vector<std::array<Type, 2>> values,
650 prec_t tolerance = get_nan(),
651 bool quiet = false) {
652
653 if (tolerance != tolerance)
654 tolerance = settings.defaultTolerance;
655
656 // Skip the test case if any tests have been picked
657 // and this one was not picked.
658 if(settings.pickedTests.size())
659 if(settings.pickedTests.find(name) == settings.pickedTests.end())
660 return;
661
662 for (const auto& v : values)
663 equals(name, v[0], v[1], tolerance, quiet);
664 }
665
666
668 inline void wait_results() {
669
670 for (auto& t : estimateThreads)
671 if (t.joinable())
672 t.join();
673
674 estimateThreads.clear();
675 }
676
677
683 inline std::vector<estimate_result> get_estimate(const std::string& name) {
684
685 this->wait_results();
686
687 return estimateResults[name];
688 }
689
690
696 inline estimate_result get_estimate(const std::string& name, unsigned int i) {
697
698 this->wait_results();
699
700 return estimateResults[name].at(i);
701 }
702
703
705 inline std::vector<equation_result> get_equation(const std::string& name) {
706 return equationResults[name];
707 }
708
709
711 inline equation_result get_equation(const std::string& name, unsigned int i) {
712 return equationResults[name].at(i);
713 }
714
715 };
716
717
723 prec_context make_context(const std::string& moduleName,
724 int argc = 0, const char** argv = nullptr) {
725
726 return prec_context(moduleName, argc, argv);
727 }
728
729
730}}
731
732#endif
Precision testing context, handling precision test cases.
Definition prec.h:84
void equals(const std::string &name, const Type &evaluated, const Type &expected, equation_options< Type > opt)
Test an equivalence up to a tolerance, with the given options (e.g.
Definition prec.h:536
std::shared_ptr< random::random_context > random
Random module settings for the context, dynamically allocated and possibly shared between multiple co...
Definition prec.h:113
void equals(const std::string &name, std::vector< std::array< Type, 2 > > values, prec_t tolerance=get_nan(), bool quiet=false)
Evaluate multiple pairs of values for equivalence up to the given tolerance (e.g.
Definition prec.h:647
void setup(const std::string &moduleName, int argc=0, const char **argv=nullptr)
Setup the precision testing module.
Definition prec.h:121
void idempotence(const std::string &name, Involution idem, const estimate_options< Type, Type > &opt)
Precision testing of an endofunction which is idempotent.
Definition prec.h:472
equation_result get_equation(const std::string &name, unsigned int i)
Get a single result of equation testing by label and index.
Definition prec.h:711
std::vector< equation_result > get_equation(const std::string &name)
Get the results of equation testing by label.
Definition prec.h:705
prec_context(const std::string &moduleName, int argc, const char **argv)
Construct a precision testing context.
Definition prec.h:234
void equals(const std::string &name, prec_t evaluated, prec_t expected, prec_t tolerance=get_nan(), bool quiet=false)
Test an equivalence up to a tolerance, with the given options (e.g.
Definition prec.h:601
estimate_result get_estimate(const std::string &name, unsigned int i)
Get a single result of error estimation by label and index.
Definition prec.h:696
void estimate(const std::string &name, EndoFunction< double > funcApprox, EndoFunction< double > funcExpected, interval domain, prec_t tolerance=get_nan(), unsigned int iterations=0, FailFunction fail=fail::fail_on_max_err(), Estimator< double, double > estimator=estimator::quadrature1D< double >(), bool quiet=false)
Estimate error integrals over a real function of real variable, with respect to an exact function.
Definition prec.h:376
void homogeneous(const std::string &name, Homogeneous hom, const estimate_options< OutputType, InputType > &opt, OutputType zero_element=OutputType(0.0))
Precision testing of an function which is homogeneous over the domain.
Definition prec.h:506
prec_settings settings
Settings for the precision testing context.
Definition prec.h:105
prec_context(const prec_context &other)
Custom copy constructor to avoid copying std::mutex.
Definition prec.h:247
void estimate(const std::string &name, Function1 funcApprox, Function2 funcExpected, estimate_options< R, Args... > opt)
Estimate error integrals over a function with respect to an exact function, with the given options.
Definition prec.h:285
void involution(const std::string &name, Involution invol, const estimate_options< Type, Type > &opt)
Precision testing of an endofunction which is an involution.
Definition prec.h:443
prec_context & operator=(const prec_context &other)
Custom assignment operator to avoid copying std::mutex.
Definition prec.h:259
void wait_results()
Wait for all concurrent test cases to finish execution.
Definition prec.h:668
void estimate(const std::string &name, Function1 funcApprox, Function2 funcExpected, std::vector< interval > domain, prec_t tolerance, unsigned int iterations, FailFunction fail, Estimator< R, Args... > estimator, bool quiet=false)
Estimate error integrals over a function with respect to an exact function.
Definition prec.h:340
void identity(const std::string &name, Identity id, const estimate_options< Type, Type > &opt)
Precision testing of an endofunction which is equivalent to the identity.
Definition prec.h:414
void equals(const std::string &name, const Type &evaluated, const Type &expected, prec_t tolerance, DistanceFunction< Type > distance, bool quiet=false)
Test an equivalence up to a tolerance, with the given options (e.g.
Definition prec.h:577
void terminate(bool exit=false)
Terminate the precision testing module.
Definition prec.h:145
~prec_context()
Destructor for the context, automatically terminates the module.
Definition prec.h:240
std::shared_ptr< output::output_context > output
Output module settings for the context, dynamically allocated and possibly shared between multiple co...
Definition prec.h:109
std::vector< estimate_result > get_estimate(const std::string &name)
Get the results of error estimation by label.
Definition prec.h:683
#define CHEBYSHEV_PREC_ITER
Default number of function evaluations in precision testing.
Definition common.h:12
#define CHEBYSHEV_PREC_TOLERANCE
Default tolerance in precision testing.
Definition common.h:17
long double prec_t
Floating-point type of higher precision, used in computations, such as error estimation.
Definition common.h:42
Default precision estimators.
Default fail functions.
prec_t absolute(Type a, Type b)
Absolute distance between two values which have an ordering with respect to zero.
Definition distance.h:21
auto fail_on_max_err()
Default fail function which marks the test as failed if the maximum error on the domain is bigger tha...
Definition fail.h:34
General namespace of the framework.
Definition benchmark.h:22
constexpr FloatType get_nan()
Get a quiet NaN of the specified floating point type.
Definition common.h:65
std::function< Type(Type)> EndoFunction
An endofunction is a function which has the same type of input and output, such as a real function of...
Definition common.h:60
The output module, with formatting capabilities.
Structures for precision testing.
std::function< bool(const estimate_result &)> FailFunction
A function which determines whether an estimation failed.
Definition prec_structures.h:66
typename estimate_options< R, Args... >::Estimator_t Estimator
Generic precision estimator function signature.
Definition prec_structures.h:162
std::function< prec_t(Type, Type)> DistanceFunction
Distance function between two elements.
Definition prec_structures.h:71
The pseudorandom number generation and sampling module.
Structure holding options for equivalence evaluation.
Definition prec_structures.h:199
prec_t tolerance
Tolerance on the absolute difference.
Definition prec_structures.h:202
A structure holding the result of an evaluation.
Definition prec_structures.h:167
bool failed
Whether the test failed.
Definition prec_structures.h:189
A structure holding the options for precision estimation.
Definition prec_structures.h:77
std::vector< interval > domain
The domain of estimation.
Definition prec_structures.h:86
A structure holding the result of precision estimation.
Definition prec_structures.h:25
An interval on the real numbers.
Definition interval.h:16
Settings for the precision testing module, used in prec_context.
Definition prec.h:32
std::map< std::string, bool > pickedTests
Target tests marked for execution, can be picked by passing test case names by command line (all test...
Definition prec.h:70
prec_t defaultTolerance
Default tolerance on max absolute error.
Definition prec.h:44
bool multithreading
Whether to use multithreading for the execution of accuracy estimation tasks (defaults to true).
Definition prec.h:77
std::string moduleName
Name of the module being tested.
Definition prec.h:35
std::vector< std::string > estimateOutputFiles
The files to write estimate results to (if empty, all results are output to a generic file).
Definition prec.h:56
std::vector< std::string > equationOutputFiles
The files to write equation results to (if empty, all results are output to a generic file).
Definition prec.h:65
FailFunction defaultFailFunction
Default fail function.
Definition prec.h:41
std::vector< std::string > outputFiles
The files to write all precision testing results to.
Definition prec.h:47
unsigned int defaultIterations
Default number of iterations for integral quadrature.
Definition prec.h:38
std::vector< std::string > estimateColumns
Default columns to print for precision estimates.
Definition prec.h:50
std::vector< std::string > equationColumns
Default columns to print for equations.
Definition prec.h:59