Merge remote-tracking branch 'origin/release-2021' into master
authorAndrey Alekseenko <al42and@gmail.com>
Tue, 14 Sep 2021 17:16:06 +0000 (20:16 +0300)
committerAndrey Alekseenko <al42and@gmail.com>
Tue, 14 Sep 2021 17:20:56 +0000 (20:20 +0300)
Conflicts:
cmake/gmxVersionInfo.cmake
src/gromacs/ewald/pme_gather.cu
src/gromacs/ewald/pme_gpu_calculate_splines.cuh
src/gromacs/ewald/pme_gpu_internal.cpp
src/gromacs/ewald/tests/pmetestcommon.cpp
src/gromacs/gmxana/gmx_cluster.cpp
src/gromacs/gmxpreprocess/gen_vsite.cpp
src/gromacs/simd/support.cpp
src/gromacs/tools/trjconv.cpp

19 files changed:
1  2 
cmake/gmxVersionInfo.cmake
docs/user-guide/mdrun-performance.rst
src/gromacs/analysisdata/modules/histogram.h
src/gromacs/ewald/pme_gather.cu
src/gromacs/ewald/pme_gpu_calculate_splines.cuh
src/gromacs/ewald/pme_gpu_internal.cpp
src/gromacs/ewald/pme_spread.clh
src/gromacs/ewald/pme_spread.cu
src/gromacs/ewald/tests/pmetestcommon.cpp
src/gromacs/gmxana/gmx_dielectric.cpp
src/gromacs/gmxana/gmx_rmsf.cpp
src/gromacs/gmxana/gmx_wham.cpp
src/gromacs/gmxpreprocess/gen_vsite.cpp
src/gromacs/gmxpreprocess/readir.cpp
src/gromacs/linearalgebra/eigensolver.cpp
src/gromacs/simd/support.cpp
src/gromacs/tools/eneconv.cpp
src/gromacs/tools/trjconv.cpp
src/gromacs/utility/coolstuff.cpp

index c4485a330f4951445b192353fb0abcb689a7b7a2,e9ef890f20a36b98456460170caa2cae4a34b248..34c3a02492a22c7c6488ef6c7352d5b0474e88cd
@@@ -60,7 -60,6 +60,7 @@@
  #         GROMACS     2019   4
  #         GROMACS     2020   5
  #         GROMACS     2021   6
 +#         GROMACS     2022   7
  #   LIBRARY_SOVERSION_MINOR so minor version for the built libraries.
  #       Should be increased for each release that changes only the implementation.
  #       In GROMACS, the typical policy is to increase it for each patch version
  
  # The GROMACS convention is that these are the version number of the next
  # release that is going to be made from this branch.
 -set(GMX_VERSION_MAJOR 2021)
 -set(GMX_VERSION_PATCH 4)
 +set(GMX_VERSION_MAJOR 2022)
 +set(GMX_VERSION_PATCH 0)
  # The suffix, on the other hand, is used mainly for betas and release
  # candidates, where it signifies the most recent such release from
  # this branch; it will be empty before the first such release, as well
