Fix random typos
[alexxy/gromacs.git] / src / gromacs / gmxpreprocess / grompp.cpp
index 2a87cec0c4bf7200834943a65c7197fc494b99db..899b5008c2becadbe5008ba31f7652865f5c7fa8 100644 (file)
@@ -3,7 +3,8 @@
  *
  * 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
+ * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
+ * Copyright (c) 2018,2019,2020,2021, 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.
 
 #include "grompp.h"
 
+#include <array>
 #include <cerrno>
 #include <climits>
 #include <cmath>
 #include <cstring>
 
 #include <algorithm>
+#include <memory>
 #include <vector>
 
 #include <sys/types.h>
 
-#include "gromacs/awh/read-params.h"
+#include "gromacs/applied_forces/awh/read_params.h"
 #include "gromacs/commandline/pargs.h"
-#include "gromacs/compat/make_unique.h"
-#include "gromacs/ewald/ewald-utils.h"
+#include "gromacs/ewald/ewald_utils.h"
 #include "gromacs/ewald/pme.h"
 #include "gromacs/fft/calcgrid.h"
 #include "gromacs/fileio/confio.h"
@@ -63,7 +65,7 @@
 #include "gromacs/gmxpreprocess/convparm.h"
 #include "gromacs/gmxpreprocess/gen_maxwell_velocities.h"
 #include "gromacs/gmxpreprocess/gpp_atomtype.h"
-#include "gromacs/gmxpreprocess/grompp-impl.h"
+#include "gromacs/gmxpreprocess/grompp_impl.h"
 #include "gromacs/gmxpreprocess/notset.h"
 #include "gromacs/gmxpreprocess/readir.h"
 #include "gromacs/gmxpreprocess/tomorse.h"
 #include "gromacs/mdlib/calc_verletbuf.h"
 #include "gromacs/mdlib/compute_io.h"
 #include "gromacs/mdlib/constr.h"
+#include "gromacs/mdlib/md_support.h"
 #include "gromacs/mdlib/perf_est.h"
-#include "gromacs/mdlib/sim_util.h"
-#include "gromacs/mdrunutility/mdmodules.h"
+#include "gromacs/mdlib/vsite.h"
+#include "gromacs/mdrun/mdmodules.h"
 #include "gromacs/mdtypes/inputrec.h"
 #include "gromacs/mdtypes/md_enums.h"
 #include "gromacs/mdtypes/nblist.h"
 #include "gromacs/utility/cstringutil.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/fatalerror.h"
+#include "gromacs/utility/filestream.h"
 #include "gromacs/utility/futil.h"
 #include "gromacs/utility/gmxassert.h"
+#include "gromacs/utility/keyvaluetreebuilder.h"
+#include "gromacs/utility/listoflists.h"
+#include "gromacs/utility/logger.h"
+#include "gromacs/utility/loggerbuilder.h"
+#include "gromacs/utility/mdmodulesnotifiers.h"
 #include "gromacs/utility/smalloc.h"
 #include "gromacs/utility/snprintf.h"
 
-static int rm_interactions(int ifunc, int nrmols, t_molinfo mols[])
+/* TODO The implementation details should move to their own source file. */
+InteractionOfType::InteractionOfType(gmx::ArrayRef<const int>  atoms,
+                                     gmx::ArrayRef<const real> params,
+                                     const std::string&        name) :
+    atoms_(atoms.begin(), atoms.end()), interactionTypeName_(name)
 {
-    int  i, n;
+    GMX_RELEASE_ASSERT(
+            params.size() <= forceParam_.size(),
+            gmx::formatString("Cannot have more parameters than the maximum number possible (%d)", MAXFORCEPARAM)
+                    .c_str());
+    std::array<real, MAXFORCEPARAM>::iterator forceParamIt = forceParam_.begin();
+    for (const auto param : params)
+    {
+        *forceParamIt++ = param;
+    }
+    while (forceParamIt != forceParam_.end())
+    {
+        *forceParamIt++ = NOTSET;
+    }
+}
+
+const int& InteractionOfType::ai() const
+{
+    GMX_RELEASE_ASSERT(!atoms_.empty(), "Need to have at least one atom set");
+    return atoms_[0];
+}
+
+const int& InteractionOfType::aj() const
+{
+    GMX_RELEASE_ASSERT(atoms_.size() > 1, "Need to have at least two atoms set");
+    return atoms_[1];
+}
+
+const int& InteractionOfType::ak() const
+{
+    GMX_RELEASE_ASSERT(atoms_.size() > 2, "Need to have at least three atoms set");
+    return atoms_[2];
+}
+
+const int& InteractionOfType::al() const
+{
+    GMX_RELEASE_ASSERT(atoms_.size() > 3, "Need to have at least four atoms set");
+    return atoms_[3];
+}
+
+const int& InteractionOfType::am() const
+{
+    GMX_RELEASE_ASSERT(atoms_.size() > 4, "Need to have at least five atoms set");
+    return atoms_[4];
+}
+
+const real& InteractionOfType::c0() const
+{
+    return forceParam_[0];
+}
+
+const real& InteractionOfType::c1() const
+{
+    return forceParam_[1];
+}
+
+const real& InteractionOfType::c2() const
+{
+    return forceParam_[2];
+}
+
+const std::string& InteractionOfType::interactionTypeName() const
+{
+    return interactionTypeName_;
+}
+
+void InteractionOfType::sortBondAtomIds()
+{
+    if (aj() < ai())
+    {
+        std::swap(atoms_[0], atoms_[1]);
+    }
+}
+
+void InteractionOfType::sortAngleAtomIds()
+{
+    if (ak() < ai())
+    {
+        std::swap(atoms_[0], atoms_[2]);
+    }
+}
+
+void InteractionOfType::sortDihedralAtomIds()
+{
+    if (al() < ai())
+    {
+        std::swap(atoms_[0], atoms_[3]);
+        std::swap(atoms_[1], atoms_[2]);
+    }
+}
 
-    n = 0;
+void InteractionOfType::sortAtomIds()
+{
+    if (isBond())
+    {
+        sortBondAtomIds();
+    }
+    else if (isAngle())
+    {
+        sortAngleAtomIds();
+    }
+    else if (isDihedral())
+    {
+        sortDihedralAtomIds();
+    }
+    else
+    {
+        GMX_THROW(gmx::InternalError(
+                "Can not sort parameters other than bonds, angles or dihedrals"));
+    }
+};
+
+void InteractionOfType::setForceParameter(int pos, real value)
+{
+    GMX_RELEASE_ASSERT(pos < MAXFORCEPARAM,
+                       "Can't set parameter beyond the maximum number of parameters");
+    forceParam_[pos] = value;
+}
+
+void MoleculeInformation::initMolInfo()
+{
+    init_block(&mols);
+    excls.clear();
+    init_t_atoms(&atoms, 0, FALSE);
+}
+
+void MoleculeInformation::partialCleanUp()
+{
+    done_block(&mols);
+}
+
+void MoleculeInformation::fullCleanUp()
+{
+    done_atom(&atoms);
+    done_block(&mols);
+}
+
+static int rm_interactions(int ifunc, gmx::ArrayRef<MoleculeInformation> mols)
+{
+    int n = 0;
     /* For all the molecule types */
-    for (i = 0; i < nrmols; i++)
+    for (auto& mol : mols)
     {
-        n += mols[i].plist[ifunc].nr;
-        mols[i].plist[ifunc].nr = 0;
+        n += mol.interactions[ifunc].size();
+        mol.interactions[ifunc].interactionTypes.clear();
     }
     return n;
 }
 
