Move more tools out of gmxana
[alexxy/gromacs.git] / src / gromacs / tools / trjconv.cpp
diff --git a/src/gromacs/tools/trjconv.cpp b/src/gromacs/tools/trjconv.cpp
new file mode 100644 (file)
index 0000000..d21cdfa
--- /dev/null
@@ -0,0 +1,2003 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2004, The GROMACS development team.
+ * Copyright (c) 2013,2014,2015,2016,2017,2018,2019, 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.
+ */
+#include "gmxpre.h"
+
+#include "trjconv.h"
+
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+
+#include <algorithm>
+#include <memory>
+
+#include "gromacs/commandline/pargs.h"
+#include "gromacs/commandline/viewit.h"
+#include "gromacs/fileio/confio.h"
+#include "gromacs/fileio/g96io.h"
+#include "gromacs/fileio/gmxfio.h"
+#include "gromacs/fileio/groio.h"
+#include "gromacs/fileio/pdbio.h"
+#include "gromacs/fileio/tngio.h"
+#include "gromacs/fileio/tpxio.h"
+#include "gromacs/fileio/trrio.h"
+#include "gromacs/fileio/trxio.h"
+#include "gromacs/fileio/xtcio.h"
+#include "gromacs/fileio/xvgr.h"
+#include "gromacs/math/do_fit.h"
+#include "gromacs/math/functions.h"
+#include "gromacs/math/vec.h"
+#include "gromacs/mdtypes/md_enums.h"
+#include "gromacs/pbcutil/pbc.h"
+#include "gromacs/pbcutil/rmpbc.h"
+#include "gromacs/topology/index.h"
+#include "gromacs/topology/topology.h"
+#include "gromacs/trajectory/trajectoryframe.h"
+#include "gromacs/utility/arrayref.h"
+#include "gromacs/utility/arraysize.h"
+#include "gromacs/utility/fatalerror.h"
+#include "gromacs/utility/futil.h"
+#include "gromacs/utility/smalloc.h"
+
+enum {
+    euSel, euRect, euTric, euCompact, euNR
+};
+
+
+static void calc_pbc_cluster(int ecenter, int nrefat, t_topology *top, int ePBC,
+                             rvec x[], const int index[], matrix box)
+{
+    int       m, i, j, j0, j1, jj, ai, aj;
+    int       imin, jmin;
+    real      fac, min_dist2;
+    rvec      dx, xtest, box_center;
+    int       nmol, imol_center;
+    int      *molind;
+    gmx_bool *bMol, *bTmp;
+    rvec     *m_com, *m_shift;
+    t_pbc     pbc;
+    int      *cluster;
+    int      *added;
+    int       ncluster, nadded;
+    real      tmp_r2;
+
+    calc_box_center(ecenter, box, box_center);
+
+    /* Initiate the pbc structure */
+    std::memset(&pbc, 0, sizeof(pbc));
+    set_pbc(&pbc, ePBC, box);
+
+    /* Convert atom index to molecular */
+    nmol   = top->mols.nr;
+    molind = top->mols.index;
+    snew(bMol, nmol);
+    snew(m_com, nmol);
+    snew(m_shift, nmol);
+    snew(cluster, nmol);
+    snew(added, nmol);
+    snew(bTmp, top->atoms.nr);
+
+    for (i = 0; (i < nrefat); i++)
+    {
+        /* Mark all molecules in the index */
+        ai       = index[i];
+        bTmp[ai] = TRUE;
+        /* Binary search assuming the molecules are sorted */
+        j0 = 0;
+        j1 = nmol-1;
+        while (j0 < j1)
+        {
+            if (ai < molind[j0+1])
+            {
+                j1 = j0;
+            }
+            else if (ai >= molind[j1])
+            {
+                j0 = j1;
+            }
+            else
+            {
+                jj = (j0+j1)/2;
+                if (ai < molind[jj+1])
+                {
+                    j1 = jj;
+                }
+                else
+                {
+                    j0 = jj;
+                }
+            }
+        }
+        bMol[j0] = TRUE;
+    }
+    /* Double check whether all atoms in all molecules that are marked are part
+     * of the cluster. Simultaneously compute the center of geometry.
+     */
+    min_dist2   = 10*gmx::square(trace(box));
+    imol_center = -1;
+    ncluster    = 0;
+    for (i = 0; i < nmol; i++)
+    {
+        for (j = molind[i]; j < molind[i+1]; j++)
+        {
+            if (bMol[i] && !bTmp[j])
+            {
+                gmx_fatal(FARGS, "Molecule %d marked for clustering but not atom %d in it - check your index!", i+1, j+1);
+            }
+            else if (!bMol[i] && bTmp[j])
+            {
+                gmx_fatal(FARGS, "Atom %d marked for clustering but not molecule %d - this is an internal error...", j+1, i+1);
+            }
+            else if (bMol[i])
+            {
+                /* Make molecule whole, move 2nd and higher atom to same periodicity as 1st atom in molecule */
+                if (j > molind[i])
+                {
+                    pbc_dx(&pbc, x[j], x[j-1], dx);
+                    rvec_add(x[j-1], dx, x[j]);
+                }
+                /* Compute center of geometry of molecule - m_com[i] was zeroed when we did snew() on it! */
+                rvec_inc(m_com[i], x[j]);
+            }
+        }
+        if (bMol[i])
+        {
+            /* Normalize center of geometry */
+            fac = 1.0/(molind[i+1]-molind[i]);
+            for (m = 0; (m < DIM); m++)
+            {
+                m_com[i][m] *= fac;
+            }
+            /* Determine which molecule is closest to the center of the box */
+            pbc_dx(&pbc, box_center, m_com[i], dx);
+            tmp_r2 = iprod(dx, dx);
+
+            if (tmp_r2 < min_dist2)
+            {
+                min_dist2   = tmp_r2;
+                imol_center = i;
+            }
+            cluster[ncluster++] = i;
+        }
+    }
+    sfree(bTmp);
+
+    if (ncluster <= 0)
+    {
+        fprintf(stderr, "No molecules selected in the cluster\n");
+        return;
+    }
+    else if (imol_center == -1)
+    {
+        fprintf(stderr, "No central molecules could be found\n");
+        return;
+    }
+
+    nadded            = 0;
+    added[nadded++]   = imol_center;
+    bMol[imol_center] = FALSE;
+
+    while (nadded < ncluster)
+    {
+        /* Find min distance between cluster molecules and those remaining to be added */
+        min_dist2   = 10*gmx::square(trace(box));
+        imin        = -1;
+        jmin        = -1;
+        /* Loop over added mols */
+        for (i = 0; i < nadded; i++)
+        {
+            ai = added[i];
+            /* Loop over all mols */
+            for (j = 0; j < ncluster; j++)
+            {
+                aj = cluster[j];
+                /* check those remaining to be added */
+                if (bMol[aj])
+                {
+                    pbc_dx(&pbc, m_com[aj], m_com[ai], dx);
+                    tmp_r2 = iprod(dx, dx);
+                    if (tmp_r2 < min_dist2)
+                    {
+                        min_dist2   = tmp_r2;
+                        imin        = ai;
+                        jmin        = aj;
+                    }
+                }
+            }
+        }
+
+        /* Add the best molecule */
+        added[nadded++]   = jmin;
+        bMol[jmin]        = FALSE;
+        /* Calculate the shift from the ai molecule */
+        pbc_dx(&pbc, m_com[jmin], m_com[imin], dx);
+        rvec_add(m_com[imin], dx, xtest);
+        rvec_sub(xtest, m_com[jmin], m_shift[jmin]);
+        rvec_inc(m_com[jmin], m_shift[jmin]);
+
+        for (j = molind[jmin]; j < molind[jmin+1]; j++)
+        {
+            rvec_inc(x[j], m_shift[jmin]);
+        }
+        fprintf(stdout, "\rClustering iteration %d of %d...", nadded, ncluster);
+        fflush(stdout);
+    }
+
+    sfree(added);
+    sfree(cluster);
+    sfree(bMol);
+    sfree(m_com);
+    sfree(m_shift);
+
+    fprintf(stdout, "\n");
+}
+
+static void put_molecule_com_in_box(int unitcell_enum, int ecenter,
+                                    t_block *mols,
+                                    int natoms, t_atom atom[],
+                                    int ePBC, matrix box, rvec x[])
+{
+    int     i, j;
+    int     d;
+    rvec    com, shift, box_center;
+    real    m;
+    double  mtot;
+    t_pbc   pbc;
+
+    calc_box_center(ecenter, box, box_center);
+    set_pbc(&pbc, ePBC, box);
+    if (mols->nr <= 0)
+    {
+        gmx_fatal(FARGS, "There are no molecule descriptions. I need a .tpr file for this pbc option.");
+    }
+    for (i = 0; (i < mols->nr); i++)
+    {
+        /* calc COM */
+        clear_rvec(com);
+        mtot = 0;
+        for (j = mols->index[i]; (j < mols->index[i+1] && j < natoms); j++)
+        {
+            m = atom[j].m;
+            for (d = 0; d < DIM; d++)
+            {
+                com[d] += m*x[j][d];
+            }
+            mtot += m;
+        }
+        /* calculate final COM */
+        svmul(1.0/mtot, com, com);
+
+        /* check if COM is outside box */
+        gmx::RVec newCom;
+        copy_rvec(com, newCom);
+        auto      newComArrayRef = gmx::arrayRefFromArray(&newCom, 1);
+        switch (unitcell_enum)
+        {
+            case euRect:
+                put_atoms_in_box(ePBC, box, newComArrayRef);
+                break;
+            case euTric:
+                put_atoms_in_triclinic_unitcell(ecenter, box, newComArrayRef);
+                break;
+            case euCompact:
+                put_atoms_in_compact_unitcell(ePBC, ecenter, box, newComArrayRef);
+                break;
+        }
+        rvec_sub(newCom, com, shift);
+        if (norm2(shift) > 0)
+        {
+            if (debug)
+            {
+                fprintf(debug, "\nShifting position of molecule %d "
+                        "by %8.3f  %8.3f  %8.3f\n", i+1,
+                        shift[XX], shift[YY], shift[ZZ]);
+            }
+            for (j = mols->index[i]; (j < mols->index[i+1] && j < natoms); j++)
+            {
+                rvec_inc(x[j], shift);
+            }
+        }
+    }
+}
+
+static void put_residue_com_in_box(int unitcell_enum, int ecenter,
+                                   int natoms, t_atom atom[],
+                                   int ePBC, matrix box, rvec x[])
+{
+    int              i, j, res_start, res_end;
+    int              d, presnr;
+    real             m;
+    double           mtot;
+    rvec             box_center, com, shift;
+    static const int NOTSET = -12347;
+    calc_box_center(ecenter, box, box_center);
+
+    presnr    = NOTSET;
+    res_start = 0;
+    clear_rvec(com);
+    mtot = 0;
+    for (i = 0; i < natoms+1; i++)
+    {
+        if (i == natoms || (presnr != atom[i].resind && presnr != NOTSET))
+        {
+            /* calculate final COM */
+            res_end = i;
+            svmul(1.0/mtot, com, com);
+
+            /* check if COM is outside box */
+            gmx::RVec newCom;
+            copy_rvec(com, newCom);
+            auto      newComArrayRef = gmx::arrayRefFromArray(&newCom, 1);
+            switch (unitcell_enum)
+            {
+                case euRect:
+                    put_atoms_in_box(ePBC, box, newComArrayRef);
+                    break;
+                case euTric:
+                    put_atoms_in_triclinic_unitcell(ecenter, box, newComArrayRef);
+                    break;
+                case euCompact:
+                    put_atoms_in_compact_unitcell(ePBC, ecenter, box, newComArrayRef);
+                    break;
+            }
+            rvec_sub(newCom, com, shift);
+            if (norm2(shift) != 0.0f)
+            {
+                if (debug)
+                {
+                    fprintf(debug, "\nShifting position of residue %d (atoms %d-%d) "
+                            "by %g,%g,%g\n", atom[res_start].resind+1,
+                            res_start+1, res_end+1, shift[XX], shift[YY], shift[ZZ]);
+                }
+                for (j = res_start; j < res_end; j++)
+                {
+                    rvec_inc(x[j], shift);
+                }
+            }
+            clear_rvec(com);
+            mtot = 0;
+
+            /* remember start of new residue */
+            res_start = i;
+        }
+        if (i < natoms)
+        {
+            /* calc COM */
+            m = atom[i].m;
+            for (d = 0; d < DIM; d++)
+            {
+                com[d] += m*x[i][d];
+            }
+            mtot += m;
+
+            presnr = atom[i].resind;
+        }
+    }
+}
+
+static void center_x(int ecenter, rvec x[], matrix box, int n, int nc, const int ci[])
+{
+    int  i, m, ai;
+    rvec cmin, cmax, box_center, dx;
+
+    if (nc > 0)
+    {
+        copy_rvec(x[ci[0]], cmin);
+        copy_rvec(x[ci[0]], cmax);
+        for (i = 0; i < nc; i++)
+        {
+            ai = ci[i];
+            for (m = 0; m < DIM; m++)
+            {
+                if (x[ai][m] < cmin[m])
+                {
+                    cmin[m] = x[ai][m];
+                }
+                else if (x[ai][m] > cmax[m])
+                {
+                    cmax[m] = x[ai][m];
+                }
+            }
+        }
+        calc_box_center(ecenter, box, box_center);
+        for (m = 0; m < DIM; m++)
+        {
+            dx[m] = box_center[m]-(cmin[m]+cmax[m])*0.5;
+        }
+
+        for (i = 0; i < n; i++)
+        {
+            rvec_inc(x[i], dx);
+        }
+    }
+}
+
+static void mk_filenm(char *base, const char *ext, int ndigit, int file_nr,
+                      char out_file[])
+{
+    char nbuf[128];
+    int  nd = 0, fnr;
+
+    std::strcpy(out_file, base);
+    fnr = file_nr;
+    do
+    {
+        fnr /= 10;
+        nd++;
+    }
+    while (fnr > 0);
+
+    if (nd < ndigit)
+    {
+        std::strncat(out_file, "00000000000", ndigit-nd);
+    }
+    sprintf(nbuf, "%d.", file_nr);
+    std::strcat(out_file, nbuf);
+    std::strcat(out_file, ext);
+}
+
+static void check_trr(const char *fn)
+{
+    if (fn2ftp(fn) != efTRR)
+    {
+        gmx_fatal(FARGS, "%s is not a trajectory file, exiting\n", fn);
+    }
+}
+
+static void do_trunc(const char *fn, real t0)
+{
+    t_fileio        *in;
+    FILE            *fp;
+    gmx_bool         bStop, bOK;
+    gmx_trr_header_t sh;
+    gmx_off_t        fpos;
+    char             yesno[256];
+    int              j;
+    real             t = 0;
+
+    if (t0 == -1)
+    {
+        gmx_fatal(FARGS, "You forgot to set the truncation time");
+    }
+
+    /* Check whether this is a .trr file */
+    check_trr(fn);
+
+    in   = gmx_trr_open(fn, "r");
+    fp   = gmx_fio_getfp(in);
+    if (fp == nullptr)
+    {
+        fprintf(stderr, "Sorry, can not trunc %s, truncation of this filetype is not supported\n", fn);
+        gmx_trr_close(in);
+    }
+    else
+    {
+        j     = 0;
+        fpos  = gmx_fio_ftell(in);
+        bStop = FALSE;
+        while (!bStop && gmx_trr_read_frame_header(in, &sh, &bOK))
+        {
+            gmx_trr_read_frame_data(in, &sh, nullptr, nullptr, nullptr, nullptr);
+            fpos = gmx_ftell(fp);
+            t    = sh.t;
+            if (t >= t0)
+            {
+                gmx_fseek(fp, fpos, SEEK_SET);
+                bStop = TRUE;
+            }
+        }
+        if (bStop)
+        {
+            fprintf(stderr, "Do you REALLY want to truncate this trajectory (%s) at:\n"
+                    "frame %d, time %g, bytes %ld ??? (type YES if so)\n",
+                    fn, j, t, static_cast<long int>(fpos));
+            if (1 != scanf("%s", yesno))
+            {
+                gmx_fatal(FARGS, "Error reading user input");
+            }
+            if (std::strcmp(yesno, "YES") == 0)
+            {
+                fprintf(stderr, "Once again, I'm gonna DO this...\n");
+                gmx_trr_close(in);
+                if (0 != gmx_truncate(fn, fpos))
+                {
+                    gmx_fatal(FARGS, "Error truncating file %s", fn);
+                }
+            }
+            else
+            {
+                fprintf(stderr, "Ok, I'll forget about it\n");
+            }
+        }
+        else
+        {
+            fprintf(stderr, "Already at end of file (t=%g)...\n", t);
+            gmx_trr_close(in);
+        }
+    }
+}
+
+/*! \brief Read a full molecular topology if useful and available.
+ *
+ * If the input trajectory file is not in TNG format, and the output
+ * file is in TNG format, then we want to try to read a full topology
+ * (if available), so that we can write molecule information to the
+ * output file. The full topology provides better molecule information
+ * than is available from the normal t_topology data used by GROMACS
+ * tools.
+ *
+ * Also, the t_topology is only read under (different) particular
+ * conditions. If both apply, then a .tpr file might be read
+ * twice. Trying to fix this redundancy while trjconv is still an
+ * all-purpose tool does not seem worthwhile.
+ *
+ * Because of the way gmx_prepare_tng_writing is implemented, the case
+ * where the input TNG file has no molecule information will never
+ * lead to an output TNG file having molecule information. Since
+ * molecule information will generally be present if the input TNG
+ * file was written by a GROMACS tool, this seems like reasonable
+ * behaviour. */
+static std::unique_ptr<gmx_mtop_t>
+read_mtop_for_tng(const char *tps_file,
+                  const char *input_file,
+                  const char *output_file)
+{
+    std::unique_ptr<gmx_mtop_t> mtop;
+
+    if (fn2bTPX(tps_file) &&
+        efTNG != fn2ftp(input_file) &&
+        efTNG == fn2ftp(output_file))
+    {
+        int temp_natoms = -1;
+        mtop = std::make_unique<gmx_mtop_t>();
+        read_tpx(tps_file, nullptr, nullptr, &temp_natoms,
+                 nullptr, nullptr, mtop.get());
+    }
+
+    return mtop;
+}
+
+int gmx_trjconv(int argc, char *argv[])
+{
+    const char *desc[] = {
+        "[THISMODULE] can convert trajectory files in many ways:",
+        "",
+        "* from one format to another",
+        "* select a subset of atoms",
+        "* change the periodicity representation",
+        "* keep multimeric molecules together",
+        "* center atoms in the box",
+        "* fit atoms to reference structure",
+        "* reduce the number of frames",
+        "* change the timestamps of the frames ([TT]-t0[tt] and [TT]-timestep[tt])",
+        "* cut the trajectory in small subtrajectories according",
+        "  to information in an index file. This allows subsequent analysis of",
+        "  the subtrajectories that could, for example, be the result of a",
+        "  cluster analysis. Use option [TT]-sub[tt].",
+        "  This assumes that the entries in the index file are frame numbers and",
+        "  dumps each group in the index file to a separate trajectory file.",
+        "* select frames within a certain range of a quantity given",
+        "  in an [REF].xvg[ref] file.",
+        "",
+        "[gmx-trjcat] is better suited for concatenating multiple trajectory files.",
+        "[PAR]",
+
+        "The following formats are supported for input and output:",
+        "[REF].xtc[ref], [REF].trr[ref], [REF].gro[ref], [TT].g96[tt]",
+        "and [REF].pdb[ref].",
+        "The file formats are detected from the file extension.",
+        "The precision of the [REF].xtc[ref] output is taken from the",
+        "input file for [REF].xtc[ref], [REF].gro[ref] and [REF].pdb[ref],",
+        "and from the [TT]-ndec[tt] option for other input formats. The precision",
+        "is always taken from [TT]-ndec[tt], when this option is set.",
+        "All other formats have fixed precision. [REF].trr[ref]",
+        "output can be single or double precision, depending on the precision",
+        "of the [THISMODULE] binary.",
+        "Note that velocities are only supported in",
+        "[REF].trr[ref], [REF].gro[ref] and [TT].g96[tt] files.[PAR]",
+
+        "Option [TT]-sep[tt] can be used to write every frame to a separate",
+        "[TT].gro, .g96[tt] or [REF].pdb[ref] file. By default, all frames all written to one file.",
+        "[REF].pdb[ref] files with all frames concatenated can be viewed with",
+        "[TT]rasmol -nmrpdb[tt].[PAR]",
+
+        "It is possible to select part of your trajectory and write it out",
+        "to a new trajectory file in order to save disk space, e.g. for leaving",
+        "out the water from a trajectory of a protein in water.",
+        "[BB]ALWAYS[bb] put the original trajectory on tape!",
+        "We recommend to use the portable [REF].xtc[ref] format for your analysis",
+        "to save disk space and to have portable files.[PAR]",
+
+        "There are two options for fitting the trajectory to a reference",
+        "either for essential dynamics analysis, etc.",
+        "The first option is just plain fitting to a reference structure",
+        "in the structure file. The second option is a progressive fit",
+        "in which the first timeframe is fitted to the reference structure ",
+        "in the structure file to obtain and each subsequent timeframe is ",
+        "fitted to the previously fitted structure. This way a continuous",
+        "trajectory is generated, which might not be the case when using the",
+        "regular fit method, e.g. when your protein undergoes large",
+        "conformational transitions.[PAR]",
+
+        "Option [TT]-pbc[tt] sets the type of periodic boundary condition",
+        "treatment:",
+        "",
+        " * [TT]mol[tt] puts the center of mass of molecules in the box,",
+        "   and requires a run input file to be supplied with [TT]-s[tt].",
+        " * [TT]res[tt] puts the center of mass of residues in the box.",
+        " * [TT]atom[tt] puts all the atoms in the box.",
+        " * [TT]nojump[tt] checks if atoms jump across the box and then puts",
+        "   them back. This has the effect that all molecules",
+        "   will remain whole (provided they were whole in the initial",
+        "   conformation). [BB]Note[bb] that this ensures a continuous trajectory but",
+        "   molecules may diffuse out of the box. The starting configuration",
+        "   for this procedure is taken from the structure file, if one is",
+        "   supplied, otherwise it is the first frame.",
+        " * [TT]cluster[tt] clusters all the atoms in the selected index",
+        "   such that they are all closest to the center of mass of the cluster,",
+        "   which is iteratively updated. [BB]Note[bb] that this will only give meaningful",
+        "   results if you in fact have a cluster. Luckily that can be checked",
+        "   afterwards using a trajectory viewer. Note also that if your molecules",
+        "   are broken this will not work either.",
+        " * [TT]whole[tt] only makes broken molecules whole.",
+        "",
+
+        "Option [TT]-ur[tt] sets the unit cell representation for options",
+        "[TT]mol[tt], [TT]res[tt] and [TT]atom[tt] of [TT]-pbc[tt].",
+        "All three options give different results for triclinic boxes and",
+        "identical results for rectangular boxes.",
+        "[TT]rect[tt] is the ordinary brick shape.",
+        "[TT]tric[tt] is the triclinic unit cell.",
+        "[TT]compact[tt] puts all atoms at the closest distance from the center",
+        "of the box. This can be useful for visualizing e.g. truncated octahedra",
+        "or rhombic dodecahedra. The center for options [TT]tric[tt] and [TT]compact[tt]",
+        "is [TT]tric[tt] (see below), unless the option [TT]-boxcenter[tt]",
+        "is set differently.[PAR]",
+
+        "Option [TT]-center[tt] centers the system in the box. The user can",
+        "select the group which is used to determine the geometrical center.",
+        "Option [TT]-boxcenter[tt] sets the location of the center of the box",
+        "for options [TT]-pbc[tt] and [TT]-center[tt]. The center options are:",
+        "[TT]tric[tt]: half of the sum of the box vectors,",
+        "[TT]rect[tt]: half of the box diagonal,",
+        "[TT]zero[tt]: zero.",
+        "Use option [TT]-pbc mol[tt] in addition to [TT]-center[tt] when you",
+        "want all molecules in the box after the centering.[PAR]",
+
+        "Option [TT]-box[tt] sets the size of the new box. This option only works",
+        "for leading dimensions and is thus generally only useful for rectangular boxes.",
+        "If you want to modify only some of the dimensions, e.g. when reading from",
+        "a trajectory, you can use -1 for those dimensions that should stay the same",
+
+        "It is not always possible to use combinations of [TT]-pbc[tt],",
+        "[TT]-fit[tt], [TT]-ur[tt] and [TT]-center[tt] to do exactly what",
+        "you want in one call to [THISMODULE]. Consider using multiple",
+        "calls, and check out the GROMACS website for suggestions.[PAR]",
+
+        "With [TT]-dt[tt], it is possible to reduce the number of ",
+        "frames in the output. This option relies on the accuracy of the times",
+        "in your input trajectory, so if these are inaccurate use the",
+        "[TT]-timestep[tt] option to modify the time (this can be done",
+        "simultaneously). For making smooth movies, the program [gmx-filter]",
+        "can reduce the number of frames while using low-pass frequency",
+        "filtering, this reduces aliasing of high frequency motions.[PAR]",
+
+        "Using [TT]-trunc[tt] [THISMODULE] can truncate [REF].trr[ref] in place, i.e.",
+        "without copying the file. This is useful when a run has crashed",
+        "during disk I/O (i.e. full disk), or when two contiguous",
+        "trajectories must be concatenated without having double frames.[PAR]",
+
+        "Option [TT]-dump[tt] can be used to extract a frame at or near",
+        "one specific time from your trajectory, but only works reliably",
+        "if the time interval between frames is uniform.[PAR]",
+
+        "Option [TT]-drop[tt] reads an [REF].xvg[ref] file with times and values.",
+        "When options [TT]-dropunder[tt] and/or [TT]-dropover[tt] are set,",
+        "frames with a value below and above the value of the respective options",
+        "will not be written."
+    };
+
+    int         pbc_enum;
+    enum
+    {
+        epSel,
+        epNone,
+        epComMol,
+        epComRes,
+        epComAtom,
+        epNojump,
+        epCluster,
+        epWhole,
+        epNR
+    };
+    const char *pbc_opt[epNR + 1] =
+    {
+        nullptr, "none", "mol", "res", "atom", "nojump", "cluster", "whole",
+        nullptr
+    };
+
+    int         unitcell_enum;
+    const char *unitcell_opt[euNR+1] =
+    { nullptr, "rect", "tric", "compact", nullptr };
+
+    enum
+    {
+        ecSel, ecTric, ecRect, ecZero, ecNR
+    };
+    const char *center_opt[ecNR+1] =
+    { nullptr, "tric", "rect", "zero", nullptr };
+    int         ecenter;
+
+    int         fit_enum;
+    enum
+    {
+        efSel, efNone, efFit, efFitXY, efReset, efResetXY, efPFit, efNR
+    };
+    const char *fit[efNR + 1] =
+    {
+        nullptr, "none", "rot+trans", "rotxy+transxy", "translation", "transxy",
+        "progressive", nullptr
+    };
+
+    static gmx_bool  bSeparate     = FALSE, bVels = TRUE, bForce = FALSE, bCONECT = FALSE;
+    static gmx_bool  bCenter       = FALSE;
+    static int       skip_nr       = 1, ndec = 3, nzero = 0;
+    static real      tzero         = 0, delta_t = 0, timestep = 0, ttrunc = -1, tdump = -1, split_t = 0;
+    static rvec      newbox        = {0, 0, 0}, shift = {0, 0, 0}, trans = {0, 0, 0};
+    static char     *exec_command  = nullptr;
+    static real      dropunder     = 0, dropover = 0;
+    static gmx_bool  bRound        = FALSE;
+
+    t_pargs
+        pa[] =
+    {
+        { "-skip", FALSE, etINT,
+          { &skip_nr }, "Only write every nr-th frame" },
+        { "-dt", FALSE, etTIME,
+          { &delta_t },
+          "Only write frame when t MOD dt = first time (%t)" },
+        { "-round", FALSE, etBOOL,
+          { &bRound }, "Round measurements to nearest picosecond"},
+        { "-dump", FALSE, etTIME,
+          { &tdump }, "Dump frame nearest specified time (%t)" },
+        { "-t0", FALSE, etTIME,
+          { &tzero },
+          "Starting time (%t) (default: don't change)" },
+        { "-timestep", FALSE, etTIME,
+          { &timestep },
+          "Change time step between input frames (%t)" },
+        { "-pbc", FALSE, etENUM,
+          { pbc_opt },
+          "PBC treatment (see help text for full description)" },
+        { "-ur", FALSE, etENUM,
+          { unitcell_opt }, "Unit-cell representation" },
+        { "-center", FALSE, etBOOL,
+          { &bCenter }, "Center atoms in box" },
+        { "-boxcenter", FALSE, etENUM,
+          { center_opt }, "Center for -pbc and -center" },
+        { "-box", FALSE, etRVEC,
+          { newbox },
+          "Size for new cubic box (default: read from input)" },
+        { "-trans", FALSE, etRVEC,
+          { trans },
+          "All coordinates will be translated by trans. This "
+          "can advantageously be combined with -pbc mol -ur "
+          "compact." },
+        { "-shift", FALSE, etRVEC,
+          { shift },
+          "All coordinates will be shifted by framenr*shift" },
+        { "-fit", FALSE, etENUM,
+          { fit },
+          "Fit molecule to ref structure in the structure file" },
+        { "-ndec", FALSE, etINT,
+          { &ndec },
+          "Number of decimal places to write to .xtc output" },
+        { "-vel", FALSE, etBOOL,
+          { &bVels }, "Read and write velocities if possible" },
+        { "-force", FALSE, etBOOL,
+          { &bForce }, "Read and write forces if possible" },
+        { "-trunc", FALSE, etTIME,
+          { &ttrunc },
+          "Truncate input trajectory file after this time (%t)" },
+        { "-exec", FALSE, etSTR,
+          { &exec_command },
+          "Execute command for every output frame with the "
+          "frame number as argument" },
+        { "-split", FALSE, etTIME,
+          { &split_t },
+          "Start writing new file when t MOD split = first "
+          "time (%t)" },
+        { "-sep", FALSE, etBOOL,
+          { &bSeparate },
+          "Write each frame to a separate .gro, .g96 or .pdb "
+          "file" },
+        { "-nzero", FALSE, etINT,
+          { &nzero },
+          "If the -sep flag is set, use these many digits "
+          "for the file numbers and prepend zeros as needed" },
+        { "-dropunder", FALSE, etREAL,
+          { &dropunder }, "Drop all frames below this value" },
+        { "-dropover", FALSE, etREAL,
+          { &dropover }, "Drop all frames above this value" },
+        { "-conect", FALSE, etBOOL,
+          { &bCONECT },
+          "Add conect records when writing [REF].pdb[ref] files. Useful "
+          "for visualization of non-standard molecules, e.g. "
+          "coarse grained ones" }
+    };
+#define NPA asize(pa)
+
+    FILE             *out    = nullptr;
+    t_trxstatus      *trxout = nullptr;
+    t_trxstatus      *trxin;
+    int               file_nr;
+    t_trxframe        fr, frout;
+    int               flags;
+    rvec             *xmem  = nullptr, *vmem = nullptr, *fmem = nullptr;
+    rvec             *xp    = nullptr, x_shift, hbox;
+    real             *w_rls = nullptr;
+    int               m, i, d, frame, outframe, natoms, nout, ncent, newstep = 0, model_nr;
+#define SKIP 10
+    t_topology       *top   = nullptr;
+    gmx_conect        gc    = nullptr;
+    int               ePBC  = -1;
+    t_atoms          *atoms = nullptr, useatoms;
+    matrix            top_box;
+    int              *index = nullptr, *cindex = nullptr;
+    char             *grpnm = nullptr;
+    int              *frindex, nrfri;
+    char             *frname;
+    int               ifit, my_clust = -1;
+    int              *ind_fit;
+    char             *gn_fit;
+    t_cluster_ndx    *clust           = nullptr;
+    t_trxstatus     **clust_status    = nullptr;
+    int              *clust_status_id = nullptr;
+    int               ntrxopen        = 0;
+    int              *nfwritten       = nullptr;
+    int               ndrop           = 0, ncol, drop0 = 0, drop1 = 0, dropuse = 0;
+    double          **dropval;
+    real              tshift = 0, dt = -1, prec;
+    gmx_bool          bFit, bPFit, bReset;
+    int               nfitdim;
+    gmx_rmpbc_t       gpbc = nullptr;
+    gmx_bool          bRmPBC, bPBCWhole, bPBCcomRes, bPBCcomMol, bPBCcomAtom, bPBC, bNoJump, bCluster;
+    gmx_bool          bCopy, bDoIt, bIndex, bTDump, bSetTime, bTPS = FALSE, bDTset = FALSE;
+    gmx_bool          bExec, bTimeStep = FALSE, bDumpFrame = FALSE, bSetXtcPrec, bNeedPrec;
+    gmx_bool          bHaveFirstFrame, bHaveNextFrame, bSetBox, bSetUR, bSplit = FALSE;
+    gmx_bool          bSubTraj = FALSE, bDropUnder = FALSE, bDropOver = FALSE, bTrans = FALSE;
+    gmx_bool          bWriteFrame, bSplitHere;
+    const char       *top_file, *in_file, *out_file = nullptr;
+    char              out_file2[256], *charpt;
+    char             *outf_base = nullptr;
+    const char       *outf_ext  = nullptr;
+    char              top_title[256], timestr[32], stepstr[32], filemode[5];
+    gmx_output_env_t *oenv;
+
+    t_filenm          fnm[] = {
+        { efTRX, "-f",   nullptr,      ffREAD  },
+        { efTRO, "-o",   nullptr,      ffWRITE },
+        { efTPS, nullptr,   nullptr,      ffOPTRD },
+        { efNDX, nullptr,   nullptr,      ffOPTRD },
+        { efNDX, "-fr",  "frames",  ffOPTRD },
+        { efNDX, "-sub", "cluster", ffOPTRD },
+        { efXVG, "-drop", "drop",    ffOPTRD }
+    };
+#define NFILE asize(fnm)
+
+    if (!parse_common_args(&argc, argv,
+                           PCA_CAN_BEGIN | PCA_CAN_END | PCA_CAN_VIEW |
+                           PCA_TIME_UNIT,
+                           NFILE, fnm, NPA, pa, asize(desc), desc,
+                           0, nullptr, &oenv))
+    {
+        return 0;
+    }
+    fprintf(stdout, "Note that major changes are planned in future for "
+            "trjconv, to improve usability and utility.\n");
+
+    top_file = ftp2fn(efTPS, NFILE, fnm);
+
+    /* Check command line */
+    in_file = opt2fn("-f", NFILE, fnm);
+
+    if (ttrunc != -1)
+    {
+        do_trunc(in_file, ttrunc);
+    }
+    else
+    {
+        /* mark active cmdline options */
+        bSetBox     = opt2parg_bSet("-box", NPA, pa);
+        bSetTime    = opt2parg_bSet("-t0", NPA, pa);
+        bSetXtcPrec = opt2parg_bSet("-ndec", NPA, pa);
+        bSetUR      = opt2parg_bSet("-ur", NPA, pa);
+        bExec       = opt2parg_bSet("-exec", NPA, pa);
+        bTimeStep   = opt2parg_bSet("-timestep", NPA, pa);
+        bTDump      = opt2parg_bSet("-dump", NPA, pa);
+        bDropUnder  = opt2parg_bSet("-dropunder", NPA, pa);
+        bDropOver   = opt2parg_bSet("-dropover", NPA, pa);
+        bTrans      = opt2parg_bSet("-trans", NPA, pa);
+        bSplit      = (split_t != 0);
+
+        /* parse enum options */
+        fit_enum      = nenum(fit);
+        bFit          = (fit_enum == efFit || fit_enum == efFitXY);
+        bReset        = (fit_enum == efReset || fit_enum == efResetXY);
+        bPFit         = fit_enum == efPFit;
+        pbc_enum      = nenum(pbc_opt);
+        bPBCWhole     = pbc_enum == epWhole;
+        bPBCcomRes    = pbc_enum == epComRes;
+        bPBCcomMol    = pbc_enum == epComMol;
+        bPBCcomAtom   = pbc_enum == epComAtom;
+        bNoJump       = pbc_enum == epNojump;
+        bCluster      = pbc_enum == epCluster;
+        bPBC          = pbc_enum != epNone;
+        unitcell_enum = nenum(unitcell_opt);
+        ecenter       = nenum(center_opt) - ecTric;
+
+        /* set and check option dependencies */
+        if (bPFit)
+        {
+            bFit = TRUE;        /* for pfit, fit *must* be set */
+        }
+        if (bFit)
+        {
+            bReset = TRUE;       /* for fit, reset *must* be set */
+        }
+        nfitdim = 0;
+        if (bFit || bReset)
+        {
+            nfitdim = (fit_enum == efFitXY || fit_enum == efResetXY) ? 2 : 3;
+        }
+        bRmPBC = bFit || bPBCWhole || bPBCcomRes || bPBCcomMol;
+
+        if (bSetUR)
+        {
+            if (!(bPBCcomRes || bPBCcomMol ||  bPBCcomAtom))
+            {
+                fprintf(stderr,
+                        "WARNING: Option for unitcell representation (-ur %s)\n"
+                        "         only has effect in combination with -pbc %s, %s or %s.\n"
+                        "         Ingoring unitcell representation.\n\n",
+                        unitcell_opt[0], pbc_opt[2], pbc_opt[3], pbc_opt[4]);
+            }
+        }
+        if (bFit && bPBC)
+        {
+            gmx_fatal(FARGS, "PBC condition treatment does not work together with rotational fit.\n"
+                      "Please do the PBC condition treatment first and then run trjconv in a second step\n"
+                      "for the rotational fit.\n"
+                      "First doing the rotational fit and then doing the PBC treatment gives incorrect\n"
+                      "results!");
+        }
+
+        /* ndec for XTC writing is in nr of decimal places, prec is a multiplication factor: */
+        prec = 1;
+        for (i = 0; i < ndec; i++)
+        {
+            prec *= 10;
+        }
+
+        bIndex = ftp2bSet(efNDX, NFILE, fnm);
+
+
+        /* Determine output type */
+        out_file = opt2fn("-o", NFILE, fnm);
+        int ftp  = fn2ftp(out_file);
+        fprintf(stderr, "Will write %s: %s\n", ftp2ext(ftp), ftp2desc(ftp));
+        bNeedPrec = (ftp == efXTC);
+        int ftpin = fn2ftp(in_file);
+        if (bVels)
+        {
+            /* check if velocities are possible in input and output files */
+            bVels = (ftp == efTRR || ftp == efGRO ||
+                     ftp == efG96 || ftp == efTNG)
+                && (ftpin == efTRR || ftpin == efGRO ||
+                    ftpin == efG96 || ftpin == efTNG || ftpin == efCPT);
+        }
+        if (bSeparate || bSplit)
+        {
+            outf_ext = std::strrchr(out_file, '.');
+            if (outf_ext == nullptr)
+            {
+                gmx_fatal(FARGS, "Output file name '%s' does not contain a '.'", out_file);
+            }
+            outf_base = gmx_strdup(out_file);
+            outf_base[outf_ext - out_file] = '\0';
+        }
+
+        bSubTraj = opt2bSet("-sub", NFILE, fnm);
+        if (bSubTraj)
+        {
+            if ((ftp != efXTC) && (ftp != efTRR))
+            {
+                /* It seems likely that other trajectory file types
+                 * could work here. */
+                gmx_fatal(FARGS, "Can only use the sub option with output file types "
+                          "xtc and trr");
+            }
+            clust = cluster_index(nullptr, opt2fn("-sub", NFILE, fnm));
+
+            /* Check for number of files disabled, as FOPEN_MAX is not the correct
+             * number to check for. In my linux box it is only 16.
+             */
+            if (/* DISABLES CODE */ (false))
+            //if (clust->clust->nr > FOPEN_MAX-4)
+            {
+                gmx_fatal(FARGS, "Can not open enough (%d) files to write all the"
+                          " trajectories.\ntry splitting the index file in %d parts.\n"
+                          "FOPEN_MAX = %d",
+                          clust->clust->nr, 1+clust->clust->nr/FOPEN_MAX, FOPEN_MAX);
+            }
+            gmx_warning("The -sub option could require as many open output files as there are\n"
+                        "index groups in the file (%d). If you get I/O errors opening new files,\n"
+                        "try reducing the number of index groups in the file, and perhaps\n"
+                        "using trjconv -sub several times on different chunks of your index file.\n",
+                        clust->clust->nr);
+
+            snew(clust_status, clust->clust->nr);
+            snew(clust_status_id, clust->clust->nr);
+            snew(nfwritten, clust->clust->nr);
+            for (i = 0; (i < clust->clust->nr); i++)
+            {
+                clust_status[i]    = nullptr;
+                clust_status_id[i] = -1;
+            }
+            bSeparate = bSplit = FALSE;
+        }
+        /* skipping */
+        if (skip_nr <= 0)
+        {
+            gmx_fatal(FARGS, "Argument for -skip (%d) needs to be greater or equal to 1.", skip_nr);
+        }
+
+        std::unique_ptr<gmx_mtop_t> mtop = read_mtop_for_tng(top_file, in_file, out_file);
+
+        /* Determine whether to read a topology */
+        bTPS = (ftp2bSet(efTPS, NFILE, fnm) ||
+                bRmPBC || bReset || bPBCcomMol || bCluster ||
+                (ftp == efGRO) || (ftp == efPDB) || bCONECT);
+
+        /* Determine if when can read index groups */
+        bIndex = (bIndex || bTPS);
+
+        if (bTPS)
+        {
+            snew(top, 1);
+            read_tps_conf(top_file, top, &ePBC, &xp, nullptr, top_box,
+                          bReset || bPBCcomRes);
+            std::strncpy(top_title, *top->name, 255);
+            top_title[255] = '\0';
+            atoms          = &top->atoms;
+
+            if (0 == top->mols.nr && (bCluster || bPBCcomMol))
+            {
+                gmx_fatal(FARGS, "Option -pbc %s requires a .tpr file for the -s option", pbc_opt[pbc_enum]);
+            }
+
+            /* top_title is only used for gro and pdb,
+             * the header in such a file is top_title, followed by
+             * t= ... and/or step= ...
+             * to prevent double t= or step=, remove it from top_title.
+             * From GROMACS-2018 we only write t/step when the frame actually
+             * has a valid time/step, so we need to check for both separately.
+             */
+            if ((charpt = std::strstr(top_title, " t= ")))
+            {
+                charpt[0] = '\0';
+            }
+            if ((charpt = std::strstr(top_title, " step= ")))
+            {
+                charpt[0] = '\0';
+            }
+
+            if (bCONECT)
+            {
+                gc = gmx_conect_generate(top);
+            }
+            if (bRmPBC)
+            {
+                gpbc = gmx_rmpbc_init(&top->idef, ePBC, top->atoms.nr);
+            }
+        }
+
+        /* get frame number index */
+        frindex = nullptr;
+        if (opt2bSet("-fr", NFILE, fnm))
+        {
+            printf("Select groups of frame number indices:\n");
+            rd_index(opt2fn("-fr", NFILE, fnm), 1, &nrfri, &frindex, &frname);
+            if (debug)
+            {
+                for (i = 0; i < nrfri; i++)
+                {
+                    fprintf(debug, "frindex[%4d]=%4d\n", i, frindex[i]);
+                }
+            }
+        }
+
+        /* get index groups etc. */
+        if (bReset)
+        {
+            printf("Select group for %s fit\n",
+                   bFit ? "least squares" : "translational");
+            get_index(atoms, ftp2fn_null(efNDX, NFILE, fnm),
+                      1, &ifit, &ind_fit, &gn_fit);
+
+            if (bFit)
+            {
+                if (ifit < 2)
+                {
+                    gmx_fatal(FARGS, "Need at least 2 atoms to fit!\n");
+                }
+                else if (ifit == 3)
+                {
+                    fprintf(stderr, "WARNING: fitting with only 2 atoms is not unique\n");
+                }
+            }
+        }
+        else if (bCluster)
+        {
+            printf("Select group for clustering\n");
+            get_index(atoms, ftp2fn_null(efNDX, NFILE, fnm),
+                      1, &ifit, &ind_fit, &gn_fit);
+        }
+
+        if (bIndex)
+        {
+            if (bCenter)
+            {
+                printf("Select group for centering\n");
+                get_index(atoms, ftp2fn_null(efNDX, NFILE, fnm),
+                          1, &ncent, &cindex, &grpnm);
+            }
+            printf("Select group for output\n");
+            get_index(atoms, ftp2fn_null(efNDX, NFILE, fnm),
+                      1, &nout, &index, &grpnm);
+        }
+        else
+        {
+            /* no index file, so read natoms from TRX */
+            if (!read_first_frame(oenv, &trxin, in_file, &fr, TRX_DONT_SKIP))
+            {
+                gmx_fatal(FARGS, "Could not read a frame from %s", in_file);
+            }
+            natoms = fr.natoms;
+            close_trx(trxin);
+            sfree(fr.x);
+            snew(index, natoms);
+            for (i = 0; i < natoms; i++)
+            {
+                index[i] = i;
+            }
+            nout = natoms;
+            if (bCenter)
+            {
+                ncent  = nout;
+                cindex = index;
+            }
+        }
+
+        if (bReset)
+        {
+            snew(w_rls, atoms->nr);
+            for (i = 0; (i < ifit); i++)
+            {
+                w_rls[ind_fit[i]] = atoms->atom[ind_fit[i]].m;
+            }
+
+            /* Restore reference structure and set to origin,
+               store original location (to put structure back) */
+            if (bRmPBC)
+            {
+                gmx_rmpbc(gpbc, top->atoms.nr, top_box, xp);
+            }
+            copy_rvec(xp[index[0]], x_shift);
+            reset_x_ndim(nfitdim, ifit, ind_fit, atoms->nr, nullptr, xp, w_rls);
+            rvec_dec(x_shift, xp[index[0]]);
+        }
+        else
+        {
+            clear_rvec(x_shift);
+        }
+
+        if (bDropUnder || bDropOver)
+        {
+            /* Read the .xvg file with the drop values */
+            fprintf(stderr, "\nReading drop file ...");
+            ndrop = read_xvg(opt2fn("-drop", NFILE, fnm), &dropval, &ncol);
+            fprintf(stderr, " %d time points\n", ndrop);
+            if (ndrop == 0 || ncol < 2)
+            {
+                gmx_fatal(FARGS, "Found no data points in %s",
+                          opt2fn("-drop", NFILE, fnm));
+            }
+            drop0 = 0;
+            drop1 = 0;
+        }
+
+        /* Make atoms struct for output in GRO or PDB files */
+        if ((ftp == efGRO) || ((ftp == efG96) && bTPS) || (ftp == efPDB))
+        {
+            /* get memory for stuff to go in .pdb file, and initialize
+             * the pdbinfo structure part if the input has it.
+             */
+            init_t_atoms(&useatoms, atoms->nr, atoms->havePdbInfo);
+            sfree(useatoms.resinfo);
+            useatoms.resinfo = atoms->resinfo;
+            for (i = 0; (i < nout); i++)
+            {
+                useatoms.atomname[i] = atoms->atomname[index[i]];
+                useatoms.atom[i]     = atoms->atom[index[i]];
+                if (atoms->havePdbInfo)
+                {
+                    useatoms.pdbinfo[i]  = atoms->pdbinfo[index[i]];
+                }
+                useatoms.nres        = std::max(useatoms.nres, useatoms.atom[i].resind+1);
+            }
+            useatoms.nr = nout;
+        }
+        /* select what to read */
+        if (ftp == efTRR)
+        {
+            flags = TRX_READ_X;
+        }
+        else
+        {
+            flags = TRX_NEED_X;
+        }
+        if (bVels)
+        {
+            flags = flags | TRX_READ_V;
+        }
+        if (bForce)
+        {
+            flags = flags | TRX_READ_F;
+        }
+
+        /* open trx file for reading */
+        bHaveFirstFrame = read_first_frame(oenv, &trxin, in_file, &fr, flags);
+        if (fr.bPrec)
+        {
+            fprintf(stderr, "\nPrecision of %s is %g (nm)\n", in_file, 1/fr.prec);
+        }
+        if (bNeedPrec)
+        {
+            if (bSetXtcPrec || !fr.bPrec)
+            {
+                fprintf(stderr, "\nSetting output precision to %g (nm)\n", 1/prec);
+            }
+            else
+            {
+                fprintf(stderr, "Using output precision of %g (nm)\n", 1/prec);
+            }
+        }
+
+        if (bHaveFirstFrame)
+        {
+            if (bTDump)
+            {
+                // Determine timestep (assuming constant spacing for now) if we
+                // need to dump frames based on time. This is required so we do not
+                // skip the first frame if that was the one that should have been dumped
+                double firstFrameTime = fr.time;
+                if (read_next_frame(oenv, trxin, &fr))
+                {
+                    dt     = fr.time - firstFrameTime;
+                    bDTset = TRUE;
+                    if (dt <= 0)
+                    {
+                        fprintf(stderr, "Warning: Frame times are not incrementing - will dump first frame.\n");
+                    }
+                }
+                // Now close and reopen so we are at first frame again
+                close_trx(trxin);
+                done_frame(&fr);
+                // Reopen at first frame (We already know it exists if we got here)
+                read_first_frame(oenv, &trxin, in_file, &fr, flags);
+            }
+
+            set_trxframe_ePBC(&fr, ePBC);
+            natoms = fr.natoms;
+
+            if (bSetTime)
+            {
+                tshift = tzero-fr.time;
+            }
+            else
+            {
+                tzero = fr.time;
+            }
+
+            bCopy = FALSE;
+            if (bIndex)
+            {
+                /* check if index is meaningful */
+                for (i = 0; i < nout; i++)
+                {
+                    if (index[i] >= natoms)
+                    {
+                        gmx_fatal(FARGS,
+                                  "Index[%d] %d is larger than the number of atoms in the\n"
+                                  "trajectory file (%d). There is a mismatch in the contents\n"
+                                  "of your -f, -s and/or -n files.", i, index[i]+1, natoms);
+                    }
+                    bCopy = bCopy || (i != index[i]);
+                }
+            }
+
+            /* open output for writing */
+            std::strcpy(filemode, "w");
+            switch (ftp)
+            {
+                case efTNG:
+                    trxout = trjtools_gmx_prepare_tng_writing(out_file,
+                                                              filemode[0],
+                                                              trxin,
+                                                              nullptr,
+                                                              nout,
+                                                              mtop.get(),
+                                                              gmx::arrayRefFromArray(index, nout),
+                                                              grpnm);
+                    break;
+                case efXTC:
+                case efTRR:
+                    out = nullptr;
+                    if (!bSplit && !bSubTraj)
+                    {
+                        trxout = open_trx(out_file, filemode);
+                    }
+                    break;
+                case efGRO:
+                case efG96:
+                case efPDB:
+                    if (( !bSeparate && !bSplit ) && !bSubTraj)
+                    {
+                        out = gmx_ffopen(out_file, filemode);
+                    }
+                    break;
+                default:
+                    gmx_incons("Illegal output file format");
+            }
+
+            if (bCopy)
+            {
+                snew(xmem, nout);
+                if (bVels)
+                {
+                    snew(vmem, nout);
+                }
+                if (bForce)
+                {
+                    snew(fmem, nout);
+                }
+            }
+
+            /* Start the big loop over frames */
+            file_nr  =  0;
+            frame    =  0;
+            outframe =  0;
+            model_nr =  0;
+
+            /* Main loop over frames */
+            do
+            {
+                if (!fr.bStep)
+                {
+                    /* set the step */
+                    fr.step = newstep;
+                    newstep++;
+                }
+                if (bSubTraj)
+                {
+                    /*if (frame >= clust->clust->nra)
+                       gmx_fatal(FARGS,"There are more frames in the trajectory than in the cluster index file\n");*/
+                    if (frame > clust->maxframe)
+                    {
+                        my_clust = -1;
+                    }
+                    else
+                    {
+                        my_clust = clust->inv_clust[frame];
+                    }
+                    if ((my_clust < 0) || (my_clust >= clust->clust->nr) ||
+                        (my_clust == -1))
+                    {
+                        my_clust = -1;
+                    }
+                }
+
+                if (bSetBox)
+                {
+                    /* generate new box */
+                    if (!fr.bBox)
+                    {
+                        clear_mat(fr.box);
+                    }
+                    for (m = 0; m < DIM; m++)
+                    {
+                        if (newbox[m] >= 0)
+                        {
+                            fr.box[m][m] = newbox[m];
+                        }
+                        else
+                        {
+                            if (!fr.bBox)
+                            {
+                                gmx_fatal(FARGS, "Cannot preserve a box that does not exist.\n");
+                            }
+                        }
+                    }
+                }
+
+                if (bTrans)
+                {
+                    for (i = 0; i < natoms; i++)
+                    {
+                        rvec_inc(fr.x[i], trans);
+                    }
+                }
+
+                if (bTDump)
+                {
+                    // If we could not read two frames or times are not incrementing
+                    // we have almost no idea what to do,
+                    // but dump the first frame so output is not broken.
+                    if (dt <= 0 || !bDTset)
+                    {
+                        bDumpFrame = true;
+                    }
+                    else
+                    {
+                        // Dump the frame if we are less than half a frame time
+                        // below it. This will also ensure we at least dump a
+                        // somewhat reasonable frame if the spacing is unequal
+                        // and we have overrun the frame time. Once we dump one
+                        // frame based on time we quit, so it does not matter
+                        // that this might be true for all subsequent frames too.
+                        bDumpFrame = (fr.time > tdump-0.5*dt);
+                    }
+                }
+                else
+                {
+                    bDumpFrame = FALSE;
+                }
+
+                /* determine if an atom jumped across the box and reset it if so */
+                if (bNoJump && (bTPS || frame != 0))
+                {
+                    for (d = 0; d < DIM; d++)
+                    {
+                        hbox[d] = 0.5*fr.box[d][d];
+                    }
+                    for (i = 0; i < natoms; i++)
+                    {
+                        if (bReset)
+                        {
+                            rvec_dec(fr.x[i], x_shift);
+                        }
+                        for (m = DIM-1; m >= 0; m--)
+                        {
+                            if (hbox[m] > 0)
+                            {
+                                while (fr.x[i][m]-xp[i][m] <= -hbox[m])
+                                {
+                                    for (d = 0; d <= m; d++)
+                                    {
+                                        fr.x[i][d] += fr.box[m][d];
+                                    }
+                                }
+                                while (fr.x[i][m]-xp[i][m] > hbox[m])
+                                {
+                                    for (d = 0; d <= m; d++)
+                                    {
+                                        fr.x[i][d] -= fr.box[m][d];
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                else if (bCluster)
+                {
+                    calc_pbc_cluster(ecenter, ifit, top, ePBC, fr.x, ind_fit, fr.box);
+                }
+
+                if (bPFit)
+                {
+                    /* Now modify the coords according to the flags,
+                       for normal fit, this is only done for output frames */
+                    if (bRmPBC)
+                    {
+                        gmx_rmpbc_trxfr(gpbc, &fr);
+                    }
+
+                    reset_x_ndim(nfitdim, ifit, ind_fit, natoms, nullptr, fr.x, w_rls);
+                    do_fit(natoms, w_rls, xp, fr.x);
+                }
+
+                /* store this set of coordinates for future use */
+                if (bPFit || bNoJump)
+                {
+                    if (xp == nullptr)
+                    {
+                        snew(xp, natoms);
+                    }
+                    for (i = 0; (i < natoms); i++)
+                    {
+                        copy_rvec(fr.x[i], xp[i]);
+                        rvec_inc(fr.x[i], x_shift);
+                    }
+                }
+
+                if (frindex)
+                {
+                    /* see if we have a frame from the frame index group */
+                    for (i = 0; i < nrfri && !bDumpFrame; i++)
+                    {
+                        bDumpFrame = frame == frindex[i];
+                    }
+                }
+                if (debug && bDumpFrame)
+                {
+                    fprintf(debug, "dumping %d\n", frame);
+                }
+
+                bWriteFrame =
+                    ( ( !bTDump && (frindex == nullptr) && frame % skip_nr == 0 ) || bDumpFrame );
+
+                if (bWriteFrame && (bDropUnder || bDropOver))
+                {
+                    while (dropval[0][drop1] < fr.time && drop1+1 < ndrop)
+                    {
+                        drop0 = drop1;
+                        drop1++;
+                    }
+                    if (std::abs(dropval[0][drop0] - fr.time)
+                        < std::abs(dropval[0][drop1] - fr.time))
+                    {
+                        dropuse = drop0;
+                    }
+                    else
+                    {
+                        dropuse = drop1;
+                    }
+                    if ((bDropUnder && dropval[1][dropuse] < dropunder) ||
+                        (bDropOver && dropval[1][dropuse] > dropover))
+                    {
+                        bWriteFrame = FALSE;
+                    }
+                }
+
+                if (bWriteFrame)
+                {
+                    /* We should avoid modifying the input frame,
+                     * but since here we don't have the output frame yet,
+                     * we introduce a temporary output frame time variable.
+                     */
+                    real frout_time;
+
+                    frout_time = fr.time;
+
+                    /* calc new time */
+                    if (bTimeStep)
+                    {
+                        frout_time = tzero + frame*timestep;
+                    }
+                    else if (bSetTime)
+                    {
+                        frout_time += tshift;
+                    }
+
+                    if (bTDump)
+                    {
+                        fprintf(stderr, "\nDumping frame at t= %g %s\n",
+                                output_env_conv_time(oenv, frout_time), output_env_get_time_unit(oenv).c_str());
+                    }
+
+                    /* check for writing at each delta_t */
+                    bDoIt = (delta_t == 0);
+                    if (!bDoIt)
+                    {
+                        if (!bRound)
+                        {
+                            bDoIt = bRmod(frout_time, tzero, delta_t);
+                        }
+                        else
+                        {
+                            /* round() is not C89 compatible, so we do this:  */
+                            bDoIt = bRmod(std::floor(frout_time+0.5), std::floor(tzero+0.5),
+                                          std::floor(delta_t+0.5));
+                        }
+                    }
+
+                    if (bDoIt || bTDump)
+                    {
+                        /* print sometimes */
+                        if ( ((outframe % SKIP) == 0) || (outframe < SKIP) )
+                        {
+                            fprintf(stderr, " ->  frame %6d time %8.3f      \r",
+                                    outframe, output_env_conv_time(oenv, frout_time));
+                            fflush(stderr);
+                        }
+
+                        if (!bPFit)
+                        {
+                            /* Now modify the coords according to the flags,
+                               for PFit we did this already! */
+
+                            if (bRmPBC)
+                            {
+                                gmx_rmpbc_trxfr(gpbc, &fr);
+                            }
+
+                            if (bReset)
+                            {
+                                reset_x_ndim(nfitdim, ifit, ind_fit, natoms, nullptr, fr.x, w_rls);
+                                if (bFit)
+                                {
+                                    do_fit_ndim(nfitdim, natoms, w_rls, xp, fr.x);
+                                }
+                                if (!bCenter)
+                                {
+                                    for (i = 0; i < natoms; i++)
+                                    {
+                                        rvec_inc(fr.x[i], x_shift);
+                                    }
+                                }
+                            }
+
+                            if (bCenter)
+                            {
+                                center_x(ecenter, fr.x, fr.box, natoms, ncent, cindex);
+                            }
+                        }
+
+                        auto positionsArrayRef = gmx::arrayRefFromArray(reinterpret_cast<gmx::RVec *>(fr.x), natoms);
+                        if (bPBCcomAtom)
+                        {
+                            switch (unitcell_enum)
+                            {
+                                case euRect:
+                                    put_atoms_in_box(ePBC, fr.box, positionsArrayRef);
+                                    break;
+                                case euTric:
+                                    put_atoms_in_triclinic_unitcell(ecenter, fr.box, positionsArrayRef);
+                                    break;
+                                case euCompact:
+                                    put_atoms_in_compact_unitcell(ePBC, ecenter, fr.box,
+                                                                  positionsArrayRef);
+                                    break;
+                            }
+                        }
+                        if (bPBCcomRes)
+                        {
+                            put_residue_com_in_box(unitcell_enum, ecenter,
+                                                   natoms, atoms->atom, ePBC, fr.box, fr.x);
+                        }
+                        if (bPBCcomMol)
+                        {
+                            put_molecule_com_in_box(unitcell_enum, ecenter,
+                                                    &top->mols,
+                                                    natoms, atoms->atom, ePBC, fr.box, fr.x);
+                        }
+                        /* Copy the input trxframe struct to the output trxframe struct */
+                        frout        = fr;
+                        frout.time   = frout_time;
+                        frout.bV     = (frout.bV && bVels);
+                        frout.bF     = (frout.bF && bForce);
+                        frout.natoms = nout;
+                        if (bNeedPrec && (bSetXtcPrec || !fr.bPrec))
+                        {
+                            frout.bPrec = TRUE;
+                            frout.prec  = prec;
+                        }
+                        if (bCopy)
+                        {
+                            frout.x = xmem;
+                            if (frout.bV)
+                            {
+                                frout.v = vmem;
+                            }
+                            if (frout.bF)
+                            {
+                                frout.f = fmem;
+                            }
+                            for (i = 0; i < nout; i++)
+                            {
+                                copy_rvec(fr.x[index[i]], frout.x[i]);
+                                if (frout.bV)
+                                {
+                                    copy_rvec(fr.v[index[i]], frout.v[i]);
+                                }
+                                if (frout.bF)
+                                {
+                                    copy_rvec(fr.f[index[i]], frout.f[i]);
+                                }
+                            }
+                        }
+
+                        if (opt2parg_bSet("-shift", NPA, pa))
+                        {
+                            for (i = 0; i < nout; i++)
+                            {
+                                for (d = 0; d < DIM; d++)
+                                {
+                                    frout.x[i][d] += outframe*shift[d];
+                                }
+                            }
+                        }
+
+                        if (!bRound)
+                        {
+                            bSplitHere = bSplit && bRmod(frout.time, tzero, split_t);
+                        }
+                        else
+                        {
+                            /* round() is not C89 compatible, so we do this: */
+                            bSplitHere = bSplit && bRmod(std::floor(frout.time+0.5),
+                                                         std::floor(tzero+0.5),
+                                                         std::floor(split_t+0.5));
+                        }
+                        if (bSeparate || bSplitHere)
+                        {
+                            mk_filenm(outf_base, ftp2ext(ftp), nzero, file_nr, out_file2);
+                        }
+
+                        std::string title;
+                        switch (ftp)
+                        {
+                            case efTNG:
+                                write_tng_frame(trxout, &frout);
+                                // TODO when trjconv behaves better: work how to read and write lambda
+                                break;
+                            case efTRR:
+                            case efXTC:
+                                if (bSplitHere)
+                                {
+                                    if (trxout)
+                                    {
+                                        close_trx(trxout);
+                                    }
+                                    trxout = open_trx(out_file2, filemode);
+                                }
+                                if (bSubTraj)
+                                {
+                                    if (my_clust != -1)
+                                    {
+                                        char buf[STRLEN];
+                                        if (clust_status_id[my_clust] == -1)
+                                        {
+                                            sprintf(buf, "%s.%s", clust->grpname[my_clust], ftp2ext(ftp));
+                                            clust_status[my_clust]    = open_trx(buf, "w");
+                                            clust_status_id[my_clust] = 1;
+                                            ntrxopen++;
+                                        }
+                                        else if (clust_status_id[my_clust] == -2)
+                                        {
+                                            gmx_fatal(FARGS, "File %s.xtc should still be open (%d open .xtc files)\n" "in order to write frame %d. my_clust = %d",
+                                                      clust->grpname[my_clust], ntrxopen, frame,
+                                                      my_clust);
+                                        }
+                                        write_trxframe(clust_status[my_clust], &frout, gc);
+                                        nfwritten[my_clust]++;
+                                        if (nfwritten[my_clust] ==
+                                            (clust->clust->index[my_clust+1]-
+                                             clust->clust->index[my_clust]))
+                                        {
+                                            close_trx(clust_status[my_clust]);
+                                            clust_status[my_clust]    = nullptr;
+                                            clust_status_id[my_clust] = -2;
+                                            ntrxopen--;
+                                            if (ntrxopen < 0)
+                                            {
+                                                gmx_fatal(FARGS, "Less than zero open .xtc files!");
+                                            }
+                                        }
+                                    }
+                                }
+                                else
+                                {
+                                    write_trxframe(trxout, &frout, gc);
+                                }
+                                break;
+                            case efGRO:
+                            case efG96:
+                            case efPDB:
+                                // Only add a generator statement if title is empty,
+                                // to avoid multiple generated-by statements from various programs
+                                if (std::strlen(top_title) == 0)
+                                {
+                                    sprintf(top_title, "Generated by trjconv");
+                                }
+                                if (frout.bTime)
+                                {
+                                    sprintf(timestr, " t= %9.5f", frout.time);
+                                }
+                                else
+                                {
+                                    std::strcpy(timestr, "");
+                                }
+                                if (frout.bStep)
+                                {
+                                    sprintf(stepstr, " step= %" PRId64, frout.step);
+                                }
+                                else
+                                {
+                                    std::strcpy(stepstr, "");
+                                }
+                                title = gmx::formatString("%s%s%s", top_title, timestr, stepstr);
+                                if (bSeparate || bSplitHere)
+                                {
+                                    out = gmx_ffopen(out_file2, "w");
+                                }
+                                switch (ftp)
+                                {
+                                    case efGRO:
+                                        write_hconf_p(out, title.c_str(), &useatoms,
+                                                      frout.x, frout.bV ? frout.v : nullptr, frout.box);
+                                        break;
+                                    case efPDB:
+                                        fprintf(out, "REMARK    GENERATED BY TRJCONV\n");
+                                        /* if reading from pdb, we want to keep the original
+                                           model numbering else we write the output frame
+                                           number plus one, because model 0 is not allowed in pdb */
+                                        if (ftpin == efPDB && fr.bStep && fr.step > model_nr)
+                                        {
+                                            model_nr = fr.step;
+                                        }
+                                        else
+                                        {
+                                            model_nr++;
+                                        }
+                                        write_pdbfile(out, title.c_str(), &useatoms, frout.x,
+                                                      frout.ePBC, frout.box, ' ', model_nr, gc);
+                                        break;
+                                    case efG96:
+                                        const char *outputTitle = "";
+                                        if (bSeparate || bTDump)
+                                        {
+                                            outputTitle = title.c_str();
+                                            if (bTPS)
+                                            {
+                                                frout.bAtoms = TRUE;
+                                            }
+                                            frout.atoms  = &useatoms;
+                                            frout.bStep  = FALSE;
+                                            frout.bTime  = FALSE;
+                                        }
+                                        else
+                                        {
+                                            if (outframe == 0)
+                                            {
+                                                outputTitle = title.c_str();
+                                            }
+                                            frout.bAtoms = FALSE;
+                                            frout.bStep  = TRUE;
+                                            frout.bTime  = TRUE;
+                                        }
+                                        write_g96_conf(out, outputTitle, &frout, -1, nullptr);
+                                }
+                                if (bSeparate || bSplitHere)
+                                {
+                                    gmx_ffclose(out);
+                                    out = nullptr;
+                                }
+                                break;
+                            default:
+                                gmx_fatal(FARGS, "DHE, ftp=%d\n", ftp);
+                        }
+                        if (bSeparate || bSplitHere)
+                        {
+                            file_nr++;
+                        }
+
+                        /* execute command */
+                        if (bExec)
+                        {
+                            char c[255];
+                            sprintf(c, "%s  %d", exec_command, file_nr-1);
+                            /*fprintf(stderr,"Executing '%s'\n",c);*/
+                            if (0 != system(c))
+                            {
+                                gmx_fatal(FARGS, "Error executing command: %s", c);
+                            }
+                        }
+                        outframe++;
+                    }
+                }
+                frame++;
+                bHaveNextFrame = read_next_frame(oenv, trxin, &fr);
+            }
+            while (!(bTDump && bDumpFrame) && bHaveNextFrame);
+        }
+
+        if (!bHaveFirstFrame || (bTDump && !bDumpFrame))
+        {
+            fprintf(stderr, "\nWARNING no output, "
+                    "last frame read at t=%g\n", fr.time);
+        }
+        fprintf(stderr, "\n");
+
+        close_trx(trxin);
+        sfree(outf_base);
+
+        if (bRmPBC)
+        {
+            gmx_rmpbc_done(gpbc);
+        }
+
+        if (trxout)
+        {
+            close_trx(trxout);
+        }
+        else if (out != nullptr)
+        {
+            gmx_ffclose(out);
+        }
+        if (bSubTraj)
+        {
+            for (i = 0; (i < clust->clust->nr); i++)
+            {
+                if (clust_status_id[i] >= 0)
+                {
+                    close_trx(clust_status[i]);
+                }
+            }
+        }
+    }
+
+    if (bTPS)
+    {
+        done_top(top);
+        sfree(top);
+    }
+    sfree(xp);
+    sfree(xmem);
+    sfree(vmem);
+    sfree(fmem);
+    sfree(grpnm);
+    sfree(index);
+    sfree(cindex);
+    done_frame(&fr);
+
+    do_view(oenv, out_file, nullptr);
+
+    output_env_done(oenv);
+    return 0;
+}