Merge branch release-2016
[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/mdrunutility/mdmodules.h"
50 #include "gromacs/mdtypes/inputrec.h"
51 #include "gromacs/utility/stringutil.h"
52
53 #include "testutils/refdata.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 dimensions,
74  * 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     private:
85         //! Environment for getting the t_inputrec structure easily
86         MDModules mdModules_;
87
88     public:
89         //! Default constructor
90         PmeSplineAndSpreadTest() = default;
91         //! The test
92         void runTest()
93         {
94             /* Getting the input */
95             Matrix3x3               box;
96             int                     pmeOrder;
97             IVec                    gridSize;
98             CoordinatesVector       coordinates;
99             ChargesVector           charges;
100
101             std::tie(box, pmeOrder, gridSize, coordinates, charges) = GetParam();
102             const size_t atomCount = coordinates.size();
103
104             /* Storing the input where it's needed */
105             t_inputrec *inputRec  = mdModules_.inputrec();
106             inputRec->nkx         = gridSize[XX];
107             inputRec->nky         = gridSize[YY];
108             inputRec->nkz         = gridSize[ZZ];
109             inputRec->pme_order   = pmeOrder;
110             inputRec->coulombtype = eelPME;
111
112             TestReferenceData                                      refData;
113
114             const std::map<CodePath, std::string>                  modesToTest   = {{CodePath::CPU, "CPU"}};
115             const std::map<PmeSplineAndSpreadOptions, std::string> optionsToTest = {{PmeSplineAndSpreadOptions::SplineAndSpreadUnified, "spline computation and charge spreading (fused)"},
116                                                                                     {PmeSplineAndSpreadOptions::SplineOnly, "spline computation"},
117                                                                                     {PmeSplineAndSpreadOptions::SpreadOnly, "charge spreading"}};
118             for (const auto &mode : modesToTest)
119             {
120                 for (const auto &option : optionsToTest)
121                 {
122                     /* Describing the test uniquely in case it fails */
123
124                     SCOPED_TRACE(formatString("Testing %s with %s for PME grid size %d %d %d"
125                                               ", order %d, %zu atoms",
126                                               option.second.c_str(), mode.second.c_str(),
127                                               gridSize[XX], gridSize[YY], gridSize[ZZ],
128                                               pmeOrder,
129                                               atomCount));
130
131                     /* Running the test */
132
133                     PmeSafePointer pmeSafe = pmeInitWithAtoms(inputRec, coordinates, charges, box);
134
135                     const bool     computeSplines = (option.first == PmeSplineAndSpreadOptions::SplineOnly) || (option.first == PmeSplineAndSpreadOptions::SplineAndSpreadUnified);
136                     const bool     spreadCharges  = (option.first == PmeSplineAndSpreadOptions::SpreadOnly) || (option.first == PmeSplineAndSpreadOptions::SplineAndSpreadUnified);
137
138                     if (!computeSplines)
139                     {
140                         // Here we should set up the results of the spline computation so that the spread can run.
141                         // What is lazy and works is running the separate spline so that it will set it up for us:
142                         pmePerformSplineAndSpread(pmeSafe.get(), mode.first, true, false);
143                         // We know that it is tested in another iteration.
144                         // TODO: Clean alternative: read and set the reference gridline indices, spline params
145                     }
146
147                     pmePerformSplineAndSpread(pmeSafe.get(), mode.first, computeSplines, spreadCharges);
148
149                     /* Outputs correctness check */
150                     /* All tolerances were picked empirically for single precision on CPU */
151
152                     TestReferenceChecker defaultChecker(refData.rootChecker());
153
154                     const auto           maxGridSize              = std::max(std::max(gridSize[XX], gridSize[YY]), gridSize[ZZ]);
155                     const auto           ulpToleranceSplineValues = 2 * (pmeOrder - 2) * maxGridSize;
156                     /* 2 is empiric, the rest follows from the amount of operations */
157
158                     if (computeSplines)
159                     {
160                         const char *dimString[] = { "X", "Y", "Z" };
161
162                         /* Spline values */
163                         SCOPED_TRACE(formatString("Testing spline values with tolerance of %ld", ulpToleranceSplineValues));
164                         TestReferenceChecker splineValuesChecker(defaultChecker.checkCompound("Splines", "Values"));
165                         splineValuesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceSplineValues));
166                         for (int i = 0; i < DIM; i++)
167                         {
168                             auto splineValuesDim = pmeGetSplineData(pmeSafe.get(), mode.first, PmeSplineDataType::Values, i);
169                             splineValuesChecker.checkSequence(splineValuesDim.begin(), splineValuesDim.end(), dimString[i]);
170                         }
171
172                         /* Spline derivatives */
173                         const auto ulpToleranceSplineDerivatives = 4 * ulpToleranceSplineValues;
174                         /* 4 is just a wild guess since the derivatives are deltas of neighbor spline values which could differ greatly */
175                         SCOPED_TRACE(formatString("Testing spline derivatives with tolerance of %ld", ulpToleranceSplineDerivatives));
176                         TestReferenceChecker splineDerivativesChecker(defaultChecker.checkCompound("Splines", "Derivatives"));
177                         splineDerivativesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceSplineDerivatives));
178                         for (int i = 0; i < DIM; i++)
179                         {
180                             auto splineDerivativesDim = pmeGetSplineData(pmeSafe.get(), mode.first, PmeSplineDataType::Derivatives, i);
181                             splineDerivativesChecker.checkSequence(splineDerivativesDim.begin(), splineDerivativesDim.end(), dimString[i]);
182                         }
183
184                         /* Particle gridline indices */
185                         auto gridLineIndices = pmeGetGridlineIndices(pmeSafe.get(), mode.first);
186                         defaultChecker.checkSequence(gridLineIndices.begin(), gridLineIndices.end(), "Gridline indices");
187                     }
188
189                     if (spreadCharges)
190                     {
191                         SparseGridValues nonZeroGridValues = pmeGetRealGrid(pmeSafe.get(), mode.first);
192
193                         /* The wrapped grid */
194                         TestReferenceChecker gridValuesChecker(defaultChecker.checkCompound("NonZeroGridValues", "RealSpaceGrid"));
195                         const auto           ulpToleranceGrid = 2 * ulpToleranceSplineValues * (int)(ceil(sqrt(atomCount)));
196                         /* 2 is empiric; sqrt(atomCount) assumes all the input charges may spread onto the same cell */
197                         SCOPED_TRACE(formatString("Testing grid values with tolerance of %ld", ulpToleranceGrid));
198                         gridValuesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceGrid));
199                         for (const auto &point : nonZeroGridValues)
200                         {
201                             gridValuesChecker.checkReal(point.second, point.first.c_str());
202                         }
203                     }
204                 }
205             }
206         }
207 };
208
209
210 /*! \brief Test for PME B-spline moduli computation */
211 TEST_P(PmeSplineAndSpreadTest, ReproducesOutputs)
212 {
213     EXPECT_NO_THROW(runTest());
214 }
215
216 /* Valid input instances */
217
218 //! A couple of valid inputs for boxes.
219 static std::vector<Matrix3x3> const sampleBoxes
220 {
221     // normal box
222     Matrix3x3 {{
223                    8.0f, 0.0f, 0.0f,
224                    0.0f, 3.4f, 0.0f,
225                    0.0f, 0.0f, 2.0f
226                }},
227     // triclinic box
228     Matrix3x3 {{
229                    7.0f, 0.0f, 0.0f,
230                    0.0f, 3.1f, 0.0f,
231                    6.0f, 4.0f, 3.2f
232                }},
233 };
234
235 //! A couple of valid inputs for grid sizes.
236 static std::vector<IVec> const sampleGridSizes
237 {
238     IVec {
239         16, 12, 14
240     },
241     IVec {
242         19, 17, 11
243     }
244 };
245
246 //! Random charges
247 static ChargesVector const sampleChargesFull
248 {
249     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
250 };
251 //! 1 charge
252 static ChargesVector const sampleCharges1(sampleChargesFull.begin(), sampleChargesFull.begin() + 1);
253 //! 2 charges
254 static ChargesVector const sampleCharges2(sampleChargesFull.begin() + 1, sampleChargesFull.begin() + 3);
255 //! 13 charges
256 static ChargesVector const sampleCharges13(sampleChargesFull.begin() + 3, sampleChargesFull.begin() + 16);
257
258 //! Random coordinate vectors
259 static CoordinatesVector const sampleCoordinatesFull
260 {
261     {
262         5.59f, 1.37f, 0.95f
263     }, {
264         16.0f, 1.02f, 0.22f      // 2 box lengths in x
265     }, {
266         0.034f, 1.65f, 0.22f
267     }, {
268         0.33f, 0.92f, 1.56f
269     }, {
270         1.16f, 0.75f, 0.39f
271     }, {
272         0.5f, 1.63f, 1.14f
273     }, {
274         16.0001f, 1.52f, 1.19f  // > 2 box lengths in x
275     }, {
276         1.43f, 1.1f, 4.1f       // > 2 box lengths in z
277     }, {
278         -1.08f, 1.19f, 0.08f    // negative x
279     }, {
280         1.6f, 0.93f, 0.53f
281     }, {
282         1.32f, -1.48f, 0.16f    // negative y
283     }, {
284         0.87f, 0.0f, 0.33f
285     }, {
286         0.95f, 7.7f, -0.48f     // > 2 box lengths in y, negative z
287     }, {
288         1.23f, 0.91f, 0.68f
289     }, {
290         0.19f, 1.45f, 0.94f
291     }, {
292         1.28f, 0.46f, 0.38f
293     }, {
294         1.21f, 0.23f, 1.0f
295     }
296 };
297 //! 1 coordinate vector
298 static CoordinatesVector const sampleCoordinates1(sampleCoordinatesFull.begin(), sampleCoordinatesFull.begin() + 1);
299 //! 2 coordinate vectors
300 static CoordinatesVector const sampleCoordinates2(sampleCoordinatesFull.begin() + 1, sampleCoordinatesFull.begin() + 3);
301 //! 13 coordinate vectors
302 static CoordinatesVector const sampleCoordinates13(sampleCoordinatesFull.begin() + 3, sampleCoordinatesFull.begin() + 16);
303
304 //! moved out from instantiantions for readability
305 auto inputBoxes     = ::testing::ValuesIn(sampleBoxes);
306 //! moved out from instantiantions for readability
307 auto inputPmeOrders = ::testing::Range(3, 5 + 1);
308 //! moved out from instantiantions for readability
309 auto inputGridSizes = ::testing::ValuesIn(sampleGridSizes);
310
311 /*! \brief Instantiation of the PME spline computation test with valid input and 1 atom */
312 INSTANTIATE_TEST_CASE_P(SaneInput1, PmeSplineAndSpreadTest, ::testing::Combine(inputBoxes, inputPmeOrders, inputGridSizes,
313                                                                                    ::testing::Values(sampleCoordinates1),
314                                                                                    ::testing::Values(sampleCharges1)
315                                                                                ));
316 /*! \brief Instantiation of the PME spline computation test with valid input and 2 atoms */
317 INSTANTIATE_TEST_CASE_P(SaneInput2, PmeSplineAndSpreadTest, ::testing::Combine(inputBoxes, inputPmeOrders, inputGridSizes,
318                                                                                    ::testing::Values(sampleCoordinates2),
319                                                                                    ::testing::Values(sampleCharges2)
320                                                                                ));
321 /*! \brief Instantiation of the PME spline computation test with valid input and 13 atoms */
322 INSTANTIATE_TEST_CASE_P(SaneInput13, PmeSplineAndSpreadTest, ::testing::Combine(inputBoxes, inputPmeOrders, inputGridSizes,
323                                                                                     ::testing::Values(sampleCoordinates13),
324                                                                                     ::testing::Values(sampleCharges13)
325                                                                                 ));
326 }
327 }
328 }