Add reading and writing to AWH module
[gromacs.git] / src / gromacs / gmxana / gmx_awh.cpp
bloba016f398dea8a624939b3e6b4f7b345d2ff4487b
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2013,2014,2015,2016,2017, 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.
36 /*! \internal \file
37 * \brief
38 * Tool for extracting AWH (Accelerated Weight Histogram) data from energy files.
40 * \author Viveca Lindahl
41 * \author Berk Hess
44 #include "gmxpre.h"
46 #include <cstdlib>
47 #include <cstring>
49 #include <algorithm>
50 #include <array>
51 #include <memory>
52 #include <string>
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/utility/arraysize.h"
68 #include "gromacs/utility/basedefinitions.h"
69 #include "gromacs/utility/fatalerror.h"
70 #include "gromacs/utility/futil.h"
71 #include "gromacs/utility/gmxassert.h"
72 #include "gromacs/utility/smalloc.h"
73 #include "gromacs/utility/stringutil.h"
75 using gmx::AwhParams;
76 using gmx::AwhBiasParams;
78 namespace
81 //! Enum for choosing AWH graph output
82 enum class AwhGraphSelection
84 Pmf, //!< Only the PMF
85 All //!< All possible AWH graphs
88 //! Energy unit options
89 enum class EnergyUnit
91 KJPerMol, //!< kJ/mol
92 KT //!< kT
95 } // namespace
97 /*! \brief All meta-data that is shared for one output file type for one bias */
98 class OutputFile
100 public:
101 /*! \brief Constructor, Set the output base file name and title.
103 * Result is a valid object, but will produce empty output files.
105 OutputFile(const std::string filename,
106 const std::string baseTitle,
107 int numBias,
108 int biasIndex);
110 /*! \brief Initializes the output file setup for the AWH output.
112 void initializeAwhOutputFile(int subblockStart,
113 int numSubblocks,
114 const AwhBiasParams *awhBiasParams,
115 AwhGraphSelection graphSelection,
116 EnergyUnit energyUnit,
117 real kTValue);
119 /*! \brief Opens a single output file for a bias, prints title and legends.
121 * \param[in] time The time for of frame to be written.
122 * \param[in] oenv The output environment.
123 * \returns the file pointer.
125 FILE * openBiasOutputFile(double time,
126 const gmx_output_env_t *oenv) const;
128 /*! \brief Writes data selected from \p block to file.
130 * \param[in] block The energy block with the data to write.
131 * \param[in] subBlockStart The sub-block start index.
132 * \param[in] fp The file pointer.
134 void writeData(const t_enxblock &block,
135 int subBlockStart,
136 FILE *fp) const;
138 private:
139 std::string baseFilename_; /**< Base of the output file name. */
140 std::string title_; /**< Title for the graph. */
141 int numDim_; /**< Number of dimensions. */
142 int firstGraphSubblock_; /**< Index in the energy sub-blocks for the first graph. */
143 int numGraph_; /**< Number of actual graphs. */
144 bool useKTForEnergy_; /**< Whether to use kT instead of kJ/mol for energy. */
145 std::vector<real> scaleFactor_; /**< Scale factors from energy file data to output for each of the numGraph quantities. */
147 std::vector<std::string> legend_; /**< Legends for the output */
148 std::string xLabel_; /**< Label for the x-axis. */
149 std::string yLabel_; /**< Label for the y-axis. */
152 /*! \brief All meta-data that is shared for all output files for one bias */
153 class BiasReader
155 public:
156 //! Constructor.
157 BiasReader(int subblockStart,
158 int numSubblocks,
159 std::unique_ptr<OutputFile> awhOutputFile) :
160 subblockStart_(subblockStart),
161 numSubblocks_(numSubblocks),
162 awhOutputFile_(std::move(awhOutputFile))
166 //! Return the AWH output file data.
167 const OutputFile &awhOutputFile() const
169 return *awhOutputFile_.get();
172 //! Return the starting subblock.
173 int subblockStart() const
175 return subblockStart_;
178 private:
179 const int subblockStart_; /**< The start index of the subblocks to read. */
180 const int numSubblocks_; /**< Number of subblocks to read. */
181 std::unique_ptr<OutputFile> awhOutputFile_; /**< The standard AWH output file data. */
182 /* NOTE: A second OutputFile will be added soon, this will also make numSubblocks_ useful. */
185 /*! \brief All options and meta-data needed for the AWH output */
186 class AwhReader
188 public:
189 //! Constructor
190 AwhReader(const AwhParams *awhParams,
191 int numFileOptions,
192 const t_filenm *filenames,
193 AwhGraphSelection awhGraphSelection,
194 EnergyUnit energyUnit,
195 real kT,
196 const t_enxblock *block);
198 //! Extract and print AWH data for an AWH block of one energy frame
199 void processAwhFrame(const t_enxblock &block,
200 double time,
201 const gmx_output_env_t *oenv) const;
203 private:
204 std::vector<BiasReader> biasReader_; /**< The readers, one for each AWH bias. */
205 public:
206 const real kT_; /**< kB*T in kJ/mol. */
209 namespace
212 /* NOTE: A second value will be added soon. */
213 enum {
214 awhGraphTypeAwh
217 /*! \brief The maximum number of observables per subblock, not including the full friction tensor.
219 * This number, as well as the legends in make_legend() should match
220 * the output that mdrun writes. It would be better to define these
221 * values in a single location.
223 static constexpr int maxAwhGraphs = 5;
225 /*! \brief Constructs a legend for a standard awh output file */
226 std::vector<std::string>makeLegend(const AwhBiasParams *awhBiasParams,
227 int awhGraphType,
228 size_t numLegend)
230 const std::array<std::string, maxAwhGraphs> legendBase =
232 { "PMF",
233 "Coord bias",
234 "Coord distr",
235 "Ref value distr",
236 "Target ref value distr" }
239 std::vector<std::string> legend;
240 /* Give legends to dimensions higher than the first */
241 for (int d = 1; d < awhBiasParams->ndim; d++)
243 legend.push_back(gmx::formatString("dim%d", d));
246 switch (awhGraphType)
248 case awhGraphTypeAwh:
250 /* Add as many legends as possible from the "base" legend list */
251 size_t legendBaseIndex = 0;
252 while (legend.size() < numLegend && legendBaseIndex < legendBase.size())
254 legend.push_back(legendBase[legendBaseIndex]);
255 legendBaseIndex++;
258 break;
261 GMX_RELEASE_ASSERT(legend.size() == numLegend, "The number of legends requested for printing and the number generated should be the same");
263 return legend;
266 } // namespace
268 OutputFile::OutputFile(const std::string filename,
269 const std::string baseTitle,
270 int numBias,
271 int biasIndex) :
272 numDim_(0),
273 firstGraphSubblock_(0),
274 numGraph_(0),
275 useKTForEnergy_(0),
276 scaleFactor_(),
277 legend_(),
278 xLabel_(),
279 yLabel_()
281 // cppcheck-suppress useInitializationList
282 baseFilename_ = filename.substr(0, filename.find('.'));
283 title_ = baseTitle;
284 if (numBias > 1)
286 baseFilename_ += gmx::formatString("%d", biasIndex + 1);
287 title_ += gmx::formatString(" %d", biasIndex + 1);
291 void OutputFile::initializeAwhOutputFile(int subblockStart,
292 int numSubblocks,
293 const AwhBiasParams *awhBiasParams,
294 AwhGraphSelection graphSelection,
295 EnergyUnit energyUnit,
296 real kTValue)
298 /* The first subblock with actual graph y-values is index 1 + ndim */
299 numDim_ = awhBiasParams->ndim;
300 firstGraphSubblock_ = subblockStart + 1 + numDim_;
301 if (graphSelection == AwhGraphSelection::All)
303 /* There are one metadata and ndim coordinate blocks */
304 numGraph_ = std::min(numSubblocks - 1 - numDim_,
305 maxAwhGraphs);
307 else
309 numGraph_ = 1;
311 useKTForEnergy_ = (energyUnit == EnergyUnit::KT);
312 scaleFactor_.resize(numGraph_, 1);
313 if (!useKTForEnergy_)
315 /* The first two graphs are in units of energy, multiply by kT */
316 std::fill(scaleFactor_.begin(), scaleFactor_.begin() + std::min(2, numGraph_), kTValue);
318 int numLegend = numDim_ - 1 + numGraph_;
319 legend_ = makeLegend(awhBiasParams, awhGraphTypeAwh, numLegend);
320 /* We could have both length and angle coordinates in a single bias */
321 xLabel_ = "(nm or deg)";
322 yLabel_ = useKTForEnergy_ ? "(k\\sB\\NT)" : "(kJ/mol)";
323 if (graphSelection == AwhGraphSelection::All)
325 yLabel_ += gmx::formatString(", (nm\\S-%d\\N or rad\\S-%d\\N), (-)",
326 awhBiasParams->ndim, awhBiasParams->ndim);
330 AwhReader::AwhReader(const AwhParams *awhParams,
331 int numFileOptions,
332 const t_filenm *filenames,
333 AwhGraphSelection awhGraphSelection,
334 EnergyUnit energyUnit,
335 real kT,
336 const t_enxblock *block) :
337 kT_(kT)
339 GMX_RELEASE_ASSERT(block != nullptr, "NULL pointer passed to initAwhReader");
341 /* The first subblock of each AWH block has metadata about
342 * the number of subblocks belonging to that AWH block.
343 * (block->nsub gives only the total number of subblocks and not how
344 * they are distributed between the AWH biases).
347 /* Keep track of the first subblock of this AWH */
348 int subblockStart = 0;
349 for (int k = 0; k < awhParams->numBias; k++)
351 AwhBiasParams *awhBiasParams = &awhParams->awhBiasParams[k];
353 int numSubblocks = (int)block->sub[subblockStart].fval[0];
355 std::unique_ptr<OutputFile> outputFileAwh(new OutputFile(opt2fn("-o", numFileOptions, filenames), "AWH", awhParams->numBias, k));
357 outputFileAwh->initializeAwhOutputFile(subblockStart, numSubblocks,
358 awhBiasParams, awhGraphSelection,
359 energyUnit, kT);
361 biasReader_.emplace_back(BiasReader(subblockStart, numSubblocks, std::move(outputFileAwh)));
363 subblockStart += numSubblocks;
367 FILE * OutputFile::openBiasOutputFile(double time,
368 const gmx_output_env_t *oenv) const
370 std::string filename = baseFilename_ + gmx::formatString("_t%g.xvg", time);
372 FILE *fp = xvgropen(filename.c_str(), title_.c_str(), xLabel_, yLabel_, oenv);
373 xvgrLegend(fp, legend_, oenv);
375 return fp;
378 /*! \brief Prints data selected by \p outputFile from \p block to \p fp */
379 void OutputFile::writeData(const t_enxblock &block,
380 int subBlockStart,
381 FILE *fp) const
383 int numPoints = block.sub[subBlockStart + 1].nr;
384 for (int j = 0; j < numPoints; j++)
386 /* Print the coordinates for numDim dimensions */
387 for (int d = 0; d < numDim_; d++)
389 fprintf(fp, " %8.4f", block.sub[subBlockStart + 1 + d].fval[j]);
392 /* Print numGraph observables */
393 for (int i = 0; i < numGraph_; i++)
395 fprintf(fp, " %g", block.sub[firstGraphSubblock_ + i].fval[j]*scaleFactor_[i]);
398 fprintf(fp, "\n");
402 void AwhReader::processAwhFrame(const t_enxblock &block,
403 double time,
404 const gmx_output_env_t *oenv) const
406 /* We look for AWH data every energy frame and count the no of AWH frames found. We only extract every 'skip' AWH frame. */
408 for (auto &biasReader : biasReader_)
410 /* Each frame and AWH instance extracted generates one xvg file. */
412 const OutputFile &awhOutputFile = biasReader.awhOutputFile();
414 const int subStart = biasReader.subblockStart();
416 FILE *fpAwh = awhOutputFile.openBiasOutputFile(time, oenv);
418 /* Now do the actual printing. Metadata in first subblock is treated separately. */
419 fprintf(fpAwh, "# AWH metadata: target error = %.2f kT = %.2f kJ/mol\n",
420 block.sub[subStart].fval[1],
421 block.sub[subStart].fval[1]*kT_);
423 fprintf(fpAwh, "# AWH metadata: log sample weight = %4.2f\n",
424 block.sub[subStart].fval[2]);
426 awhOutputFile.writeData(block, subStart, fpAwh);
428 gmx_ffclose(fpAwh);
433 /*! \brief The main function for the AWH tool */
434 int gmx_awh(int argc, char *argv[])
436 const char *desc[] = {
437 "[THISMODULE] extracts AWH data from an energy file.",
438 "One file is written per AWH bias per time frame.",
439 "The bias index, if more than one, is appended to the file, as well as",
440 "the time of the frame. By default only the PMF is printed.",
441 "With [TT]-more[tt] the bias, target and coordinate distributions",
442 "are also printed.",
444 static gmx_bool moreGraphs = FALSE;
445 static int skip = 0;
446 static gmx_bool kTUnit = FALSE;
447 t_pargs pa[] = {
448 { "-skip", FALSE, etINT, {&skip},
449 "Skip number of frames between data points" },
450 { "-more", FALSE, etBOOL, {&moreGraphs},
451 "Print more output" },
452 { "-kt", FALSE, etBOOL, {&kTUnit},
453 "Print free energy output in units of kT instead of kJ/mol" }
456 ener_file_t fp;
457 t_inputrec ir;
458 gmx_enxnm_t *enm = nullptr;
459 t_enxframe *frame;
460 int nre;
461 gmx_output_env_t *oenv;
463 t_filenm fnm[] = {
464 { efEDR, "-f", nullptr, ffREAD },
465 { efTPR, "-s", nullptr, ffREAD },
466 { efXVG, "-o", "awh", ffWRITE }
468 const int nfile = asize(fnm);
469 if (!parse_common_args(&argc, argv,
470 PCA_CAN_VIEW | PCA_CAN_BEGIN | PCA_CAN_END,
471 nfile, fnm, asize(pa), pa, asize(desc), desc, 0, nullptr, &oenv))
473 return 0;
476 snew(frame, 1);
477 fp = open_enx(ftp2fn(efEDR, nfile, fnm), "r");
478 do_enxnms(fp, &nre, &enm);
480 /* We just need the AWH parameters from inputrec. These are used to initialize
481 the AWH reader when we have a frame to read later on. */
482 gmx_mtop_t mtop;
483 int natoms;
484 matrix box;
485 read_tpx(ftp2fn(efTPR, nfile, fnm), &ir, box, &natoms, nullptr, nullptr, &mtop);
487 if (!ir.bDoAwh)
489 gmx_fatal(FARGS, "No AWH data in %s\n", opt2fn("-f", nfile, fnm));
492 std::unique_ptr<AwhReader> awhReader;
494 /* Initiate counters */
495 gmx_bool haveFrame;
496 int awhFrameCounter = 0;
497 int timeCheck = 0;
500 haveFrame = do_enx(fp, frame);
502 bool useFrame = false;
504 t_enxblock *block = nullptr;
506 if (haveFrame)
508 timeCheck = check_times(frame->t);
510 if (timeCheck == 0)
512 block = find_block_id_enxframe(frame, enxAWH, nullptr);
514 if (block != nullptr)
516 if ((skip == 0) || (awhFrameCounter % skip == 0))
518 useFrame = true;
520 awhFrameCounter++;
525 if (useFrame)
527 /* We read a valid frame with an AWH block, so we can use it */
529 /* Currently we have the number of subblocks per AWH stored
530 * in the first subblock (we cannot get this directly from the tpr),
531 * so we have to read an AWH block before making the legends.
533 if (awhReader == nullptr)
535 AwhGraphSelection awhGraphSelection = (moreGraphs ? AwhGraphSelection::All : AwhGraphSelection::Pmf);
536 EnergyUnit energyUnit = (kTUnit ? EnergyUnit::KT : EnergyUnit::KJPerMol);
537 awhReader =
538 std::unique_ptr<AwhReader>(new AwhReader(ir.awhParams,
539 nfile, fnm,
540 awhGraphSelection,
541 energyUnit, BOLTZ*ir.opts.ref_t[0],
542 block));
545 awhReader->processAwhFrame(*block, frame->t, oenv);
548 while (haveFrame && (timeCheck <= 0));
550 fprintf(stderr, "\n");
552 close_enx(fp);
554 return 0;