Reimplement constant acceleration groups
[alexxy/gromacs.git] / src / gromacs / gmxpreprocess / readir.cpp
index 4c8ba71fa7c1d194fb2fd0958ff0fe05149d1379..557a361c8f2f472ffc94c0b719a7a6633d9b2cde 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, 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 <cstdlib>
 
 #include <algorithm>
+#include <memory>
+#include <numeric>
 #include <string>
 
-#include "gromacs/awh/read_params.h"
+#include "gromacs/applied_forces/awh/read_params.h"
 #include "gromacs/fileio/readinp.h"
 #include "gromacs/fileio/warninp.h"
 #include "gromacs/gmxlib/network.h"
 #include "gromacs/gmxpreprocess/toputil.h"
 #include "gromacs/math/functions.h"
 #include "gromacs/math/units.h"
+#include "gromacs/math/utilities.h"
 #include "gromacs/math/vec.h"
 #include "gromacs/mdlib/calc_verletbuf.h"
+#include "gromacs/mdlib/vcm.h"
 #include "gromacs/mdrun/mdmodules.h"
+#include "gromacs/mdtypes/awh_params.h"
 #include "gromacs/mdtypes/inputrec.h"
 #include "gromacs/mdtypes/md_enums.h"
+#include "gromacs/mdtypes/multipletimestepping.h"
 #include "gromacs/mdtypes/pull_params.h"
 #include "gromacs/options/options.h"
 #include "gromacs/options/treesupport.h"
@@ -69,6 +76,7 @@
 #include "gromacs/topology/mtop_util.h"
 #include "gromacs/topology/symtab.h"
 #include "gromacs/topology/topology.h"
+#include "gromacs/utility/arrayref.h"
 #include "gromacs/utility/cstringutil.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/fatalerror.h"
 #include "gromacs/utility/keyvaluetreebuilder.h"
 #include "gromacs/utility/keyvaluetreemdpwriter.h"
 #include "gromacs/utility/keyvaluetreetransform.h"
-#include "gromacs/utility/mdmodulenotification.h"
+#include "gromacs/utility/mdmodulesnotifiers.h"
 #include "gromacs/utility/smalloc.h"
 #include "gromacs/utility/strconvert.h"
 #include "gromacs/utility/stringcompare.h"
 #include "gromacs/utility/stringutil.h"
 #include "gromacs/utility/textwriter.h"
 
-#define MAXPTR 254
-#define NOGID  255
+#define NOGID 255
+
+using gmx::BasicVector;
 
 /* Resource parameters
  * Do not change any of these until you read the instruction
  * message.
  */
 
-typedef struct t_inputrec_strings
+struct gmx_inputrec_strings
 {
-    char tcgrps[STRLEN], tau_t[STRLEN], ref_t[STRLEN],
-         acc[STRLEN], accgrps[STRLEN], freeze[STRLEN], frdim[STRLEN],
-         energy[STRLEN], user1[STRLEN], user2[STRLEN], vcm[STRLEN], x_compressed_groups[STRLEN],
-         couple_moltype[STRLEN], orirefitgrp[STRLEN], egptable[STRLEN], egpexcl[STRLEN],
-         wall_atomtype[STRLEN], wall_density[STRLEN], deform[STRLEN], QMMM[STRLEN],
-         imd_grp[STRLEN];
-    char   fep_lambda[efptNR][STRLEN];
-    char   lambda_weights[STRLEN];
-    char **pull_grp;
-    char **rot_grp;
-    char   anneal[STRLEN], anneal_npoints[STRLEN],
-           anneal_time[STRLEN], anneal_temp[STRLEN];
-    char   QMmethod[STRLEN], QMbasis[STRLEN], QMcharge[STRLEN], QMmult[STRLEN],
-           bSH[STRLEN], CASorbitals[STRLEN], CASelectrons[STRLEN], SAon[STRLEN],
-           SAoff[STRLEN], SAsteps[STRLEN];
-
-} gmx_inputrec_strings;
-
-static gmx_inputrec_strings *is = nullptr;
+    char tcgrps[STRLEN], tau_t[STRLEN], ref_t[STRLEN], accelerationGroups[STRLEN],
+            acceleration[STRLEN], freeze[STRLEN], frdim[STRLEN], energy[STRLEN], user1[STRLEN],
+            user2[STRLEN], vcm[STRLEN], x_compressed_groups[STRLEN], couple_moltype[STRLEN],
+            orirefitgrp[STRLEN], egptable[STRLEN], egpexcl[STRLEN], wall_atomtype[STRLEN],
+            wall_density[STRLEN], deform[STRLEN], QMMM[STRLEN], imd_grp[STRLEN];
+    gmx::EnumerationArray<FreeEnergyPerturbationCouplingType, std::string> fep_lambda;
+    char                                                                   lambda_weights[STRLEN];
+    std::vector<std::string>                                               pullGroupNames;
+    std::vector<std::string>                                               rotateGroupNames;
+    char anneal[STRLEN], anneal_npoints[STRLEN], anneal_time[STRLEN], anneal_temp[STRLEN];
+};
+
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static gmx_inputrec_strings* inputrecStrings = nullptr;
 
 void init_inputrec_strings()
 {
-    if (is)
+    if (inputrecStrings)
     {
-        gmx_incons("Attempted to call init_inputrec_strings before calling done_inputrec_strings. Only one inputrec (i.e. .mdp file) can be parsed at a time.");
+        gmx_incons(
+                "Attempted to call init_inputrec_strings before calling done_inputrec_strings. "
+                "Only one inputrec (i.e. .mdp file) can be parsed at a time.");
     }
-    snew(is, 1);
+    inputrecStrings = new gmx_inputrec_strings();
 }
 
 void done_inputrec_strings()
 {
-    sfree(is);
-    is = nullptr;
+    delete inputrecStrings;
+    inputrecStrings = nullptr;
 }
 
 
-enum {
-    egrptpALL,         /* All particles have to be a member of a group.     */
-    egrptpALL_GENREST, /* A rest group with name is generated for particles *
-                        * that are not part of any group.                   */
-    egrptpPART,        /* As egrptpALL_GENREST, but no name is generated    *
-                        * for the rest group.                               */
-    egrptpONE          /* Merge all selected groups into one group,         *
-                        * make a rest group for the remaining particles.    */
+//! How to treat coverage of the whole system for a set of atom groupsx
+enum class GroupCoverage
+{
+    All,             //!< All particles have to be a member of a group
+    AllGenerateRest, //<! A rest group with name is generated for particles not part of any group
+    Partial,         //<! As \p AllGenerateRest, but no name for the rest group is generated
+    OneGroup //<! Merge all selected groups into one group, make a rest group for the remaining particles
 };
 
-static const char *constraints[eshNR+1]    = {
-    "none", "h-bonds", "all-bonds", "h-angles", "all-angles", nullptr
-};
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static const char* constraints[eshNR + 1] = { "none",     "h-bonds",    "all-bonds",
+                                              "h-angles", "all-angles", nullptr };
 
-static const char *couple_lam[ecouplamNR+1]    = {
-    "vdw-q", "vdw", "q", "none", nullptr
-};
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static const char* couple_lam[ecouplamNR + 1] = { "vdw-q", "vdw", "q", "none", nullptr };
 
-static void GetSimTemps(int ntemps, t_simtemp *simtemp, double *temperature_lambdas)
+static void getSimTemps(int ntemps, t_simtemp* simtemp, gmx::ArrayRef<double> temperature_lambdas)
 {
 
     int i;
@@ -160,30 +164,36 @@ static void GetSimTemps(int ntemps, t_simtemp *simtemp, double *temperature_lamb
     for (i = 0; i < ntemps; i++)
     {
         /* simple linear scaling -- allows more control */
-        if (simtemp->eSimTempScale == esimtempLINEAR)
+        if (simtemp->eSimTempScale == SimulatedTempering::Linear)
         {
-            simtemp->temperatures[i] = simtemp->simtemp_low + (simtemp->simtemp_high-simtemp->simtemp_low)*temperature_lambdas[i];
+            simtemp->temperatures[i] =
+                    simtemp->simtemp_low
+                    + (simtemp->simtemp_high - simtemp->simtemp_low) * temperature_lambdas[i];
         }
-        else if (simtemp->eSimTempScale == esimtempGEOMETRIC)  /* should give roughly equal acceptance for constant heat capacity . . . */
+        else if (simtemp->eSimTempScale
+                 == SimulatedTempering::Geometric) /* should give roughly equal acceptance for constant heat capacity . . . */
         {
-            simtemp->temperatures[i] = simtemp->simtemp_low * std::pow(simtemp->simtemp_high/simtemp->simtemp_low, static_cast<real>((1.0*i)/(ntemps-1)));
+            simtemp->temperatures[i] = simtemp->simtemp_low
+                                       * std::pow(simtemp->simtemp_high / simtemp->simtemp_low,
+                                                  static_cast<real>((1.0 * i) / (ntemps - 1)));
         }
-        else if (simtemp->eSimTempScale == esimtempEXPONENTIAL)
+        else if (simtemp->eSimTempScale == SimulatedTempering::Exponential)
         {
-            simtemp->temperatures[i] = simtemp->simtemp_low + (simtemp->simtemp_high-simtemp->simtemp_low)*(std::expm1(temperature_lambdas[i])/std::expm1(1.0));
+            simtemp->temperatures[i] = simtemp->simtemp_low
+                                       + (simtemp->simtemp_high - simtemp->simtemp_low)
+                                                 * (std::expm1(temperature_lambdas[i]) / std::expm1(1.0));
         }
         else
         {
             char errorstr[128];
-            sprintf(errorstr, "eSimTempScale=%d not defined", simtemp->eSimTempScale);
+            sprintf(errorstr, "eSimTempScale=%s not defined", enumValueToString(simtemp->eSimTempScale));
             gmx_fatal(FARGS, "%s", errorstr);
         }
     }
 }
 
 
-
-static void _low_check(bool b, const char *s, warninp_t wi)
+static void _low_check(bool b, const char* s, warninp_t wi)
 {
     if (b)
     {
@@ -191,49 +201,33 @@ static void _low_check(bool b, const char *s, warninp_t wi)
     }
 }
 
-static void check_nst(const char *desc_nst, int nst,
-                      const char *desc_p, int *p,
-                      warninp_t wi)
+static void check_nst(const char* desc_nst, int nst, const char* desc_p, int* p, warninp_t wi)
 {
     char buf[STRLEN];
 
     if (*p > 0 && *p % nst != 0)
     {
         /* Round up to the next multiple of nst */
-        *p = ((*p)/nst + 1)*nst;
-        sprintf(buf, "%s should be a multiple of %s, changing %s to %d\n",
-                desc_p, desc_nst, desc_p, *p);
+        *p = ((*p) / nst + 1) * nst;
+        sprintf(buf, "%s should be a multiple of %s, changing %s to %d\n", desc_p, desc_nst, desc_p, *p);
         warning(wi, buf);
     }
 }
 
-static int lcd(int n1, int n2)
-{
-    int d, i;
-
-    d = 1;
-    for (i = 2; (i <= n1 && i <= n2); i++)
-    {
-        if (n1 % i == 0 && n2 % i == 0)
-        {
-            d = i;
-        }
-    }
-
-    return d;
-}
-
 //! Convert legacy mdp entries to modern ones.
-static void process_interaction_modifier(int *eintmod)
+static void process_interaction_modifier(InteractionModifiers* eintmod)
 {
-    if (*eintmod == eintmodPOTSHIFT_VERLET_UNSUPPORTED)
+    if (*eintmod == InteractionModifiers::PotShiftVerletUnsupported)
     {
-        *eintmod = eintmodPOTSHIFT;
+        *eintmod = InteractionModifiers::PotShift;
     }
 }
 
-void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifier,
-              t_inputrec *ir, t_gromppopts *opts, warninp_t wi)
+void check_ir(const char*                    mdparin,
+              const gmx::MDModulesNotifiers& mdModulesNotifiers,
+              t_inputrec*                    ir,
+              t_gromppopts*                  opts,
+              warninp_t                      wi)
 /* Check internal consistency.
  * NOTE: index groups are not set here yet, don't check things
  * like temperature coupling group options here, but in triple_check
@@ -247,16 +241,30 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
     char        err_buf[256], warn_buf[STRLEN];
     int         i, j;
     real        dt_pcoupl;
-    t_lambda   *fep    = ir->fepvals;
-    t_expanded *expand = ir->expandedvals;
+    t_lambda*   fep    = ir->fepvals.get();
+    t_expanded* expand = ir->expandedvals.get();
 
     set_warning_line(wi, mdparin, -1);
 
-    if (ir->coulombtype == eelRF_NEC_UNSUPPORTED)
+    /* We cannot check MTS requirements with an invalid MTS setup
+     * and we will already have generated errors with an invalid MTS setup.
+     */
+    if (gmx::haveValidMtsSetup(*ir))
     {
-        sprintf(warn_buf, "%s electrostatics is no longer supported",
-                eel_names[eelRF_NEC_UNSUPPORTED]);
-        warning_error(wi, warn_buf);
+        std::vector<std::string> errorMessages = gmx::checkMtsRequirements(*ir);
+
+        for (const auto& errorMessage : errorMessages)
+        {
+            warning_error(wi, errorMessage.c_str());
+        }
+    }
+
+    if (ir->coulombtype == CoulombInteractionType::RFNecUnsupported)
+    {
+        std::string message =
+                gmx::formatString("%s electrostatics is no longer supported",
+                                  enumValueToString(CoulombInteractionType::RFNecUnsupported));
+        warning_error(wi, message);
     }
 
     /* BASIC CUT-OFF STUFF */
@@ -268,24 +276,26 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
     {
         warning_error(wi, "rvdw should be >= 0");
     }
-    if (ir->rlist < 0 &&
-        !(ir->cutoff_scheme == ecutsVERLET && ir->verletbuf_tol > 0))
+    if (ir->rlist < 0 && !(ir->cutoff_scheme == CutoffScheme::Verlet && ir->verletbuf_tol > 0))
     {
         warning_error(wi, "rlist should be >= 0");
     }
-    sprintf(err_buf, "nstlist can not be smaller than 0. (If you were trying to use the heuristic neighbour-list update scheme for efficient buffering for improved energy conservation, please use the Verlet cut-off scheme instead.)");
+    sprintf(err_buf,
+            "nstlist can not be smaller than 0. (If you were trying to use the heuristic "
+            "neighbour-list update scheme for efficient buffering for improved energy "
+            "conservation, please use the Verlet cut-off scheme instead.)");
     CHECK(ir->nstlist < 0);
 
     process_interaction_modifier(&ir->coulomb_modifier);
     process_interaction_modifier(&ir->vdw_modifier);
 
-    if (ir->cutoff_scheme == ecutsGROUP)
+    if (ir->cutoff_scheme == CutoffScheme::Group)
     {
         gmx_fatal(FARGS,
                   "The group cutoff scheme has been removed since GROMACS 2020. "
                   "Please use the Verlet cutoff scheme.");
     }
-    if (ir->cutoff_scheme == ecutsVERLET)
+    if (ir->cutoff_scheme == CutoffScheme::Verlet)
     {
         real rc_max;
 
@@ -300,53 +310,70 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
         {
             // Since we have PME coulomb + LJ cut-off kernels with rcoulomb>rvdw
             // for PME load balancing, we can support this exception.
-            bool bUsesPmeTwinRangeKernel = (EEL_PME_EWALD(ir->coulombtype) &&
-                                            ir->vdwtype == evdwCUT &&
-                                            ir->rcoulomb > ir->rvdw);
+            bool bUsesPmeTwinRangeKernel =
+                    (EEL_PME_EWALD(ir->coulombtype) && ir->vdwtype == VanDerWaalsType::Cut
+                     && ir->rcoulomb > ir->rvdw);
             if (!bUsesPmeTwinRangeKernel)
             {
-                warning_error(wi, "With Verlet lists rcoulomb!=rvdw is not supported (except for rcoulomb>rvdw with PME electrostatics)");
+                warning_error(wi,
+                              "With Verlet lists rcoulomb!=rvdw is not supported (except for "
+                              "rcoulomb>rvdw with PME electrostatics)");
             }
         }
 
-        if (ir->vdwtype == evdwSHIFT || ir->vdwtype == evdwSWITCH)
+        if (ir->vdwtype == VanDerWaalsType::Shift || ir->vdwtype == VanDerWaalsType::Switch)
         {
-            if (ir->vdw_modifier == eintmodNONE ||
-                ir->vdw_modifier == eintmodPOTSHIFT)
+            if (ir->vdw_modifier == InteractionModifiers::None
+                || ir->vdw_modifier == InteractionModifiers::PotShift)
             {
-                ir->vdw_modifier = (ir->vdwtype == evdwSHIFT ? eintmodFORCESWITCH : eintmodPOTSWITCH);
-
-                sprintf(warn_buf, "Replacing vdwtype=%s by the equivalent combination of vdwtype=%s and vdw_modifier=%s", evdw_names[ir->vdwtype], evdw_names[evdwCUT], eintmod_names[ir->vdw_modifier]);
+                ir->vdw_modifier =
+                        (ir->vdwtype == VanDerWaalsType::Shift ? InteractionModifiers::ForceSwitch
+                                                               : InteractionModifiers::PotSwitch);
+
+                sprintf(warn_buf,
+                        "Replacing vdwtype=%s by the equivalent combination of vdwtype=%s and "
+                        "vdw_modifier=%s",
+                        enumValueToString(ir->vdwtype),
+                        enumValueToString(VanDerWaalsType::Cut),
+                        enumValueToString(ir->vdw_modifier));
                 warning_note(wi, warn_buf);
 
-                ir->vdwtype = evdwCUT;
+                ir->vdwtype = VanDerWaalsType::Cut;
             }
             else
             {
-                sprintf(warn_buf, "Unsupported combination of vdwtype=%s and vdw_modifier=%s", evdw_names[ir->vdwtype], eintmod_names[ir->vdw_modifier]);
+                sprintf(warn_buf,
+                        "Unsupported combination of vdwtype=%s and vdw_modifier=%s",
+                        enumValueToString(ir->vdwtype),
+                        enumValueToString(ir->vdw_modifier));
                 warning_error(wi, warn_buf);
             }
         }
 
-        if (!(ir->vdwtype == evdwCUT || ir->vdwtype == evdwPME))
+        if (!(ir->vdwtype == VanDerWaalsType::Cut || ir->vdwtype == VanDerWaalsType::Pme))
         {
-            warning_error(wi, "With Verlet lists only cut-off and PME LJ interactions are supported");
+            warning_error(wi,
+                          "With Verlet lists only cut-off and PME LJ interactions are supported");
         }
-        if (!(ir->coulombtype == eelCUT || EEL_RF(ir->coulombtype) ||
-              EEL_PME(ir->coulombtype) || ir->coulombtype == eelEWALD))
+        if (!(ir->coulombtype == CoulombInteractionType::Cut || EEL_RF(ir->coulombtype)
+              || EEL_PME(ir->coulombtype) || ir->coulombtype == CoulombInteractionType::Ewald))
         {
-            warning_error(wi, "With Verlet lists only cut-off, reaction-field, PME and Ewald electrostatics are supported");
+            warning_error(wi,
+                          "With Verlet lists only cut-off, reaction-field, PME and Ewald "
+                          "electrostatics are supported");
         }
-        if (!(ir->coulomb_modifier == eintmodNONE ||
-              ir->coulomb_modifier == eintmodPOTSHIFT))
+        if (!(ir->coulomb_modifier == InteractionModifiers::None
+              || ir->coulomb_modifier == InteractionModifiers::PotShift))
         {
-            sprintf(warn_buf, "coulomb_modifier=%s is not supported", eintmod_names[ir->coulomb_modifier]);
+            sprintf(warn_buf, "coulomb_modifier=%s is not supported", enumValueToString(ir->coulomb_modifier));
             warning_error(wi, warn_buf);
         }
 
         if (EEL_USER(ir->coulombtype))
         {
-            sprintf(warn_buf, "Coulomb type %s is not supported with the verlet scheme", eel_names[ir->coulombtype]);
+            sprintf(warn_buf,
+                    "Coulomb type %s is not supported with the verlet scheme",
+                    enumValueToString(ir->coulombtype));
             warning_error(wi, warn_buf);
         }
 
@@ -357,7 +384,10 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
 
         if (ir->nstlist < 10)
         {
-            warning_note(wi, "With Verlet lists the optimal nstlist is >= 10, with GPUs >= 20. Note that with the Verlet scheme, nstlist has no effect on the accuracy of your simulation.");
+            warning_note(wi,
+                         "With Verlet lists the optimal nstlist is >= 10, with GPUs >= 20. Note "
+                         "that with the Verlet scheme, nstlist has no effect on the accuracy of "
+                         "your simulation.");
         }
 
         rc_max = std::max(ir->rvdw, ir->rcoulomb);
@@ -377,19 +407,27 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
 
             if (ir->rlist < rc_max)
             {
-                warning_error(wi, "With verlet lists rlist can not be smaller than rvdw or rcoulomb");
+                warning_error(wi,
+                              "With verlet lists rlist can not be smaller than rvdw or rcoulomb");
             }
 
             if (ir->rlist == rc_max && ir->nstlist > 1)
             {
-                warning_note(wi, "rlist is equal to rvdw and/or rcoulomb: there is no explicit Verlet buffer. The cluster pair list does have a buffering effect, but choosing a larger rlist might be necessary for good energy conservation.");
+                warning_note(
+                        wi,
+                        "rlist is equal to rvdw and/or rcoulomb: there is no explicit Verlet "
+                        "buffer. The cluster pair list does have a buffering effect, but choosing "
+                        "a larger rlist might be necessary for good energy conservation.");
             }
         }
         else
         {
             if (ir->rlist > rc_max)
             {
-                warning_note(wi, "You have set rlist larger than the interaction cut-off, but you also have verlet-buffer-tolerance > 0. Will set rlist using verlet-buffer-tolerance.");
+                warning_note(wi,
+                             "You have set rlist larger than the interaction cut-off, but you also "
+                             "have verlet-buffer-tolerance > 0. Will set rlist using "
+                             "verlet-buffer-tolerance.");
             }
 
             if (ir->nstlist == 1)
@@ -403,7 +441,12 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
                 {
                     if (inputrec2nboundeddim(ir) < 3)
                     {
-                        warning_error(wi, "The box volume is required for calculating rlist from the energy drift with verlet-buffer-tolerance > 0. You are using at least one unbounded dimension, so no volume can be computed. Either use a finite box, or set rlist yourself together with verlet-buffer-tolerance = -1.");
+                        warning_error(wi,
+                                      "The box volume is required for calculating rlist from the "
+                                      "energy drift with verlet-buffer-tolerance > 0. You are "
+                                      "using at least one unbounded dimension, so no volume can be "
+                                      "computed. Either use a finite box, or set rlist yourself "
+                                      "together with verlet-buffer-tolerance = -1.");
                     }
                     /* Set rlist temporarily so we can continue processing */
                     ir->rlist = rc_max;
@@ -411,7 +454,7 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
                 else
                 {
                     /* Set the buffer to 5% of the cut-off */
-                    ir->rlist = (1.0 + verlet_buffer_ratio_nodynamics)*rc_max;
+                    ir->rlist = (1.0 + verlet_buffer_ratio_nodynamics) * rc_max;
                 }
             }
         }
@@ -420,33 +463,51 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
     /* GENERAL INTEGRATOR STUFF */
     if (!EI_MD(ir->eI))
     {
-        if (ir->etc != etcNO)
+        if (ir->etc != TemperatureCoupling::No)
         {
             if (EI_RANDOM(ir->eI))
             {
-                sprintf(warn_buf, "Setting tcoupl from '%s' to 'no'. %s handles temperature coupling implicitly. See the documentation for more information on which parameters affect temperature for %s.", etcoupl_names[ir->etc], ei_names[ir->eI], ei_names[ir->eI]);
+                sprintf(warn_buf,
+                        "Setting tcoupl from '%s' to 'no'. %s handles temperature coupling "
+                        "implicitly. See the documentation for more information on which "
+                        "parameters affect temperature for %s.",
+                        enumValueToString(ir->etc),
+                        enumValueToString(ir->eI),
+                        enumValueToString(ir->eI));
             }
             else
             {
-                sprintf(warn_buf, "Setting tcoupl from '%s' to 'no'. Temperature coupling does not apply to %s.", etcoupl_names[ir->etc], ei_names[ir->eI]);
+                sprintf(warn_buf,
+                        "Setting tcoupl from '%s' to 'no'. Temperature coupling does not apply to "
+                        "%s.",
+                        enumValueToString(ir->etc),
+                        enumValueToString(ir->eI));
             }
             warning_note(wi, warn_buf);
         }
-        ir->etc = etcNO;
+        ir->etc = TemperatureCoupling::No;
     }
-    if (ir->eI == eiVVAK)
+    if (ir->eI == IntegrationAlgorithm::VVAK)
     {
-        sprintf(warn_buf, "Integrator method %s is implemented primarily for validation purposes; for molecular dynamics, you should probably be using %s or %s", ei_names[eiVVAK], ei_names[eiMD], ei_names[eiVV]);
+        sprintf(warn_buf,
+                "Integrator method %s is implemented primarily for validation purposes; for "
+                "molecular dynamics, you should probably be using %s or %s",
+                enumValueToString(IntegrationAlgorithm::VVAK),
+                enumValueToString(IntegrationAlgorithm::MD),
+                enumValueToString(IntegrationAlgorithm::VV));
         warning_note(wi, warn_buf);
     }
     if (!EI_DYNAMICS(ir->eI))
     {
-        if (ir->epc != epcNO)
+        if (ir->epc != PressureCoupling::No)
         {
-            sprintf(warn_buf, "Setting pcoupl from '%s' to 'no'. Pressure coupling does not apply to %s.", epcoupl_names[ir->epc], ei_names[ir->eI]);
+            sprintf(warn_buf,
+                    "Setting pcoupl from '%s' to 'no'. Pressure coupling does not apply to %s.",
+                    enumValueToString(ir->epc),
+                    enumValueToString(ir->eI));
             warning_note(wi, warn_buf);
         }
-        ir->epc = epcNO;
+        ir->epc = PressureCoupling::No;
     }
     if (EI_DYNAMICS(ir->eI))
     {
@@ -460,7 +521,7 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
                  */
                 if (ir->nstlist > 0)
                 {
-                    ir->nstcalcenergy = lcd(ir->nstenergy, ir->nstlist);
+                    ir->nstcalcenergy = std::gcd(ir->nstenergy, ir->nstlist);
                 }
                 else
                 {
@@ -468,67 +529,69 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
                 }
             }
         }
-        else if ( (ir->nstenergy > 0 && ir->nstcalcenergy > ir->nstenergy) ||
-                  (ir->efep != efepNO && ir->fepvals->nstdhdl > 0 &&
-                   (ir->nstcalcenergy > ir->fepvals->nstdhdl) ) )
+        else if ((ir->nstenergy > 0 && ir->nstcalcenergy > ir->nstenergy)
+                 || (ir->efep != FreeEnergyPerturbationType::No && ir->fepvals->nstdhdl > 0
+                     && (ir->nstcalcenergy > ir->fepvals->nstdhdl)))
 
         {
-            const char *nsten    = "nstenergy";
-            const char *nstdh    = "nstdhdl";
-            const char *min_name = nsten;
+            const charnsten    = "nstenergy";
+            const charnstdh    = "nstdhdl";
+            const charmin_name = nsten;
             int         min_nst  = ir->nstenergy;
 
             /* find the smallest of ( nstenergy, nstdhdl ) */
-            if (ir->efep != efepNO && ir->fepvals->nstdhdl > 0 &&
-                (ir->nstenergy == 0 || ir->fepvals->nstdhdl < ir->nstenergy))
+            if (ir->efep != FreeEnergyPerturbationType::No && ir->fepvals->nstdhdl > 0
+                && (ir->nstenergy == 0 || ir->fepvals->nstdhdl < ir->nstenergy))
             {
                 min_nst  = ir->fepvals->nstdhdl;
                 min_name = nstdh;
             }
             /* If the user sets nstenergy small, we should respect that */
-            sprintf(warn_buf,
-                    "Setting nstcalcenergy (%d) equal to %s (%d)",
-                    ir->nstcalcenergy, min_name, min_nst);
+            sprintf(warn_buf, "Setting nstcalcenergy (%d) equal to %s (%d)", ir->nstcalcenergy, min_name, min_nst);
             warning_note(wi, warn_buf);
             ir->nstcalcenergy = min_nst;
         }
 
-        if (ir->epc != epcNO)
+        if (ir->epc != PressureCoupling::No)
         {
             if (ir->nstpcouple < 0)
             {
                 ir->nstpcouple = ir_optimal_nstpcouple(ir);
             }
+            if (ir->useMts && ir->nstpcouple % ir->mtsLevels.back().stepFactor != 0)
+            {
+                warning_error(wi,
+                              "With multiple time stepping, nstpcouple should be a mutiple of "
+                              "mts-factor");
+            }
         }
 
         if (ir->nstcalcenergy > 0)
         {
-            if (ir->efep != efepNO)
+            if (ir->efep != FreeEnergyPerturbationType::No)
             {
                 /* nstdhdl should be a multiple of nstcalcenergy */
-                check_nst("nstcalcenergy", ir->nstcalcenergy,
-                          "nstdhdl", &ir->fepvals->nstdhdl, wi);
+                check_nst("nstcalcenergy", ir->nstcalcenergy, "nstdhdl", &ir->fepvals->nstdhdl, wi);
             }
             if (ir->bExpanded)
             {
                 /* nstexpanded should be a multiple of nstcalcenergy */
-                check_nst("nstcalcenergy", ir->nstcalcenergy,
-                          "nstexpanded", &ir->expandedvals->nstexpanded, wi);
+                check_nst("nstcalcenergy", ir->nstcalcenergy, "nstexpanded", &ir->expandedvals->nstexpanded, wi);
             }
             /* for storing exact averages nstenergy should be
              * a multiple of nstcalcenergy
              */
-            check_nst("nstcalcenergy", ir->nstcalcenergy,
-                      "nstenergy", &ir->nstenergy, wi);
+            check_nst("nstcalcenergy", ir->nstcalcenergy, "nstenergy", &ir->nstenergy, wi);
         }
 
-        // Inquire all MdModules, if their parameters match with the energy
+        // Inquire all MDModules, if their parameters match with the energy
         // calculation frequency
         gmx::EnergyCalculationFrequencyErrors energyCalculationFrequencyErrors(ir->nstcalcenergy);
-        mdModulesNotifier.notifier_.notify(&energyCalculationFrequencyErrors);
+        mdModulesNotifiers.preProcessingNotifier_.notify(&energyCalculationFrequencyErrors);
 
         // Emit all errors from the energy calculation frequency checks
-        for (const std::string &energyFrequencyErrorMessage : energyCalculationFrequencyErrors.errorMessages())
+        for (const std::string& energyFrequencyErrorMessage :
+             energyCalculationFrequencyErrors.errorMessages())
         {
             warning_error(wi, energyFrequencyErrorMessage);
         }
@@ -536,21 +599,24 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
 
     if (ir->nsteps == 0 && !ir->bContinuation)
     {
-        warning_note(wi, "For a correct single-point energy evaluation with nsteps = 0, use continuation = yes to avoid constraining the input coordinates.");
+        warning_note(wi,
+                     "For a correct single-point energy evaluation with nsteps = 0, use "
+                     "continuation = yes to avoid constraining the input coordinates.");
     }
 
     /* LD STUFF */
-    if ((EI_SD(ir->eI) || ir->eI == eiBD) &&
-        ir->bContinuation && ir->ld_seed != -1)
+    if ((EI_SD(ir->eI) || ir->eI == IntegrationAlgorithm::BD) && ir->bContinuation && ir->ld_seed != -1)
     {
-        warning_note(wi, "You are doing a continuation with SD or BD, make sure that ld_seed is different from the previous run (using ld_seed=-1 will ensure this)");
+        warning_note(wi,
+                     "You are doing a continuation with SD or BD, make sure that ld_seed is "
+                     "different from the previous run (using ld_seed=-1 will ensure this)");
     }
 
     /* TPI STUFF */
     if (EI_TPI(ir->eI))
     {
-        sprintf(err_buf, "TPI only works with pbc = %s", epbc_names[epbcXYZ]);
-        CHECK(ir->ePBC != epbcXYZ);
+        sprintf(err_buf, "TPI only works with pbc = %s", c_pbcTypeNames[PbcType::Xyz].c_str());
+        CHECK(ir->pbcType != PbcType::Xyz);
         sprintf(err_buf, "with TPI nstlist should be larger than zero");
         CHECK(ir->nstlist <= 0);
         sprintf(err_buf, "TPI does not work with full electrostatics other than PME");
@@ -558,17 +624,17 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
     }
 
     /* SHAKE / LINCS */
-    if ( (opts->nshake > 0) && (opts->bMorse) )
+    if ((opts->nshake > 0) && (opts->bMorse))
     {
-        sprintf(warn_buf,
-                "Using morse bond-potentials while constraining bonds is useless");
+        sprintf(warn_buf, "Using morse bond-potentials while constraining bonds is useless");
         warning(wi, warn_buf);
     }
 
-    if ((EI_SD(ir->eI) || ir->eI == eiBD) &&
-        ir->bContinuation && ir->ld_seed != -1)
+    if ((EI_SD(ir->eI) || ir->eI == IntegrationAlgorithm::BD) && ir->bContinuation && ir->ld_seed != -1)
     {
-        warning_note(wi, "You are doing a continuation with SD or BD, make sure that ld_seed is different from the previous run (using ld_seed=-1 will ensure this)");
+        warning_note(wi,
+                     "You are doing a continuation with SD or BD, make sure that ld_seed is "
+                     "different from the previous run (using ld_seed=-1 will ensure this)");
     }
     /* verify simulated tempering options */
 
@@ -577,9 +643,15 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
         bool bAllTempZero = TRUE;
         for (i = 0; i < fep->n_lambda; i++)
         {
-            sprintf(err_buf, "Entry %d for %s must be between 0 and 1, instead is %g", i, efpt_names[efptTEMPERATURE], fep->all_lambda[efptTEMPERATURE][i]);
-            CHECK((fep->all_lambda[efptTEMPERATURE][i] < 0) || (fep->all_lambda[efptTEMPERATURE][i] > 1));
-            if (fep->all_lambda[efptTEMPERATURE][i] > 0)
+            sprintf(err_buf,
+                    "Entry %d for %s must be between 0 and 1, instead is %g",
+                    i,
+                    enumValueToString(FreeEnergyPerturbationCouplingType::Temperature),
+                    fep->all_lambda[static_cast<int>(FreeEnergyPerturbationCouplingType::Temperature)][i]);
+            CHECK((fep->all_lambda[static_cast<int>(FreeEnergyPerturbationCouplingType::Temperature)][i] < 0)
+                  || (fep->all_lambda[static_cast<int>(FreeEnergyPerturbationCouplingType::Temperature)][i]
+                      > 1));
+            if (fep->all_lambda[static_cast<int>(FreeEnergyPerturbationCouplingType::Temperature)][i] > 0)
             {
                 bAllTempZero = FALSE;
             }
@@ -588,52 +660,69 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
         CHECK(bAllTempZero == TRUE);
 
         sprintf(err_buf, "Simulated tempering is currently only compatible with md-vv");
-        CHECK(ir->eI != eiVV);
+        CHECK(ir->eI != IntegrationAlgorithm::VV);
 
         /* check compatability of the temperature coupling with simulated tempering */
 
-        if (ir->etc == etcNOSEHOOVER)
+        if (ir->etc == TemperatureCoupling::NoseHoover)
         {
-            sprintf(warn_buf, "Nose-Hoover based temperature control such as [%s] my not be entirelyconsistent with simulated tempering", etcoupl_names[ir->etc]);
+            sprintf(warn_buf,
+                    "Nose-Hoover based temperature control such as [%s] my not be "
+                    "entirelyconsistent with simulated tempering",
+                    enumValueToString(ir->etc));
             warning_note(wi, warn_buf);
         }
 
         /* check that the temperatures make sense */
 
-        sprintf(err_buf, "Higher simulated tempering temperature (%g) must be >= than the simulated tempering lower temperature (%g)", ir->simtempvals->simtemp_high, ir->simtempvals->simtemp_low);
+        sprintf(err_buf,
+                "Higher simulated tempering temperature (%g) must be >= than the simulated "
+                "tempering lower temperature (%g)",
+                ir->simtempvals->simtemp_high,
+                ir->simtempvals->simtemp_low);
         CHECK(ir->simtempvals->simtemp_high <= ir->simtempvals->simtemp_low);
 
-        sprintf(err_buf, "Higher simulated tempering temperature (%g) must be >= zero", ir->simtempvals->simtemp_high);
+        sprintf(err_buf,
+                "Higher simulated tempering temperature (%g) must be >= zero",
+                ir->simtempvals->simtemp_high);
         CHECK(ir->simtempvals->simtemp_high <= 0);
 
-        sprintf(err_buf, "Lower simulated tempering temperature (%g) must be >= zero", ir->simtempvals->simtemp_low);
+        sprintf(err_buf,
+                "Lower simulated tempering temperature (%g) must be >= zero",
+                ir->simtempvals->simtemp_low);
         CHECK(ir->simtempvals->simtemp_low <= 0);
     }
 
     /* verify free energy options */
 
