2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2013,2014,2015,2016,2017,2018,2019, 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.
38 * Tool for extracting AWH (Accelerated Weight Histogram) data from energy files.
40 * \author Viveca Lindahl
54 #include "gromacs/commandline/pargs.h"
55 #include "gromacs/fileio/enxio.h"
56 #include "gromacs/fileio/gmxfio.h"
57 #include "gromacs/fileio/oenv.h"
58 #include "gromacs/fileio/tpxio.h"
59 #include "gromacs/fileio/trxio.h"
60 #include "gromacs/fileio/xvgr.h"
61 #include "gromacs/gmxana/gmx_ana.h"
62 #include "gromacs/math/units.h"
63 #include "gromacs/mdtypes/awh_params.h"
64 #include "gromacs/mdtypes/inputrec.h"
65 #include "gromacs/topology/mtop_util.h"
66 #include "gromacs/topology/topology.h"
67 #include "gromacs/trajectory/energyframe.h"
68 #include "gromacs/utility/arraysize.h"
69 #include "gromacs/utility/basedefinitions.h"
70 #include "gromacs/utility/fatalerror.h"
71 #include "gromacs/utility/futil.h"
72 #include "gromacs/utility/gmxassert.h"
73 #include "gromacs/utility/smalloc.h"
74 #include "gromacs/utility/stringutil.h"
76 using gmx::AwhBiasParams;
82 //! Enum for choosing AWH graph output
83 enum class AwhGraphSelection
85 Pmf, //!< Only the PMF
86 All //!< All possible AWH graphs
89 //! Energy unit options
98 /*! \brief All meta-data that is shared for one output file type for one bias */
102 /*! \brief Constructor, Set the output base file name and title.
104 * Result is a valid object, but will produce empty output files.
106 * \param[in] filename The name for output files, frame time, and possibly bias number, will be added per file/frame.
107 * \param[in] baseTitle The base title of the plot, the bias number might be added.
108 * \param[in] numBias The total number of AWH biases in the system.
109 * \param[in] biasIndex The index of this bias.
111 OutputFile(const std::string& filename, const std::string& baseTitle, int numBias, int biasIndex);
113 /*! \brief Initializes the output file setup for the AWH output.
115 * \param[in] subBlockStart Index of the first sub-block to write in the energy frame.
116 * \param[in] numSubBlocks The total number of sub-blocks in the framw.
117 * \param[in] awhBiasParams The AWH bias parameters.
118 * \param[in] graphSelection Selects which graphs to plot.
119 * \param[in] energyUnit Requested energy unit in output.
120 * \param[in] kTValue kB*T in kJ/mol.
122 void initializeAwhOutputFile(int subBlockStart,
124 const AwhBiasParams* awhBiasParams,
125 AwhGraphSelection graphSelection,
126 EnergyUnit energyUnit,
129 /*! \brief Initializes the output file setup for the fricion output.
131 * \param[in] subBlockStart Index of the first sub-block to write in the energy frame.
132 * \param[in] numSubBlocks The total number of sub-blocks in the framw.
133 * \param[in] awhBiasParams The AWH bias parameters.
134 * \param[in] energyUnit Requested energy unit in output.
135 * \param[in] kTValue kB*T in kJ/mol.
137 void initializeFrictionOutputFile(int subBlockStart,
139 const AwhBiasParams* awhBiasParams,
140 EnergyUnit energyUnit,
143 /*! \brief Opens a single output file for a bias, prints title and legends.
145 * \param[in] time The time for of frame to be written.
146 * \param[in] oenv The output environment.
147 * \returns the file pointer.
149 FILE* openBiasOutputFile(double time, const gmx_output_env_t* oenv) const;
151 /*! \brief Writes data selected from \p block to file.
153 * \param[in] block The energy block with the data to write.
154 * \param[in] subBlockStart The sub-block start index.
155 * \param[in] fp The file pointer.
157 void writeData(const t_enxblock& block, int subBlockStart, FILE* fp) const;
160 std::string baseFilename_; /**< Base of the output file name. */
161 std::string title_; /**< Title for the graph. */
162 int numDim_; /**< Number of dimensions. */
163 int firstGraphSubBlock_; /**< Index in the energy sub-blocks for the first graph. */
164 int numGraph_; /**< Number of actual graphs. */
165 bool useKTForEnergy_; /**< Whether to use kT instead of kJ/mol for energy. */
166 std::vector<real> scaleFactor_; /**< Scale factors from energy file data to output for each of the numGraph quantities. */
168 std::vector<std::string> legend_; /**< Legends for the output */
169 std::string xLabel_; /**< Label for the x-axis. */
170 std::string yLabel_; /**< Label for the y-axis. */
173 /*! \brief All meta-data that is shared for all output files for one bias */
174 struct BiasOutputSetup
177 BiasOutputSetup(int subblockStart,
178 std::unique_ptr<OutputFile> awhOutputFile,
179 std::unique_ptr<OutputFile> frictionOutputFile) :
180 subblockStart_(subblockStart),
181 awhOutputFile_(std::move(awhOutputFile)),
182 frictionOutputFile_(std::move(frictionOutputFile))
186 //! Return the AWH output file data.
187 const OutputFile& awhOutputFile() const
189 GMX_RELEASE_ASSERT(awhOutputFile_ != nullptr,
190 "awhOutputFile() called without initialized AWH output file");
192 return *awhOutputFile_;
195 //! Return the a pointer to the friction output file data, can return nullptr
196 const OutputFile* frictionOutputFile() const { return frictionOutputFile_.get(); }
198 //! Return the starting subblock.
199 int subblockStart() const { return subblockStart_; }
202 const int subblockStart_; /**< The start index of the subblocks to read. */
203 std::unique_ptr<OutputFile> awhOutputFile_; /**< The standard AWH output file data. */
204 std::unique_ptr<OutputFile> frictionOutputFile_; /**< The friction/metric tensor output file data */
207 /*! \brief All options and meta-data needed for the AWH output */
212 AwhReader(const AwhParams* awhParams,
214 const t_filenm* filenames,
215 AwhGraphSelection awhGraphSelection,
216 EnergyUnit energyUnit,
218 const t_enxblock* block);
220 //! Extract and print AWH data for an AWH block of one energy frame
221 void processAwhFrame(const t_enxblock& block, double time, const gmx_output_env_t* oenv) const;
224 std::vector<BiasOutputSetup> biasOutputSetups_; /**< The output setups, one for each AWH bias. */
226 const real kT_; /**< kB*T in kJ/mol. */
232 //! Enum for selecting output file type.
233 enum class OutputFileType
235 Awh, //!< AWH, all data except friction tensor.
236 Friction //!< The full friction tensor.
239 /*! \brief The maximum number of observables per subblock, not including the full friction tensor.
241 * This number, as well as the legends in make_legend() should match
242 * the output that mdrun writes. It would be better to define these
243 * values in a single location.
245 constexpr int maxAwhGraphs = 6;
247 /*! \brief Constructs a legend for a standard awh output file */
248 std::vector<std::string> makeLegend(const AwhBiasParams* awhBiasParams,
249 OutputFileType outputFileType,
252 const std::array<std::string, maxAwhGraphs> legendBase = {
253 { "PMF", "Coord bias", "Coord distr", "Ref value distr", "Target ref value distr",
257 std::vector<std::string> legend;
258 /* Give legends to dimensions higher than the first */
259 for (int d = 1; d < awhBiasParams->ndim; d++)
261 legend.push_back(gmx::formatString("dim%d", d));
264 switch (outputFileType)
266 case OutputFileType::Awh:
268 /* Add as many legends as possible from the "base" legend list */
269 size_t legendBaseIndex = 0;
270 while (legend.size() < numLegend && legendBaseIndex < legendBase.size())
272 legend.push_back(legendBase[legendBaseIndex]);
277 case OutputFileType::Friction:
278 for (int i0 = 0; i0 < awhBiasParams->ndim; i0++)
280 for (int i1 = 0; i1 <= i0; i1++)
282 legend.push_back(gmx::formatString("%d,%d", i0, i1));
288 GMX_RELEASE_ASSERT(legend.size() == numLegend,
289 "The number of legends requested for printing and the number generated "
290 "should be the same");
297 OutputFile::OutputFile(const std::string& filename, const std::string& baseTitle, int numBias, int biasIndex) :
299 firstGraphSubBlock_(0),
301 useKTForEnergy_(false)
304 baseFilename_ = filename.substr(0, filename.find('.'));
308 baseFilename_ += gmx::formatString("%d", biasIndex + 1);
309 title_ += gmx::formatString(" %d", biasIndex + 1);
313 void OutputFile::initializeAwhOutputFile(int subblockStart,
315 const AwhBiasParams* awhBiasParams,
316 AwhGraphSelection graphSelection,
317 EnergyUnit energyUnit,
320 /* The first subblock with actual graph y-values is index 1 + ndim */
321 numDim_ = awhBiasParams->ndim;
322 firstGraphSubBlock_ = subblockStart + 1 + numDim_;
323 if (graphSelection == AwhGraphSelection::All)
325 /* There are one metadata and ndim coordinate blocks */
326 numGraph_ = std::min(numSubBlocks - 1 - numDim_, maxAwhGraphs);
332 useKTForEnergy_ = (energyUnit == EnergyUnit::KT);
333 scaleFactor_.resize(numGraph_, 1);
334 if (!useKTForEnergy_)
336 /* The first two graphs are in units of energy, multiply by kT */
337 std::fill(scaleFactor_.begin(), scaleFactor_.begin() + std::min(2, numGraph_), kTValue);
339 int numLegend = numDim_ - 1 + numGraph_;
340 legend_ = makeLegend(awhBiasParams, OutputFileType::Awh, numLegend);
341 /* We could have both length and angle coordinates in a single bias */
342 xLabel_ = "(nm or deg)";
343 yLabel_ = useKTForEnergy_ ? "(k\\sB\\NT)" : "(kJ/mol)";
344 if (graphSelection == AwhGraphSelection::All)
346 yLabel_ += gmx::formatString(", (nm\\S-%d\\N or rad\\S-%d\\N), (-)", awhBiasParams->ndim,
347 awhBiasParams->ndim);
351 /*! \brief Initializes the output file setup for the fricion output (note that the filename is not set here). */
352 void OutputFile::initializeFrictionOutputFile(int subBlockStart,
354 const AwhBiasParams* awhBiasParams,
355 EnergyUnit energyUnit,
358 /* The first subblock with actual graph y-values is index 1 + ndim */
359 numDim_ = awhBiasParams->ndim;
360 int numTensorElements = (numDim_ * (numDim_ + 1)) / 2;
362 /* The friction tensor elements are always the last subblocks */
363 if (numSubBlocks < 1 + numDim_ + maxAwhGraphs + numTensorElements)
366 "You requested friction tensor output, but the AWH data in the energy file does "
367 "not contain the friction tensor");
369 GMX_ASSERT(numSubBlocks == 1 + numDim_ + maxAwhGraphs + numTensorElements,
370 "The number of sub-blocks per bias should be 1 + ndim + maxAwhGraphs + (ndim*(ndim "
373 firstGraphSubBlock_ = subBlockStart + numSubBlocks - numTensorElements;
374 numGraph_ = numTensorElements;
375 useKTForEnergy_ = (energyUnit == EnergyUnit::KT);
376 scaleFactor_.resize(numGraph_, useKTForEnergy_ ? 1 : kTValue);
377 int numLegend = numDim_ - 1 + numGraph_;
378 legend_ = makeLegend(awhBiasParams, OutputFileType::Friction, numLegend);
379 xLabel_ = "(nm or deg)";
382 yLabel_ = "friction/k\\sB\\NT (nm\\S-2\\Nps or rad\\S-2\\Nps)";
386 yLabel_ = "friction (kJ mol\\S-1\\Nnm\\S-2\\Nps or kJ mol\\S-1\\Nrad\\S-2\\Nps)";
390 AwhReader::AwhReader(const AwhParams* awhParams,
392 const t_filenm* filenames,
393 AwhGraphSelection awhGraphSelection,
394 EnergyUnit energyUnit,
396 const t_enxblock* block) :
399 GMX_RELEASE_ASSERT(block != nullptr, "NULL pointer passed to initAwhReader");
401 bool outputFriction = opt2bSet("-fric", numFileOptions, filenames);
403 /* The first subblock of each AWH block has metadata about
404 * the number of subblocks belonging to that AWH block.
405 * (block->nsub gives only the total number of subblocks and not how
406 * they are distributed between the AWH biases).
409 /* Keep track of the first subblock of this AWH */
410 int subblockStart = 0;
411 for (int k = 0; k < awhParams->numBias; k++)
413 AwhBiasParams* awhBiasParams = &awhParams->awhBiasParams[k];
415 int numSubBlocks = static_cast<int>(block->sub[subblockStart].fval[0]);
417 std::unique_ptr<OutputFile> awhOutputFile(new OutputFile(
418 opt2fn("-o", numFileOptions, filenames), "AWH", awhParams->numBias, k));
420 awhOutputFile->initializeAwhOutputFile(subblockStart, numSubBlocks, awhBiasParams,
421 awhGraphSelection, energyUnit, kT);
423 std::unique_ptr<OutputFile> frictionOutputFile;
426 frictionOutputFile = std::make_unique<OutputFile>(
427 opt2fn("-fric", numFileOptions, filenames), "Friction tensor", awhParams->numBias, k);
429 frictionOutputFile->initializeFrictionOutputFile(subblockStart, numSubBlocks,
430 awhBiasParams, energyUnit, kT);
433 biasOutputSetups_.emplace_back(BiasOutputSetup(subblockStart, std::move(awhOutputFile),
434 std::move(frictionOutputFile)));
436 subblockStart += numSubBlocks;
440 FILE* OutputFile::openBiasOutputFile(double time, const gmx_output_env_t* oenv) const
442 std::string filename = baseFilename_ + gmx::formatString("_t%g.xvg", time);
444 FILE* fp = xvgropen(filename.c_str(), title_.c_str(), xLabel_, yLabel_, oenv);
445 xvgrLegend(fp, legend_, oenv);
450 /*! \brief Prints data selected by \p outputFile from \p block to \p fp */
451 void OutputFile::writeData(const t_enxblock& block, int subBlockStart, FILE* fp) const
453 int numPoints = block.sub[subBlockStart + 1].nr;
454 for (int j = 0; j < numPoints; j++)
456 /* Print the coordinates for numDim dimensions */
457 for (int d = 0; d < numDim_; d++)
459 fprintf(fp, " %8.4f", block.sub[subBlockStart + 1 + d].fval[j]);
462 /* Print numGraph observables */
463 for (int i = 0; i < numGraph_; i++)
465 fprintf(fp, " %g", block.sub[firstGraphSubBlock_ + i].fval[j] * scaleFactor_[i]);
472 void AwhReader::processAwhFrame(const t_enxblock& block, double time, const gmx_output_env_t* oenv) const
474 /* We look for AWH data every energy frame and count the no of AWH frames found. We only extract every 'skip' AWH frame. */
476 for (const auto& setup : biasOutputSetups_)
478 const int subStart = setup.subblockStart();
480 /* Each frame and AWH instance extracted generates one xvg file. */
482 const OutputFile& awhOutputFile = setup.awhOutputFile();
484 FILE* fpAwh = awhOutputFile.openBiasOutputFile(time, oenv);
486 /* Now do the actual printing. Metadata in first subblock is treated separately. */
487 fprintf(fpAwh, "# AWH metadata: target error = %.2f kT = %.2f kJ/mol\n",
488 block.sub[subStart].fval[1], block.sub[subStart].fval[1] * kT_);
490 fprintf(fpAwh, "# AWH metadata: log sample weight = %4.2f\n", block.sub[subStart].fval[2]);
492 awhOutputFile.writeData(block, subStart, fpAwh);
497 const OutputFile* frictionOutputFile = setup.frictionOutputFile();
498 if (frictionOutputFile != nullptr)
500 FILE* fpFriction = frictionOutputFile->openBiasOutputFile(time, oenv);
502 frictionOutputFile->writeData(block, subStart, fpFriction);
504 gmx_ffclose(fpFriction);
509 /*! \brief The main function for the AWH tool */
510 int gmx_awh(int argc, char* argv[])
512 const char* desc[] = { "[THISMODULE] extracts AWH data from an energy file.",
513 "One or two files are written per AWH bias per time frame.",
514 "The bias index, if more than one, is appended to the file, as well as",
515 "the time of the frame. By default only the PMF is printed.",
516 "With [TT]-more[tt] the bias, target and coordinate distributions",
518 "With [TT]-more[tt] the bias, target and coordinate distributions",
519 "are also printed, as well as the metric sqrt(det(friction_tensor))",
520 "normalized such that the average is 1.",
521 "Option [TT]-fric[tt] prints all components of the friction tensor",
522 "to an additional set of files." };
523 static gmx_bool moreGraphs = FALSE;
525 static gmx_bool kTUnit = FALSE;
527 { "-skip", FALSE, etINT, { &skip }, "Skip number of frames between data points" },
528 { "-more", FALSE, etBOOL, { &moreGraphs }, "Print more output" },
533 "Print free energy output in units of kT instead of kJ/mol" }
538 gmx_enxnm_t* enm = nullptr;
541 gmx_output_env_t* oenv;
543 t_filenm fnm[] = { { efEDR, "-f", nullptr, ffREAD },
544 { efTPR, "-s", nullptr, ffREAD },
545 { efXVG, "-o", "awh", ffWRITE },
546 { efXVG, "-fric", "friction", ffOPTWR } };
547 const int nfile = asize(fnm);
548 if (!parse_common_args(&argc, argv, PCA_CAN_VIEW | PCA_CAN_BEGIN | PCA_CAN_END, nfile, fnm,
549 asize(pa), pa, asize(desc), desc, 0, nullptr, &oenv))
555 fp = open_enx(ftp2fn(efEDR, nfile, fnm), "r");
556 do_enxnms(fp, &nre, &enm);
558 /* We just need the AWH parameters from inputrec. These are used to initialize
559 the AWH reader when we have a frame to read later on. */
563 read_tpx(ftp2fn(efTPR, nfile, fnm), &ir, box, &natoms, nullptr, nullptr, &mtop);
567 gmx_fatal(FARGS, "No AWH data in %s\n", opt2fn("-f", nfile, fnm));
570 std::unique_ptr<AwhReader> awhReader;
572 /* Initiate counters */
574 int awhFrameCounter = 0;
578 haveFrame = do_enx(fp, frame);
580 bool useFrame = false;
582 t_enxblock* block = nullptr;
586 timeCheck = check_times(frame->t);
590 block = find_block_id_enxframe(frame, enxAWH, nullptr);
592 if (block != nullptr)
594 if ((skip == 0) || (awhFrameCounter % skip == 0))
605 /* We read a valid frame with an AWH block, so we can use it */
607 /* Currently we have the number of subblocks per AWH stored
608 * in the first subblock (we cannot get this directly from the tpr),
609 * so we have to read an AWH block before making the legends.
611 if (awhReader == nullptr)
613 AwhGraphSelection awhGraphSelection =
614 (moreGraphs ? AwhGraphSelection::All : AwhGraphSelection::Pmf);
615 EnergyUnit energyUnit = (kTUnit ? EnergyUnit::KT : EnergyUnit::KJPerMol);
616 awhReader = std::make_unique<AwhReader>(ir.awhParams, nfile, fnm, awhGraphSelection,
617 energyUnit, BOLTZ * ir.opts.ref_t[0], block);
620 awhReader->processAwhFrame(*block, frame->t, oenv);
622 } while (haveFrame && (timeCheck <= 0));
624 fprintf(stderr, "\n");