Enable fp-exceptions
[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, 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 <vector>
42
43 #include "gromacs/math/utilities.h"
44 #include "gromacs/options/basicoptions.h"
45 #include "gromacs/simd/simd.h"
46
47 #include "simd.h"
48
49 namespace gmx
50 {
51 namespace test
52 {
53
54 /*! \cond internal */
55 /*! \addtogroup module_simd */
56 /*! \{ */
57
58 #ifdef GMX_SIMD_HAVE_REAL
59
60 class SimdMathTest : public SimdTest
61 {
62     public:
63         ::testing::AssertionResult
64                             compareSimdMathFunction(const char * refFuncExpr, const char *simdFuncExpr,
65                                                     real refFunc(real x),     gmx_simd_real_t gmx_simdcall simdFunc(gmx_simd_real_t x));
66 };
67
68 /*! \brief Test approximate equality of SIMD vs reference version of a function.
69  *
70  * This macro takes vanilla C and SIMD flavors of a function and tests it with
71  * the number of points, range, and tolerances specified by the test fixture class.
72  */
73 #define GMX_EXPECT_SIMD_FUNC_NEAR(refFunc, tstFunc) \
74     EXPECT_PRED_FORMAT2(compareSimdMathFunction, refFunc, tstFunc)
75
76 /*! \brief Implementation routine to compare SIMD vs reference functions.
77  *
78  * \param refFuncExpr   Description of reference function expression
79  * \param simdFuncExpr  Description of SIMD function expression
80  * \param refFunc       Reference math function pointer
81  * \param simdFunc      SIMD math function pointer
82  *
83  * The function will be tested with the range and tolerances specified in
84  * the SimdBaseTest class. You should not never call this function directly,
85  * but use the macro GMX_EXPECT_SIMD_FUNC_NEAR(refFunc,tstFunc) instead.
86  */
87 ::testing::AssertionResult
88 SimdMathTest::compareSimdMathFunction(const char * refFuncExpr, const char *simdFuncExpr,
89                                       real refFunc(real x),     gmx_simd_real_t gmx_simdcall simdFunc(gmx_simd_real_t x))
90 {
91     std::vector<real>            vx(GMX_SIMD_REAL_WIDTH);
92     std::vector<real>            vref(GMX_SIMD_REAL_WIDTH);
93     std::vector<real>            vtst(GMX_SIMD_REAL_WIDTH);
94     real                         dx, absDiff;
95     gmx_int64_t                  ulpDiff, maxUlpDiff;
96     real                         maxUlpDiffPos;
97     real                         refValMaxUlpDiff, simdValMaxUlpDiff;
98     bool                         absOk, signOk;
99     int                          i, iter;
100     int                          niter   = s_nPoints/GMX_SIMD_REAL_WIDTH;
101     int                          npoints = niter*GMX_SIMD_REAL_WIDTH;
102 #    ifdef GMX_DOUBLE
103     union {
104         double r; gmx_int64_t i;
105     } conv0, conv1;
106 #    else
107     union {
108         float  r; gmx_int32_t i;
109     } conv0, conv1;
110 #    endif
111
112     maxUlpDiff = 0;
113     dx         = (range_.second-range_.first)/npoints;
114
115     for (iter = 0; iter < niter; iter++)
116     {
117         for (i = 0; i < GMX_SIMD_REAL_WIDTH; i++)
118         {
119             vx[i]   = range_.first+dx*(iter*GMX_SIMD_REAL_WIDTH+i);
120             vref[i] = refFunc(vx[i]);
121         }
122         vtst  = simdReal2Vector(simdFunc(vector2SimdReal(vx)));
123
124         for (i = 0, signOk = true, absOk = true; i < GMX_SIMD_REAL_WIDTH; i++)
125         {
126             absDiff = fabs(vref[i]-vtst[i]);
127             absOk   = absOk  && ( absDiff < absTol_ );
128             signOk  = signOk && ( (vref[i] >= 0 && vtst[i] >= 0) ||
129                                   (vref[i] <= 0 && vtst[i] <= 0));
130
131             if (absDiff >= absTol_)
132             {
133                 /* We replicate the trivial ulp differences comparison here rather than
134                  * calling the lower-level routine for comparing them, since this enables
135                  * us to run through the entire test range and report the largest deviation
136                  * without lots of extra glue routines.
137                  */
138                 conv0.r           = vref[i];
139                 conv1.r           = vtst[i];
140                 ulpDiff           = llabs(conv0.i-conv1.i);
141                 if (ulpDiff > maxUlpDiff)
142                 {
143                     maxUlpDiff        = ulpDiff;
144                     maxUlpDiffPos     = vx[i];
145                     refValMaxUlpDiff  = vref[i];
146                     simdValMaxUlpDiff = vtst[i];
147                 }
148             }
149         }
150         if ( (absOk == false) && (signOk == false) )
151         {
152             return ::testing::AssertionFailure()
153                    << "Failing SIMD math function comparison due to sign differences." << std::endl
154                    << "Reference function: " << refFuncExpr << std::endl
155                    << "Simd function:      " << simdFuncExpr << std::endl
156                    << "Test range is ( " << range_.first << " , " << range_.second << " ) " << std::endl
157                    << "First sign difference around x=" << std::setprecision(20) << ::testing::PrintToString(vx) << std::endl
158                    << "Ref values:  " << std::setprecision(20) << ::testing::PrintToString(vref) << std::endl
159                    << "SIMD values: " << std::setprecision(20) << ::testing::PrintToString(vtst) << std::endl;
160         }
161     }
162
163     if (maxUlpDiff <= ulpTol_)
164     {
165         return ::testing::AssertionSuccess();
166     }
167     else
168     {
169         return ::testing::AssertionFailure()
170                << "Failing SIMD math function ulp comparison between " << refFuncExpr << " and " << simdFuncExpr << std::endl
171                << "Requested ulp tolerance: " << ulpTol_ << std::endl
172                << "Requested abs tolerance: " << absTol_ << std::endl
173                << "Largest Ulp difference occurs for x=" << std::setprecision(20) << maxUlpDiffPos << std::endl
174                << "Ref  values: " << std::setprecision(20) << refValMaxUlpDiff << std::endl
175                << "SIMD values: " << std::setprecision(20) << simdValMaxUlpDiff << std::endl
176                << "Ulp diff.:   " << std::setprecision(20) << maxUlpDiff << std::endl;
177     }
178 }
179
180 /*! \} */
181 /*! \endcond */
182
183
184 // Actual math function tests below
185
186
187 namespace
188 {
189
190 /*! \cond internal */
191 /*! \addtogroup module_simd */
192 /*! \{ */
193
194 TEST_F(SimdMathTest, gmxSimdXorSignR)
195 {
196     GMX_EXPECT_SIMD_REAL_EQ(setSimdRealFrom3R(-4, 5, 6), gmx_simd_xor_sign_r(setSimdRealFrom3R(4, 5, 6), setSimdRealFrom3R(-5, 2, 0)));
197     GMX_EXPECT_SIMD_REAL_EQ(setSimdRealFrom3R(4, -5, -6), gmx_simd_xor_sign_r(setSimdRealFrom3R(-4, -5, -6), setSimdRealFrom3R(-5, 2, 0)));
198 }
199
200 /*! \brief Function wrapper to evaluate reference 1/sqrt(x) */
201 static real
202 ref_invsqrt(real x)
203 {
204     return 1.0/sqrt(x);
205 }
206
207 TEST_F(SimdMathTest, gmxSimdInvsqrtR)
208 {
209     setRange(1e-10, 1e10);
210     GMX_EXPECT_SIMD_FUNC_NEAR(ref_invsqrt, gmx_simd_invsqrt_r);
211 }
212
213 /*! \brief Function wrapper to return first result when testing \ref gmx_simd_invsqrt_pair_r */
214 gmx_simd_real_t gmx_simdcall
215 tst_invsqrt_pair0(gmx_simd_real_t x)
216 {
217     gmx_simd_real_t r0, r1;
218     gmx_simd_invsqrt_pair_r(x, x, &r0, &r1);
219     return r0;
220 }
221
222 /*! \brief Function wrapper to return second result when testing \ref gmx_simd_invsqrt_pair_r */
223 gmx_simd_real_t gmx_simdcall
224 tst_invsqrt_pair1(gmx_simd_real_t x)
225 {
226     gmx_simd_real_t r0, r1;
227     gmx_simd_invsqrt_pair_r(x, x, &r0, &r1);
228     return r1;
229 }
230
231 TEST_F(SimdMathTest, gmxSimdInvsqrtPairR)
232 {
233     setRange(1e-10, 1e10);
234     // The accuracy conversions lose a bit of extra accuracy compared to
235     // doing the iterations in all-double.
236     setUlpTol(4*ulpTol_);
237
238     GMX_EXPECT_SIMD_FUNC_NEAR(ref_invsqrt, tst_invsqrt_pair0);
239     GMX_EXPECT_SIMD_FUNC_NEAR(ref_invsqrt, tst_invsqrt_pair1);
240 }
241
242 TEST_F(SimdMathTest, gmxSimdSqrtR)
243 {
244     // Just make sure sqrt(0)=0 works and isn't evaluated as 0*1/sqrt(0)=NaN
245     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(0, 2, 3), gmx_simd_sqrt_r(setSimdRealFrom3R(0, 4, 9)));
246 }
247
248 /*! \brief Function wrapper to evaluate reference 1/x */
249 real ref_inv(real x)
250 {
251     return 1.0/x;
252 }
253
254 TEST_F(SimdMathTest, gmxSimdInvR)
255 {
256     // test <0
257     setRange(-1e10, -1e-10);
258     GMX_EXPECT_SIMD_FUNC_NEAR(ref_inv, gmx_simd_inv_r);
259     setRange(1e-10, 1e10);
260     GMX_EXPECT_SIMD_FUNC_NEAR(ref_inv, gmx_simd_inv_r);
261 }
262
263 /*! \brief Function wrapper for log(x), with argument/return in default Gromacs precision */
264 real ref_log(real x)
265 {
266     return log(x);
267 }
268
269 TEST_F(SimdMathTest, gmxSimdLogR)
270 {
271     setRange(1e-30, 1e30);
272     GMX_EXPECT_SIMD_FUNC_NEAR(ref_log, gmx_simd_log_r);
273 }
274
275 // MSVC does not support exp2(), so we have no reference to test against
276 #ifndef _MSC_VER
277 /*! \brief Function wrapper for exp2(x), with argument/return in default Gromacs precision */
278 real ref_exp2(real x)
279 {
280     return exp2(x);
281 }
282
283 TEST_F(SimdMathTest, gmxSimdExp2R)
284 {
285     setRange(-100, 100);
286     GMX_EXPECT_SIMD_FUNC_NEAR(ref_exp2, gmx_simd_exp2_r);
287
288     // We do not care about the SIMD implementation getting denormal values right,
289     // but they must be clamped to zero rather than producing garbage.
290     // Check by setting the absolute tolerance to machine precision.
291     setAbsTol(GMX_REAL_EPS);
292
293     // First two values will have denormal results in single, third value in double too.
294     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(ref_exp2(-150.0), ref_exp2(-300.0), ref_exp2(-1050.0)),
295                               gmx_simd_exp2_r(setSimdRealFrom3R(-150.0, -300.0, -1050.0)));
296
297     // Reset absolute tolerance to enforce ULP checking
298     setAbsTol(0.0);
299
300     // Make sure that underflowing values are set to zero.
301     // First two values underflow in single, third value in double too.
302     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(ref_exp2(-200.0), ref_exp2(-600.0), ref_exp2(-1500.0)),
303                               gmx_simd_exp2_r(setSimdRealFrom3R(-200.0, -600.0, -1500.0)));
304 }
305 #endif
306
307 /*! \brief Function wrapper for exp(x), with argument/return in default Gromacs precision */
308 real ref_exp(real x)
309 {
310     return exp(x);
311 }
312
313 TEST_F(SimdMathTest, gmxSimdExpR)
314 {
315     setRange(-75, 75);
316     GMX_EXPECT_SIMD_FUNC_NEAR(ref_exp, gmx_simd_exp_r);
317
318     // We do not care about the SIMD implementation getting denormal values right,
319     // but they must be clamped to zero rather than producing garbage.
320     // Check by setting the absolute tolerance to machine precision.
321     setAbsTol(GMX_REAL_EPS);
322     // First two values will have denormal results in single, third value in double too.
323     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(ref_exp(-90.0), ref_exp(-100.0), ref_exp(-725.0)),
324                               gmx_simd_exp_r(setSimdRealFrom3R(-90.0, -100.0, -725.0)));
325
326     // Reset absolute tolerance to enforce ULP checking
327     setAbsTol(0.0);
328
329     // Make sure that underflowing values are set to zero.
330     // First two values underflow in single, third value in double too.
331     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom3R(ref_exp(-150.0), ref_exp(-300.0), ref_exp(-800.0)),
332                               gmx_simd_exp_r(setSimdRealFrom3R(-150.0, -300.0, -800.0)));
333 }
334
335 /*! \brief Function wrapper for erf(x), with argument/return in default Gromacs precision.
336  *
337  * \note The single-precision gmx_erff() in gmxlib is slightly lower precision
338  * than the SIMD flavor, so we use double for reference.
339  */
340 real ref_erf(real x)
341 {
342     return gmx_erfd(x);
343 }
344
345 TEST_F(SimdMathTest, gmxSimdErfR)
346 {
347     setRange(-9, 9);
348     setAbsTol(GMX_REAL_MIN);
349     GMX_EXPECT_SIMD_FUNC_NEAR(ref_erf, gmx_simd_erf_r);
350 }
351
352 /*! \brief Function wrapper for erfc(x), with argument/return in default Gromacs precision.
353  *
354  * \note The single-precision gmx_erfcf() in gmxlib is slightly lower precision
355  * than the SIMD flavor, so we use double for reference.
356  */
357 real ref_erfc(real x)
358 {
359     return gmx_erfcd(x);
360 }
361
362 TEST_F(SimdMathTest, gmxSimdErfcR)
363 {
364     setRange(-9, 9);
365     setAbsTol(GMX_REAL_MIN);
366     // Our erfc algorithm has 4 ulp accuracy, so relax defaultTol a bit
367     setUlpTol(4*ulpTol_);
368     GMX_EXPECT_SIMD_FUNC_NEAR(ref_erfc, gmx_simd_erfc_r);
369 }
370
371 /*! \brief Function wrapper for sin(x), with argument/return in default Gromacs precision */
372 real ref_sin(real x)
373 {
374     return sin(x);
375 }
376
377 TEST_F(SimdMathTest, gmxSimdSinR)
378 {
379     setRange(-8*M_PI, 8*M_PI);
380     GMX_EXPECT_SIMD_FUNC_NEAR(ref_sin, gmx_simd_sin_r);
381     // Range reduction leads to accuracy loss, so we might want higher tolerance here
382     setRange(-10000, 10000);
383     setUlpTol(2*ulpTol_);
384     GMX_EXPECT_SIMD_FUNC_NEAR(ref_sin, gmx_simd_sin_r);
385 }
386
387 /*! \brief Function wrapper for cos(x), with argument/return in default Gromacs precision */
388 real ref_cos(real x)
389 {
390     return cos(x);
391 }
392
393 TEST_F(SimdMathTest, gmxSimdCosR)
394 {
395     setRange(-8*M_PI, 8*M_PI);
396     GMX_EXPECT_SIMD_FUNC_NEAR(ref_cos, gmx_simd_cos_r);
397     // Range reduction leads to accuracy loss, so we might want higher tolerance here
398     setRange(-10000, 10000);
399     setUlpTol(2*ulpTol_);
400     GMX_EXPECT_SIMD_FUNC_NEAR(ref_cos, gmx_simd_cos_r);
401 }
402
403 /*! \brief Function wrapper for tan(x), with argument/return in default Gromacs precision */
404 real ref_tan(real x)
405 {
406     return tan(x);
407 }
408
409 TEST_F(SimdMathTest, gmxSimdTanR)
410 {
411     // Tan(x) is a little sensitive due to the division in the algorithm.
412     // Rather than using lots of extra FP operations, we accept the algorithm
413     // presently only achieves a ~3 ulp error and use the medium tolerance.
414     setRange(-8*M_PI, 8*M_PI);
415     GMX_EXPECT_SIMD_FUNC_NEAR(ref_tan, gmx_simd_tan_r);
416     // Range reduction leads to accuracy loss, so we might want higher tolerance here
417     setRange(-10000, 10000);
418     setUlpTol(2*ulpTol_);
419     GMX_EXPECT_SIMD_FUNC_NEAR(ref_tan, gmx_simd_tan_r);
420 }
421
422 /*! \brief Function wrapper for asin(x), with argument/return in default Gromacs precision */
423 real ref_asin(real x)
424 {
425     return asin(x);
426 }
427
428 TEST_F(SimdMathTest, gmxSimdAsinR)
429 {
430     // Our present asin(x) algorithm achieves 2-3 ulp accuracy
431     setRange(-1, 1);
432     GMX_EXPECT_SIMD_FUNC_NEAR(ref_asin, gmx_simd_asin_r);
433 }
434
435 /*! \brief Function wrapper for acos(x), with argument/return in default Gromacs precision */
436 real ref_acos(real x)
437 {
438     return acos(x);
439 }
440
441 TEST_F(SimdMathTest, gmxSimdAcosR)
442 {
443     // Our present acos(x) algorithm achieves 2-3 ulp accuracy
444     setRange(-1, 1);
445     GMX_EXPECT_SIMD_FUNC_NEAR(ref_acos, gmx_simd_acos_r);
446 }
447
448 /*! \brief Function wrapper for atan(x), with argument/return in default Gromacs precision */
449 real ref_atan(real x)
450 {
451     return atan(x);
452 }
453
454 TEST_F(SimdMathTest, gmxSimdAtanR)
455 {
456     // Our present atan(x) algorithm achieves 1 ulp accuracy
457     setRange(-10000, 10000);
458     GMX_EXPECT_SIMD_FUNC_NEAR(ref_atan, gmx_simd_atan_r);
459 }
460
461 TEST_F(SimdMathTest, gmxSimdAtan2R)
462 {
463     // test each quadrant
464     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(atan2(1.0, 1.0)), gmx_simd_atan2_r(rSimd_1_2_3, rSimd_1_2_3));
465     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(atan2(-1.0, 1.0)), gmx_simd_atan2_r(rSimd_m1_m2_m3, rSimd_1_2_3));
466     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(atan2(-1.0, -1.0)), gmx_simd_atan2_r(rSimd_m1_m2_m3, rSimd_m1_m2_m3));
467     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(atan2(1.0, -1.0)), gmx_simd_atan2_r(rSimd_1_2_3, rSimd_m1_m2_m3));
468     // cases important for calculating angles
469     // values on coordinate axes
470     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(atan2(0.0, 1.0)), gmx_simd_atan2_r(gmx_simd_setzero_r(), rSimd_1_2_3));
471     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(atan2(1.0, 0.0)), gmx_simd_atan2_r(rSimd_1_2_3, gmx_simd_setzero_r()));
472     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(atan2(0.0, -1.0)), gmx_simd_atan2_r(gmx_simd_setzero_r(), rSimd_m1_m2_m3));
473     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(atan2(-1.0, 0.0)), gmx_simd_atan2_r(rSimd_m1_m2_m3, gmx_simd_setzero_r()));
474     // degenerate value (origin) should return 0.0
475     GMX_EXPECT_SIMD_REAL_NEAR(setSimdRealFrom1R(atan2(0.0, 0.0)), gmx_simd_atan2_r(setSimdRealFrom3R(0.0, 0.0, 0.0), gmx_simd_setzero_r()));
476 }
477
478 /*! \brief Evaluate reference version of PME force correction. */
479 real ref_pmecorrF(real x)
480 {
481     if (x != 0)
482     {
483         real y = sqrt(x);
484         return 2*exp(-x)/(sqrt(M_PI)*x) - gmx_erfd(y)/(x*y);
485     }
486     else
487     {
488         return -4/(3*sqrt(M_PI));
489     }
490 }
491
492 // The PME corrections will be added to ~1/r2, so absolute tolerance of EPS is fine.
493 TEST_F(SimdMathTest, gmxSimdPmecorrForceR)
494 {
495     // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
496 #ifdef GMX_DOUBLE
497     setUlpTol((gmx_int64_t)(5e-10/GMX_REAL_EPS));
498 #else
499     setUlpTol((gmx_int64_t)(5e-6/GMX_REAL_EPS));
500 #endif
501
502     setRange(0.15, 4);
503     setAbsTol(GMX_REAL_EPS);
504     GMX_EXPECT_SIMD_FUNC_NEAR(ref_pmecorrF, gmx_simd_pmecorrF_r);
505 }
506
507 /*! \brief Evaluate reference version of PME potential correction. */
508 real ref_pmecorrV(real x)
509 {
510     real y = sqrt(x);
511     return gmx_erfd(y)/y;
512 }
513
514 // The PME corrections will be added to ~1/r, so absolute tolerance of EPS is fine.
515 TEST_F(SimdMathTest, gmxSimdPmecorrPotentialR)
516 {
517     // Pme correction only needs to be ~1e-6 accuracy single, 1e-10 double
518 #ifdef GMX_DOUBLE
519     setUlpTol((gmx_int64_t)(5e-10/GMX_REAL_EPS));
520 #else
521     setUlpTol((gmx_int64_t)(5e-6/GMX_REAL_EPS));
522 #endif
523     setRange(0.15, 4);
524     setAbsTol(GMX_REAL_EPS);
525     GMX_EXPECT_SIMD_FUNC_NEAR(ref_pmecorrV, gmx_simd_pmecorrV_r);
526 }
527
528 }      // namespace
529
530 #endif // GMX_SIMD_HAVE_REAL
531
532 /*! \} */
533 /*! \endcond */
534
535 }      // namespace
536 }      // namespace