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 * \author Mark Abraham <mark.j.abraham@gmail.com>
41 * \ingroup module_ewald
48 #include <gmock/gmock.h>
50 #include "gromacs/mdtypes/inputrec.h"
51 #include "gromacs/utility/stringutil.h"
53 #include "testutils/refdata.h"
54 #include "testutils/test_hardware_environment.h"
55 #include "testutils/testasserts.h"
57 #include "pmetestcommon.h"
66 //! A couple of valid inputs for grid sizes
67 std::vector<IVec> const c_inputGridSizes{ IVec{ 16, 12, 14 }, IVec{ 19, 17, 11 } };
69 //! PME spline and spread code path being tested
70 enum class SplineAndSpreadOptions
74 SplineAndSpreadUnified,
80 CoordinatesVector coordinates;
81 ChargesVector charges;
84 const std::unordered_map<std::string, TestSystem> c_testSystems = {
85 { "1 atom", { CoordinatesVector{ { 5.59F, 1.37F, 0.95F } }, ChargesVector{ 4.95F } } },
89 { 16.0F, 1.02F, 0.22F },
90 { 0.034F, 1.65F, 0.22F },
98 { 0.33F, 0.92F, 1.56F },
99 { 1.16F, 0.75F, 0.39F },
100 { 0.5F, 1.63F, 1.14F },
101 // > 2 box lengths in x
102 { 16.0001F, 1.52F, 1.19F },
103 // > 2 box lengths in z
104 { 1.43F, 1.1F, 4.1F },
106 { -1.08F, 1.19F, 0.08F },
107 { 1.6F, 0.93F, 0.53F },
109 { 1.32F, -1.48F, 0.16F },
110 { 0.87F, 0.0F, 0.33F },
111 // > 2 box lengths in y, negative z
112 { 0.95F, 7.7F, -0.48F },
113 { 1.23F, 0.91F, 0.68F },
114 { 0.19F, 1.45F, 0.94F },
115 { 1.28F, 0.46F, 0.38F },
117 ChargesVector{ 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 } } },
120 /* Valid input instances */
122 /*! \brief Convenience typedef of input parameters - unit cell box, PME interpolation order, grid
123 * dimensions, particle coordinates, particle charges
124 * TODO: consider inclusion of local grid offsets/sizes or PME nodes counts to test the PME DD
126 typedef std::tuple<std::string, int, IVec, std::string> SplineAndSpreadInputParameters;
128 //! Help GoogleTest name our test cases
129 std::string nameOfTest(const testing::TestParamInfo<SplineAndSpreadInputParameters>& info)
131 std::string testName = formatString(
136 std::get<0>(info.param).c_str(),
137 std::get<1>(info.param),
138 std::get<2>(info.param)[XX],
139 std::get<2>(info.param)[YY],
140 std::get<2>(info.param)[ZZ],
141 std::get<3>(info.param).c_str());
143 // Note that the returned names must be unique and may use only
144 // alphanumeric ASCII characters. It's not supposed to contain
145 // underscores (see the GoogleTest FAQ
146 // why-should-test-suite-names-and-test-names-not-contain-underscore),
147 // but doing so works for now, is likely to remain so, and makes
148 // such test names much more readable.
149 testName = replaceAll(testName, "-", "_");
150 testName = replaceAll(testName, ".", "_");
151 testName = replaceAll(testName, " ", "_");
155 const char* enumValueToString(SplineAndSpreadOptions enumValue)
157 static constexpr gmx::EnumerationArray<SplineAndSpreadOptions, const char*> s_splineAndSpreadOptionsNames = {
158 "spline computation",
160 "spline computation and charge spreading (fused)",
162 return s_splineAndSpreadOptionsNames[enumValue];
165 /*! \brief Test fixture for testing both atom spline parameter computation and charge spreading.
166 * These 2 stages of PME are tightly coupled in the code.
168 class SplineAndSpreadTest : public ::testing::TestWithParam<SplineAndSpreadInputParameters>
171 SplineAndSpreadTest() = default;
173 static void SetUpTestSuite()
175 s_pmeTestHardwareContexts = createPmeTestHardwareContextList();
176 g_allowPmeWithSyclForTesting = true; // We support PmeSplineAndSpread with SYCL
179 static void TearDownTestSuite()
181 // Revert the value back.
182 g_allowPmeWithSyclForTesting = false;
186 static void runTest()
188 /* Getting the input */
191 std::string boxName, testSystemName;
193 std::tie(boxName, pmeOrder, gridSize, testSystemName) = GetParam();
194 Matrix3x3 box = c_inputBoxes.at(boxName);
195 const CoordinatesVector& coordinates = c_testSystems.at(testSystemName).coordinates;
196 const ChargesVector& charges = c_testSystems.at(testSystemName).charges;
197 const size_t atomCount = coordinates.size();
199 /* Storing the input where it's needed */
201 inputRec.nkx = gridSize[XX];
202 inputRec.nky = gridSize[YY];
203 inputRec.nkz = gridSize[ZZ];
204 inputRec.pme_order = pmeOrder;
205 inputRec.coulombtype = CoulombInteractionType::Pme;
206 inputRec.epsilon_r = 1.0;
208 // There is a subtle problem with multiple comparisons against same reference data:
209 // The subsequent (GPU) spreading runs at one point didn't actually copy the output grid
210 // into the proper buffer, but the reference data was already marked as checked
211 // (hasBeenChecked_) by the CPU run, so nothing failed. For now we will manually track that
212 // the count of the grid entries is the same on each run. This is just a hack for a single
213 // specific output though. What would be much better TODO is to split different codepaths
214 // into separate tests, while making them use the same reference files.
215 bool gridValuesSizeAssigned = false;
216 size_t previousGridValuesSize;
218 TestReferenceData refData;
219 for (const auto& pmeTestHardwareContext : s_pmeTestHardwareContexts)
221 pmeTestHardwareContext->activate();
222 CodePath codePath = pmeTestHardwareContext->codePath();
223 const bool supportedInput = pmeSupportsInputForMode(
224 *getTestHardwareEnvironment()->hwinfo(), &inputRec, codePath);
227 /* Testing the failure for the unsupported input */
228 EXPECT_THROW_GMX(pmeInitWrapper(&inputRec, codePath, nullptr, nullptr, nullptr, box),
229 NotImplementedError);
233 for (SplineAndSpreadOptions option : EnumerationWrapper<SplineAndSpreadOptions>())
235 /* Describing the test uniquely in case it fails */
238 formatString("Testing %s on %s for PME grid size %d %d %d"
239 ", order %d, %zu atoms",
240 enumValueToString(option),
241 pmeTestHardwareContext->description().c_str(),
248 /* Running the test */
250 PmeSafePointer pmeSafe = pmeInitWrapper(&inputRec,
252 pmeTestHardwareContext->deviceContext(),
253 pmeTestHardwareContext->deviceStream(),
254 pmeTestHardwareContext->pmeGpuProgram(),
256 std::unique_ptr<StatePropagatorDataGpu> stateGpu =
257 (codePath == CodePath::GPU)
258 ? makeStatePropagatorDataGpu(*pmeSafe.get(),
259 pmeTestHardwareContext->deviceContext(),
260 pmeTestHardwareContext->deviceStream())
263 pmeInitAtoms(pmeSafe.get(), stateGpu.get(), codePath, coordinates, charges);
265 const bool computeSplines = (option == SplineAndSpreadOptions::SplineOnly)
266 || (option == SplineAndSpreadOptions::SplineAndSpreadUnified);
267 const bool spreadCharges = (option == SplineAndSpreadOptions::SpreadOnly)
268 || (option == SplineAndSpreadOptions::SplineAndSpreadUnified);
272 // Here we should set up the results of the spline computation so that the spread can run.
273 // What is lazy and works is running the separate spline so that it will set it up for us:
274 pmePerformSplineAndSpread(pmeSafe.get(), codePath, true, false);
275 // We know that it is tested in another iteration.
276 // TODO: Clean alternative: read and set the reference gridline indices, spline params
279 pmePerformSplineAndSpread(pmeSafe.get(), codePath, computeSplines, spreadCharges);
280 pmeFinalizeTest(pmeSafe.get(), codePath);
282 /* Outputs correctness check */
283 /* All tolerances were picked empirically for single precision on CPU */
285 TestReferenceChecker rootChecker(refData.rootChecker());
287 const auto maxGridSize = std::max(std::max(gridSize[XX], gridSize[YY]), gridSize[ZZ]);
288 const auto ulpToleranceSplineValues = 4 * (pmeOrder - 2) * maxGridSize;
289 /* 4 is a modest estimate for amount of operations; (pmeOrder - 2) is a number of iterations;
290 * maxGridSize is inverse of the smallest positive fractional coordinate (which are interpolated by the splines).
295 const char* dimString[] = { "X", "Y", "Z" };
298 SCOPED_TRACE(formatString("Testing spline values with tolerance of %d",
299 ulpToleranceSplineValues));
300 TestReferenceChecker splineValuesChecker(
301 rootChecker.checkCompound("Splines", "Values"));
302 splineValuesChecker.setDefaultTolerance(
303 relativeToleranceAsUlp(1.0, ulpToleranceSplineValues));
304 for (int i = 0; i < DIM; i++)
306 auto splineValuesDim =
307 pmeGetSplineData(pmeSafe.get(), codePath, PmeSplineDataType::Values, i);
308 splineValuesChecker.checkSequence(
309 splineValuesDim.begin(), splineValuesDim.end(), dimString[i]);
312 /* Spline derivatives */
313 const auto ulpToleranceSplineDerivatives = 4 * ulpToleranceSplineValues;
314 /* 4 is just a wild guess since the derivatives are deltas of neighbor spline values which could differ greatly */
315 SCOPED_TRACE(formatString("Testing spline derivatives with tolerance of %d",
316 ulpToleranceSplineDerivatives));
317 TestReferenceChecker splineDerivativesChecker(
318 rootChecker.checkCompound("Splines", "Derivatives"));
319 splineDerivativesChecker.setDefaultTolerance(
320 relativeToleranceAsUlp(1.0, ulpToleranceSplineDerivatives));
321 for (int i = 0; i < DIM; i++)
323 auto splineDerivativesDim = pmeGetSplineData(
324 pmeSafe.get(), codePath, PmeSplineDataType::Derivatives, i);
325 splineDerivativesChecker.checkSequence(
326 splineDerivativesDim.begin(), splineDerivativesDim.end(), dimString[i]);
329 /* Particle gridline indices */
330 auto gridLineIndices = pmeGetGridlineIndices(pmeSafe.get(), codePath);
331 rootChecker.checkSequence(
332 gridLineIndices.begin(), gridLineIndices.end(), "Gridline indices");
337 /* The wrapped grid */
338 SparseRealGridValuesOutput nonZeroGridValues = pmeGetRealGrid(pmeSafe.get(), codePath);
339 TestReferenceChecker gridValuesChecker(
340 rootChecker.checkCompound("NonZeroGridValues", "RealSpaceGrid"));
341 const auto ulpToleranceGrid =
342 2 * ulpToleranceSplineValues
343 * static_cast<int>(ceil(sqrt(static_cast<real>(atomCount))));
344 /* 2 is empiric; sqrt(atomCount) assumes all the input charges may spread onto the same cell */
345 SCOPED_TRACE(formatString("Testing grid values with tolerance of %d", ulpToleranceGrid));
346 if (!gridValuesSizeAssigned)
348 previousGridValuesSize = nonZeroGridValues.size();
349 gridValuesSizeAssigned = true;
353 EXPECT_EQ(previousGridValuesSize, nonZeroGridValues.size());
356 gridValuesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceGrid));
357 for (const auto& point : nonZeroGridValues)
359 gridValuesChecker.checkReal(point.second, point.first.c_str());
366 static std::vector<std::unique_ptr<PmeTestHardwareContext>> s_pmeTestHardwareContexts;
369 std::vector<std::unique_ptr<PmeTestHardwareContext>> SplineAndSpreadTest::s_pmeTestHardwareContexts;
372 /*! \brief Test for spline parameter computation and charge spreading. */
373 TEST_P(SplineAndSpreadTest, WorksWith)
375 EXPECT_NO_THROW_GMX(runTest());
378 //! Moved out from instantiations for readability
379 const auto c_inputBoxNames = ::testing::Values("rect", "tric");
380 //! Moved out from instantiations for readability
381 const auto c_inputGridNames = ::testing::Values("first", "second");
382 //! Moved out from instantiations for readability
383 const auto c_inputTestSystemNames = ::testing::Values("1 atom", "2 atoms", "13 atoms");
385 INSTANTIATE_TEST_SUITE_P(Pme,
387 ::testing::Combine(c_inputBoxNames,
388 ::testing::ValuesIn(c_inputPmeOrders),
389 ::testing::ValuesIn(c_inputGridSizes),
390 c_inputTestSystemNames),