2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2016,2017,2018,2019,2020, 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.
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.
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.
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.
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.
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.
37 * Implements PME solving tests.
39 * \author Aleksei Iupinov <a.yupinov@gmail.com>
40 * \ingroup module_ewald
47 #include <gmock/gmock.h>
49 #include "gromacs/mdtypes/inputrec.h"
50 #include "gromacs/utility/stringutil.h"
52 #include "testutils/refdata.h"
53 #include "testutils/test_hardware_environment.h"
54 #include "testutils/testasserts.h"
56 #include "pmetestcommon.h"
64 /*! \brief Convenience typedef of the test input parameters - unit cell box, complex grid dimensions, complex grid values,
65 * electrostatic constant epsilon_r, Ewald splitting parameters ewaldcoeff_q and ewaldcoeff_lj, solver type
66 * Output: transformed local grid (Fourier space); optionally reciprocal energy and virial matrix.
68 * Implement and test Lorentz-Berthelot
70 typedef std::tuple<Matrix3x3, IVec, SparseComplexGridValuesInput, double, double, double, PmeSolveAlgorithm> SolveInputParameters;
73 class PmeSolveTest : public ::testing::TestWithParam<SolveInputParameters>
76 PmeSolveTest() = default;
78 //! Sets the programs once
79 static void SetUpTestCase() { s_pmeTestHardwareContexts = createPmeTestHardwareContextList(); }
84 /* Getting the input */
87 SparseComplexGridValuesInput nonZeroGridValues;
91 PmeSolveAlgorithm method;
92 std::tie(box, gridSize, nonZeroGridValues, epsilon_r, ewaldCoeff_q, ewaldCoeff_lj, method) =
95 /* Storing the input where it's needed, running the test */
97 inputRec.nkx = gridSize[XX];
98 inputRec.nky = gridSize[YY];
99 inputRec.nkz = gridSize[ZZ];
100 inputRec.pme_order = 4;
101 inputRec.coulombtype = eelPME;
102 inputRec.epsilon_r = epsilon_r;
105 case PmeSolveAlgorithm::Coulomb: break;
107 case PmeSolveAlgorithm::LennardJones: inputRec.vdwtype = evdwPME; break;
109 default: GMX_THROW(InternalError("Unknown PME solver"));
112 TestReferenceData refData;
113 for (const auto& pmeTestHardwareContext : s_pmeTestHardwareContexts)
115 pmeTestHardwareContext->activate();
116 CodePath codePath = pmeTestHardwareContext->codePath();
117 const bool supportedInput = pmeSupportsInputForMode(
118 *getTestHardwareEnvironment()->hwinfo(), &inputRec, codePath);
121 /* Testing the failure for the unsupported input */
122 EXPECT_THROW_GMX(pmeInitWrapper(&inputRec, codePath, nullptr, nullptr, nullptr, box,
123 ewaldCoeff_q, ewaldCoeff_lj),
124 NotImplementedError);
128 std::map<GridOrdering, std::string> gridOrderingsToTest = { { GridOrdering::YZX,
130 if (codePath == CodePath::GPU)
132 gridOrderingsToTest[GridOrdering::XYZ] = "XYZ";
134 for (const auto& gridOrdering : gridOrderingsToTest)
136 for (bool computeEnergyAndVirial : { false, true })
138 /* Describing the test*/
139 SCOPED_TRACE(formatString(
140 "Testing solving (%s, %s, %s energy/virial) on %s for PME grid "
141 "size %d %d %d, Ewald coefficients %g %g",
142 (method == PmeSolveAlgorithm::LennardJones) ? "Lennard-Jones" : "Coulomb",
143 gridOrdering.second.c_str(), computeEnergyAndVirial ? "with" : "without",
144 pmeTestHardwareContext->description().c_str(), gridSize[XX],
145 gridSize[YY], gridSize[ZZ], ewaldCoeff_q, ewaldCoeff_lj));
147 /* Running the test */
148 PmeSafePointer pmeSafe = pmeInitWrapper(
149 &inputRec, codePath, pmeTestHardwareContext->deviceContext(),
150 pmeTestHardwareContext->deviceStream(),
151 pmeTestHardwareContext->pmeGpuProgram(), box, ewaldCoeff_q, ewaldCoeff_lj);
152 pmeSetComplexGrid(pmeSafe.get(), codePath, gridOrdering.first, nonZeroGridValues);
153 const real cellVolume = box[0] * box[4] * box[8];
154 // FIXME - this is box[XX][XX] * box[YY][YY] * box[ZZ][ZZ], should be stored in the PME structure
155 pmePerformSolve(pmeSafe.get(), codePath, method, cellVolume, gridOrdering.first,
156 computeEnergyAndVirial);
157 pmeFinalizeTest(pmeSafe.get(), codePath);
159 /* Check the outputs */
160 TestReferenceChecker checker(refData.rootChecker());
162 SparseComplexGridValuesOutput nonZeroGridValuesOutput =
163 pmeGetComplexGrid(pmeSafe.get(), codePath, gridOrdering.first);
164 /* Transformed grid */
165 TestReferenceChecker gridValuesChecker(
166 checker.checkCompound("NonZeroGridValues", "ComplexSpaceGrid"));
168 real gridValuesMagnitude = 1.0;
169 for (const auto& point : nonZeroGridValuesOutput)
171 gridValuesMagnitude = std::max(std::fabs(point.second.re), gridValuesMagnitude);
172 gridValuesMagnitude = std::max(std::fabs(point.second.im), gridValuesMagnitude);
174 // Spline moduli participate 3 times in the computation; 2 is an additional factor for SIMD exp() precision
175 uint64_t gridUlpToleranceFactor = DIM * 2;
176 if (method == PmeSolveAlgorithm::LennardJones)
178 // Lennard Jones is more complex and also uses erfc(), relax more
179 gridUlpToleranceFactor *= 2;
181 const uint64_t splineModuliDoublePrecisionUlps =
182 getSplineModuliDoublePrecisionUlps(inputRec.pme_order + 1);
183 auto gridTolerance = relativeToleranceAsPrecisionDependentUlp(
184 gridValuesMagnitude, gridUlpToleranceFactor * c_splineModuliSinglePrecisionUlps,
185 gridUlpToleranceFactor * splineModuliDoublePrecisionUlps);
186 gridValuesChecker.setDefaultTolerance(gridTolerance);
188 for (const auto& point : nonZeroGridValuesOutput)
190 // we want an additional safeguard for denormal numbers as they cause an exception in string conversion;
191 // however, using GMX_REAL_MIN causes an "unused item warning" for single precision builds
192 if (fabs(point.second.re) >= GMX_FLOAT_MIN)
194 gridValuesChecker.checkReal(point.second.re, (point.first + " re").c_str());
196 if (fabs(point.second.im) >= GMX_FLOAT_MIN)
198 gridValuesChecker.checkReal(point.second.im, (point.first + " im").c_str());
202 if (computeEnergyAndVirial)
204 // Extract the energy and virial
206 pmeGetReciprocalEnergyAndVirial(pmeSafe.get(), codePath, method);
207 const auto& energy = (method == PmeSolveAlgorithm::Coulomb)
208 ? output.coulombEnergy_
209 : output.lennardJonesEnergy_;
210 const auto& virial = (method == PmeSolveAlgorithm::Coulomb)
211 ? output.coulombVirial_
212 : output.lennardJonesVirial_;
214 // These quantities are computed based on the grid values, so must have
215 // checking relative tolerances at least as large. Virial needs more flops
216 // than energy, so needs a larger tolerance.
219 double energyMagnitude = 10.0;
220 // TODO This factor is arbitrary, do a proper error-propagation analysis
221 uint64_t energyUlpToleranceFactor = gridUlpToleranceFactor * 2;
222 auto energyTolerance = relativeToleranceAsPrecisionDependentUlp(
223 energyMagnitude, energyUlpToleranceFactor * c_splineModuliSinglePrecisionUlps,
224 energyUlpToleranceFactor * splineModuliDoublePrecisionUlps);
225 TestReferenceChecker energyChecker(checker);
226 energyChecker.setDefaultTolerance(energyTolerance);
227 energyChecker.checkReal(energy, "Energy");
230 double virialMagnitude = 1000.0;
231 // TODO This factor is arbitrary, do a proper error-propagation analysis
232 uint64_t virialUlpToleranceFactor = energyUlpToleranceFactor * 2;
233 auto virialTolerance = relativeToleranceAsPrecisionDependentUlp(
234 virialMagnitude, virialUlpToleranceFactor * c_splineModuliSinglePrecisionUlps,
235 virialUlpToleranceFactor * splineModuliDoublePrecisionUlps);
236 TestReferenceChecker virialChecker(
237 checker.checkCompound("Matrix", "Virial"));
238 virialChecker.setDefaultTolerance(virialTolerance);
239 for (int i = 0; i < DIM; i++)
241 for (int j = 0; j <= i; j++)
243 std::string valueId = formatString("Cell %d %d", i, j);
244 virialChecker.checkReal(virial[i][j], valueId.c_str());
253 static std::vector<std::unique_ptr<PmeTestHardwareContext>> s_pmeTestHardwareContexts;
256 std::vector<std::unique_ptr<PmeTestHardwareContext>> PmeSolveTest::s_pmeTestHardwareContexts;
258 /*! \brief Test for PME solving */
259 TEST_P(PmeSolveTest, ReproducesOutputs)
261 EXPECT_NO_THROW_GMX(runTest());
264 /* Valid input instances */
266 //! A couple of valid inputs for boxes.
267 std::vector<Matrix3x3> const c_sampleBoxes{
269 Matrix3x3{ { 8.0F, 0.0F, 0.0F, 0.0F, 3.4F, 0.0F, 0.0F, 0.0F, 2.0F } },
271 Matrix3x3{ { 7.0F, 0.0F, 0.0F, 0.0F, 4.1F, 0.0F, 3.5F, 2.0F, 12.2F } },
274 //! A couple of valid inputs for grid sizes
275 std::vector<IVec> const c_sampleGridSizes{ IVec{ 16, 12, 28 }, IVec{ 9, 7, 23 } };
277 //! Moved out from instantiations for readability
278 const auto c_inputBoxes = ::testing::ValuesIn(c_sampleBoxes);
279 //! Moved out from instantiations for readability
280 const auto c_inputGridSizes = ::testing::ValuesIn(c_sampleGridSizes);
282 //! 2 sample complex grids - only non-zero values have to be listed
283 std::vector<SparseComplexGridValuesInput> const c_sampleGrids{
284 SparseComplexGridValuesInput{
285 { IVec{ 0, 0, 0 }, t_complex{ 3.5F, 6.7F } },
286 { IVec{ 7, 0, 0 }, t_complex{ -2.5F, -0.7F } },
287 { IVec{ 3, 5, 7 }, t_complex{ -0.006F, 1e-8F } },
288 { IVec{ 3, 1, 2 }, t_complex{ 0.6F, 7.9F } },
289 { IVec{ 6, 2, 4 }, t_complex{ 30.1F, 2.45F } },
291 SparseComplexGridValuesInput{
292 { IVec{ 0, 4, 0 }, t_complex{ 0.0F, 0.3F } },
293 { IVec{ 4, 2, 7 }, t_complex{ 13.76F, -40.0F } },
294 { IVec{ 0, 6, 7 }, t_complex{ 3.6F, 0.0F } },
295 { IVec{ 2, 5, 10 }, t_complex{ 3.6F, 10.65F } },
299 //! Moved out from instantiations for readability
300 const auto c_inputGrids = ::testing::ValuesIn(c_sampleGrids);
301 //! Moved out from instantiations for readability
302 const auto c_inputEpsilon_r = ::testing::Values(1.2);
303 //! Moved out from instantiations for readability
304 const auto c_inputEwaldCoeff_q = ::testing::Values(2.0);
305 //! Moved out from instantiations for readability
306 const auto c_inputEwaldCoeff_lj = ::testing::Values(0.7);
307 //! Moved out from instantiations for readability
308 const auto c_inputMethods = ::testing::Values(PmeSolveAlgorithm::Coulomb, PmeSolveAlgorithm::LennardJones);
310 //! Instantiation of the PME solving test
311 INSTANTIATE_TEST_CASE_P(SaneInput,
313 ::testing::Combine(c_inputBoxes,
318 c_inputEwaldCoeff_lj,
321 //! A few more instances to check that different ewaldCoeff_q actually affects results of the Coulomb solver
322 INSTANTIATE_TEST_CASE_P(DifferentEwaldCoeffQ,
324 ::testing::Combine(c_inputBoxes,
328 ::testing::Values(0.4),
329 c_inputEwaldCoeff_lj,
330 ::testing::Values(PmeSolveAlgorithm::Coulomb)));
332 //! A few more instances to check that different ewaldCoeff_lj actually affects results of the Lennard-Jones solver.
333 //! The value has to be approximately larger than 1 / (box dimensions) to have a meaningful output grid.
334 //! Previous value of 0.3 caused one of the grid cells to be less or greater than GMX_FLOAT_MIN, depending on the architecture.
335 INSTANTIATE_TEST_CASE_P(DifferentEwaldCoeffLJ,
337 ::testing::Combine(c_inputBoxes,
342 ::testing::Values(2.35),
343 ::testing::Values(PmeSolveAlgorithm::LennardJones)));
345 //! A few more instances to check that different epsilon_r actually affects results of all solvers
346 INSTANTIATE_TEST_CASE_P(DifferentEpsilonR,
348 ::testing::Combine(c_inputBoxes,
351 testing::Values(1.9),
353 c_inputEwaldCoeff_lj,