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