2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2016,2017,2018,2019,2020,2021, 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 spline computation and charge spreading 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"
65 //! PME spline and spread code path being tested
66 enum class PmeSplineAndSpreadOptions
70 SplineAndSpreadUnified
73 /*! \brief Convenience typedef of input parameters - unit cell box, PME interpolation order, grid
74 * dimensions, particle coordinates, particle charges
75 * TODO: consider inclusion of local grid offsets/sizes or PME nodes counts to test the PME DD
77 typedef std::tuple<Matrix3x3, int, IVec, CoordinatesVector, ChargesVector> SplineAndSpreadInputParameters;
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.
82 class PmeSplineAndSpreadTest : public ::testing::TestWithParam<SplineAndSpreadInputParameters>
85 PmeSplineAndSpreadTest() = default;
87 //! Sets the programs once
88 static void SetUpTestSuite()
90 s_pmeTestHardwareContexts = createPmeTestHardwareContextList();
91 g_allowPmeWithSyclForTesting = true; // We support PmeSplineAndSpread with SYCL
94 static void TearDownTestSuite()
96 // Revert the value back.
97 g_allowPmeWithSyclForTesting = false;
101 static void runTest()
103 /* Getting the input */
107 CoordinatesVector coordinates;
108 ChargesVector charges;
110 std::tie(box, pmeOrder, gridSize, coordinates, charges) = GetParam();
111 const size_t atomCount = coordinates.size();
113 /* Storing the input where it's needed */
115 inputRec.nkx = gridSize[XX];
116 inputRec.nky = gridSize[YY];
117 inputRec.nkz = gridSize[ZZ];
118 inputRec.pme_order = pmeOrder;
119 inputRec.coulombtype = CoulombInteractionType::Pme;
120 inputRec.epsilon_r = 1.0;
122 const std::map<PmeSplineAndSpreadOptions, std::string> optionsToTest = {
123 { PmeSplineAndSpreadOptions::SplineAndSpreadUnified,
124 "spline computation and charge spreading (fused)" },
125 { PmeSplineAndSpreadOptions::SplineOnly, "spline computation" },
126 { PmeSplineAndSpreadOptions::SpreadOnly, "charge spreading" }
129 // There is a subtle problem with multiple comparisons against same reference data:
130 // The subsequent (GPU) spreading runs at one point didn't actually copy the output grid
131 // into the proper buffer, but the reference data was already marked as checked
132 // (hasBeenChecked_) by the CPU run, so nothing failed. For now we will manually track that
133 // the count of the grid entries is the same on each run. This is just a hack for a single
134 // specific output though. What would be much better TODO is to split different codepaths
135 // into separate tests, while making them use the same reference files.
136 bool gridValuesSizeAssigned = false;
137 size_t previousGridValuesSize;
139 TestReferenceData refData;
140 for (const auto& pmeTestHardwareContext : s_pmeTestHardwareContexts)
142 pmeTestHardwareContext->activate();
143 CodePath codePath = pmeTestHardwareContext->codePath();
144 const bool supportedInput = pmeSupportsInputForMode(
145 *getTestHardwareEnvironment()->hwinfo(), &inputRec, codePath);
148 /* Testing the failure for the unsupported input */
149 EXPECT_THROW_GMX(pmeInitWrapper(&inputRec, codePath, nullptr, nullptr, nullptr, box),
150 NotImplementedError);
154 for (const auto& option : optionsToTest)
156 /* Describing the test uniquely in case it fails */
159 formatString("Testing %s on %s for PME grid size %d %d %d"
160 ", order %d, %zu atoms",
161 option.second.c_str(),
162 pmeTestHardwareContext->description().c_str(),
169 /* Running the test */
171 PmeSafePointer pmeSafe = pmeInitWrapper(&inputRec,
173 pmeTestHardwareContext->deviceContext(),
174 pmeTestHardwareContext->deviceStream(),
175 pmeTestHardwareContext->pmeGpuProgram(),
177 std::unique_ptr<StatePropagatorDataGpu> stateGpu =
178 (codePath == CodePath::GPU)
179 ? makeStatePropagatorDataGpu(*pmeSafe.get(),
180 pmeTestHardwareContext->deviceContext(),
181 pmeTestHardwareContext->deviceStream())
184 pmeInitAtoms(pmeSafe.get(), stateGpu.get(), codePath, coordinates, charges);
186 const bool computeSplines =
187 (option.first == PmeSplineAndSpreadOptions::SplineOnly)
188 || (option.first == PmeSplineAndSpreadOptions::SplineAndSpreadUnified);
189 const bool spreadCharges =
190 (option.first == PmeSplineAndSpreadOptions::SpreadOnly)
191 || (option.first == PmeSplineAndSpreadOptions::SplineAndSpreadUnified);
195 // Here we should set up the results of the spline computation so that the spread can run.
196 // What is lazy and works is running the separate spline so that it will set it up for us:
197 pmePerformSplineAndSpread(pmeSafe.get(), codePath, true, false);
198 // We know that it is tested in another iteration.
199 // TODO: Clean alternative: read and set the reference gridline indices, spline params
202 pmePerformSplineAndSpread(pmeSafe.get(), codePath, computeSplines, spreadCharges);
203 pmeFinalizeTest(pmeSafe.get(), codePath);
205 /* Outputs correctness check */
206 /* All tolerances were picked empirically for single precision on CPU */
208 TestReferenceChecker rootChecker(refData.rootChecker());
210 const auto maxGridSize = std::max(std::max(gridSize[XX], gridSize[YY]), gridSize[ZZ]);
211 const auto ulpToleranceSplineValues = 4 * (pmeOrder - 2) * maxGridSize;
212 /* 4 is a modest estimate for amount of operations; (pmeOrder - 2) is a number of iterations;
213 * maxGridSize is inverse of the smallest positive fractional coordinate (which are interpolated by the splines).
218 const char* dimString[] = { "X", "Y", "Z" };
221 SCOPED_TRACE(formatString("Testing spline values with tolerance of %d",
222 ulpToleranceSplineValues));
223 TestReferenceChecker splineValuesChecker(
224 rootChecker.checkCompound("Splines", "Values"));
225 splineValuesChecker.setDefaultTolerance(
226 relativeToleranceAsUlp(1.0, ulpToleranceSplineValues));
227 for (int i = 0; i < DIM; i++)
229 auto splineValuesDim =
230 pmeGetSplineData(pmeSafe.get(), codePath, PmeSplineDataType::Values, i);
231 splineValuesChecker.checkSequence(
232 splineValuesDim.begin(), splineValuesDim.end(), dimString[i]);
235 /* Spline derivatives */
236 const auto ulpToleranceSplineDerivatives = 4 * ulpToleranceSplineValues;
237 /* 4 is just a wild guess since the derivatives are deltas of neighbor spline values which could differ greatly */
238 SCOPED_TRACE(formatString("Testing spline derivatives with tolerance of %d",
239 ulpToleranceSplineDerivatives));
240 TestReferenceChecker splineDerivativesChecker(
241 rootChecker.checkCompound("Splines", "Derivatives"));
242 splineDerivativesChecker.setDefaultTolerance(
243 relativeToleranceAsUlp(1.0, ulpToleranceSplineDerivatives));
244 for (int i = 0; i < DIM; i++)
246 auto splineDerivativesDim = pmeGetSplineData(
247 pmeSafe.get(), codePath, PmeSplineDataType::Derivatives, i);
248 splineDerivativesChecker.checkSequence(
249 splineDerivativesDim.begin(), splineDerivativesDim.end(), dimString[i]);
252 /* Particle gridline indices */
253 auto gridLineIndices = pmeGetGridlineIndices(pmeSafe.get(), codePath);
254 rootChecker.checkSequence(
255 gridLineIndices.begin(), gridLineIndices.end(), "Gridline indices");
260 /* The wrapped grid */
261 SparseRealGridValuesOutput nonZeroGridValues = pmeGetRealGrid(pmeSafe.get(), codePath);
262 TestReferenceChecker gridValuesChecker(
263 rootChecker.checkCompound("NonZeroGridValues", "RealSpaceGrid"));
264 const auto ulpToleranceGrid =
265 2 * ulpToleranceSplineValues
266 * static_cast<int>(ceil(sqrt(static_cast<real>(atomCount))));
267 /* 2 is empiric; sqrt(atomCount) assumes all the input charges may spread onto the same cell */
268 SCOPED_TRACE(formatString("Testing grid values with tolerance of %d", ulpToleranceGrid));
269 if (!gridValuesSizeAssigned)
271 previousGridValuesSize = nonZeroGridValues.size();
272 gridValuesSizeAssigned = true;
276 EXPECT_EQ(previousGridValuesSize, nonZeroGridValues.size());
279 gridValuesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceGrid));
280 for (const auto& point : nonZeroGridValues)
282 gridValuesChecker.checkReal(point.second, point.first.c_str());
289 static std::vector<std::unique_ptr<PmeTestHardwareContext>> s_pmeTestHardwareContexts;
292 std::vector<std::unique_ptr<PmeTestHardwareContext>> PmeSplineAndSpreadTest::s_pmeTestHardwareContexts;
295 /*! \brief Test for spline parameter computation and charge spreading. */
296 TEST_P(PmeSplineAndSpreadTest, ReproducesOutputs)
298 EXPECT_NO_THROW_GMX(runTest());
301 /* Valid input instances */
303 //! A couple of valid inputs for boxes.
304 std::vector<Matrix3x3> const c_sampleBoxes{
306 Matrix3x3{ { 8.0F, 0.0F, 0.0F, 0.0F, 3.4F, 0.0F, 0.0F, 0.0F, 2.0F } },
308 Matrix3x3{ { 7.0F, 0.0F, 0.0F, 0.0F, 4.1F, 0.0F, 3.5F, 2.0F, 12.2F } },
311 //! A couple of valid inputs for grid sizes.
312 std::vector<IVec> const c_sampleGridSizes{ IVec{ 16, 12, 14 }, IVec{ 19, 17, 11 } };
315 std::vector<real> const c_sampleChargesFull{ 4.95F, 3.11F, 3.97F, 1.08F, 2.09F, 1.1F,
316 4.13F, 3.31F, 2.8F, 5.83F, 5.09F, 6.1F,
317 2.86F, 0.24F, 5.76F, 5.19F, 0.72F };
319 auto const c_sampleCharges1 = ChargesVector(c_sampleChargesFull).subArray(0, 1);
321 auto const c_sampleCharges2 = ChargesVector(c_sampleChargesFull).subArray(1, 2);
323 auto const c_sampleCharges13 = ChargesVector(c_sampleChargesFull).subArray(3, 13);
325 //! Random coordinate vectors
326 CoordinatesVector const c_sampleCoordinatesFull{ { 5.59F, 1.37F, 0.95F },
330 0.22F // 2 box lengths in x
332 { 0.034F, 1.65F, 0.22F },
333 { 0.33F, 0.92F, 1.56F },
334 { 1.16F, 0.75F, 0.39F },
335 { 0.5F, 1.63F, 1.14F },
339 1.19F // > 2 box lengths in x
344 4.1F // > 2 box lengths in z
351 { 1.6F, 0.93F, 0.53F },
357 { 0.87F, 0.0F, 0.33F },
361 -0.48F // > 2 box lengths in y, negative z
363 { 1.23F, 0.91F, 0.68F },
364 { 0.19F, 1.45F, 0.94F },
365 { 1.28F, 0.46F, 0.38F },
366 { 1.21F, 0.23F, 1.0F } };
367 //! 1 coordinate vector
368 CoordinatesVector const c_sampleCoordinates1(c_sampleCoordinatesFull.begin(),
369 c_sampleCoordinatesFull.begin() + 1);
370 //! 2 coordinate vectors
371 CoordinatesVector const c_sampleCoordinates2(c_sampleCoordinatesFull.begin() + 1,
372 c_sampleCoordinatesFull.begin() + 3);
373 //! 13 coordinate vectors
374 CoordinatesVector const c_sampleCoordinates13(c_sampleCoordinatesFull.begin() + 3,
375 c_sampleCoordinatesFull.begin() + 16);
377 //! moved out from instantiantions for readability
378 auto c_inputBoxes = ::testing::ValuesIn(c_sampleBoxes);
379 //! moved out from instantiantions for readability
380 auto c_inputPmeOrders = ::testing::Range(3, 5 + 1);
381 //! moved out from instantiantions for readability
382 auto c_inputGridSizes = ::testing::ValuesIn(c_sampleGridSizes);
384 /*! \brief Instantiation of the test with valid input and 1 atom */
385 INSTANTIATE_TEST_SUITE_P(SaneInput1,
386 PmeSplineAndSpreadTest,
387 ::testing::Combine(c_inputBoxes,
390 ::testing::Values(c_sampleCoordinates1),
391 ::testing::Values(c_sampleCharges1)));
393 /*! \brief Instantiation of the test with valid input and 2 atoms */
394 INSTANTIATE_TEST_SUITE_P(SaneInput2,
395 PmeSplineAndSpreadTest,
396 ::testing::Combine(c_inputBoxes,
399 ::testing::Values(c_sampleCoordinates2),
400 ::testing::Values(c_sampleCharges2)));
401 /*! \brief Instantiation of the test with valid input and 13 atoms */
402 INSTANTIATE_TEST_SUITE_P(SaneInput13,
403 PmeSplineAndSpreadTest,
404 ::testing::Combine(c_inputBoxes,
407 ::testing::Values(c_sampleCoordinates13),
408 ::testing::Values(c_sampleCharges13)));