7c1e65195abe7063f5059e914048b3e1cd4844eb
[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     const int                    niter   = s_nPoints/GMX_SIMD_REAL_WIDTH;
126     int                          npoints = niter*GMX_SIMD_REAL_WIDTH;
127 #    if GMX_DOUBLE
128     union {
129         double r; std::int64_t i;
130     } conv0, conv1;
131 #    else
132     union {
133         float  r; std::int32_t i;
134     } conv0, conv1;
135 #    endif
136
137     maxUlpDiff = 0;
138     dx         = (range_.second-range_.first)/npoints;
139
140     for (int iter = 0; iter < niter; iter++)
141     {
142         for (int i = 0; i < GMX_SIMD_REAL_WIDTH; i++)
143         {
144             vx[i]   = range_.first+dx*(iter*GMX_SIMD_REAL_WIDTH+i);
145             vref[i] = refFunc(vx[i]);
146         }
147         vtst  = simdReal2Vector(simdFunc(vector2SimdReal(vx)));
148
149         bool absOk = true, signOk = true;
150         for (int i = 0; i < GMX_SIMD_REAL_WIDTH; i++)
151         {
152             if (denormalsToZero)
153             {
154                 // Clamp denormal values to zero if requested
155                 if (std::abs(vref[i]) <= GMX_REAL_MIN)
156                 {
157                     vref[i] = 0.0;
158                 }
159                 if (std::abs(vtst[i]) <= GMX_REAL_MIN)
160                 {
161                     vtst[i] = 0.0;
162                 }
163             }
164
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));
169
170             if (absDiff >= absTol_)
171             {
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.
176                  */
177                 conv0.r           = vref[i];
178                 conv1.r           = vtst[i];
179                 ulpDiff           = llabs(conv0.i-conv1.i);
180                 if (ulpDiff > maxUlpDiff)
181                 {
182                     maxUlpDiff        = ulpDiff;
183                     maxUlpDiffPos     = vx[i];
184                     refValMaxUlpDiff  = vref[i];
185                     simdValMaxUlpDiff = vtst[i];
186                 }
187             }
188         }
189         if ( (absOk == false) && (signOk == false) )
190         {
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;
200         }
201     }
202     GMX_RELEASE_ASSERT(ulpTol_ >= 0, "Invalid ulp value.");
203     if (maxUlpDiff <= ulpTol_)
204     {
205         return ::testing::AssertionSuccess();
206     }
207     else
208     {
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;
218     }
219 }
220
221 /*! \} */
222 /*! \endcond */
223
224
225 // Actual math function tests below
226
227
228 namespace
229 {
230
231 /*! \cond internal */
232 /*! \addtogroup module_simd */
233 /*! \{ */
234
235 TEST_F(SimdMathTest, copysign)
236 {
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)));
241 }
242
243 /*! \brief Function wrapper to evaluate reference 1/sqrt(x) */
244 static real
245 refInvsqrt(real x)
246 {
247     return 1.0/std::sqrt(x);
248 }
249
250 TEST_F(SimdMathTest, invsqrt)
251 {
252     setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
253     GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, invsqrt);
254 }
255
256 TEST_F(SimdMathTest, maskzInvsqrt)
257 {
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));
262 }
263
264 /*! \brief Function wrapper to return first result when testing \ref invsqrtPair */
265 SimdReal gmx_simdcall
266 tstInvsqrtPair0(SimdReal x)
267 {
268     SimdReal r0, r1;
269     invsqrtPair(x, x, &r0, &r1);
270     return r0;
271 }
272
273 /*! \brief Function wrapper to return second result when testing \ref invsqrtPair */
274 SimdReal gmx_simdcall
275 tstInvsqrtPair1(SimdReal x)
276 {
277     SimdReal r0, r1;
278     invsqrtPair(x, x, &r0, &r1);
279     return r1;
280 }
281
282 TEST_F(SimdMathTest, invsqrtPair)
283 {
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_);
288
289     GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tstInvsqrtPair0);
290     GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tstInvsqrtPair1);
291 }
292
293 /*! \brief Function wrapper to evaluate reference sqrt(x) */
294 static real
295 refSqrt(real x)
296 {
297     return std::sqrt(x);
298 }
299
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)
303 {
304     return 0.0;
305 }
306
307
308 TEST_F(SimdMathTest, sqrt)
309 {
310     // The accuracy conversions lose a bit of extra accuracy compared to
311     // doing the iterations in all-double.
312     setUlpTol(4*ulpTol_);
313
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)));
316
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);
321
322 #if GMX_DOUBLE
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);
326 #endif
327 }
328
329 TEST_F(SimdMathTest, sqrtUnsafe)
330 {
331     // The accuracy conversions lose a bit of extra accuracy compared to
332     // doing the iterations in all-double.
333     setUlpTol(4*ulpTol_);
334
335     setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
336     GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrt<MathOptimization::Unsafe>);
337 }
338
339 /*! \brief Function wrapper to evaluate reference 1/x */
340 real refInv(real x)
341 {
342     return 1.0/x;
343 }
344
345 TEST_F(SimdMathTest, inv)
346 {
347     // test <0
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);
352 }
353
354 TEST_F(SimdMathTest, maskzInv)
355 {
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));
360 }
361
362 TEST_F(SimdMathTest, log)
363 {
364     setRange(1e-30, 1e30);
365     GMX_EXPECT_SIMD_FUNC_NEAR(std::log, log);
366 }
367
368 TEST_F(SimdMathTest, exp2)
369 {
370     // Test normal/denormal/zero range separately to make errors clearer
371
372     // First test the range where we get normalized (non-denormal) results,
373     // since we don't require denormal results to be reproduced correctly.
374 #if GMX_DOUBLE
375     setRange(-1022, 1023);
376 #else
377     setRange(-126, 127);
378 #endif
379     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2);
380
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
385 #if GMX_DOUBLE
386     setRange(-1075, -1022);
387 #else
388     setRange(-150, -126);
389 #endif
390     GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2);
391
392     // For arguments smaller than the subnormal the result should be zero
393     // both in the reference and our implementations.
394 #if GMX_DOUBLE
395     setRange(-1000000.0, -1075.0);
396 #else
397     setRange(-100000.0, -150.0);
398 #endif
399     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2);
400
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)));
405 }
406
407 TEST_F(SimdMathTest, exp2Unsafe)
408 {
409     // The unsafe version is only defined in this range
410 #if GMX_DOUBLE
411     setRange(-1022, 1023);
412 #else
413     setRange(-126, 127);
414 #endif
415     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2<MathOptimization::Unsafe>);
416 }
417
418 TEST_F(SimdMathTest, exp)
419 {
420     // Test normal/denormal/zero range separately to make errors clearer
421
422     // First test the range where we get normalized (non-denormal) results,
423     // since we don't require denormal results to be reproduced correctly.
424     //
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).
431 #if GMX_DOUBLE
432 #if GMX_SIMD_HAVE_FMA
433     setRange(-708.3, 709.1);
434 #else
435     setRange(-690, 709.1);
436 #endif
437 #else
438 #if GMX_SIMD_HAVE_FMA
439     setRange(-87.3, 88.0);
440 #else
441     setRange(-80, 88.0);
442 #endif
443 #endif
444     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
445
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
451 #if GMX_DOUBLE
452     setRange(-746.0, -708.4);
453 #else
454     setRange(-104.0, -87.3);
455 #endif
456     GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, exp);
457
458     // For arguments smaller than the subnormal the result should be zero
459     // both in the reference and our implementations.
460 #if GMX_DOUBLE
461     setRange(-1000000.0, -746.0);
462 #else
463     setRange(-100000.0, -104.0);
464 #endif
465     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
466
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)));
471 }
472
473 TEST_F(SimdMathTest, expUnsafe)
474 {
475 #if GMX_DOUBLE
476 #if GMX_SIMD_HAVE_FMA
477     setRange(-708.3, 709.1);
478 #else
479     setRange(-690, 709.1);
480 #endif
481 #else
482 #if GMX_SIMD_HAVE_FMA
483     setRange(-87.3, 88.0);
484 #else
485     setRange(-80, 88.0);
486 #endif
487 #endif
488     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp<MathOptimization::Unsafe>);
489 }
490
491 /*! \brief Function wrapper for erf(x), with argument/return in default Gromacs precision.
492  *
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.
495  */
496 real
497 refErf(real x)
498 {
499     return std::erf(static_cast<double>(x));
500 }
501
502 TEST_F(SimdMathTest, erf)
503 {
504     setRange(-9, 9);
505     setAbsTol(GMX_REAL_MIN);
506     GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erf);
507 }
508
509 /*! \brief Function wrapper for erfc(x), with argument/return in default Gromacs precision.
510  *
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.
513  */
514 real
515 refErfc(real x)
516 {
517     return std::erfc(static_cast<double>(x));
518 }
519
520 TEST_F(SimdMathTest, erfc)
521 {
522     setRange(-9, 9);
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);
527 }
528
529 TEST_F(SimdMathTest, sin)
530 {
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);
537 }
538
539 TEST_F(SimdMathTest, cos)
540 {
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);
547 }
548
549 TEST_F(SimdMathTest, tan)
550 {
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);
560 }
561
562 TEST_F(SimdMathTest, asin)
563 {
564     // Our present asin(x) algorithm achieves 2-3 ulp accuracy
565     setRange(-1, 1);
566     GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asin);
567 }
568
569 TEST_F(SimdMathTest, acos)
570 {
571     // Our present acos(x) algorithm achieves 2-3 ulp accuracy
572     setRange(-1, 1);
573     GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acos);
574 }
575
576 TEST_F(SimdMathTest, atan)
577 {
578     // Our present atan(x) algorithm achieves 1 ulp accuracy
579     setRange(-10000, 10000);
580     GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atan);
581 }
582
583 TEST_F(SimdMathTest, atan2)
584 {
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));
594
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()));
608 }
609
610 /*! \brief Evaluate reference version of PME force correction. */
611 real
612 refPmeForceCorrection(real x)
613 {
614     if (x != 0)
615     {
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);
618     }
619     else
620     {
621         return -4/(3*std::sqrt(M_PI));
622     }
623 }
624
625 // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
626 TEST_F(SimdMathTest, pmeForceCorrection)
627 {
628     // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
629 #if GMX_DOUBLE
630     setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
631 #else
632     setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
633 #endif
634
635     setRange(0.15, 4);
636     setAbsTol(GMX_REAL_EPS);
637     GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrection);
638 }
639
640 /*! \brief Evaluate reference version of PME potential correction. */
641 real
642 refPmePotentialCorrection(real x)
643 {
644     real y = std::sqrt(x);
645     return std::erf(static_cast<double>(y))/y;
646 }
647
648 // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
649 TEST_F(SimdMathTest, pmePotentialCorrection)
650 {
651     // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
652 #if GMX_DOUBLE
653     setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
654 #else
655     setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
656 #endif
657     setRange(0.15, 4);
658     setAbsTol(GMX_REAL_EPS);
659     GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrection);
660 }
661
662 // Functions that only target single accuracy, even for double SIMD data
663
664 TEST_F(SimdMathTest, invsqrtSingleAccuracy)
665 {
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)));
668
669     setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
670     GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, invsqrtSingleAccuracy);
671 }
672
673 /*! \brief Function wrapper to return first result when testing \ref invsqrtPairSingleAccuracy */
674 SimdReal gmx_simdcall
675 tst_invsqrt_SingleAccuracy_pair0(SimdReal x)
676 {
677     SimdReal r0, r1;
678     invsqrtPairSingleAccuracy(x, x, &r0, &r1);
679     return r0;
680 }
681
682 /*! \brief Function wrapper to return second result when testing \ref invsqrtPairSingleAccuracy */
683 SimdReal gmx_simdcall
684 tst_invsqrt_SingleAccuracy_pair1(SimdReal x)
685 {
686     SimdReal r0, r1;
687     invsqrtPairSingleAccuracy(x, x, &r0, &r1);
688     return r1;
689 }
690
691 TEST_F(SimdMathTest, invsqrtPairSingleAccuracy)
692 {
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)));
695
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);
699 }
700
701 TEST_F(SimdMathTest, sqrtSingleAccuracy)
702 {
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)));
705
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)));
708
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);
713
714 #if GMX_DOUBLE
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);
718 #endif
719 }
720
721 TEST_F(SimdMathTest, sqrtSingleAccuracyUnsafe)
722 {
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)));
725
726     // Test the full range
727     setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
728     GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrtSingleAccuracy<MathOptimization::Unsafe>);
729 }
730
731 TEST_F(SimdMathTest, invSingleAccuracy)
732 {
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)));
735
736     // test <0
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);
741 }
742
743 TEST_F(SimdMathTest, logSingleAccuracy)
744 {
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)));
747
748     setRange(1e-30, 1e30);
749     GMX_EXPECT_SIMD_FUNC_NEAR(std::log, logSingleAccuracy);
750 }
751
752 TEST_F(SimdMathTest, exp2SingleAccuracy)
753 {
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)));
756
757 #if GMX_DOUBLE
758     setRange(-1022.49, 1023.49);
759 #else
760     setRange(-126, 127.49);
761 #endif
762     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy);
763
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).
768 #if GMX_DOUBLE
769     setRange(-1000000.0, -1075.0);
770 #else
771     setRange(-100000.0, -150.0);
772 #endif
773     GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2SingleAccuracy);
774
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)));
778 }
779
780 TEST_F(SimdMathTest, exp2SingleAccuracyUnsafe)
781 {
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)));
784
785 #if GMX_DOUBLE
786     setRange(-1022.49, 1023.49);
787 #else
788     setRange(-126, 127.49);
789 #endif
790     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy<MathOptimization::Unsafe>);
791 }
792
793 TEST_F(SimdMathTest, expSingleAccuracy)
794 {
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)));
797
798 #if GMX_DOUBLE
799     setRange(-708.7, 709.4);
800 #else
801     setRange(-87.3, 88.3);
802 #endif
803     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy);
804
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().
810 #if GMX_DOUBLE
811     setRange(-1000000.0, -746.0);
812 #else
813     setRange(-100000.0, -104.0);
814 #endif
815     GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, expSingleAccuracy);
816
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)));
821 }
822
823 TEST_F(SimdMathTest, expSingleAccuracyUnsafe)
824 {
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)));
827
828 #if GMX_DOUBLE
829     setRange(-708.7, 709.4);
830 #else
831     setRange(-87.3, 88.3);
832 #endif
833     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy<MathOptimization::Unsafe>);
834 }
835
836 TEST_F(SimdMathTest, erfSingleAccuracy)
837 {
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)));
840
841     setRange(-9, 9);
842     setAbsTol(GMX_REAL_MIN);
843     GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erfSingleAccuracy);
844 }
845
846 TEST_F(SimdMathTest, erfcSingleAccuracy)
847 {
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)));
850
851     setRange(-9, 9);
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);
856 }
857
858
859 TEST_F(SimdMathTest, sinSingleAccuracy)
860 {
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)));
863
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);
870 }
871
872 TEST_F(SimdMathTest, cosSingleAccuracy)
873 {
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)));
876
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);
882 }
883
884 TEST_F(SimdMathTest, tanSingleAccuracy)
885 {
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)));
888
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);
897 }
898
899 TEST_F(SimdMathTest, asinSingleAccuracy)
900 {
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)));
903
904     // Our present asin(x) algorithm achieves 2-3 ulp accuracy
905     setRange(-1, 1);
906     GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asinSingleAccuracy);
907 }
908
909 TEST_F(SimdMathTest, acosSingleAccuracy)
910 {
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)));
913
914     // Our present acos(x) algorithm achieves 2-3 ulp accuracy
915     setRange(-1, 1);
916     GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acosSingleAccuracy);
917 }
918
919 TEST_F(SimdMathTest, atanSingleAccuracy)
920 {
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)));
923
924     // Our present atan(x) algorithm achieves 1 ulp accuracy
925     setRange(-10000, 10000);
926     GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atanSingleAccuracy);
927 }
928
929 TEST_F(SimdMathTest, atan2SingleAccuracy)
930 {
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)));
933
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()));
953
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()));
957 }
958
959 TEST_F(SimdMathTest, pmeForceCorrectionSingleAccuracy)
960 {
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)));
965
966     setRange(0.15, 4);
967     setAbsTol(GMX_FLOAT_EPS);
968     GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrectionSingleAccuracy);
969 }
970
971 TEST_F(SimdMathTest, pmePotentialCorrectionSingleAccuracy)
972 {
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)));
977
978     setRange(0.15, 4);
979     setAbsTol(GMX_FLOAT_EPS);
980     GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrectionSingleAccuracy);
981 }
982
983 }      // namespace
984
985 #endif // GMX_SIMD_HAVE_REAL
986
987 /*! \} */
988 /*! \endcond */
989
990 }      // namespace
991 }      // namespace
992
993 #endif // GMX_SIMD