Enable output of average pull force and positions.
authorMagnus Lundborg <lundborg.magnus@gmail.com>
Thu, 17 Nov 2016 15:48:57 +0000 (16:48 +0100)
committerMark Abraham <mark.j.abraham@gmail.com>
Mon, 15 Oct 2018 08:32:46 +0000 (10:32 +0200)
Write pull data to checkpoints to keep a proper average.
Add option whether to write instantaneous or average pull
coordinates and forces, respectively.

Fixes #2626

Change-Id: Id0268e00486028f497463dc13ae8d65a6c5df325

16 files changed:
docs/release-notes/features.rst
docs/user-guide/mdp-options.rst
src/gromacs/fileio/checkpoint.cpp
src/gromacs/fileio/tpxio.cpp
src/gromacs/gmxpreprocess/readpull.cpp
src/gromacs/mdrun/md.cpp
src/gromacs/mdrun/runner.cpp
src/gromacs/mdtypes/inputrec.cpp
src/gromacs/mdtypes/observableshistory.cpp
src/gromacs/mdtypes/observableshistory.h
src/gromacs/mdtypes/pull-params.h
src/gromacs/mdtypes/pullhistory.h [new file with mode: 0644]
src/gromacs/pulling/output.cpp
src/gromacs/pulling/output.h
src/gromacs/pulling/pull.cpp
src/gromacs/pulling/pull_internal.h

index beb0f27487797b9ee0c66a415e2bef309dceca7b..a5d0c3218b082821aede7bab5fe38d1c8f5a1118 100644 (file)
@@ -29,3 +29,11 @@ reference atom, which can sometimes move a lot during the simulation.
 With this option the PBC reference atom is only used at initialization.
 This can be of use when using large pull groups or groups with potentially
 large relative movement of atoms.
+
+Enable output of average pull forces and positions
+"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+
+Normally the pull module writes instantaneous output of positions and forces, however
+now it can write the average of these values over the period since the last output.
+This works correctly even if a checkpoint restart intervened. This is enabled using the
+new options ``pull-fout-average`` and ``pull-xout-average``.
index 8d2158dd82f7bc14cb2eb67294f834dd2b83d03f..987004474942c146367c8a602086e029bd111a56 100644 (file)
@@ -1637,6 +1637,30 @@ pull-coord2-vec, pull-coord2-k, and so on.
       be located centrally in the group. Using the COM from the
       previous step can be useful if one or more pull groups are large.
 
+.. mdp:: pull-xout-average
+
+   .. mdp-value:: no
+
+      Write the instantaneous coordinates for all the pulled groups.
+
+   .. mdp-value:: yes
+
+      Write the average coordinates (since last output) for all the
+      pulled groups. N.b., some analysis tools might expect instantaneous
+      pull output.
+
+.. mdp:: pull-fout-average
+
+   .. mdp-value:: no
+
+      Write the instantaneous force for all the pulled groups.
+
+   .. mdp-value:: yes
+
+      Write the average force (since last output) for all the
+      pulled groups. N.b., some analysis tools might expect instantaneous
+      pull output.
+
 .. mdp:: pull-ngroups
 
    (1)
index 9c37b116092fca9381ab2785b607269c96130bc6..2e15928db2224369f62010ab28496ee788dd18f1 100644 (file)
@@ -74,6 +74,7 @@
 #include "gromacs/mdtypes/inputrec.h"
 #include "gromacs/mdtypes/md_enums.h"
 #include "gromacs/mdtypes/observableshistory.h"
+#include "gromacs/mdtypes/pullhistory.h"
 #include "gromacs/mdtypes/state.h"
 #include "gromacs/mdtypes/swaphistory.h"
 #include "gromacs/trajectory/trajectoryframe.h"
@@ -110,6 +111,7 @@ enum cptv {
     cptv_Unknown = 17,                                       /**< Version before numbering scheme */
     cptv_RemoveBuildMachineInformation,                      /**< remove functionality that makes mdrun builds non-reproducible */
     cptv_ComPrevStepAsPullGroupReference,                    /**< Allow using COM of previous step as pull group PBC reference */
+    cptv_PullAverage,                                        /**< Added possibility to output average pull force and position */
     cptv_Count                                               /**< the total number of cptv versions */
 };
 
@@ -164,6 +166,21 @@ enum {
     eenhNR
 };
 
+enum {
+    epullhPULL_NUMCOORDINATES, epullhPULL_NUMGROUPS, epullhPULL_NUMVALUESINXSUM,
+    epullhPULL_NUMVALUESINFSUM,  epullhNR
+};
+
+enum {
+    epullcoordh_VALUE_REF_SUM, epullcoordh_VALUE_SUM, epullcoordh_DR01_SUM,
+    epullcoordh_DR23_SUM, epullcoordh_DR45_SUM, epullcoordh_FSCAL_SUM,
+    epullcoordh_DYNAX_SUM, epullcoordh_NR
+};
+
+enum {
+    epullgrouph_X_SUM, epullgrouph_NR
+};
+
 static const char *eenh_names[eenhNR] =
 {
     "energy_n", "energy_aver", "energy_sum", "energy_nsum",
@@ -175,6 +192,12 @@ static const char *eenh_names[eenhNR] =
     "energy_delta_h_start_lambda"
 };
 
+static const char *ePullhNames[epullhNR] =
+{
+    "pullhistory_numcoordinates", "pullhistory_numgroups", "pullhistory_numvaluesinxsum",
+    "pullhistory_numvaluesinfsum"
+};
+
 /* free energy history variables -- need to be preserved over checkpoint */
 enum {
     edfhBEQUIL, edfhNATLAMBDA, edfhWLHISTO, edfhWLDELTA, edfhSUMWEIGHTS, edfhSUMDG, edfhSUMMINVAR, edfhSUMVAR,
@@ -242,7 +265,8 @@ enum class StatePart
     energyHistory,      //!< Energy observable statistics
     freeEnergyHistory,  //!< Free-energy state and observable statistics
     accWeightHistogram, //!< Accelerated weight histogram method state
-    pullState           //!< COM of previous step.
+    pullState,          //!< COM of previous step.
+    pullHistory         //!< Pull history statistics (sums since last written output)
 };
 
 //! \brief Return the name of a checkpoint entry based on part and part entry
