e7549d85c42c75644bf063091717a712e7bad20e
[alexxy/gromacs.git] / src / gromacs / ewald / tests / pmesolvetest.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2016,2017,2018, 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 solving 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 /*! \brief Convenience typedef of the test input parameters - unit cell box, complex grid dimensions, complex grid values,
64  * electrostatic constant epsilon_r, Ewald splitting parameters ewaldcoeff_q and ewaldcoeff_lj, solver type
65  * Output: transformed local grid (Fourier space); optionally reciprocal energy and virial matrix.
66  * TODO:
67  * Implement and test Lorentz-Berthelot
68  */
69 typedef std::tuple<Matrix3x3, IVec, SparseComplexGridValuesInput, double, double, double, PmeSolveAlgorithm> SolveInputParameters;
70
71 //! Test fixture
72 class PmeSolveTest : public ::testing::TestWithParam<SolveInputParameters>
73 {
74     public:
75         PmeSolveTest() = default;
76
77         //! The test
78         void runTest()
79         {
80             /* Getting the input */
81             Matrix3x3                      box;
82             IVec                           gridSize;
83             SparseComplexGridValuesInput   nonZeroGridValues;
84             double                         epsilon_r;
85             double                         ewaldCoeff_q;
86             double                         ewaldCoeff_lj;
87             PmeSolveAlgorithm              method;
88             std::tie(box, gridSize, nonZeroGridValues, epsilon_r, ewaldCoeff_q, ewaldCoeff_lj, method) = GetParam();
89
90             /* Storing the input where it's needed, running the test */
91             t_inputrec inputRec;
92             inputRec.nkx         = gridSize[XX];
93             inputRec.nky         = gridSize[YY];
94             inputRec.nkz         = gridSize[ZZ];
95             inputRec.pme_order   = 4;
96             inputRec.coulombtype = eelPME;
97             inputRec.epsilon_r   = epsilon_r;
98             switch (method)
99             {
100                 case PmeSolveAlgorithm::Coulomb:
101                     break;
102
103                 case PmeSolveAlgorithm::LennardJones:
104                     inputRec.vdwtype = evdwPME;
105                     break;
106
107                 default:
108                     GMX_THROW(InternalError("Unknown PME solver"));
109             }
110
111             TestReferenceData refData;
112             for (const auto &context : getPmeTestEnv()->getHardwareContexts())
113             {
114                 CodePath   codePath       = context->getCodePath();
115                 const bool supportedInput = pmeSupportsInputForMode(*getPmeTestEnv()->hwinfo(), &inputRec, codePath);
116                 if (!supportedInput)
117                 {
118                     /* Testing the failure for the unsupported input */
119                     EXPECT_THROW(pmeInitEmpty(&inputRec, codePath, nullptr, nullptr, box, ewaldCoeff_q, ewaldCoeff_lj), NotImplementedError);
120                     continue;
121                 }
122
123                 std::map<GridOrdering, std::string> gridOrderingsToTest = {{GridOrdering::YZX, "YZX"}};
124                 if (codePath == CodePath::GPU)
125                 {
126                     gridOrderingsToTest[GridOrdering::XYZ] = "XYZ";
127                 }
128                 for (const auto &gridOrdering : gridOrderingsToTest)
129                 {
130                     for (bool computeEnergyAndVirial : {false, true})
131                     {
132                         /* Describing the test*/
133                         SCOPED_TRACE(formatString("Testing solving (%s, %s, %s energy/virial) with %s %sfor PME grid size %d %d %d, Ewald coefficients %g %g",
134                                                   (method == PmeSolveAlgorithm::LennardJones) ? "Lennard-Jones" : "Coulomb",
135                                                   gridOrdering.second.c_str(),
136                                                   computeEnergyAndVirial ? "with" : "without",
137                                                   codePathToString(codePath),
138                                                   context->getDescription().c_str(),
139                                                   gridSize[XX], gridSize[YY], gridSize[ZZ],
140                                                   ewaldCoeff_q, ewaldCoeff_lj
141                                                   ));
142
143                         /* Running the test */
144                         PmeSafePointer pmeSafe = pmeInitEmpty(&inputRec, codePath, context->getDeviceInfo(),
145                                                               context->getPmeGpuProgram(), box, ewaldCoeff_q, ewaldCoeff_lj);
146                         pmeSetComplexGrid(pmeSafe.get(), codePath, gridOrdering.first, nonZeroGridValues);
147                         const real     cellVolume = box[0] * box[4] * box[8];
148                         //FIXME - this is box[XX][XX] * box[YY][YY] * box[ZZ][ZZ], should be stored in the PME structure
149                         pmePerformSolve(pmeSafe.get(), codePath, method, cellVolume, gridOrdering.first, computeEnergyAndVirial);
150                         pmeFinalizeTest(pmeSafe.get(), codePath);
151
152                         /* Check the outputs */
153                         TestReferenceChecker          checker(refData.rootChecker());
154
155                         SparseComplexGridValuesOutput nonZeroGridValuesOutput = pmeGetComplexGrid(pmeSafe.get(), codePath, gridOrdering.first);
156                         /* Transformed grid */
157                         TestReferenceChecker          gridValuesChecker(checker.checkCompound("NonZeroGridValues", "ComplexSpaceGrid"));
158
159                         real gridValuesMagnitude = 1.0;
160                         for (const auto &point : nonZeroGridValuesOutput)
161                         {
162                             gridValuesMagnitude = std::max(std::fabs(point.second.re), gridValuesMagnitude);
163                             gridValuesMagnitude = std::max(std::fabs(point.second.im), gridValuesMagnitude);
164                         }
165                         // Spline moduli participate 3 times in the computation; 2 is an additional factor for SIMD exp() precision
166                         uint64_t gridUlpToleranceFactor = DIM * 2;
167                         if (method == PmeSolveAlgorithm::LennardJones)
168                         {
169                             // Lennard Jones is more complex and also uses erfc(), relax more
170                             gridUlpToleranceFactor *= 2;
171                         }
172                         const uint64_t     splineModuliDoublePrecisionUlps
173                             = getSplineModuliDoublePrecisionUlps(inputRec.pme_order + 1);
174                         auto               gridTolerance
175                             = relativeToleranceAsPrecisionDependentUlp(gridValuesMagnitude,
176                                                                        gridUlpToleranceFactor * c_splineModuliSinglePrecisionUlps,
177                                                                        gridUlpToleranceFactor * splineModuliDoublePrecisionUlps);
178                         gridValuesChecker.setDefaultTolerance(gridTolerance);
179
180                         for (const auto &point : nonZeroGridValuesOutput)
181                         {
182                             // we want an additional safeguard for denormal numbers as they cause an exception in string conversion;
183                             // however, using GMX_REAL_MIN causes an "unused item warning" for single precision builds
184                             if (fabs(point.second.re) >= GMX_FLOAT_MIN)
185                             {
186                                 gridValuesChecker.checkReal(point.second.re, (point.first + " re").c_str());
187                             }
188                             if (fabs(point.second.im) >= GMX_FLOAT_MIN)
189                             {
190                                 gridValuesChecker.checkReal(point.second.im, (point.first + " im").c_str());
191                             }
192                         }
193
194                         if (computeEnergyAndVirial)
195                         {
196                             // Extract the energy and virial
197                             const auto  output          = pmeGetReciprocalEnergyAndVirial(pmeSafe.get(), codePath, method);
198                             const auto &energy          = (method == PmeSolveAlgorithm::Coulomb) ? output.coulombEnergy_ : output.lennardJonesEnergy_;
199                             const auto &virial          = (method == PmeSolveAlgorithm::Coulomb) ? output.coulombVirial_ : output.lennardJonesVirial_;
200
201                             // These quantities are computed based on the grid values, so must have
202                             // checking relative tolerances at least as large. Virial needs more flops
203                             // than energy, so needs a larger tolerance.
204
205                             /* Energy */
206                             double       energyMagnitude = 10.0;
207                             // TODO This factor is arbitrary, do a proper error-propagation analysis
208                             uint64_t     energyUlpToleranceFactor = gridUlpToleranceFactor * 2;
209                             auto         energyTolerance
210                                 = relativeToleranceAsPrecisionDependentUlp(energyMagnitude,
211                                                                            energyUlpToleranceFactor * c_splineModuliSinglePrecisionUlps,
212                                                                            energyUlpToleranceFactor * splineModuliDoublePrecisionUlps);
213                             TestReferenceChecker energyChecker(checker);
214                             energyChecker.setDefaultTolerance(energyTolerance);
215                             energyChecker.checkReal(energy, "Energy");
216
217                             /* Virial */
218                             double       virialMagnitude = 1000.0;
219                             // TODO This factor is arbitrary, do a proper error-propagation analysis
220                             uint64_t     virialUlpToleranceFactor = energyUlpToleranceFactor * 2;
221                             auto         virialTolerance
222                                 = relativeToleranceAsPrecisionDependentUlp(virialMagnitude,
223                                                                            virialUlpToleranceFactor * c_splineModuliSinglePrecisionUlps,
224                                                                            virialUlpToleranceFactor * splineModuliDoublePrecisionUlps);
225                             TestReferenceChecker virialChecker(checker.checkCompound("Matrix", "Virial"));
226                             virialChecker.setDefaultTolerance(virialTolerance);
227                             for (int i = 0; i < DIM; i++)
228                             {
229                                 for (int j = 0; j <= i; j++)
230                                 {
231                                     std::string valueId = formatString("Cell %d %d", i, j);
232                                     virialChecker.checkReal(virial[i][j], valueId.c_str());
233                                 }
234                             }
235                         }
236                     }
237                 }
238
239             }
240         }
241 };
242
243 /*! \brief Test for PME solving */
244 TEST_P(PmeSolveTest, ReproducesOutputs)
245 {
246     EXPECT_NO_THROW(runTest());
247 }
248
249 /* Valid input instances */
250
251 //! A couple of valid inputs for boxes.
252 std::vector<Matrix3x3> const c_sampleBoxes
253 {
254     // normal box
255     Matrix3x3 {{
256                    8.0f, 0.0f, 0.0f,
257                    0.0f, 3.4f, 0.0f,
258                    0.0f, 0.0f, 2.0f
259                }},
260     // triclinic box
261     Matrix3x3 {{
262                    7.0f, 0.0f, 0.0f,
263                    0.0f, 4.1f, 0.0f,
264                    3.5f, 2.0f, 12.2f
265                }},
266 };
267
268 //! A couple of valid inputs for grid sizes
269 std::vector<IVec> const c_sampleGridSizes
270 {
271     IVec {
272         16, 12, 28
273     },
274     IVec {
275         9, 7, 23
276     }
277 };
278
279 //! Moved out from instantiations for readability
280 const auto c_inputBoxes     = ::testing::ValuesIn(c_sampleBoxes);
281 //! Moved out from instantiations for readability
282 const auto c_inputGridSizes = ::testing::ValuesIn(c_sampleGridSizes);
283
284 //! 2 sample complex grids - only non-zero values have to be listed
285 std::vector<SparseComplexGridValuesInput> const c_sampleGrids
286 {
287     SparseComplexGridValuesInput {{
288                                       IVec {
289                                           0, 0, 0
290                                       }, t_complex {
291                                           3.5f, 6.7f
292                                       }
293                                   }, {
294                                       IVec {
295                                           7, 0, 0
296                                       }, t_complex {
297                                           -2.5f, -0.7f
298                                       }
299                                   }, {
300                                       IVec {
301                                           3, 5, 7
302                                       }, t_complex {
303                                           -0.006f, 1e-8f
304                                       }
305                                   }, {
306                                       IVec {
307                                           3, 1, 2
308                                       }, t_complex {
309                                           0.6f, 7.9f
310                                       }
311                                   },  {
312                                       IVec {
313                                           6, 2, 4
314                                       }, t_complex {
315                                           30.1f, 2.45f
316                                       }
317                                   }, },
318     SparseComplexGridValuesInput {{
319                                       IVec {
320                                           0, 4, 0
321                                       }, t_complex {
322                                           0.0f, 0.3f
323                                       }
324                                   }, {
325                                       IVec {
326                                           4, 2, 7
327                                       }, t_complex {
328                                           13.76f, -40.0f
329                                       }
330                                   }, {
331                                       IVec {
332                                           0, 6, 7
333                                       }, t_complex {
334                                           3.6f, 0.0f
335                                       }
336                                   }, {
337                                       IVec {
338                                           2, 5, 10
339                                       }, t_complex {
340                                           3.6f, 10.65f
341                                       }
342                                   }, }
343 };
344
345 //! Moved out from instantiations for readability
346 const auto c_inputGrids = ::testing::ValuesIn(c_sampleGrids);
347 //! Moved out from instantiations for readability
348 const auto c_inputEpsilon_r = ::testing::Values(1.2);
349 //! Moved out from instantiations for readability
350 const auto c_inputEwaldCoeff_q = ::testing::Values(2.0);
351 //! Moved out from instantiations for readability
352 const auto c_inputEwaldCoeff_lj = ::testing::Values(0.7);
353 //! Moved out from instantiations for readability
354 const auto c_inputMethods = ::testing::Values(PmeSolveAlgorithm::Coulomb, PmeSolveAlgorithm::LennardJones);
355
356 //! Instantiation of the PME solving test
357 INSTANTIATE_TEST_CASE_P(SaneInput, PmeSolveTest, ::testing::Combine(c_inputBoxes, c_inputGridSizes, c_inputGrids,
358                                                                     c_inputEpsilon_r, c_inputEwaldCoeff_q, c_inputEwaldCoeff_lj, c_inputMethods));
359
360 //! A few more instances to check that different ewaldCoeff_q actually affects results of the Coulomb solver
361 INSTANTIATE_TEST_CASE_P(DifferentEwaldCoeffQ, PmeSolveTest, ::testing::Combine(c_inputBoxes, c_inputGridSizes, c_inputGrids,
362                                                                                c_inputEpsilon_r, ::testing::Values(0.4), c_inputEwaldCoeff_lj,
363                                                                                    ::testing::Values(PmeSolveAlgorithm::Coulomb)));
364
365 //! A few more instances to check that different ewaldCoeff_lj actually affects results of the Lennard-Jones solver.
366 //! The value has to be approximately larger than 1 / (box dimensions) to have a meaningful output grid.
367 //! Previous value of 0.3 caused one of the grid cells to be less or greater than GMX_FLOAT_MIN, depending on the architecture.
368 INSTANTIATE_TEST_CASE_P(DifferentEwaldCoeffLJ, PmeSolveTest, ::testing::Combine(c_inputBoxes, c_inputGridSizes, c_inputGrids,
369                                                                                 c_inputEpsilon_r, c_inputEwaldCoeff_q, ::testing::Values(2.35),
370                                                                                     ::testing::Values(PmeSolveAlgorithm::LennardJones)));
371
372 //! A few more instances to check that different epsilon_r actually affects results of all solvers
373 INSTANTIATE_TEST_CASE_P(DifferentEpsilonR, PmeSolveTest, ::testing::Combine(c_inputBoxes, c_inputGridSizes, c_inputGrids,
374                                                                             testing::Values(1.9), c_inputEwaldCoeff_q, c_inputEwaldCoeff_lj,
375                                                                             c_inputMethods));
376
377 }  // namespace
378 }  // namespace test
379 }  // namespace gmx