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
= 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.
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();
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 real
refZero(real gmx_unused x
)
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
);
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
);
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 */
344 TEST_F(SimdMathTest
, inv
)
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.
374 setRange(-1022, 1023);
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
385 setRange(-1075, -1022);
387 setRange(-150, -126);
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.
394 setRange(-1000000.0, -1075.0);
396 setRange(-100000.0, -150.0);
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
410 setRange(-1022, 1023);
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).
431 #if GMX_SIMD_HAVE_FMA
432 setRange(-708.3, 709.1);
434 setRange(-690, 709.1);
437 #if GMX_SIMD_HAVE_FMA
438 setRange(-87.3, 88.0);
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
451 setRange(-746.0, -708.4);
453 setRange(-104.0, -87.3);
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.
460 setRange(-1000000.0, -746.0);
462 setRange(-100000.0, -104.0);
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
)
475 #if GMX_SIMD_HAVE_FMA
476 setRange(-708.3, 709.1);
478 setRange(-690, 709.1);
481 #if GMX_SIMD_HAVE_FMA
482 setRange(-87.3, 88.0);
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.
498 return std::erf(static_cast<double>(x
));
501 TEST_F(SimdMathTest
, erf
)
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.
516 return std::erfc(static_cast<double>(x
));
519 TEST_F(SimdMathTest
, erfc
)
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
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
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. */
611 refPmeForceCorrection(real x
)
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
);
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
629 setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS
));
631 setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS
));
635 setAbsTol(GMX_REAL_EPS
);
636 GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection
, pmeForceCorrection
);
639 /*! \brief Evaluate reference version of PME potential correction. */
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
652 setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS
));
654 setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS
));
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
)
677 invsqrtPairSingleAccuracy(x
, x
, &r0
, &r1
);
681 /*! \brief Function wrapper to return second result when testing \ref invsqrtPairSingleAccuracy */
682 SimdReal gmx_simdcall
683 tst_invsqrt_SingleAccuracy_pair1(SimdReal x
)
686 invsqrtPairSingleAccuracy(x
, x
, &r0
, &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
);
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
);
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_
);
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_
);
757 setRange(-1022.49, 1023.49);
759 setRange(-126, 127.49);
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).
768 setRange(-1000000.0, -1075.0);
770 setRange(-100000.0, -150.0);
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_
);
785 setRange(-1022.49, 1023.49);
787 setRange(-126, 127.49);
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_
);
798 setRange(-708.7, 709.4);
800 setRange(-87.3, 88.3);
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().
810 setRange(-1000000.0, -746.0);
812 setRange(-100000.0, -104.0);
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_
);
828 setRange(-708.7, 709.4);
830 setRange(-87.3, 88.3);
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_
);
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_
);
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
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
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
));
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
));
978 setAbsTol(GMX_FLOAT_EPS
);
979 GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection
, pmePotentialCorrectionSingleAccuracy
);
984 #endif // GMX_SIMD_HAVE_REAL