41552e2f5844a967610362616c2e619592bea3b0
[alexxy/gromacs.git] / src / gromacs / mdlib / tests / settle.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2014,2015,2016,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.
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 Tests for SETTLE constraints
37  *
38  * The test runs on several small systems, containing 1 to 17 water molecules,
39  * with and without periodic boundary conditions, with and without velocity
40  * and virial updates. The CPU and GPU versions are tested, if the code was
41  * compiled with CUDA support and there is a CUDA-capable GPU in the system.
42  *
43  * The tests check:
44  * 1. If the final distances between constrained atoms are within tolerance
45  *    from the target distance.
46  * 2. If the velocities were updated when needed.
47  * 3. If the virial was computed.
48  *
49  * The test also compares the results from the CPU and GPU versions of the
50  * algorithm: final coordinates, velocities and virial should be within
51  * tolerance to one another.
52  *
53  * \todo This also tests that if the calling code requires velocities
54  *       and virial updates, that those outputs do change, but does not
55  *       test that those changes are correct.
56  *
57  * \todo Only no-PBC and cubic-PBC are tested here, but the correct
58  *       function of the SIMD version of set_pbx_auic in all cases
59  *       should be tested elsewhere.
60  *
61  * \todo The CPU and GPU versions are tested against each other. This
62  *       should be changed to a proper test against pre-computed
63  *       reference values. Also, these test will dry-run on a CUDA
64  *       build if no CUDA-capable GPU is available.
65  *
66  * \author Mark Abraham  <mark.j.abraham@gmail.com>
67  * \author Artem Zhmurov <zhmurov@gmail.com>
68  *
69  * \ingroup module_mdlib
70  */
71 #include "gmxpre.h"
72
73 #include "gromacs/mdlib/settle.h"
74
75 #include "config.h"
76
77 #include <algorithm>
78 #include <unordered_map>
79 #include <vector>
80
81 #include <gtest/gtest.h>
82
83 #include "gromacs/gpu_utils/gpu_testutils.h"
84 #include "gromacs/math/vec.h"
85 #include "gromacs/math/vectypes.h"
86 #include "gromacs/mdtypes/mdatom.h"
87 #include "gromacs/pbcutil/pbc.h"
88 #include "gromacs/topology/idef.h"
89 #include "gromacs/topology/ifunc.h"
90 #include "gromacs/topology/topology.h"
91 #include "gromacs/utility/stringutil.h"
92 #include "gromacs/utility/unique_cptr.h"
93
94 #include "gromacs/mdlib/tests/watersystem.h"
95 #include "testutils/refdata.h"
96 #include "testutils/testasserts.h"
97
98 #include "settletestdata.h"
99 #include "settletestrunners.h"
100
101 namespace gmx
102 {
103 namespace test
104 {
105 namespace
106 {
107
108 /*! \brief Parameters that will vary from test to test.
109  */
110 struct SettleTestParameters
111 {
112     //! Number of water molecules (SETTLEs) [1, 2, 4, 5, 7, 10, 12, 15, 17]
113     int numSettles;
114     //! If the velocities should be updated while constraining [true/false]
115     bool updateVelocities;
116     //! If the virial should be computed [true/false]
117     bool calcVirial;
118     //! Periodic boundary conditions [PBCXYZ/PBCNone]
119     std::string pbcName;
120 };
121
122 /*! \brief Sets of parameters on which to run the tests.
123  */
124 const SettleTestParameters parametersSets[] = {
125     { 1, false, false, "PBCXYZ" },   // 1 water molecule
126     { 2, false, false, "PBCXYZ" },   // 2 water molecules
127     { 4, false, false, "PBCXYZ" },   // 4 water molecules
128     { 5, false, false, "PBCXYZ" },   // 5 water molecules
129     { 6, false, false, "PBCXYZ" },   // 6 water molecules
130     { 10, false, false, "PBCXYZ" },  // 10 water molecules
131     { 12, false, false, "PBCXYZ" },  // 12 water molecules
132     { 15, false, false, "PBCXYZ" },  // 15 water molecules
133     { 17, true, false, "PBCXYZ" },   // Update velocities
134     { 17, false, true, "PBCXYZ" },   // Compute virial
135     { 17, false, false, "PBCNone" }, // No periodic boundary
136     { 17, true, true, "PBCNone" },   // Update velocities, compute virial, without PBC
137     { 17, true, true, "PBCXYZ" }
138 }; // Update velocities, compute virial, with PBC
139
140 /*! \brief Test fixture for testing SETTLE.
141  */
142 class SettleTest : public ::testing::TestWithParam<SettleTestParameters>
143 {
144 public:
145     //! PBC setups
146     std::unordered_map<std::string, t_pbc> pbcs_;
147     //! Runners (CPU and GPU versions of SETTLE)
148     std::unordered_map<std::string,
149                        void (*)(SettleTestData* testData, const t_pbc pbc, const bool updateVelocities, const bool calcVirial, const std::string& testDescription)>
150             runners_;
151     //! Reference data
152     TestReferenceData refData_;
153     //! Checker for reference data
154     TestReferenceChecker checker_;
155
156     /*! \brief Test setup function.
157      *
158      * Setting up the PBCs and algorithms. Note, that corresponding string keywords
159      * have to be explicitly specified when parameters are initialied.
160      *
161      */
162     SettleTest() : checker_(refData_.rootChecker())
163     {
164
165         //
166         // PBC initialization
167         //
168         t_pbc pbc;
169
170         // Infinitely small box
171         matrix boxNone = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } };
172         set_pbc(&pbc, PbcType::No, boxNone);
173         pbcs_["PBCNone"] = pbc;
174
175         // Rectangular box
176         matrix boxXyz = { { real(1.86206), 0, 0 }, { 0, real(1.86206), 0 }, { 0, 0, real(1.86206) } };
177         set_pbc(&pbc, PbcType::Xyz, boxXyz);
178         pbcs_["PBCXYZ"] = pbc;
179
180         //
181         // All SETTLE runners should be registered here under appropriate conditions
182         //
183         runners_["SETTLE"] = applySettle;
184
185         // CUDA version will be tested only if:
186         // 1. The code was compiled with CUDA
187         // 2. There is a CUDA-capable GPU in a system
188         // 3. This GPU is detectable
189         // 4. GPU detection was not disabled by GMX_DISABLE_GPU_DETECTION environment variable
190         if (GMX_GPU_CUDA && s_hasCompatibleGpus)
191         {
192             runners_["SETTLE_GPU"] = applySettleGpu;
193         }
194     }
195
196     /*! \brief Check if the final interatomic distances are equal to target set by constraints.
197      *
198      * \param[in]  numSettles        Number of water molecules in the tested system.
199      * \param[in]  tolerance         Tolerance to compare floating point numbers.
200      * \param[in]  testData          An object, containing all the data structures needed by SETTLE.
201      */
202     static void checkConstrainsSatisfied(const int                    numSettles,
203                                          const FloatingPointTolerance tolerance,
204                                          const SettleTestData&        testData)
205     {
206         for (int i = 0; i < numSettles; ++i)
207         {
208             const gmx::RVec& positionO  = testData.xPrime_[i * testData.atomsPerSettle_ + 0];
209             const gmx::RVec& positionH1 = testData.xPrime_[i * testData.atomsPerSettle_ + 1];
210             const gmx::RVec& positionH2 = testData.xPrime_[i * testData.atomsPerSettle_ + 2];
211
212             real dOH = testData.dOH_;
213             real dHH = testData.dHH_;
214
215             EXPECT_REAL_EQ_TOL(dOH * dOH, distance2(positionO, positionH1), tolerance)
216                     << formatString("for water %d. ", i);
217             EXPECT_REAL_EQ_TOL(dOH * dOH, distance2(positionO, positionH2), tolerance)
218                     << formatString("for water %d. ", i);
219             EXPECT_REAL_EQ_TOL(dHH * dHH, distance2(positionH1, positionH2), tolerance)
220                     << formatString("for water %d. ", i);
221         }
222     }
223
224     /*! \brief Check if the virial was updated and symmetric.
225      *
226      * The two tests on virial are:
227      * 1. If it was updated in case calcVirial is true.
228      * 2. If it is symmetrical.
229      *
230      * \param[in]  calcVirial        If the virial is computed.
231      * \param[in]  tolerance         Tolerance to compare floating point numbers.
232      * \param[in]  testData          An object, containing all the data structures needed by SETTLE.
233      */
234     static void checkVirialSymmetric(const bool                   calcVirial,
235                                      const FloatingPointTolerance tolerance,
236                                      const SettleTestData&        testData)
237     {
238         for (int d = 0; d < DIM; ++d)
239         {
240             for (int dd = 0; dd < DIM; ++dd)
241             {
242
243                 EXPECT_TRUE(calcVirial == (0. != testData.virial_[d][dd]))
244                         << formatString("for virial component[%d][%d]. ", d, dd);
245
246                 if (calcVirial)
247                 {
248                     EXPECT_REAL_EQ_TOL(testData.virial_[d][dd], testData.virial_[dd][d], tolerance)
249                             << formatString("Virial is not symmetrical for [%d][%d]. ", d, dd);
250                 }
251             }
252         }
253     }
254
255     /*! \brief Check if the final positions correspond to reference values.
256      *
257      * \param[in]  numSettles        Number of water molecules in the tested system.
258      * \param[in]  testData          An object, containing all the data structures needed by SETTLE.
259      */
260     void checkFinalPositions(const int numSettles, const SettleTestData& testData)
261     {
262         TestReferenceChecker finalCoordinatesRef(
263                 checker_.checkSequenceCompound("FinalCoordinates", numSettles));
264         for (int i = 0; i < numSettles; ++i)
265         {
266             TestReferenceChecker settlerRef(finalCoordinatesRef.checkCompound("Settler", nullptr));
267             TestReferenceChecker atomsRef(
268                     settlerRef.checkSequenceCompound("Atoms", testData.atomsPerSettle_));
269             for (int j = 0; j < testData.atomsPerSettle_; ++j)
270             {
271                 const gmx::RVec&     xPrime = testData.xPrime_[testData.atomsPerSettle_ * i + j];
272                 TestReferenceChecker xPrimeRef(atomsRef.checkCompound("Atom", nullptr));
273                 xPrimeRef.checkReal(xPrime[XX], "XX");
274                 xPrimeRef.checkReal(xPrime[YY], "YY");
275                 xPrimeRef.checkReal(xPrime[ZZ], "ZZ");
276             }
277         }
278     }
279
280     /*! \brief Check if the final velocities correspond to reference values.
281      *
282      * \param[in]  numSettles        Number of water molecules in the tested system.
283      * \param[in]  testData          An object, containing all the data structures needed by SETTLE.
284      */
285     void checkFinalVelocities(const int numSettles, const SettleTestData& testData)
286     {
287         TestReferenceChecker finalCoordinatesRef(
288                 checker_.checkSequenceCompound("FinalVelocities", numSettles));
289         for (int i = 0; i < numSettles; ++i)
290         {
291             TestReferenceChecker settlerRef(finalCoordinatesRef.checkCompound("Settler", nullptr));
292             TestReferenceChecker atomsRef(
293                     settlerRef.checkSequenceCompound("Atoms", testData.atomsPerSettle_));
294             for (int j = 0; j < testData.atomsPerSettle_; ++j)
295             {
296                 const gmx::RVec&     v = testData.v_[testData.atomsPerSettle_ * i + j];
297                 TestReferenceChecker vRef(atomsRef.checkCompound("Atom", nullptr));
298                 vRef.checkReal(v[XX], "XX");
299                 vRef.checkReal(v[YY], "YY");
300                 vRef.checkReal(v[ZZ], "ZZ");
301             }
302         }
303     }
304
305     /*! \brief Check if the computed virial correspond to reference values.
306      *
307      * \param[in]  testData          An object, containing all the data structures needed by SETTLE.
308      */
309     void checkVirial(const SettleTestData& testData)
310     {
311         const tensor&        virial = testData.virial_;
312         TestReferenceChecker virialRef(checker_.checkCompound("Virial", nullptr));
313
314         // TODO: Is it worth it to make this in a loop??
315         virialRef.checkReal(virial[XX][XX], "XX");
316         virialRef.checkReal(virial[XX][YY], "XY");
317         virialRef.checkReal(virial[XX][ZZ], "XZ");
318         virialRef.checkReal(virial[YY][XX], "YX");
319         virialRef.checkReal(virial[YY][YY], "YY");
320         virialRef.checkReal(virial[YY][ZZ], "YZ");
321         virialRef.checkReal(virial[ZZ][XX], "ZX");
322         virialRef.checkReal(virial[ZZ][YY], "ZY");
323         virialRef.checkReal(virial[ZZ][ZZ], "ZZ");
324     }
325
326     //! Store whether any compatible GPUs exist.
327     static bool s_hasCompatibleGpus;
328     //! Before any test is run, work out whether any compatible GPUs exist.
329     static void SetUpTestCase() { s_hasCompatibleGpus = canComputeOnGpu(); }
330 };
331
332 bool SettleTest::s_hasCompatibleGpus = false;
333
334 TEST_P(SettleTest, SatisfiesConstraints)
335 {
336     // Cycle through all available runners
337     for (const auto& runner : runners_)
338     {
339         std::string runnerName = runner.first;
340
341         // Make some symbolic names for the parameter combination.
342         SettleTestParameters params = GetParam();
343
344         int         numSettles       = params.numSettles;
345         bool        updateVelocities = params.updateVelocities;
346         bool        calcVirial       = params.calcVirial;
347         std::string pbcName          = params.pbcName;
348
349
350         // Make a string that describes which parameter combination is
351         // being tested, to help make failing tests comprehensible.
352         std::string testDescription = formatString(
353                 "Testing %s with %d SETTLEs, %s, %svelocities and %scalculating the virial.",
354                 runnerName.c_str(), numSettles, pbcName.c_str(),
355                 updateVelocities ? "with " : "without ", calcVirial ? "" : "not ");
356
357         SCOPED_TRACE(testDescription);
358
359         auto testData = std::make_unique<SettleTestData>(numSettles);
360
361         ASSERT_LE(numSettles, testData->xPrime_.size() / testData->atomsPerSettle_)
362                 << "cannot test that many SETTLEs. " << testDescription;
363
364         t_pbc pbc = pbcs_.at(pbcName);
365
366         // Apply SETTLE
367         runner.second(testData.get(), pbc, updateVelocities, calcVirial, testDescription);
368
369         // The necessary tolerances for the test to pass were determined
370         // empirically. This isn't nice, but the required behavior that
371         // SETTLE produces constrained coordinates consistent with
372         // sensible sampling needs to be tested at a much higher level.
373         // TODO: Re-evaluate the tolerances.
374         real                   dOH       = testData->dOH_;
375         FloatingPointTolerance tolerance = relativeToleranceAsPrecisionDependentUlp(dOH * dOH, 80, 380);
376         FloatingPointTolerance toleranceVirial = absoluteTolerance(0.000001);
377
378         FloatingPointTolerance tolerancePositions  = absoluteTolerance(0.000001);
379         FloatingPointTolerance toleranceVelocities = absoluteTolerance(0.0001);
380
381         checkConstrainsSatisfied(numSettles, tolerance, *testData);
382         checkVirialSymmetric(calcVirial, toleranceVirial, *testData);
383
384         checker_.setDefaultTolerance(tolerancePositions);
385         checkFinalPositions(numSettles, *testData);
386
387         if (updateVelocities)
388         {
389             checker_.setDefaultTolerance(toleranceVelocities);
390             checkFinalVelocities(numSettles, *testData);
391         }
392
393         if (calcVirial)
394         {
395             checker_.setDefaultTolerance(toleranceVirial);
396             checkVirial(*testData);
397         }
398     }
399 }
400
401 // Run test on pre-determined set of combinations for test parameters, which include the numbers of SETTLEs (water
402 // molecules), whether or not velocities are updated and virial contribution is computed, was the PBC enabled.
403 // The test will cycle through all available runners, including CPU and, if applicable, GPU implementations of SETTLE.
404 INSTANTIATE_TEST_CASE_P(WithParameters, SettleTest, ::testing::ValuesIn(parametersSets));
405
406 } // namespace
407 } // namespace test
408 } // namespace gmx