2 * This file is part of the GROMACS molecular simulation package.
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.
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 * \brief Utility functions for tests to verify that a simulator that only does some actions
38 * periodically produces the expected results.
40 * \author Mark Abraham <mark.j.abraham@gmail.com>
41 * \ingroup module_mdrun_integration_tests
47 #include "periodicactions.h"
54 /*! \brief Mdp parameters that determine the manner of simulation
56 using PropagationParameters = MdpFieldValues;
58 /*! \brief Mdp parameters that should only affect the observations,
59 * not the simulation propagation. */
60 using PeriodicOutputParameters = MdpFieldValues;
62 //! Function type to produce sets of .mdp parameters for testing periodic output
63 using OutputParameterGeneratorFunction = std::vector<PeriodicOutputParameters> (*)();
65 void PeriodicActionsTest::doMdrun(const PeriodicOutputParameters& output)
67 auto propagation = std::get<0>(GetParam());
69 formatString("Doing %s simulation with %s integrator, %s tcoupling and %s pcoupling\n",
70 propagation["simulationName"].c_str(),
71 propagation["integrator"].c_str(),
72 propagation["tcoupl"].c_str(),
73 propagation["pcoupl"].c_str()));
74 auto mdpFieldValues = prepareMdpFieldValues(propagation["simulationName"],
75 propagation["integrator"],
76 propagation["tcoupl"],
77 propagation["pcoupl"]);
79 // This lambda writes all mdp options in `source` into `target`, overwriting options already
80 // present in `target`. It also filters out non-mdp option entries in the source maps
81 auto overWriteMdpMapValues = [](const MdpFieldValues& source, MdpFieldValues& target) {
82 for (auto const& [key, value] : source)
84 if (key == "simulationName" || key == "maxGromppWarningsTolerated" || key == "description")
86 // Remove non-mdp entries used in propagation and output
92 // Add options in propagation and output to the mdp options
93 overWriteMdpMapValues(propagation, mdpFieldValues);
94 overWriteMdpMapValues(output, mdpFieldValues);
96 // prepare the tpr file
99 caller.append("grompp");
100 caller.addOption("-maxwarn", propagation["maxGromppWarningsTolerated"]);
101 runner_.useTopGroAndNdxFromDatabase(propagation["simulationName"]);
102 runner_.useStringAsMdpFile(prepareMdpFileContents(mdpFieldValues));
103 EXPECT_EQ(0, runner_.callGrompp(caller));
107 CommandLine mdrunCaller;
108 mdrunCaller.append("mdrun");
109 ASSERT_EQ(0, runner_.callMdrun(mdrunCaller));
113 void PeriodicActionsTest::prepareReferenceData()
115 SCOPED_TRACE("Preparing reference data");
117 // Configure the SimulationRunner to write output to files we can
118 // use in comparing each test run.
119 std::swap(runner_.edrFileName_, referenceFileNames_.edrFileName_);
121 // Run the reference simulation with everything output every step
122 PeriodicOutputParameters outputEveryStep = {
123 { "nstenergy", "1" },
126 { "description", "output everything every step" },
128 doMdrun(outputEveryStep);
130 // Restore the standard filenames we'll use for the runs whose behavior we are testing.
131 std::swap(runner_.edrFileName_, referenceFileNames_.edrFileName_);
134 /*! \brief Compare the next frame returned by \c testFrameReader with the
135 * matching frame from \c referenceTestReader using \c comparator.
137 * \returns True when a successful comparison was made
139 template<typename Reader, typename Comparator>
140 bool compareFrames(Reader referenceFrameReader, Reader testFrameReader, const Comparator& comparator)
142 if (!testFrameReader->readNextFrame())
147 auto testFrame = testFrameReader->frame();
148 std::string frameName = testFrame.frameName();
149 SCOPED_TRACE("Found frame from test file named " + frameName);
150 bool foundMatch = false;
153 if (referenceFrameReader->readNextFrame())
155 auto referenceFrame = referenceFrameReader->frame();
156 // Can the reference and test frames be compared, because they have the same name?
157 if (frameName == referenceFrame.frameName())
159 SCOPED_TRACE("Found frame from reference file named " + frameName);
160 comparator(referenceFrame, testFrame);
166 ADD_FAILURE() << "Ran out of reference frames to compare";
173 TEST_P(PeriodicActionsTest, PeriodicActionsAgreeWithReference)
175 auto propagation = std::get<0>(GetParam());
176 SCOPED_TRACE(formatString("Comparing two simulations of '%s' with integrator '%s'",
177 propagation["simulationName"].c_str(),
178 propagation["integrator"].c_str()));
180 prepareReferenceData();
182 auto outputParametersGenerator = std::get<1>(GetParam());
183 for (const PeriodicOutputParameters& output : outputParametersGenerator())
185 SCOPED_TRACE("Comparing to observe " + output.at("description"));
187 // Run the test simulation
190 // Prepare readers for the reference and test output files
191 auto referenceEnergyFrameReader =
192 openEnergyFileToReadTerms(referenceFileNames_.edrFileName_, namesOfEnergiesToMatch_);
193 auto testEnergyFrameReader =
194 openEnergyFileToReadTerms(runner_.edrFileName_, namesOfEnergiesToMatch_);
196 const bool shouldCompareEnergies = fromString<int>(output.at("nstenergy")) > 0;
197 bool shouldContinueComparing = shouldCompareEnergies;
198 while (shouldContinueComparing)
200 if (shouldCompareEnergies)
202 SCOPED_TRACE("Comparing energy frames from reference '" + referenceFileNames_.edrFileName_
203 + "' and test '" + runner_.edrFileName_ + "'");
204 shouldContinueComparing = shouldContinueComparing
205 && compareFrames(referenceEnergyFrameReader.get(),
206 testEnergyFrameReader.get(),
213 /*! \brief Some common choices of periodic output mdp parameters to
214 * simplify defining values for the combinations under test */
215 static PeriodicOutputParameters g_basicPeriodicOutputParameters = {
216 { "nstenergy", "0" }, { "nstlog", "0" }, { "nstxout", "0" },
217 { "nstvout", "0" }, { "nstfout", "0" }, { "nstxout-compressed", "0" },
218 { "nstdhdl", "0" }, { "description", "unknown" }
221 // \todo Test nstlog, nstdhdl, nstxout-compressed
222 std::vector<PeriodicOutputParameters> outputParameters()
224 std::vector<PeriodicOutputParameters> parameterSets;
226 parameterSets.push_back(g_basicPeriodicOutputParameters);
227 parameterSets.back()["nstenergy"] = "1";
228 parameterSets.back()["description"] = "energies every step works";
230 parameterSets.push_back(g_basicPeriodicOutputParameters);
231 parameterSets.back()["nstenergy"] = "4";
232 parameterSets.back()["description"] = "energies every fourth step works";
234 parameterSets.push_back(g_basicPeriodicOutputParameters);
235 parameterSets.back()["nstxout"] = "4";
236 parameterSets.back()["description"] = "coordinates every fourth step works";
238 parameterSets.push_back(g_basicPeriodicOutputParameters);
239 parameterSets.back()["nstvout"] = "4";
240 parameterSets.back()["description"] = "velocities every fourth step works";
242 parameterSets.push_back(g_basicPeriodicOutputParameters);
243 parameterSets.back()["nstfout"] = "4";
244 parameterSets.back()["description"] = "forces every fourth step works";
246 return parameterSets;
249 std::vector<PropagationParameters> simplePropagationParameters()
252 { { "simulationName", "argon12" },
253 { "integrator", "md" },
254 { "comm-mode", "linear" },
257 { "nsttcouple", "0" },
259 { "nstpcouple", "0" },
260 { "maxGromppWarningsTolerated", "0" } },
264 std::vector<PropagationParameters> propagationParametersWithCoupling()
266 std::string nsttcouple = "2";
267 std::string nstpcouple = "3";
268 std::string comm_mode = "linear";
269 std::string nstcomm = "5";
271 std::vector<PropagationParameters> parameterSets;
272 std::vector<std::string> simulations = { "argon12" };
273 for (const std::string& simulationName : simulations)
275 std::vector<std::string> integrators{ "md", "sd", "md-vv" };
276 for (const std::string& integrator : integrators)
278 std::vector<std::string> tcouplValues{ "no", "v-rescale", "Nose-Hoover" };
279 for (const std::string& tcoupl : tcouplValues)
281 // SD doesn't support temperature-coupling algorithms,
282 if (integrator == "sd" && tcoupl != "no")
286 for (std::string pcoupl : { "no", "Berendsen", "Parrinello-Rahman", "C-rescale" })
288 // VV supports few algorithm combinations
289 if (integrator == "md-vv")
291 // P-R with VV is called MTTK
292 if (pcoupl == "Parrinello-Rahman")
296 if ((tcoupl == "Nose-Hoover" && pcoupl == "Berendsen")
297 || (tcoupl != "Nose-Hoover" && pcoupl == "MTTK"))
302 if (pcoupl == "MTTK" && simulationName != "argon12")
304 // MTTK does not support constraints
308 int maxGromppWarningsTolerated = 0;
309 if (pcoupl == "Berendsen")
311 ++maxGromppWarningsTolerated;
313 parameterSets.emplace_back(PropagationParameters{
314 { "simulationName", simulationName },
315 { "integrator", integrator },
316 { "comm-mode", comm_mode },
317 { "nstcomm", nstcomm },
318 { "tcoupl", tcoupl },
319 { "nsttcouple", nsttcouple },
320 { "pcoupl", pcoupl },
321 { "nstpcouple", nstpcouple },
322 { "maxGromppWarningsTolerated", toString(maxGromppWarningsTolerated) } });
327 return parameterSets;
330 std::vector<PropagationParameters> propagationParametersWithConstraints()
332 std::string nsttcouple = "2";
333 std::string nstpcouple = "3";
334 std::string comm_mode = "linear";
335 std::string nstcomm = "5";
337 std::vector<PropagationParameters> parameterSets;
338 std::vector<std::string> simulations = { "tip3p5" };
339 for (const std::string& simulationName : simulations)
341 std::vector<std::string> integrators{ "md", "sd", "md-vv" };
342 for (const std::string& integrator : integrators)
344 std::vector<std::string> tcouplValues{ "no", "v-rescale" };
345 for (const std::string& tcoupl : tcouplValues)
347 // SD doesn't support temperature-coupling algorithms,
348 if (integrator == "sd" && tcoupl != "no")
352 for (std::string pcoupl : { "no", "Parrinello-Rahman", "C-rescale" })
354 // VV supports few algorithm combinations
355 if (integrator == "md-vv")
357 // P-R with VV is called MTTK
358 if (pcoupl == "Parrinello-Rahman")
362 if ((tcoupl == "Nose-Hoover" && pcoupl == "Berendsen")
363 || (tcoupl != "Nose-Hoover" && pcoupl == "MTTK"))
368 if (pcoupl == "MTTK" && simulationName != "argon12")
370 // MTTK does not support constraints
374 int maxGromppWarningsTolerated = 0;
375 parameterSets.emplace_back(PropagationParameters{
376 { "simulationName", simulationName },
377 { "integrator", integrator },
378 { "comm-mode", comm_mode },
379 { "nstcomm", nstcomm },
380 { "tcoupl", tcoupl },
381 { "nsttcouple", nsttcouple },
382 { "pcoupl", pcoupl },
383 { "nstpcouple", nstpcouple },
384 { "maxGromppWarningsTolerated", toString(maxGromppWarningsTolerated) } });
389 return parameterSets;