@@ -256,6 +280,7 @@ static const char *entryName(StatePart part, int ecpt)
         case StatePart::freeEnergyHistory:  return edfh_names[ecpt];
         case StatePart::accWeightHistogram: return eawhh_names[ecpt];
         case StatePart::pullState:          return epull_prev_step_com_names[ecpt];
+        case StatePart::pullHistory:        return ePullhNames[ecpt];
     }
 
     return nullptr;
@@ -694,12 +719,12 @@ static int doVectorLow(XDR *xd, StatePart part, int ecpt, int sflags,
     return 0;
 }
 
-//! \brief Read/Write a std::vector.
+//! \brief Read/Write a std::vector, on read checks the number of elements matches \p numElements, if specified.
 template <typename T>
 static int doVector(XDR *xd, StatePart part, int ecpt, int sflags,
-                    std::vector<T> *vector, FILE *list)
+                    std::vector<T> *vector, FILE *list, int numElements = -1)
 {
-    return doVectorLow<T>(xd, part, ecpt, sflags, -1, nullptr, nullptr, vector, list, CptElementType::real);
+    return doVectorLow<T>(xd, part, ecpt, sflags, numElements, nullptr, nullptr, vector, list, CptElementType::real);
 }
 
 //! \brief Read/Write an ArrayRef<real>.
@@ -956,6 +981,7 @@ struct CheckpointHeaderContents
     int         flags_state;
     int         flags_eks;
     int         flags_enh;
+    int         flagsPullHistory;
     int         flags_dfh;
     int         flags_awhh;
     int         nED;
@@ -1122,6 +1148,15 @@ static void do_cpt_header(XDR *xd, gmx_bool bRead, FILE *list,
     {
         contents->flags_awhh = 0;
     }
+
+    if (contents->file_version >= 18)
+    {
+        do_cpt_int_err(xd, "pull history flags", &contents->flagsPullHistory, list);
+    }
+    else
+    {
+        contents->flagsPullHistory = 0;
+    }
 }
 
 static int do_cpt_footer(XDR *xd, int file_version)
@@ -1477,6 +1512,140 @@ static int do_cpt_enerhist(XDR *xd, gmx_bool bRead,
     return ret;
 }
 
+static int doCptPullCoordHist(XDR *xd, PullCoordinateHistory *pullCoordHist,
+                              const StatePart part, FILE *list)
+{
+    int ret   = 0;
+    int flags = 0;
+
+    flags |= ((1<<epullcoordh_VALUE_REF_SUM) | (1<<epullcoordh_VALUE_SUM) | (1<<epullcoordh_DR01_SUM) |
+              (1<<epullcoordh_DR23_SUM) | (1<<epullcoordh_DR45_SUM) | (1<<epullcoordh_FSCAL_SUM));
+
+    for (int i = 0; i < epullcoordh_NR && ret == 0; i++)
+    {
+        switch (i)
+        {
+            case epullcoordh_VALUE_REF_SUM: ret = do_cpte_double(xd, part, i, flags, &(pullCoordHist->valueRef), list); break;
+            case epullcoordh_VALUE_SUM:     ret = do_cpte_double(xd, part, i, flags, &(pullCoordHist->value), list); break;
+            case epullcoordh_DR01_SUM:
+                for (int j = 0; j < DIM && ret == 0; j++)
+                {
+                    ret = do_cpte_double(xd, part, i, flags, &(pullCoordHist->dr01[j]), list);
+                }
+                break;
+            case epullcoordh_DR23_SUM:
+                for (int j = 0; j < DIM && ret == 0; j++)
+                {
+                    ret = do_cpte_double(xd, part, i, flags, &(pullCoordHist->dr23[j]), list);
+                }
+                break;
+            case epullcoordh_DR45_SUM:
+                for (int j = 0; j < DIM && ret == 0; j++)
+                {
+                    ret = do_cpte_double(xd, part, i, flags, &(pullCoordHist->dr45[j]), list);
+                }
+                break;
+            case epullcoordh_FSCAL_SUM:     ret = do_cpte_double(xd, part, i, flags, &(pullCoordHist->scalarForce), list); break;
+            case epullcoordh_DYNAX_SUM:
+                for (int j = 0; j < DIM && ret == 0; j++)
+                {
+                    ret = do_cpte_double(xd, part, i, flags, &(pullCoordHist->dynaX[j]), list);
+                }
+                break;
+        }
+    }
+
+    return ret;
+}
+
+static int doCptPullGroupHist(XDR *xd, PullGroupHistory *pullGroupHist,
+                              const StatePart part, FILE *list)
+{
+    int ret   = 0;
+    int flags = 0;
+
+    flags |= ((1<<epullgrouph_X_SUM));
+
+    for (int i = 0; i < epullgrouph_NR; i++)
+    {
+        switch (i)
+        {
+            case epullgrouph_X_SUM:
+                for (int j = 0; j < DIM && ret == 0; j++)
+                {
+                    ret = do_cpte_double(xd, part, i, flags, &(pullGroupHist->x[j]), list);
+                }
+                break;
+        }
+    }
+
+    return ret;
+}
+
+
+static int doCptPullHist(XDR *xd, gmx_bool bRead,
+                         int fflags, PullHistory *pullHist,
+                         const StatePart part,
+                         FILE *list)
+{
+    int ret                       = 0;
+    int pullHistoryNumCoordinates = 0;
+    int pullHistoryNumGroups      = 0;
+
+    /* Retain the number of terms in the sum and the number of coordinates (used for writing
+     * average pull forces and coordinates) in the pull history, in temporary variables,
+     * in case they cannot be read from the checkpoint, in order to have backward compatibility */
+    if (bRead)
+    {
+        pullHist->numValuesInXSum = 0;
+        pullHist->numValuesInFSum = 0;
+    }
+    else if (pullHist != nullptr)
+    {
+        pullHistoryNumCoordinates = pullHist->pullCoordinateSums.size();
+        pullHistoryNumGroups      = pullHist->pullGroupSums.size();
+    }
+    else
+    {
+        GMX_RELEASE_ASSERT(fflags == 0, "Without pull history, all flags should be off");
+    }
+
+    for (int i = 0; (i < epullhNR && ret == 0); i++)
+    {
+        if (fflags & (1<<i))
+        {
+            switch (i)
+            {
+                case epullhPULL_NUMCOORDINATES:         ret = do_cpte_int(xd, part, i, fflags, &pullHistoryNumCoordinates, list); break;
+                case epullhPULL_NUMGROUPS:              do_cpt_int_err(xd, eenh_names[i], &pullHistoryNumGroups, list); break;
+                case epullhPULL_NUMVALUESINXSUM:        do_cpt_int_err(xd, eenh_names[i], &pullHist->numValuesInXSum, list); break;
+                case epullhPULL_NUMVALUESINFSUM:        do_cpt_int_err(xd, eenh_names[i], &pullHist->numValuesInFSum, list); break;
+                default:
+                    gmx_fatal(FARGS, "Unknown pull history entry %d\n"
+                              "You are probably reading a new checkpoint file with old code", i);
+            }
+        }
+    }
+    if (bRead)
+    {
+        pullHist->pullCoordinateSums.resize(pullHistoryNumCoordinates);
+        pullHist->pullGroupSums.resize(pullHistoryNumGroups);
+    }
+    if (pullHist->numValuesInXSum > 0 || pullHist->numValuesInFSum > 0)
+    {
+        for (size_t i = 0; i < pullHist->pullCoordinateSums.size() && ret == 0; i++)
+        {
+            ret = doCptPullCoordHist(xd, &(pullHist->pullCoordinateSums[i]), part, list);
+        }
+        for (size_t i = 0; i < pullHist->pullGroupSums.size() && ret == 0; i++)
+        {
+            ret = doCptPullGroupHist(xd, &(pullHist->pullGroupSums[i]), part, list);
+        }
+    }
+
+    return ret;
+}
+
 static int do_cpt_df_hist(XDR *xd, int fflags, int nlambda, df_history_t **dfhistPtr, FILE *list)
 {
     int ret = 0;
@@ -1919,6 +2088,14 @@ void write_checkpoint(const char *fn, gmx_bool bNumberAndKeep,
         }
     }
 
