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.
37 #include "gromacs/simd/simd_math.h"
46 #include "gromacs/math/utilities.h"
47 #include "gromacs/options/basicoptions.h"
48 #include "gromacs/simd/simd.h"
60 /*! \addtogroup module_simd */
63 #if GMX_SIMD_HAVE_REAL
65 class SimdMathTest : public SimdTest
68 ::testing::AssertionResult
69 compareSimdMathFunction(const char * refFuncExpr,
70 const char * simdFuncExpr,
71 const char * denormalsToZeroExpr,
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);
122 std::int64_t ulpDiff, maxUlpDiff;
124 real refValMaxUlpDiff, simdValMaxUlpDiff;
127 int niter = s_nPoints/GMX_SIMD_REAL_WIDTH;
128 int npoints = niter*GMX_SIMD_REAL_WIDTH;
131 double r; std::int64_t i;
135 float r; std::int32_t i;
140 dx = (range_.second-range_.first)/npoints;
142 for (iter = 0; iter < niter; iter++)
144 for (i = 0; i < GMX_SIMD_REAL_WIDTH; i++)
146 vx[i] = range_.first+dx*(iter*GMX_SIMD_REAL_WIDTH+i);
147 vref[i] = refFunc(vx[i]);
149 vtst = simdReal2Vector(simdFunc(vector2SimdReal(vx)));
151 for (i = 0, signOk = true, absOk = true; i < GMX_SIMD_REAL_WIDTH; i++)
155 // Clamp denormal values to zero if requested
156 if (std::abs(vref[i]) <= GMX_REAL_MIN)
160 if (std::abs(vtst[i]) <= GMX_REAL_MIN)
166 absDiff = fabs(vref[i]-vtst[i]);
167 absOk = absOk && ( absDiff < absTol_ );
168 signOk = signOk && ( (vref[i] >= 0 && vtst[i] >= 0) ||
169 (vref[i] <= 0 && vtst[i] <= 0));
171 if (absDiff >= absTol_)
173 /* We replicate the trivial ulp differences comparison here rather than
174 * calling the lower-level routine for comparing them, since this enables
175 * us to run through the entire test range and report the largest deviation
176 * without lots of extra glue routines.
180 ulpDiff = llabs(conv0.i-conv1.i);
181 if (ulpDiff > maxUlpDiff)
183 maxUlpDiff = ulpDiff;
184 maxUlpDiffPos = vx[i];
185 refValMaxUlpDiff = vref[i];
186 simdValMaxUlpDiff = vtst[i];
190 if ( (absOk == false) && (signOk == false) )
192 return ::testing::AssertionFailure()
193 << "Failing SIMD math function comparison due to sign differences." << std::endl
194 << "Reference function: " << refFuncExpr << std::endl
195 << "Simd function: " << simdFuncExpr << std::endl
196 << "Test range is ( " << range_.first << " , " << range_.second << " ) " << std::endl
197 << "Denormals can be 0: " << denormalsToZeroExpr << std::endl
198 << "First sign difference around x=" << std::setprecision(20) << ::testing::PrintToString(vx) << std::endl
199 << "Ref values: " << std::setprecision(20) << ::testing::PrintToString(vref) << std::endl
200 << "SIMD values: " << std::setprecision(20) << ::testing::PrintToString(vtst) << std::endl;
204 if (maxUlpDiff <= ulpTol_)
206 return ::testing::AssertionSuccess();
210 return ::testing::AssertionFailure()
211 << "Failing SIMD math function ulp comparison between " << refFuncExpr << " and " << simdFuncExpr << std::endl
212 << "Requested ulp tolerance: " << ulpTol_ << std::endl
213 << "Requested abs tolerance: " << absTol_ << std::endl
214 << "Denormals can be 0: " << denormalsToZeroExpr << std::endl
215 << "Largest Ulp difference occurs for x=" << std::setprecision(20) << maxUlpDiffPos << std::endl
216 << "Ref values: " << std::setprecision(20) << refValMaxUlpDiff << std::endl
217 << "SIMD values: " << std::setprecision(20) << simdValMaxUlpDiff << std::endl
218 << "Ulp diff.: " << std::setprecision(20) << maxUlpDiff << std::endl;
226 // Actual math function tests below
232 /*! \cond internal */
233 /*! \addtogroup module_simd */
236 TEST_F(SimdMathTest, copysign)
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)));
241 GMX_EXPECT_SIMD_REAL_EQ(setSimdRealFrom3R( c0, -c1, c2), copysign(setSimdRealFrom3R(-c0, -c1, -c2), setSimdRealFrom3R( c3, -c4, 0)));
244 /*! \brief Function wrapper to evaluate reference 1/sqrt(x) */
248 return 1.0/std::sqrt(x);
251 TEST_F(SimdMathTest, invsqrt)
253 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
254 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, invsqrt);
257 TEST_F(SimdMathTest, maskzInvsqrt)
259 SimdReal x = setSimdRealFrom3R(c1, 0.0, c2);
260 SimdBool m = (setZero() < x);
261 SimdReal ref = setSimdRealFrom3R(1.0/std::sqrt(c1), 0.0, 1.0/std::sqrt(c2));
262 GMX_EXPECT_SIMD_REAL_NEAR(ref, maskzInvsqrt(x, m));
265 /*! \brief Function wrapper to return first result when testing \ref invsqrtPair */
266 SimdReal gmx_simdcall
267 tstInvsqrtPair0(SimdReal x)
270 invsqrtPair(x, x, &r0, &r1);
274 /*! \brief Function wrapper to return second result when testing \ref invsqrtPair */
275 SimdReal gmx_simdcall
276 tstInvsqrtPair1(SimdReal x)
279 invsqrtPair(x, x, &r0, &r1);
283 TEST_F(SimdMathTest, invsqrtPair)
285 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
286 // The accuracy conversions lose a bit of extra accuracy compared to
287 // doing the iterations in all-double.
288 setUlpTol(4*ulpTol_);
290 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tstInvsqrtPair0);
291 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tstInvsqrtPair1);
294 /*! \brief Function wrapper to evaluate reference sqrt(x) */
301 /*! \brief Dummy function returning 0.0 to test function ranges that should be zero */
302 gmx_unused static real
303 refZero(real gmx_unused x)
309 TEST_F(SimdMathTest, sqrt)
311 // The accuracy conversions lose a bit of extra accuracy compared to
312 // doing the iterations in all-double.
313 setUlpTol(4*ulpTol_);
315 // First test that 0.0 and a few other values works
316 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(0, std::sqrt(c1), std::sqrt(c2)), sqrt(setSimdRealFrom3R(0, c1, c2)));
318 // Values smaller-than-or-equal to GMX_FLOAT_MIN will be clamped to 0.0,
319 // so only test larger values
320 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
321 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrt);
324 // Make sure that values smaller than GMX_FLOAT_MIN lead to result 0.0
325 setRange(0.0, 0.99*GMX_FLOAT_MIN);
326 GMX_EXPECT_SIMD_FUNC_NEAR(refZero, sqrt);
330 TEST_F(SimdMathTest, sqrtUnsafe)
332 // The accuracy conversions lose a bit of extra accuracy compared to
333 // doing the iterations in all-double.
334 setUlpTol(4*ulpTol_);
336 setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
337 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrt<MathOptimization::Unsafe>);
340 /*! \brief Function wrapper to evaluate reference 1/x */
346 TEST_F(SimdMathTest, inv)
349 setRange(-1e10, -1e-10);
350 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, inv);
351 setRange(1e-10, 1e10);
352 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, inv);
355 TEST_F(SimdMathTest, maskzInv)
357 SimdReal x = setSimdRealFrom3R(c1, 0.0, c2);
358 SimdBool m = (setZero() < x);
359 SimdReal ref = setSimdRealFrom3R(1.0/c1, 0.0, 1.0/c2);
360 GMX_EXPECT_SIMD_REAL_NEAR(ref, maskzInv(x, m));
363 TEST_F(SimdMathTest, log)
365 setRange(1e-30, 1e30);
366 GMX_EXPECT_SIMD_FUNC_NEAR(std::log, log);
369 TEST_F(SimdMathTest, exp2)
371 // Test normal/denormal/zero range separately to make errors clearer
373 // First test the range where we get normalized (non-denormal) results,
374 // since we don't require denormal results to be reproduced correctly.
376 setRange(-1022, 1023);
380 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2);
382 // Some implementations might have denormal support, in which case they
383 // support an extended range, adding roughly the number of bits in the
384 // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
385 // In this range we allow the value to be either correct (denormal) or 0.0
387 setRange(-1075, -1022);
389 setRange(-150, -126);
391 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2);
393 // For arguments smaller than the subnormal the result should be zero
394 // both in the reference and our implementations.
396 setRange(-1000000.0, -1075.0);
398 setRange(-100000.0, -150.0);
400 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2);
402 // Test a few very negative values, including values so small that they
403 // will start to cause inf values in the polynomial interpolations
404 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp2(-GMX_FLOAT_MAX), std::exp2(-0.1*GMX_REAL_MAX), std::exp2(-GMX_REAL_MAX)),
405 exp2(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
408 TEST_F(SimdMathTest, exp2Unsafe)
410 // The unsafe version is only defined in this range
412 setRange(-1022, 1023);
416 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2<MathOptimization::Unsafe>);
419 TEST_F(SimdMathTest, exp)
421 // Test normal/denormal/zero range separately to make errors clearer
423 // First test the range where we get normalized (non-denormal) results,
424 // since we don't require denormal results to be reproduced correctly.
426 // For very small arguments that would produce results close to the
427 // smallest representable value, some of the intermediate values might
428 // trigger flush-to-zero denormals without FMA operations,
429 // e.g. for the icc compiler. Since we never use such values in Gromacs, we
430 // shrink the range a bit in that case instead of requiring the compiler to
431 // handle denormals (which might reduce performance).
433 #if GMX_SIMD_HAVE_FMA
434 setRange(-708.3, 709.1);
436 setRange(-690, 709.1);
439 #if GMX_SIMD_HAVE_FMA
440 setRange(-87.3, 88.0);
445 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
447 // Some implementations might have denormal support, in which case they
448 // support an extended range, adding roughly the number of bits in the
449 // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
450 // Then multiply with ln(2) to get our limit for exp().
451 // In this range we allow the value to be either correct (denormal) or 0.0
453 setRange(-746.0, -708.4);
455 setRange(-104.0, -87.3);
457 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, exp);
459 // For arguments smaller than the subnormal the result should be zero
460 // both in the reference and our implementations.
462 setRange(-1000000.0, -746.0);
464 setRange(-100000.0, -104.0);
466 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
468 // Test a few very negative values, including values so small that they
469 // will start to cause inf values in the polynomial interpolations
470 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp(-GMX_FLOAT_MAX), std::exp(-0.1*GMX_REAL_MAX), std::exp(-GMX_REAL_MAX)),
471 exp(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
474 TEST_F(SimdMathTest, expUnsafe)
477 #if GMX_SIMD_HAVE_FMA
478 setRange(-708.3, 709.1);
480 setRange(-690, 709.1);
483 #if GMX_SIMD_HAVE_FMA
484 setRange(-87.3, 88.0);
489 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp<MathOptimization::Unsafe>);
492 /*! \brief Function wrapper for erf(x), with argument/return in default Gromacs precision.
494 * \note Single-precision erf() in some libraries can be slightly lower precision
495 * than the SIMD flavor, so we use a cast to force double precision for reference.
500 return std::erf(static_cast<double>(x));
503 TEST_F(SimdMathTest, erf)
506 setAbsTol(GMX_REAL_MIN);
507 GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erf);
510 /*! \brief Function wrapper for erfc(x), with argument/return in default Gromacs precision.
512 * \note Single-precision erfc() in some libraries can be slightly lower precision
513 * than the SIMD flavor, so we use a cast to force double precision for reference.
518 return std::erfc(static_cast<double>(x));
521 TEST_F(SimdMathTest, erfc)
524 setAbsTol(GMX_REAL_MIN);
525 // Our erfc algorithm has 4 ulp accuracy, so relax defaultTol a bit
526 setUlpTol(4*ulpTol_);
527 GMX_EXPECT_SIMD_FUNC_NEAR(refErfc, erfc);
530 TEST_F(SimdMathTest, sin)
532 setRange(-8*M_PI, 8*M_PI);
533 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sin);
534 // Range reduction leads to accuracy loss, so we might want higher tolerance here
535 setRange(-10000, 10000);
536 setUlpTol(2*ulpTol_);
537 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sin);
540 TEST_F(SimdMathTest, cos)
542 setRange(-8*M_PI, 8*M_PI);
543 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cos);
544 // Range reduction leads to accuracy loss, so we might want higher tolerance here
545 setRange(-10000, 10000);
546 setUlpTol(2*ulpTol_);
547 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cos);
550 TEST_F(SimdMathTest, tan)
552 // Tan(x) is a little sensitive due to the division in the algorithm.
553 // Rather than using lots of extra FP operations, we accept the algorithm
554 // presently only achieves a ~3 ulp error and use the medium tolerance.
555 setRange(-8*M_PI, 8*M_PI);
556 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tan);
557 // Range reduction leads to accuracy loss, so we might want higher tolerance here
558 setRange(-10000, 10000);
559 setUlpTol(2*ulpTol_);
560 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tan);
563 TEST_F(SimdMathTest, asin)
565 // Our present asin(x) algorithm achieves 2-3 ulp accuracy
567 GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asin);
570 TEST_F(SimdMathTest, acos)
572 // Our present acos(x) algorithm achieves 2-3 ulp accuracy
574 GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acos);
577 TEST_F(SimdMathTest, atan)
579 // Our present atan(x) algorithm achieves 1 ulp accuracy
580 setRange(-10000, 10000);
581 GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atan);
584 TEST_F(SimdMathTest, atan2)
586 // test each quadrant
587 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, c3), std::atan2(c1, c4), std::atan2(c2, c5)),
588 atan2(rSimd_c0c1c2, rSimd_c3c4c5));
589 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, c3), std::atan2(-c1, c4), std::atan2(-c2, c5)),
590 atan2(rSimd_m0m1m2, rSimd_c3c4c5));
591 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, -c3), std::atan2(-c1, -c0), std::atan2(-c2, -c4)),
592 atan2(rSimd_m0m1m2, rSimd_m3m0m4));
593 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, -c3), std::atan2(c1, -c0), std::atan2(c2, -c4)),
594 atan2(rSimd_c0c1c2, rSimd_m3m0m4));
596 // cases important for calculating angles
597 // values on coordinate axes
598 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, c0), std::atan2(0, c1), std::atan2(0, c2)),
599 atan2(setZero(), rSimd_c0c1c2));
600 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, 0), std::atan2(c1, 0), std::atan2(c2, 0)),
601 atan2(rSimd_c0c1c2, setZero()));
602 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, -c0), std::atan2(0, -c1), std::atan2(0, -c2)),
603 atan2(setZero(), rSimd_m0m1m2));
604 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, 0), std::atan2(-c1, 0), std::atan2(-c2, 0)),
605 atan2(rSimd_m0m1m2, setZero()));
606 // degenerate value (origin) should return 0.0. At least IBM xlc 13.1.5 gets the reference
607 // value wrong (-nan) at -O3 optimization, so we compare to the correct value (0.0) instead.
608 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(0.0), atan2(setSimdRealFrom3R(0.0, 0.0, 0.0), setZero()));
611 /*! \brief Evaluate reference version of PME force correction. */
613 refPmeForceCorrection(real x)
617 real y = std::sqrt(x);
618 return 2*std::exp(-x)/(std::sqrt(M_PI)*x) - std::erf(static_cast<double>(y))/(x*y);
622 return -4/(3*std::sqrt(M_PI));
626 // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
627 TEST_F(SimdMathTest, pmeForceCorrection)
629 // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
631 setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
633 setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
637 setAbsTol(GMX_REAL_EPS);
638 GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrection);
641 /*! \brief Evaluate reference version of PME potential correction. */
643 refPmePotentialCorrection(real x)
645 real y = std::sqrt(x);
646 return std::erf(static_cast<double>(y))/y;
649 // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
650 TEST_F(SimdMathTest, pmePotentialCorrection)
652 // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
654 setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
656 setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
659 setAbsTol(GMX_REAL_EPS);
660 GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrection);
663 // Functions that only target single accuracy, even for double SIMD data
665 TEST_F(SimdMathTest, invsqrtSingleAccuracy)
667 /* Increase the allowed error by the difference between the actual precision and single */
668 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
670 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
671 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, invsqrtSingleAccuracy);
674 /*! \brief Function wrapper to return first result when testing \ref invsqrtPairSingleAccuracy */
675 SimdReal gmx_simdcall
676 tst_invsqrt_SingleAccuracy_pair0(SimdReal x)
679 invsqrtPairSingleAccuracy(x, x, &r0, &r1);
683 /*! \brief Function wrapper to return second result when testing \ref invsqrtPairSingleAccuracy */
684 SimdReal gmx_simdcall
685 tst_invsqrt_SingleAccuracy_pair1(SimdReal x)
688 invsqrtPairSingleAccuracy(x, x, &r0, &r1);
692 TEST_F(SimdMathTest, invsqrtPairSingleAccuracy)
694 /* Increase the allowed error by the difference between the actual precision and single */
695 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
697 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
698 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tst_invsqrt_SingleAccuracy_pair0);
699 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tst_invsqrt_SingleAccuracy_pair1);
702 TEST_F(SimdMathTest, sqrtSingleAccuracy)
704 /* Increase the allowed error by the difference between the actual precision and single */
705 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
707 // First test that 0.0 and a few other values works
708 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(0, std::sqrt(c0), std::sqrt(c1)), sqrtSingleAccuracy(setSimdRealFrom3R(0, c0, c1)));
710 // Values smaller-than-or-equal to GMX_FLOAT_MIN will be clamped to 0.0,
711 // so only test larger values
712 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
713 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrtSingleAccuracy);
716 // Make sure that values smaller than GMX_FLOAT_MIN lead to result 0.0
717 setRange(0.0, 0.99*GMX_FLOAT_MIN);
718 GMX_EXPECT_SIMD_FUNC_NEAR(refZero, sqrtSingleAccuracy);
722 TEST_F(SimdMathTest, sqrtSingleAccuracyUnsafe)
724 /* Increase the allowed error by the difference between the actual precision and single */
725 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
727 // Test the full range
728 setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
729 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrtSingleAccuracy<MathOptimization::Unsafe>);
732 TEST_F(SimdMathTest, invSingleAccuracy)
734 /* Increase the allowed error by the difference between the actual precision and single */
735 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
738 setRange(-1e10, -1e-10);
739 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, invSingleAccuracy);
740 setRange(1e-10, 1e10);
741 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, invSingleAccuracy);
744 TEST_F(SimdMathTest, logSingleAccuracy)
746 /* Increase the allowed error by the difference between the actual precision and single */
747 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
749 setRange(1e-30, 1e30);
750 GMX_EXPECT_SIMD_FUNC_NEAR(std::log, logSingleAccuracy);
753 TEST_F(SimdMathTest, exp2SingleAccuracy)
755 /* Increase the allowed error by the difference between the actual precision and single */
756 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
759 setRange(-1022.49, 1023.49);
761 setRange(-126, 127.49);
763 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy);
765 // Test a range that should be zero both for reference and simd versions.
766 // Some implementations might have subnormal support, in which case they
767 // support an extended range, adding roughly the number of bits in the
768 // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
770 setRange(-1000000.0, -1075.0);
772 setRange(-100000.0, -150.0);
774 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2SingleAccuracy);
776 // Test a few very negative values
777 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp2(-GMX_FLOAT_MAX), std::exp2(-0.1*GMX_REAL_MAX), std::exp2(-GMX_REAL_MAX)),
778 exp2SingleAccuracy(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
781 TEST_F(SimdMathTest, exp2SingleAccuracyUnsafe)
783 /* Increase the allowed error by the difference between the actual precision and single */
784 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
787 setRange(-1022.49, 1023.49);
789 setRange(-126, 127.49);
791 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy<MathOptimization::Unsafe>);
794 TEST_F(SimdMathTest, expSingleAccuracy)
796 /* Increase the allowed error by the difference between the actual precision and single */
797 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
800 setRange(-708.7, 709.4);
802 setRange(-87.3, 88.3);
804 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy);
806 // Test a range that should be zero both for reference and simd versions.
807 // Some implementations might have subnormal support, in which case they
808 // support an extended range, adding roughly the number of bits in the
809 // mantissa to the smallest result exponent (1023+52 in double, 127+23 single).
810 // Then multiply with ln(2) to get our limit for exp().
812 setRange(-1000000.0, -746.0);
814 setRange(-100000.0, -104.0);
816 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, expSingleAccuracy);
818 // Test a few very negative values, including values so small that they
819 // will start to cause inf values in the polynomial interpolations
820 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp(-GMX_FLOAT_MAX), std::exp(-0.1*GMX_REAL_MAX), std::exp(-GMX_REAL_MAX)),
821 expSingleAccuracy(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
824 TEST_F(SimdMathTest, expSingleAccuracyUnsafe)
826 /* Increase the allowed error by the difference between the actual precision and single */
827 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
830 setRange(-708.7, 709.4);
832 setRange(-87.3, 88.3);
834 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy<MathOptimization::Unsafe>);
837 TEST_F(SimdMathTest, erfSingleAccuracy)
839 /* Increase the allowed error by the difference between the actual precision and single */
840 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
843 setAbsTol(GMX_REAL_MIN);
844 GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erfSingleAccuracy);
847 TEST_F(SimdMathTest, erfcSingleAccuracy)
849 /* Increase the allowed error by the difference between the actual precision and single */
850 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
853 setAbsTol(GMX_REAL_MIN);
854 // Our erfc algorithm has 4 ulp accuracy, so relax tolerance a bit
855 setUlpTol(4*ulpTol_);
856 GMX_EXPECT_SIMD_FUNC_NEAR(refErfc, erfcSingleAccuracy);
860 TEST_F(SimdMathTest, sinSingleAccuracy)
862 /* Increase the allowed error by the difference between the actual precision and single */
863 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
865 setRange(-8*M_PI, 8*M_PI);
866 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sinSingleAccuracy);
867 // Range reduction leads to accuracy loss, so we might want higher tolerance here
868 setRange(-10000, 10000);
869 setUlpTol(2*ulpTol_);
870 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sinSingleAccuracy);
873 TEST_F(SimdMathTest, cosSingleAccuracy)
875 /* Increase the allowed error by the difference between the actual precision and single */
876 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
878 setRange(-8*M_PI, 8*M_PI);
879 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cosSingleAccuracy);
880 // Range reduction leads to accuracy loss, so we might want higher tolerance here
881 setRange(-10000, 10000);
882 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cosSingleAccuracy);
885 TEST_F(SimdMathTest, tanSingleAccuracy)
887 /* Increase the allowed error by the difference between the actual precision and single */
888 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
890 // Tan(x) is a little sensitive due to the division in the algorithm.
891 // Rather than using lots of extra FP operations, we accept the algorithm
892 // presently only achieves a ~3 ulp error and use the medium tolerance.
893 setRange(-8*M_PI, 8*M_PI);
894 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tanSingleAccuracy);
895 // Range reduction leads to accuracy loss, so we might want higher tolerance here
896 setRange(-10000, 10000);
897 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tanSingleAccuracy);
900 TEST_F(SimdMathTest, asinSingleAccuracy)
902 /* Increase the allowed error by the difference between the actual precision and single */
903 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
905 // Our present asin(x) algorithm achieves 2-3 ulp accuracy
907 GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asinSingleAccuracy);
910 TEST_F(SimdMathTest, acosSingleAccuracy)
912 /* Increase the allowed error by the difference between the actual precision and single */
913 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
915 // Our present acos(x) algorithm achieves 2-3 ulp accuracy
917 GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acosSingleAccuracy);
920 TEST_F(SimdMathTest, atanSingleAccuracy)
922 /* Increase the allowed error by the difference between the actual precision and single */
923 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
925 // Our present atan(x) algorithm achieves 1 ulp accuracy
926 setRange(-10000, 10000);
927 GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atanSingleAccuracy);
930 TEST_F(SimdMathTest, atan2SingleAccuracy)
932 /* Increase the allowed error by the difference between the actual precision and single */
933 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
935 // test each quadrant
936 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, c3), std::atan2(c1, c4), std::atan2(c2, c5)),
937 atan2SingleAccuracy(rSimd_c0c1c2, rSimd_c3c4c5));
938 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, c3), std::atan2(-c1, c4), std::atan2(-c2, c5)),
939 atan2SingleAccuracy(rSimd_m0m1m2, rSimd_c3c4c5));
940 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, -c3), std::atan2(-c1, -c0), std::atan2(-c2, -c4)),
941 atan2SingleAccuracy(rSimd_m0m1m2, rSimd_m3m0m4));
942 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, -c3), std::atan2(c1, -c0), std::atan2(c2, -c4)),
943 atan2SingleAccuracy(rSimd_c0c1c2, rSimd_m3m0m4));
944 // cases important for calculating angles
945 // values on coordinate axes
946 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, c0), std::atan2(0, c1), std::atan2(0, c2)),
947 atan2SingleAccuracy(setZero(), rSimd_c0c1c2));
948 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, 0), std::atan2(c1, 0), std::atan2(c2, 0)),
949 atan2SingleAccuracy(rSimd_c0c1c2, setZero()));
950 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, -c0), std::atan2(0, -c1), std::atan2(0, -c2)),
951 atan2SingleAccuracy(setZero(), rSimd_m0m1m2));
952 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, 0), std::atan2(-c1, 0), std::atan2(-c2, 0)),
953 atan2SingleAccuracy(rSimd_m0m1m2, setZero()));
955 // degenerate value (origin) should return 0.0. At least IBM xlc 13.1.5 gets the reference
956 // value wrong (-nan) at -O3 optimization, so we compare to the correct value (0.0) instead.
957 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(0.0), atan2SingleAccuracy(setSimdRealFrom3R(0.0, 0.0, 0.0), setZero()));
960 TEST_F(SimdMathTest, pmeForceCorrectionSingleAccuracy)
962 // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
963 // Pme correction only needs to be ~1e-6 accuracy single.
964 // Then increase the allowed error by the difference between the actual precision and single.
965 setUlpTol( (std::int64_t(5e-6/GMX_FLOAT_EPS)) * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
968 setAbsTol(GMX_FLOAT_EPS);
969 GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrectionSingleAccuracy);
972 TEST_F(SimdMathTest, pmePotentialCorrectionSingleAccuracy)
974 // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
975 // Pme correction only needs to be ~1e-6 accuracy single.
976 // Then increase the allowed error by the difference between the actual precision and single.
977 setUlpTol( (std::int64_t(5e-6/GMX_FLOAT_EPS)) * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
980 setAbsTol(GMX_FLOAT_EPS);
981 GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrectionSingleAccuracy);
986 #endif // GMX_SIMD_HAVE_REAL