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