Merge branch release-2016 into release-2018
[alexxy/gromacs.git] / src / gromacs / ewald / tests / pmesplinespreadtest.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2016,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 /*! \internal \file
36  * \brief
37  * Implements PME spline computation and charge spreading tests.
38  *
39  * \author Aleksei Iupinov <a.yupinov@gmail.com>
40  * \ingroup module_ewald
41  */
42
43 #include "gmxpre.h"
44
45 #include <string>
46
47 #include <gmock/gmock.h>
48
49 #include "gromacs/mdtypes/inputrec.h"
50 #include "gromacs/utility/stringutil.h"
51
52 #include "testutils/refdata.h"
53 #include "testutils/testasserts.h"
54
55 #include "pmetestcommon.h"
56
57 namespace gmx
58 {
59 namespace test
60 {
61 namespace
62 {
63
64 //! PME spline and spread code path being tested
65 enum class PmeSplineAndSpreadOptions
66 {
67     SplineOnly,
68     SpreadOnly,
69     SplineAndSpreadUnified
70 };
71
72 /*! \brief Convenience typedef of input parameters - unit cell box, PME interpolation order, grid dimensions,
73  * particle coordinates, particle charges
74  * TODO: consider inclusion of local grid offsets/sizes or PME nodes counts to test the PME DD
75  */
76 typedef std::tuple<Matrix3x3, int, IVec, CoordinatesVector, ChargesVector> SplineAndSpreadInputParameters;
77
78 /*! \brief Test fixture for testing both atom spline parameter computation and charge spreading.
79  * These 2 stages of PME are tightly coupled in the code.
80  */
81 class PmeSplineAndSpreadTest : public ::testing::TestWithParam<SplineAndSpreadInputParameters>
82 {
83     public:
84         //! Default constructor
85         PmeSplineAndSpreadTest() = default;
86         //! The test
87         void runTest()
88         {
89             /* Getting the input */
90             Matrix3x3               box;
91             int                     pmeOrder;
92             IVec                    gridSize;
93             CoordinatesVector       coordinates;
94             ChargesVector           charges;
95
96             std::tie(box, pmeOrder, gridSize, coordinates, charges) = GetParam();
97             const size_t atomCount = coordinates.size();
98
99             /* Storing the input where it's needed */
100             t_inputrec inputRec;
101             inputRec.nkx         = gridSize[XX];
102             inputRec.nky         = gridSize[YY];
103             inputRec.nkz         = gridSize[ZZ];
104             inputRec.pme_order   = pmeOrder;
105             inputRec.coulombtype = eelPME;
106             inputRec.epsilon_r   = 1.0;
107
108             TestReferenceData                                      refData;
109
110             const std::map<CodePath, std::string>                  modesToTest   = {{CodePath::CPU, "CPU"},
111                                                                                     {CodePath::CUDA, "CUDA"}};
112
113             const std::map<PmeSplineAndSpreadOptions, std::string> optionsToTest = {{PmeSplineAndSpreadOptions::SplineAndSpreadUnified, "spline computation and charge spreading (fused)"},
114                                                                                     {PmeSplineAndSpreadOptions::SplineOnly, "spline computation"},
115                                                                                     {PmeSplineAndSpreadOptions::SpreadOnly, "charge spreading"}};
116
117             // There is a subtle problem with multiple comparisons against same reference data:
118             // The subsequent (GPU) spreading runs at one point didn't actually copy the output grid into the proper buffer,
119             // but the reference data was already marked as checked (hasBeenChecked_) by the CPU run, so nothing failed.
120             // For now we will manually track that the count of the grid entries is the same on each run.
121             // This is just a hack for a single specific output though.
122             // What would be much better TODO is to split different codepaths into separate tests,
123             // while making them use the same reference files.
124             bool   gridValuesSizeAssigned = false;
125             size_t previousGridValuesSize;
126
127             for (const auto &mode : modesToTest)
128             {
129                 const bool supportedInput = pmeSupportsInputForMode(&inputRec, mode.first);
130                 if (!supportedInput)
131                 {
132                     /* Testing the failure for the unsupported input */
133                     EXPECT_THROW(pmeInitAtoms(&inputRec, mode.first, nullptr, coordinates, charges, box), NotImplementedError);
134                     continue;
135                 }
136
137                 const auto contextsToTest = getPmeTestEnv()->getHardwareContexts(mode.first);
138                 for (const auto &context : contextsToTest)
139                 {
140                     for (const auto &option : optionsToTest)
141                     {
142                         /* Describing the test uniquely in case it fails */
143
144                         SCOPED_TRACE(formatString("Testing %s with %s %sfor PME grid size %d %d %d"
145                                                   ", order %d, %zu atoms",
146                                                   option.second.c_str(), mode.second.c_str(),
147                                                   context.getDescription().c_str(),
148                                                   gridSize[XX], gridSize[YY], gridSize[ZZ],
149                                                   pmeOrder,
150                                                   atomCount));
151
152                         /* Running the test */
153
154                         PmeSafePointer pmeSafe = pmeInitAtoms(&inputRec, mode.first, context.getDeviceInfo(), coordinates, charges, box);
155
156                         const bool     computeSplines = (option.first == PmeSplineAndSpreadOptions::SplineOnly) || (option.first == PmeSplineAndSpreadOptions::SplineAndSpreadUnified);
157                         const bool     spreadCharges  = (option.first == PmeSplineAndSpreadOptions::SpreadOnly) || (option.first == PmeSplineAndSpreadOptions::SplineAndSpreadUnified);
158
159                         if (!computeSplines)
160                         {
161                             // Here we should set up the results of the spline computation so that the spread can run.
162                             // What is lazy and works is running the separate spline so that it will set it up for us:
163                             pmePerformSplineAndSpread(pmeSafe.get(), mode.first, true, false);
164                             // We know that it is tested in another iteration.
165                             // TODO: Clean alternative: read and set the reference gridline indices, spline params
166                         }
167
168                         pmePerformSplineAndSpread(pmeSafe.get(), mode.first, computeSplines, spreadCharges);
169                         pmeFinalizeTest(pmeSafe.get(), mode.first);
170
171                         /* Outputs correctness check */
172                         /* All tolerances were picked empirically for single precision on CPU */
173
174                         TestReferenceChecker rootChecker(refData.rootChecker());
175
176                         const auto           maxGridSize              = std::max(std::max(gridSize[XX], gridSize[YY]), gridSize[ZZ]);
177                         const auto           ulpToleranceSplineValues = 4 * (pmeOrder - 2) * maxGridSize;
178                         /* 4 is a modest estimate for amount of operations; (pmeOrder - 2) is a number of iterations;
179                          * maxGridSize is inverse of the smallest positive fractional coordinate (which are interpolated by the splines).
180                          */
181
182                         if (computeSplines)
183                         {
184                             const char *dimString[] = { "X", "Y", "Z" };
185
186                             /* Spline values */
187                             SCOPED_TRACE(formatString("Testing spline values with tolerance of %ld", ulpToleranceSplineValues));
188                             TestReferenceChecker splineValuesChecker(rootChecker.checkCompound("Splines", "Values"));
189                             splineValuesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceSplineValues));
190                             for (int i = 0; i < DIM; i++)
191                             {
192                                 auto splineValuesDim = pmeGetSplineData(pmeSafe.get(), mode.first, PmeSplineDataType::Values, i);
193                                 splineValuesChecker.checkSequence(splineValuesDim.begin(), splineValuesDim.end(), dimString[i]);
194                             }
195
196                             /* Spline derivatives */
197                             const auto ulpToleranceSplineDerivatives = 4 * ulpToleranceSplineValues;
198                             /* 4 is just a wild guess since the derivatives are deltas of neighbor spline values which could differ greatly */
199                             SCOPED_TRACE(formatString("Testing spline derivatives with tolerance of %ld", ulpToleranceSplineDerivatives));
200                             TestReferenceChecker splineDerivativesChecker(rootChecker.checkCompound("Splines", "Derivatives"));
201                             splineDerivativesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceSplineDerivatives));
202                             for (int i = 0; i < DIM; i++)
203                             {
204                                 auto splineDerivativesDim = pmeGetSplineData(pmeSafe.get(), mode.first, PmeSplineDataType::Derivatives, i);
205                                 splineDerivativesChecker.checkSequence(splineDerivativesDim.begin(), splineDerivativesDim.end(), dimString[i]);
206                             }
207
208                             /* Particle gridline indices */
209                             auto gridLineIndices = pmeGetGridlineIndices(pmeSafe.get(), mode.first);
210                             rootChecker.checkSequence(gridLineIndices.begin(), gridLineIndices.end(), "Gridline indices");
211                         }
212
213                         if (spreadCharges)
214                         {
215                             /* The wrapped grid */
216                             SparseRealGridValuesOutput nonZeroGridValues = pmeGetRealGrid(pmeSafe.get(), mode.first);
217                             TestReferenceChecker       gridValuesChecker(rootChecker.checkCompound("NonZeroGridValues", "RealSpaceGrid"));
218                             const auto                 ulpToleranceGrid = 2 * ulpToleranceSplineValues * (int)(ceil(sqrt(atomCount)));
219                             /* 2 is empiric; sqrt(atomCount) assumes all the input charges may spread onto the same cell */
220                             SCOPED_TRACE(formatString("Testing grid values with tolerance of %ld", ulpToleranceGrid));
221                             if (!gridValuesSizeAssigned)
222                             {
223                                 previousGridValuesSize = nonZeroGridValues.size();
224                                 gridValuesSizeAssigned = true;
225                             }
226                             else
227                             {
228                                 EXPECT_EQ(previousGridValuesSize, nonZeroGridValues.size());
229                             }
230
231                             gridValuesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceGrid));
232                             for (const auto &point : nonZeroGridValues)
233                             {
234                                 gridValuesChecker.checkReal(point.second, point.first.c_str());
235                             }
236                         }
237                     }
238                 }
239             }
240         }
241 };
242
243
244 /*! \brief Test for spline parameter computation and charge spreading. */
245 TEST_P(PmeSplineAndSpreadTest, ReproducesOutputs)
246 {
247     EXPECT_NO_THROW(runTest());
248 }
249
250 /* Valid input instances */
251
252 //! A couple of valid inputs for boxes.
253 static std::vector<Matrix3x3> const c_sampleBoxes
254 {
255     // normal box
256     Matrix3x3 {{
257                    8.0f, 0.0f, 0.0f,
258                    0.0f, 3.4f, 0.0f,
259                    0.0f, 0.0f, 2.0f
260                }},
261     // triclinic box
262     Matrix3x3 {{
263                    7.0f, 0.0f, 0.0f,
264                    0.0f, 4.1f, 0.0f,
265                    3.5f, 2.0f, 12.2f
266                }},
267 };
268
269 //! A couple of valid inputs for grid sizes.
270 static std::vector<IVec> const c_sampleGridSizes
271 {
272     IVec {
273         16, 12, 14
274     },
275     IVec {
276         19, 17, 11
277     }
278 };
279
280 //! Random charges
281 static std::vector<real> const c_sampleChargesFull
282 {
283     4.95f, 3.11f, 3.97f, 1.08f, 2.09f, 1.1f, 4.13f, 3.31f, 2.8f, 5.83f, 5.09f, 6.1f, 2.86f, 0.24f, 5.76f, 5.19f, 0.72f
284 };
285 //! 1 charge
286 static auto const c_sampleCharges1 = ChargesVector(c_sampleChargesFull).subArray(0, 1);
287 //! 2 charges
288 static auto const c_sampleCharges2 = ChargesVector(c_sampleChargesFull).subArray(1, 2);
289 //! 13 charges
290 static auto const c_sampleCharges13 = ChargesVector(c_sampleChargesFull).subArray(3, 13);
291
292 //! Random coordinate vectors
293 static CoordinatesVector const c_sampleCoordinatesFull
294 {
295     {
296         5.59f, 1.37f, 0.95f
297     }, {
298         16.0f, 1.02f, 0.22f      // 2 box lengths in x
299     }, {
300         0.034f, 1.65f, 0.22f
301     }, {
302         0.33f, 0.92f, 1.56f
303     }, {
304         1.16f, 0.75f, 0.39f
305     }, {
306         0.5f, 1.63f, 1.14f
307     }, {
308         16.0001f, 1.52f, 1.19f  // > 2 box lengths in x
309     }, {
310         1.43f, 1.1f, 4.1f       // > 2 box lengths in z
311     }, {
312         -1.08f, 1.19f, 0.08f    // negative x
313     }, {
314         1.6f, 0.93f, 0.53f
315     }, {
316         1.32f, -1.48f, 0.16f    // negative y
317     }, {
318         0.87f, 0.0f, 0.33f
319     }, {
320         0.95f, 7.7f, -0.48f     // > 2 box lengths in y, negative z
321     }, {
322         1.23f, 0.91f, 0.68f
323     }, {
324         0.19f, 1.45f, 0.94f
325     }, {
326         1.28f, 0.46f, 0.38f
327     }, {
328         1.21f, 0.23f, 1.0f
329     }
330 };
331 //! 1 coordinate vector
332 static CoordinatesVector const c_sampleCoordinates1(c_sampleCoordinatesFull.begin(), c_sampleCoordinatesFull.begin() + 1);
333 //! 2 coordinate vectors
334 static CoordinatesVector const c_sampleCoordinates2(c_sampleCoordinatesFull.begin() + 1, c_sampleCoordinatesFull.begin() + 3);
335 //! 13 coordinate vectors
336 static CoordinatesVector const c_sampleCoordinates13(c_sampleCoordinatesFull.begin() + 3, c_sampleCoordinatesFull.begin() + 16);
337
338 //! moved out from instantiantions for readability
339 auto c_inputBoxes     = ::testing::ValuesIn(c_sampleBoxes);
340 //! moved out from instantiantions for readability
341 auto c_inputPmeOrders = ::testing::Range(3, 5 + 1);
342 //! moved out from instantiantions for readability
343 auto c_inputGridSizes = ::testing::ValuesIn(c_sampleGridSizes);
344
345 /*! \brief Instantiation of the test with valid input and 1 atom */
346 INSTANTIATE_TEST_CASE_P(SaneInput1, PmeSplineAndSpreadTest, ::testing::Combine(c_inputBoxes, c_inputPmeOrders, c_inputGridSizes,
347                                                                                    ::testing::Values(c_sampleCoordinates1),
348                                                                                    ::testing::Values(c_sampleCharges1)
349                                                                                ));
350
351 /*! \brief Instantiation of the test with valid input and 2 atoms */
352 INSTANTIATE_TEST_CASE_P(SaneInput2, PmeSplineAndSpreadTest, ::testing::Combine(c_inputBoxes, c_inputPmeOrders, c_inputGridSizes,
353                                                                                    ::testing::Values(c_sampleCoordinates2),
354                                                                                    ::testing::Values(c_sampleCharges2)
355                                                                                ));
356 /*! \brief Instantiation of the test with valid input and 13 atoms */
357 INSTANTIATE_TEST_CASE_P(SaneInput13, PmeSplineAndSpreadTest, ::testing::Combine(c_inputBoxes, c_inputPmeOrders, c_inputGridSizes,
358                                                                                     ::testing::Values(c_sampleCoordinates13),
359                                                                                     ::testing::Values(c_sampleCharges13)
360                                                                                 ));
361 }
362 }
363 }