Merge "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 rootChecker(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(rootChecker.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(rootChecker.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                         rootChecker.checkSequence(gridLineIndices.begin(), gridLineIndices.end(), "Gridline indices");
187                     }
188
189                     if (spreadCharges)
190                     {
191                         /* The wrapped grid */
192                         SparseRealGridValuesOutput nonZeroGridValues = pmeGetRealGrid(pmeSafe.get(), mode.first);
193                         TestReferenceChecker       gridValuesChecker(rootChecker.checkCompound("NonZeroGridValues", "RealSpaceGrid"));
194                         const auto                 ulpToleranceGrid = 2 * ulpToleranceSplineValues * (int)(ceil(sqrt(atomCount)));
195                         /* 2 is empiric; sqrt(atomCount) assumes all the input charges may spread onto the same cell */
196                         SCOPED_TRACE(formatString("Testing grid values with tolerance of %ld", ulpToleranceGrid));
197                         gridValuesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceGrid));
198                         for (const auto &point : nonZeroGridValues)
199                         {
200                             gridValuesChecker.checkReal(point.second, point.first.c_str());
201                         }
202                     }
203                 }
204             }
205         }
206 };
207
208
209 /*! \brief Test for PME B-spline moduli computation */
210 TEST_P(PmeSplineAndSpreadTest, ReproducesOutputs)
211 {
212     EXPECT_NO_THROW(runTest());
213 }
214
215 /* Valid input instances */
216
217 //! A couple of valid inputs for boxes.
218 static std::vector<Matrix3x3> const sampleBoxes
219 {
220     // normal box
221     Matrix3x3 {{
222                    8.0f, 0.0f, 0.0f,
223                    0.0f, 3.4f, 0.0f,
224                    0.0f, 0.0f, 2.0f
225                }},
226     // triclinic box
227     Matrix3x3 {{
228                    7.0f, 0.0f, 0.0f,
229                    0.0f, 4.1f, 0.0f,
230                    3.5f, 2.0f, 12.2f
231                }},
232 };
233
234 //! A couple of valid inputs for grid sizes.
235 static std::vector<IVec> const sampleGridSizes
236 {
237     IVec {
238         16, 12, 14
239     },
240     IVec {
241         19, 17, 11
242     }
243 };
244
245 //! Random charges
246 static std::vector<real> const sampleChargesFull
247 {
248     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
249 };
250 //! 1 charge
251 static auto const sampleCharges1 = ChargesVector::fromVector(sampleChargesFull.begin(), sampleChargesFull.begin() + 1);
252 //! 2 charges
253 static auto const sampleCharges2 = ChargesVector::fromVector(sampleChargesFull.begin() + 1, sampleChargesFull.begin() + 3);
254 //! 13 charges
255 static auto const sampleCharges13 = ChargesVector::fromVector(sampleChargesFull.begin() + 3, sampleChargesFull.begin() + 16);
256
257 //! Random coordinate vectors
258 static CoordinatesVector const sampleCoordinatesFull
259 {
260     {
261         5.59f, 1.37f, 0.95f
262     }, {
263         16.0f, 1.02f, 0.22f      // 2 box lengths in x
264     }, {
265         0.034f, 1.65f, 0.22f
266     }, {
267         0.33f, 0.92f, 1.56f
268     }, {
269         1.16f, 0.75f, 0.39f
270     }, {
271         0.5f, 1.63f, 1.14f
272     }, {
273         16.0001f, 1.52f, 1.19f  // > 2 box lengths in x
274     }, {
275         1.43f, 1.1f, 4.1f       // > 2 box lengths in z
276     }, {
277         -1.08f, 1.19f, 0.08f    // negative x
278     }, {
279         1.6f, 0.93f, 0.53f
280     }, {
281         1.32f, -1.48f, 0.16f    // negative y
282     }, {
283         0.87f, 0.0f, 0.33f
284     }, {
285         0.95f, 7.7f, -0.48f     // > 2 box lengths in y, negative z
286     }, {
287         1.23f, 0.91f, 0.68f
288     }, {
289         0.19f, 1.45f, 0.94f
290     }, {
291         1.28f, 0.46f, 0.38f
292     }, {
293         1.21f, 0.23f, 1.0f
294     }
295 };
296 //! 1 coordinate vector
297 static CoordinatesVector const sampleCoordinates1(sampleCoordinatesFull.begin(), sampleCoordinatesFull.begin() + 1);
298 //! 2 coordinate vectors
299 static CoordinatesVector const sampleCoordinates2(sampleCoordinatesFull.begin() + 1, sampleCoordinatesFull.begin() + 3);
300 //! 13 coordinate vectors
301 static CoordinatesVector const sampleCoordinates13(sampleCoordinatesFull.begin() + 3, sampleCoordinatesFull.begin() + 16);
302
303 //! moved out from instantiantions for readability
304 auto inputBoxes     = ::testing::ValuesIn(sampleBoxes);
305 //! moved out from instantiantions for readability
306 auto inputPmeOrders = ::testing::Range(3, 5 + 1);
307 //! moved out from instantiantions for readability
308 auto inputGridSizes = ::testing::ValuesIn(sampleGridSizes);
309
310 /*! \brief Instantiation of the PME spline computation test with valid input and 1 atom */
311 INSTANTIATE_TEST_CASE_P(SaneInput1, PmeSplineAndSpreadTest, ::testing::Combine(inputBoxes, inputPmeOrders, inputGridSizes,
312                                                                                    ::testing::Values(sampleCoordinates1),
313                                                                                    ::testing::Values(sampleCharges1)
314                                                                                ));
315 /*! \brief Instantiation of the PME spline computation test with valid input and 2 atoms */
316 INSTANTIATE_TEST_CASE_P(SaneInput2, PmeSplineAndSpreadTest, ::testing::Combine(inputBoxes, inputPmeOrders, inputGridSizes,
317                                                                                    ::testing::Values(sampleCoordinates2),
318                                                                                    ::testing::Values(sampleCharges2)
319                                                                                ));
320 /*! \brief Instantiation of the PME spline computation test with valid input and 13 atoms */
321 INSTANTIATE_TEST_CASE_P(SaneInput13, PmeSplineAndSpreadTest, ::testing::Combine(inputBoxes, inputPmeOrders, inputGridSizes,
322                                                                                     ::testing::Values(sampleCoordinates13),
323                                                                                     ::testing::Values(sampleCharges13)
324                                                                                 ));
325 }
326 }
327 }