-static int check_atom_names(const char *fn1, const char *fn2,
-                            gmx_mtop_t *mtop, const t_atoms *at)
+static int check_atom_names(const char*          fn1,
+                            const char*          fn2,
+                            gmx_mtop_t*          mtop,
+                            const t_atoms*       at,
+                            const gmx::MDLogger& logger)
 {
     int      m, i, j, nmismatch;
-    t_atoms *tat;
-#define MAXMISMATCH 20
+    t_atoms* tat;
+
+    constexpr int c_maxNumberOfMismatches = 20;
 
     if (mtop->natoms != at->nr)
     {
@@ -131,24 +284,33 @@ static int check_atom_names(const char *fn1, const char *fn2,
 
     nmismatch = 0;
     i         = 0;
-    for (const gmx_molblock_t &molb : mtop->molblock)
+    for (const gmx_molblock_tmolb : mtop->molblock)
     {
         tat = &mtop->moltype[molb.type].atoms;
         for (m = 0; m < molb.nmol; m++)
         {
             for (j = 0; j < tat->nr; j++)
             {
-                if (strcmp( *(tat->atomname[j]), *(at->atomname[i]) ) != 0)
+                if (strcmp(*(tat->atomname[j]), *(at->atomname[i])) != 0)
                 {
-                    if (nmismatch < MAXMISMATCH)
+                    if (nmismatch < c_maxNumberOfMismatches)
                     {
-                        fprintf(stderr,
-                                "Warning: atom name %d in %s and %s does not match (%s - %s)\n",
-                                i+1, fn1, fn2, *(tat->atomname[j]), *(at->atomname[i]));
+                        GMX_LOG(logger.warning)
+                                .asParagraph()
+                                .appendTextFormatted(
+                                        "atom name %d in %s and %s does not match (%s - %s)",
+                                        i + 1,
+                                        fn1,
+                                        fn2,
+                                        *(tat->atomname[j]),
+                                        *(at->atomname[i]));
                     }
-                    else if (nmismatch == MAXMISMATCH)
+                    else if (nmismatch == c_maxNumberOfMismatches)
                     {
-                        fprintf(stderr, "(more than %d non-matching atom names)\n", MAXMISMATCH);
+                        GMX_LOG(logger.warning)
+                                .asParagraph()
+                                .appendTextFormatted("(more than %d non-matching atom names)",
+                                                     c_maxNumberOfMismatches);
                     }
                     nmismatch++;
                 }
@@ -160,71 +322,7 @@ static int check_atom_names(const char *fn1, const char *fn2,
     return nmismatch;
 }
 
-static void check_eg_vs_cg(gmx_mtop_t *mtop)
-{
-    int            astart, m, cg, j, firstj;
-    unsigned char  firsteg, eg;
-    gmx_moltype_t *molt;
-
-    /* Go through all the charge groups and make sure all their
-     * atoms are in the same energy group.
-     */
-
-    astart = 0;
-    for (const gmx_molblock_t &molb : mtop->molblock)
-    {
-        molt = &mtop->moltype[molb.type];
-        for (m = 0; m < molb.nmol; m++)
-        {
-            for (cg = 0; cg < molt->cgs.nr; cg++)
-            {
-                /* Get the energy group of the first atom in this charge group */
-                firstj  = astart + molt->cgs.index[cg];
-                firsteg = getGroupType(&mtop->groups, egcENER, firstj);
-                for (j = molt->cgs.index[cg]+1; j < molt->cgs.index[cg+1]; j++)
-                {
-                    eg = getGroupType(&mtop->groups, egcENER, astart+j);
-                    if (eg != firsteg)
-                    {
-                        gmx_fatal(FARGS, "atoms %d and %d in charge group %d of molecule type '%s' are in different energy groups",
-                                  firstj+1, astart+j+1, cg+1, *molt->name);
-                    }
-                }
-            }
-            astart += molt->atoms.nr;
-        }
-    }
-}
-
-static void check_cg_sizes(const char *topfn, const t_block *cgs, warninp_t wi)
-{
-    int  maxsize, cg;
-    char warn_buf[STRLEN];
-
-    maxsize = 0;
-    for (cg = 0; cg < cgs->nr; cg++)
-    {
-        maxsize = std::max(maxsize, cgs->index[cg+1]-cgs->index[cg]);
-    }
-
-    if (maxsize > MAX_CHARGEGROUP_SIZE)
-    {
-        gmx_fatal(FARGS, "The largest charge group contains %d atoms. The maximum is %d.", maxsize, MAX_CHARGEGROUP_SIZE);
-    }
-    else if (maxsize > 10)
-    {
-        set_warning_line(wi, topfn, -1);
-        sprintf(warn_buf,
-                "The largest charge group contains %d atoms.\n"
-                "Since atoms only see each other when the centers of geometry of the charge groups they belong to are within the cut-off distance, too large charge groups can lead to serious cut-off artifacts.\n"
-                "For efficiency and accuracy, charge group should consist of a few atoms.\n"
-                "For all-atom force fields use: CH3, CH2, CH, NH2, NH, OH, CO2, CO, etc.",
-                maxsize);
-        warning_note(wi, warn_buf);
-    }
-}
-
-static void check_bonds_timestep(const gmx_mtop_t *mtop, double dt, warninp_t wi)
+static void check_bonds_timestep(const gmx_mtop_t* mtop, double dt, warninp* wi)
 {
     /* This check is not intended to ensure accurate integration,
      * rather it is to signal mistakes in the mdp settings.
@@ -244,31 +342,30 @@ static void check_bonds_timestep(const gmx_mtop_t *mtop, double dt, warninp_t wi
      * of dt=1 fs without constraints and dt=2 fs with only H-bond constraints
      * we set the note limit to 10.
      */
-    int            min_steps_warn = 5;
-    int            min_steps_note = 10;
-    int            ftype;
-    int            i, a1, a2, w_a1, w_a2, j;
-    real           twopi2, limit2, fc, re, m1, m2, period2, w_period2;
-    bool           bFound, bWater, bWarn;
-    char           warn_buf[STRLEN];
+    int  min_steps_warn = 5;
+    int  min_steps_note = 10;
+    int  ftype;
+    int  i, a1, a2, w_a1, w_a2, j;
+    real twopi2, limit2, fc, re, m1, m2, period2, w_period2;
+    bool bFound, bWater, bWarn;
 
     /* Get the interaction parameters */
     gmx::ArrayRef<const t_iparams> ip = mtop->ffparams.iparams;
 
-    twopi2 = gmx::square(2*M_PI);
+    twopi2 = gmx::square(2 * M_PI);
 
-    limit2 = gmx::square(min_steps_note*dt);
+    limit2 = gmx::square(min_steps_note * dt);
 
-    w_a1      = w_a2 = -1;
-    w_period2 = -1.0;
+    w_a1 = w_a2 = -1;
+    w_period2   = -1.0;
 
-    const gmx_moltype_t *w_moltype = nullptr;
-    for (const gmx_moltype_t &moltype : mtop->moltype)
+    const gmx_moltype_tw_moltype = nullptr;
+    for (const gmx_moltype_tmoltype : mtop->moltype)
     {
-        const t_atom           *atom  = moltype.atoms.atom;
-        const InteractionLists &ilist = moltype.ilist;
-        const InteractionList  &ilc   = ilist[F_CONSTR];
-        const InteractionList  &ils   = ilist[F_SETTLE];
+        const t_atom*           atom  = moltype.atoms.atom;
+        const InteractionListsilist = moltype.ilist;
+        const InteractionList&  ilc   = ilist[F_CONSTR];
+        const InteractionList&  ils   = ilist[F_SETTLE];
         for (ftype = 0; ftype < F_NRE; ftype++)
         {
             if (!(ftype == F_BONDS || ftype == F_G96BONDS || ftype == F_HARMONIC))
@@ -276,7 +373,7 @@ static void check_bonds_timestep(const gmx_mtop_t *mtop, double dt, warninp_t wi
                 continue;
             }
 
-            const InteractionList &ilb = ilist[ftype];
+            const InteractionListilb = ilist[ftype];
             for (i = 0; i < ilb.size(); i += 3)
             {
                 fc = ip[ilb.iatoms[i]].harmonic.krA;
@@ -284,15 +381,15 @@ static void check_bonds_timestep(const gmx_mtop_t *mtop, double dt, warninp_t wi
                 if (ftype == F_G96BONDS)
                 {
                     /* Convert squared sqaure fc to harmonic fc */
-                    fc = 2*fc*re;
+                    fc = 2 * fc * re;
                 }
-                a1 = ilb.iatoms[i+1];
-                a2 = ilb.iatoms[i+2];
+                a1 = ilb.iatoms[i + 1];
+                a2 = ilb.iatoms[i + 2];
                 m1 = atom[a1].m;
                 m2 = atom[a2].m;
                 if (fc > 0 && m1 > 0 && m2 > 0)
                 {
-                    period2 = twopi2*m1*m2/((m1 + m2)*fc);
+                    period2 = twopi2 * m1 * m2 / ((m1 + m2) * fc);
                 }
                 else
                 {
@@ -300,30 +397,29 @@ static void check_bonds_timestep(const gmx_mtop_t *mtop, double dt, warninp_t wi
                 }
                 if (debug)
                 {
-                    fprintf(debug, "fc %g m1 %g m2 %g period %g\n",
-                            fc, m1, m2, std::sqrt(period2));
+                    fprintf(debug, "fc %g m1 %g m2 %g period %g\n", fc, m1, m2, std::sqrt(period2));
                 }
                 if (period2 < limit2)
                 {
                     bFound = false;
                     for (j = 0; j < ilc.size(); j += 3)
                     {
-                        if ((ilc.iatoms[j+1] == a1 && ilc.iatoms[j+2] == a2) ||
-                            (ilc.iatoms[j+1] == a2 && ilc.iatoms[j+2] == a1))
+                        if ((ilc.iatoms[j + 1] == a1 && ilc.iatoms[j + 2] == a2)
+                            || (ilc.iatoms[j + 1] == a2 && ilc.iatoms[j + 2] == a1))
                         {
                             bFound = true;
                         }
                     }
                     for (j = 0; j < ils.size(); j += 4)
                     {
-                        if ((a1 == ils.iatoms[j+1] || a1 == ils.iatoms[j+2] || a1 == ils.iatoms[j+3]) &&
-                            (a2 == ils.iatoms[j+1] || a2 == ils.iatoms[j+2] || a2 == ils.iatoms[j+3]))
+                        if ((a1 == ils.iatoms[j + 1] || a1 == ils.iatoms[j + 2] || a1 == ils.iatoms[j + 3])
+                            && (a2 == ils.iatoms[j + 1] || a2 == ils.iatoms[j + 2]
+                                || a2 == ils.iatoms[j + 3]))
                         {
                             bFound = true;
                         }
                     }
-                    if (!bFound &&
-                        (w_moltype == nullptr || period2 < w_period2))
+                    if (!bFound && (w_moltype == nullptr || period2 < w_period2))
                     {
                         w_moltype = &moltype;
                         w_a1      = a1;
@@ -337,62 +433,57 @@ static void check_bonds_timestep(const gmx_mtop_t *mtop, double dt, warninp_t wi
 
     if (w_moltype != nullptr)
     {
-        bWarn = (w_period2 < gmx::square(min_steps_warn*dt));
+        bWarn = (w_period2 < gmx::square(min_steps_warn * dt));
         /* A check that would recognize most water models */
-        bWater = ((*w_moltype->atoms.atomname[0])[0] == 'O' &&
-                  w_moltype->atoms.nr <= 5);
-        sprintf(warn_buf, "The bond in molecule-type %s between atoms %d %s and %d %s has an estimated oscillational period of %.1e ps, which is less than %d times the time step of %.1e ps.\n"
+        bWater = ((*w_moltype->atoms.atomname[0])[0] == 'O' && w_moltype->atoms.nr <= 5);
+        std::string warningMessage = gmx::formatString(
+                "The bond in molecule-type %s between atoms %d %s and %d %s has an estimated "
+                "oscillational period of %.1e ps, which is less than %d times the time step of "
+                "%.1e ps.\n"
                 "%s",
                 *w_moltype->name,
-                w_a1+1, *w_moltype->atoms.atomname[w_a1],
-                w_a2+1, *w_moltype->atoms.atomname[w_a2],
-                std::sqrt(w_period2), bWarn ? min_steps_warn : min_steps_note, dt,
-                bWater ?
-                "Maybe you asked for fexible water." :
-                "Maybe you forgot to change the constraints mdp option.");
+                w_a1 + 1,
+                *w_moltype->atoms.atomname[w_a1],
+                w_a2 + 1,
+                *w_moltype->atoms.atomname[w_a2],
+                std::sqrt(w_period2),
+                bWarn ? min_steps_warn : min_steps_note,
+                dt,
+                bWater ? "Maybe you asked for fexible water."
+                       : "Maybe you forgot to change the constraints mdp option.");
         if (bWarn)
         {
-            warning(wi, warn_buf);
+            warning(wi, warningMessage.c_str());
         }
         else
         {
-            warning_note(wi, warn_buf);
+            warning_note(wi, warningMessage.c_str());
         }
     }
 }
 
-static void check_vel(gmx_mtop_t *mtop, rvec v[])
+static void check_vel(gmx_mtop_tmtop, rvec v[])
 {
-    gmx_mtop_atomloop_all_t aloop;
-    const t_atom           *atom;
-    int                     a;
-
-    aloop = gmx_mtop_atomloop_all_init(mtop);
-    while (gmx_mtop_atomloop_all_next(aloop, &a, &atom))
+    for (const AtomProxy atomP : AtomRange(*mtop))
     {
-        if (atom->ptype == eptShell ||
-            atom->ptype == eptBond  ||
-            atom->ptype == eptVSite)
+        const t_atom& local = atomP.atom();
+        int           i     = atomP.globalAtomNumber();
+        if (local.ptype == ParticleType::Shell || local.ptype == ParticleType::Bond
+            || local.ptype == ParticleType::VSite)
         {
-            clear_rvec(v[a]);
+            clear_rvec(v[i]);
         }
     }
 }
 
-static void check_shells_inputrec(gmx_mtop_t *mtop,
-                                  t_inputrec *ir,
-                                  warninp_t   wi)
+static void check_shells_inputrec(gmx_mtop_t* mtop, t_inputrec* ir, warninp* wi)
 {
-    gmx_mtop_atomloop_all_t aloop;
-    const t_atom           *atom;
-    int                     a, nshells = 0;
-    char                    warn_buf[STRLEN];
+    int nshells = 0;
 
-    aloop = gmx_mtop_atomloop_all_init(mtop);
-    while (gmx_mtop_atomloop_all_next(aloop, &a, &atom))
+    for (const AtomProxy atomP : AtomRange(*mtop))
     {
-        if (atom->ptype == eptShell ||
-            atom->ptype == eptBond)
+        const t_atom& local = atomP.atom();
+        if (local.ptype == ParticleType::Shell || local.ptype == ParticleType::Bond)
         {
             nshells++;
         }
@@ -400,22 +491,21 @@ static void check_shells_inputrec(gmx_mtop_t *mtop,
     if ((nshells > 0) && (ir->nstcalcenergy != 1))
     {
         set_warning_line(wi, "unknown", -1);
-        snprintf(warn_buf, STRLEN,
-                 "There are %d shells, changing nstcalcenergy from %d to 1",
-                 nshells, ir->nstcalcenergy);
+        std::string warningMessage = gmx::formatString(
+                "There are %d shells, changing nstcalcenergy from %d to 1", nshells, ir->nstcalcenergy);
         ir->nstcalcenergy = 1;
-        warning(wi, warn_buf);
+        warning(wi, warningMessage.c_str());
     }
 }
 
 /* TODO Decide whether this function can be consolidated with
  * gmx_mtop_ftype_count */
-static int nint_ftype(gmx_mtop_t *mtop, t_molinfo *mi, int ftype)
+static int nint_ftype(gmx_mtop_t* mtop, gmx::ArrayRef<const MoleculeInformation> mi, int ftype)
 {
     int nint = 0;
-    for (const gmx_molblock_t &molb : mtop->molblock)
+    for (const gmx_molblock_tmolb : mtop->molblock)
     {
-        nint += molb.nmol*mi[molb.type].plist[ftype].nr;
+        nint += molb.nmol * mi[molb.type].interactions[ftype].size();
     }
 
     return nint;
@@ -425,110 +515,115 @@ static int nint_ftype(gmx_mtop_t *mtop, t_molinfo *mi, int ftype)
  * in the order of use in the molblocks,
  * unused molecule types are deleted.
  */
-static void renumber_moltypes(gmx_mtop_t *sys,
-                              int *nmolinfo, t_molinfo **molinfo)
+static void renumber_moltypes(gmx_mtop_t* sys, std::vector<MoleculeInformation>* molinfo)
 {
-    int       *order, norder;
-    t_molinfo *minew;
 
-    snew(order, *nmolinfo);
-    norder = 0;
-    for (gmx_molblock_t &molblock : sys->molblock)
+    std::vector<int> order;
+    for (gmx_molblock_t& molblock : sys->molblock)
     {
-        int i;
-        for (i = 0; i < norder; i++)
+        const auto found = std::find_if(order.begin(), order.end(), [&molblock](const auto& entry) {
+            return molblock.type == entry;
+        });
+        if (found == order.end())
         {
-            if (order[i] == molblock.type)
-            {
-                break;
-            }
+            /* This type did not occur yet, add it */
+            order.push_back(molblock.type);
+            molblock.type = order.size() - 1;
         }
-        if (i == norder)
+        else
         {
-            /* This type did not occur yet, add it */
-            order[norder] = molblock.type;
-            /* Renumber the moltype in the topology */
-            norder++;
+            molblock.type = std::distance(order.begin(), found);
         }
-        molblock.type = i;
     }
 
     /* We still need to reorder the molinfo structs */
-    snew(minew, norder);
-    for (int mi = 0; mi < *nmolinfo; mi++)
+    std::vector<MoleculeInformation> minew(order.size());
+    int                              index = 0;
+    for (auto& mi : *molinfo)
     {
-        int i;
-        for (i = 0; i < norder; i++)
+        const auto found = std::find(order.begin(), order.end(), index);
+        if (found != order.end())
         {
-            if (order[i] == mi)
-            {
-                break;
-            }
-        }
-        if (i == norder)
-        {
-            done_mi(&(*molinfo)[mi]);
+            int pos    = std::distance(order.begin(), found);
+            minew[pos] = mi;
         }
         else
         {
-            minew[i] = (*molinfo)[mi];
+            // Need to manually clean up memory ....
+            mi.fullCleanUp();
         }
+        index++;
     }
-    sfree(order);
-    sfree(*molinfo);
 
-    *nmolinfo = norder;
-    *molinfo  = minew;
+    *molinfo = minew;
 }
 
-static void molinfo2mtop(int nmi, t_molinfo *mi, gmx_mtop_t *mtop)
+static void molinfo2mtop(gmx::ArrayRef<const MoleculeInformation> mi, gmx_mtop_t* mtop)
 {
-    mtop->moltype.resize(nmi);
-    for (int m = 0; m < nmi; m++)
+    mtop->moltype.resize(mi.size());
+    int pos = 0;
+    for (const auto& mol : mi)
     {
-        gmx_moltype_t &molt = mtop->moltype[m];
-        molt.name           = mi[m].name;
-        molt.atoms          = mi[m].atoms;
+        gmx_moltype_t& molt = mtop->moltype[pos];
+        molt.name           = mol.name;
+        molt.atoms          = mol.atoms;
         /* ilists are copied later */
-        molt.cgs            = mi[m].cgs;
-        molt.excls          = mi[m].excls;
+        molt.excls = mol.excls;
+        pos++;
     }
 }
 
-static void
-new_status(const char *topfile, const char *topppfile, const char *confin,
-           t_gromppopts *opts, t_inputrec *ir, gmx_bool bZero,
-           bool bGenVel, bool bVerbose, t_state *state,
-           gpp_atomtype_t atype, gmx_mtop_t *sys,
-           int *nmi, t_molinfo **mi, t_molinfo **intermolecular_interactions,
-           t_params plist[],
-           int *comb, double *reppow, real *fudgeQQ,
-           gmx_bool bMorse,
-           warninp_t wi)
+static void new_status(const char*                           topfile,
+                       const char*                           topppfile,
+                       const char*                           confin,
+                       t_gromppopts*                         opts,
+                       t_inputrec*                           ir,
+                       gmx_bool                              bZero,
+                       bool                                  bGenVel,
+                       bool                                  bVerbose,
+                       t_state*                              state,
+                       PreprocessingAtomTypes*               atypes,
+                       gmx_mtop_t*                           sys,
+                       std::vector<MoleculeInformation>*     mi,
+                       std::unique_ptr<MoleculeInformation>* intermolecular_interactions,
+                       gmx::ArrayRef<InteractionsOfType>     interactions,
+                       CombinationRule*                      comb,
+                       double*                               reppow,
+                       real*                                 fudgeQQ,
+                       gmx_bool                              bMorse,
+                       warninp*                              wi,
+                       const gmx::MDLogger&                  logger)
 {
-    t_molinfo                  *molinfo = nullptr;
     std::vector<gmx_molblock_t> molblock;
-    int                         i, nrmols, nmismatch;
+    int                         i, nmismatch;
     bool                        ffParametrizedWithHBondConstraints;
-    char                        buf[STRLEN];
-    char                        warn_buf[STRLEN];
 
     /* TOPOLOGY processing */
-    sys->name = do_top(bVerbose, topfile, topppfile, opts, bZero, &(sys->symtab),
-                       plist, comb, reppow, fudgeQQ,
-                       atype, &nrmols, &molinfo, intermolecular_interactions,
+    sys->name = do_top(bVerbose,
+                       topfile,
+                       topppfile,
+                       opts,
+                       bZero,
+                       &(sys->symtab),
+                       interactions,
+                       comb,
+                       reppow,
+                       fudgeQQ,
+                       atypes,
+                       mi,
+                       intermolecular_interactions,
                        ir,
                        &molblock,
                        &ffParametrizedWithHBondConstraints,
-                       wi);
+                       wi,
+                       logger);
 
     sys->molblock.clear();
 
     sys->natoms = 0;
-    for (const gmx_molblock_t &molb : molblock)
+    for (const gmx_molblock_tmolb : molblock)
     {
-        if (!sys->molblock.empty() &&
-            molb.type == sys->molblock.back().type)
+        if (!sys->molblock.empty() && molb.type == sys->molblock.back().type)
         {
             /* Merge consecutive blocks with the same molecule type */
             sys->molblock.back().nmol += molb.nmol;
@@ -538,167 +633,161 @@ new_status(const char *topfile, const char *topppfile, const char *confin,
             /* Add a new molblock to the topology */
             sys->molblock.push_back(molb);
         }
-        sys->natoms += molb.nmol*molinfo[sys->molblock.back().type].atoms.nr;
+        sys->natoms += molb.nmol * (*mi)[sys->molblock.back().type].atoms.nr;
     }
     if (sys->molblock.empty())
     {
         gmx_fatal(FARGS, "No molecules were defined in the system");
     }
 
-    renumber_moltypes(sys, &nrmols, &molinfo);
+    renumber_moltypes(sys, mi);
 
     if (bMorse)
     {
-        convert_harmonics(nrmols, molinfo, atype);
+        convert_harmonics(*mi, atypes);
     }
 
-    if (ir->eDisre == edrNone)
+    if (ir->eDisre == DistanceRestraintRefinement::None)
     {
-        i = rm_interactions(F_DISRES, nrmols, molinfo);
+        i = rm_interactions(F_DISRES, *mi);
         if (i > 0)
         {
             set_warning_line(wi, "unknown", -1);
-            sprintf(warn_buf, "disre = no, removed %d distance restraints", i);
-            warning_note(wi, warn_buf);
+            std::string warningMessage =
+                    gmx::formatString("disre = no, removed %d distance restraints", i);
+            warning_note(wi, warningMessage.c_str());
         }
     }
     if (!opts->bOrire)
     {
-        i = rm_interactions(F_ORIRES, nrmols, molinfo);
+        i = rm_interactions(F_ORIRES, *mi);
         if (i > 0)
         {
             set_warning_line(wi, "unknown", -1);
-            sprintf(warn_buf, "orire = no, removed %d orientation restraints", i);
-            warning_note(wi, warn_buf);
+            std::string warningMessage =
+                    gmx::formatString("orire = no, removed %d orientation restraints", i);
+            warning_note(wi, warningMessage.c_str());
         }
     }
 
     /* Copy structures from msys to sys */
-    molinfo2mtop(nrmols, molinfo, sys);
+    molinfo2mtop(*mi, sys);
 
-    gmx_mtop_finalize(sys);
+    sys->finalize();
 
     /* Discourage using the, previously common, setup of applying constraints
      * to all bonds with force fields that have been parametrized with H-bond
      * constraints only. Do not print note with large timesteps or vsites.
      */
-    if (opts->nshake == eshALLBONDS &&
-        ffParametrizedWithHBondConstraints &&
-        ir->delta_t < 0.0026 &&
-        gmx_mtop_ftype_count(sys, F_VSITE3FD) == 0)
+    if (opts->nshake == eshALLBONDS && ffParametrizedWithHBondConstraints && ir->delta_t < 0.0026
+        && gmx_mtop_ftype_count(*sys, F_VSITE3FD) == 0)
     {
         set_warning_line(wi, "unknown", -1);
-        warning_note(wi, "You are using constraints on all bonds, whereas the forcefield "
+        warning_note(wi,
+                     "You are using constraints on all bonds, whereas the forcefield "
                      "has been parametrized only with constraints involving hydrogen atoms. "
-                     "We suggest using constraints = h-bonds instead, this will also improve performance.");
+                     "We suggest using constraints = h-bonds instead, this will also improve "
+                     "performance.");
     }
 
     /* COORDINATE file processing */
     if (bVerbose)
     {
-        fprintf(stderr, "processing coordinates...\n");
+        GMX_LOG(logger.info).asParagraph().appendTextFormatted("processing coordinates...");
     }
 
-    t_topology *conftop;
-    rvec       *x = nullptr;
-    rvec       *v = nullptr;
+    t_topologyconftop;
+    rvec*       x = nullptr;
+    rvec*       v = nullptr;
     snew(conftop, 1);
     read_tps_conf(confin, conftop, nullptr, &x, &v, state->box, FALSE);
     state->natoms = conftop->atoms.nr;
     if (state->natoms != sys->natoms)
     {
-        gmx_fatal(FARGS, "number of coordinates in coordinate file (%s, %d)\n"
+        gmx_fatal(FARGS,
+                  "number of coordinates in coordinate file (%s, %d)\n"
                   "             does not match topology (%s, %d)",
-                  confin, state->natoms, topfile, sys->natoms);
+                  confin,
+                  state->natoms,
+                  topfile,
+                  sys->natoms);
     }
     /* It would be nice to get rid of the copies below, but we don't know
      * a priori if the number of atoms in confin matches what we expect.
      */
-    state->flags |= (1 << estX);
+    state->flags |= enumValueToBitMask(StateEntry::X);
     if (v != nullptr)
     {
-        state->flags |= (1 << estV);
+        state->flags |= enumValueToBitMask(StateEntry::V);
     }
     state_change_natoms(state, state->natoms);
-    std::copy(x, x+state->natoms, state->x.data());
+    std::copy(x, x + state->natoms, state->x.data());
     sfree(x);
     if (v != nullptr)
     {
-        std::copy(v, v+state->natoms, state->v.data());
+        std::copy(v, v + state->natoms, state->v.data());
         sfree(v);
     }
     /* This call fixes the box shape for runs with pressure scaling */
     set_box_rel(ir, state);
 
-    nmismatch = check_atom_names(topfile, confin, sys, &conftop->atoms);
+    nmismatch = check_atom_names(topfile, confin, sys, &conftop->atoms, logger);
     done_top(conftop);
     sfree(conftop);
 
     if (nmismatch)
     {
-        sprintf(buf, "%d non-matching atom name%s\n"
+        std::string warningMessage = gmx::formatString(
+                "%d non-matching atom name%s\n"
                 "atom names from %s will be used\n"
                 "atom names from %s will be ignored\n",
-                nmismatch, (nmismatch == 1) ? "" : "s", topfile, confin);
-        warning(wi, buf);
-    }
-
-    /* If using the group scheme, make sure charge groups are made whole to avoid errors
-     * in calculating charge group size later on
-     */
-    if (ir->cutoff_scheme == ecutsGROUP && ir->ePBC != epbcNONE)
-    {
-        // Need temporary rvec for coordinates
-        do_pbc_first_mtop(nullptr, ir->ePBC, state->box, sys, state->x.rvec_array());
+                nmismatch,
+                (nmismatch == 1) ? "" : "s",
+                topfile,
+                confin);
+        warning(wi, warningMessage.c_str());
     }
 
     /* Do more checks, mostly related to constraints */
     if (bVerbose)
     {
-        fprintf(stderr, "double-checking input for internal consistency...\n");
+        GMX_LOG(logger.info)
+                .asParagraph()
+                .appendTextFormatted("double-checking input for internal consistency...");
     }
     {
-        bool bHasNormalConstraints = 0 < (nint_ftype(sys, molinfo, F_CONSTR) +
-                                          nint_ftype(sys, molinfo, F_CONSTRNC));
-        bool bHasAnyConstraints = bHasNormalConstraints || 0 < nint_ftype(sys, molinfo, F_SETTLE);
-        double_check(ir, state->box,
-                     bHasNormalConstraints,
-                     bHasAnyConstraints,
-                     wi);
+        bool bHasNormalConstraints =
+                0 < (nint_ftype(sys, *mi, F_CONSTR) + nint_ftype(sys, *mi, F_CONSTRNC));
+        bool bHasAnyConstraints = bHasNormalConstraints || 0 < nint_ftype(sys, *mi, F_SETTLE);
+        double_check(ir, state->box, bHasNormalConstraints, bHasAnyConstraints, wi);
     }
 
     if (bGenVel)
     {
-        real                   *mass;
-        gmx_mtop_atomloop_all_t aloop;
-        const t_atom           *atom;
+        real* mass;
 
         snew(mass, state->natoms);
-        aloop = gmx_mtop_atomloop_all_init(sys);
-        while (gmx_mtop_atomloop_all_next(aloop, &i, &atom))
+        for (const AtomProxy atomP : AtomRange(*sys))
         {
-            mass[i] = atom->m;
+            const t_atom& local = atomP.atom();
+            int           i     = atomP.globalAtomNumber();
+            mass[i]             = local.m;
         }
 
         if (opts->seed == -1)
         {
             opts->seed = static_cast<int>(gmx::makeRandomSeed());
-            fprintf(stderr, "Setting gen_seed to %d\n", opts->seed);
+            GMX_LOG(logger.info).asParagraph().appendTextFormatted("Setting gen_seed to %d", opts->seed);
         }
-        state->flags |= (1 << estV);
-        maxwell_speed(opts->tempi, opts->seed, sys, state->v.rvec_array());
+        state->flags |= enumValueToBitMask(StateEntry::V);
+        maxwell_speed(opts->tempi, opts->seed, sys, state->v.rvec_array(), logger);
 
-        stop_cm(stdout, state->natoms, mass, state->x.rvec_array(), state->v.rvec_array());
+        stop_cm(logger, state->natoms, mass, state->x.rvec_array(), state->v.rvec_array());
         sfree(mass);
     }
-
-    *nmi = nrmols;
-    *mi  = molinfo;
 }
 
-static void copy_state(const char *slog, t_trxframe *fr,
-                       bool bReadVel, t_state *state,
-                       double *use_time)
+static void copy_state(const char* slog, t_trxframe* fr, bool bReadVel, t_state* state, double* use_time)
 {
     if (fr->not_ok & FRAME_NOT_OK)
     {
@@ -706,18 +795,17 @@ static void copy_state(const char *slog, t_trxframe *fr,
     }
     if (!fr->bX)
     {
-        gmx_fatal(FARGS, "Did not find a frame with coordinates in file %s",
-                  slog);
+        gmx_fatal(FARGS, "Did not find a frame with coordinates in file %s", slog);
     }
 
-    std::copy(fr->x, fr->x+state->natoms, state->x.data());
+    std::copy(fr->x, fr->x + state->natoms, state->x.data());
     if (bReadVel)
     {
         if (!fr->bV)
         {
             gmx_incons("Trajecory frame unexpectedly does not contain velocities");
         }
-        std::copy(fr->v, fr->v+state->natoms, state->v.data());
+        std::copy(fr->v, fr->v + state->natoms, state->v.data());
     }
     if (fr->bBox)
     {
@@ -727,37 +815,46 @@ static void copy_state(const char *slog, t_trxframe *fr,
     *use_time = fr->time;
 }
 
-static void cont_status(const char *slog, const char *ener,
-                        bool bNeedVel, bool bGenVel, real fr_time,
-                        t_inputrec *ir, t_state *state,
-                        gmx_mtop_t *sys,
-                        const gmx_output_env_t *oenv)
+static void cont_status(const char*             slog,
+                        const char*             ener,
+                        bool                    bNeedVel,
+                        bool                    bGenVel,
+                        real                    fr_time,
+                        t_inputrec*             ir,
+                        t_state*                state,
+                        gmx_mtop_t*             sys,
+                        const gmx_output_env_t* oenv,
+                        const gmx::MDLogger&    logger)
 /* If fr_time == -1 read the last frame available which is complete */
 {
     bool         bReadVel;
     t_trxframe   fr;
-    t_trxstatus *fp;
+    t_trxstatusfp;
     double       use_time;
 
     bReadVel = (bNeedVel && !bGenVel);
 
-    fprintf(stderr,
-            "Reading Coordinates%s and Box size from old trajectory\n",
-            bReadVel ? ", Velocities" : "");
+    GMX_LOG(logger.info)
+            .asParagraph()
+            .appendTextFormatted("Reading Coordinates%s and Box size from old trajectory",
+                                 bReadVel ? ", Velocities" : "");
     if (fr_time == -1)
     {
-        fprintf(stderr, "Will read whole trajectory\n");
+        GMX_LOG(logger.info).asParagraph().appendTextFormatted("Will read whole trajectory");
     }
     else
     {
-        fprintf(stderr, "Will read till time %g\n", fr_time);
+        GMX_LOG(logger.info).asParagraph().appendTextFormatted("Will read till time %g", fr_time);
     }
     if (!bReadVel)
     {
         if (bGenVel)
         {
-            fprintf(stderr, "Velocities generated: "
-                    "ignoring velocities in input trajectory\n");
+            GMX_LOG(logger.info)
+                    .asParagraph()
+                    .appendTextFormatted(
+                            "Velocities generated: "
+                            "ignoring velocities in input trajectory");
         }
         read_first_frame(oenv, &fp, slog, &fr, TRX_NEED_X);
     }
@@ -767,13 +864,15 @@ static void cont_status(const char *slog, const char *ener,
 
         if (!fr.bV)
         {
-            fprintf(stderr,
-                    "\n"
-                    "WARNING: Did not find a frame with velocities in file %s,\n"
-                    "         all velocities will be set to zero!\n\n", slog);
-            for (auto &vi : makeArrayRef(state->v))
+            GMX_LOG(logger.warning)
+                    .asParagraph()
+                    .appendTextFormatted(
+                            "WARNING: Did not find a frame with velocities in file %s,\n"
+                            "         all velocities will be set to zero!",
+                            slog);
+            for (auto& vi : makeArrayRef(state->v))
             {
-                vi = {0, 0, 0};
+                vi = { 0, 0, 0 };
             }
             close_trx(fp);
             /* Search for a frame without velocities */
@@ -786,14 +885,14 @@ static void cont_status(const char *slog, const char *ener,
 
     if (sys->natoms != state->natoms)
     {
-        gmx_fatal(FARGS, "Number of atoms in Topology "
+        gmx_fatal(FARGS,
+                  "Number of atoms in Topology "
                   "is not the same as in Trajectory");
     }
     copy_state(slog, &fr, bReadVel, state, &use_time);
 
     /* Find the appropriate frame */
-    while ((fr_time == -1 || fr.time < fr_time) &&
-           read_next_frame(oenv, fp, &fr))
+    while ((fr_time == -1 || fr.time < fr_time) && read_next_frame(oenv, fp, &fr))
     {
         copy_state(slog, &fr, bReadVel, state, &use_time);
     }
@@ -806,33 +905,35 @@ static void cont_status(const char *slog, const char *ener,
      */
     set_box_rel(ir, state);
 
-    fprintf(stderr, "Using frame at t = %g ps\n", use_time);
-    fprintf(stderr, "Starting time for run is %g ps\n", ir->init_t);
+    GMX_LOG(logger.info).asParagraph().appendTextFormatted("Using frame at t = %g ps", use_time);
+    GMX_LOG(logger.info).asParagraph().appendTextFormatted("Starting time for run is %g ps", ir->init_t);
 
-    if ((ir->epc != epcNO  || ir->etc == etcNOSEHOOVER) && ener)
+    if ((ir->epc != PressureCoupling::No || ir->etc == TemperatureCoupling::NoseHoover) && ener)
     {
-        get_enx_state(ener, use_time, &sys->groups, ir, state);
+        get_enx_state(ener, use_time, sys->groups, ir, state);
         preserve_box_shape(ir, state->box_rel, state->boxv);
     }
 }
 
-static void read_posres(gmx_mtop_t *mtop, t_molinfo *molinfo, gmx_bool bTopB,
-                        const char *fn,
-                        int rc_scaling, int ePBC,
-                        rvec com,
-                        warninp_t wi)
+static void read_posres(gmx_mtop_t*                              mtop,
+                        gmx::ArrayRef<const MoleculeInformation> molinfo,
+                        gmx_bool                                 bTopB,
+                        const char*                              fn,
+                        RefCoordScaling                          rc_scaling,
+                        PbcType                                  pbcType,
+                        rvec                                     com,
+                        warninp*                                 wi,
+                        const gmx::MDLogger&                     logger)
 {
-    gmx_bool           *hadAtom;
-    rvec               *x, *v;
-    dvec                sum;
-    double              totmass;
-    t_topology         *top;
-    matrix              box, invbox;
-    int                 natoms, npbcdim = 0;
-    char                warn_buf[STRLEN];
-    int                 a, i, ai, j, k, nat_molb;
-    t_params           *pr, *prfb;
-    t_atom             *atom;
+    gmx_bool*   hadAtom;
+    rvec *      x, *v;
+    dvec        sum;
+    double      totmass;
+    t_topology* top;
+    matrix      box, invbox;
+    int         natoms, npbcdim = 0;
+    int         a, nat_molb;
+    t_atom*     atom;
 
     snew(top, 1);
     read_tps_conf(fn, top, nullptr, &x, &v, box, FALSE);
@@ -841,16 +942,24 @@ static void read_posres(gmx_mtop_t *mtop, t_molinfo *molinfo, gmx_bool bTopB,
     sfree(top);
     if (natoms != mtop->natoms)
     {
-        sprintf(warn_buf, "The number of atoms in %s (%d) does not match the number of atoms in the topology (%d). Will assume that the first %d atoms in the topology and %s match.", fn, natoms, mtop->natoms, std::min(mtop->natoms, natoms), fn);
-        warning(wi, warn_buf);
+        std::string warningMessage = gmx::formatString(
+                "The number of atoms in %s (%d) does not match the number of atoms in the topology "
+                "(%d). Will assume that the first %d atoms in the topology and %s match.",
+                fn,
+                natoms,
+                mtop->natoms,
+                std::min(mtop->natoms, natoms),
+                fn);
+        warning(wi, warningMessage.c_str());
     }
 
-    npbcdim = ePBC2npbcdim(ePBC);
+    npbcdim = numPbcDimensions(pbcType);
+    GMX_RELEASE_ASSERT(npbcdim <= DIM, "Invalid npbcdim");
     clear_rvec(com);
-    if (rc_scaling != erscNO)
+    if (rc_scaling != RefCoordScaling::No)
     {
         copy_mat(box, invbox);
-        for (j = npbcdim; j < DIM; j++)
+        for (int j = npbcdim; j < DIM; j++)
         {
             clear_rvec(invbox[j]);
             invbox[j][j] = 1;
@@ -863,108 +972,124 @@ static void read_posres(gmx_mtop_t *mtop, t_molinfo *molinfo, gmx_bool bTopB,
     totmass = 0;
     a       = 0;
     snew(hadAtom, natoms);
-    for (gmx_molblock_t &molb : mtop->molblock)
+    for (gmx_molblock_tmolb : mtop->molblock)
     {
-        nat_molb = molb.nmol*mtop->moltype[molb.type].atoms.nr;
-        pr       = &(molinfo[molb.type].plist[F_POSRES]);
-        prfb     = &(molinfo[molb.type].plist[F_FBPOSRES]);
-        if (pr->nr > 0 || prfb->nr > 0)
+        nat_molb                       = molb.nmol * mtop->moltype[molb.type].atoms.nr;
+        const InteractionsOfType* pr   = &(molinfo[molb.type].interactions[F_POSRES]);
+        const InteractionsOfType* prfb = &(molinfo[molb.type].interactions[F_FBPOSRES]);
+        if (pr->size() > 0 || prfb->size() > 0)
         {
             atom = mtop->moltype[molb.type].atoms.atom;
-            for (i = 0; (i < pr->nr); i++)
+            for (const auto& restraint : pr->interactionTypes)
             {
-                ai = pr->param[i].ai();
+                int ai = restraint.ai();
                 if (ai >= natoms)
                 {
-                    gmx_fatal(FARGS, "Position restraint atom index (%d) in moltype '%s' is larger than number of atoms in %s (%d).\n",
-                              ai+1, *molinfo[molb.type].name, fn, natoms);
+                    gmx_fatal(FARGS,
+                              "Position restraint atom index (%d) in moltype '%s' is larger than "
+                              "number of atoms in %s (%d).\n",
+                              ai + 1,
+                              *molinfo[molb.type].name,
+                              fn,
+                              natoms);
                 }
                 hadAtom[ai] = TRUE;
-                if (rc_scaling == erscCOM)
+                if (rc_scaling == RefCoordScaling::Com)
                 {
                     /* Determine the center of mass of the posres reference coordinates */
-                    for (j = 0; j < npbcdim; j++)
+                    for (int j = 0; j < npbcdim; j++)
                     {
-                        sum[j] += atom[ai].m*x[a+ai][j];
+                        sum[j] += atom[ai].m * x[a + ai][j];
                     }
-                    totmass  += atom[ai].m;
+                    totmass += atom[ai].m;
                 }
             }
             /* Same for flat-bottomed posres, but do not count an atom twice for COM */
-            for (i = 0; (i < prfb->nr); i++)
+            for (const auto& restraint : prfb->interactionTypes)
             {
-                ai = prfb->param[i].ai();
+                int ai = restraint.ai();
                 if (ai >= natoms)
                 {
-                    gmx_fatal(FARGS, "Position restraint atom index (%d) in moltype '%s' is larger than number of atoms in %s (%d).\n",
-                              ai+1, *molinfo[molb.type].name, fn, natoms);
+                    gmx_fatal(FARGS,
+                              "Position restraint atom index (%d) in moltype '%s' is larger than "
+                              "number of atoms in %s (%d).\n",
+                              ai + 1,
+                              *molinfo[molb.type].name,
+                              fn,
+                              natoms);
                 }
-                if (rc_scaling == erscCOM && !hadAtom[ai])
+                if (rc_scaling == RefCoordScaling::Com && !hadAtom[ai])
                 {
                     /* Determine the center of mass of the posres reference coordinates */
-                    for (j = 0; j < npbcdim; j++)
+                    for (int j = 0; j < npbcdim; j++)
                     {
-                        sum[j] += atom[ai].m*x[a+ai][j];
+                        sum[j] += atom[ai].m * x[a + ai][j];
                     }
-                    totmass  += atom[ai].m;
+                    totmass += atom[ai].m;
                 }
             }
             if (!bTopB)
             {
                 molb.posres_xA.resize(nat_molb);
-                for (i = 0; i < nat_molb; i++)
+                for (int i = 0; i < nat_molb; i++)
                 {
-                    copy_rvec(x[a+i], molb.posres_xA[i]);
+                    copy_rvec(x[a + i], molb.posres_xA[i]);
                 }
             }
             else
             {
                 molb.posres_xB.resize(nat_molb);
-                for (i = 0; i < nat_molb; i++)
+                for (int i = 0; i < nat_molb; i++)
                 {
-                    copy_rvec(x[a+i], molb.posres_xB[i]);
+                    copy_rvec(x[a + i], molb.posres_xB[i]);
                 }
             }
         }
         a += nat_molb;
     }
-    if (rc_scaling == erscCOM)
+    if (rc_scaling == RefCoordScaling::Com)
     {
         if (totmass == 0)
         {
             gmx_fatal(FARGS, "The total mass of the position restraint atoms is 0");
         }
-        for (j = 0; j < npbcdim; j++)
+        for (int j = 0; j < npbcdim; j++)
         {
-            com[j] = sum[j]/totmass;
+            com[j] = sum[j] / totmass;
         }
-        fprintf(stderr, "The center of mass of the position restraint coord's is %6.3f %6.3f %6.3f\n", com[XX], com[YY], com[ZZ]);
+        GMX_LOG(logger.info)
+                .asParagraph()
+                .appendTextFormatted(
+                        "The center of mass of the position restraint coord's is %6.3f %6.3f %6.3f",
+                        com[XX],
+                        com[YY],
+                        com[ZZ]);
     }
 
-    if (rc_scaling != erscNO)
+    if (rc_scaling != RefCoordScaling::No)
     {
         GMX_ASSERT(npbcdim <= DIM, "Only DIM dimensions can have PBC");
 
-        for (gmx_molblock_t &molb : mtop->molblock)
+        for (gmx_molblock_tmolb : mtop->molblock)
         {
-            nat_molb = molb.nmol*mtop->moltype[molb.type].atoms.nr;
+            nat_molb = molb.nmol * mtop->moltype[molb.type].atoms.nr;
             if (!molb.posres_xA.empty() || !molb.posres_xB.empty())
             {
-                std::vector<gmx::RVec> &xp = (!bTopB ? molb.posres_xA : molb.posres_xB);
-                for (i = 0; i < nat_molb; i++)
+                std::vector<gmx::RVec>xp = (!bTopB ? molb.posres_xA : molb.posres_xB);
+                for (int i = 0; i < nat_molb; i++)
                 {
-                    for (j = 0; j < npbcdim; j++)
+                    for (int j = 0; j < npbcdim; j++)
                     {
-                        if (rc_scaling == erscALL)
+                        if (rc_scaling == RefCoordScaling::All)
                         {
                             /* Convert from Cartesian to crystal coordinates */
                             xp[i][j] *= invbox[j][j];
-                            for (k = j+1; k < npbcdim; k++)
+                            for (int k = j + 1; k < npbcdim; k++)
                             {
-                                xp[i][j] += invbox[k][j]*xp[i][k];
+                                xp[i][j] += invbox[k][j] * xp[i][k];
                             }
                         }
-                        else if (rc_scaling == erscCOM)
+                        else if (rc_scaling == RefCoordScaling::Com)
                         {
                             /* Subtract the center of mass */
                             xp[i][j] -= com[j];
@@ -974,15 +1099,15 @@ static void read_posres(gmx_mtop_t *mtop, t_molinfo *molinfo, gmx_bool bTopB,
             }
         }
 
-        if (rc_scaling == erscCOM)
+        if (rc_scaling == RefCoordScaling::Com)
         {
             /* Convert the COM from Cartesian to crystal coordinates */
-            for (j = 0; j < npbcdim; j++)
+            for (int j = 0; j < npbcdim; j++)
             {
                 com[j] *= invbox[j][j];
-                for (k = j+1; k < npbcdim; k++)
+                for (int k = j + 1; k < npbcdim; k++)
                 {
-                    com[j] += invbox[k][j]*com[k];
+                    com[j] += invbox[k][j] * com[k];
                 }
             }
         }
@@ -993,41 +1118,53 @@ static void read_posres(gmx_mtop_t *mtop, t_molinfo *molinfo, gmx_bool bTopB,
     sfree(hadAtom);
 }
 
-static void gen_posres(gmx_mtop_t *mtop, t_molinfo *mi,
-                       const char *fnA, const char *fnB,
-                       int rc_scaling, int ePBC,
-                       rvec com, rvec comB,
-                       warninp_t wi)
+static void gen_posres(gmx_mtop_t*                              mtop,
+                       gmx::ArrayRef<const MoleculeInformation> mi,
+                       const char*                              fnA,
+                       const char*                              fnB,
+                       RefCoordScaling                          rc_scaling,
+                       PbcType                                  pbcType,
+                       rvec                                     com,
+                       rvec                                     comB,
+                       warninp*                                 wi,
+                       const gmx::MDLogger&                     logger)
 {
-    read_posres  (mtop, mi, FALSE, fnA, rc_scaling, ePBC, com, wi);
+    read_posres(mtop, mi, FALSE, fnA, rc_scaling, pbcType, com, wi, logger);
     /* It is safer to simply read the b-state posres rather than trying
      * to be smart and copy the positions.
      */
-    read_posres(mtop, mi, TRUE, fnB, rc_scaling, ePBC, comB, wi);
+    read_posres(mtop, mi, TRUE, fnB, rc_scaling, pbcType, comB, wi, logger);
 }
 
-static void set_wall_atomtype(gpp_atomtype_t at, t_gromppopts *opts,
-                              t_inputrec *ir, warninp_t wi)
+static void set_wall_atomtype(PreprocessingAtomTypes* at,
+                              t_gromppopts*           opts,
+                              t_inputrec*             ir,
+                              warninp*                wi,
+                              const gmx::MDLogger&    logger)
 {
-    int  i;
-    char warn_buf[STRLEN];
+    int i;
 
     if (ir->nwall > 0)
     {
-        fprintf(stderr, "Searching the wall atom type(s)\n");
+        GMX_LOG(logger.info).asParagraph().appendTextFormatted("Searching the wall atom type(s)");
     }
     for (i = 0; i < ir->nwall; i++)
     {
-        ir->wall_atomtype[i] = get_atomtype_type(opts->wall_atomtype[i], at);
-        if (ir->wall_atomtype[i] == NOTSET)
+        auto atomType = at->atomTypeFromName(opts->wall_atomtype[i]);
+        if (!atomType.has_value())
         {
-            sprintf(warn_buf, "Specified wall atom type %s is not defined", opts->wall_atomtype[i]);
-            warning_error(wi, warn_buf);
+            std::string warningMessage = gmx::formatString(
+                    "Specified wall atom type %s is not defined", opts->wall_atomtype[i]);
+            warning_error(wi, warningMessage.c_str());
+        }
+        else
+        {
+            ir->wall_atomtype[i] = *atomType;
         }
     }
 }
 
-static int nrdf_internal(t_atoms *atoms)
+static int nrdf_internal(const t_atoms* atoms)
 {
     int i, nmass, nrdf;
 
@@ -1035,8 +1172,7 @@ static int nrdf_internal(t_atoms *atoms)
     for (i = 0; i < atoms->nr; i++)
     {
         /* Vsite ptype might not be set here yet, so also check the mass */
-        if ((atoms->atom[i].ptype == eptAtom ||
-             atoms->atom[i].ptype == eptNucleus)
+        if ((atoms->atom[i].ptype == ParticleType::Atom || atoms->atom[i].ptype == ParticleType::Nucleus)
             && atoms->atom[i].m > 0)
         {
             nmass++;
@@ -1044,21 +1180,16 @@ static int nrdf_internal(t_atoms *atoms)
     }
     switch (nmass)
     {
-        case 0:  nrdf = 0; break;
-        case 1:  nrdf = 0; break;
-        case 2:  nrdf = 1; break;
-        default: nrdf = nmass*3 - 6; break;
+        case 0: // Fall through intended
+        case 1: nrdf = 0; break;
+        case 2: nrdf = 1; break;
+        default: nrdf = nmass * 3 - 6; break;
     }
 
     return nrdf;
 }
 
-static void
-spline1d( double              dx,
-          const double *      y,
-          int                 n,
-          double       *      u,
-          double       *      y2 )
+static void spline1d(double dx, const double* y, int n, double* u, double* y2)
 {
     int    i;
     double p, q;
@@ -1066,65 +1197,57 @@ spline1d( double              dx,
     y2[0] = 0.0;
     u[0]  = 0.0;
 
-    for (i = 1; i < n-1; i++)
+    for (i = 1; i < n - 1; i++)
     {
-        p     = 0.5*y2[i-1]+2.0;
-        y2[i] = -0.5/p;
-        q     = (y[i+1]-2.0*y[i]+y[i-1])/dx;
-        u[i]  = (3.0*q/dx-0.5*u[i-1])/p;
+        p     = 0.5 * y2[i - 1] + 2.0;
+        y2[i] = -0.5 / p;
+        q     = (y[i + 1] - 2.0 * y[i] + y[i - 1]) / dx;
+        u[i]  = (3.0 * q / dx - 0.5 * u[i - 1]) / p;
     }
 
-    y2[n-1] = 0.0;
+    y2[n - 1] = 0.0;
 
-    for (i = n-2; i >= 0; i--)
+    for (i = n - 2; i >= 0; i--)
     {
-        y2[i] = y2[i]*y2[i+1]+u[i];
+        y2[i] = y2[i] * y2[i + 1] + u[i];
     }
 }
 
 
 static void
-interpolate1d( double           xmin,
-               double           dx,
-               const double *   ya,
-               const double *   y2a,
-               double           x,
-               double       *   y,
-               double       *   y1)
+interpolate1d(double xmin, double dx, const double* ya, const double* y2a, double x, double* y, double* y1)
 {
     int    ix;
     double a, b;
 
-    ix = static_cast<int>((x-xmin)/dx);
+    ix = static_cast<int>((x - xmin) / dx);
 
-    a = (xmin+(ix+1)*dx-x)/dx;
-    b = (x-xmin-ix*dx)/dx;
+    a = (xmin + (ix + 1) * dx - x) / dx;
+    b = (x - xmin - ix * dx) / dx;
 
-    *y  = a*ya[ix]+b*ya[ix+1]+((a*a*a-a)*y2a[ix]+(b*b*b-b)*y2a[ix+1])*(dx*dx)/6.0;
-    *y1 = (ya[ix+1]-ya[ix])/dx-(3.0*a*a-1.0)/6.0*dx*y2a[ix]+(3.0*b*b-1.0)/6.0*dx*y2a[ix+1];
+    *y = a * ya[ix] + b * ya[ix + 1]
+         + ((a * a * a - a) * y2a[ix] + (b * b * b - b) * y2a[ix + 1]) * (dx * dx) / 6.0;
+    *y1 = (ya[ix + 1] - ya[ix]) / dx - (3.0 * a * a - 1.0) / 6.0 * dx * y2a[ix]
+          + (3.0 * b * b - 1.0) / 6.0 * dx * y2a[ix + 1];
 }
 
 
-static void
-setup_cmap (int                    grid_spacing,
-            int                    nc,
-            const real *           grid,
-            gmx_cmap_t       *     cmap_grid)
+static void setup_cmap(int grid_spacing, int nc, gmx::ArrayRef<const real> grid, gmx_cmap_t* cmap_grid)
 {
-    int                 i, j, k, ii, jj, kk, idx;
-    int                 offset;
-    double              dx, xmin, v, v1, v2, v12;
-    double              phi, psi;
+    int    i, j, k, ii, jj, kk, idx;
+    int    offset;
+    double dx, xmin, v, v1, v2, v12;
+    double phi, psi;
 
-    std::vector<double> tmp_u(2*grid_spacing, 0.0);
-    std::vector<double> tmp_u2(2*grid_spacing, 0.0);
-    std::vector<double> tmp_yy(2*grid_spacing, 0.0);
-    std::vector<double> tmp_y1(2*grid_spacing, 0.0);
-    std::vector<double> tmp_t2(2*grid_spacing*2*grid_spacing, 0.0);
-    std::vector<double> tmp_grid(2*grid_spacing*2*grid_spacing, 0.0);
+    std::vector<double> tmp_u(2 * grid_spacing, 0.0);
+    std::vector<double> tmp_u2(2 * grid_spacing, 0.0);
+    std::vector<double> tmp_yy(2 * grid_spacing, 0.0);
+    std::vector<double> tmp_y1(2 * grid_spacing, 0.0);
+    std::vector<double> tmp_t2(2 * grid_spacing * 2 * grid_spacing, 0.0);
+    std::vector<double> tmp_grid(2 * grid_spacing * 2 * grid_spacing, 0.0);
 
-    dx   = 360.0/grid_spacing;
-    xmin = -180.0-dx*grid_spacing/2;
+    dx   = 360.0 / grid_spacing;
+    xmin = -180.0 - dx * grid_spacing / 2;
 
     for (kk = 0; kk < nc; kk++)
     {
@@ -1134,121 +1257,125 @@ setup_cmap (int                    grid_spacing,
          */
         offset = kk * grid_spacing * grid_spacing * 2;
 
-        for (i = 0; i < 2*grid_spacing; i++)
+        for (i = 0; i < 2 * grid_spacing; i++)
         {
-            ii = (i+grid_spacing-grid_spacing/2)%grid_spacing;
+            ii = (i + grid_spacing - grid_spacing / 2) % grid_spacing;
 
-            for (j = 0; j < 2*grid_spacing; j++)
+            for (j = 0; j < 2 * grid_spacing; j++)
             {
-                jj = (j+grid_spacing-grid_spacing/2)%grid_spacing;
-                tmp_grid[i*grid_spacing*2+j] = grid[offset+ii*grid_spacing+jj];
+                jj = (j + grid_spacing - grid_spacing / 2) % grid_spacing;
+                tmp_grid[i * grid_spacing * 2 + j] = grid[offset + ii * grid_spacing + jj];
             }
         }
 
-        for (i = 0; i < 2*grid_spacing; i++)
+        for (i = 0; i < 2 * grid_spacing; i++)
         {
-            spline1d(dx, &(tmp_grid[2*grid_spacing*i]), 2*grid_spacing, tmp_u.data(), &(tmp_t2[2*grid_spacing*i]));
+            spline1d(dx,
+                     &(tmp_grid[2 * grid_spacing * i]),
+                     2 * grid_spacing,
+                     tmp_u.data(),
+                     &(tmp_t2[2 * grid_spacing * i]));
         }
 
-        for (i = grid_spacing/2; i < grid_spacing+grid_spacing/2; i++)
+        for (i = grid_spacing / 2; i < grid_spacing + grid_spacing / 2; i++)
         {
-            ii  = i-grid_spacing/2;
-            phi = ii*dx-180.0;
+            ii  = i - grid_spacing / 2;
+            phi = ii * dx - 180.0;
 
-            for (j = grid_spacing/2; j < grid_spacing+grid_spacing/2; j++)
+            for (j = grid_spacing / 2; j < grid_spacing + grid_spacing / 2; j++)
             {
-                jj  = j-grid_spacing/2;
-                psi = jj*dx-180.0;
+                jj  = j - grid_spacing / 2;
+                psi = jj * dx - 180.0;
 
-                for (k = 0; k < 2*grid_spacing; k++)
+                for (k = 0; k < 2 * grid_spacing; k++)
                 {
-                    interpolate1d(xmin, dx, &(tmp_grid[2*grid_spacing*k]),
-                                  &(tmp_t2[2*grid_spacing*k]), psi, &tmp_yy[k], &tmp_y1[k]);
+                    interpolate1d(xmin,
+                                  dx,
+                                  &(tmp_grid[2 * grid_spacing * k]),
+                                  &(tmp_t2[2 * grid_spacing * k]),
+                                  psi,
+                                  &tmp_yy[k],
+                                  &tmp_y1[k]);
                 }
 
-                spline1d(dx, tmp_yy.data(), 2*grid_spacing, tmp_u.data(), tmp_u2.data());
+                spline1d(dx, tmp_yy.data(), 2 * grid_spacing, tmp_u.data(), tmp_u2.data());
                 interpolate1d(xmin, dx, tmp_yy.data(), tmp_u2.data(), phi, &v, &v1);
-                spline1d(dx, tmp_y1.data(), 2*grid_spacing, tmp_u.data(), tmp_u2.data());
+                spline1d(dx, tmp_y1.data(), 2 * grid_spacing, tmp_u.data(), tmp_u2.data());
                 interpolate1d(xmin, dx, tmp_y1.data(), tmp_u2.data(), phi, &v2, &v12);
 
-                idx = ii*grid_spacing+jj;
-                cmap_grid->cmapdata[kk].cmap[idx*4]   = grid[offset+ii*grid_spacing+jj];
-                cmap_grid->cmapdata[kk].cmap[idx*4+1] = v1;
-                cmap_grid->cmapdata[kk].cmap[idx*4+2] = v2;
-                cmap_grid->cmapdata[kk].cmap[idx*4+3] = v12;
+                idx                                       = ii * grid_spacing + jj;
+                cmap_grid->cmapdata[kk].cmap[idx * 4]     = grid[offset + ii * grid_spacing + jj];
+                cmap_grid->cmapdata[kk].cmap[idx * 4 + 1] = v1;
+                cmap_grid->cmapdata[kk].cmap[idx * 4 + 2] = v2;
+                cmap_grid->cmapdata[kk].cmap[idx * 4 + 3] = v12;
             }
         }
     }
 }
 
-static void init_cmap_grid(gmx_cmap_t *cmap_grid, int ngrid, int grid_spacing)
+static void init_cmap_grid(gmx_cmap_tcmap_grid, int ngrid, int grid_spacing)
 {
     int i, nelem;
 
     cmap_grid->grid_spacing = grid_spacing;
-    nelem                   = cmap_grid->grid_spacing*cmap_grid->grid_spacing;
+    nelem                   = cmap_grid->grid_spacing * cmap_grid->grid_spacing;
 
     cmap_grid->cmapdata.resize(ngrid);
 
     for (i = 0; i < ngrid; i++)
     {
-        cmap_grid->cmapdata[i].cmap.resize(4*nelem);
+        cmap_grid->cmapdata[i].cmap.resize(4 * nelem);
     }
 }
 
 
-static int count_constraints(const gmx_mtop_t *mtop, t_molinfo *mi, warninp_t wi)
+static int count_constraints(const gmx_mtop_t* mtop, gmx::ArrayRef<const MoleculeInformation> mi, warninp* wi)
 {
-    int             count, count_mol, i;
-    t_params       *plist;
-    char            buf[STRLEN];
+    int count, count_mol;
 
     count = 0;
-    for (const gmx_molblock_t &molb : mtop->molblock)
+    for (const gmx_molblock_tmolb : mtop->molblock)
     {
-        count_mol = 0;
-        plist     = mi[molb.type].plist;
+        count_mol                                            = 0;
+        gmx::ArrayRef<const InteractionsOfType> interactions = mi[molb.type].interactions;
 
-        for (i = 0; i < F_NRE; i++)
+        for (int i = 0; i < F_NRE; i++)
         {
             if (i == F_SETTLE)
             {
-                count_mol += 3*plist[i].nr;
+                count_mol += 3 * interactions[i].size();
             }
             else if (interaction_function[i].flags & IF_CONSTRAINT)
             {
-                count_mol += plist[i].nr;
+                count_mol += interactions[i].size();
             }
         }
 
         if (count_mol > nrdf_internal(&mi[molb.type].atoms))
         {
-            sprintf(buf,
+            std::string warningMessage = gmx::formatString(
                     "Molecule type '%s' has %d constraints.\n"
-                    "For stability and efficiency there should not be more constraints than internal number of degrees of freedom: %d.\n",
-                    *mi[molb.type].name, count_mol,
+                    "For stability and efficiency there should not be more constraints than "
+                    "internal number of degrees of freedom: %d.\n",
+                    *mi[molb.type].name,
+                    count_mol,
                     nrdf_internal(&mi[molb.type].atoms));
-            warning(wi, buf);
+            warning(wi, warningMessage.c_str());
         }
-        count += molb.nmol*count_mol;
+        count += molb.nmol * count_mol;
     }
 
     return count;
 }
 
-static real calc_temp(const gmx_mtop_t *mtop,
-                      const t_inputrec *ir,
-                      rvec             *v)
+static real calc_temp(const gmx_mtop_t* mtop, const t_inputrec* ir, rvec* v)
 {
-    gmx_mtop_atomloop_all_t aloop;
-    const t_atom           *atom;
-    int                     a;
-
-    double                  sum_mv2 = 0;
-    aloop = gmx_mtop_atomloop_all_init(mtop);
-    while (gmx_mtop_atomloop_all_next(aloop, &a, &atom))
+    double sum_mv2 = 0;
+    for (const AtomProxy atomP : AtomRange(*mtop))
     {
-        sum_mv2 += atom->m*norm2(v[a]);
+        const t_atom& local = atomP.atom();
+        int           i     = atomP.globalAtomNumber();
+        sum_mv2 += local.m * norm2(v[i]);
     }
 
     double nrdf = 0;
@@ -1257,15 +1384,14 @@ static real calc_temp(const gmx_mtop_t *mtop,
         nrdf += ir->opts.nrdf[g];
     }
 
-    return sum_mv2/(nrdf*BOLTZ);
+    return sum_mv2 / (nrdf * gmx::c_boltz);
 }
 
-static real get_max_reference_temp(const t_inputrec *ir,
-                                   warninp_t         wi)
+static real get_max_reference_temp(const t_inputrec* ir, warninp* wi)
 {
-    real         ref_t;
-    int          i;
-    bool         bNoCoupl;
+    real ref_t;
+    int  i;
+    bool bNoCoupl;
 
     ref_t    = 0;
     bNoCoupl = false;
@@ -1283,11 +1409,12 @@ static real get_max_reference_temp(const t_inputrec *ir,
 
     if (bNoCoupl)
     {
-        char buf[STRLEN];
-
-        sprintf(buf, "Some temperature coupling groups do not use temperature coupling. We will assume their temperature is not more than %.3f K. If their temperature is higher, the energy error and the Verlet buffer might be underestimated.",
+        std::string warningMessage = gmx::formatString(
+                "Some temperature coupling groups do not use temperature coupling. We will assume "
+                "their temperature is not more than %.3f K. If their temperature is higher, the "
+                "energy error and the Verlet buffer might be underestimated.",
                 ref_t);
-        warning(wi, buf);
+        warning(wi, warningMessage.c_str());
     }
 
     return ref_t;
@@ -1296,11 +1423,9 @@ static real get_max_reference_temp(const t_inputrec *ir,
 /* Checks if there are unbound atoms in moleculetype molt.
  * Prints a note for each unbound atoms and a warning if any is present.
  */
-static void checkForUnboundAtoms(const gmx_moltype_t     *molt,
-                                 gmx_bool                 bVerbose,
-                                 warninp_t                wi)
+static void checkForUnboundAtoms(const gmx_moltype_t* molt, gmx_bool bVerbose, warninp* wi, const gmx::MDLogger& logger)
 {
-    const t_atoms *atoms = &molt->atoms;
+    const t_atomsatoms = &molt->atoms;
 
     if (atoms->nr == 1)
     {
@@ -1312,11 +1437,10 @@ static void checkForUnboundAtoms(const gmx_moltype_t     *molt,
 
     for (int ftype = 0; ftype < F_NRE; ftype++)
     {
-        if (((interaction_function[ftype].flags & IF_BOND) && ftype != F_CONNBONDS) ||
-            (interaction_function[ftype].flags & IF_CONSTRAINT) ||
-            ftype == F_SETTLE)
+        if (((interaction_function[ftype].flags & IF_BOND) && NRAL(ftype) == 2 && ftype != F_CONNBONDS)
+            || (interaction_function[ftype].flags & IF_CONSTRAINT) || ftype == F_SETTLE)
         {
-            const InteractionList &il   = molt->ilist[ftype];
+            const InteractionListil   = molt->ilist[ftype];
             const int              nral = NRAL(ftype);
 
             for (int i = 0; i < il.size(); i += 1 + nral)
@@ -1332,13 +1456,18 @@ static void checkForUnboundAtoms(const gmx_moltype_t     *molt,
     int numDanglingAtoms = 0;
     for (int a = 0; a < atoms->nr; a++)
     {
-        if (atoms->atom[a].ptype != eptVSite &&
-            count[a] == 0)
+        if (atoms->atom[a].ptype != ParticleType::VSite && count[a] == 0)
         {
             if (bVerbose)
             {
-                fprintf(stderr, "\nAtom %d '%s' in moleculetype '%s' is not bound by a potential or constraint to any other atom in the same moleculetype.\n",
-                        a + 1, *atoms->atomname[a], *molt->name);
+                GMX_LOG(logger.warning)
+                        .asParagraph()
+                        .appendTextFormatted(
+                                "Atom %d '%s' in moleculetype '%s' is not bound by a potential or "
+                                "constraint to any other atom in the same moleculetype.",
+                                a + 1,
+                                *atoms->atomname[a],
+                                *molt->name);
             }
             numDanglingAtoms++;
         }
@@ -1346,21 +1475,24 @@ static void checkForUnboundAtoms(const gmx_moltype_t     *molt,
 
     if (numDanglingAtoms > 0)
     {
-        char buf[STRLEN];
-        sprintf(buf, "In moleculetype '%s' %d atoms are not bound by a potential or constraint to any other atom in the same moleculetype. Although technically this might not cause issues in a simulation, this often means that the user forgot to add a bond/potential/constraint or put multiple molecules in the same moleculetype definition by mistake. Run with -v to get information for each atom.",
-                *molt->name, numDanglingAtoms);
-        warning_note(wi, buf);
+        std::string warningMessage = gmx::formatString(
+                "In moleculetype '%s' %d atoms are not bound by a potential or constraint to any "
+                "other atom in the same moleculetype. Although technically this might not cause "
+                "issues in a simulation, this often means that the user forgot to add a "
+                "bond/potential/constraint or put multiple molecules in the same moleculetype "
+                "definition by mistake. Run with -v to get information for each atom.",
+                *molt->name,
+                numDanglingAtoms);
+        warning_note(wi, warningMessage.c_str());
     }
 }
 
 /* Checks all moleculetypes for unbound atoms */
-static void checkForUnboundAtoms(const gmx_mtop_t     *mtop,
-                                 gmx_bool              bVerbose,
-                                 warninp_t             wi)
+static void checkForUnboundAtoms(const gmx_mtop_t* mtop, gmx_bool bVerbose, warninp* wi, const gmx::MDLogger& logger)
 {
-    for (const gmx_moltype_t &molt : mtop->moltype)
+    for (const gmx_moltype_tmolt : mtop->moltype)
     {
-        checkForUnboundAtoms(&molt, bVerbose, wi);
+        checkForUnboundAtoms(&molt, bVerbose, wi, logger);
     }
 }
 
@@ -1371,29 +1503,27 @@ static void checkForUnboundAtoms(const gmx_mtop_t     *mtop,
  * involved in a single constraint; the mass of the two atoms needs to
  * differ by more than \p massFactorThreshold.
  */
-static bool haveDecoupledModeInMol(const gmx_moltype_t            &molt,
-                                   gmx::ArrayRef<const t_iparams>  iparams,
-                                   real                            massFactorThreshold)
+static bool haveDecoupledModeInMol(const gmx_moltype_t&           molt,
+                                   gmx::ArrayRef<const t_iparams> iparams,
+                                   real                           massFactorThreshold)
 {
-    if (molt.ilist[F_CONSTR].size() == 0 &&
-        molt.ilist[F_CONSTRNC].size() == 0)
+    if (molt.ilist[F_CONSTR].empty() && molt.ilist[F_CONSTRNC].empty())
     {
         return false;
     }
 
-    const t_atom * atom = molt.atoms.atom;
+    const t_atom* atom = molt.atoms.atom;
 
-    t_blocka       atomToConstraints =
-        gmx::make_at2con(molt, iparams,
-                         gmx::FlexibleConstraintTreatment::Exclude);
+    const auto atomToConstraints =
+            gmx::make_at2con(molt, iparams, gmx::FlexibleConstraintTreatment::Exclude);
 
-    bool           haveDecoupledMode = false;
+    bool haveDecoupledMode = false;
     for (int ftype = 0; ftype < F_NRE; ftype++)
     {
         if (interaction_function[ftype].flags & IF_ATYPE)
         {
             const int              nral = NRAL(ftype);
-            const InteractionList &il   = molt.ilist[ftype];
+            const InteractionListil   = molt.ilist[ftype];
             for (int i = 0; i < il.size(); i += 1 + nral)
             {
                 /* Here we check for the mass difference between the atoms
@@ -1413,24 +1543,22 @@ static bool haveDecoupledModeInMol(const gmx_moltype_t            &molt,
                 int a0 = il.iatoms[1 + i];
                 int a1 = il.iatoms[1 + i + 1];
                 int a2 = il.iatoms[1 + i + 2];
-                if ((atom[a0].m > atom[a2].m*massFactorThreshold ||
-                     atom[a2].m > atom[a0].m*massFactorThreshold) &&
-                    atomToConstraints.index[a0 + 1] - atomToConstraints.index[a0] == 1 &&
-                    atomToConstraints.index[a2 + 1] - atomToConstraints.index[a2] == 1 &&
-                    atomToConstraints.index[a1 + 1] - atomToConstraints.index[a1] >= 3)
+                if ((atom[a0].m > atom[a2].m * massFactorThreshold || atom[a2].m > atom[a0].m * massFactorThreshold)
+                    && atomToConstraints[a0].ssize() == 1 && atomToConstraints[a2].ssize() == 1
+                    && atomToConstraints[a1].ssize() >= 3)
                 {
-                    int      constraint0 = atomToConstraints.a[atomToConstraints.index[a0]];
-                    int      constraint2 = atomToConstraints.a[atomToConstraints.index[a2]];
+                    int constraint0 = atomToConstraints[a0][0];
+                    int constraint2 = atomToConstraints[a2][0];
 
-                    bool     foundAtom0  = false;
-                    bool     foundAtom2  = false;
-                    for (int conIndex = atomToConstraints.index[a1]; conIndex < atomToConstraints.index[a1 + 1]; conIndex++)
+                    bool foundAtom0 = false;
+                    bool foundAtom2 = false;
+                    for (const int constraint : atomToConstraints[a1])
                     {
-                        if (atomToConstraints.a[conIndex] == constraint0)
+                        if (constraint == constraint0)
                         {
                             foundAtom0 = true;
                         }
-                        if (atomToConstraints.a[conIndex] == constraint2)
+                        if (constraint == constraint2)
                         {
                             foundAtom2 = true;
                         }
@@ -1444,19 +1572,15 @@ static bool haveDecoupledModeInMol(const gmx_moltype_t            &molt,
         }
     }
 
-    done_blocka(&atomToConstraints);
-
     return haveDecoupledMode;
 }
 
 /*! \brief Checks if the Verlet buffer and constraint accuracy is sufficient for decoupled dynamic modes.
  *
- * When decoupled modes are present and the accuray in insufficient,
+ * When decoupled modes are present and the accuracy in insufficient,
  * this routine issues a warning if the accuracy is insufficient.
  */
-static void checkDecoupledModeAccuracy(const gmx_mtop_t *mtop,
-                                       const t_inputrec *ir,
-                                       warninp_t         wi)
+static void checkDecoupledModeAccuracy(const gmx_mtop_t* mtop, const t_inputrec* ir, warninp* wi)
 {
     /* We only have issues with decoupled modes with normal MD.
      * With stochastic dynamics equipartitioning is enforced strongly.
@@ -1481,26 +1605,27 @@ static void checkDecoupledModeAccuracy(const gmx_mtop_t *mtop,
      * energy/time, respectively, so they will "only" work correctly
      * for atomistic force fields using MD units.
      */
-    const real     massFactorThreshold      = 13.0;
-    const real     bufferToleranceThreshold = 1e-4;
-    const int      lincsIterationThreshold  = 2;
-    const int      lincsOrderThreshold      = 4;
-    const real     shakeToleranceThreshold  = 0.005*ir->delta_t;
-
-    bool           lincsWithSufficientTolerance = (ir->eConstrAlg == econtLINCS && ir->nLincsIter >= lincsIterationThreshold && ir->nProjOrder >= lincsOrderThreshold);
-    bool           shakeWithSufficientTolerance = (ir->eConstrAlg == econtSHAKE && ir->shake_tol <= 1.1*shakeToleranceThreshold);
-    if (ir->cutoff_scheme == ecutsVERLET &&
-        ir->verletbuf_tol <= 1.1*bufferToleranceThreshold &&
-        (lincsWithSufficientTolerance || shakeWithSufficientTolerance))
+    const real massFactorThreshold      = 13.0;
+    const real bufferToleranceThreshold = 1e-4;
+    const int  lincsIterationThreshold  = 2;
+    const int  lincsOrderThreshold      = 4;
+    const real shakeToleranceThreshold  = 0.005 * ir->delta_t;
+
+    bool lincsWithSufficientTolerance =
+            (ir->eConstrAlg == ConstraintAlgorithm::Lincs
+             && ir->nLincsIter >= lincsIterationThreshold && ir->nProjOrder >= lincsOrderThreshold);
+    bool shakeWithSufficientTolerance = (ir->eConstrAlg == ConstraintAlgorithm::Shake
+                                         && ir->shake_tol <= 1.1 * shakeToleranceThreshold);
+    if (ir->cutoff_scheme == CutoffScheme::Verlet && ir->verletbuf_tol <= 1.1 * bufferToleranceThreshold
+        && (lincsWithSufficientTolerance || shakeWithSufficientTolerance))
     {
         return;
     }
 
     bool haveDecoupledMode = false;
-    for (const gmx_moltype_t &molt : mtop->moltype)
+    for (const gmx_moltype_tmolt : mtop->moltype)
     {
-        if (haveDecoupledModeInMol(molt, mtop->ffparams.iparams,
-                                   massFactorThreshold))
+        if (haveDecoupledModeInMol(molt, mtop->ffparams.iparams, massFactorThreshold))
         {
             haveDecoupledMode = true;
         }
@@ -1509,86 +1634,110 @@ static void checkDecoupledModeAccuracy(const gmx_mtop_t *mtop,
     if (haveDecoupledMode)
     {
         std::string message = gmx::formatString(
-                    "There are atoms at both ends of an angle, connected by constraints "
-                    "and with masses that differ by more than a factor of %g. This means "
-                    "that there are likely dynamic modes that are only very weakly coupled.",
-                    massFactorThreshold);
-        if (ir->cutoff_scheme == ecutsVERLET)
+                "There are atoms at both ends of an angle, connected by constraints "
+                "and with masses that differ by more than a factor of %g. This means "
+                "that there are likely dynamic modes that are only very weakly coupled.",
+                massFactorThreshold);
+        if (ir->cutoff_scheme == CutoffScheme::Verlet)
         {
             message += gmx::formatString(
-                        " To ensure good equipartitioning, you need to either not use "
-                        "constraints on all bonds (but, if possible, only on bonds involving "
-                        "hydrogens) or use integrator = %s or decrease one or more tolerances: "
-                        "verlet-buffer-tolerance <= %g, LINCS iterations >= %d, LINCS order "
-                        ">= %d or SHAKE tolerance <= %g",
-                        ei_names[eiSD1],
-                        bufferToleranceThreshold,
-                        lincsIterationThreshold, lincsOrderThreshold,
-                        shakeToleranceThreshold);
+                    " To ensure good equipartitioning, you need to either not use "
+                    "constraints on all bonds (but, if possible, only on bonds involving "
+                    "hydrogens) or use integrator = %s or decrease one or more tolerances: "
+                    "verlet-buffer-tolerance <= %g, LINCS iterations >= %d, LINCS order "
+                    ">= %d or SHAKE tolerance <= %g",
+                    enumValueToString(IntegrationAlgorithm::SD1),
+                    bufferToleranceThreshold,
+                    lincsIterationThreshold,
+                    lincsOrderThreshold,
+                    shakeToleranceThreshold);
         }
         else
         {
             message += gmx::formatString(
-                        " To ensure good equipartitioning, we suggest to switch to the %s "
-                        "cutoff-scheme, since that allows for better control over the Verlet "
-                        "buffer size and thus over the energy drift.",
-                        ecutscheme_names[ecutsVERLET]);
+                    " To ensure good equipartitioning, we suggest to switch to the %s "
+                    "cutoff-scheme, since that allows for better control over the Verlet "
+                    "buffer size and thus over the energy drift.",
+                    enumValueToString(CutoffScheme::Verlet));
         }
         warning(wi, message);
     }
 }
 
-static void set_verlet_buffer(const gmx_mtop_t *mtop,
-                              t_inputrec       *ir,
-                              real              buffer_temp,
-                              matrix            box,
-                              warninp_t         wi)
+static void set_verlet_buffer(const gmx_mtop_t*    mtop,
+                              t_inputrec*          ir,
+                              real                 buffer_temp,
+                              matrix               box,
+                              warninp*             wi,
+                              const gmx::MDLogger& logger)
 {
-    real                   rlist_1x1;
-    int                    n_nonlin_vsite;
-    char                   warn_buf[STRLEN];
-
-    printf("Determining Verlet buffer for a tolerance of %g kJ/mol/ps at %g K\n", ir->verletbuf_tol, buffer_temp);
+    GMX_LOG(logger.info)
+            .asParagraph()
+            .appendTextFormatted(
+                    "Determining Verlet buffer for a tolerance of %g kJ/mol/ps at %g K",
+                    ir->verletbuf_tol,
+                    buffer_temp);
 
     /* Calculate the buffer size for simple atom vs atoms list */
     VerletbufListSetup listSetup1x1;
     listSetup1x1.cluster_size_i = 1;
     listSetup1x1.cluster_size_j = 1;
-    calc_verlet_buffer_size(mtop, det(box), ir, ir->nstlist, ir->nstlist - 1,
-                            buffer_temp, &listSetup1x1,
-                            &n_nonlin_vsite, &rlist_1x1);
+    const real rlist_1x1        = calcVerletBufferSize(
+            *mtop, det(box), *ir, ir->nstlist, ir->nstlist - 1, buffer_temp, listSetup1x1);
 
     /* Set the pair-list buffer size in ir */
-    VerletbufListSetup listSetup4x4 =
-        verletbufGetSafeListSetup(ListSetupType::CpuNoSimd);
-    calc_verlet_buffer_size(mtop, det(box), ir, ir->nstlist, ir->nstlist - 1,
-                            buffer_temp, &listSetup4x4,
-                            &n_nonlin_vsite, &ir->rlist);
+    VerletbufListSetup listSetup4x4 = verletbufGetSafeListSetup(ListSetupType::CpuNoSimd);
+    ir->rlist                       = calcVerletBufferSize(
+            *mtop, det(box), *ir, ir->nstlist, ir->nstlist - 1, buffer_temp, listSetup4x4);
 
+    const int n_nonlin_vsite = gmx::countNonlinearVsites(*mtop);
     if (n_nonlin_vsite > 0)
     {
-        sprintf(warn_buf, "There are %d non-linear virtual site constructions. Their contribution to the energy error is approximated. In most cases this does not affect the error significantly.", n_nonlin_vsite);
-        warning_note(wi, warn_buf);
-    }
-
-    printf("Calculated rlist for %dx%d atom pair-list as %.3f nm, buffer size %.3f nm\n",
-           1, 1, rlist_1x1, rlist_1x1-std::max(ir->rvdw, ir->rcoulomb));
-
-    printf("Set rlist, assuming %dx%d atom pair-list, to %.3f nm, buffer size %.3f nm\n",
-           listSetup4x4.cluster_size_i, listSetup4x4.cluster_size_j,
-           ir->rlist, ir->rlist-std::max(ir->rvdw, ir->rcoulomb));
-
-    printf("Note that mdrun will redetermine rlist based on the actual pair-list setup\n");
-
-    if (gmx::square(ir->rlist) >= max_cutoff2(ir->ePBC, box))
-    {
-        gmx_fatal(FARGS, "The pair-list cut-off (%g nm) is longer than half the shortest box vector or longer than the smallest box diagonal element (%g nm). Increase the box size or decrease nstlist or increase verlet-buffer-tolerance.", ir->rlist, std::sqrt(max_cutoff2(ir->ePBC, box)));
+        std::string warningMessage = gmx::formatString(
+                "There are %d non-linear virtual site constructions. Their contribution to the "
+                "energy error is approximated. In most cases this does not affect the error "
+                "significantly.",
+                n_nonlin_vsite);
+        warning_note(wi, warningMessage);
+    }
+
+    GMX_LOG(logger.info)
+            .asParagraph()
+            .appendTextFormatted(
+                    "Calculated rlist for %dx%d atom pair-list as %.3f nm, buffer size %.3f nm",
+                    1,
+                    1,
+                    rlist_1x1,
+                    rlist_1x1 - std::max(ir->rvdw, ir->rcoulomb));
+
+    GMX_LOG(logger.info)
+            .asParagraph()
+            .appendTextFormatted(
+                    "Set rlist, assuming %dx%d atom pair-list, to %.3f nm, buffer size %.3f nm",
+                    listSetup4x4.cluster_size_i,
+                    listSetup4x4.cluster_size_j,
+                    ir->rlist,
+                    ir->rlist - std::max(ir->rvdw, ir->rcoulomb));
+
+    GMX_LOG(logger.info)
+            .asParagraph()
+            .appendTextFormatted(
+                    "Note that mdrun will redetermine rlist based on the actual pair-list setup");
+
+    if (gmx::square(ir->rlist) >= max_cutoff2(ir->pbcType, box))
+    {
+        gmx_fatal(FARGS,
+                  "The pair-list cut-off (%g nm) is longer than half the shortest box vector or "
+                  "longer than the smallest box diagonal element (%g nm). Increase the box size or "
+                  "decrease nstlist or increase verlet-buffer-tolerance.",
+                  ir->rlist,
+                  std::sqrt(max_cutoff2(ir->pbcType, box)));
     }
 }
 
-int gmx_grompp(int argc, char *argv[])
+int gmx_grompp(int argc, charargv[])
 {
-    const char            *desc[] = {
+    const chardesc[] = {
         "[THISMODULE] (the gromacs preprocessor)",
         "reads a molecular topology file, checks the validity of the",
         "file, expands the topology from a molecular description to an atomic",
@@ -1601,9 +1750,7 @@ int gmx_grompp(int argc, char *argv[])
         "Then a coordinate file is read and velocities can be generated",
         "from a Maxwellian distribution if requested.",
         "[THISMODULE] also reads parameters for [gmx-mdrun] ",
-        "(eg. number of MD steps, time step, cut-off), and others such as",
-        "NEMD parameters, which are corrected so that the net acceleration",
-        "is zero.",
+        "(eg. number of MD steps, time step, cut-off).",
         "Eventually a binary file is produced that can serve as the sole input",
         "file for the MD program.[PAR]",
 
@@ -1686,64 +1833,70 @@ int gmx_grompp(int argc, char *argv[])
         "interpret the output messages before attempting to bypass them with",
         "this option."
     };
-    t_gromppopts          *opts;
-    int                    nmi;
-    t_molinfo             *mi, *intermolecular_interactions;
-    gpp_atomtype_t         atype;
-    int                    nvsite, comb;
-    t_params              *plist;
-    real                   fudgeQQ;
-    double                 reppow;
-    const char            *mdparin;
-    int                    ntype;
-    bool                   bNeedVel, bGenVel;
-    gmx_bool               have_atomnumber;
-    gmx_output_env_t      *oenv;
-    gmx_bool               bVerbose = FALSE;
-    warninp_t              wi;
-    char                   warn_buf[STRLEN];
-
-    t_filenm               fnm[] = {
-        { efMDP, nullptr,  nullptr,        ffREAD  },
-        { efMDP, "-po", "mdout",     ffWRITE },
-        { efSTX, "-c",  nullptr,        ffREAD  },
-        { efSTX, "-r",  "restraint", ffOPTRD },
-        { efSTX, "-rb", "restraint", ffOPTRD },
-        { efNDX, nullptr,  nullptr,        ffOPTRD },
-        { efTOP, nullptr,  nullptr,        ffREAD  },
-        { efTOP, "-pp", "processed", ffOPTWR },
-        { efTPR, "-o",  nullptr,        ffWRITE },
-        { efTRN, "-t",  nullptr,        ffOPTRD },
-        { efEDR, "-e",  nullptr,        ffOPTRD },
-        /* This group is needed by the VMD viewer as the start configuration for IMD sessions: */
-        { efGRO, "-imd", "imdgroup", ffOPTWR },
-        { efTRN, "-ref", "rotref",   ffOPTRW }
-    };
+    std::vector<MoleculeInformation>     mi;
+    std::unique_ptr<MoleculeInformation> intermolecular_interactions;
+    int                                  nvsite;
+    CombinationRule                      comb;
+    real                                 fudgeQQ;
+    double                               reppow;
+    const char*                          mdparin;
+    bool                                 bNeedVel, bGenVel;
+    gmx_output_env_t*                    oenv;
+    gmx_bool                             bVerbose = FALSE;
+    warninp*                             wi;
+
+    t_filenm fnm[] = { { efMDP, nullptr, nullptr, ffREAD },
+                       { efMDP, "-po", "mdout", ffWRITE },
+                       { efSTX, "-c", nullptr, ffREAD },
+                       { efSTX, "-r", "restraint", ffOPTRD },
+                       { efSTX, "-rb", "restraint", ffOPTRD },
+                       { efNDX, nullptr, nullptr, ffOPTRD },
+                       { efTOP, nullptr, nullptr, ffREAD },
+                       { efTOP, "-pp", "processed", ffOPTWR },
+                       { efTPR, "-o", nullptr, ffWRITE },
+                       { efTRN, "-t", nullptr, ffOPTRD },
+                       { efEDR, "-e", nullptr, ffOPTRD },
+                       /* This group is needed by the VMD viewer as the start configuration for IMD sessions: */
+                       { efGRO, "-imd", "imdgroup", ffOPTWR },
+                       { efTRN, "-ref", "rotref", ffOPTRW | ffALLOW_MISSING },
+                       /* This group is needed by the QMMM MDModule: */
+                       { efQMI, "-qmi", nullptr, ffOPTRD } };
 #define NFILE asize(fnm)
 
     /* Command line options */
-    gmx_bool            bRenum   = TRUE;
-    gmx_bool            bRmVSBds = TRUE, bZero = FALSE;
-    int                 i, maxwarn = 0;
-    real                fr_time = -1;
-    t_pargs             pa[]    = {
-        { "-v",       FALSE, etBOOL, {&bVerbose},
-          "Be loud and noisy" },
-        { "-time",    FALSE, etREAL, {&fr_time},
-          "Take frame at or first after this time." },
-        { "-rmvsbds", FALSE, etBOOL, {&bRmVSBds},
+    gmx_bool bRenum   = TRUE;
+    gmx_bool bRmVSBds = TRUE, bZero = FALSE;
+    int      i, maxwarn             = 0;
+    real     fr_time = -1;
+    t_pargs  pa[]    = {
+        { "-v", FALSE, etBOOL, { &bVerbose }, "Be loud and noisy" },
+        { "-time", FALSE, etREAL, { &fr_time }, "Take frame at or first after this time." },
+        { "-rmvsbds",
+          FALSE,
+          etBOOL,
+          { &bRmVSBds },
           "Remove constant bonded interactions with virtual sites" },
-        { "-maxwarn", FALSE, etINT,  {&maxwarn},
-          "Number of allowed warnings during input processing. Not for normal use and may generate unstable systems" },
-        { "-zero",    FALSE, etBOOL, {&bZero},
-          "Set parameters for bonded interactions without defaults to zero instead of generating an error" },
-        { "-renum",   FALSE, etBOOL, {&bRenum},
+        { "-maxwarn",
+          FALSE,
+          etINT,
+          { &maxwarn },
+          "Number of allowed warnings during input processing. Not for normal use and may "
+          "generate unstable systems" },
+        { "-zero",
+          FALSE,
+          etBOOL,
+          { &bZero },
+          "Set parameters for bonded interactions without defaults to zero instead of "
+          "generating an error" },
+        { "-renum",
+          FALSE,
+          etBOOL,
+          { &bRenum },
           "Renumber atomtypes and minimize number of atomtypes" }
     };
 
     /* Parse the command line */
-    if (!parse_common_args(&argc, argv, 0, NFILE, fnm, asize(pa), pa,
-                           asize(desc), desc, 0, nullptr, &oenv))
+    if (!parse_common_args(&argc, argv, 0, NFILE, fnm, asize(pa), pa, asize(desc), desc, 0, nullptr, &oenv))
     {
         return 0;
     }
@@ -1751,11 +1904,19 @@ int gmx_grompp(int argc, char *argv[])
     /* Initiate some variables */
     gmx::MDModules mdModules;
     t_inputrec     irInstance;
-    t_inputrec    *ir = &irInstance;
-    snew(opts, 1);
+    t_inputrec*    ir = &irInstance;
+    t_gromppopts   optsInstance;
+    t_gromppopts*  opts = &optsInstance;
     snew(opts->include, STRLEN);
     snew(opts->define, STRLEN);
 
+    gmx::LoggerBuilder builder;
+    builder.addTargetStream(gmx::MDLogger::LogLevel::Info, &gmx::TextOutputFile::standardOutput());
+    builder.addTargetStream(gmx::MDLogger::LogLevel::Warning, &gmx::TextOutputFile::standardError());
+    gmx::LoggerOwner    logOwner(builder.build());
+    const gmx::MDLogger logger(logOwner.logger());
+
+
     wi = init_warning(TRUE, maxwarn);
 
     /* PARAMETER file processing */
@@ -1765,59 +1926,94 @@ int gmx_grompp(int argc, char *argv[])
     {
         get_ir(mdparin, opt2fn("-po", NFILE, fnm), &mdModules, ir, opts, WriteMdpHeader::yes, wi);
     }
-    GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
+    GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR
+
+    // Now that the MDModules have their options assigned from get_ir, subscribe
+    // to eventual notifications during pre-processing their data
+    mdModules.subscribeToPreProcessingNotifications();
+
+    // Notify MDModules of existing logger
+    mdModules.notifiers().preProcessingNotifier_.notify(logger);
+
+    // Notify MDModules of existing warninp
+    mdModules.notifiers().preProcessingNotifier_.notify(wi);
+
+    // Notify QMMM MDModule of external QM input file command-line option
+    {
+        gmx::QMInputFileName qmInputFileName = { ftp2bSet(efQMI, NFILE, fnm), ftp2fn(efQMI, NFILE, fnm) };
+        mdModules.notifiers().preProcessingNotifier_.notify(qmInputFileName);
+    }
 
     if (bVerbose)
     {
-        fprintf(stderr, "checking input for internal consistency...\n");
+        GMX_LOG(logger.info)
+                .asParagraph()
+                .appendTextFormatted("checking input for internal consistency...");
     }
-    check_ir(mdparin, ir, opts, wi);
+    check_ir(mdparin, mdModules.notifiers(), ir, opts, wi);
 
     if (ir->ld_seed == -1)
     {
         ir->ld_seed = static_cast<int>(gmx::makeRandomSeed());
-        fprintf(stderr, "Setting the LD random seed to %" PRId64 "\n", ir->ld_seed);
+        GMX_LOG(logger.info)
+                .asParagraph()
+                .appendTextFormatted("Setting the LD random seed to %" PRId64 "", ir->ld_seed);
     }
 
     if (ir->expandedvals->lmc_seed == -1)
     {
         ir->expandedvals->lmc_seed = static_cast<int>(gmx::makeRandomSeed());
-        fprintf(stderr, "Setting the lambda MC random seed to %d\n", ir->expandedvals->lmc_seed);
+        GMX_LOG(logger.info)
+                .asParagraph()
+                .appendTextFormatted("Setting the lambda MC random seed to %d", ir->expandedvals->lmc_seed);
     }
 
     bNeedVel = EI_STATE_VELOCITY(ir->eI);
     bGenVel  = (bNeedVel && opts->bGenVel);
     if (bGenVel && ir->bContinuation)
     {
-        sprintf(warn_buf,
+        std::string warningMessage = gmx::formatString(
                 "Generating velocities is inconsistent with attempting "
                 "to continue a previous run. Choose only one of "
                 "gen-vel = yes and continuation = yes.");
-        warning_error(wi, warn_buf);
+        warning_error(wi, warningMessage);
     }
 
-    snew(plist, F_NRE);
-    init_plist(plist);
-    gmx_mtop_t sys;
-    atype = init_atomtype();
+    std::array<InteractionsOfType, F_NRE> interactions;
+    gmx_mtop_t                            sys;
+    PreprocessingAtomTypes                atypes;
     if (debug)
     {
         pr_symtab(debug, 0, "Just opened", &sys.symtab);
     }
 
-    const char *fn = ftp2fn(efTOP, NFILE, fnm);
+    const charfn = ftp2fn(efTOP, NFILE, fnm);
     if (!gmx_fexist(fn))
     {
         gmx_fatal(FARGS, "%s does not exist", fn);
     }
 
     t_state state;
-    new_status(fn, opt2fn_null("-pp", NFILE, fnm), opt2fn("-c", NFILE, fnm),
-               opts, ir, bZero, bGenVel, bVerbose, &state,
-               atype, &sys, &nmi, &mi, &intermolecular_interactions,
-               plist, &comb, &reppow, &fudgeQQ,
+    new_status(fn,
+               opt2fn_null("-pp", NFILE, fnm),
+               opt2fn("-c", NFILE, fnm),
+               opts,
+               ir,
+               bZero,
+               bGenVel,
+               bVerbose,
+               &state,
+               &atypes,
+               &sys,
+               &mi,
+               &intermolecular_interactions,
+               interactions,
+               &comb,
+               &reppow,
+               &fudgeQQ,
                opts->bMorse,
-               wi);
+               wi,
+               logger);
 
     if (debug)
     {
@@ -1828,8 +2024,7 @@ int gmx_grompp(int argc, char *argv[])
     /* set parameters for virtual site construction (not for vsiten) */
     for (size_t mt = 0; mt < sys.moltype.size(); mt++)
     {
-        nvsite +=
-            set_vsites(bVerbose, &sys.moltype[mt].atoms, atype, mi[mt].plist);
+        nvsite += set_vsites(bVerbose, &sys.moltype[mt].atoms, &atypes, mi[mt].interactions, logger);
     }
     /* now throw away all obsolete bonds, angles and dihedrals: */
     /* note: constraints are ALWAYS removed */
@@ -1837,75 +2032,56 @@ int gmx_grompp(int argc, char *argv[])
     {
         for (size_t mt = 0; mt < sys.moltype.size(); mt++)
         {
-            clean_vsite_bondeds(mi[mt].plist, sys.moltype[mt].atoms.nr, bRmVSBds);
+            clean_vsite_bondeds(mi[mt].interactions, sys.moltype[mt].atoms.nr, bRmVSBds, logger);
         }
     }
 
-    if (ir->cutoff_scheme == ecutsVERLET)
-    {
-        fprintf(stderr, "Removing all charge groups because cutoff-scheme=%s\n",
-                ecutscheme_names[ir->cutoff_scheme]);
-
-        /* Remove all charge groups */
-        gmx_mtop_remove_chargegroups(&sys);
-    }
-
-    if (count_constraints(&sys, mi, wi) && (ir->eConstrAlg == econtSHAKE))
+    if ((count_constraints(&sys, mi, wi) != 0) && (ir->eConstrAlg == ConstraintAlgorithm::Shake))
     {
-        if (ir->eI == eiCG || ir->eI == eiLBFGS)
+        if (ir->eI == IntegrationAlgorithm::CG || ir->eI == IntegrationAlgorithm::LBFGS)
         {
-            sprintf(warn_buf, "Can not do %s with %s, use %s",
-                    EI(ir->eI), econstr_names[econtSHAKE], econstr_names[econtLINCS]);
-            warning_error(wi, warn_buf);
+            std::string warningMessage =
+                    gmx::formatString("Can not do %s with %s, use %s",
+                                      enumValueToString(ir->eI),
+                                      enumValueToString(ConstraintAlgorithm::Shake),
+                                      enumValueToString(ConstraintAlgorithm::Lincs));
+            warning_error(wi, warningMessage);
         }
         if (ir->bPeriodicMols)
         {
-            sprintf(warn_buf, "Can not do periodic molecules with %s, use %s",
-                    econstr_names[econtSHAKE], econstr_names[econtLINCS]);
-            warning_error(wi, warn_buf);
+            std::string warningMessage =
+                    gmx::formatString("Can not do periodic molecules with %s, use %s",
+                                      enumValueToString(ConstraintAlgorithm::Shake),
+                                      enumValueToString(ConstraintAlgorithm::Lincs));
+            warning_error(wi, warningMessage);
         }
     }
 
-    if (EI_SD (ir->eI) &&  ir->etc != etcNO)
+    if (EI_SD(ir->eI) && ir->etc != TemperatureCoupling::No)
     {
         warning_note(wi, "Temperature coupling is ignored with SD integrators.");
     }
 
-    /* If we are doing QM/MM, check that we got the atom numbers */
-    have_atomnumber = TRUE;
-    for (i = 0; i < get_atomtype_ntypes(atype); i++)
-    {
-        have_atomnumber = have_atomnumber && (get_atomtype_atomnumber(i, atype) >= 0);
-    }
-    if (!have_atomnumber && ir->bQMMM)
-    {
-        warning_error(wi,
-                      "\n"
-                      "It appears as if you are trying to run a QM/MM calculation, but the force\n"
-                      "field you are using does not contain atom numbers fields. This is an\n"
-                      "optional field (introduced in GROMACS 3.3) for general runs, but mandatory\n"
-                      "for QM/MM. The good news is that it is easy to add - put the atom number as\n"
-                      "an integer just before the mass column in ffXXXnb.itp.\n"
-                      "NB: United atoms have the same atom numbers as normal ones.\n\n");
-    }
-
     /* Check for errors in the input now, since they might cause problems
      * during processing further down.
      */
     check_warning_error(wi, FARGS);
 
-    if (nint_ftype(&sys, mi, F_POSRES) > 0 ||
-        nint_ftype(&sys, mi, F_FBPOSRES) > 0)
+    if (nint_ftype(&sys, mi, F_POSRES) > 0 || nint_ftype(&sys, mi, F_FBPOSRES) > 0)
     {
-        if (ir->epc == epcPARRINELLORAHMAN || ir->epc == epcMTTK)
+        if (ir->epc == PressureCoupling::ParrinelloRahman || ir->epc == PressureCoupling::Mttk)
         {
-            sprintf(warn_buf, "You are combining position restraints with %s pressure coupling, which can lead to instabilities. If you really want to combine position restraints with pressure coupling, we suggest to use %s pressure coupling instead.",
-                    EPCOUPLTYPE(ir->epc), EPCOUPLTYPE(epcBERENDSEN));
-            warning_note(wi, warn_buf);
+            std::string warningMessage = gmx::formatString(
+                    "You are combining position restraints with %s pressure coupling, which can "
+                    "lead to instabilities. If you really want to combine position restraints with "
+                    "pressure coupling, we suggest to use %s pressure coupling instead.",
+                    enumValueToString(ir->epc),
+                    enumValueToString(PressureCoupling::Berendsen));
+            warning_note(wi, warningMessage);
         }
 
-        const char *fn = opt2fn("-r", NFILE, fnm);
-        const char *fnB;
+        const charfn = opt2fn("-r", NFILE, fnm);
+        const charfnB;
 
         if (!gmx_fexist(fn))
         {
@@ -1913,7 +2089,8 @@ int gmx_grompp(int argc, char *argv[])
                       "Cannot find position restraint file %s (option -r).\n"
                       "From GROMACS-2018, you need to specify the position restraint "
                       "coordinate files explicitly to avoid mistakes, although you can "
-                      "still use the same file as you specify for the -c option.", fn);
+                      "still use the same file as you specify for the -c option.",
+                      fn);
         }
 
         if (opt2bSet("-rb", NFILE, fnm))
@@ -1925,7 +2102,8 @@ int gmx_grompp(int argc, char *argv[])
                           "Cannot find B-state position restraint file %s (option -rb).\n"
                           "From GROMACS-2018, you need to specify the position restraint "
                           "coordinate files explicitly to avoid mistakes, although you can "
-                          "still use the same file as you specify for the -c option.", fn);
+                          "still use the same file as you specify for the -c option.",
+                          fn);
             }
         }
         else
@@ -1935,34 +2113,32 @@ int gmx_grompp(int argc, char *argv[])
 
         if (bVerbose)
         {
-            fprintf(stderr, "Reading position restraint coords from %s", fn);
-            if (strcmp(fn, fnB) == 0)
+            std::string message = gmx::formatString("Reading position restraint coords from %s", fn);
+            if (strcmp(fn, fnB) != 0)
             {
-                fprintf(stderr, "\n");
-            }
-            else
-            {
-                fprintf(stderr, " and %s\n", fnB);
+                message += gmx::formatString(" and %s", fnB);
             }
+            GMX_LOG(logger.info).asParagraph().appendText(message);
         }
-        gen_posres(&sys, mi, fn, fnB,
-                   ir->refcoord_scaling, ir->ePBC,
-                   ir->posres_com, ir->posres_comB,
-                   wi);
+        gen_posres(&sys, mi, fn, fnB, ir->refcoord_scaling, ir->pbcType, ir->posres_com, ir->posres_comB, wi, logger);
     }
 
     /* If we are using CMAP, setup the pre-interpolation grid */
-    if (plist[F_CMAP].ncmap > 0)
+    if (interactions[F_CMAP].ncmap() > 0)
     {
-        init_cmap_grid(&sys.ffparams.cmap_grid, plist[F_CMAP].nc, plist[F_CMAP].grid_spacing);
-        setup_cmap(plist[F_CMAP].grid_spacing, plist[F_CMAP].nc, plist[F_CMAP].cmap, &sys.ffparams.cmap_grid);
+        init_cmap_grid(&sys.ffparams.cmap_grid,
+                       interactions[F_CMAP].cmapAngles,
+                       interactions[F_CMAP].cmakeGridSpacing);
+        setup_cmap(interactions[F_CMAP].cmakeGridSpacing,
+                   interactions[F_CMAP].cmapAngles,
+                   interactions[F_CMAP].cmap,
+                   &sys.ffparams.cmap_grid);
     }
 
-    set_wall_atomtype(atype, opts, ir, wi);
+    set_wall_atomtype(&atypes, opts, ir, wi, logger);
     if (bRenum)
     {
-        renum_atype(plist, &sys, ir->wall_atomtype, atype, bVerbose);
-        get_atomtype_ntypes(atype);
+        atypes.renumberTypes(interactions, &sys, ir->wall_atomtype, bVerbose);
     }
 
     if (ir->implicit_solvent)
@@ -1971,31 +2147,31 @@ int gmx_grompp(int argc, char *argv[])
     }
 
     /* PELA: Copy the atomtype data to the topology atomtype list */
-    copy_atomtype_atomtypes(atype, &(sys.atomtypes));
+    atypes.copyTot_atomtypes(&(sys.atomtypes));
 
     if (debug)
     {
-        pr_symtab(debug, 0, "After renum_atype", &sys.symtab);
+        pr_symtab(debug, 0, "After atype.renumberTypes", &sys.symtab);
     }
 
     if (bVerbose)
     {
-        fprintf(stderr, "converting bonded parameters...\n");
+        GMX_LOG(logger.info).asParagraph().appendTextFormatted("converting bonded parameters...");
     }
 
-    ntype = get_atomtype_ntypes(atype);
-    convert_params(ntype, plist, mi, intermolecular_interactions,
-                   comb, reppow, fudgeQQ, &sys);
+    const int ntype = atypes.size();
+    convertInteractionsOfType(
+            ntype, interactions, mi, intermolecular_interactions.get(), comb, reppow, fudgeQQ, &sys);
 
     if (debug)
     {
-        pr_symtab(debug, 0, "After convert_params", &sys.symtab);
+        pr_symtab(debug, 0, "After converInteractionsOfType", &sys.symtab);
     }
 
     /* set ptype to VSite for virtual sites */
-    for (gmx_moltype_t &moltype : sys.moltype)
+    for (gmx_moltype_tmoltype : sys.moltype)
     {
-        set_vsites_ptype(FALSE, &moltype);
+        set_vsites_ptype(FALSE, &moltype, logger);
     }
     if (debug)
     {
@@ -2013,14 +2189,16 @@ int gmx_grompp(int argc, char *argv[])
     /* check masses */
     check_mol(&sys, wi);
 
-    checkForUnboundAtoms(&sys, bVerbose, wi);
-
-    for (const gmx_moltype_t &moltype : sys.moltype)
+    if (haveFepPerturbedMassesInSettles(sys))
     {
-        check_cg_sizes(ftp2fn(efTOP, NFILE, fnm), &moltype.cgs, wi);
+        warning_error(wi,
+                      "SETTLE is not implemented for atoms whose mass is perturbed. "
+                      "You might instead use normal constraints.");
     }
 
-    if (EI_DYNAMICS(ir->eI) && ir->eI != eiBD)
+    checkForUnboundAtoms(&sys, bVerbose, wi, logger);
+
+    if (EI_DYNAMICS(ir->eI) && ir->eI != IntegrationAlgorithm::BD)
     {
         check_bonds_timestep(&sys, ir->delta_t, wi);
     }
@@ -2029,26 +2207,32 @@ int gmx_grompp(int argc, char *argv[])
 
     if (EI_ENERGY_MINIMIZATION(ir->eI) && 0 == ir->nsteps)
     {
-        warning_note(wi, "Zero-step energy minimization will alter the coordinates before calculating the energy. If you just want the energy of a single point, try zero-step MD (with unconstrained_start = yes). To do multiple single-point energy evaluations of different configurations of the same topology, use mdrun -rerun.");
+        warning_note(
+                wi,
+                "Zero-step energy minimization will alter the coordinates before calculating the "
+                "energy. If you just want the energy of a single point, try zero-step MD (with "
+                "unconstrained_start = yes). To do multiple single-point energy evaluations of "
+                "different configurations of the same topology, use mdrun -rerun.");
     }
 
     check_warning_error(wi, FARGS);
 
     if (bVerbose)
     {
-        fprintf(stderr, "initialising group options...\n");
+        GMX_LOG(logger.info).asParagraph().appendTextFormatted("initialising group options...");
     }
-    do_index(mdparin, ftp2fn_null(efNDX, NFILE, fnm),
-             &sys, bVerbose, ir,
-             wi);
+    do_index(mdparin, ftp2fn_null(efNDX, NFILE, fnm), &sys, bVerbose, mdModules.notifiers(), ir, wi);
 
-    if (ir->cutoff_scheme == ecutsVERLET && ir->verletbuf_tol > 0)
+    // Notify topology to MdModules for pre-processing after all indexes were built
+    mdModules.notifiers().preProcessingNotifier_.notify(&sys);
+
+    if (ir->cutoff_scheme == CutoffScheme::Verlet && ir->verletbuf_tol > 0)
     {
         if (EI_DYNAMICS(ir->eI) && inputrec2nboundeddim(ir) == 3)
         {
             real buffer_temp;
 
-            if (EI_MD(ir->eI) && ir->etc == etcNO)
+            if (EI_MD(ir->eI) && ir->etc == TemperatureCoupling::No)
             {
                 if (bGenVel)
                 {
@@ -2060,14 +2244,19 @@ int gmx_grompp(int argc, char *argv[])
                 }
                 if (buffer_temp > 0)
                 {
-                    sprintf(warn_buf, "NVE simulation: will use the initial temperature of %.3f K for determining the Verlet buffer size", buffer_temp);
-                    warning_note(wi, warn_buf);
+                    std::string warningMessage = gmx::formatString(
+                            "NVE simulation: will use the initial temperature of %.3f K for "
+                            "determining the Verlet buffer size",
+                            buffer_temp);
+                    warning_note(wi, warningMessage);
                 }
                 else
                 {
-                    sprintf(warn_buf, "NVE simulation with an initial temperature of zero: will use a Verlet buffer of %d%%. Check your energy drift!",
-                            gmx::roundToInt(verlet_buffer_ratio_NVE_T0*100));
-                    warning_note(wi, warn_buf);
+                    std::string warningMessage = gmx::formatString(
+                            "NVE simulation with an initial temperature of zero: will use a Verlet "
+                            "buffer of %d%%. Check your energy drift!",
+                            gmx::roundToInt(verlet_buffer_ratio_NVE_T0 * 100));
+                    warning_note(wi, warningMessage);
                 }
             }
             else
@@ -2075,14 +2264,14 @@ int gmx_grompp(int argc, char *argv[])
                 buffer_temp = get_max_reference_temp(ir, wi);
             }
 
-            if (EI_MD(ir->eI) && ir->etc == etcNO && buffer_temp == 0)
+            if (EI_MD(ir->eI) && ir->etc == TemperatureCoupling::No && buffer_temp == 0)
             {
                 /* NVE with initial T=0: we add a fixed ratio to rlist.
                  * Since we don't actually use verletbuf_tol, we set it to -1
                  * so it can't be misused later.
                  */
-                ir->rlist         *= 1.0 + verlet_buffer_ratio_NVE_T0;
-                ir->verletbuf_tol  = -1;
+                ir->rlist *= 1.0 + verlet_buffer_ratio_NVE_T0;
+                ir->verletbuf_tol = -1;
             }
             else
             {
@@ -2091,27 +2280,32 @@ int gmx_grompp(int argc, char *argv[])
                  * Note that we can't warn when nsteps=0, since we don't
                  * know how many steps the user intends to run.
                  */
-                if (EI_MD(ir->eI) && ir->etc == etcNO && ir->nstlist > 1 &&
-                    ir->nsteps > 0)
+                if (EI_MD(ir->eI) && ir->etc == TemperatureCoupling::No && ir->nstlist > 1 && ir->nsteps > 0)
                 {
                     const real driftTolerance = 0.01;
                     /* We use 2 DOF per atom = 2kT pot+kin energy,
                      * to be on the safe side with constraints.
                      */
-                    const real totalEnergyDriftPerAtomPerPicosecond = 2*BOLTZ*buffer_temp/(ir->nsteps*ir->delta_t);
+                    const real totalEnergyDriftPerAtomPerPicosecond =
+                            2 * gmx::c_boltz * buffer_temp / (ir->nsteps * ir->delta_t);
 
-                    if (ir->verletbuf_tol > 1.1*driftTolerance*totalEnergyDriftPerAtomPerPicosecond)
+                    if (ir->verletbuf_tol > 1.1 * driftTolerance * totalEnergyDriftPerAtomPerPicosecond)
                     {
-                        sprintf(warn_buf, "You are using a Verlet buffer tolerance of %g kJ/mol/ps for an NVE simulation of length %g ps, which can give a final drift of %d%%. For conserving energy to %d%% when using constraints, you might need to set verlet-buffer-tolerance to %.1e.",
-                                ir->verletbuf_tol, ir->nsteps*ir->delta_t,
-                                gmx::roundToInt(ir->verletbuf_tol/totalEnergyDriftPerAtomPerPicosecond*100),
-                                gmx::roundToInt(100*driftTolerance),
-                                driftTolerance*totalEnergyDriftPerAtomPerPicosecond);
-                        warning_note(wi, warn_buf);
+                        std::string warningMessage = gmx::formatString(
+                                "You are using a Verlet buffer tolerance of %g kJ/mol/ps for an "
+                                "NVE simulation of length %g ps, which can give a final drift of "
+                                "%d%%. For conserving energy to %d%% when using constraints, you "
+                                "might need to set verlet-buffer-tolerance to %.1e.",
+                                ir->verletbuf_tol,
+                                ir->nsteps * ir->delta_t,
+                                gmx::roundToInt(ir->verletbuf_tol / totalEnergyDriftPerAtomPerPicosecond * 100),
+                                gmx::roundToInt(100 * driftTolerance),
+                                driftTolerance * totalEnergyDriftPerAtomPerPicosecond);
+                        warning_note(wi, warningMessage);
                     }
                 }
 
-                set_verlet_buffer(&sys, ir, buffer_temp, state.box, wi);
+                set_verlet_buffer(&sys, ir, buffer_temp, state.box, wi, logger);
             }
         }
     }
@@ -2119,12 +2313,6 @@ int gmx_grompp(int argc, char *argv[])
     /* Init the temperature coupling state */
     init_gtc_state(&state, ir->opts.ngtc, 0, ir->opts.nhchainlength); /* need to add nnhpres here? */
 
-    if (bVerbose)
-    {
-        fprintf(stderr, "Checking consistency between energy and charge groups...\n");
-    }
-    check_eg_vs_cg(&sys);
-
     if (debug)
     {
         pr_symtab(debug, 0, "After index", &sys.symtab);
@@ -2137,50 +2325,41 @@ int gmx_grompp(int argc, char *argv[])
         pr_symtab(debug, 0, "After close", &sys.symtab);
     }
 
-    /* make exclusions between QM atoms */
-    if (ir->bQMMM)
+    if (ir->eI == IntegrationAlgorithm::Mimic)
     {
-        if (ir->QMMMscheme == eQMMMschemenormal && ir->ns_type == ensSIMPLE)
-        {
-            gmx_fatal(FARGS, "electrostatic embedding only works with grid neighboursearching, use ns-type=grid instead\n");
-        }
-        else
-        {
-            generate_qmexcl(&sys, ir, wi, GmxQmmmMode::GMX_QMMM_ORIGINAL);
-        }
-    }
-
-    if (ir->eI == eiMimic)
-    {
-        generate_qmexcl(&sys, ir, wi, GmxQmmmMode::GMX_QMMM_MIMIC);
+        generate_qmexcl(&sys, ir, logger);
     }
 
     if (ftp2bSet(efTRN, NFILE, fnm))
     {
         if (bVerbose)
         {
-            fprintf(stderr, "getting data from old trajectory ...\n");
+            GMX_LOG(logger.info)
+                    .asParagraph()
+                    .appendTextFormatted("getting data from old trajectory ...");
         }
-        cont_status(ftp2fn(efTRN, NFILE, fnm), ftp2fn_null(efEDR, NFILE, fnm),
-                    bNeedVel, bGenVel, fr_time, ir, &state, &sys, oenv);
+        cont_status(ftp2fn(efTRN, NFILE, fnm),
+                    ftp2fn_null(efEDR, NFILE, fnm),
+                    bNeedVel,
+                    bGenVel,
+                    fr_time,
+                    ir,
+                    &state,
+                    &sys,
+                    oenv,
+                    logger);
     }
 
-    if (ir->ePBC == epbcXY && ir->nwall != 2)
+    if (ir->pbcType == PbcType::XY && ir->nwall != 2)
     {
         clear_rvec(state.box[ZZ]);
     }
 
-    if (ir->cutoff_scheme != ecutsVERLET && ir->rlist > 0)
-    {
-        set_warning_line(wi, mdparin, -1);
-        check_chargegroup_radii(&sys, ir, state.x.rvec_array(), wi);
-    }
-
     if (EEL_FULL(ir->coulombtype) || EVDW_PME(ir->vdwtype))
     {
         /* Calculate the optimal grid dimensions */
         matrix          scaledBox;
-        EwaldBoxZScaler boxScaler(*ir);
+        EwaldBoxZScaler boxScaler(inputrecPbcXY2Walls(ir), ir->wall_ewald_zfac);
         boxScaler.scaleBox(state.box, scaledBox);
 
         if (ir->nkx > 0 && ir->nky > 0 && ir->nkz > 0)
@@ -2191,26 +2370,26 @@ int gmx_grompp(int argc, char *argv[])
         else if (ir->nkx != 0 && ir->nky != 0 && ir->nkz != 0)
         {
             set_warning_line(wi, mdparin, -1);
-            warning_error(wi, "Some of the Fourier grid sizes are set, but all of them need to be set.");
+            warning_error(
+                    wi, "Some of the Fourier grid sizes are set, but all of them need to be set.");
         }
         const int minGridSize = minimalPmeGridSize(ir->pme_order);
-        calcFftGrid(stdout, scaledBox, ir->fourier_spacing, minGridSize,
-                    &(ir->nkx), &(ir->nky), &(ir->nkz));
-        if (ir->nkx < minGridSize ||
-            ir->nky < minGridSize ||
-            ir->nkz < minGridSize)
+        calcFftGrid(stdout, scaledBox, ir->fourier_spacing, minGridSize, &(ir->nkx), &(ir->nky), &(ir->nkz));
+        if (ir->nkx < minGridSize || ir->nky < minGridSize || ir->nkz < minGridSize)
         {
-            warning_error(wi, "The PME grid size should be >= 2*(pme-order - 1); either manually increase the grid size or decrease pme-order");
+            warning_error(wi,
+                          "The PME grid size should be >= 2*(pme-order - 1); either manually "
+                          "increase the grid size or decrease pme-order");
         }
     }
 
     /* MRS: eventually figure out better logic for initializing the fep
        values that makes declaring the lambda and declaring the state not
        potentially conflict if not handled correctly. */
-    if (ir->efep != efepNO)
+    if (ir->efep != FreeEnergyPerturbationType::No)
     {
         state.fep_state = ir->fepvals->init_fep_state;
-        for (i = 0; i < efptNR; i++)
+        for (i = 0; i < static_cast<int>(FreeEnergyPerturbationCouplingType::Count); i++)
         {
             /* init_lambda trumps state definitions*/
             if (ir->fepvals->init_lambda >= 0)
@@ -2219,7 +2398,7 @@ int gmx_grompp(int argc, char *argv[])
             }
             else
             {
-                if (ir->fepvals->all_lambda[i] == nullptr)
+                if (ir->fepvals->all_lambda[i].empty())
                 {
                     gmx_fatal(FARGS, "Values of lambda not set for a free energy calculation!");
                 }
@@ -2231,11 +2410,12 @@ int gmx_grompp(int argc, char *argv[])
         }
     }
 
-    struct pull_t *pull = nullptr;
+    pull_t* pull = nullptr;
 
     if (ir->bPull)
     {
-        pull = set_pull_init(ir, &sys, state.x.rvec_array(), state.box, state.lambda[efptMASS], wi);
+        pull = set_pull_init(
+                ir, sys, state.x, state.box, state.lambda[FreeEnergyPerturbationCouplingType::Mass], wi);
     }
 
     /* Modules that supply external potential for pull coordinates
@@ -2246,13 +2426,23 @@ int gmx_grompp(int argc, char *argv[])
     if (ir->bDoAwh)
     {
         tensor compressibility = { { 0 } };
-        if (ir->epc != epcNO)
+        if (ir->epc != PressureCoupling::No)
         {
             copy_mat(ir->compress, compressibility);
         }
-        setStateDependentAwhParams(ir->awhParams, ir->pull, pull,
-                                   state.box, ir->ePBC, compressibility,
-                                   &ir->opts, wi);
+        setStateDependentAwhParams(
+                ir->awhParams.get(),
+                *ir->pull,
+                pull,
+                state.box,
+                ir->pbcType,
+                compressibility,
+                &ir->opts,
+                ir->efep != FreeEnergyPerturbationType::No ? ir->fepvals->all_lambda[static_cast<int>(
+                        FreeEnergyPerturbationCouplingType::Fep)][ir->fepvals->init_fep_state]
+                                                           : 0,
+                sys,
+                wi);
     }
 
     if (ir->bPull)
@@ -2262,8 +2452,11 @@ int gmx_grompp(int argc, char *argv[])
 
     if (ir->bRot)
     {
-        set_reference_positions(ir->rot, state.x.rvec_array(), state.box,
-                                opt2fn("-ref", NFILE, fnm), opt2bSet("-ref", NFILE, fnm),
+        set_reference_positions(ir->rot,
+                                state.x.rvec_array(),
+                                state.box,
+                                opt2fn("-ref", NFILE, fnm),
+                                opt2bSet("-ref", NFILE, fnm),
                                 wi);
     }
 
@@ -2271,67 +2464,108 @@ int gmx_grompp(int argc, char *argv[])
 
     if (EEL_PME(ir->coulombtype))
     {
-        float ratio = pme_load_estimate(&sys, ir, state.box);
-        fprintf(stderr, "Estimate for the relative computational load of the PME mesh part: %.2f\n", ratio);
+        float ratio = pme_load_estimate(sys, *ir, state.box);
+        GMX_LOG(logger.info)
+                .asParagraph()
+                .appendTextFormatted(
+                        "Estimate for the relative computational load of the PME mesh part: %.2f", ratio);
         /* With free energy we might need to do PME both for the A and B state
          * charges. This will double the cost, but the optimal performance will
          * then probably be at a slightly larger cut-off and grid spacing.
          */
-        if ((ir->efep == efepNO && ratio > 1.0/2.0) ||
-            (ir->efep != efepNO && ratio > 2.0/3.0))
+        if ((ir->efep == FreeEnergyPerturbationType::No && ratio > 1.0 / 2.0)
+            || (ir->efep != FreeEnergyPerturbationType::No && ratio > 2.0 / 3.0))
         {
-            warning_note(wi,
-                         "The optimal PME mesh load for parallel simulations is below 0.5\n"
-                         "and for highly parallel simulations between 0.25 and 0.33,\n"
-                         "for higher performance, increase the cut-off and the PME grid spacing.\n");
-            if (ir->efep != efepNO)
+            warning_note(
+                    wi,
+                    "The optimal PME mesh load for parallel simulations is below 0.5\n"
+                    "and for highly parallel simulations between 0.25 and 0.33,\n"
+                    "for higher performance, increase the cut-off and the PME grid spacing.\n");
+            if (ir->efep != FreeEnergyPerturbationType::No)
             {
                 warning_note(wi,
-                             "For free energy simulations, the optimal load limit increases from 0.5 to 0.667\n");
+                             "For free energy simulations, the optimal load limit increases from "
+                             "0.5 to 0.667\n");
             }
         }
     }
 
     {
-        char   warn_buf[STRLEN];
-        double cio = compute_io(ir, sys.natoms, &sys.groups, F_NRE, 1);
-        sprintf(warn_buf, "This run will generate roughly %.0f Mb of data", cio);
+        double      cio = compute_io(ir, sys.natoms, sys.groups, F_NRE, 1);
+        std::string warningMessage =
+                gmx::formatString("This run will generate roughly %.0f Mb of data", cio);
         if (cio > 2000)
         {
             set_warning_line(wi, mdparin, -1);
-            warning_note(wi, warn_buf);
+            warning_note(wi, warningMessage);
         }
         else
         {
-            printf("%s\n", warn_buf);
+            GMX_LOG(logger.info).asParagraph().appendText(warningMessage);
+        }
+    }
+
+    // Hand over box and coordiantes to MdModules before they evaluate their final parameters
+    {
+        gmx::CoordinatesAndBoxPreprocessed coordinatesAndBoxPreprocessed;
+        coordinatesAndBoxPreprocessed.coordinates_ = state.x.arrayRefWithPadding();
+        copy_mat(state.box, coordinatesAndBoxPreprocessed.box_);
+        coordinatesAndBoxPreprocessed.pbc_ = ir->pbcType;
+        mdModules.notifiers().preProcessingNotifier_.notify(coordinatesAndBoxPreprocessed);
+    }
+
+    // Add the md modules internal parameters that are not mdp options
+    // e.g., atom indices
+
+    {
+        gmx::KeyValueTreeBuilder internalParameterBuilder;
+        mdModules.notifiers().preProcessingNotifier_.notify(internalParameterBuilder.rootObject());
+        ir->internalParameters =
+                std::make_unique<gmx::KeyValueTreeObject>(internalParameterBuilder.build());
+    }
+
+    if (ir->comm_mode != ComRemovalAlgorithm::No)
+    {
+        const int nstglobalcomm = computeGlobalCommunicationPeriod(ir);
+        if (ir->nstcomm % nstglobalcomm != 0)
+        {
+            warning_note(
+                    wi,
+                    gmx::formatString(
+                            "COM removal frequency is set to (%d).\n"
+                            "Other settings require a global communication frequency of %d.\n"
+                            "Note that this will require additional global communication steps,\n"
+                            "which will reduce performance when using multiple ranks.\n"
+                            "Consider setting nstcomm to a multiple of %d.",
+                            ir->nstcomm,
+                            nstglobalcomm,
+                            nstglobalcomm));
         }
     }
 
     if (bVerbose)
     {
-        fprintf(stderr, "writing run input file...\n");
+        GMX_LOG(logger.info).asParagraph().appendTextFormatted("writing run input file...");
     }
 
     done_warning(wi, FARGS);
-    write_tpx_state(ftp2fn(efTPR, NFILE, fnm), ir, &state, &sys);
+    write_tpx_state(ftp2fn(efTPR, NFILE, fnm), ir, &state, sys);
 
     /* Output IMD group, if bIMD is TRUE */
-    write_IMDgroup_to_file(ir->bIMD, ir, &state, &sys, NFILE, fnm);
+    gmx::write_IMDgroup_to_file(ir->bIMD, ir, &state, sys, NFILE, fnm);
 
     sfree(opts->define);
+    sfree(opts->wall_atomtype[0]);
+    sfree(opts->wall_atomtype[1]);
     sfree(opts->include);
-    sfree(opts);
-    done_plist(plist);
-    sfree(plist);
-    for (int i = 0; i < nmi; i++)
+    sfree(opts->couple_moltype);
+
+    for (auto& mol : mi)
     {
         // Some of the contents of molinfo have been stolen, so
-        // done_mi can't be called.
-        done_block(&mi[i].mols);
-        done_plist(mi[i].plist);
+        // fullCleanUp can't be called.
+        mol.partialCleanUp();
     }
-    sfree(mi);
-    done_atomtype(atype);
     done_inputrec_strings();
     output_env_done(oenv);