2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2014,2015,2016,2018,2019, 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 #include "gromacs/mdlib/settle.h"
44 #include <gtest/gtest.h>
46 #include "gromacs/gpu_utils/gpu_utils.h"
47 #include "gromacs/math/paddedvector.h"
48 #include "gromacs/math/vec.h"
49 #include "gromacs/math/vectypes.h"
50 #include "gromacs/mdlib/settle_cuda.h"
51 #include "gromacs/mdtypes/mdatom.h"
52 #include "gromacs/pbcutil/pbc.h"
53 #include "gromacs/topology/idef.h"
54 #include "gromacs/topology/ifunc.h"
55 #include "gromacs/topology/topology.h"
56 #include "gromacs/utility/smalloc.h"
57 #include "gromacs/utility/stringutil.h"
58 #include "gromacs/utility/unique_cptr.h"
60 #include "gromacs/mdlib/tests/watersystem.h"
61 #include "testutils/testasserts.h"
69 //! Simple cubic simulation box to use in tests
70 matrix g_box = {{real(1.86206), 0, 0}, {0, real(1.86206), 0}, {0, 0, real(1.86206)}};
72 //! Convenience typedef
73 typedef std::tuple<int, bool, bool, bool> SettleTestParameters;
75 /*! \brief Test fixture for testing SETTLE position updates
77 * \todo This also tests that if the calling code requires velocities
78 * and virial updates, that those outputs do change, but does not test
79 * that those changes are correct.
81 * \todo Only no-PBC and cubic-PBC are tested here, but the correct
82 * function of the SIMD version of set_pbx_auic in all cases should be
85 class SettleTest : public ::testing::TestWithParam<SettleTestParameters>
88 //! Updated water atom positions to constrain (DIM reals per atom)
89 PaddedVector<gmx::RVec> updatedPositions_;
90 //! Water atom velocities to constrain (DIM reals per atom)
91 PaddedVector<gmx::RVec> velocities_;
92 //! No periodic boundary conditions
94 //! Rectangular periodic box
99 // Moved these to the constructor body so that uncrustify will not confuse doxygen
100 updatedPositions_.resizeWithPadding(c_waterPositions.size());
101 velocities_.resizeWithPadding(c_waterPositions.size());
103 std::copy(c_waterPositions.begin(), c_waterPositions.end(), updatedPositions_.begin());
105 // No periodic boundary conditions
106 set_pbc(&pbcNone_, epbcNONE, g_box);
108 // Rectangular periodic box
109 set_pbc(&pbcXyz_, epbcXYZ, g_box);
111 // Perturb the atom positions, to appear like an
112 // "update," and where there is definitely constraining
114 for (int i = 0; i < updatedPositions_.size()*DIM; ++i)
118 updatedPositions_[i / DIM][i % DIM] += 0.01;
122 updatedPositions_[i / DIM][i % DIM] -= 0.01;
126 updatedPositions_[i / DIM][i % DIM] += 0.02;
130 updatedPositions_[i / DIM][i % DIM] -= 0.02;
132 velocities_[i / DIM][i % DIM] = 0.0;
137 TEST_P(SettleTest, SatisfiesConstraints)
140 bool usePbc, useVelocities, calcVirial;
141 // Make some symbolic names for the parameter combination.
142 std::tie(numSettles, usePbc, useVelocities, calcVirial) = GetParam();
144 // Make a string that describes which parameter combination is
145 // being tested, to help make failing tests comprehensible.
146 std::string testDescription = formatString("while testing %d SETTLEs, %sPBC, %svelocities and %scalculating the virial",
148 usePbc ? "with " : "without ",
149 useVelocities ? "with " : "without ",
150 calcVirial ? "" : "not ");
152 const int settleType = 0;
153 const int atomsPerSettle = NRAL(F_SETTLE);
154 ASSERT_LE(numSettles, updatedPositions_.size() / atomsPerSettle) << "cannot test that many SETTLEs " << testDescription;
156 // Set up the topology.
158 mtop.moltype.resize(1);
159 mtop.molblock.resize(1);
160 mtop.molblock[0].type = 0;
161 std::vector<int> &iatoms = mtop.moltype[0].ilist[F_SETTLE].iatoms;
162 for (int i = 0; i < numSettles; ++i)
164 iatoms.push_back(settleType);
165 iatoms.push_back(i*atomsPerSettle + 0);
166 iatoms.push_back(i*atomsPerSettle + 1);
167 iatoms.push_back(i*atomsPerSettle + 2);
170 // Set up the SETTLE parameters.
171 const real dOH = 0.09572;
172 const real dHH = 0.15139;
174 iparams.settle.doh = dOH;
175 iparams.settle.dhh = dHH;
176 mtop.ffparams.iparams.push_back(iparams);
178 // Set up the masses.
180 std::vector<real> mass, massReciprocal;
181 mtop.moltype[0].atoms.atom = static_cast<t_atom*>(calloc(numSettles*atomsPerSettle, sizeof(t_atom)));
182 const real oxygenMass = 15.9994, hydrogenMass = 1.008;
183 for (int i = 0; i < numSettles; ++i)
185 mass.push_back(oxygenMass);
186 mass.push_back(hydrogenMass);
187 mass.push_back(hydrogenMass);
188 massReciprocal.push_back(1./oxygenMass);
189 massReciprocal.push_back(1./hydrogenMass);
190 massReciprocal.push_back(1./hydrogenMass);
191 mtop.moltype[0].atoms.atom[i*atomsPerSettle + 0].m = oxygenMass;
192 mtop.moltype[0].atoms.atom[i*atomsPerSettle + 1].m = hydrogenMass;
193 mtop.moltype[0].atoms.atom[i*atomsPerSettle + 2].m = hydrogenMass;
195 mdatoms.massT = mass.data();
196 mdatoms.invmass = massReciprocal.data();
197 mdatoms.homenr = numSettles * atomsPerSettle;
199 const t_ilist ilist = { mtop.moltype[0].ilist[F_SETTLE].size(), 0, mtop.moltype[0].ilist[F_SETTLE].iatoms.data(), 0 };
201 // Copy the original positions from the array of doubles to a vector of reals
202 PaddedVector<gmx::RVec> startingPositions;
203 startingPositions.resizeWithPadding(c_waterPositions.size());
204 std::copy(c_waterPositions.begin(), c_waterPositions.end(), startingPositions.begin());
206 const real reciprocalTimeStep = 1.0/0.002;
207 tensor virial = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
209 #if GMX_GPU == GMX_GPU_CUDA
210 // Make a copy of all data-structures for GPU code testing
211 PaddedVector<gmx::RVec> updatedPositionsGpu = updatedPositions_;
212 PaddedVector<gmx::RVec> velocitiesGpu = velocities_;
213 tensor virialGpu = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
216 // Finally make the settle data structures
217 settledata *settled = settle_init(mtop);
219 settle_set_constraints(settled, &ilist, mdatoms);
223 int numThreads = 1, threadIndex = 0;
224 csettle(settled, numThreads, threadIndex,
225 usePbc ? &pbcXyz_ : &pbcNone_,
226 static_cast<real *>(startingPositions[0]), static_cast<real *>(updatedPositions_[0]), reciprocalTimeStep,
227 useVelocities ? static_cast<real *>(velocities_[0]) : nullptr,
228 calcVirial, virial, &errorOccured);
229 settle_free(settled);
230 EXPECT_FALSE(errorOccured) << testDescription;
232 // The necessary tolerances for the test to pass were determined
233 // empirically. This isn't nice, but the required behavior that
234 // SETTLE produces constrained coordinates consistent with
235 // sensible sampling needs to be tested at a much higher level.
236 FloatingPointTolerance tolerance =
237 relativeToleranceAsPrecisionDependentUlp(dOH*dOH, 80, 380);
239 // Verify the updated coordinates match the requirements
240 for (int i = 0; i < numSettles; ++i)
242 const gmx::RVec &positionO = updatedPositions_[i*3 + 0];
243 const gmx::RVec &positionH1 = updatedPositions_[i*3 + 1];
244 const gmx::RVec &positionH2 = updatedPositions_[i*3 + 2];
246 EXPECT_REAL_EQ_TOL(dOH*dOH, distance2(positionO, positionH1), tolerance) << formatString("for water %d ", i) << testDescription;
247 EXPECT_REAL_EQ_TOL(dOH*dOH, distance2(positionO, positionH2), tolerance) << formatString("for water %d ", i) << testDescription;
248 EXPECT_REAL_EQ_TOL(dHH*dHH, distance2(positionH1, positionH2), tolerance) << formatString("for water %d ", i) << testDescription;
250 // This merely tests whether the velocities were
251 // updated from the starting values of zero (or not),
252 // but not whether the update was correct.
253 for (int j = 0; j < atomsPerSettle; ++j)
255 for (int d = 0; d < DIM; ++d)
257 EXPECT_TRUE(useVelocities == (0. != velocities_[i*3 + j][d]))
258 << formatString("for water %d velocity atom %d dim %d", i, j, d)
264 FloatingPointTolerance toleranceVirial = absoluteTolerance(0.000001);
265 // This merely tests whether the viral was updated from
266 // the starting values of zero (or not), but not whether
267 // any update was correct.
268 for (int d = 0; d < DIM; ++d)
270 for (int dd = 0; dd < DIM; ++dd)
273 EXPECT_TRUE(calcVirial == (0. != virial[d][dd]))
274 << formatString("for virial component[%d][%d] ", d, dd)
279 EXPECT_REAL_EQ_TOL(virial[d][dd], virial[dd][d], toleranceVirial)
280 << formatString("Virial is not symmetrical for [%d][%d] ", d, dd)
286 // CUDA version will be tested only if
287 // 1. The code was compiled with cuda
288 // 2. There is a CUDA-capable GPU in a system
289 #if GMX_GPU == GMX_GPU_CUDA
290 // TODO: Here we should check that at least 1 suitable GPU is available
291 if (canPerformGpuDetection())
293 // Run the CUDA code and check if it gives identical results to CPU code
295 idef.il[F_SETTLE] = ilist;
297 std::unique_ptr<SettleCuda> settleCuda = std::make_unique<SettleCuda>(mtop);
298 settleCuda->setPbc(usePbc ? &pbcXyz_ : &pbcNone_);
299 settleCuda->set(idef, mdatoms);
301 settleCuda->copyApplyCopy(mdatoms.homenr,
302 as_rvec_array(startingPositions.data()),
303 as_rvec_array(updatedPositionsGpu.data()),
305 as_rvec_array(velocitiesGpu.data()),
310 FloatingPointTolerance toleranceGpuCpu = absoluteTolerance(0.0001);
312 for (unsigned i = 0; i < updatedPositionsGpu.size(); ++i)
314 for (int d = 0; d < DIM; ++d)
316 EXPECT_REAL_EQ_TOL(updatedPositionsGpu[i][d], updatedPositions_[i][d], toleranceGpuCpu)
317 << formatString("Different coordinates in GPU and CPU codes for particle %d component %d ", i, d)
321 for (unsigned i = 0; i < velocitiesGpu.size(); ++i)
323 for (int d = 0; d < DIM; ++d)
325 EXPECT_REAL_EQ_TOL(velocitiesGpu[i][d], velocities_[i][d], toleranceGpuCpu)
326 << formatString("Different velocities in GPU and CPU codes for particle %d component %d ", i, d)
330 for (int d1 = 0; d1 < DIM; ++d1)
332 for (int d2 = 0; d2 < DIM; ++d2)
334 EXPECT_REAL_EQ_TOL(virialGpu[d1][d2], virial[d1][d2], toleranceGpuCpu)
335 << formatString("Different virial components in GPU and CPU codes for components %d and %d ", d1, d2)
343 // Scan the full Cartesian product of numbers of SETTLE interactions
344 // (4 and 17 are chosen to test cases that do and do not match
345 // hardware SIMD widths), and whether or not we use PBC, velocities or
346 // calculate the virial contribution.
347 INSTANTIATE_TEST_CASE_P(WithParameters, SettleTest,
348 ::testing::Combine(::testing::Values(1, 2, 4, 5, 7, 10, 12, 15, 17),