Add cool quote
[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, 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 #if GMX_DOUBLE
426     setRange(-708.3, 709.1);
427 #else
428     setRange(-87.3, 88.0);
429 #endif
430     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
431
432     // Some implementations might have denormal support, in which case they
433     // support an extended range, adding roughly the number of bits in the
434     // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
435     // Then multiply with ln(2) to get our limit for exp().
436     // In this range we allow the value to be either correct (denormal) or 0.0
437 #if GMX_DOUBLE
438     setRange(-746.0, -708.3);
439 #else
440     setRange(-104.0, -87.3);
441 #endif
442     GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, exp);
443
444     // For arguments smaller than the subnormal the result should be zero
445     // both in the reference and our implementations.
446 #if GMX_DOUBLE
447     setRange(-1000000.0, -746.0);
448 #else
449     setRange(-100000.0, -104.0);
450 #endif
451     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp);
452
453     // Test a few very negative values, including values so small that they
454     // will start to cause inf values in the polynomial interpolations
455     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp(-GMX_FLOAT_MAX), std::exp(-0.1*GMX_REAL_MAX), std::exp(-GMX_REAL_MAX)),
456                               exp(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
457 }
458
459 TEST_F(SimdMathTest, expUnsafe)
460 {
461 #if GMX_DOUBLE
462     setRange(-708.3, 709.1);
463 #else
464     setRange(-87.3, 88.0);
465 #endif
466     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, exp<MathOptimization::Unsafe>);
467 }
468
469 /*! \brief Function wrapper for erf(x), with argument/return in default Gromacs precision.
470  *
471  * \note Single-precision erf() in some libraries can be slightly lower precision
472  * than the SIMD flavor, so we use a cast to force double precision for reference.
473  */
474 real
475 refErf(real x)
476 {
477     return std::erf(static_cast<double>(x));
478 }
479
480 TEST_F(SimdMathTest, erf)
481 {
482     setRange(-9, 9);
483     setAbsTol(GMX_REAL_MIN);
484     GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erf);
485 }
486
487 /*! \brief Function wrapper for erfc(x), with argument/return in default Gromacs precision.
488  *
489  * \note Single-precision erfc() in some libraries can be slightly lower precision
490  * than the SIMD flavor, so we use a cast to force double precision for reference.
491  */
492 real
493 refErfc(real x)
494 {
495     return std::erfc(static_cast<double>(x));
496 }
497
498 TEST_F(SimdMathTest, erfc)
499 {
500     setRange(-9, 9);
501     setAbsTol(GMX_REAL_MIN);
502     // Our erfc algorithm has 4 ulp accuracy, so relax defaultTol a bit
503     setUlpTol(4*ulpTol_);
504     GMX_EXPECT_SIMD_FUNC_NEAR(refErfc, erfc);
505 }
506
507 TEST_F(SimdMathTest, sin)
508 {
509     setRange(-8*M_PI, 8*M_PI);
510     GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sin);
511     // Range reduction leads to accuracy loss, so we might want higher tolerance here
512     setRange(-10000, 10000);
513     setUlpTol(2*ulpTol_);
514     GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sin);
515 }
516
517 TEST_F(SimdMathTest, cos)
518 {
519     setRange(-8*M_PI, 8*M_PI);
520     GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cos);
521     // Range reduction leads to accuracy loss, so we might want higher tolerance here
522     setRange(-10000, 10000);
523     setUlpTol(2*ulpTol_);
524     GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cos);
525 }
526
527 TEST_F(SimdMathTest, tan)
528 {
529     // Tan(x) is a little sensitive due to the division in the algorithm.
530     // Rather than using lots of extra FP operations, we accept the algorithm
531     // presently only achieves a ~3 ulp error and use the medium tolerance.
532     setRange(-8*M_PI, 8*M_PI);
533     GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tan);
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::tan, tan);
538 }
539
540 TEST_F(SimdMathTest, asin)
541 {
542     // Our present asin(x) algorithm achieves 2-3 ulp accuracy
543     setRange(-1, 1);
544     GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asin);
545 }
546
547 TEST_F(SimdMathTest, acos)
548 {
549     // Our present acos(x) algorithm achieves 2-3 ulp accuracy
550     setRange(-1, 1);
551     GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acos);
552 }
553
554 TEST_F(SimdMathTest, atan)
555 {
556     // Our present atan(x) algorithm achieves 1 ulp accuracy
557     setRange(-10000, 10000);
558     GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atan);
559 }
560
561 TEST_F(SimdMathTest, atan2)
562 {
563     // test each quadrant
564     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, c3), std::atan2(c1, c4), std::atan2(c2, c5)),
565                               atan2(rSimd_c0c1c2, rSimd_c3c4c5));
566     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, c3), std::atan2(-c1, c4), std::atan2(-c2, c5)),
567                               atan2(rSimd_m0m1m2, rSimd_c3c4c5));
568     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, -c3), std::atan2(-c1, -c0), std::atan2(-c2, -c4)),
569                               atan2(rSimd_m0m1m2, rSimd_m3m0m4));
570     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, -c3), std::atan2(c1, -c0), std::atan2(c2, -c4)),
571                               atan2(rSimd_c0c1c2, rSimd_m3m0m4));
572
573     // cases important for calculating angles
574     // values on coordinate axes
575     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, c0), std::atan2(0, c1), std::atan2(0, c2)),
576                               atan2(setZero(), rSimd_c0c1c2));
577     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, 0), std::atan2(c1, 0), std::atan2(c2, 0)),
578                               atan2(rSimd_c0c1c2, setZero()));
579     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, -c0), std::atan2(0, -c1), std::atan2(0, -c2)),
580                               atan2(setZero(), rSimd_m0m1m2));
581     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, 0), std::atan2(-c1, 0), std::atan2(-c2, 0)),
582                               atan2(rSimd_m0m1m2, setZero()));
583     // degenerate value (origin) should return 0.0. At least IBM xlc 13.1.5 gets the reference
584     // value wrong (-nan) at -O3 optimization, so we compare to the correct value (0.0) instead.
585     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(0.0), atan2(setSimdRealFrom3R(0.0, 0.0, 0.0), setZero()));
586 }
587
588 /*! \brief Evaluate reference version of PME force correction. */
589 real
590 refPmeForceCorrection(real x)
591 {
592     if (x != 0)
593     {
594         real y = std::sqrt(x);
595         return 2*std::exp(-x)/(std::sqrt(M_PI)*x) - std::erf(static_cast<double>(y))/(x*y);
596     }
597     else
598     {
599         return -4/(3*std::sqrt(M_PI));
600     }
601 }
602
603 // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
604 TEST_F(SimdMathTest, pmeForceCorrection)
605 {
606     // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
607 #if GMX_DOUBLE
608     setUlpTol(std::int64_t(5e-10/GMX_REAL_EPS));
609 #else
610     setUlpTol(std::int64_t(5e-6/GMX_REAL_EPS));
611 #endif
612
613     setRange(0.15, 4);
614     setAbsTol(GMX_REAL_EPS);
615     GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrection);
616 }
617
618 /*! \brief Evaluate reference version of PME potential correction. */
619 real
620 refPmePotentialCorrection(real x)
621 {
622     real y = std::sqrt(x);
623     return std::erf(static_cast<double>(y))/y;
624 }
625
626 // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
627 TEST_F(SimdMathTest, pmePotentialCorrection)
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     setRange(0.15, 4);
636     setAbsTol(GMX_REAL_EPS);
637     GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrection);
638 }
639
640 // Functions that only target single accuracy, even for double SIMD data
641
642 TEST_F(SimdMathTest, invsqrtSingleAccuracy)
643 {
644     /* Increase the allowed error by the difference between the actual precision and single */
645     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
646
647     setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
648     GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, invsqrtSingleAccuracy);
649 }
650
651 /*! \brief Function wrapper to return first result when testing \ref invsqrtPairSingleAccuracy */
652 SimdReal gmx_simdcall
653 tst_invsqrt_SingleAccuracy_pair0(SimdReal x)
654 {
655     SimdReal r0, r1;
656     invsqrtPairSingleAccuracy(x, x, &r0, &r1);
657     return r0;
658 }
659
660 /*! \brief Function wrapper to return second result when testing \ref invsqrtPairSingleAccuracy */
661 SimdReal gmx_simdcall
662 tst_invsqrt_SingleAccuracy_pair1(SimdReal x)
663 {
664     SimdReal r0, r1;
665     invsqrtPairSingleAccuracy(x, x, &r0, &r1);
666     return r1;
667 }
668
669 TEST_F(SimdMathTest, invsqrtPairSingleAccuracy)
670 {
671     /* Increase the allowed error by the difference between the actual precision and single */
672     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
673
674     setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
675     GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tst_invsqrt_SingleAccuracy_pair0);
676     GMX_EXPECT_SIMD_FUNC_NEAR(refInvsqrt, tst_invsqrt_SingleAccuracy_pair1);
677 }
678
679 TEST_F(SimdMathTest, sqrtSingleAccuracy)
680 {
681     /* Increase the allowed error by the difference between the actual precision and single */
682     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
683
684     // First test that 0.0 and a few other values works
685     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(0, std::sqrt(c0), std::sqrt(c1)), sqrtSingleAccuracy(setSimdRealFrom3R(0, c0, c1)));
686
687     // Values smaller-than-or-equal to GMX_FLOAT_MIN will be clamped to 0.0,
688     // so only test larger values
689     setRange(1.01*GMX_FLOAT_MIN, GMX_FLOAT_MAX);
690     GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrtSingleAccuracy);
691
692 #if GMX_DOUBLE
693     // Make sure that values smaller than GMX_FLOAT_MIN lead to result 0.0
694     setRange(0.0, 0.99*GMX_FLOAT_MIN);
695     GMX_EXPECT_SIMD_FUNC_NEAR(refZero, sqrtSingleAccuracy);
696 #endif
697 }
698
699 TEST_F(SimdMathTest, sqrtSingleAccuracyUnsafe)
700 {
701     /* Increase the allowed error by the difference between the actual precision and single */
702     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
703
704     // Test the full range
705     setRange(GMX_FLOAT_MIN, GMX_FLOAT_MAX);
706     GMX_EXPECT_SIMD_FUNC_NEAR(refSqrt, sqrtSingleAccuracy<MathOptimization::Unsafe>);
707 }
708
709 TEST_F(SimdMathTest, invSingleAccuracy)
710 {
711     /* Increase the allowed error by the difference between the actual precision and single */
712     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
713
714     // test <0
715     setRange(-1e10, -1e-10);
716     GMX_EXPECT_SIMD_FUNC_NEAR(refInv, invSingleAccuracy);
717     setRange(1e-10, 1e10);
718     GMX_EXPECT_SIMD_FUNC_NEAR(refInv, invSingleAccuracy);
719 }
720
721 TEST_F(SimdMathTest, logSingleAccuracy)
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     setRange(1e-30, 1e30);
727     GMX_EXPECT_SIMD_FUNC_NEAR(std::log, logSingleAccuracy);
728 }
729
730 TEST_F(SimdMathTest, exp2SingleAccuracy)
731 {
732     /* Increase the allowed error by the difference between the actual precision and single */
733     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
734
735 #if GMX_DOUBLE
736     setRange(-1022.49, 1023.49);
737 #else
738     setRange(-126, 127.49);
739 #endif
740     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy);
741
742     // Test a range that should be zero both for reference and simd versions.
743     // Some implementations might have subnormal support, in which case they
744     // support an extended range, adding roughly the number of bits in the
745     // mantissa to the smallest allowed arg (1023+52 in double, 127+23 single).
746 #if GMX_DOUBLE
747     setRange(-1000000.0, -1075.0);
748 #else
749     setRange(-100000.0, -150.0);
750 #endif
751     GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp2, exp2SingleAccuracy);
752
753     // Test a few very negative values
754     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp2(-GMX_FLOAT_MAX), std::exp2(-0.1*GMX_REAL_MAX), std::exp2(-GMX_REAL_MAX)),
755                               exp2SingleAccuracy(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
756 }
757
758 TEST_F(SimdMathTest, exp2SingleAccuracyUnsafe)
759 {
760     /* Increase the allowed error by the difference between the actual precision and single */
761     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
762
763 #if GMX_DOUBLE
764     setRange(-1022.49, 1023.49);
765 #else
766     setRange(-126, 127.49);
767 #endif
768     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp2, exp2SingleAccuracy<MathOptimization::Unsafe>);
769 }
770
771 TEST_F(SimdMathTest, expSingleAccuracy)
772 {
773     /* Increase the allowed error by the difference between the actual precision and single */
774     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
775
776 #if GMX_DOUBLE
777     setRange(-708.7, 709.4);
778 #else
779     setRange(-87.3, 88.3);
780 #endif
781     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy);
782
783     // Test a range that should be zero both for reference and simd versions.
784     // Some implementations might have subnormal support, in which case they
785     // support an extended range, adding roughly the number of bits in the
786     // mantissa to the smallest result exponent (1023+52 in double, 127+23 single).
787     // Then multiply with ln(2) to get our limit for exp().
788 #if GMX_DOUBLE
789     setRange(-1000000.0, -746.0);
790 #else
791     setRange(-100000.0, -104.0);
792 #endif
793     GMX_EXPECT_SIMD_FUNC_NEAR_DTZ(std::exp, expSingleAccuracy);
794
795     // Test a few very negative values, including values so small that they
796     // will start to cause inf values in the polynomial interpolations
797     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::exp(-GMX_FLOAT_MAX), std::exp(-0.1*GMX_REAL_MAX), std::exp(-GMX_REAL_MAX)),
798                               expSingleAccuracy(setSimdRealFrom3R(-GMX_FLOAT_MAX, -0.1*GMX_REAL_MAX, -GMX_REAL_MAX)));
799 }
800
801 TEST_F(SimdMathTest, expSingleAccuracyUnsafe)
802 {
803     /* Increase the allowed error by the difference between the actual precision and single */
804     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
805
806 #if GMX_DOUBLE
807     setRange(-708.7, 709.4);
808 #else
809     setRange(-87.3, 88.3);
810 #endif
811     GMX_EXPECT_SIMD_FUNC_NEAR(std::exp, expSingleAccuracy<MathOptimization::Unsafe>);
812 }
813
814 TEST_F(SimdMathTest, erfSingleAccuracy)
815 {
816     /* Increase the allowed error by the difference between the actual precision and single */
817     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
818
819     setRange(-9, 9);
820     setAbsTol(GMX_REAL_MIN);
821     GMX_EXPECT_SIMD_FUNC_NEAR(refErf, erfSingleAccuracy);
822 }
823
824 TEST_F(SimdMathTest, erfcSingleAccuracy)
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     setRange(-9, 9);
830     setAbsTol(GMX_REAL_MIN);
831     // Our erfc algorithm has 4 ulp accuracy, so relax tolerance a bit
832     setUlpTol(4*ulpTol_);
833     GMX_EXPECT_SIMD_FUNC_NEAR(refErfc, erfcSingleAccuracy);
834 }
835
836
837 TEST_F(SimdMathTest, sinSingleAccuracy)
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(-8*M_PI, 8*M_PI);
843     GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sinSingleAccuracy);
844     // Range reduction leads to accuracy loss, so we might want higher tolerance here
845     setRange(-10000, 10000);
846     setUlpTol(2*ulpTol_);
847     GMX_EXPECT_SIMD_FUNC_NEAR(std::sin, sinSingleAccuracy);
848 }
849
850 TEST_F(SimdMathTest, cosSingleAccuracy)
851 {
852     /* Increase the allowed error by the difference between the actual precision and single */
853     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
854
855     setRange(-8*M_PI, 8*M_PI);
856     GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cosSingleAccuracy);
857     // Range reduction leads to accuracy loss, so we might want higher tolerance here
858     setRange(-10000, 10000);
859     GMX_EXPECT_SIMD_FUNC_NEAR(std::cos, cosSingleAccuracy);
860 }
861
862 TEST_F(SimdMathTest, tanSingleAccuracy)
863 {
864     /* Increase the allowed error by the difference between the actual precision and single */
865     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
866
867     // Tan(x) is a little sensitive due to the division in the algorithm.
868     // Rather than using lots of extra FP operations, we accept the algorithm
869     // presently only achieves a ~3 ulp error and use the medium tolerance.
870     setRange(-8*M_PI, 8*M_PI);
871     GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tanSingleAccuracy);
872     // Range reduction leads to accuracy loss, so we might want higher tolerance here
873     setRange(-10000, 10000);
874     GMX_EXPECT_SIMD_FUNC_NEAR(std::tan, tanSingleAccuracy);
875 }
876
877 TEST_F(SimdMathTest, asinSingleAccuracy)
878 {
879     /* Increase the allowed error by the difference between the actual precision and single */
880     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
881
882     // Our present asin(x) algorithm achieves 2-3 ulp accuracy
883     setRange(-1, 1);
884     GMX_EXPECT_SIMD_FUNC_NEAR(std::asin, asinSingleAccuracy);
885 }
886
887 TEST_F(SimdMathTest, acosSingleAccuracy)
888 {
889     /* Increase the allowed error by the difference between the actual precision and single */
890     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
891
892     // Our present acos(x) algorithm achieves 2-3 ulp accuracy
893     setRange(-1, 1);
894     GMX_EXPECT_SIMD_FUNC_NEAR(std::acos, acosSingleAccuracy);
895 }
896
897 TEST_F(SimdMathTest, atanSingleAccuracy)
898 {
899     /* Increase the allowed error by the difference between the actual precision and single */
900     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
901
902     // Our present atan(x) algorithm achieves 1 ulp accuracy
903     setRange(-10000, 10000);
904     GMX_EXPECT_SIMD_FUNC_NEAR(std::atan, atanSingleAccuracy);
905 }
906
907 TEST_F(SimdMathTest, atan2SingleAccuracy)
908 {
909     /* Increase the allowed error by the difference between the actual precision and single */
910     setUlpTol(ulpTol_ * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
911
912     // test each quadrant
913     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, c3), std::atan2(c1, c4), std::atan2(c2, c5)),
914                               atan2SingleAccuracy(rSimd_c0c1c2, rSimd_c3c4c5));
915     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, c3), std::atan2(-c1, c4), std::atan2(-c2, c5)),
916                               atan2SingleAccuracy(rSimd_m0m1m2, rSimd_c3c4c5));
917     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, -c3), std::atan2(-c1, -c0), std::atan2(-c2, -c4)),
918                               atan2SingleAccuracy(rSimd_m0m1m2, rSimd_m3m0m4));
919     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, -c3), std::atan2(c1, -c0), std::atan2(c2, -c4)),
920                               atan2SingleAccuracy(rSimd_c0c1c2, rSimd_m3m0m4));
921     // cases important for calculating angles
922     // values on coordinate axes
923     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, c0), std::atan2(0, c1), std::atan2(0, c2)),
924                               atan2SingleAccuracy(setZero(), rSimd_c0c1c2));
925     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(c0, 0), std::atan2(c1, 0), std::atan2(c2, 0)),
926                               atan2SingleAccuracy(rSimd_c0c1c2, setZero()));
927     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(0, -c0), std::atan2(0, -c1), std::atan2(0, -c2)),
928                               atan2SingleAccuracy(setZero(), rSimd_m0m1m2));
929     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(std::atan2(-c0, 0), std::atan2(-c1, 0), std::atan2(-c2, 0)),
930                               atan2SingleAccuracy(rSimd_m0m1m2, setZero()));
931
932     // degenerate value (origin) should return 0.0. At least IBM xlc 13.1.5 gets the reference
933     // value wrong (-nan) at -O3 optimization, so we compare to the correct value (0.0) instead.
934     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(0.0), atan2SingleAccuracy(setSimdRealFrom3R(0.0, 0.0, 0.0), setZero()));
935 }
936
937 TEST_F(SimdMathTest, pmeForceCorrectionSingleAccuracy)
938 {
939     // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
940     // Pme correction only needs to be ~1e-6 accuracy single.
941     // Then increase the allowed error by the difference between the actual precision and single.
942     setUlpTol( (std::int64_t(5e-6/GMX_FLOAT_EPS)) * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
943
944     setRange(0.15, 4);
945     setAbsTol(GMX_FLOAT_EPS);
946     GMX_EXPECT_SIMD_FUNC_NEAR(refPmeForceCorrection, pmeForceCorrectionSingleAccuracy);
947 }
948
949 TEST_F(SimdMathTest, pmePotentialCorrectionSingleAccuracy)
950 {
951     // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
952     // Pme correction only needs to be ~1e-6 accuracy single.
953     // Then increase the allowed error by the difference between the actual precision and single.
954     setUlpTol( (std::int64_t(5e-6/GMX_FLOAT_EPS)) * (1LL << (std::numeric_limits<real>::digits-std::numeric_limits<float>::digits)));
955
956     setRange(0.15, 4);
957     setAbsTol(GMX_FLOAT_EPS);
958     GMX_EXPECT_SIMD_FUNC_NEAR(refPmePotentialCorrection, pmePotentialCorrectionSingleAccuracy);
959 }
960
961 }      // namespace
962
963 #endif // GMX_SIMD_HAVE_REAL
964
965 /*! \} */
966 /*! \endcond */
967
968 }      // namespace
969 }      // namespace
970
971 #endif // GMX_SIMD