@@@ -215,7 -214,7 +215,7 @@@ set(GMX_VERSION_SUFFIX ""
  # here. The important thing is to minimize the chance of third-party
  # code being able to dynamically link with a version of libgromacs
  # that might not work.
 -set(LIBRARY_SOVERSION_MAJOR 6)
 +set(LIBRARY_SOVERSION_MAJOR 7)
  set(LIBRARY_SOVERSION_MINOR 0)
  set(LIBRARY_VERSION ${LIBRARY_SOVERSION_MAJOR}.${LIBRARY_SOVERSION_MINOR}.0)
  
@@@ -229,6 -228,15 +229,15 @@@ else(
  endif()
  set(GMX_VERSION_STRING "${GMX_VERSION}${GMX_VERSION_SUFFIX}")
  
 -set(REGRESSIONTEST_BRANCH "release-2021")
+ set(REGRESSIONTEST_VERSION "${GMX_VERSION_STRING}")
++set(REGRESSIONTEST_BRANCH "master")
+ # Run the regressiontests packaging job with the correct pakage
+ # version string, and the release box checked, in order to have it
+ # build the regressiontests tarball with all the right naming. The
+ # naming affects the md5sum that has to go here, and if it isn't right
+ # release workflow will report a failure.
+ set(REGRESSIONTEST_MD5SUM "93956ea42c4d16fdd541518c05972989" CACHE INTERNAL "MD5 sum of the regressiontests tarball for this GROMACS version")
  # If you are making a custom fork of GROMACS, please describe your
  # fork, perhaps with its version number, in the value of
  # GMX_VERSION_STRING_OF_FORK here. This string will appear in the
@@@ -256,20 -264,11 +265,11 @@@ if (NOT SOURCE_IS_SOURCE_DISTRIBUTION A
      set(GMX_VERSION_STRING "${GMX_VERSION_STRING}-dev")
  endif()
  
- set(REGRESSIONTEST_VERSION "${GMX_VERSION_STRING}")
- set(REGRESSIONTEST_BRANCH "master")
- # Run the regressiontests packaging job with the correct pakage
- # version string, and the release box checked, in order to have it
- # build the regressiontests tarball with all the right naming. The
- # naming affects the md5sum that has to go here, and if it isn't right
- # release workflow will report a failure.
- set(REGRESSIONTEST_MD5SUM "93956ea42c4d16fdd541518c05972989" CACHE INTERNAL "MD5 sum of the regressiontests tarball for this GROMACS version")
  math(EXPR GMX_VERSION_NUMERIC
       "${GMX_VERSION_MAJOR}*10000 + ${GMX_VERSION_PATCH}")
  set(GMX_API_VERSION ${GMX_VERSION_NUMERIC})
  
- # If run with cmake -P from releng scripts, print out necessary version info
+ # If run with cmake -P from GitLab scripts, print out necessary version info
  # as JSON.
  if (CMAKE_SCRIPT_MODE_FILE)
      message("{ \"version\": \"${GMX_VERSION_STRING}\", \"regressiontest-md5sum\": \"${REGRESSIONTEST_MD5SUM}\" }")
index e97494a76c63447f15d2b6e096ca86556ae81c59,8fb1bdab826c0fb5a51860b8ed55730c3a4d2ffb..fce655a622c9f798dd38c7d9607fd3f40859a4ab
@@@ -759,7 -759,7 +759,7 @@@ Running :ref:`mdrun <gmx mdrun>` on mor
  
  This requires configuring |Gromacs| to build with an external MPI
  library. By default, this :ref:`mdrun <gmx mdrun>` executable is run with
 -:ref:`mdrun_mpi`. All of the considerations for running single-node
 +``gmx_mpi mdrun``. All of the considerations for running single-node
  :ref:`mdrun <gmx mdrun>` still apply, except that ``-ntmpi`` and ``-nt`` cause a fatal
  error, and instead the number of ranks is controlled by the
  MPI environment.
@@@ -830,7 -830,7 +830,7 @@@ to choose the number of MPI ranks
  
      mpirun -np 16 gmx_mpi mdrun
  
 -Starts :ref:`mdrun_mpi` with 16 ranks, which are mapped to
 +Starts :ref:`gmx mdrun` with 16 ranks, which are mapped to
  the hardware by the MPI library, e.g. as specified
  in an MPI hostfile. The available cores will be
  automatically split among ranks using OpenMP threads,
@@@ -841,7 -841,7 +841,7 @@@ such as ``OMP_NUM_THREADS``
  
      mpirun -np 16 gmx_mpi mdrun -npme 5
  
 -Starts :ref:`mdrun_mpi` with 16 ranks, as above, and
 +Starts :ref:`gmx mdrun` with 16 ranks, as above, and
  require that 5 of them are dedicated to the PME
  component.
  
  
      mpirun -np 11 gmx_mpi mdrun -ntomp 2 -npme 6 -ntomp_pme 1
  
 -Starts :ref:`mdrun_mpi` with 11 ranks, as above, and
 +Starts :ref:`gmx mdrun` with 11 ranks, as above, and
  require that six of them are dedicated to the PME
  component with one OpenMP thread each. The remaining
  five do the PP component, with two OpenMP threads
@@@ -859,7 -859,7 +859,7 @@@ each
  
      mpirun -np 4 gmx_mpi mdrun -ntomp 6 -nb gpu -gputasks 00
  
 -Starts :ref:`mdrun_mpi` on a machine with two nodes, using
 +Starts :ref:`gmx mdrun` on a machine with two nodes, using
  four total ranks, each rank with six OpenMP threads,
  and both ranks on a node sharing GPU with ID 0.
  
      mpirun -np 8 gmx_mpi mdrun -ntomp 3 -gputasks 0000
  
  Using a same/similar hardware as above,
 -starts :ref:`mdrun_mpi` on a machine with two nodes, using
 +starts :ref:`gmx mdrun` on a machine with two nodes, using
  eight total ranks, each rank with three OpenMP threads,
  and all four ranks on a node sharing GPU with ID 0.
  This may or may not be faster than the previous setup
@@@ -878,7 -878,7 +878,7 @@@ on the same hardware
  
      mpirun -np 20 gmx_mpi mdrun -ntomp 4 -gputasks 00
  
 -Starts :ref:`mdrun_mpi` with 20 ranks, and assigns the CPU cores evenly
 +Starts :ref:`gmx mdrun` with 20 ranks, and assigns the CPU cores evenly
  across ranks each to one OpenMP thread. This setup is likely to be
  suitable when there are ten nodes, each with one GPU, and each node
  has two sockets each of four cores.
  
      mpirun -np 10 gmx_mpi mdrun -gpu_id 1
  
 -Starts :ref:`mdrun_mpi` with 20 ranks, and assigns the CPU cores evenly
 +Starts :ref:`gmx mdrun` with 20 ranks, and assigns the CPU cores evenly
  across ranks each to one OpenMP thread. This setup is likely to be
  suitable when there are ten nodes, each with two GPUs, but another
  job on each node is using GPU 0. The job scheduler should set the
@@@ -898,7 -898,7 +898,7 @@@ performance of :ref:`mdrun <gmx mdrun>
  
      mpirun -np 20 gmx_mpi mdrun -gpu_id 01
  
 -Starts :ref:`mdrun_mpi` with 20 ranks. This setup is likely
 +Starts :ref:`gmx mdrun` with 20 ranks. This setup is likely
  to be suitable when there are ten nodes, each with two
  GPUs, but there is no need to specify ``-gpu_id`` for the
  normal case where all the GPUs on the node are available
@@@ -922,7 -922,7 +922,7 @@@ The Wallcycle module is used for runtim
  At the end of the log file of each run, the "Real cycle and time accounting" section
  provides a table with runtime statistics for different parts of the :ref:`gmx mdrun` code
  in rows of the table.
- The table contains colums indicating the number of ranks and threads that
+ The table contains columns indicating the number of ranks and threads that
  executed the respective part of the run, wall-time and cycle
  count aggregates (across all threads and ranks) averaged over the entire run.
  The last column also shows what precentage of the total runtime each row represents.
@@@ -1362,7 -1362,7 +1362,7 @@@ of 2. So it can be useful go through th
  * Don't use double precision unless you're absolute sure you need it.
  * Compile the FFTW library (yourself) with the correct flags on x86 (in most
    cases, the correct flags are automatically configured).
 -* On x86, use gcc or icc as the compiler (not pgi or the Cray compiler).
 +* On x86, use gcc as the compiler (not icc, pgi or the Cray compiler).
  * On POWER, use gcc instead of IBM's xlc.
  * Use a new compiler version, especially for gcc (e.g. from version 5 to 6
    the performance of the compiled code improved a lot).
index 8f4b5c39dfa7b62dad16bb2af770b4e2d210be7b,0e653b6f798ad0a6df77f87021d4806059ba7bf8..f71a1e0b615fc53a245bf2d0366cbd6fab45a35c
@@@ -425,7 -425,7 +425,7 @@@ private
  
      class Impl;
  
 -    PrivateImplPointer<Impl> impl_;
 +    std::unique_ptr<Impl> impl_;
  
      // Copy and assign disallowed by base.
  };
   * data.  Each frame contains the histogram(s) for the points in that frame,
   * interpreted such that the first column passed to pointsAdded() determines
   * the bin and the rest give weights to be added to that bin (input data should
-  * have at least two colums, and at least two columns should be added at the
+  * have at least two columns, and at least two columns should be added at the
   * same time).
   * Each input data set is processed independently into the corresponding output
   * data set.
@@@ -487,7 -487,7 +487,7 @@@ private
  
      class Impl;
  
 -    PrivateImplPointer<Impl> impl_;
 +    std::unique_ptr<Impl> impl_;
  
      // Copy and assign disallowed by base.
  };
   * that bin.
   * The input data is interpreted such that the first column passed to
   * pointsAdded() determines the bin and the rest give values to be added to
-  * that bin (input data should have at least two colums, and at least two
+  * that bin (input data should have at least two columns, and at least two
   * columns should be added at the same time).
   * All input columns for a data set are averaged into the same histogram.
   *
@@@ -535,7 -535,7 +535,7 @@@ public
  private:
      class Impl;
  
 -    PrivateImplPointer<Impl> impl_;
 +    std::unique_ptr<Impl> impl_;
  
      // Copy and assign disallowed by base.
  };
index eedee8a67e94b15329434b6a2a654dab43276f73,40eea2d2dc6a30a527fb9616cf7a636d7c4dc906..ec1d9ecbcfd74fa9c1aed1d38d72d6a90119f5df
@@@ -1,8 -1,7 +1,8 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
 - * Copyright (c) 2016,2017,2018,2019,2020,2021, by the GROMACS development team, led by
 + * Copyright (c) 2016,2017,2018,2019,2020 by the GROMACS development team.
 + * Copyright (c) 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.
@@@ -63,7 -62,7 +63,7 @@@ __device__ __forceinline__ float read_g
          case ZZ: return realGridSizeFP[ZZ];
      }
      assert(false);
 -    return 0.0f;
 +    return 0.0F;
  }
  
  /*! \brief Reduce the partial force contributions.
@@@ -89,11 -88,11 +89,11 @@@ __device__ __forceinline__ void reduce_
                                                     const int    splineIndex,
                                                     const int    lineIndex,
                                                     const float* realGridSizeFP,
 -                                                   float&       fx,
 -                                                   float&       fy,
 -                                                   float&       fz)
 +                                                   float& fx, // NOLINT(google-runtime-references)
 +                                                   float& fy, // NOLINT(google-runtime-references)
 +                                                   float& fz) // NOLINT(google-runtime-references)
  {
 -    if (!(order & (order - 1))) // Only for orders of power of 2
 +    if (gmx::isPowerOfTwo(order)) // Only for orders of power of 2
      {
          const unsigned int activeMask = c_fullWarpMask;
  
          if (dimIndex < DIM)
          {
              const float n = read_grid_size(realGridSizeFP, dimIndex);
 -            *((float*)(&sm_forces[atomIndexLocal]) + dimIndex) = fx * n;
 +            float* __restrict__ sm_forcesAtomIndexOffset =
 +                    reinterpret_cast<float*>(&sm_forces[atomIndexLocal]);
 +            sm_forcesAtomIndexOffset[dimIndex] = fx * n;
          }
      }
      else
  
              if (sourceIndex == minStride * atomIndex)
              {
 -                *((float*)(&sm_forces[atomIndex]) + dimIndex) =
 +                float* __restrict__ sm_forcesAtomIndexOffset =
 +                        reinterpret_cast<float*>(&sm_forces[atomIndex]);
 +                sm_forcesAtomIndexOffset[dimIndex] =
                          (sm_forceTemp[dimIndex][sourceIndex] + sm_forceTemp[dimIndex][sourceIndex + 1]) * n;
              }
          }
@@@ -355,8 -350,7 +355,8 @@@ __launch_bounds__(c_gatherMaxThreadsPer
      const float* __restrict__ gm_coefficientsB = kernelParams.atoms.d_coefficients[1];
      const float* __restrict__ gm_gridA         = kernelParams.grid.d_realGrid[0];
      const float* __restrict__ gm_gridB         = kernelParams.grid.d_realGrid[1];
 -    float* __restrict__ gm_forces              = kernelParams.atoms.d_forces;
 +    static_assert(sizeof(*kernelParams.atoms.d_forces) == 3 * sizeof(float));
 +    float* __restrict__ gm_forces = reinterpret_cast<float*>(kernelParams.atoms.d_forces);
  
      /* Global memory pointers for readGlobal */
      const float* __restrict__ gm_theta         = kernelParams.atoms.d_theta;
              atomX      = gm_coordinates[atomIndexGlobal];
              atomCharge = gm_coefficientsA[atomIndexGlobal];
          }
-         calculate_splines<order, atomsPerBlock, atomsPerWarp, true, false>(
+         calculate_splines<order, atomsPerBlock, atomsPerWarp, true, false, numGrids>(
                  kernelParams, atomIndexOffset, atomX, atomCharge, sm_theta, sm_dtheta, sm_gridlineIndices);
          __syncwarp();
      }
 -    float fx = 0.0f;
 -    float fy = 0.0f;
 -    float fz = 0.0f;
 +    float fx = 0.0F;
 +    float fy = 0.0F;
 +    float fz = 0.0F;
  
      const int chargeCheck = pme_gpu_check_atom_charge(gm_coefficientsA[atomIndexGlobal]);
  
      const int ithyMax = (threadsPerAtom == ThreadsPerAtom::Order) ? order : threadIdx.y + 1;
      if (chargeCheck)
      {
 -        sumForceComponents<order, atomsPerWarp, wrapX, wrapY>(
 -                &fx, &fy, &fz, ithyMin, ithyMax, ixBase, iz, nx, ny, pny, pnz, atomIndexLocal,
 -                splineIndexBase, tdz, sm_gridlineIndices, sm_theta, sm_dtheta, gm_gridA);
 +        sumForceComponents<order, atomsPerWarp, wrapX, wrapY>(&fx,
 +                                                              &fy,
 +                                                              &fz,
 +                                                              ithyMin,
 +                                                              ithyMax,
 +                                                              ixBase,
 +                                                              iz,
 +                                                              nx,
 +                                                              ny,
 +                                                              pny,
 +                                                              pnz,
 +                                                              atomIndexLocal,
 +                                                              splineIndexBase,
 +                                                              tdz,
 +                                                              sm_gridlineIndices,
 +                                                              sm_theta,
 +                                                              sm_dtheta,
 +                                                              gm_gridA);
      }
      // Reduction of partial force contributions
      __shared__ float3 sm_forces[atomsPerBlock];
 -    reduce_atom_forces<order, atomDataSize, blockSize>(sm_forces, atomIndexLocal, splineIndex, lineIndex,
 -                                                       kernelParams.grid.realGridSizeFP, fx, fy, fz);
 +    reduce_atom_forces<order, atomDataSize, blockSize>(
 +            sm_forces, atomIndexLocal, splineIndex, lineIndex, kernelParams.grid.realGridSizeFP, fx, fy, fz);
      __syncthreads();
  
      /* Calculating the final forces with no component branching, atomsPerBlock threads */
      const float scale            = kernelParams.current.scale;
      if (forceIndexLocal < atomsPerBlock)
      {
 -        calculateAndStoreGridForces(sm_forces, forceIndexLocal, forceIndexGlobal,
 -                                    kernelParams.current.recipBox, scale, gm_coefficientsA);
 +        calculateAndStoreGridForces(
 +                sm_forces, forceIndexLocal, forceIndexGlobal, kernelParams.current.recipBox, scale, gm_coefficientsA);
      }
  
      __syncwarp();
          {
              int   outputIndexLocal       = i * iterThreads + threadLocalId;
              int   outputIndexGlobal      = blockIndex * blockForcesSize + outputIndexLocal;
 -            float outputForceComponent   = ((float*)sm_forces)[outputIndexLocal];
 +            float outputForceComponent   = (reinterpret_cast<float*>(sm_forces)[outputIndexLocal]);
              gm_forces[outputIndexGlobal] = outputForceComponent;
          }
      }
      {
          /* We must sync here since the same shared memory is used as above. */
          __syncthreads();
 -        fx                    = 0.0f;
 -        fy                    = 0.0f;
 -        fz                    = 0.0f;
 +        fx                    = 0.0F;
 +        fy                    = 0.0F;
 +        fz                    = 0.0F;
          const int chargeCheck = pme_gpu_check_atom_charge(gm_coefficientsB[atomIndexGlobal]);
          if (chargeCheck)
          {
 -            sumForceComponents<order, atomsPerWarp, wrapX, wrapY>(
 -                    &fx, &fy, &fz, ithyMin, ithyMax, ixBase, iz, nx, ny, pny, pnz, atomIndexLocal,
 -                    splineIndexBase, tdz, sm_gridlineIndices, sm_theta, sm_dtheta, gm_gridB);
 +            sumForceComponents<order, atomsPerWarp, wrapX, wrapY>(&fx,
 +                                                                  &fy,
 +                                                                  &fz,
 +                                                                  ithyMin,
 +                                                                  ithyMax,
 +                                                                  ixBase,
 +                                                                  iz,
 +                                                                  nx,
 +                                                                  ny,
 +                                                                  pny,
 +                                                                  pnz,
 +                                                                  atomIndexLocal,
 +                                                                  splineIndexBase,
 +                                                                  tdz,
 +                                                                  sm_gridlineIndices,
 +                                                                  sm_theta,
 +                                                                  sm_dtheta,
 +                                                                  gm_gridB);
          }
          // Reduction of partial force contributions
 -        reduce_atom_forces<order, atomDataSize, blockSize>(sm_forces, atomIndexLocal, splineIndex,
 -                                                           lineIndex, kernelParams.grid.realGridSizeFP,
 -                                                           fx, fy, fz);
 +        reduce_atom_forces<order, atomDataSize, blockSize>(
 +                sm_forces, atomIndexLocal, splineIndex, lineIndex, kernelParams.grid.realGridSizeFP, fx, fy, fz);
          __syncthreads();
  
          /* Calculating the final forces with no component branching, atomsPerBlock threads */
          if (forceIndexLocal < atomsPerBlock)
          {
 -            calculateAndStoreGridForces(sm_forces, forceIndexLocal, forceIndexGlobal,
 -                                        kernelParams.current.recipBox, 1.0F - scale, gm_coefficientsB);
 +            calculateAndStoreGridForces(sm_forces,
 +                                        forceIndexLocal,
 +                                        forceIndexGlobal,
 +                                        kernelParams.current.recipBox,
 +                                        1.0F - scale,
 +                                        gm_coefficientsB);
          }
  
          __syncwarp();
              {
                  int   outputIndexLocal     = i * iterThreads + threadLocalId;
                  int   outputIndexGlobal    = blockIndex * blockForcesSize + outputIndexLocal;
 -                float outputForceComponent = ((float*)sm_forces)[outputIndexLocal];
 +                float outputForceComponent = (reinterpret_cast<float*>(sm_forces)[outputIndexLocal]);
                  gm_forces[outputIndexGlobal] += outputForceComponent;
              }
          }
index 6e663175e0eefd05b04bed49d7616b6fecbd97f8,1e3ccefb01aa57bf1aaa268efcbe16aee13ecf03..1ff60f7ed17497fceda74771c9269667f3775df6
@@@ -104,10 -104,10 +104,10 @@@ int __device__ __forceinline__ getSplin
   *
   * This is called from the spline_and_spread and gather PME kernels.
   */
 -int __device__ __forceinline__ pme_gpu_check_atom_charge(const float coefficient)
 +bool __device__ __forceinline__ pme_gpu_check_atom_charge(const float coefficient)
  {
      assert(isfinite(coefficient));
 -    return c_skipNeutralAtoms ? (coefficient != 0.0f) : 1;
 +    return c_skipNeutralAtoms ? (coefficient != 0.0F) : true;
  }
  
  //! Controls if the atom and charge data is prefeched into shared memory or loaded per thread from global
@@@ -126,15 -126,15 +126,15 @@@ __device__ inline void assertIsFinite(
  template<>
  __device__ inline void assertIsFinite(float3 gmx_unused arg)
  {
 -    assert(isfinite(float(arg.x)));
 -    assert(isfinite(float(arg.y)));
 -    assert(isfinite(float(arg.z)));
 +    assert(isfinite(static_cast<float>(arg.x)));
 +    assert(isfinite(static_cast<float>(arg.y)));
 +    assert(isfinite(static_cast<float>(arg.z)));
  }
  
  template<typename T>
  __device__ inline void assertIsFinite(T gmx_unused arg)
  {
 -    assert(isfinite(float(arg)));
 +    assert(isfinite(static_cast<float>(arg)));
  }
  
  /*! \brief
@@@ -173,8 -173,12 +173,12 @@@ __device__ __forceinline__ void pme_gpu
   * \tparam[in] atomsPerBlock        Number of atoms processed by a block - should be accounted for
   *                                  in the sizes of the shared memory arrays.
   * \tparam[in] atomsPerWarp         Number of atoms processed by a warp
-  * \tparam[in] writeSmDtheta        Bool controlling if the theta derivative should be written to shared memory. Enables calculation of dtheta if set.
-  * \tparam[in] writeGlobal          A boolean which tells if the theta values and gridlines should be written to global memory. Enables calculation of dtheta if set.
 - * \tparam[in] writeSmDtheta        Bool controling if the theta derivative should be written to
++ * \tparam[in] writeSmDtheta        Bool controlling if the theta derivative should be written to
+  *                                  shared memory. Enables calculation of dtheta if set.
+  * \tparam[in] writeGlobal          A boolean which tells if the theta values and gridlines should
+  *                                  be written to global memory. Enables calculation of dtheta if
+  *                                  set.
+  * \tparam[in] numGrids             The number of grids using the splines.
   * \param[in]  kernelParams         Input PME CUDA data in constant memory.
   * \param[in]  atomIndexOffset      Starting atom index for the execution block w.r.t. global memory.
   * \param[in]  atomX                Atom coordinate of atom processed by thread.
   * \param[out] sm_gridlineIndices   Atom gridline indices in the shared memory.
   */
  
- template<int order, int atomsPerBlock, int atomsPerWarp, bool writeSmDtheta, bool writeGlobal>
+ template<int order, int atomsPerBlock, int atomsPerWarp, bool writeSmDtheta, bool writeGlobal, int numGrids>
  __device__ __forceinline__ void calculate_splines(const PmeGpuCudaKernelParams kernelParams,
                                                    const int                    atomIndexOffset,
                                                    const float3                 atomX,
                                                    float* __restrict__ sm_dtheta,
                                                    int* __restrict__ sm_gridlineIndices)
  {
+     assert(numGrids == 1 || numGrids == 2);
+     assert(numGrids == 1 || c_skipNeutralAtoms == false);
      /* Global memory pointers for output */
      float* __restrict__ gm_theta         = kernelParams.atoms.d_theta;
      float* __restrict__ gm_dtheta        = kernelParams.atoms.d_dtheta;
              const float shift = c_pmeMaxUnitcellShift;
              /* Fractional coordinates along box vectors, adding a positive shift to ensure t is positive for triclinic boxes */
              t    = (t + shift) * n;
 -            tInt = (int)t;
 +            tInt = static_cast<int>(t);
              assert(sharedMemoryIndex < atomsPerBlock * DIM);
              sm_fractCoords[sharedMemoryIndex] = t - tInt;
              tableIndex += tInt;
  
              // TODO have shared table for both parameters to share the fetch, as index is always same?
              // TODO compare texture/LDG performance
 -            sm_fractCoords[sharedMemoryIndex] +=
 -                    fetchFromParamLookupTable(kernelParams.grid.d_fractShiftsTable,
 -                                              kernelParams.fractShiftsTableTexture, tableIndex);
 +            sm_fractCoords[sharedMemoryIndex] += fetchFromParamLookupTable(
 +                    kernelParams.grid.d_fractShiftsTable, kernelParams.fractShiftsTableTexture, tableIndex);
              sm_gridlineIndices[sharedMemoryIndex] =
                      fetchFromParamLookupTable(kernelParams.grid.d_gridlineIndicesTable,
 -                                              kernelParams.gridlineIndicesTableTexture, tableIndex);
 +                                              kernelParams.gridlineIndicesTableTexture,
 +                                              tableIndex);
              if (writeGlobal)
              {
                  gm_gridlineIndices[atomIndexOffset * DIM + sharedMemoryIndex] =
          /* B-spline calculation */
  
          const int chargeCheck = pme_gpu_check_atom_charge(atomCharge);
-         if (chargeCheck)
+         /* With FEP (numGrids == 2), we might have 0 charge in state A, but !=0 in state B, so we always calculate splines */
+         if (numGrids == 2 || chargeCheck)
          {
              float div;
              int o = orderIndex; // This is an index that is set once for PME_GPU_PARALLEL_SPLINE == 1
              assert(isfinite(dr));
  
              /* dr is relative offset from lower cell limit */
 -            splineData[order - 1] = 0.0f;
 +            splineData[order - 1] = 0.0F;
              splineData[1]         = dr;
 -            splineData[0]         = 1.0f - dr;
 +            splineData[0]         = 1.0F - dr;
  
  #pragma unroll
              for (int k = 3; k < order; k++)
              {
 -                div               = 1.0f / (k - 1.0f);
 +                div               = 1.0F / (k - 1.0F);
                  splineData[k - 1] = div * dr * splineData[k - 2];
  #pragma unroll
                  for (int l = 1; l < (k - 1); l++)
                      splineData[k - l - 1] =
                              div * ((dr + l) * splineData[k - l - 2] + (k - l - dr) * splineData[k - l - 1]);
                  }
 -                splineData[0] = div * (1.0f - dr) * splineData[0];
 +                splineData[0] = div * (1.0F - dr) * splineData[0];
              }
  
              const int thetaIndexBase =
                      const int thetaIndex =
                              getSplineParamIndex<order, atomsPerWarp>(thetaIndexBase, dimIndex, o);
  
 -                    const float dtheta = ((o > 0) ? splineData[o - 1] : 0.0f) - splineData[o];
 +                    const float dtheta = ((o > 0) ? splineData[o - 1] : 0.0F) - splineData[o];
                      assert(isfinite(dtheta));
                      assert(thetaIndex < order * DIM * atomsPerBlock);
                      if (writeSmDtheta)
                  }
              }
  
 -            div                   = 1.0f / (order - 1.0f);
 +            div                   = 1.0F / (order - 1.0F);
              splineData[order - 1] = div * dr * splineData[order - 2];
  #pragma unroll
              for (int k = 1; k < (order - 1); k++)
                                              * ((dr + k) * splineData[order - k - 2]
                                                 + (order - k - dr) * splineData[order - k - 1]);
              }
 -            splineData[0] = div * (1.0f - dr) * splineData[0];
 +            splineData[0] = div * (1.0F - dr) * splineData[0];
  
              /* Storing the spline values (theta) */
  #pragma unroll
index e9998c803e78c142d439703f7520510f2f44b38c,8b9dea9fab9d5734c2e283c3e28e8736689f119a..185fea7de3420607514eb54726087591e39f63a2
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2016,2017,2018,2019,2020, by the GROMACS development team.
+  * Copyright (c) 2016,2017,2018,2019,2020 by the GROMACS development team.
   * Copyright (c) 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
  #include <string>
  
  #include "gromacs/ewald/ewald_utils.h"
 +#include "gromacs/fft/gpu_3dfft.h"
  #include "gromacs/gpu_utils/device_context.h"
  #include "gromacs/gpu_utils/device_stream.h"
  #include "gromacs/gpu_utils/gpu_utils.h"
 +#include "gromacs/gpu_utils/pmalloc.h"
 +#if GMX_GPU_SYCL
 +#    include "gromacs/gpu_utils/syclutils.h"
 +#endif
  #include "gromacs/hardware/device_information.h"
  #include "gromacs/math/invertmatrix.h"
  #include "gromacs/math/units.h"
  #include "gromacs/utility/gmxassert.h"
  #include "gromacs/utility/logger.h"
  #include "gromacs/utility/stringutil.h"
 +#include "gromacs/ewald/pme.h"
  
  #if GMX_GPU_CUDA
 -#    include "gromacs/gpu_utils/pmalloc_cuda.h"
 -
  #    include "pme.cuh"
 -#elif GMX_GPU_OPENCL
 -#    include "gromacs/gpu_utils/gmxopencl.h"
  #endif
  
 -#include "gromacs/ewald/pme.h"
 -
 -#include "pme_gpu_3dfft.h"
  #include "pme_gpu_calculate_splines.h"
  #include "pme_gpu_constants.h"
  #include "pme_gpu_program_impl.h"
@@@ -142,8 -143,7 +142,8 @@@ void pme_gpu_alloc_energy_virial(PmeGpu
      for (int gridIndex = 0; gridIndex < pmeGpu->common->ngrids; gridIndex++)
      {
          allocateDeviceBuffer(&pmeGpu->kernelParams->constants.d_virialAndEnergy[gridIndex],
 -                             c_virialAndEnergyCount, pmeGpu->archSpecific->deviceContext_);
 +                             c_virialAndEnergyCount,
 +                             pmeGpu->archSpecific->deviceContext_);
          pmalloc(reinterpret_cast<void**>(&pmeGpu->staging.h_virialAndEnergy[gridIndex]), energyAndVirialSize);
      }
  }
@@@ -162,10 -162,8 +162,10 @@@ void pme_gpu_clear_energy_virial(const 
  {
      for (int gridIndex = 0; gridIndex < pmeGpu->common->ngrids; gridIndex++)
      {
 -        clearDeviceBufferAsync(&pmeGpu->kernelParams->constants.d_virialAndEnergy[gridIndex], 0,
 -                               c_virialAndEnergyCount, pmeGpu->archSpecific->pmeStream_);
 +        clearDeviceBufferAsync(&pmeGpu->kernelParams->constants.d_virialAndEnergy[gridIndex],
 +                               0,
 +                               c_virialAndEnergyCount,
 +                               pmeGpu->archSpecific->pmeStream_);
      }
  }
  
@@@ -177,8 -175,7 +177,8 @@@ void pme_gpu_realloc_and_copy_bspline_v
      GMX_ASSERT(gridIndex < pmeGpu->common->ngrids,
                 "Invalid combination of gridIndex and number of grids");
  
 -    const int splineValuesOffset[DIM] = { 0, pmeGpu->kernelParams->grid.realGridSize[XX],
 +    const int splineValuesOffset[DIM] = { 0,
 +                                          pmeGpu->kernelParams->grid.realGridSize[XX],
                                            pmeGpu->kernelParams->grid.realGridSize[XX]
                                                    + pmeGpu->kernelParams->grid.realGridSize[YY] };
      memcpy(&pmeGpu->kernelParams->grid.splineValuesOffset, &splineValuesOffset, sizeof(splineValuesOffset));
                                      + pmeGpu->kernelParams->grid.realGridSize[ZZ];
      const bool shouldRealloc = (newSplineValuesSize > pmeGpu->archSpecific->splineValuesSize[gridIndex]);
      reallocateDeviceBuffer(&pmeGpu->kernelParams->grid.d_splineModuli[gridIndex],
 -                           newSplineValuesSize, &pmeGpu->archSpecific->splineValuesSize[gridIndex],
 +                           newSplineValuesSize,
 +                           &pmeGpu->archSpecific->splineValuesSize[gridIndex],
                             &pmeGpu->archSpecific->splineValuesCapacity[gridIndex],
                             pmeGpu->archSpecific->deviceContext_);
      if (shouldRealloc)
      for (int i = 0; i < DIM; i++)
      {
          memcpy(pmeGpu->staging.h_splineModuli[gridIndex] + splineValuesOffset[i],
 -               pmeGpu->common->bsp_mod[i].data(), pmeGpu->common->bsp_mod[i].size() * sizeof(float));
 +               pmeGpu->common->bsp_mod[i].data(),
 +               pmeGpu->common->bsp_mod[i].size() * sizeof(float));
      }
      /* TODO: pin original buffer instead! */
      copyToDeviceBuffer(&pmeGpu->kernelParams->grid.d_splineModuli[gridIndex],
 -                       pmeGpu->staging.h_splineModuli[gridIndex], 0, newSplineValuesSize,
 -                       pmeGpu->archSpecific->pmeStream_, pmeGpu->settings.transferKind, nullptr);
 +                       pmeGpu->staging.h_splineModuli[gridIndex],
 +                       0,
 +                       newSplineValuesSize,
 +                       pmeGpu->archSpecific->pmeStream_,
 +                       pmeGpu->settings.transferKind,
 +                       nullptr);
  }
  
  void pme_gpu_free_bspline_values(const PmeGpu* pmeGpu)
  
  void pme_gpu_realloc_forces(PmeGpu* pmeGpu)
  {
 -    const size_t newForcesSize = pmeGpu->nAtomsAlloc * DIM;
 +    const size_t newForcesSize = pmeGpu->nAtomsAlloc;
      GMX_ASSERT(newForcesSize > 0, "Bad number of atoms in PME GPU");
 -    reallocateDeviceBuffer(&pmeGpu->kernelParams->atoms.d_forces, newForcesSize,
 -                           &pmeGpu->archSpecific->forcesSize, &pmeGpu->archSpecific->forcesSizeAlloc,
 +    reallocateDeviceBuffer(&pmeGpu->kernelParams->atoms.d_forces,
 +                           newForcesSize,
 +                           &pmeGpu->archSpecific->forcesSize,
 +                           &pmeGpu->archSpecific->forcesSizeAlloc,
                             pmeGpu->archSpecific->deviceContext_);
      pmeGpu->staging.h_forces.reserveWithPadding(pmeGpu->nAtomsAlloc);
      pmeGpu->staging.h_forces.resizeWithPadding(pmeGpu->kernelParams->atoms.nAtoms);
@@@ -245,25 -234,19 +245,25 @@@ void pme_gpu_free_forces(const PmeGpu* 
  void pme_gpu_copy_input_forces(PmeGpu* pmeGpu)
  {
      GMX_ASSERT(pmeGpu->kernelParams->atoms.nAtoms > 0, "Bad number of atoms in PME GPU");
 -    float* h_forcesFloat = reinterpret_cast<float*>(pmeGpu->staging.h_forces.data());
 -    copyToDeviceBuffer(&pmeGpu->kernelParams->atoms.d_forces, h_forcesFloat, 0,
 -                       DIM * pmeGpu->kernelParams->atoms.nAtoms, pmeGpu->archSpecific->pmeStream_,
 -                       pmeGpu->settings.transferKind, nullptr);
 +    copyToDeviceBuffer(&pmeGpu->kernelParams->atoms.d_forces,
 +                       pmeGpu->staging.h_forces.data(),
 +                       0,
 +                       pmeGpu->kernelParams->atoms.nAtoms,
 +                       pmeGpu->archSpecific->pmeStream_,
 +                       pmeGpu->settings.transferKind,
 +                       nullptr);
  }
  
  void pme_gpu_copy_output_forces(PmeGpu* pmeGpu)
  {
      GMX_ASSERT(pmeGpu->kernelParams->atoms.nAtoms > 0, "Bad number of atoms in PME GPU");
 -    float* h_forcesFloat = reinterpret_cast<float*>(pmeGpu->staging.h_forces.data());
 -    copyFromDeviceBuffer(h_forcesFloat, &pmeGpu->kernelParams->atoms.d_forces, 0,
 -                         DIM * pmeGpu->kernelParams->atoms.nAtoms, pmeGpu->archSpecific->pmeStream_,
 -                         pmeGpu->settings.transferKind, nullptr);
 +    copyFromDeviceBuffer(pmeGpu->staging.h_forces.data(),
 +                         &pmeGpu->kernelParams->atoms.d_forces,
 +                         0,
 +                         pmeGpu->kernelParams->atoms.nAtoms,
 +                         pmeGpu->archSpecific->pmeStream_,
 +                         pmeGpu->settings.transferKind,
 +                         nullptr);
  }
  
  void pme_gpu_realloc_and_copy_input_coefficients(const PmeGpu* pmeGpu,
      const size_t newCoefficientsSize = pmeGpu->nAtomsAlloc;
      GMX_ASSERT(newCoefficientsSize > 0, "Bad number of atoms in PME GPU");
      reallocateDeviceBuffer(&pmeGpu->kernelParams->atoms.d_coefficients[gridIndex],
 -                           newCoefficientsSize, &pmeGpu->archSpecific->coefficientsSize[gridIndex],
 +                           newCoefficientsSize,
 +                           &pmeGpu->archSpecific->coefficientsSize[gridIndex],
                             &pmeGpu->archSpecific->coefficientsCapacity[gridIndex],
                             pmeGpu->archSpecific->deviceContext_);
      copyToDeviceBuffer(&pmeGpu->kernelParams->atoms.d_coefficients[gridIndex],
 -                       const_cast<float*>(h_coefficients), 0, pmeGpu->kernelParams->atoms.nAtoms,
 -                       pmeGpu->archSpecific->pmeStream_, pmeGpu->settings.transferKind, nullptr);
 +                       const_cast<float*>(h_coefficients),
 +                       0,
 +                       pmeGpu->kernelParams->atoms.nAtoms,
 +                       pmeGpu->archSpecific->pmeStream_,
 +                       pmeGpu->settings.transferKind,
 +                       nullptr);
  
      const size_t paddingIndex = pmeGpu->kernelParams->atoms.nAtoms;
      const size_t paddingCount = pmeGpu->nAtomsAlloc - paddingIndex;
      if (paddingCount > 0)
      {
 -        clearDeviceBufferAsync(&pmeGpu->kernelParams->atoms.d_coefficients[gridIndex], paddingIndex,
 -                               paddingCount, pmeGpu->archSpecific->pmeStream_);
 +        clearDeviceBufferAsync(&pmeGpu->kernelParams->atoms.d_coefficients[gridIndex],
 +                               paddingIndex,
 +                               paddingCount,
 +                               pmeGpu->archSpecific->pmeStream_);
      }
  }
  
@@@ -314,15 -290,10 +314,15 @@@ void pme_gpu_realloc_spline_data(PmeGpu
      const bool shouldRealloc        = (newSplineDataSize > pmeGpu->archSpecific->splineDataSize);
      int        currentSizeTemp      = pmeGpu->archSpecific->splineDataSize;
      int        currentSizeTempAlloc = pmeGpu->archSpecific->splineDataSizeAlloc;
 -    reallocateDeviceBuffer(&pmeGpu->kernelParams->atoms.d_theta, newSplineDataSize, &currentSizeTemp,
 -                           &currentSizeTempAlloc, pmeGpu->archSpecific->deviceContext_);
 -    reallocateDeviceBuffer(&pmeGpu->kernelParams->atoms.d_dtheta, newSplineDataSize,
 -                           &pmeGpu->archSpecific->splineDataSize, &pmeGpu->archSpecific->splineDataSizeAlloc,
 +    reallocateDeviceBuffer(&pmeGpu->kernelParams->atoms.d_theta,
 +                           newSplineDataSize,
 +                           &currentSizeTemp,
 +                           &currentSizeTempAlloc,
 +                           pmeGpu->archSpecific->deviceContext_);
 +    reallocateDeviceBuffer(&pmeGpu->kernelParams->atoms.d_dtheta,
 +                           newSplineDataSize,
 +                           &pmeGpu->archSpecific->splineDataSize,
 +                           &pmeGpu->archSpecific->splineDataSizeAlloc,
                             pmeGpu->archSpecific->deviceContext_);
      // the host side reallocation
      if (shouldRealloc)
@@@ -347,8 -318,7 +347,8 @@@ void pme_gpu_realloc_grid_indices(PmeGp
  {
      const size_t newIndicesSize = DIM * pmeGpu->nAtomsAlloc;
      GMX_ASSERT(newIndicesSize > 0, "Bad number of atoms in PME GPU");
 -    reallocateDeviceBuffer(&pmeGpu->kernelParams->atoms.d_gridlineIndices, newIndicesSize,
 +    reallocateDeviceBuffer(&pmeGpu->kernelParams->atoms.d_gridlineIndices,
 +                           newIndicesSize,
                             &pmeGpu->archSpecific->gridlineIndicesSize,
                             &pmeGpu->archSpecific->gridlineIndicesSizeAlloc,
                             pmeGpu->archSpecific->deviceContext_);
@@@ -378,13 -348,11 +378,13 @@@ void pme_gpu_realloc_grids(PmeGpu* pmeG
          if (pmeGpu->archSpecific->performOutOfPlaceFFT)
          {
              /* 2 separate grids */
 -            reallocateDeviceBuffer(&kernelParamsPtr->grid.d_fourierGrid[gridIndex], newComplexGridSize,
 +            reallocateDeviceBuffer(&kernelParamsPtr->grid.d_fourierGrid[gridIndex],
 +                                   newComplexGridSize,
                                     &pmeGpu->archSpecific->complexGridSize[gridIndex],
                                     &pmeGpu->archSpecific->complexGridCapacity[gridIndex],
                                     pmeGpu->archSpecific->deviceContext_);
 -            reallocateDeviceBuffer(&kernelParamsPtr->grid.d_realGrid[gridIndex], newRealGridSize,
 +            reallocateDeviceBuffer(&kernelParamsPtr->grid.d_realGrid[gridIndex],
 +                                   newRealGridSize,
                                     &pmeGpu->archSpecific->realGridSize[gridIndex],
                                     &pmeGpu->archSpecific->realGridCapacity[gridIndex],
                                     pmeGpu->archSpecific->deviceContext_);
          {
              /* A single buffer so that any grid will fit */
              const int newGridsSize = std::max(newRealGridSize, newComplexGridSize);
 -            reallocateDeviceBuffer(&kernelParamsPtr->grid.d_realGrid[gridIndex], newGridsSize,
 +            reallocateDeviceBuffer(&kernelParamsPtr->grid.d_realGrid[gridIndex],
 +                                   newGridsSize,
                                     &pmeGpu->archSpecific->realGridSize[gridIndex],
                                     &pmeGpu->archSpecific->realGridCapacity[gridIndex],
                                     pmeGpu->archSpecific->deviceContext_);
@@@ -422,8 -389,7 +422,8 @@@ void pme_gpu_clear_grids(const PmeGpu* 
  {
      for (int gridIndex = 0; gridIndex < pmeGpu->common->ngrids; gridIndex++)
      {
 -        clearDeviceBufferAsync(&pmeGpu->kernelParams->grid.d_realGrid[gridIndex], 0,
 +        clearDeviceBufferAsync(&pmeGpu->kernelParams->grid.d_realGrid[gridIndex],
 +                               0,
                                 pmeGpu->archSpecific->realGridSize[gridIndex],
                                 pmeGpu->archSpecific->pmeStream_);
      }
@@@ -446,16 -412,12 +446,16 @@@ void pme_gpu_realloc_and_copy_fract_shi
      const int newFractShiftsSize = cellCount * (nx + ny + nz);
  
      initParamLookupTable(&kernelParamsPtr->grid.d_fractShiftsTable,
 -                         &kernelParamsPtr->fractShiftsTableTexture, pmeGpu->common->fsh.data(),
 -                         newFractShiftsSize, pmeGpu->archSpecific->deviceContext_);
 +                         &kernelParamsPtr->fractShiftsTableTexture,
 +                         pmeGpu->common->fsh.data(),
 +                         newFractShiftsSize,
 +                         pmeGpu->archSpecific->deviceContext_);
  
      initParamLookupTable(&kernelParamsPtr->grid.d_gridlineIndicesTable,
 -                         &kernelParamsPtr->gridlineIndicesTableTexture, pmeGpu->common->nn.data(),
 -                         newFractShiftsSize, pmeGpu->archSpecific->deviceContext_);
 +                         &kernelParamsPtr->gridlineIndicesTableTexture,
 +                         pmeGpu->common->nn.data(),
 +                         newFractShiftsSize,
 +                         pmeGpu->archSpecific->deviceContext_);
  }
  
  void pme_gpu_free_fract_shifts(const PmeGpu* pmeGpu)
      auto* kernelParamsPtr = pmeGpu->kernelParams.get();
  #if GMX_GPU_CUDA
      destroyParamLookupTable(&kernelParamsPtr->grid.d_fractShiftsTable,
 -                            kernelParamsPtr->fractShiftsTableTexture);
 +                            &kernelParamsPtr->fractShiftsTableTexture);
      destroyParamLookupTable(&kernelParamsPtr->grid.d_gridlineIndicesTable,
 -                            kernelParamsPtr->gridlineIndicesTableTexture);
 -#elif GMX_GPU_OPENCL
 +                            &kernelParamsPtr->gridlineIndicesTableTexture);
 +#elif GMX_GPU_OPENCL || GMX_GPU_SYCL
      freeDeviceBuffer(&kernelParamsPtr->grid.d_fractShiftsTable);
      freeDeviceBuffer(&kernelParamsPtr->grid.d_gridlineIndicesTable);
  #endif
@@@ -479,24 -441,16 +479,24 @@@ bool pme_gpu_stream_query(const PmeGpu
  
  void pme_gpu_copy_input_gather_grid(const PmeGpu* pmeGpu, const float* h_grid, const int gridIndex)
  {
 -    copyToDeviceBuffer(&pmeGpu->kernelParams->grid.d_realGrid[gridIndex], h_grid, 0,
 +    copyToDeviceBuffer(&pmeGpu->kernelParams->grid.d_realGrid[gridIndex],
 +                       h_grid,
 +                       0,
                         pmeGpu->archSpecific->realGridSize[gridIndex],
 -                       pmeGpu->archSpecific->pmeStream_, pmeGpu->settings.transferKind, nullptr);
 +                       pmeGpu->archSpecific->pmeStream_,
 +                       pmeGpu->settings.transferKind,
 +                       nullptr);
  }
  
  void pme_gpu_copy_output_spread_grid(const PmeGpu* pmeGpu, float* h_grid, const int gridIndex)
  {
 -    copyFromDeviceBuffer(h_grid, &pmeGpu->kernelParams->grid.d_realGrid[gridIndex], 0,
 +    copyFromDeviceBuffer(h_grid,
 +                         &pmeGpu->kernelParams->grid.d_realGrid[gridIndex],
 +                         0,
                           pmeGpu->archSpecific->realGridSize[gridIndex],
 -                         pmeGpu->archSpecific->pmeStream_, pmeGpu->settings.transferKind, nullptr);
 +                         pmeGpu->archSpecific->pmeStream_,
 +                         pmeGpu->settings.transferKind,
 +                         nullptr);
      pmeGpu->archSpecific->syncSpreadGridD2H.markEvent(pmeGpu->archSpecific->pmeStream_);
  }
  
@@@ -504,27 -458,13 +504,27 @@@ void pme_gpu_copy_output_spread_atom_da
  {
      const size_t splinesCount    = DIM * pmeGpu->nAtomsAlloc * pmeGpu->common->pme_order;
      auto*        kernelParamsPtr = pmeGpu->kernelParams.get();
 -    copyFromDeviceBuffer(pmeGpu->staging.h_dtheta, &kernelParamsPtr->atoms.d_dtheta, 0, splinesCount,
 -                         pmeGpu->archSpecific->pmeStream_, pmeGpu->settings.transferKind, nullptr);
 -    copyFromDeviceBuffer(pmeGpu->staging.h_theta, &kernelParamsPtr->atoms.d_theta, 0, splinesCount,
 -                         pmeGpu->archSpecific->pmeStream_, pmeGpu->settings.transferKind, nullptr);
 -    copyFromDeviceBuffer(pmeGpu->staging.h_gridlineIndices, &kernelParamsPtr->atoms.d_gridlineIndices,
 -                         0, kernelParamsPtr->atoms.nAtoms * DIM, pmeGpu->archSpecific->pmeStream_,
 -                         pmeGpu->settings.transferKind, nullptr);
 +    copyFromDeviceBuffer(pmeGpu->staging.h_dtheta,
 +                         &kernelParamsPtr->atoms.d_dtheta,
 +                         0,
 +                         splinesCount,
 +                         pmeGpu->archSpecific->pmeStream_,
 +                         pmeGpu->settings.transferKind,
 +                         nullptr);
 +    copyFromDeviceBuffer(pmeGpu->staging.h_theta,
 +                         &kernelParamsPtr->atoms.d_theta,
 +                         0,
 +                         splinesCount,
 +                         pmeGpu->archSpecific->pmeStream_,
 +                         pmeGpu->settings.transferKind,
 +                         nullptr);
 +    copyFromDeviceBuffer(pmeGpu->staging.h_gridlineIndices,
 +                         &kernelParamsPtr->atoms.d_gridlineIndices,
 +                         0,
 +                         kernelParamsPtr->atoms.nAtoms * DIM,
 +                         pmeGpu->archSpecific->pmeStream_,
 +                         pmeGpu->settings.transferKind,
 +                         nullptr);
  }
  
  void pme_gpu_copy_input_gather_atom_data(const PmeGpu* pmeGpu)
      auto*        kernelParamsPtr = pmeGpu->kernelParams.get();
  
      // TODO: could clear only the padding and not the whole thing, but this is a test-exclusive code anyway
 -    clearDeviceBufferAsync(&kernelParamsPtr->atoms.d_gridlineIndices, 0, pmeGpu->nAtomsAlloc * DIM,
 +    clearDeviceBufferAsync(&kernelParamsPtr->atoms.d_gridlineIndices,
 +                           0,
 +                           pmeGpu->nAtomsAlloc * DIM,
                             pmeGpu->archSpecific->pmeStream_);
 -    clearDeviceBufferAsync(&kernelParamsPtr->atoms.d_dtheta, 0,
 +    clearDeviceBufferAsync(&kernelParamsPtr->atoms.d_dtheta,
 +                           0,
                             pmeGpu->nAtomsAlloc * pmeGpu->common->pme_order * DIM,
                             pmeGpu->archSpecific->pmeStream_);
 -    clearDeviceBufferAsync(&kernelParamsPtr->atoms.d_theta, 0,
 +    clearDeviceBufferAsync(&kernelParamsPtr->atoms.d_theta,
 +                           0,
                             pmeGpu->nAtomsAlloc * pmeGpu->common->pme_order * DIM,
                             pmeGpu->archSpecific->pmeStream_);
  
 -    copyToDeviceBuffer(&kernelParamsPtr->atoms.d_dtheta, pmeGpu->staging.h_dtheta, 0, splinesCount,
 -                       pmeGpu->archSpecific->pmeStream_, pmeGpu->settings.transferKind, nullptr);
 -    copyToDeviceBuffer(&kernelParamsPtr->atoms.d_theta, pmeGpu->staging.h_theta, 0, splinesCount,
 -                       pmeGpu->archSpecific->pmeStream_, pmeGpu->settings.transferKind, nullptr);
 -    copyToDeviceBuffer(&kernelParamsPtr->atoms.d_gridlineIndices, pmeGpu->staging.h_gridlineIndices,
 -                       0, kernelParamsPtr->atoms.nAtoms * DIM, pmeGpu->archSpecific->pmeStream_,
 -                       pmeGpu->settings.transferKind, nullptr);
 +    copyToDeviceBuffer(&kernelParamsPtr->atoms.d_dtheta,
 +                       pmeGpu->staging.h_dtheta,
 +                       0,
 +                       splinesCount,
 +                       pmeGpu->archSpecific->pmeStream_,
 +                       pmeGpu->settings.transferKind,
 +                       nullptr);
 +    copyToDeviceBuffer(&kernelParamsPtr->atoms.d_theta,
 +                       pmeGpu->staging.h_theta,
 +                       0,
 +                       splinesCount,
 +                       pmeGpu->archSpecific->pmeStream_,
 +                       pmeGpu->settings.transferKind,
 +                       nullptr);
 +    copyToDeviceBuffer(&kernelParamsPtr->atoms.d_gridlineIndices,
 +                       pmeGpu->staging.h_gridlineIndices,
 +                       0,
 +                       kernelParamsPtr->atoms.nAtoms * DIM,
 +                       pmeGpu->archSpecific->pmeStream_,
 +                       pmeGpu->settings.transferKind,
 +                       nullptr);
  }
  
  void pme_gpu_sync_spread_grid(const PmeGpu* pmeGpu)
@@@ -606,40 -528,9 +606,40 @@@ void pme_gpu_reinit_3dfft(const PmeGpu
      if (pme_gpu_settings(pmeGpu).performGPUFFT)
      {
          pmeGpu->archSpecific->fftSetup.resize(0);
 +        const bool         performOutOfPlaceFFT      = pmeGpu->archSpecific->performOutOfPlaceFFT;
 +        const bool         allocateGrid              = false;
 +        MPI_Comm           comm                      = MPI_COMM_NULL;
 +        std::array<int, 1> gridOffsetsInXForEachRank = { 0 };
 +        std::array<int, 1> gridOffsetsInYForEachRank = { 0 };
 +#if GMX_GPU_CUDA
 +        const gmx::FftBackend backend = gmx::FftBackend::Cufft;
 +#elif GMX_GPU_OPENCL
 +        const gmx::FftBackend backend = gmx::FftBackend::Ocl;
 +#elif GMX_GPU_SYCL
 +        const gmx::FftBackend backend = gmx::FftBackend::Sycl;
 +#else
 +        GMX_RELEASE_ASSERT(false, "Unknown GPU backend");
 +        const gmx::FftBackend backend = gmx::FftBackend::Count;
 +#endif
 +
 +        PmeGpuGridParams& grid = pme_gpu_get_kernel_params_base_ptr(pmeGpu)->grid;
          for (int gridIndex = 0; gridIndex < pmeGpu->common->ngrids; gridIndex++)
          {
 -            pmeGpu->archSpecific->fftSetup.push_back(std::make_unique<GpuParallel3dFft>(pmeGpu, gridIndex));
 +            pmeGpu->archSpecific->fftSetup.push_back(
 +                    std::make_unique<gmx::Gpu3dFft>(backend,
 +                                                    allocateGrid,
 +                                                    comm,
 +                                                    gridOffsetsInXForEachRank,
 +                                                    gridOffsetsInYForEachRank,
 +                                                    grid.realGridSize[ZZ],
 +                                                    performOutOfPlaceFFT,
 +                                                    pmeGpu->archSpecific->deviceContext_,
 +                                                    pmeGpu->archSpecific->pmeStream_,
 +                                                    grid.realGridSize,
 +                                                    grid.realGridSizePadded,
 +                                                    grid.complexGridSizePadded,
 +                                                    &(grid.d_realGrid[gridIndex]),
 +                                                    &(grid.d_fourierGrid[gridIndex])));
          }
      }
  }
@@@ -859,7 -750,7 +859,7 @@@ static void pme_gpu_copy_common_data_fr
      pmeGpu->common->nn.insert(pmeGpu->common->nn.end(), pme->nnz, pme->nnz + cellCount * pme->nkz);
      pmeGpu->common->runMode       = pme->runMode;
      pmeGpu->common->isRankPmeOnly = !pme->bPPnode;
 -    pmeGpu->common->boxScaler     = pme->boxScaler;
 +    pmeGpu->common->boxScaler     = pme->boxScaler.get();
  }
  
  /*! \libinternal \brief
@@@ -924,7 -815,7 +924,7 @@@ static void pme_gpu_init(gmx_pme_t
      GMX_ASSERT(pmeGpu->common->epsilon_r != 0.0F, "PME GPU: bad electrostatic coefficient");
  
      auto* kernelParamsPtr               = pme_gpu_get_kernel_params_base_ptr(pmeGpu);
 -    kernelParamsPtr->constants.elFactor = ONE_4PI_EPS0 / pmeGpu->common->epsilon_r;
 +    kernelParamsPtr->constants.elFactor = gmx::c_one4PiEps0 / pmeGpu->common->epsilon_r;
  }
  
  void pme_gpu_get_real_grid_sizes(const PmeGpu* pmeGpu, gmx::IVec* gridSize, gmx::IVec* paddedGridSize)
@@@ -1044,23 -935,23 +1044,23 @@@ void pme_gpu_reinit_atoms(PmeGpu* pmeGp
   * In CUDA result can be nullptr stub, per GpuRegionTimer implementation.
   *
   * \param[in] pmeGpu         The PME GPU data structure.
 - * \param[in] PMEStageId     The PME GPU stage gtPME_ index from the enum in src/gromacs/timing/gpu_timing.h
 + * \param[in] pmeStageId     The PME GPU stage gtPME_ index from the enum in src/gromacs/timing/gpu_timing.h
   */
 -static CommandEvent* pme_gpu_fetch_timing_event(const PmeGpu* pmeGpu, size_t PMEStageId)
 +static CommandEvent* pme_gpu_fetch_timing_event(const PmeGpu* pmeGpu, PmeStage pmeStageId)
  {
      CommandEvent* timingEvent = nullptr;
      if (pme_gpu_timings_enabled(pmeGpu))
      {
 -        GMX_ASSERT(PMEStageId < pmeGpu->archSpecific->timingEvents.size(),
 -                   "Wrong PME GPU timing event index");
 -        timingEvent = pmeGpu->archSpecific->timingEvents[PMEStageId].fetchNextEvent();
 +        GMX_ASSERT(pmeStageId < PmeStage::Count, "Wrong PME GPU timing event index");
 +        timingEvent = pmeGpu->archSpecific->timingEvents[pmeStageId].fetchNextEvent();
      }
      return timingEvent;
  }
  
  void pme_gpu_3dfft(const PmeGpu* pmeGpu, gmx_fft_direction dir, const int grid_index)
  {
 -    int timerId = (dir == GMX_FFT_REAL_TO_COMPLEX) ? gtPME_FFT_R2C : gtPME_FFT_C2R;
 +    PmeStage timerId = (dir == GMX_FFT_REAL_TO_COMPLEX) ? PmeStage::FftTransformR2C
 +                                                        : PmeStage::FftTransformC2R;
  
      pme_gpu_start_timing(pmeGpu, timerId);
      pmeGpu->archSpecific->fftSetup[grid_index]->perform3dFft(
@@@ -1164,8 -1055,8 +1164,8 @@@ static auto selectSplineAndSpreadKernel
   *
   * \return Pointer to CUDA kernel
   */
 -static auto selectSplineKernelPtr(const PmeGpu*  pmeGpu,
 -                                  ThreadsPerAtom threadsPerAtom,
 +static auto selectSplineKernelPtr(const PmeGpu*   pmeGpu,
 +                                  ThreadsPerAtom  threadsPerAtom,
                                    bool gmx_unused writeSplinesToGlobal,
                                    const int       numGrids)
  {
@@@ -1223,6 -1114,7 +1223,7 @@@ static auto selectSpreadKernelPtr(cons
              {
                  kernelPtr = pmeGpu->programHandle_->impl_->spreadKernelThPerAtom4Dual;
              }
+             else
              {
                  kernelPtr = pmeGpu->programHandle_->impl_->spreadKernelThPerAtom4Single;
              }
@@@ -1341,34 -1233,30 +1342,34 @@@ void pme_gpu_spread(const PmeGpu
      config.gridSize[0]  = dimGrid.first;
      config.gridSize[1]  = dimGrid.second;
  
 -    int                                timingId;
 +    PmeStage                           timingId;
      PmeGpuProgramImpl::PmeKernelHandle kernelPtr = nullptr;
      if (computeSplines)
      {
          if (spreadCharges)
          {
 -            timingId  = gtPME_SPLINEANDSPREAD;
 -            kernelPtr = selectSplineAndSpreadKernelPtr(pmeGpu, pmeGpu->settings.threadsPerAtom,
 +            timingId  = PmeStage::SplineAndSpread;
 +            kernelPtr = selectSplineAndSpreadKernelPtr(pmeGpu,
 +                                                       pmeGpu->settings.threadsPerAtom,
                                                         writeGlobal || (!recalculateSplines),
                                                         pmeGpu->common->ngrids);
          }
          else
          {
 -            timingId  = gtPME_SPLINE;
 -            kernelPtr = selectSplineKernelPtr(pmeGpu, pmeGpu->settings.threadsPerAtom,
 +            timingId  = PmeStage::Spline;
 +            kernelPtr = selectSplineKernelPtr(pmeGpu,
 +                                              pmeGpu->settings.threadsPerAtom,
                                                writeGlobal || (!recalculateSplines),
                                                pmeGpu->common->ngrids);
          }
      }
      else
      {
 -        timingId  = gtPME_SPREAD;
 -        kernelPtr = selectSpreadKernelPtr(pmeGpu, pmeGpu->settings.threadsPerAtom,
 -                                          writeGlobal || (!recalculateSplines), pmeGpu->common->ngrids);
 +        timingId  = PmeStage::Spread;
 +        kernelPtr = selectSpreadKernelPtr(pmeGpu,
 +                                          pmeGpu->settings.threadsPerAtom,
 +                                          writeGlobal || (!recalculateSplines),
 +                                          pmeGpu->common->ngrids);
      }
  
  
  #if c_canEmbedBuffers
      const auto kernelArgs = prepareGpuKernelArguments(kernelPtr, config, kernelParamsPtr);
  #else
 -    const auto kernelArgs = prepareGpuKernelArguments(
 -            kernelPtr, config, kernelParamsPtr, &kernelParamsPtr->atoms.d_theta,
 -            &kernelParamsPtr->atoms.d_dtheta, &kernelParamsPtr->atoms.d_gridlineIndices,
 -            &kernelParamsPtr->grid.d_realGrid[FEP_STATE_A], &kernelParamsPtr->grid.d_realGrid[FEP_STATE_B],
 -            &kernelParamsPtr->grid.d_fractShiftsTable, &kernelParamsPtr->grid.d_gridlineIndicesTable,
 -            &kernelParamsPtr->atoms.d_coefficients[FEP_STATE_A],
 -            &kernelParamsPtr->atoms.d_coefficients[FEP_STATE_B], &kernelParamsPtr->atoms.d_coordinates);
 +    const auto kernelArgs =
 +            prepareGpuKernelArguments(kernelPtr,
 +                                      config,
 +                                      kernelParamsPtr,
 +                                      &kernelParamsPtr->atoms.d_theta,
 +                                      &kernelParamsPtr->atoms.d_dtheta,
 +                                      &kernelParamsPtr->atoms.d_gridlineIndices,
 +                                      &kernelParamsPtr->grid.d_realGrid[FEP_STATE_A],
 +                                      &kernelParamsPtr->grid.d_realGrid[FEP_STATE_B],
 +                                      &kernelParamsPtr->grid.d_fractShiftsTable,
 +                                      &kernelParamsPtr->grid.d_gridlineIndicesTable,
 +                                      &kernelParamsPtr->atoms.d_coefficients[FEP_STATE_A],
 +                                      &kernelParamsPtr->atoms.d_coefficients[FEP_STATE_B],
 +                                      &kernelParamsPtr->atoms.d_coordinates);
  #endif
  
 -    launchGpuKernel(kernelPtr, config, pmeGpu->archSpecific->pmeStream_, timingEvent,
 -                    "PME spline/spread", kernelArgs);
 +    launchGpuKernel(
 +            kernelPtr, config, pmeGpu->archSpecific->pmeStream_, timingEvent, "PME spline/spread", kernelArgs);
      pme_gpu_stop_timing(pmeGpu, timingId);
  
      const auto& settings    = pmeGpu->settings;
@@@ -1435,13 -1316,9 +1436,13 @@@ void pme_gpu_solve(const PmeGpu* pmeGpu
      float* h_gridFloat = reinterpret_cast<float*>(h_grid);
      if (copyInputAndOutputGrid)
      {
 -        copyToDeviceBuffer(&kernelParamsPtr->grid.d_fourierGrid[gridIndex], h_gridFloat, 0,
 +        copyToDeviceBuffer(&kernelParamsPtr->grid.d_fourierGrid[gridIndex],
 +                           h_gridFloat,
 +                           0,
                             pmeGpu->archSpecific->complexGridSize[gridIndex],
 -                           pmeGpu->archSpecific->pmeStream_, pmeGpu->settings.transferKind, nullptr);
 +                           pmeGpu->archSpecific->pmeStream_,
 +                           pmeGpu->settings.transferKind,
 +                           nullptr);
      }
  
      int majorDim = -1, middleDim = -1, minorDim = -1;
                           / gridLinesPerBlock;
      config.gridSize[2] = pmeGpu->kernelParams->grid.complexGridSize[majorDim];
  
 -    int                                timingId  = gtPME_SOLVE;
 +    PmeStage                           timingId  = PmeStage::Solve;
      PmeGpuProgramImpl::PmeKernelHandle kernelPtr = nullptr;
      if (gridOrdering == GridOrdering::YZX)
      {
  #if c_canEmbedBuffers
      const auto kernelArgs = prepareGpuKernelArguments(kernelPtr, config, kernelParamsPtr);
  #else
 -    const auto kernelArgs = prepareGpuKernelArguments(
 -            kernelPtr, config, kernelParamsPtr, &kernelParamsPtr->grid.d_splineModuli[gridIndex],
 -            &kernelParamsPtr->constants.d_virialAndEnergy[gridIndex],
 -            &kernelParamsPtr->grid.d_fourierGrid[gridIndex]);
 +    const auto kernelArgs =
 +            prepareGpuKernelArguments(kernelPtr,
 +                                      config,
 +                                      kernelParamsPtr,
 +                                      &kernelParamsPtr->grid.d_splineModuli[gridIndex],
 +                                      &kernelParamsPtr->constants.d_virialAndEnergy[gridIndex],
 +                                      &kernelParamsPtr->grid.d_fourierGrid[gridIndex]);
  #endif
 -    launchGpuKernel(kernelPtr, config, pmeGpu->archSpecific->pmeStream_, timingEvent, "PME solve",
 -                    kernelArgs);
 +    launchGpuKernel(kernelPtr, config, pmeGpu->archSpecific->pmeStream_, timingEvent, "PME solve", kernelArgs);
      pme_gpu_stop_timing(pmeGpu, timingId);
  
      if (computeEnergyAndVirial)
      {
          copyFromDeviceBuffer(pmeGpu->staging.h_virialAndEnergy[gridIndex],
 -                             &kernelParamsPtr->constants.d_virialAndEnergy[gridIndex], 0,
 -                             c_virialAndEnergyCount, pmeGpu->archSpecific->pmeStream_,
 -                             pmeGpu->settings.transferKind, nullptr);
 +                             &kernelParamsPtr->constants.d_virialAndEnergy[gridIndex],
 +                             0,
 +                             c_virialAndEnergyCount,
 +                             pmeGpu->archSpecific->pmeStream_,
 +                             pmeGpu->settings.transferKind,
 +                             nullptr);
      }
  
      if (copyInputAndOutputGrid)
      {
 -        copyFromDeviceBuffer(h_gridFloat, &kernelParamsPtr->grid.d_fourierGrid[gridIndex], 0,
 +        copyFromDeviceBuffer(h_gridFloat,
 +                             &kernelParamsPtr->grid.d_fourierGrid[gridIndex],
 +                             0,
                               pmeGpu->archSpecific->complexGridSize[gridIndex],
 -                             pmeGpu->archSpecific->pmeStream_, pmeGpu->settings.transferKind, nullptr);
 +                             pmeGpu->archSpecific->pmeStream_,
 +                             pmeGpu->settings.transferKind,
 +                             nullptr);
      }
  }
  
@@@ -1683,12 -1551,10 +1684,12 @@@ void pme_gpu_gather(PmeGpu* pmeGpu, rea
  
      // TODO test different cache configs
  
 -    int                                timingId = gtPME_GATHER;
 +    PmeStage                           timingId = PmeStage::Gather;
      PmeGpuProgramImpl::PmeKernelHandle kernelPtr =
 -            selectGatherKernelPtr(pmeGpu, pmeGpu->settings.threadsPerAtom,
 -                                  readGlobal || (!recalculateSplines), pmeGpu->common->ngrids);
 +            selectGatherKernelPtr(pmeGpu,
 +                                  pmeGpu->settings.threadsPerAtom,
 +                                  readGlobal || (!recalculateSplines),
 +                                  pmeGpu->common->ngrids);
      // TODO design kernel selection getters and make PmeGpu a friend of PmeGpuProgramImpl
  
      pme_gpu_start_timing(pmeGpu, timingId);
  #if c_canEmbedBuffers
      const auto kernelArgs = prepareGpuKernelArguments(kernelPtr, config, kernelParamsPtr);
  #else
 -    const auto kernelArgs = prepareGpuKernelArguments(
 -            kernelPtr, config, kernelParamsPtr, &kernelParamsPtr->atoms.d_coefficients[FEP_STATE_A],
 -            &kernelParamsPtr->atoms.d_coefficients[FEP_STATE_B],
 -            &kernelParamsPtr->grid.d_realGrid[FEP_STATE_A], &kernelParamsPtr->grid.d_realGrid[FEP_STATE_B],
 -            &kernelParamsPtr->atoms.d_theta, &kernelParamsPtr->atoms.d_dtheta,
 -            &kernelParamsPtr->atoms.d_gridlineIndices, &kernelParamsPtr->atoms.d_forces);
 +    const auto kernelArgs =
 +            prepareGpuKernelArguments(kernelPtr,
 +                                      config,
 +                                      kernelParamsPtr,
 +                                      &kernelParamsPtr->atoms.d_coefficients[FEP_STATE_A],
 +                                      &kernelParamsPtr->atoms.d_coefficients[FEP_STATE_B],
 +                                      &kernelParamsPtr->grid.d_realGrid[FEP_STATE_A],
 +                                      &kernelParamsPtr->grid.d_realGrid[FEP_STATE_B],
 +                                      &kernelParamsPtr->atoms.d_theta,
 +                                      &kernelParamsPtr->atoms.d_dtheta,
 +                                      &kernelParamsPtr->atoms.d_gridlineIndices,
 +                                      &kernelParamsPtr->atoms.d_forces);
  #endif
 -    launchGpuKernel(kernelPtr, config, pmeGpu->archSpecific->pmeStream_, timingEvent, "PME gather",
 -                    kernelArgs);
 +    launchGpuKernel(kernelPtr, config, pmeGpu->archSpecific->pmeStream_, timingEvent, "PME gather", kernelArgs);
      pme_gpu_stop_timing(pmeGpu, timingId);
  
      if (pmeGpu->settings.useGpuForceReduction)
      }
  }
  
 -void* pme_gpu_get_kernelparam_forces(const PmeGpu* pmeGpu)
 +DeviceBuffer<gmx::RVec> pme_gpu_get_kernelparam_forces(const PmeGpu* pmeGpu)
  {
      if (pmeGpu && pmeGpu->kernelParams)
      {
      }
      else
      {
 -        return nullptr;
 +        return DeviceBuffer<gmx::RVec>{};
      }
  }
  
index d63c40ecbcd62bf920cd0e97ac9b1d39a333d669,7f6f4beccdf4b224100d49c9953ec88721ca0d08..de624b5f95a1cf72ad0414b28cfeced18892d380
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2018,2019,2020, by the GROMACS development team, led by
+  * 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.
@@@ -132,6 -132,8 +132,8 @@@ gmx_opencl_inline void calculate_spline
      /* Thread index w.r.t. block */
      assert((get_local_id(2) * get_local_size(1) + get_local_id(1)) * get_local_size(0) + get_local_id(0)
             < MAX_INT);
+     assert(numGrids == 1 || numGrids == 2);
+     assert(numGrids == 1 || c_skipNeutralAtoms == false);
      const int threadLocalIndex =
              (int)((get_local_id(2) * get_local_size(1) + get_local_id(1)) * get_local_size(0)
                    + get_local_id(0));
          /* B-spline calculation */
  
          const int chargeCheck = pme_gpu_check_atom_charge(sm_coefficients[atomIndexLocal]);
-         if (chargeCheck)
+         /* With FEP (numGrids == 2), we might have 0 charge in state A, but !=0 in state B, so we always calculate splines */
+         if (numGrids == 2 || chargeCheck)
          {
              int o = orderIndex; // This is an index that is set once for PME_GPU_PARALLEL_SPLINE == 1
  
@@@ -452,18 -455,9 +455,18 @@@ __attribute__((reqd_work_group_size(ord
          pme_gpu_stage_atom_data(sm_coordinates, gm_coordinates, DIM);
  
          barrier(CLK_LOCAL_MEM_FENCE);
 -        calculate_splines(kernelParams, atomIndexOffset, sm_coordinates, sm_coefficients, sm_theta,
 -                          sm_gridlineIndices, sm_fractCoords, gm_theta, gm_dtheta,
 -                          gm_gridlineIndices, gm_fractShiftsTable, gm_gridlineIndicesTable);
 +        calculate_splines(kernelParams,
 +                          atomIndexOffset,
 +                          sm_coordinates,
 +                          sm_coefficients,
 +                          sm_theta,
 +                          sm_gridlineIndices,
 +                          sm_fractCoords,
 +                          gm_theta,
 +                          gm_dtheta,
 +                          gm_gridlineIndices,
 +                          gm_fractShiftsTable,
 +                          gm_gridlineIndicesTable);
  #if !defined(_AMD_SOURCE_) && !defined(_NVIDIA_SOURCE_)
          /* This is only here for execution of e.g. 32-sized warps on 16-wide hardware; this was
           * __syncwarp() in CUDA. #2519
          /* Spline data - only thetas (dthetas will only be needed in gather) */
          pme_gpu_stage_atom_data(sm_theta, gm_theta, DIM * order);
          /* Gridline indices - they're actually int and not float, but C99 is angry about overloads */
 -        pme_gpu_stage_atom_data((__local float*)sm_gridlineIndices,
 -                                (__global const float*)gm_gridlineIndices, DIM);
 +        pme_gpu_stage_atom_data(
 +                (__local float*)sm_gridlineIndices, (__global const float*)gm_gridlineIndices, DIM);
  
          barrier(CLK_LOCAL_MEM_FENCE);
      }
index 62f3a61c8bcbf5ecae1c6240805878e9c4bb399f,38f41ec5b26e7504d2f1c6586a0d26ff37a077f2..4765c9c4c8cdc81af1e01349924f149a56c72132
@@@ -3,7 -3,7 +3,7 @@@
   *
   * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
   * Copyright (c) 2001-2004, The GROMACS development team.
-  * Copyright (c) 2013-2016,2017,2018,2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 2013-2016,2017,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.
@@@ -252,7 -252,7 +252,7 @@@ __launch_bounds__(c_spreadMaxThreadsPer
          {
              atomX = gm_coordinates[atomIndexGlobal];
          }
-         calculate_splines<order, atomsPerBlock, atomsPerWarp, false, writeGlobal>(
+         calculate_splines<order, atomsPerBlock, atomsPerWarp, false, writeGlobal, numGrids>(
                  kernelParams, atomIndexOffset, atomX, atomCharge, sm_theta, &dtheta, sm_gridlineIndices);
          __syncwarp();
      }
      /* Spreading */
      if (spreadCharges)
      {
 -        spread_charges<order, wrapX, wrapY, 0, threadsPerAtom>(kernelParams, &atomCharge,
 -                                                               sm_gridlineIndices, sm_theta);
 +        spread_charges<order, wrapX, wrapY, 0, threadsPerAtom>(
 +                kernelParams, &atomCharge, sm_gridlineIndices, sm_theta);
      }
      if (numGrids == 2)
      {
          }
          if (spreadCharges)
          {
 -            spread_charges<order, wrapX, wrapY, 1, threadsPerAtom>(kernelParams, &atomCharge,
 -                                                                   sm_gridlineIndices, sm_theta);
 +            spread_charges<order, wrapX, wrapY, 1, threadsPerAtom>(
 +                    kernelParams, &atomCharge, sm_gridlineIndices, sm_theta);
          }
      }
  }
index 3eda383e7acfc6986df140f789f7ca817ae175be,7cdabb9c498013f0573108aa17b4e119aeddb601..a3c4409fca0ac70314de02aa2217d452999f8d2b
@@@ -1,7 -1,8 +1,8 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2016,2017,2018,2019,2020,2021, by the GROMACS development team, led by
+  * Copyright (c) 2016,2017,2018,2019,2020 by the GROMACS development team.
+  * Copyright (c) 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.
@@@ -120,21 -121,9 +121,21 @@@ PmeSafePointer pmeInitWrapper(const t_i
      const auto     runMode       = (mode == CodePath::CPU) ? PmeRunMode::CPU : PmeRunMode::Mixed;
      t_commrec      dummyCommrec  = { 0 };
      NumPmeDomains  numPmeDomains = { 1, 1 };
 -    gmx_pme_t* pmeDataRaw = gmx_pme_init(&dummyCommrec, numPmeDomains, inputRec, false, false, true,
 -                                         ewaldCoeff_q, ewaldCoeff_lj, 1, runMode, nullptr,
 -                                         deviceContext, deviceStream, pmeGpuProgram, dummyLogger);
 +    gmx_pme_t*     pmeDataRaw    = gmx_pme_init(&dummyCommrec,
 +                                         numPmeDomains,
 +                                         inputRec,
 +                                         false,
 +                                         false,
 +                                         true,
 +                                         ewaldCoeff_q,
 +                                         ewaldCoeff_lj,
 +                                         1,
 +                                         runMode,
 +                                         nullptr,
 +                                         deviceContext,
 +                                         deviceStream,
 +                                         pmeGpuProgram,
 +                                         dummyLogger);
      PmeSafePointer pme(pmeDataRaw); // taking ownership
  
      // TODO get rid of this with proper matrix type
@@@ -178,8 -167,8 +179,8 @@@ std::unique_ptr<StatePropagatorDataGpu
      // TODO: Pin the host buffer and use async memory copies
      // TODO: Special constructor for PME-only rank / PME-tests is used here. There should be a mechanism to
      //       restrict one from using other constructor here.
 -    return std::make_unique<StatePropagatorDataGpu>(deviceStream, *deviceContext, GpuApiCallBehavior::Sync,
 -                                                    pme_gpu_get_block_size(&pme), nullptr);
 +    return std::make_unique<StatePropagatorDataGpu>(
 +            deviceStream, *deviceContext, GpuApiCallBehavior::Sync, pme_gpu_get_block_size(&pme), nullptr);
  }
  
  //! PME initialization with atom data
@@@ -199,7 -188,7 +200,7 @@@ void pmeInitAtoms(gmx_pme_t
              atc              = &(pme->atc[0]);
              atc->x           = coordinates;
              atc->coefficient = charges;
 -            gmx_pme_reinit_atoms(pme, atomCount, charges.data(), nullptr);
 +            gmx_pme_reinit_atoms(pme, atomCount, charges, {});
              /* With decomposition there would be more boilerplate atc code here, e.g. do_redist_pos_coeffs */
              break;
  
              atc = &(pme->atc[0]);
              // We need to set atc->n for passing the size in the tests
              atc->setNumAtoms(atomCount);
 -            gmx_pme_reinit_atoms(pme, atomCount, charges.data(), nullptr);
 +            gmx_pme_reinit_atoms(pme, atomCount, charges, {});
  
              stateGpu->reinit(atomCount, atomCount);
              stateGpu->copyCoordinatesToGpu(arrayRefFromArray(coordinates.data(), coordinates.size()),
 -                                           gmx::AtomLocality::All);
 +                                           gmx::AtomLocality::Local);
              pme_gpu_set_kernelparam_coordinates(pme->gpu, stateGpu->getCoordinates());
  
              break;
@@@ -239,8 -228,8 +240,8 @@@ static void pmeGetRealGridSizesInternal
      switch (mode)
      {
          case CodePath::CPU:
 -            gmx_parallel_3dfft_real_limits(pme->pfft_setup[gridIndex], gridSize, gridOffsetUnused,
 -                                           paddedGridSize);
 +            gmx_parallel_3dfft_real_limits(
 +                    pme->pfft_setup[gridIndex], gridSize, gridOffsetUnused, paddedGridSize);
              break;
  
          case CodePath::GPU:
@@@ -265,8 -254,8 +266,8 @@@ static void pmeGetComplexGridSizesInter
  {
      const size_t gridIndex = 0;
      IVec         gridOffsetUnused, complexOrderUnused;
 -    gmx_parallel_3dfft_complex_limits(pme->pfft_setup[gridIndex], complexOrderUnused, gridSize,
 -                                      gridOffsetUnused, paddedGridSize); // TODO: what about YZX ordering?
 +    gmx_parallel_3dfft_complex_limits(
 +            pme->pfft_setup[gridIndex], complexOrderUnused, gridSize, gridOffsetUnused, paddedGridSize); // TODO: what about YZX ordering?
  }
  
  //! Getting the PME grid memory buffer and its sizes - template definition
@@@ -278,7 -267,7 +279,7 @@@ static void pmeGetGridAndSizesInternal(
                                         IVec& /*unused*/)       //NOLINT(google-runtime-references)
  {
      GMX_THROW(InternalError("Deleted function call"));
 -    // explicitly deleting general template does not compile in clang/icc, see https://llvm.org/bugs/show_bug.cgi?id=17537
 +    // explicitly deleting general template does not compile in clang, see https://llvm.org/bugs/show_bug.cgi?id=17537
  }
  
  //! Getting the PME real grid memory buffer and its sizes
@@@ -317,14 -306,9 +318,14 @@@ void pmePerformSplineAndSpread(gmx_pme_
      switch (mode)
      {
          case CodePath::CPU:
 -            spread_on_grid(pme, atc, &pme->pmegrid[gridIndex], computeSplines, spreadCharges,
 +            spread_on_grid(pme,
 +                           atc,
 +                           &pme->pmegrid[gridIndex],
 +                           computeSplines,
 +                           spreadCharges,
                             fftgrid != nullptr ? fftgrid[gridIndex] : nullptr,
 -                           computeSplinesForZeroCharges, gridIndex);
 +                           computeSplinesForZeroCharges,
 +                           gridIndex);
              if (spreadCharges && !pme->bUseThreads)
              {
                  wrap_periodic_pmegrid(pme, pmegrid);
@@@ -398,13 -382,8 +399,13 @@@ void pmePerformSolve(const gmx_pme_t*  
                      break;
  
                  case PmeSolveAlgorithm::LennardJones:
 -                    solve_pme_lj_yzx(pme, &h_grid, useLorentzBerthelot, cellVolume,
 -                                     computeEnergyAndVirial, pme->nthread, threadIndex);
 +                    solve_pme_lj_yzx(pme,
 +                                     &h_grid,
 +                                     useLorentzBerthelot,
 +                                     cellVolume,
 +                                     computeEnergyAndVirial,
 +                                     pme->nthread,
 +                                     threadIndex);
                      break;
  
                  default: GMX_THROW(InternalError("Test not implemented for this mode"));
@@@ -681,8 -660,7 +682,8 @@@ void pmeSetGridLineIndices(gmx_pme_t* p
      switch (mode)
      {
          case CodePath::GPU:
 -            memcpy(pme_gpu_staging(pme->gpu).h_gridlineIndices, gridLineIndices.data(),
 +            memcpy(pme_gpu_staging(pme->gpu).h_gridlineIndices,
 +                   gridLineIndices.data(),
                     atomCount * sizeof(gridLineIndices[0]));
              break;
  
@@@ -728,7 -706,8 +729,7 @@@ static void pmeSetGridInternal(const gm
      {
          case CodePath::GPU: // intentional absence of break, the grid will be copied from the host buffer in testing mode
          case CodePath::CPU:
 -            std::memset(grid, 0,
 -                        paddedGridSize[XX] * paddedGridSize[YY] * paddedGridSize[ZZ] * sizeof(ValueType));
 +            std::memset(grid, 0, paddedGridSize[XX] * paddedGridSize[YY] * paddedGridSize[ZZ] * sizeof(ValueType));
              for (const auto& gridValue : gridValues)
              {
                  for (int i = 0; i < DIM; i++)
@@@ -910,7 -889,8 +911,7 @@@ const char* codePathToString(CodePath c
  PmeTestHardwareContext::PmeTestHardwareContext() : codePath_(CodePath::CPU) {}
  
  PmeTestHardwareContext::PmeTestHardwareContext(TestDevice* testDevice) :
-     codePath_(CodePath::CPU), testDevice_(testDevice)
 -    codePath_(CodePath::GPU),
 -    testDevice_(testDevice)
++    codePath_(CodePath::GPU), testDevice_(testDevice)
  {
      setActiveDevice(testDevice_->deviceInfo());
      pmeGpuProgram_ = buildPmeGpuProgram(testDevice_->deviceContext());
index ae2563b5756625d423a55806f1b74fe9a8792fc6,eef8de70205d3543247043c52d45451af664c81b..0d4d0ec284b08b76c11267a3d0f060645fb9026b
@@@ -52,7 -52,6 +52,7 @@@
  #include "gromacs/gmxana/gmx_ana.h"
  #include "gromacs/gmxana/gstat.h"
  #include "gromacs/math/gmxcomplex.h"
 +#include "gromacs/math/units.h"
  #include "gromacs/math/utilities.h"
  #include "gromacs/utility/arraysize.h"
  #include "gromacs/utility/fatalerror.h"
@@@ -237,9 -236,7 +237,9 @@@ static void do_four(const char
          fprintf(fp, "%10.5e  %10.5e  %10.5e\n", nu, kw.re, kw.im);
          fprintf(cp, "%10.5e  %10.5e\n", kw.re, kw.im);
      }
 -    printf("MAXEPS = %10.5e at frequency %10.5e GHz (tauD = %8.1f ps)\n", maxeps, numax,
 +    printf("MAXEPS = %10.5e at frequency %10.5e GHz (tauD = %8.1f ps)\n",
 +           maxeps,
 +           numax,
             1000 / (2 * M_PI * numax));
      xvgrclose(fp);
      xvgrclose(cp);
@@@ -322,8 -319,8 +322,8 @@@ int gmx_dielectric(int argc, char* argv
          { "-nsmooth", FALSE, etINT, { &nsmooth }, "Number of points for smoothing" }
      };
  
 -    if (!parse_common_args(&argc, argv, PCA_CAN_TIME | PCA_CAN_VIEW, NFILE, fnm, asize(pa), pa,
 -                           asize(desc), desc, 0, nullptr, &oenv))
 +    if (!parse_common_args(
 +                &argc, argv, PCA_CAN_TIME | PCA_CAN_VIEW, NFILE, fnm, asize(pa), pa, asize(desc), desc, 0, nullptr, &oenv))
      {
          return 0;
      }
      dt     = yd[0][1] - yd[0][0];
      nxtail = std::min(static_cast<int>(tail / dt), nx);
  
-     printf("Read data set containing %d colums and %d rows\n", ny, nx);
+     printf("Read data set containing %d columns and %d rows\n", ny, nx);
      printf("Assuming (from data) that timestep is %g, nxtail = %d\n", dt, nxtail);
      snew(y, 6);
      for (i = 0; (i < ny); i++)
      }
      printf("DATA INTEGRAL: %5.1f, tauD(old) = %5.1f ps, "
             "tau_slope = %5.1f, tau_slope,D = %5.1f ps\n",
 -           integral, integral * rffac, fitparms[0], fitparms[0] * rffac);
 +           integral,
 +           integral * rffac,
 +           fitparms[0],
 +           fitparms[0] * rffac);
  
 -    printf("tau_D from tau1 = %8.3g , eps(Infty) = %8.3f\n", fitparms[0] * (1 + fitparms[1] * lambda),
 +    printf("tau_D from tau1 = %8.3g , eps(Infty) = %8.3f\n",
 +           fitparms[0] * (1 + fitparms[1] * lambda),
             1 + ((1 - fitparms[1]) * (eps0 - 1)) / (1 + fitparms[1] * lambda));
  
      fitintegral = numerical_deriv(nx, y[0], y[1], y[3], y[4], y[5], tendInt, nsmooth);
index 233f217e944b25f0340ff3203e75b8d4a7a79fbe,ac302bbddeb8927089cfe6d7de15635b1801207c..83f8e2b2e5ae6efbe986775a7060d6261c532269
@@@ -52,7 -52,6 +52,7 @@@
  #include "gromacs/linearalgebra/eigensolver.h"
  #include "gromacs/math/do_fit.h"
  #include "gromacs/math/functions.h"
 +#include "gromacs/math/units.h"
  #include "gromacs/math/utilities.h"
  #include "gromacs/math/vec.h"
  #include "gromacs/pbcutil/rmpbc.h"
@@@ -213,7 -212,7 +213,7 @@@ int gmx_rmsf(int argc, char* argv[]
          "respect to the reference structure is calculated.[PAR]",
          "With the option [TT]-aniso[tt], [THISMODULE] will compute anisotropic",
          "temperature factors and then it will also output average coordinates",
-         "and a [REF].pdb[ref] file with ANISOU records (corresonding to the [TT]-oq[tt]",
+         "and a [REF].pdb[ref] file with ANISOU records (corresponding to the [TT]-oq[tt]",
          "or [TT]-ox[tt] option). Please note that the U values",
          "are orientation-dependent, so before comparison with experimental data",
          "you should verify that you fit to the experimental coordinates.[PAR]",
      static gmx_bool bRes = FALSE, bAniso = FALSE, bFit = TRUE;
      t_pargs         pargs[] = {
          { "-res", FALSE, etBOOL, { &bRes }, "Calculate averages for each residue" },
-         { "-aniso", FALSE, etBOOL, { &bAniso }, "Compute anisotropic termperature factors" },
+         { "-aniso", FALSE, etBOOL, { &bAniso }, "Compute anisotropic temperature factors" },
          { "-fit",
            FALSE,
            etBOOL,
                         { efXVG, "-oc", "correl", ffOPTWR },  { efLOG, "-dir", "rmsf", ffOPTWR } };
  #define NFILE asize(fnm)
  
 -    if (!parse_common_args(&argc, argv, PCA_CAN_TIME | PCA_CAN_VIEW, NFILE, fnm, asize(pargs),
 -                           pargs, asize(desc), desc, 0, nullptr, &oenv))
 +    if (!parse_common_args(
 +                &argc, argv, PCA_CAN_TIME | PCA_CAN_VIEW, NFILE, fnm, asize(pargs), pargs, asize(desc), desc, 0, nullptr, &oenv))
      {
          return 0;
      }
                  || top.atoms.atom[index[i]].resind != top.atoms.atom[index[i + 1]].resind)
              {
                  resind   = top.atoms.atom[index[i]].resind;
 -                pdb_bfac = find_pdb_bfac(pdbatoms, &top.atoms.resinfo[resind],
 -                                         *(top.atoms.atomname[index[i]]));
 +                pdb_bfac = find_pdb_bfac(
 +                        pdbatoms, &top.atoms.resinfo[resind], *(top.atoms.atomname[index[i]]));
  
 -                fprintf(fp, "%5d  %10.5f  %10.5f\n",
 +                fprintf(fp,
 +                        "%5d  %10.5f  %10.5f\n",
                          bRes ? top.atoms.resinfo[top.atoms.atom[index[i]].resind].nr : index[i] + 1,
 -                        rmsf[i] * bfac, pdb_bfac);
 +                        rmsf[i] * bfac,
 +                        pdb_bfac);
              }
          }
          xvgrclose(fp);
              if (!bRes || i + 1 == isize
                  || top.atoms.atom[index[i]].resind != top.atoms.atom[index[i + 1]].resind)
              {
 -                fprintf(fp, "%5d %8.4f\n",
 +                fprintf(fp,
 +                        "%5d %8.4f\n",
                          bRes ? top.atoms.resinfo[top.atoms.atom[index[i]].resind].nr : index[i] + 1,
                          std::sqrt(rmsf[i]));
              }
              if (!bRes || i + 1 == isize
                  || top.atoms.atom[index[i]].resind != top.atoms.atom[index[i + 1]].resind)
              {
 -                fprintf(fp, "%5d %8.4f\n",
 +                fprintf(fp,
 +                        "%5d %8.4f\n",
                          bRes ? top.atoms.resinfo[top.atoms.atom[index[i]].resind].nr : index[i] + 1,
                          std::sqrt(rmsf[i]));
              }
          {
              rvec_inc(pdbx[index[i]], xcm);
          }
 -        write_sto_conf_indexed(opt2fn("-oq", NFILE, fnm), title, pdbatoms, pdbx, nullptr, pbcType,
 -                               pdbbox, isize, index);
 +        write_sto_conf_indexed(
 +                opt2fn("-oq", NFILE, fnm), title, pdbatoms, pdbx, nullptr, pbcType, pdbbox, isize, index);
      }
      if (opt2bSet("-ox", NFILE, fnm))
      {
              }
          }
          /* Write a .pdb file with B-factors and optionally anisou records */
 -        write_sto_conf_indexed(opt2fn("-ox", NFILE, fnm), title, pdbatoms, bFactorX, nullptr,
 -                               pbcType, pdbbox, isize, index);
 +        write_sto_conf_indexed(
 +                opt2fn("-ox", NFILE, fnm), title, pdbatoms, bFactorX, nullptr, pbcType, pdbbox, isize, index);
          sfree(bFactorX);
      }
      if (bAniso)
index f747c892990f1af2e5123dc1b44460fabc7d041d,bcace1190ff52cbbcf247eee0c034def632ad184..5da2b4d1d7553b10ab692ecef0b8b8462140be4e
@@@ -53,6 -53,7 +53,6 @@@
  #include <cstring>
  
  #include <algorithm>
 -#include <sstream>
  
  #include "gromacs/commandline/pargs.h"
  #include "gromacs/fileio/tpxio.h"
@@@ -95,13 -96,14 +95,13 @@@ enu
      enNr
  };
  /*! \brief
 - * enum for type of input files (pdos, tpr, or pullf)
 + * enum for type of input files (tpr or pullx/pullf)
   */
  enum
  {
      whamin_unknown,
      whamin_tpr,
      whamin_pullxf,
 -    whamin_pdo
  };
  
  /*! \brief enum for bootstrapping method
@@@ -133,17 -135,17 +133,17 @@@ enu
      bsMethod_trajGauss
  };
  
- //! Parameters of one pull coodinate
+ //! Parameters of one pull coordinate
  typedef struct
  {
 -    int  pull_type;       //!< such as constraint, umbrella, ...
 -    int  geometry;        //!< such as distance, direction, cylinder
 -    int  ngroup;          //!< the number of pull groups involved
 -    ivec dim;             //!< pull dimension with geometry distance
 -    int  ndim;            //!< nr of pull_dim != 0
 -    real k;               //!< force constants in tpr file
 -    real init_dist;       //!< reference displacement
 -    char coord_unit[256]; //!< unit of the displacement
 +    PullingAlgorithm  pull_type;       //!< such as constraint, umbrella, ...
 +    PullGroupGeometry geometry;        //!< such as distance, direction, cylinder
 +    int               ngroup;          //!< the number of pull groups involved
 +    ivec              dim;             //!< pull dimension with geometry distance
 +    int               ndim;            //!< nr of pull_dim != 0
 +    real              k;               //!< force constants in tpr file
 +    real              init_dist;       //!< reference displacement
 +    char              coord_unit[256]; //!< unit of the displacement
  } t_pullcoord;
  
  //! Parameters of the umbrella potentials
@@@ -160,20 -162,33 +160,20 @@@ typedef struc
      gmx_bool     bPrintComp;     //!< Components of pull distance written to pullx.xvg ?
  
      /*!\}*/
 -    /*!
 -     * \name Using PDO files common until gromacs 3.x
 -     */
 -    /*!\{*/
 -    int    nSkip;
 -    char   Reference[256];
 -    int    nPull;
 -    int    nDim;
 -    ivec   Dims;
 -    char   PullName[4][256];
 -    double UmbPos[4][3];
 -    double UmbCons[4][3];
 -    /*!\}*/
  } t_UmbrellaHeader;
  
  //! Data in the umbrella histograms
  typedef struct
  {
 -    int      nPull; //!< nr of pull groups in this pdo or pullf/x file
 +    int      nPull; //!< nr of pull groups in this pullf/pullx file
      double** Histo; //!< nPull histograms
      double** cum;   //!< nPull cumulative distribution functions
      int      nBin;  //!< nr of bins. identical to opt->bins
      double*  k;     //!< force constants for the nPull coords
      double*  pos;   //!< umbrella positions for the nPull coords
      double* z; //!< z=(-Fi/kT) for the nPull coords. These values are iteratively computed during wham
 -    int* N;    //!< nr of data points in nPull histograms.
 -    int* Ntot; //!< also nr of data points. N and Ntot only differ if bHistEq==TRUE
 +    int*    N;    //!< nr of data points in nPull histograms.
 +    int*    Ntot; //!< also nr of data points. N and Ntot only differ if bHistEq==TRUE
  
      /*! \brief  g = 1 + 2*tau[int]/dt where tau is the integrated autocorrelation time.
       *
@@@ -216,9 -231,9 +216,9 @@@ typedef struct UmbrellaOptions // NOLIN
       */
      /*!\{*/
      const char *fnTpr, *fnPullf, *fnCoordSel;
 -    const char *fnPdo, *fnPullx;            //!< file names of input
 -    gmx_bool    bTpr, bPullf, bPdo, bPullx; //!< input file types given?
 -    real        tmin, tmax, dt;             //!< only read input within tmin and tmax with dt
 +    const char* fnPullx;              //!< file names of input
 +    gmx_bool    bTpr, bPullf, bPullx; //!< input file types given?
 +    real        tmin, tmax, dt;       //!< only read input within tmin and tmax with dt
  
      gmx_bool bInitPotByIntegration; //!< before WHAM, guess potential by force integration. Yields 1.5 to 2 times faster convergence
      int stepUpdateContrib; //!< update contribution table every ... iterations. Accelerates WHAM.
       * \name Basic WHAM options
       */
      /*!\{*/
 -    int      bins; //!< nr of bins, min, max, and dz of profile
 -    real     min, max, dz;
 -    real     Temperature, Tolerance; //!< temperature, converged when probability changes less than Tolerance
 -    gmx_bool bCycl;                  //!< generate cyclic (periodic) PMF
 +    int  bins; //!< nr of bins, min, max, and dz of profile
 +    real min, max, dz;
 +    real Temperature, Tolerance; //!< temperature, converged when probability changes less than Tolerance
 +    gmx_bool bCycl;              //!< generate cyclic (periodic) PMF
      /*!\}*/
      /*!
       * \name Output control
@@@ -429,10 -444,109 +429,10 @@@ static void setup_tab(const char* fn, t
          opt->tabX[i] = y[0][i];
          opt->tabY[i] = y[1][i];
      }
 -    printf("Found equally spaced tabulated potential from %g to %g, spacing %g\n", opt->tabMin,
 -           opt->tabMax, opt->tabDz);
 -}
 -
 -//! Read the header of an PDO file (position, force const, nr of groups)
 -static void read_pdo_header(FILE* file, t_UmbrellaHeader* header, t_UmbrellaOptions* opt)
 -{
 -    char               line[2048];
 -    char               Buffer0[256], Buffer1[256], Buffer2[256], Buffer3[256], Buffer4[256];
 -    int                i;
 -    std::istringstream ist;
 -
 -    /*  line 1 */
 -    if (fgets(line, 2048, file) == nullptr)
 -    {
 -        gmx_fatal(FARGS, "Error reading header from pdo file\n");
 -    }
 -    ist.str(line);
 -    ist >> Buffer0 >> Buffer1 >> Buffer2;
 -    if (std::strcmp(Buffer1, "UMBRELLA") != 0)
 -    {
 -        gmx_fatal(FARGS,
 -                  "This does not appear to be a valid pdo file. Found %s, expected %s\n"
 -                  "(Found in first line: `%s')\n",
 -                  Buffer1, "UMBRELLA", line);
 -    }
 -    if (std::strcmp(Buffer2, "3.0") != 0)
 -    {
 -        gmx_fatal(FARGS, "This does not appear to be a version 3.0 pdo file");
 -    }
 -
 -    /*  line 2 */
 -    if (fgets(line, 2048, file) == nullptr)
 -    {
 -        gmx_fatal(FARGS, "Error reading header from pdo file\n");
 -    }
 -    ist.str(line);
 -    ist >> Buffer0 >> Buffer1 >> Buffer2 >> header->Dims[0] >> header->Dims[1] >> header->Dims[2];
 -    /* printf("%d %d %d\n", header->Dims[0],header->Dims[1],header->Dims[2]); */
 -
 -    header->nDim = header->Dims[0] + header->Dims[1] + header->Dims[2];
 -    if (header->nDim != 1)
 -    {
 -        gmx_fatal(FARGS, "Currently only supports one dimension");
 -    }
 -
 -    /* line3 */
 -    if (fgets(line, 2048, file) == nullptr)
 -    {
 -        gmx_fatal(FARGS, "Error reading header from pdo file\n");
 -    }
 -    ist.str(line);
 -    ist >> Buffer0 >> Buffer1 >> header->nSkip;
 -
 -    /* line 4 */
 -    if (fgets(line, 2048, file) == nullptr)
 -    {
 -        gmx_fatal(FARGS, "Error reading header from pdo file\n");
 -    }
 -    ist.str(line);
 -    ist >> Buffer0 >> Buffer1 >> Buffer2 >> header->Reference;
 -
 -    /* line 5 */
 -    if (fgets(line, 2048, file) == nullptr)
 -    {
 -        gmx_fatal(FARGS, "Error reading header from pdo file\n");
 -    }
 -    ist.str(line);
 -    ist >> Buffer0 >> Buffer1 >> Buffer2 >> Buffer3 >> Buffer4 >> header->nPull;
 -
 -    if (opt->verbose)
 -    {
 -        printf("\tFound nPull=%d , nSkip=%d, ref=%s\n", header->nPull, header->nSkip, header->Reference);
 -    }
 -
 -    for (i = 0; i < header->nPull; ++i)
 -    {
 -        if (fgets(line, 2048, file) == nullptr)
 -        {
 -            gmx_fatal(FARGS, "Error reading header from pdo file\n");
 -        }
 -        ist.str(line);
 -        ist >> Buffer0 >> Buffer1 >> Buffer2 >> header->PullName[i];
 -        ist >> Buffer0 >> Buffer1 >> header->UmbPos[i][0];
 -        ist >> Buffer0 >> Buffer1 >> header->UmbCons[i][0];
 -
 -        if (opt->verbose)
 -        {
 -            printf("\tpullgroup %d, pullname = %s, UmbPos = %g, UmbConst = %g\n", i,
 -                   header->PullName[i], header->UmbPos[i][0], header->UmbCons[i][0]);
 -        }
 -    }
 -
 -    if (fgets(line, 2048, file) == nullptr)
 -    {
 -        gmx_fatal(FARGS, "Cannot read from file\n");
 -    }
 -    ist.str(line);
 -    ist >> Buffer3;
 -    if (std::strcmp(Buffer3, "#####") != 0)
 -    {
 -        gmx_fatal(FARGS, "Expected '#####', found %s. Hick.\n", Buffer3);
 -    }
 +    printf("Found equally spaced tabulated potential from %g to %g, spacing %g\n",
 +           opt->tabMin,
 +           opt->tabMax,
 +           opt->tabDz);
  }
  
  //! smarter fgets
