fac276b9f65285b5f61a53fe232234ea81356dc4
[alexxy/gromacs.git] / src / gromacs / mdlib / tests / energyoutput.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2018,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 /*! \internal \file
36  * \brief
37  * Tests for energy output to log and .edr files.
38  *
39  * \todo Position and orientation restraints tests.
40  * \todo Average and sum in edr file test.
41  * \todo AWH output tests.
42  * \todo The log and edr outputs are currently saved to the file on the disk and then read
43  *       to compare with the reference data. This will be more elegant (and run faster) when we
44  *       refactor the output routines to write to a stream interface, which can already be handled
45  *       in-memory when running tests.
46  *
47  * \author Mark Abraham <mark.j.abraham@gmail.com>
48  * \author Artem Zhmurov <zhmurov@gmail.com>
49  *
50  * \ingroup module_mdlib
51  */
52 #include "gmxpre.h"
53
54 #include "gromacs/mdlib/energyoutput.h"
55
56 #include <cstdio>
57
58 #include <gtest/gtest.h>
59
60 #include "gromacs/mdlib/ebin.h"
61 #include "gromacs/mdlib/makeconstraints.h"
62 #include "gromacs/mdrunutility/handlerestart.h"
63 #include "gromacs/mdtypes/commrec.h"
64 #include "gromacs/mdtypes/fcdata.h"
65 #include "gromacs/mdtypes/group.h"
66 #include "gromacs/mdtypes/inputrec.h"
67 #include "gromacs/mdtypes/mdatom.h"
68 #include "gromacs/mdtypes/state.h"
69 #include "gromacs/topology/topology.h"
70 #include "gromacs/utility/cstringutil.h"
71 #include "gromacs/utility/mdmodulenotification.h"
72 #include "gromacs/utility/stringutil.h"
73 #include "gromacs/utility/textreader.h"
74 #include "gromacs/utility/unique_cptr.h"
75
76 #include "testutils/refdata.h"
77 #include "testutils/setenv.h"
78 #include "testutils/testasserts.h"
79 #include "testutils/testfilemanager.h"
80
81 namespace gmx
82 {
83 namespace test
84 {
85 namespace
86 {
87
88 //! Wraps fclose to discard the return value to use it as a deleter with gmx::unique_cptr.
89 void fcloseWrapper(FILE* fp)
90 {
91     fclose(fp);
92 }
93
94 /*! \brief Test parameters space.
95  *
96  * The test will run on a set of combinations of this steucture parameters.
97  */
98 struct EnergyOutputTestParameters
99 {
100     //! Thermostat (enum)
101     TemperatureCoupling temperatureCouplingScheme;
102     //! Barostat (enum)
103     PressureCoupling pressureCouplingScheme;
104     //! Integrator
105     IntegrationAlgorithm integrator;
106     //! Number of saved energy frames (to test averages output).
107     int numFrames;
108     //! If output should be initialized as a rerun.
109     bool isRerun;
110     //! Is box triclinic (off-diagonal elements will be printed).
111     bool isBoxTriclinic;
112 };
113
114 /*! \brief Sets of parameters on which to run the tests.
115  *
116  * Only several combinations of the parameters are used. Using all possible combinations will
117  * require ~10 MB of test data and ~2 sec to run the tests.
118  */
119 const EnergyOutputTestParameters parametersSets[] = {
120     { TemperatureCoupling::No, PressureCoupling::No, IntegrationAlgorithm::MD, 1, false, false },
121     { TemperatureCoupling::No, PressureCoupling::No, IntegrationAlgorithm::MD, 1, true, false },
122     { TemperatureCoupling::No, PressureCoupling::No, IntegrationAlgorithm::MD, 1, false, true },
123     { TemperatureCoupling::No, PressureCoupling::No, IntegrationAlgorithm::MD, 0, false, false },
124     { TemperatureCoupling::No, PressureCoupling::No, IntegrationAlgorithm::MD, 10, false, false },
125     { TemperatureCoupling::VRescale, PressureCoupling::No, IntegrationAlgorithm::MD, 1, false, false },
126     { TemperatureCoupling::NoseHoover, PressureCoupling::No, IntegrationAlgorithm::MD, 1, false, false },
127     { TemperatureCoupling::No, PressureCoupling::ParrinelloRahman, IntegrationAlgorithm::MD, 1, false, false },
128     { TemperatureCoupling::No, PressureCoupling::Mttk, IntegrationAlgorithm::MD, 1, false, false },
129     { TemperatureCoupling::No, PressureCoupling::No, IntegrationAlgorithm::VV, 1, false, false },
130     { TemperatureCoupling::No, PressureCoupling::Mttk, IntegrationAlgorithm::VV, 1, false, false }
131 };
132
133 /*! \brief Test fixture to test energy output.
134  *
135  * The class is initialized to maximize amount of energy terms printed.
136  * The test is run for different combinations of temperature and pressure control
137  * schemes. Different number of printed steps is also tested.
138  */
139 class EnergyOutputTest : public ::testing::TestWithParam<EnergyOutputTestParameters>
140 {
141 public:
142     //! File manager
143     TestFileManager fileManager_;
144     //! Energy (.edr) file
145     ener_file_t energyFile_;
146     //! Input data
147     t_inputrec inputrec_;
148     //! Topology
149     gmx_mtop_t mtop_;
150     //! Simulation time
151     double time_;
152     //! Total mass
153     real tmass_;
154     //! Potential energy data
155     std::unique_ptr<gmx_enerdata_t> enerdata_;
156     //! Kinetic energy data (for temperatures output)
157     gmx_ekindata_t ekindata_;
158     //! System state
159     t_state state_;
160     //! PBC box
161     matrix box_;
162     //! Virial from constraints
163     tensor constraintsVirial_;
164     //! Virial from force computation
165     tensor forceVirial_;
166     //! Total virial
167     tensor totalVirial_;
168     //! Pressure
169     tensor pressure_;
170     //! Names for the groups.
171     std::vector<std::string> groupNameStrings_ = { "Protein", "Water", "Lipid" };
172     //! Names for the groups as C strings.
173     std::vector<std::vector<char>> groupNameCStrings_;
174     //! Handles to the names as C strings in the way needed for SimulationGroups.
175     std::vector<char*> groupNameHandles_;
176     //! Total dipole momentum
177     rvec muTotal_;
178     //! Communication record
179     t_commrec cr_;
180     //! Constraints object (for constraints RMSD output in case of LINCS)
181     std::unique_ptr<Constraints> constraints_;
182     //! Temporary output filename
183     std::string logFilename_;
184     //! Temporary energy output filename
185     std::string edrFilename_;
186     //! Pointer to a temporary output file
187     FILE* log_;
188     //! Log file wrapper
189     unique_cptr<FILE, fcloseWrapper> logFileGuard_;
190     //! Reference data
191     TestReferenceData refData_;
192     //! Checker for reference data
193     TestReferenceChecker checker_;
194
195     EnergyOutputTest() :
196         logFilename_(fileManager_.getTemporaryFilePath(".log")),
197         edrFilename_(fileManager_.getTemporaryFilePath(".edr")),
198         log_(std::fopen(logFilename_.c_str(), "w")),
199         logFileGuard_(log_),
200         checker_(refData_.rootChecker())
201     {
202         // Input record
203         inputrec_.delta_t = 0.001;
204
205         // F_EQM
206         inputrec_.bQMMM = true;
207         // F_RF_EXCL will not be tested - group scheme is not supported any more
208         inputrec_.cutoff_scheme = CutoffScheme::Verlet;
209         // F_COUL_RECIP
210         inputrec_.coulombtype = CoulombInteractionType::Pme;
211         // F_LJ_RECIP
212         inputrec_.vdwtype = VanDerWaalsType::Pme;
213
214         // F_DVDL_COUL, F_DVDL_VDW, F_DVDL_BONDED, F_DVDL_RESTRAINT, F_DKDL and F_DVDL
215         inputrec_.efep = FreeEnergyPerturbationType::Yes;
216         inputrec_.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Coul]      = true;
217         inputrec_.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Vdw]       = true;
218         inputrec_.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Bonded]    = true;
219         inputrec_.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Restraint] = true;
220         inputrec_.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Mass]      = true;
221         inputrec_.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Coul]      = true;
222         inputrec_.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Fep]       = true;
223
224         // F_DISPCORR and F_PDISPCORR
225         inputrec_.eDispCorr = DispersionCorrectionType::Ener;
226         inputrec_.bRot      = true;
227
228         // F_ECONSERVED
229         inputrec_.ref_p[YY][XX] = 0.0;
230         inputrec_.ref_p[ZZ][XX] = 0.0;
231         inputrec_.ref_p[ZZ][YY] = 0.0;
232
233         // Dipole (mu)
234         inputrec_.ewald_geometry = EwaldGeometry::ThreeDC;
235
236         // GMX_CONSTRAINTVIR environment variable should also be
237         // set to print constraints and force virials separately.
238         gmxSetenv("GMX_CONSTRAINTVIR", "true", 1);
239         // To print constrain RMSD, constraints algorithm should be set to LINCS.
240         inputrec_.eConstrAlg = ConstraintAlgorithm::Lincs;
241
242         mtop_.bIntermolecularInteractions = false;
243
244         // Constructing molecular topology
245         gmx_moltype_t molType;
246
247         molType.atoms.nr = 2;
248
249         // F_CONSTR
250         // This must be initialized so that Constraints object can be created below.
251         InteractionList interactionListConstr;
252         interactionListConstr.iatoms.resize(NRAL(F_CONSTR) + 1);
253         interactionListConstr.iatoms[0] = 0;
254         interactionListConstr.iatoms[1] = 0;
255         interactionListConstr.iatoms[2] = 1;
256         molType.ilist.at(F_CONSTR)      = interactionListConstr;
257
258         InteractionList interactionListEmpty;
259         interactionListEmpty.iatoms.resize(0);
260         molType.ilist.at(F_CONSTRNC) = interactionListEmpty;
261         molType.ilist.at(F_SETTLE)   = interactionListEmpty;
262
263         // F_LJ14 and F_COUL14
264         InteractionList interactionListLJ14;
265         interactionListLJ14.iatoms.resize(NRAL(F_LJ14) + 1);
266         molType.ilist.at(F_LJ14) = interactionListLJ14;
267
268         // F_LJC14_Q
269         InteractionList interactionListLJC14Q;
270         interactionListLJC14Q.iatoms.resize(NRAL(F_LJC14_Q) + 1);
271         molType.ilist.at(F_LJC14_Q) = interactionListLJC14Q;
272
273         // TODO Do proper initialization for distance and orientation
274         //      restraints and remove comments to enable their output
275         // F_DISRES
276         // InteractionList interactionListDISRES;
277         // interactionListDISRES.iatoms.resize(NRAL(F_DISRES) + 1);
278         // molType.ilist.at(F_DISRES)   = interactionListDISRES;
279         //
280         // F_ORIRES
281         // InteractionList interactionListORIRES;
282         // interactionListORIRES.iatoms.resize(NRAL(F_ORIRES) + 1);
283         // molType.ilist.at(F_ORIRES)   = interactionListORIRES;
284
285         mtop_.moltype.push_back(molType);
286
287         gmx_molblock_t molBlock;
288         molBlock.type = 0;
289         molBlock.nmol = 1;
290         mtop_.molblock.push_back(molBlock);
291
292         // This is to keep constraints initialization happy
293         mtop_.natoms = 2;
294         mtop_.ffparams.iparams.resize(F_NRE);
295         mtop_.ffparams.functype.resize(F_NRE);
296         mtop_.ffparams.iparams.at(F_CONSTR).constr.dA   = 1.0;
297         mtop_.ffparams.iparams.at(F_CONSTR).constr.dB   = 1.0;
298         mtop_.ffparams.iparams.at(F_CONSTRNC).constr.dA = 1.0;
299         mtop_.ffparams.iparams.at(F_CONSTRNC).constr.dB = 1.0;
300
301         // Groups for energy output, temperature coupling and acceleration
302         for (const auto& string : groupNameStrings_)
303         {
304             std::vector<char> cString(string.begin(), string.end());
305             // Need to add null termination
306             cString.push_back('\0');
307             groupNameCStrings_.emplace_back(cString);
308             groupNameHandles_.emplace_back(groupNameCStrings_.back().data());
309         }
310         for (auto& handle : groupNameHandles_)
311         {
312             mtop_.groups.groupNames.emplace_back(&handle);
313         }
314
315         mtop_.groups.groups[SimulationAtomGroupType::EnergyOutput].resize(3);
316         mtop_.groups.groups[SimulationAtomGroupType::EnergyOutput][0] = 0;
317         mtop_.groups.groups[SimulationAtomGroupType::EnergyOutput][1] = 1;
318         mtop_.groups.groups[SimulationAtomGroupType::EnergyOutput][2] = 2;
319
320         mtop_.groups.groups[SimulationAtomGroupType::TemperatureCoupling].resize(3);
321         mtop_.groups.groups[SimulationAtomGroupType::TemperatureCoupling][0] = 0;
322         mtop_.groups.groups[SimulationAtomGroupType::TemperatureCoupling][1] = 1;
323         mtop_.groups.groups[SimulationAtomGroupType::TemperatureCoupling][2] = 2;
324
325         // Nose-Hoover chains
326         inputrec_.bPrintNHChains     = true;
327         inputrec_.opts.nhchainlength = 2;
328         state_.nosehoover_xi.resize(
329                 mtop_.groups.groups[SimulationAtomGroupType::TemperatureCoupling].size()
330                 * inputrec_.opts.nhchainlength);
331         state_.nosehoover_vxi.resize(
332                 mtop_.groups.groups[SimulationAtomGroupType::TemperatureCoupling].size()
333                 * inputrec_.opts.nhchainlength);
334
335         // This will be needed only with MTTK barostat
336         state_.nhpres_xi.resize(1 * inputrec_.opts.nhchainlength);
337         state_.nhpres_vxi.resize(1 * inputrec_.opts.nhchainlength);
338
339         // Group pairs
340         enerdata_ = std::make_unique<gmx_enerdata_t>(
341                 mtop_.groups.groups[SimulationAtomGroupType::EnergyOutput].size(), 0);
342
343         // Kinetic energy and related data
344         ekindata_.tcstat.resize(mtop_.groups.groups[SimulationAtomGroupType::TemperatureCoupling].size());
345
346         // This is needed so that the ebin space will be allocated
347         inputrec_.cos_accel = 1.0;
348         // This is to keep the destructor happy (otherwise sfree() segfaults)
349         ekindata_.nthreads = 0;
350         snew(ekindata_.ekin_work_alloc, 1);
351         snew(ekindata_.ekin_work, 1);
352         snew(ekindata_.dekindl_work, 1);
353
354         // Group options for annealing output
355         inputrec_.opts.ngtc = 3;
356         snew(inputrec_.opts.ref_t, inputrec_.opts.ngtc);
357         snew(inputrec_.opts.annealing, inputrec_.opts.ngtc);
358         inputrec_.opts.annealing[0] = SimulatedAnnealing::No;
359         inputrec_.opts.annealing[1] = SimulatedAnnealing::Single;
360         inputrec_.opts.annealing[2] = SimulatedAnnealing::Periodic;
361
362         // This is to keep done_inputrec happy (otherwise sfree() segfaults)
363         snew(inputrec_.opts.anneal_time, inputrec_.opts.ngtc);
364         snew(inputrec_.opts.anneal_temp, inputrec_.opts.ngtc);
365
366         // Communication record (for Constraints constructor)
367         cr_.nnodes = 1;
368         cr_.dd     = nullptr;
369
370         // Constraints object (to get constraints RMSD from)
371         // TODO EnergyOutput should not take Constraints object
372         // TODO This object will always return zero as RMSD value.
373         //      It is more relevant to have non-zero value for testing.
374         constraints_ = makeConstraints(
375                 mtop_, inputrec_, nullptr, false, nullptr, &cr_, nullptr, nullptr, nullptr, false);
376     }
377
378     /*! \brief Helper function to generate synthetic data to output
379      *
380      * \param[in,out] testValue    Base value fr energy data.
381      */
382     void setStepData(double* testValue)
383     {
384
385         time_  = (*testValue += 0.1);
386         tmass_ = (*testValue += 0.1);
387
388         enerdata_->term[F_LJ]      = (*testValue += 0.1);
389         enerdata_->term[F_COUL_SR] = (*testValue += 0.1);
390         enerdata_->term[F_EPOT]    = (*testValue += 0.1);
391         enerdata_->term[F_EKIN]    = (*testValue += 0.1);
392         enerdata_->term[F_ETOT]    = (*testValue += 0.1);
393         enerdata_->term[F_TEMP]    = (*testValue += 0.1);
394         enerdata_->term[F_PRES]    = (*testValue += 0.1);
395
396         enerdata_->term[F_BHAM]         = (*testValue += 0.1);
397         enerdata_->term[F_EQM]          = (*testValue += 0.1);
398         enerdata_->term[F_RF_EXCL]      = (*testValue += 0.1);
399         enerdata_->term[F_COUL_RECIP]   = (*testValue += 0.1);
400         enerdata_->term[F_LJ_RECIP]     = (*testValue += 0.1);
401         enerdata_->term[F_LJ14]         = (*testValue += 0.1);
402         enerdata_->term[F_COUL14]       = (*testValue += 0.1);
403         enerdata_->term[F_LJC14_Q]      = (*testValue += 0.1);
404         enerdata_->term[F_LJC_PAIRS_NB] = (*testValue += 0.1);
405
406         enerdata_->term[F_DVDL_COUL]      = (*testValue += 0.1);
407         enerdata_->term[F_DVDL_VDW]       = (*testValue += 0.1);
408         enerdata_->term[F_DVDL_BONDED]    = (*testValue += 0.1);
409         enerdata_->term[F_DVDL_RESTRAINT] = (*testValue += 0.1);
410         enerdata_->term[F_DKDL]           = (*testValue += 0.1);
411         enerdata_->term[F_DVDL]           = (*testValue += 0.1);
412
413         enerdata_->term[F_DISPCORR]   = (*testValue += 0.1);
414         enerdata_->term[F_PDISPCORR]  = (*testValue += 0.1);
415         enerdata_->term[F_DISRESVIOL] = (*testValue += 0.1);
416         enerdata_->term[F_ORIRESDEV]  = (*testValue += 0.1);
417         enerdata_->term[F_COM_PULL]   = (*testValue += 0.1);
418         enerdata_->term[F_ECONSERVED] = (*testValue += 0.1);
419
420         // Group pairs
421         for (int i = 0; i < enerdata_->grpp.nener; i++)
422         {
423             for (int k = 0; k < static_cast<int>(NonBondedEnergyTerms::Count); k++)
424             {
425                 enerdata_->grpp.energyGroupPairTerms[k][i] = (*testValue += 0.1);
426             }
427         }
428
429         // Kinetic energy and related data
430         for (auto& tcstat : ekindata_.tcstat)
431         {
432             tcstat.T      = (*testValue += 0.1);
433             tcstat.lambda = (*testValue += 0.1);
434         }
435         // Removing constant acceleration removed a total increment of 0.6
436         // To avoid unnecessary changes in reference data, we keep the increment
437         (*testValue += 0.6);
438
439         // This conditional is to check whether the ebin was allocated.
440         // Otherwise it will print cosacc data into the first bin.
441         if (inputrec_.cos_accel != 0)
442         {
443             ekindata_.cosacc.cos_accel = (*testValue += 0.1);
444             ekindata_.cosacc.vcos      = (*testValue += 0.1);
445         }
446
447         state_.box[XX][XX] = (*testValue += 0.1);
448         state_.box[XX][YY] = (*testValue += 0.1);
449         state_.box[XX][ZZ] = (*testValue += 0.1);
450         state_.box[YY][XX] = (*testValue += 0.1);
451         state_.box[YY][YY] = (*testValue += 0.1);
452         state_.box[YY][ZZ] = (*testValue += 0.1);
453         state_.box[ZZ][XX] = (*testValue += 0.1);
454         state_.box[ZZ][YY] = (*testValue += 0.1);
455         state_.box[ZZ][ZZ] = (*testValue += 0.1);
456
457         box_[XX][XX] = (*testValue += 0.1);
458         box_[XX][YY] = (*testValue += 0.1);
459         box_[XX][ZZ] = (*testValue += 0.1);
460         box_[YY][XX] = (*testValue += 0.1);
461         box_[YY][YY] = (*testValue += 0.1);
462         box_[YY][ZZ] = (*testValue += 0.1);
463         box_[ZZ][XX] = (*testValue += 0.1);
464         box_[ZZ][YY] = (*testValue += 0.1);
465         box_[ZZ][ZZ] = (*testValue += 0.1);
466
467         constraintsVirial_[XX][XX] = (*testValue += 0.1);
468         constraintsVirial_[XX][YY] = (*testValue += 0.1);
469         constraintsVirial_[XX][ZZ] = (*testValue += 0.1);
470         constraintsVirial_[YY][XX] = (*testValue += 0.1);
471         constraintsVirial_[YY][YY] = (*testValue += 0.1);
472         constraintsVirial_[YY][ZZ] = (*testValue += 0.1);
473         constraintsVirial_[ZZ][XX] = (*testValue += 0.1);
474         constraintsVirial_[ZZ][YY] = (*testValue += 0.1);
475         constraintsVirial_[ZZ][ZZ] = (*testValue += 0.1);
476
477         forceVirial_[XX][XX] = (*testValue += 0.1);
478         forceVirial_[XX][YY] = (*testValue += 0.1);
479         forceVirial_[XX][ZZ] = (*testValue += 0.1);
480         forceVirial_[YY][XX] = (*testValue += 0.1);
481         forceVirial_[YY][YY] = (*testValue += 0.1);
482         forceVirial_[YY][ZZ] = (*testValue += 0.1);
483         forceVirial_[ZZ][XX] = (*testValue += 0.1);
484         forceVirial_[ZZ][YY] = (*testValue += 0.1);
485         forceVirial_[ZZ][ZZ] = (*testValue += 0.1);
486
487         totalVirial_[XX][XX] = (*testValue += 0.1);
488         totalVirial_[XX][YY] = (*testValue += 0.1);
489         totalVirial_[XX][ZZ] = (*testValue += 0.1);
490         totalVirial_[YY][XX] = (*testValue += 0.1);
491         totalVirial_[YY][YY] = (*testValue += 0.1);
492         totalVirial_[YY][ZZ] = (*testValue += 0.1);
493         totalVirial_[ZZ][XX] = (*testValue += 0.1);
494         totalVirial_[ZZ][YY] = (*testValue += 0.1);
495         totalVirial_[ZZ][ZZ] = (*testValue += 0.1);
496
497         pressure_[XX][XX] = (*testValue += 0.1);
498         pressure_[XX][YY] = (*testValue += 0.1);
499         pressure_[XX][ZZ] = (*testValue += 0.1);
500         pressure_[YY][XX] = (*testValue += 0.1);
501         pressure_[YY][YY] = (*testValue += 0.1);
502         pressure_[YY][ZZ] = (*testValue += 0.1);
503         pressure_[ZZ][XX] = (*testValue += 0.1);
504         pressure_[ZZ][YY] = (*testValue += 0.1);
505         pressure_[ZZ][ZZ] = (*testValue += 0.1);
506
507         muTotal_[XX] = (*testValue += 0.1);
508         muTotal_[YY] = (*testValue += 0.1);
509         muTotal_[ZZ] = (*testValue += 0.1);
510
511         state_.boxv[XX][XX] = (*testValue += 0.1);
512         state_.boxv[XX][YY] = (*testValue += 0.1);
513         state_.boxv[XX][ZZ] = (*testValue += 0.1);
514         state_.boxv[YY][XX] = (*testValue += 0.1);
515         state_.boxv[YY][YY] = (*testValue += 0.1);
516         state_.boxv[YY][ZZ] = (*testValue += 0.1);
517         state_.boxv[ZZ][XX] = (*testValue += 0.1);
518         state_.boxv[ZZ][YY] = (*testValue += 0.1);
519         state_.boxv[ZZ][ZZ] = (*testValue += 0.1);
520
521         for (int i = 0; i < inputrec_.opts.ngtc; i++)
522         {
523             inputrec_.opts.ref_t[i] = (*testValue += 0.1);
524         }
525
526         for (index k = 0; k < ssize(mtop_.groups.groups[SimulationAtomGroupType::TemperatureCoupling])
527                                       * inputrec_.opts.nhchainlength;
528              k++)
529         {
530             state_.nosehoover_xi[k]  = (*testValue += 0.1);
531             state_.nosehoover_vxi[k] = (*testValue += 0.1);
532         }
533         for (int k = 0; k < inputrec_.opts.nhchainlength; k++)
534         {
535             state_.nhpres_xi[k]  = (*testValue += 0.1);
536             state_.nhpres_vxi[k] = (*testValue += 0.1);
537         }
538     }
539
540     /*! \brief Check if the contents of the .edr file correspond to the reference data.
541      *
542      * The code below is based on the 'gmx dump' tool.
543      *
544      * \param[in] fileName    Name of the file to check.
545      * \param[in] frameCount  Number of frames to check.
546      */
547     void checkEdrFile(const char* fileName, int frameCount)
548     {
549         ener_file_t  edrFile;
550         gmx_enxnm_t* energyTermsEdr = nullptr;
551         int          numEnergyTermsEdr;
552
553         edrFile = open_enx(fileName, "r");
554         do_enxnms(edrFile, &numEnergyTermsEdr, &energyTermsEdr);
555         assert(energyTermsEdr);
556
557         // Check header
558         TestReferenceChecker edrFileRef(checker_.checkCompound("File", "EnergyFile"));
559         TestReferenceChecker energyTermsRef(
560                 edrFileRef.checkSequenceCompound("EnergyTerms", numEnergyTermsEdr));
561         for (int i = 0; i < numEnergyTermsEdr; i++)
562         {
563             TestReferenceChecker energyTermRef(energyTermsRef.checkCompound("EnergyTerm", nullptr));
564             energyTermRef.checkString(energyTermsEdr[i].name, "Name");
565             energyTermRef.checkString(energyTermsEdr[i].unit, "Units");
566         }
567
568         // Check frames
569         TestReferenceChecker framesRef(edrFileRef.checkSequenceCompound("Frames", frameCount));
570         t_enxframe*          frameEdr;
571         snew(frameEdr, 1);
572         char buffer[22];
573         for (int frameId = 0; frameId < frameCount; frameId++)
574         {
575             bool bCont = do_enx(edrFile, frameEdr);
576             EXPECT_TRUE(bCont) << gmx::formatString("Cant read frame %d from .edr file.", frameId);
577
578             TestReferenceChecker frameRef(framesRef.checkCompound("Frame", nullptr));
579             frameRef.checkReal(frameEdr->t, "Time");
580             frameRef.checkReal(frameEdr->dt, "Timestep");
581             frameRef.checkString(gmx_step_str(frameEdr->step, buffer), "Step");
582             frameRef.checkString(gmx_step_str(frameEdr->nsum, buffer), "NumSteps");
583
584             EXPECT_EQ(frameEdr->nre, numEnergyTermsEdr)
585                     << gmx::formatString("Wrong number of energy terms in frame %d.", frameId);
586             TestReferenceChecker energyValuesRef(
587                     frameRef.checkSequenceCompound("EnergyTerms", numEnergyTermsEdr));
588             for (int i = 0; i < numEnergyTermsEdr; i++)
589             {
590                 TestReferenceChecker energyValueRef(energyValuesRef.checkCompound("EnergyTerm", nullptr));
591                 energyValueRef.checkString(energyTermsEdr[i].name, "Name");
592                 energyValueRef.checkReal(frameEdr->ener[i].e, "Value");
593             }
594         }
595
596         free_enxnms(numEnergyTermsEdr, energyTermsEdr);
597         done_ener_file(edrFile);
598
599         free_enxframe(frameEdr);
600         sfree(frameEdr);
601     }
602 };
603
604 TEST_P(EnergyOutputTest, CheckOutput)
605 {
606     ASSERT_NE(log_, nullptr);
607     // Binary output will be written to the temporary location
608     energyFile_ = open_enx(edrFilename_.c_str(), "w");
609     ASSERT_NE(energyFile_, nullptr);
610
611     EnergyOutputTestParameters parameters = GetParam();
612     inputrec_.etc                         = parameters.temperatureCouplingScheme;
613     inputrec_.epc                         = parameters.pressureCouplingScheme;
614     inputrec_.eI                          = parameters.integrator;
615
616     if (parameters.isBoxTriclinic)
617     {
618         inputrec_.ref_p[YY][XX] = 1.0;
619     }
620
621     MdModulesNotifier             mdModulesNotifier;
622     std::unique_ptr<EnergyOutput> energyOutput =
623             std::make_unique<EnergyOutput>(energyFile_,
624                                            mtop_,
625                                            inputrec_,
626                                            nullptr,
627                                            nullptr,
628                                            parameters.isRerun,
629                                            StartingBehavior::NewSimulation,
630                                            false,
631                                            mdModulesNotifier);
632
633     // Add synthetic data for a single step
634     double testValue = 10.0;
635     for (int frame = 0; frame < parameters.numFrames; frame++)
636     {
637         setStepData(&testValue);
638         energyOutput->addDataAtEnergyStep(false,
639                                           true,
640                                           time_,
641                                           tmass_,
642                                           enerdata_.get(),
643                                           nullptr,
644                                           nullptr,
645                                           box_,
646                                           PTCouplingArrays({ state_.boxv,
647                                                              state_.nosehoover_xi,
648                                                              state_.nosehoover_vxi,
649                                                              state_.nhpres_xi,
650                                                              state_.nhpres_vxi }),
651                                           state_.fep_state,
652                                           constraintsVirial_,
653                                           forceVirial_,
654                                           totalVirial_,
655                                           pressure_,
656                                           &ekindata_,
657                                           muTotal_,
658                                           constraints_.get());
659
660         energyOutput->printAnnealingTemperatures(log_, &mtop_.groups, &inputrec_.opts);
661         energyOutput->printStepToEnergyFile(
662                 energyFile_, true, false, false, log_, 100 * frame, time_, nullptr, nullptr);
663         time_ += 1.0;
664     }
665
666     energyOutput->printAnnealingTemperatures(log_, &mtop_.groups, &inputrec_.opts);
667     energyOutput->printAverages(log_, &mtop_.groups);
668
669     // We need to close the file before the contents are available.
670     logFileGuard_.reset(nullptr);
671
672     done_ener_file(energyFile_);
673
674     // Set tolerance
675     checker_.setDefaultTolerance(relativeToleranceAsFloatingPoint(testValue, 1.0e-5));
676
677     if (parameters.numFrames > 0)
678     {
679         // Test binary output
680         checkEdrFile(edrFilename_.c_str(), parameters.numFrames);
681     }
682
683     // Test printed values
684     checker_.checkInteger(energyOutput->numEnergyTerms(), "Number of Energy Terms");
685     checker_.checkString(TextReader::readFileToString(logFilename_), "log");
686 }
687
688 INSTANTIATE_TEST_CASE_P(WithParameters, EnergyOutputTest, ::testing::ValuesIn(parametersSets));
689
690 } // namespace
691 } // namespace test
692 } // namespace gmx