-    if (ir->efep != efepNO)
+    if (ir->efep != FreeEnergyPerturbationType::No)
     {
-        fep = ir->fepvals;
-        sprintf(err_buf, "The soft-core power is %d and can only be 1 or 2",
-                fep->sc_power);
+        fep = ir->fepvals.get();
+        sprintf(err_buf, "The soft-core power is %d and can only be 1 or 2", fep->sc_power);
         CHECK(fep->sc_alpha != 0 && fep->sc_power != 1 && fep->sc_power != 2);
 
-        sprintf(err_buf, "The soft-core sc-r-power is %d and can only be 6 or 48",
+        sprintf(err_buf,
+                "The soft-core sc-r-power is %d and can only be 6. (sc-r-power 48 is no longer "
+                "supported.)",
                 static_cast<int>(fep->sc_r_power));
-        CHECK(fep->sc_alpha != 0 && fep->sc_r_power != 6.0 && fep->sc_r_power != 48.0);
+        CHECK(fep->sc_alpha != 0 && fep->sc_r_power != 6.0);
 
-        sprintf(err_buf, "Can't use positive delta-lambda (%g) if initial state/lambda does not start at zero", fep->delta_lambda);
-        CHECK(fep->delta_lambda > 0 && ((fep->init_fep_state > 0) ||  (fep->init_lambda > 0)));
+        sprintf(err_buf,
+                "Can't use positive delta-lambda (%g) if initial state/lambda does not start at "
+                "zero",
+                fep->delta_lambda);
+        CHECK(fep->delta_lambda > 0 && ((fep->init_fep_state > 0) || (fep->init_lambda > 0)));
 
-        sprintf(err_buf, "Can't use positive delta-lambda (%g) with expanded ensemble simulations", fep->delta_lambda);
-        CHECK(fep->delta_lambda > 0 && (ir->efep == efepEXPANDED));
+        sprintf(err_buf,
+                "Can't use positive delta-lambda (%g) with expanded ensemble simulations",
+                fep->delta_lambda);
+        CHECK(fep->delta_lambda > 0 && (ir->efep == FreeEnergyPerturbationType::Expanded));
 
         sprintf(err_buf, "Can only use expanded ensemble with md-vv (for now)");
-        CHECK(!(EI_VV(ir->eI)) && (ir->efep == efepEXPANDED));
+        CHECK(!(EI_VV(ir->eI)) && (ir->efep == FreeEnergyPerturbationType::Expanded));
 
         sprintf(err_buf, "Free-energy not implemented for Ewald");
-        CHECK(ir->coulombtype == eelEWALD);
+        CHECK(ir->coulombtype == CoulombInteractionType::Ewald);
 
         /* check validty of lambda inputs */
         if (fep->n_lambda == 0)
@@ -644,24 +733,30 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
         }
         else
         {
-            sprintf(err_buf, "initial thermodynamic state %d does not exist, only goes to %d", fep->init_fep_state, fep->n_lambda-1);
+            sprintf(err_buf,
+                    "initial thermodynamic state %d does not exist, only goes to %d",
+                    fep->init_fep_state,
+                    fep->n_lambda - 1);
             CHECK((fep->init_fep_state >= fep->n_lambda));
         }
 
-        sprintf(err_buf, "Lambda state must be set, either with init-lambda-state or with init-lambda");
+        sprintf(err_buf,
+                "Lambda state must be set, either with init-lambda-state or with init-lambda");
         CHECK((fep->init_fep_state < 0) && (fep->init_lambda < 0));
 
-        sprintf(err_buf, "init-lambda=%g while init-lambda-state=%d. Lambda state must be set either with init-lambda-state or with init-lambda, but not both",
-                fep->init_lambda, fep->init_fep_state);
+        sprintf(err_buf,
+                "init-lambda=%g while init-lambda-state=%d. Lambda state must be set either with "
+                "init-lambda-state or with init-lambda, but not both",
+                fep->init_lambda,
+                fep->init_fep_state);
         CHECK((fep->init_fep_state >= 0) && (fep->init_lambda >= 0));
 
 
-
         if ((fep->init_lambda >= 0) && (fep->delta_lambda == 0))
         {
             int n_lambda_terms;
             n_lambda_terms = 0;
-            for (i = 0; i < efptNR; i++)
+            for (i = 0; i < static_cast<int>(FreeEnergyPerturbationCouplingType::Count); i++)
             {
                 if (fep->separate_dvdl[i])
                 {
@@ -670,22 +765,31 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
             }
             if (n_lambda_terms > 1)
             {
-                sprintf(warn_buf, "If lambda vector states (fep-lambdas, coul-lambdas etc.) are set, don't use init-lambda to set lambda state (except for slow growth). Use init-lambda-state instead.");
+                sprintf(warn_buf,
+                        "If lambda vector states (fep-lambdas, coul-lambdas etc.) are set, don't "
+                        "use init-lambda to set lambda state (except for slow growth). Use "
+                        "init-lambda-state instead.");
                 warning(wi, warn_buf);
             }
 
             if (n_lambda_terms < 2 && fep->n_lambda > 0)
             {
                 warning_note(wi,
-                             "init-lambda is deprecated for setting lambda state (except for slow growth). Use init-lambda-state instead.");
+                             "init-lambda is deprecated for setting lambda state (except for slow "
+                             "growth). Use init-lambda-state instead.");
             }
         }
 
-        for (j = 0; j < efptNR; j++)
+        for (j = 0; j < static_cast<int>(FreeEnergyPerturbationCouplingType::Count); j++)
         {
             for (i = 0; i < fep->n_lambda; i++)
             {
-                sprintf(err_buf, "Entry %d for %s must be between 0 and 1, instead is %g", i, efpt_names[j], fep->all_lambda[j][i]);
+                auto enumValue = static_cast<FreeEnergyPerturbationCouplingType>(j);
+                sprintf(err_buf,
+                        "Entry %d for %s must be between 0 and 1, instead is %g",
+                        i,
+                        enumValueToString(enumValue),
+                        fep->all_lambda[j][i]);
                 CHECK((fep->all_lambda[j][i] < 0) || (fep->all_lambda[j][i] > 1));
             }
         }
@@ -694,13 +798,19 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
         {
             for (i = 0; i < fep->n_lambda; i++)
             {
-                sprintf(err_buf, "For state %d, vdw-lambdas (%f) is changing with vdw softcore, while coul-lambdas (%f) is nonzero without coulomb softcore: this will lead to crashes, and is not supported.", i, fep->all_lambda[efptVDW][i],
-                        fep->all_lambda[efptCOUL][i]);
-                CHECK((fep->sc_alpha > 0) &&
-                      (((fep->all_lambda[efptCOUL][i] > 0.0) &&
-                        (fep->all_lambda[efptCOUL][i] < 1.0)) &&
-                       ((fep->all_lambda[efptVDW][i] > 0.0) &&
-                        (fep->all_lambda[efptVDW][i] < 1.0))));
+                sprintf(err_buf,
+                        "For state %d, vdw-lambdas (%f) is changing with vdw softcore, while "
+                        "coul-lambdas (%f) is nonzero without coulomb softcore: this will lead to "
+                        "crashes, and is not supported.",
+                        i,
+                        fep->all_lambda[static_cast<int>(FreeEnergyPerturbationCouplingType::Vdw)][i],
+                        fep->all_lambda[static_cast<int>(FreeEnergyPerturbationCouplingType::Coul)][i]);
+                CHECK((fep->sc_alpha > 0)
+                      && (((fep->all_lambda[static_cast<int>(FreeEnergyPerturbationCouplingType::Coul)][i] > 0.0)
+                           && (fep->all_lambda[static_cast<int>(FreeEnergyPerturbationCouplingType::Coul)][i] < 1.0))
+                          && ((fep->all_lambda[static_cast<int>(FreeEnergyPerturbationCouplingType::Vdw)][i] > 0.0)
+                              && (fep->all_lambda[static_cast<int>(FreeEnergyPerturbationCouplingType::Vdw)][i]
+                                  < 1.0))));
             }
         }
 
@@ -708,78 +818,139 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
         {
             real sigma, lambda, r_sc;
 
-            sigma  = 0.34;
+            sigma = 0.34;
             /* Maximum estimate for A and B charges equal with lambda power 1 */
             lambda = 0.5;
-            r_sc   = std::pow(lambda*fep->sc_alpha*std::pow(sigma/ir->rcoulomb, fep->sc_r_power) + 1.0, 1.0/fep->sc_r_power);
-            sprintf(warn_buf, "With PME there is a minor soft core effect present at the cut-off, proportional to (LJsigma/rcoulomb)^%g. This could have a minor effect on energy conservation, but usually other effects dominate. With a common sigma value of %g nm the fraction of the particle-particle potential at the cut-off at lambda=%g is around %.1e, while ewald-rtol is %.1e.",
+            r_sc = std::pow(lambda * fep->sc_alpha * std::pow(sigma / ir->rcoulomb, fep->sc_r_power) + 1.0,
+                            1.0 / fep->sc_r_power);
+            sprintf(warn_buf,
+                    "With PME there is a minor soft core effect present at the cut-off, "
+                    "proportional to (LJsigma/rcoulomb)^%g. This could have a minor effect on "
+                    "energy conservation, but usually other effects dominate. With a common sigma "
+                    "value of %g nm the fraction of the particle-particle potential at the cut-off "
+                    "at lambda=%g is around %.1e, while ewald-rtol is %.1e.",
                     fep->sc_r_power,
-                    sigma, lambda, r_sc - 1.0, ir->ewald_rtol);
+                    sigma,
+                    lambda,
+                    r_sc - 1.0,
+                    ir->ewald_rtol);
             warning_note(wi, warn_buf);
         }
 
         /*  Free Energy Checks -- In an ideal world, slow growth and FEP would
             be treated differently, but that's the next step */
 
-        for (i = 0; i < efptNR; i++)
+        for (i = 0; i < static_cast<int>(FreeEnergyPerturbationCouplingType::Count); i++)
         {
+            auto enumValue = static_cast<FreeEnergyPerturbationCouplingType>(i);
             for (j = 0; j < fep->n_lambda; j++)
             {
-                sprintf(err_buf, "%s[%d] must be between 0 and 1", efpt_names[i], j);
+                sprintf(err_buf, "%s[%d] must be between 0 and 1", enumValueToString(enumValue), j);
                 CHECK((fep->all_lambda[i][j] < 0) || (fep->all_lambda[i][j] > 1));
             }
         }
+
+        if (fep->softcoreFunction == SoftcoreType::Gapsys)
+        {
+            if (fep->scGapsysScaleLinpointQ < 0.0)
+            {
+                sprintf(warn_buf,
+                        "sc_scale_linpoint_Q_gapsys is equal %g but must be >= 0",
+                        fep->scGapsysScaleLinpointQ);
+                warning_note(wi, warn_buf);
+            }
+
+            if ((fep->scGapsysScaleLinpointLJ < 0.0) || (fep->scGapsysScaleLinpointLJ >= 1.0))
+            {
+                sprintf(warn_buf,
+                        "sc_scale_linpoint_LJ_gapsys is equal %g but must be in [0,1) when used "
+                        "with "
+                        "sc_function=gapsys.",
+                        fep->scGapsysScaleLinpointLJ);
+                warning_note(wi, warn_buf);
+            }
+        }
     }
 
-    if ((ir->bSimTemp) || (ir->efep == efepEXPANDED))
+    if ((ir->bSimTemp) || (ir->efep == FreeEnergyPerturbationType::Expanded))
     {
-        fep    = ir->fepvals;
+        fep = ir->fepvals.get();
 
         /* checking equilibration of weights inputs for validity */
 
-        sprintf(err_buf, "weight-equil-number-all-lambda (%d) is ignored if lmc-weights-equil is not equal to %s",
-                expand->equil_n_at_lam, elmceq_names[elmceqNUMATLAM]);
-        CHECK((expand->equil_n_at_lam > 0) && (expand->elmceq != elmceqNUMATLAM));
+        sprintf(err_buf,
+                "weight-equil-number-all-lambda (%d) is ignored if lmc-weights-equil is not equal "
+                "to %s",
+                expand->equil_n_at_lam,
+                enumValueToString(LambdaWeightWillReachEquilibrium::NumAtLambda));
+        CHECK((expand->equil_n_at_lam > 0)
+              && (expand->elmceq != LambdaWeightWillReachEquilibrium::NumAtLambda));
 
-        sprintf(err_buf, "weight-equil-number-samples (%d) is ignored if lmc-weights-equil is not equal to %s",
-                expand->equil_samples, elmceq_names[elmceqSAMPLES]);
-        CHECK((expand->equil_samples > 0) && (expand->elmceq != elmceqSAMPLES));
+        sprintf(err_buf,
+                "weight-equil-number-samples (%d) is ignored if lmc-weights-equil is not equal to "
+                "%s",
+                expand->equil_samples,
+                enumValueToString(LambdaWeightWillReachEquilibrium::Samples));
+        CHECK((expand->equil_samples > 0) && (expand->elmceq != LambdaWeightWillReachEquilibrium::Samples));
 
-        sprintf(err_buf, "weight-equil-number-steps (%d) is ignored if lmc-weights-equil is not equal to %s",
-                expand->equil_steps, elmceq_names[elmceqSTEPS]);
-        CHECK((expand->equil_steps > 0) && (expand->elmceq != elmceqSTEPS));
+        sprintf(err_buf,
+                "weight-equil-number-steps (%d) is ignored if lmc-weights-equil is not equal to %s",
+                expand->equil_steps,
+                enumValueToString(LambdaWeightWillReachEquilibrium::Steps));
+        CHECK((expand->equil_steps > 0) && (expand->elmceq != LambdaWeightWillReachEquilibrium::Steps));
 
-        sprintf(err_buf, "weight-equil-wl-delta (%d) is ignored if lmc-weights-equil is not equal to %s",
-                expand->equil_samples, elmceq_names[elmceqWLDELTA]);
-        CHECK((expand->equil_wl_delta > 0) && (expand->elmceq != elmceqWLDELTA));
+        sprintf(err_buf,
+                "weight-equil-wl-delta (%d) is ignored if lmc-weights-equil is not equal to %s",
+                expand->equil_samples,
+                enumValueToString(LambdaWeightWillReachEquilibrium::WLDelta));
+        CHECK((expand->equil_wl_delta > 0) && (expand->elmceq != LambdaWeightWillReachEquilibrium::WLDelta));
 
-        sprintf(err_buf, "weight-equil-count-ratio (%f) is ignored if lmc-weights-equil is not equal to %s",
-                expand->equil_ratio, elmceq_names[elmceqRATIO]);
-        CHECK((expand->equil_ratio > 0) && (expand->elmceq != elmceqRATIO));
+        sprintf(err_buf,
+                "weight-equil-count-ratio (%f) is ignored if lmc-weights-equil is not equal to %s",
+                expand->equil_ratio,
+                enumValueToString(LambdaWeightWillReachEquilibrium::Ratio));
+        CHECK((expand->equil_ratio > 0) && (expand->elmceq != LambdaWeightWillReachEquilibrium::Ratio));
 
-        sprintf(err_buf, "weight-equil-number-all-lambda (%d) must be a positive integer if lmc-weights-equil=%s",
-                expand->equil_n_at_lam, elmceq_names[elmceqNUMATLAM]);
-        CHECK((expand->equil_n_at_lam <= 0) && (expand->elmceq == elmceqNUMATLAM));
+        sprintf(err_buf,
+                "weight-equil-number-all-lambda (%d) must be a positive integer if "
+                "lmc-weights-equil=%s",
+                expand->equil_n_at_lam,
+                enumValueToString(LambdaWeightWillReachEquilibrium::NumAtLambda));
+        CHECK((expand->equil_n_at_lam <= 0)
+              && (expand->elmceq == LambdaWeightWillReachEquilibrium::NumAtLambda));
 
-        sprintf(err_buf, "weight-equil-number-samples (%d) must be a positive integer if lmc-weights-equil=%s",
-                expand->equil_samples, elmceq_names[elmceqSAMPLES]);
-        CHECK((expand->equil_samples <= 0) && (expand->elmceq == elmceqSAMPLES));
+        sprintf(err_buf,
+                "weight-equil-number-samples (%d) must be a positive integer if "
+                "lmc-weights-equil=%s",
+                expand->equil_samples,
+                enumValueToString(LambdaWeightWillReachEquilibrium::Samples));
+        CHECK((expand->equil_samples <= 0) && (expand->elmceq == LambdaWeightWillReachEquilibrium::Samples));
 
-        sprintf(err_buf, "weight-equil-number-steps (%d) must be a positive integer if lmc-weights-equil=%s",
-                expand->equil_steps, elmceq_names[elmceqSTEPS]);
-        CHECK((expand->equil_steps <= 0) && (expand->elmceq == elmceqSTEPS));
+        sprintf(err_buf,
+                "weight-equil-number-steps (%d) must be a positive integer if lmc-weights-equil=%s",
+                expand->equil_steps,
+                enumValueToString(LambdaWeightWillReachEquilibrium::Steps));
+        CHECK((expand->equil_steps <= 0) && (expand->elmceq == LambdaWeightWillReachEquilibrium::Steps));
 
-        sprintf(err_buf, "weight-equil-wl-delta (%f) must be > 0 if lmc-weights-equil=%s",
-                expand->equil_wl_delta, elmceq_names[elmceqWLDELTA]);
-        CHECK((expand->equil_wl_delta <= 0) && (expand->elmceq == elmceqWLDELTA));
+        sprintf(err_buf,
+                "weight-equil-wl-delta (%f) must be > 0 if lmc-weights-equil=%s",
+                expand->equil_wl_delta,
+                enumValueToString(LambdaWeightWillReachEquilibrium::WLDelta));
+        CHECK((expand->equil_wl_delta <= 0)
+              && (expand->elmceq == LambdaWeightWillReachEquilibrium::WLDelta));
 
-        sprintf(err_buf, "weight-equil-count-ratio (%f) must be > 0 if lmc-weights-equil=%s",
-                expand->equil_ratio, elmceq_names[elmceqRATIO]);
-        CHECK((expand->equil_ratio <= 0) && (expand->elmceq == elmceqRATIO));
+        sprintf(err_buf,
+                "weight-equil-count-ratio (%f) must be > 0 if lmc-weights-equil=%s",
+                expand->equil_ratio,
+                enumValueToString(LambdaWeightWillReachEquilibrium::Ratio));
+        CHECK((expand->equil_ratio <= 0) && (expand->elmceq == LambdaWeightWillReachEquilibrium::Ratio));
 
-        sprintf(err_buf, "lmc-weights-equil=%s only possible when lmc-stats = %s or lmc-stats %s",
-                elmceq_names[elmceqWLDELTA], elamstats_names[elamstatsWL], elamstats_names[elamstatsWWL]);
-        CHECK((expand->elmceq == elmceqWLDELTA) && (!EWL(expand->elamstats)));
+        sprintf(err_buf,
+                "lmc-weights-equil=%s only possible when lmc-stats = %s or lmc-stats %s",
+                enumValueToString(LambdaWeightWillReachEquilibrium::WLDelta),
+                enumValueToString(LambdaWeightCalculation::WL),
+                enumValueToString(LambdaWeightCalculation::WWL));
+        CHECK((expand->elmceq == LambdaWeightWillReachEquilibrium::WLDelta) && (!EWL(expand->elamstats)));
 
         sprintf(err_buf, "lmc-repeats (%d) must be greater than 0", expand->lmc_repeats);
         CHECK((expand->lmc_repeats <= 0));
@@ -787,12 +958,18 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
         CHECK((expand->minvarmin <= 0));
         sprintf(err_buf, "weight-c-range (%d) must be greater or equal to 0", expand->c_range);
         CHECK((expand->c_range < 0));
-        sprintf(err_buf, "init-lambda-state (%d) must be zero if lmc-forced-nstart (%d)> 0 and lmc-move != 'no'",
-                fep->init_fep_state, expand->lmc_forced_nstart);
-        CHECK((fep->init_fep_state != 0) && (expand->lmc_forced_nstart > 0) && (expand->elmcmove != elmcmoveNO));
+        sprintf(err_buf,
+                "init-lambda-state (%d) must be zero if lmc-forced-nstart (%d)> 0 and lmc-move != "
+                "'no'",
+                fep->init_fep_state,
+                expand->lmc_forced_nstart);
+        CHECK((fep->init_fep_state != 0) && (expand->lmc_forced_nstart > 0)
+              && (expand->elmcmove != LambdaMoveCalculation::No));
         sprintf(err_buf, "lmc-forced-nstart (%d) must not be negative", expand->lmc_forced_nstart);
         CHECK((expand->lmc_forced_nstart < 0));
-        sprintf(err_buf, "init-lambda-state (%d) must be in the interval [0,number of lambdas)", fep->init_fep_state);
+        sprintf(err_buf,
+                "init-lambda-state (%d) must be in the interval [0,number of lambdas)",
+                fep->init_fep_state);
         CHECK((fep->init_fep_state < 0) || (fep->init_fep_state >= fep->n_lambda));
 
         sprintf(err_buf, "init-wl-delta (%f) must be greater than or equal to 0", expand->init_wl_delta);
@@ -803,9 +980,12 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
         CHECK((expand->wl_scale <= 0) || (expand->wl_scale >= 1));
 
         /* if there is no temperature control, we need to specify an MC temperature */
-        if (!integratorHasReferenceTemperature(ir) && (expand->elmcmove != elmcmoveNO) && (expand->mc_temp <= 0.0))
+        if (!integratorHasReferenceTemperature(ir)
+            && (expand->elmcmove != LambdaMoveCalculation::No) && (expand->mc_temp <= 0.0))
         {
-            sprintf(err_buf, "If there is no temperature control, and lmc-mcmove!='no', mc_temp must be set to a positive number");
+            sprintf(err_buf,
+                    "If there is no temperature control, and lmc-mcmove!='no', mc_temp must be set "
+                    "to a positive number");
             warning_error(wi, err_buf);
         }
         if (expand->nstTij > 0)
@@ -815,56 +995,65 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
             // Avoid modulus by zero in the case that already triggered an error exit.
             if (ir->nstlog != 0)
             {
-                sprintf(err_buf, "nst-transition-matrix (%d) must be an integer multiple of nstlog (%d)",
-                        expand->nstTij, ir->nstlog);
+                sprintf(err_buf,
+                        "nst-transition-matrix (%d) must be an integer multiple of nstlog (%d)",
+                        expand->nstTij,
+                        ir->nstlog);
                 CHECK((expand->nstTij % ir->nstlog) != 0);
             }
         }
     }
 
     /* PBC/WALLS */
-    sprintf(err_buf, "walls only work with pbc=%s", epbc_names[epbcXY]);
-    CHECK(ir->nwall && ir->ePBC != epbcXY);
+    sprintf(err_buf, "walls only work with pbc=%s", c_pbcTypeNames[PbcType::XY].c_str());
+    CHECK(ir->nwall && ir->pbcType != PbcType::XY);
 
     /* VACUUM STUFF */
-    if (ir->ePBC != epbcXYZ && ir->nwall != 2)
+    if (ir->pbcType != PbcType::Xyz && ir->nwall != 2)
     {
-        if (ir->ePBC == epbcNONE)
+        if (ir->pbcType == PbcType::No)
         {
-            if (ir->epc != epcNO)
+            if (ir->epc != PressureCoupling::No)
             {
                 warning(wi, "Turning off pressure coupling for vacuum system");
-                ir->epc = epcNO;
+                ir->epc = PressureCoupling::No;
             }
         }
         else
         {
-            sprintf(err_buf, "Can not have pressure coupling with pbc=%s",
-                    epbc_names[ir->ePBC]);
-            CHECK(ir->epc != epcNO);
+            sprintf(err_buf,
+                    "Can not have pressure coupling with pbc=%s",
+                    c_pbcTypeNames[ir->pbcType].c_str());
+            CHECK(ir->epc != PressureCoupling::No);
         }
-        sprintf(err_buf, "Can not have Ewald with pbc=%s", epbc_names[ir->ePBC]);
+        sprintf(err_buf, "Can not have Ewald with pbc=%s", c_pbcTypeNames[ir->pbcType].c_str());
         CHECK(EEL_FULL(ir->coulombtype));
 
-        sprintf(err_buf, "Can not have dispersion correction with pbc=%s",
-                epbc_names[ir->ePBC]);
-        CHECK(ir->eDispCorr != edispcNO);
+        sprintf(err_buf,
+                "Can not have dispersion correction with pbc=%s",
+                c_pbcTypeNames[ir->pbcType].c_str());
+        CHECK(ir->eDispCorr != DispersionCorrectionType::No);
     }
 
     if (ir->rlist == 0.0)
     {
-        sprintf(err_buf, "can only have neighborlist cut-off zero (=infinite)\n"
+        sprintf(err_buf,
+                "can only have neighborlist cut-off zero (=infinite)\n"
                 "with coulombtype = %s or coulombtype = %s\n"
                 "without periodic boundary conditions (pbc = %s) and\n"
                 "rcoulomb and rvdw set to zero",
-                eel_names[eelCUT], eel_names[eelUSER], epbc_names[epbcNONE]);
-        CHECK(((ir->coulombtype != eelCUT) && (ir->coulombtype != eelUSER)) ||
-              (ir->ePBC     != epbcNONE) ||
-              (ir->rcoulomb != 0.0)      || (ir->rvdw != 0.0));
+                enumValueToString(CoulombInteractionType::Cut),
+                enumValueToString(CoulombInteractionType::User),
+                c_pbcTypeNames[PbcType::No].c_str());
+        CHECK(((ir->coulombtype != CoulombInteractionType::Cut)
+               && (ir->coulombtype != CoulombInteractionType::User))
+              || (ir->pbcType != PbcType::No) || (ir->rcoulomb != 0.0) || (ir->rvdw != 0.0));
 
         if (ir->nstlist > 0)
         {
-            warning_note(wi, "Simulating without cut-offs can be (slightly) faster with nstlist=0, nstype=simple and only one MPI rank");
+            warning_note(wi,
+                         "Simulating without cut-offs can be (slightly) faster with nstlist=0, "
+                         "nstype=simple and only one MPI rank");
         }
     }
 
@@ -873,9 +1062,9 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
     {
         // TODO Change this behaviour. There should be exactly one way
         // to turn off an algorithm.
-        ir->comm_mode = ecmNO;
+        ir->comm_mode = ComRemovalAlgorithm::No;
     }
-    if (ir->comm_mode != ecmNO)
+    if (ir->comm_mode != ComRemovalAlgorithm::No)
     {
         if (ir->nstcomm < 0)
         {
@@ -883,53 +1072,74 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
             // helpful for a few years, we should reject such input,
             // lest we have to support every historical decision
             // forever.
-            warning(wi, "If you want to remove the rotation around the center of mass, you should set comm_mode = Angular instead of setting nstcomm < 0. nstcomm is modified to its absolute value");
+            warning(wi,
+                    "If you want to remove the rotation around the center of mass, you should set "
+                    "comm_mode = Angular instead of setting nstcomm < 0. nstcomm is modified to "
+                    "its absolute value");
             ir->nstcomm = abs(ir->nstcomm);
         }
 
-        if (ir->nstcalcenergy > 0 && ir->nstcomm < ir->nstcalcenergy)
+        if (ir->nstcalcenergy > 0 && ir->nstcomm < ir->nstcalcenergy
+            && ir->comm_mode != ComRemovalAlgorithm::LinearAccelerationCorrection)
         {
-            warning_note(wi, "nstcomm < nstcalcenergy defeats the purpose of nstcalcenergy, setting nstcomm to nstcalcenergy");
-            ir->nstcomm = ir->nstcalcenergy;
+            warning_note(wi,
+                         "nstcomm < nstcalcenergy defeats the purpose of nstcalcenergy, consider "
+                         "setting nstcomm equal to nstcalcenergy for less overhead");
         }
 
-        if (ir->comm_mode == ecmANGULAR)
+        if (ir->comm_mode == ComRemovalAlgorithm::Angular)
         {
-            sprintf(err_buf, "Can not remove the rotation around the center of mass with periodic molecules");
+            sprintf(err_buf,
+                    "Can not remove the rotation around the center of mass with periodic "
+                    "molecules");
             CHECK(ir->bPeriodicMols);
-            if (ir->ePBC != epbcNONE)
+            if (ir->pbcType != PbcType::No)
             {
-                warning(wi, "Removing the rotation around the center of mass in a periodic system, this can lead to artifacts. Only use this on a single (cluster of) molecules. This cluster should not cross periodic boundaries.");
+                warning(wi,
+                        "Removing the rotation around the center of mass in a periodic system, "
+                        "this can lead to artifacts. Only use this on a single (cluster of) "
+                        "molecules. This cluster should not cross periodic boundaries.");
             }
         }
     }
 
-    if (EI_STATE_VELOCITY(ir->eI) && !EI_SD(ir->eI) && ir->ePBC == epbcNONE && ir->comm_mode != ecmANGULAR)
+    if (EI_STATE_VELOCITY(ir->eI) && !EI_SD(ir->eI) && ir->pbcType == PbcType::No
+        && ir->comm_mode != ComRemovalAlgorithm::Angular)
     {
-        sprintf(warn_buf, "Tumbling and flying ice-cubes: We are not removing rotation around center of mass in a non-periodic system. You should probably set comm_mode = ANGULAR or use integrator = %s.", ei_names[eiSD1]);
+        sprintf(warn_buf,
+                "Tumbling and flying ice-cubes: We are not removing rotation around center of mass "
+                "in a non-periodic system. You should probably set comm_mode = ANGULAR or use "
+                "integrator = %s.",
+                enumValueToString(IntegrationAlgorithm::SD1));
         warning_note(wi, warn_buf);
     }
 
     /* TEMPERATURE COUPLING */
-    if (ir->etc == etcYES)
+    if (ir->etc == TemperatureCoupling::Yes)
     {
-        ir->etc = etcBERENDSEN;
-        warning_note(wi, "Old option for temperature coupling given: "
+        ir->etc = TemperatureCoupling::Berendsen;
+        warning_note(wi,
+                     "Old option for temperature coupling given: "
                      "changing \"yes\" to \"Berendsen\"\n");
     }
 
-    if ((ir->etc == etcNOSEHOOVER) || (ir->epc == epcMTTK))
+    if ((ir->etc == TemperatureCoupling::NoseHoover) || (ir->epc == PressureCoupling::Mttk))
     {
         if (ir->opts.nhchainlength < 1)
         {
-            sprintf(warn_buf, "number of Nose-Hoover chains (currently %d) cannot be less than 1,reset to 1\n", ir->opts.nhchainlength);
+            sprintf(warn_buf,
+                    "number of Nose-Hoover chains (currently %d) cannot be less than 1,reset to "
+                    "1\n",
+                    ir->opts.nhchainlength);
             ir->opts.nhchainlength = 1;
             warning(wi, warn_buf);
         }
 
-        if (ir->etc == etcNOSEHOOVER && !EI_VV(ir->eI) && ir->opts.nhchainlength > 1)
+        if (ir->etc == TemperatureCoupling::NoseHoover && !EI_VV(ir->eI) && ir->opts.nhchainlength > 1)
         {
-            warning_note(wi, "leapfrog does not yet support Nose-Hoover chains, nhchainlength reset to 1");
+            warning_note(
+                    wi,
+                    "leapfrog does not yet support Nose-Hoover chains, nhchainlength reset to 1");
             ir->opts.nhchainlength = 1;
         }
     }
@@ -938,73 +1148,97 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
         ir->opts.nhchainlength = 0;
     }
 
-    if (ir->eI == eiVVAK)
+    if (ir->eI == IntegrationAlgorithm::VVAK)
     {
-        sprintf(err_buf, "%s implemented primarily for validation, and requires nsttcouple = 1 and nstpcouple = 1.",
-                ei_names[eiVVAK]);
+        sprintf(err_buf,
+                "%s implemented primarily for validation, and requires nsttcouple = 1 and "
+                "nstpcouple = 1.",
+                enumValueToString(IntegrationAlgorithm::VVAK));
         CHECK((ir->nsttcouple != 1) || (ir->nstpcouple != 1));
     }
 
     if (ETC_ANDERSEN(ir->etc))
     {
-        sprintf(err_buf, "%s temperature control not supported for integrator %s.", etcoupl_names[ir->etc], ei_names[ir->eI]);
+        sprintf(err_buf,
+                "%s temperature control not supported for integrator %s.",
+                enumValueToString(ir->etc),
+                enumValueToString(ir->eI));
         CHECK(!(EI_VV(ir->eI)));
 
-        if (ir->nstcomm > 0 && (ir->etc == etcANDERSEN))
+        if (ir->nstcomm > 0 && (ir->etc == TemperatureCoupling::Andersen))
         {
-            sprintf(warn_buf, "Center of mass removal not necessary for %s.  All velocities of coupled groups are rerandomized periodically, so flying ice cube errors will not occur.", etcoupl_names[ir->etc]);
+            sprintf(warn_buf,
+                    "Center of mass removal not necessary for %s.  All velocities of coupled "
+                    "groups are rerandomized periodically, so flying ice cube errors will not "
+                    "occur.",
+                    enumValueToString(ir->etc));
             warning_note(wi, warn_buf);
         }
 
-        sprintf(err_buf, "nstcomm must be 1, not %d for %s, as velocities of atoms in coupled groups are randomized every time step", ir->nstcomm, etcoupl_names[ir->etc]);
-        CHECK(ir->nstcomm > 1 && (ir->etc == etcANDERSEN));
+        sprintf(err_buf,
+                "nstcomm must be 1, not %d for %s, as velocities of atoms in coupled groups are "
+                "randomized every time step",
+                ir->nstcomm,
+                enumValueToString(ir->etc));
+        CHECK(ir->nstcomm > 1 && (ir->etc == TemperatureCoupling::Andersen));
     }
 
-    if (ir->etc == etcBERENDSEN)
+    if (ir->etc == TemperatureCoupling::Berendsen)
     {
-        sprintf(warn_buf, "The %s thermostat does not generate the correct kinetic energy distribution. You might want to consider using the %s thermostat.",
-                ETCOUPLTYPE(ir->etc), ETCOUPLTYPE(etcVRESCALE));
+        sprintf(warn_buf,
+                "The %s thermostat does not generate the correct kinetic energy distribution. You "
+                "might want to consider using the %s thermostat.",
+                enumValueToString(ir->etc),
+                enumValueToString(TemperatureCoupling::VRescale));
         warning_note(wi, warn_buf);
     }
 