@@@ -466,6 -580,220 +466,6 @@@ static char* fgets3(FILE* fp, char ptr[
      return ptr;
  }
  
 -/*! \brief Read the data columns of and PDO file.
 - *
 - *  TO DO: Get rid of the scanf function to avoid the clang warning.
 - *         At the moment, this warning is avoided by hiding the format string
 - *         the variable fmtlf.
 - */
 -static void read_pdo_data(FILE*              file,
 -                          t_UmbrellaHeader*  header,
 -                          int                fileno,
 -                          t_UmbrellaWindow*  win,
 -                          t_UmbrellaOptions* opt,
 -                          gmx_bool           bGetMinMax,
 -                          real*              mintmp,
 -                          real*              maxtmp)
 -{
 -    int               i, inttemp, bins, count, ntot;
 -    real              minval, maxval, minfound = 1e20, maxfound = -1e20;
 -    double            temp, time, time0 = 0, dt;
 -    char*             ptr    = nullptr;
 -    t_UmbrellaWindow* window = nullptr;
 -    gmx_bool          timeok, dt_ok = true;
 -    char *            tmpbuf = nullptr, fmt[256], fmtign[256], fmtlf[5] = "%lf";
 -    int               len = STRLEN, dstep = 1;
 -    const int         blocklen = 4096;
 -    int*              lennow   = nullptr;
 -
 -    if (!bGetMinMax)
 -    {
 -        bins   = opt->bins;
 -        minval = opt->min;
 -        maxval = opt->max;
 -
 -        window = win + fileno;
 -        /* Need to alocate memory and set up structure */
 -        window->nPull = header->nPull;
 -        window->nBin  = bins;
 -
 -        snew(window->Histo, window->nPull);
 -        snew(window->z, window->nPull);
 -        snew(window->k, window->nPull);
 -        snew(window->pos, window->nPull);
 -        snew(window->N, window->nPull);
 -        snew(window->Ntot, window->nPull);
 -        snew(window->g, window->nPull);
 -        snew(window->bsWeight, window->nPull);
 -
 -        window->bContrib = nullptr;
 -
 -        if (opt->bCalcTauInt)
 -        {
 -            snew(window->ztime, window->nPull);
 -        }
 -        else
 -        {
 -            window->ztime = nullptr;
 -        }
 -        snew(lennow, window->nPull);
 -
 -        for (i = 0; i < window->nPull; ++i)
 -        {
 -            window->z[i]        = 1;
 -            window->bsWeight[i] = 1.;
 -            snew(window->Histo[i], bins);
 -            window->k[i]    = header->UmbCons[i][0];
 -            window->pos[i]  = header->UmbPos[i][0];
 -            window->N[i]    = 0;
 -            window->Ntot[i] = 0;
 -            window->g[i]    = 1.;
 -            if (opt->bCalcTauInt)
 -            {
 -                window->ztime[i] = nullptr;
 -            }
 -        }
 -
 -        /* Done with setup */
 -    }
 -    else
 -    {
 -        minfound = 1e20;
 -        maxfound = -1e20;
 -        minval = maxval = bins = 0; /* Get rid of warnings */
 -    }
 -
 -    count = 0;
 -    snew(tmpbuf, len);
 -    while ((ptr = fgets3(file, tmpbuf, &len)) != nullptr)
 -    {
 -        trim(ptr);
 -
 -        if (ptr[0] == '#' || std::strlen(ptr) < 2)
 -        {
 -            continue;
 -        }
 -
 -        /* Initiate format string */
 -        fmtign[0] = '\0';
 -        std::strcat(fmtign, "%*s");
 -
 -        sscanf(ptr, fmtlf, &time); /* printf("Time %f\n",time); */
 -        /* Round time to fs */
 -        time = 1.0 / 1000 * (gmx::roundToInt64(time * 1000));
 -
 -        /* get time step of pdo file */
 -        if (count == 0)
 -        {
 -            time0 = time;
 -        }
 -        else if (count == 1)
 -        {
 -            dt = time - time0;
 -            if (opt->dt > 0.0)
 -            {
 -                dstep = gmx::roundToInt(opt->dt / dt);
 -                if (dstep == 0)
 -                {
 -                    dstep = 1;
 -                }
 -            }
 -            if (!bGetMinMax)
 -            {
 -                window->dt = dt * dstep;
 -            }
 -        }
 -        count++;
 -
 -        dt_ok  = ((count - 1) % dstep == 0);
 -        timeok = (dt_ok && time >= opt->tmin && time <= opt->tmax);
 -        /* if (opt->verbose)
 -           printf(" time = %f, (tmin,tmax)=(%e,%e), dt_ok=%d timeok=%d\n",
 -           time,opt->tmin, opt->tmax, dt_ok,timeok); */
 -
 -        if (timeok)
 -        {
 -            for (i = 0; i < header->nPull; ++i)
 -            {
 -                std::strcpy(fmt, fmtign);
 -                std::strcat(fmt, "%lf");    /* Creating a format stings such as "%*s...%*s%lf" */
 -                std::strcat(fmtign, "%*s"); /* ignoring one more entry in the next loop */
 -                if (sscanf(ptr, fmt, &temp))
 -                {
 -                    temp += header->UmbPos[i][0];
 -                    if (bGetMinMax)
 -                    {
 -                        if (temp < minfound)
 -                        {
 -                            minfound = temp;
 -                        }
 -                        if (temp > maxfound)
 -                        {
 -                            maxfound = temp;
 -                        }
 -                    }
 -                    else
 -                    {
 -                        if (opt->bCalcTauInt)
 -                        {
 -                            /* save time series for autocorrelation analysis */
 -                            ntot = window->Ntot[i];
 -                            if (ntot >= lennow[i])
 -                            {
 -                                lennow[i] += blocklen;
 -                                srenew(window->ztime[i], lennow[i]);
 -                            }
 -                            window->ztime[i][ntot] = temp;
 -                        }
 -
 -                        temp -= minval;
 -                        temp /= (maxval - minval);
 -                        temp *= bins;
 -                        temp = std::floor(temp);
 -
 -                        inttemp = static_cast<int>(temp);
 -                        if (opt->bCycl)
 -                        {
 -                            if (inttemp < 0)
 -                            {
 -                                inttemp += bins;
 -                            }
 -                            else if (inttemp >= bins)
 -                            {
 -                                inttemp -= bins;
 -                            }
 -                        }
 -
 -                        if (inttemp >= 0 && inttemp < bins)
 -                        {
 -                            window->Histo[i][inttemp] += 1.;
 -                            window->N[i]++;
 -                        }
 -                        window->Ntot[i]++;
 -                    }
 -                }
 -            }
 -        }
 -        if (time > opt->tmax)
 -        {
 -            if (opt->verbose)
 -            {
 -                printf("time %f larger than tmax %f, stop reading pdo file\n", time, opt->tmax);
 -            }
 -            break;
 -        }
 -    }
 -
 -    if (bGetMinMax)
 -    {
 -        *mintmp = minfound;
 -        *maxtmp = maxfound;
 -    }
 -
 -    sfree(lennow);
 -    sfree(tmpbuf);
 -}
 -
  /*! \brief Set identical weights for all histograms
   *
   * Normally, the weight is given by the number data points in each
@@@ -512,9 -840,7 +512,9 @@@ static double tabulated_pot(double dist
          gmx_fatal(FARGS,
                    "Distance %f out of bounds of tabulated potential (jl=%d, ju=%d).\n"
                    "Provide an extended table.",
 -                  dist, jl, ju);
 +                  dist,
 +                  jl,
 +                  ju);
      }
      pl = opt->tabY[jl];
      pu = opt->tabY[ju];
@@@ -580,8 -906,8 +580,8 @@@ static void setup_acc_wham(const double
                      }
                  }
                  /* Note: there are two contributions to bin k in the wham equations:
 -                   i)  N[j]*exp(- U/(BOLTZ*opt->Temperature) + window[i].z[j])
 -                   ii) exp(- U/(BOLTZ*opt->Temperature))
 +                   i)  N[j]*exp(- U/(c_boltz*opt->Temperature) + window[i].z[j])
 +                   ii) exp(- U/(c_boltz*opt->Temperature))
                     where U is the umbrella potential
                     If any of these number is larger wham_contrib_lim, I set contrib=TRUE
                   */
                  {
                      U = tabulated_pot(distance, opt); /* Use tabulated potential     */
                  }
 -                contrib1 = profile[k] * std::exp(-U / (BOLTZ * opt->Temperature));
 -                contrib2 = window[i].N[j] * std::exp(-U / (BOLTZ * opt->Temperature) + window[i].z[j]);
 +                contrib1 = profile[k] * std::exp(-U / (gmx::c_boltz * opt->Temperature));
 +                contrib2 = window[i].N[j]
 +                           * std::exp(-U / (gmx::c_boltz * opt->Temperature) + window[i].z[j]);
                  window[i].bContrib[j][k] = (contrib1 > wham_contrib_lim || contrib2 > wham_contrib_lim);
                  bAnyContrib              = bAnyContrib || window[i].bContrib[j][k];
                  if (window[i].bContrib[j][k])
      {
          printf("Initialized rapid wham stuff (contrib tolerance %g)\n"
                 "Evaluating only %d of %d expressions.\n\n",
 -               wham_contrib_lim, nContrib, nTot);
 +               wham_contrib_lim,
 +               nContrib,
 +               nTot);
      }
  
      if (opt->verbose)
