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