2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
5 * Copyright (c) 2018,2019,2020,2021, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
39 * Tool for extracting AWH (Accelerated Weight Histogram) data from energy files.
41 * \author Viveca Lindahl
55 #include "gromacs/applied_forces/awh/read_params.h"
56 #include "gromacs/commandline/pargs.h"
57 #include "gromacs/fileio/enxio.h"
58 #include "gromacs/fileio/gmxfio.h"
59 #include "gromacs/fileio/oenv.h"
60 #include "gromacs/fileio/tpxio.h"
61 #include "gromacs/fileio/trxio.h"
62 #include "gromacs/fileio/xvgr.h"
63 #include "gromacs/gmxana/gmx_ana.h"
64 #include "gromacs/math/units.h"
65 #include "gromacs/mdtypes/awh_params.h"
66 #include "gromacs/mdtypes/inputrec.h"
67 #include "gromacs/topology/mtop_util.h"
68 #include "gromacs/topology/topology.h"
69 #include "gromacs/trajectory/energyframe.h"
70 #include "gromacs/utility/arraysize.h"
71 #include "gromacs/utility/basedefinitions.h"
72 #include "gromacs/utility/fatalerror.h"
73 #include "gromacs/utility/futil.h"
74 #include "gromacs/utility/gmxassert.h"
75 #include "gromacs/utility/smalloc.h"
76 #include "gromacs/utility/stringutil.h"
78 using gmx::AwhBiasParams;
84 //! Enum for choosing AWH graph output
85 enum class AwhGraphSelection
87 Pmf, //!< Only the PMF
88 All //!< All possible AWH graphs
91 //! Energy unit options
100 /*! \brief All meta-data that is shared for one output file type for one bias */
104 /*! \brief Constructor, Set the output base file name and title.
106 * Result is a valid object, but will produce empty output files.
108 * \param[in] filename The name for output files, frame time, and possibly bias number, will be added per file/frame.
109 * \param[in] baseTitle The base title of the plot, the bias number might be added.
110 * \param[in] numBias The total number of AWH biases in the system.
111 * \param[in] biasIndex The index of this bias.
113 OutputFile(const std::string& filename, const std::string& baseTitle, int numBias, int biasIndex);
115 /*! \brief Initializes the output file setup for the AWH output.
117 * \param[in] subBlockStart Index of the first sub-block to write in the energy frame.
118 * \param[in] numSubBlocks The total number of sub-blocks in the framw.
119 * \param[in] awhBiasParams The AWH bias parameters.
120 * \param[in] graphSelection Selects which graphs to plot.
121 * \param[in] energyUnit Requested energy unit in output.
122 * \param[in] kTValue kB*T in kJ/mol.
124 void initializeAwhOutputFile(int subBlockStart,
126 const AwhBiasParams& awhBiasParams,
127 AwhGraphSelection graphSelection,
128 EnergyUnit energyUnit,
131 /*! \brief Initializes the output file setup for the fricion output.
133 * \param[in] subBlockStart Index of the first sub-block to write in the energy frame.
134 * \param[in] numSubBlocks The total number of sub-blocks in the framw.
135 * \param[in] awhBiasParams The AWH bias parameters.
136 * \param[in] energyUnit Requested energy unit in output.
137 * \param[in] kTValue kB*T in kJ/mol.
139 void initializeFrictionOutputFile(int subBlockStart,
141 const AwhBiasParams& awhBiasParams,
142 EnergyUnit energyUnit,
145 /*! \brief Opens a single output file for a bias, prints title and legends.
147 * \param[in] time The time for of frame to be written.
148 * \param[in] oenv The output environment.
149 * \returns the file pointer.
151 FILE* openBiasOutputFile(double time, const gmx_output_env_t* oenv) const;
153 /*! \brief Writes data selected from \p block to file.
155 * \param[in] block The energy block with the data to write.
156 * \param[in] subBlockStart The sub-block start index.
157 * \param[in] fp The file pointer.
159 void writeData(const t_enxblock& block, int subBlockStart, FILE* fp) const;
162 std::string baseFilename_; /**< Base of the output file name. */
163 std::string title_; /**< Title for the graph. */
164 int numDim_; /**< Number of dimensions. */
165 int firstGraphSubBlock_; /**< Index in the energy sub-blocks for the first graph. */
166 int numGraph_; /**< Number of actual graphs. */
167 bool useKTForEnergy_; /**< Whether to use kT instead of kJ/mol for energy. */
168 std::vector<real> scaleFactor_; /**< Scale factors from energy file data to output for each of the numGraph quantities. */
170 std::vector<std::string> legend_; /**< Legends for the output */
171 std::string xLabel_; /**< Label for the x-axis. */
172 std::string yLabel_; /**< Label for the y-axis. */
175 /*! \brief All meta-data that is shared for all output files for one bias */
176 struct BiasOutputSetup
179 BiasOutputSetup(int subblockStart,
180 std::unique_ptr<OutputFile> awhOutputFile,
181 std::unique_ptr<OutputFile> frictionOutputFile) :
182 subblockStart_(subblockStart),
183 awhOutputFile_(std::move(awhOutputFile)),
184 frictionOutputFile_(std::move(frictionOutputFile))
188 //! Return the AWH output file data.
189 const OutputFile& awhOutputFile() const
191 GMX_RELEASE_ASSERT(awhOutputFile_ != nullptr,
192 "awhOutputFile() called without initialized AWH output file");
194 return *awhOutputFile_;
197 //! Return the a pointer to the friction output file data, can return nullptr
198 const OutputFile* frictionOutputFile() const { return frictionOutputFile_.get(); }
200 //! Return the starting subblock.
201 int subblockStart() const { return subblockStart_; }
204 const int subblockStart_; /**< The start index of the subblocks to read. */
205 std::unique_ptr<OutputFile> awhOutputFile_; /**< The standard AWH output file data. */
206 std::unique_ptr<OutputFile> frictionOutputFile_; /**< The friction/metric tensor output file data */
209 /*! \brief All options and meta-data needed for the AWH output */
214 AwhReader(const AwhParams& awhParams,
216 const t_filenm* filenames,
217 AwhGraphSelection awhGraphSelection,
218 EnergyUnit energyUnit,
220 const t_enxblock* block);
222 //! Extract and print AWH data for an AWH block of one energy frame
223 void processAwhFrame(const t_enxblock& block, double time, const gmx_output_env_t* oenv) const;
226 std::vector<BiasOutputSetup> biasOutputSetups_; /**< The output setups, one for each AWH bias. */
228 const real kT_; /**< kB*T in kJ/mol. */
234 //! Enum for selecting output file type.
235 enum class OutputFileType
237 Awh, //!< AWH, all data except friction tensor.
238 Friction //!< The full friction tensor.
241 /*! \brief The maximum number of observables per subblock, not including the full friction tensor.
243 * This number, as well as the legends in make_legend() should match
244 * the output that mdrun writes. It would be better to define these
245 * values in a single location.
247 constexpr int maxAwhGraphs = 6;
249 /*! \brief Constructs a legend for a standard awh output file */
250 std::vector<std::string> makeLegend(const AwhBiasParams& awhBiasParams,
251 OutputFileType outputFileType,
254 const std::array<std::string, maxAwhGraphs> legendBase = { { "PMF",
258 "Target ref value distr",
259 "Friction metric" } };
261 std::vector<std::string> legend;
262 /* Give legends to dimensions higher than the first */
263 for (int d = 1; d < awhBiasParams.ndim(); d++)
265 legend.push_back(gmx::formatString("dim%d", d));
268 switch (outputFileType)
270 case OutputFileType::Awh:
272 /* Add as many legends as possible from the "base" legend list */
273 size_t legendBaseIndex = 0;
274 while (legend.size() < numLegend && legendBaseIndex < legendBase.size())
276 legend.push_back(legendBase[legendBaseIndex]);
281 case OutputFileType::Friction:
282 for (int i0 = 0; i0 < awhBiasParams.ndim(); i0++)
284 for (int i1 = 0; i1 <= i0; i1++)
286 legend.push_back(gmx::formatString("%d,%d", i0, i1));
292 GMX_RELEASE_ASSERT(legend.size() == numLegend,
293 "The number of legends requested for printing and the number generated "
294 "should be the same");
301 OutputFile::OutputFile(const std::string& filename, const std::string& baseTitle, int numBias, int biasIndex) :
303 firstGraphSubBlock_(0),
305 useKTForEnergy_(false)
308 baseFilename_ = filename.substr(0, filename.find('.'));
312 baseFilename_ += gmx::formatString("%d", biasIndex + 1);
313 title_ += gmx::formatString(" %d", biasIndex + 1);
317 void OutputFile::initializeAwhOutputFile(int subblockStart,
319 const AwhBiasParams& awhBiasParams,
320 AwhGraphSelection graphSelection,
321 EnergyUnit energyUnit,
324 /* The first subblock with actual graph y-values is index 1 + ndim */
325 numDim_ = awhBiasParams.ndim();
326 firstGraphSubBlock_ = subblockStart + 1 + numDim_;
327 if (graphSelection == AwhGraphSelection::All)
329 /* There are one metadata and ndim coordinate blocks */
330 numGraph_ = std::min(numSubBlocks - 1 - numDim_, maxAwhGraphs);
336 useKTForEnergy_ = (energyUnit == EnergyUnit::KT);
337 scaleFactor_.resize(numGraph_, 1);
338 if (!useKTForEnergy_)
340 /* The first two graphs are in units of energy, multiply by kT */
341 std::fill(scaleFactor_.begin(), scaleFactor_.begin() + std::min(2, numGraph_), kTValue);
343 int numLegend = numDim_ - 1 + numGraph_;
344 legend_ = makeLegend(awhBiasParams, OutputFileType::Awh, numLegend);
345 /* We could have both length and angle coordinates in a single bias */
346 xLabel_ = "(nm, deg or lambda state)";
347 yLabel_ = useKTForEnergy_ ? "(k\\sB\\NT)" : "(kJ/mol)";
348 if (graphSelection == AwhGraphSelection::All)
350 yLabel_ += gmx::formatString(
351 ", (nm\\S-%d\\N or rad\\S-%d\\N), (-)", awhBiasParams.ndim(), awhBiasParams.ndim());
355 /*! \brief Initializes the output file setup for the fricion output (note that the filename is not set here). */
356 void OutputFile::initializeFrictionOutputFile(int subBlockStart,
358 const AwhBiasParams& awhBiasParams,
359 EnergyUnit energyUnit,
362 /* The first subblock with actual graph y-values is index 1 + ndim */
363 numDim_ = awhBiasParams.ndim();
364 int numTensorElements = (numDim_ * (numDim_ + 1)) / 2;
366 /* The friction tensor elements are always the last subblocks */
367 if (numSubBlocks < 1 + numDim_ + maxAwhGraphs + numTensorElements)
370 "You requested friction tensor output, but the AWH data in the energy file does "
371 "not contain the friction tensor");
373 GMX_ASSERT(numSubBlocks == 1 + numDim_ + maxAwhGraphs + numTensorElements,
374 "The number of sub-blocks per bias should be 1 + ndim + maxAwhGraphs + (ndim*(ndim "
377 firstGraphSubBlock_ = subBlockStart + numSubBlocks - numTensorElements;
378 numGraph_ = numTensorElements;
379 useKTForEnergy_ = (energyUnit == EnergyUnit::KT);
380 scaleFactor_.resize(numGraph_, useKTForEnergy_ ? 1 : kTValue);
381 int numLegend = numDim_ - 1 + numGraph_;
382 legend_ = makeLegend(awhBiasParams, OutputFileType::Friction, numLegend);
383 xLabel_ = "(nm, deg or lambda state)";
386 yLabel_ = "friction/k\\sB\\NT (nm\\S-2\\Nps, rad\\S-2\\Nps or ps)";
391 "friction (kJ mol\\S-1\\Nnm\\S-2\\Nps, kJ mol\\S-1\\Nrad\\S-2\\Nps or kJ "
396 AwhReader::AwhReader(const AwhParams& awhParams,
398 const t_filenm* filenames,
399 AwhGraphSelection awhGraphSelection,
400 EnergyUnit energyUnit,
402 const t_enxblock* block) :
405 GMX_RELEASE_ASSERT(block != nullptr, "NULL pointer passed to initAwhReader");
407 bool outputFriction = opt2bSet("-fric", numFileOptions, filenames);
409 /* The first subblock of each AWH block has metadata about
410 * the number of subblocks belonging to that AWH block.
411 * (block->nsub gives only the total number of subblocks and not how
412 * they are distributed between the AWH biases).
415 /* Keep track of the first subblock of this AWH */
416 int subblockStart = 0;
417 for (int k = 0; k < awhParams.numBias(); k++)
419 const AwhBiasParams& awhBiasParams = awhParams.awhBiasParams()[k];
421 int numSubBlocks = static_cast<int>(block->sub[subblockStart].fval[0]);
423 std::unique_ptr<OutputFile> awhOutputFile(new OutputFile(
424 opt2fn("-o", numFileOptions, filenames), "AWH", awhParams.numBias(), k));
426 awhOutputFile->initializeAwhOutputFile(
427 subblockStart, numSubBlocks, awhBiasParams, awhGraphSelection, energyUnit, kT);
429 std::unique_ptr<OutputFile> frictionOutputFile;
432 frictionOutputFile = std::make_unique<OutputFile>(
433 opt2fn("-fric", numFileOptions, filenames), "Friction tensor", awhParams.numBias(), k);
435 frictionOutputFile->initializeFrictionOutputFile(
436 subblockStart, numSubBlocks, awhBiasParams, energyUnit, kT);
439 biasOutputSetups_.emplace_back(BiasOutputSetup(
440 subblockStart, std::move(awhOutputFile), std::move(frictionOutputFile)));
442 subblockStart += numSubBlocks;
446 FILE* OutputFile::openBiasOutputFile(double time, const gmx_output_env_t* oenv) const
448 std::string filename = baseFilename_ + gmx::formatString("_t%g.xvg", time);
450 FILE* fp = xvgropen(filename.c_str(), title_.c_str(), xLabel_, yLabel_, oenv);
451 xvgrLegend(fp, legend_, oenv);
456 /*! \brief Prints data selected by \p outputFile from \p block to \p fp */
457 void OutputFile::writeData(const t_enxblock& block, int subBlockStart, FILE* fp) const
459 int numPoints = block.sub[subBlockStart + 1].nr;
460 for (int j = 0; j < numPoints; j++)
462 /* Print the coordinates for numDim dimensions */
463 for (int d = 0; d < numDim_; d++)
465 fprintf(fp, " %8.4f", block.sub[subBlockStart + 1 + d].fval[j]);
468 /* Print numGraph observables */
469 for (int i = 0; i < numGraph_; i++)
471 fprintf(fp, " %g", block.sub[firstGraphSubBlock_ + i].fval[j] * scaleFactor_[i]);
478 void AwhReader::processAwhFrame(const t_enxblock& block, double time, const gmx_output_env_t* oenv) const
480 /* We look for AWH data every energy frame and count the no of AWH frames found. We only extract every 'skip' AWH frame. */
482 for (const auto& setup : biasOutputSetups_)
484 const int subStart = setup.subblockStart();
486 /* Each frame and AWH instance extracted generates one xvg file. */
488 const OutputFile& awhOutputFile = setup.awhOutputFile();
490 FILE* fpAwh = awhOutputFile.openBiasOutputFile(time, oenv);
492 /* Now do the actual printing. Metadata in first subblock is treated separately. */
494 "# AWH metadata: target error = %.2f kT = %.2f kJ/mol\n",
495 block.sub[subStart].fval[1],
496 block.sub[subStart].fval[1] * kT_);
498 fprintf(fpAwh, "# AWH metadata: log sample weight = %4.2f\n", block.sub[subStart].fval[2]);
500 awhOutputFile.writeData(block, subStart, fpAwh);
505 const OutputFile* frictionOutputFile = setup.frictionOutputFile();
506 if (frictionOutputFile != nullptr)
508 FILE* fpFriction = frictionOutputFile->openBiasOutputFile(time, oenv);
510 frictionOutputFile->writeData(block, subStart, fpFriction);
512 gmx_ffclose(fpFriction);
517 /*! \brief The main function for the AWH tool */
518 int gmx_awh(int argc, char* argv[])
520 const char* desc[] = { "[THISMODULE] extracts AWH data from an energy file.",
521 "One or two files are written per AWH bias per time frame.",
522 "The bias index, if more than one, is appended to the file, as well as",
523 "the time of the frame. By default only the PMF is printed.",
524 "With [TT]-more[tt] the bias, target and coordinate distributions",
526 "With [TT]-more[tt] the bias, target and coordinate distributions",
527 "are also printed, as well as the metric sqrt(det(friction_tensor))",
528 "normalized such that the average is 1.",
529 "Option [TT]-fric[tt] prints all components of the friction tensor",
530 "to an additional set of files." };
531 static gmx_bool moreGraphs = FALSE;
533 static gmx_bool kTUnit = FALSE;
535 { "-skip", FALSE, etINT, { &skip }, "Skip number of frames between data points" },
536 { "-more", FALSE, etBOOL, { &moreGraphs }, "Print more output" },
541 "Print free energy output in units of kT instead of kJ/mol" }
546 gmx_enxnm_t* enm = nullptr;
549 gmx_output_env_t* oenv;
551 t_filenm fnm[] = { { efEDR, "-f", nullptr, ffREAD },
552 { efTPR, "-s", nullptr, ffREAD },
553 { efXVG, "-o", "awh", ffWRITE },
554 { efXVG, "-fric", "friction", ffOPTWR } };
555 const int nfile = asize(fnm);
556 if (!parse_common_args(&argc,
558 PCA_CAN_VIEW | PCA_CAN_BEGIN | PCA_CAN_END,
573 fp = open_enx(ftp2fn(efEDR, nfile, fnm), "r");
574 do_enxnms(fp, &nre, &enm);
576 /* We just need the AWH parameters from inputrec. These are used to initialize
577 the AWH reader when we have a frame to read later on. */
581 read_tpx(ftp2fn(efTPR, nfile, fnm), &ir, box, &natoms, nullptr, nullptr, &mtop);
585 gmx_fatal(FARGS, "No AWH data in %s\n", opt2fn("-f", nfile, fnm));
588 std::unique_ptr<AwhReader> awhReader;
590 /* Initiate counters */
592 int awhFrameCounter = 0;
596 haveFrame = do_enx(fp, frame);
598 bool useFrame = false;
600 t_enxblock* block = nullptr;
604 timeCheck = check_times(frame->t);
608 block = find_block_id_enxframe(frame, enxAWH, nullptr);
610 if (block != nullptr)
612 if ((skip == 0) || (awhFrameCounter % skip == 0))
623 /* We read a valid frame with an AWH block, so we can use it */
625 /* Currently we have the number of subblocks per AWH stored
626 * in the first subblock (we cannot get this directly from the tpr),
627 * so we have to read an AWH block before making the legends.
629 if (awhReader == nullptr)
631 AwhGraphSelection awhGraphSelection =
632 (moreGraphs ? AwhGraphSelection::All : AwhGraphSelection::Pmf);
633 EnergyUnit energyUnit = (kTUnit ? EnergyUnit::KT : EnergyUnit::KJPerMol);
634 awhReader = std::make_unique<AwhReader>(*ir.awhParams,
639 gmx::c_boltz * ir.opts.ref_t[0],
643 awhReader->processAwhFrame(*block, frame->t, oenv);
645 } while (haveFrame && (timeCheck <= 0));
647 fprintf(stderr, "\n");