@@@ -690,7 -1013,7 +690,7 @@@ static void calc_profile(double* profil
                              U = tabulated_pot(distance, opt); /* Use tabulated potential     */
                          }
                          denom += invg * window[j].N[k]
 -                                 * std::exp(-U / (BOLTZ * opt->Temperature) + window[j].z[k]);
 +                                 * std::exp(-U / (gmx::c_boltz * opt->Temperature) + window[j].z[k]);
                      }
                  }
                  profile[i] = num / denom;
@@@ -756,7 -1079,7 +756,7 @@@ static double calc_z(const double* prof
                          {
                              U = tabulated_pot(distance, opt); /* Use tabulated potential     */
                          }
 -                        total += profile[k] * std::exp(-U / (BOLTZ * opt->Temperature));
 +                        total += profile[k] * std::exp(-U / (gmx::c_boltz * opt->Temperature));
                      }
                      /* Avoid floating point exception if window is far outside min and max */
                      if (total != 0.0)
@@@ -802,7 -1125,8 +802,7 @@@ static void symmetrizeProfile(double* p
  
      if (min > 0. || max < 0.)
      {
 -        gmx_fatal(FARGS, "Cannot symmetrize profile around z=0 with min=%f and max=%f\n", opt->min,
 -                  opt->max);
 +        gmx_fatal(FARGS, "Cannot symmetrize profile around z=0 with min=%f and max=%f\n", opt->min, opt->max);
      }
  
      snew(prof2, bins);
@@@ -853,11 -1177,11 +853,11 @@@ static void prof_normalization_and_unit
      }
      else if (opt->unit == en_kJ)
      {
 -        unit_factor = BOLTZ * opt->Temperature;
 +        unit_factor = gmx::c_boltz * opt->Temperature;
      }
      else if (opt->unit == en_kCal)
      {
 -        unit_factor = (BOLTZ / CAL2JOULE) * opt->Temperature;
 +        unit_factor = (gmx::c_boltz / gmx::c_cal2Joule) * opt->Temperature;
      }
      else
      {
@@@ -925,11 -1249,7 +925,11 @@@ static void getRandomIntArray(int nPull
              gmx_fatal(FARGS,
                        "Ups, random iWin = %d, nPull = %d, nr = %d, "
                        "blockLength = %d, blockBase = %d\n",
 -                      ipullRandom, nPull, nr, blockLength, blockBase);
 +                      ipullRandom,
 +                      nPull,
 +                      nr,
 +                      blockLength,
 +                      blockBase);
          }
          randomArray[ipull] = ipullRandom;
      }
@@@ -1476,7 -1796,7 +1476,7 @@@ static void do_bootstrapping(const char
      printf("Wrote boot strap result to %s\n", fnres);
  }
  
 -//! Return type of input file based on file extension (xvg, pdo, or tpr)
 +//! Return type of input file based on file extension (xvg or tpr)
  static int whaminFileType(char* fn)
  {
      int len;
      {
          return whamin_pullxf;
      }
 -    else if (std::strcmp(fn + len - 3, "pdo") == 0 || std::strcmp(fn + len - 6, "pdo.gz") == 0)
 -    {
 -        return whamin_pdo;
 -    }
      else
      {
 -        gmx_fatal(FARGS, "Unknown file type of %s. Should be tpr, xvg, or pdo.\n", fn);
 +        gmx_fatal(FARGS,
 +                  "Unknown file type of %s. Should be tpr or xvg. Use GROMACS 2021 or earlier to "
 +                  "read pdo files.\n",
 +                  fn);
      }
  }
  
 -//! Read the files names in pdo-files.dat, pullf/x-files.dat, tpr-files.dat
 +//! Read the files names in pullf/pullx-files.dat, tpr-files.dat
  static void read_wham_in(const char* fn, char*** filenamesRet, int* nfilesRet, t_UmbrellaOptions* opt)
  {
      char **filename = nullptr, tmp[WHAM_MAXFILELEN + 2];
      *nfilesRet    = nread;
  }
  
 -//! Open a file or a pipe to a gzipped file
 -static FILE* open_pdo_pipe(const char* fn, t_UmbrellaOptions* opt, gmx_bool* bPipeOpen)
 -{
 -    char            Buffer[2048], gunzip[1024], *Path = nullptr;
 -    FILE*           pipe   = nullptr;
 -    static gmx_bool bFirst = true;
 -
 -    /* gzipped pdo file? */
 -    if ((std::strcmp(fn + std::strlen(fn) - 3, ".gz") == 0))
 -    {
 -        /* search gunzip executable */
 -        if (!(Path = getenv("GMX_PATH_GZIP")))
 -        {
 -            if (gmx_fexist("/bin/gunzip"))
 -            {
 -                sprintf(gunzip, "%s", "/bin/gunzip");
 -            }
 -            else if (gmx_fexist("/usr/bin/gunzip"))
 -            {
 -                sprintf(gunzip, "%s", "/usr/bin/gunzip");
 -            }
 -            else
 -            {
 -                gmx_fatal(FARGS,
 -                          "Cannot find executable gunzip in /bin or /usr/bin.\n"
 -                          "You may want to define the path to gunzip "
 -                          "with the environment variable GMX_PATH_GZIP.");
 -            }
 -        }
 -        else
 -        {
 -            sprintf(gunzip, "%s/gunzip", Path);
 -            if (!gmx_fexist(gunzip))
 -            {
 -                gmx_fatal(FARGS,
 -                          "Cannot find executable %s. Please define the path to gunzip"
 -                          " in the environmental varialbe GMX_PATH_GZIP.",
 -                          gunzip);
 -            }
 -        }
 -        if (bFirst)
 -        {
 -            printf("Using gunzip executable %s\n", gunzip);
 -            bFirst = false;
 -        }
 -        if (!gmx_fexist(fn))
 -        {
 -            gmx_fatal(FARGS, "File %s does not exist.\n", fn);
 -        }
 -        sprintf(Buffer, "%s -c < %s", gunzip, fn);
 -        if (opt->verbose)
 -        {
 -            printf("Executing command '%s'\n", Buffer);
 -        }
 -#if HAVE_PIPES
 -        if ((pipe = popen(Buffer, "r")) == nullptr)
 -        {
 -            gmx_fatal(FARGS, "Unable to open pipe to `%s'\n", Buffer);
 -        }
 -#else
 -        gmx_fatal(FARGS, "Cannot open a compressed file on platform without pipe support");
 -#endif
 -        *bPipeOpen = TRUE;
 -    }
 -    else
 -    {
 -        pipe       = gmx_ffopen(fn, "r");
 -        *bPipeOpen = FALSE;
 -    }
 -
 -    return pipe;
 -}
 -
 -//! Close file or pipe
 -static void pdo_close_file(FILE* fp)
 -{
 -#if HAVE_PIPES
 -    pclose(fp);
 -#else
 -    gmx_ffclose(fp);
 -#endif
 -}
 -
 -//! Reading all pdo files
 -static void read_pdo_files(char** fn, int nfiles, t_UmbrellaHeader* header, t_UmbrellaWindow* window, t_UmbrellaOptions* opt)
 -{
 -    FILE*    file;
 -    real     mintmp, maxtmp, done = 0.;
 -    int      i;
 -    gmx_bool bPipeOpen;
 -    /* char Buffer0[1000]; */
 -
 -    if (nfiles < 1)
 -    {
 -        gmx_fatal(FARGS, "No files found. Hick.");
 -    }
 -
 -    /* if min and max are not given, get min and max from the input files */
 -    if (opt->bAuto)
 -    {
 -        printf("Automatic determination of boundaries from %d pdo files...\n", nfiles);
 -        opt->min = 1e20;
 -        opt->max = -1e20;
 -        for (i = 0; i < nfiles; ++i)
 -        {
 -            file = open_pdo_pipe(fn[i], opt, &bPipeOpen);
 -            /*fgets(Buffer0,999,file);
 -               fprintf(stderr,"First line '%s'\n",Buffer0); */
 -            done = 100.0 * (i + 1) / nfiles;
 -            fprintf(stdout, "\rOpening %s ... [%2.0f%%]", fn[i], done);
 -            fflush(stdout);
 -            if (opt->verbose)
 -            {
 -                printf("\n");
 -            }
 -            read_pdo_header(file, header, opt);
 -            /* here only determine min and max of this window */
 -            read_pdo_data(file, header, i, nullptr, opt, TRUE, &mintmp, &maxtmp);
 -            if (maxtmp > opt->max)
 -            {
 -                opt->max = maxtmp;
 -            }
 -            if (mintmp < opt->min)
 -            {
 -                opt->min = mintmp;
 -            }
 -            if (bPipeOpen)
 -            {
 -                pdo_close_file(file);
 -            }
 -            else
 -            {
 -                gmx_ffclose(file);
 -            }
 -        }
 -        printf("\n");
 -        printf("\nDetermined boundaries to %f and %f\n\n", opt->min, opt->max);
 -        if (opt->bBoundsOnly)
 -        {
 -            printf("Found option -boundsonly, now exiting.\n");
 -            exit(0);
 -        }
 -    }
 -    /* store stepsize in profile */
 -    opt->dz = (opt->max - opt->min) / opt->bins;
 -
 -    /* Having min and max, we read in all files */
 -    /* Loop over all files */
 -    for (i = 0; i < nfiles; ++i)
 -    {
 -        done = 100.0 * (i + 1) / nfiles;
 -        fprintf(stdout, "\rOpening %s ... [%2.0f%%]", fn[i], done);
 -        fflush(stdout);
 -        if (opt->verbose)
 -        {
 -            printf("\n");
 -        }
 -        file = open_pdo_pipe(fn[i], opt, &bPipeOpen);
 -        read_pdo_header(file, header, opt);
 -        /* load data into window */
 -        read_pdo_data(file, header, i, window, opt, FALSE, nullptr, nullptr);
 -        if ((window + i)->Ntot[0] == 0)
 -        {
 -            fprintf(stderr, "\nWARNING, no data points read from file %s (check -b option)\n", fn[i]);
 -        }
 -        if (bPipeOpen)
 -        {
 -            pdo_close_file(file);
 -        }
 -        else
 -        {
 -            gmx_ffclose(file);
 -        }
 -    }
 -    printf("\n");
 -    for (i = 0; i < nfiles; ++i)
 -    {
 -        sfree(fn[i]);
 -    }
 -    sfree(fn);
 -}
 -
  //! translate 0/1 to N/Y to write pull dimensions
  #define int2YN(a) (((a) == 0) ? ("N") : ("Y"))
  
@@@ -1580,17 -2083,18 +1580,17 @@@ static void read_tpr_header(const char
           * so we need to multiply with the internal units (radians for angle)
           * to user units (degrees for an angle) with the same power.
           */
 -        header->pcrd[i].k =
 -                ir->pull->coord[i].k
 -                / gmx::square(pull_conversion_factor_internal2userinput(&ir->pull->coord[i]));
 +        header->pcrd[i].k = ir->pull->coord[i].k
 +                            / gmx::square(pull_conversion_factor_internal2userinput(ir->pull->coord[i]));
          header->pcrd[i].init_dist = ir->pull->coord[i].init;
  
          copy_ivec(ir->pull->coord[i].dim, header->pcrd[i].dim);
          header->pcrd[i].ndim =
                  header->pcrd[i].dim[XX] + header->pcrd[i].dim[YY] + header->pcrd[i].dim[ZZ];
  
 -        std::strcpy(header->pcrd[i].coord_unit, pull_coordinate_units(&ir->pull->coord[i]));
 +        std::strcpy(header->pcrd[i].coord_unit, pull_coordinate_units(ir->pull->coord[i]));
  
 -        if (ir->efep != efepNO && ir->pull->coord[i].k != ir->pull->coord[i].kB)
 +        if (ir->efep != FreeEnergyPerturbationType::No && ir->pull->coord[i].k != ir->pull->coord[i].kB)
          {
              gmx_fatal(FARGS,
                        "Seems like you did free-energy perturbation, and you perturbed the force "
              gmx_fatal(FARGS,
                        "Found %d pull coordinates in %s, but %d columns in the respective line\n"
                        "coordinate selection file (option -is)\n",
 -                      ir->pull->ncoord, fn, coordsel->n);
 +                      ir->pull->ncoord,
 +                      fn,
 +                      coordsel->n);
          }
      }
  
      /* Check pull coords for consistency */
 -    int  geom          = -1;
 -    ivec thedim        = { 0, 0, 0 };
 -    bool geometryIsSet = false;
 +    PullGroupGeometry geom          = PullGroupGeometry::Count;
 +    ivec              thedim        = { 0, 0, 0 };
 +    bool              geometryIsSet = false;
      for (int i = 0; i < ir->pull->ncoord; i++)
      {
          if (coordsel == nullptr || coordsel->bUse[i])
          {
 -            if (header->pcrd[i].pull_type != epullUMBRELLA)
 +            if (header->pcrd[i].pull_type != PullingAlgorithm::Umbrella)
              {
                  gmx_fatal(FARGS,
                            "%s: Pull coordinate %d is of type \"%s\", expected \"umbrella\". Only "
                            "umbrella coodinates can enter WHAM.\n"
-                           "If you have umrella and non-umbrella coordinates, you can select the "
+                           "If you have umbrella and non-umbrella coordinates, you can select the "
                            "umbrella coordinates with gmx wham -is\n",
 -                          fn, i + 1, epull_names[header->pcrd[i].pull_type]);
 +                          fn,
 +                          i + 1,
 +                          enumValueToString(header->pcrd[i].pull_type));
              }
              if (!geometryIsSet)
              {
                            "%s, coordinate %d: %s)\n"
                            "If you want to use only some pull coordinates in WHAM, please select "
                            "them with option gmx wham -is\n",
 -                          fn, epullg_names[geom], i + 1, epullg_names[header->pcrd[i].geometry]);
 +                          fn,
 +                          enumValueToString(geom),
 +                          i + 1,
 +                          enumValueToString(header->pcrd[i].geometry));
              }
              if (thedim[XX] != header->pcrd[i].dim[XX] || thedim[YY] != header->pcrd[i].dim[YY]
                  || thedim[ZZ] != header->pcrd[i].dim[ZZ])
                            "%s %s %s, coordinate %d: %s %s %s)\n"
                            "If you want to use only some pull coordinates in WHAM, please select "
                            "them with option gmx wham -is\n",
 -                          fn, int2YN(thedim[XX]), int2YN(thedim[YY]), int2YN(thedim[ZZ]), i + 1,
 -                          int2YN(header->pcrd[i].dim[XX]), int2YN(header->pcrd[i].dim[YY]),
 +                          fn,
 +                          int2YN(thedim[XX]),
 +                          int2YN(thedim[YY]),
 +                          int2YN(thedim[ZZ]),
 +                          i + 1,
 +                          int2YN(header->pcrd[i].dim[XX]),
 +                          int2YN(header->pcrd[i].dim[YY]),
                            int2YN(header->pcrd[i].dim[ZZ]));
              }
 -            if (header->pcrd[i].geometry == epullgCYL)
 +            if (header->pcrd[i].geometry == PullGroupGeometry::Cylinder)
              {
                  if (header->pcrd[i].dim[XX] || header->pcrd[i].dim[YY] || (!header->pcrd[i].dim[ZZ]))
                  {
                              FARGS,
                              "With pull geometry 'cylinder', expected pulling in Z direction only.\n"
                              "However, found dimensions [%s %s %s]\n",
 -                            int2YN(header->pcrd[i].dim[XX]), int2YN(header->pcrd[i].dim[YY]),
 +                            int2YN(header->pcrd[i].dim[XX]),
 +                            int2YN(header->pcrd[i].dim[YY]),
                              int2YN(header->pcrd[i].dim[ZZ]));
                  }
              }
                  gmx_fatal(FARGS,
                            "%s: Pull coordinate %d has force constant of of %g.\n"
                            "That doesn't seem to be an Umbrella tpr.\n",
 -                          fn, i + 1, header->pcrd[i].k);
 +                          fn,
 +                          i + 1,
 +                          header->pcrd[i].k);
              }
          }
      }
          int maxlen = 0;
          for (int i = 0; i < ir->pull->ncoord; i++)
          {
 -            int lentmp = strlen(epullg_names[header->pcrd[i].geometry]);
 +            int lentmp = strlen(enumValueToString(header->pcrd[i].geometry));
              maxlen     = (lentmp > maxlen) ? lentmp : maxlen;
          }
          char fmt[STRLEN];
          for (int i = 0; i < ir->pull->ncoord; i++)
          {
              bool use = (coordsel == nullptr || coordsel->bUse[i]);
 -            printf(fmt, epullg_names[header->pcrd[i].geometry], header->pcrd[i].k, header->pcrd[i].init_dist,
 -                   int2YN(header->pcrd[i].dim[XX]), int2YN(header->pcrd[i].dim[YY]),
 -                   int2YN(header->pcrd[i].dim[ZZ]), header->pcrd[i].ndim, use ? "Yes" : "No");
 +            printf(fmt,
 +                   enumValueToString(header->pcrd[i].geometry),
 +                   header->pcrd[i].k,
 +                   header->pcrd[i].init_dist,
 +                   int2YN(header->pcrd[i].dim[XX]),
 +                   int2YN(header->pcrd[i].dim[YY]),
 +                   int2YN(header->pcrd[i].dim[ZZ]),
 +                   header->pcrd[i].ndim,
 +                   use ? "Yes" : "No");
              printf("\tPull group coordinates of %d groups expected in pullx files.\n",
                     ir->pull->bPrintCOM ? header->pcrd[i].ngroup : 0);
          }
@@@ -1818,9 -2301,7 +1818,9 @@@ static void read_pull_xf(const char
              printf("\t\treaction coordinate:             %d\n"
                     "\t\tcenter-of-mass of groups:        %d\n"
                     "\t\treference position column:       %s\n",
 -                   1, nColCOMCrd[i], (header->bPrintRefValue ? "Yes" : "No"));
 +                   1,
 +                   nColCOMCrd[i],
 +                   (header->bPrintRefValue ? "Yes" : "No"));
          }
          printf("\tFound %d times in %s\n", nt, fn);
          bFirst = FALSE;
          gmx_fatal(FARGS,
                    "Expected %d columns (including time column) in %s, but found %d."
                    " Maybe you confused options -if and -ix ?",
 -                  nColExpect, fn, ny);
 +                  nColExpect,
 +                  fn,
 +                  ny);
      }
  
      if (!bGetMinMax)
                  gmx_fatal(FARGS,
                            "tpr file contains %d pull groups, but expected %d from group selection "
                            "file\n",
 -                          header->npullcrds, coordsel->n);
 +                          header->npullcrds,
 +                          coordsel->n);
              }
              window->nPull = coordsel->nUse;
          }
          /* Do you want that time frame? */
          t = 1.0 / 1000 * (gmx::roundToInt64((y[0][i] * 1000))); /* round time to fs */
  
 -        /* get time step of pdo file and get dstep from opt->dt */
 +        /* get time step and get dstep from opt->dt */
          if (i == 0)
          {
              time0 = t;
                          gmx_fatal(FARGS,
                                    "gUsed too large (%d, nPull=%d). This error should have been "
                                    "caught before.\n",
 -                                  gUsed, window->nPull);
 +                                  gUsed,
 +                                  window->nPull);
                      }
  
                      if (opt->bCalcTauInt && !bGetMinMax)