-    if ((ir->etc == etcNOSEHOOVER || ETC_ANDERSEN(ir->etc))
-        && ir->epc == epcBERENDSEN)
+    if ((ir->etc == TemperatureCoupling::NoseHoover || ETC_ANDERSEN(ir->etc))
+        && ir->epc == PressureCoupling::Berendsen)
     {
-        sprintf(warn_buf, "Using Berendsen pressure coupling invalidates the "
+        sprintf(warn_buf,
+                "Using Berendsen pressure coupling invalidates the "
                 "true ensemble for the thermostat");
         warning(wi, warn_buf);
     }
 
     /* PRESSURE COUPLING */
-    if (ir->epc == epcISOTROPIC)
+    if (ir->epc == PressureCoupling::Isotropic)
     {
-        ir->epc = epcBERENDSEN;
-        warning_note(wi, "Old option for pressure coupling given: "
+        ir->epc = PressureCoupling::Berendsen;
+        warning_note(wi,
+                     "Old option for pressure coupling given: "
                      "changing \"Isotropic\" to \"Berendsen\"\n");
     }
 
-    if (ir->epc != epcNO)
+    if (ir->epc != PressureCoupling::No)
     {
-        dt_pcoupl = ir->nstpcouple*ir->delta_t;
+        dt_pcoupl = ir->nstpcouple * ir->delta_t;
 
         sprintf(err_buf, "tau-p must be > 0 instead of %g\n", ir->tau_p);
         CHECK(ir->tau_p <= 0);
 
-        if (ir->tau_p/dt_pcoupl < pcouple_min_integration_steps(ir->epc) - 10*GMX_REAL_EPS)
+        if (ir->tau_p / dt_pcoupl < pcouple_min_integration_steps(ir->epc) - 10 * GMX_REAL_EPS)
         {
-            sprintf(warn_buf, "For proper integration of the %s barostat, tau-p (%g) should be at least %d times larger than nstpcouple*dt (%g)",
-                    EPCOUPLTYPE(ir->epc), ir->tau_p, pcouple_min_integration_steps(ir->epc), dt_pcoupl);
+            sprintf(warn_buf,
+                    "For proper integration of the %s barostat, tau-p (%g) should be at least %d "
+                    "times larger than nstpcouple*dt (%g)",
+                    enumValueToString(ir->epc),
+                    ir->tau_p,
+                    pcouple_min_integration_steps(ir->epc),
+                    dt_pcoupl);
             warning(wi, warn_buf);
         }
 
-        sprintf(err_buf, "compressibility must be > 0 when using pressure"
-                " coupling %s\n", EPCOUPLTYPE(ir->epc));
-        CHECK(ir->compress[XX][XX] < 0 || ir->compress[YY][YY] < 0 ||
-              ir->compress[ZZ][ZZ] < 0 ||
-              (trace(ir->compress) == 0 && ir->compress[YY][XX] <= 0 &&
-               ir->compress[ZZ][XX] <= 0 && ir->compress[ZZ][YY] <= 0));
+        sprintf(err_buf,
+                "compressibility must be > 0 when using pressure"
+                " coupling %s\n",
+                enumValueToString(ir->epc));
+        CHECK(ir->compress[XX][XX] < 0 || ir->compress[YY][YY] < 0 || ir->compress[ZZ][ZZ] < 0
+              || (trace(ir->compress) == 0 && ir->compress[YY][XX] <= 0 && ir->compress[ZZ][XX] <= 0
+                  && ir->compress[ZZ][YY] <= 0));
 
-        if (epcPARRINELLORAHMAN == ir->epc && opts->bGenVel)
+        if (PressureCoupling::ParrinelloRahman == ir->epc && opts->bGenVel)
         {
             sprintf(warn_buf,
                     "You are generating velocities so I am assuming you "
@@ -1014,14 +1248,14 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
                     "equilibrating first with Berendsen pressure coupling. If "
                     "you are not equilibrating the system, you can probably "
                     "ignore this warning.",
-                    epcoupl_names[ir->epc]);
+                    enumValueToString(ir->epc));
             warning(wi, warn_buf);
         }
     }
 
     if (!EI_VV(ir->eI))
     {
-        if (ir->epc == epcMTTK)
+        if (ir->epc == PressureCoupling::Mttk)
         {
             warning_error(wi, "MTTK pressure coupling requires a Velocity-verlet integrator");
         }
@@ -1030,18 +1264,22 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
     /* ELECTROSTATICS */
     /* More checks are in triple check (grompp.c) */
 
-    if (ir->coulombtype == eelSWITCH)
+    if (ir->coulombtype == CoulombInteractionType::Switch)
     {
-        sprintf(warn_buf, "coulombtype = %s is only for testing purposes and can lead to serious "
+        sprintf(warn_buf,
+                "coulombtype = %s is only for testing purposes and can lead to serious "
                 "artifacts, advice: use coulombtype = %s",
-                eel_names[ir->coulombtype],
-                eel_names[eelRF_ZERO]);
+                enumValueToString(ir->coulombtype),
+                enumValueToString(CoulombInteractionType::RFZero));
         warning(wi, warn_buf);
     }
 
     if (EEL_RF(ir->coulombtype) && ir->epsilon_rf == 1 && ir->epsilon_r != 1)
     {
-        sprintf(warn_buf, "epsilon-r = %g and epsilon-rf = 1 with reaction field, proceeding assuming old format and exchanging epsilon-r and epsilon-rf", ir->epsilon_r);
+        sprintf(warn_buf,
+                "epsilon-r = %g and epsilon-rf = 1 with reaction field, proceeding assuming old "
+                "format and exchanging epsilon-r and epsilon-rf",
+                ir->epsilon_r);
         warning(wi, warn_buf);
         ir->epsilon_rf = ir->epsilon_r;
         ir->epsilon_r  = 1.0;
@@ -1050,8 +1288,10 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
     if (ir->epsilon_r == 0)
     {
         sprintf(err_buf,
-                "It is pointless to use long-range electrostatics with infinite relative permittivity."
-                "Since you are effectively turning of electrostatics, a plain cutoff will be much faster.");
+                "It is pointless to use long-range electrostatics with infinite relative "
+                "permittivity."
+                "Since you are effectively turning of electrostatics, a plain cutoff will be much "
+                "faster.");
         CHECK(EEL_FULL(ir->coulombtype));
     }
 
@@ -1065,21 +1305,22 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
     {
         /* reaction field (at the cut-off) */
 
-        if (ir->coulombtype == eelRF_ZERO && ir->epsilon_rf != 0)
+        if (ir->coulombtype == CoulombInteractionType::RFZero && ir->epsilon_rf != 0)
         {
-            sprintf(warn_buf, "With coulombtype = %s, epsilon-rf must be 0, assuming you meant epsilon_rf=0",
-                    eel_names[ir->coulombtype]);
+            sprintf(warn_buf,
+                    "With coulombtype = %s, epsilon-rf must be 0, assuming you meant epsilon_rf=0",
+                    enumValueToString(ir->coulombtype));
             warning(wi, warn_buf);
             ir->epsilon_rf = 0.0;
         }
 
         sprintf(err_buf, "epsilon-rf must be >= epsilon-r");
-        CHECK((ir->epsilon_rf < ir->epsilon_r && ir->epsilon_rf != 0) ||
-              (ir->epsilon_r == 0));
+        CHECK((ir->epsilon_rf < ir->epsilon_r && ir->epsilon_rf != 0) || (ir->epsilon_r == 0));
         if (ir->epsilon_rf == ir->epsilon_r)
         {
-            sprintf(warn_buf, "Using epsilon-rf = epsilon-r with %s does not make sense",
-                    eel_names[ir->coulombtype]);
+            sprintf(warn_buf,
+                    "Using epsilon-rf = epsilon-r with %s does not make sense",
+                    enumValueToString(ir->coulombtype));
             warning(wi, warn_buf);
         }
     }
@@ -1092,61 +1333,78 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
         if (ir_coulomb_switched(ir))
         {
             sprintf(err_buf,
-                    "With coulombtype = %s rcoulomb_switch must be < rcoulomb. Or, better: Use the potential modifier options!",
-                    eel_names[ir->coulombtype]);
+                    "With coulombtype = %s rcoulomb_switch must be < rcoulomb. Or, better: Use the "
+                    "potential modifier options!",
+                    enumValueToString(ir->coulombtype));
             CHECK(ir->rcoulomb_switch >= ir->rcoulomb);
         }
     }
 
-    if (ir->coulombtype == eelSWITCH || ir->coulombtype == eelSHIFT)
+    if (ir->coulombtype == CoulombInteractionType::Switch || ir->coulombtype == CoulombInteractionType::Shift)
     {
         sprintf(err_buf,
-                "Explicit switch/shift coulomb interactions cannot be used in combination with a secondary coulomb-modifier.");
-        CHECK( ir->coulomb_modifier != eintmodNONE);
+                "Explicit switch/shift coulomb interactions cannot be used in combination with a "
+                "secondary coulomb-modifier.");
+        CHECK(ir->coulomb_modifier != InteractionModifiers::None);
     }
-    if (ir->vdwtype == evdwSWITCH || ir->vdwtype == evdwSHIFT)
+    if (ir->vdwtype == VanDerWaalsType::Switch || ir->vdwtype == VanDerWaalsType::Shift)
     {
         sprintf(err_buf,
-                "Explicit switch/shift vdw interactions cannot be used in combination with a secondary vdw-modifier.");
-        CHECK( ir->vdw_modifier != eintmodNONE);
+                "Explicit switch/shift vdw interactions cannot be used in combination with a "
+                "secondary vdw-modifier.");
+        CHECK(ir->vdw_modifier != InteractionModifiers::None);
     }
 
-    if (ir->coulombtype == eelSWITCH || ir->coulombtype == eelSHIFT ||
-        ir->vdwtype == evdwSWITCH || ir->vdwtype == evdwSHIFT)
+    if (ir->coulombtype == CoulombInteractionType::Switch || ir->coulombtype == CoulombInteractionType::Shift
+        || ir->vdwtype == VanDerWaalsType::Switch || ir->vdwtype == VanDerWaalsType::Shift)
     {
         sprintf(warn_buf,
-                "The switch/shift interaction settings are just for compatibility; you will get better "
+                "The switch/shift interaction settings are just for compatibility; you will get "
+                "better "
                 "performance from applying potential modifiers to your interactions!\n");
         warning_note(wi, warn_buf);
     }
 
-    if (ir->coulombtype == eelPMESWITCH || ir->coulomb_modifier == eintmodPOTSWITCH)
+    if (ir->coulombtype == CoulombInteractionType::PmeSwitch
+        || ir->coulomb_modifier == InteractionModifiers::PotSwitch)
     {
-        if (ir->rcoulomb_switch/ir->rcoulomb < 0.9499)
+        if (ir->rcoulomb_switch / ir->rcoulomb < 0.9499)
         {
-            real percentage  = 100*(ir->rcoulomb-ir->rcoulomb_switch)/ir->rcoulomb;
-            sprintf(warn_buf, "The switching range should be 5%% or less (currently %.2f%% using a switching range of %4f-%4f) for accurate electrostatic energies, energy conservation will be good regardless, since ewald_rtol = %g.",
-                    percentage, ir->rcoulomb_switch, ir->rcoulomb, ir->ewald_rtol);
+            real percentage = 100 * (ir->rcoulomb - ir->rcoulomb_switch) / ir->rcoulomb;
+            sprintf(warn_buf,
+                    "The switching range should be 5%% or less (currently %.2f%% using a switching "
+                    "range of %4f-%4f) for accurate electrostatic energies, energy conservation "
+                    "will be good regardless, since ewald_rtol = %g.",
+                    percentage,
+                    ir->rcoulomb_switch,
+                    ir->rcoulomb,
+                    ir->ewald_rtol);
             warning(wi, warn_buf);
         }
     }
 
-    if (ir->vdwtype == evdwSWITCH || ir->vdw_modifier == eintmodPOTSWITCH)
+    if (ir->vdwtype == VanDerWaalsType::Switch || ir->vdw_modifier == InteractionModifiers::PotSwitch)
     {
         if (ir->rvdw_switch == 0)
         {
-            sprintf(warn_buf, "rvdw-switch is equal 0 even though you are using a switched Lennard-Jones potential.  This suggests it was not set in the mdp, which can lead to large energy errors.  In GROMACS, 0.05 to 0.1 nm is often a reasonable vdw switching range.");
+            sprintf(warn_buf,
+                    "rvdw-switch is equal 0 even though you are using a switched Lennard-Jones "
+                    "potential.  This suggests it was not set in the mdp, which can lead to large "
+                    "energy errors.  In GROMACS, 0.05 to 0.1 nm is often a reasonable vdw "
+                    "switching range.");
             warning(wi, warn_buf);
         }
     }
 
     if (EEL_FULL(ir->coulombtype))
     {
-        if (ir->coulombtype == eelPMESWITCH || ir->coulombtype == eelPMEUSER ||
-            ir->coulombtype == eelPMEUSERSWITCH)
+        if (ir->coulombtype == CoulombInteractionType::PmeSwitch
+            || ir->coulombtype == CoulombInteractionType::PmeUser
+            || ir->coulombtype == CoulombInteractionType::PmeUserSwitch)
         {
-            sprintf(err_buf, "With coulombtype = %s, rcoulomb must be <= rlist",
-                    eel_names[ir->coulombtype]);
+            sprintf(err_buf,
+                    "With coulombtype = %s, rcoulomb must be <= rlist",
+                    enumValueToString(ir->coulombtype));
             CHECK(ir->rcoulomb > ir->rlist);
         }
     }
@@ -1155,32 +1413,41 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
     {
         // TODO: Move these checks into the ewald module with the options class
         int orderMin = 3;
-        int orderMax = (ir->coulombtype == eelP3M_AD ? 8 : 12);
+        int orderMax = (ir->coulombtype == CoulombInteractionType::P3mAD ? 8 : 12);
 
         if (ir->pme_order < orderMin || ir->pme_order > orderMax)
         {
-            sprintf(warn_buf, "With coulombtype = %s, you should have %d <= pme-order <= %d", eel_names[ir->coulombtype], orderMin, orderMax);
+            sprintf(warn_buf,
+                    "With coulombtype = %s, you should have %d <= pme-order <= %d",
+                    enumValueToString(ir->coulombtype),
+                    orderMin,
+                    orderMax);
             warning_error(wi, warn_buf);
         }
     }
 
     if (ir->nwall == 2 && EEL_FULL(ir->coulombtype))
     {
-        if (ir->ewald_geometry == eewg3D)
+        if (ir->ewald_geometry == EwaldGeometry::ThreeD)
         {
-            sprintf(warn_buf, "With pbc=%s you should use ewald-geometry=%s",
-                    epbc_names[ir->ePBC], eewg_names[eewg3DC]);
+            sprintf(warn_buf,
+                    "With pbc=%s you should use ewald-geometry=%s",
+                    c_pbcTypeNames[ir->pbcType].c_str(),
+                    enumValueToString(EwaldGeometry::ThreeDC));
             warning(wi, warn_buf);
         }
         /* This check avoids extra pbc coding for exclusion corrections */
         sprintf(err_buf, "wall-ewald-zfac should be >= 2");
         CHECK(ir->wall_ewald_zfac < 2);
     }
-    if ((ir->ewald_geometry == eewg3DC) && (ir->ePBC != epbcXY) &&
-        EEL_FULL(ir->coulombtype))
+    if ((ir->ewald_geometry == EwaldGeometry::ThreeDC) && (ir->pbcType != PbcType::XY)
+        && EEL_FULL(ir->coulombtype))
     {
-        sprintf(warn_buf, "With %s and ewald_geometry = %s you should use pbc = %s",
-                eel_names[ir->coulombtype], eewg_names[eewg3DC], epbc_names[epbcXY]);
+        sprintf(warn_buf,
+                "With %s and ewald_geometry = %s you should use pbc = %s",
+                enumValueToString(ir->coulombtype),
+                enumValueToString(EwaldGeometry::ThreeDC),
+                c_pbcTypeNames[PbcType::XY].c_str());
         warning(wi, warn_buf);
     }
     if ((ir->epsilon_surface != 0) && EEL_FULL(ir->coulombtype))
@@ -1191,7 +1458,8 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
         warning_note(wi, warn_buf);
         sprintf(warn_buf,
                 "With epsilon_surface > 0 you can only use domain decomposition "
-                "when there are only small molecules with all bonds constrained (mdrun will check for this).");
+                "when there are only small molecules with all bonds constrained (mdrun will check "
+                "for this).");
         warning_note(wi, warn_buf);
     }
 
@@ -1200,65 +1468,75 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
         sprintf(err_buf, "With switched vdw forces or potentials, rvdw-switch must be < rvdw");
         CHECK(ir->rvdw_switch >= ir->rvdw);
 
-        if (ir->rvdw_switch < 0.5*ir->rvdw)
+        if (ir->rvdw_switch < 0.5 * ir->rvdw)
         {
-            sprintf(warn_buf, "You are applying a switch function to vdw forces or potentials from %g to %g nm, which is more than half the interaction range, whereas switch functions are intended to act only close to the cut-off.",
-                    ir->rvdw_switch, ir->rvdw);
+            sprintf(warn_buf,
+                    "You are applying a switch function to vdw forces or potentials from %g to %g "
+                    "nm, which is more than half the interaction range, whereas switch functions "
+                    "are intended to act only close to the cut-off.",
+                    ir->rvdw_switch,
+                    ir->rvdw);
             warning_note(wi, warn_buf);
         }
     }
 
-    if (ir->vdwtype == evdwPME)
+    if (ir->vdwtype == VanDerWaalsType::Pme)
     {
-        if (!(ir->vdw_modifier == eintmodNONE || ir->vdw_modifier == eintmodPOTSHIFT))
+        if (!(ir->vdw_modifier == InteractionModifiers::None
+              || ir->vdw_modifier == InteractionModifiers::PotShift))
         {
-            sprintf(err_buf, "With vdwtype = %s, the only supported modifiers are %s and %s",
-                    evdw_names[ir->vdwtype],
-                    eintmod_names[eintmodPOTSHIFT],
-                    eintmod_names[eintmodNONE]);
+            sprintf(err_buf,
+                    "With vdwtype = %s, the only supported modifiers are %s and %s",
+                    enumValueToString(ir->vdwtype),
+                    enumValueToString(InteractionModifiers::PotShift),
+                    enumValueToString(InteractionModifiers::None));
             warning_error(wi, err_buf);
         }
     }
 
-    if (ir->vdwtype == evdwUSER && ir->eDispCorr != edispcNO)
+    if (ir->vdwtype == VanDerWaalsType::User && ir->eDispCorr != DispersionCorrectionType::No)
     {
-        warning_note(wi, "You have selected user tables with dispersion correction, the dispersion will be corrected to -C6/r^6 beyond rvdw_switch (the tabulated interaction between rvdw_switch and rvdw will not be double counted). Make sure that you really want dispersion correction to -C6/r^6.");
+        warning_note(wi,
+                     "You have selected user tables with dispersion correction, the dispersion "
+                     "will be corrected to -C6/r^6 beyond rvdw_switch (the tabulated interaction "
+                     "between rvdw_switch and rvdw will not be double counted). Make sure that you "
+                     "really want dispersion correction to -C6/r^6.");
     }
 
-    if (ir->eI == eiLBFGS && (ir->coulombtype == eelCUT || ir->vdwtype == evdwCUT)
+    if (ir->eI == IntegrationAlgorithm::LBFGS
+        && (ir->coulombtype == CoulombInteractionType::Cut || ir->vdwtype == VanDerWaalsType::Cut)
         && ir->rvdw != 0)
     {
         warning(wi, "For efficient BFGS minimization, use switch/shift/pme instead of cut-off.");
     }
 
-    if (ir->eI == eiLBFGS && ir->nbfgscorr <= 0)
+    if (ir->eI == IntegrationAlgorithm::LBFGS && ir->nbfgscorr <= 0)
     {
         warning(wi, "Using L-BFGS with nbfgscorr<=0 just gets you steepest descent.");
     }
 
     /* IMPLICIT SOLVENT */
-    if (ir->coulombtype == eelGB_NOTUSED)
+    if (ir->coulombtype == CoulombInteractionType::GBNotused)
     {
-        sprintf(warn_buf, "Invalid option %s for coulombtype",
-                eel_names[ir->coulombtype]);
+        sprintf(warn_buf, "Invalid option %s for coulombtype", enumValueToString(ir->coulombtype));
         warning_error(wi, warn_buf);
     }
 
     if (ir->bQMMM)
     {
-        warning_error(wi, "QMMM is currently not supported");
-        if (!EI_DYNAMICS(ir->eI))
-        {
-            char buf[STRLEN];
-            sprintf(buf, "QMMM is only supported with dynamics, not with integrator %s", ei_names[ir->eI]);
-            warning_error(wi, buf);
-        }
+        warning_error(wi, "The QMMM integration you are trying to use is no longer supported");
     }
 
     if (ir->bAdress)
     {
         gmx_fatal(FARGS, "AdResS simulations are no longer supported");
     }
+
+    // cosine acceleration is only supported in leap-frog
+    if (ir->cos_accel != 0.0 && ir->eI != IntegrationAlgorithm::MD)
+    {
+        warning_error(wi, "cos-acceleration is only supported by integrator = md");
+    }
 }
 
 /* interpret a number of doubles from a string and put them in an array,
@@ -1266,60 +1544,62 @@ void check_ir(const char *mdparin, const gmx::MdModulesNotifier &mdModulesNotifi
    str = the input string
    n = the (pre-allocated) number of doubles read
    r = the output array of doubles. */
-static void parse_n_real(char *str, int *n, real **r, warninp_t wi)
+static std::vector<real> parse_n_real(const std::string& str, int* n, warninp_t wi)
 {
     auto values = gmx::splitString(str);
-    *n = values.size();
+    *n          = values.size();
 
-    snew(*r, *n);
+    std::vector<real> r;
     for (int i = 0; i < *n; i++)
     {
         try
         {
-            (*r)[i] = gmx::fromString<real>(values[i]);
+            r.emplace_back(gmx::fromString<real>(values[i]));
         }
-        catch (gmx::GromacsException &)
+        catch (gmx::GromacsException&)
         {
-            warning_error(wi, "Invalid value " + values[i] + " in string in mdp file. Expected a real number.");
+            warning_error(wi,
+                          "Invalid value " + values[i]
+                                  + " in string in mdp file. Expected a real number.");
         }
     }
+    return r;
 }
 
 
-static void do_fep_params(t_inputrec *ir, char fep_lambda[][STRLEN], char weights[STRLEN], warninp_t wi)
+static void do_fep_params(t_inputrec* ir, gmx::ArrayRef<std::string> fep_lambda, char weights[STRLEN], warninp_t wi)
 {
 
-    int         i, j, max_n_lambda, nweights, nfep[efptNR];
-    t_lambda   *fep    = ir->fepvals;
-    t_expanded *expand = ir->expandedvals;
-    real      **count_fep_lambdas;
-    bool        bOneLambda = TRUE;
-
-    snew(count_fep_lambdas, efptNR);
+    int         i, j, max_n_lambda, nweights;
+    t_lambda*   fep    = ir->fepvals.get();
+    t_expanded* expand = ir->expandedvals.get();
+    gmx::EnumerationArray<FreeEnergyPerturbationCouplingType, std::vector<real>> count_fep_lambdas;
+    bool                                                                         bOneLambda = TRUE;
+    gmx::EnumerationArray<FreeEnergyPerturbationCouplingType, int>               nfep;
 
     /* FEP input processing */
     /* first, identify the number of lambda values for each type.
        All that are nonzero must have the same number */
 
-    for (i = 0; i < efptNR; i++)
+    for (auto i : keysOf(nfep))
     {
-        parse_n_real(fep_lambda[i], &(nfep[i]), &(count_fep_lambdas[i]), wi);
+        count_fep_lambdas[i] = parse_n_real(fep_lambda[static_cast<int>(i)], &(nfep[i]), wi);
     }
 
     /* now, determine the number of components.  All must be either zero, or equal. */
 
     max_n_lambda = 0;
-    for (i = 0; i < efptNR; i++)
+    for (auto i : keysOf(nfep))
     {
         if (nfep[i] > max_n_lambda)
         {
-            max_n_lambda = nfep[i];  /* here's a nonzero one.  All of them
-                                        must have the same number if its not zero.*/
+            max_n_lambda = nfep[i]; /* here's a nonzero one.  All of them
+                                       must have the same number if its not zero.*/
             break;
         }
     }
 
-    for (i = 0; i < efptNR; i++)
+    for (auto i : keysOf(nfep))
     {
         if (nfep[i] == 0)
         {
@@ -1327,56 +1607,56 @@ static void do_fep_params(t_inputrec *ir, char fep_lambda[][STRLEN], char weight
         }
         else if (nfep[i] == max_n_lambda)
         {
-            if (i != efptTEMPERATURE)  /* we treat this differently -- not really a reason to compute the derivative with
-                                          respect to the temperature currently */
+            if (i != FreeEnergyPerturbationCouplingType::Temperature) /* we treat this differently -- not really a reason to compute
+                                         the derivative with respect to the temperature currently */
             {
                 ir->fepvals->separate_dvdl[i] = TRUE;
             }
         }
         else
         {
-            gmx_fatal(FARGS, "Number of lambdas (%d) for FEP type %s not equal to number of other types (%d)",
-                      nfep[i], efpt_names[i], max_n_lambda);
+            gmx_fatal(FARGS,
+                      "Number of lambdas (%d) for FEP type %s not equal to number of other types "
+                      "(%d)",
+                      nfep[i],
+                      enumValueToString(i),
+                      max_n_lambda);
         }
     }
     /* we don't print out dhdl if the temperature is changing, since we can't correctly define dhdl in this case */
-    ir->fepvals->separate_dvdl[efptTEMPERATURE] = FALSE;
+    ir->fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Temperature] = FALSE;
 
     /* the number of lambdas is the number we've read in, which is either zero
        or the same for all */
     fep->n_lambda = max_n_lambda;
 
-    /* allocate space for the array of lambda values */
-    snew(fep->all_lambda, efptNR);
     /* if init_lambda is defined, we need to set lambda */
     if ((fep->init_lambda > 0) && (fep->n_lambda == 0))
     {
-        ir->fepvals->separate_dvdl[efptFEP] = TRUE;
+        ir->fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Fep] = TRUE;
     }
     /* otherwise allocate the space for all of the lambdas, and transfer the data */
-    for (i = 0; i < efptNR; i++)
+    for (auto i : keysOf(nfep))
     {
-        snew(fep->all_lambda[i], fep->n_lambda);
-        if (nfep[i] > 0)  /* if it's zero, then the count_fep_lambda arrays
-                             are zero */
+        fep->all_lambda[i].resize(fep->n_lambda);
+        if (nfep[i] > 0) /* if it's zero, then the count_fep_lambda arrays
+                            are zero */
         {
             for (j = 0; j < fep->n_lambda; j++)
             {
                 fep->all_lambda[i][j] = static_cast<double>(count_fep_lambdas[i][j]);
             }
-            sfree(count_fep_lambdas[i]);
         }
     }
-    sfree(count_fep_lambdas);
 
-    /* "fep-vals" is either zero or the full number. If zero, we'll need to define fep-lambdas for internal
-       bookkeeping -- for now, init_lambda */
+    /* "fep-vals" is either zero or the full number. If zero, we'll need to define fep-lambdas for
+       internal bookkeeping -- for now, init_lambda */
 
-    if ((nfep[efptFEP] == 0) && (fep->init_lambda >= 0))
+    if ((nfep[FreeEnergyPerturbationCouplingType::Fep] == 0) && (fep->init_lambda >= 0))
     {
         for (i = 0; i < fep->n_lambda; i++)
         {
-            fep->all_lambda[efptFEP][i] = fep->init_lambda;
+            fep->all_lambda[FreeEnergyPerturbationCouplingType::Fep][i] = fep->init_lambda;
         }
     }
 
