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