@@@ -2102,16 -2579,9 +2102,16 @@@ static void read_tpr_pullxf_files(char*
              if (whaminFileType(fnPull[i]) != whamin_pullxf)
              {
                  gmx_fatal(FARGS,
 -                          "Expected the %d'th file in input file to be a xvg (pullx/pullf) file\n", i);
 -            }
 -            read_pull_xf(fnPull[i], header, nullptr, opt, TRUE, &mintmp, &maxtmp,
 +                          "Expected the %d'th file in input file to be a xvg (pullx/pullf) file\n",
 +                          i);
 +            }
 +            read_pull_xf(fnPull[i],
 +                         header,
 +                         nullptr,
 +                         opt,
 +                         TRUE,
 +                         &mintmp,
 +                         &maxtmp,
                           (opt->nCoordsel > 0) ? &opt->coordsel[i] : nullptr);
              if (maxtmp > opt->max)
              {
          read_tpr_header(fnTprs[i], header, opt, (opt->nCoordsel > 0) ? &opt->coordsel[i] : nullptr);
          if (whaminFileType(fnPull[i]) != whamin_pullxf)
          {
 -            gmx_fatal(FARGS,
 -                      "Expected the %d'th file in input file to be a xvg (pullx/pullf) file\n", i);
 +            gmx_fatal(
 +                    FARGS, "Expected the %d'th file in input file to be a xvg (pullx/pullf) file\n", i);
          }
 -        read_pull_xf(fnPull[i], header, window + i, opt, FALSE, nullptr, nullptr,
 +        read_pull_xf(fnPull[i],
 +                     header,
 +                     window + i,
 +                     opt,
 +                     FALSE,
 +                     nullptr,
 +                     nullptr,
                       (opt->nCoordsel > 0) ? &opt->coordsel[i] : nullptr);
          if (window[i].Ntot[0] == 0)
          {
@@@ -2192,11 -2656,8 +2192,11 @@@ static void readIntegratedAutocorrelati
      nlines = read_xvg(fn, &iact, &ny);
      if (nlines != nwins)
      {
 -        gmx_fatal(FARGS, "Found %d lines with integrated autocorrelation times in %s.\nExpected %d",
 -                  nlines, fn, nwins);
 +        gmx_fatal(FARGS,
 +                  "Found %d lines with integrated autocorrelation times in %s.\nExpected %d",
 +                  nlines,
 +                  fn,
 +                  nwins);
      }
      for (i = 0; i < nlines; i++)
      {
@@@ -2297,18 -2758,14 +2297,18 @@@ static void calcIntegratedAutocorrelati
  
      if (opt->verbose)
      {
 -        fpcorr = xvgropen("hist_autocorr.xvg", "Autocorrelation functions of umbrella windows",
 -                          "time [ps]", "autocorrelation function", opt->oenv);
 +        fpcorr = xvgropen("hist_autocorr.xvg",
 +                          "Autocorrelation functions of umbrella windows",
 +                          "time [ps]",
 +                          "autocorrelation function",
 +                          opt->oenv);
      }
  
      printf("\n");
      for (i = 0; i < nwins; i++)
      {
 -        fprintf(stdout, "\rEstimating integrated autocorrelation times ... [%2.0f%%] ...",
 +        fprintf(stdout,
 +                "\rEstimating integrated autocorrelation times ... [%2.0f%%] ...",
                  100. * (i + 1) / nwins);
          fflush(stdout);
          ntot = window[i].Ntot[0];
                  gmx_fatal(FARGS,
                            "Encountered different nr of frames in different pull groups.\n"
                            "That should not happen. (%d and %d)\n",
 -                          ntot, window[i].Ntot[ig]);
 +                          ntot,
 +                          window[i].Ntot[ig]);
              }
              ztime = window[i].ztime[ig];
  
@@@ -2608,8 -3064,7 +2608,8 @@@ static void checkReactionCoordinateCove
              fprintf(stderr,
                      "\nWARNING, no data point in bin %d (z=%g) !\n"
                      "You may not get a reasonable profile. Check your histograms!\n",
 -                    j, z);
 +                    j,
 +                    z);
          }
          /* and check for poor sampling */
          else if (relcount < 0.005 && !bBoundary)
@@@ -2711,7 -3166,7 +2711,7 @@@ static void guessPotByIntegration(t_Umb
       */
      for (j = 0; j < opt->bins; ++j)
      {
 -        pot[j] = std::exp(-pot[j] / (BOLTZ * opt->Temperature));
 +        pot[j] = std::exp(-pot[j] / (gmx::c_boltz * opt->Temperature));
      }
      calc_z(pot, window, nWindows, opt, TRUE);
  
@@@ -2801,9 -3256,7 +2801,9 @@@ static void readPullCoordSelection(t_Um
      printf("\nUse only these pull coordinates:\n");
      for (iline = 0; iline < nTpr; iline++)
      {
 -        printf("%s (%d of %d coordinates):", fnTpr[iline], opt->coordsel[iline].nUse,
 +        printf("%s (%d of %d coordinates):",
 +               fnTpr[iline],
 +               opt->coordsel[iline].nUse,
                 opt->coordsel[iline].n);
          for (i = 0; i < opt->coordsel[iline].n; i++)
          {
@@@ -2850,7 -3303,27 +2850,7 @@@ int gmx_wham(int argc, char* argv[]
          "  provides the pull force output file names ([TT]pullf.xvg[tt]) with option [TT]-if[tt].",
          "  From the pull force the position in the umbrella potential is",
          "  computed. This does not work with tabulated umbrella potentials.",
 -        "* With option [TT]-ip[tt], the user provides file names of (gzipped) [REF].pdo[ref] ",
 -        "  files, i.e.",
 -        "  the GROMACS 3.3 umbrella output files. If you have some unusual",
 -        "  reaction coordinate you may also generate your own [REF].pdo[ref] files and",
 -        "  feed them with the [TT]-ip[tt] option into to [THISMODULE]. The [REF].pdo[ref] file ",
 -        "  header must be similar to the following::",
 -        "",
 -        "  # UMBRELLA      3.0",
 -        "  # Component selection: 0 0 1",
 -        "  # nSkip 1",
 -        "  # Ref. Group 'TestAtom'",
 -        "  # Nr. of pull groups 2",
 -        "  # Group 1 'GR1'  Umb. Pos. 5.0 Umb. Cons. 1000.0",
 -        "  # Group 2 'GR2'  Umb. Pos. 2.0 Umb. Cons. 500.0",
 -        "  #####",
          "",
 -        "  The number of pull groups, umbrella positions, force constants, and names ",
 -        "  may (of course) differ. Following the header, a time column and ",
 -        "  a data column for each pull group follows (i.e. the displacement",
 -        "  with respect to the umbrella center). Up to four pull groups are possible ",
 -        "  per [REF].pdo[ref] file at present.[PAR]",
          "By default, all pull coordinates found in all pullx/pullf files are used in WHAM. If ",
          "only ",
          "some of the pull coordinates should be used, a pull coordinate selection file (option ",
          "",
          "Always check whether the histograms sufficiently overlap.[PAR]",
          "The umbrella potential is assumed to be harmonic and the force constants are ",
 -        "read from the [REF].tpr[ref] or [REF].pdo[ref] files. If a non-harmonic umbrella force ",
 +        "read from the [REF].tpr[ref] files. If a non-harmonic umbrella force ",
          "was applied ",
          "a tabulated potential can be provided with [TT]-tab[tt].",
          "",
          "less robust) method such as fitting to a double exponential, you can ",
          "compute the IACTs with [gmx-analyze] and provide them to [THISMODULE] with the file ",
          "[TT]iact-in.dat[tt] (option [TT]-iiact[tt]), which should contain one line per ",
 -        "input file ([REF].pdo[ref] or pullx/f file) and one column per pull coordinate in the ",
 +        "input file (pullx/pullf file) and one column per pull coordinate in the ",
          "respective file.",
          "",
          "Error analysis",
          { efDAT, "-ix", "pullx-files", ffOPTRD }, /* wham input: pullf.xvg's and tprs           */
          { efDAT, "-if", "pullf-files", ffOPTRD }, /* wham input: pullf.xvg's and tprs           */
          { efDAT, "-it", "tpr-files", ffOPTRD },   /* wham input: tprs                           */
 -        { efDAT, "-ip", "pdo-files", ffOPTRD },   /* wham input: pdo files (gmx3 style)         */
          { efDAT, "-is", "coordsel", ffOPTRD },    /* input: select pull coords to use           */
          { efXVG, "-o", "profile", ffWRITE },      /* output file for profile                     */
          { efXVG, "-hist", "histo", ffWRITE },     /* output file for histograms                  */
      t_UmbrellaWindow* window = nullptr;
      double *          profile, maxchange = 1e20;
      gmx_bool          bMinSet, bMaxSet, bAutoSet, bExact = FALSE;
 -    char **           fninTpr, **fninPull, **fninPdo;
 +    char **           fninTpr, **fninPull;
      const char*       fnPull;
      FILE *            histout, *profout;
      char              xlabel[STRLEN], ylabel[256], title[256];
      opt.stepchange            = 100;
      opt.stepUpdateContrib     = 100;
  
 -    if (!parse_common_args(&argc, argv, 0, NFILE, fnm, asize(pa), pa, asize(desc), desc, 0, nullptr,
 -                           &opt.oenv))
 +    if (!parse_common_args(
 +                &argc, argv, 0, NFILE, fnm, asize(pa), pa, asize(desc), desc, 0, nullptr, &opt.oenv))
      {
          return 0;
      }
      opt.bProf0Set = opt2parg_bSet("-zprof0", asize(pa), pa);
  
      opt.bTab         = opt2bSet("-tab", NFILE, fnm);
 -    opt.bPdo         = opt2bSet("-ip", NFILE, fnm);
      opt.bTpr         = opt2bSet("-it", NFILE, fnm);
      opt.bPullx       = opt2bSet("-ix", NFILE, fnm);
      opt.bPullf       = opt2bSet("-if", NFILE, fnm);
      {
          gmx_fatal(FARGS,
                    "Force input does not work with tabulated potentials. "
 -                  "Provide pullx.xvg or pdo files!");
 +                  "Provide pullx.xvg files!");
      }
  
 -    if (!opt.bPdo && !WHAMBOOLXOR(opt.bPullx, opt.bPullf))
 +    if (!WHAMBOOLXOR(opt.bPullx, opt.bPullf))
      {
          gmx_fatal(FARGS, "Give either pullx (-ix) OR pullf (-if) data. Not both.");
      }
 -    if (!opt.bPdo && !(opt.bTpr || opt.bPullf || opt.bPullx))
 -    {
 -        gmx_fatal(FARGS,
 -                  "gmx wham supports three input modes, pullx, pullf, or pdo file input."
 -                  "\n\n Check gmx wham -h !");
 -    }
  
 -    opt.fnPdo      = opt2fn("-ip", NFILE, fnm);
      opt.fnTpr      = opt2fn("-it", NFILE, fnm);
      opt.fnPullf    = opt2fn("-if", NFILE, fnm);
      opt.fnPullx    = opt2fn("-ix", NFILE, fnm);
      }
  
      /* Reading gmx4/gmx5 pull output and tpr files */
 -    if (opt.bTpr || opt.bPullf || opt.bPullx)
 -    {
 -        read_wham_in(opt.fnTpr, &fninTpr, &nfiles, &opt);
 -
 -        fnPull = opt.bPullf ? opt.fnPullf : opt.fnPullx;
 -        read_wham_in(fnPull, &fninPull, &nfiles2, &opt);
 -        printf("Found %d tpr and %d pull %s files in %s and %s, respectively\n", nfiles, nfiles2,
 -               opt.bPullf ? "force" : "position", opt.fnTpr, fnPull);
 -        if (nfiles != nfiles2)
 -        {
 -            gmx_fatal(FARGS, "Found %d file names in %s, but %d in %s\n", nfiles, opt.fnTpr, nfiles2, fnPull);
 -        }
 -
 -        /* Read file that selects the pull group to be used */
 -        if (opt.fnCoordSel != nullptr)
 -        {
 -            readPullCoordSelection(&opt, fninTpr, nfiles);
 -        }
 +    read_wham_in(opt.fnTpr, &fninTpr, &nfiles, &opt);
  
 -        window = initUmbrellaWindows(nfiles);
 -        read_tpr_pullxf_files(fninTpr, fninPull, nfiles, &header, window, &opt);
 +    fnPull = opt.bPullf ? opt.fnPullf : opt.fnPullx;
 +    read_wham_in(fnPull, &fninPull, &nfiles2, &opt);
 +    printf("Found %d tpr and %d pull %s files in %s and %s, respectively\n",
 +           nfiles,
 +           nfiles2,
 +           opt.bPullf ? "force" : "position",
 +           opt.fnTpr,
 +           fnPull);
 +    if (nfiles != nfiles2)
 +    {
 +        gmx_fatal(FARGS, "Found %d file names in %s, but %d in %s\n", nfiles, opt.fnTpr, nfiles2, fnPull);
      }
 -    else
 -    { /* reading pdo files */
 -        if (opt.fnCoordSel != nullptr)
 -        {
 -            gmx_fatal(FARGS,
 -                      "Reading a -is file is not supported with PDO input files.\n"
 -                      "Use awk or a similar tool to pick the required pull groups from your PDO "
 -                      "files\n");
 -        }
 -        read_wham_in(opt.fnPdo, &fninPdo, &nfiles, &opt);
 -        printf("Found %d pdo files in %s\n", nfiles, opt.fnPdo);
 -        window = initUmbrellaWindows(nfiles);
 -        read_pdo_files(fninPdo, nfiles, &header, window, &opt);
 +
 +    /* Read file that selects the pull group to be used */
 +    if (opt.fnCoordSel != nullptr)
 +    {
 +        readPullCoordSelection(&opt, fninTpr, nfiles);
      }
  
 +    window = initUmbrellaWindows(nfiles);
 +    read_tpr_pullxf_files(fninTpr, fninPull, nfiles, &header, window, &opt);
 +
      /* It is currently assumed that all pull coordinates have the same geometry, so they also have the same coordinate units.
         We can therefore get the units for the xlabel from the first coordinate. */
      sprintf(xlabel, "\\xx\\f{} (%s)", header.pcrd[0].coord_unit);
      /* Bootstrap Method */
      if (opt.nBootStrap)
      {
 -        do_bootstrapping(opt2fn("-bsres", NFILE, fnm), opt2fn("-bsprof", NFILE, fnm),
 -                         opt2fn("-hist", NFILE, fnm), xlabel, ylabel, profile, window, nwins, &opt);
 +        do_bootstrapping(opt2fn("-bsres", NFILE, fnm),
 +                         opt2fn("-bsprof", NFILE, fnm),
 +                         opt2fn("-hist", NFILE, fnm),
 +                         xlabel,
 +                         ylabel,
 +                         profile,
 +                         window,
 +                         nwins,
 +                         &opt);
      }
  
      sfree(profile);
index 648734805326ece23b07b598965805f07dcc5738,ffa7fde37ec42ff0cc121d4b053fb8324dd24382..b7e663640ce99d664779b61938320e322e002e57
@@@ -45,7 -45,6 +45,7 @@@
  #include <cstring>
  
  #include <algorithm>
 +#include <optional>
  #include <string>
  #include <utility>
  #include <vector>
@@@ -59,7 -58,6 +59,7 @@@
  #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/mdtypes/md_enums.h"
  #include "gromacs/topology/ifunc.h"
@@@ -98,7 -96,11 +98,7 @@@ struct VirtualSiteConfiguratio
                                        int                nhyd,
                                        const std::string& nextheavy,
                                        const std::string& dummy) :
 -        atomtype(type),
 -        isplanar(planar),
 -        nHydrogens(nhyd),
 -        nextHeavyType(nextheavy),
 -        dummyMass(dummy)
 +        atomtype(type), isplanar(planar), nHydrogens(nhyd), nextHeavyType(nextheavy), dummyMass(dummy)
      {
      }
      //! Type for the XH3/XH2 atom.
@@@ -148,7 -150,9 +148,7 @@@ struct VirtualSiteTopolog
           * \param[in] v Value for distance.
           */
          VirtualSiteBond(const std::string& a1, const std::string& a2, real v) :
 -            atom1(a1),
 -            atom2(a2),
 -            value(v)
 +            atom1(a1), atom2(a2), value(v)
          {
          }
          //! Atom 1 in bond.
           * \param[in] v Value for angle.
           */
          VirtualSiteAngle(const std::string& a1, const std::string& a2, const std::string& a3, real v) :
 -            atom1(a1),
 -            atom2(a2),
 -            atom3(a3),
 -            value(v)
 +            atom1(a1), atom2(a2), atom3(a3), value(v)
          {
          }
          //! Atom 1 in angle.
@@@ -265,7 -272,7 +265,7 @@@ static void read_vsite_database(const c
          {
              if (pline[0] == OPENDIR)
              {
 -                strncpy(dirstr, pline + 1, STRLEN - 2);
 +                strncpy(dirstr, pline + 1, STRLEN - 1);
                  if ((ch = strchr(dirstr, CLOSEDIR)) != nullptr)
                  {
                      (*ch) = 0;
                          else if (numberOfSites == 4)
                          {
                              /* angle */
 -                            vsitetoplist->back().angle.emplace_back(s1String, s2String, s3String,
 -                                                                    strtod(s4, nullptr));
 +                            vsitetoplist->back().angle.emplace_back(
 +                                    s1String, s2String, s3String, strtod(s4, nullptr));
                              /* angle */
                          }
                          else
                          {
                              gmx_fatal(FARGS,
                                        "Need 3 or 4 values to specify bond/angle values in %s: %s\n",
 -                                      ddbname, pline);
 +                                      ddbname,
 +                                      pline);
                          }
                      }
                      break;
                      default:
                          gmx_fatal(FARGS,
-                                   "Didnt find a case for directive %s in read_vsite_database\n",
 -                                  "Didn't find a case for directive %s in read_vsite_database\n", dirstr);
++                                  "Didn't find a case for directive %s in read_vsite_database\n",
 +                                  dirstr);
                  }
              }
          }
@@@ -445,11 -450,8 +445,11 @@@ static real get_ddb_bond(gmx::ArrayRef<
              });
      if (foundBond == found->bond.end())
      {
 -        gmx_fatal(FARGS, "Couldnt find bond %s-%s for residue %s in vsite database.\n",
 -                  atom1.c_str(), atom2.c_str(), res.c_str());
 +        gmx_fatal(FARGS,
 +                  "Couldnt find bond %s-%s for residue %s in vsite database.\n",
 +                  atom1.c_str(),
 +                  atom2.c_str(),
 +                  res.c_str());
      }
  
      return foundBond->value;
@@@ -480,12 -482,8 +480,12 @@@ static real get_ddb_angle(gmx::ArrayRef
  
      if (foundAngle == found->angle.end())
      {
 -        gmx_fatal(FARGS, "Couldnt find angle %s-%s-%s for residue %s in vsite database.\n",
 -                  atom1.c_str(), atom2.c_str(), atom3.c_str(), res.c_str());
 +        gmx_fatal(FARGS,
 +                  "Couldnt find angle %s-%s-%s for residue %s in vsite database.\n",
 +                  atom1.c_str(),
 +                  atom2.c_str(),
 +                  atom3.c_str(),
 +                  res.c_str());
      }
  
      return foundAngle->value;
@@@ -507,8 -505,7 +507,8 @@@ static void count_bonds(in
      /* find heavy atom bound to this hydrogen */
      heavy = NOTSET;
      for (auto parm = psb->interactionTypes.begin();
 -         (parm != psb->interactionTypes.end()) && (heavy == NOTSET); parm++)
 +         (parm != psb->interactionTypes.end()) && (heavy == NOTSET);
 +         parm++)
      {
          if (parm->ai() == atom)
          {
@@@ -578,10 -575,7 +578,10 @@@ print_bonds(FILE* fp, int o2n[], int nr
      fprintf(fp, "\n");
  }
  
 -static int get_atype(int atom, t_atoms* at, gmx::ArrayRef<const PreprocessResidue> rtpFFDB, ResidueType* rt)
 +static int get_atype(int                                    atom,
 +                     t_atoms*                               at,
 +                     gmx::ArrayRef<const PreprocessResidue> rtpFFDB,
 +                     const ResidueTypeMap&                  residueTypeMap)
  {
      int  type;
      bool bNterm;
      {
          /* get type from rtpFFDB */
          auto localPpResidue = getDatabaseEntry(*(at->resinfo[at->atom[atom].resind].name), rtpFFDB);
 -        bNterm = rt->namedResidueHasType(*(at->resinfo[at->atom[atom].resind].name), "Protein")
 +        bNterm              = namedResidueHasType(
 +                         residueTypeMap, *(at->resinfo[at->atom[atom].resind].name), "Protein")
                   && (at->atom[atom].resind == 0);
          int j = search_jtype(*localPpResidue, *(at->atomname[atom]), bNterm);
          type  = localPpResidue->atom[j].type;
  
  static int vsite_nm2type(const char* name, PreprocessingAtomTypes* atype)
  {
 -    int tp;
 -
 -    tp = atype->atomTypeFromName(name);
 -    if (tp == NOTSET)
 +    auto tp = atype->atomTypeFromName(name);
 +    if (!tp.has_value())
      {
          gmx_fatal(FARGS, "Dummy mass type (%s) not found in atom type database", name);
      }
  
 -    return tp;
 +    return *tp;
  }
  
 -static real get_amass(int atom, t_atoms* at, gmx::ArrayRef<const PreprocessResidue> rtpFFDB, ResidueType* rt)
 +static real get_amass(int                                    atom,
 +                      t_atoms*                               at,
 +                      gmx::ArrayRef<const PreprocessResidue> rtpFFDB,
 +                      const ResidueTypeMap&                  residueTypeMap)
  {
      real mass;
      bool bNterm;
      {
          /* get mass from rtpFFDB */
          auto localPpResidue = getDatabaseEntry(*(at->resinfo[at->atom[atom].resind].name), rtpFFDB);
 -        bNterm = rt->namedResidueHasType(*(at->resinfo[at->atom[atom].resind].name), "Protein")
 +        bNterm              = namedResidueHasType(
 +                         residueTypeMap, *(at->resinfo[at->atom[atom].resind].name), "Protein")
                   && (at->atom[atom].resind == 0);
          int j = search_jtype(*localPpResidue, *(at->atomname[atom]), bNterm);
          mass  = localPpResidue->atom[j].m;
@@@ -677,8 -668,7 +677,8 @@@ static void add_vsites(gmx::ArrayRef<In
                  gmx_fatal(FARGS,
                            "cannot make constraint in add_vsites for %d heavy "
                            "atoms and %d hydrogen atoms",
 -                          nrheavies, nrHatoms);
 +                          nrheavies,
 +                          nrHatoms);
              }
              my_add_param(&(plist[F_CONSTRNC]), Hatoms[i], heavies[0], NOTSET);
          }
                  case F_VSITE3OUT:
                      if (nrheavies < 2)
                      {
 -                        gmx_fatal(FARGS, "Not enough heavy atoms (%d) for %s (min 3)",
 -                                  nrheavies + 1, interaction_function[vsite_type[Hatoms[i]]].name);
 +                        gmx_fatal(FARGS,
 +                                  "Not enough heavy atoms (%d) for %s (min 3)",
 +                                  nrheavies + 1,
 +                                  interaction_function[vsite_type[Hatoms[i]]].name);
                      }
                      add_vsite3_atoms(&plist[ftype], Hatoms[i], Heavy, heavies[0], heavies[1], bSwapParity);
                      break;
                  case F_VSITE4FDN:
                      if (nrheavies < 3)
                      {
 -                        gmx_fatal(FARGS, "Not enough heavy atoms (%d) for %s (min 4)",
 -                                  nrheavies + 1, interaction_function[vsite_type[Hatoms[i]]].name);
 +                        gmx_fatal(FARGS,
 +                                  "Not enough heavy atoms (%d) for %s (min 4)",
 +                                  nrheavies + 1,
 +                                  interaction_function[vsite_type[Hatoms[i]]].name);
                      }
                      add_vsite4_atoms(&plist[ftype], Hatoms[0], Heavy, heavies[0], heavies[1], heavies[2]);
                      break;
  
                  default:
 -                    gmx_fatal(FARGS, "can't use add_vsites for interaction function %s",
 +                    gmx_fatal(FARGS,
 +                              "can't use add_vsites for interaction function %s",
                                interaction_function[vsite_type[Hatoms[i]]].name);
              } /* switch ftype */
          }     /* else */
      }         /* for i */
  }
  
 -#define ANGLE_6RING (DEG2RAD * 120)
 +#define ANGLE_6RING (gmx::c_deg2Rad * 120)
  
  /* cosine rule: a^2 = b^2 + c^2 - 2 b c cos(alpha) */
  /* get a^2 when a, b and alpha are given: */
@@@ -1019,21 -1004,21 +1019,21 @@@ static int gen_vsites_trp(Preprocessing
      b_CE3_HE3 = get_ddb_bond(vsitetop, "TRP", "CE3", "HE3");
      b_CZ3_HZ3 = get_ddb_bond(vsitetop, "TRP", "CZ3", "HZ3");
  
 -    a_NE1_CE2_CD2 = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "NE1", "CE2", "CD2");
 -    a_CE2_CD2_CG  = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "CE2", "CD2", "CG");
 -    a_CB_CG_CD2   = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "CB", "CG", "CD2");
 -    a_CD2_CG_CD1  = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "CD2", "CG", "CD1");
 -
 -    a_CE2_CD2_CE3 = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "CE2", "CD2", "CE3");
 -    a_CD2_CE2_CZ2 = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "CD2", "CE2", "CZ2");
 -    a_CD2_CE3_CZ3 = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "CD2", "CE3", "CZ3");
 -    a_CE3_CZ3_HZ3 = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "CE3", "CZ3", "HZ3");
 -    a_CZ2_CH2_HH2 = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "CZ2", "CH2", "HH2");
 -    a_CE2_CZ2_HZ2 = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "CE2", "CZ2", "HZ2");
 -    a_CE2_CZ2_CH2 = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "CE2", "CZ2", "CH2");
 -    a_CG_CD1_HD1  = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "CG", "CD1", "HD1");
 -    a_HE1_NE1_CE2 = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "HE1", "NE1", "CE2");
 -    a_CD2_CE3_HE3 = DEG2RAD * get_ddb_angle(vsitetop, "TRP", "CD2", "CE3", "HE3");
 +    a_NE1_CE2_CD2 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "NE1", "CE2", "CD2");
 +    a_CE2_CD2_CG  = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "CE2", "CD2", "CG");
 +    a_CB_CG_CD2   = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "CB", "CG", "CD2");
 +    a_CD2_CG_CD1  = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "CD2", "CG", "CD1");
 +
 +    a_CE2_CD2_CE3 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "CE2", "CD2", "CE3");
 +    a_CD2_CE2_CZ2 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "CD2", "CE2", "CZ2");
 +    a_CD2_CE3_CZ3 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "CD2", "CE3", "CZ3");
 +    a_CE3_CZ3_HZ3 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "CE3", "CZ3", "HZ3");
 +    a_CZ2_CH2_HH2 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "CZ2", "CH2", "HH2");
 +    a_CE2_CZ2_HZ2 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "CE2", "CZ2", "HZ2");
 +    a_CE2_CZ2_CH2 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "CE2", "CZ2", "CH2");
 +    a_CG_CD1_HD1  = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "CG", "CD1", "HD1");
 +    a_HE1_NE1_CE2 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "HE1", "NE1", "CE2");
 +    a_CD2_CE3_HE3 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TRP", "CD2", "CE3", "HE3");
  
      /* Calculate local coordinates.
       * y-axis (x=0) is the bond CD2-CE2.
       */
      rvec_sub(x[ats[atCB]], x[ats[atCG]], r_ij);
      rvec_sub(x[ats[atCD2]], x[ats[atCG]], r_ik);
 -    calc_vsite3_param(xcom[0], ycom[0], xi[atCG], yi[atCG], xi[atCB], yi[atCB], xi[atCD2],
 -                      yi[atCD2], &a, &b);
 +    calc_vsite3_param(
 +            xcom[0], ycom[0], xi[atCG], yi[atCG], xi[atCB], yi[atCB], xi[atCD2], yi[atCD2], &a, &b);
      svmul(a, r_ij, t1);
      svmul(b, r_ik, t2);
      rvec_add(t1, t2, t1);
      rvec_add(t1, x[ats[atCG]], (*newx)[atM[0]]);
  
 -    calc_vsite3_param(xcom[1], ycom[1], xi[atCG], yi[atCG], xi[atCB], yi[atCB], xi[atCD2],
 -                      yi[atCD2], &a, &b);
 +    calc_vsite3_param(
 +            xcom[1], ycom[1], xi[atCG], yi[atCG], xi[atCB], yi[atCB], xi[atCD2], yi[atCD2], &a, &b);
      svmul(a, r_ij, t1);
      svmul(b, r_ik, t2);
      rvec_add(t1, t2, t1);
          (*newatom)[atM[j]].m = (*newatom)[atM[j]].mB = mM[j];
          (*newatom)[atM[j]].q = (*newatom)[atM[j]].qB = 0.0;
          (*newatom)[atM[j]].type = (*newatom)[atM[j]].typeB = tpM;
 -        (*newatom)[atM[j]].ptype                           = eptAtom;
 +        (*newatom)[atM[j]].ptype                           = ParticleType::Atom;
          (*newatom)[atM[j]].resind                          = at->atom[i0].resind;
          (*newatom)[atM[j]].elem[0]                         = 'M';
          (*newatom)[atM[j]].elem[1]                         = '\0';
      {
          if ((*vsite_type)[ats[i]] == F_VSITE3)
          {
 -            calc_vsite3_param(xi[i], yi[i], xcom[0], ycom[0], xcom[1], ycom[1], xi[atCB], yi[atCB],
 -                              &a, &b);
 -            add_vsite3_param(&plist[F_VSITE3], ats[i], add_shift + atM[0], add_shift + atM[1],
 -                             ats[atCB], a, b);
 +            calc_vsite3_param(
 +                    xi[i], yi[i], xcom[0], ycom[0], xcom[1], ycom[1], xi[atCB], yi[atCB], &a, &b);
 +            add_vsite3_param(
 +                    &plist[F_VSITE3], ats[i], add_shift + atM[0], add_shift + atM[1], ats[atCB], a, b);
          }
      }
      return nvsite;
@@@ -1291,7 -1276,7 +1291,7 @@@ static int gen_vsites_tyr(Preprocessing
      bond_ch   = get_ddb_bond(vsitetop, "TYR", "CD1", "HD1");
      bond_co   = get_ddb_bond(vsitetop, "TYR", "CZ", "OH");
      bond_oh   = get_ddb_bond(vsitetop, "TYR", "OH", "HH");
 -    angle_coh = DEG2RAD * get_ddb_angle(vsitetop, "TYR", "CZ", "OH", "HH");
 +    angle_coh = gmx::c_deg2Rad * get_ddb_angle(vsitetop, "TYR", "CZ", "OH", "HH");
  
      xi[atCG]  = -bond_cc + bond_cc * std::cos(ANGLE_6RING);
      xi[atCD1] = -bond_cc;
      (*newatom)[atM].m = (*newatom)[atM].mB = mM;
      (*newatom)[atM].q = (*newatom)[atM].qB = 0.0;
      (*newatom)[atM].type = (*newatom)[atM].typeB = tpM;
 -    (*newatom)[atM].ptype                        = eptAtom;
 +    (*newatom)[atM].ptype                        = ParticleType::Atom;
      (*newatom)[atM].resind                       = at->atom[i0].resind;
      (*newatom)[atM].elem[0]                      = 'M';
      (*newatom)[atM].elem[1]                      = '\0';
@@@ -1471,30 -1456,30 +1471,30 @@@ static int gen_vsites_his(t_atoms
      b_CE1_NE2     = get_ddb_bond(vsitetop, resname, "CE1", "NE2");
      b_CG_CD2      = get_ddb_bond(vsitetop, resname, "CG", "CD2");
      b_CD2_NE2     = get_ddb_bond(vsitetop, resname, "CD2", "NE2");
 -    a_CG_ND1_CE1  = DEG2RAD * get_ddb_angle(vsitetop, resname, "CG", "ND1", "CE1");
 -    a_CG_CD2_NE2  = DEG2RAD * get_ddb_angle(vsitetop, resname, "CG", "CD2", "NE2");
 -    a_ND1_CE1_NE2 = DEG2RAD * get_ddb_angle(vsitetop, resname, "ND1", "CE1", "NE2");
 -    a_CE1_NE2_CD2 = DEG2RAD * get_ddb_angle(vsitetop, resname, "CE1", "NE2", "CD2");
 +    a_CG_ND1_CE1  = gmx::c_deg2Rad * get_ddb_angle(vsitetop, resname, "CG", "ND1", "CE1");
 +    a_CG_CD2_NE2  = gmx::c_deg2Rad * get_ddb_angle(vsitetop, resname, "CG", "CD2", "NE2");
 +    a_ND1_CE1_NE2 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, resname, "ND1", "CE1", "NE2");
 +    a_CE1_NE2_CD2 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, resname, "CE1", "NE2", "CD2");
  
      if (ats[atHD1] != NOTSET)
      {
          b_ND1_HD1     = get_ddb_bond(vsitetop, resname, "ND1", "HD1");
 -        a_CE1_ND1_HD1 = DEG2RAD * get_ddb_angle(vsitetop, resname, "CE1", "ND1", "HD1");
 +        a_CE1_ND1_HD1 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, resname, "CE1", "ND1", "HD1");
      }
      if (ats[atHE2] != NOTSET)
      {
          b_NE2_HE2     = get_ddb_bond(vsitetop, resname, "NE2", "HE2");
 -        a_CE1_NE2_HE2 = DEG2RAD * get_ddb_angle(vsitetop, resname, "CE1", "NE2", "HE2");
 +        a_CE1_NE2_HE2 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, resname, "CE1", "NE2", "HE2");
      }
      if (ats[atHD2] != NOTSET)
      {
          b_CD2_HD2     = get_ddb_bond(vsitetop, resname, "CD2", "HD2");
 -        a_NE2_CD2_HD2 = DEG2RAD * get_ddb_angle(vsitetop, resname, "NE2", "CD2", "HD2");
 +        a_NE2_CD2_HD2 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, resname, "NE2", "CD2", "HD2");
      }
      if (ats[atHE1] != NOTSET)
      {
          b_CE1_HE1     = get_ddb_bond(vsitetop, resname, "CE1", "HE1");
 -        a_NE2_CE1_HE1 = DEG2RAD * get_ddb_angle(vsitetop, resname, "NE2", "CE1", "HE1");
 +        a_NE2_CE1_HE1 = gmx::c_deg2Rad * get_ddb_angle(vsitetop, resname, "NE2", "CE1", "HE1");
      }
  
      /* constraints between CG, CE1 and NE1 */
      /* HE1 */
      if (ats[atHE1] != NOTSET)
      {
 -        calc_vsite3_param(x[atHE1], y[atHE1], x[atCE1], y[atCE1], x[atNE2], y[atNE2], x[atCG],
 -                          y[atCG], &a, &b);
 +        calc_vsite3_param(
 +                x[atHE1], y[atHE1], x[atCE1], y[atCE1], x[atNE2], y[atNE2], x[atCG], y[atCG], &a, &b);
          add_vsite3_param(&plist[F_VSITE3], ats[atHE1], ats[atCE1], ats[atNE2], ats[atCG], a, b);
      }
      /* HE2 */
      if (ats[atHE2] != NOTSET)
      {
 -        calc_vsite3_param(x[atHE2], y[atHE2], x[atNE2], y[atNE2], x[atCE1], y[atCE1], x[atCG],
 -                          y[atCG], &a, &b);
 +        calc_vsite3_param(
 +                x[atHE2], y[atHE2], x[atNE2], y[atNE2], x[atCE1], y[atCE1], x[atCG], y[atCG], &a, &b);
          add_vsite3_param(&plist[F_VSITE3], ats[atHE2], ats[atNE2], ats[atCE1], ats[atCG], a, b);
      }
  
      /* ND1 */
 -    calc_vsite3_param(x[atND1], y[atND1], x[atNE2], y[atNE2], x[atCE1], y[atCE1], x[atCG], y[atCG],
 -                      &a, &b);
 +    calc_vsite3_param(
 +            x[atND1], y[atND1], x[atNE2], y[atNE2], x[atCE1], y[atCE1], x[atCG], y[atCG], &a, &b);
      add_vsite3_param(&plist[F_VSITE3], ats[atND1], ats[atNE2], ats[atCE1], ats[atCG], a, b);
  
      /* CD2 */
 -    calc_vsite3_param(x[atCD2], y[atCD2], x[atCE1], y[atCE1], x[atNE2], y[atNE2], x[atCG], y[atCG],
 -                      &a, &b);
 +    calc_vsite3_param(
 +            x[atCD2], y[atCD2], x[atCE1], y[atCE1], x[atNE2], y[atNE2], x[atCG], y[atCG], &a, &b);
      add_vsite3_param(&plist[F_VSITE3], ats[atCD2], ats[atCE1], ats[atNE2], ats[atCG], a, b);
  
      /* HD1 */
      if (ats[atHD1] != NOTSET)
      {
 -        calc_vsite3_param(x[atHD1], y[atHD1], x[atNE2], y[atNE2], x[atCE1], y[atCE1], x[atCG],
 -                          y[atCG], &a, &b);
 +        calc_vsite3_param(
 +                x[atHD1], y[atHD1], x[atNE2], y[atNE2], x[atCE1], y[atCE1], x[atCG], y[atCG], &a, &b);
          add_vsite3_param(&plist[F_VSITE3], ats[atHD1], ats[atNE2], ats[atCE1], ats[atCG], a, b);
      }
      /* HD2 */
      if (ats[atHD2] != NOTSET)
      {
 -        calc_vsite3_param(x[atHD2], y[atHD2], x[atCE1], y[atCE1], x[atNE2], y[atNE2], x[atCG],
 -                          y[atCG], &a, &b);
 +        calc_vsite3_param(
 +                x[atHD2], y[atHD2], x[atCE1], y[atCE1], x[atNE2], y[atNE2], x[atCG], y[atCG], &a, &b);
          add_vsite3_param(&plist[F_VSITE3], ats[atHD2], ats[atCE1], ats[atNE2], ats[atCG], a, b);
      }
      return nvsite;
