2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5 * Copyright (c) 2001-2004, The GROMACS development team.
6 * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
7 * Copyright (c) 2018,2019,2020, by the GROMACS development team, led by
8 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
9 * and including many others, as listed in the AUTHORS file in the
10 * top-level source directory and at http://www.gromacs.org.
12 * GROMACS is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 2.1
15 * of the License, or (at your option) any later version.
17 * GROMACS is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with GROMACS; if not, see
24 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
25 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 * If you want to redistribute modifications to GROMACS, please
28 * consider that scientific software is very special. Version
29 * control is crucial - bugs must be traceable. We will be happy to
30 * consider code for inclusion in the official distribution, but
31 * derived work must not be called official GROMACS. Details are found
32 * in the README & COPYING files - if they are missing, get the
33 * official version at http://www.gromacs.org.
35 * To help us fund GROMACS development, we humbly ask that you cite
36 * the research papers on the package. Check out http://www.gromacs.org.
46 #include "gromacs/commandline/filenm.h"
47 #include "gromacs/fileio/gmxfio.h"
48 #include "gromacs/fileio/xvgr.h"
49 #include "gromacs/math/vec.h"
50 #include "gromacs/mdrunutility/handlerestart.h"
51 #include "gromacs/mdtypes/mdrunoptions.h"
52 #include "gromacs/mdtypes/observableshistory.h"
53 #include "gromacs/mdtypes/pullhistory.h"
54 #include "gromacs/pulling/pull.h"
55 #include "gromacs/utility/cstringutil.h"
56 #include "gromacs/utility/exceptions.h"
57 #include "gromacs/utility/fatalerror.h"
58 #include "gromacs/utility/gmxassert.h"
60 #include "pull_internal.h"
62 static std::string append_before_extension(const std::string& pathname, const std::string& to_append)
64 /* Appends to_append before last '.' in pathname */
65 size_t extPos = pathname.find_last_of('.');
66 if (extPos == std::string::npos)
68 return pathname + to_append;
72 return pathname.substr(0, extPos) + to_append + pathname.substr(extPos, std::string::npos);
76 static void addToPullxHistory(pull_t* pull)
78 GMX_RELEASE_ASSERT(pull->coordForceHistory, "Pull history does not exist.");
79 pull->coordForceHistory->numValuesInXSum++;
80 for (size_t c = 0; c < pull->coord.size(); c++)
82 const pull_coord_work_t& pcrd = pull->coord[c];
83 PullCoordinateHistory& pcrdHistory = pull->coordForceHistory->pullCoordinateSums[c];
85 pcrdHistory.value += pcrd.spatialData.value;
86 pcrdHistory.valueRef += pcrd.value_ref;
88 for (int m = 0; m < DIM; m++)
90 pcrdHistory.dr01[m] += pcrd.spatialData.dr01[m];
91 pcrdHistory.dr23[m] += pcrd.spatialData.dr23[m];
92 pcrdHistory.dr45[m] += pcrd.spatialData.dr45[m];
94 if (pcrd.params.eGeom == epullgCYL)
96 for (int m = 0; m < DIM; m++)
98 pcrdHistory.dynaX[m] += pull->dyna[c].x[m];
102 for (size_t g = 0; g < pull->group.size(); g++)
104 PullGroupHistory& pgrpHistory = pull->coordForceHistory->pullGroupSums[g];
105 for (int m = 0; m < DIM; m++)
107 pgrpHistory.x[m] += pull->group[g].x[m];
112 static void addToPullfHistory(pull_t* pull)
114 GMX_RELEASE_ASSERT(pull->coordForceHistory, "Pull history does not exist.");
115 pull->coordForceHistory->numValuesInFSum++;
116 for (size_t c = 0; c < pull->coord.size(); c++)
118 const pull_coord_work_t& pcrd = pull->coord[c];
120 PullCoordinateHistory& pcrdHistory = pull->coordForceHistory->pullCoordinateSums[c];
122 pcrdHistory.scalarForce += pcrd.scalarForce;
126 static void pullResetHistory(PullHistory* history, bool resetXHistory, bool resetFHistory)
130 history->numValuesInXSum = 0;
132 for (PullCoordinateHistory& pcrdHistory : history->pullCoordinateSums)
134 pcrdHistory.value = 0;
135 pcrdHistory.valueRef = 0;
137 clear_dvec(pcrdHistory.dr01);
138 clear_dvec(pcrdHistory.dr23);
139 clear_dvec(pcrdHistory.dr45);
140 clear_dvec(pcrdHistory.dynaX);
143 for (PullGroupHistory& pgrpHistory : history->pullGroupSums)
145 clear_dvec(pgrpHistory.x);
150 history->numValuesInFSum = 0;
151 for (PullCoordinateHistory& pcrdHistory : history->pullCoordinateSums)
153 pcrdHistory.scalarForce = 0;
158 static void pull_print_coord_dr_components(FILE* out, const ivec dim, const dvec dr, const int numValuesInSum)
160 for (int m = 0; m < DIM; m++)
164 fprintf(out, "\t%g", dr[m] / numValuesInSum);
170 static void pull_print_coord_dr(FILE* out,
171 const pull_params_t& pullParams,
172 const t_pull_coord& coordParams,
174 double referenceValue,
175 const int numValuesInSum)
177 const double unit_factor = pull_conversion_factor_internal2userinput(&coordParams);
179 fprintf(out, "\t%g", pcrdData.value * unit_factor / numValuesInSum);
181 if (pullParams.bPrintRefValue && coordParams.eType != epullEXTERNAL)
183 fprintf(out, "\t%g", referenceValue * unit_factor / numValuesInSum);
186 if (pullParams.bPrintComp)
188 pull_print_coord_dr_components(out, coordParams.dim, pcrdData.dr01, numValuesInSum);
189 if (coordParams.ngroup >= 4)
191 pull_print_coord_dr_components(out, coordParams.dim, pcrdData.dr23, numValuesInSum);
193 if (coordParams.ngroup >= 6)
195 pull_print_coord_dr_components(out, coordParams.dim, pcrdData.dr45, numValuesInSum);
200 static void pull_print_x(FILE* out, pull_t* pull, double t)
202 fprintf(out, "%.4f", t);
204 for (size_t c = 0; c < pull->coord.size(); c++)
206 const pull_coord_work_t& pcrd = pull->coord[c];
207 int numValuesInSum = 1;
208 const PullCoordinateHistory* pcrdHistory = nullptr;
210 if (pull->bXOutAverage)
212 pcrdHistory = &pull->coordForceHistory->pullCoordinateSums[c];
214 numValuesInSum = pull->coordForceHistory->numValuesInXSum;
215 pull_print_coord_dr(out, pull->params, pcrd.params, *pcrdHistory, pcrdHistory->valueRef,
220 pull_print_coord_dr(out, pull->params, pcrd.params, pcrd.spatialData, pcrd.value_ref,
224 if (pull->params.bPrintCOM)
226 if (pcrd.params.eGeom == epullgCYL)
228 for (int m = 0; m < DIM; m++)
230 if (pcrd.params.dim[m])
232 /* This equates to if (pull->bXOutAverage) */
235 fprintf(out, "\t%g", pcrdHistory->dynaX[m] / numValuesInSum);
239 fprintf(out, "\t%g", pull->dyna[c].x[m]);
246 for (int m = 0; m < DIM; m++)
248 if (pcrd.params.dim[m])
250 if (pull->bXOutAverage)
253 pull->coordForceHistory->pullGroupSums[pcrd.params.group[0]].x[m]
258 fprintf(out, "\t%g", pull->group[pcrd.params.group[0]].x[m]);
263 for (int g = 1; g < pcrd.params.ngroup; g++)
265 for (int m = 0; m < DIM; m++)
267 if (pcrd.params.dim[m])
269 if (pull->bXOutAverage)
272 pull->coordForceHistory->pullGroupSums[pcrd.params.group[g]].x[m]
277 fprintf(out, "\t%g", pull->group[pcrd.params.group[g]].x[m]);
286 if (pull->bXOutAverage)
288 pullResetHistory(pull->coordForceHistory, TRUE, FALSE);
292 static void pull_print_f(FILE* out, const pull_t* pull, double t)
294 fprintf(out, "%.4f", t);
296 if (pull->bFOutAverage)
298 for (size_t c = 0; c < pull->coord.size(); c++)
301 pull->coordForceHistory->pullCoordinateSums[c].scalarForce
302 / pull->coordForceHistory->numValuesInFSum);
307 for (const pull_coord_work_t& coord : pull->coord)
309 fprintf(out, "\t%g", coord.scalarForce);
314 if (pull->bFOutAverage)
316 pullResetHistory(pull->coordForceHistory, FALSE, TRUE);
320 void pull_print_output(struct pull_t* pull, int64_t step, double time)
322 GMX_ASSERT(pull->numExternalPotentialsStillToBeAppliedThisStep == 0,
323 "pull_print_output called before all external pull potentials have been applied");
325 if (pull->params.nstxout != 0)
327 /* Do not update the average if the number of observations already equal (or are
328 * higher than) what should be in each average output. This can happen when
329 * appending to a file from a checkpoint, which would otherwise include the
330 * last value twice.*/
331 if (pull->bXOutAverage && !pull->coord.empty()
332 && pull->coordForceHistory->numValuesInXSum < pull->params.nstxout)
334 addToPullxHistory(pull);
336 if (step % pull->params.nstxout == 0)
338 pull_print_x(pull->out_x, pull, time);
342 if (pull->params.nstfout != 0)
344 /* Do not update the average if the number of observations already equal (or are
345 * higher than) what should be in each average output. This can happen when
346 * appending to a file from a checkpoint, which would otherwise include the
347 * last value twice.*/
348 if (pull->bFOutAverage && !pull->coord.empty()
349 && pull->coordForceHistory->numValuesInFSum < pull->params.nstfout)
351 addToPullfHistory(pull);
353 if (step % pull->params.nstfout == 0)
355 pull_print_f(pull->out_f, pull, time);
360 static void set_legend_for_coord_components(const pull_coord_work_t* pcrd,
365 /* Loop over the distance vectors and print their components. Each vector is made up of two consecutive groups. */
366 for (int g = 0; g < pcrd->params.ngroup; g += 2)
368 /* Loop over the components */
369 for (int m = 0; m < DIM; m++)
371 if (pcrd->params.dim[m])
375 if (g == 0 && pcrd->params.ngroup <= 2)
377 /* For the simplest case we print a simplified legend without group indices,
378 just the cooordinate index and which dimensional component it is. */
379 sprintf(legend, "%d d%c", coord_index + 1, 'X' + m);
383 /* Otherwise, print also the group indices (relative to the coordinate, not the global ones). */
384 sprintf(legend, "%d g %d-%d d%c", coord_index + 1, g + 1, g + 2, 'X' + m);
387 setname[*nsets_ptr] = gmx_strdup(legend);
394 static FILE* open_pull_out(const char* fn,
396 const gmx_output_env_t* oenv,
398 const bool restartWithAppending)
402 char **setname, buf[50];
404 if (restartWithAppending)
406 fp = gmx_fio_fopen(fn, "a+");
410 fp = gmx_fio_fopen(fn, "w+");
413 sprintf(buf, "Position (nm%s)", pull->bAngle ? ", deg" : "");
414 if (pull->bXOutAverage)
416 xvgr_header(fp, "Pull Average COM", "Time (ps)", buf, exvggtXNY, oenv);
420 xvgr_header(fp, "Pull COM", "Time (ps)", buf, exvggtXNY, oenv);
425 sprintf(buf, "Force (kJ/mol/nm%s)", pull->bAngle ? ", kJ/mol/rad" : "");
426 if (pull->bFOutAverage)
428 xvgr_header(fp, "Pull Average force", "Time (ps)", buf, exvggtXNY, oenv);
432 xvgr_header(fp, "Pull force", "Time (ps)", buf, exvggtXNY, oenv);
436 /* With default mdp options only the actual coordinate value is printed (1),
437 * but optionally the reference value (+ 1),
438 * the group COMs for all the groups (+ ngroups_max*DIM)
439 * and the components of the distance vectors can be printed (+ (ngroups_max/2)*DIM).
441 snew(setname, pull->coord.size()
442 * (1 + 1 + c_pullCoordNgroupMax * DIM + c_pullCoordNgroupMax / 2 * DIM));
445 for (size_t c = 0; c < pull->coord.size(); c++)
449 /* The order of this legend should match the order of printing
450 * the data in print_pull_x above.
453 /* The pull coord distance */
454 sprintf(buf, "%zu", c + 1);
455 setname[nsets] = gmx_strdup(buf);
457 if (pull->params.bPrintRefValue && pull->coord[c].params.eType != epullEXTERNAL)
459 sprintf(buf, "%zu ref", c + 1);
460 setname[nsets] = gmx_strdup(buf);
463 if (pull->params.bPrintComp)
465 set_legend_for_coord_components(&pull->coord[c], c, setname, &nsets);
468 if (pull->params.bPrintCOM)
470 for (int g = 0; g < pull->coord[c].params.ngroup; g++)
472 /* Legend for reference group position */
473 for (m = 0; m < DIM; m++)
475 if (pull->coord[c].params.dim[m])
477 sprintf(buf, "%zu g %d %c", c + 1, g + 1, 'X' + m);
478 setname[nsets] = gmx_strdup(buf);
487 /* For the pull force we always only use one scalar */
488 sprintf(buf, "%zu", c + 1);
489 setname[nsets] = gmx_strdup(buf);
495 xvgr_legend(fp, nsets, setname, oenv);
497 for (int c = 0; c < nsets; c++)
507 void init_pull_output_files(pull_t* pull,
509 const t_filenm fnm[],
510 const gmx_output_env_t* oenv,
511 const gmx::StartingBehavior startingBehavior)
513 /* Check for px and pf filename collision, if we are writing
515 std::string px_filename, pf_filename;
516 std::string px_appended, pf_appended;
519 px_filename = std::string(opt2fn("-px", nfile, fnm));
520 pf_filename = std::string(opt2fn("-pf", nfile, fnm));
522 GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR
524 bool restartWithAppending = startingBehavior == gmx::StartingBehavior::RestartWithAppending;
525 if ((pull->params.nstxout != 0) && (pull->params.nstfout != 0) && (px_filename == pf_filename))
527 if (!opt2bSet("-px", nfile, fnm) && !opt2bSet("-pf", nfile, fnm))
529 /* We are writing both pull files but neither set directly. */
532 px_appended = append_before_extension(px_filename, "_pullx");
533 pf_appended = append_before_extension(pf_filename, "_pullf");
535 GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR
536 pull->out_x = open_pull_out(px_appended.c_str(), pull, oenv, TRUE, restartWithAppending);
537 pull->out_f = open_pull_out(pf_appended.c_str(), pull, oenv, FALSE, restartWithAppending);
542 /* If at least one of -px and -pf is set but the filenames are identical: */
543 gmx_fatal(FARGS, "Identical pull_x and pull_f output filenames %s", px_filename.c_str());
546 if (pull->params.nstxout != 0)
548 pull->out_x = open_pull_out(opt2fn("-px", nfile, fnm), pull, oenv, TRUE, restartWithAppending);
550 if (pull->params.nstfout != 0)
552 pull->out_f = open_pull_out(opt2fn("-pf", nfile, fnm), pull, oenv, FALSE, restartWithAppending);
556 void initPullHistory(pull_t* pull, ObservablesHistory* observablesHistory)
558 GMX_RELEASE_ASSERT(pull, "Need a valid pull object");
560 if (observablesHistory == nullptr)
562 pull->coordForceHistory = nullptr;
565 /* If pull->coordForceHistory is already set we are starting from a checkpoint. Do not reset it. */
566 if (observablesHistory->pullHistory == nullptr)
568 observablesHistory->pullHistory = std::make_unique<PullHistory>();
569 pull->coordForceHistory = observablesHistory->pullHistory.get();
570 pull->coordForceHistory->numValuesInXSum = 0;
571 pull->coordForceHistory->numValuesInFSum = 0;
572 pull->coordForceHistory->pullCoordinateSums.resize(pull->coord.size());
573 pull->coordForceHistory->pullGroupSums.resize(pull->group.size());
577 pull->coordForceHistory = observablesHistory->pullHistory.get();