@@ -1389,9 +1669,9 @@ static void do_fep_params(t_inputrec *ir, char fep_lambda[][STRLEN], char weight
     }
     else
     {
-        for (i = 0; i < efptNR; i++)
+        for (auto i : keysOf(nfep))
         {
-            if ((nfep[i] != 0) && (i != efptFEP))
+            if ((nfep[i] != 0) && (i != FreeEnergyPerturbationCouplingType::Fep))
             {
                 bOneLambda = FALSE;
             }
@@ -1406,39 +1686,32 @@ static void do_fep_params(t_inputrec *ir, char fep_lambda[][STRLEN], char weight
        specified (i.e. nfep[i] == 0).  This means if fep is not defined,
        they are all zero. */
 
-    for (i = 0; i < efptNR; i++)
+    for (auto i : keysOf(nfep))
     {
-        if ((nfep[i] == 0) && (i != efptFEP))
+        if ((nfep[i] == 0) && (i != FreeEnergyPerturbationCouplingType::Fep))
         {
             for (j = 0; j < fep->n_lambda; j++)
             {
-                fep->all_lambda[i][j] = fep->all_lambda[efptFEP][j];
+                fep->all_lambda[i][j] = fep->all_lambda[FreeEnergyPerturbationCouplingType::Fep][j];
             }
         }
     }
 
 
-    /* make it easier if sc_r_power = 48 by increasing it to the 4th power, to be in the right scale. */
-    if (fep->sc_r_power == 48)
-    {
-        if (fep->sc_alpha > 0.1)
-        {
-            gmx_fatal(FARGS, "sc_alpha (%f) for sc_r_power = 48 should usually be between 0.001 and 0.004", fep->sc_alpha);
-        }
-    }
-
     /* now read in the weights */
-    parse_n_real(weights, &nweights, &(expand->init_lambda_weights), wi);
+    expand->init_lambda_weights = parse_n_real(weights, &nweights, wi);
     if (nweights == 0)
     {
-        snew(expand->init_lambda_weights, fep->n_lambda); /* initialize to zero */
+        expand->init_lambda_weights.resize(fep->n_lambda); /* initialize to zero */
     }
     else if (nweights != fep->n_lambda)
     {
-        gmx_fatal(FARGS, "Number of weights (%d) is not equal to number of lambda values (%d)",
-                  nweights, fep->n_lambda);
+        gmx_fatal(FARGS,
+                  "Number of weights (%d) is not equal to number of lambda values (%d)",
+                  nweights,
+                  fep->n_lambda);
     }
-    if ((expand->nstexpanded < 0) && (ir->efep != efepNO))
+    if ((expand->nstexpanded < 0) && (ir->efep != FreeEnergyPerturbationType::No))
     {
         expand->nstexpanded = fep->nstdhdl;
         /* if you don't specify nstexpanded when doing expanded ensemble free energy calcs, it is set to nstdhdl */
@@ -1446,78 +1719,75 @@ static void do_fep_params(t_inputrec *ir, char fep_lambda[][STRLEN], char weight
 }
 
 
-static void do_simtemp_params(t_inputrec *ir)
+static void do_simtemp_params(t_inputrecir)
 {
-
-    snew(ir->simtempvals->temperatures, ir->fepvals->n_lambda);
-    GetSimTemps(ir->fepvals->n_lambda, ir->simtempvals, ir->fepvals->all_lambda[efptTEMPERATURE]);
-}
-
-static void
-convertYesNos(warninp_t /*wi*/, gmx::ArrayRef<const std::string> inputs, const char * /*name*/, gmx_bool *outputs)
-{
-    int i = 0;
-    for (const auto &input : inputs)
-    {
-        outputs[i] = gmx::equalCaseInsensitive(input, "Y", 1);
-        ++i;
-    }
+    ir->simtempvals->temperatures.resize(ir->fepvals->n_lambda);
+    getSimTemps(ir->fepvals->n_lambda,
+                ir->simtempvals.get(),
+                ir->fepvals->all_lambda[FreeEnergyPerturbationCouplingType::Temperature]);
 }
 
-template <typename T> void
-convertInts(warninp_t wi, gmx::ArrayRef<const std::string> inputs, const char *name, T *outputs)
+template<typename T>
+void convertInts(warninp_t wi, gmx::ArrayRef<const std::string> inputs, const char* name, T* outputs)
 {
     int i = 0;
-    for (const auto &input : inputs)
+    for (const autoinput : inputs)
     {
         try
         {
             outputs[i] = gmx::fromStdString<T>(input);
         }
-        catch (gmx::GromacsException &)
+        catch (gmx::GromacsException&)
         {
-            auto message = gmx::formatString("Invalid value for mdp option %s. %s should only consist of integers separated by spaces.",
-                                             name, name);
+            auto message = gmx::formatString(
+                    "Invalid value for mdp option %s. %s should only consist of integers separated "
+                    "by spaces.",
+                    name,
+                    name);
             warning_error(wi, message);
         }
         ++i;
     }
 }
 
-static void
-convertReals(warninp_t wi, gmx::ArrayRef<const std::string> inputs, const char *name, real *outputs)
+static void convertReals(warninp_t wi, gmx::ArrayRef<const std::string> inputs, const char* name, real* outputs)
 {
     int i = 0;
-    for (const auto &input : inputs)
+    for (const autoinput : inputs)
     {
         try
         {
             outputs[i] = gmx::fromString<real>(input);
         }
-        catch (gmx::GromacsException &)
+        catch (gmx::GromacsException&)
         {
-            auto message = gmx::formatString("Invalid value for mdp option %s. %s should only consist of real numbers separated by spaces.",
-                                             name, name);
+            auto message = gmx::formatString(
+                    "Invalid value for mdp option %s. %s should only consist of real numbers "
+                    "separated by spaces.",
+                    name,
+                    name);
             warning_error(wi, message);
         }
         ++i;
     }
 }
 
-static void
-convertRvecs(warninp_t wi, gmx::ArrayRef<const std::string> inputs, const char *name, rvec *outputs)
+static void convertRvecs(warninp_t wi, gmx::ArrayRef<const std::string> inputs, const char* name, rvec* outputs)
 {
     int i = 0, d = 0;
-    for (const auto &input : inputs)
+    for (const autoinput : inputs)
     {
         try
         {
             outputs[i][d] = gmx::fromString<real>(input);
         }
-        catch (gmx::GromacsException &)
+        catch (gmx::GromacsException&)
         {
-            auto message = gmx::formatString("Invalid value for mdp option %s. %s should only consist of real numbers separated by spaces.",
-                                             name, name);
+            auto message = gmx::formatString(
+                    "Invalid value for mdp option %s. %s should only consist of real numbers "
+                    "separated by spaces.",
+                    name,
+                    name);
             warning_error(wi, message);
         }
         ++d;
@@ -1529,10 +1799,7 @@ convertRvecs(warninp_t wi, gmx::ArrayRef<const std::string> inputs, const char *
     }
 }
 
-static void do_wall_params(t_inputrec *ir,
-                           char *wall_atomtype, char *wall_density,
-                           t_gromppopts *opts,
-                           warninp_t wi)
+static void do_wall_params(t_inputrec* ir, char* wall_atomtype, char* wall_density, t_gromppopts* opts, warninp_t wi)
 {
     opts->wall_atomtype[0] = nullptr;
     opts->wall_atomtype[1] = nullptr;
@@ -1547,20 +1814,26 @@ static void do_wall_params(t_inputrec *ir,
         auto wallAtomTypes = gmx::splitString(wall_atomtype);
         if (wallAtomTypes.size() != size_t(ir->nwall))
         {
-            gmx_fatal(FARGS, "Expected %d elements for wall_atomtype, found %zu",
-                      ir->nwall, wallAtomTypes.size());
+            gmx_fatal(FARGS,
+                      "Expected %d elements for wall_atomtype, found %zu",
+                      ir->nwall,
+                      wallAtomTypes.size());
         }
+        GMX_RELEASE_ASSERT(ir->nwall < 3, "Invalid number of walls");
         for (int i = 0; i < ir->nwall; i++)
         {
             opts->wall_atomtype[i] = gmx_strdup(wallAtomTypes[i].c_str());
         }
 
-        if (ir->wall_type == ewt93 || ir->wall_type == ewt104)
+        if (ir->wall_type == WallType::NineThree || ir->wall_type == WallType::TenFour)
         {
             auto wallDensity = gmx::splitString(wall_density);
             if (wallDensity.size() != size_t(ir->nwall))
             {
-                gmx_fatal(FARGS, "Expected %d elements for wall-density, found %zu", ir->nwall, wallDensity.size());
+                gmx_fatal(FARGS,
+                          "Expected %d elements for wall-density, found %zu",
+                          ir->nwall,
+                          wallDensity.size());
             }
             convertReals(wi, wallDensity, "wall-density", ir->wall_density);
             for (int i = 0; i < ir->nwall; i++)
@@ -1574,50 +1847,47 @@ static void do_wall_params(t_inputrec *ir,
     }
 }
 
-static void add_wall_energrps(SimulationGroups *groups, int nwall, t_symtab *symtab)
+static void add_wall_energrps(SimulationGroups* groups, int nwall, t_symtab* symtab)
 {
     if (nwall > 0)
     {
-        AtomGroupIndices *grps = &(groups->groups[SimulationAtomGroupType::EnergyOutput]);
+        AtomGroupIndicesgrps = &(groups->groups[SimulationAtomGroupType::EnergyOutput]);
         for (int i = 0; i < nwall; i++)
         {
-            groups->groupNames.emplace_back(
-                    put_symtab(
-                            symtab,
-                            gmx::formatString("wall%d", i).c_str()));
+            groups->groupNames.emplace_back(put_symtab(symtab, gmx::formatString("wall%d", i).c_str()));
             grps->emplace_back(groups->groupNames.size() - 1);
         }
     }
 }
 
-static void read_expandedparams(std::vector<t_inpfile> *inp,
-                                t_expanded *expand, warninp_t wi)
+static void read_expandedparams(std::vector<t_inpfile>* inp, t_expanded* expand, warninp_t wi)
 {
     /* read expanded ensemble parameters */
     printStringNewline(inp, "expanded ensemble variables");
-    expand->nstexpanded    = get_eint(inp, "nstexpanded", -1, wi);
-    expand->elamstats      = get_eeenum(inp, "lmc-stats", elamstats_names, wi);
-    expand->elmcmove       = get_eeenum(inp, "lmc-move", elmcmove_names, wi);
-    expand->elmceq         = get_eeenum(inp, "lmc-weights-equil", elmceq_names, wi);
+    expand->nstexpanded = get_eint(inp, "nstexpanded", -1, wi);
+    expand->elamstats   = getEnum<LambdaWeightCalculation>(inp, "lmc-stats", wi);
+    expand->elmcmove    = getEnum<LambdaMoveCalculation>(inp, "lmc-move", wi);
+    expand->elmceq      = getEnum<LambdaWeightWillReachEquilibrium>(inp, "lmc-weights-equil", wi);
     expand->equil_n_at_lam = get_eint(inp, "weight-equil-number-all-lambda", -1, wi);
     expand->equil_samples  = get_eint(inp, "weight-equil-number-samples", -1, wi);
     expand->equil_steps    = get_eint(inp, "weight-equil-number-steps", -1, wi);
     expand->equil_wl_delta = get_ereal(inp, "weight-equil-wl-delta", -1, wi);
     expand->equil_ratio    = get_ereal(inp, "weight-equil-count-ratio", -1, wi);
     printStringNewline(inp, "Seed for Monte Carlo in lambda space");
-    expand->lmc_seed            = get_eint(inp, "lmc-seed", -1, wi);
-    expand->mc_temp             = get_ereal(inp, "mc-temperature", -1, wi);
-    expand->lmc_repeats         = get_eint(inp, "lmc-repeats", 1, wi);
-    expand->gibbsdeltalam       = get_eint(inp, "lmc-gibbsdelta", -1, wi);
-    expand->lmc_forced_nstart   = get_eint(inp, "lmc-forced-nstart", 0, wi);
-    expand->bSymmetrizedTMatrix = (get_eeenum(inp, "symmetrized-transition-matrix", yesno_names, wi) != 0);
-    expand->nstTij              = get_eint(inp, "nst-transition-matrix", -1, wi);
-    expand->minvarmin           = get_eint(inp, "mininum-var-min", 100, wi); /*default is reasonable */
-    expand->c_range             = get_eint(inp, "weight-c-range", 0, wi);    /* default is just C=0 */
-    expand->wl_scale            = get_ereal(inp, "wl-scale", 0.8, wi);
-    expand->wl_ratio            = get_ereal(inp, "wl-ratio", 0.8, wi);
-    expand->init_wl_delta       = get_ereal(inp, "init-wl-delta", 1.0, wi);
-    expand->bWLoneovert         = (get_eeenum(inp, "wl-oneovert", yesno_names, wi) != 0);
+    expand->lmc_seed          = get_eint(inp, "lmc-seed", -1, wi);
+    expand->mc_temp           = get_ereal(inp, "mc-temperature", -1, wi);
+    expand->lmc_repeats       = get_eint(inp, "lmc-repeats", 1, wi);
+    expand->gibbsdeltalam     = get_eint(inp, "lmc-gibbsdelta", -1, wi);
+    expand->lmc_forced_nstart = get_eint(inp, "lmc-forced-nstart", 0, wi);
+    expand->bSymmetrizedTMatrix =
+            (getEnum<Boolean>(inp, "symmetrized-transition-matrix", wi) != Boolean::No);
+    expand->nstTij        = get_eint(inp, "nst-transition-matrix", -1, wi);
+    expand->minvarmin     = get_eint(inp, "mininum-var-min", 100, wi); /*default is reasonable */
+    expand->c_range       = get_eint(inp, "weight-c-range", 0, wi);    /* default is just C=0 */
+    expand->wl_scale      = get_ereal(inp, "wl-scale", 0.8, wi);
+    expand->wl_ratio      = get_ereal(inp, "wl-ratio", 0.8, wi);
+    expand->init_wl_delta = get_ereal(inp, "init-wl-delta", 1.0, wi);
+    expand->bWLoneovert   = (getEnum<Boolean>(inp, "wl-oneovert", wi) != Boolean::No);
 }
 
 /*! \brief Return whether an end state with the given coupling-lambda
@@ -1628,8 +1898,7 @@ static void read_expandedparams(std::vector<t_inpfile> *inp,
  */
 static bool couple_lambda_has_vdw_on(int couple_lambda_value)
 {
-    return (couple_lambda_value == ecouplamVDW ||
-            couple_lambda_value == ecouplamVDWQ);
+    return (couple_lambda_value == ecouplamVDW || couple_lambda_value == ecouplamVDWQ);
 }
 
 namespace
@@ -1637,57 +1906,55 @@ namespace
 
 class MdpErrorHandler : public gmx::IKeyValueTreeErrorHandler
 {
-    public:
-        explicit MdpErrorHandler(warninp_t wi)
-            : wi_(wi), mapping_(nullptr)
-        {
-        }
+public:
+    explicit MdpErrorHandler(warninp_t wi) : wi_(wi), mapping_(nullptr) {}
 
-        void setBackMapping(const gmx::IKeyValueTreeBackMapping &mapping)
-        {
-            mapping_ = &mapping;
-        }
+    void setBackMapping(const gmx::IKeyValueTreeBackMapping& mapping) { mapping_ = &mapping; }
 
-        bool onError(gmx::UserInputError *ex, const gmx::KeyValueTreePath &context) override
-        {
-            ex->prependContext(gmx::formatString("Error in mdp option \"%s\":",
-                                                 getOptionName(context).c_str()));
-            std::string message = gmx::formatExceptionMessageToString(*ex);
-            warning_error(wi_, message.c_str());
-            return true;
-        }
+    bool onError(gmx::UserInputError* ex, const gmx::KeyValueTreePath& context) override
+    {
+        ex->prependContext(
+                gmx::formatString("Error in mdp option \"%s\":", getOptionName(context).c_str()));
+        std::string message = gmx::formatExceptionMessageToString(*ex);
+        warning_error(wi_, message.c_str());
+        return true;
+    }
 
-    private:
-        std::string getOptionName(const gmx::KeyValueTreePath &context)
+private:
+    std::string getOptionName(const gmx::KeyValueTreePath& context)
+    {
+        if (mapping_ != nullptr)
         {
-            if (mapping_ != nullptr)
-            {
-                gmx::KeyValueTreePath path = mapping_->originalPath(context);
-                GMX_ASSERT(path.size() == 1, "Inconsistent mapping back to mdp options");
-                return path[0];
-            }
-            GMX_ASSERT(context.size() == 1, "Inconsistent context for mdp option parsing");
-            return context[0];
+            gmx::KeyValueTreePath path = mapping_->originalPath(context);
+            GMX_ASSERT(path.size() == 1, "Inconsistent mapping back to mdp options");
+            return path[0];
         }
+        GMX_ASSERT(context.size() == 1, "Inconsistent context for mdp option parsing");
+        return context[0];
+    }
 
-        warninp_t                            wi_;
-        const gmx::IKeyValueTreeBackMapping *mapping_;
+    warninp_t                            wi_;
+    const gmx::IKeyValueTreeBackMapping* mapping_;
 };
 
 } // namespace
 
-void get_ir(const char *mdparin, const char *mdparout,
-            gmx::MDModules *mdModules, t_inputrec *ir, t_gromppopts *opts,
-            WriteMdpHeader writeMdpHeader, warninp_t wi)
+void get_ir(const char*     mdparin,
+            const char*     mdparout,
+            gmx::MDModules* mdModules,
+            t_inputrec*     ir,
+            t_gromppopts*   opts,
+            WriteMdpHeader  writeMdpHeader,
+            warninp_t       wi)
 {
-    char                  *dumstr[2];
-    double                 dumdub[2][6];
-    int                    i, j, m;
-    char                   warn_buf[STRLEN];
-    t_lambda              *fep    = ir->fepvals;
-    t_expanded            *expand = ir->expandedvals;
+    char*       dumstr[2];
+    double      dumdub[2][6];
+    int         i, j, m;
+    char        warn_buf[STRLEN];
+    t_lambda*   fep    = ir->fepvals.get();
+    t_expanded* expand = ir->expandedvals.get();
 
-    const char            *no_names[] = { "no", nullptr };
+    const charno_names[] = { "no", nullptr };
 
     init_inputrec_strings();
     gmx::TextInputFile     stream(mdparin);
@@ -1746,119 +2013,143 @@ void get_ir(const char *mdparin, const char *mdparout,
     printStringNewline(&inp, "VARIOUS PREPROCESSING OPTIONS");
     printStringNoNewline(&inp, "Preprocessor information: use cpp syntax.");
     printStringNoNewline(&inp, "e.g.: -I/home/joe/doe -I/home/mary/roe");
-    setStringEntry(&inp, "include", opts->include,  nullptr);
-    printStringNoNewline(&inp, "e.g.: -DPOSRES -DFLEXIBLE (note these variable names are case sensitive)");
-    setStringEntry(&inp, "define",  opts->define,   nullptr);
+    setStringEntry(&inp, "include", opts->include, nullptr);
+    printStringNoNewline(
+            &inp, "e.g.: -DPOSRES -DFLEXIBLE (note these variable names are case sensitive)");
+    setStringEntry(&inp, "define", opts->define, nullptr);
 
     printStringNewline(&inp, "RUN CONTROL PARAMETERS");
-    ir->eI = get_eeenum(&inp, "integrator",         ei_names, wi);
+    ir->eI = getEnum<IntegrationAlgorithm>(&inp, "integrator", wi);
     printStringNoNewline(&inp, "Start time and timestep in ps");
     ir->init_t  = get_ereal(&inp, "tinit", 0.0, wi);
-    ir->delta_t = get_ereal(&inp, "dt",    0.001, wi);
-    ir->nsteps  = get_eint64(&inp, "nsteps",     0, wi);
+    ir->delta_t = get_ereal(&inp, "dt", 0.001, wi);
+    ir->nsteps  = get_eint64(&inp, "nsteps", 0, wi);
     printStringNoNewline(&inp, "For exact run continuation or redoing part of a run");
-    ir->init_step = get_eint64(&inp, "init-step",  0, wi);
-    printStringNoNewline(&inp, "Part index is updated automatically on checkpointing (keeps files separate)");
+    ir->init_step = get_eint64(&inp, "init-step", 0, wi);
+    printStringNoNewline(
+            &inp, "Part index is updated automatically on checkpointing (keeps files separate)");
     ir->simulation_part = get_eint(&inp, "simulation-part", 1, wi);
+    printStringNoNewline(&inp, "Multiple time-stepping");
+    ir->useMts = (getEnum<Boolean>(&inp, "mts", wi) != Boolean::No);
+    if (ir->useMts)
+    {
+        gmx::GromppMtsOpts& mtsOpts = opts->mtsOpts;
+        mtsOpts.numLevels           = get_eint(&inp, "mts-levels", 2, wi);
+        mtsOpts.level2Forces = setStringEntry(&inp, "mts-level2-forces", "longrange-nonbonded");
+        mtsOpts.level2Factor = get_eint(&inp, "mts-level2-factor", 2, wi);
+
+        // We clear after reading without dynamics to not force the user to remove MTS mdp options
+        if (!EI_DYNAMICS(ir->eI))
+        {
+            ir->useMts = false;
+        }
+    }
     printStringNoNewline(&inp, "mode for center of mass motion removal");
-    ir->comm_mode = get_eeenum(&inp, "comm-mode",  ecm_names, wi);
+    ir->comm_mode = getEnum<ComRemovalAlgorithm>(&inp, "comm-mode", wi);
     printStringNoNewline(&inp, "number of steps for center of mass motion removal");
-    ir->nstcomm = get_eint(&inp, "nstcomm",    100, wi);
+    ir->nstcomm = get_eint(&inp, "nstcomm", 100, wi);
     printStringNoNewline(&inp, "group(s) for center of mass motion removal");
-    setStringEntry(&inp, "comm-grps",   is->vcm,            nullptr);
+    setStringEntry(&inp, "comm-grps", inputrecStrings->vcm, nullptr);
 
     printStringNewline(&inp, "LANGEVIN DYNAMICS OPTIONS");
     printStringNoNewline(&inp, "Friction coefficient (amu/ps) and random seed");
-    ir->bd_fric = get_ereal(&inp, "bd-fric",    0.0, wi);
-    ir->ld_seed = get_eint64(&inp, "ld-seed",    -1, wi);
+    ir->bd_fric = get_ereal(&inp, "bd-fric", 0.0, wi);
+    ir->ld_seed = get_eint64(&inp, "ld-seed", -1, wi);
 
     /* Em stuff */
     printStringNewline(&inp, "ENERGY MINIMIZATION OPTIONS");
     printStringNoNewline(&inp, "Force tolerance and initial step-size");
-    ir->em_tol      = get_ereal(&inp, "emtol",     10.0, wi);
+    ir->em_tol      = get_ereal(&inp, "emtol", 10.0, wi);
     ir->em_stepsize = get_ereal(&inp, "emstep", 0.01, wi);
     printStringNoNewline(&inp, "Max number of iterations in relax-shells");
-    ir->niter = get_eint(&inp, "niter",      20, wi);
+    ir->niter = get_eint(&inp, "niter", 20, wi);
     printStringNoNewline(&inp, "Step size (ps^2) for minimization of flexible constraints");
     ir->fc_stepsize = get_ereal(&inp, "fcstep", 0, wi);
     printStringNoNewline(&inp, "Frequency of steepest descents steps when doing CG");
     ir->nstcgsteep = get_eint(&inp, "nstcgsteep", 1000, wi);
-    ir->nbfgscorr  = get_eint(&inp, "nbfgscorr",  10, wi);
+    ir->nbfgscorr  = get_eint(&inp, "nbfgscorr", 10, wi);
 
     printStringNewline(&inp, "TEST PARTICLE INSERTION OPTIONS");
-    ir->rtpi = get_ereal(&inp, "rtpi",   0.05, wi);
+    ir->rtpi = get_ereal(&inp, "rtpi", 0.05, wi);
 
     /* Output options */
     printStringNewline(&inp, "OUTPUT CONTROL OPTIONS");
     printStringNoNewline(&inp, "Output frequency for coords (x), velocities (v) and forces (f)");
-    ir->nstxout = get_eint(&inp, "nstxout",    0, wi);
-    ir->nstvout = get_eint(&inp, "nstvout",    0, wi);
-    ir->nstfout = get_eint(&inp, "nstfout",    0, wi);
+    ir->nstxout = get_eint(&inp, "nstxout", 0, wi);
+    ir->nstvout = get_eint(&inp, "nstvout", 0, wi);
+    ir->nstfout = get_eint(&inp, "nstfout", 0, wi);
     printStringNoNewline(&inp, "Output frequency for energies to log file and energy file");
     ir->nstlog        = get_eint(&inp, "nstlog", 1000, wi);
     ir->nstcalcenergy = get_eint(&inp, "nstcalcenergy", 100, wi);
-    ir->nstenergy     = get_eint(&inp, "nstenergy",  1000, wi);
+    ir->nstenergy     = get_eint(&inp, "nstenergy", 1000, wi);
     printStringNoNewline(&inp, "Output frequency and precision for .xtc file");
-    ir->nstxout_compressed      = get_eint(&inp, "nstxout-compressed",  0, wi);
+    ir->nstxout_compressed      = get_eint(&inp, "nstxout-compressed", 0, wi);
     ir->x_compression_precision = get_ereal(&inp, "compressed-x-precision", 1000.0, wi);
     printStringNoNewline(&inp, "This selects the subset of atoms for the compressed");
     printStringNoNewline(&inp, "trajectory file. You can select multiple groups. By");
     printStringNoNewline(&inp, "default, all atoms will be written.");
-    setStringEntry(&inp, "compressed-x-grps", is->x_compressed_groups, nullptr);
+    setStringEntry(&inp, "compressed-x-grps", inputrecStrings->x_compressed_groups, nullptr);
     printStringNoNewline(&inp, "Selection of energy groups");
-    setStringEntry(&inp, "energygrps",  is->energy,         nullptr);
+    setStringEntry(&inp, "energygrps", inputrecStrings->energy, nullptr);
 
     /* Neighbor searching */
     printStringNewline(&inp, "NEIGHBORSEARCHING PARAMETERS");
-    printStringNoNewline(&inp, "cut-off scheme (Verlet: particle based cut-offs, group: using charge groups)");
-    ir->cutoff_scheme = get_eeenum(&inp, "cutoff-scheme",    ecutscheme_names, wi);
+    printStringNoNewline(&inp, "cut-off scheme (Verlet: particle based cut-offs)");
+    ir->cutoff_scheme = getEnum<CutoffScheme>(&inp, "cutoff-scheme", wi);
     printStringNoNewline(&inp, "nblist update frequency");
-    ir->nstlist = get_eint(&inp, "nstlist",    10, wi);
+    ir->nstlist = get_eint(&inp, "nstlist", 10, wi);
     printStringNoNewline(&inp, "Periodic boundary conditions: xyz, no, xy");
-    ir->ePBC          = get_eeenum(&inp, "pbc",       epbc_names, wi);
-    ir->bPeriodicMols = get_eeenum(&inp, "periodic-molecules", yesno_names, wi) != 0;
-    printStringNoNewline(&inp, "Allowed energy error due to the Verlet buffer in kJ/mol/ps per atom,");
+    // TODO This conversion should be removed when proper std:string handling will be added to get_eeenum(...), etc.
+    std::vector<const char*> pbcTypesNamesChar;
+    for (const auto& pbcTypeName : c_pbcTypeNames)
+    {
+        pbcTypesNamesChar.push_back(pbcTypeName.c_str());
+    }
+    ir->pbcType       = static_cast<PbcType>(get_eeenum(&inp, "pbc", pbcTypesNamesChar.data(), wi));
+    ir->bPeriodicMols = getEnum<Boolean>(&inp, "periodic-molecules", wi) != Boolean::No;
+    printStringNoNewline(&inp,
+                         "Allowed energy error due to the Verlet buffer in kJ/mol/ps per atom,");
     printStringNoNewline(&inp, "a value of -1 means: use rlist");
-    ir->verletbuf_tol = get_ereal(&inp, "verlet-buffer-tolerance",    0.005, wi);
+    ir->verletbuf_tol = get_ereal(&inp, "verlet-buffer-tolerance", 0.005, wi);
     printStringNoNewline(&inp, "nblist cut-off");
-    ir->rlist = get_ereal(&inp, "rlist",  1.0, wi);
+    ir->rlist = get_ereal(&inp, "rlist", 1.0, wi);
     printStringNoNewline(&inp, "long-range cut-off for switched potentials");
 
     /* Electrostatics */
     printStringNewline(&inp, "OPTIONS FOR ELECTROSTATICS AND VDW");
     printStringNoNewline(&inp, "Method for doing electrostatics");
-    ir->coulombtype      = get_eeenum(&inp, "coulombtype",    eel_names, wi);
-    ir->coulomb_modifier = get_eeenum(&inp, "coulomb-modifier",    eintmod_names, wi);
+    ir->coulombtype      = getEnum<CoulombInteractionType>(&inp, "coulombtype", wi);
+    ir->coulomb_modifier = getEnum<InteractionModifiers>(&inp, "coulomb-modifier", wi);
     printStringNoNewline(&inp, "cut-off lengths");
-    ir->rcoulomb_switch = get_ereal(&inp, "rcoulomb-switch",    0.0, wi);
-    ir->rcoulomb        = get_ereal(&inp, "rcoulomb",   1.0, wi);
+    ir->rcoulomb_switch = get_ereal(&inp, "rcoulomb-switch", 0.0, wi);
+    ir->rcoulomb        = get_ereal(&inp, "rcoulomb", 1.0, wi);
     printStringNoNewline(&inp, "Relative dielectric constant for the medium and the reaction field");
-    ir->epsilon_r  = get_ereal(&inp, "epsilon-r",  1.0, wi);
+    ir->epsilon_r  = get_ereal(&inp, "epsilon-r", 1.0, wi);
     ir->epsilon_rf = get_ereal(&inp, "epsilon-rf", 0.0, wi);
     printStringNoNewline(&inp, "Method for doing Van der Waals");
-    ir->vdwtype      = get_eeenum(&inp, "vdw-type",    evdw_names, wi);
-    ir->vdw_modifier = get_eeenum(&inp, "vdw-modifier",    eintmod_names, wi);
+    ir->vdwtype      = getEnum<VanDerWaalsType>(&inp, "vdw-type", wi);
+    ir->vdw_modifier = getEnum<InteractionModifiers>(&inp, "vdw-modifier", wi);
     printStringNoNewline(&inp, "cut-off lengths");
-    ir->rvdw_switch = get_ereal(&inp, "rvdw-switch",    0.0, wi);
-    ir->rvdw        = get_ereal(&inp, "rvdw",   1.0, wi);
+    ir->rvdw_switch = get_ereal(&inp, "rvdw-switch", 0.0, wi);
+    ir->rvdw        = get_ereal(&inp, "rvdw", 1.0, wi);
     printStringNoNewline(&inp, "Apply long range dispersion corrections for Energy and Pressure");
-    ir->eDispCorr = get_eeenum(&inp, "DispCorr",  edispc_names, wi);
+    ir->eDispCorr = getEnum<DispersionCorrectionType>(&inp, "DispCorr", wi);
     printStringNoNewline(&inp, "Extension of the potential lookup tables beyond the cut-off");
     ir->tabext = get_ereal(&inp, "table-extension", 1.0, wi);
     printStringNoNewline(&inp, "Separate tables between energy group pairs");
-    setStringEntry(&inp, "energygrp-table", is->egptable,   nullptr);
+    setStringEntry(&inp, "energygrp-table", inputrecStrings->egptable, nullptr);
     printStringNoNewline(&inp, "Spacing for the PME/PPPM FFT grid");
     ir->fourier_spacing = get_ereal(&inp, "fourierspacing", 0.12, wi);
     printStringNoNewline(&inp, "FFT grid size, when a value is 0 fourierspacing will be used");
-    ir->nkx = get_eint(&inp, "fourier-nx",         0, wi);
-    ir->nky = get_eint(&inp, "fourier-ny",         0, wi);
-    ir->nkz = get_eint(&inp, "fourier-nz",         0, wi);
+    ir->nkx = get_eint(&inp, "fourier-nx", 0, wi);
+    ir->nky = get_eint(&inp, "fourier-ny", 0, wi);
+    ir->nkz = get_eint(&inp, "fourier-nz", 0, wi);
     printStringNoNewline(&inp, "EWALD/PME/PPPM parameters");
-    ir->pme_order              = get_eint(&inp, "pme-order",   4, wi);
+    ir->pme_order              = get_eint(&inp, "pme-order", 4, wi);
     ir->ewald_rtol             = get_ereal(&inp, "ewald-rtol", 0.00001, wi);
     ir->ewald_rtol_lj          = get_ereal(&inp, "ewald-rtol-lj", 0.001, wi);
-    ir->ljpme_combination_rule = get_eeenum(&inp, "lj-pme-comb-rule", eljpme_names, wi);
-    ir->ewald_geometry         = get_eeenum(&inp, "ewald-geometry", eewg_names, wi);
+    ir->ljpme_combination_rule = getEnum<LongRangeVdW>(&inp, "lj-pme-comb-rule", wi);
+    ir->ewald_geometry         = getEnum<EwaldGeometry>(&inp, "ewald-geometry", wi);
     ir->epsilon_surface        = get_ereal(&inp, "epsilon-surface", 0.0, wi);
 
     /* Implicit solvation is no longer supported, but we need grompp
@@ -1869,78 +2160,60 @@ void get_ir(const char *mdparin, const char *mdparout,
     /* Coupling stuff */
     printStringNewline(&inp, "OPTIONS FOR WEAK COUPLING ALGORITHMS");
     printStringNoNewline(&inp, "Temperature coupling");
-    ir->etc                = get_eeenum(&inp, "tcoupl",        etcoupl_names, wi);
-    ir->nsttcouple         = get_eint(&inp, "nsttcouple",  -1, wi);
+    ir->etc                = getEnum<TemperatureCoupling>(&inp, "tcoupl", wi);
+    ir->nsttcouple         = get_eint(&inp, "nsttcouple", -1, wi);
     ir->opts.nhchainlength = get_eint(&inp, "nh-chain-length", 10, wi);
-    ir->bPrintNHChains     = (get_eeenum(&inp, "print-nose-hoover-chain-variables", yesno_names, wi) != 0);
+    ir->bPrintNHChains = (getEnum<Boolean>(&inp, "print-nose-hoover-chain-variables", wi) != Boolean::No);
     printStringNoNewline(&inp, "Groups to couple separately");
-    setStringEntry(&inp, "tc-grps",     is->tcgrps,         nullptr);
+    setStringEntry(&inp, "tc-grps", inputrecStrings->tcgrps, nullptr);
     printStringNoNewline(&inp, "Time constant (ps) and reference temperature (K)");
-    setStringEntry(&inp, "tau-t",   is->tau_t,      nullptr);
-    setStringEntry(&inp, "ref-t",   is->ref_t,      nullptr);
+    setStringEntry(&inp, "tau-t", inputrecStrings->tau_t, nullptr);
+    setStringEntry(&inp, "ref-t", inputrecStrings->ref_t, nullptr);
     printStringNoNewline(&inp, "pressure coupling");
-    ir->epc        = get_eeenum(&inp, "pcoupl",        epcoupl_names, wi);
-    ir->epct       = get_eeenum(&inp, "pcoupltype",       epcoupltype_names, wi);
-    ir->nstpcouple = get_eint(&inp, "nstpcouple",  -1, wi);
+    ir->epc        = getEnum<PressureCoupling>(&inp, "pcoupl", wi);
+    ir->epct       = getEnum<PressureCouplingType>(&inp, "pcoupltype", wi);
+    ir->nstpcouple = get_eint(&inp, "nstpcouple", -1, wi);
     printStringNoNewline(&inp, "Time constant (ps), compressibility (1/bar) and reference P (bar)");
-    ir->tau_p = get_ereal(&inp, "tau-p",  1.0, wi);
-    setStringEntry(&inp, "compressibility", dumstr[0],  nullptr);
-    setStringEntry(&inp, "ref-p",       dumstr[1],      nullptr);
+    ir->tau_p = get_ereal(&inp, "tau-p", 1.0, wi);
+    setStringEntry(&inp, "compressibility", dumstr[0], nullptr);
+    setStringEntry(&inp, "ref-p", dumstr[1], nullptr);
     printStringNoNewline(&inp, "Scaling of reference coordinates, No, All or COM");
-    ir->refcoord_scaling = get_eeenum(&inp, "refcoord-scaling", erefscaling_names, wi);
+    ir->refcoord_scaling = getEnum<RefCoordScaling>(&inp, "refcoord-scaling", wi);
 
     /* QMMM */
     printStringNewline(&inp, "OPTIONS FOR QMMM calculations");
-    ir->bQMMM = (get_eeenum(&inp, "QMMM", yesno_names, wi) != 0);
-    printStringNoNewline(&inp, "Groups treated Quantum Mechanically");
-    setStringEntry(&inp, "QMMM-grps",  is->QMMM,          nullptr);
-    printStringNoNewline(&inp, "QM method");
-    setStringEntry(&inp, "QMmethod",     is->QMmethod, nullptr);
-    printStringNoNewline(&inp, "QMMM scheme");
-    ir->QMMMscheme = get_eeenum(&inp, "QMMMscheme",    eQMMMscheme_names, wi);
-    printStringNoNewline(&inp, "QM basisset");
-    setStringEntry(&inp, "QMbasis",      is->QMbasis, nullptr);
-    printStringNoNewline(&inp, "QM charge");
-    setStringEntry(&inp, "QMcharge",    is->QMcharge, nullptr);
-    printStringNoNewline(&inp, "QM multiplicity");
-    setStringEntry(&inp, "QMmult",      is->QMmult, nullptr);
-    printStringNoNewline(&inp, "Surface Hopping");
-    setStringEntry(&inp, "SH",          is->bSH, nullptr);
-    printStringNoNewline(&inp, "CAS space options");
-    setStringEntry(&inp, "CASorbitals",      is->CASorbitals,   nullptr);
-    setStringEntry(&inp, "CASelectrons",     is->CASelectrons,  nullptr);
-    setStringEntry(&inp, "SAon", is->SAon, nullptr);
-    setStringEntry(&inp, "SAoff", is->SAoff, nullptr);
-    setStringEntry(&inp, "SAsteps", is->SAsteps, nullptr);
-    printStringNoNewline(&inp, "Scale factor for MM charges");
-    ir->scalefactor = get_ereal(&inp, "MMChargeScaleFactor", 1.0, wi);
+    ir->bQMMM = (getEnum<Boolean>(&inp, "QMMM", wi) != Boolean::No);
+    printStringNoNewline(&inp, "Groups treated with MiMiC");
+    setStringEntry(&inp, "QMMM-grps", inputrecStrings->QMMM, nullptr);
 
     /* Simulated annealing */
     printStringNewline(&inp, "SIMULATED ANNEALING");
     printStringNoNewline(&inp, "Type of annealing for each temperature group (no/single/periodic)");
-    setStringEntry(&inp, "annealing",   is->anneal,      nullptr);
-    printStringNoNewline(&inp, "Number of time points to use for specifying annealing in each group");
-    setStringEntry(&inp, "annealing-npoints", is->anneal_npoints, nullptr);
+    setStringEntry(&inp, "annealing", inputrecStrings->anneal, nullptr);
+    printStringNoNewline(&inp,
+                         "Number of time points to use for specifying annealing in each group");
+    setStringEntry(&inp, "annealing-npoints", inputrecStrings->anneal_npoints, nullptr);
     printStringNoNewline(&inp, "List of times at the annealing points for each group");
-    setStringEntry(&inp, "annealing-time",       is->anneal_time,       nullptr);
+    setStringEntry(&inp, "annealing-time", inputrecStrings->anneal_time, nullptr);
     printStringNoNewline(&inp, "Temp. at each annealing point, for each group.");
-    setStringEntry(&inp, "annealing-temp",  is->anneal_temp,  nullptr);
+    setStringEntry(&inp, "annealing-temp", inputrecStrings->anneal_temp, nullptr);
 
     /* Startup run */
     printStringNewline(&inp, "GENERATE VELOCITIES FOR STARTUP RUN");
-    opts->bGenVel = (get_eeenum(&inp, "gen-vel",  yesno_names, wi) != 0);
-    opts->tempi   = get_ereal(&inp, "gen-temp",    300.0, wi);
-    opts->seed    = get_eint(&inp, "gen-seed",     -1, wi);
+    opts->bGenVel = (getEnum<Boolean>(&inp, "gen-vel", wi) != Boolean::No);
+    opts->tempi   = get_ereal(&inp, "gen-temp", 300.0, wi);
+    opts->seed    = get_eint(&inp, "gen-seed", -1, wi);
 
     /* Shake stuff */
     printStringNewline(&inp, "OPTIONS FOR BONDS");
-    opts->nshake = get_eeenum(&inp, "constraints",   constraints, wi);
+    opts->nshake = get_eeenum(&inp, "constraints", constraints, wi);
     printStringNoNewline(&inp, "Type of constraint algorithm");
-    ir->eConstrAlg = get_eeenum(&inp, "constraint-algorithm", econstr_names, wi);
+    ir->eConstrAlg = getEnum<ConstraintAlgorithm>(&inp, "constraint-algorithm", wi);
     printStringNoNewline(&inp, "Do not constrain the start configuration");
-    ir->bContinuation = (get_eeenum(&inp, "continuation", yesno_names, wi) != 0);
-    printStringNoNewline(&inp, "Use successive overrelaxation to reduce the number of shake iterations");
-    ir->bShakeSOR = (get_eeenum(&inp, "Shake-SOR", yesno_names, wi) != 0);
+    ir->bContinuation = (getEnum<Boolean>(&inp, "continuation", wi) != Boolean::No);
+    printStringNoNewline(&inp,
+                         "Use successive overrelaxation to reduce the number of shake iterations");
+    ir->bShakeSOR = (getEnum<Boolean>(&inp, "Shake-SOR", wi) != Boolean::No);
     printStringNoNewline(&inp, "Relative tolerance of shake");
     ir->shake_tol = get_ereal(&inp, "shake-tol", 0.0001, wi);
     printStringNoNewline(&inp, "Highest order in the expansion of the constraint coupling matrix");
@@ -1953,63 +2226,72 @@ void get_ir(const char *mdparin, const char *mdparout,
     printStringNoNewline(&inp, "rotates over more degrees than");
     ir->LincsWarnAngle = get_ereal(&inp, "lincs-warnangle", 30.0, wi);
     printStringNoNewline(&inp, "Convert harmonic bonds to morse potentials");
-    opts->bMorse = (get_eeenum(&inp, "morse", yesno_names, wi) != 0);
+    opts->bMorse = (getEnum<Boolean>(&inp, "morse", wi) != Boolean::No);
 
     /* Energy group exclusions */
     printStringNewline(&inp, "ENERGY GROUP EXCLUSIONS");
-    printStringNoNewline(&inp, "Pairs of energy groups for which all non-bonded interactions are excluded");
-    setStringEntry(&inp, "energygrp-excl", is->egpexcl,     nullptr);
+    printStringNoNewline(
+            &inp, "Pairs of energy groups for which all non-bonded interactions are excluded");
+    setStringEntry(&inp, "energygrp-excl", inputrecStrings->egpexcl, nullptr);
 
     /* Walls */
     printStringNewline(&inp, "WALLS");
-    printStringNoNewline(&inp, "Number of walls, type, atom types, densities and box-z scale factor for Ewald");
+    printStringNoNewline(
+            &inp, "Number of walls, type, atom types, densities and box-z scale factor for Ewald");
     ir->nwall         = get_eint(&inp, "nwall", 0, wi);
-    ir->wall_type     = get_eeenum(&inp, "wall-type",   ewt_names, wi);
+    ir->wall_type     = getEnum<WallType>(&inp, "wall-type", wi);
     ir->wall_r_linpot = get_ereal(&inp, "wall-r-linpot", -1, wi);
-    setStringEntry(&inp, "wall-atomtype", is->wall_atomtype, nullptr);
-    setStringEntry(&inp, "wall-density",  is->wall_density,  nullptr);
+    setStringEntry(&inp, "wall-atomtype", inputrecStrings->wall_atomtype, nullptr);
+    setStringEntry(&inp, "wall-density", inputrecStrings->wall_density, nullptr);
     ir->wall_ewald_zfac = get_ereal(&inp, "wall-ewald-zfac", 3, wi);
 
     /* COM pulling */
     printStringNewline(&inp, "COM PULLING");
-    ir->bPull = (get_eeenum(&inp, "pull", yesno_names, wi) != 0);
+    ir->bPull = (getEnum<Boolean>(&inp, "pull", wi) != Boolean::No);
     if (ir->bPull)
     {
-        snew(ir->pull, 1);
-        is->pull_grp = read_pullparams(&inp, ir->pull, wi);
+        ir->pull                        = std::make_unique<pull_params_t>();
+        inputrecStrings->pullGroupNames = read_pullparams(&inp, ir->pull.get(), wi);
+
+        if (ir->useMts)
+        {
+            for (int c = 0; c < ir->pull->ncoord; c++)
+            {
+                if (ir->pull->coord[c].eType == PullingAlgorithm::Constraint)
+                {
+                    warning_error(wi,
+                                  "Constraint COM pulling is not supported in combination with "
+                                  "multiple time stepping");
+                    break;
+                }
+            }
+        }
     }
 
     /* AWH biasing
-       NOTE: needs COM pulling input */
+       NOTE: needs COM pulling or free energy input */
     printStringNewline(&inp, "AWH biasing");
-    ir->bDoAwh = (get_eeenum(&inp, "awh", yesno_names, wi) != 0);
+    ir->bDoAwh = (getEnum<Boolean>(&inp, "awh", wi) != Boolean::No);
     if (ir->bDoAwh)
     {
-        if (ir->bPull)
-        {
-            ir->awhParams = gmx::readAndCheckAwhParams(&inp, ir, wi);
-        }
-        else
-        {
-            gmx_fatal(FARGS, "AWH biasing is only compatible with COM pulling turned on");
-        }
+        ir->awhParams = std::make_unique<gmx::AwhParams>(&inp, wi);
     }
 
     /* Enforced rotation */
     printStringNewline(&inp, "ENFORCED ROTATION");
     printStringNoNewline(&inp, "Enforced rotation: No or Yes");
-    ir->bRot = (get_eeenum(&inp, "rotation", yesno_names, wi) != 0);
+    ir->bRot = (getEnum<Boolean>(&inp, "rotation", wi) != Boolean::No);
     if (ir->bRot)
     {
         snew(ir->rot, 1);
-        is->rot_grp = read_rotparams(&inp, ir->rot, wi);
+        inputrecStrings->rotateGroupNames = read_rotparams(&inp, ir->rot, wi);
     }
 
     /* Interactive MD */
     ir->bIMD = FALSE;
     printStringNewline(&inp, "Group to display and/or manipulate in interactive MD session");
-    setStringEntry(&inp, "IMD-group", is->imd_grp, nullptr);
-    if (is->imd_grp[0] != '\0')
+    setStringEntry(&inp, "IMD-group", inputrecStrings->imd_grp, nullptr);
+    if (inputrecStrings->imd_grp[0] != '\0')
     {
         snew(ir->imd, 1);
         ir->bIMD = TRUE;
@@ -2018,78 +2300,90 @@ void get_ir(const char *mdparin, const char *mdparout,
     /* Refinement */
     printStringNewline(&inp, "NMR refinement stuff");
     printStringNoNewline(&inp, "Distance restraints type: No, Simple or Ensemble");
-    ir->eDisre = get_eeenum(&inp, "disre",     edisre_names, wi);
-    printStringNoNewline(&inp, "Force weighting of pairs in one distance restraint: Conservative or Equal");
-    ir->eDisreWeighting = get_eeenum(&inp, "disre-weighting", edisreweighting_names, wi);
+    ir->eDisre = getEnum<DistanceRestraintRefinement>(&inp, "disre", wi);
+    printStringNoNewline(
+            &inp, "Force weighting of pairs in one distance restraint: Conservative or Equal");
+    ir->eDisreWeighting = getEnum<DistanceRestraintWeighting>(&inp, "disre-weighting", wi);
     printStringNoNewline(&inp, "Use sqrt of the time averaged times the instantaneous violation");
-    ir->bDisreMixed = (get_eeenum(&inp, "disre-mixed", yesno_names, wi) != 0);
-    ir->dr_fc       = get_ereal(&inp, "disre-fc",  1000.0, wi);
+    ir->bDisreMixed = (getEnum<Boolean>(&inp, "disre-mixed", wi) != Boolean::No);
+    ir->dr_fc       = get_ereal(&inp, "disre-fc", 1000.0, wi);
     ir->dr_tau      = get_ereal(&inp, "disre-tau", 0.0, wi);
     printStringNoNewline(&inp, "Output frequency for pair distances to energy file");
     ir->nstdisreout = get_eint(&inp, "nstdisreout", 100, wi);
     printStringNoNewline(&inp, "Orientation restraints: No or Yes");
-    opts->bOrire = (get_eeenum(&inp, "orire",   yesno_names, wi) != 0);
+    opts->bOrire = (getEnum<Boolean>(&inp, "orire", wi) != Boolean::No);
     printStringNoNewline(&inp, "Orientation restraints force constant and tau for time averaging");
-    ir->orires_fc  = get_ereal(&inp, "orire-fc",  0.0, wi);
+    ir->orires_fc  = get_ereal(&inp, "orire-fc", 0.0, wi);
     ir->orires_tau = get_ereal(&inp, "orire-tau", 0.0, wi);
-    setStringEntry(&inp, "orire-fitgrp", is->orirefitgrp,    nullptr);
+    setStringEntry(&inp, "orire-fitgrp", inputrecStrings->orirefitgrp, nullptr);
     printStringNoNewline(&inp, "Output frequency for trace(SD) and S to energy file");
     ir->nstorireout = get_eint(&inp, "nstorireout", 100, wi);
 
     /* free energy variables */
     printStringNewline(&inp, "Free energy variables");
-    ir->efep = get_eeenum(&inp, "free-energy", efep_names, wi);
-    setStringEntry(&inp, "couple-moltype",  is->couple_moltype,  nullptr);
+    ir->efep = getEnum<FreeEnergyPerturbationType>(&inp, "free-energy", wi);
+    setStringEntry(&inp, "couple-moltype", inputrecStrings->couple_moltype, nullptr);
     opts->couple_lam0  = get_eeenum(&inp, "couple-lambda0", couple_lam, wi);
     opts->couple_lam1  = get_eeenum(&inp, "couple-lambda1", couple_lam, wi);
-    opts->bCoupleIntra = (get_eeenum(&inp, "couple-intramol", yesno_names, wi) != 0);
+    opts->bCoupleIntra = (getEnum<Boolean>(&inp, "couple-intramol", wi) != Boolean::No);
 
-    fep->init_lambda    = get_ereal(&inp, "init-lambda", -1, wi); /* start with -1 so
-                                                                            we can recognize if
-                                                                            it was not entered */
+    fep->init_lambda = get_ereal(&inp, "init-lambda", -1, wi); /* start with -1 so
+                                                                         we can recognize if
+                                                                         it was not entered */
     fep->init_fep_state = get_eint(&inp, "init-lambda-state", -1, wi);
     fep->delta_lambda   = get_ereal(&inp, "delta-lambda", 0.0, wi);
     fep->nstdhdl        = get_eint(&inp, "nstdhdl", 50, wi);
-    setStringEntry(&inp, "fep-lambdas", is->fep_lambda[efptFEP], nullptr);
-    setStringEntry(&inp, "mass-lambdas", is->fep_lambda[efptMASS], nullptr);
-    setStringEntry(&inp, "coul-lambdas", is->fep_lambda[efptCOUL], nullptr);
-    setStringEntry(&inp, "vdw-lambdas", is->fep_lambda[efptVDW], nullptr);
-    setStringEntry(&inp, "bonded-lambdas", is->fep_lambda[efptBONDED], nullptr);
-    setStringEntry(&inp, "restraint-lambdas", is->fep_lambda[efptRESTRAINT], nullptr);
-    setStringEntry(&inp, "temperature-lambdas", is->fep_lambda[efptTEMPERATURE], nullptr);
+    inputrecStrings->fep_lambda[FreeEnergyPerturbationCouplingType::Fep] =
+            setStringEntry(&inp, "fep-lambdas", "");
+    inputrecStrings->fep_lambda[FreeEnergyPerturbationCouplingType::Mass] =
+            setStringEntry(&inp, "mass-lambdas", "");
+    inputrecStrings->fep_lambda[FreeEnergyPerturbationCouplingType::Coul] =
+            setStringEntry(&inp, "coul-lambdas", "");
+    inputrecStrings->fep_lambda[FreeEnergyPerturbationCouplingType::Vdw] =
+            setStringEntry(&inp, "vdw-lambdas", "");
+    inputrecStrings->fep_lambda[FreeEnergyPerturbationCouplingType::Bonded] =
+            setStringEntry(&inp, "bonded-lambdas", "");
+    inputrecStrings->fep_lambda[FreeEnergyPerturbationCouplingType::Restraint] =
+            setStringEntry(&inp, "restraint-lambdas", "");
+    inputrecStrings->fep_lambda[FreeEnergyPerturbationCouplingType::Temperature] =
+            setStringEntry(&inp, "temperature-lambdas", "");
     fep->lambda_neighbors = get_eint(&inp, "calc-lambda-neighbors", 1, wi);
-    setStringEntry(&inp, "init-lambda-weights", is->lambda_weights, nullptr);
-    fep->edHdLPrintEnergy   = get_eeenum(&inp, "dhdl-print-energy", edHdLPrintEnergy_names, wi);
-    fep->sc_alpha           = get_ereal(&inp, "sc-alpha", 0.0, wi);
-    fep->sc_power           = get_eint(&inp, "sc-power", 1, wi);
-    fep->sc_r_power         = get_ereal(&inp, "sc-r-power", 6.0, wi);
-    fep->sc_sigma           = get_ereal(&inp, "sc-sigma", 0.3, wi);
-    fep->bScCoul            = (get_eeenum(&inp, "sc-coul", yesno_names, wi) != 0);
-    fep->dh_hist_size       = get_eint(&inp, "dh_hist_size", 0, wi);
-    fep->dh_hist_spacing    = get_ereal(&inp, "dh_hist_spacing", 0.1, wi);
-    fep->separate_dhdl_file = get_eeenum(&inp, "separate-dhdl-file", separate_dhdl_file_names, wi);
-    fep->dhdl_derivatives   = get_eeenum(&inp, "dhdl-derivatives", dhdl_derivatives_names, wi);
-    fep->dh_hist_size       = get_eint(&inp, "dh_hist_size", 0, wi);
-    fep->dh_hist_spacing    = get_ereal(&inp, "dh_hist_spacing", 0.1, wi);
+    setStringEntry(&inp, "init-lambda-weights", inputrecStrings->lambda_weights, nullptr);
+    fep->edHdLPrintEnergy        = getEnum<FreeEnergyPrintEnergy>(&inp, "dhdl-print-energy", wi);
+    fep->softcoreFunction        = getEnum<SoftcoreType>(&inp, "sc-function", wi);
+    fep->sc_alpha                = get_ereal(&inp, "sc-alpha", 0.0, wi);
+    fep->sc_power                = get_eint(&inp, "sc-power", 1, wi);
+    fep->sc_r_power              = get_ereal(&inp, "sc-r-power", 6.0, wi);
+    fep->sc_sigma                = get_ereal(&inp, "sc-sigma", 0.3, wi);
+    fep->bScCoul                 = (getEnum<Boolean>(&inp, "sc-coul", wi) != Boolean::No);
+    fep->scGapsysScaleLinpointLJ = get_ereal(&inp, "sc-gapsys-scale-linpoint-lj", 0.85, wi);
+    fep->scGapsysScaleLinpointQ  = get_ereal(&inp, "sc-gapsys-scale-linpoint-q", 0.3, wi);
+    fep->scGapsysSigmaLJ         = get_ereal(&inp, "sc-gapsys-sigma-lj", 0.3, wi);
+    fep->dh_hist_size            = get_eint(&inp, "dh_hist_size", 0, wi);
+    fep->dh_hist_spacing         = get_ereal(&inp, "dh_hist_spacing", 0.1, wi);
+    fep->separate_dhdl_file      = getEnum<SeparateDhdlFile>(&inp, "separate-dhdl-file", wi);
+    fep->dhdl_derivatives        = getEnum<DhDlDerivativeCalculation>(&inp, "dhdl-derivatives", wi);
+    fep->dh_hist_size            = get_eint(&inp, "dh_hist_size", 0, wi);
+    fep->dh_hist_spacing         = get_ereal(&inp, "dh_hist_spacing", 0.1, wi);
 
     /* Non-equilibrium MD stuff */
     printStringNewline(&inp, "Non-equilibrium MD stuff");
-    setStringEntry(&inp, "acc-grps",    is->accgrps,        nullptr);
-    setStringEntry(&inp, "accelerate",  is->acc,            nullptr);
-    setStringEntry(&inp, "freezegrps",  is->freeze,         nullptr);
-    setStringEntry(&inp, "freezedim",   is->frdim,          nullptr);
+    setStringEntry(&inp, "acc-grps", inputrecStrings->accelerationGroups, nullptr);
+    setStringEntry(&inp, "accelerate", inputrecStrings->acceleration, nullptr);
+    setStringEntry(&inp, "freezegrps", inputrecStrings->freeze, nullptr);
+    setStringEntry(&inp, "freezedim", inputrecStrings->frdim, nullptr);
     ir->cos_accel = get_ereal(&inp, "cos-acceleration", 0, wi);
-    setStringEntry(&inp, "deform",      is->deform,         nullptr);
+    setStringEntry(&inp, "deform", inputrecStrings->deform, nullptr);
 
     /* simulated tempering variables */
     printStringNewline(&inp, "simulated tempering variables");
-    ir->bSimTemp                   = (get_eeenum(&inp, "simulated-tempering", yesno_names, wi) != 0);
-    ir->simtempvals->eSimTempScale = get_eeenum(&inp, "simulated-tempering-scaling", esimtemp_names, wi);
-    ir->simtempvals->simtemp_low   = get_ereal(&inp, "sim-temp-low", 300.0, wi);
-    ir->simtempvals->simtemp_high  = get_ereal(&inp, "sim-temp-high", 300.0, wi);
+    ir->bSimTemp = (getEnum<Boolean>(&inp, "simulated-tempering", wi) != Boolean::No);
+    ir->simtempvals->eSimTempScale = getEnum<SimulatedTempering>(&inp, "simulated-tempering-scaling", wi);
+    ir->simtempvals->simtemp_low  = get_ereal(&inp, "sim-temp-low", 300.0, wi);
+    ir->simtempvals->simtemp_high = get_ereal(&inp, "sim-temp-high", 300.0, wi);
 
     /* expanded ensemble variables */
-    if (ir->efep == efepEXPANDED || ir->bSimTemp)
+    if (ir->efep == FreeEnergyPerturbationType::Expanded || ir->bSimTemp)
     {
         read_expandedparams(&inp, expand, wi);
     }
@@ -2098,28 +2392,27 @@ void get_ir(const char *mdparin, const char *mdparout,
     {
         gmx::KeyValueTreeObject      convertedValues = flatKeyValueTreeFromInpFile(inp);
         gmx::KeyValueTreeTransformer transform;
-        transform.rules()->addRule()
-            .keyMatchType("/", gmx::StringCompareType::CaseAndDashInsensitive);
+        transform.rules()->addRule().keyMatchType("/", gmx::StringCompareType::CaseAndDashInsensitive);
         mdModules->initMdpTransform(transform.rules());
-        for (const auto &path : transform.mappedPaths())
+        for (const autopath : transform.mappedPaths())
         {
             GMX_ASSERT(path.size() == 1, "Inconsistent mapping back to mdp options");
             mark_einp_set(inp, path[0].c_str());
         }
-        MdpErrorHandler              errorHandler(wi);
-        auto                         result
-                   = transform.transform(convertedValues, &errorHandler);
-        ir->params = new gmx::KeyValueTreeObject(result.object());
+        MdpErrorHandler errorHandler(wi);
+        auto            result = transform.transform(convertedValues, &errorHandler);
+        ir->params             = new gmx::KeyValueTreeObject(result.object());
         mdModules->adjustInputrecBasedOnModules(ir);
         errorHandler.setBackMapping(result.backMapping());
         mdModules->assignOptionsToModules(*ir->params, &errorHandler);
     }
 
     /* Ion/water position swapping ("computational electrophysiology") */
-    printStringNewline(&inp, "Ion/water position swapping for computational electrophysiology setups");
+    printStringNewline(&inp,
+                       "Ion/water position swapping for computational electrophysiology setups");
     printStringNoNewline(&inp, "Swap positions along direction: no, X, Y, Z");
-    ir->eSwapCoords = get_eeenum(&inp, "swapcoords", eSwapTypes_names, wi);
-    if (ir->eSwapCoords != eswapNO)
+    ir->eSwapCoords = getEnum<SwapType>(&inp, "swapcoords", wi);
+    if (ir->eSwapCoords != SwapType::No)
     {
         char buf[STRLEN];
         int  nIonTypes;
@@ -2134,25 +2427,43 @@ void get_ir(const char *mdparin, const char *mdparout,
         {
             warning_error(wi, "You need to provide at least one ion type for position exchanges.");
         }
-        ir->swap->ngrp = nIonTypes + eSwapFixedGrpNR;
+        ir->swap->ngrp = nIonTypes + static_cast<int>(SwapGroupSplittingType::Count);
         snew(ir->swap->grp, ir->swap->ngrp);
         for (i = 0; i < ir->swap->ngrp; i++)
         {
             snew(ir->swap->grp[i].molname, STRLEN);
         }
-        printStringNoNewline(&inp, "Two index groups that contain the compartment-partitioning atoms");
-        setStringEntry(&inp, "split-group0", ir->swap->grp[eGrpSplit0].molname, nullptr);
-        setStringEntry(&inp, "split-group1", ir->swap->grp[eGrpSplit1].molname, nullptr);
-        printStringNoNewline(&inp, "Use center of mass of split groups (yes/no), otherwise center of geometry is used");
-        ir->swap->massw_split[0] = (get_eeenum(&inp, "massw-split0", yesno_names, wi) != 0);
-        ir->swap->massw_split[1] = (get_eeenum(&inp, "massw-split1", yesno_names, wi) != 0);
+        printStringNoNewline(&inp,
+                             "Two index groups that contain the compartment-partitioning atoms");
+        setStringEntry(&inp,
+                       "split-group0",
+                       ir->swap->grp[static_cast<int>(SwapGroupSplittingType::Split0)].molname,
+                       nullptr);
+        setStringEntry(&inp,
+                       "split-group1",
+                       ir->swap->grp[static_cast<int>(SwapGroupSplittingType::Split1)].molname,
+                       nullptr);
+        printStringNoNewline(&inp,
+                             "Use center of mass of split groups (yes/no), otherwise center of "
+                             "geometry is used");
+        ir->swap->massw_split[0] = (getEnum<Boolean>(&inp, "massw-split0", wi) != Boolean::No);
+        ir->swap->massw_split[1] = (getEnum<Boolean>(&inp, "massw-split1", wi) != Boolean::No);
 
         printStringNoNewline(&inp, "Name of solvent molecules");
-        setStringEntry(&inp, "solvent-group", ir->swap->grp[eGrpSolvent].molname, nullptr);
-
-        printStringNoNewline(&inp, "Split cylinder: radius, upper and lower extension (nm) (this will define the channels)");
-        printStringNoNewline(&inp, "Note that the split cylinder settings do not have an influence on the swapping protocol,");
-        printStringNoNewline(&inp, "however, if correctly defined, the permeation events are recorded per channel");
+        setStringEntry(&inp,
+                       "solvent-group",
+                       ir->swap->grp[static_cast<int>(SwapGroupSplittingType::Solvent)].molname,
+                       nullptr);
+
+        printStringNoNewline(&inp,
+                             "Split cylinder: radius, upper and lower extension (nm) (this will "
+                             "define the channels)");
+        printStringNoNewline(&inp,
+                             "Note that the split cylinder settings do not have an influence on "
+                             "the swapping protocol,");
+        printStringNoNewline(
+                &inp,
+                "however, if correctly defined, the permeation events are recorded per channel");
         ir->swap->cyl0r = get_ereal(&inp, "cyl0-r", 2.0, wi);
         ir->swap->cyl0u = get_ereal(&inp, "cyl0-up", 1.0, wi);
         ir->swap->cyl0l = get_ereal(&inp, "cyl0-down", 1.0, wi);
@@ -2160,15 +2471,19 @@ void get_ir(const char *mdparin, const char *mdparout,
         ir->swap->cyl1u = get_ereal(&inp, "cyl1-up", 1.0, wi);
         ir->swap->cyl1l = get_ereal(&inp, "cyl1-down", 1.0, wi);
 
-        printStringNoNewline(&inp, "Average the number of ions per compartment over these many swap attempt steps");
+        printStringNoNewline(
+                &inp,
+                "Average the number of ions per compartment over these many swap attempt steps");
         ir->swap->nAverage = get_eint(&inp, "coupl-steps", 10, wi);
 
-        printStringNoNewline(&inp, "Names of the ion types that can be exchanged with solvent molecules,");
-        printStringNoNewline(&inp, "and the requested number of ions of this type in compartments A and B");
+        printStringNoNewline(
+                &inp, "Names of the ion types that can be exchanged with solvent molecules,");
+        printStringNoNewline(
+                &inp, "and the requested number of ions of this type in compartments A and B");
         printStringNoNewline(&inp, "-1 means fix the numbers as found in step 0");
         for (i = 0; i < nIonTypes; i++)
         {
-            int ig = eSwapFixedGrpNR + i;
+            int ig = static_cast<int>(SwapGroupSplittingType::Count) + i;
 
             sprintf(buf, "iontype%d-name", i);
             setStringEntry(&inp, buf, ir->swap->grp[ig].molname, nullptr);
@@ -2178,19 +2493,27 @@ void get_ir(const char *mdparin, const char *mdparout,
             ir->swap->grp[ig].nmolReq[1] = get_eint(&inp, buf, -1, wi);
         }
 
-        printStringNoNewline(&inp, "By default (i.e. bulk offset = 0.0), ion/water exchanges happen between layers");
-        printStringNoNewline(&inp, "at maximum distance (= bulk concentration) to the split group layers. However,");
-        printStringNoNewline(&inp, "an offset b (-1.0 < b < +1.0) can be specified to offset the bulk layer from the middle at 0.0");
-        printStringNoNewline(&inp, "towards one of the compartment-partitioning layers (at +/- 1.0).");
+        printStringNoNewline(
+                &inp,
+                "By default (i.e. bulk offset = 0.0), ion/water exchanges happen between layers");
+        printStringNoNewline(
+                &inp,
+                "at maximum distance (= bulk concentration) to the split group layers. However,");
+        printStringNoNewline(&inp,
+                             "an offset b (-1.0 < b < +1.0) can be specified to offset the bulk "
+                             "layer from the middle at 0.0");
+        printStringNoNewline(&inp,
+                             "towards one of the compartment-partitioning layers (at +/- 1.0).");
         ir->swap->bulkOffset[0] = get_ereal(&inp, "bulk-offsetA", 0.0, wi);
         ir->swap->bulkOffset[1] = get_ereal(&inp, "bulk-offsetB", 0.0, wi);
         if (!(ir->swap->bulkOffset[0] > -1.0 && ir->swap->bulkOffset[0] < 1.0)
-            || !(ir->swap->bulkOffset[1] > -1.0 && ir->swap->bulkOffset[1] < 1.0) )
+            || !(ir->swap->bulkOffset[1] > -1.0 && ir->swap->bulkOffset[1] < 1.0))
         {
             warning_error(wi, "Bulk layer offsets must be > -1.0 and < 1.0 !");
         }
 
-        printStringNoNewline(&inp, "Start to swap ions if threshold difference to requested count is reached");
+        printStringNoNewline(
+                &inp, "Start to swap ions if threshold difference to requested count is reached");
         ir->swap->threshold = get_ereal(&inp, "threshold", 1.0, wi);
     }
 
@@ -2200,18 +2523,19 @@ void get_ir(const char *mdparin, const char *mdparout,
 
     /* User defined thingies */
     printStringNewline(&inp, "User defined thingies");
-    setStringEntry(&inp, "user1-grps",  is->user1,          nullptr);
-    setStringEntry(&inp, "user2-grps",  is->user2,          nullptr);
-    ir->userint1  = get_eint(&inp, "userint1",   0, wi);
-    ir->userint2  = get_eint(&inp, "userint2",   0, wi);
-    ir->userint3  = get_eint(&inp, "userint3",   0, wi);
-    ir->userint4  = get_eint(&inp, "userint4",   0, wi);
-    ir->userreal1 = get_ereal(&inp, "userreal1",  0, wi);
-    ir->userreal2 = get_ereal(&inp, "userreal2",  0, wi);
-    ir->userreal3 = get_ereal(&inp, "userreal3",  0, wi);
-    ir->userreal4 = get_ereal(&inp, "userreal4",  0, wi);
+    setStringEntry(&inp, "user1-grps", inputrecStrings->user1, nullptr);
+    setStringEntry(&inp, "user2-grps", inputrecStrings->user2, nullptr);
+    ir->userint1  = get_eint(&inp, "userint1", 0, wi);
+    ir->userint2  = get_eint(&inp, "userint2", 0, wi);
+    ir->userint3  = get_eint(&inp, "userint3", 0, wi);
+    ir->userint4  = get_eint(&inp, "userint4", 0, wi);
+    ir->userreal1 = get_ereal(&inp, "userreal1", 0, wi);
+    ir->userreal2 = get_ereal(&inp, "userreal2", 0, wi);
+    ir->userreal3 = get_ereal(&inp, "userreal3", 0, wi);
+    ir->userreal4 = get_ereal(&inp, "userreal4", 0, wi);
 #undef CTYPE
 
+    if (mdparout)
     {
         gmx::TextOutputFile stream(mdparout);
         write_inpfile(&stream, mdparout, &inp, FALSE, writeMdpHeader, wi);
@@ -2230,40 +2554,53 @@ void get_ir(const char *mdparin, const char *mdparout,
     /* Process options if necessary */
     for (m = 0; m < 2; m++)
     {
-        for (i = 0; i < 2*DIM; i++)
+        for (i = 0; i < 2 * DIM; i++)
         {
             dumdub[m][i] = 0.0;
         }
-        if (ir->epc)
+        if (ir->epc != PressureCoupling::No)
         {
             switch (ir->epct)
             {
-                case epctISOTROPIC:
+                case PressureCouplingType::Isotropic:
                     if (sscanf(dumstr[m], "%lf", &(dumdub[m][XX])) != 1)
                     {
-                        warning_error(wi, "Pressure coupling incorrect number of values (I need exactly 1)");
+                        warning_error(
+                                wi,
+                                "Pressure coupling incorrect number of values (I need exactly 1)");
                     }
                     dumdub[m][YY] = dumdub[m][ZZ] = dumdub[m][XX];
                     break;
-                case epctSEMIISOTROPIC:
-                case epctSURFACETENSION:
+                case PressureCouplingType::SemiIsotropic:
+                case PressureCouplingType::SurfaceTension:
                     if (sscanf(dumstr[m], "%lf%lf", &(dumdub[m][XX]), &(dumdub[m][ZZ])) != 2)
                     {
-                        warning_error(wi, "Pressure coupling incorrect number of values (I need exactly 2)");
+                        warning_error(
+                                wi,
+                                "Pressure coupling incorrect number of values (I need exactly 2)");
                     }
                     dumdub[m][YY] = dumdub[m][XX];
                     break;
-                case epctANISOTROPIC:
-                    if (sscanf(dumstr[m], "%lf%lf%lf%lf%lf%lf",
-                               &(dumdub[m][XX]), &(dumdub[m][YY]), &(dumdub[m][ZZ]),
-                               &(dumdub[m][3]), &(dumdub[m][4]), &(dumdub[m][5])) != 6)
+                case PressureCouplingType::Anisotropic:
+                    if (sscanf(dumstr[m],
+                               "%lf%lf%lf%lf%lf%lf",
+                               &(dumdub[m][XX]),
+                               &(dumdub[m][YY]),
+                               &(dumdub[m][ZZ]),
+                               &(dumdub[m][3]),
+                               &(dumdub[m][4]),
+                               &(dumdub[m][5]))
+                        != 6)
                     {
-                        warning_error(wi, "Pressure coupling incorrect number of values (I need exactly 6)");
+                        warning_error(
+                                wi,
+                                "Pressure coupling incorrect number of values (I need exactly 6)");
                     }
                     break;
                 default:
-                    gmx_fatal(FARGS, "Pressure coupling type %s not implemented yet",
-                              epcoupltype_names[ir->epct]);
+                    gmx_fatal(FARGS,
+                              "Pressure coupling type %s not implemented yet",
+                              enumValueToString(ir->epct));
             }
         }
     }
@@ -2274,14 +2611,16 @@ void get_ir(const char *mdparin, const char *mdparout,
         ir->ref_p[i][i]    = dumdub[1][i];
         ir->compress[i][i] = dumdub[0][i];
     }
-    if (ir->epct == epctANISOTROPIC)
+    if (ir->epct == PressureCouplingType::Anisotropic)
     {
         ir->ref_p[XX][YY] = dumdub[1][3];
         ir->ref_p[XX][ZZ] = dumdub[1][4];
         ir->ref_p[YY][ZZ] = dumdub[1][5];
         if (ir->ref_p[XX][YY] != 0 && ir->ref_p[XX][ZZ] != 0 && ir->ref_p[YY][ZZ] != 0)
         {
-            warning(wi, "All off-diagonal reference pressures are non-zero. Are you sure you want to apply a threefold shear stress?\n");
+            warning(wi,
+                    "All off-diagonal reference pressures are non-zero. Are you sure you want to "
+                    "apply a threefold shear stress?\n");
         }
         ir->compress[XX][YY] = dumdub[0][3];
         ir->compress[XX][ZZ] = dumdub[0][4];
@@ -2296,49 +2635,55 @@ void get_ir(const char *mdparin, const char *mdparout,
         }
     }
 
-    if (ir->comm_mode == ecmNO)
+    if (ir->comm_mode == ComRemovalAlgorithm::No)
     {
         ir->nstcomm = 0;
     }
 
     opts->couple_moltype = nullptr;
-    if (strlen(is->couple_moltype) > 0)
+    if (strlen(inputrecStrings->couple_moltype) > 0)
     {
-        if (ir->efep != efepNO)
+        if (ir->efep != FreeEnergyPerturbationType::No)
         {
-            opts->couple_moltype = gmx_strdup(is->couple_moltype);
+            opts->couple_moltype = gmx_strdup(inputrecStrings->couple_moltype);
             if (opts->couple_lam0 == opts->couple_lam1)
             {
                 warning(wi, "The lambda=0 and lambda=1 states for coupling are identical");
             }
-            if (ir->eI == eiMD && (opts->couple_lam0 == ecouplamNONE ||
-                                   opts->couple_lam1 == ecouplamNONE))
+            if (ir->eI == IntegrationAlgorithm::MD
+                && (opts->couple_lam0 == ecouplamNONE || opts->couple_lam1 == ecouplamNONE))
             {
-                warning(wi, "For proper sampling of the (nearly) decoupled state, stochastic dynamics should be used");
+                warning_note(
+                        wi,
+                        "For proper sampling of the (nearly) decoupled state, stochastic dynamics "
+                        "should be used");
             }
         }
         else
         {
-            warning_note(wi, "Free energy is turned off, so we will not decouple the molecule listed in your input.");
+            warning_note(wi,
+                         "Free energy is turned off, so we will not decouple the molecule listed "
+                         "in your input.");
         }
     }
     /* FREE ENERGY AND EXPANDED ENSEMBLE OPTIONS */
-    if (ir->efep != efepNO)
+    if (ir->efep != FreeEnergyPerturbationType::No)
     {
-        if (fep->delta_lambda > 0)
+        if (fep->delta_lambda != 0)
         {
-            ir->efep = efepSLOWGROWTH;
+            ir->efep = FreeEnergyPerturbationType::SlowGrowth;
         }
     }
 
-    if (fep->edHdLPrintEnergy == edHdLPrintEnergyYES)
+    if (fep->edHdLPrintEnergy == FreeEnergyPrintEnergy::Yes)
     {
-        fep->edHdLPrintEnergy = edHdLPrintEnergyTOTAL;
-        warning_note(wi, "Old option for dhdl-print-energy given: "
+        fep->edHdLPrintEnergy = FreeEnergyPrintEnergy::Total;
+        warning_note(wi,
+                     "Old option for dhdl-print-energy given: "
                      "changing \"yes\" to \"total\"\n");
     }
 
-    if (ir->bSimTemp && (fep->edHdLPrintEnergy == edHdLPrintEnergyNO))
+    if (ir->bSimTemp && (fep->edHdLPrintEnergy == FreeEnergyPrintEnergy::No))
     {
         /* always print out the energy to dhdl if we are doing
            expanded ensemble, since we need the total energy for
@@ -2347,17 +2692,17 @@ void get_ir(const char *mdparin, const char *mdparout,
            we will allow that if the appropriate mdp setting has
            been enabled. Otherwise, total it is:
          */
-        fep->edHdLPrintEnergy = edHdLPrintEnergyTOTAL;
+        fep->edHdLPrintEnergy = FreeEnergyPrintEnergy::Total;
     }
 
-    if ((ir->efep != efepNO) || ir->bSimTemp)
+    if ((ir->efep != FreeEnergyPerturbationType::No) || ir->bSimTemp)
     {
         ir->bExpanded = FALSE;
-        if ((ir->efep == efepEXPANDED) || ir->bSimTemp)
+        if ((ir->efep == FreeEnergyPerturbationType::Expanded) || ir->bSimTemp)
         {
             ir->bExpanded = TRUE;
         }
-        do_fep_params(ir, is->fep_lambda, is->lambda_weights, wi);
+        do_fep_params(ir, inputrecStrings->fep_lambda, inputrecStrings->lambda_weights, wi);
         if (ir->bSimTemp) /* done after fep params */
         {
             do_simtemp_params(ir);
@@ -2371,12 +2716,15 @@ void get_ir(const char *mdparin, const char *mdparout,
          * If the (advanced) user does FEP through manual topology changes,
          * this check will not be triggered.
          */
-        if (ir->efep != efepNO && ir->fepvals->n_lambda == 0 &&
-            ir->fepvals->sc_alpha != 0 &&
-            (couple_lambda_has_vdw_on(opts->couple_lam0) &&
-             couple_lambda_has_vdw_on(opts->couple_lam1)))
+        if (ir->efep != FreeEnergyPerturbationType::No && ir->fepvals->n_lambda == 0
+            && ir->fepvals->sc_alpha != 0
+            && (couple_lambda_has_vdw_on(opts->couple_lam0) && couple_lambda_has_vdw_on(opts->couple_lam1)))
         {
-            warning(wi, "You are using soft-core interactions while the Van der Waals interactions are not decoupled (note that the sc-coul option is only active when using lambda states). Although this will not lead to errors, you will need much more sampling than without soft-core interactions. Consider using sc-alpha=0.");
+            warning(wi,
+                    "You are using soft-core interactions while the Van der Waals interactions are "
+                    "not decoupled (note that the sc-coul option is only active when using lambda "
+                    "states). Although this will not lead to errors, you will need much more "
+                    "sampling than without soft-core interactions. Consider using sc-alpha=0.");
         }
     }
     else
@@ -2386,11 +2734,11 @@ void get_ir(const char *mdparin, const char *mdparout,
 
     /* WALL PARAMETERS */
 
-    do_wall_params(ir, is->wall_atomtype, is->wall_density, opts, wi);
+    do_wall_params(ir, inputrecStrings->wall_atomtype, inputrecStrings->wall_density, opts, wi);
 
     /* ORIENTATION RESTRAINT PARAMETERS */
 
-    if (opts->bOrire && gmx::splitString(is->orirefitgrp).size() != 1)
+    if (opts->bOrire && gmx::splitString(inputrecStrings->orirefitgrp).size() != 1)
     {
         warning_error(wi, "ERROR: Need one orientation restraint fit group\n");
     }
@@ -2404,13 +2752,23 @@ void get_ir(const char *mdparin, const char *mdparout,
     }
 
     double gmx_unused canary;
-    int               ndeform = sscanf(is->deform, "%lf %lf %lf %lf %lf %lf %lf",
-                                       &(dumdub[0][0]), &(dumdub[0][1]), &(dumdub[0][2]),
-                                       &(dumdub[0][3]), &(dumdub[0][4]), &(dumdub[0][5]), &canary);
-
-    if (strlen(is->deform) > 0 && ndeform != 6)
-    {
-        warning_error(wi, gmx::formatString("Cannot parse exactly 6 box deformation velocities from string '%s'", is->deform).c_str());
+    int               ndeform = sscanf(inputrecStrings->deform,
+                         "%lf %lf %lf %lf %lf %lf %lf",
+                         &(dumdub[0][0]),
+                         &(dumdub[0][1]),
+                         &(dumdub[0][2]),
+                         &(dumdub[0][3]),
+                         &(dumdub[0][4]),
+                         &(dumdub[0][5]),
+                         &canary);
+
+    if (strlen(inputrecStrings->deform) > 0 && ndeform != 6)
+    {
+        warning_error(wi,
+                      gmx::formatString(
+                              "Cannot parse exactly 6 box deformation velocities from string '%s'",
+                              inputrecStrings->deform)
+                              .c_str());
     }
     for (i = 0; i < 3; i++)
     {
@@ -2419,7 +2777,7 @@ void get_ir(const char *mdparin, const char *mdparout,
     ir->deform[YY][XX] = dumdub[0][3];
     ir->deform[ZZ][XX] = dumdub[0][4];
     ir->deform[ZZ][YY] = dumdub[0][5];
-    if (ir->epc != epcNO)
+    if (ir->epc != PressureCoupling::No)
     {
         for (i = 0; i < 3; i++)
         {
@@ -2441,7 +2799,10 @@ void get_ir(const char *mdparin, const char *mdparout,
                     {
                         if (ir->compress[m][j] != 0)
                         {
-                            sprintf(warn_buf, "An off-diagonal box element has deform set while compressibility > 0 for the same component of another box vector, this might lead to spurious periodicity effects.");
+                            sprintf(warn_buf,
+                                    "An off-diagonal box element has deform set while "
+                                    "compressibility > 0 for the same component of another box "
+                                    "vector, this might lead to spurious periodicity effects.");
                             warning(wi, warn_buf);
                         }
                     }
@@ -2451,7 +2812,7 @@ void get_ir(const char *mdparin, const char *mdparout,
     }
 
     /* Ion/water position swapping checks */
-    if (ir->eSwapCoords != eswapNO)
+    if (ir->eSwapCoords != SwapType::No)
     {
         if (ir->swap->nstswap < 1)
         {
@@ -2467,31 +2828,28 @@ void get_ir(const char *mdparin, const char *mdparout,
         }
     }
 
-    sfree(dumstr[0]);
-    sfree(dumstr[1]);
-}
-
-static int search_QMstring(const char *s, int ng, const char *gn[])
-{
-    /* same as normal search_string, but this one searches QM strings */
-    int i;
-
-    for (i = 0; (i < ng); i++)
+    /* Set up MTS levels, this needs to happen before checking AWH parameters */
+    if (ir->useMts)
     {
-        if (gmx_strcasecmp(s, gn[i]) == 0)
+        std::vector<std::string> errorMessages;
+        ir->mtsLevels = gmx::setupMtsLevels(opts->mtsOpts, &errorMessages);
+
+        for (const auto& errorMessage : errorMessages)
         {
-            return i;
+            warning_error(wi, errorMessage.c_str());
         }
     }
 
-    gmx_fatal(FARGS, "this QM method or basisset (%s) is not implemented\n!", s);
-} /* search_QMstring */
+    if (ir->bDoAwh)
+    {
+        gmx::checkAwhParams(*ir->awhParams, *ir, wi);
+    }
+
+    sfree(dumstr[0]);
+    sfree(dumstr[1]);
+}
 
-/* We would like gn to be const as well, but C doesn't allow this */
-/* TODO this is utility functionality (search for the index of a
-   string in a collection), so should be refactored and located more
-   centrally. */
-int search_string(const char *s, int ng, char *gn[])
+int search_string(const char* s, int ng, char* const gn[])
 {
     int i;
 
@@ -2511,19 +2869,50 @@ int search_string(const char *s, int ng, char *gn[])
               s);
 }
 
-static bool do_numbering(int natoms, SimulationGroups *groups,
-                         gmx::ArrayRef<std::string> groupsFromMdpFile,
-                         t_blocka *block, char *gnames[],
-                         SimulationAtomGroupType gtype, int restnm,
-                         int grptp, bool bVerbose,
-                         warninp_t wi)
+static void atomGroupRangeValidation(int natoms, int groupIndex, const t_blocka& block)
 {
-    unsigned short           *cbuf;
-    AtomGroupIndices         *grps = &(groups->groups[gtype]);
-    int                       j, gid, aj, ognr, ntot = 0;
-    const char               *title;
-    bool                      bRest;
-    char                      warn_buf[STRLEN];
+    /* Now go over the atoms in the group */
+    for (int j = block.index[groupIndex]; (j < block.index[groupIndex + 1]); j++)
+    {
+        int aj = block.a[j];
+
+        /* Range checking */
+        if ((aj < 0) || (aj >= natoms))
+        {
+            gmx_fatal(FARGS, "Invalid atom number %d in indexfile", aj + 1);
+        }
+    }
+}
+
+/*! Creates the groups of atom indices for group type \p gtype
+ *
+ * \param[in] natoms  The total number of atoms in the system
+ * \param[in,out] groups  Index \p gtype in this list of list of groups will be set
+ * \param[in] groupsFromMdpFile  The list of group names set for \p gtype in the mdp file
+ * \param[in] block       The list of atom indices for all available index groups
+ * \param[in] gnames      The list of names for all available index groups
+ * \param[in] gtype       The group type to creates groups for
+ * \param[in] restnm      The index of rest group name in \p gnames
+ * \param[in] coverage    How to treat coverage of all atoms in the system
+ * \param[in] bVerbose    Whether to print when we make a rest group
+ * \param[in,out] wi      List of warnings
+ */
+static void do_numbering(const int                        natoms,
+                         SimulationGroups*                groups,
+                         gmx::ArrayRef<const std::string> groupsFromMdpFile,
+                         const t_blocka*                  block,
+                         char* const                      gnames[],
+                         const SimulationAtomGroupType    gtype,
+                         const int                        restnm,
+                         const GroupCoverage              coverage,
+                         const bool                       bVerbose,
+                         warninp_t                        wi)
+{
+    unsigned short*   cbuf;
+    AtomGroupIndices* grps = &(groups->groups[gtype]);
+    int               ntot = 0;
+    const char*       title;
+    char              warn_buf[STRLEN];
 
     title = shortName(gtype);
 
@@ -2537,34 +2926,27 @@ static bool do_numbering(int natoms, SimulationGroups *groups,
     for (int i = 0; i != groupsFromMdpFile.ssize(); ++i)
     {
         /* Lookup the group name in the block structure */
-        gid = search_string(groupsFromMdpFile[i].c_str(), block->nr, gnames);
-        if ((grptp != egrptpONE) || (i == 0))
+        const int gid = search_string(groupsFromMdpFile[i].c_str(), block->nr, gnames);
+        if ((coverage != GroupCoverage::OneGroup) || (i == 0))
         {
             grps->emplace_back(gid);
         }
-
+        GMX_ASSERT(block, "Can't have a nullptr block");
+        atomGroupRangeValidation(natoms, gid, *block);
         /* Now go over the atoms in the group */
-        for (j = block->index[gid]; (j < block->index[gid+1]); j++)
+        for (int j = block->index[gid]; (j < block->index[gid + 1]); j++)
         {
-
-            aj = block->a[j];
-
-            /* Range checking */
-            if ((aj < 0) || (aj >= natoms))
-            {
-                gmx_fatal(FARGS, "Invalid atom number %d in indexfile", aj + 1);
-            }
+            const int aj = block->a[j];
             /* Lookup up the old group number */
-            ognr = cbuf[aj];
+            const int ognr = cbuf[aj];
             if (ognr != NOGID)
             {
-                gmx_fatal(FARGS, "Atom %d in multiple %s groups (%d and %d)",
-                          aj+1, title, ognr+1, i+1);
+                gmx_fatal(FARGS, "Atom %d in multiple %s groups (%d and %d)", aj + 1, title, ognr + 1, i + 1);
             }
             else
             {
                 /* Store the group number in buffer */
-                if (grptp == egrptpONE)
+                if (coverage == GroupCoverage::OneGroup)
                 {
                     cbuf[aj] = 0;
                 }
@@ -2578,42 +2960,36 @@ static bool do_numbering(int natoms, SimulationGroups *groups,
     }
 
     /* Now check whether we have done all atoms */
-    bRest = FALSE;
     if (ntot != natoms)
     {
-        if (grptp == egrptpALL)
+        if (coverage == GroupCoverage::All)
         {
-            gmx_fatal(FARGS, "%d atoms are not part of any of the %s groups",
-                      natoms-ntot, title);
+            gmx_fatal(FARGS, "%d atoms are not part of any of the %s groups", natoms - ntot, title);
         }
-        else if (grptp == egrptpPART)
+        else if (coverage == GroupCoverage::Partial)
         {
-            sprintf(warn_buf, "%d atoms are not part of any of the %s groups",
-                    natoms-ntot, title);
+            sprintf(warn_buf, "%d atoms are not part of any of the %s groups", natoms - ntot, title);
             warning_note(wi, warn_buf);
         }
         /* Assign all atoms currently unassigned to a rest group */
-        for (j = 0; (j < natoms); j++)
+        for (int j = 0; (j < natoms); j++)
         {
             if (cbuf[j] == NOGID)
             {
                 cbuf[j] = grps->size();
-                bRest   = TRUE;
             }
         }
-        if (grptp != egrptpPART)
+        if (coverage != GroupCoverage::Partial)
         {
             if (bVerbose)
             {
-                fprintf(stderr,
-                        "Making dummy/rest group for %s containing %d elements\n",
-                        title, natoms-ntot);
+                fprintf(stderr, "Making dummy/rest group for %s containing %d elements\n", title, natoms - ntot);
             }
             /* Add group name "rest" */
             grps->emplace_back(restnm);
 
             /* Assign the rest name to all atoms not currently assigned to a group */
-            for (j = 0; (j < natoms); j++)
+            for (int j = 0; (j < natoms); j++)
             {
                 if (cbuf[j] == NOGID)
                 {
@@ -2638,19 +3014,17 @@ static bool do_numbering(int natoms, SimulationGroups *groups,
     }
 
     sfree(cbuf);
-
-    return (bRest && grptp == egrptpPART);
 }
 
-static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
+static void calc_nrdf(const gmx_mtop_t* mtop, t_inputrec* ir, char** gnames)
 {
-    t_grpopts              *opts;
-    pull_params_t          *pull;
-    int                     natoms, imin, jmin;
-    int                    *nrdf2, *na_vcm, na_tot;
-    double                 *nrdf_tc, *nrdf_vcm, nrdf_uc, *nrdf_vcm_sub;
-    ivec                   *dof_vcm;
-    int                     as;
+    t_grpopts*     opts;
+    pull_params_tpull;
+    int            natoms, imin, jmin;
+    int *          nrdf2, *na_vcm, na_tot;
+    double *       nrdf_tc, *nrdf_vcm, nrdf_uc, *nrdf_vcm_sub;
+    ivec*          dof_vcm;
+    int            as;
 
     /* Calculate nrdf.
      * First calc 3xnr-atoms for each group
@@ -2661,26 +3035,28 @@ static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
 
     opts = &ir->opts;
 
-    const SimulationGroups &groups = mtop->groups;
-    natoms = mtop->natoms;
+    const SimulationGroupsgroups = mtop->groups;
+    natoms                         = mtop->natoms;
 
     /* Allocate one more for a possible rest group */
     /* We need to sum degrees of freedom into doubles,
      * since floats give too low nrdf's above 3 million atoms.
      */
-    snew(nrdf_tc, groups.groups[SimulationAtomGroupType::TemperatureCoupling].size()+1);
-    snew(nrdf_vcm, groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval].size()+1);
-    snew(dof_vcm, groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval].size()+1);
-    snew(na_vcm, groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval].size()+1);
-    snew(nrdf_vcm_sub, groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval].size()+1);
+    snew(nrdf_tc, groups.groups[SimulationAtomGroupType::TemperatureCoupling].size() + 1);
+    snew(nrdf_vcm, groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval].size() + 1);
+    snew(dof_vcm, groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval].size() + 1);
+    snew(na_vcm, groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval].size() + 1);
+    snew(nrdf_vcm_sub, groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval].size() + 1);
 
     for (gmx::index i = 0; i < gmx::ssize(groups.groups[SimulationAtomGroupType::TemperatureCoupling]); i++)
     {
         nrdf_tc[i] = 0;
     }
-    for (gmx::index i = 0; i < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval])+1; i++)
+    for (gmx::index i = 0;
+         i < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval]) + 1;
+         i++)
     {
-        nrdf_vcm[i]     = 0;
+        nrdf_vcm[i] = 0;
         clear_ivec(dof_vcm[i]);
         na_vcm[i]       = 0;
         nrdf_vcm_sub[i] = 0;
@@ -2688,10 +3064,10 @@ static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
     snew(nrdf2, natoms);
     for (const AtomProxy atomP : AtomRange(*mtop))
     {
-        const t_atom &local = atomP.atom();
+        const t_atomlocal = atomP.atom();
         int           i     = atomP.globalAtomNumber();
-        nrdf2[i] = 0;
-        if (local.ptype == eptAtom || local.ptype == eptNucleus)
+        nrdf2[i]            = 0;
+        if (local.ptype == ParticleType::Atom || local.ptype == ParticleType::Nucleus)
         {
             int g = getGroupType(groups, SimulationAtomGroupType::Freeze, i);
             for (int d = 0; d < DIM; d++)
@@ -2699,27 +3075,30 @@ static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
                 if (opts->nFreeze[g][d] == 0)
                 {
                     /* Add one DOF for particle i (counted as 2*1) */
-                    nrdf2[i]                              += 2;
+                    nrdf2[i] += 2;
                     /* VCM group i has dim d as a DOF */
-                    dof_vcm[getGroupType(groups, SimulationAtomGroupType::MassCenterVelocityRemoval, i)][d]  = 1;
+                    dof_vcm[getGroupType(groups, SimulationAtomGroupType::MassCenterVelocityRemoval, i)][d] =
+                            1;
                 }
             }
-            nrdf_tc [getGroupType(groups, SimulationAtomGroupType::TemperatureCoupling, i)]       += 0.5*nrdf2[i];
-            nrdf_vcm[getGroupType(groups, SimulationAtomGroupType::MassCenterVelocityRemoval, i)] += 0.5*nrdf2[i];
+            nrdf_tc[getGroupType(groups, SimulationAtomGroupType::TemperatureCoupling, i)] +=
+                    0.5 * nrdf2[i];
+            nrdf_vcm[getGroupType(groups, SimulationAtomGroupType::MassCenterVelocityRemoval, i)] +=
+                    0.5 * nrdf2[i];
         }
     }
 
     as = 0;
-    for (const gmx_molblock_t &molb : mtop->molblock)
+    for (const gmx_molblock_tmolb : mtop->molblock)
     {
-        const gmx_moltype_t &molt = mtop->moltype[molb.type];
-        const t_atom        *atom = molt.atoms.atom;
+        const gmx_moltype_tmolt = mtop->moltype[molb.type];
+        const t_atom*        atom = molt.atoms.atom;
         for (int mol = 0; mol < molb.nmol; mol++)
         {
             for (int ftype = F_CONSTR; ftype <= F_CONSTRNC; ftype++)
             {
                 gmx::ArrayRef<const int> ia = molt.ilist[ftype].iatoms;
-                for (int i = 0; i < molt.ilist[ftype].size(); )
+                for (int i = 0; i < molt.ilist[ftype].size();)
                 {
                     /* Subtract degrees of freedom for the constraints,
                      * if the particles still have degrees of freedom left.
@@ -2730,10 +3109,10 @@ static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
                      */
                     int ai = as + ia[i + 1];
                     int aj = as + ia[i + 2];
-                    if (((atom[ia[i + 1]].ptype == eptNucleus) ||
-                         (atom[ia[i + 1]].ptype == eptAtom)) &&
-                        ((atom[ia[i + 2]].ptype == eptNucleus) ||
-                         (atom[ia[i + 2]].ptype == eptAtom)))
+                    if (((atom[ia[i + 1]].ptype == ParticleType::Nucleus)
+                         || (atom[ia[i + 1]].ptype == ParticleType::Atom))
+                        && ((atom[ia[i + 2]].ptype == ParticleType::Nucleus)
+                            || (atom[ia[i + 2]].ptype == ParticleType::Atom)))
                     {
                         if (nrdf2[ai] > 0)
                         {
@@ -2751,31 +3130,37 @@ static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
                         {
                             imin = 2;
                         }
-                        imin       = std::min(imin, nrdf2[ai]);
-                        jmin       = std::min(jmin, nrdf2[aj]);
+                        imin = std::min(imin, nrdf2[ai]);
+                        jmin = std::min(jmin, nrdf2[aj]);
                         nrdf2[ai] -= imin;
                         nrdf2[aj] -= jmin;
-                        nrdf_tc [getGroupType(groups, SimulationAtomGroupType::TemperatureCoupling, ai)]       -= 0.5*imin;
-                        nrdf_tc [getGroupType(groups, SimulationAtomGroupType::TemperatureCoupling, aj)]       -= 0.5*jmin;
-                        nrdf_vcm[getGroupType(groups, SimulationAtomGroupType::MassCenterVelocityRemoval, ai)] -= 0.5*imin;
-                        nrdf_vcm[getGroupType(groups, SimulationAtomGroupType::MassCenterVelocityRemoval, aj)] -= 0.5*jmin;
+                        nrdf_tc[getGroupType(groups, SimulationAtomGroupType::TemperatureCoupling, ai)] -=
+                                0.5 * imin;
+                        nrdf_tc[getGroupType(groups, SimulationAtomGroupType::TemperatureCoupling, aj)] -=
+                                0.5 * jmin;
+                        nrdf_vcm[getGroupType(groups, SimulationAtomGroupType::MassCenterVelocityRemoval, ai)] -=
+                                0.5 * imin;
+                        nrdf_vcm[getGroupType(groups, SimulationAtomGroupType::MassCenterVelocityRemoval, aj)] -=
+                                0.5 * jmin;
                     }
-                    i  += interaction_function[ftype].nratoms+1;
+                    i += interaction_function[ftype].nratoms + 1;
                 }
             }
             gmx::ArrayRef<const int> ia = molt.ilist[F_SETTLE].iatoms;
-            for (int i = 0; i < molt.ilist[F_SETTLE].size(); )
+            for (int i = 0; i < molt.ilist[F_SETTLE].size();)
             {
                 /* Subtract 1 dof from every atom in the SETTLE */
                 for (int j = 0; j < 3; j++)
                 {
-                    int ai         = as + ia[i + 1 + j];
-                    imin       = std::min(2, nrdf2[ai]);
+                    int ai = as + ia[i + 1 + j];
+                    imin   = std::min(2, nrdf2[ai]);
                     nrdf2[ai] -= imin;
-                    nrdf_tc [getGroupType(groups, SimulationAtomGroupType::TemperatureCoupling, ai)]       -= 0.5*imin;
-                    nrdf_vcm[getGroupType(groups, SimulationAtomGroupType::MassCenterVelocityRemoval, ai)] -= 0.5*imin;
+                    nrdf_tc[getGroupType(groups, SimulationAtomGroupType::TemperatureCoupling, ai)] -=
+                            0.5 * imin;
+                    nrdf_vcm[getGroupType(groups, SimulationAtomGroupType::MassCenterVelocityRemoval, ai)] -=
+                            0.5 * imin;
                 }
-                i  += 4;
+                i += 4;
             }
             as += molt.atoms.nr;
         }
@@ -2789,11 +3174,11 @@ static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
          * belong to different TC or VCM groups it is anyhow difficult
          * to determine the optimal nrdf assignment.
          */
-        pull = ir->pull;
+        pull = ir->pull.get();
 
         for (int i = 0; i < pull->ncoord; i++)
         {
-            if (pull->coord[i].eType != epullCONSTRAINT)
+            if (pull->coord[i].eType != PullingAlgorithm::Constraint)
             {
                 continue;
             }
@@ -2802,19 +3187,25 @@ static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
 
             for (int j = 0; j < 2; j++)
             {
-                const t_pull_group *pgrp;
+                const t_pull_grouppgrp;
 
                 pgrp = &pull->group[pull->coord[i].group[j]];
 
-                if (pgrp->nat > 0)
+                if (!pgrp->ind.empty())
                 {
                     /* Subtract 1/2 dof from each group */
                     int ai = pgrp->ind[0];
-                    nrdf_tc [getGroupType(groups, SimulationAtomGroupType::TemperatureCoupling, ai)]       -= 0.5*imin;
-                    nrdf_vcm[getGroupType(groups, SimulationAtomGroupType::MassCenterVelocityRemoval, ai)] -= 0.5*imin;
+                    nrdf_tc[getGroupType(groups, SimulationAtomGroupType::TemperatureCoupling, ai)] -=
+                            0.5 * imin;
+                    nrdf_vcm[getGroupType(groups, SimulationAtomGroupType::MassCenterVelocityRemoval, ai)] -=
+                            0.5 * imin;
                     if (nrdf_tc[getGroupType(groups, SimulationAtomGroupType::TemperatureCoupling, ai)] < 0)
                     {
-                        gmx_fatal(FARGS, "Center of mass pulling constraints caused the number of degrees of freedom for temperature coupling group %s to be negative", gnames[groups.groups[SimulationAtomGroupType::TemperatureCoupling][getGroupType(groups, SimulationAtomGroupType::TemperatureCoupling, ai)]]);
+                        gmx_fatal(FARGS,
+                                  "Center of mass pulling constraints caused the number of degrees "
+                                  "of freedom for temperature coupling group %s to be negative",
+                                  gnames[groups.groups[SimulationAtomGroupType::TemperatureCoupling][getGroupType(
+                                          groups, SimulationAtomGroupType::TemperatureCoupling, ai)]]);
                     }
                 }
                 else
@@ -2828,21 +3219,25 @@ static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
 
     if (ir->nstcomm != 0)
     {
-        int ndim_rm_vcm;
+        GMX_RELEASE_ASSERT(!groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval].empty(),
+                           "Expect at least one group when removing COM motion");
 
         /* We remove COM motion up to dim ndof_com() */
-        ndim_rm_vcm = ndof_com(ir);
+        const int ndim_rm_vcm = ndof_com(ir);
 
         /* Subtract ndim_rm_vcm (or less with frozen dimensions) from
          * the number of degrees of freedom in each vcm group when COM
          * translation is removed and 6 when rotation is removed as well.
+         * Note that we do not and should not include the rest group here.
          */
-        for (gmx::index j = 0; j < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval])+1; j++)
+        for (gmx::index j = 0;
+             j < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval]);
+             j++)
         {
             switch (ir->comm_mode)
             {
-                case ecmLINEAR:
-                case ecmLINEAR_ACCELERATION_CORRECTION:
+                case ComRemovalAlgorithm::Linear:
+                case ComRemovalAlgorithm::LinearAccelerationCorrection:
                     nrdf_vcm_sub[j] = 0;
                     for (int d = 0; d < ndim_rm_vcm; d++)
                     {
@@ -2852,18 +3247,19 @@ static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
                         }
                     }
                     break;
-                case ecmANGULAR:
-                    nrdf_vcm_sub[j] = 6;
-                    break;
-                default:
-                    gmx_incons("Checking comm_mode");
+                case ComRemovalAlgorithm::Angular: nrdf_vcm_sub[j] = 6; break;
+                default: gmx_incons("Checking comm_mode");
             }
         }
 
-        for (gmx::index i = 0; i < gmx::ssize(groups.groups[SimulationAtomGroupType::TemperatureCoupling]); i++)
+        for (gmx::index i = 0;
+             i < gmx::ssize(groups.groups[SimulationAtomGroupType::TemperatureCoupling]);
+             i++)
         {
             /* Count the number of atoms of TC group i for every VCM group */
-            for (gmx::index j = 0; j < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval])+1; j++)
+            for (gmx::index j = 0;
+                 j < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval]) + 1;
+                 j++)
             {
                 na_vcm[j] = 0;
             }
@@ -2881,12 +3277,14 @@ static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
              */
             nrdf_uc    = nrdf_tc[i];
             nrdf_tc[i] = 0;
-            for (gmx::index j = 0; j < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval])+1; j++)
+            for (gmx::index j = 0;
+                 j < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval]) + 1;
+                 j++)
             {
                 if (nrdf_vcm[j] > nrdf_vcm_sub[j])
                 {
-                    nrdf_tc[i] += nrdf_uc*(static_cast<double>(na_vcm[j])/static_cast<double>(na_tot))*
-                        (nrdf_vcm[j] - nrdf_vcm_sub[j])/nrdf_vcm[j];
+                    nrdf_tc[i] += nrdf_uc * (static_cast<double>(na_vcm[j]) / static_cast<double>(na_tot))
+                                  * (nrdf_vcm[j] - nrdf_vcm_sub[j]) / nrdf_vcm[j];
                 }
             }
         }
@@ -2900,7 +3298,8 @@ static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
         }
         fprintf(stderr,
                 "Number of degrees of freedom in T-Coupling group %s is %.2f\n",
-                gnames[groups.groups[SimulationAtomGroupType::TemperatureCoupling][i]], opts->nrdf[i]);
+                gnames[groups.groups[SimulationAtomGroupType::TemperatureCoupling][i]],
+                opts->nrdf[i]);
     }
 
     sfree(nrdf2);
@@ -2911,18 +3310,16 @@ static void calc_nrdf(const gmx_mtop_t *mtop, t_inputrec *ir, char **gnames)
     sfree(nrdf_vcm_sub);
 }
 
-static bool do_egp_flag(t_inputrec *ir, SimulationGroups *groups,
-                        const char *option, const char *val, int flag)
+static bool do_egp_flag(t_inputrec* ir, SimulationGroups* groups, const char* option, const char* val, int flag)
 {
     /* The maximum number of energy group pairs would be MAXPTR*(MAXPTR+1)/2.
      * But since this is much larger than STRLEN, such a line can not be parsed.
      * The real maximum is the number of names that fit in a string: STRLEN/2.
      */
-#define EGP_MAX (STRLEN/2)
-    int      j, k, nr;
-    bool     bSet;
+    int  j, k, nr;
+    bool bSet;
 
-    auto     names = gmx::splitString(val);
+    auto names = gmx::splitString(val);
     if (names.size() % 2 != 0)
     {
         gmx_fatal(FARGS, "The number of groups for %s is odd", option);
@@ -2933,31 +3330,33 @@ static bool do_egp_flag(t_inputrec *ir, SimulationGroups *groups,
     {
         // TODO this needs to be replaced by a solution using std::find_if
         j = 0;
-        while ((j < nr) &&
-               gmx_strcasecmp(names[2*i].c_str(), *(groups->groupNames[groups->groups[SimulationAtomGroupType::EnergyOutput][j]])))
+        while ((j < nr)
+               && gmx_strcasecmp(
+                       names[2 * i].c_str(),
+                       *(groups->groupNames[groups->groups[SimulationAtomGroupType::EnergyOutput][j]])))
         {
             j++;
         }
         if (j == nr)
         {
-            gmx_fatal(FARGS, "%s in %s is not an energy group\n",
-                      names[2*i].c_str(), option);
+            gmx_fatal(FARGS, "%s in %s is not an energy group\n", names[2 * i].c_str(), option);
         }
         k = 0;
-        while ((k < nr) &&
-               gmx_strcasecmp(names[2*i+1].c_str(), *(groups->groupNames[groups->groups[SimulationAtomGroupType::EnergyOutput][k]])))
+        while ((k < nr)
+               && gmx_strcasecmp(
+                       names[2 * i + 1].c_str(),
+                       *(groups->groupNames[groups->groups[SimulationAtomGroupType::EnergyOutput][k]])))
         {
             k++;
         }
         if (k == nr)
         {
-            gmx_fatal(FARGS, "%s in %s is not an energy group\n",
-                      names[2*i+1].c_str(), option);
+            gmx_fatal(FARGS, "%s in %s is not an energy group\n", names[2 * i + 1].c_str(), option);
         }
         if ((j < nr) && (k < nr))
         {
-            ir->opts.egp_flags[nr*j+k] |= flag;
-            ir->opts.egp_flags[nr*k+j] |= flag;
+            ir->opts.egp_flags[nr * j + k] |= flag;
+            ir->opts.egp_flags[nr * k + j] |= flag;
             bSet = TRUE;
         }
     }
@@ -2966,19 +3365,20 @@ static bool do_egp_flag(t_inputrec *ir, SimulationGroups *groups,
 }
 
 
-static void make_swap_groups(
-        t_swapcoords  *swap,
-        t_blocka      *grps,
-        char         **gnames)
+static void make_swap_groups(t_swapcoords* swap, t_blocka* grps, char** gnames)
 {
     int          ig = -1, i = 0, gind;
-    t_swapGroup *swapg;
+    t_swapGroupswapg;
 
 
     /* Just a quick check here, more thorough checks are in mdrun */
-    if (strcmp(swap->grp[eGrpSplit0].molname, swap->grp[eGrpSplit1].molname) == 0)
+    if (strcmp(swap->grp[static_cast<int>(SwapGroupSplittingType::Split0)].molname,
+               swap->grp[static_cast<int>(SwapGroupSplittingType::Split1)].molname)
+        == 0)
     {
-        gmx_fatal(FARGS, "The split groups can not both be '%s'.", swap->grp[eGrpSplit0].molname);
+        gmx_fatal(FARGS,
+                  "The split groups can not both be '%s'.",
+                  swap->grp[static_cast<int>(SwapGroupSplittingType::Split0)].molname);
     }
 
     /* Get the index atoms of the split0, split1, solvent, and swap groups */
@@ -2986,17 +3386,19 @@ static void make_swap_groups(
     {
         swapg      = &swap->grp[ig];
         gind       = search_string(swap->grp[ig].molname, grps->nr, gnames);
-        swapg->nat = grps->index[gind+1] - grps->index[gind];
+        swapg->nat = grps->index[gind + 1] - grps->index[gind];
 
         if (swapg->nat > 0)
         {
-            fprintf(stderr, "%s group '%s' contains %d atoms.\n",
-                    ig < 3 ? eSwapFixedGrp_names[ig] : "Swap",
-                    swap->grp[ig].molname, swapg->nat);
+            fprintf(stderr,
+                    "%s group '%s' contains %d atoms.\n",
+                    ig < 3 ? enumValueToString(static_cast<SwapGroupSplittingType>(ig)) : "Swap",
+                    swap->grp[ig].molname,
+                    swapg->nat);
             snew(swapg->ind, swapg->nat);
             for (i = 0; i < swapg->nat; i++)
             {
-                swapg->ind[i] = grps->a[grps->index[gind]+i];
+                swapg->ind[i] = grps->a[grps->index[gind] + i];
             }
         }
         else
@@ -3007,43 +3409,131 @@ static void make_swap_groups(
 }
 
 
-static void make_IMD_group(t_IMD *IMDgroup, char *IMDgname, t_blocka *grps, char **gnames)
+static void make_IMD_group(t_IMD* IMDgroup, char* IMDgname, t_blocka* grps, char** gnames)
 {
-    int      ig, i;
+    int ig, i;
 
 
     ig            = search_string(IMDgname, grps->nr, gnames);
-    IMDgroup->nat = grps->index[ig+1] - grps->index[ig];
+    IMDgroup->nat = grps->index[ig + 1] - grps->index[ig];
 
     if (IMDgroup->nat > 0)
     {
-        fprintf(stderr, "Group '%s' with %d atoms can be activated for interactive molecular dynamics (IMD).\n",
-                IMDgname, IMDgroup->nat);
+        fprintf(stderr,
+                "Group '%s' with %d atoms can be activated for interactive molecular dynamics "
+                "(IMD).\n",
+                IMDgname,
+                IMDgroup->nat);
         snew(IMDgroup->ind, IMDgroup->nat);
         for (i = 0; i < IMDgroup->nat; i++)
         {
-            IMDgroup->ind[i] = grps->a[grps->index[ig]+i];
+            IMDgroup->ind[i] = grps->a[grps->index[ig] + i];
         }
     }
 }
 
-void do_index(const char* mdparin, const char *ndx,
-              gmx_mtop_t *mtop,
-              bool bVerbose,
-              const gmx::MdModulesNotifier &notifier,
-              t_inputrec *ir,
-              warninp_t wi)
+/* Checks whether atoms are both part of a COM removal group and frozen.
+ * If a fully frozen atom is part of a COM removal group, it is removed
+ * from the COM removal group. A note is issued if such atoms are present.
+ * A warning is issued for atom with one or two dimensions frozen that
+ * are part of a COM removal group (mdrun would need to compute COM mass
+ * per dimension to handle this correctly).
+ * Also issues a warning when non-frozen atoms are not part of a COM
+ * removal group while COM removal is active.
+ */
+static void checkAndUpdateVcmFreezeGroupConsistency(SimulationGroups* groups,
+                                                    const int         numAtoms,
+                                                    const t_grpopts&  opts,
+                                                    warninp_t         wi)
 {
-    t_blocka *defaultIndexGroups;
+    const int vcmRestGroup =
+            std::max(int(groups->groups[SimulationAtomGroupType::MassCenterVelocityRemoval].size()), 1);
+
+    int numFullyFrozenVcmAtoms     = 0;
+    int numPartiallyFrozenVcmAtoms = 0;
+    int numNonVcmAtoms             = 0;
+    for (int a = 0; a < numAtoms; a++)
+    {
+        const int freezeGroup   = getGroupType(*groups, SimulationAtomGroupType::Freeze, a);
+        int       numFrozenDims = 0;
+        for (int d = 0; d < DIM; d++)
+        {
+            numFrozenDims += opts.nFreeze[freezeGroup][d];
+        }
+
+        const int vcmGroup = getGroupType(*groups, SimulationAtomGroupType::MassCenterVelocityRemoval, a);
+        if (vcmGroup < vcmRestGroup)
+        {
+            if (numFrozenDims == DIM)
+            {
+                /* Do not remove COM motion for this fully frozen atom */
+                if (groups->groupNumbers[SimulationAtomGroupType::MassCenterVelocityRemoval].empty())
+                {
+                    groups->groupNumbers[SimulationAtomGroupType::MassCenterVelocityRemoval].resize(
+                            numAtoms, 0);
+                }
+                groups->groupNumbers[SimulationAtomGroupType::MassCenterVelocityRemoval][a] = vcmRestGroup;
+                numFullyFrozenVcmAtoms++;
+            }
+            else if (numFrozenDims > 0)
+            {
+                numPartiallyFrozenVcmAtoms++;
+            }
+        }
+        else if (numFrozenDims < DIM)
+        {
+            numNonVcmAtoms++;
+        }
+    }
+
+    if (numFullyFrozenVcmAtoms > 0)
+    {
+        std::string warningText = gmx::formatString(
+                "There are %d atoms that are fully frozen and part of COMM removal group(s), "
+                "removing these atoms from the COMM removal group(s)",
+                numFullyFrozenVcmAtoms);
+        warning_note(wi, warningText.c_str());
+    }
+    if (numPartiallyFrozenVcmAtoms > 0 && numPartiallyFrozenVcmAtoms < numAtoms)
+    {
+        std::string warningText = gmx::formatString(
+                "There are %d atoms that are frozen along less then %d dimensions and part of COMM "
+                "removal group(s), due to limitations in the code these still contribute to the "
+                "mass of the COM along frozen dimensions and therefore the COMM correction will be "
+                "too small.",
+                numPartiallyFrozenVcmAtoms,
+                DIM);
+        warning(wi, warningText.c_str());
+    }
+    if (numNonVcmAtoms > 0)
+    {
+        std::string warningText = gmx::formatString(
+                "%d atoms are not part of any center of mass motion removal group.\n"
+                "This may lead to artifacts.\n"
+                "In most cases one should use one group for the whole system.",
+                numNonVcmAtoms);
+        warning(wi, warningText.c_str());
+    }
+}
+
+void do_index(const char*                    mdparin,
+              const char*                    ndx,
+              gmx_mtop_t*                    mtop,
+              bool                           bVerbose,
+              const gmx::MDModulesNotifiers& mdModulesNotifiers,
+              t_inputrec*                    ir,
+              warninp_t                      wi)
+{
+    t_blocka* defaultIndexGroups;
     int       natoms;
-    t_symtab *symtab;
+    t_symtabsymtab;
     t_atoms   atoms_all;
-    char      warnbuf[STRLEN], **gnames;
+    char**    gnames;
     int       nr;
     real      tau_min;
     int       nstcmin;
     int       i, j, k, restnm;
-    bool      bExcl, bTable, bAnneal, bRest;
+    bool      bExcl, bTable, bAnneal;
     char      warn_buf[STRLEN];
 
     if (bVerbose)
@@ -3055,7 +3545,7 @@ void do_index(const char* mdparin, const char *ndx,
         snew(defaultIndexGroups, 1);
         snew(defaultIndexGroups->index, 1);
         snew(gnames, 1);
-        atoms_all = gmx_mtop_global_atoms(mtop);
+        atoms_all = gmx_mtop_global_atoms(*mtop);
         analyse(&atoms_all, defaultIndexGroups, &gnames, FALSE, TRUE);
         done_atom(&atoms_all);
     }
@@ -3064,29 +3554,30 @@ void do_index(const char* mdparin, const char *ndx,
         defaultIndexGroups = init_index(ndx, &gnames);
     }
 
-    SimulationGroups *groups = &mtop->groups;
-    natoms = mtop->natoms;
-    symtab = &mtop->symtab;
+    SimulationGroupsgroups = &mtop->groups;
+    natoms                   = mtop->natoms;
+    symtab                   = &mtop->symtab;
 
     for (int i = 0; (i < defaultIndexGroups->nr); i++)
     {
         groups->groupNames.emplace_back(put_symtab(symtab, gnames[i]));
     }
     groups->groupNames.emplace_back(put_symtab(symtab, "rest"));
-    restnm             = groups->groupNames.size() - 1;
+    restnm = groups->groupNames.size() - 1;
     GMX_RELEASE_ASSERT(restnm == defaultIndexGroups->nr, "Size of allocations must match");
-    srenew(gnames, defaultIndexGroups->nr+1);
-    gnames[restnm]   = *(groups->groupNames.back());
+    srenew(gnames, defaultIndexGroups->nr + 1);
+    gnames[restnm] = *(groups->groupNames.back());
 
     set_warning_line(wi, mdparin, -1);
 
-    auto temperatureCouplingTauValues       = gmx::splitString(is->tau_t);
-    auto temperatureCouplingReferenceValues = gmx::splitString(is->ref_t);
-    auto temperatureCouplingGroupNames      = gmx::splitString(is->tcgrps);
-    if (temperatureCouplingTauValues.size() != temperatureCouplingGroupNames.size() ||
-        temperatureCouplingReferenceValues.size() != temperatureCouplingGroupNames.size())
+    auto temperatureCouplingTauValues       = gmx::splitString(inputrecStrings->tau_t);
+    auto temperatureCouplingReferenceValues = gmx::splitString(inputrecStrings->ref_t);
+    auto temperatureCouplingGroupNames      = gmx::splitString(inputrecStrings->tcgrps);
+    if (temperatureCouplingTauValues.size() != temperatureCouplingGroupNames.size()
+        || temperatureCouplingReferenceValues.size() != temperatureCouplingGroupNames.size())
     {
-        gmx_fatal(FARGS, "Invalid T coupling input: %zu groups, %zu ref-t values and "
+        gmx_fatal(FARGS,
+                  "Invalid T coupling input: %zu groups, %zu ref-t values and "
                   "%zu tau-t values",
                   temperatureCouplingGroupNames.size(),
                   temperatureCouplingReferenceValues.size(),
@@ -3094,15 +3585,22 @@ void do_index(const char* mdparin, const char *ndx,
     }
 
     const bool useReferenceTemperature = integratorHasReferenceTemperature(ir);
-    do_numbering(natoms, groups, temperatureCouplingGroupNames, defaultIndexGroups, gnames,
+    do_numbering(natoms,
+                 groups,
+                 temperatureCouplingGroupNames,
+                 defaultIndexGroups,
+                 gnames,
                  SimulationAtomGroupType::TemperatureCoupling,
-                 restnm, useReferenceTemperature ? egrptpALL : egrptpALL_GENREST, bVerbose, wi);
+                 restnm,
+                 useReferenceTemperature ? GroupCoverage::All : GroupCoverage::AllGenerateRest,
+                 bVerbose,
+                 wi);
     nr            = groups->groups[SimulationAtomGroupType::TemperatureCoupling].size();
     ir->opts.ngtc = nr;
     snew(ir->opts.nrdf, nr);
     snew(ir->opts.tau_t, nr);
     snew(ir->opts.ref_t, nr);
-    if (ir->eI == eiBD && ir->bd_fric == 0)
+    if (ir->eI == IntegrationAlgorithm::BD && ir->bd_fric == 0)
     {
         fprintf(stderr, "bd-fric=0, so tau-t will be used as the inverse friction constant(s)\n");
     }
@@ -3118,15 +3616,20 @@ void do_index(const char* mdparin, const char *ndx,
         convertReals(wi, temperatureCouplingTauValues, "tau-t", ir->opts.tau_t);
         for (i = 0; (i < nr); i++)
         {
-            if ((ir->eI == eiBD) && ir->opts.tau_t[i] <= 0)
+            if ((ir->eI == IntegrationAlgorithm::BD) && ir->opts.tau_t[i] <= 0)
             {
-                sprintf(warn_buf, "With integrator %s tau-t should be larger than 0", ei_names[ir->eI]);
+                sprintf(warn_buf,
+                        "With integrator %s tau-t should be larger than 0",
+                        enumValueToString(ir->eI));
                 warning_error(wi, warn_buf);
             }
 
-            if (ir->etc != etcVRESCALE && ir->opts.tau_t[i] == 0)
+            if (ir->etc != TemperatureCoupling::VRescale && ir->opts.tau_t[i] == 0)
             {
-                warning_note(wi, "tau-t = -1 is the value to signal that a group should not have temperature coupling. Treating your use of tau-t = 0 as if you used -1.");
+                warning_note(
+                        wi,
+                        "tau-t = -1 is the value to signal that a group should not have "
+                        "temperature coupling. Treating your use of tau-t = 0 as if you used -1.");
             }
 
             if (ir->opts.tau_t[i] >= 0)
@@ -3134,30 +3637,39 @@ void do_index(const char* mdparin, const char *ndx,
                 tau_min = std::min(tau_min, ir->opts.tau_t[i]);
             }
         }
-        if (ir->etc != etcNO && ir->nsttcouple == -1)
+        if (ir->etc != TemperatureCoupling::No && ir->nsttcouple == -1)
         {
             ir->nsttcouple = ir_optimal_nsttcouple(ir);
         }
 
         if (EI_VV(ir->eI))
         {
-            if ((ir->etc == etcNOSEHOOVER) && (ir->epc == epcBERENDSEN))
+            if ((ir->etc == TemperatureCoupling::NoseHoover) && (ir->epc == PressureCoupling::Berendsen))
             {
-                gmx_fatal(FARGS, "Cannot do Nose-Hoover temperature with Berendsen pressure control with md-vv; use either vrescale temperature with berendsen pressure or Nose-Hoover temperature with MTTK pressure");
+                gmx_fatal(FARGS,
+                          "Cannot do Nose-Hoover temperature with Berendsen pressure control with "
+                          "md-vv; use either vrescale temperature with berendsen pressure or "
+                          "Nose-Hoover temperature with MTTK pressure");
             }
-            if (ir->epc == epcMTTK)
+            if (ir->epc == PressureCoupling::Mttk)
             {
-                if (ir->etc != etcNOSEHOOVER)
+                if (ir->etc != TemperatureCoupling::NoseHoover)
                 {
-                    gmx_fatal(FARGS, "Cannot do MTTK pressure coupling without Nose-Hoover temperature control");
+                    gmx_fatal(FARGS,
+                              "Cannot do MTTK pressure coupling without Nose-Hoover temperature "
+                              "control");
                 }
                 else
                 {
                     if (ir->nstpcouple != ir->nsttcouple)
                     {
-                        int mincouple = std::min(ir->nstpcouple, ir->nsttcouple);
+                        int mincouple  = std::min(ir->nstpcouple, ir->nsttcouple);
                         ir->nstpcouple = ir->nsttcouple = mincouple;
-                        sprintf(warn_buf, "for current Trotter decomposition methods with vv, nsttcouple and nstpcouple must be equal.  Both have been reset to min(nsttcouple,nstpcouple) = %d", mincouple);
+                        sprintf(warn_buf,
+                                "for current Trotter decomposition methods with vv, nsttcouple and "
+                                "nstpcouple must be equal.  Both have been reset to "
+                                "min(nsttcouple,nstpcouple) = %d",
+                                mincouple);
                         warning_note(wi, warn_buf);
                     }
                 }
@@ -3171,19 +3683,25 @@ void do_index(const char* mdparin, const char *ndx,
             if (ir->nsttcouple != 1)
             {
                 ir->nsttcouple = 1;
-                sprintf(warn_buf, "Andersen temperature control methods assume nsttcouple = 1; there is no need for larger nsttcouple > 1, since no global parameters are computed. nsttcouple has been reset to 1");
+                sprintf(warn_buf,
+                        "Andersen temperature control methods assume nsttcouple = 1; there is no "
+                        "need for larger nsttcouple > 1, since no global parameters are computed. "
+                        "nsttcouple has been reset to 1");
                 warning_note(wi, warn_buf);
             }
         }
         nstcmin = tcouple_min_integration_steps(ir->etc);
         if (nstcmin > 1)
         {
-            if (tau_min/(ir->delta_t*ir->nsttcouple) < nstcmin - 10*GMX_REAL_EPS)
+            if (tau_min / (ir->delta_t * ir->nsttcouple) < nstcmin - 10 * GMX_REAL_EPS)
             {
-                sprintf(warn_buf, "For proper integration of the %s thermostat, tau-t (%g) should be at least %d times larger than nsttcouple*dt (%g)",
-                        ETCOUPLTYPE(ir->etc),
-                        tau_min, nstcmin,
-                        ir->nsttcouple*ir->delta_t);
+                sprintf(warn_buf,
+                        "For proper integration of the %s thermostat, tau-t (%g) should be at "
+                        "least %d times larger than nsttcouple*dt (%g)",
+                        enumValueToString(ir->etc),
+                        tau_min,
+                        nstcmin,
+                        ir->nsttcouple * ir->delta_t);
                 warning(wi, warn_buf);
             }
         }
@@ -3204,17 +3722,18 @@ void do_index(const char* mdparin, const char *ndx,
     }
 
     /* Simulated annealing for each group. There are nr groups */
-    auto simulatedAnnealingGroupNames = gmx::splitString(is->anneal);
-    if (simulatedAnnealingGroupNames.size() == 1 &&
-        gmx::equalCaseInsensitive(simulatedAnnealingGroupNames[0], "N", 1))
+    auto simulatedAnnealingGroupNames = gmx::splitString(inputrecStrings->anneal);
+    if (simulatedAnnealingGroupNames.size() == 1
+        && gmx::equalCaseInsensitive(simulatedAnnealingGroupNames[0], "N", 1))
     {
         simulatedAnnealingGroupNames.resize(0);
     }
-    if (!simulatedAnnealingGroupNames.empty() &&
-        gmx::ssize(simulatedAnnealingGroupNames) != nr)
+    if (!simulatedAnnealingGroupNames.empty() && gmx::ssize(simulatedAnnealingGroupNames) != nr)
     {
-        gmx_fatal(FARGS, "Wrong number of annealing values: %zu (for %d groups)\n",
-                  simulatedAnnealingGroupNames.size(), nr);
+        gmx_fatal(FARGS,
+                  "Wrong number of annealing values: %zu (for %d groups)\n",
+                  simulatedAnnealingGroupNames.size(),
+                  nr);
     }
     else
     {
@@ -3224,7 +3743,7 @@ void do_index(const char* mdparin, const char *ndx,
         snew(ir->opts.anneal_temp, nr);
         for (i = 0; i < nr; i++)
         {
-            ir->opts.annealing[i]      = eannNO;
+            ir->opts.annealing[i]      = SimulatedAnnealing::No;
             ir->opts.anneal_npoints[i] = 0;
             ir->opts.anneal_time[i]    = nullptr;
             ir->opts.anneal_temp[i]    = nullptr;
@@ -3236,27 +3755,29 @@ void do_index(const char* mdparin, const char *ndx,
             {
                 if (gmx::equalCaseInsensitive(simulatedAnnealingGroupNames[i], "N", 1))
                 {
-                    ir->opts.annealing[i] = eannNO;
+                    ir->opts.annealing[i] = SimulatedAnnealing::No;
                 }
                 else if (gmx::equalCaseInsensitive(simulatedAnnealingGroupNames[i], "S", 1))
                 {
-                    ir->opts.annealing[i] = eannSINGLE;
+                    ir->opts.annealing[i] = SimulatedAnnealing::Single;
                     bAnneal               = TRUE;
                 }
                 else if (gmx::equalCaseInsensitive(simulatedAnnealingGroupNames[i], "P", 1))
                 {
-                    ir->opts.annealing[i] = eannPERIODIC;
+                    ir->opts.annealing[i] = SimulatedAnnealing::Periodic;
                     bAnneal               = TRUE;
                 }
             }
             if (bAnneal)
             {
                 /* Read the other fields too */
-                auto simulatedAnnealingPoints = gmx::splitString(is->anneal_npoints);
+                auto simulatedAnnealingPoints = gmx::splitString(inputrecStrings->anneal_npoints);
                 if (simulatedAnnealingPoints.size() != simulatedAnnealingGroupNames.size())
                 {
-                    gmx_fatal(FARGS, "Found %zu annealing-npoints values for %zu groups\n",
-                              simulatedAnnealingPoints.size(), simulatedAnnealingGroupNames.size());
+                    gmx_fatal(FARGS,
+                              "Found %zu annealing-npoints values for %zu groups\n",
+                              simulatedAnnealingPoints.size(),
+                              simulatedAnnealingGroupNames.size());
                 }
                 convertInts(wi, simulatedAnnealingPoints, "annealing points", ir->opts.anneal_npoints);
                 size_t numSimulatedAnnealingFields = 0;
@@ -3264,31 +3785,40 @@ void do_index(const char* mdparin, const char *ndx,
                 {
                     if (ir->opts.anneal_npoints[i] == 1)
                     {
-                        gmx_fatal(FARGS, "Please specify at least a start and an end point for annealing\n");
+                        gmx_fatal(
+                                FARGS,
+                                "Please specify at least a start and an end point for annealing\n");
                     }
                     snew(ir->opts.anneal_time[i], ir->opts.anneal_npoints[i]);
                     snew(ir->opts.anneal_temp[i], ir->opts.anneal_npoints[i]);
                     numSimulatedAnnealingFields += ir->opts.anneal_npoints[i];
                 }
 
-                auto simulatedAnnealingTimes = gmx::splitString(is->anneal_time);
+                auto simulatedAnnealingTimes = gmx::splitString(inputrecStrings->anneal_time);
 
                 if (simulatedAnnealingTimes.size() != numSimulatedAnnealingFields)
                 {
-                    gmx_fatal(FARGS, "Found %zu annealing-time values, wanted %zu\n",
-                              simulatedAnnealingTimes.size(), numSimulatedAnnealingFields);
+                    gmx_fatal(FARGS,
+                              "Found %zu annealing-time values, wanted %zu\n",
+                              simulatedAnnealingTimes.size(),
+                              numSimulatedAnnealingFields);
                 }
-                auto simulatedAnnealingTemperatures = gmx::splitString(is->anneal_temp);
+                auto simulatedAnnealingTemperatures = gmx::splitString(inputrecStrings->anneal_temp);
                 if (simulatedAnnealingTemperatures.size() != numSimulatedAnnealingFields)
                 {
-                    gmx_fatal(FARGS, "Found %zu annealing-temp values, wanted %zu\n",
-                              simulatedAnnealingTemperatures.size(), numSimulatedAnnealingFields);
+                    gmx_fatal(FARGS,
+                              "Found %zu annealing-temp values, wanted %zu\n",
+                              simulatedAnnealingTemperatures.size(),
+                              numSimulatedAnnealingFields);
                 }
 
                 std::vector<real> allSimulatedAnnealingTimes(numSimulatedAnnealingFields);
                 std::vector<real> allSimulatedAnnealingTemperatures(numSimulatedAnnealingFields);
                 convertReals(wi, simulatedAnnealingTimes, "anneal-time", allSimulatedAnnealingTimes.data());
-                convertReals(wi, simulatedAnnealingTemperatures, "anneal-temp", allSimulatedAnnealingTemperatures.data());
+                convertReals(wi,
+                             simulatedAnnealingTemperatures,
+                             "anneal-temp",
+                             allSimulatedAnnealingTemperatures.data());
                 for (i = 0, k = 0; i < nr; i++)
                 {
                     for (j = 0; j < ir->opts.anneal_npoints[i]; j++)
@@ -3297,7 +3827,7 @@ void do_index(const char* mdparin, const char *ndx,
                         ir->opts.anneal_temp[i][j] = allSimulatedAnnealingTemperatures[k];
                         if (j == 0)
                         {
-                            if (ir->opts.anneal_time[i][0] > (ir->init_t+GMX_REAL_EPS))
+                            if (ir->opts.anneal_time[i][0] > (ir->init_t + GMX_REAL_EPS))
                             {
                                 gmx_fatal(FARGS, "First time point for annealing > init_t.\n");
                             }
@@ -3305,15 +3835,20 @@ void do_index(const char* mdparin, const char *ndx,
                         else
                         {
                             /* j>0 */
-                            if (ir->opts.anneal_time[i][j] < ir->opts.anneal_time[i][j-1])
+                            if (ir->opts.anneal_time[i][j] < ir->opts.anneal_time[i][j - 1])
                             {
-                                gmx_fatal(FARGS, "Annealing timepoints out of order: t=%f comes after t=%f\n",
-                                          ir->opts.anneal_time[i][j], ir->opts.anneal_time[i][j-1]);
+                                gmx_fatal(FARGS,
+                                          "Annealing timepoints out of order: t=%f comes after "
+                                          "t=%f\n",
+                                          ir->opts.anneal_time[i][j],
+                                          ir->opts.anneal_time[i][j - 1]);
                             }
                         }
                         if (ir->opts.anneal_temp[i][j] < 0)
                         {
-                            gmx_fatal(FARGS, "Found negative temperature in annealing: %f\n", ir->opts.anneal_temp[i][j]);
+                            gmx_fatal(FARGS,
+                                      "Found negative temperature in annealing: %f\n",
+                                      ir->opts.anneal_temp[i][j]);
                         }
                         k++;
                     }
@@ -3321,31 +3856,44 @@ void do_index(const char* mdparin, const char *ndx,
                 /* Print out some summary information, to make sure we got it right */
                 for (i = 0; i < nr; i++)
                 {
-                    if (ir->opts.annealing[i] != eannNO)
+                    if (ir->opts.annealing[i] != SimulatedAnnealing::No)
                     {
                         j = groups->groups[SimulationAtomGroupType::TemperatureCoupling][i];
-                        fprintf(stderr, "Simulated annealing for group %s: %s, %d timepoints\n",
-                                *(groups->groupNames[j]), eann_names[ir->opts.annealing[i]],
+                        fprintf(stderr,
+                                "Simulated annealing for group %s: %s, %d timepoints\n",
+                                *(groups->groupNames[j]),
+                                enumValueToString(ir->opts.annealing[i]),
                                 ir->opts.anneal_npoints[i]);
                         fprintf(stderr, "Time (ps)   Temperature (K)\n");
                         /* All terms except the last one */
-                        for (j = 0; j < (ir->opts.anneal_npoints[i]-1); j++)
+                        for (j = 0; j < (ir->opts.anneal_npoints[i] - 1); j++)
                         {
-                            fprintf(stderr, "%9.1f      %5.1f\n", ir->opts.anneal_time[i][j], ir->opts.anneal_temp[i][j]);
+                            fprintf(stderr,
+                                    "%9.1f      %5.1f\n",
+                                    ir->opts.anneal_time[i][j],
+                                    ir->opts.anneal_temp[i][j]);
                         }
 
                         /* Finally the last one */
-                        j = ir->opts.anneal_npoints[i]-1;
-                        if (ir->opts.annealing[i] == eannSINGLE)
+                        j = ir->opts.anneal_npoints[i] - 1;
+                        if (ir->opts.annealing[i] == SimulatedAnnealing::Single)
                         {
-                            fprintf(stderr, "%9.1f-     %5.1f\n", ir->opts.anneal_time[i][j], ir->opts.anneal_temp[i][j]);
+                            fprintf(stderr,
+                                    "%9.1f-     %5.1f\n",
+                                    ir->opts.anneal_time[i][j],
+                                    ir->opts.anneal_temp[i][j]);
                         }
                         else
                         {
-                            fprintf(stderr, "%9.1f      %5.1f\n", ir->opts.anneal_time[i][j], ir->opts.anneal_temp[i][j]);
-                            if (std::fabs(ir->opts.anneal_temp[i][j]-ir->opts.anneal_temp[i][0]) > GMX_REAL_EPS)
+                            fprintf(stderr,
+                                    "%9.1f      %5.1f\n",
+                                    ir->opts.anneal_time[i][j],
+                                    ir->opts.anneal_temp[i][j]);
+                            if (std::fabs(ir->opts.anneal_temp[i][j] - ir->opts.anneal_temp[i][0]) > GMX_REAL_EPS)
                             {
-                                warning_note(wi, "There is a temperature jump when your annealing loops back.\n");
+                                warning_note(wi,
+                                             "There is a temperature jump when your annealing "
+                                             "loops back.\n");
                             }
                         }
                     }
@@ -3356,17 +3904,25 @@ void do_index(const char* mdparin, const char *ndx,
 
     if (ir->bPull)
     {
-        make_pull_groups(ir->pull, is->pull_grp, defaultIndexGroups, gnames);
+        for (int i = 1; i < ir->pull->ngroup; i++)
+        {
+            const int gid = search_string(
+                    inputrecStrings->pullGroupNames[i].c_str(), defaultIndexGroups->nr, gnames);
+            GMX_ASSERT(defaultIndexGroups, "Must have initialized default index groups");
+            atomGroupRangeValidation(natoms, gid, *defaultIndexGroups);
+        }
+
+        process_pull_groups(ir->pull->group, inputrecStrings->pullGroupNames, defaultIndexGroups, gnames);
 
-        make_pull_coords(ir->pull);
+        checkPullCoords(ir->pull->group, ir->pull->coord);
     }
 
     if (ir->bRot)
     {
-        make_rotation_groups(ir->rot, is->rot_grp, defaultIndexGroups, gnames);
+        make_rotation_groups(ir->rot, inputrecStrings->rotateGroupNames, defaultIndexGroups, gnames);
     }
 
-    if (ir->eSwapCoords != eswapNO)
+    if (ir->eSwapCoords != SwapType::No)
     {
         make_swap_groups(ir->swap, defaultIndexGroups, gnames);
     }
@@ -3374,39 +3930,57 @@ void do_index(const char* mdparin, const char *ndx,
     /* Make indices for IMD session */
     if (ir->bIMD)
     {
-        make_IMD_group(ir->imd, is->imd_grp, defaultIndexGroups, gnames);
+        make_IMD_group(ir->imd, inputrecStrings->imd_grp, defaultIndexGroups, gnames);
     }
 
     gmx::IndexGroupsAndNames defaultIndexGroupsAndNames(
             *defaultIndexGroups, gmx::arrayRefFromArray(gnames, defaultIndexGroups->nr));
-    notifier.notifier_.notify(defaultIndexGroupsAndNames);
+    mdModulesNotifiers.preProcessingNotifier_.notify(defaultIndexGroupsAndNames);
 
-    auto accelerations          = gmx::splitString(is->acc);
-    auto accelerationGroupNames = gmx::splitString(is->accgrps);
+    auto accelerations          = gmx::splitString(inputrecStrings->acceleration);
+    auto accelerationGroupNames = gmx::splitString(inputrecStrings->accelerationGroups);
     if (accelerationGroupNames.size() * DIM != accelerations.size())
     {
-        gmx_fatal(FARGS, "Invalid Acceleration input: %zu groups and %zu acc. values",
-                  accelerationGroupNames.size(), accelerations.size());
-    }
-    do_numbering(natoms, groups, accelerationGroupNames, defaultIndexGroups, gnames,
+        gmx_fatal(FARGS,
+                  "Invalid Acceleration input: %zu groups and %zu acc. values",
+                  accelerationGroupNames.size(),
+                  accelerations.size());
+    }
+    do_numbering(natoms,
+                 groups,
+                 accelerationGroupNames,
+                 defaultIndexGroups,
+                 gnames,
                  SimulationAtomGroupType::Acceleration,
-                 restnm, egrptpALL_GENREST, bVerbose, wi);
+                 restnm,
+                 GroupCoverage::AllGenerateRest,
+                 bVerbose,
+                 wi);
     nr = groups->groups[SimulationAtomGroupType::Acceleration].size();
-    snew(ir->opts.acc, nr);
+    snew(ir->opts.acceleration, nr);
     ir->opts.ngacc = nr;
 
-    convertRvecs(wi, accelerations, "anneal-time", ir->opts.acc);
+    convertRvecs(wi, accelerations, "accelerations", ir->opts.acceleration);
 
-    auto freezeDims       = gmx::splitString(is->frdim);
-    auto freezeGroupNames = gmx::splitString(is->freeze);
+    auto freezeDims       = gmx::splitString(inputrecStrings->frdim);
+    auto freezeGroupNames = gmx::splitString(inputrecStrings->freeze);
     if (freezeDims.size() != DIM * freezeGroupNames.size())
     {
-        gmx_fatal(FARGS, "Invalid Freezing input: %zu groups and %zu freeze values",
-                  freezeGroupNames.size(), freezeDims.size());
-    }
-    do_numbering(natoms, groups, freezeGroupNames, defaultIndexGroups, gnames,
+        gmx_fatal(FARGS,
+                  "Invalid Freezing input: %zu groups and %zu freeze values",
+                  freezeGroupNames.size(),
+                  freezeDims.size());
+    }
+    do_numbering(natoms,
+                 groups,
+                 freezeGroupNames,
+                 defaultIndexGroups,
+                 gnames,
                  SimulationAtomGroupType::Freeze,
-                 restnm, egrptpALL_GENREST, bVerbose, wi);
+                 restnm,
+                 GroupCoverage::AllGenerateRest,
+                 bVerbose,
+                 wi);
     nr             = groups->groups[SimulationAtomGroupType::Freeze].size();
     ir->opts.ngfrz = nr;
     snew(ir->opts.nFreeze, nr);
@@ -3419,8 +3993,10 @@ void do_index(const char* mdparin, const char *ndx,
             {
                 if (!gmx::equalCaseInsensitive(freezeDims[k], "N", 1))
                 {
-                    sprintf(warnbuf, "Please use Y(ES) or N(O) for freezedim only "
-                            "(not %s)", freezeDims[k].c_str());
+                    sprintf(warn_buf,
+                            "Please use Y(ES) or N(O) for freezedim only "
+                            "(not %s)",
+                            freezeDims[k].c_str());
                     warning(wi, warn_buf);
                 }
             }
@@ -3434,128 +4010,111 @@ void do_index(const char* mdparin, const char *ndx,
         }
     }
 
-    auto energyGroupNames = gmx::splitString(is->energy);
-    do_numbering(natoms, groups, energyGroupNames, defaultIndexGroups, gnames,
+    auto energyGroupNames = gmx::splitString(inputrecStrings->energy);
+    do_numbering(natoms,
+                 groups,
+                 energyGroupNames,
+                 defaultIndexGroups,
+                 gnames,
                  SimulationAtomGroupType::EnergyOutput,
-                 restnm, egrptpALL_GENREST, bVerbose, wi);
+                 restnm,
+                 GroupCoverage::AllGenerateRest,
+                 bVerbose,
+                 wi);
     add_wall_energrps(groups, ir->nwall, symtab);
-    ir->opts.ngener = groups->groups[SimulationAtomGroupType::EnergyOutput].size();
-    auto vcmGroupNames = gmx::splitString(is->vcm);
-    bRest           =
-        do_numbering(natoms, groups, vcmGroupNames, defaultIndexGroups, gnames,
-                     SimulationAtomGroupType::MassCenterVelocityRemoval,
-                     restnm, vcmGroupNames.empty() ? egrptpALL_GENREST : egrptpPART, bVerbose, wi);
-    if (bRest)
-    {
-        warning(wi, "Some atoms are not part of any center of mass motion removal group.\n"
-                "This may lead to artifacts.\n"
-                "In most cases one should use one group for the whole system.");
+    ir->opts.ngener    = groups->groups[SimulationAtomGroupType::EnergyOutput].size();
+    auto vcmGroupNames = gmx::splitString(inputrecStrings->vcm);
+    do_numbering(natoms,
+                 groups,
+                 vcmGroupNames,
+                 defaultIndexGroups,
+                 gnames,
+                 SimulationAtomGroupType::MassCenterVelocityRemoval,
+                 restnm,
+                 vcmGroupNames.empty() ? GroupCoverage::AllGenerateRest : GroupCoverage::Partial,
+                 bVerbose,
+                 wi);
+
+    if (ir->comm_mode != ComRemovalAlgorithm::No)
+    {
+        checkAndUpdateVcmFreezeGroupConsistency(groups, natoms, ir->opts, wi);
     }
 
     /* Now we have filled the freeze struct, so we can calculate NRDF */
     calc_nrdf(mtop, ir, gnames);
 
-    auto user1GroupNames = gmx::splitString(is->user1);
-    do_numbering(natoms, groups, user1GroupNames, defaultIndexGroups, gnames,
+    auto user1GroupNames = gmx::splitString(inputrecStrings->user1);
+    do_numbering(natoms,
+                 groups,
+                 user1GroupNames,
+                 defaultIndexGroups,
+                 gnames,
                  SimulationAtomGroupType::User1,
-                 restnm, egrptpALL_GENREST, bVerbose, wi);
-    auto user2GroupNames = gmx::splitString(is->user2);
-    do_numbering(natoms, groups, user2GroupNames, defaultIndexGroups, gnames,
+                 restnm,
+                 GroupCoverage::AllGenerateRest,
+                 bVerbose,
+                 wi);
+    auto user2GroupNames = gmx::splitString(inputrecStrings->user2);
+    do_numbering(natoms,
+                 groups,
+                 user2GroupNames,
+                 defaultIndexGroups,
+                 gnames,
                  SimulationAtomGroupType::User2,
-                 restnm, egrptpALL_GENREST, bVerbose, wi);
-    auto compressedXGroupNames = gmx::splitString(is->x_compressed_groups);
-    do_numbering(natoms, groups, compressedXGroupNames, defaultIndexGroups, gnames,
+                 restnm,
+                 GroupCoverage::AllGenerateRest,
+                 bVerbose,
+                 wi);
+    auto compressedXGroupNames = gmx::splitString(inputrecStrings->x_compressed_groups);
+    do_numbering(natoms,
+                 groups,
+                 compressedXGroupNames,
+                 defaultIndexGroups,
+                 gnames,
                  SimulationAtomGroupType::CompressedPositionOutput,
-                 restnm, egrptpONE, bVerbose, wi);
-    auto orirefFitGroupNames = gmx::splitString(is->orirefitgrp);
-    do_numbering(natoms, groups, orirefFitGroupNames, defaultIndexGroups, gnames,
+                 restnm,
+                 GroupCoverage::OneGroup,
+                 bVerbose,
+                 wi);
+    auto orirefFitGroupNames = gmx::splitString(inputrecStrings->orirefitgrp);
+    do_numbering(natoms,
+                 groups,
+                 orirefFitGroupNames,
+                 defaultIndexGroups,
+                 gnames,
                  SimulationAtomGroupType::OrientationRestraintsFit,
-                 restnm, egrptpALL_GENREST, bVerbose, wi);
-
-    /* QMMM input processing */
-    auto qmGroupNames = gmx::splitString(is->QMMM);
-    auto qmMethods    = gmx::splitString(is->QMmethod);
-    auto qmBasisSets  = gmx::splitString(is->QMbasis);
-    if (ir->eI != eiMimic)
-    {
-        if (qmMethods.size() != qmGroupNames.size() ||
-            qmBasisSets.size() != qmGroupNames.size())
-        {
-            gmx_fatal(FARGS, "Invalid QMMM input: %zu groups %zu basissets"
-                      " and %zu methods\n", qmGroupNames.size(),
-                      qmBasisSets.size(), qmMethods.size());
-        }
-        /* group rest, if any, is always MM! */
-        do_numbering(natoms, groups, qmGroupNames, defaultIndexGroups, gnames,
-                     SimulationAtomGroupType::QuantumMechanics,
-                     restnm, egrptpALL_GENREST, bVerbose, wi);
-        nr            = qmGroupNames.size(); /*atoms->grps[egcQMMM].nr;*/
-        ir->opts.ngQM = qmGroupNames.size();
-        snew(ir->opts.QMmethod, nr);
-        snew(ir->opts.QMbasis, nr);
-        for (i = 0; i < nr; i++)
-        {
-            /* input consists of strings: RHF CASSCF PM3 .. These need to be
-             * converted to the corresponding enum in names.c
-             */
-            ir->opts.QMmethod[i] = search_QMstring(qmMethods[i].c_str(),
-                                                   eQMmethodNR,
-                                                   eQMmethod_names);
-            ir->opts.QMbasis[i] = search_QMstring(qmBasisSets[i].c_str(),
-                                                  eQMbasisNR,
-                                                  eQMbasis_names);
-
-        }
-        auto qmMultiplicities = gmx::splitString(is->QMmult);
-        auto qmCharges        = gmx::splitString(is->QMcharge);
-        auto qmbSH            = gmx::splitString(is->bSH);
-        snew(ir->opts.QMmult, nr);
-        snew(ir->opts.QMcharge, nr);
-        snew(ir->opts.bSH, nr);
-        convertInts(wi, qmMultiplicities, "QMmult", ir->opts.QMmult);
-        convertInts(wi, qmCharges, "QMcharge", ir->opts.QMcharge);
-        convertYesNos(wi, qmbSH, "bSH", ir->opts.bSH);
-
-        auto CASelectrons = gmx::splitString(is->CASelectrons);
-        auto CASorbitals  = gmx::splitString(is->CASorbitals);
-        snew(ir->opts.CASelectrons, nr);
-        snew(ir->opts.CASorbitals, nr);
-        convertInts(wi, CASelectrons, "CASelectrons", ir->opts.CASelectrons);
-        convertInts(wi, CASorbitals, "CASOrbitals", ir->opts.CASorbitals);
-
-        auto SAon    = gmx::splitString(is->SAon);
-        auto SAoff   = gmx::splitString(is->SAoff);
-        auto SAsteps = gmx::splitString(is->SAsteps);
-        snew(ir->opts.SAon, nr);
-        snew(ir->opts.SAoff, nr);
-        snew(ir->opts.SAsteps, nr);
-        convertInts(wi, SAon, "SAon", ir->opts.SAon);
-        convertInts(wi, SAoff, "SAoff", ir->opts.SAoff);
-        convertInts(wi, SAsteps, "SAsteps", ir->opts.SAsteps);
-    }
-    else
-    {
-        /* MiMiC */
-        if (qmGroupNames.size() > 1)
-        {
-            gmx_fatal(FARGS, "Currently, having more than one QM group in MiMiC is not supported");
-        }
-        /* group rest, if any, is always MM! */
-        do_numbering(natoms, groups, qmGroupNames, defaultIndexGroups, gnames,
-                     SimulationAtomGroupType::QuantumMechanics,
-                     restnm, egrptpALL_GENREST, bVerbose, wi);
-
-        ir->opts.ngQM = qmGroupNames.size();
-    }
-
-    /* end of QMMM input */
+                 restnm,
+                 GroupCoverage::AllGenerateRest,
+                 bVerbose,
+                 wi);
+
+    /* MiMiC QMMM input processing */
+    auto qmGroupNames = gmx::splitString(inputrecStrings->QMMM);
+    if (qmGroupNames.size() > 1)
+    {
+        gmx_fatal(FARGS, "Currently, having more than one QM group in MiMiC is not supported");
+    }
+    /* group rest, if any, is always MM! */
+    do_numbering(natoms,
+                 groups,
+                 qmGroupNames,
+                 defaultIndexGroups,
+                 gnames,
+                 SimulationAtomGroupType::QuantumMechanics,
+                 restnm,
+                 GroupCoverage::AllGenerateRest,
+                 bVerbose,
+                 wi);
+    ir->opts.ngQM = qmGroupNames.size();
+
+    /* end of MiMiC QMMM input */
 
     if (bVerbose)
     {
         for (auto group : gmx::keysOf(groups->groups))
         {
             fprintf(stderr, "%-16s has %zu element(s):", shortName(group), groups->groups[group].size());
-            for (const auto &entry : groups->groups[group])
+            for (const autoentry : groups->groups[group])
             {
                 fprintf(stderr, " %s", *(groups->groupNames[entry]));
             }
@@ -3564,10 +4123,10 @@ void do_index(const char* mdparin, const char *ndx,
     }
 
     nr = groups->groups[SimulationAtomGroupType::EnergyOutput].size();
-    snew(ir->opts.egp_flags, nr*nr);
+    snew(ir->opts.egp_flags, nr * nr);
 
-    bExcl = do_egp_flag(ir, groups, "energygrp-excl", is->egpexcl, EGP_EXCL);
-    if (bExcl && ir->cutoff_scheme == ecutsVERLET)
+    bExcl = do_egp_flag(ir, groups, "energygrp-excl", inputrecStrings->egpexcl, EGP_EXCL);
+    if (bExcl && ir->cutoff_scheme == CutoffScheme::Verlet)
     {
         warning_error(wi, "Energy group exclusions are currently not supported");
     }
@@ -3576,12 +4135,15 @@ void do_index(const char* mdparin, const char *ndx,
         warning(wi, "Can not exclude the lattice Coulomb energy between energy groups");
     }
 
-    bTable = do_egp_flag(ir, groups, "energygrp-table", is->egptable, EGP_TABLE);
-    if (bTable && !(ir->vdwtype == evdwUSER) &&
-        !(ir->coulombtype == eelUSER) && !(ir->coulombtype == eelPMEUSER) &&
-        !(ir->coulombtype == eelPMEUSERSWITCH))
+    bTable = do_egp_flag(ir, groups, "energygrp-table", inputrecStrings->egptable, EGP_TABLE);
+    if (bTable && !(ir->vdwtype == VanDerWaalsType::User)
+        && !(ir->coulombtype == CoulombInteractionType::User)
+        && !(ir->coulombtype == CoulombInteractionType::PmeUser)
+        && !(ir->coulombtype == CoulombInteractionType::PmeUserSwitch))
     {
-        gmx_fatal(FARGS, "Can only have energy group pair tables in combination with user tables for VdW and/or Coulomb");
+        gmx_fatal(FARGS,
+                  "Can only have energy group pair tables in combination with user tables for VdW "
+                  "and/or Coulomb");
     }
 
     /* final check before going out of scope if simulated tempering variables
@@ -3589,11 +4151,13 @@ void do_index(const char* mdparin, const char *ndx,
      */
     if ((ir->expandedvals->nstexpanded < 0) && ir->bSimTemp)
     {
-        ir->expandedvals->nstexpanded = 2*static_cast<int>(ir->opts.tau_t[0]/ir->delta_t);
-        warning(wi, gmx::formatString("the value for nstexpanded was not specified for "
-                                      " expanded ensemble simulated tempering. It is set to 2*tau_t (%d) "
-                                      "by default, but it is recommended to set it to an explicit value!",
-                                      ir->expandedvals->nstexpanded));
+        ir->expandedvals->nstexpanded = 2 * static_cast<int>(ir->opts.tau_t[0] / ir->delta_t);
+        warning(wi,
+                gmx::formatString(
+                        "the value for nstexpanded was not specified for "
+                        " expanded ensemble simulated tempering. It is set to 2*tau_t (%d) "
+                        "by default, but it is recommended to set it to an explicit value!",
+                        ir->expandedvals->nstexpanded));
     }
     for (i = 0; (i < defaultIndexGroups->nr); i++)
     {
@@ -3602,16 +4166,14 @@ void do_index(const char* mdparin, const char *ndx,
     sfree(gnames);
     done_blocka(defaultIndexGroups);
     sfree(defaultIndexGroups);
-
 }
 
 
-
-static void check_disre(const gmx_mtop_t *mtop)
+static void check_disre(const gmx_mtop_t& mtop)
 {
     if (gmx_mtop_ftype_count(mtop, F_DISRES) > 0)
     {
-        const gmx_ffparams_t &ffparams  = mtop->ffparams;
+        const gmx_ffparams_t& ffparams  = mtop.ffparams;
         int                   ndouble   = 0;
         int                   old_label = -1;
         for (int i = 0; i < ffparams.numTypes(); i++)
@@ -3630,117 +4192,110 @@ static void check_disre(const gmx_mtop_t *mtop)
         }
         if (ndouble > 0)
         {
-            gmx_fatal(FARGS, "Found %d double distance restraint indices,\n"
+            gmx_fatal(FARGS,
+                      "Found %d double distance restraint indices,\n"
                       "probably the parameters for multiple pairs in one restraint "
-                      "are not identical\n", ndouble);
+                      "are not identical\n",
+                      ndouble);
         }
     }
 }
 
-static bool absolute_reference(t_inputrec *ir, gmx_mtop_t *sys,
-                               bool posres_only,
-                               ivec AbsRef)
+//! Returns whether dimensions have an absolute reference due to walls, pbc or freezing
+static BasicVector<bool> haveAbsoluteReference(const t_inputrec& ir)
 {
-    int                    d, g, i;
-    gmx_mtop_ilistloop_t   iloop;
-    int                    nmol;
-    t_iparams             *pr;
-
-    clear_ivec(AbsRef);
+    BasicVector<bool> absRef = { false, false, false };
 
-    if (!posres_only)
+    /* Check the degrees of freedom of the COM (not taking COMM removal into account) */
+    for (int d = 0; d < DIM; d++)
     {
-        /* Check the COM */
-        for (d = 0; d < DIM; d++)
-        {
-            AbsRef[d] = (d < ndof_com(ir) ? 0 : 1);
-        }
-        /* Check for freeze groups */
-        for (g = 0; g < ir->opts.ngfrz; g++)
+        absRef[d] = (d >= ndof_com(&ir));
+    }
+    /* Check for freeze groups */
+    for (int g = 0; g < ir.opts.ngfrz; g++)
+    {
+        for (int d = 0; d < DIM; d++)
         {
-            for (d = 0; d < DIM; d++)
+            if (ir.opts.nFreeze[g][d] != 0)
             {
-                if (ir->opts.nFreeze[g][d] != 0)
-                {
-                    AbsRef[d] = 1;
-                }
+                absRef[d] = true;
             }
         }
     }
 
-    /* Check for position restraints */
-    iloop = gmx_mtop_ilistloop_init(sys);
-    while (const InteractionLists *ilist = gmx_mtop_ilistloop_next(iloop, &nmol))
+    return absRef;
+}
+
+//! Returns whether position restraints are used for dimensions
+static BasicVector<bool> havePositionRestraints(const gmx_mtop_t& sys)
+{
+    BasicVector<bool> havePosres = { false, false, false };
+
+    for (const auto ilists : IListRange(sys))
     {
-        if (nmol > 0 &&
-            (AbsRef[XX] == 0 || AbsRef[YY] == 0 || AbsRef[ZZ] == 0))
+        const auto& posResList   = ilists.list()[F_POSRES];
+        const auto& fbPosResList = ilists.list()[F_FBPOSRES];
+        if (ilists.nmol() > 0 && (!havePosres[XX] || !havePosres[YY] || !havePosres[ZZ]))
         {
-            for (i = 0; i < (*ilist)[F_POSRES].size(); i += 2)
+            for (int i = 0; i < posResList.size(); i += 2)
             {
-                pr = &sys->ffparams.iparams[(*ilist)[F_POSRES].iatoms[i]];
-                for (d = 0; d < DIM; d++)
+                const t_iparams& pr = sys.ffparams.iparams[posResList.iatoms[i]];
+                for (int d = 0; d < DIM; d++)
                 {
-                    if (pr->posres.fcA[d] != 0)
+                    if (pr.posres.fcA[d] != 0)
                     {
-                        AbsRef[d] = 1;
+                        havePosres[d] = true;
                     }
                 }
             }
-            for (i = 0; i < (*ilist)[F_FBPOSRES].size(); i += 2)
+            for (int i = 0; i < fbPosResList.size(); i += 2)
             {
                 /* Check for flat-bottom posres */
-                pr = &sys->ffparams.iparams[(*ilist)[F_FBPOSRES].iatoms[i]];
-                if (pr->fbposres.k != 0)
+                const t_iparams& pr = sys.ffparams.iparams[fbPosResList.iatoms[i]];
+                if (pr.fbposres.k != 0)
                 {
-                    switch (pr->fbposres.geom)
+                    switch (pr.fbposres.geom)
                     {
-                        case efbposresSPHERE:
-                            AbsRef[XX] = AbsRef[YY] = AbsRef[ZZ] = 1;
-                            break;
-                        case efbposresCYLINDERX:
-                            AbsRef[YY] = AbsRef[ZZ] = 1;
-                            break;
-                        case efbposresCYLINDERY:
-                            AbsRef[XX] = AbsRef[ZZ] = 1;
-                            break;
+                        case efbposresSPHERE: havePosres = { true, true, true }; break;
+                        case efbposresCYLINDERX: havePosres[YY] = havePosres[ZZ] = true; break;
+                        case efbposresCYLINDERY: havePosres[XX] = havePosres[ZZ] = true; break;
                         case efbposresCYLINDER:
                         /* efbposres is a synonym for efbposresCYLINDERZ for backwards compatibility */
-                        case efbposresCYLINDERZ:
-                            AbsRef[XX] = AbsRef[YY] = 1;
-                            break;
+                        case efbposresCYLINDERZ: havePosres[XX] = havePosres[YY] = true; break;
                         case efbposresX: /* d=XX */
                         case efbposresY: /* d=YY */
                         case efbposresZ: /* d=ZZ */
-                            d         = pr->fbposres.geom - efbposresX;
-                            AbsRef[d] = 1;
+                            havePosres[pr.fbposres.geom - efbposresX] = true;
                             break;
                         default:
-                            gmx_fatal(FARGS, " Invalid geometry for flat-bottom position restraint.\n"
-                                      "Expected nr between 1 and %d. Found %d\n", efbposresNR-1,
-                                      pr->fbposres.geom);
+                            gmx_fatal(FARGS,
+                                      "Invalid geometry for flat-bottom position restraint.\n"
+                                      "Expected nr between 1 and %d. Found %d\n",
+                                      efbposresNR - 1,
+                                      pr.fbposres.geom);
                     }
                 }
             }
         }
     }
 
-    return (AbsRef[XX] != 0 && AbsRef[YY] != 0 && AbsRef[ZZ] != 0);
+    return havePosres;
 }
 
-static void
-check_combination_rule_differences(const gmx_mtop_t *mtop, int state,
-                                   bool *bC6ParametersWorkWithGeometricRules,
-                                   bool *bC6ParametersWorkWithLBRules,
-                                   bool *bLBRulesPossible)
+static void check_combination_rule_differences(const gmx_mtop_t& mtop,
+                                               int               state,
+                                               bool* bC6ParametersWorkWithGeometricRules,
+                                               bool* bC6ParametersWorkWithLBRules,
+                                               bool* bLBRulesPossible)
 {
-    int           ntypes, tpi, tpj;
-    int          *typecount;
-    real          tol;
-    double        c6i, c6j, c12i, c12j;
-    double        c6, c6_geometric, c6_LB;
-    double        sigmai, sigmaj, epsi, epsj;
-    bool          bCanDoLBRules, bCanDoGeometricRules;
-    const char   *ptr;
+    int         ntypes, tpi, tpj;
+    int*        typecount;
+    real        tol;
+    double      c6i, c6j, c12i, c12j;
+    double      c6, c6_geometric, c6_LB;
+    double      sigmai, sigmaj, epsi, epsj;
+    bool        bCanDoLBRules, bCanDoGeometricRules;
+    const charptr;
 
     /* A tolerance of 1e-5 seems reasonable for (possibly hand-typed)
      * force-field floating point parameters.
@@ -3754,37 +4309,38 @@ check_combination_rule_differences(const gmx_mtop_t *mtop, int state,
 
         if (sscanf(ptr, "%lf%lf", &dbl, &canary) != 1)
         {
-            gmx_fatal(FARGS, "Could not parse a single floating-point number from GMX_LJCOMB_TOL (%s)", ptr);
+            gmx_fatal(
+                    FARGS, "Could not parse a single floating-point number from GMX_LJCOMB_TOL (%s)", ptr);
         }
         tol = dbl;
     }
 
-    *bC6ParametersWorkWithLBRules         = TRUE;
-    *bC6ParametersWorkWithGeometricRules  = TRUE;
-    bCanDoLBRules                         = TRUE;
-    ntypes                                = mtop->ffparams.atnr;
+    *bC6ParametersWorkWithLBRules        = TRUE;
+    *bC6ParametersWorkWithGeometricRules = TRUE;
+    bCanDoLBRules                        = TRUE;
+    ntypes                               = mtop.ffparams.atnr;
     snew(typecount, ntypes);
     gmx_mtop_count_atomtypes(mtop, state, typecount);
-    *bLBRulesPossible       = TRUE;
+    *bLBRulesPossible = TRUE;
     for (tpi = 0; tpi < ntypes; ++tpi)
     {
-        c6i  = mtop->ffparams.iparams[(ntypes + 1) * tpi].lj.c6;
-        c12i = mtop->ffparams.iparams[(ntypes + 1) * tpi].lj.c12;
+        c6i  = mtop.ffparams.iparams[(ntypes + 1) * tpi].lj.c6;
+        c12i = mtop.ffparams.iparams[(ntypes + 1) * tpi].lj.c12;
         for (tpj = tpi; tpj < ntypes; ++tpj)
         {
-            c6j          = mtop->ffparams.iparams[(ntypes + 1) * tpj].lj.c6;
-            c12j         = mtop->ffparams.iparams[(ntypes + 1) * tpj].lj.c12;
-            c6           = mtop->ffparams.iparams[ntypes * tpi + tpj].lj.c6;
+            c6j          = mtop.ffparams.iparams[(ntypes + 1) * tpj].lj.c6;
+            c12j         = mtop.ffparams.iparams[(ntypes + 1) * tpj].lj.c12;
+            c6           = mtop.ffparams.iparams[ntypes * tpi + tpj].lj.c6;
             c6_geometric = std::sqrt(c6i * c6j);
             if (!gmx_numzero(c6_geometric))
             {
                 if (!gmx_numzero(c12i) && !gmx_numzero(c12j))
                 {
-                    sigmai   = gmx::sixthroot(c12i / c6i);
-                    sigmaj   = gmx::sixthroot(c12j / c6j);
-                    epsi     = c6i * c6i /(4.0 * c12i);
-                    epsj     = c6j * c6j /(4.0 * c12j);
-                    c6_LB    = 4.0 * std::sqrt(epsi * epsj) * gmx::power6(0.5 * (sigmai + sigmaj));
+                    sigmai = gmx::sixthroot(c12i / c6i);
+                    sigmaj = gmx::sixthroot(c12j / c6j);
+                    epsi   = c6i * c6i / (4.0 * c12i);
+                    epsj   = c6j * c6j / (4.0 * c12j);
+                    c6_LB  = 4.0 * std::sqrt(epsi * epsj) * gmx::power6(0.5 * (sigmai + sigmaj));
                 }
                 else
                 {
@@ -3810,21 +4366,18 @@ check_combination_rule_differences(const gmx_mtop_t *mtop, int state,
     sfree(typecount);
 }
 
-static void
-check_combination_rules(const t_inputrec *ir, const gmx_mtop_t *mtop,
-                        warninp_t wi)
+static void check_combination_rules(const t_inputrec* ir, const gmx_mtop_t& mtop, warninp_t wi)
 {
     bool bLBRulesPossible, bC6ParametersWorkWithGeometricRules, bC6ParametersWorkWithLBRules;
 
-    check_combination_rule_differences(mtop, 0,
-                                       &bC6ParametersWorkWithGeometricRules,
-                                       &bC6ParametersWorkWithLBRules,
-                                       &bLBRulesPossible);
-    if (ir->ljpme_combination_rule == eljpmeLB)
+    check_combination_rule_differences(
+            mtop, 0, &bC6ParametersWorkWithGeometricRules, &bC6ParametersWorkWithLBRules, &bLBRulesPossible);
+    if (ir->ljpme_combination_rule == LongRangeVdW::LB)
     {
         if (!bC6ParametersWorkWithLBRules || !bLBRulesPossible)
         {
-            warning(wi, "You are using arithmetic-geometric combination rules "
+            warning(wi,
+                    "You are using arithmetic-geometric combination rules "
                     "in LJ-PME, but your non-bonded C6 parameters do not "
                     "follow these rules.");
         }
@@ -3833,47 +4386,62 @@ check_combination_rules(const t_inputrec *ir, const gmx_mtop_t *mtop,
     {
         if (!bC6ParametersWorkWithGeometricRules)
         {
-            if (ir->eDispCorr != edispcNO)
+            if (ir->eDispCorr != DispersionCorrectionType::No)
             {
-                warning_note(wi, "You are using geometric combination rules in "
+                warning_note(wi,
+                             "You are using geometric combination rules in "
                              "LJ-PME, but your non-bonded C6 parameters do "
                              "not follow these rules. "
                              "This will introduce very small errors in the forces and energies in "
                              "your simulations. Dispersion correction will correct total energy "
-                             "and/or pressure for isotropic systems, but not forces or surface tensions.");
+                             "and/or pressure for isotropic systems, but not forces or surface "
+                             "tensions.");
             }
             else
             {
-                warning_note(wi, "You are using geometric combination rules in "
+                warning_note(wi,
+                             "You are using geometric combination rules in "
                              "LJ-PME, but your non-bonded C6 parameters do "
                              "not follow these rules. "
                              "This will introduce very small errors in the forces and energies in "
-                             "your simulations. If your system is homogeneous, consider using dispersion correction "
+                             "your simulations. If your system is homogeneous, consider using "
+                             "dispersion correction "
                              "for the total energy and pressure.");
             }
         }
     }
 }
 
-void triple_check(const char *mdparin, t_inputrec *ir, gmx_mtop_t *sys,
-                  warninp_t wi)
+static bool allTrue(const BasicVector<bool>& boolVector)
 {
+    return boolVector[0] && boolVector[1] && boolVector[2];
+}
+
+void triple_check(const char* mdparin, t_inputrec* ir, gmx_mtop_t* sys, warninp_t wi)
+{
+    // Not meeting MTS requirements should have resulted in a fatal error, so we can assert here
+    GMX_ASSERT(gmx::checkMtsRequirements(*ir).empty(), "All MTS requirements should be met here");
+
     char                      err_buf[STRLEN];
     int                       i, m, c, nmol;
-    bool                      bCharge, bAcc;
-    real                     *mgrp, mt;
-    rvec                      acc;
+    bool                      bCharge;
+    real *                    mgrp, mt;
     gmx_mtop_atomloop_block_t aloopb;
-    ivec                      AbsRef;
     char                      warn_buf[STRLEN];
 
     set_warning_line(wi, mdparin, -1);
 
-    if (ir->cutoff_scheme == ecutsVERLET &&
-        ir->verletbuf_tol > 0 &&
-        ir->nstlist > 1 &&
-        ((EI_MD(ir->eI) || EI_SD(ir->eI)) &&
-         (ir->etc == etcVRESCALE || ir->etc == etcBERENDSEN)))
+    if (ir->comm_mode != ComRemovalAlgorithm::No && allTrue(havePositionRestraints(*sys)))
+    {
+        warning_note(wi,
+                     "Removing center of mass motion in the presence of position restraints might "
+                     "cause artifacts. When you are using position restraints to equilibrate a "
+                     "macro-molecule, the artifacts are usually negligible.");
+    }
+
+    if (ir->cutoff_scheme == CutoffScheme::Verlet && ir->verletbuf_tol > 0 && ir->nstlist > 1
+        && ((EI_MD(ir->eI) || EI_SD(ir->eI))
+            && (ir->etc == TemperatureCoupling::VRescale || ir->etc == TemperatureCoupling::Berendsen)))
     {
         /* Check if a too small Verlet buffer might potentially
          * cause more drift than the thermostat can couple off.
@@ -3882,7 +4450,7 @@ void triple_check(const char *mdparin, t_inputrec *ir, gmx_mtop_t *sys,
         const real T_error_warn    = 0.002;
         const real T_error_suggest = 0.001;
         /* For safety: 2 DOF per atom (typical with constraints) */
-        const real nrdf_at         = 2;
+        const real nrdf_at = 2;
         real       T, tau, max_T_error;
         int        i;
 
@@ -3900,14 +4468,20 @@ void triple_check(const char *mdparin, t_inputrec *ir, gmx_mtop_t *sys,
              * of errors. The factor 0.5 is because energy distributes
              * equally over Ekin and Epot.
              */
-            max_T_error = 0.5*tau*ir->verletbuf_tol/(nrdf_at*BOLTZ*T);
+            max_T_error = 0.5 * tau * ir->verletbuf_tol / (nrdf_at * gmx::c_boltz * T);
             if (max_T_error > T_error_warn)
             {
-                sprintf(warn_buf, "With a verlet-buffer-tolerance of %g kJ/mol/ps, a reference temperature of %g and a tau_t of %g, your temperature might be off by up to %.1f%%. To ensure the error is below %.1f%%, decrease verlet-buffer-tolerance to %.0e or decrease tau_t.",
-                        ir->verletbuf_tol, T, tau,
-                        100*max_T_error,
-                        100*T_error_suggest,
-                        ir->verletbuf_tol*T_error_suggest/max_T_error);
+                sprintf(warn_buf,
+                        "With a verlet-buffer-tolerance of %g kJ/mol/ps, a reference temperature "
+                        "of %g and a tau_t of %g, your temperature might be off by up to %.1f%%. "
+                        "To ensure the error is below %.1f%%, decrease verlet-buffer-tolerance to "
+                        "%.0e or decrease tau_t.",
+                        ir->verletbuf_tol,
+                        T,
+                        tau,
+                        100 * max_T_error,
+                        100 * T_error_suggest,
+                        ir->verletbuf_tol * T_error_suggest / max_T_error);
                 warning(wi, warn_buf);
             }
         }
@@ -3919,63 +4493,83 @@ void triple_check(const char *mdparin, t_inputrec *ir, gmx_mtop_t *sys,
 
         for (i = 0; i < ir->opts.ngtc; i++)
         {
-            sprintf(err_buf, "all tau_t must currently be equal using Andersen temperature control, violated for group %d", i);
+            sprintf(err_buf,
+                    "all tau_t must currently be equal using Andersen temperature control, "
+                    "violated for group %d",
+                    i);
             CHECK(ir->opts.tau_t[0] != ir->opts.tau_t[i]);
-            sprintf(err_buf, "all tau_t must be positive using Andersen temperature control, tau_t[%d]=%10.6f",
-                    i, ir->opts.tau_t[i]);
+            sprintf(err_buf,
+                    "all tau_t must be positive using Andersen temperature control, "
+                    "tau_t[%d]=%10.6f",
+                    i,
+                    ir->opts.tau_t[i]);
             CHECK(ir->opts.tau_t[i] < 0);
         }
 
-        if (ir->etc == etcANDERSENMASSIVE && ir->comm_mode != ecmNO)
+        if (ir->etc == TemperatureCoupling::AndersenMassive && ir->comm_mode != ComRemovalAlgorithm::No)
         {
             for (i = 0; i < ir->opts.ngtc; i++)
             {
-                int nsteps = gmx::roundToInt(ir->opts.tau_t[i]/ir->delta_t);
-                sprintf(err_buf, "tau_t/delta_t for group %d for temperature control method %s must be a multiple of nstcomm (%d), as velocities of atoms in coupled groups are randomized every time step. The input tau_t (%8.3f) leads to %d steps per randomization", i, etcoupl_names[ir->etc], ir->nstcomm, ir->opts.tau_t[i], nsteps);
+                int nsteps = gmx::roundToInt(ir->opts.tau_t[i] / ir->delta_t);
+                sprintf(err_buf,
+                        "tau_t/delta_t for group %d for temperature control method %s must be a "
+                        "multiple of nstcomm (%d), as velocities of atoms in coupled groups are "
+                        "randomized every time step. The input tau_t (%8.3f) leads to %d steps per "
+                        "randomization",
+                        i,
+                        enumValueToString(ir->etc),
+                        ir->nstcomm,
+                        ir->opts.tau_t[i],
+                        nsteps);
                 CHECK(nsteps % ir->nstcomm != 0);
             }
         }
     }
 
-    if (EI_DYNAMICS(ir->eI) && !EI_SD(ir->eI) && ir->eI != eiBD &&
-        ir->comm_mode == ecmNO &&
-        !(absolute_reference(ir, sys, FALSE, AbsRef) || ir->nsteps <= 10) &&
-        !ETC_ANDERSEN(ir->etc))
+    if (EI_DYNAMICS(ir->eI) && !EI_SD(ir->eI) && ir->eI != IntegrationAlgorithm::BD
+        && ir->comm_mode == ComRemovalAlgorithm::No
+        && !(allTrue(haveAbsoluteReference(*ir)) || allTrue(havePositionRestraints(*sys)) || ir->nsteps <= 10)
+        && !ETC_ANDERSEN(ir->etc))
     {
-        warning(wi, "You are not using center of mass motion removal (mdp option comm-mode), numerical rounding errors can lead to build up of kinetic energy of the center of mass");
+        warning(wi,
+                "You are not using center of mass motion removal (mdp option comm-mode), numerical "
+                "rounding errors can lead to build up of kinetic energy of the center of mass");
     }
 
-    if (ir->epc == epcPARRINELLORAHMAN &&
-        ir->etc == etcNOSEHOOVER)
+    if (ir->epc == PressureCoupling::ParrinelloRahman && ir->etc == TemperatureCoupling::NoseHoover)
     {
         real tau_t_max = 0;
         for (int g = 0; g < ir->opts.ngtc; g++)
         {
             tau_t_max = std::max(tau_t_max, ir->opts.tau_t[g]);
         }
-        if (ir->tau_p < 1.9*tau_t_max)
-        {
-            std::string message =
-                gmx::formatString("With %s T-coupling and %s p-coupling, "
-                                  "%s (%g) should be at least twice as large as %s (%g) to avoid resonances",
-                                  etcoupl_names[ir->etc],
-                                  epcoupl_names[ir->epc],
-                                  "tau-p", ir->tau_p,
-                                  "tau-t", tau_t_max);
+        if (ir->tau_p < 1.9 * tau_t_max)
+        {
+            std::string message = gmx::formatString(
+                    "With %s T-coupling and %s p-coupling, "
+                    "%s (%g) should be at least twice as large as %s (%g) to avoid resonances",
+                    enumValueToString(ir->etc),
+                    enumValueToString(ir->epc),
+                    "tau-p",
+                    ir->tau_p,
+                    "tau-t",
+                    tau_t_max);
             warning(wi, message.c_str());
         }
     }
 
     /* Check for pressure coupling with absolute position restraints */
-    if (ir->epc != epcNO && ir->refcoord_scaling == erscNO)
+    if (ir->epc != PressureCoupling::No && ir->refcoord_scaling == RefCoordScaling::No)
     {
-        absolute_reference(ir, sys, TRUE, AbsRef);
+        const BasicVector<bool> havePosres = havePositionRestraints(*sys);
         {
             for (m = 0; m < DIM; m++)
             {
-                if (AbsRef[m] && norm2(ir->compress[m]) > 0)
+                if (havePosres[m] && norm2(ir->compress[m]) > 0)
                 {
-                    warning(wi, "You are using pressure coupling with absolute position restraints, this will give artifacts. Use the refcoord_scaling option.");
+                    warning(wi,
+                            "You are using pressure coupling with absolute position restraints, "
+                            "this will give artifacts. Use the refcoord_scaling option.");
                     break;
                 }
             }
@@ -3983,8 +4577,8 @@ void triple_check(const char *mdparin, t_inputrec *ir, gmx_mtop_t *sys,
     }
 
     bCharge = FALSE;
-    aloopb  = gmx_mtop_atomloop_block_init(sys);
-    const t_atom *atom;
+    aloopb  = gmx_mtop_atomloop_block_init(*sys);
+    const t_atomatom;
     while (gmx_mtop_atomloop_block_next(aloopb, &atom, &nmol))
     {
         if (atom->q != 0 || atom->qB != 0)
@@ -3999,19 +4593,21 @@ void triple_check(const char *mdparin, t_inputrec *ir, gmx_mtop_t *sys,
         {
             sprintf(err_buf,
                     "You are using full electrostatics treatment %s for a system without charges.\n"
-                    "This costs a lot of performance for just processing zeros, consider using %s instead.\n",
-                    EELTYPE(ir->coulombtype), EELTYPE(eelCUT));
+                    "This costs a lot of performance for just processing zeros, consider using %s "
+                    "instead.\n",
+                    enumValueToString(ir->coulombtype),
+                    enumValueToString(CoulombInteractionType::Cut));
             warning(wi, err_buf);
         }
     }
     else
     {
-        if (ir->coulombtype == eelCUT && ir->rcoulomb > 0)
+        if (ir->coulombtype == CoulombInteractionType::Cut && ir->rcoulomb > 0)
         {
             sprintf(err_buf,
                     "You are using a plain Coulomb cut-off, which might produce artifacts.\n"
                     "You might want to consider using %s electrostatics.\n",
-                    EELTYPE(eelPME));
+                    enumValueToString(CoulombInteractionType::Pme));
             warning_note(wi, err_buf);
         }
     }
@@ -4019,34 +4615,33 @@ void triple_check(const char *mdparin, t_inputrec *ir, gmx_mtop_t *sys,
     /* Check if combination rules used in LJ-PME are the same as in the force field */
     if (EVDW_PME(ir->vdwtype))
     {
-        check_combination_rules(ir, sys, wi);
+        check_combination_rules(ir, *sys, wi);
     }
 
     /* Generalized reaction field */
-    if (ir->coulombtype == eelGRF_NOTUSED)
+    if (ir->coulombtype == CoulombInteractionType::GRFNotused)
     {
-        warning_error(wi, "Generalized reaction-field electrostatics is no longer supported. "
-                      "You can use normal reaction-field instead and compute the reaction-field constant by hand.");
+        warning_error(wi,
+                      "Generalized reaction-field electrostatics is no longer supported. "
+                      "You can use normal reaction-field instead and compute the reaction-field "
+                      "constant by hand.");
     }
 
-    bAcc = FALSE;
+    ir->useConstantAcceleration = false;
     for (int i = 0; (i < gmx::ssize(sys->groups.groups[SimulationAtomGroupType::Acceleration])); i++)
     {
-        for (m = 0; (m < DIM); m++)
+        if (norm2(ir->opts.acceleration[i]) != 0)
         {
-            if (fabs(ir->opts.acc[i][m]) > 1e-6)
-            {
-                bAcc = TRUE;
-            }
+            ir->useConstantAcceleration = true;
         }
     }
-    if (bAcc)
+    if (ir->useConstantAcceleration)
     {
-        clear_rvec(acc);
+        gmx::RVec acceleration = { 0.0_real, 0.0_real, 0.0_real };
         snew(mgrp, sys->groups.groups[SimulationAtomGroupType::Acceleration].size());
         for (const AtomProxy atomP : AtomRange(*sys))
         {
-            const t_atom &local = atomP.atom();
+            const t_atomlocal = atomP.atom();
             int           i     = atomP.globalAtomNumber();
             mgrp[getGroupType(sys->groups, SimulationAtomGroupType::Acceleration, i)] += local.m;
         }
@@ -4055,24 +4650,27 @@ void triple_check(const char *mdparin, t_inputrec *ir, gmx_mtop_t *sys,
         {
             for (m = 0; (m < DIM); m++)
             {
-                acc[m] += ir->opts.acc[i][m]*mgrp[i];
+                acceleration[m] += ir->opts.acceleration[i][m] * mgrp[i];
             }
             mt += mgrp[i];
         }
         for (m = 0; (m < DIM); m++)
         {
-            if (fabs(acc[m]) > 1e-6)
+            if (fabs(acceleration[m]) > 1e-6)
             {
-                const char *dim[DIM] = { "X", "Y", "Z" };
+                const chardim[DIM] = { "X", "Y", "Z" };
                 fprintf(stderr,
                         "Net Acceleration in %s direction, will %s be corrected\n",
-                        dim[m], ir->nstcomm != 0 ? "" : "not");
+                        dim[m],
+                        ir->nstcomm != 0 ? "" : "not");
                 if (ir->nstcomm != 0 && m < ndof_com(ir))
                 {
-                    acc[m] /= mt;
-                    for (i = 0; (i < gmx::ssize(sys->groups.groups[SimulationAtomGroupType::Acceleration])); i++)
+                    acceleration[m] /= mt;
+                    for (i = 0;
+                         (i < gmx::ssize(sys->groups.groups[SimulationAtomGroupType::Acceleration]));
+                         i++)
                     {
-                        ir->opts.acc[i][m] -= acc[m];
+                        ir->opts.acceleration[i][m] -= acceleration[m];
                     }
                 }
             }
@@ -4080,8 +4678,8 @@ void triple_check(const char *mdparin, t_inputrec *ir, gmx_mtop_t *sys,
         sfree(mgrp);
     }
 
-    if (ir->efep != efepNO && ir->fepvals->sc_alpha != 0 &&
-        !gmx_within_tol(sys->ffparams.reppow, 12.0, 10*GMX_DOUBLE_EPS))
+    if (ir->efep != FreeEnergyPerturbationType::No && ir->fepvals->sc_alpha != 0
+        && !gmx_within_tol(sys->ffparams.reppow, 12.0, 10 * GMX_DOUBLE_EPS))
     {
         gmx_fatal(FARGS, "Soft-core interactions are only supported with VdW repulsion power 12");
     }
@@ -4093,15 +4691,19 @@ void triple_check(const char *mdparin, t_inputrec *ir, gmx_mtop_t *sys,
         bWarned = FALSE;
         for (i = 0; i < ir->pull->ncoord && !bWarned; i++)
         {
-            if (ir->pull->coord[i].group[0] == 0 ||
-                ir->pull->coord[i].group[1] == 0)
+            if (ir->pull->coord[i].eGeom != PullGroupGeometry::Transformation
+                && (ir->pull->coord[i].group[0] == 0 || ir->pull->coord[i].group[1] == 0))
             {
-                absolute_reference(ir, sys, FALSE, AbsRef);
+                const auto absRef     = haveAbsoluteReference(*ir);
+                const auto havePosres = havePositionRestraints(*sys);
                 for (m = 0; m < DIM; m++)
                 {
-                    if (ir->pull->coord[i].dim[m] && !AbsRef[m])
+                    if (ir->pull->coord[i].dim[m] && !(absRef[m] || havePosres[m]))
                     {
-                        warning(wi, "You are using an absolute reference for pulling, but the rest of the system does not have an absolute reference. This will lead to artifacts.");
+                        warning(wi,
+                                "You are using an absolute reference for pulling, but the rest of "
+                                "the system does not have an absolute reference. This will lead to "
+                                "artifacts.");
                         bWarned = TRUE;
                         break;
                     }
@@ -4113,15 +4715,18 @@ void triple_check(const char *mdparin, t_inputrec *ir, gmx_mtop_t *sys,
         {
             for (m = 0; m <= i; m++)
             {
-                if ((ir->epc != epcNO && ir->compress[i][m] != 0) ||
-                    ir->deform[i][m] != 0)
+                if ((ir->epc != PressureCoupling::No && ir->compress[i][m] != 0) || ir->deform[i][m] != 0)
                 {
                     for (c = 0; c < ir->pull->ncoord; c++)
                     {
-                        if (ir->pull->coord[c].eGeom == epullgDIRPBC &&
-                            ir->pull->coord[c].vec[m] != 0)
+                        if (ir->pull->coord[c].eGeom == PullGroupGeometry::DirectionPBC
+                            && ir->pull->coord[c].vec[m] != 0)
                         {
-                            gmx_fatal(FARGS, "Can not have dynamic box while using pull geometry '%s' (dim %c)", EPULLGEOM(ir->pull->coord[c].eGeom), 'x'+m);
+                            gmx_fatal(FARGS,
+                                      "Can not have dynamic box while using pull geometry '%s' "
+                                      "(dim %c)",
+                                      enumValueToString(ir->pull->coord[c].eGeom),
+                                      'x' + m);
                         }
                     }
                 }
@@ -4129,55 +4734,55 @@ void triple_check(const char *mdparin, t_inputrec *ir, gmx_mtop_t *sys,
         }
     }
 
-    check_disre(sys);
+    check_disre(*sys);
 }
 
-void double_check(t_inputrec *ir, matrix box,
-                  bool bHasNormalConstraints,
-                  bool bHasAnyConstraints,
-                  warninp_t wi)
+void double_check(t_inputrec* ir, matrix box, bool bHasNormalConstraints, bool bHasAnyConstraints, warninp_t wi)
 {
     char        warn_buf[STRLEN];
-    const char *ptr;
+    const charptr;
 
-    ptr = check_box(ir->ePBC, box);
+    ptr = check_box(ir->pbcType, box);
     if (ptr)
     {
         warning_error(wi, ptr);
     }
 
-    if (bHasNormalConstraints && ir->eConstrAlg == econtSHAKE)
+    if (bHasNormalConstraints && ir->eConstrAlg == ConstraintAlgorithm::Shake)
     {
         if (ir->shake_tol <= 0.0)
         {
-            sprintf(warn_buf, "ERROR: shake-tol must be > 0 instead of %g\n",
-                    ir->shake_tol);
+            sprintf(warn_buf, "ERROR: shake-tol must be > 0 instead of %g\n", ir->shake_tol);
             warning_error(wi, warn_buf);
         }
     }
 
-    if ( (ir->eConstrAlg == econtLINCS) && bHasNormalConstraints)
+    if ((ir->eConstrAlg == ConstraintAlgorithm::Lincs) && bHasNormalConstraints)
     {
         /* If we have Lincs constraints: */
-        if (ir->eI == eiMD && ir->etc == etcNO &&
-            ir->eConstrAlg == econtLINCS && ir->nLincsIter == 1)
+        if (ir->eI == IntegrationAlgorithm::MD && ir->etc == TemperatureCoupling::No
+            && ir->eConstrAlg == ConstraintAlgorithm::Lincs && ir->nLincsIter == 1)
         {
-            sprintf(warn_buf, "For energy conservation with LINCS, lincs_iter should be 2 or larger.\n");
+            sprintf(warn_buf,
+                    "For energy conservation with LINCS, lincs_iter should be 2 or larger.\n");
             warning_note(wi, warn_buf);
         }
 
-        if ((ir->eI == eiCG || ir->eI == eiLBFGS) && (ir->nProjOrder < 8))
+        if ((ir->eI == IntegrationAlgorithm::CG || ir->eI == IntegrationAlgorithm::LBFGS)
+            && (ir->nProjOrder < 8))
         {
-            sprintf(warn_buf, "For accurate %s with LINCS constraints, lincs-order should be 8 or more.", ei_names[ir->eI]);
+            sprintf(warn_buf,
+                    "For accurate %s with LINCS constraints, lincs-order should be 8 or more.",
+                    enumValueToString(ir->eI));
             warning_note(wi, warn_buf);
         }
-        if (ir->epc == epcMTTK)
+        if (ir->epc == PressureCoupling::Mttk)
         {
             warning_error(wi, "MTTK not compatible with lincs -- use shake instead.");
         }
     }
 
-    if (bHasAnyConstraints && ir->epc == epcMTTK)
+    if (bHasAnyConstraints && ir->epc == PressureCoupling::Mttk)
     {
         warning_error(wi, "Constraints are not implemented with MTTK pressure control.");
     }
@@ -4189,15 +4794,20 @@ void double_check(t_inputrec *ir, matrix box,
         ir->LincsWarnAngle = 90.0;
     }
 
-    if (ir->ePBC != epbcNONE)
+    if (ir->pbcType != PbcType::No)
     {
         if (ir->nstlist == 0)
         {
-            warning(wi, "With nstlist=0 atoms are only put into the box at step 0, therefore drifting atoms might cause the simulation to crash.");
+            warning(wi,
+                    "With nstlist=0 atoms are only put into the box at step 0, therefore drifting "
+                    "atoms might cause the simulation to crash.");
         }
-        if (gmx::square(ir->rlist) >= max_cutoff2(ir->ePBC, box))
+        if (gmx::square(ir->rlist) >= max_cutoff2(ir->pbcType, box))
         {
-            sprintf(warn_buf, "ERROR: The cut-off length is longer than half the shortest box vector or longer than the smallest box diagonal element. Increase the box size or decrease rlist.\n");
+            sprintf(warn_buf,
+                    "ERROR: The cut-off length is longer than half the shortest box vector or "
+                    "longer than the smallest box diagonal element. Increase the box size or "
+                    "decrease rlist.\n");
             warning_error(wi, warn_buf);
         }
     }