@@@ -1656,7 -1641,7 +1656,7 @@@ static bool is_vsite(int vsite_type
      }
  }
  
 -static char atomnamesuffix[] = "1234";
 +static const char atomnamesuffix[] = "1234";
  
  void do_vsites(gmx::ArrayRef<const PreprocessResidue> rtpFFDB,
                 PreprocessingAtomTypes*                atype,
      /* the atnms for every residue MUST correspond to the enums in the
         gen_vsites_* (one for each residue) routines! */
      /* also the atom names in atnms MUST be in the same order as in the .rtp! */
 -    const char* atnms[resNR][MAXATOMSPERRESIDUE + 1] = {
 -        { "CG", /* PHE */
 -          "CD1", "HD1", "CD2", "HD2", "CE1", "HE1", "CE2", "HE2", "CZ", "HZ", nullptr },
 -        { "CB", /* TRP */
 -          "CG", "CD1", "HD1", "CD2", "NE1", "HE1", "CE2", "CE3", "HE3", "CZ2", "HZ2", "CZ3", "HZ3",
 -          "CH2", "HH2", nullptr },
 -        { "CG", /* TYR */
 -          "CD1", "HD1", "CD2", "HD2", "CE1", "HE1", "CE2", "HE2", "CZ", "OH", "HH", nullptr },
 -        { "CG", /* HIS */
 -          "ND1", "HD1", "CD2", "HD2", "CE1", "HE1", "NE2", "HE2", nullptr }
 -    };
 +    const char* atnms[resNR][MAXATOMSPERRESIDUE + 1] = { { "CG", /* PHE */
 +                                                           "CD1",
 +                                                           "HD1",
 +                                                           "CD2",
 +                                                           "HD2",
 +                                                           "CE1",
 +                                                           "HE1",
 +                                                           "CE2",
 +                                                           "HE2",
 +                                                           "CZ",
 +                                                           "HZ",
 +                                                           nullptr },
 +                                                         { "CB", /* TRP */
 +                                                           "CG",
 +                                                           "CD1",
 +                                                           "HD1",
 +                                                           "CD2",
 +                                                           "NE1",
 +                                                           "HE1",
 +                                                           "CE2",
 +                                                           "CE3",
 +                                                           "HE3",
 +                                                           "CZ2",
 +                                                           "HZ2",
 +                                                           "CZ3",
 +                                                           "HZ3",
 +                                                           "CH2",
 +                                                           "HH2",
 +                                                           nullptr },
 +                                                         { "CG", /* TYR */
 +                                                           "CD1",
 +                                                           "HD1",
 +                                                           "CD2",
 +                                                           "HD2",
 +                                                           "CE1",
 +                                                           "HE1",
 +                                                           "CE2",
 +                                                           "HE2",
 +                                                           "CZ",
 +                                                           "OH",
 +                                                           "HH",
 +                                                           nullptr },
 +                                                         { "CG", /* HIS */
 +                                                           "ND1",
 +                                                           "HD1",
 +                                                           "CD2",
 +                                                           "HD2",
 +                                                           "CE1",
 +                                                           "HE1",
 +                                                           "NE2",
 +                                                           "HE2",
 +                                                           nullptr } };
  
      if (debug)
      {
      /* make index to tell which residues were already processed */
      std::vector<bool> bResProcessed(at->nres);
  
 -    ResidueType rt;
 +    ResidueTypeMap residueTypeMap = residueTypeMapFromLibraryFile("residuetypes.dat");
  
      /* generate vsite constructions */
      /* loop over all atoms */
           * N-terminus that must be treated first.
           */
          if (bVsiteAromatics && (strcmp(*(at->atomname[i]), "CA") == 0) && !bResProcessed[resind]
 -            && rt.namedResidueHasType(*(at->resinfo[resind].name), "Protein"))
 +            && namedResidueHasType(residueTypeMap, *(at->resinfo[resind].name), "Protein"))
          {
              /* mark this residue */
              bResProcessed[resind] = TRUE;
                                    "not enough atoms found (%d, need %d) in "
                                    "residue %s %d while\n             "
                                    "generating aromatics virtual site construction",
 -                                  nrfound, needed, resnm, at->resinfo[resind].nr);
 +                                  nrfound,
 +                                  needed,
 +                                  resnm,
 +                                  at->resinfo[resind].nr);
                      }
                      /* Advance overall atom counter */
                      i++;
                      {
                          fprintf(stderr, "TRP at %d\n", o2n[ats[0]] + 1);
                      }
 -                    nvsite += gen_vsites_trp(atype, &newx, &newatom, &newatomname, &o2n,
 -                                             &newvsite_type, &newcgnr, symtab, &nadd, *x, cgnr, at,
 -                                             vsite_type, plist, nrfound, ats, add_shift, vsitetop);
 +                    nvsite += gen_vsites_trp(atype,
 +                                             &newx,
 +                                             &newatom,
 +                                             &newatomname,
 +                                             &o2n,
 +                                             &newvsite_type,
 +                                             &newcgnr,
 +                                             symtab,
 +                                             &nadd,
 +                                             *x,
 +                                             cgnr,
 +                                             at,
 +                                             vsite_type,
 +                                             plist,
 +                                             nrfound,
 +                                             ats,
 +                                             add_shift,
 +                                             vsitetop);
                      break;
                  case resTYR:
                      if (debug)
                      {
                          fprintf(stderr, "TYR at %d\n", o2n[ats[0]] + 1);
                      }
 -                    nvsite += gen_vsites_tyr(atype, &newx, &newatom, &newatomname, &o2n,
 -                                             &newvsite_type, &newcgnr, symtab, &nadd, *x, cgnr, at,
 -                                             vsite_type, plist, nrfound, ats, add_shift, vsitetop);
 +                    nvsite += gen_vsites_tyr(atype,
 +                                             &newx,
 +                                             &newatom,
 +                                             &newatomname,
 +                                             &o2n,
 +                                             &newvsite_type,
 +                                             &newcgnr,
 +                                             symtab,
 +                                             &nadd,
 +                                             *x,
 +                                             cgnr,
 +                                             at,
 +                                             vsite_type,
 +                                             plist,
 +                                             nrfound,
 +                                             ats,
 +                                             add_shift,
 +                                             vsitetop);
                      break;
                  case resHIS:
                      if (debug)
          {
              /* find heavy atom, count #bonds from it and #H atoms bound to it
                 and return H atom numbers (Hatoms) and heavy atom numbers (heavies) */
 -            count_bonds(i, &plist[F_BONDS], at->atomname, &nrbonds, &nrHatoms, Hatoms, &Heavy,
 -                        &nrheavies, heavies);
 +            count_bonds(i, &plist[F_BONDS], at->atomname, &nrbonds, &nrHatoms, Hatoms, &Heavy, &nrheavies, heavies);
              /* get Heavy atom type */
 -            tpHeavy = get_atype(Heavy, at, rtpFFDB, &rt);
 -            strcpy(tpname, atype->atomNameFromAtomType(tpHeavy));
 +            tpHeavy = get_atype(Heavy, at, rtpFFDB, residueTypeMap);
 +            strcpy(tpname, *atype->atomNameFromAtomType(tpHeavy));
  
              bWARNING       = FALSE;
              bAddVsiteParam = TRUE;
                      /* get dummy mass type from first char of heavy atom type (N or C) */
  
                      strcpy(nexttpname,
 -                           atype->atomNameFromAtomType(get_atype(heavies[0], at, rtpFFDB, &rt)));
 +                           *atype->atomNameFromAtomType(get_atype(heavies[0], at, rtpFFDB, residueTypeMap)));
                      std::string ch = get_dummymass_name(vsiteconflist, tpname, nexttpname);
                      std::string name;
                      if (ch.empty())
                                      FARGS,
                                      "Can't find dummy mass for type %s bonded to type %s in the "
                                      "virtual site database (.vsd files). Add it to the database!\n",
 -                                    tpname, nexttpname);
 +                                    tpname,
 +                                    nexttpname);
                          }
                          else
                          {
                              gmx_fatal(FARGS,
                                        "A dummy mass for type %s bonded to type %s is required, but "
                                        "no virtual site database (.vsd) files where found.\n",
 -                                      tpname, nexttpname);
 +                                      tpname,
 +                                      nexttpname);
                          }
                      }
                      else
                      /* get atom masses, and set Heavy and Hatoms mass to zero */
                      for (int j = 0; j < nrHatoms; j++)
                      {
 -                        mHtot += get_amass(Hatoms[j], at, rtpFFDB, &rt);
 +                        mHtot += get_amass(Hatoms[j], at, rtpFFDB, residueTypeMap);
                          at->atom[Hatoms[j]].m = at->atom[Hatoms[j]].mB = 0;
                      }
 -                    mtot              = mHtot + get_amass(Heavy, at, rtpFFDB, &rt);
 +                    mtot              = mHtot + get_amass(Heavy, at, rtpFFDB, residueTypeMap);
                      at->atom[Heavy].m = at->atom[Heavy].mB = 0;
                      if (mHmult != 1.0)
                      {
                          newatom[ni0 + j].m = newatom[ni0 + j].mB = mtot / NMASS;
                          newatom[ni0 + j].q = newatom[ni0 + j].qB = 0.0;
                          newatom[ni0 + j].type = newatom[ni0 + j].typeB = tpM;
 -                        newatom[ni0 + j].ptype                         = eptAtom;
 +                        newatom[ni0 + j].ptype                         = ParticleType::Atom;
                          newatom[ni0 + j].resind                        = at->atom[i0].resind;
                          newatom[ni0 + j].elem[0]                       = 'M';
                          newatom[ni0 + j].elem[1]                       = '\0';
  
                      /* generate Heavy, H1, H2 and H3 from M1, M2 and heavies[0] */
                      /* note that vsite_type cannot be NOTSET, because we just set it */
 -                    add_vsite3_atoms(&plist[(*vsite_type)[Heavy]], Heavy, heavies[0],
 -                                     add_shift + ni0, add_shift + ni0 + 1, FALSE);
 +                    add_vsite3_atoms(&plist[(*vsite_type)[Heavy]],
 +                                     Heavy,
 +                                     heavies[0],
 +                                     add_shift + ni0,
 +                                     add_shift + ni0 + 1,
 +                                     FALSE);
                      for (int j = 0; j < nrHatoms; j++)
                      {
 -                        add_vsite3_atoms(&plist[(*vsite_type)[Hatoms[j]]], Hatoms[j], heavies[0],
 -                                         add_shift + ni0, add_shift + ni0 + 1, Hat_SwapParity[j]);
 +                        add_vsite3_atoms(&plist[(*vsite_type)[Hatoms[j]]],
 +                                         Hatoms[j],
 +                                         heavies[0],
 +                                         add_shift + ni0,
 +                                         add_shift + ni0 + 1,
 +                                         Hat_SwapParity[j]);
                      }
  #undef NMASS
                  }
                            "Cannot convert atom %d %s (bound to a heavy atom "
                            "%s with \n"
                            "         %d bonds and %d bound hydrogens atoms) to virtual site\n",
 -                          i + 1, *(at->atomname[i]), tpname, nrbonds, nrHatoms);
 +                          i + 1,
 +                          *(at->atomname[i]),
 +                          tpname,
 +                          nrbonds,
 +                          nrHatoms);
              }
              if (bAddVsiteParam)
              {
          fprintf(debug, "Before inserting new atoms:\n");
          for (int i = 0; i < at->nr; i++)
          {
 -            fprintf(debug, "%4d %4d %4s %4d %4s %6d %-10s\n", i + 1, o2n[i] + 1,
 -                    at->atomname[i] ? *(at->atomname[i]) : "(NULL)", at->resinfo[at->atom[i].resind].nr,
 +            fprintf(debug,
 +                    "%4d %4d %4s %4d %4s %6d %-10s\n",
 +                    i + 1,
 +                    o2n[i] + 1,
 +                    at->atomname[i] ? *(at->atomname[i]) : "(NULL)",
 +                    at->resinfo[at->atom[i].resind].nr,
                      at->resinfo[at->atom[i].resind].name ? *(at->resinfo[at->atom[i].resind].name) : "(NULL)",
                      (*cgnr)[i],
                      ((*vsite_type)[i] == NOTSET) ? "NOTSET" : interaction_function[(*vsite_type)[i]].name);
          {
              if (newatomname[i])
              {
 -                fprintf(debug, "%4d %4s %4d %6d %-10s\n", i + 1,
 -                        newatomname[i] ? *(newatomname[i]) : "(NULL)", newatom[i].resind, newcgnr[i],
 +                fprintf(debug,
 +                        "%4d %4s %4d %6d %-10s\n",
 +                        i + 1,
 +                        newatomname[i] ? *(newatomname[i]) : "(NULL)",
 +                        newatom[i].resind,
 +                        newcgnr[i],
                          (newvsite_type[i] == NOTSET) ? "NOTSET"
                                                       : interaction_function[newvsite_type[i]].name);
              }
          gmx_fatal(FARGS,
                    "Added impossible amount of dummy masses "
                    "(%d on a total of %d atoms)\n",
 -                  nadd, at->nr - nadd);
 +                  nadd,
 +                  at->nr - nadd);
      }
  
      if (debug)
          fprintf(debug, "After inserting new atoms:\n");
          for (int i = 0; i < at->nr; i++)
          {
 -            fprintf(debug, "%4d %4s %4d %4s %6d %-10s\n", i + 1,
 -                    at->atomname[i] ? *(at->atomname[i]) : "(NULL)", at->resinfo[at->atom[i].resind].nr,
 +            fprintf(debug,
 +                    "%4d %4s %4d %4s %6d %-10s\n",
 +                    i + 1,
 +                    at->atomname[i] ? *(at->atomname[i]) : "(NULL)",
 +                    at->resinfo[at->atom[i].resind].nr,
                      at->resinfo[at->atom[i].resind].name ? *(at->resinfo[at->atom[i].resind].name) : "(NULL)",
                      (*cgnr)[i],
                      ((*vsite_type)[i] == NOTSET) ? "NOTSET" : interaction_function[(*vsite_type)[i]].name);
@@@ -2393,8 -2279,7 +2393,8 @@@ void do_h_mass(InteractionsOfType* psb
              /* find bonded heavy atom */
              int a = NOTSET;
              for (auto parm = psb->interactionTypes.begin();
 -                 (parm != psb->interactionTypes.end()) && (a == NOTSET); parm++)
 +                 (parm != psb->interactionTypes.end()) && (a == NOTSET);
 +                 parm++)
              {
                  /* if other atom is not a virtual site, it is the one we want */
                  if ((parm->ai() == i) && !is_vsite(vsite_type[parm->aj()]))
index 6b8c64ea3abaff4a4932206e73bd6bb237bb96f2,05c87c37864a57f81d4bcf41f985736be02d1ac0..28b4184f14c761463635096a9dfe92cbcfd03e95
@@@ -46,7 -46,6 +46,7 @@@
  
  #include <algorithm>
  #include <memory>
 +#include <numeric>
  #include <string>
  
  #include "gromacs/applied_forces/awh/read_params.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/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
  
  using gmx::BasicVector;
  
  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];
 -    std::vector<std::string> pullGroupNames;
 -    std::vector<std::string> rotateGroupNames;
 +    char tcgrps[STRLEN], tau_t[STRLEN], ref_t[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()
@@@ -150,14 -147,12 +150,14 @@@ enu
                          * make a rest group for the remaining particles.    */
  };
  
 +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
  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* 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;
      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];
          }
          else if (simtemp->eSimTempScale
 -                 == esimtempGEOMETRIC) /* should give roughly equal acceptance for constant heat capacity . . . */
 +                 == 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)));
          }
 -        else if (simtemp->eSimTempScale == esimtempEXPONENTIAL)
 +        else if (simtemp->eSimTempScale == SimulatedTempering::Exponential)
          {
              simtemp->temperatures[i] = simtemp->simtemp_low
                                         + (simtemp->simtemp_high - simtemp->simtemp_low)
          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);
          }
      }
@@@ -215,20 -210,135 +215,20 @@@ static void check_nst(const char* desc_
      }
  }
  
 -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)
 -{
 -    if (*eintmod == eintmodPOTSHIFT_VERLET_UNSUPPORTED)
 -    {
 -        *eintmod = eintmodPOTSHIFT;
 -    }
 -}
 -
 -static void checkMtsRequirement(const t_inputrec& ir, const char* param, const int nstValue, warninp_t wi)
 +static void process_interaction_modifier(InteractionModifiers* eintmod)
  {
 -    GMX_RELEASE_ASSERT(ir.mtsLevels.size() >= 2, "Need at least two levels for MTS");
 -    const int mtsFactor = ir.mtsLevels.back().stepFactor;
 -    if (nstValue % mtsFactor != 0)
 +    if (*eintmod == InteractionModifiers::PotShiftVerletUnsupported)
      {
 -        auto message = gmx::formatString(
 -                "With MTS, %s = %d should be a multiple of mts-factor = %d", param, nstValue, mtsFactor);
 -        warning_error(wi, message.c_str());
 +        *eintmod = InteractionModifiers::PotShift;
      }
  }
  
 -static void setupMtsLevels(gmx::ArrayRef<gmx::MtsLevel> mtsLevels,
 -                           const t_inputrec&            ir,
 -                           const t_gromppopts&          opts,
 -                           warninp_t                    wi)
 -{
 -    /* MD-VV has no MTS support yet.
 -     * SD1 needs different scaling coefficients for the different MTS forces
 -     * and the different forces are currently not available in ForceBuffers.
 -     */
 -    if (ir.eI != eiMD)
 -    {
 -        auto message = gmx::formatString(
 -                "Multiple time stepping is only supported with integrator %s", ei_names[eiMD]);
 -        warning_error(wi, message.c_str());
 -    }
 -    if (opts.numMtsLevels != 2)
 -    {
 -        warning_error(wi, "Only mts-levels = 2 is supported");
 -    }
 -    else
 -    {
 -        const std::vector<std::string> inputForceGroups = gmx::splitString(opts.mtsLevel2Forces);
 -        auto&                          forceGroups      = mtsLevels[1].forceGroups;
 -        for (const auto& inputForceGroup : inputForceGroups)
 -        {
 -            bool found     = false;
 -            int  nameIndex = 0;
 -            for (const auto& forceGroupName : gmx::mtsForceGroupNames)
 -            {
 -                if (gmx::equalCaseInsensitive(inputForceGroup, forceGroupName))
 -                {
 -                    forceGroups.set(nameIndex);
 -                    found = true;
 -                }
 -                nameIndex++;
 -            }
 -            if (!found)
 -            {
 -                auto message =
 -                        gmx::formatString("Unknown MTS force group '%s'", inputForceGroup.c_str());
 -                warning_error(wi, message.c_str());
 -            }
 -        }
 -
 -        if (mtsLevels[1].stepFactor <= 1)
 -        {
 -            gmx_fatal(FARGS, "mts-factor should be larger than 1");
 -        }
 -
 -        // Make the level 0 use the complement of the force groups of group 1
 -        mtsLevels[0].forceGroups = ~mtsLevels[1].forceGroups;
 -        mtsLevels[0].stepFactor  = 1;
 -
 -        if ((EEL_FULL(ir.coulombtype) || EVDW_PME(ir.vdwtype))
 -            && !mtsLevels[1].forceGroups[static_cast<int>(gmx::MtsForceGroups::LongrangeNonbonded)])
 -        {
 -            warning_error(wi,
 -                          "With long-range electrostatics and/or LJ treatment, the long-range part "
 -                          "has to be part of the mts-level2-forces");
 -        }
 -
 -        if (ir.nstcalcenergy > 0)
 -        {
 -            checkMtsRequirement(ir, "nstcalcenergy", ir.nstcalcenergy, wi);
 -        }
 -        checkMtsRequirement(ir, "nstenergy", ir.nstenergy, wi);
 -        checkMtsRequirement(ir, "nstlog", ir.nstlog, wi);
 -        if (ir.efep != efepNO)
 -        {
 -            checkMtsRequirement(ir, "nstdhdl", ir.fepvals->nstdhdl, wi);
 -        }
 -
 -        if (ir.bPull)
 -        {
 -            const int pullMtsLevel = gmx::forceGroupMtsLevel(ir.mtsLevels, gmx::MtsForceGroups::Pull);
 -            if (ir.pull->nstxout % ir.mtsLevels[pullMtsLevel].stepFactor != 0)
 -            {
 -                warning_error(wi, "pull-nstxout should be a multiple of mts-factor");
 -            }
 -            if (ir.pull->nstfout % ir.mtsLevels[pullMtsLevel].stepFactor != 0)
 -            {
 -                warning_error(wi, "pull-nstfout should be a multiple of mts-factor");
 -            }
 -        }
 -    }
 -}
 -
 -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
      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 */
      {
          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");
      }
      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;
  
          {
              // 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,
              }
          }
  
 -        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);
 +                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",
 -                        evdw_names[ir->vdwtype], evdw_names[evdwCUT], eintmod_names[ir->vdw_modifier]);
 +                        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");
          }
 -        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");
          }
 -        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);
          }
  
      /* GENERAL INTEGRATOR STUFF */
      if (!EI_MD(ir->eI))
      {
 -        if (ir->etc != etcNO)
 +        if (ir->etc != TemperatureCoupling::No)
          {
              if (EI_RANDOM(ir->eI))
              {
                          "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]);
 +                        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]);
 +                        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]);
 +                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]);
 +                    enumValueToString(ir->epc),
 +                    enumValueToString(ir->eI));
              warning_note(wi, warn_buf);
          }
 -        ir->epc = epcNO;
 +        ir->epc = PressureCoupling::No;
      }
      if (EI_DYNAMICS(ir->eI))
      {
                   */
                  if (ir->nstlist > 0)
                  {
 -                    ir->nstcalcenergy = lcd(ir->nstenergy, ir->nstlist);
 +                    ir->nstcalcenergy = std::gcd(ir->nstenergy, ir->nstlist);
                  }
                  else
                  {
              }
          }
          else if ((ir->nstenergy > 0 && ir->nstcalcenergy > ir->nstenergy)
 -                 || (ir->efep != efepNO && ir->fepvals->nstdhdl > 0
 +                 || (ir->efep != FreeEnergyPerturbationType::No && ir->fepvals->nstdhdl > 0
                       && (ir->nstcalcenergy > ir->fepvals->nstdhdl)))
  
          {
              int         min_nst  = ir->nstenergy;
  
              /* find the smallest of ( nstenergy, nstdhdl ) */
 -            if (ir->efep != efepNO && ir->fepvals->nstdhdl > 0
 +            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)
              {
  
          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);
              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);
          }
  
 -        // 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.preProcessingNotifications_.notify(&energyCalculationFrequencyErrors);
 +        mdModulesNotifiers.preProcessingNotifier_.notify(&energyCalculationFrequencyErrors);
  
          // Emit all errors from the energy calculation frequency checks
          for (const std::string& energyFrequencyErrorMessage :
      }
  
      /* 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 "
          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 "
          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;
              }
          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]);
 +                    enumValueToString(ir->etc));
              warning_note(wi, warn_buf);
          }
  
          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);
 +                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",
 +        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",
 +        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;
 +        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);
  
                  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",
 +        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));
 +        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)
          {
              /* Clear output in case of no states:*/
 -            sprintf(err_buf, "init-lambda-state set to %d: no lambda states are defined.",
 -                    fep->init_fep_state);
 +            sprintf(err_buf, "init-lambda-state set to %d: no lambda states are defined.", fep->init_fep_state);
              CHECK((fep->init_fep_state >= 0) && (fep->n_lambda == 0));
          }
          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,
                  "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);
 +                fep->init_lambda,
 +                fep->init_fep_state);
          CHECK((fep->init_fep_state >= 0) && (fep->init_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])
                  {
              }
          }
  
 -        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));
              }
          }
                          "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]);
 +                        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[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))));
 +                      && (((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))));
              }
          }
  
                      "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);
 +                    fep->sc_r_power,
 +                    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 ((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));
 +                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));
 +                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));
 +                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));
 +                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));
 +                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));
 +                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));
 +                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));
 +                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));
          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);
 +                fep->init_fep_state,
 +                expand->lmc_forced_nstart);
          CHECK((fep->init_fep_state != 0) && (expand->lmc_forced_nstart > 0)
 -              && (expand->elmcmove != elmcmoveNO));
 +              && (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)",
 +        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));
  
          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 "
              {
                  sprintf(err_buf,
                          "nst-transition-matrix (%d) must be an integer multiple of nstlog (%d)",
 -                        expand->nstTij, ir->nstlog);
 +                        expand->nstTij,
 +                        ir->nstlog);
                  CHECK((expand->nstTij % ir->nstlog) != 0);
              }
          }
      {
          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",
 +            sprintf(err_buf,
 +                    "Can not have pressure coupling with pbc=%s",
                      c_pbcTypeNames[ir->pbcType].c_str());
 -            CHECK(ir->epc != epcNO);
 +            CHECK(ir->epc != PressureCoupling::No);
          }
          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",
 +        sprintf(err_buf,
 +                "Can not have dispersion correction with pbc=%s",
                  c_pbcTypeNames[ir->pbcType].c_str());
 -        CHECK(ir->eDispCorr != edispcNO);
 +        CHECK(ir->eDispCorr != DispersionCorrectionType::No);
      }
  
      if (ir->rlist == 0.0)
                  "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], c_pbcTypeNames[PbcType::No].c_str());
 -        CHECK(((ir->coulombtype != eelCUT) && (ir->coulombtype != eelUSER))
 +                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)
      {
          // 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)
          {
              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;
 +                         "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 "
          }
      }
  
 -    if (EI_STATE_VELOCITY(ir->eI) && !EI_SD(ir->eI) && ir->pbcType == PbcType::No && 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]);
 +                enumValueToString(IntegrationAlgorithm::SD1));
          warning_note(wi, warn_buf);
      }
  
      /* TEMPERATURE COUPLING */
 -    if (ir->etc == etcYES)
 +    if (ir->etc == TemperatureCoupling::Yes)
      {
 -        ir->etc = etcBERENDSEN;
 +        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)
          {
              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,
          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]);
 +                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]);
 +                    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));
 +                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));
 +                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 "
      }
  
      /* PRESSURE COUPLING */
 -    if (ir->epc == epcISOTROPIC)
 +    if (ir->epc == PressureCoupling::Isotropic)
      {
 -        ir->epc = epcBERENDSEN;
 +        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;
  
              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);
 +                    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));
 +                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 "
                      "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");
          }
      /* 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 "
                  "artifacts, advice: use coulombtype = %s",
 -                eel_names[ir->coulombtype], eel_names[eelRF_ZERO]);
 +                enumValueToString(ir->coulombtype),
 +                enumValueToString(CoulombInteractionType::RFZero));
          warning(wi, warn_buf);
      }
  
      {
          /* 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]);
 +                    enumValueToString(ir->coulombtype));
              warning(wi, warn_buf);
              ir->epsilon_rf = 0.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);
          }
      }
              sprintf(err_buf,
                      "With coulombtype = %s rcoulomb_switch must be < rcoulomb. Or, better: Use the "
                      "potential modifier options!",
 -                    eel_names[ir->coulombtype]);
 +                    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);
 +        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);
 +        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 "
          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)
          {
                      "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);
 +                    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)
          {
  
      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);
          }
      }
      {
          // 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",
 -                    c_pbcTypeNames[ir->pbcType].c_str(), 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->pbcType != PbcType::XY) && 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], c_pbcTypeNames[PbcType::XY].c_str());
 +        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))
                      "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);
 +                    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 "
                       "really want dispersion correction to -C6/r^6.");
      }
  
 -    if (ir->eI == eiLBFGS && (ir->coulombtype == eelCUT || ir->vdwtype == evdwCUT) && ir->rvdw != 0)
 +    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);
      }
  
      }
  
      // cosine acceleration is only supported in leap-frog
 -    if (ir->cos_accel != 0.0 && ir->eI != eiMD)
 +    if (ir->cos_accel != 0.0 && ir->eI != IntegrationAlgorithm::MD)
      {
          warning_error(wi, "cos-acceleration is only supported by integrator = md");
      }
     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();
  
 -    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&)
          {
 -            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)
          {
          }
      }
  
 -    for (i = 0; i < efptNR; i++)
 +    for (auto i : keysOf(nfep))
      {
          if (nfep[i] == 0)
          {
          }
          else if (nfep[i] == max_n_lambda)
          {
 -            if (i != efptTEMPERATURE) /* we treat this differently -- not really a reason to compute
 +            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;
              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);
 +                      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);
 +        fep->all_lambda[i].resize(fep->n_lambda);
          if (nfep[i] > 0) /* if it's zero, then the count_fep_lambda arrays
                              are zero */
          {
              {
                  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 */
  
 -    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;
          }
      }
  
      }
      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;
              }
         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];
              }
          }
      }
  
  
      /* 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 */
  
  static void do_simtemp_params(t_inputrec* ir)
  {
 -
 -    snew(ir->simtempvals->temperatures, ir->fepvals->n_lambda);
 -    GetSimTemps(ir->fepvals->n_lambda, ir->simtempvals, ir->fepvals->all_lambda[efptTEMPERATURE]);
 +    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>
@@@ -1722,8 -1717,7 +1722,8 @@@ void convertInts(warninp_t wi, gmx::Arr
              auto message = gmx::formatString(
                      "Invalid value for mdp option %s. %s should only consist of integers separated "
                      "by spaces.",
 -                    name, name);
 +                    name,
 +                    name);
              warning_error(wi, message);
          }
          ++i;
@@@ -1744,14 -1738,39 +1744,14 @@@ static void convertReals(warninp_t wi, 
              auto message = gmx::formatString(
                      "Invalid value for mdp option %s. %s should only consist of real numbers "
                      "separated by spaces.",
 -                    name, name);
 +                    name,
 +                    name);
              warning_error(wi, message);
          }
          ++i;
      }
  }
  
 -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)
 -    {
 -        try
 -        {
 -            outputs[i][d] = gmx::fromString<real>(input);
 -        }
 -        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);
 -            warning_error(wi, message);
 -        }
 -        ++d;
 -        if (d == DIM)
 -        {
 -            d = 0;
 -            ++i;
 -        }
 -    }
 -}
 -
  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;
          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,
 +            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");
              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,
 +                gmx_fatal(FARGS,
 +                          "Expected %d elements for wall-density, found %zu",
 +                          ir->nwall,
                            wallDensity.size());
              }
              convertReals(wi, wallDensity, "wall-density", ir->wall_density);
