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