Merge branch release-2018
[alexxy/gromacs.git] / src / gromacs / simd / tests / simd_math.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
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.
8  *
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.
13  *
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.
18  *
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.
23  *
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.
31  *
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.
34  */
35 #include "gmxpre.h"
36
37 #include "gromacs/simd/simd_math.h"
38
39 #include "config.h"
40
41 #include <cmath>
42 #include <cstdint>
43
44 #include <vector>
45
46 #include "gromacs/math/utilities.h"
47 #include "gromacs/options/basicoptions.h"
48 #include "gromacs/simd/simd.h"
49
50 #include "simd.h"
51
52 #if GMX_SIMD
53
54 namespace gmx
55 {
56 namespace test
57 {
58
59 /*! \cond internal */
60 /*! \addtogroup module_simd */
61 /*! \{ */
62
63 #if GMX_SIMD_HAVE_REAL
64
65 class SimdMathTest : public SimdTest
66 {
67     public:
68         ::testing::AssertionResult
69                             compareSimdMathFunction(const char *  refFuncExpr,
70                                                     const char *  simdFuncExpr,
71                                                     const char *  denormalsToZeroExpr,
72                                                     real          refFunc(real x),
73                                                     SimdReal      gmx_simdcall simdFunc(SimdReal x),
74                                                     bool          denormalsToZero);
75 };
76
77 /*! \brief Test approximate equality of SIMD vs reference version of a function.
78  *
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.
81  */
82 #define GMX_EXPECT_SIMD_FUNC_NEAR(refFunc, tstFunc) \
83     EXPECT_PRED_FORMAT3(compareSimdMathFunction, refFunc, tstFunc, false)
84
85 /*! \brief Test approximate equality of SIMD vs reference function, denormals can be zero.
86  *
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.
89  *
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.
92  */
93 #define GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(refFunc, tstFunc) \
94     EXPECT_PRED_FORMAT3(compareSimdMathFunction, refFunc, tstFunc, true)
95
96 /*! \brief Implementation routine to compare SIMD vs reference functions.
97  *
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.
105  *
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.
109  */
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)
117 {
118     std::vector<real>            vx(GMX_SIMD_REAL_WIDTH);
119     std::vector<real>            vref(GMX_SIMD_REAL_WIDTH);
120     std::vector<real>            vtst(GMX_SIMD_REAL_WIDTH);
121     real                         dx, absDiff;
122     std::int64_t                 ulpDiff, maxUlpDiff;
123     real                         maxUlpDiffPos;
124     real                         refValMaxUlpDiff, simdValMaxUlpDiff;
125     bool                         absOk, signOk;
126     int                          i, iter;
127     int                          niter   = s_nPoints/GMX_SIMD_REAL_WIDTH;
128     int                          npoints = niter*GMX_SIMD_REAL_WIDTH;
129 #    if GMX_DOUBLE
130     union {
131         double r; std::int64_t i;
132     } conv0, conv1;
133 #    else
134     union {
135         float  r; std::int32_t i;
136     } conv0, conv1;
137 #    endif
138
139     maxUlpDiff = 0;
140     dx         = (range_.second-range_.first)/npoints;
141
142     for (iter = 0; iter < niter; iter++)
143     {
144         for (i = 0; i < GMX_SIMD_REAL_WIDTH; i++)
145         {
146             vx[i]   = range_.first+dx*(iter*GMX_SIMD_REAL_WIDTH+i);
147             vref[i] = refFunc(vx[i]);
148         }
149         vtst  = simdReal2Vector(simdFunc(vector2SimdReal(vx)));
150
151         for (i = 0, signOk = true, absOk = true; i < GMX_SIMD_REAL_WIDTH; i++)
152         {
153             if (denormalsToZero)
154             {
155                 // Clamp denormal values to zero if requested
156                 if (std::abs(vref[i]) <= GMX_REAL_MIN)
157                 {
158                     vref[i] = 0.0;
159                 }
160                 if (std::abs(vtst[i]) <= GMX_REAL_MIN)
161                 {
162                     vtst[i] = 0.0;
163                 }
164             }
165
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));
170
171             if (absDiff >= absTol_)
172             {
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.
177                  */
178                 conv0.r           = vref[i];
179                 conv1.r           = vtst[i];
180                 ulpDiff           = llabs(conv0.i-conv1.i);
181                 if (ulpDiff > maxUlpDiff)
182                 {
183                     maxUlpDiff        = ulpDiff;
184                     maxUlpDiffPos     = vx[i];
185                     refValMaxUlpDiff  = vref[i];
186                     simdValMaxUlpDiff = vtst[i];
187                 }
188             }
189         }
190         if ( (absOk == false) && (signOk == false) )
191         {
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;
201         }
202     }
203
204     if (maxUlpDiff <= ulpTol_)
205     {
206         return ::testing::AssertionSuccess();
207     }
208     else
209     {
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;
219     }
220 }
221
222 /*! \} */
223 /*! \endcond */
224
225
226 // Actual math function tests below
227
228
229 namespace
230 {
231
232 /*! \cond internal */
233 /*! \addtogroup module_simd */
234 /*! \{ */
235
236 TEST_F(SimdMathTest, copysign)
237 {
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)));
242 }
243
244 /*! \brief Function wrapper to evaluate reference 1/sqrt(x) */
245 static real
246 refInvsqrt(real x)
247 {
248     return 1.0/std::sqrt(x);
249 }
250
251 TEST_F(SimdMathTest, invsqrt)
252 {
253     setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
254     GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, invsqrt);
255 }
256
257 TEST_F(SimdMathTest, maskzInvsqrt)
258 {
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));
263 }
264
265 /*! \brief Function wrapper to return first result when testing \ref invsqrtPair */
266 SimdReal gmx_simdcall
267 tstInvsqrtPair0(SimdReal x)
268 {
269     SimdReal r0, r1;
270     invsqrtPair(x, x, &r0, &r1);
271     return r0;
272 }
273
274 /*! \brief Function wrapper to return second result when testing \ref invsqrtPair */
275 SimdReal gmx_simdcall
276 tstInvsqrtPair1(SimdReal x)
277 {
278     SimdReal r0, r1;
279     invsqrtPair(x, x, &r0, &r1);
280     return r1;
281 }
282
283 TEST_F(SimdMathTest, invsqrtPair)
284 {
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_);
289
290     GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tstInvsqrtPair0);
291     GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tstInvsqrtPair1);
292 }
293
294 /*! \brief Function wrapper to evaluate reference sqrt(x) */
295 static real
296 refSqrt(real x)
297 {
298     return std::sqrt(x);
299 }
300
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)
304 {
305     return 0.0;
306 }
307
308
309 TEST_F(SimdMathTest, sqrt)
310 {
311     // The accuracy conversions lose a bit of extra accuracy compared to
312     // doing the iterations in all-double.
313     setUlpTol(4*ulpTol_);
314
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)));
317
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);
322
323 #if GMX_DOUBLE
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);
327 #endif
328 }
329
330 TEST_F(SimdMathTest, sqrtUnsafe)
331 {
332     // The accuracy conversions lose a bit of extra accuracy compared to
333     // doing the iterations in all-double.
334     setUlpTol(4*ulpTol_);
335
336     setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
337     GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrt<MathOptimization::Unsafe>);
338 }
339
340 /*! \brief Function wrapper to evaluate reference 1/x */
341 real refInv(real x)
342 {
343     return 1.0/x;
344 }
345
346 TEST_F(SimdMathTest, inv)
347 {
348     // test <0
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);
353 }
354
355 TEST_F(SimdMathTest, maskzInv)
356 {
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));
361 }
362
363 TEST_F(SimdMathTest, log)
364 {
365     setRange(1e-30, 1e30);
366     GMX_EXPECT_SIMD_FUNC_NEAR(std::log, log);
367 }
368
369 TEST_F(SimdMathTest, exp2)
370 {
371     // Test normal/denormal/zero range separately to make errors clearer
372
373     // First test the range where we get normalized (non-denormal) results,
374     // since we don't require denormal results to be reproduced correctly.
375 #if GMX_DOUBLE
376     setRange(-1022, 1023);
377 #else
378     setRange(-126, 127);
379 #endif
380     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2);
381
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
386 #if GMX_DOUBLE
387     setRange(-1075, -1022);
388 #else
389     setRange(-150, -126);
390 #endif
391     GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2);
392
393     // For arguments smaller than the subnormal the result should be zero
394     // both in the reference and our implementations.
395 #if GMX_DOUBLE
396     setRange(-1000000.0, -1075.0);
397 #else
398     setRange(-100000.0, -150.0);
399 #endif
400     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2);
401
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)));
406 }
407
408 TEST_F(SimdMathTest, exp2Unsafe)
409 {
410     // The unsafe version is only defined in this range
411 #if GMX_DOUBLE
412     setRange(-1022, 1023);
413 #else
414     setRange(-126, 127);
415 #endif
416     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2<MathOptimization::Unsafe>);
417 }
418
419 TEST_F(SimdMathTest, exp)
420 {
421     // Test normal/denormal/zero range separately to make errors clearer
422
423     // First test the range where we get normalized (non-denormal) results,
424     // since we don't require denormal results to be reproduced correctly.
425     //
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).
432 #if GMX_DOUBLE
433 #if GMX_SIMD_HAVE_FMA
434     setRange(-708.3, 709.1);
435 #else
436     setRange(-690, 709.1);
437 #endif
438 #else
439 #if GMX_SIMD_HAVE_FMA
440     setRange(-87.3, 88.0);
441 #else
442     setRange(-80, 88.0);
443 #endif
444 #endif
445     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
446
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
452 #if GMX_DOUBLE
453     setRange(-746.0, -708.4);
454 #else
455     setRange(-104.0, -87.3);
456 #endif
457     GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, exp);
458
459     // For arguments smaller than the subnormal the result should be zero
460     // both in the reference and our implementations.
461 #if GMX_DOUBLE
462     setRange(-1000000.0, -746.0);
463 #else
464     setRange(-100000.0, -104.0);
465 #endif
466     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
467
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)));
472 }
473
474 TEST_F(SimdMathTest, expUnsafe)
475 {
476 #if GMX_DOUBLE
477 #if GMX_SIMD_HAVE_FMA
478     setRange(-708.3, 709.1);
479 #else
480     setRange(-690, 709.1);
481 #endif
482 #else
483 #if GMX_SIMD_HAVE_FMA
484     setRange(-87.3, 88.0);
485 #else
486     setRange(-80, 88.0);
487 #endif
488 #endif
489     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp<MathOptimization::Unsafe>);
490 }
491
492 /*! \brief Function wrapper for erf(x), with argument/return in default Gromacs precision.
493  *
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.
496  */
497 real
498 refErf(real x)
499 {
500     return std::erf(static_cast<double>(x));
501 }
502
503 TEST_F(SimdMathTest, erf)
504 {
505     setRange(-9, 9);
506     setAbsTol(GMX_REAL_MIN);
507     GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erf);
508 }
509
510 /*! \brief Function wrapper for erfc(x), with argument/return in default Gromacs precision.
511  *
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.
514  */
515 real
516 refErfc(real x)
517 {
518     return std::erfc(static_cast<double>(x));
519 }
520
521 TEST_F(SimdMathTest, erfc)
522 {
523     setRange(-9, 9);
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);
528 }
529
530 TEST_F(SimdMathTest, sin)
531 {
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);
538 }
539
540 TEST_F(SimdMathTest, cos)
541 {
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);
548 }
549
550 TEST_F(SimdMathTest, tan)
551 {
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);
561 }
562
563 TEST_F(SimdMathTest, asin)
564 {
565     // Our present asin(x) algorithm achieves 2-3 ulp accuracy
566     setRange(-1, 1);
567     GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asin);
568 }
569
570 TEST_F(SimdMathTest, acos)
571 {
572     // Our present acos(x) algorithm achieves 2-3 ulp accuracy
573     setRange(-1, 1);
574     GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acos);
575 }
576
577 TEST_F(SimdMathTest, atan)
578 {
579     // Our present atan(x) algorithm achieves 1 ulp accuracy
580     setRange(-10000, 10000);
581     GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atan);
582 }
583
584 TEST_F(SimdMathTest, atan2)
585 {
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));
595
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()));
609 }
610
611 /*! \brief Evaluate reference version of PME force correction. */
612 real
613 refPmeForceCorrection(real x)
614 {
615     if (x != 0)
616     {
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);
619     }
620     else
621     {
622         return -4/(3*std::sqrt(M_PI));
623     }
624 }
625
626 // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
627 TEST_F(SimdMathTest, pmeForceCorrection)
628 {
629     // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
630 #if GMX_DOUBLE
631     setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
632 #else
633     setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
634 #endif
635
636     setRange(0.15, 4);
637     setAbsTol(GMX_REAL_EPS);
638     GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrection);
639 }
640
641 /*! \brief Evaluate reference version of PME potential correction. */
642 real
643 refPmePotentialCorrection(real x)
644 {
645     real y = std::sqrt(x);
646     return std::erf(static_cast<double>(y))/y;
647 }
648
649 // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
650 TEST_F(SimdMathTest, pmePotentialCorrection)
651 {
652     // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
653 #if GMX_DOUBLE
654     setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
655 #else
656     setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
657 #endif
658     setRange(0.15, 4);
659     setAbsTol(GMX_REAL_EPS);
660     GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrection);
661 }
662
663 // Functions that only target single accuracy, even for double SIMD data
664
665 TEST_F(SimdMathTest, invsqrtSingleAccuracy)
666 {
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)));
669
670     setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
671     GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, invsqrtSingleAccuracy);
672 }
673
674 /*! \brief Function wrapper to return first result when testing \ref invsqrtPairSingleAccuracy */
675 SimdReal gmx_simdcall
676 tst_invsqrt_SingleAccuracy_pair0(SimdReal x)
677 {
678     SimdReal r0, r1;
679     invsqrtPairSingleAccuracy(x, x, &r0, &r1);
680     return r0;
681 }
682
683 /*! \brief Function wrapper to return second result when testing \ref invsqrtPairSingleAccuracy */
684 SimdReal gmx_simdcall
685 tst_invsqrt_SingleAccuracy_pair1(SimdReal x)
686 {
687     SimdReal r0, r1;
688     invsqrtPairSingleAccuracy(x, x, &r0, &r1);
689     return r1;
690 }
691
692 TEST_F(SimdMathTest, invsqrtPairSingleAccuracy)
693 {
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)));
696
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);
700 }
701
702 TEST_F(SimdMathTest, sqrtSingleAccuracy)
703 {
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)));
706
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)));
709
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);
714
715 #if GMX_DOUBLE
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);
719 #endif
720 }
721
722 TEST_F(SimdMathTest, sqrtSingleAccuracyUnsafe)
723 {
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)));
726
727     // Test the full range
728     setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
729     GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrtSingleAccuracy<MathOptimization::Unsafe>);
730 }
731
732 TEST_F(SimdMathTest, invSingleAccuracy)
733 {
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)));
736
737     // test <0
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);
742 }
743
744 TEST_F(SimdMathTest, logSingleAccuracy)
745 {
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)));
748
749     setRange(1e-30, 1e30);
750     GMX_EXPECT_SIMD_FUNC_NEAR(std::log, logSingleAccuracy);
751 }
752
753 TEST_F(SimdMathTest, exp2SingleAccuracy)
754 {
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)));
757
758 #if GMX_DOUBLE
759     setRange(-1022.49, 1023.49);
760 #else
761     setRange(-126, 127.49);
762 #endif
763     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy);
764
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).
769 #if GMX_DOUBLE
770     setRange(-1000000.0, -1075.0);
771 #else
772     setRange(-100000.0, -150.0);
773 #endif
774     GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2SingleAccuracy);
775
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)));
779 }
780
781 TEST_F(SimdMathTest, exp2SingleAccuracyUnsafe)
782 {
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)));
785
786 #if GMX_DOUBLE
787     setRange(-1022.49, 1023.49);
788 #else
789     setRange(-126, 127.49);
790 #endif
791     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy<MathOptimization::Unsafe>);
792 }
793
794 TEST_F(SimdMathTest, expSingleAccuracy)
795 {
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)));
798
799 #if GMX_DOUBLE
800     setRange(-708.7, 709.4);
801 #else
802     setRange(-87.3, 88.3);
803 #endif
804     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy);
805
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().
811 #if GMX_DOUBLE
812     setRange(-1000000.0, -746.0);
813 #else
814     setRange(-100000.0, -104.0);
815 #endif
816     GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, expSingleAccuracy);
817
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)));
822 }
823
824 TEST_F(SimdMathTest, expSingleAccuracyUnsafe)
825 {
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)));
828
829 #if GMX_DOUBLE
830     setRange(-708.7, 709.4);
831 #else
832     setRange(-87.3, 88.3);
833 #endif
834     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy<MathOptimization::Unsafe>);
835 }
836
837 TEST_F(SimdMathTest, erfSingleAccuracy)
838 {
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)));
841
842     setRange(-9, 9);
843     setAbsTol(GMX_REAL_MIN);
844     GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erfSingleAccuracy);
845 }
846
847 TEST_F(SimdMathTest, erfcSingleAccuracy)
848 {
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)));
851
852     setRange(-9, 9);
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);
857 }
858
859
860 TEST_F(SimdMathTest, sinSingleAccuracy)
861 {
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)));
864
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);
871 }
872
873 TEST_F(SimdMathTest, cosSingleAccuracy)
874 {
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)));
877
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);
883 }
884
885 TEST_F(SimdMathTest, tanSingleAccuracy)
886 {
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)));
889
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);
898 }
899
900 TEST_F(SimdMathTest, asinSingleAccuracy)
901 {
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)));
904
905     // Our present asin(x) algorithm achieves 2-3 ulp accuracy
906     setRange(-1, 1);
907     GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asinSingleAccuracy);
908 }
909
910 TEST_F(SimdMathTest, acosSingleAccuracy)
911 {
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)));
914
915     // Our present acos(x) algorithm achieves 2-3 ulp accuracy
916     setRange(-1, 1);
917     GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acosSingleAccuracy);
918 }
919
920 TEST_F(SimdMathTest, atanSingleAccuracy)
921 {
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)));
924
925     // Our present atan(x) algorithm achieves 1 ulp accuracy
926     setRange(-10000, 10000);
927     GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atanSingleAccuracy);
928 }
929
930 TEST_F(SimdMathTest, atan2SingleAccuracy)
931 {
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)));
934
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()));
954
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()));
958 }
959
960 TEST_F(SimdMathTest, pmeForceCorrectionSingleAccuracy)
961 {
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)));
966
967     setRange(0.15, 4);
968     setAbsTol(GMX_FLOAT_EPS);
969     GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrectionSingleAccuracy);
970 }
971
972 TEST_F(SimdMathTest, pmePotentialCorrectionSingleAccuracy)
973 {
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)));
978
979     setRange(0.15, 4);
980     setAbsTol(GMX_FLOAT_EPS);
981     GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrectionSingleAccuracy);
982 }
983
984 }      // namespace
985
986 #endif // GMX_SIMD_HAVE_REAL
987
988 /*! \} */
989 /*! \endcond */
990
991 }      // namespace
992 }      // namespace
993
994 #endif // GMX_SIMD