@@@ -1817,10 -1832,10 +1817,10 @@@ static void read_expandedparams(std::ve
  {
      /* 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->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);
 +            (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   = (get_eeenum(inp, "wl-oneovert", yesno_names, wi) != 0);
 +    expand->bWLoneovert   = (getEnum<Boolean>(inp, "wl-oneovert", wi) != Boolean::No);
  }
  
  /*! \brief Return whether an end state with the given coupling-lambda
@@@ -1904,8 -1919,8 +1904,8 @@@ void get_ir(const char*     mdparin
      double      dumdub[2][6];
      int         i, j, m;
      char        warn_buf[STRLEN];
 -    t_lambda*   fep    = ir->fepvals;
 -    t_expanded* expand = ir->expandedvals;
 +    t_lambda*   fep    = ir->fepvals.get();
 +    t_expanded* expand = ir->expandedvals.get();
  
      const char* no_names[] = { "no", nullptr };
  
      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);
              &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 = (get_eeenum(&inp, "mts", yesno_names, wi) != 0);
 +    ir->useMts = (getEnum<Boolean>(&inp, "mts", wi) != Boolean::No);
      if (ir->useMts)
      {
 -        opts->numMtsLevels = get_eint(&inp, "mts-levels", 2, wi);
 -        ir->mtsLevels.resize(2);
 -        gmx::MtsLevel& mtsLevel = ir->mtsLevels[1];
 -        opts->mtsLevel2Forces   = setStringEntry(&inp, "mts-level2-forces", "longrange-nonbonded");
 -        mtsLevel.stepFactor     = get_eint(&inp, "mts-level2-factor", 2, wi);
 +        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;
 -            ir->mtsLevels.clear();
          }
      }
      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);
      printStringNoNewline(&inp, "group(s) for center of mass motion removal");
      /* Neighbor searching */
      printStringNewline(&inp, "NEIGHBORSEARCHING PARAMETERS");
      printStringNoNewline(&inp, "cut-off scheme (Verlet: particle based cut-offs)");
 -    ir->cutoff_scheme = get_eeenum(&inp, "cutoff-scheme", ecutscheme_names, wi);
 +    ir->cutoff_scheme = getEnum<CutoffScheme>(&inp, "cutoff-scheme", wi);
      printStringNoNewline(&inp, "nblist update frequency");
      ir->nstlist = get_eint(&inp, "nstlist", 10, wi);
      printStringNoNewline(&inp, "Periodic boundary conditions: xyz, no, xy");
          pbcTypesNamesChar.push_back(pbcTypeName.c_str());
      }
      ir->pbcType       = static_cast<PbcType>(get_eeenum(&inp, "pbc", pbcTypesNamesChar.data(), wi));
 -    ir->bPeriodicMols = get_eeenum(&inp, "periodic-molecules", yesno_names, wi) != 0;
 +    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");
      /* 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);
 -    printStringNoNewline(&inp,
 -                         "Relative dielectric constant for the medium and the reaction field");
 +    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_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);
      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");
      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
      /* Coupling stuff */
      printStringNewline(&inp, "OPTIONS FOR WEAK COUPLING ALGORITHMS");
      printStringNoNewline(&inp, "Temperature coupling");
 -    ir->etc                = get_eeenum(&inp, "tcoupl", etcoupl_names, 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", inputrecStrings->tcgrps, nullptr);
      printStringNoNewline(&inp, "Time constant (ps) and reference temperature (K)");
      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->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);
      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);
 +    ir->bQMMM = (getEnum<Boolean>(&inp, "QMMM", wi) != Boolean::No);
      printStringNoNewline(&inp, "Groups treated with MiMiC");
      setStringEntry(&inp, "QMMM-grps", inputrecStrings->QMMM, nullptr);
  
  
      /* Startup run */
      printStringNewline(&inp, "GENERATE VELOCITIES FOR STARTUP RUN");
 -    opts->bGenVel = (get_eeenum(&inp, "gen-vel", yesno_names, wi) != 0);
 +    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);
  
      printStringNewline(&inp, "OPTIONS FOR BONDS");
      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);
 +    ir->bContinuation = (getEnum<Boolean>(&inp, "continuation", wi) != Boolean::No);
      printStringNoNewline(&inp,
                           "Use successive overrelaxation to reduce the number of shake iterations");
 -    ir->bShakeSOR = (get_eeenum(&inp, "Shake-SOR", yesno_names, wi) != 0);
 +    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");
      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, "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", inputrecStrings->wall_atomtype, nullptr);
      setStringEntry(&inp, "wall-density", inputrecStrings->wall_density, nullptr);
  
      /* 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)
      {
          ir->pull                        = std::make_unique<pull_params_t>();
          {
              for (int c = 0; c < ir->pull->ncoord; c++)
              {
 -                if (ir->pull->coord[c].eType == epullCONSTRAINT)
 +                if (ir->pull->coord[c].eType == PullingAlgorithm::Constraint)
                  {
                      warning_error(wi,
                                    "Constraint COM pulling is not supported in combination with "
      /* AWH biasing
         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)
      {
 -        ir->awhParams = gmx::readAwhParams(&inp, wi);
 +        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);
      /* Refinement */
      printStringNewline(&inp, "NMR refinement stuff");
      printStringNoNewline(&inp, "Distance restraints type: No, Simple or Ensemble");
 -    ir->eDisre = get_eeenum(&inp, "disre", edisre_names, wi);
 +    ir->eDisre = getEnum<DistanceRestraintRefinement>(&inp, "disre", 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->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->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_tau = get_ereal(&inp, "orire-tau", 0.0, wi);
  
      /* free energy variables */
      printStringNewline(&inp, "Free energy variables");
 -    ir->efep = get_eeenum(&inp, "free-energy", efep_names, wi);
 +    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
      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", inputrecStrings->fep_lambda[efptFEP], nullptr);
 -    setStringEntry(&inp, "mass-lambdas", inputrecStrings->fep_lambda[efptMASS], nullptr);
 -    setStringEntry(&inp, "coul-lambdas", inputrecStrings->fep_lambda[efptCOUL], nullptr);
 -    setStringEntry(&inp, "vdw-lambdas", inputrecStrings->fep_lambda[efptVDW], nullptr);
 -    setStringEntry(&inp, "bonded-lambdas", inputrecStrings->fep_lambda[efptBONDED], nullptr);
 -    setStringEntry(&inp, "restraint-lambdas", inputrecStrings->fep_lambda[efptRESTRAINT], nullptr);
 -    setStringEntry(&inp, "temperature-lambdas", inputrecStrings->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", inputrecStrings->lambda_weights, nullptr);
 -    fep->edHdLPrintEnergy   = get_eeenum(&inp, "dhdl-print-energy", edHdLPrintEnergy_names, wi);
 +    fep->edHdLPrintEnergy   = getEnum<FreeEnergyPrintEnergy>(&inp, "dhdl-print-energy", 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->bScCoul            = (getEnum<Boolean>(&inp, "sc-coul", wi) != Boolean::No);
      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->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", inputrecStrings->accgrps, nullptr);
 -    setStringEntry(&inp, "accelerate", inputrecStrings->acc, nullptr);
      setStringEntry(&inp, "freezegrps", inputrecStrings->freeze, nullptr);
      setStringEntry(&inp, "freezedim", inputrecStrings->frdim, nullptr);
      ir->cos_accel = get_ereal(&inp, "cos-acceleration", 0, wi);
  
      /* 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->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);
      }
      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;
          {
              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++)
          {
          }
          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);
 +        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] = (get_eeenum(&inp, "massw-split0", yesno_names, wi) != 0);
 -        ir->swap->massw_split[1] = (get_eeenum(&inp, "massw-split1", yesno_names, wi) != 0);
 +        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);
 +        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 "
          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);
          {
              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(
                      }
                      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(
                      }
                      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]))
 +                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(
                      }
                      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));
              }
          }
      }
          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];
          }
      }
  
 -    if (ir->comm_mode == ecmNO)
 +    if (ir->comm_mode == ComRemovalAlgorithm::No)
      {
          ir->nstcomm = 0;
      }
      opts->couple_moltype = nullptr;
      if (strlen(inputrecStrings->couple_moltype) > 0)
      {
 -        if (ir->efep != efepNO)
 +        if (ir->efep != FreeEnergyPerturbationType::No)
          {
              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_note(
                          wi,
          }
      }
      /* FREE ENERGY AND EXPANDED ENSEMBLE OPTIONS */
 -    if (ir->efep != efepNO)
 +    if (ir->efep != FreeEnergyPerturbationType::No)
      {
          if (fep->delta_lambda != 0)
          {
 -            ir->efep = efepSLOWGROWTH;
 +            ir->efep = FreeEnergyPerturbationType::SlowGrowth;
          }
      }
  
 -    if (fep->edHdLPrintEnergy == edHdLPrintEnergyYES)
 +    if (fep->edHdLPrintEnergy == FreeEnergyPrintEnergy::Yes)
      {
 -        fep->edHdLPrintEnergy = edHdLPrintEnergyTOTAL;
 +        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
             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;
          }
           * 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
 +        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,
      }
  
      double gmx_unused canary;
 -    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);
 +    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)
      {
      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++)
          {
      }
  
      /* Ion/water position swapping checks */
 -    if (ir->eSwapCoords != eswapNO)
 +    if (ir->eSwapCoords != SwapType::No)
      {
          if (ir->swap->nstswap < 1)
          {
      /* Set up MTS levels, this needs to happen before checking AWH parameters */
      if (ir->useMts)
      {
 -        setupMtsLevels(ir->mtsLevels, *ir, *opts, wi);
 +        std::vector<std::string> errorMessages;
 +        ir->mtsLevels = gmx::setupMtsLevels(opts->mtsOpts, &errorMessages);
 +
 +        for (const auto& errorMessage : errorMessages)
 +        {
 +            warning_error(wi, errorMessage.c_str());
 +        }
      }
  
      if (ir->bDoAwh)
      {
 -        gmx::checkAwhParams(ir->awhParams, ir, wi);
 +        gmx::checkAwhParams(*ir->awhParams, *ir, wi);
      }
  
      sfree(dumstr[0]);
@@@ -2878,7 -2861,8 +2878,7 @@@ static void do_numbering(in
              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
              {
          {
              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);
@@@ -2990,8 -2975,7 +2990,8 @@@ static void calc_nrdf(const gmx_mtop_t
          nrdf_tc[i] = 0;
      }
      for (gmx::index i = 0;
 -         i < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval]) + 1; i++)
 +         i < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval]) + 1;
 +         i++)
      {
          nrdf_vcm[i] = 0;
          clear_ivec(dof_vcm[i]);
          const t_atom& local = atomP.atom();
          int           i     = atomP.globalAtomNumber();
          nrdf2[i]            = 0;
 -        if (local.ptype == eptAtom || local.ptype == eptNucleus)
 +        if (local.ptype == ParticleType::Atom || local.ptype == ParticleType::Nucleus)
          {
              int g = getGroupType(groups, SimulationAtomGroupType::Freeze, i);
              for (int d = 0; d < DIM; d++)
                       */
                      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)
                          {
  
          for (int i = 0; i < pull->ncoord; i++)
          {
 -            if (pull->coord[i].eType != epullCONSTRAINT)
 +            if (pull->coord[i].eType != PullingAlgorithm::Constraint)
              {
                  continue;
              }
           * 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]); j++)
 +             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++)
                      {
                          }
                      }
                      break;
 -                case ecmANGULAR: nrdf_vcm_sub[j] = 6; break;
 +                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++)
 +             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++)
 +                 j < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval]) + 1;
 +                 j++)
              {
                  na_vcm[j] = 0;
              }
              nrdf_uc    = nrdf_tc[i];
              nrdf_tc[i] = 0;
              for (gmx::index j = 0;
 -                 j < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval]) + 1; j++)
 +                 j < gmx::ssize(groups.groups[SimulationAtomGroupType::MassCenterVelocityRemoval]) + 1;
 +                 j++)
              {
                  if (nrdf_vcm[j] > nrdf_vcm_sub[j])
                  {
          {
              opts->nrdf[i] = 0;
          }
 -        fprintf(stderr, "Number of degrees of freedom in T-Coupling group %s is %.2f\n",
 -                gnames[groups.groups[SimulationAtomGroupType::TemperatureCoupling][i]], opts->nrdf[i]);
 +        fprintf(stderr,
 +                "Number of degrees of freedom in T-Coupling group %s is %.2f\n",
 +                gnames[groups.groups[SimulationAtomGroupType::TemperatureCoupling][i]],
 +                opts->nrdf[i]);
      }
  
      sfree(nrdf2);
@@@ -3253,6 -3229,7 +3253,6 @@@ static bool do_egp_flag(t_inputrec* ir
       * 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;
  
          j = 0;
          while ((j < nr)
                 && gmx_strcasecmp(
 -                          names[2 * i].c_str(),
 -                          *(groups->groupNames[groups->groups[SimulationAtomGroupType::EnergyOutput][j]])))
 +                       names[2 * i].c_str(),
 +                       *(groups->groupNames[groups->groups[SimulationAtomGroupType::EnergyOutput][j]])))
          {
              j++;
          }
          k = 0;
          while ((k < nr)
                 && gmx_strcasecmp(
 -                          names[2 * i + 1].c_str(),
 -                          *(groups->groupNames[groups->groups[SimulationAtomGroupType::EnergyOutput][k]])))
 +                       names[2 * i + 1].c_str(),
 +                       *(groups->groupNames[groups->groups[SimulationAtomGroupType::EnergyOutput][k]])))
          {
              k++;
          }
@@@ -3309,13 -3286,9 +3309,13 @@@ static void make_swap_groups(t_swapcoor
  
  
      /* 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 */
  
          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++)
              {
@@@ -3359,8 -3329,7 +3359,8 @@@ static void make_IMD_group(t_IMD* IMDgr
          fprintf(stderr,
                  "Group '%s' with %d atoms can be activated for interactive molecular dynamics "
                  "(IMD).\n",
 -                IMDgname, IMDgroup->nat);
 +                IMDgname,
 +                IMDgroup->nat);
          snew(IMDgroup->ind, IMDgroup->nat);
          for (i = 0; i < IMDgroup->nat; i++)
          {
@@@ -3438,8 -3407,7 +3438,8 @@@ static void checkAndUpdateVcmFreezeGrou
                  "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);
 +                numPartiallyFrozenVcmAtoms,
 +                DIM);
          warning(wi, warningText.c_str());
      }
      if (numNonVcmAtoms > 0)
      }
  }
  
 -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)
 +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;
          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);
      }
          gmx_fatal(FARGS,
                    "Invalid T coupling input: %zu groups, %zu ref-t values and "
                    "%zu tau-t values",
 -                  temperatureCouplingGroupNames.size(), temperatureCouplingReferenceValues.size(),
 +                  temperatureCouplingGroupNames.size(),
 +                  temperatureCouplingReferenceValues.size(),
                    temperatureCouplingTauValues.size());
      }
  
      const bool useReferenceTemperature = integratorHasReferenceTemperature(ir);
 -    do_numbering(natoms, groups, temperatureCouplingGroupNames, defaultIndexGroups, gnames,
 -                 SimulationAtomGroupType::TemperatureCoupling, restnm,
 -                 useReferenceTemperature ? egrptpALL : egrptpALL_GENREST, bVerbose, wi);
 +    do_numbering(natoms,
 +                 groups,
 +                 temperatureCouplingGroupNames,
 +                 defaultIndexGroups,
 +                 gnames,
 +                 SimulationAtomGroupType::TemperatureCoupling,
 +                 restnm,
 +                 useReferenceTemperature ? egrptpALL : egrptpALL_GENREST,
 +                 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");
      }
          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_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");
              }
 -            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 "
                  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);
 +                        enumValueToString(ir->etc),
 +                        tau_min,
 +                        nstcmin,
 +                        ir->nsttcouple * ir->delta_t);
                  warning(wi, warn_buf);
              }
          }
      }
      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
      {
          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;
              {
                  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;
                  }
              }
                  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;
  
                  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(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",
 +                convertReals(wi, simulatedAnnealingTimes, "anneal-time", allSimulatedAnnealingTimes.data());
 +                convertReals(wi,
 +                             simulatedAnnealingTemperatures,
 +                             "anneal-temp",
                               allSimulatedAnnealingTemperatures.data());
                  for (i = 0, k = 0; i < nr; i++)
                  {
                                  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]);
 +                                          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",
 +                            gmx_fatal(FARGS,
 +                                      "Found negative temperature in annealing: %f\n",
                                        ir->opts.anneal_temp[i][j]);
                          }
                          k++;
                  /* 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++)
                          {
 -                            fprintf(stderr, "%9.1f      %5.1f\n", ir->opts.anneal_time[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)
 +                        if (ir->opts.annealing[i] == SimulatedAnnealing::Single)
                          {
 -                            fprintf(stderr, "%9.1f-     %5.1f\n", ir->opts.anneal_time[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],
 +                            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)
                              {
      {
          for (int i = 1; i < ir->pull->ngroup; i++)
          {
 -            const int gid = search_string(inputrecStrings->pullGroupNames[i].c_str(),
 -                                          defaultIndexGroups->nr, gnames);
 +            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);
          }
          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);
      }
  
      gmx::IndexGroupsAndNames defaultIndexGroupsAndNames(
              *defaultIndexGroups, gmx::arrayRefFromArray(gnames, defaultIndexGroups->nr));
 -    notifier.preProcessingNotifications_.notify(defaultIndexGroupsAndNames);
 -
 -    auto accelerations          = gmx::splitString(inputrecStrings->acc);
 -    auto accelerationGroupNames = gmx::splitString(inputrecStrings->accgrps);
 -    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,
 -                 SimulationAtomGroupType::Acceleration, restnm, egrptpALL_GENREST, bVerbose, wi);
 -    nr = groups->groups[SimulationAtomGroupType::Acceleration].size();
 -    snew(ir->opts.acc, nr);
 -    ir->opts.ngacc = nr;
 -
 -    convertRvecs(wi, accelerations, "anneal-time", ir->opts.acc);
 +    mdModulesNotifiers.preProcessingNotifier_.notify(defaultIndexGroupsAndNames);
  
      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,
 -                 SimulationAtomGroupType::Freeze, restnm, egrptpALL_GENREST, bVerbose, wi);
 +        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);
      nr             = groups->groups[SimulationAtomGroupType::Freeze].size();
      ir->opts.ngfrz = nr;
      snew(ir->opts.nFreeze, nr);
      }
  
      auto energyGroupNames = gmx::splitString(inputrecStrings->energy);
 -    do_numbering(natoms, groups, energyGroupNames, defaultIndexGroups, gnames,
 -                 SimulationAtomGroupType::EnergyOutput, restnm, egrptpALL_GENREST, bVerbose, wi);
 +    do_numbering(natoms,
 +                 groups,
 +                 energyGroupNames,
 +                 defaultIndexGroups,
 +                 gnames,
 +                 SimulationAtomGroupType::EnergyOutput,
 +                 restnm,
 +                 egrptpALL_GENREST,
 +                 bVerbose,
 +                 wi);
      add_wall_energrps(groups, ir->nwall, symtab);
      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() ? egrptpALL_GENREST : egrptpPART, bVerbose, wi);
 -
 -    if (ir->comm_mode != ecmNO)
 +    do_numbering(natoms,
 +                 groups,
 +                 vcmGroupNames,
 +                 defaultIndexGroups,
 +                 gnames,
 +                 SimulationAtomGroupType::MassCenterVelocityRemoval,
 +                 restnm,
 +                 vcmGroupNames.empty() ? egrptpALL_GENREST : egrptpPART,
 +                 bVerbose,
 +                 wi);
 +
 +    if (ir->comm_mode != ComRemovalAlgorithm::No)
      {
          checkAndUpdateVcmFreezeGroupConsistency(groups, natoms, ir->opts, wi);
      }
      calc_nrdf(mtop, ir, gnames);
  
      auto user1GroupNames = gmx::splitString(inputrecStrings->user1);
 -    do_numbering(natoms, groups, user1GroupNames, defaultIndexGroups, gnames,
 -                 SimulationAtomGroupType::User1, restnm, egrptpALL_GENREST, bVerbose, wi);
 +    do_numbering(natoms,
 +                 groups,
 +                 user1GroupNames,
 +                 defaultIndexGroups,
 +                 gnames,
 +                 SimulationAtomGroupType::User1,
 +                 restnm,
 +                 egrptpALL_GENREST,
 +                 bVerbose,
 +                 wi);
      auto user2GroupNames = gmx::splitString(inputrecStrings->user2);
 -    do_numbering(natoms, groups, user2GroupNames, defaultIndexGroups, gnames,
 -                 SimulationAtomGroupType::User2, restnm, egrptpALL_GENREST, bVerbose, wi);
 +    do_numbering(natoms,
 +                 groups,
 +                 user2GroupNames,
 +                 defaultIndexGroups,
 +                 gnames,
 +                 SimulationAtomGroupType::User2,
 +                 restnm,
 +                 egrptpALL_GENREST,
 +                 bVerbose,
 +                 wi);
      auto compressedXGroupNames = gmx::splitString(inputrecStrings->x_compressed_groups);
 -    do_numbering(natoms, groups, compressedXGroupNames, defaultIndexGroups, gnames,
 -                 SimulationAtomGroupType::CompressedPositionOutput, restnm, egrptpONE, bVerbose, wi);
 +    do_numbering(natoms,
 +                 groups,
 +                 compressedXGroupNames,
 +                 defaultIndexGroups,
 +                 gnames,
 +                 SimulationAtomGroupType::CompressedPositionOutput,
 +                 restnm,
 +                 egrptpONE,
 +                 bVerbose,
 +                 wi);
      auto orirefFitGroupNames = gmx::splitString(inputrecStrings->orirefitgrp);
 -    do_numbering(natoms, groups, orirefFitGroupNames, defaultIndexGroups, gnames,
 -                 SimulationAtomGroupType::OrientationRestraintsFit, restnm, egrptpALL_GENREST,
 -                 bVerbose, wi);
 +    do_numbering(natoms,
 +                 groups,
 +                 orirefFitGroupNames,
 +                 defaultIndexGroups,
 +                 gnames,
 +                 SimulationAtomGroupType::OrientationRestraintsFit,
 +                 restnm,
 +                 egrptpALL_GENREST,
 +                 bVerbose,
 +                 wi);
  
      /* MiMiC QMMM input processing */
      auto qmGroupNames = gmx::splitString(inputrecStrings->QMMM);
          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);
 +    do_numbering(natoms,
 +                 groups,
 +                 qmGroupNames,
 +                 defaultIndexGroups,
 +                 gnames,
 +                 SimulationAtomGroupType::QuantumMechanics,
 +                 restnm,
 +                 egrptpALL_GENREST,
 +                 bVerbose,
 +                 wi);
      ir->opts.ngQM = qmGroupNames.size();
  
      /* end of MiMiC QMMM input */
      snew(ir->opts.egp_flags, nr * nr);
  
      bExcl = do_egp_flag(ir, groups, "energygrp-excl", inputrecStrings->egpexcl, EGP_EXCL);
 -    if (bExcl && ir->cutoff_scheme == ecutsVERLET)
 +    if (bExcl && ir->cutoff_scheme == CutoffScheme::Verlet)
      {
          warning_error(wi, "Energy group exclusions are currently not supported");
      }
      }
  
      bTable = do_egp_flag(ir, groups, "energygrp-table", inputrecStrings->egptable, EGP_TABLE);
 -    if (bTable && !(ir->vdwtype == evdwUSER) && !(ir->coulombtype == eelUSER)
 -        && !(ir->coulombtype == eelPMEUSER) && !(ir->coulombtype == eelPMEUSERSWITCH))
 +    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 "
      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));
 +        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++)
      {
  }
  
  
 -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++)
