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;
125 const int niter = s_nPoints/GMX_SIMD_REAL_WIDTH;
126 int npoints = niter*GMX_SIMD_REAL_WIDTH;
129 double r; std::int64_t i;
133 float r; std::int32_t i;
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++)
154 // Clamp denormal values to zero if requested
155 if (std::abs(vref[i]) <= GMX_REAL_MIN)
159 if (std::abs(vtst[i]) <= GMX_REAL_MIN)
165 absDiff = fabs(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.
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 == false) && (signOk == false) )
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();
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;
225 // Actual math function tests below
231 /*! \cond internal */
232 /*! \addtogroup module_simd */
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) */
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)
269 invsqrtPair(x, x, &r0, &r1);
273 /*! \brief Function wrapper to return second result when testing \ref invsqrtPair */
274 SimdReal gmx_simdcall
275 tstInvsqrtPair1(SimdReal x)
278 invsqrtPair(x, x, &r0, &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) */
300 /*! \brief Dummy function returning 0.0 to test function ranges that should be zero */
301 gmx_unused static real
302 refZero(real gmx_unused x)
308 TEST_F(SimdMathTest, sqrt)
310 // The accuracy conversions lose a bit of extra accuracy compared to
311 // doing the iterations in all-double.
312 setUlpTol(4*ulpTol_);
314 // First test that 0.0 and a few other values works
315 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(0, std::sqrt(c1), std::sqrt(c2)), sqrt(setSimdRealFrom3R(0, c1, c2)));
317 // Values smaller-than-or-equal to GMX_FLOAT_MIN will be clamped to 0.0,
318 // so only test larger values
319 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
320 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrt);
323 // Make sure that values smaller than GMX_FLOAT_MIN lead to result 0.0
324 setRange(0.0, 0.99*GMX_FLOAT_MIN);
325 GMX_EXPECT_SIMD_FUNC_NEAR(refZero, sqrt);
329 TEST_F(SimdMathTest, sqrtUnsafe)
331 // The accuracy conversions lose a bit of extra accuracy compared to
332 // doing the iterations in all-double.
333 setUlpTol(4*ulpTol_);
335 setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
336 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrt<MathOptimization::Unsafe>);
339 /*! \brief Function wrapper to evaluate reference 1/x */
345 TEST_F(SimdMathTest, inv)
348 setRange(-1e10, -1e-10);
349 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, inv);
350 setRange(1e-10, 1e10);
351 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, inv);
354 TEST_F(SimdMathTest, maskzInv)
356 SimdReal x = setSimdRealFrom3R(c1, 0.0, c2);
357 SimdBool m = (setZero() < x);
358 SimdReal ref = setSimdRealFrom3R(1.0/c1, 0.0, 1.0/c2);
359 GMX_EXPECT_SIMD_REAL_NEAR(ref, maskzInv(x, m));
362 TEST_F(SimdMathTest, log)
364 setRange(1e-30, 1e30);
365 GMX_EXPECT_SIMD_FUNC_NEAR(std::log, log);
368 TEST_F(SimdMathTest, exp2)
370 // Test normal/denormal/zero range separately to make errors clearer
372 // First test the range where we get normalized (non-denormal) results,
373 // since we don't require denormal results to be reproduced correctly.
375 setRange(-1022, 1023);
379 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2);
381 // Some implementations might have denormal support, in which case they
382 // support an extended range, adding roughly the number of bits in the
383 // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
384 // In this range we allow the value to be either correct (denormal) or 0.0
386 setRange(-1075, -1022);
388 setRange(-150, -126);
390 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2);
392 // For arguments smaller than the subnormal the result should be zero
393 // both in the reference and our implementations.
395 setRange(-1000000.0, -1075.0);
397 setRange(-100000.0, -150.0);
399 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2);
401 // Test a few very negative values, including values so small that they
402 // will start to cause inf values in the polynomial interpolations
403 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp2(-GMX_FLOAT_MAX), std::exp2(-0.1*GMX_REAL_MAX), std::exp2(-GMX_REAL_MAX)),
404 exp2(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
407 TEST_F(SimdMathTest, exp2Unsafe)
409 // The unsafe version is only defined in this range
411 setRange(-1022, 1023);
415 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2<MathOptimization::Unsafe>);
418 TEST_F(SimdMathTest, exp)
420 // Test normal/denormal/zero range separately to make errors clearer
422 // First test the range where we get normalized (non-denormal) results,
423 // since we don't require denormal results to be reproduced correctly.
425 // For very small arguments that would produce results close to the
426 // smallest representable value, some of the intermediate values might
427 // trigger flush-to-zero denormals without FMA operations,
428 // e.g. for the icc compiler. Since we never use such values in Gromacs, we
429 // shrink the range a bit in that case instead of requiring the compiler to
430 // handle denormals (which might reduce performance).
432 #if GMX_SIMD_HAVE_FMA
433 setRange(-708.3, 709.1);
435 setRange(-690, 709.1);
438 #if GMX_SIMD_HAVE_FMA
439 setRange(-87.3, 88.0);
444 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
446 // Some implementations might have denormal support, in which case they
447 // support an extended range, adding roughly the number of bits in the
448 // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
449 // Then multiply with ln(2) to get our limit for exp().
450 // In this range we allow the value to be either correct (denormal) or 0.0
452 setRange(-746.0, -708.4);
454 setRange(-104.0, -87.3);
456 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, exp);
458 // For arguments smaller than the subnormal the result should be zero
459 // both in the reference and our implementations.
461 setRange(-1000000.0, -746.0);
463 setRange(-100000.0, -104.0);
465 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
467 // Test a few very negative values, including values so small that they
468 // will start to cause inf values in the polynomial interpolations
469 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp(-GMX_FLOAT_MAX), std::exp(-0.1*GMX_REAL_MAX), std::exp(-GMX_REAL_MAX)),
470 exp(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
473 TEST_F(SimdMathTest, expUnsafe)
476 #if GMX_SIMD_HAVE_FMA
477 setRange(-708.3, 709.1);
479 setRange(-690, 709.1);
482 #if GMX_SIMD_HAVE_FMA
483 setRange(-87.3, 88.0);
488 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp<MathOptimization::Unsafe>);
491 /*! \brief Function wrapper for erf(x), with argument/return in default Gromacs precision.
493 * \note Single-precision erf() in some libraries can be slightly lower precision
494 * than the SIMD flavor, so we use a cast to force double precision for reference.
499 return std::erf(static_cast<double>(x));
502 TEST_F(SimdMathTest, erf)
505 setAbsTol(GMX_REAL_MIN);
506 GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erf);
509 /*! \brief Function wrapper for erfc(x), with argument/return in default Gromacs precision.
511 * \note Single-precision erfc() in some libraries can be slightly lower precision
512 * than the SIMD flavor, so we use a cast to force double precision for reference.
517 return std::erfc(static_cast<double>(x));
520 TEST_F(SimdMathTest, erfc)
523 setAbsTol(GMX_REAL_MIN);
524 // Our erfc algorithm has 4 ulp accuracy, so relax defaultTol a bit
525 setUlpTol(4*ulpTol_);
526 GMX_EXPECT_SIMD_FUNC_NEAR(refErfc, erfc);
529 TEST_F(SimdMathTest, sin)
531 setRange(-8*M_PI, 8*M_PI);
532 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sin);
533 // Range reduction leads to accuracy loss, so we might want higher tolerance here
534 setRange(-10000, 10000);
535 setUlpTol(2*ulpTol_);
536 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sin);
539 TEST_F(SimdMathTest, cos)
541 setRange(-8*M_PI, 8*M_PI);
542 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cos);
543 // Range reduction leads to accuracy loss, so we might want higher tolerance here
544 setRange(-10000, 10000);
545 setUlpTol(2*ulpTol_);
546 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cos);
549 TEST_F(SimdMathTest, tan)
551 // Tan(x) is a little sensitive due to the division in the algorithm.
552 // Rather than using lots of extra FP operations, we accept the algorithm
553 // presently only achieves a ~3 ulp error and use the medium tolerance.
554 setRange(-8*M_PI, 8*M_PI);
555 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tan);
556 // Range reduction leads to accuracy loss, so we might want higher tolerance here
557 setRange(-10000, 10000);
558 setUlpTol(2*ulpTol_);
559 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tan);
562 TEST_F(SimdMathTest, asin)
564 // Our present asin(x) algorithm achieves 2-3 ulp accuracy
566 GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asin);
569 TEST_F(SimdMathTest, acos)
571 // Our present acos(x) algorithm achieves 2-3 ulp accuracy
573 GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acos);
576 TEST_F(SimdMathTest, atan)
578 // Our present atan(x) algorithm achieves 1 ulp accuracy
579 setRange(-10000, 10000);
580 GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atan);
583 TEST_F(SimdMathTest, atan2)
585 // test each quadrant
586 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, c3), std::atan2(c1, c4), std::atan2(c2, c5)),
587 atan2(rSimd_c0c1c2, rSimd_c3c4c5));
588 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, c3), std::atan2(-c1, c4), std::atan2(-c2, c5)),
589 atan2(rSimd_m0m1m2, rSimd_c3c4c5));
590 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, -c3), std::atan2(-c1, -c0), std::atan2(-c2, -c4)),
591 atan2(rSimd_m0m1m2, rSimd_m3m0m4));
592 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, -c3), std::atan2(c1, -c0), std::atan2(c2, -c4)),
593 atan2(rSimd_c0c1c2, rSimd_m3m0m4));
595 // cases important for calculating angles
596 // values on coordinate axes
597 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, c0), std::atan2(0, c1), std::atan2(0, c2)),
598 atan2(setZero(), rSimd_c0c1c2));
599 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, 0), std::atan2(c1, 0), std::atan2(c2, 0)),
600 atan2(rSimd_c0c1c2, setZero()));
601 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, -c0), std::atan2(0, -c1), std::atan2(0, -c2)),
602 atan2(setZero(), rSimd_m0m1m2));
603 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, 0), std::atan2(-c1, 0), std::atan2(-c2, 0)),
604 atan2(rSimd_m0m1m2, setZero()));
605 // degenerate value (origin) should return 0.0. At least IBM xlc 13.1.5 gets the reference
606 // value wrong (-nan) at -O3 optimization, so we compare to the correct value (0.0) instead.
607 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(0.0), atan2(setSimdRealFrom3R(0.0, 0.0, 0.0), setZero()));
610 /*! \brief Evaluate reference version of PME force correction. */
612 refPmeForceCorrection(real x)
616 real y = std::sqrt(x);
617 return 2*std::exp(-x)/(std::sqrt(M_PI)*x) - std::erf(static_cast<double>(y))/(x*y);
621 return -4/(3*std::sqrt(M_PI));
625 // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
626 TEST_F(SimdMathTest, pmeForceCorrection)
628 // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
630 setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
632 setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
636 setAbsTol(GMX_REAL_EPS);
637 GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrection);
640 /*! \brief Evaluate reference version of PME potential correction. */
642 refPmePotentialCorrection(real x)
644 real y = std::sqrt(x);
645 return std::erf(static_cast<double>(y))/y;
648 // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
649 TEST_F(SimdMathTest, pmePotentialCorrection)
651 // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
653 setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
655 setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
658 setAbsTol(GMX_REAL_EPS);
659 GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrection);
662 // Functions that only target single accuracy, even for double SIMD data
664 TEST_F(SimdMathTest, invsqrtSingleAccuracy)
666 /* Increase the allowed error by the difference between the actual precision and single */
667 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
669 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
670 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, invsqrtSingleAccuracy);
673 /*! \brief Function wrapper to return first result when testing \ref invsqrtPairSingleAccuracy */
674 SimdReal gmx_simdcall
675 tst_invsqrt_SingleAccuracy_pair0(SimdReal x)
678 invsqrtPairSingleAccuracy(x, x, &r0, &r1);
682 /*! \brief Function wrapper to return second result when testing \ref invsqrtPairSingleAccuracy */
683 SimdReal gmx_simdcall
684 tst_invsqrt_SingleAccuracy_pair1(SimdReal x)
687 invsqrtPairSingleAccuracy(x, x, &r0, &r1);
691 TEST_F(SimdMathTest, invsqrtPairSingleAccuracy)
693 /* Increase the allowed error by the difference between the actual precision and single */
694 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
696 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
697 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tst_invsqrt_SingleAccuracy_pair0);
698 GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tst_invsqrt_SingleAccuracy_pair1);
701 TEST_F(SimdMathTest, sqrtSingleAccuracy)
703 /* Increase the allowed error by the difference between the actual precision and single */
704 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
706 // First test that 0.0 and a few other values works
707 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(0, std::sqrt(c0), std::sqrt(c1)), sqrtSingleAccuracy(setSimdRealFrom3R(0, c0, c1)));
709 // Values smaller-than-or-equal to GMX_FLOAT_MIN will be clamped to 0.0,
710 // so only test larger values
711 setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
712 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrtSingleAccuracy);
715 // Make sure that values smaller than GMX_FLOAT_MIN lead to result 0.0
716 setRange(0.0, 0.99*GMX_FLOAT_MIN);
717 GMX_EXPECT_SIMD_FUNC_NEAR(refZero, sqrtSingleAccuracy);
721 TEST_F(SimdMathTest, sqrtSingleAccuracyUnsafe)
723 /* Increase the allowed error by the difference between the actual precision and single */
724 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
726 // Test the full range
727 setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
728 GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrtSingleAccuracy<MathOptimization::Unsafe>);
731 TEST_F(SimdMathTest, invSingleAccuracy)
733 /* Increase the allowed error by the difference between the actual precision and single */
734 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
737 setRange(-1e10, -1e-10);
738 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, invSingleAccuracy);
739 setRange(1e-10, 1e10);
740 GMX_EXPECT_SIMD_FUNC_NEAR(refInv, invSingleAccuracy);
743 TEST_F(SimdMathTest, logSingleAccuracy)
745 /* Increase the allowed error by the difference between the actual precision and single */
746 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
748 setRange(1e-30, 1e30);
749 GMX_EXPECT_SIMD_FUNC_NEAR(std::log, logSingleAccuracy);
752 TEST_F(SimdMathTest, exp2SingleAccuracy)
754 /* Increase the allowed error by the difference between the actual precision and single */
755 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
758 setRange(-1022.49, 1023.49);
760 setRange(-126, 127.49);
762 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy);
764 // Test a range that should be zero both for reference and simd versions.
765 // Some implementations might have subnormal support, in which case they
766 // support an extended range, adding roughly the number of bits in the
767 // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
769 setRange(-1000000.0, -1075.0);
771 setRange(-100000.0, -150.0);
773 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2SingleAccuracy);
775 // Test a few very negative values
776 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp2(-GMX_FLOAT_MAX), std::exp2(-0.1*GMX_REAL_MAX), std::exp2(-GMX_REAL_MAX)),
777 exp2SingleAccuracy(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
780 TEST_F(SimdMathTest, exp2SingleAccuracyUnsafe)
782 /* Increase the allowed error by the difference between the actual precision and single */
783 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
786 setRange(-1022.49, 1023.49);
788 setRange(-126, 127.49);
790 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy<MathOptimization::Unsafe>);
793 TEST_F(SimdMathTest, expSingleAccuracy)
795 /* Increase the allowed error by the difference between the actual precision and single */
796 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
799 setRange(-708.7, 709.4);
801 setRange(-87.3, 88.3);
803 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy);
805 // Test a range that should be zero both for reference and simd versions.
806 // Some implementations might have subnormal support, in which case they
807 // support an extended range, adding roughly the number of bits in the
808 // mantissa to the smallest result exponent (1023+52 in double, 127+23 single).
809 // Then multiply with ln(2) to get our limit for exp().
811 setRange(-1000000.0, -746.0);
813 setRange(-100000.0, -104.0);
815 GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, expSingleAccuracy);
817 // Test a few very negative values, including values so small that they
818 // will start to cause inf values in the polynomial interpolations
819 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp(-GMX_FLOAT_MAX), std::exp(-0.1*GMX_REAL_MAX), std::exp(-GMX_REAL_MAX)),
820 expSingleAccuracy(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
823 TEST_F(SimdMathTest, expSingleAccuracyUnsafe)
825 /* Increase the allowed error by the difference between the actual precision and single */
826 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
829 setRange(-708.7, 709.4);
831 setRange(-87.3, 88.3);
833 GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy<MathOptimization::Unsafe>);
836 TEST_F(SimdMathTest, erfSingleAccuracy)
838 /* Increase the allowed error by the difference between the actual precision and single */
839 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
842 setAbsTol(GMX_REAL_MIN);
843 GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erfSingleAccuracy);
846 TEST_F(SimdMathTest, erfcSingleAccuracy)
848 /* Increase the allowed error by the difference between the actual precision and single */
849 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
852 setAbsTol(GMX_REAL_MIN);
853 // Our erfc algorithm has 4 ulp accuracy, so relax tolerance a bit
854 setUlpTol(4*ulpTol_);
855 GMX_EXPECT_SIMD_FUNC_NEAR(refErfc, erfcSingleAccuracy);
859 TEST_F(SimdMathTest, sinSingleAccuracy)
861 /* Increase the allowed error by the difference between the actual precision and single */
862 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
864 setRange(-8*M_PI, 8*M_PI);
865 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sinSingleAccuracy);
866 // Range reduction leads to accuracy loss, so we might want higher tolerance here
867 setRange(-10000, 10000);
868 setUlpTol(2*ulpTol_);
869 GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sinSingleAccuracy);
872 TEST_F(SimdMathTest, cosSingleAccuracy)
874 /* Increase the allowed error by the difference between the actual precision and single */
875 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
877 setRange(-8*M_PI, 8*M_PI);
878 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cosSingleAccuracy);
879 // Range reduction leads to accuracy loss, so we might want higher tolerance here
880 setRange(-10000, 10000);
881 GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cosSingleAccuracy);
884 TEST_F(SimdMathTest, tanSingleAccuracy)
886 /* Increase the allowed error by the difference between the actual precision and single */
887 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
889 // Tan(x) is a little sensitive due to the division in the algorithm.
890 // Rather than using lots of extra FP operations, we accept the algorithm
891 // presently only achieves a ~3 ulp error and use the medium tolerance.
892 setRange(-8*M_PI, 8*M_PI);
893 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tanSingleAccuracy);
894 // Range reduction leads to accuracy loss, so we might want higher tolerance here
895 setRange(-10000, 10000);
896 GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tanSingleAccuracy);
899 TEST_F(SimdMathTest, asinSingleAccuracy)
901 /* Increase the allowed error by the difference between the actual precision and single */
902 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
904 // Our present asin(x) algorithm achieves 2-3 ulp accuracy
906 GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asinSingleAccuracy);
909 TEST_F(SimdMathTest, acosSingleAccuracy)
911 /* Increase the allowed error by the difference between the actual precision and single */
912 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
914 // Our present acos(x) algorithm achieves 2-3 ulp accuracy
916 GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acosSingleAccuracy);
919 TEST_F(SimdMathTest, atanSingleAccuracy)
921 /* Increase the allowed error by the difference between the actual precision and single */
922 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
924 // Our present atan(x) algorithm achieves 1 ulp accuracy
925 setRange(-10000, 10000);
926 GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atanSingleAccuracy);
929 TEST_F(SimdMathTest, atan2SingleAccuracy)
931 /* Increase the allowed error by the difference between the actual precision and single */
932 setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
934 // test each quadrant
935 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, c3), std::atan2(c1, c4), std::atan2(c2, c5)),
936 atan2SingleAccuracy(rSimd_c0c1c2, rSimd_c3c4c5));
937 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, c3), std::atan2(-c1, c4), std::atan2(-c2, c5)),
938 atan2SingleAccuracy(rSimd_m0m1m2, rSimd_c3c4c5));
939 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, -c3), std::atan2(-c1, -c0), std::atan2(-c2, -c4)),
940 atan2SingleAccuracy(rSimd_m0m1m2, rSimd_m3m0m4));
941 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, -c3), std::atan2(c1, -c0), std::atan2(c2, -c4)),
942 atan2SingleAccuracy(rSimd_c0c1c2, rSimd_m3m0m4));
943 // cases important for calculating angles
944 // values on coordinate axes
945 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, c0), std::atan2(0, c1), std::atan2(0, c2)),
946 atan2SingleAccuracy(setZero(), rSimd_c0c1c2));
947 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, 0), std::atan2(c1, 0), std::atan2(c2, 0)),
948 atan2SingleAccuracy(rSimd_c0c1c2, setZero()));
949 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, -c0), std::atan2(0, -c1), std::atan2(0, -c2)),
950 atan2SingleAccuracy(setZero(), rSimd_m0m1m2));
951 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, 0), std::atan2(-c1, 0), std::atan2(-c2, 0)),
952 atan2SingleAccuracy(rSimd_m0m1m2, setZero()));
954 // degenerate value (origin) should return 0.0. At least IBM xlc 13.1.5 gets the reference
955 // value wrong (-nan) at -O3 optimization, so we compare to the correct value (0.0) instead.
956 GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(0.0), atan2SingleAccuracy(setSimdRealFrom3R(0.0, 0.0, 0.0), setZero()));
959 TEST_F(SimdMathTest, pmeForceCorrectionSingleAccuracy)
961 // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
962 // Pme correction only needs to be ~1e-6 accuracy single.
963 // Then increase the allowed error by the difference between the actual precision and single.
964 setUlpTol( (std::int64_t(5e-6/GMX_FLOAT_EPS)) * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
967 setAbsTol(GMX_FLOAT_EPS);
968 GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrectionSingleAccuracy);
971 TEST_F(SimdMathTest, pmePotentialCorrectionSingleAccuracy)
973 // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
974 // Pme correction only needs to be ~1e-6 accuracy single.
975 // Then increase the allowed error by the difference between the actual precision and single.
976 setUlpTol( (std::int64_t(5e-6/GMX_FLOAT_EPS)) * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
979 setAbsTol(GMX_FLOAT_EPS);
980 GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrectionSingleAccuracy);
985 #endif // GMX_SIMD_HAVE_REAL