clang-tidy: google tests applicable
[gromacs.git] / src / gromacs / simd / tests / simd_math.cpp
blob8fa8733a66def50859d0dfa49271156453572ba0
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2014,2015,2017,2018, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
35 #include "gmxpre.h"
37 #include "gromacs/simd/simd_math.h"
39 #include "config.h"
41 #include <cmath>
42 #include <cstdint>
44 #include <vector>
46 #include "gromacs/math/utilities.h"
47 #include "gromacs/options/basicoptions.h"
48 #include "gromacs/simd/simd.h"
50 #include "simd.h"
52 #if GMX_SIMD
54 namespace gmx
56 namespace test
59 /*! \cond internal */
60 /*! \addtogroup module_simd */
61 /*! \{ */
63 #if GMX_SIMD_HAVE_REAL
65 class SimdMathTest : public SimdTest
67 public:
68 ::testing::AssertionResult
69 compareSimdMathFunction(const char * refFuncExpr,
70 const char * simdFuncExpr,
71 const char * denormalsToZeroExpr,
72 real refFunc(real x),
73 SimdReal gmx_simdcall simdFunc(SimdReal x),
74 bool denormalsToZero);
77 /*! \brief Test approximate equality of SIMD vs reference version of a function.
79 * This macro takes vanilla C and SIMD flavors of a function and tests it with
80 * the number of points, range, and tolerances specified by the test fixture class.
82 #define GMX_EXPECT_SIMD_FUNC_NEAR(refFunc, tstFunc) \
83 EXPECT_PRED_FORMAT3(compareSimdMathFunction, refFunc, tstFunc, false)
85 /*! \brief Test approximate equality of SIMD vs reference function, denormals can be zero.
87 * This macro takes vanilla C and SIMD flavors of a function and tests it with
88 * the number of points, range, and tolerances specified by the test fixture class.
90 * This version of the function will also return success if the test function
91 * returns zero where the reference function returns a denormal value.
93 #define GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(refFunc, tstFunc) \
94 EXPECT_PRED_FORMAT3(compareSimdMathFunction, refFunc, tstFunc, true)
96 /*! \brief Implementation routine to compare SIMD vs reference functions.
98 * \param refFuncExpr Description of reference function expression
99 * \param simdFuncExpr Description of SIMD function expression
100 * \param denormalsToZeroExpr Description of denormal-to-zero setting
101 * \param refFunc Reference math function pointer
102 * \param simdFunc SIMD math function pointer
103 * \param denormalsToZero If true, the function will consider denormal
104 * values equivalent to 0.0.
106 * The function will be tested with the range and tolerances specified in
107 * the SimdBaseTest class. You should not never call this function directly,
108 * but use the macro GMX_EXPECT_SIMD_FUNC_NEAR(refFunc,tstFunc) instead.
110 ::testing::AssertionResult
111 SimdMathTest::compareSimdMathFunction(const char * refFuncExpr,
112 const char * simdFuncExpr,
113 const char * denormalsToZeroExpr,
114 real refFunc(real x),
115 SimdReal gmx_simdcall simdFunc(SimdReal x),
116 bool denormalsToZero)
118 std::vector<real> vx(GMX_SIMD_REAL_WIDTH);
119 std::vector<real> vref(GMX_SIMD_REAL_WIDTH);
120 std::vector<real> vtst(GMX_SIMD_REAL_WIDTH);
121 real dx, absDiff;
122 std::int64_t ulpDiff, maxUlpDiff;
123 real maxUlpDiffPos;
124 real refValMaxUlpDiff, simdValMaxUlpDiff;
125 const int niter = s_nPoints/GMX_SIMD_REAL_WIDTH;
126 int npoints = niter*GMX_SIMD_REAL_WIDTH;
127 # if GMX_DOUBLE
128 union {
129 double r; std::int64_t i;
130 } conv0, conv1;
131 # else
132 union {
133 float r; std::int32_t i;
134 } conv0, conv1;
135 # endif
137 maxUlpDiff = 0;
138 dx = (range_.second-range_.first)/npoints;
140 for (int iter = 0; iter < niter; iter++)
142 for (int i = 0; i < GMX_SIMD_REAL_WIDTH; i++)
144 vx[i] = range_.first+dx*(iter*GMX_SIMD_REAL_WIDTH+i);
145 vref[i] = refFunc(vx[i]);
147 vtst = simdReal2Vector(simdFunc(vector2SimdReal(vx)));
149 bool absOk = true, signOk = true;
150 for (int i = 0; i < GMX_SIMD_REAL_WIDTH; i++)
152 if (denormalsToZero)
154 // Clamp denormal values to zero if requested
155 if (std::abs(vref[i]) <= GMX_REAL_MIN)
157 vref[i] = 0.0;
159 if (std::abs(vtst[i]) <= GMX_REAL_MIN)
161 vtst[i] = 0.0;
165 absDiff = std::abs(vref[i]-vtst[i]);
166 absOk = absOk && ( absDiff < absTol_ );
167 signOk = signOk && ( (vref[i] >= 0 && vtst[i] >= 0) ||
168 (vref[i] <= 0 && vtst[i] <= 0));
170 if (absDiff >= absTol_)
172 /* We replicate the trivial ulp differences comparison here rather than
173 * calling the lower-level routine for comparing them, since this enables
174 * us to run through the entire test range and report the largest deviation
175 * without lots of extra glue routines.
177 conv0.r = vref[i];
178 conv1.r = vtst[i];
179 ulpDiff = llabs(conv0.i-conv1.i);
180 if (ulpDiff > maxUlpDiff)
182 maxUlpDiff = ulpDiff;
183 maxUlpDiffPos = vx[i];
184 refValMaxUlpDiff = vref[i];
185 simdValMaxUlpDiff = vtst[i];
189 if ( (!absOk) && (!signOk) )
191 return ::testing::AssertionFailure()
192 << "Failing SIMD math function comparison due to sign differences." << std::endl
193 << "Reference function: " << refFuncExpr << std::endl
194 << "Simd function: " << simdFuncExpr << std::endl
195 << "Test range is ( " << range_.first << " , " << range_.second << " ) " << std::endl
196 << "Denormals can be 0: " << denormalsToZeroExpr << std::endl
197 << "First sign difference around x=" << std::setprecision(20) << ::testing::PrintToString(vx) << std::endl
198 << "Ref values: " << std::setprecision(20) << ::testing::PrintToString(vref) << std::endl
199 << "SIMD values: " << std::setprecision(20) << ::testing::PrintToString(vtst) << std::endl;
202 GMX_RELEASE_ASSERT(ulpTol_ >= 0, "Invalid ulp value.");
203 if (maxUlpDiff <= ulpTol_)
205 return ::testing::AssertionSuccess();
207 else
209 return ::testing::AssertionFailure()
210 << "Failing SIMD math function ulp comparison between " << refFuncExpr << " and " << simdFuncExpr << std::endl
211 << "Requested ulp tolerance: " << ulpTol_ << std::endl
212 << "Requested abs tolerance: " << absTol_ << std::endl
213 << "Denormals can be 0: " << denormalsToZeroExpr << std::endl
214 << "Largest Ulp difference occurs for x=" << std::setprecision(20) << maxUlpDiffPos << std::endl
215 << "Ref values: " << std::setprecision(20) << refValMaxUlpDiff << std::endl
216 << "SIMD values: " << std::setprecision(20) << simdValMaxUlpDiff << std::endl
217 << "Ulp diff.: " << std::setprecision(20) << maxUlpDiff << std::endl;
221 /*! \} */
222 /*! \endcond */
225 // Actual math function tests below
228 namespace
231 /*! \cond internal */
232 /*! \addtogroup module_simd */
233 /*! \{ */
235 TEST_F(SimdMathTest, copysign)
237 GMX_EXPECT_SIMD_REAL_EQ(setSimdRealFrom3R(-c0, c1, c2), copysign(setSimdRealFrom3R( c0, c1, c2), setSimdRealFrom3R(-c3, c4, 0)));
238 GMX_EXPECT_SIMD_REAL_EQ(setSimdRealFrom3R(-c0, c1, c2), copysign(setSimdRealFrom3R(-c0, -c1, -c2), setSimdRealFrom3R(-c3, c4, 0)));
239 GMX_EXPECT_SIMD_REAL_EQ(setSimdRealFrom3R( c0, -c1, c2), copysign(setSimdRealFrom3R( c0, c1, c2), setSimdRealFrom3R( c3, -c4, 0)));
240 GMX_EXPECT_SIMD_REAL_EQ(setSimdRealFrom3R( c0, -c1, c2), copysign(setSimdRealFrom3R(-c0, -c1, -c2), setSimdRealFrom3R( c3, -c4, 0)));
243 /*! \brief Function wrapper to evaluate reference 1/sqrt(x) */
244 real
245 refInvsqrt(real x)
247 return 1.0/std::sqrt(x);
250 TEST_F(SimdMathTest, invsqrt)
252 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
253 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, invsqrt);
256 TEST_F(SimdMathTest, maskzInvsqrt)
258 SimdReal x = setSimdRealFrom3R(c1, 0.0, c2);
259 SimdBool m = (setZero() < x);
260 SimdReal ref = setSimdRealFrom3R(1.0/std::sqrt(c1), 0.0, 1.0/std::sqrt(c2));
261 GMX_EXPECT_SIMD_REAL_NEAR(ref, maskzInvsqrt(x, m));
264 /*! \brief Function wrapper to return first result when testing \ref invsqrtPair */
265 SimdReal gmx_simdcall
266 tstInvsqrtPair0(SimdReal x)
268 SimdReal r0, r1;
269 invsqrtPair(x, x, &r0, &r1);
270 return r0;
273 /*! \brief Function wrapper to return second result when testing \ref invsqrtPair */
274 SimdReal gmx_simdcall
275 tstInvsqrtPair1(SimdReal x)
277 SimdReal r0, r1;
278 invsqrtPair(x, x, &r0, &r1);
279 return r1;
282 TEST_F(SimdMathTest, invsqrtPair)
284 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
285 // The accuracy conversions lose a bit of extra accuracy compared to
286 // doing the iterations in all-double.
287 setUlpTol(4*ulpTol_);
289 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tstInvsqrtPair0);
290 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tstInvsqrtPair1);
293 /*! \brief Function wrapper to evaluate reference sqrt(x) */
294 real
295 refSqrt(real x)
297 return std::sqrt(x);
300 /*! \brief Dummy function returning 0.0 to test function ranges that should be zero */
301 gmx_unused real refZero(real gmx_unused x)
303 return 0.0;
307 TEST_F(SimdMathTest, sqrt)
309 // The accuracy conversions lose a bit of extra accuracy compared to
310 // doing the iterations in all-double.
311 setUlpTol(4*ulpTol_);
313 // First test that 0.0 and a few other values works
314 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(0, std::sqrt(c1), std::sqrt(c2)), sqrt(setSimdRealFrom3R(0, c1, c2)));
316 // Values smaller-than-or-equal to GMX_FLOAT_MIN will be clamped to 0.0,
317 // so only test larger values
318 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
319 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrt);
321 #if GMX_DOUBLE
322 // Make sure that values smaller than GMX_FLOAT_MIN lead to result 0.0
323 setRange(0.0, 0.99*GMX_FLOAT_MIN);
324 GMX_EXPECT_SIMD_FUNC_NEAR(refZero, sqrt);
325 #endif
328 TEST_F(SimdMathTest, sqrtUnsafe)
330 // The accuracy conversions lose a bit of extra accuracy compared to
331 // doing the iterations in all-double.
332 setUlpTol(4*ulpTol_);
334 setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
335 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrt<MathOptimization::Unsafe>);
338 /*! \brief Function wrapper to evaluate reference 1/x */
339 real refInv(real x)
341 return 1.0/x;
344 TEST_F(SimdMathTest, inv)
346 // test <0
347 setRange(-1e10, -1e-10);
348 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, inv);
349 setRange(1e-10, 1e10);
350 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, inv);
353 TEST_F(SimdMathTest, maskzInv)
355 SimdReal x = setSimdRealFrom3R(c1, 0.0, c2);
356 SimdBool m = (setZero() < x);
357 SimdReal ref = setSimdRealFrom3R(1.0/c1, 0.0, 1.0/c2);
358 GMX_EXPECT_SIMD_REAL_NEAR(ref, maskzInv(x, m));
361 TEST_F(SimdMathTest, log)
363 setRange(1e-30, 1e30);
364 GMX_EXPECT_SIMD_FUNC_NEAR(std::log, log);
367 TEST_F(SimdMathTest, exp2)
369 // Test normal/denormal/zero range separately to make errors clearer
371 // First test the range where we get normalized (non-denormal) results,
372 // since we don't require denormal results to be reproduced correctly.
373 #if GMX_DOUBLE
374 setRange(-1022, 1023);
375 #else
376 setRange(-126, 127);
377 #endif
378 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2);
380 // Some implementations might have denormal support, in which case they
381 // support an extended range, adding roughly the number of bits in the
382 // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
383 // In this range we allow the value to be either correct (denormal) or 0.0
384 #if GMX_DOUBLE
385 setRange(-1075, -1022);
386 #else
387 setRange(-150, -126);
388 #endif
389 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2);
391 // For arguments smaller than the subnormal the result should be zero
392 // both in the reference and our implementations.
393 #if GMX_DOUBLE
394 setRange(-1000000.0, -1075.0);
395 #else
396 setRange(-100000.0, -150.0);
397 #endif
398 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2);
400 // Test a few very negative values, including values so small that they
401 // will start to cause inf values in the polynomial interpolations
402 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp2(-GMX_FLOAT_MAX), std::exp2(-0.1*GMX_REAL_MAX), std::exp2(-GMX_REAL_MAX)),
403 exp2(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
406 TEST_F(SimdMathTest, exp2Unsafe)
408 // The unsafe version is only defined in this range
409 #if GMX_DOUBLE
410 setRange(-1022, 1023);
411 #else
412 setRange(-126, 127);
413 #endif
414 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2<MathOptimization::Unsafe>);
417 TEST_F(SimdMathTest, exp)
419 // Test normal/denormal/zero range separately to make errors clearer
421 // First test the range where we get normalized (non-denormal) results,
422 // since we don't require denormal results to be reproduced correctly.
424 // For very small arguments that would produce results close to the
425 // smallest representable value, some of the intermediate values might
426 // trigger flush-to-zero denormals without FMA operations,
427 // e.g. for the icc compiler. Since we never use such values in Gromacs, we
428 // shrink the range a bit in that case instead of requiring the compiler to
429 // handle denormals (which might reduce performance).
430 #if GMX_DOUBLE
431 #if GMX_SIMD_HAVE_FMA
432 setRange(-708.3, 709.1);
433 #else
434 setRange(-690, 709.1);
435 #endif
436 #else
437 #if GMX_SIMD_HAVE_FMA
438 setRange(-87.3, 88.0);
439 #else
440 setRange(-80, 88.0);
441 #endif
442 #endif
443 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
445 // Some implementations might have denormal support, in which case they
446 // support an extended range, adding roughly the number of bits in the
447 // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
448 // Then multiply with ln(2) to get our limit for exp().
449 // In this range we allow the value to be either correct (denormal) or 0.0
450 #if GMX_DOUBLE
451 setRange(-746.0, -708.4);
452 #else
453 setRange(-104.0, -87.3);
454 #endif
455 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, exp);
457 // For arguments smaller than the subnormal the result should be zero
458 // both in the reference and our implementations.
459 #if GMX_DOUBLE
460 setRange(-1000000.0, -746.0);
461 #else
462 setRange(-100000.0, -104.0);
463 #endif
464 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
466 // Test a few very negative values, including values so small that they
467 // will start to cause inf values in the polynomial interpolations
468 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp(-GMX_FLOAT_MAX), std::exp(-0.1*GMX_REAL_MAX), std::exp(-GMX_REAL_MAX)),
469 exp(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
472 TEST_F(SimdMathTest, expUnsafe)
474 #if GMX_DOUBLE
475 #if GMX_SIMD_HAVE_FMA
476 setRange(-708.3, 709.1);
477 #else
478 setRange(-690, 709.1);
479 #endif
480 #else
481 #if GMX_SIMD_HAVE_FMA
482 setRange(-87.3, 88.0);
483 #else
484 setRange(-80, 88.0);
485 #endif
486 #endif
487 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp<MathOptimization::Unsafe>);
490 /*! \brief Function wrapper for erf(x), with argument/return in default Gromacs precision.
492 * \note Single-precision erf() in some libraries can be slightly lower precision
493 * than the SIMD flavor, so we use a cast to force double precision for reference.
495 real
496 refErf(real x)
498 return std::erf(static_cast<double>(x));
501 TEST_F(SimdMathTest, erf)
503 setRange(-9, 9);
504 setAbsTol(GMX_REAL_MIN);
505 GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erf);
508 /*! \brief Function wrapper for erfc(x), with argument/return in default Gromacs precision.
510 * \note Single-precision erfc() in some libraries can be slightly lower precision
511 * than the SIMD flavor, so we use a cast to force double precision for reference.
513 real
514 refErfc(real x)
516 return std::erfc(static_cast<double>(x));
519 TEST_F(SimdMathTest, erfc)
521 setRange(-9, 9);
522 setAbsTol(GMX_REAL_MIN);
523 // Our erfc algorithm has 4 ulp accuracy, so relax defaultTol a bit
524 setUlpTol(4*ulpTol_);
525 GMX_EXPECT_SIMD_FUNC_NEAR(refErfc, erfc);
528 TEST_F(SimdMathTest, sin)
530 setRange(-8*M_PI, 8*M_PI);
531 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sin);
532 // Range reduction leads to accuracy loss, so we might want higher tolerance here
533 setRange(-10000, 10000);
534 setUlpTol(2*ulpTol_);
535 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sin);
538 TEST_F(SimdMathTest, cos)
540 setRange(-8*M_PI, 8*M_PI);
541 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cos);
542 // Range reduction leads to accuracy loss, so we might want higher tolerance here
543 setRange(-10000, 10000);
544 setUlpTol(2*ulpTol_);
545 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cos);
548 TEST_F(SimdMathTest, tan)
550 // Tan(x) is a little sensitive due to the division in the algorithm.
551 // Rather than using lots of extra FP operations, we accept the algorithm
552 // presently only achieves a ~3 ulp error and use the medium tolerance.
553 setRange(-8*M_PI, 8*M_PI);
554 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tan);
555 // Range reduction leads to accuracy loss, so we might want higher tolerance here
556 setRange(-10000, 10000);
557 setUlpTol(2*ulpTol_);
558 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tan);
561 TEST_F(SimdMathTest, asin)
563 // Our present asin(x) algorithm achieves 2-3 ulp accuracy
564 setRange(-1, 1);
565 GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asin);
568 TEST_F(SimdMathTest, acos)
570 // Our present acos(x) algorithm achieves 2-3 ulp accuracy
571 setRange(-1, 1);
572 GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acos);
575 TEST_F(SimdMathTest, atan)
577 // Our present atan(x) algorithm achieves 1 ulp accuracy
578 setRange(-10000, 10000);
579 GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atan);
582 TEST_F(SimdMathTest, atan2)
584 // test each quadrant
585 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, c3), std::atan2(c1, c4), std::atan2(c2, c5)),
586 atan2(rSimd_c0c1c2, rSimd_c3c4c5));
587 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, c3), std::atan2(-c1, c4), std::atan2(-c2, c5)),
588 atan2(rSimd_m0m1m2, rSimd_c3c4c5));
589 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, -c3), std::atan2(-c1, -c0), std::atan2(-c2, -c4)),
590 atan2(rSimd_m0m1m2, rSimd_m3m0m4));
591 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, -c3), std::atan2(c1, -c0), std::atan2(c2, -c4)),
592 atan2(rSimd_c0c1c2, rSimd_m3m0m4));
594 // cases important for calculating angles
595 // values on coordinate axes
596 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, c0), std::atan2(0, c1), std::atan2(0, c2)),
597 atan2(setZero(), rSimd_c0c1c2));
598 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, 0), std::atan2(c1, 0), std::atan2(c2, 0)),
599 atan2(rSimd_c0c1c2, setZero()));
600 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, -c0), std::atan2(0, -c1), std::atan2(0, -c2)),
601 atan2(setZero(), rSimd_m0m1m2));
602 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, 0), std::atan2(-c1, 0), std::atan2(-c2, 0)),
603 atan2(rSimd_m0m1m2, setZero()));
604 // degenerate value (origin) should return 0.0. At least IBM xlc 13.1.5 gets the reference
605 // value wrong (-nan) at -O3 optimization, so we compare to the correct value (0.0) instead.
606 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(0.0), atan2(setSimdRealFrom3R(0.0, 0.0, 0.0), setZero()));
609 /*! \brief Evaluate reference version of PME force correction. */
610 real
611 refPmeForceCorrection(real x)
613 if (x != 0)
615 real y = std::sqrt(x);
616 return 2*std::exp(-x)/(std::sqrt(M_PI)*x) - std::erf(static_cast<double>(y))/(x*y);
618 else
620 return -4/(3*std::sqrt(M_PI));
624 // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
625 TEST_F(SimdMathTest, pmeForceCorrection)
627 // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
628 #if GMX_DOUBLE
629 setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
630 #else
631 setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
632 #endif
634 setRange(0.15, 4);
635 setAbsTol(GMX_REAL_EPS);
636 GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrection);
639 /*! \brief Evaluate reference version of PME potential correction. */
640 real
641 refPmePotentialCorrection(real x)
643 real y = std::sqrt(x);
644 return std::erf(static_cast<double>(y))/y;
647 // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
648 TEST_F(SimdMathTest, pmePotentialCorrection)
650 // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
651 #if GMX_DOUBLE
652 setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
653 #else
654 setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
655 #endif
656 setRange(0.15, 4);
657 setAbsTol(GMX_REAL_EPS);
658 GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrection);
661 // Functions that only target single accuracy, even for double SIMD data
663 TEST_F(SimdMathTest, invsqrtSingleAccuracy)
665 /* Increase the allowed error by the difference between the actual precision and single */
666 setUlpTolSingleAccuracy(ulpTol_);
668 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
669 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, invsqrtSingleAccuracy);
672 /*! \brief Function wrapper to return first result when testing \ref invsqrtPairSingleAccuracy */
673 SimdReal gmx_simdcall
674 tst_invsqrt_SingleAccuracy_pair0(SimdReal x)
676 SimdReal r0, r1;
677 invsqrtPairSingleAccuracy(x, x, &r0, &r1);
678 return r0;
681 /*! \brief Function wrapper to return second result when testing \ref invsqrtPairSingleAccuracy */
682 SimdReal gmx_simdcall
683 tst_invsqrt_SingleAccuracy_pair1(SimdReal x)
685 SimdReal r0, r1;
686 invsqrtPairSingleAccuracy(x, x, &r0, &r1);
687 return r1;
690 TEST_F(SimdMathTest, invsqrtPairSingleAccuracy)
692 /* Increase the allowed error by the difference between the actual precision and single */
693 setUlpTolSingleAccuracy(ulpTol_);
695 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
696 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tst_invsqrt_SingleAccuracy_pair0);
697 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tst_invsqrt_SingleAccuracy_pair1);
700 TEST_F(SimdMathTest, sqrtSingleAccuracy)
702 /* Increase the allowed error by the difference between the actual precision and single */
703 setUlpTolSingleAccuracy(ulpTol_);
705 // First test that 0.0 and a few other values works
706 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(0, std::sqrt(c0), std::sqrt(c1)), sqrtSingleAccuracy(setSimdRealFrom3R(0, c0, c1)));
708 // Values smaller-than-or-equal to GMX_FLOAT_MIN will be clamped to 0.0,
709 // so only test larger values
710 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
711 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrtSingleAccuracy);
713 #if GMX_DOUBLE
714 // Make sure that values smaller than GMX_FLOAT_MIN lead to result 0.0
715 setRange(0.0, 0.99*GMX_FLOAT_MIN);
716 GMX_EXPECT_SIMD_FUNC_NEAR(refZero, sqrtSingleAccuracy);
717 #endif
720 TEST_F(SimdMathTest, sqrtSingleAccuracyUnsafe)
722 /* Increase the allowed error by the difference between the actual precision and single */
723 setUlpTolSingleAccuracy(ulpTol_);
725 // Test the full range
726 setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
727 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrtSingleAccuracy<MathOptimization::Unsafe>);
730 TEST_F(SimdMathTest, invSingleAccuracy)
732 /* Increase the allowed error by the difference between the actual precision and single */
733 setUlpTolSingleAccuracy(ulpTol_);
735 // test <0
736 setRange(-1e10, -1e-10);
737 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, invSingleAccuracy);
738 setRange(1e-10, 1e10);
739 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, invSingleAccuracy);
742 TEST_F(SimdMathTest, logSingleAccuracy)
744 /* Increase the allowed error by the difference between the actual precision and single */
745 setUlpTolSingleAccuracy(ulpTol_);
747 setRange(1e-30, 1e30);
748 GMX_EXPECT_SIMD_FUNC_NEAR(std::log, logSingleAccuracy);
751 TEST_F(SimdMathTest, exp2SingleAccuracy)
753 /* Increase the allowed error by the difference between the actual precision and single */
754 setUlpTolSingleAccuracy(ulpTol_);
756 #if GMX_DOUBLE
757 setRange(-1022.49, 1023.49);
758 #else
759 setRange(-126, 127.49);
760 #endif
761 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy);
763 // Test a range that should be zero both for reference and simd versions.
764 // Some implementations might have subnormal support, in which case they
765 // support an extended range, adding roughly the number of bits in the
766 // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
767 #if GMX_DOUBLE
768 setRange(-1000000.0, -1075.0);
769 #else
770 setRange(-100000.0, -150.0);
771 #endif
772 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2SingleAccuracy);
774 // Test a few very negative values
775 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp2(-GMX_FLOAT_MAX), std::exp2(-0.1*GMX_REAL_MAX), std::exp2(-GMX_REAL_MAX)),
776 exp2SingleAccuracy(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
779 TEST_F(SimdMathTest, exp2SingleAccuracyUnsafe)
781 /* Increase the allowed error by the difference between the actual precision and single */
782 setUlpTolSingleAccuracy(ulpTol_);
784 #if GMX_DOUBLE
785 setRange(-1022.49, 1023.49);
786 #else
787 setRange(-126, 127.49);
788 #endif
789 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy<MathOptimization::Unsafe>);
792 TEST_F(SimdMathTest, expSingleAccuracy)
794 /* Increase the allowed error by the difference between the actual precision and single */
795 setUlpTolSingleAccuracy(ulpTol_);
797 #if GMX_DOUBLE
798 setRange(-708.7, 709.4);
799 #else
800 setRange(-87.3, 88.3);
801 #endif
802 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy);
804 // Test a range that should be zero both for reference and simd versions.
805 // Some implementations might have subnormal support, in which case they
806 // support an extended range, adding roughly the number of bits in the
807 // mantissa to the smallest result exponent (1023+52 in double, 127+23 single).
808 // Then multiply with ln(2) to get our limit for exp().
809 #if GMX_DOUBLE
810 setRange(-1000000.0, -746.0);
811 #else
812 setRange(-100000.0, -104.0);
813 #endif
814 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, expSingleAccuracy);
816 // Test a few very negative values, including values so small that they
817 // will start to cause inf values in the polynomial interpolations
818 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp(-GMX_FLOAT_MAX), std::exp(-0.1*GMX_REAL_MAX), std::exp(-GMX_REAL_MAX)),
819 expSingleAccuracy(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
822 TEST_F(SimdMathTest, expSingleAccuracyUnsafe)
824 /* Increase the allowed error by the difference between the actual precision and single */
825 setUlpTolSingleAccuracy(ulpTol_);
827 #if GMX_DOUBLE
828 setRange(-708.7, 709.4);
829 #else
830 setRange(-87.3, 88.3);
831 #endif
832 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy<MathOptimization::Unsafe>);
835 TEST_F(SimdMathTest, erfSingleAccuracy)
837 /* Increase the allowed error by the difference between the actual precision and single */
838 setUlpTolSingleAccuracy(ulpTol_);
840 setRange(-9, 9);
841 setAbsTol(GMX_REAL_MIN);
842 GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erfSingleAccuracy);
845 TEST_F(SimdMathTest, erfcSingleAccuracy)
847 /* Increase the allowed error by the difference between the actual precision and single */
848 setUlpTolSingleAccuracy(ulpTol_);
850 setRange(-9, 9);
851 setAbsTol(GMX_REAL_MIN);
852 // Our erfc algorithm has 4 ulp accuracy, so relax tolerance a bit
853 setUlpTol(4*ulpTol_);
854 GMX_EXPECT_SIMD_FUNC_NEAR(refErfc, erfcSingleAccuracy);
858 TEST_F(SimdMathTest, sinSingleAccuracy)
860 /* Increase the allowed error by the difference between the actual precision and single */
861 setUlpTolSingleAccuracy(ulpTol_);
863 setRange(-8*M_PI, 8*M_PI);
864 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sinSingleAccuracy);
865 // Range reduction leads to accuracy loss, so we might want higher tolerance here
866 setRange(-10000, 10000);
867 setUlpTol(2*ulpTol_);
868 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sinSingleAccuracy);
871 TEST_F(SimdMathTest, cosSingleAccuracy)
873 /* Increase the allowed error by the difference between the actual precision and single */
874 setUlpTolSingleAccuracy(ulpTol_);
876 setRange(-8*M_PI, 8*M_PI);
877 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cosSingleAccuracy);
878 // Range reduction leads to accuracy loss, so we might want higher tolerance here
879 setRange(-10000, 10000);
880 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cosSingleAccuracy);
883 TEST_F(SimdMathTest, tanSingleAccuracy)
885 /* Increase the allowed error by the difference between the actual precision and single */
886 setUlpTolSingleAccuracy(ulpTol_);
888 // Tan(x) is a little sensitive due to the division in the algorithm.
889 // Rather than using lots of extra FP operations, we accept the algorithm
890 // presently only achieves a ~3 ulp error and use the medium tolerance.
891 setRange(-8*M_PI, 8*M_PI);
892 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tanSingleAccuracy);
893 // Range reduction leads to accuracy loss, so we might want higher tolerance here
894 setRange(-10000, 10000);
895 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tanSingleAccuracy);
898 TEST_F(SimdMathTest, asinSingleAccuracy)
900 /* Increase the allowed error by the difference between the actual precision and single */
901 setUlpTolSingleAccuracy(ulpTol_);
903 // Our present asin(x) algorithm achieves 2-3 ulp accuracy
904 setRange(-1, 1);
905 GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asinSingleAccuracy);
908 TEST_F(SimdMathTest, acosSingleAccuracy)
910 /* Increase the allowed error by the difference between the actual precision and single */
911 setUlpTolSingleAccuracy(ulpTol_);
913 // Our present acos(x) algorithm achieves 2-3 ulp accuracy
914 setRange(-1, 1);
915 GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acosSingleAccuracy);
918 TEST_F(SimdMathTest, atanSingleAccuracy)
920 /* Increase the allowed error by the difference between the actual precision and single */
921 setUlpTolSingleAccuracy(ulpTol_);
923 // Our present atan(x) algorithm achieves 1 ulp accuracy
924 setRange(-10000, 10000);
925 GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atanSingleAccuracy);
928 TEST_F(SimdMathTest, atan2SingleAccuracy)
930 /* Increase the allowed error by the difference between the actual precision and single */
931 setUlpTolSingleAccuracy(ulpTol_);
933 // test each quadrant
934 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, c3), std::atan2(c1, c4), std::atan2(c2, c5)),
935 atan2SingleAccuracy(rSimd_c0c1c2, rSimd_c3c4c5));
936 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, c3), std::atan2(-c1, c4), std::atan2(-c2, c5)),
937 atan2SingleAccuracy(rSimd_m0m1m2, rSimd_c3c4c5));
938 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, -c3), std::atan2(-c1, -c0), std::atan2(-c2, -c4)),
939 atan2SingleAccuracy(rSimd_m0m1m2, rSimd_m3m0m4));
940 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, -c3), std::atan2(c1, -c0), std::atan2(c2, -c4)),
941 atan2SingleAccuracy(rSimd_c0c1c2, rSimd_m3m0m4));
942 // cases important for calculating angles
943 // values on coordinate axes
944 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, c0), std::atan2(0, c1), std::atan2(0, c2)),
945 atan2SingleAccuracy(setZero(), rSimd_c0c1c2));
946 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, 0), std::atan2(c1, 0), std::atan2(c2, 0)),
947 atan2SingleAccuracy(rSimd_c0c1c2, setZero()));
948 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, -c0), std::atan2(0, -c1), std::atan2(0, -c2)),
949 atan2SingleAccuracy(setZero(), rSimd_m0m1m2));
950 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, 0), std::atan2(-c1, 0), std::atan2(-c2, 0)),
951 atan2SingleAccuracy(rSimd_m0m1m2, setZero()));
953 // degenerate value (origin) should return 0.0. At least IBM xlc 13.1.5 gets the reference
954 // value wrong (-nan) at -O3 optimization, so we compare to the correct value (0.0) instead.
955 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(0.0), atan2SingleAccuracy(setSimdRealFrom3R(0.0, 0.0, 0.0), setZero()));
958 TEST_F(SimdMathTest, pmeForceCorrectionSingleAccuracy)
960 // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
961 // Pme correction only needs to be ~1e-6 accuracy single.
962 // Then increase the allowed error by the difference between the actual precision and single.
963 setUlpTolSingleAccuracy(std::int64_t(5e-6/GMX_FLOAT_EPS));
965 setRange(0.15, 4);
966 setAbsTol(GMX_FLOAT_EPS);
967 GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrectionSingleAccuracy);
970 TEST_F(SimdMathTest, pmePotentialCorrectionSingleAccuracy)
972 // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
973 // Pme correction only needs to be ~1e-6 accuracy single.
974 // Then increase the allowed error by the difference between the actual precision and single.
975 setUlpTolSingleAccuracy(std::int64_t(5e-6/GMX_FLOAT_EPS));
977 setRange(0.15, 4);
978 setAbsTol(GMX_FLOAT_EPS);
979 GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrectionSingleAccuracy);
982 } // namespace
984 #endif // GMX_SIMD_HAVE_REAL
986 /*! \} */
987 /*! \endcond */
989 } // namespace test
990 } // namespace gmx
992 #endif // GMX_SIMD