@@@ -4143,15 -4027,15 +4143,15 @@@ static BasicVector<bool> havePositionRe
  {
      BasicVector<bool> havePosres = { false, false, false };
  
 -    gmx_mtop_ilistloop_t iloop = gmx_mtop_ilistloop_init(&sys);
 -    int                  nmol;
 -    while (const InteractionLists* ilist = gmx_mtop_ilistloop_next(iloop, &nmol))
 +    for (const auto ilists : IListRange(sys))
      {
 -        if (nmol > 0 && (!havePosres[XX] || !havePosres[YY] || !havePosres[ZZ]))
 +        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 (int i = 0; i < (*ilist)[F_POSRES].size(); i += 2)
 +            for (int i = 0; i < posResList.size(); i += 2)
              {
 -                const t_iparams& pr = sys.ffparams.iparams[(*ilist)[F_POSRES].iatoms[i]];
 +                const t_iparams& pr = sys.ffparams.iparams[posResList.iatoms[i]];
                  for (int d = 0; d < DIM; d++)
                  {
                      if (pr.posres.fcA[d] != 0)
                      }
                  }
              }
 -            for (int i = 0; i < (*ilist)[F_FBPOSRES].size(); i += 2)
 +            for (int i = 0; i < fbPosResList.size(); i += 2)
              {
                  /* Check for flat-bottom posres */
 -                const t_iparams& pr = sys.ffparams.iparams[(*ilist)[F_FBPOSRES].iatoms[i]];
 +                const t_iparams& pr = sys.ffparams.iparams[fbPosResList.iatoms[i]];
                  if (pr.fbposres.k != 0)
                  {
                      switch (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);
 +                                      efbposresNR - 1,
 +                                      pr.fbposres.geom);
                      }
                  }
              }
      return havePosres;
  }
  
 -static void check_combination_rule_differences(const gmx_mtop_t* mtop,
 +static void check_combination_rule_differences(const gmx_mtop_t& mtop,
                                                 int               state,
                                                 bool* bC6ParametersWorkWithGeometricRules,
                                                 bool* bC6ParametersWorkWithLBRules,
      ptr = getenv("GMX_LJCOMB_TOL");
      if (ptr != nullptr)
      {
 -        double dbl;
 +        double            dbl;
          double gmx_unused canary;
  
          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;
 +    ntypes                               = mtop.ffparams.atnr;
      snew(typecount, ntypes);
      gmx_mtop_count_atomtypes(mtop, state, typecount);
      *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))
              {
      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)
          {
      {
          if (!bC6ParametersWorkWithGeometricRules)
          {
 -            if (ir->eDispCorr != edispcNO)
 +            if (ir->eDispCorr != DispersionCorrectionType::No)
              {
                  warning_note(wi,
                               "You are using geometric combination rules in "
@@@ -4332,17 -4215,19 +4332,17 @@@ static bool allTrue(const BasicVector<b
  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::assertMtsRequirements(*ir);
 +    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;
      gmx_mtop_atomloop_block_t aloopb;
      char                      warn_buf[STRLEN];
  
      set_warning_line(wi, mdparin, -1);
  
-     if (allTrue(haveAbsoluteReference(*ir)) && allTrue(havePositionRestraints(*sys)))
 -    if (ir->comm_mode != ecmNO && allTrue(havePositionRestraints(*sys)))
++    if (ir->comm_mode != ComRemovalAlgorithm::No && allTrue(havePositionRestraints(*sys)))
      {
          warning_note(wi,
                       "Removing center of mass motion in the presence of position restraints might "
                       "macro-molecule, the artifacts are usually negligible.");
      }
  
 -    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->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.
               * 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,
                          "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,
 +                        tau,
 +                        100 * max_T_error,
 +                        100 * T_error_suggest,
                          ir->verletbuf_tol * T_error_suggest / max_T_error);
                  warning(wi, warn_buf);
              }
              sprintf(err_buf,
                      "all tau_t must be positive using Andersen temperature control, "
                      "tau_t[%d]=%10.6f",
 -                    i, ir->opts.tau_t[i]);
 +                    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++)
              {
                          "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);
 +                        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
 +    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))
      {
                  "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++)
              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",
 +                    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)
      {
          const BasicVector<bool> havePosres = havePositionRestraints(*sys);
          {
      }
  
      bCharge = FALSE;
 -    aloopb  = gmx_mtop_atomloop_block_init(sys);
 +    aloopb  = gmx_mtop_atomloop_block_init(*sys);
      const t_atom* atom;
      while (gmx_mtop_atomloop_block_next(aloopb, &atom, &nmol))
      {
                      "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));
 +                    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);
          }
      }
      /* 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. "
                        "constant by hand.");
      }
  
 -    bAcc = FALSE;
 -    for (int i = 0; (i < gmx::ssize(sys->groups.groups[SimulationAtomGroupType::Acceleration])); i++)
 -    {
 -        for (m = 0; (m < DIM); m++)
 -        {
 -            if (fabs(ir->opts.acc[i][m]) > 1e-6)
 -            {
 -                bAcc = TRUE;
 -            }
 -        }
 -    }
 -    if (bAcc)
 -    {
 -        clear_rvec(acc);
 -        snew(mgrp, sys->groups.groups[SimulationAtomGroupType::Acceleration].size());
 -        for (const AtomProxy atomP : AtomRange(*sys))
 -        {
 -            const t_atom& local = atomP.atom();
 -            int           i     = atomP.globalAtomNumber();
 -            mgrp[getGroupType(sys->groups, SimulationAtomGroupType::Acceleration, i)] += local.m;
 -        }
 -        mt = 0.0;
 -        for (i = 0; (i < gmx::ssize(sys->groups.groups[SimulationAtomGroupType::Acceleration])); i++)
 -        {
 -            for (m = 0; (m < DIM); m++)
 -            {
 -                acc[m] += ir->opts.acc[i][m] * mgrp[i];
 -            }
 -            mt += mgrp[i];
 -        }
 -        for (m = 0; (m < DIM); m++)
 -        {
 -            if (fabs(acc[m]) > 1e-6)
 -            {
 -                const char* dim[DIM] = { "X", "Y", "Z" };
 -                fprintf(stderr, "Net Acceleration in %s direction, will %s be corrected\n", 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++)
 -                    {
 -                        ir->opts.acc[i][m] -= acc[m];
 -                    }
 -                }
 -            }
 -        }
 -        sfree(mgrp);
 -    }
 -
 -    if (ir->efep != efepNO && ir->fepvals->sc_alpha != 0
 +    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");
          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))
              {
                  const auto absRef     = haveAbsoluteReference(*ir);
                  const auto havePosres = havePositionRestraints(*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);
 +                                      enumValueToString(ir->pull->coord[c].eGeom),
 +                                      'x' + m);
                          }
                      }
                  }
          }
      }
  
 -    check_disre(sys);
 +    check_disre(*sys);
  }
  
  void double_check(t_inputrec* ir, matrix box, bool bHasNormalConstraints, bool bHasAnyConstraints, warninp_t wi)
          warning_error(wi, ptr);
      }
  
 -    if (bHasNormalConstraints && ir->eConstrAlg == econtSHAKE)
 +    if (bHasNormalConstraints && ir->eConstrAlg == ConstraintAlgorithm::Shake)
      {
          if (ir->shake_tol <= 0.0)
          {
          }
      }
  
 -    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");
              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]);
 +                    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.");
      }
index e365d9b1d06afaca8d41e2bcd8c1ac1ed2c0ebaa,e5247e79bac037a961e483621adc6b2063b064ca..13ade2a338a48b73012c2a191d3860397955e842
@@@ -4,7 -4,7 +4,7 @@@
   * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
   * Copyright (c) 2001-2004, The GROMACS development team.
   * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
-  * Copyright (c) 2017,2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 2017,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.
@@@ -91,56 -91,18 +91,56 @@@ void eigensolver(real* a, int n, int in
       */
  #if GMX_DOUBLE
      F77_FUNC(dsyevr, DSYEVR)
 -    (jobz, "I", "L", &n, a, &n, &vl, &vu, &index_lower, &index_upper, &abstol, &m, eigenvalues,
 -     eigenvectors, &n, isuppz, &w0, &lwork, &iw0, &liwork, &info);
 +    (jobz,
 +     "I",
 +     "L",
 +     &n,
 +     a,
 +     &n,
 +     &vl,
 +     &vu,
 +     &index_lower,
 +     &index_upper,
 +     &abstol,
 +     &m,
 +     eigenvalues,
 +     eigenvectors,
 +     &n,
 +     isuppz,
 +     &w0,
 +     &lwork,
 +     &iw0,
 +     &liwork,
 +     &info);
  #else
      F77_FUNC(ssyevr, SSYEVR)
 -    (jobz, "I", "L", &n, a, &n, &vl, &vu, &index_lower, &index_upper, &abstol, &m, eigenvalues,
 -     eigenvectors, &n, isuppz, &w0, &lwork, &iw0, &liwork, &info);
 +    (jobz,
 +     "I",
 +     "L",
 +     &n,
 +     a,
 +     &n,
 +     &vl,
 +     &vu,
 +     &index_lower,
 +     &index_upper,
 +     &abstol,
 +     &m,
 +     eigenvalues,
 +     eigenvectors,
 +     &n,
 +     isuppz,
 +     &w0,
 +     &lwork,
 +     &iw0,
 +     &liwork,
 +     &info);
  #endif
  
      if (info != 0)
      {
          sfree(isuppz);
-         gmx_fatal(FARGS, "Internal errror in LAPACK diagonalization.");
+         gmx_fatal(FARGS, "Internal error in LAPACK diagonalization.");
      }
  
      lwork  = static_cast<int>(w0);
  
  #if GMX_DOUBLE
      F77_FUNC(dsyevr, DSYEVR)
 -    (jobz, "I", "L", &n, a, &n, &vl, &vu, &index_lower, &index_upper, &abstol, &m, eigenvalues,
 -     eigenvectors, &n, isuppz, work, &lwork, iwork, &liwork, &info);
 +    (jobz,
 +     "I",
 +     "L",
 +     &n,
 +     a,
 +     &n,
 +     &vl,
 +     &vu,
 +     &index_lower,
 +     &index_upper,
 +     &abstol,
 +     &m,
 +     eigenvalues,
 +     eigenvectors,
 +     &n,
 +     isuppz,
 +     work,
 +     &lwork,
 +     iwork,
 +     &liwork,
 +     &info);
  #else
      F77_FUNC(ssyevr, SSYEVR)
 -    (jobz, "I", "L", &n, a, &n, &vl, &vu, &index_lower, &index_upper, &abstol, &m, eigenvalues,
 -     eigenvectors, &n, isuppz, work, &lwork, iwork, &liwork, &info);
 +    (jobz,
 +     "I",
 +     "L",
 +     &n,
 +     a,
 +     &n,
 +     &vl,
 +     &vu,
 +     &index_lower,
 +     &index_upper,
 +     &abstol,
 +     &m,
 +     eigenvalues,
 +     eigenvectors,
 +     &n,
 +     isuppz,
 +     work,
 +     &lwork,
 +     iwork,
 +     &liwork,
 +     &info);
  #endif
  
      sfree(isuppz);
@@@ -274,10 -198,12 +274,10 @@@ void sparse_parallel_eigensolver(gmx_sp
      {
  #    if GMX_DOUBLE
          F77_FUNC(pdsaupd, PDSAUPD)
 -        (&ido, "I", &n, "SA", &neig, &abstol, resid, &ncv, v, &n, iparam, ipntr, workd, iwork,
 -         workl, &lworkl, &info);
 +        (&ido, "I", &n, "SA", &neig, &abstol, resid, &ncv, v, &n, iparam, ipntr, workd, iwork, workl, &lworkl, &info);
  #    else
          F77_FUNC(pssaupd, PSSAUPD)
 -        (&ido, "I", &n, "SA", &neig, &abstol, resid, &ncv, v, &n, iparam, ipntr, workd, iwork,
 -         workl, &lworkl, &info);
 +        (&ido, "I", &n, "SA", &neig, &abstol, resid, &ncv, v, &n, iparam, ipntr, workd, iwork, workl, &lworkl, &info);
  #    endif
          if (ido == -1 || ido == 1)
          {
          gmx_fatal(FARGS,
                    "Maximum number of iterations (%d) reached in Arnoldi\n"
                    "diagonalization, but only %d of %d eigenvectors converged.\n",
 -                  maxiter, iparam[4], neig);
 +                  maxiter,
 +                  iparam[4],
 +                  neig);
      }
      else if (info != 0)
      {
  
  #    if GMX_DOUBLE
      F77_FUNC(pdseupd, PDSEUPD)
 -    (&dovec, "A", select, eigenvalues, eigenvectors, &n, NULL, "I", &n, "SA", &neig, &abstol, resid,
 -     &ncv, v, &n, iparam, ipntr, workd, workl, &lworkl, &info);
 +    (&dovec,
 +     "A",
 +     select,
 +     eigenvalues,
 +     eigenvectors,
 +     &n,
 +     NULL,
 +     "I",
 +     &n,
 +     "SA",
 +     &neig,
 +     &abstol,
 +     resid,
 +     &ncv,
 +     v,
 +     &n,
 +     iparam,
 +     ipntr,
 +     workd,
 +     workl,
 +     &lworkl,
 +     &info);
  #    else
      F77_FUNC(psseupd, PSSEUPD)
 -    (&dovec, "A", select, eigenvalues, eigenvectors, &n, NULL, "I", &n, "SA", &neig, &abstol, resid,
 -     &ncv, v, &n, iparam, ipntr, workd, workl, &lworkl, &info);
 +    (&dovec,
 +     "A",
 +     select,
 +     eigenvalues,
 +     eigenvectors,
 +     &n,
 +     NULL,
 +     "I",
 +     &n,
 +     "SA",
 +     &neig,
 +     &abstol,
 +     resid,
 +     &ncv,
 +     v,
 +     &n,
 +     iparam,
 +     ipntr,
 +     workd,
 +     workl,
 +     &lworkl,
 +     &info);
  #    endif
  
      sfree(v);
@@@ -434,10 -318,12 +434,10 @@@ void sparse_eigensolver(gmx_sparsematri
      {
  #if GMX_DOUBLE
          F77_FUNC(dsaupd, DSAUPD)
 -        (&ido, "I", &n, "SA", &neig, &abstol, resid, &ncv, v, &n, iparam, ipntr, workd, iwork,
 -         workl, &lworkl, &info);
 +        (&ido, "I", &n, "SA", &neig, &abstol, resid, &ncv, v, &n, iparam, ipntr, workd, iwork, workl, &lworkl, &info);
  #else
          F77_FUNC(ssaupd, SSAUPD)
 -        (&ido, "I", &n, "SA", &neig, &abstol, resid, &ncv, v, &n, iparam, ipntr, workd, iwork,
 -         workl, &lworkl, &info);
 +        (&ido, "I", &n, "SA", &neig, &abstol, resid, &ncv, v, &n, iparam, ipntr, workd, iwork, workl, &lworkl, &info);
  #endif
          if (ido == -1 || ido == 1)
          {
          gmx_fatal(FARGS,
                    "Maximum number of iterations (%d) reached in Arnoldi\n"
                    "diagonalization, but only %d of %d eigenvectors converged.\n",
 -                  maxiter, iparam[4], neig);
 +                  maxiter,
 +                  iparam[4],
 +                  neig);
      }
      else if (info != 0)
      {
  
  #if GMX_DOUBLE
      F77_FUNC(dseupd, DSEUPD)
 -    (&dovec, "A", select, eigenvalues, eigenvectors, &n, nullptr, "I", &n, "SA", &neig, &abstol,
 -     resid, &ncv, v, &n, iparam, ipntr, workd, workl, &lworkl, &info);
 +    (&dovec,
 +     "A",
 +     select,
 +     eigenvalues,
 +     eigenvectors,
 +     &n,
 +     nullptr,
 +     "I",
 +     &n,
 +     "SA",
 +     &neig,
 +     &abstol,
 +     resid,
 +     &ncv,
 +     v,
 +     &n,
 +     iparam,
 +     ipntr,
 +     workd,
 +     workl,
 +     &lworkl,
 +     &info);
  #else
      F77_FUNC(sseupd, SSEUPD)
 -    (&dovec, "A", select, eigenvalues, eigenvectors, &n, nullptr, "I", &n, "SA", &neig, &abstol,
 -     resid, &ncv, v, &n, iparam, ipntr, workd, workl, &lworkl, &info);
 +    (&dovec,
 +     "A",
 +     select,
 +     eigenvalues,
 +     eigenvectors,
 +     &n,
 +     nullptr,
 +     "I",
 +     &n,
 +     "SA",
 +     &neig,
 +     &abstol,
 +     resid,
 +     &ncv,
 +     v,
 +     &n,
 +     iparam,
 +     ipntr,
 +     workd,
 +     workl,
 +     &lworkl,
 +     &info);
  #endif
  
      sfree(v);
index 954a840529e94746b881576f638122ca0743073a,146a28c8a31b8dea772cfd8a604be7d801f87a58..71b7a8bec4b34ae78d02f05ba25a2cbc9fadc7df
@@@ -70,25 -70,52 +70,25 @@@ namespace gm
  
  const std::string& simdString(SimdType s)
  {
 -    static const std::map<SimdType, std::string> name = {
 -        { SimdType::None, "None" },
 -        { SimdType::Reference, "Reference" },
 -        { SimdType::Generic, "Generic" },
 -        { SimdType::X86_Sse2, "SSE2" },
 -        { SimdType::X86_Sse4_1, "SSE4.1" },
 -        { SimdType::X86_Avx128Fma, "AVX_128_FMA" },
 -        { SimdType::X86_Avx, "AVX_256" },
 -        { SimdType::X86_Avx2, "AVX2_256" },
 -        { SimdType::X86_Avx2_128, "AVX2_128" },
 -        { SimdType::X86_Avx512, "AVX_512" },
 -        { SimdType::X86_Avx512Knl, "AVX_512_KNL" },
 -        { SimdType::X86_Mic, "X86_MIC" },
 -        { SimdType::Arm_Neon, "ARM_NEON" },
 -        { SimdType::Arm_NeonAsimd, "ARM_NEON_ASIMD" },
 -        { SimdType::Arm_Sve, "ARM_SVE" },
 -        { SimdType::Ibm_Vmx, "IBM_VMX" },
 -        { SimdType::Ibm_Vsx, "IBM_VSX" },
 -        { SimdType::Fujitsu_HpcAce, "Fujitsu HPC-ACE" }
 -    };
 +    static const std::map<SimdType, std::string> name = { { SimdType::None, "None" },
 +                                                          { SimdType::Reference, "Reference" },
 +                                                          { SimdType::Generic, "Generic" },
 +                                                          { SimdType::X86_Sse2, "SSE2" },
 +                                                          { SimdType::X86_Sse4_1, "SSE4.1" },
 +                                                          { SimdType::X86_Avx128Fma, "AVX_128_FMA" },
 +                                                          { SimdType::X86_Avx, "AVX_256" },
 +                                                          { SimdType::X86_Avx2, "AVX2_256" },
 +                                                          { SimdType::X86_Avx2_128, "AVX2_128" },
 +                                                          { SimdType::X86_Avx512, "AVX_512" },
 +                                                          { SimdType::X86_Avx512Knl, "AVX_512_KNL" },
 +                                                          { SimdType::Arm_NeonAsimd,
 +                                                            "ARM_NEON_ASIMD" },
 +                                                          { SimdType::Arm_Sve, "ARM_SVE" },
 +                                                          { SimdType::Ibm_Vsx, "IBM_VSX" } };
  
      return name.at(s);
  }
  
 -namespace
 -{
 -
 -
 -//! Helper to detect correct AMD Zen architecture.
 -bool cpuIsAmdZen1(const CpuInfo& cpuInfo)
 -{
 -    // Both Zen/Zen+/Zen2 have family==23
 -    // Model numbers for Zen:
 -    // 1)  Naples, Whitehaven, Summit ridge, and Snowy Owl
 -    // 17) Raven ridge
 -    // Model numbers for Zen+:
 -    // 8)  Pinnacle Ridge
 -    // 24) Picasso
 -    return (cpuInfo.vendor() == gmx::CpuInfo::Vendor::Amd && cpuInfo.family() == 23
 -            && (cpuInfo.model() == 1 || cpuInfo.model() == 17 || cpuInfo.model() == 8
 -                || cpuInfo.model() == 24));
 -}
 -
 -} // namespace
 -
 -
  SimdType simdSuggested(const CpuInfo& c)
  {
      SimdType suggested = SimdType::None;
                  }
                  else if (c.feature(CpuInfo::Feature::Arm_Neon))
                  {
 -                    suggested = SimdType::Arm_Neon;
 +                    suggested = SimdType::None;
                  }
                  break;
              case CpuInfo::Vendor::Ibm:
                  }
                  else if (c.feature(CpuInfo::Feature::Ibm_Vmx))
                  {
 -                    suggested = SimdType::Ibm_Vmx;
 +                    suggested = SimdType::None;
                  }
                  break;
              case CpuInfo::Vendor::Fujitsu:
                  if (c.feature(CpuInfo::Feature::Fujitsu_HpcAce))
                  {
 -                    suggested = SimdType::Fujitsu_HpcAce;
 +                    suggested = SimdType::None;
                  }
                  break;
              default: break;
@@@ -199,6 -226,8 +199,6 @@@ SimdType simdCompiled(
      return SimdType::X86_Avx512Knl;
  #elif GMX_SIMD_X86_AVX_512
      return SimdType::X86_Avx512;
 -#elif GMX_SIMD_X86_MIC
 -    return SimdType::X86_Mic;
  #elif GMX_SIMD_X86_AVX2_256
      return SimdType::X86_Avx2;
  #elif GMX_SIMD_X86_AVX2_128
      return SimdType::X86_Sse4_1;
  #elif GMX_SIMD_X86_SSE2
      return SimdType::X86_Sse2;
 -#elif GMX_SIMD_ARM_NEON
 -    return SimdType::Arm_Neon;
  #elif GMX_SIMD_ARM_NEON_ASIMD
      return SimdType::Arm_NeonAsimd;
  #elif GMX_SIMD_ARM_SVE
      return SimdType::Arm_Sve;
 -#elif GMX_SIMD_IBM_VMX
 -    return SimdType::Ibm_Vmx;
  #elif GMX_SIMD_IBM_VSX
      return SimdType::Ibm_Vsx;
 -#elif GMX_SIMD_SPARC64_HPC_ACE
 -    return SimdType::Fujitsu_HpcAce;
  #elif GMX_SIMD_REFERENCE
      return SimdType::Reference;
  #else
@@@ -237,37 -272,33 +237,37 @@@ bool simdCheck(gmx::SimdType wanted, FI
      if (compiled == SimdType::X86_Avx2 && wanted == SimdType::X86_Avx512)
      {
          logMsg  = wrapper.wrapToString(formatString(
-                 "Highest SIMD level requested by all nodes in run: %s\n"
+                 "Highest SIMD level supported by all nodes in run: %s\n"
                  "SIMD instructions selected at compile time:       %s\n"
                  "This program was compiled for different hardware than you are running on, "
                  "which could influence performance. This build might have been configured on "
                  "a login node with only a single AVX-512 FMA unit (in which case AVX2 is faster), "
                  "while the node you are running on has dual AVX-512 FMA units.",
 -                simdString(wanted).c_str(), simdString(compiled).c_str()));
 +                simdString(wanted).c_str(),
 +                simdString(compiled).c_str()));
          warnMsg = wrapper.wrapToString(formatString(
                  "Compiled SIMD: %s, but for this host/run %s might be better (see log).",
 -                simdString(compiled).c_str(), simdString(wanted).c_str()));
 +                simdString(compiled).c_str(),
 +                simdString(wanted).c_str()));
      }
      else if (compiled == SimdType::X86_Avx512 && wanted == SimdType::X86_Avx2
               && identifyAvx512FmaUnits() == 1)
      {
          // The reason for explicitly checking the number of FMA units above is to avoid triggering
-         // this conditional if the AVX2 SIMD was requested by some other node in a heterogeneous MPI run.
+         // this conditional if the AVX2 SIMD was supported by some other node in a heterogeneous MPI run.
          logMsg  = wrapper.wrapToString(formatString(
-                 "Highest SIMD level requested by all nodes in run: %s\n"
+                 "Highest SIMD level supported by all nodes in run: %s\n"
                  "SIMD instructions selected at compile time:       %s\n"
                  "This program was compiled for different hardware than you are running on, "
                  "which could influence performance."
                  "This host supports AVX-512, but since it only has 1 AVX-512"
                  "FMA unit, it would be faster to use AVX2 instead.",
 -                simdString(wanted).c_str(), simdString(compiled).c_str()));
 +                simdString(wanted).c_str(),
 +                simdString(compiled).c_str()));
          warnMsg = wrapper.wrapToString(formatString(
                  "Compiled SIMD: %s, but for this host/run %s might be better (see log).",
 -                simdString(compiled).c_str(), simdString(wanted).c_str()));
 +                simdString(compiled).c_str(),
 +                simdString(wanted).c_str()));
      }
      else if (compiled == SimdType::X86_Avx2 && wanted == SimdType::X86_Avx2_128)
      {
          // the supported one, but AVX128Fma is an exception: AMD CPUs will (strongly) prefer
          // AVX128Fma, but they will work fine with AVX too. Thus, make an exception for this.
          logMsg = wrapper.wrapToString(
-                 formatString("Highest SIMD level requested by all nodes in run: %s\n"
+                 formatString("Highest SIMD level supported by all nodes in run: %s\n"
                               "SIMD instructions selected at compile time:       %s\n"
-                              "Compiled SIMD newer than requested; program might crash.",
+                              "Compiled SIMD newer than supported; program might crash.",
 -                             simdString(wanted).c_str(), simdString(compiled).c_str()));
 +                             simdString(wanted).c_str(),
 +                             simdString(compiled).c_str()));
          warnMsg = logMsg;
      }
      else if (wanted != compiled)
      {
          // This warning will also occur if compiled is X86_Avx and wanted is X86_Avx128Fma
          logMsg  = wrapper.wrapToString(formatString(
-                 "Highest SIMD level requested by all nodes in run: %s\n"
+                 "Highest SIMD level supported by all nodes in run: %s\n"
                  "SIMD instructions selected at compile time:       %s\n"
                  "This program was compiled for different hardware than you are running on, "
                  "which could influence performance.",
 -                simdString(wanted).c_str(), simdString(compiled).c_str()));
 +                simdString(wanted).c_str(),
 +                simdString(compiled).c_str()));
          warnMsg = wrapper.wrapToString(formatString(
                  "Compiled SIMD: %s, but for this host/run %s might be better (see log).",
 -                simdString(compiled).c_str(), simdString(wanted).c_str()));
 +                simdString(compiled).c_str(),
 +                simdString(wanted).c_str()));
  #if GMX_SIMD_ARM_SVE
      }
      else if ((compiled == SimdType::Arm_Sve) && (svcntb() != GMX_SIMD_ARM_SVE_LENGTH_VALUE / 8))
      {
          logMsg  = wrapper.wrapToString(formatString(
-                 "Longest SVE length requested by all nodes in run: %d\n"
+                 "Longest SVE length supported by all nodes in run: %d\n"
                  "SVE length selected at compile time:               %ld\n"
                  "This program was compiled for different hardware than you are running on, "
                  "which will lead to incorrect behavior.\n"
                  "Aborting",
 -                GMX_SIMD_ARM_SVE_LENGTH_VALUE, svcntb() * 8));
 +                GMX_SIMD_ARM_SVE_LENGTH_VALUE,
 +                svcntb() * 8));
          warnMsg = wrapper.wrapToString(formatString(
                  "Compiled SVE Length: %d, but for this process requires %ld (see log).",
 -                GMX_SIMD_ARM_SVE_LENGTH_VALUE, svcntb() * 8));
 +                GMX_SIMD_ARM_SVE_LENGTH_VALUE,
 +                svcntb() * 8));
  #endif
      }
  
index eabfffaabee0345575d470be6d1ef694341b8558,8fea09d047ea7649a23e53944d2f368f3bcf00cd..d6011872094d8cf7505266f4ad22b895f1dd7725
@@@ -4,7 -4,7 +4,7 @@@
   * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
   * Copyright (c) 2001-2004, The GROMACS development team.
   * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
-  * Copyright (c) 2018,2019,2020, by the GROMACS development team, led by
+  * 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.
@@@ -187,10 -187,7 +187,10 @@@ static int scan_ene_files(const std::ve
                  fprintf(stderr,
                          "Energy files don't match, different number of energies:\n"
                          " %s: %d\n %s: %d\n",
 -                        files[f - 1].c_str(), nresav, files[f].c_str(), fr->nre);
 +                        files[f - 1].c_str(),
 +                        nresav,
 +                        files[f].c_str(),
 +                        fr->nre);
                  fprintf(stderr,
                          "\nContinue conversion using only the first %d terms (n/y)?\n"
                          "(you should be sure that the energy terms match)\n",
@@@ -445,7 -442,7 +445,7 @@@ int gmx_eneconv(int argc, char* argv[]
          "Reads one energy file and writes another, applying the [TT]-dt[tt],",
          "[TT]-offset[tt], [TT]-t0[tt] and [TT]-settime[tt] options and",
          "converting to a different format if necessary (indicated by file",
-         "extentions).[PAR]",
+         "extensions).[PAR]",
          "[TT]-settime[tt] is applied first, then [TT]-dt[tt]/[TT]-offset[tt]",
          "followed by [TT]-b[tt] and [TT]-e[tt] to select which frames to write."
      };
          { "-error", FALSE, etBOOL, { &bError }, "Stop on errors in the file" }
      };
  
 -    if (!parse_common_args(&argc, argv, 0, NFILE, fnm, asize(pa), pa, asize(desc), desc,
 -                           asize(bugs), bugs, &oenv))
 +    if (!parse_common_args(
 +                &argc, argv, 0, NFILE, fnm, asize(pa), pa, asize(desc), desc, asize(bugs), bugs, &oenv))
      {
          return 0;
      }
  
              if (debug)
              {
 -                fprintf(debug, "fr->step %s, fr->t %.4f, fro->step %s fro->t %.4f, w %s\n",
 -                        gmx_step_str(fr->step, buf), fr->t, gmx_step_str(fro->step, buf2), fro->t,
 +                fprintf(debug,
 +                        "fr->step %s, fr->t %.4f, fro->step %s fro->t %.4f, w %s\n",
 +                        gmx_step_str(fr->step, buf),
 +                        fr->t,
 +                        gmx_step_str(fro->step, buf2),
 +                        fro->t,
                          gmx::boolToString(bWrite));
              }
  
                  if (bNewOutput)
                  {
                      bNewOutput = FALSE;
 -                    fprintf(stderr, "\nContinue writing frames from t=%g, step=%s\n", fro->t,
 +                    fprintf(stderr,
 +                            "\nContinue writing frames from t=%g, step=%s\n",
 +                            fro->t,
                              gmx_step_str(fro->step, buf));
                  }
  
                                                 "samples away.\n"
                                                 "         Use g_energy -odh option to extract these "
                                                 "samples.\n",
 -                                               files[f].c_str(), size);
 +                                               files[f].c_str(),
 +                                               size);
                                          warned_about_dh = TRUE;
                                          break;
                                      }
          {
              f--;
          }
 -        printf("\nLast step written from %s: t %g, step %s\n", files[f].c_str(), last_t,
 +        printf("\nLast step written from %s: t %g, step %s\n",
 +               files[f].c_str(),
 +               last_t,
                 gmx_step_str(laststep, buf));
          lastfilestep = laststep;
  
      }
      else
      {
 -        fprintf(stderr, "Last frame written was at step %s, time %f\n",
 -                gmx_step_str(fro->step, buf), fro->t);
 +        fprintf(stderr, "Last frame written was at step %s, time %f\n", gmx_step_str(fro->step, buf), fro->t);
          fprintf(stderr, "Wrote %d frames\n", noutfr);
      }
  
index 95975b6809c55c0c1320d02850bab85ec54f17af,8406a83ab592fa577e860794a03a404dd9fa52cc..185e8c3b153457eab0732fcdf3e7f087cb3ac515
@@@ -152,10 -152,7 +152,10 @@@ static void do_trunc(const char* fn, re
              fprintf(stderr,
                      "Do you REALLY want to truncate this trajectory (%s) at:\n"
                      "frame %d, time %g, bytes %ld ??? (type YES if so)\n",
 -                    fn, j, t, static_cast<long int>(fpos));
 +                    fn,
 +                    j,
 +                    t,
 +                    static_cast<long int>(fpos));
              if (1 != scanf("%s", yesno))
              {
                  gmx_fatal(FARGS, "Error reading user input");
@@@ -402,14 -399,14 +402,14 @@@ int gmx_trjconv(int argc, char* argv[]
      const char* fit[efNR + 1] = { nullptr,       "none",    "rot+trans",   "rotxy+transxy",
                                    "translation", "transxy", "progressive", nullptr };
  
 -    static gmx_bool bSeparate = FALSE, bVels = TRUE, bForce = FALSE, bCONECT = FALSE;
 -    static gmx_bool bCenter = FALSE;
 -    static int      skip_nr = 1, ndec = 3, nzero = 0;
 -    static real     tzero = 0, delta_t = 0, timestep = 0, ttrunc = -1, tdump = -1, split_t = 0;
 -    static rvec     newbox = { 0, 0, 0 }, shift = { 0, 0, 0 }, trans = { 0, 0, 0 };
 -    static char*    exec_command = nullptr;
 -    static real     dropunder = 0, dropover = 0;
 -    static gmx_bool bRound = FALSE;
 +    gmx_bool bSeparate = FALSE, bVels = TRUE, bForce = FALSE, bCONECT = FALSE;
 +    gmx_bool bCenter = FALSE;
 +    int      skip_nr = 1, ndec = 3, nzero = 0;
 +    real     tzero = 0, delta_t = 0, timestep = 0, ttrunc = -1, tdump = -1, split_t = 0;
 +    rvec     newbox = { 0, 0, 0 }, shift = { 0, 0, 0 }, trans = { 0, 0, 0 };
 +    char*    exec_command = nullptr;
 +    real     dropunder = 0, dropover = 0;
 +    gmx_bool bRound = FALSE;
  
      t_pargs pa[] = {
          { "-skip", FALSE, etINT, { &skip_nr }, "Only write every nr-th frame" },
            FALSE,
            etBOOL,
            { &bCONECT },
-           "Add conect records when writing [REF].pdb[ref] files. Useful "
+           "Add CONECT PDB records when writing [REF].pdb[ref] files. Useful "
            "for visualization of non-standard molecules, e.g. "
            "coarse grained ones" }
      };
                         { efXVG, "-drop", "drop", ffOPTRD } };
  #define NFILE asize(fnm)
  
 -    if (!parse_common_args(&argc, argv, PCA_CAN_BEGIN | PCA_CAN_END | PCA_CAN_VIEW | PCA_TIME_UNIT,
 -                           NFILE, fnm, NPA, pa, asize(desc), desc, 0, nullptr, &oenv))
 +    if (!parse_common_args(&argc,
 +                           argv,
 +                           PCA_CAN_BEGIN | PCA_CAN_END | PCA_CAN_VIEW | PCA_TIME_UNIT,
 +                           NFILE,
 +                           fnm,
 +                           NPA,
 +                           pa,
 +                           asize(desc),
 +                           desc,
 +                           0,
 +                           nullptr,
 +                           &oenv))
      {
          return 0;
      }
                  fprintf(stderr,
                          "WARNING: Option for unitcell representation (-ur %s)\n"
                          "         only has effect in combination with -pbc %s, %s or %s.\n"
 -                        "         Ignoring unitcell representation.\n\n",
 -                        unitcell_opt[0], pbc_opt[2], pbc_opt[3], pbc_opt[4]);
 +                        "         Ingoring unitcell representation.\n\n",
 +                        unitcell_opt[0],
 +                        pbc_opt[2],
 +                        pbc_opt[3],
 +                        pbc_opt[4]);
              }
          }
          if (bFit && bPBC)
  
              if (0 == top->mols.nr && (bCluster || bPBCcomMol))
              {
 -                gmx_fatal(FARGS, "Option -pbc %s requires a .tpr file for the -s option",
 -                          pbc_opt[pbc_enum]);
 +                gmx_fatal(FARGS, "Option -pbc %s requires a .tpr file for the -s option", pbc_opt[pbc_enum]);
              }
  
              /* top_title is only used for gro and pdb,
                                    "Index[%d] %d is larger than the number of atoms in the\n"
                                    "trajectory file (%d). There is a mismatch in the contents\n"
                                    "of your -f, -s and/or -n files.",
 -                                  i, index[i] + 1, natoms);
 +                                  i,
 +                                  index[i] + 1,
 +                                  natoms);
                      }
                      bCopy = bCopy || (i != index[i]);
                  }
              switch (ftp)
              {
                  case efTNG:
 -                    trxout = trjtools_gmx_prepare_tng_writing(
 -                            out_file, filemode[0], trxin, nullptr, nout, mtop.get(),
 -                            gmx::arrayRefFromArray(index, nout), grpnm);
 +                    trxout = trjtools_gmx_prepare_tng_writing(out_file,
 +                                                              filemode[0],
 +                                                              trxin,
 +                                                              nullptr,
 +                                                              nout,
 +                                                              mtop.get(),
 +                                                              gmx::arrayRefFromArray(index, nout),
 +                                                              grpnm);
                      break;
                  case efXTC:
                  case efTRR:
  
                      if (bTDump)
                      {
 -                        fprintf(stderr, "\nDumping frame at t= %g %s\n",
 +                        fprintf(stderr,
 +                                "\nDumping frame at t= %g %s\n",
                                  output_env_conv_time(oenv, frout_time),
                                  output_env_get_time_unit(oenv).c_str());
                      }
                          else
                          {
                              /* round() is not C89 compatible, so we do this:  */
 -                            bDoIt = bRmod(std::floor(frout_time + 0.5), std::floor(tzero + 0.5),
 +                            bDoIt = bRmod(std::floor(frout_time + 0.5),
 +                                          std::floor(tzero + 0.5),
                                            std::floor(delta_t + 0.5));
                          }
                      }
                          /* print sometimes */
                          if (((outframe % SKIP) == 0) || (outframe < SKIP))
                          {
 -                            fprintf(stderr, " ->  frame %6d time %8.3f      \r", outframe,
 +                            fprintf(stderr,
 +                                    " ->  frame %6d time %8.3f      \r",
 +                                    outframe,
                                      output_env_conv_time(oenv, frout_time));
                              fflush(stderr);
                          }
                                      put_atoms_in_triclinic_unitcell(ecenter, fr.box, positionsArrayRef);
                                      break;
                                  case euCompact:
 -                                    put_atoms_in_compact_unitcell(pbcType, ecenter, fr.box,
 -                                                                  positionsArrayRef);
 +                                    put_atoms_in_compact_unitcell(
 +                                            pbcType, ecenter, fr.box, positionsArrayRef);
                                      break;
                              }
                          }
                          if (bPBCcomRes)
                          {
 -                            put_residue_com_in_box(unitcell_enum, ecenter, natoms, atoms->atom,
 -                                                   pbcType, fr.box, fr.x);
 +                            put_residue_com_in_box(
 +                                    unitcell_enum, ecenter, natoms, atoms->atom, pbcType, fr.box, fr.x);
                          }
                          if (bPBCcomMol)
                          {
 -                            put_molecule_com_in_box(unitcell_enum, ecenter, &top->mols, natoms,
 -                                                    atoms->atom, pbcType, fr.box, fr.x);
 +                            put_molecule_com_in_box(
 +                                    unitcell_enum, ecenter, &top->mols, natoms, atoms->atom, pbcType, fr.box, fr.x);
                          }
                          /* Copy the input trxframe struct to the output trxframe struct */
                          frout        = fr;
                              /* round() is not C89 compatible, so we do this: */
                              bSplitHere = bSplit
                                           && bRmod(std::floor(frout.time + 0.5),
 -                                                  std::floor(tzero + 0.5), std::floor(split_t + 0.5));
 +                                                  std::floor(tzero + 0.5),
 +                                                  std::floor(split_t + 0.5));
                          }
                          if (bSeparate || bSplitHere)
                          {
                                  switch (ftp)
                                  {
                                      case efGRO:
 -                                        write_hconf_p(out, title.c_str(), &useatoms, frout.x,
 -                                                      frout.bV ? frout.v : nullptr, frout.box);
 +                                        write_hconf_p(out,
 +                                                      title.c_str(),
 +                                                      &useatoms,
 +                                                      frout.x,
 +                                                      frout.bV ? frout.v : nullptr,
 +                                                      frout.box);
                                          break;
                                      case efPDB:
                                          fprintf(out, "REMARK    GENERATED BY TRJCONV\n");
                                          {
                                              model_nr++;
                                          }
 -                                        write_pdbfile(out, title.c_str(), &useatoms, frout.x,
 -                                                      frout.pbcType, frout.box, ' ', model_nr, gc);
 +                                        write_pdbfile(out,
 +                                                      title.c_str(),
 +                                                      &useatoms,
 +                                                      frout.x,
 +                                                      frout.pbcType,
 +                                                      frout.box,
 +                                                      ' ',
 +                                                      model_nr,
 +                                                      gc);
                                          break;
                                      case efG96:
                                          const char* outputTitle = "";
index a8ee771ca839e3596917d552d99926593e8604d1,d04b7131896c3985853904545d572b014b74a9a1..43d4156b3caa60ec3c064d8884cfa1bb5220f404
@@@ -295,7 -295,7 +295,7 @@@ std::string getCoolQuote(
          { "With a Lead Filled Snowshoe", "F. Zappa" },
          { "Right Between the Eyes", "F. Zappa" },
          { "BioBeat is Not Available In Regular Shops", "P.J. Meulenhoff" },
-         { "Rub It Right Accross Your Eyes", "F. Zappa" },
+         { "Rub It Right Across Your Eyes", "F. Zappa" },
          { "Shake Yourself", "YES" },
          { "I Am a Wonderful Thing", "Kid Creole" },
          { "Way to Go Dude", "Beavis and Butthead" },
          { "Disturb the Peace of a John Q Citizen", "Urban Dance Squad" },
          { "Wicky-wicky Wa-wild West", "Will Smith" },
          { "This is Tense !", "Star Wars Episode I The Phantom Menace" },
 -        { "Fly to the Court of England and Unfold",
 -          "Macbeth, Act 3, Scene 6, William Shakespeare" },
 +        { "Fly to the Court of England and Unfold", "Macbeth, Act 3, Scene 6, William Shakespeare" },
          { "Why, how now, Claudio ! Whence Comes this Restraint ?",
            "Lucio in Measure for measure, Act 1, Scene 4, William Shakespeare" },
          { "In the End Science Comes Down to Praying", "P. v.d. Berg" },
          { "Nobody Never Learnt No-Nothing from No History", "Gogol Bordello" },
          { "I'd be Safe and Warm if I was in L.A.", "The Mamas and the Papas" },
          { "It's Unacceptable That Chocolate Makes You Fat", "MI 3" },
 -        { "My Brothers are Protons (Protons!), My Sisters are Neurons (Neurons)",
 -          "Gogol Bordello" },
 +        { "My Brothers are Protons (Protons!), My Sisters are Neurons (Neurons)", "Gogol Bordello" },
          { "Put Me Inside SSC, Let's Test Superstring Theory, Oh Yoi Yoi Accelerate the Protons",
            "Gogol Bordello" },
          { "Do You Have Sex Maniacs or Schizophrenics or Astrophysicists in Your Family?",
          { "The scientific method is an integral part of human intelligence, and when it has once "
            "been set at work it can only be dismissed by dismissing the intelligence itself",
            "George H. Mead" },
 -        { "Der Ball ist rund, das Spiel dauert 90 minuten, alles andere ist Theorie",
 -          "Lola rennt" },
 +        { "Der Ball ist rund, das Spiel dauert 90 minuten, alles andere ist Theorie", "Lola rennt" },
          { "Life in the streets is not easy", "Marky Mark" },
          { "How will I know it's working right?", "MGMT" },
          { "There was no preconception on what to do", "Daft Punk" },
          { "All sorts of things can happen when you're open to new ideas and playing around with "
            "things.",
            "Stephanie Kwolek, inventor of Kevlar" },
 -        { "As always in life, people want a simple answer... and it's always wrong.",
 -          "Marie Daly" },
 +        { "As always in life, people want a simple answer... and it's always wrong.", "Marie Daly" },
          { "For a research worker the unforgotten moments of his life are those rare ones which "
            "come after years of plodding work, when the veil over natures secret seems suddenly to "
            "lift & when what was dark & chaotic appears in a clear & beautiful light & pattern.",
            "3-phosphoshikimate-carboxyvinyl transferase?' Shopkeeper: 'You mean Roundup?' "
            "Scientist: 'Yeah, that's it. I can never remember that dang name!'",
            "John Pickett" },
 -        { "It is not clear that intelligence has any long-term survival value.",
 -          "Stephen Hawking" },
 +        { "It is not clear that intelligence has any long-term survival value.", "Stephen Hawking" },
          { "The greatest shortcoming of the human race is our inability to understand the "
            "exponential function.",
            "Albert Bartlett" },
            "married, it never occurred to him to verify this statement by examining his wives' "
            "mouths.",
            "Bertrand Russell" },
 -        { "I had trouble with physics in college. When I signed up I thought it said psychics.",
 -          "Greg Tamblyn" },
          { "I don't believe in astrology; I'm a Sagittarian and we're skeptical.",
            "Arthur C. Clarke" },
          { "I see they found out the universe is 80 million years older than we thought. It's also "
            "attempted.",
            "Anonymous" },
          { "If my PhD doesn't allow me to be right on the internet, what is it even good for?",
 -          "Martin Vögele" }
 +          "Martin Vögele" },
 +        { "A little less conversation, a little more action, please.", "Elvis Presley" },
 +        { "Friends don't let friends use Berendsen!", "John Chodera (on Twitter)" }
      };
  
      if (beCool())