+    PullHistory *pullHist         = observablesHistory->pullHistory.get();
+    int          flagsPullHistory = 0;
+    if (pullHist != nullptr && (pullHist->numValuesInXSum > 0 || pullHist->numValuesInFSum > 0))
+    {
+        flagsPullHistory |= (1<<epullhPULL_NUMCOORDINATES);
+        flagsPullHistory |= ((1<<epullhPULL_NUMGROUPS) | (1<<epullhPULL_NUMVALUESINXSUM) | (1<<epullhPULL_NUMVALUESINFSUM));
+    }
+
     int flags_dfh;
     if (bExpanded)
     {
@@ -1973,7 +2150,8 @@ void write_checkpoint(const char *fn, gmx_bool bNumberAndKeep,
         eIntegrator, simulation_part, step, t, nppnodes,
         {0}, npmenodes,
         state->natoms, state->ngtc, state->nnhpres,
-        state->nhchainlength, nlambda, state->flags, flags_eks, flags_enh, flags_dfh, flags_awhh,
+        state->nhchainlength, nlambda, state->flags, flags_eks, flags_enh,
+        flagsPullHistory, flags_dfh, flags_awhh,
         nED, eSwapCoords
     };
     std::strcpy(headerContents.version, gmx_version());
@@ -1989,6 +2167,7 @@ void write_checkpoint(const char *fn, gmx_bool bNumberAndKeep,
     if ((do_cpt_state(gmx_fio_getxdr(fp), state->flags, state, nullptr) < 0)        ||
         (do_cpt_ekinstate(gmx_fio_getxdr(fp), flags_eks, &state->ekinstate, nullptr) < 0) ||
         (do_cpt_enerhist(gmx_fio_getxdr(fp), FALSE, flags_enh, enerhist, nullptr) < 0)  ||
+        (doCptPullHist(gmx_fio_getxdr(fp), FALSE, flagsPullHistory, pullHist, StatePart::pullHistory, nullptr) < 0)  ||
         (do_cpt_df_hist(gmx_fio_getxdr(fp), flags_dfh, nlambda, &state->dfhist, nullptr) < 0)  ||
         (do_cpt_EDstate(gmx_fio_getxdr(fp), FALSE, nED, edsamhist, nullptr) < 0)      ||
         (do_cpt_awh(gmx_fio_getxdr(fp), FALSE, flags_awhh, state->awhHistory.get(), nullptr) < 0) ||
@@ -2410,6 +2589,20 @@ static void read_checkpoint(const char *fn, t_fileio *logfio,
         cp_error();
     }
 
+    if (headerContents->flagsPullHistory)
+    {
+        if (observablesHistory->pullHistory == nullptr)
+        {
+            observablesHistory->pullHistory = gmx::compat::make_unique<PullHistory>();
+        }
+        ret = doCptPullHist(gmx_fio_getxdr(fp), TRUE,
+                            headerContents->flagsPullHistory, observablesHistory->pullHistory.get(), StatePart::pullHistory, nullptr);
+        if (ret)
+        {
+            cp_error();
+        }
+    }
+
     if (headerContents->file_version < 6)
     {
         gmx_fatal(FARGS, "Continuing from checkpoint files written before GROMACS 4.5 is not supported");
@@ -2633,6 +2826,14 @@ static void read_checkpoint_data(t_fileio *fp, int *simulation_part,
     {
         cp_error();
     }
+    PullHistory pullHist = {};
+    ret = doCptPullHist(gmx_fio_getxdr(fp), TRUE,
+                        headerContents.flagsPullHistory, &pullHist, StatePart::pullHistory, nullptr);
+    if (ret)
+    {
+        cp_error();
+    }
+
     ret = do_cpt_df_hist(gmx_fio_getxdr(fp), headerContents.flags_dfh, headerContents.nlambda, &state->dfhist, nullptr);
     if (ret)
     {
@@ -2744,6 +2945,13 @@ void list_checkpoint(const char *fn, FILE *out)
     ret = do_cpt_enerhist(gmx_fio_getxdr(fp), TRUE,
                           headerContents.flags_enh, &enerhist, out);
 
+    if (ret == 0)
+    {
+        PullHistory pullHist = {};
+        ret = doCptPullHist(gmx_fio_getxdr(fp), TRUE,
+                            headerContents.flagsPullHistory, &pullHist, StatePart::pullHistory, out);
+    }
+
     if (ret == 0)
     {
         ret = do_cpt_df_hist(gmx_fio_getxdr(fp),
index 5e40fe03a9becf5d2b45e5746534bfa655706a62..8ce2b60ec27b767ed385e3a0d028c8438c781177 100644 (file)
@@ -122,6 +122,7 @@ enum tpxv {
     tpxv_RemoveImplicitSolvation,                            /**< removed support for implicit solvation */
     tpxv_PullPrevStepCOMAsReference,                         /**< Enabled using the COM of the pull group of the last frame as reference for PBC */
     tpxv_MimicQMMM,                                          /**< Inroduced support for MiMiC QM/MM interface */
+    tpxv_PullAverage,                                        /**< Added possibility to output average pull force and position */
     tpxv_Count                                               /**< the total number of tpxv versions */
 };
 
@@ -799,6 +800,17 @@ static void do_pull(t_fileio *fio, pull_params_t *pull, gmx_bool bRead,
                           bRead, file_version, ePullOld, eGeomOld, dimOld);
         }
     }
+    if (file_version >= tpxv_PullAverage)
+    {
+        gmx_bool v;
+
+        v                  = pull->bXOutAverage;
+        gmx_fio_do_gmx_bool(fio, v);
+        pull->bXOutAverage = v;
+        v                  = pull->bFOutAverage;
+        gmx_fio_do_gmx_bool(fio, v);
+        pull->bFOutAverage = v;
+    }
 }
 
 
index 943560aaf47d856fc2797fe9381a98b8619e31ef..f910615911fb9912b9a8fad5a165d1d32530131b 100644 (file)
@@ -287,6 +287,8 @@ char **read_pullparams(std::vector<t_inpfile> *inp,
     pull->nstxout                 = get_eint(inp, "pull-nstxout", 50, wi);
     pull->nstfout                 = get_eint(inp, "pull-nstfout", 50, wi);
     pull->bSetPbcRefToPrevStepCOM = (get_eeenum(inp, "pull-pbc-ref-prev-step-com", yesno_names, wi) != 0);
+    pull->bXOutAverage            = (get_eeenum(inp, "pull-xout-average", yesno_names, wi) != 0);
+    pull->bFOutAverage            = (get_eeenum(inp, "pull-fout-average", yesno_names, wi) != 0);
     printStringNoNewline(inp, "Number of pull groups");
     pull->ngroup = get_eint(inp, "pull-ngroups", 1, wi);
     printStringNoNewline(inp, "Number of pull coordinates");
index 06f1332407be0a98d3fc7ead5ab1432c19b65485..b109901b6673b6e0549860393f08e129e6e41892 100644 (file)
 #include "gromacs/mdtypes/md_enums.h"
 #include "gromacs/mdtypes/mdatom.h"
 #include "gromacs/mdtypes/observableshistory.h"
+#include "gromacs/mdtypes/pullhistory.h"
 #include "gromacs/mdtypes/state.h"
 #include "gromacs/pbcutil/mshift.h"
 #include "gromacs/pbcutil/pbc.h"
@@ -352,13 +353,22 @@ void gmx::Integrator::do_md()
             {
                 restore_energyhistory_from_state(mdebin, observablesHistory->energyHistory.get());
             }
-            else if (observablesHistory->energyHistory != nullptr)
+            else
             {
-                /* We might have read an energy history from checkpoint.
-                 * As we are not appending, we want to restart the statistics.
-                 * Free the allocated memory and reset the counts.
+                if (observablesHistory->energyHistory != nullptr)
+                {
+                    /* We might have read an energy history from checkpoint.
+                     * As we are not appending, we want to restart the statistics.
+                     * Free the allocated memory and reset the counts.
+                     */
+                    observablesHistory->energyHistory = {};
+                }
+                /* We might have read a pull history from checkpoint.
+                 * We will still want to keep the statistics, so that the files
+                 * can be joined and still be meaningful.
+                 * This means that observablesHistory->pullHistory
+                 * should not be reset.
                  */
-                observablesHistory->energyHistory = {};
             }
             if (ir->pull && ir->pull->bSetPbcRefToPrevStepCOM)
             {
@@ -379,6 +389,10 @@ void gmx::Integrator::do_md()
         {
             observablesHistory->energyHistory = compat::make_unique<energyhistory_t>();
         }
+        if (observablesHistory->pullHistory == nullptr)
+        {
+            observablesHistory->pullHistory = compat::make_unique<PullHistory>();
+        }
         /* Set the initial energy history in state by updating once */
         update_energyhistory(observablesHistory->energyHistory.get(), mdebin);
     }
index 8634958ced14575ae7595355c8fc931b26e870cb..be5e716b5f96aa0b8440a834cf593b8f2d7c3d64 100644 (file)
@@ -1332,6 +1332,10 @@ int Mdrunner::mdrunner()
             inputrec->pull_work =
                 init_pull(fplog, inputrec->pull, inputrec,
                           &mtop, cr, &atomSets, inputrec->fepvals->init_lambda);
+            if (inputrec->pull->bXOutAverage || inputrec->pull->bFOutAverage)
+            {
+                initPullHistory(inputrec->pull_work, &observablesHistory);
+            }
             if (EI_DYNAMICS(inputrec->eI) && MASTER(cr))
             {
                 init_pull_output_files(inputrec->pull_work,
index 7318d397b6cb1a9088a6af8d29a7b61ae42e375d..6edc255c4f4dc6796515ffb5bca9d3ac07a3297e 100644 (file)
@@ -643,6 +643,8 @@ static void pr_pull(FILE *fp, int indent, const pull_params_t *pull)
     PI("pull-nstxout", pull->nstxout);
     PI("pull-nstfout", pull->nstfout);
     PS("pull-pbc-ref-prev-step-com", EBOOL(pull->bSetPbcRefToPrevStepCOM));
+    PS("pull-xout-average", EBOOL(pull->bXOutAverage));
+    PS("pull-fout-average", EBOOL(pull->bFOutAverage));
     PI("pull-ngroups", pull->ngroup);
     for (g = 0; g < pull->ngroup; g++)
     {
index be86a2bedc80ceffcacb6b068afa9cd0fee8d0dc..0b5983a59cc13a32cd26819708307f4e382ed676 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2017, by the GROMACS development team, led by
+ * Copyright (c) 2017,2018, by the GROMACS development team, led by
  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
  * and including many others, as listed in the AUTHORS file in the
  * top-level source directory and at http://www.gromacs.org.
@@ -39,6 +39,7 @@
 
 #include "gromacs/mdtypes/edsamhistory.h"
 #include "gromacs/mdtypes/energyhistory.h"
+#include "gromacs/mdtypes/pullhistory.h"
 #include "gromacs/mdtypes/swaphistory.h"
 
 ObservablesHistory::ObservablesHistory()  = default;
index 61a67fe2ab26a2bb76eb505de5064af2242953fa..d2ba1d820f7291302bf85b30d2c465ed92e07159 100644 (file)
@@ -56,6 +56,7 @@
 #include <memory>
 
 class energyhistory_t;
+class PullHistory;
 struct edsamhistory_t;
 struct swaphistory_t;
 
@@ -66,6 +67,9 @@ struct ObservablesHistory
     //! History for energy observables, used for output only
     std::unique_ptr<energyhistory_t> energyHistory;
 
+    //! History for pulling observables, used for output only
+    std::unique_ptr<PullHistory> pullHistory;
+
     //! Essential dynamics and flooding history
     std::unique_ptr<edsamhistory_t> edsamHistory;
 
index 57d8aa1c476a73c670c21e4a8fc5601521203647..ab58be708f46095de5b8123fa3141885b3d4d392 100644 (file)
@@ -97,6 +97,8 @@ typedef struct pull_params_t {
     gmx_bool       bSetPbcRefToPrevStepCOM; /**< Use the COM of each group from the previous step as reference */
     int            nstxout;                 /**< Output interval for pull x */
     int            nstfout;                 /**< Output interval for pull f */
+    bool           bXOutAverage;            /**< Write the average coordinate during the output interval */
+    bool           bFOutAverage;            /**< Write the average force during the output interval */
 
     t_pull_group  *group;                   /**< groups to pull/restrain/etc/ */
     t_pull_coord  *coord;                   /**< the pull coordinates */
diff --git a/src/gromacs/mdtypes/pullhistory.h b/src/gromacs/mdtypes/pullhistory.h
new file mode 100644 (file)
index 0000000..624bad6
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2016,2017,2018, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+
+/*! \libinternal \file
+ *
+ *
+ * \brief
+ * This file contains datatypes for pull statistics history.
+ *
+ * \author Magnus Lundborg, Berk Hess
+ *
+ * \inlibraryapi
+ * \ingroup module_mdtypes
+ */
+
+#ifndef GMX_MDLIB_PULLHISTORY_H
+#define GMX_MDLIB_PULLHISTORY_H
+
+#include <vector>
+
+//! \cond INTERNAL
+
+//! \brief Contains the sum of coordinate observables to enable calculation of the average of pull data.
+class PullCoordinateHistory
+{
+    public:
+        double value;           //!< The sum of the current value of the coordinate, units of nm or rad.
+        double valueRef;        //!< The sum of the reference value of the coordinate, units of nm or rad.
+        double scalarForce;     //!< The sum of the scalar force of the coordinate.
+        dvec   dr01;            //!< The sum of the direction vector of group 1 relative to group 0.
+        dvec   dr23;            //!< The sum of the direction vector of group 3 relative to group 2.
+        dvec   dr45;            //!< The sum of the direction vector of group 5 relative to group 4.
+        dvec   dynaX;           //!< The sum of the coordinate of the dynamic groups for geom=cylinder.
+
+        //! Constructor
+        PullCoordinateHistory() : value      (0),
+                                  valueRef   (0),
+                                  scalarForce(0),
+                                  dr01       (),
+                                  dr23       (),
+                                  dr45       (),
+                                  dynaX      ()
+        {
+        }
+};
+
+//! \brief Contains the sum of group observables to enable calculation of the average of pull data.
+class PullGroupHistory
+{
+    public:
+        dvec   x;               //!< The sum of the coordinates of the group.
+
+        //! Constructor
+        PullGroupHistory() : x ()
+        {
+        }
+};
+
+
+//! \brief Pull statistics history, to allow output of average pull data.
+class PullHistory
+{
+    public:
+        int                                numValuesInXSum;        //!< Number of values of the coordinate values in the pull sums.
+        int                                numValuesInFSum;        //!< Number of values in the pull force sums.
+        std::vector<PullCoordinateHistory> pullCoordinateSums;     //!< The container of the sums of the values of the pull coordinate, also contains the scalar force.
+        std::vector<PullGroupHistory>      pullGroupSums;          //!< The container of the sums of the values of the pull group.
+
+        //! Constructor
+        PullHistory() : numValuesInXSum(0),
+                        numValuesInFSum(0)
+        {
+        }
+};
+
+//! \endcond
+
+#endif
index 1874293d92c2c41f3f4c29697507d96f895ffd9f..d0503544adb79cd249956113afc74cf83eb3f5a7 100644 (file)
 #include <cstdio>
 
 #include "gromacs/commandline/filenm.h"
+#include "gromacs/compat/make_unique.h"
 #include "gromacs/fileio/gmxfio.h"
 #include "gromacs/fileio/xvgr.h"
+#include "gromacs/math/vec.h"
+#include "gromacs/mdtypes/observableshistory.h"
+#include "gromacs/mdtypes/pullhistory.h"
 #include "gromacs/pulling/pull.h"
 #include "gromacs/utility/cstringutil.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/fatalerror.h"
+#include "gromacs/utility/gmxassert.h"
 
 #include "pull_internal.h"
 
@@ -66,115 +71,282 @@ static std::string append_before_extension(const std::string &pathname,
     }
 }
 
-static void pull_print_group_x(FILE *out, const ivec dim,
-                               const pull_group_work_t *pgrp)
+static void addToPullxHistory(pull_t *pull)
 {
-    int m;
+    GMX_RELEASE_ASSERT(pull->coordForceHistory, "Pull history does not exist.");
+    pull->coordForceHistory->numValuesInXSum++;
+    for (size_t c = 0; c < pull->coord.size(); c++)
+    {
+        const pull_coord_work_t &pcrd        = pull->coord[c];
+        PullCoordinateHistory   &pcrdHistory = pull->coordForceHistory->pullCoordinateSums[c];
 
-    for (m = 0; m < DIM; m++)
+        pcrdHistory.value    += pcrd.spatialData.value;
+        pcrdHistory.valueRef += pcrd.value_ref;
+
+        for (int m = 0; m < DIM; m++)
+        {
+            pcrdHistory.dr01[m] += pcrd.spatialData.dr01[m];
+            pcrdHistory.dr23[m] += pcrd.spatialData.dr23[m];
+            pcrdHistory.dr45[m] += pcrd.spatialData.dr45[m];
+        }
+        if (pcrd.params.eGeom == epullgCYL)
+        {
+            for (int m = 0; m < DIM; m++)
+            {
+                pcrdHistory.dynaX[m] += pull->dyna[c].x[m];
+            }
+        }
+    }
+    for (size_t g = 0; g < pull->group.size(); g++)
     {
-        if (dim[m])
+        PullGroupHistory &pgrpHistory = pull->coordForceHistory->pullGroupSums[g];
+        for (int m = 0; m < DIM; m++)
+        {
+            pgrpHistory.x[m] += pull->group[g].x[m];
+        }
+    }
+}
+
+static void addToPullfHistory(pull_t *pull)
+{
+    GMX_RELEASE_ASSERT(pull->coordForceHistory, "Pull history does not exist.");
+    pull->coordForceHistory->numValuesInFSum++;
+    for (size_t c = 0; c < pull->coord.size(); c++)
+    {
+        const pull_coord_work_t &pcrd        = pull->coord[c];;
+        PullCoordinateHistory   &pcrdHistory = pull->coordForceHistory->pullCoordinateSums[c];
+
+        pcrdHistory.scalarForce += pcrd.scalarForce;
+    }
+}
+
+static void pullResetHistory(PullHistory *history,
+                             bool         resetXHistory,
+                             bool         resetFHistory)
+{
+    if (resetXHistory)
+    {
+        history->numValuesInXSum = 0;
+
+        for (PullCoordinateHistory &pcrdHistory : history->pullCoordinateSums)
+        {
+            pcrdHistory.value        = 0;
+            pcrdHistory.valueRef     = 0;
+
+            clear_dvec(pcrdHistory.dr01);
+            clear_dvec(pcrdHistory.dr23);
+            clear_dvec(pcrdHistory.dr45);
+            clear_dvec(pcrdHistory.dynaX);
+        }
+
+        for (PullGroupHistory &pgrpHistory : history->pullGroupSums)
+        {
+            clear_dvec(pgrpHistory.x);
+        }
+    }
+    if (resetFHistory)
+    {
+        history->numValuesInFSum = 0;
+        for (PullCoordinateHistory &pcrdHistory : history->pullCoordinateSums)
         {
-            fprintf(out, "\t%g", pgrp->x[m]);
+            pcrdHistory.scalarForce = 0;
         }
     }
 }
 
-static void pull_print_coord_dr_components(FILE *out, const ivec dim, const dvec dr)
+static void pull_print_coord_dr_components(FILE *out, const ivec dim, const dvec dr,
+                                           const int numValuesInSum)
 {
     for (int m = 0; m < DIM; m++)
     {
         if (dim[m])
         {
-            fprintf(out, "\t%g", dr[m]);
+            fprintf(out, "\t%g", dr[m] / numValuesInSum);
         }
     }
 }
 
-static void pull_print_coord_dr(FILE *out, const pull_coord_work_t *pcrd,
-                                gmx_bool bPrintRefValue,
-                                gmx_bool bPrintComponents)
+template <typename T>
+static void pull_print_coord_dr(FILE                *out,
+                                const pull_params_t &pullParams,
+                                const t_pull_coord  &coordParams,
+                                const T             &pcrdData,
+                                double               referenceValue,
+                                const int            numValuesInSum)
 {
-    double unit_factor = pull_conversion_factor_internal2userinput(&pcrd->params);
+    const double unit_factor = pull_conversion_factor_internal2userinput(&coordParams);
 
-    fprintf(out, "\t%g", pcrd->spatialData.value*unit_factor);
+    fprintf(out, "\t%g", pcrdData.value*unit_factor/numValuesInSum);
 
-    if (bPrintRefValue)
+    if (pullParams.bPrintRefValue && coordParams.eType != epullEXTERNAL)
     {
-        fprintf(out, "\t%g", pcrd->value_ref*unit_factor);
+        fprintf(out, "\t%g", referenceValue*unit_factor/numValuesInSum);
     }
 
-    if (bPrintComponents)
+    if (pullParams.bPrintComp)
     {
-        pull_print_coord_dr_components(out, pcrd->params.dim, pcrd->spatialData.dr01);
-        if (pcrd->params.ngroup >= 4)
+        pull_print_coord_dr_components(out, coordParams.dim, pcrdData.dr01, numValuesInSum);
+        if (coordParams.ngroup >= 4)
         {
-            pull_print_coord_dr_components(out, pcrd->params.dim, pcrd->spatialData.dr23);
+            pull_print_coord_dr_components(out, coordParams.dim, pcrdData.dr23, numValuesInSum);
         }
-        if (pcrd->params.ngroup >= 6)
+        if (coordParams.ngroup >= 6)
         {
-            pull_print_coord_dr_components(out, pcrd->params.dim, pcrd->spatialData.dr45);
+            pull_print_coord_dr_components(out, coordParams.dim, pcrdData.dr45, numValuesInSum);
         }
     }
 }
 
-static void pull_print_x(FILE *out, struct pull_t *pull, double t)
+static void pull_print_x(FILE *out, pull_t *pull, double t)
 {
     fprintf(out, "%.4f", t);
 
     for (size_t c = 0; c < pull->coord.size(); c++)
     {
-        pull_coord_work_t *pcrd;
+        const pull_coord_work_t     &pcrd           = pull->coord[c];
+        int                          numValuesInSum = 1;
+        const PullCoordinateHistory *pcrdHistory    = nullptr;
 
-        pcrd = &pull->coord[c];
+        if (pull->bXOutAverage)
+        {
+            pcrdHistory = &pull->coordForceHistory->pullCoordinateSums[c];
 
-        pull_print_coord_dr(out, pcrd,
-                            pull->params.bPrintRefValue && pcrd->params.eType != epullEXTERNAL,
-                            pull->params.bPrintComp);
+            numValuesInSum = pull->coordForceHistory->numValuesInXSum;
+            pull_print_coord_dr(out, pull->params, pcrd.params,
+                                *pcrdHistory, pcrdHistory->valueRef,
+                                numValuesInSum);
+        }
+        else
+        {
+            pull_print_coord_dr(out, pull->params, pcrd.params,
+                                pcrd.spatialData, pcrd.value_ref,
+                                numValuesInSum);
+        }
 
         if (pull->params.bPrintCOM)
         {
-            if (pcrd->params.eGeom == epullgCYL)
+            if (pcrd.params.eGeom == epullgCYL)
             {
-                pull_print_group_x(out, pcrd->params.dim, &pull->dyna[c]);
+                for (int m = 0; m < DIM; m++)
+                {
+                    if (pcrd.params.dim[m])
+                    {
+                        /* This equates to if (pull->bXOutAverage) */
+                        if (pcrdHistory)
+                        {
+                            fprintf(out, "\t%g", pcrdHistory->dynaX[m] / numValuesInSum);
+                        }
+                        else
+                        {
+                            fprintf(out, "\t%g", pull->dyna[c].x[m]);
+                        }
+                    }
+                }
             }
             else
             {
-                pull_print_group_x(out, pcrd->params.dim,
-                                   &pull->group[pcrd->params.group[0]]);
+                for (int m = 0; m < DIM; m++)
+                {
+                    if (pcrd.params.dim[m])
+                    {
+                        if (pull->bXOutAverage)
+                        {
+                            fprintf(out, "\t%g", pull->coordForceHistory->pullGroupSums[pcrd.params.group[0]].x[m] / numValuesInSum);
+                        }
+                        else
+                        {
+                            fprintf(out, "\t%g", pull->group[pcrd.params.group[0]].x[m]);
+                        }
+                    }
+                }
             }
-            for (int g = 1; g < pcrd->params.ngroup; g++)
+            for (int g = 1; g < pcrd.params.ngroup; g++)
             {
-                pull_print_group_x(out, pcrd->params.dim, &pull->group[pcrd->params.group[g]]);
+                for (int m = 0; m < DIM; m++)
+                {
+                    if (pcrd.params.dim[m])
+                    {
+                        if (pull->bXOutAverage)
+                        {
+                            fprintf(out, "\t%g", pull->coordForceHistory->pullGroupSums[pcrd.params.group[g]].x[m] / numValuesInSum);
+                        }
+                        else
+                        {
+                            fprintf(out, "\t%g", pull->group[pcrd.params.group[g]].x[m]);
+                        }
+                    }
+                }
             }
         }
     }
     fprintf(out, "\n");
+
+    if (pull->bXOutAverage)
+    {
+        pullResetHistory(pull->coordForceHistory, TRUE, FALSE);
+    }
 }
 
 static void pull_print_f(FILE *out, const pull_t *pull, double t)
 {
     fprintf(out, "%.4f", t);
 
-    for (const pull_coord_work_t &coord : pull->coord)
+    if (pull->bFOutAverage)
     {
-        fprintf(out, "\t%g", coord.scalarForce);
+        for (size_t c = 0; c < pull->coord.size(); c++)
+        {
+            fprintf(out, "\t%g", pull->coordForceHistory->pullCoordinateSums[c].scalarForce / pull->coordForceHistory->numValuesInFSum);
+        }
+    }
+    else
+    {
+        for (const pull_coord_work_t &coord : pull->coord)
+        {
+            fprintf(out, "\t%g", coord.scalarForce);
+        }
     }
     fprintf(out, "\n");
+
+    if (pull->bFOutAverage)
+    {
+        pullResetHistory(pull->coordForceHistory, FALSE, TRUE);
+    }
 }
 
 void pull_print_output(struct pull_t *pull, int64_t step, double time)
 {
     GMX_ASSERT(pull->numExternalPotentialsStillToBeAppliedThisStep == 0, "pull_print_output called before all external pull potentials have been applied");
 
-    if ((pull->params.nstxout != 0) && (step % pull->params.nstxout == 0))
+    if (pull->params.nstxout != 0)
     {
-        pull_print_x(pull->out_x, pull, time);
+        /* Do not update the average if the number of observations already equal (or are
+         * higher than) what should be in each average output. This can happen when
+         * appending to a file from a checkpoint, which would otherwise include the
+         * last value twice.*/
+        if (pull->bXOutAverage && !pull->coord.empty() && pull->coordForceHistory->numValuesInXSum < pull->params.nstxout)
+        {
+            addToPullxHistory(pull);
+        }
+        if (step % pull->params.nstxout == 0)
+        {
+            pull_print_x(pull->out_x, pull, time);
+        }
     }
 
-    if ((pull->params.nstfout != 0) && (step % pull->params.nstfout == 0))
+    if (pull->params.nstfout != 0)
     {
-        pull_print_f(pull->out_f, pull, time);
+        /* Do not update the average if the number of observations already equal (or are
+         * higher than) what should be in each average output. This can happen when
+         * appending to a file from a checkpoint, which would otherwise include the
+         * last value twice.*/
+        if (pull->bFOutAverage && !pull->coord.empty() && pull->coordForceHistory->numValuesInFSum < pull->params.nstfout)
+        {
+            addToPullfHistory(pull);
+        }
+        if (step % pull->params.nstfout == 0)
+        {
+            pull_print_f(pull->out_f, pull, time);
+        }
     }
 }
 
@@ -228,14 +400,30 @@ static FILE *open_pull_out(const char *fn, struct pull_t *pull,
         if (bCoord)
         {
             sprintf(buf, "Position (nm%s)", pull->bAngle ? ", deg" : "");
-            xvgr_header(fp, "Pull COM",  "Time (ps)", buf,
-                        exvggtXNY, oenv);
+            if (pull->bXOutAverage)
+            {
+                xvgr_header(fp, "Pull Average COM",  "Time (ps)", buf,
+                            exvggtXNY, oenv);
+            }
+            else
+            {
+                xvgr_header(fp, "Pull COM",  "Time (ps)", buf,
+                            exvggtXNY, oenv);
+            }
         }
         else
         {
             sprintf(buf, "Force (kJ/mol/nm%s)", pull->bAngle ? ", kJ/mol/rad" : "");
-            xvgr_header(fp, "Pull force", "Time (ps)", buf,
-                        exvggtXNY, oenv);
+            if (pull->bFOutAverage)
+            {
+                xvgr_header(fp, "Pull Average force", "Time (ps)", buf,
+                            exvggtXNY, oenv);
+            }
+            else
+            {
+                xvgr_header(fp, "Pull force", "Time (ps)", buf,
+                            exvggtXNY, oenv);
+            }
         }
 
         /* With default mdp options only the actual coordinate value is printed (1),
@@ -363,3 +551,29 @@ void init_pull_output_files(pull_t                    *pull,
                                     FALSE, continuationOptions);
     }
 }
+
+void initPullHistory(pull_t             *pull,
+                     ObservablesHistory *observablesHistory)
+{
+    GMX_RELEASE_ASSERT(pull, "Need a valid pull object");
+
+    if (observablesHistory == nullptr)
+    {
+        pull->coordForceHistory = nullptr;
+        return;
+    }
+    /* If pull->coordForceHistory is already set we are starting from a checkpoint. Do not reset it. */
+    if (observablesHistory->pullHistory == nullptr)
+    {
+        observablesHistory->pullHistory           = gmx::compat::make_unique<PullHistory>();
+        pull->coordForceHistory                   = observablesHistory->pullHistory.get();
+        pull->coordForceHistory->numValuesInXSum  = 0;
+        pull->coordForceHistory->numValuesInFSum  = 0;
+        pull->coordForceHistory->pullCoordinateSums.resize(pull->coord.size());
+        pull->coordForceHistory->pullGroupSums.resize(pull->group.size());
+    }
+    else
+    {
+        pull->coordForceHistory                   = observablesHistory->pullHistory.get();
+    }
+}
index a8af131fae35e5f11c857458e4e876ec4708c248..3da5993b9fc77524a09608a46de89c7d5ab86b2b 100644 (file)
@@ -53,6 +53,7 @@
 struct pull_t;
 struct ContinuationOptions;
 struct gmx_output_env_t;
+struct ObservablesHistory;
 struct t_filenm;
 
 /*! \brief Set up and open the pull output files, when requested.
@@ -80,4 +81,11 @@ void init_pull_output_files(pull_t                    *pull,
  */
 void pull_print_output(pull_t *pull, int64_t step, double time);
 
+/*! \brief Allocate and initialize pull work history (for average pull output) and set it in a pull work struct
+ *
+ * \param pull                The pull work struct
+ * \param observablesHistory  Container of history data, e.g., pull history.
+ */
+void initPullHistory(pull_t *pull, ObservablesHistory *observablesHistory);
+
 #endif
index 56a730b915a13756d5c4f67ab152073226f6fe1a..3824b23df9d3276ba182e371ed79d89d068df93b 100644 (file)
@@ -1531,9 +1531,12 @@ real pull_potential(struct pull_t *pull, const t_mdatoms *md, t_pbc *pbc,
         const bool  computeVirial = (force->computeVirial_ && MASTER(cr));
         for (size_t c = 0; c < pull->coord.size(); c++)
         {
+            pull_coord_work_t *pcrd;
+            pcrd = &pull->coord[c];
+
             /* For external potential the force is assumed to be given by an external module by a call to
                apply_pull_coord_external_force */
-            if (pull->coord[c].params.eType == epullCONSTRAINT || pull->coord[c].params.eType == epullEXTERNAL)
+            if (pcrd->params.eType == epullCONSTRAINT || pcrd->params.eType == epullEXTERNAL)
             {
                 continue;
             }
@@ -1882,10 +1885,12 @@ init_pull(FILE *fplog, const pull_params_t *pull_params, const t_inputrec *ir,
         }
     }
 
-    pull->bPotential  = FALSE;
-    pull->bConstraint = FALSE;
-    pull->bCylinder   = FALSE;
-    pull->bAngle      = FALSE;
+    pull->bPotential   = FALSE;
+    pull->bConstraint  = FALSE;
+    pull->bCylinder    = FALSE;
+    pull->bAngle       = FALSE;
+    pull->bXOutAverage = pull_params->bXOutAverage;
+    pull->bFOutAverage = pull_params->bFOutAverage;
 
     GMX_RELEASE_ASSERT(pull->group[0].params.nat == 0, "pull group 0 is an absolute reference group and should not contain atoms");
     pull->group[0].x_prev_step[XX] = NAN;
index c56bb7c2045055697ca6b0d46940d0ef828a52ac..fdd3300b11b78ab43a7e113b5a20bf9b6480746d 100644 (file)
@@ -69,6 +69,8 @@ static const int c_pullMaxNumLocalAtomsSingleThreaded = 100;
 static const int c_pullMaxNumLocalAtomsSingleThreaded = 1;
 #endif
 
+class PullHistory;
+
 enum {
     epgrppbcNONE, epgrppbcREFAT, epgrppbcCOS, epgrppbcPREVSTEPCOM
 };
@@ -231,15 +233,20 @@ struct pull_t
     std::vector<pull_coord_work_t> coord;  /* The pull group param and work data */
 
     /* Global dynamic data */
-    gmx_bool             bSetPBCatoms; /* Do we need to set x_pbc for the groups? */
+    gmx_bool             bSetPBCatoms;      /* Do we need to set x_pbc for the groups? */
+
+    int                  nthreads;          /* Number of threads used by the pull code */
+    std::vector<ComSums> comSums;           /* Work array for summing for COM, 1 entry per thread */
+
+    pull_comm_t          comm;              /* Communication parameters, communicator and buffers */
 
-    int                  nthreads;     /* Number of threads used by the pull code */
-    std::vector<ComSums> comSums;      /* Work array for summing for COM, 1 entry per thread */
+    FILE                *out_x;             /* Output file for pull data */
+    FILE                *out_f;             /* Output file for pull data */
 
-    pull_comm_t          comm;         /* Communication parameters, communicator and buffers */
+    bool                 bXOutAverage;      /* Output average pull coordinates */
+    bool                 bFOutAverage;      /* Output average pull forces */
 
-    FILE                *out_x;        /* Output file for pull data */
-    FILE                *out_f;        /* Output file for pull data */
+    PullHistory         *coordForceHistory; /* Pull coordinate and force history */
 
     /* The number of coordinates using an external potential */
     int                numCoordinatesWithExternalPotential;