JayBeams  0.1
Another project to have fun coding.
check_close_enough.hpp
Go to the documentation of this file.
1 #ifndef jb_testing_check_close_enough_hpp
2 #define jb_testing_check_close_enough_hpp
3 
4 #include <boost/multi_array.hpp>
5 #include <boost/test/unit_test.hpp>
6 #include <cmath>
7 #include <complex>
8 #include <limits>
9 #include <type_traits>
10 
11 namespace jb {
12 namespace testing {
13 
14 #ifndef JB_TESTING_MAX_DIFFERENCES_PRINTED
15 #define JB_TESTING_MAX_DIFFERENCES_PRINTED 8
16 #endif // JB_TESTING_MAX_DIFFERENCES_PRINTED
17 
18 /**
19  * Given two numbers of the same integer type check if the difference is within
20  * a given tolerance.
21  *
22  * Tolerance is expressed as the difference between any pair of integer numbers.
23  *
24  * @return true if the numbers are within tolerance.
25  *
26  * @param num_a the first number to compare
27  * @param num_b the second number to compare
28  * @param tol tolerance that the pair of numbers have to be within
29  * @tparam value_t the type of the integral argument numbers
30  */
31 template <
32  typename value_t,
33  typename std::enable_if<std::is_integral<value_t>::value>::type* = nullptr>
34 bool check_close_enough(value_t num_a, value_t num_b, int tol) {
35  using value_type = value_t;
36  value_type value_tol = static_cast<value_type>(tol);
37  if (num_a == num_b) {
38  return true;
39  }
40  if ((num_a < num_b - value_tol) or (num_a > num_b + value_tol)) {
41  return false;
42  }
43  return true;
44 }
45 
46 /**
47  * Given two numbers of the same floating-point type check if the difference is
48  * within
49  * a given tolerance.
50  *
51  * Tolerance is expressed in "number of epsilons" (i.e., the function will
52  * tolerate errors up to tolerance * std::numeric_limit<real>::epsilon()
53  *
54  * @return true if the numbers are within tolerance.
55  *
56  * @param num_a the first number to compare
57  * @param num_b the second number to compare
58  * @param tol tolerance that the pair of numbers have to be within
59  * @tparam value_t the type of the floating-point argument numbers
60  */
61 template <
62  typename value_t,
63  typename std::enable_if<std::is_floating_point<value_t>::value>::type* =
64  nullptr>
65 bool check_close_enough(value_t num_a, value_t num_b, int tol) {
66  using value_type = value_t;
67  value_type const eps = tol * std::numeric_limits<value_type>::epsilon();
68  if (std::abs(num_a) <= eps) {
69  return std::abs(num_b) <= eps;
70  }
71  return std::abs((num_a - num_b) / num_b) <= eps;
72 }
73 
74 /**
75  * Given two complex numbers of the same value type check if the difference is
76  * within
77  * a given tolerance.
78  *
79  * If value types are floating-point numbers, tolerance is expressed
80  * in "number of epsilons" (i.e., the function will tolerate errors up to
81  * tolerance * std::numeric_limit<real>::epsilon()
82  *
83  * If value types are integer numbers, tolerance is expressed as the
84  * difference between any pair of integer numbers.
85  *
86  * @return true if the both the real and imaginary parts are within
87  * tolerance
88  *
89  * @param com_a the first complex number to compare
90  * @param com_b the second complex number to compare
91  * @param tol tolerance that the pair of numbers have to be within
92  * @tparam value_type the type of std::complex number
93  */
94 template <typename value_t>
96  std::complex<value_t> com_a, std::complex<value_t> com_b, int tol = 1) {
97  return (
98  check_close_enough(com_a.real(), com_b.real(), tol) and
99  check_close_enough(com_a.imag(), com_b.imag(), tol));
100 }
101 
102 /**
103  * Compare complex number (as arrays of size 2) values with some
104  * tolerance.
105  *
106  * @return true if the both the real and imaginary parts are within
107  * tolerance.
108  *
109  * @param com_a the first complex number to compare
110  * @param com_b the second complex number to compare
111  * @param tol tolerance that the pair of numbers have to be within
112  * @tparam value_type the type of std::complex number
113  */
114 template <typename value_t>
115 bool check_close_enough(value_t com_a[2], value_t com_b[2], int tol = 1) {
116  return (
117  check_close_enough(com_a[0], com_b[0], tol) and
118  check_close_enough(com_a[1], com_b[1], tol));
119 }
120 
121 /**
122  * Verify that a floating point value is "close enough" to a small number.
123  */
124 template <typename floating>
125 void check_small(floating t, double small) {
126  BOOST_CHECK_SMALL(t, floating(small));
127 }
128 
129 /**
130  * Verify that a complex number is "close enough" to a small number.
131  */
132 template <typename real>
133 void check_small(std::complex<real> t, double small) {
134  BOOST_CHECK_SMALL(t.real(), real(small));
135  BOOST_CHECK_SMALL(t.imag(), real(small));
136 }
137 
138 /// Wrap FFTW-style complex numbers in std::complex for iostreaming
139 template <typename precision_t>
140 std::complex<precision_t> format(precision_t v[2]) {
141  return std::complex<precision_t>(v[0], v[1]);
142 }
143 
144 /// Allow generic treatment of FFTW-style complex numbers and other types.
145 template <typename value_type>
146 value_type format(value_type t) {
147  return t;
148 }
149 
150 /**
151  * Calculate the relative error between two float point numbers.
152  *
153  * @return the relative error of actual compared to offset.
154  *
155  * @param actual the number the test got
156  * @param expected the number the test expected
157  *
158  * @tparam real the type of floating point number (float, double, long double)
159  */
160 template <
161  typename real, typename std::enable_if<
162  std::is_floating_point<real>::value>::type* = nullptr>
163 real relative_error(real actual, real expected) {
164  if (std::numeric_limits<real>::is_integer) {
165  return std::abs(actual - expected);
166  }
167  if (std::abs(expected) < std::numeric_limits<real>::epsilon()) {
168  return std::abs(actual);
169  }
170  return std::abs((actual - expected) / expected);
171 }
172 
173 /**
174  * Adapt relative_error() for integral numbers.
175  *
176  * @return the absolute difference.
177  *
178  * @param actual the number the test got
179  * @param expected the number the test expected
180  *
181  * @tparam integral the type of integral number
182  */
183 template <
184  typename integral,
185  typename std::enable_if<std::is_integral<integral>::value>::type* = nullptr>
186 integral relative_error(integral actual, integral expected) {
187  if (actual > expected) {
188  return actual - expected;
189  }
190  return expected - actual;
191 }
192 
193 /**
194  * Calculate the relative error between two complex numbers.
195  *
196  * @return the relative error of actual compared to offset, using the
197  * Manhattan metric.
198  *
199  * @param actual the number the test got
200  * @param expected the number the test expected
201  *
202  * @tparam real the type of floating point number (float, double, long double)
203  */
204 template <typename real>
205 real relative_error(std::complex<real> actual, std::complex<real> expected) {
206  return std::max(
207  relative_error(actual.real(), expected.real()),
208  relative_error(actual.imag(), expected.imag()));
209 }
210 
211 /**
212  * Calculate the relative error between two complex numbers in FFTW
213  * representation.
214  *
215  * @return the relative error of actual compared to offset, using the
216  * Manhattan metric.
217  *
218  * @param actual the number the test got
219  * @param expected the number the test expected
220  *
221  * @tparam real the type of floating point number (float, double, long double)
222  */
223 template <typename real>
224 real relative_error(real actual[2], real expected[2]) {
225  return std::max(
226  relative_error(actual[0], expected[0]),
227  relative_error(actual[1], expected[1]));
228 }
229 
230 /**
231  * Given two collections of numbers of the same value type, find the differences
232  * that are out of a given tolerance and report them via Boost.Test functions.
233  *
234  * Collections of numbers are any container representation (e.g. vector, deque),
235  * boost::multi_array, c-like array, etc.
236  *
237  * Value types are integers, floating-point, and std::complex (integers or
238  * floating-point).
239  *
240  * If value types are floating-point numbers, tolerance is expressed
241  * in "number of epsilons" (i.e., the function will tolerate errors up to
242  * tolerance * std::numeric_limit<real>::epsilon()
243  *
244  * If value types are integer numbers, tolerance is expressed as the
245  * difference between any pair of integer numbers.
246  *
247  * @return true if the numbers are within tolerance.
248  *
249  * @param a the first collection of numbers to compare
250  * @param b the second collection of numbers to compare
251  * @param tol tolerance that each pair of numbers have to be within
252  * @param max_differences_printed how many differences will be printed
253  * out in full detail, some of the collections are large and printing
254  * all the differences can be overwhelming
255  * @tparam collection_t the type of the collection containing the numbers
256  * (e.g. std::vector<>, boost::multi_array)
257  */
258 template <typename collection_t>
260  collection_t const& a, collection_t const& b, int tol = 1,
261  int max_differences_printed = JB_TESTING_MAX_DIFFERENCES_PRINTED) {
262  BOOST_CHECK_EQUAL(a.size(), b.size());
263  if (a.size() != b.size()) {
264  return false;
265  }
266 
267  int count = 0;
268  for (std::size_t i = 0; i != a.size(); ++i) {
269  if (check_close_enough(a[i], b[i], tol)) {
270  continue;
271  }
272  if (++count <= max_differences_printed) {
273  auto error = relative_error(a[i], b[i]);
274  BOOST_ERROR(
275  "in item i=" << i << " difference higher than tolerance=" << tol
276  << ", actual[i]=" << format(a[i]) << ", expected[i]="
277  << format(b[i]) << ", error=" << error);
278  }
279  }
280  return count == 0;
281 }
282 
283 /**
284  * Specialization for boost::multi_array type
285  *
286  * @return true if the numbers are within tolerance.
287  *
288  * @param a the first multi array of numbers to compare
289  * @param b the second multi array of numbers to compare
290  * @param tol tolerance that each pair of numbers have to be within
291  * @param max_differences_printed how many differences will be printed
292  * out in full detail, some of the collections are large and printing
293  * all the differences can be overwhelming
294  * @tparam T type of the value stored on the timeseries
295  * @tparam K timeseries dimensionality
296  * @tparam A an Allocator type for type T allocator storage
297  */
298 template <typename T, std::size_t K>
300  boost::multi_array<T, K> const& a, boost::multi_array<T, K> const& b,
301  int tol = 1,
302  int max_differences_printed = JB_TESTING_MAX_DIFFERENCES_PRINTED) {
303  BOOST_CHECK_EQUAL(a.num_elements(), b.num_elements());
304  if (a.num_elements() != b.num_elements()) {
305  return false;
306  }
307 
308  int count = 0;
309  for (std::size_t i = 0; i != a.num_elements(); ++i) {
310  if (check_close_enough(a.data()[i], b.data()[i], tol)) {
311  continue;
312  }
313  if (++count <= max_differences_printed) {
314  auto error = relative_error(a.data()[i], b.data()[i]);
315  BOOST_ERROR(
316  "in item i=" << i << " difference higher than tolerance=" << tol
317  << ", actual[i]=" << format(a.data()[i])
318  << ", expected[i]=" << format(b.data()[i])
319  << ", error=" << error);
320  }
321  }
322  return count == 0;
323 }
324 
325 /**
326  * Given two collections of integers, floating point or complex numbers
327  * find the differences that are out of a given tolerance and report them
328  * via Boost.Test functions.
329  *
330  * @return true if the numbers are within tolerance.
331  *
332  * @param size the size of collection
333  * @param a the first multi array of numbers to compare
334  * @param b the second multi array of numbers to compare
335  * @param tol tolerance that each pair of numbers have to be within
336  * @param max_differences_printed how many differences will be printed
337  * out in full detail, some of the collections are large and printing
338  * all the differences can be overwhelming
339  * @tparam value_t value type of the number stored on collections
340  */
341 template <typename value_t>
343  std::size_t size, value_t const* a, value_t const* b, int tol = 1,
344  int max_differences_printed = JB_TESTING_MAX_DIFFERENCES_PRINTED) {
345  int count = 0;
346  for (std::size_t i = 0; i != size; ++i) {
347  if (check_close_enough(a[i], b[i], tol)) {
348  continue;
349  }
350  if (++count <= max_differences_printed) {
351  auto error = relative_error(a[i], b[i]);
352  BOOST_ERROR(
353  "in item i=" << i << " difference higher than tolerance=" << tol
354  << ", a[i]=" << format(a[i]) << ", b[i]=" << format(b[i])
355  << ", error=" << error);
356  }
357  }
358  return count == 0;
359 }
360 
361 } // namespace testing
362 } // namespace jb
363 
364 #endif // jb_testing_check_close_enough_hpp
real relative_error(real actual, real expected)
Calculate the relative error between two float point numbers.
void check_small(floating t, double small)
Verify that a floating point value is "close enough" to a small number.
std::complex< precision_t > format(precision_t v[2])
Wrap FFTW-style complex numbers in std::complex for iostreaming.
bool check_collection_close_enough(collection_t const &a, collection_t const &b, int tol=1, int max_differences_printed=JB_TESTING_MAX_DIFFERENCES_PRINTED)
Given two collections of numbers of the same value type, find the differences that are out of a given...
#define JB_TESTING_MAX_DIFFERENCES_PRINTED
The top-level namespace for the JayBeams library.
Definition: as_hhmmss.hpp:7
bool check_close_enough(value_t num_a, value_t num_b, int tol)
Given two numbers of the same integer type check if the difference is within a given tolerance...