Add -maxtau option to gmx msd
[alexxy/gromacs.git] / src / gromacs / trajectoryanalysis / tests / msd.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 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 /*! \internal \file
36  * \brief
37  * Tests for functionality of the "msd" trajectory analysis module.
38  *
39  * \author Kevin Boyd <kevin44boyd@gmail.com>
40  * \ingroup module_trajectoryanalysis
41  */
42 #include "gmxpre.h"
43
44 #include "gromacs/trajectoryanalysis/modules/msd.h"
45
46 #include <gtest/gtest.h>
47
48 #include "gromacs/gmxpreprocess/grompp.h"
49 #include "gromacs/utility/path.h"
50 #include "gromacs/utility/stringutil.h"
51 #include "gromacs/utility/textstream.h"
52 #include "gromacs/utility/textwriter.h"
53
54 #include "testutils/cmdlinetest.h"
55 #include "testutils/refdata.h"
56 #include "testutils/testasserts.h"
57 #include "testutils/testfilemanager.h"
58 #include "testutils/textblockmatchers.h"
59
60 #include "moduletest.h"
61
62 namespace gmx::test
63 {
64
65 namespace
66 {
67
68 using gmx::test::CommandLine;
69
70 /*! \brief Returns whether or not we care about a header line in an xvg file, for matching purposes.
71  *
72  * \todo This is mostly taken from xvgtest.cpp. We could probably create some modular checker
73  * functionality where each line is compared against a set of subcheckers automatically. Then we
74  * could build matchers like this out of the modular components.
75  */
76 bool isRelevantXvgHeader(const std::string& line)
77 {
78     return startsWith(line, "@")
79            && (contains(line, " title ") || contains(line, " subtitle ") || contains(line, " label ")
80                || contains(line, "@TYPE ") || contains(line, " legend \""));
81 }
82
83 //! Helper function to check a single xvg value in a sequence.
84 void checkXvgDataPoint(TestReferenceChecker* checker, const std::string& value)
85 {
86     checker->checkRealFromString(value, nullptr);
87 }
88
89 /*! \brief MsdMatcher is effectively an extension of XvgMatcher for gmx msd results.
90  *
91  * In addition to the usual fields XvgMatcher checks, MsdMatcher checks for properly reported
92  * diffusion coefficients.
93  */
94 class MsdMatcher : public ITextBlockMatcher
95 {
96 public:
97     MsdMatcher() = default;
98
99     void checkStream(TextInputStream* stream, TestReferenceChecker* checker) override
100     {
101         checker->setDefaultTolerance(
102                 gmx::test::FloatingPointTolerance(gmx::test::absoluteTolerance(1.0e-5)));
103         TestReferenceChecker dCoefficientChecker(
104                 checker->checkCompound("XvgLegend", "DiffusionCoefficient"));
105         TestReferenceChecker legendChecker(checker->checkCompound("XvgLegend", "Legend"));
106         TestReferenceChecker dataChecker(checker->checkCompound("XvgData", "Data"));
107         std::string          line;
108         int                  rowCount = 0;
109         while (stream->readLine(&line))
110         {
111             // Legend and titles.
112             if (isRelevantXvgHeader(line))
113             {
114                 legendChecker.checkString(stripString(line.substr(1)), nullptr);
115                 continue;
116             }
117             // All other comment lines we don't care about.
118             if (startsWith(line, "#") || startsWith(line, "@"))
119             {
120                 continue;
121             }
122             // Actual data.
123             const std::vector<std::string> columns = splitString(line);
124             const std::string              id      = formatString("Row%d", rowCount);
125             dataChecker.checkSequence(columns.begin(), columns.end(), id.c_str(), &checkXvgDataPoint);
126             rowCount++;
127         }
128     }
129 };
130
131 class MsdMatch : public ITextBlockMatcherSettings
132 {
133 public:
134     [[nodiscard]] TextBlockMatcherPointer createMatcher() const override
135     {
136         return std::make_unique<MsdMatcher>();
137     }
138 };
139
140 class MsdModuleTest : public gmx::test::TrajectoryAnalysisModuleTestFixture<gmx::analysismodules::MsdInfo>
141 {
142 public:
143     MsdModuleTest() { setOutputFile("-o", "msd.xvg", MsdMatch()); }
144     // Creates a TPR for the given starting structure and topology. Builds an mdp in place prior
145     // to calling grompp. sets the -s input to the generated tpr
146     void createTpr(const std::string& structure, const std::string& topology, const std::string& index)
147     {
148         std::string tpr             = fileManager().getTemporaryFilePath(".tpr");
149         std::string mdp             = fileManager().getTemporaryFilePath(".mdp");
150         std::string mdpFileContents = gmx::formatString(
151                 "cutoff-scheme = verlet\n"
152                 "rcoulomb      = 0.85\n"
153                 "rvdw          = 0.85\n"
154                 "rlist         = 0.85\n");
155         gmx::TextWriter::writeFileFromString(mdp, mdpFileContents);
156
157         // Prepare a .tpr file
158         CommandLine caller;
159         const auto* simDB = gmx::test::TestFileManager::getTestSimulationDatabaseDirectory();
160         caller.append("grompp");
161         caller.addOption("-maxwarn", 0);
162         caller.addOption("-f", mdp.c_str());
163         std::string gro = gmx::Path::join(simDB, structure);
164         caller.addOption("-c", gro.c_str());
165         std::string top = gmx::Path::join(simDB, topology);
166         caller.addOption("-p", top.c_str());
167         std::string ndx = gmx::Path::join(simDB, index);
168         caller.addOption("-n", ndx.c_str());
169         caller.addOption("-o", tpr.c_str());
170         ASSERT_EQ(0, gmx_grompp(caller.argc(), caller.argv()));
171
172         // setInputFile() doesn't like the temporary tpr path.
173         CommandLine& cmdline = commandLine();
174         cmdline.addOption("-s", tpr.c_str());
175     }
176
177     // Convenience function to set input trajectory, tpr, and index, if all of the input files
178     // share a common prefix.
179     void setAllInputs(const std::string& prefix)
180     {
181         setInputFile("-f", prefix + ".xtc");
182         setInputFile("-n", prefix + ".ndx");
183         createTpr(prefix + ".gro", prefix + ".top", prefix + ".ndx");
184     }
185 };
186
187 // ----------------------------------------------
188 // These tests check the basic MSD and diffusion coefficient reporting capabilities on toy systems.
189 // Trestart is set to larger than the size of the trajectory so that all frames are only compared
190 // against the first frame and diffusion coefficients can be predicted exactly.
191 // ----------------------------------------------
192
193 // for 3D, (8 + 4 + 0) / 3 should yield 4 cm^2 / s
194 TEST_F(MsdModuleTest, threeDimensionalDiffusion)
195 {
196     setInputFile("-f", "msd_traj.xtc");
197     setInputFile("-s", "msd_coords.gro");
198     setInputFile("-n", "msd.ndx");
199     const char* const cmdline[] = {
200         "-trestart",
201         "200",
202         "-sel",
203         "0",
204     };
205     runTest(CommandLine(cmdline));
206 }
207
208 // for lateral z, (8 + 4) / 2 should yield 6 cm^2 /s
209 TEST_F(MsdModuleTest, twoDimensionalDiffusion)
210 {
211     setInputFile("-f", "msd_traj.xtc");
212     setInputFile("-s", "msd_coords.gro");
213     setInputFile("-n", "msd.ndx");
214     const char* const cmdline[] = { "-trestart", "200", "-lateral", "z", "-sel", "0" };
215     runTest(CommandLine(cmdline));
216 }
217
218 // for type x, should yield 8 cm^2 / s
219 TEST_F(MsdModuleTest, oneDimensionalDiffusion)
220 {
221     setInputFile("-f", "msd_traj.xtc");
222     setInputFile("-s", "msd_coords.gro");
223     setInputFile("-n", "msd.ndx");
224     const char* const cmdline[] = { "-trestart", "200", "-type", "x", "-sel", "0" };
225     runTest(CommandLine(cmdline));
226 }
227
228 TEST_F(MsdModuleTest, oneDimensionalDiffusionWithMaxTau)
229 {
230     setInputFile("-f", "msd_traj.xtc");
231     setInputFile("-s", "msd_coords.gro");
232     setInputFile("-n", "msd.ndx");
233     const char* const cmdline[] = { "-trestart", "200", "-type", "x", "-sel", "0", "-maxtau", "5" };
234     runTest(CommandLine(cmdline));
235 }
236
237
238 // -------------------------------------------------------------------------
239 // These tests operate on a more realistic trajectory, with a solvated protein,
240 // with 20 frames at a 2 ps dt. Note that this box is also non-square, so we're validating
241 // non-trivial pbc removal.
242
243 // Group 1 = protein, 2 = all waters, 3 = a subset of 6 water molecules
244 // ------------------------------------------------------------------------
245 TEST_F(MsdModuleTest, multipleGroupsWork)
246 {
247     setAllInputs("alanine_vsite_solvated");
248     // Restart every frame, select protein and water separately. Note that the reported diffusion
249     // coefficient for protein is not well-sampled and doesn't correspond to anything physical.
250     const char* const cmdline[] = { "-trestart", "2", "-sel", "1;2" };
251     runTest(CommandLine(cmdline));
252 }
253
254 TEST_F(MsdModuleTest, trestartLessThanDt)
255 {
256     setAllInputs("alanine_vsite_solvated");
257     const char* const cmdline[] = { "-trestart", "1", "-sel", "2" };
258     runTest(CommandLine(cmdline));
259 }
260
261 TEST_F(MsdModuleTest, trestartGreaterThanDt)
262 {
263     setAllInputs("alanine_vsite_solvated");
264     const char* const cmdline[] = { "-trestart", "10", "-sel", "2" };
265     runTest(CommandLine(cmdline));
266 }
267
268 TEST_F(MsdModuleTest, molTest)
269 {
270     setAllInputs("alanine_vsite_solvated");
271     setOutputFile("-mol", "diff_mol.xvg", MsdMatch());
272     const char* const cmdline[] = { "-trestart", "10", "-sel", "3" };
273     runTest(CommandLine(cmdline));
274 }
275
276 TEST_F(MsdModuleTest, beginFit)
277 {
278     setAllInputs("alanine_vsite_solvated");
279     const char* const cmdline[] = { "-trestart", "2", "-sel", "3", "-beginfit", "20" };
280     runTest(CommandLine(cmdline));
281 }
282
283 TEST_F(MsdModuleTest, endFit)
284 {
285     setAllInputs("alanine_vsite_solvated");
286     const char* const cmdline[] = { "-trestart", "2", "-sel", "3", "-endfit", "25" };
287     runTest(CommandLine(cmdline));
288 }
289
290 TEST_F(MsdModuleTest, notEnoughPointsForFitErrorEstimate)
291 {
292     setAllInputs("alanine_vsite_solvated");
293     const char* const cmdline[] = { "-trestart", "2",        "-beginfit", "5",    "-endfit",
294                                     "9",         "-lateral", "x",         "-sel", "all" };
295     runTest(CommandLine(cmdline));
296 }
297
298 } // namespace
299
300 } // namespace gmx::test