Update bundled GoogleTest to current HEAD
[alexxy/gromacs.git] / src / programs / mdrun / tests / simulator.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 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.
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
36 /*! \internal \file
37  * \brief
38  * Tests to compare two simulators which are expected to be identical
39  *
40  * \author Mark Abraham <mark.j.abraham@gmail.com>
41  * \author Pascal Merz <pascal.merz@me.com>
42  * \ingroup module_mdrun_integration_tests
43  */
44 #include "gmxpre.h"
45
46 #include "config.h"
47
48 #include "gromacs/topology/ifunc.h"
49 #include "gromacs/utility/stringutil.h"
50
51 #include "testutils/mpitest.h"
52 #include "testutils/setenv.h"
53 #include "testutils/simulationdatabase.h"
54
55 #include "moduletest.h"
56 #include "simulatorcomparison.h"
57
58 namespace gmx
59 {
60 namespace test
61 {
62 namespace
63 {
64
65 /*! \brief Test fixture base for two equivalent simulators
66  *
67  * This test ensures that two simulator code paths (called via different mdp
68  * options and/or environment variables) yield identical coordinate, velocity,
69  * box, force and energy trajectories, up to some (arbitrary) precision.
70  *
71  * These tests are useful to check that re-implementations of existing simulators
72  * are correct, and that different code paths expected to yield identical results
73  * are equivalent.
74  */
75 using SimulatorComparisonTestParams =
76         std::tuple<std::tuple<std::string, std::string, std::string, std::string, MdpParameterDatabase>, std::string>;
77 class SimulatorComparisonTest :
78     public MdrunTestFixture,
79     public ::testing::WithParamInterface<SimulatorComparisonTestParams>
80 {
81 };
82
83 TEST_P(SimulatorComparisonTest, WithinTolerances)
84 {
85     const auto& params              = GetParam();
86     const auto& mdpParams           = std::get<0>(params);
87     const auto& simulationName      = std::get<0>(mdpParams);
88     const auto& integrator          = std::get<1>(mdpParams);
89     const auto& tcoupling           = std::get<2>(mdpParams);
90     const auto& pcoupling           = std::get<3>(mdpParams);
91     const auto& additionalParameter = std::get<4>(mdpParams);
92     const auto& environmentVariable = std::get<1>(params);
93
94     int maxNumWarnings = 0;
95
96     const bool isAndersen     = (tcoupling == "andersen-massive" || tcoupling == "andersen");
97     const bool hasConstraints = (simulationName != "argon12");
98
99     // TODO At some point we should also test PME-only ranks.
100     const int numRanksAvailable = getNumberOfTestMpiRanks();
101     if (!isNumberOfPpRanksSupported(simulationName, numRanksAvailable))
102     {
103         fprintf(stdout,
104                 "Test system '%s' cannot run with %d ranks.\n"
105                 "The supported numbers are: %s\n",
106                 simulationName.c_str(),
107                 numRanksAvailable,
108                 reportNumbersOfPpRanksSupported(simulationName).c_str());
109         return;
110     }
111
112     if (integrator == "md-vv" && pcoupling == "Parrinello-Rahman")
113     {
114         // do_md calls this MTTK, requires Nose-Hoover, and
115         // does not work with constraints or anisotropically
116         return;
117     }
118
119     if (isAndersen && pcoupling == "berendsen")
120     {
121         // "Using Berendsen pressure coupling invalidates the true ensemble for the thermostat"
122         maxNumWarnings++;
123     }
124     if (tcoupling == "andersen" && hasConstraints)
125     {
126         // Constraints are not allowed with non-massive Andersen
127         return;
128     }
129
130     if (tcoupling == "nose-hoover" && pcoupling == "berendsen")
131     {
132         if (integrator == "md-vv")
133         {
134             // Combination not allowed by legacy do_md.
135             return;
136         }
137         else
138         {
139             // "Using Berendsen pressure coupling invalidates the true ensemble for the thermostat"
140             maxNumWarnings++;
141         }
142     }
143
144     const bool systemHasConstraints = (simulationName != "argon12");
145     if (pcoupling == "mttk" && (tcoupling != "nose-hoover" || systemHasConstraints))
146     {
147         // Legacy mttk works only with Nose-Hoover and without constraints
148         return;
149     }
150
151     const std::string envVariableModSimOn  = "GMX_USE_MODULAR_SIMULATOR";
152     const std::string envVariableModSimOff = "GMX_DISABLE_MODULAR_SIMULATOR";
153
154     GMX_RELEASE_ASSERT(
155             environmentVariable == envVariableModSimOn || environmentVariable == envVariableModSimOff,
156             ("Expected tested environment variable to be " + envVariableModSimOn + " or " + envVariableModSimOff)
157                     .c_str());
158
159     const auto hasConservedField = !(tcoupling == "no" && pcoupling == "no")
160                                    && !(tcoupling == "andersen-massive" || tcoupling == "andersen");
161
162     SCOPED_TRACE(formatString(
163             "Comparing two simulations of '%s' "
164             "with integrator '%s', '%s' temperature coupling, and '%s' pressure coupling "
165             "switching environment variable '%s'",
166             simulationName.c_str(),
167             integrator.c_str(),
168             tcoupling.c_str(),
169             pcoupling.c_str(),
170             environmentVariable.c_str()));
171
172     auto mdpFieldValues = prepareMdpFieldValues(
173             simulationName.c_str(), integrator.c_str(), tcoupling.c_str(), pcoupling.c_str(), additionalParameter);
174     if (tcoupling == "andersen")
175     {
176         // Fixes error "nstcomm must be 1, not 4 for Andersen, as velocities of
177         //              atoms in coupled groups are randomized every time step"
178         mdpFieldValues["nstcomm"]       = "1";
179         mdpFieldValues["nstcalcenergy"] = "1";
180     }
181     if (pcoupling == "mttk")
182     {
183         // Standard parameters use compressibility of 5e-5
184         // Increasing compressibility makes this test significantly more sensitive
185         mdpFieldValues["compressibility"] = "1";
186     }
187
188     EnergyTermsToCompare energyTermsToCompare{ {
189             { interaction_function[F_EPOT].longname, relativeToleranceAsPrecisionDependentUlp(60.0, 200, 160) },
190             { interaction_function[F_EKIN].longname, relativeToleranceAsPrecisionDependentUlp(60.0, 200, 160) },
191             { interaction_function[F_PRES].longname,
192               relativeToleranceAsPrecisionDependentFloatingPoint(10.0, 0.01, 0.001) },
193     } };
194     if (hasConservedField)
195     {
196         energyTermsToCompare.emplace(interaction_function[F_ECONSERVED].longname,
197                                      relativeToleranceAsPrecisionDependentUlp(50.0, 100, 80));
198     }
199
200     if (simulationName == "argon12")
201     {
202         // Without constraints, we can be more strict
203         energyTermsToCompare = { {
204                 { interaction_function[F_EPOT].longname,
205                   relativeToleranceAsPrecisionDependentUlp(10.0, 24, 80) },
206                 { interaction_function[F_EKIN].longname,
207                   relativeToleranceAsPrecisionDependentUlp(10.0, 24, 80) },
208                 { interaction_function[F_PRES].longname,
209                   relativeToleranceAsPrecisionDependentFloatingPoint(10.0, 0.001, 0.0001) },
210         } };
211         if (hasConservedField)
212         {
213             energyTermsToCompare.emplace(interaction_function[F_ECONSERVED].longname,
214                                          relativeToleranceAsPrecisionDependentUlp(10.0, 24, 80));
215         }
216     }
217
218     // Specify how trajectory frame matching must work.
219     const TrajectoryFrameMatchSettings trajectoryMatchSettings{ true,
220                                                                 true,
221                                                                 true,
222                                                                 ComparisonConditions::MustCompare,
223                                                                 ComparisonConditions::MustCompare,
224                                                                 ComparisonConditions::MustCompare,
225                                                                 MaxNumFrames::compareAllFrames() };
226     TrajectoryTolerances trajectoryTolerances = TrajectoryComparison::s_defaultTrajectoryTolerances;
227     if (simulationName != "argon12")
228     {
229         trajectoryTolerances.velocities = trajectoryTolerances.coordinates;
230     }
231
232     // Build the functor that will compare reference and test
233     // trajectory frames in the chosen way.
234     const TrajectoryComparison trajectoryComparison{ trajectoryMatchSettings, trajectoryTolerances };
235
236     // Set file names
237     const auto simulator1TrajectoryFileName = fileManager_.getTemporaryFilePath("sim1.trr");
238     const auto simulator1EdrFileName        = fileManager_.getTemporaryFilePath("sim1.edr");
239     const auto simulator2TrajectoryFileName = fileManager_.getTemporaryFilePath("sim2.trr");
240     const auto simulator2EdrFileName        = fileManager_.getTemporaryFilePath("sim2.edr");
241
242     // Run grompp
243     runner_.tprFileName_ = fileManager_.getTemporaryFilePath("sim.tpr");
244     runner_.useTopGroAndNdxFromDatabase(simulationName);
245     runner_.useStringAsMdpFile(prepareMdpFileContents(mdpFieldValues));
246     runGrompp(&runner_, { SimulationOptionTuple("-maxwarn", std::to_string(maxNumWarnings)) });
247
248     // Backup current state of both environment variables and unset them
249     const char* environmentVariableBackupOn  = getenv(envVariableModSimOn.c_str());
250     const char* environmentVariableBackupOff = getenv(envVariableModSimOff.c_str());
251     gmxUnsetenv(envVariableModSimOn.c_str());
252     gmxUnsetenv(envVariableModSimOff.c_str());
253
254     // Do first mdrun
255     runner_.fullPrecisionTrajectoryFileName_ = simulator1TrajectoryFileName;
256     runner_.edrFileName_                     = simulator1EdrFileName;
257     runMdrun(&runner_);
258
259     // Set tested environment variable
260     const int overWriteEnvironmentVariable = 1;
261     gmxSetenv(environmentVariable.c_str(), "ON", overWriteEnvironmentVariable);
262
263     // Do second mdrun
264     runner_.fullPrecisionTrajectoryFileName_ = simulator2TrajectoryFileName;
265     runner_.edrFileName_                     = simulator2EdrFileName;
266     runMdrun(&runner_);
267
268     // Unset tested environment variable
269     gmxUnsetenv(environmentVariable.c_str());
270     // Reset both environment variables to leave further tests undisturbed
271     if (environmentVariableBackupOn != nullptr)
272     {
273         gmxSetenv(environmentVariable.c_str(), environmentVariableBackupOn, overWriteEnvironmentVariable);
274     }
275     if (environmentVariableBackupOff != nullptr)
276     {
277         gmxSetenv(environmentVariable.c_str(), environmentVariableBackupOff, overWriteEnvironmentVariable);
278     }
279
280     // Compare simulation results
281     compareEnergies(simulator1EdrFileName, simulator2EdrFileName, energyTermsToCompare);
282     compareTrajectories(simulator1TrajectoryFileName, simulator2TrajectoryFileName, trajectoryComparison);
283 }
284
285 // TODO: The time for OpenCL kernel compilation means these tests time
286 //       out. Once that compilation is cached for the whole process, these
287 //       tests can run in such configurations.
288 // These tests are very sensitive, so we only run them in double precision.
289 // As we change call ordering, they might actually become too strict to be useful.
290 #if !GMX_GPU_OPENCL && GMX_DOUBLE
291 INSTANTIATE_TEST_SUITE_P(
292         SimulatorsAreEquivalentDefaultModular,
293         SimulatorComparisonTest,
294         ::testing::Combine(
295                 ::testing::Combine(::testing::Values("argon12", "tip3p5"),
296                                    ::testing::Values("md-vv"),
297                                    ::testing::Values("no",
298                                                      "v-rescale",
299                                                      "berendsen",
300                                                      "nose-hoover",
301                                                      "andersen-massive",
302                                                      "andersen"),
303                                    ::testing::Values("mttk", "no", "berendsen", "c-rescale", "mttk"),
304                                    ::testing::Values(MdpParameterDatabase::Default)),
305                 ::testing::Values("GMX_DISABLE_MODULAR_SIMULATOR")));
306 INSTANTIATE_TEST_SUITE_P(
307         SimulatorsAreEquivalentDefaultLegacy,
308         SimulatorComparisonTest,
309         ::testing::Combine(
310                 ::testing::Combine(
311                         ::testing::Values("argon12", "tip3p5"),
312                         ::testing::Values("md"),
313                         ::testing::Values("no", "v-rescale", "berendsen", "nose-hoover"),
314                         ::testing::Values("no", "Parrinello-Rahman", "berendsen", "c-rescale"),
315                         ::testing::Values(MdpParameterDatabase::Default)),
316                 ::testing::Values("GMX_USE_MODULAR_SIMULATOR")));
317 #else
318 INSTANTIATE_TEST_SUITE_P(
319         DISABLED_SimulatorsAreEquivalentDefaultModular,
320         SimulatorComparisonTest,
321         ::testing::Combine(
322                 ::testing::Combine(::testing::Values("argon12", "tip3p5"),
323                                    ::testing::Values("md-vv"),
324                                    ::testing::Values("no",
325                                                      "v-rescale",
326                                                      "berendsen",
327                                                      "andersen-massive",
328                                                      "andersen",
329                                                      "nose-hoover"),
330                                    ::testing::Values("no", "berendsen", "c-rescale", "mttk"),
331                                    ::testing::Values(MdpParameterDatabase::Default)),
332                 ::testing::Values("GMX_DISABLE_MODULAR_SIMULATOR")));
333 INSTANTIATE_TEST_SUITE_P(
334         DISABLED_SimulatorsAreEquivalentDefaultLegacy,
335         SimulatorComparisonTest,
336         ::testing::Combine(
337                 ::testing::Combine(
338                         ::testing::Values("argon12", "tip3p5"),
339                         ::testing::Values("md"),
340                         ::testing::Values("no", "v-rescale", "berendsen", "nose-hoover"),
341                         ::testing::Values("no", "Parrinello-Rahman", "berendsen", "c-rescale"),
342                         ::testing::Values(MdpParameterDatabase::Default)),
343                 ::testing::Values("GMX_USE_MODULAR_SIMULATOR")));
344 #endif
345
346 } // namespace
347 } // namespace test
348 } // namespace gmx