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 std::cout << "Starting precision testing of the ";
136 std::cout << moduleName << " module ..." << std::endl;
137
138 settings.moduleName = moduleName;
139 wasTerminated = false;
140 }
141
142
146 inline void terminate(bool exit = false) {
147
148 // Ensure all test cases have been executed
149 this->wait_results();
150 // std::lock_guard<std::mutex> lock(estimateMutex);
151
152 unsigned int totalTests = 0;
153 unsigned int failedTests = 0;
154
155 for (const auto& pair : estimateResults) {
156 for (const auto& testCase : pair.second) {
157 totalTests++;
158 failedTests += testCase.failed ? 1 : 0;
159 }
160 }
161
162 for (const auto& pair : equationResults) {
163 for (const auto& testCase : pair.second) {
164 totalTests++;
165 failedTests += testCase.failed ? 1 : 0;
166 }
167 }
168
169
170 // Ensure that an output file is specified
171 if( output->settings.outputToFile &&
172 !output->settings.outputFiles.size() &&
175 !settings.outputFiles.size()) {
176
177 settings.outputFiles = { settings.moduleName + "_results" };
178 }
179
180
181 // Print test results
182 std::vector<std::string> outputFiles;
183
184 // Print estimate results
185 outputFiles = settings.outputFiles;
186 outputFiles.insert(
187 outputFiles.end(),
190 );
191
192 output->print_results(
193 estimateResults, settings.estimateColumns, outputFiles
194 );
195
196
197 // Print equation results
198 outputFiles = settings.outputFiles;
199 outputFiles.insert(
200 outputFiles.end(),
203 );
204
205 output->print_results(
206 equationResults, settings.equationColumns, outputFiles
207 );
208
209
210 // Print overall test results
211 std::cout << "Finished testing " << settings.moduleName << '\n';
212 std::cout << totalTests << " total tests, ";
213 std::cout << failedTests << " failed";
214
215 // Print proportion of failed test, avoiding division by zero
216 if (totalTests > 0) {
217
218 const double percent = (failedTests / (double) totalTests) * 100;
219 std::cout << " (" << std::setprecision(3) << percent << "%)" << std::endl;
220
221 } else {
222 std::cout << "\nNo tests were run!" << std::endl;
223 }
224
225 if(exit) {
226 output->terminate();
227 std::exit(failedTests);
228 }
229
230 wasTerminated = true;
231 }
232
233
238 prec_context(const std::string& moduleName, int argc, const char** argv) {
239 setup(moduleName, argc, argv);
240 }
241
242
245 if (!wasTerminated)
246 terminate();
247 }
248
249
252
253 std::lock_guard<std::mutex> lock(estimateMutex);
254 estimateResults = other.estimateResults;
255 equationResults = other.equationResults;
256 settings = other.settings;
257 output = other.output;
258 random = other.random;
259 }
260
261
264
265 std::lock_guard<std::mutex> lock(estimateMutex);
266 estimateResults = other.estimateResults;
267 equationResults = other.equationResults;
268 settings = other.settings;
269 output = other.output;
270 random = other.random;
271
272 return *this;
273 }
274
275
284 template <
285 typename R, typename ...Args,
286 typename Function1 = std::function<R(Args...)>,
287 typename Function2 = Function1
288 >
289 inline void estimate(
290 const std::string& name,
294
295 // Skip the test case if any tests have been picked
296 // and this one was not picked.
297 if(settings.pickedTests.size())
298 if(settings.pickedTests.find(name) == settings.pickedTests.end())
299 return;
300
301 auto task = [this, name, funcApprox, funcExpected, opt]() {
302
303 // Use the estimator to estimate error integrals.
304 auto res = opt.estimator(funcApprox, funcExpected, opt);
305
306 res.name = name;
307 res.domain = opt.domain;
308 res.tolerance = opt.tolerance;
309 res.quiet = opt.quiet;
310 res.iterations = opt.iterations;
311
312 // Use the fail function to determine whether the test failed.
313 res.failed = opt.fail(res);
314
315 std::lock_guard<std::mutex> lock(estimateMutex);
316 estimateResults[name].push_back(res);
317 };
318
320 estimateThreads.emplace_back(task) : task();
321 }
322
323
337 template <
338 typename R, typename ...Args,
339 typename Function1 = std::function<R(Args...)>,
340 typename Function2 = Function1
341 >
342 inline void estimate(
343 const std::string& name,
346 std::vector<interval> domain,
347 long double tolerance, unsigned int iterations,
348 FailFunction fail,
349 Estimator<R, Args...> estimator,
350 bool quiet = false) {
351
352 estimate_options<R, Args...> opt {};
353 opt.domain = domain;
354 opt.tolerance = tolerance;
355 opt.iterations = iterations;
356 opt.fail = fail;
357 opt.estimator = estimator;
358 opt.quiet = quiet;
359
361 }
362
363
378 inline void estimate(
379 const std::string& name,
382 interval domain,
383 long double tolerance = get_nan(),
384 unsigned int iterations = 0,
386 Estimator<double, double> estimator = estimator::quadrature1D<double>(),
387 bool quiet = false) {
388
389 if (tolerance != tolerance)
390 tolerance = settings.defaultTolerance;
391
392 if (iterations == 0)
393 iterations = settings.defaultIterations;
394
396 opt.domain = { domain };
397 opt.tolerance = tolerance;
398 opt.iterations = iterations;
399 opt.fail = fail;
400 opt.estimator = estimator;
401 opt.quiet = quiet;
402
404 }
405
406
413 template <
414 typename Type, typename Identity = EndoFunction<Type>
415 >
416 inline void identity(
417 const std::string& name,
418 Identity id,
420
421 // Apply the identity function
423 return id(x);
424 };
425
426 // And compare it to the identity
428 return x;
429 };
430
432 }
433
434
442 template <
443 typename Type, typename Involution = EndoFunction<Type>
444 >
445 inline void involution(
446 const std::string& name,
449
450 // Apply the involution two times
452 return invol(invol(x));
453 };
454
455 // And compare it to the identity
457 return x;
458 };
459
461 }
462
463
471 template <
472 typename Type, typename Involution = EndoFunction<Type>
473 >
474 inline void idempotence(
475 const std::string& name,
478
479 // Apply the idem two times
481 return idem(idem(x));
482 };
483
484 // And compare it to the identity
486 return idem(x);
487 };
488
490 }
491
492
504 template <
505 typename InputType, typename OutputType = InputType,
506 typename Homogeneous = std::function<OutputType(InputType)>
507 >
508 inline void homogeneous(
509 const std::string& name,
513
514 // Apply the homogeneous function
515 std::function<OutputType(InputType)> funcApprox =
516 [=](InputType x) -> OutputType {
517 return hom(x);
518 };
519
520 // And compare it to the zero element
521 std::function<OutputType(InputType)> funcExpected =
522 [=](InputType x) -> OutputType {
523 return zero_element;
524 };
525
527 }
528
529
537 template<typename Type = double>
538 inline void equals(
539 const std::string& name,
540 const Type& evaluated, const Type& expected,
542
543 // Skip the test case if any tests have been picked
544 // and this one was not picked.
545 if(settings.pickedTests.size())
546 if(settings.pickedTests.find(name) == settings.pickedTests.end())
547 return;
548
550
551 long double diff = opt.distance(evaluated, expected);
552
553 // Mark the test as failed if the
554 // distance between the two values
555 // is bigger than the tolerance.
556 res.failed = (diff > opt.tolerance);
557
558 res.name = name;
559 res.difference = diff;
560 res.tolerance = opt.tolerance;
561 res.quiet = opt.quiet;
562
563 // Register the result of the equation by name
564 equationResults[name].push_back(res);
565 }
566
567
577 template<typename Type = double>
578 inline void equals(
579 const std::string& name,
580 const Type& evaluated, const Type& expected,
581 long double tolerance,
582 DistanceFunction<Type> distance,
583 bool quiet = false) {
584
586 opt.tolerance = tolerance;
587 opt.distance = distance;
588 opt.quiet = quiet;
589
590 equals(name, evaluated, expected, opt);
591 }
592
593
602 inline void equals(
603 const std::string& name,
604 long double evaluated, long double expected,
605 long double tolerance = get_nan(),
606 bool quiet = false) {
607
608 if (tolerance != tolerance)
609 tolerance = settings.defaultTolerance;
610
611 // Skip the test case if any tests have been picked
612 // and this one was not picked.
613 if(settings.pickedTests.size())
614 if(settings.pickedTests.find(name) == settings.pickedTests.end())
615 return;
616
618
619 long double diff = distance::abs_distance(evaluated, expected);
620
621 // Mark the test as failed if the
622 // distance between the two values
623 // is bigger than the tolerance.
624 res.failed = (diff > tolerance);
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 long double 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=equation_options< Type >())
Test an equivalence up to a tolerance, with the given options (e.g.
Definition prec.h:538
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 estimate(const std::string &name, Function1 funcApprox, Function2 funcExpected, std::vector< interval > domain, long double 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:342
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:474
void equals(const std::string &name, long double evaluated, long double expected, long double tolerance=get_nan(), bool quiet=false)
Test an equivalence up to a tolerance, with the given options (e.g.
Definition prec.h:602
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
void equals(const std::string &name, const Type &evaluated, const Type &expected, long double tolerance, DistanceFunction< Type > distance, bool quiet=false)
Test an equivalence up to a tolerance, with the given options (e.g.
Definition prec.h:578
prec_context(const std::string &moduleName, int argc, const char **argv)
Construct a precision testing context.
Definition prec.h:238
void homogeneous(const std::string &name, Homogeneous hom, const estimate_options< InputType, OutputType > &opt, OutputType zero_element=OutputType(0.0))
Precision testing of an function which is homogeneous over the domain.
Definition prec.h:508
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
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:251
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:289
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:445
prec_context & operator=(const prec_context &other)
Custom assignment operator to avoid copying std::mutex.
Definition prec.h:263
void wait_results()
Wait for all concurrent test cases to finish execution.
Definition prec.h:668
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:416
void terminate(bool exit=false)
Terminate the precision testing module.
Definition prec.h:146
~prec_context()
Destructor for the context, automatically terminates the module.
Definition prec.h:244
void equals(const std::string &name, std::vector< std::array< Type, 2 > > values, long double tolerance=get_nan(), bool quiet=false)
Evaluate multiple pairs of values for equivalence up to the given tolerance (e.g.
Definition prec.h:647
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
void estimate(const std::string &name, EndoFunction< double > funcApprox, EndoFunction< double > funcExpected, interval domain, long double 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:378
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
Default precision estimators.
Default fail functions.
Type abs_distance(Type a, Type b)
Absolute distance between two real values.
Definition distance.h:27
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< long double(Type, Type)> DistanceFunction
Distance function between two elements.
Definition prec_structures.h:71
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
The pseudorandom number generation and sampling module.
Structure holding options for equivalence evaluation.
Definition prec_structures.h:199
long double 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
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
long double defaultTolerance
Default tolerance on max absolute error.
Definition prec.h:44
std::vector< std::string > equationColumns
Default columns to print for equations.
Definition prec.h:59