Merge branch release-2021
authorMark Abraham <mark.j.abraham@gmail.com>
Fri, 29 Jan 2021 07:57:31 +0000 (08:57 +0100)
committerMark Abraham <mark.j.abraham@gmail.com>
Fri, 29 Jan 2021 09:17:20 +0000 (10:17 +0100)
Resolved conflicts:

        admin/gitlab-ci/gromacs.gitlab-ci.yml
        admin/gitlab-ci/python-gmxapi01.gitlab-ci.yml
        api/nblib/gmxsetup.cpp
        api/nblib/listed_forces/gmxcalculator.cpp
        api/nblib/listed_forces/tests/calculator.cpp
        api/nblib/molecules.cpp
        api/nblib/molecules.h
        api/nblib/ppmap.h
        api/nblib/tests/integrator.cpp
        api/nblib/topologyhelpers.cpp
        docs/install-guide/index.rst
        python_packaging/src/setup.py
        src/gromacs/gmxpreprocess/pdb2gmx.cpp
        src/gromacs/gmxpreprocess/readir.cpp
        src/gromacs/gmxpreprocess/topdirs.cpp
        src/gromacs/math/densityfit.cpp
        src/gromacs/mdrun/md.cpp
        src/gromacs/mdrunutility/handlerestart.cpp
        src/gromacs/modularsimulator/simulatoralgorithm.cpp
        src/gromacs/nbnxm/cuda/nbnxm_cuda_data_mgmt.cu
        src/gromacs/nbnxm/nbnxm_gpu_data_mgmt.cpp
        src/gromacs/nbnxm/nbnxm_gpu_data_mgmt.h
        src/gromacs/nbnxm/opencl/nbnxm_ocl_data_mgmt.cpp
        src/gromacs/pulling/pull.cpp

Mostly from refactoring in master that clashed with fixes in
release-2021. Preserved the functionality from the latter in the form
of the former.

Some changes are simply due to the change to clang-format
configuration.

One CI configuration change for mdrun-only build is adapted, since
mdrun-only build has been removed.

63 files changed:
1  2 
.gitlab-ci.yml
CMakeLists.txt
admin/gitlab-ci/gromacs.matrix/gromacs.gcc-8-cuda-11.0-release.gitlab-ci.yml
admin/gitlab-ci/python-gmxapi01.gitlab-ci.yml
api/legacy/include/gromacs/fileio/tpxio.h
api/nblib/CMakeLists.txt
api/nblib/gmxsetup.cpp
api/nblib/interactions.cpp
api/nblib/listed_forces/tests/bondtypes.cpp
api/nblib/listed_forces/tests/calculator.cpp
api/nblib/listed_forces/tests/helpers.cpp
api/nblib/listed_forces/transformations.cpp
api/nblib/molecules.cpp
api/nblib/particlesequencer.cpp
api/nblib/samples/argon-forces-integration.cpp
api/nblib/samples/methane-water-integration.cpp
api/nblib/tests/integrator.cpp
api/nblib/tests/nbkernelsystem.cpp
api/nblib/tests/testsystems.cpp
api/nblib/tests/topology.cpp
api/nblib/topology.cpp
api/nblib/topologyhelpers.cpp
api/nblib/util/setup.cpp
cmake/gmxVersionInfo.cmake
docs/CMakeLists.txt
docs/install-guide/index.rst
docs/release-notes/2021/major/deprecated-functionality.rst
docs/release-notes/index.rst
docs/user-guide/mdp-options.rst
python_packaging/src/gmxapi/version.py
python_packaging/src/setup.py
src/gromacs/applied_forces/awh/biasstate.cpp
src/gromacs/applied_forces/electricfield.cpp
src/gromacs/fileio/checkpoint.cpp
src/gromacs/fileio/tpxio.cpp
src/gromacs/gmxana/gmx_msd.cpp
src/gromacs/gmxana/gmx_xpm2ps.cpp
src/gromacs/gmxpreprocess/pdb2gmx.cpp
src/gromacs/gmxpreprocess/readir.cpp
src/gromacs/gmxpreprocess/topdirs.cpp
src/gromacs/gmxpreprocess/topio.cpp
src/gromacs/hardware/device_management_sycl.cpp
src/gromacs/listed_forces/gpubonded_impl.cu
src/gromacs/listed_forces/gpubondedkernels.cu
src/gromacs/listed_forces/listed_forces.cpp
src/gromacs/math/densityfit.cpp
src/gromacs/mdlib/coupling.cpp
src/gromacs/mdlib/expanded.cpp
src/gromacs/mdrunutility/handlerestart.cpp
src/gromacs/modularsimulator/signallers.h
src/gromacs/modularsimulator/simulatoralgorithm.cpp
src/gromacs/nbnxm/cuda/nbnxm_cuda_data_mgmt.cu
src/gromacs/nbnxm/gpu_data_mgmt.h
src/gromacs/nbnxm/nbnxm_gpu_data_mgmt.cpp
src/gromacs/nbnxm/nbnxm_gpu_data_mgmt.h
src/gromacs/nbnxm/opencl/nbnxm_ocl_data_mgmt.cpp
src/gromacs/pulling/pull.cpp
src/gromacs/simd/impl_arm_sve/impl_arm_sve_simd_double.h
src/gromacs/simd/impl_arm_sve/impl_arm_sve_util_double.h
src/gromacs/taskassignment/resourcedivision.cpp
src/gromacs/topology/idef.cpp
src/gromacs/utility/binaryinformation.cpp
tests/CMakeLists.txt

diff --combined .gitlab-ci.yml
index 14a91b6e8cedb8655469925aa7c9b738ee731f2c,033b5eef4509306cc3d9dda19c6d5789bf80ea6e..61a480878599dc246589d664df588ebaf91bcba5
@@@ -42,9 -42,7 +42,9 @@@ include
    - local: '/admin/gitlab-ci/global.gitlab-ci.yml'
    - local: '/admin/gitlab-ci/rules.gitlab-ci.yml'
    # gmxapi Python package.
 -  - local: '/admin/gitlab-ci/python-gmxapi.gitlab-ci.yml'
 +  - local: '/admin/gitlab-ci/python-gmxapi01.gitlab-ci.yml'
 +  - local: '/admin/gitlab-ci/python-gmxapi02.gitlab-ci.yml'
 +  - local: '/admin/gitlab-ci/python-gmxapi03.gitlab-ci.yml'
    # Further API validation and usability of sample gmxapi extension package.
    - local: '/admin/gitlab-ci/sample_restraint.gitlab-ci.yml'
    # API regression testing using sample gmxapi extension package.
@@@ -53,6 -51,7 +53,7 @@@
    # To do: Consider expanding matrix here to improve transparency and reduce file sizes.
    # E.g. '/admin/gitlab-ci/matrix/clang-8.gitlab-ci.yml
    - local: '/admin/gitlab-ci/gromacs.gitlab-ci.yml'
+   - local: '/admin/gitlab-ci/gromacs.matrix.gitlab-ci.yml'
    # Repository cleanliness. Source tidiness, linting, and policy compliance.
    - local: '/admin/gitlab-ci/lint.gitlab-ci.yml'
    # Web page and manual.
diff --combined CMakeLists.txt
index 684ece050b17a362f467832972717cf5f4e51dfb,e4b6ffd4fb812240d2cdb231c165366184f34d2d..de9a8d67a185a5820943bca51be6856e47178093
@@@ -248,6 -248,8 +248,6 @@@ option(GMX_OPENMP "Enable OpenMP-based 
  
  option(GMX_USE_TNG "Use the TNG library for trajectory I/O" ON)
  
 -option(GMX_BUILD_MDRUN_ONLY "Build and install only the mdrun binary" OFF)
 -
  option(GMX_CYCLE_SUBCOUNTERS "Enable cycle subcounters to get a more detailed cycle timings" OFF)
  mark_as_advanced(GMX_CYCLE_SUBCOUNTERS)
  
@@@ -508,7 -510,7 +508,7 @@@ if (GMX_HWLOC
          message(FATAL_ERROR "HWLOC package support required, but not found.")
      endif()
  
-     if (HWLOC_VERSION VERSION_LESS "2")
+     if (HWLOC_FOUND AND HWLOC_VERSION VERSION_LESS "2")
          message(STATUS "Support for hwloc versions 1.x is deprecated")
      endif()
  
@@@ -607,7 -609,10 +607,7 @@@ gmx_add_cache_dependency(GMX_BUILD_UNIT
  # Our own GROMACS tests
  ########################################################################
  
 -include_directories(BEFORE ${CMAKE_SOURCE_DIR}/src)
  include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/src/external)
 -# Required for config.h, maybe should only be set in src/CMakeLists.txt
 -include_directories(BEFORE ${CMAKE_BINARY_DIR}/src)
  
  include(gmxTestInlineASM)
  gmx_test_inline_asm_gcc_x86(GMX_X86_GCC_INLINE_ASM)
@@@ -786,13 -791,15 +786,13 @@@ endif(
  option(GMX_PYTHON_PACKAGE "Configure gmxapi Python package" OFF)
  mark_as_advanced(GMX_PYTHON_PACKAGE)
  
 -if (NOT GMX_BUILD_MDRUN_ONLY)
 -    find_package(ImageMagick QUIET COMPONENTS convert)
 -    include(gmxTestImageMagick)
 -    GMX_TEST_IMAGEMAGICK(IMAGE_CONVERT_POSSIBLE)
 -    # TODO: Resolve circular dependency between docs, gromacs, and python_packaging
 -    add_subdirectory(docs)
 -    add_subdirectory(share)
 -    add_subdirectory(scripts)
 -endif()
 +find_package(ImageMagick QUIET COMPONENTS convert)
 +include(gmxTestImageMagick)
 +GMX_TEST_IMAGEMAGICK(IMAGE_CONVERT_POSSIBLE)
 +# TODO: Resolve circular dependency between docs, gromacs, and python_packaging
 +add_subdirectory(docs)
 +add_subdirectory(share)
 +add_subdirectory(scripts)
  add_subdirectory(api)
  add_subdirectory(src)
  
@@@ -800,7 -807,7 +800,7 @@@ if (BUILD_TESTING
      add_subdirectory(tests)
  endif()
  
 -if(GMX_PYTHON_PACKAGE AND NOT GMX_BUILD_MDRUN_ONLY)
 +if(GMX_PYTHON_PACKAGE)
      add_subdirectory(python_packaging)
  endif()
  
index 0000000000000000000000000000000000000000,b2e302b486f2706c250a513f242e79c3aa225c2e..0b9458e5636cd4c369dce5c900c27fc49e4dfd82
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,69 +1,67 @@@
 -# Test goal: GCC with newest CUDA; Mdrun-only build
++# Test goal: GCC with newest CUDA
+ # Test intents (should change rarely and conservatively):
+ #   OS: Ubuntu oldest supported
+ #   GPU: CUDA newest supported
+ #   HW: NVIDIA GPU
 -#   Features: Mdrun-only build
+ #   Scope: configure, build, unit tests
+ # Test implementation choices (free to change as needed):
+ #   OS: Ubuntu 18.04
+ #   Build type: RelWithAssert
+ #   Compiler: GCC 8
+ #   MPI: thread_MPI
+ #   GPU: CUDA 11.0
+ #   SIMD: AVX2_256
+ #   FFT: FFTW3
+ #   Parallelism nt/ntomp: 4/2 (unit tests)
+ gromacs:gcc-8-cuda-11.0:release:configure:
+   extends:
+     - .gromacs:base:release:configure
+     - .use-gcc:base
+     - .use-mpi
+     - .use-cuda
+     - .rules:nightly-only-for-release
+   image: ${CI_REGISTRY}/gromacs/gromacs/ci-ubuntu-18.04-gcc-8-cuda-11.0
+   variables:
+     CMAKE: /usr/local/cmake-3.15.7/bin/cmake
+     COMPILER_MAJOR_VERSION: 8
+     RELEASE_BUILD_DIR: release-builds-gcc
 -    CMAKE_EXTRA_OPTIONS: "-DGMX_BUILD_MDRUN_ONLY=ON"
+     CMAKE_BUILD_TYPE_OPTIONS : "-DCMAKE_BUILD_TYPE=RelWithAssert"
+     CMAKE_REGRESSIONTEST_OPTIONS: ""
+   dependencies:
+     - archive:package
+     - regressiontests:package
+     - prepare-release-version
+ gromacs:gcc-8-cuda-11.0:release:build:
+   extends:
+     - .variables:default
+     - .gromacs:base:build
+     - .before_script:default
+     - .use-ccache
+     - .rules:nightly-only-for-release
+   stage: release-build
+   variables:
+     CMAKE: /usr/local/cmake-3.15.7/bin/cmake
+     BUILD_DIR: release-builds-gcc
+   image: ${CI_REGISTRY}/gromacs/gromacs/ci-ubuntu-18.04-gcc-8-cuda-11.0
+   needs:
+     - job: gromacs:gcc-8-cuda-11.0:release:configure
+ gromacs:gcc-8-cuda-11.0:release:test:
+   extends:
+     - .gromacs:base:test
+     - .rules:nightly-only-for-release
+   stage: release-tests
+   image: ${CI_REGISTRY}/gromacs/gromacs/ci-ubuntu-18.04-gcc-8-cuda-11.0
+   variables:
+     CMAKE: /usr/local/cmake-3.15.7/bin/cmake
+     KUBERNETES_EXTENDED_RESOURCE_NAME: "nvidia.com/gpu"
+     KUBERNETES_EXTENDED_RESOURCE_LIMIT: 1
+     BUILD_DIR: release-builds-gcc
+   tags:
+     - k8s-scilifelab
+   needs:
+     - job: gromacs:gcc-8-cuda-11.0:release:configure
+     - job: gromacs:gcc-8-cuda-11.0:release:build
index 1038badd0b368183480475025192b17897310334,32de191be57460f81a52de89e0a5c1a710c76f04..32de191be57460f81a52de89e0a5c1a710c76f04
@@@ -73,3 -73,69 +73,69 @@@ gmxapi-0.1:clang-8:py-3.8.2
    variables:
      VENVPATH: "/root/venv/py3.8"
      PY_VER: "3.8.2"
+ .gmxapi-0.2:gcc-10:gmx2021:
+   extends:
+     - .variables:default
+     - .use-clang:base
+   image: ${CI_REGISTRY}/gromacs/gromacs/ci-ubuntu-20.04-gcc-10
+   stage: test
+   variables:
+     KUBERNETES_CPU_LIMIT: 2
+     KUBERNETES_CPU_REQUEST: 2
+     KUBERNETES_MEMORY_LIMIT: 2Gi
+     KUBERNETES_MEMORY_REQUEST: 2Gi
+     PY_UNIT_TEST_XML: $CI_PROJECT_DIR/py-JUnitTestResults.xml
+     PY_MPI_UNIT_TEST_XML: $CI_PROJECT_DIR/py-mpi-JUnitTestResults.xml
+     PY_ACCEPTANCE_TEST_XML: $CI_PROJECT_DIR/gmxapi-acceptance-JUnitTestResults.xml
+     PY_MPI_ACCEPTANCE_TEST_XML: $CI_PROJECT_DIR/gmxapi-acceptance-mpi-JUnitTestResults.xml
+   script:
+     - source $INSTALL_DIR/bin/GMXRC
+     - source $VENVPATH/bin/activate && INSTALL_DIR=$PWD/$INSTALL_DIR OMP_NUM_THREADS=1 bash admin/ci-scripts/build-and-test-py-gmxapi-0.2.sh
+   artifacts:
+     reports:
+       junit:
+         - $PY_UNIT_TEST_XML
+         - $PY_MPI_UNIT_TEST_XML
+         - $PY_ACCEPTANCE_TEST_XML
+         - $PY_MPI_ACCEPTANCE_TEST_XML
+     when: always
+     expire_in: 1 week
+   tags:
+     - k8s-scilifelab
+   # The dependency means we need to use the same tag restriction as upstream.
+   needs:
+     - job: gromacs:gcc-10:build
+       artifacts: true
+ gmxapi-0.2:gcc-10:gmx2021:py-3.6.10:
+   extends:
+     - .gmxapi-0.2:gcc-10:gmx2021
+     - .rules:merge-requests:release-2021
+   variables:
+     VENVPATH: "/root/venv/py3.6"
+     PY_VER: "3.6.10"
+ gmxapi-0.2:gcc-10:gmx2021:py-3.7.7:
+   extends:
+     - .gmxapi-0.2:gcc-10:gmx2021
+     - .rules:merge-requests:release-2021
+   variables:
+     VENVPATH: "/root/venv/py3.7"
+     PY_VER: "3.7.7"
+ gmxapi-0.2:gcc-10:gmx2021:py-3.8.2:
+   extends:
+     - .gmxapi-0.2:gcc-10:gmx2021
+     - .rules:merge-requests:release-2021
+   variables:
+     VENVPATH: "/root/venv/py3.8"
+     PY_VER: "3.8.2"
+ gmxapi-0.2:gcc-10:gmx2021:py-3.9.1:
+   extends:
+     - .gmxapi-0.2:gcc-10:gmx2021
+     - .rules:merge-requests:release-2021
+   variables:
+     VENVPATH: "/root/venv/py3.9"
+     PY_VER: "3.9.1"
index 5fc0bea2bce3b675c67b49982c45d42d541fb849,08229c62a4bcb7866b3c3d91352a41267850330b..08229c62a4bcb7866b3c3d91352a41267850330b
@@@ -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.
@@@ -63,6 -63,9 +63,9 @@@ class ArrayRef
   * \brief
   * First part of the TPR file structure containing information about
   * the general aspect of the system.
+  *
+  * When adding to or making breaking changes to reading this struct,
+  * update TpxGeneration.
   */
  struct TpxFileHeader
  {
diff --combined api/nblib/CMakeLists.txt
index 99fcfae3d5126ff8b58c1a386c4f6c8f805c7503,270755922c9e45c92920ab03ba4d9a7f52c728f5..fb700b2387de19fa49546be682edc21d103d998a
@@@ -1,7 -1,7 +1,7 @@@
  #
  # This file is part of the GROMACS molecular simulation package.
  #
- # Copyright (c) 2020, by the GROMACS development team, led by
+ # Copyright (c) 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.
@@@ -31,7 -31,7 +31,7 @@@
  #
  # To help us fund GROMACS development, we humbly ask that you cite
  # the research papers on the package. Check out http://www.gromacs.org.
- #
  # \author Victor Holanda <victor.holanda@cscs.ch>
  # \author Joe Jordan <ejjordan@kth.se>
  # \author Prashanth Kanduri <kanduri@cscs.ch>
@@@ -100,6 -100,7 +100,7 @@@ target_sources(nbli
          integrator.cpp
          interactions.cpp
          molecules.cpp
+         particlesequencer.cpp
          particletype.cpp
          simulationstate.cpp
          topologyhelpers.cpp
@@@ -111,11 -112,6 +112,11 @@@ gmx_target_compile_options(nblib
  target_link_libraries(nblib PRIVATE libgromacs)
  target_include_directories(nblib PRIVATE ${PROJECT_SOURCE_DIR}/api)
  include_directories(BEFORE ${CMAKE_SOURCE_DIR}/api)
 +target_link_libraries(nblib PRIVATE common)
 +# There are transitive dependencies on the legacy GROMACS headers.
 +target_link_libraries(nblib PUBLIC legacy_api)
 +# TODO: Explicitly link specific modules.
 +target_link_libraries(nblib PRIVATE legacy_modules)
  
  install(TARGETS nblib
          EXPORT nblib
@@@ -136,11 -132,10 +137,10 @@@ if(GMX_INSTALL_NBLIB_API
              molecules.h
              kerneloptions.h
              nblib.h
+             particlesequencer.h
              particletype.h
-             ppmap.h
              simulationstate.h
              topology.h
-             topologyhelpers.h
              DESTINATION include/nblib)
  endif()
  
diff --combined api/nblib/gmxsetup.cpp
index 0853099e7c73bc28a1d74084ae135924caf7935a,eb95e2fc15d6cfbffe602ad7a02f3f3a4a468820..362105448a080b7a6a930756b61837ba54ff8bd7
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -57,6 -57,7 +57,7 @@@
  #include "gromacs/nbnxm/pairsearch.h"
  #include "gromacs/pbcutil/pbc.h"
  #include "gromacs/utility/logger.h"
+ #include "gromacs/utility/listoflists.h"
  #include "gromacs/utility/smalloc.h"
  #include "nblib/exception.h"
  #include "nblib/kerneloptions.h"
@@@ -185,28 -186,24 +186,28 @@@ void NbvSetupUtil::setupNbnxmInstance(c
      Nbnxm::KernelSetup kernelSetup = getKernelSetup(options);
  
      PairlistParams pairlistParams(kernelSetup.kernelType, false, options.pairlistCutoff, false);
 -    Nbnxm::GridSet gridSet(PbcType::Xyz, false, nullptr, nullptr, pairlistParams.pairlistType,
 -                           false, numThreads, pinPolicy);
 -    auto           pairlistSets = std::make_unique<PairlistSets>(pairlistParams, false, 0);
 -    auto           pairSearch =
 -            std::make_unique<PairSearch>(PbcType::Xyz, false, nullptr, nullptr,
 -                                         pairlistParams.pairlistType, false, numThreads, pinPolicy);
 +    Nbnxm::GridSet gridSet(
 +            PbcType::Xyz, false, nullptr, nullptr, pairlistParams.pairlistType, false, numThreads, pinPolicy);
 +    auto pairlistSets = std::make_unique<PairlistSets>(pairlistParams, false, 0);
 +    auto pairSearch   = std::make_unique<PairSearch>(
 +            PbcType::Xyz, false, nullptr, nullptr, pairlistParams.pairlistType, false, numThreads, pinPolicy);
  
      auto atomData = std::make_unique<nbnxn_atomdata_t>(pinPolicy);
  
-     // Put everything together
-     auto nbv = std::make_unique<nonbonded_verlet_t>(
-             std::move(pairlistSets), std::move(pairSearch), std::move(atomData), kernelSetup, nullptr, nullWallcycle);
      // Needs to be called with the number of unique ParticleTypes
 -    nbnxn_atomdata_init(gmx::MDLogger(), atomData.get(), kernelSetup.kernelType, combinationRule,
 -                        numParticleTypes, nonbondedParameters_, 1, numThreads);
 +    nbnxn_atomdata_init(gmx::MDLogger(),
-                         nbv->nbat.get(),
++                        atomData.get(),
 +                        kernelSetup.kernelType,
 +                        combinationRule,
 +                        numParticleTypes,
 +                        nonbondedParameters_,
 +                        1,
 +                        numThreads);
  
 -    auto nbv = std::make_unique<nonbonded_verlet_t>(std::move(pairlistSets), std::move(pairSearch),
 -                                                    std::move(atomData), kernelSetup, nullptr,
 -                                                    nullWallcycle);
+     // Put everything together
++    auto nbv = std::make_unique<nonbonded_verlet_t>(
++            std::move(pairlistSets), std::move(pairSearch), std::move(atomData), kernelSetup, nullptr, nullWallcycle);
      gmxForceCalculator_->nbv_ = std::move(nbv);
  }
  
@@@ -268,8 -265,7 +269,8 @@@ void NbvSetupUtil::setupInteractionCons
          gmxForceCalculator_->interactionConst_->epsfac = 0;
      }
  
 -    calc_rffac(nullptr, gmxForceCalculator_->interactionConst_->epsilon_r,
 +    calc_rffac(nullptr,
 +               gmxForceCalculator_->interactionConst_->epsilon_r,
                 gmxForceCalculator_->interactionConst_->epsilon_rf,
                 gmxForceCalculator_->interactionConst_->rcoulomb,
                 &gmxForceCalculator_->interactionConst_->k_rf,
@@@ -302,10 -298,12 +303,12 @@@ void NbvSetupUtil::setParticlesOnGrid(c
      gmxForceCalculator_->setParticlesOnGrid(particleInfoAllVdw_, coordinates, box);
  }
  
- void NbvSetupUtil::constructPairList(const gmx::ListOfLists<int>& exclusions)
+ void NbvSetupUtil::constructPairList(ExclusionLists<int> exclusionLists)
  {
 -    gmxForceCalculator_->nbv_->constructPairlist(gmx::InteractionLocality::Local, exclusions, 0,
 -                                                 gmxForceCalculator_->nrnb_.get());
+     gmx::ListOfLists<int> exclusions(std::move(exclusionLists.ListRanges),
+                                      std::move(exclusionLists.ListElements));
 +    gmxForceCalculator_->nbv_->constructPairlist(
 +            gmx::InteractionLocality::Local, exclusions, 0, gmxForceCalculator_->nrnb_.get());
  }
  
  
@@@ -322,7 -320,7 +325,7 @@@ std::unique_ptr<GmxForceCalculator> Gmx
      nbvSetupUtil.setupStepWorkload(options);
      nbvSetupUtil.setupNbnxmInstance(system.topology().getParticleTypes().size(), options);
      nbvSetupUtil.setParticlesOnGrid(system.coordinates(), system.box());
-     nbvSetupUtil.constructPairList(system.topology().getGmxExclusions());
+     nbvSetupUtil.constructPairList(system.topology().exclusionLists());
      nbvSetupUtil.setAtomProperties(system.topology().getParticleTypeIdOfAllParticles(),
                                     system.topology().getCharges());
      nbvSetupUtil.setupForceRec(system.box().legacyMatrix());
index 9297521875cc933bab07c2950bd7968365478bb6,f97b01702e55c20d1e1ec886d9708374ac1dba19..461e26ff68dfe276014995d8c22a626bead5d950
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -47,7 -47,6 +47,6 @@@
  
  #include "nblib/exception.h"
  #include "nblib/interactions.h"
- #include "nblib/util/internal.h"
  
  namespace nblib
  {
@@@ -138,8 -137,7 +137,8 @@@ ParticleTypesInteractions& ParticleType
              std::string message = formatString(
                      "Attempting to add nonbonded interaction parameters between the particle types "
                      "{} {} twice",
 -                    particleTypeName1.value(), particleTypeName2.value());
 +                    particleTypeName1.value(),
 +                    particleTypeName2.value());
              throw InputException(message);
          }
      }
@@@ -164,8 -162,8 +163,8 @@@ NonBondedInteractionMap ParticleTypesIn
              C6  c6_combo{ combineNonbondedParameters(c6_1, c6_2, combinationRule_) };
              C12 c12_combo{ combineNonbondedParameters(c12_1, c12_2, combinationRule_) };
  
 -            nonbondedParameters_.setInteractions(particleType1.first, particleType2.first, c6_combo,
 -                                                 c12_combo);
 +            nonbondedParameters_.setInteractions(
 +                    particleType1.first, particleType2.first, c6_combo, c12_combo);
          }
      }
  
              if (nonbondedParameters_.count(interactionKey) == 0)
              {
                  std::string message = formatString("Missing interaction between {} {}",
 -                                                   particleTypeName1.value(), particleTypeName2.value());
 +                                                   particleTypeName1.value(),
 +                                                   particleTypeName2.value());
                  throw InputException(message);
              }
          }
@@@ -219,9 -216,7 +218,9 @@@ void ParticleTypesInteractions::merge(c
  
      for (const auto& keyval : other.twoParticlesInteractionsMap_)
      {
 -        add(std::get<0>(keyval.first), std::get<1>(keyval.first), std::get<0>(keyval.second),
 +        add(std::get<0>(keyval.first),
 +            std::get<1>(keyval.first),
 +            std::get<0>(keyval.second),
              std::get<1>(keyval.second));
      }
  }
index 31ddb4de4fe8864493e67a95648b5954c70a263e,22250f0f323c6d6b285f30044931d6424b373b3a..c26fb10acb05c4f436858e8f650a1155bcf9b006
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -42,7 -42,7 +42,7 @@@
   * \author Sebastian Keller <keller@cscs.ch>
   */
  #include "nblib/listed_forces/bondtypes.h"
- #include "nblib/util/internal.h"
+ #include "nblib/util/util.hpp"
  
  #include "testutils/testasserts.h"
  
@@@ -53,9 -53,8 +53,8 @@@ namespace test_detai
  {
  
  template<class B>
- void testTwoParameterBondEquality(const B& deduceType)
+ void testTwoParameterBondEquality([[maybe_unused]] const B& deduceType)
  {
-     ignore_unused(deduceType);
      B a(1, 2);
      B b(1, 2);
      EXPECT_TRUE(a == b);
@@@ -65,9 -64,8 +64,8 @@@
  }
  
  template<class B>
- void testThreeParameterBondEquality(const B& deduceType)
+ void testThreeParameterBondEquality([[maybe_unused]] const B& deduceType)
  {
-     ignore_unused(deduceType);
      B a(1, 2, 3);
      B b(1, 2, 3);
      EXPECT_TRUE(a == b);
@@@ -77,9 -75,8 +75,8 @@@
  }
  
  template<class B>
- void testTwoParameterBondLessThan(const B& deduceType)
+ void testTwoParameterBondLessThan([[maybe_unused]] const B& deduceType)
  {
-     ignore_unused(deduceType);
      B a(1, 2);
      B b(1, 3);
      EXPECT_TRUE(a < b);
@@@ -96,9 -93,8 +93,8 @@@
  }
  
  template<class B>
- void testThreeParameterBondLessThan(const B& deduceType)
+ void testThreeParameterBondLessThan([[maybe_unused]] const B& deduceType)
  {
-     ignore_unused(deduceType);
      B a(1, 2, 1);
      B b(1, 3, 1);
      EXPECT_TRUE(a < b);
  
  TEST(NBlibTest, BondTypesOperatorEqualWorks)
  {
 -    auto bondList3 = std::make_tuple(HarmonicBondType(), G96BondType(), FENEBondType(),
 -                                     HalfAttractiveQuarticBondType());
 +    auto bondList3 = std::make_tuple(
 +            HarmonicBondType(), G96BondType(), FENEBondType(), HalfAttractiveQuarticBondType());
      for_each_tuple([](const auto& b) { test_detail::testTwoParameterBondEquality(b); }, bondList3);
  
      auto bondList4 = std::make_tuple(CubicBondType(), MorseBondType());
  
  TEST(NBlibTest, BondTypesLessThanWorks)
  {
 -    auto bondList3 = std::make_tuple(HarmonicBondType(), G96BondType(), FENEBondType(),
 -                                     HalfAttractiveQuarticBondType());
 +    auto bondList3 = std::make_tuple(
 +            HarmonicBondType(), G96BondType(), FENEBondType(), HalfAttractiveQuarticBondType());
      for_each_tuple([](const auto& b) { test_detail::testTwoParameterBondLessThan(b); }, bondList3);
  
      auto bondList4 = std::make_tuple(CubicBondType(), MorseBondType());
index b1876581b8515167c2efca386ac40ee45b9584f2,247f926208f1a9353dda0b289377b35aa3923f8f..fe0c107cd5ff3b372ad6aef7ce783df312f13487
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -83,9 -83,7 +83,9 @@@ void compareVectors(const TestSeq
          for (int m = 0; m < dimSize; ++m)
          {
              EXPECT_FLOAT_DOUBLE_EQ_TOL(
 -                    forces[i][m], refForcesFloat[i][m], refForcesDouble[i][m],
 +                    forces[i][m],
 +                    refForcesFloat[i][m],
 +                    refForcesDouble[i][m],
                      // Todo: why does the tolerance need to be so low?
                      gmx::test::relativeToleranceAsFloatingPoint(refForcesDouble[i][m], 5e-5));
          }
@@@ -104,46 -102,20 +104,20 @@@ protected
          // one bond between atoms 0-1 with bond1 parameters and another between atoms 1-2 with bond2 parameters
          std::vector<InteractionIndex<HarmonicBondType>> bondIndices{ { 0, 1, 0 }, { 1, 2, 1 } };
  
-         DefaultAngle                                angle(Degrees(108.53), 397.5);
-         std::vector<DefaultAngle>                   angles{ angle };
-         std::vector<InteractionIndex<DefaultAngle>> angleIndices{ { 0, 1, 2, 0 } };
+         HarmonicAngleType                                angle(Degrees(108.53), 397.5);
+         std::vector<HarmonicAngleType>                   angles{ angle };
+         std::vector<InteractionIndex<HarmonicAngleType>> angleIndices{ { 0, 1, 2, 0 } };
  
          pickType<HarmonicBondType>(interactions).indices    = bondIndices;
          pickType<HarmonicBondType>(interactions).parameters = bonds;
  
-         pickType<DefaultAngle>(interactions).indices    = angleIndices;
-         pickType<DefaultAngle>(interactions).parameters = angles;
+         pickType<HarmonicAngleType>(interactions).indices    = angleIndices;
+         pickType<HarmonicAngleType>(interactions).parameters = angles;
  
          // initial position for the methanol atoms from the spc-water example
          x = std::vector<gmx::RVec>{ { 1.97, 1.46, 1.209 }, { 1.978, 1.415, 1.082 }, { 1.905, 1.46, 1.03 } };
          forces = std::vector<gmx::RVec>(3, gmx::RVec{ 0, 0, 0 });
  
-         refBondForcesFloat =
-                 std::valarray<gmx::BasicVector<float>>{ { -22.8980637, 128.801575, 363.505951 },
-                                                         { -43.2698593, -88.0130997, -410.639252 },
-                                                         { 66.167923, -40.788475, 47.1333084 } };
-         refAngleForcesFloat =
-                 std::valarray<gmx::BasicVector<float>>{ { 54.7276611, -40.1688995, 17.6805191 },
-                                                         { -81.8118973, 86.1988525, 60.1752243 },
-                                                         { 27.0842342, -46.0299492, -77.8557434 } };
-         refBondForcesDouble = std::valarray<gmx::BasicVector<double>>{
-             { -22.89764839974935, 128.79927224858977, 363.50016834602064 },
-             { -43.24622441913251, -88.025652017772231, -410.61635172385434 },
-             { 66.14387281888186, -40.773620230817542, 47.116183377833721 }
-         };
-         refAngleForcesDouble = std::valarray<gmx::BasicVector<double>>{
-             { 54.726206806506234, -40.167809526198099, 17.680008528590257 },
-             { -81.809781666748606, 86.196545126117257, 60.173723525141448 },
-             { 27.083574860242372, -46.028735599919159, -77.853732053731704 }
-         };
-         refBondEnergyFloat  = 0.2113433;
-         refAngleEnergyFloat = 0.112774156;
-         refBondEnergyDouble  = 0.2113273434867636;
-         refAngleEnergyDouble = 0.11276812148357591;
          box.reset(new Box(3, 3, 3));
          pbc.reset(new PbcHolder(*box));
      }
  
      std::shared_ptr<Box>       box;
      std::shared_ptr<PbcHolder> pbc;
+ };
  
-     // reference values
-     std::valarray<gmx::BasicVector<float>>  refBondForcesFloat, refAngleForcesFloat;
-     std::valarray<gmx::BasicVector<double>> refBondForcesDouble, refAngleForcesDouble;
+ TEST_F(ListedExampleData, ComputeHarmonicBondForces)
+ {
+     auto indices = pickType<HarmonicBondType>(interactions).indices;
+     auto bonds   = pickType<HarmonicBondType>(interactions).parameters;
+     computeForces(indices, bonds, x, &forces, *pbc);
  
-     float  refBondEnergyFloat, refAngleEnergyFloat;
-     double refBondEnergyDouble, refAngleEnergyDouble;
- };
+     Vector3DTest vector3DTest(1e-3);
+     vector3DTest.testVectors(forces, "Bond forces");
+ }
  
- TEST_F(ListedExampleData, DISABLED_ComputeHarmonicBondForces)
+ TEST_F(ListedExampleData, ComputeHarmonicBondEnergies)
  {
      auto indices = pickType<HarmonicBondType>(interactions).indices;
      auto bonds   = pickType<HarmonicBondType>(interactions).parameters;
      real energy  = computeForces(indices, bonds, x, &forces, *pbc);
  
-     EXPECT_FLOAT_DOUBLE_EQ_TOL(energy,
-                                refBondEnergyFloat,
-                                refBondEnergyDouble,
-                                gmx::test::relativeToleranceAsFloatingPoint(refBondEnergyDouble, 1e-5));
-     compareVectors(forces, refBondForcesFloat, refBondForcesDouble);
+     Vector3DTest vector3DTest(1e-4);
+     vector3DTest.testReal(energy, "Bond energy");
  }
  
  TEST_F(ListedExampleData, ComputeHarmonicAngleForces)
  {
-     auto indices = pickType<DefaultAngle>(interactions).indices;
-     auto angles  = pickType<DefaultAngle>(interactions).parameters;
-     real energy  = computeForces(indices, angles, x, &forces, *pbc);
+     auto indices = pickType<HarmonicAngleType>(interactions).indices;
+     auto angles  = pickType<HarmonicAngleType>(interactions).parameters;
+     computeForces(indices, angles, x, &forces, *pbc);
+     Vector3DTest vector3DTest(1e-4);
+     vector3DTest.testVectors(forces, "Angle forces");
+ }
  
-     EXPECT_FLOAT_DOUBLE_EQ_TOL(energy,
-                                refAngleEnergyFloat,
-                                refAngleEnergyDouble,
-                                gmx::test::relativeToleranceAsFloatingPoint(refAngleEnergyDouble, 1e-5));
+ TEST_F(ListedExampleData, CanReduceForces)
+ {
+     reduceListedForces(interactions, x, &forces, *pbc);
  
-     compareVectors(forces, refAngleForcesFloat, refAngleForcesDouble);
+     Vector3DTest vector3DTest(1e-2);
+     vector3DTest.testVectors(forces, "Reduced forces");
  }
  
- TEST_F(ListedExampleData, DISABLED_CanReduceForces)
+ TEST_F(ListedExampleData, CanReduceEnergies)
  {
      auto energies    = reduceListedForces(interactions, x, &forces, *pbc);
      real totalEnergy = std::accumulate(begin(energies), end(energies), 0.0);
  
-     EXPECT_FLOAT_DOUBLE_EQ_TOL(totalEnergy,
-                                refBondEnergyFloat + refAngleEnergyFloat,
-                                refBondEnergyDouble + refAngleEnergyDouble,
-                                gmx::test::relativeToleranceAsFloatingPoint(refBondEnergyDouble, 1e-5));
-     compareVectors(forces, refBondForcesFloat + refAngleForcesFloat, refBondForcesDouble + refAngleForcesDouble);
+     Vector3DTest vector3DTest(1e-4);
+     vector3DTest.testReal(totalEnergy, "Reduced energy");
  }
  
  
@@@ -211,8 -182,7 +184,8 @@@ void compareArray(const ListedForceCalc
  {
      for (size_t i = 0; i < energies.size(); ++i)
      {
 -        EXPECT_REAL_EQ_TOL(energies[i], refEnergies[i],
 +        EXPECT_REAL_EQ_TOL(energies[i],
 +                           refEnergies[i],
                             gmx::test::relativeToleranceAsFloatingPoint(refEnergies[i], 1e-5));
      }
  }
index 47236951d838adb1feed0172f6d45b9e48f28ecd,917d84be5984023a220985a3e3f75237cdcea286..87f16ff9f04e1848067021646d36dc8263e53e42
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -62,8 -62,8 +62,8 @@@ TEST(NBlibTest, CanSplitListedWork
  {
      ListedInteractionData interactions;
  
-     DefaultAngle     angle(Degrees(1), 1);
-     HarmonicBondType bond(1, 1);
+     HarmonicAngleType angle(Degrees(1), 1);
+     HarmonicBondType  bond(1, 1);
  
      int largestIndex = 20;
      int nSplits      = 3; // split ranges: [0,5], [6,11], [12, 19]
      std::vector<InteractionIndex<HarmonicBondType>> bondIndices{
          { 0, 1, 0 }, { 0, 6, 0 }, { 11, 12, 0 }, { 18, 19, 0 }
      };
-     std::vector<InteractionIndex<DefaultAngle>> angleIndices{
+     std::vector<InteractionIndex<HarmonicAngleType>> angleIndices{
          { 0, 1, 2, 0 }, { 0, 6, 7, 0 }, { 11, 12, 13, 0 }, { 17, 19, 18, 0 }
      };
  
-     pickType<HarmonicBondType>(interactions).indices = bondIndices;
-     pickType<DefaultAngle>(interactions).indices     = angleIndices;
+     pickType<HarmonicBondType>(interactions).indices  = bondIndices;
+     pickType<HarmonicAngleType>(interactions).indices = angleIndices;
  
      std::vector<ListedInteractionData> splitInteractions =
              splitListedWork(interactions, largestIndex, nSplits);
  
      std::vector<InteractionIndex<HarmonicBondType>> refBondIndices0{ { 0, 1, 0 }, { 0, 6, 0 } };
-     std::vector<InteractionIndex<DefaultAngle>> refAngleIndices0{ { 0, 1, 2, 0 }, { 0, 6, 7, 0 } };
-     std::vector<InteractionIndex<HarmonicBondType>> refBondIndices1{ { 11, 12, 0 } };
-     std::vector<InteractionIndex<DefaultAngle>>     refAngleIndices1{ { 11, 12, 13, 0 } };
-     std::vector<InteractionIndex<HarmonicBondType>> refBondIndices2{ { 18, 19, 0 } };
-     std::vector<InteractionIndex<DefaultAngle>>     refAngleIndices2{ { 17, 19, 18, 0 } };
+     std::vector<InteractionIndex<HarmonicAngleType>> refAngleIndices0{ { 0, 1, 2, 0 }, { 0, 6, 7, 0 } };
+     std::vector<InteractionIndex<HarmonicBondType>>  refBondIndices1{ { 11, 12, 0 } };
+     std::vector<InteractionIndex<HarmonicAngleType>> refAngleIndices1{ { 11, 12, 13, 0 } };
+     std::vector<InteractionIndex<HarmonicBondType>>  refBondIndices2{ { 18, 19, 0 } };
+     std::vector<InteractionIndex<HarmonicAngleType>> refAngleIndices2{ { 17, 19, 18, 0 } };
  
      EXPECT_EQ(refBondIndices0, pickType<HarmonicBondType>(splitInteractions[0]).indices);
      EXPECT_EQ(refBondIndices1, pickType<HarmonicBondType>(splitInteractions[1]).indices);
      EXPECT_EQ(refBondIndices2, pickType<HarmonicBondType>(splitInteractions[2]).indices);
  
-     EXPECT_EQ(refAngleIndices0, pickType<DefaultAngle>(splitInteractions[0]).indices);
-     EXPECT_EQ(refAngleIndices1, pickType<DefaultAngle>(splitInteractions[1]).indices);
-     EXPECT_EQ(refAngleIndices2, pickType<DefaultAngle>(splitInteractions[2]).indices);
+     EXPECT_EQ(refAngleIndices0, pickType<HarmonicAngleType>(splitInteractions[0]).indices);
+     EXPECT_EQ(refAngleIndices1, pickType<HarmonicAngleType>(splitInteractions[1]).indices);
+     EXPECT_EQ(refAngleIndices2, pickType<HarmonicAngleType>(splitInteractions[2]).indices);
  }
  
  
@@@ -131,8 -131,8 +131,8 @@@ TEST(NBlibTest, ListedForceBuffer
      {
          for (size_t m = 0; m < dimSize; ++m)
          {
 -            EXPECT_REAL_EQ_TOL(refMasterBuffer[i][m], masterBuffer[i][m],
 -                               gmx::test::defaultRealTolerance());
 +            EXPECT_REAL_EQ_TOL(
 +                    refMasterBuffer[i][m], masterBuffer[i][m], gmx::test::defaultRealTolerance());
          }
      }
  
index fc039575937f7c186ed2ac34cde3157e1ea3f67c,f16970ada2590f2af8153b9f9adc858f5999ca30..f6e92ac86864b7fb1b149e01c0601d24c2cda6ca
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 2020,2021, by the GROMACS development team, led by
   * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
   * and including many others, as listed in the AUTHORS file in the
   * top-level source directory and at http://www.gromacs.org.
   */
  #include "nblib/listed_forces/transformations.h"
  
- #include "nblib/util/internal.h"
+ #include "nblib/util/util.hpp"
  
  namespace nblib
  {
  
  void sortInteractions(ListedInteractionData& interactions)
  {
-     auto sortKeyObj = [](const auto& lhs, const auto& rhs) { return interactionSortKey(lhs, rhs); };
-     auto sortOneElement = [comparison = sortKeyObj](auto& interactionElement) {
-         std::sort(begin(interactionElement.indices), end(interactionElement.indices), comparison);
+     auto sortOneElement = [](auto& interactionElement) {
+         using InteractionContainerType = std::decay_t<decltype(interactionElement)>;
+         using InteractionType          = typename InteractionContainerType::type;
 -        std::sort(begin(interactionElement.indices), end(interactionElement.indices),
++        std::sort(begin(interactionElement.indices),
++                  end(interactionElement.indices),
+                   interactionSortKey<InteractionType>);
      };
  
      for_each_tuple(sortOneElement, interactions);
diff --combined api/nblib/molecules.cpp
index 679d888cd160ac2a7026297bddada1db3522b07c,db3772039a35d67137c4a3959b806e369d3faa93..9b243bf6cdae5430f6a29b5b01a85e19a50ffbe5
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 2020,2021, by the GROMACS development team, led by
   * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
   * and including many others, as listed in the AUTHORS file in the
   * top-level source directory and at http://www.gromacs.org.
  #include "nblib/exception.h"
  #include "nblib/molecules.h"
  #include "nblib/particletype.h"
- #include "nblib/util/internal.h"
  
  namespace nblib
  {
  
  Molecule::Molecule(MoleculeName moleculeName) : name_(std::move(moleculeName)) {}
  
  MoleculeName Molecule::name() const
@@@ -115,139 -113,114 +113,135 @@@ Molecule& Molecule::addParticle(const P
      return *this;
  }
  
- //! Two-particle interactions such as bonds and LJ1-4
- template<class Interaction>
- void Molecule::addInteraction(const ParticleName& particleNameI,
-                               const ResidueName&  residueNameI,
-                               const ParticleName& particleNameJ,
-                               const ResidueName&  residueNameJ,
-                               const Interaction&  interaction)
+ ResidueName Molecule::residueName(const ParticleIdentifier& particleIdentifier)
+ {
+     return (particleIdentifier.residueName() == ResidueName{}) ? ResidueName(name_)
+                                                                : particleIdentifier.residueName();
+ }
+ template<class ListedVariant, class... ParticleIdentifiers>
+ void Molecule::addInteractionImpl(const ListedVariant& interaction, const ParticleIdentifiers&... particles)
  {
-     if (particleNameI == particleNameJ and residueNameI == residueNameJ)
+     auto storeInteraction = [&](const auto& interaction_) {
+         using Interaction = std::decay_t<decltype(interaction_)>;
+         auto& interactionContainer = pickType<Interaction>(interactionData_);
+         interactionContainer.interactions_.emplace_back(particles...);
+         interactionContainer.interactionTypes_.push_back(interaction_);
+     };
+     // add the interaction to the correct location in interactionData_
+     std::visit(storeInteraction, interaction);
+ }
+ void Molecule::addInteraction(const ParticleIdentifier&   particleI,
+                               const ParticleIdentifier&   particleJ,
+                               const TwoCenterInteraction& interaction)
+ {
+     if (particleI == particleJ)
      {
          throw InputException(std::string("Cannot add interaction of particle ")
-                              + particleNameI.value() + " with itself in molecule " + name_.value());
+                              + particleI.particleName().value() + " with itself in molecule "
+                              + name_.value());
      }
  
-     auto& interactionContainer = pickType<Interaction>(interactionData_);
-     interactionContainer.interactions_.emplace_back(particleNameI, residueNameI, particleNameJ, residueNameJ);
-     interactionContainer.interactionTypes_.push_back(interaction);
 -    addInteractionImpl(interaction, particleI.particleName(), residueName(particleI),
 -                       particleJ.particleName(), residueName(particleJ));
++    addInteractionImpl(interaction,
++                       particleI.particleName(),
++                       residueName(particleI),
++                       particleJ.particleName(),
++                       residueName(particleJ));
  }
  
- //! \cond DO_NOT_DOCUMENT
- #define ADD_INTERACTION_INSTANTIATE_TEMPLATE(x)                               \
-     template void Molecule::addInteraction(const ParticleName& particleNameI, \
-                                            const ResidueName&  residueNameI,  \
-                                            const ParticleName& particleNameJ, \
-                                            const ResidueName&  residueNameJ,  \
-                                            const x&            interaction);
- MAP(ADD_INTERACTION_INSTANTIATE_TEMPLATE, SUPPORTED_TWO_CENTER_TYPES)
- #undef ADD_INTERACTION_INSTANTIATE_TEMPLATE
- //! \endcond
- // add interactions with default residue name
- template<class Interaction>
- void Molecule::addInteraction(const ParticleName& particleNameI,
-                               const ParticleName& particleNameJ,
-                               const Interaction&  interaction)
+ void Molecule::addInteraction(const ParticleIdentifier&     particleI,
+                               const ParticleIdentifier&     particleJ,
+                               const ParticleIdentifier&     particleK,
+                               const ThreeCenterInteraction& interaction)
  {
-     addInteraction(particleNameI, ResidueName(name_), particleNameJ, ResidueName(name_), interaction);
+     if (particleI == particleJ and particleJ == particleK)
+     {
+         throw InputException(std::string("Cannot add interaction of particle ")
+                              + particleI.particleName().value() + " with itself in molecule "
+                              + name_.value());
+     }
 -    addInteractionImpl(interaction, particleI.particleName(), residueName(particleI),
 -                       particleJ.particleName(), residueName(particleJ), particleK.particleName(),
++    addInteractionImpl(interaction,
++                       particleI.particleName(),
++                       residueName(particleI),
++                       particleJ.particleName(),
++                       residueName(particleJ),
++                       particleK.particleName(),
+                        residueName(particleK));
  }
  
- //! \cond DO_NOT_DOCUMENT
- #define ADD_INTERACTION_INSTANTIATE_TEMPLATE(x) \
-     template void Molecule::addInteraction(     \
-             const ParticleName& particleNameI, const ParticleName& particleNameJ, const x& interaction);
- MAP(ADD_INTERACTION_INSTANTIATE_TEMPLATE, SUPPORTED_TWO_CENTER_TYPES)
- #undef ADD_INTERACTION_INSTANTIATE_TEMPLATE
- //! 3-particle interactions such as angles
- template<class Interaction>
- void Molecule::addInteraction(const ParticleName& particleNameI,
-                               const ResidueName&  residueNameI,
-                               const ParticleName& particleNameJ,
-                               const ResidueName&  residueNameJ,
-                               const ParticleName& particleNameK,
-                               const ResidueName&  residueNameK,
-                               const Interaction&  interaction)
+ void Molecule::addInteraction(const ParticleIdentifier&    particleI,
+                               const ParticleIdentifier&    particleJ,
+                               const ParticleIdentifier&    particleK,
+                               const ParticleIdentifier&    particleL,
+                               const FourCenterInteraction& interaction)
  {
-     if (particleNameI == particleNameJ and particleNameJ == particleNameK)
+     if (particleI == particleJ and particleJ == particleK and particleK == particleL)
      {
          throw InputException(std::string("Cannot add interaction of particle ")
-                              + particleNameI.value() + " with itself in molecule " + name_.value());
+                              + particleI.particleName().value() + " with itself in molecule "
+                              + name_.value());
      }
  
-     auto& interactionContainer = pickType<Interaction>(interactionData_);
-     interactionContainer.interactions_.emplace_back(
-             particleNameI, residueNameI, particleNameJ, residueNameJ, particleNameK, residueNameK);
-     interactionContainer.interactionTypes_.push_back(interaction);
 -    addInteractionImpl(interaction, particleI.particleName(), residueName(particleI),
 -                       particleJ.particleName(), residueName(particleJ), particleK.particleName(),
 -                       residueName(particleK), particleL.particleName(), residueName(particleL));
++    addInteractionImpl(interaction,
++                       particleI.particleName(),
++                       residueName(particleI),
++                       particleJ.particleName(),
++                       residueName(particleJ),
++                       particleK.particleName(),
++                       residueName(particleK),
++                       particleL.particleName(),
++                       residueName(particleL));
  }
  
- #define ADD_INTERACTION_INSTANTIATE_TEMPLATE(x)                               \
-     template void Molecule::addInteraction(const ParticleName& particleNameI, \
-                                            const ResidueName&  residueNameI,  \
-                                            const ParticleName& particleNameJ, \
-                                            const ResidueName&  residueNameJ,  \
-                                            const ParticleName& particleNameK, \
-                                            const ResidueName&  residueNameK,  \
-                                            const x&            interaction);
- MAP(ADD_INTERACTION_INSTANTIATE_TEMPLATE, SUPPORTED_THREE_CENTER_TYPES)
- #undef ADD_INTERACTION_INSTANTIATE_TEMPLATE
- template<class Interaction>
- void Molecule::addInteraction(const ParticleName& particleNameI,
-                               const ParticleName& particleNameJ,
-                               const ParticleName& particleNameK,
-                               const Interaction&  interaction)
+ void Molecule::addInteraction(const ParticleIdentifier&    particleI,
+                               const ParticleIdentifier&    particleJ,
+                               const ParticleIdentifier&    particleK,
+                               const ParticleIdentifier&    particleL,
+                               const ParticleIdentifier&    particleM,
+                               const FiveCenterInteraction& interaction)
  {
-     addInteraction(particleNameI,
-                    ResidueName(name_),
-                    particleNameJ,
-                    ResidueName(name_),
-                    particleNameK,
-                    ResidueName(name_),
-                    interaction);
+     if (particleI == particleJ and particleJ == particleK and particleK == particleL and particleL == particleM)
+     {
+         throw InputException(std::string("Cannot add interaction of particle ")
+                              + particleI.particleName().value() + " with itself in molecule "
+                              + name_.value());
+     }
 -    addInteractionImpl(interaction, particleI.particleName(), residueName(particleI),
 -                       particleJ.particleName(), residueName(particleJ), particleK.particleName(),
 -                       residueName(particleK), particleL.particleName(), residueName(particleL),
 -                       particleM.particleName(), residueName(particleM));
++    addInteractionImpl(interaction,
++                       particleI.particleName(),
++                       residueName(particleI),
++                       particleJ.particleName(),
++                       residueName(particleJ),
++                       particleK.particleName(),
++                       residueName(particleK),
++                       particleL.particleName(),
++                       residueName(particleL),
++                       particleM.particleName(),
++                       residueName(particleM));
  }
  
- #define ADD_INTERACTION_INSTANTIATE_TEMPLATE(x)                               \
-     template void Molecule::addInteraction(const ParticleName& particleNameI, \
-                                            const ParticleName& particleNameJ, \
-                                            const ParticleName& particleNameK, \
-                                            const x&            interaction);
- MAP(ADD_INTERACTION_INSTANTIATE_TEMPLATE, SUPPORTED_THREE_CENTER_TYPES)
- #undef ADD_INTERACTION_INSTANTIATE_TEMPLATE
- //! \endcond
  
  int Molecule::numParticlesInMolecule() const
  {
      return particles_.size();
  }
  
- void Molecule::addExclusion(const int particleIndex, const int particleIndexToExclude)
+ void Molecule::addExclusion(const ParticleIdentifier& particle, const ParticleIdentifier& particleToExclude)
  {
-     // We do not need to add exclusion in case the particle indexes are the same
-     // because self exclusion are added by addParticle
-     if (particleIndex != particleIndexToExclude)
+     if (particle == particleToExclude)
      {
-         exclusions_.emplace_back(particleIndex, particleIndexToExclude);
-         exclusions_.emplace_back(particleIndexToExclude, particleIndex);
+         return;
      }
- }
  
- void Molecule::addExclusion(std::tuple<ParticleName, ResidueName> particle,
-                             std::tuple<ParticleName, ResidueName> particleToExclude)
- {
      // duplication for the swapped pair happens in getExclusions()
-     exclusionsByName_.emplace_back(std::make_tuple(std::get<0>(particle),
-                                                    std::get<1>(particle),
-                                                    std::get<0>(particleToExclude),
-                                                    std::get<1>(particleToExclude)));
- }
- void Molecule::addExclusion(const ParticleName& particleName, const ParticleName& particleNameToExclude)
- {
-     addExclusion(std::make_tuple(particleName, ResidueName(name_)),
-                  std::make_tuple(particleNameToExclude, ResidueName(name_)));
 -    exclusionsByName_.emplace_back(std::make_tuple(particle.particleName(), residueName(particle),
++    exclusionsByName_.emplace_back(std::make_tuple(particle.particleName(),
++                                                   residueName(particle),
+                                                    particleToExclude.particleName(),
+                                                    residueName(particleToExclude)));
  }
  
  const Molecule::InteractionTuple& Molecule::interactionData() const
@@@ -307,10 -280,8 +301,10 @@@ std::vector<std::tuple<int, int>> Molec
          const std::string& residueName2  = std::get<3>(tup);
  
          // look up first index (binary search)
 -        auto it1 = std::lower_bound(std::begin(indexKey), std::end(indexKey),
 -                                    std::make_tuple(particleName1, residueName2, 0), sortKey);
 +        auto it1 = std::lower_bound(std::begin(indexKey),
 +                                    std::end(indexKey),
 +                                    std::make_tuple(particleName1, residueName2, 0),
 +                                    sortKey);
  
          // make sure we have the (particleName,residueName) combo
          if (it1 == std::end(indexKey) or std::get<0>(*it1) != particleName1 or std::get<1>(*it1) != residueName1)
          int firstIndex = std::get<2>(*it1);
  
          // look up second index (binary search)
 -        auto it2 = std::lower_bound(std::begin(indexKey), std::end(indexKey),
 -                                    std::make_tuple(particleName2, residueName2, 0), sortKey);
 +        auto it2 = std::lower_bound(std::begin(indexKey),
 +                                    std::end(indexKey),
 +                                    std::make_tuple(particleName2, residueName2, 0),
 +                                    sortKey);
  
          // make sure we have the (particleName,residueName) combo
          if (it2 == std::end(indexKey) or std::get<0>(*it2) != particleName2 or std::get<1>(*it2) != residueName2)
index 0000000000000000000000000000000000000000,fb97ac298e2245bdfb1ad191bd20cf19a6f88d59..7bb98bed165eaf963b75cd4cdf9ffa7eb4e628c7
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,102 +1,105 @@@
 -            printf("No particle %s in residue %s in molecule %s found\n", particleName.value().c_str(),
 -                   residueName.value().c_str(), moleculeName.value().c_str());
+ /*
+  * This file is part of the GROMACS molecular simulation package.
+  *
+  * Copyright (c) 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.
+  *
+  * GROMACS is free software; you can redistribute it and/or
+  * modify it under the terms of the GNU Lesser General Public License
+  * as published by the Free Software Foundation; either version 2.1
+  * of the License, or (at your option) any later version.
+  *
+  * GROMACS is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with GROMACS; if not, see
+  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+  *
+  * If you want to redistribute modifications to GROMACS, please
+  * consider that scientific software is very special. Version
+  * control is crucial - bugs must be traceable. We will be happy to
+  * consider code for inclusion in the official distribution, but
+  * derived work must not be called official GROMACS. Details are found
+  * in the README & COPYING files - if they are missing, get the
+  * official version at http://www.gromacs.org.
+  *
+  * To help us fund GROMACS development, we humbly ask that you cite
+  * the research papers on the package. Check out http://www.gromacs.org.
+  */
+ /*! \internal \file
+  * \brief
+  * Implements ParticleSequencer class
+  *
+  * \author Victor Holanda <victor.holanda@cscs.ch>
+  * \author Joe Jordan <ejjordan@kth.se>
+  * \author Prashanth Kanduri <kanduri@cscs.ch>
+  * \author Sebastian Keller <keller@cscs.ch>
+  * \author Artem Zhmurov <zhmurov@gmail.com>
+  */
+ #include <algorithm>
+ #include "nblib/exception.h"
+ #include "nblib/particlesequencer.h"
+ namespace nblib
+ {
+ int ParticleSequencer::operator()(const MoleculeName& moleculeName,
+                                   int                 moleculeNr,
+                                   const ResidueName&  residueName,
+                                   const ParticleName& particleName) const
+ {
+     try
+     {
+         return data_.at(moleculeName).at(moleculeNr).at(residueName).at(particleName);
+     }
+     catch (const std::out_of_range& outOfRange)
+     {
+         // TODO: use string format function once we have it
+         if (moleculeName.value() == residueName.value())
+         {
 -            printf("No particle %s in molecule %s found\n", particleName.value().c_str(),
++            printf("No particle %s in residue %s in molecule %s found\n",
++                   particleName.value().c_str(),
++                   residueName.value().c_str(),
++                   moleculeName.value().c_str());
+         }
+         else
+         {
++            printf("No particle %s in molecule %s found\n",
++                   particleName.value().c_str(),
+                    moleculeName.value().c_str());
+         }
+         throw InputException(outOfRange.what());
+     }
+ }
+ void ParticleSequencer::build(const std::vector<std::tuple<Molecule, int>>& moleculesList)
+ {
+     int currentID = 0;
+     for (const auto& molNumberTuple : moleculesList)
+     {
+         const Molecule& molecule = std::get<0>(molNumberTuple);
+         const size_t    numMols  = std::get<1>(molNumberTuple);
+         auto& moleculeMap = data_[molecule.name()];
+         for (size_t i = 0; i < numMols; ++i)
+         {
+             auto& moleculeNrMap = moleculeMap[i];
+             for (int j = 0; j < molecule.numParticlesInMolecule(); ++j)
+             {
+                 moleculeNrMap[molecule.residueName(j)][molecule.particleName(j)] = currentID++;
+             }
+         }
+     }
+ }
+ } // namespace nblib
index 45e2d922f91370f77a3d3660fdce0476b7006146,b54996d6af04f5138c8203653601e9d3c62cabc6..75416ea5df2a53a71950168cace5b1952346077d
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 2020,2021, by the GROMACS development team, led by
   * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
   * and including many others, as listed in the AUTHORS file in the
   * top-level source directory and at http://www.gromacs.org.
  
  #include <cstdio>
  
+ #include "gromacs/utility/arrayref.h"
  // The entire nblib public API can be included with a single header or individual components
  // can be included via their respective headers.
  #include "nblib/nblib.h"
  
- // User defined coordinates.
- std::vector<nblib::Vec3> coordinates = {
-     { 0.794, 1.439, 0.610 }, { 1.397, 0.673, 1.916 }, { 0.659, 1.080, 0.573 },
-     { 1.105, 0.090, 3.431 }, { 1.741, 1.291, 3.432 }, { 1.936, 1.441, 5.873 },
-     { 0.960, 2.246, 1.659 }, { 0.382, 3.023, 2.793 }, { 0.053, 4.857, 4.242 },
-     { 2.655, 5.057, 2.211 }, { 4.114, 0.737, 0.614 }, { 5.977, 5.104, 5.217 },
- };
- // User defined velocities.
- std::vector<nblib::Vec3> velocities = {
-     { 0.0055, -0.1400, 0.2127 },   { 0.0930, -0.0160, -0.0086 }, { 0.1678, 0.2476, -0.0660 },
-     { 0.1591, -0.0934, -0.0835 },  { -0.0317, 0.0573, 0.1453 },  { 0.0597, 0.0013, -0.0462 },
-     { 0.0484, -0.0357, 0.0168 },   { 0.0530, 0.0295, -0.2694 },  { -0.0550, -0.0896, 0.0494 },
-     { -0.0799, -0.2534, -0.0079 }, { 0.0436, -0.1557, 0.1849 },  { -0.0214, 0.0446, 0.0758 },
- };
- // Force buffer initialization for each particle.
- std::vector<nblib::Vec3> forces = {
-     { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 },
-     { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 },
-     { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 },
-     { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 },
- };
  // Main function to write the MD program.
  int main(); // Keep the compiler happy
  
@@@ -84,8 -62,8 +62,8 @@@ int main(
      // Add the argon particle to a molecule. The names are for bookkeeping and need not match.
      argonMolecule.addParticle(nblib::ParticleName("Argon"), argonAtom);
      // Define Lennard-Jones params for argon (parameters from gromos43A1).
-     const nblib::C6  ArC6{ 0.0062647225 };  // C6 parameter
-     const nblib::C12 ArC12{ 9.847044e-06 }; // C12 parameter
+     nblib::C6  ArC6{ 0.0062647225 };  // C6 parameter
+     nblib::C12 ArC12{ 9.847044e-06 }; // C12 parameter
      // Holder for non-bonded interactions.
      nblib::ParticleTypesInteractions interactions;
      // Add non-bonded interactions for argon.
@@@ -93,7 -71,7 +71,7 @@@
      // The TopologyBuilder builds the Topology!
      nblib::TopologyBuilder topologyBuilder;
      // Number of Argon particles (molecules) in the system.
-     const int numParticles = 12;
+     int numParticles = 12;
      // Add the requested number of argon molecules to a topology.
      topologyBuilder.addMolecule(argonMolecule, numParticles);
      // Add the argon interactions to the topology.
      nblib::Topology topology = topologyBuilder.buildTopology();
      // The system needs a bounding box. Only cubic and rectangular boxes are supported.
      nblib::Box box(6.05449);
+     // User defined coordinates.
+     std::vector<nblib::Vec3> coordinates = {
+         { 0.794, 1.439, 0.610 }, { 1.397, 0.673, 1.916 }, { 0.659, 1.080, 0.573 },
+         { 1.105, 0.090, 3.431 }, { 1.741, 1.291, 3.432 }, { 1.936, 1.441, 5.873 },
+         { 0.960, 2.246, 1.659 }, { 0.382, 3.023, 2.793 }, { 0.053, 4.857, 4.242 },
+         { 2.655, 5.057, 2.211 }, { 4.114, 0.737, 0.614 }, { 5.977, 5.104, 5.217 },
+     };
+     // User defined velocities.
+     std::vector<nblib::Vec3> velocities = {
+         { 0.0055, -0.1400, 0.2127 },   { 0.0930, -0.0160, -0.0086 }, { 0.1678, 0.2476, -0.0660 },
+         { 0.1591, -0.0934, -0.0835 },  { -0.0317, 0.0573, 0.1453 },  { 0.0597, 0.0013, -0.0462 },
+         { 0.0484, -0.0357, 0.0168 },   { 0.0530, 0.0295, -0.2694 },  { -0.0550, -0.0896, 0.0494 },
+         { -0.0799, -0.2534, -0.0079 }, { 0.0436, -0.1557, 0.1849 },  { -0.0214, 0.0446, 0.0758 },
+     };
+     // Force buffer initialization for each particle.
+     std::vector<nblib::Vec3> forces = {
+         { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 },
+         { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 },
+         { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 },
+         { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 }, { 0.0000, 0.0000, 0.0000 },
+     };
      // A simulation state contains all the molecular information about the system.
      nblib::SimulationState simState(coordinates, velocities, forces, box, topology);
      // Kernel options are flags needed for force calculation.
      gmx::ArrayRef<nblib::Vec3> userForces(simState.forces());
      forceCalculator.compute(simState.coordinates(), userForces);
      // Print some diagnostic info
 -    printf("  final forces on particle 0: x %4f y %4f z %4f\n", userForces[0][0], userForces[0][1],
 +    printf("  final forces on particle 0: x %4f y %4f z %4f\n",
 +           userForces[0][0],
 +           userForces[0][1],
             userForces[0][2]);
      // User may modify forces stored in simState.forces() if needed
      // Print some diagnostic info
 -    printf("initial position of particle 0: x %4f y %4f z %4f\n", simState.coordinates()[0][0],
 -           simState.coordinates()[0][1], simState.coordinates()[0][2]);
 +    printf("initial position of particle 0: x %4f y %4f z %4f\n",
 +           simState.coordinates()[0][0],
 +           simState.coordinates()[0][1],
 +           simState.coordinates()[0][2]);
      // Integrate with a time step of 1 fs
      integrator.integrate(1.0, simState.coordinates(), simState.velocities(), simState.forces());
      // Print some diagnostic info
  
 -    printf("  final position of particle 0: x %4f y %4f z %4f\n", simState.coordinates()[0][0],
 -           simState.coordinates()[0][1], simState.coordinates()[0][2]);
 +    printf("  final position of particle 0: x %4f y %4f z %4f\n",
 +           simState.coordinates()[0][0],
 +           simState.coordinates()[0][1],
 +           simState.coordinates()[0][2]);
      return 0;
  }
index 0000000000000000000000000000000000000000,a54839cd4c670814e05873d0ae8514ee5724f2f6..e574fea72e516a2c3c57a99a92f99f22bbae4c08
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,214 +1,218 @@@
 -    ListedForceCalculator listedForceCalculator(topology.getInteractionData(),
 -                                                topology.numParticles(), 4, box);
+ /*
+  * This file is part of the GROMACS molecular simulation package.
+  *
+  * 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.
+  *
+  * GROMACS is free software; you can redistribute it and/or
+  * modify it under the terms of the GNU Lesser General Public License
+  * as published by the Free Software Foundation; either version 2.1
+  * of the License, or (at your option) any later version.
+  *
+  * GROMACS is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with GROMACS; if not, see
+  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+  *
+  * If you want to redistribute modifications to GROMACS, please
+  * consider that scientific software is very special. Version
+  * control is crucial - bugs must be traceable. We will be happy to
+  * consider code for inclusion in the official distribution, but
+  * derived work must not be called official GROMACS. Details are found
+  * in the README & COPYING files - if they are missing, get the
+  * official version at http://www.gromacs.org.
+  *
+  * To help us fund GROMACS development, we humbly ask that you cite
+  * the research papers on the package. Check out http://www.gromacs.org.
+  */
+ /*! \internal \file
+  * \brief
+  * This tests that sample code can run
+  *
+  * \author Victor Holanda <victor.holanda@cscs.ch>
+  * \author Joe Jordan <ejjordan@kth.se>
+  * \author Prashanth Kanduri <kanduri@cscs.ch>
+  * \author Sebastian Keller <keller@cscs.ch>
+  */
+ #include <cstdio>
+ #include "gromacs/utility/arrayref.h"
+ // The entire nblib public API can be included with a single header or individual components
+ // can be included via their respective headers.
+ #include "nblib/nblib.h"
+ using namespace nblib;
+ // Main function to write the MD program.
+ int main(); // Keep the compiler happy
+ int main()
+ {
+     // Create the particles
+     ParticleType Ow(ParticleTypeName("Ow"), Mass(15.999));
+     ParticleType Hw(ParticleTypeName("Hw"), Mass(1.00784));
+     ParticleType Cm(ParticleTypeName("Cm"), Mass(12.0107));
+     ParticleType Hc(ParticleTypeName("Hc"), Mass(1.00784));
+     ParticleTypesInteractions interactions(CombinationRule::Geometric);
+     // Parameters from a GROMOS compatible force-field 2016H66
+     // add non-bonded interactions for the particle types
+     interactions.add(Ow.name(), C6(0.0026173456), C12(2.634129e-06));
+     interactions.add(Hw.name(), C6(0.0), C12(0.0));
+     interactions.add(Cm.name(), C6(0.01317904), C12(34.363044e-06));
+     interactions.add(Hc.name(), C6(8.464e-05), C12(15.129e-09));
+     Molecule water(MoleculeName("Water"));
+     Molecule methane(MoleculeName("Methane"));
+     water.addParticle(ParticleName("O"), Ow);
+     water.addParticle(ParticleName("H1"), Hw);
+     water.addParticle(ParticleName("H2"), Hw);
+     water.addExclusion(ParticleName("H1"), ParticleName("O"));
+     water.addExclusion(ParticleName("H2"), ParticleName("O"));
+     methane.addParticle(ParticleName("C"), Cm);
+     methane.addParticle(ParticleName("H1"), Hc);
+     methane.addParticle(ParticleName("H2"), Hc);
+     methane.addParticle(ParticleName("H3"), Hc);
+     methane.addParticle(ParticleName("H4"), Hc);
+     methane.addExclusion(ParticleName("H1"), ParticleName("C"));
+     methane.addExclusion(ParticleName("H2"), ParticleName("C"));
+     methane.addExclusion(ParticleName("H3"), ParticleName("C"));
+     methane.addExclusion(ParticleName("H4"), ParticleName("C"));
+     HarmonicBondType ohHarmonicBond(1, 1);
+     HarmonicBondType hcHarmonicBond(2, 1);
+     HarmonicAngleType hohAngle(Degrees(120), 1);
+     HarmonicAngleType hchAngle(Degrees(109.5), 1);
+     // add harmonic bonds for water
+     water.addInteraction(ParticleName("O"), ParticleName("H1"), ohHarmonicBond);
+     water.addInteraction(ParticleName("O"), ParticleName("H2"), ohHarmonicBond);
+     // add the angle for water
+     water.addInteraction(ParticleName("H1"), ParticleName("O"), ParticleName("H2"), hohAngle);
+     // add harmonic bonds for methane
+     methane.addInteraction(ParticleName("H1"), ParticleName("C"), hcHarmonicBond);
+     methane.addInteraction(ParticleName("H2"), ParticleName("C"), hcHarmonicBond);
+     methane.addInteraction(ParticleName("H3"), ParticleName("C"), hcHarmonicBond);
+     methane.addInteraction(ParticleName("H4"), ParticleName("C"), hcHarmonicBond);
+     // add the angles for methane
+     methane.addInteraction(ParticleName("H1"), ParticleName("C"), ParticleName("H2"), hchAngle);
+     methane.addInteraction(ParticleName("H1"), ParticleName("C"), ParticleName("H3"), hchAngle);
+     methane.addInteraction(ParticleName("H1"), ParticleName("C"), ParticleName("H4"), hchAngle);
+     methane.addInteraction(ParticleName("H2"), ParticleName("C"), ParticleName("H3"), hchAngle);
+     methane.addInteraction(ParticleName("H2"), ParticleName("C"), ParticleName("H4"), hchAngle);
+     methane.addInteraction(ParticleName("H3"), ParticleName("C"), ParticleName("H4"), hchAngle);
+     // Define a box for the simulation
+     Box box(6.05449);
+     // Define options for the non-bonded kernels
+     NBKernelOptions options;
+     // Use a simple cutoff rule for Coulomb
+     options.coulombType = nblib::CoulombType::Cutoff;
+     // Disable SIMD for this example
+     options.nbnxmSimd = SimdKernels::SimdNo;
+     TopologyBuilder topologyBuilder;
+     // add molecules
+     topologyBuilder.addMolecule(water, 2);
+     topologyBuilder.addMolecule(methane, 1);
+     // add non-bonded interaction map
+     topologyBuilder.addParticleTypesInteractions(interactions);
+     Topology topology = topologyBuilder.buildTopology();
+     // User defined coordinates.
+     std::vector<Vec3> coordinates = {
+         { 0.005, 0.600, 0.244 },    // Oxygen from water_1
+         { -0.017, 0.690, 0.270 },   // Hydrogen_1 from water_1
+         { 0.051, 0.610, 0.161 },    // Hydrogen_2 from water_1
+         { 0.155, 0.341, 0.735 },    // Oxygen from water_2
+         { 0.140, 0.284, 0.660 },    // Hydrogen_1 from water_2
+         { 0.081, 0.402, 0.734 },    // Hydrogen_2 from water_2
+         { -0.024, -0.222, -0.640 }, // Carbon from methane_1
+         { -0.083, -0.303, -0.646 }, // Hydrogen_1 from methane_1
+         { -0.080, -0.140, -0.642 }, // Hydrogen_2 from methane_1
+         { 0.040, -0.221, -0.716 },  // Hydrogen_3 from methane_1
+         { 0.027, -0.225, -0.553 }   // Hydrogen_4 from methane_1
+     };
+     // User defined velocities.
+     std::vector<Vec3> velocities = {
+         { 0.1823, -0.4158, 0.487 },   // Oxygen from water_1
+         { -1.7457, -0.5883, -0.460 }, // Hydrogen_1 from water_1
+         { 2.5085, -0.1501, 1.762 },   // Hydrogen_2 from water_1
+         { 0.6282, 0.4390, 0.001 },    // Oxygen from water_2
+         { -0.3206, 0.0700, 0.4630 },  // Hydrogen_1 from water_2
+         { -0.1556, -0.4529, 1.440 },  // Hydrogen_2 from water_2
+         { 0.0, 0.0, 0.0 },            // Carbon from methane_1
+         { 0.0, 0.0, 0.0 },            // Hydrogen_1 from methane_1
+         { 0.0, 0.0, 0.0 },            // Hydrogen_2 from methane_1
+         { 0.0, 0.0, 0.0 },            // Hydrogen_3 from methane_1
+         { 0.0, 0.0, 0.0 },            // Hydrogen_4 from methane_1
+     };
+     // Force buffer initialization for each particle.
+     std::vector<Vec3> forces(topology.numParticles(), Vec3{ 0, 0, 0 });
+     SimulationState simulationState(coordinates, velocities, forces, box, topology);
+     // The non-bonded force calculator contains all the data needed to compute forces
+     ForceCalculator forceCalculator(simulationState, options);
+     // The listed force calculator is also initialized with the required arguments
 -    printf("initial position of particle 0: x %4f y %4f z %4f\n", simulationState.coordinates()[0][0],
 -           simulationState.coordinates()[0][1], simulationState.coordinates()[0][2]);
++    ListedForceCalculator listedForceCalculator(
++            topology.getInteractionData(), topology.numParticles(), 4, box);
+     // Integrator is initialized with an array of inverse masses (constructed from topology) and
+     // the bounding box
+     LeapFrog integrator(topology, box);
+     // Print some diagnostic info
 -        integrator.integrate(1.0, simulationState.coordinates(), simulationState.velocities(),
 -                             simulationState.forces());
++    printf("initial position of particle 0: x %4f y %4f z %4f\n",
++           simulationState.coordinates()[0][0],
++           simulationState.coordinates()[0][1],
++           simulationState.coordinates()[0][2]);
+     // MD Loop
+     int numSteps = 2;
+     for (auto i = 0; i < numSteps; i++)
+     {
+         zeroCartesianArray(simulationState.forces());
+         forceCalculator.compute(simulationState.coordinates(), simulationState.forces());
+         listedForceCalculator.compute(simulationState.coordinates(), simulationState.forces());
+         // Integrate with a time step of 1 fs, positions, velocities and forces
 -    printf("  final position of particle 9: x %4f y %4f z %4f\n", simulationState.coordinates()[9][0],
 -           simulationState.coordinates()[9][1], simulationState.coordinates()[9][2]);
++        integrator.integrate(
++                1.0, simulationState.coordinates(), simulationState.velocities(), simulationState.forces());
+     }
++    printf("  final position of particle 9: x %4f y %4f z %4f\n",
++           simulationState.coordinates()[9][0],
++           simulationState.coordinates()[9][1],
++           simulationState.coordinates()[9][2]);
+     return 0;
+ } // main
index 6d26988a1b4c6deaea03a07c69b05ce4c8045ad6,52ba075b1382e21a1427df73834798fc7d4ba49b..75e5584548ddbd1f6a74c721bc6c5bac5cd8bdda
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -48,7 -48,6 +48,6 @@@
  #include "nblib/particletype.h"
  #include "nblib/simulationstate.h"
  #include "nblib/topology.h"
- #include "nblib/util/internal.h"
  
  #include "testutils/testasserts.h"
  
@@@ -77,10 -76,8 +76,8 @@@ TEST(NBlibTest, IntegratorWorks
      topologyBuilder.addParticleTypesInteractions(interactions);
      Topology topology = topologyBuilder.buildTopology();
  
-     // Some random starting conditions
-     std::vector<Vec3> x(numAtoms, { -9.0, 8.0, -7.0 });
-     std::vector<Vec3> v(numAtoms, { 0.6, -0.5, 0.4 });
-     // Constant force acting on the atom
+     std::vector<Vec3> x(numAtoms, { 0.0, 0.0, 0.0 });
+     std::vector<Vec3> v(numAtoms, { 0.0, 0.0, 0.0 });
      std::vector<Vec3> f(numAtoms, { 1.0, 2.0, 0.0 });
  
      Box box(100);
                          << formatString(
                                     "Coordinate {} of atom {} is different from analytical solution "
                                     "at step {}.",
 -                                   d, i, step);
 +                                   d,
 +                                   i,
 +                                   step);
  
                  EXPECT_REAL_EQ_TOL(vAnalytical[d], simulationState.velocities()[i][d], tolerance)
                          << formatString(
                                     "Velocity component {} of atom {} is different from analytical "
                                     "solution at step {}.",
 -                                   d, i, step);
 +                                   d,
 +                                   i,
 +                                   step);
              }
 -            integrator.integrate(dt, simulationState.coordinates(), simulationState.velocities(),
++            integrator.integrate(dt,
++                                 simulationState.coordinates(),
++                                 simulationState.velocities(),
+                                  simulationState.forces());
          }
-         integrator.integrate(
-                 dt, simulationState.coordinates(), simulationState.velocities(), simulationState.forces());
      }
  }
  
index 3e15e93878247aca106f675c21f666faff336f6d,c3ec7d9b0263a23e1168855bd20afa1967aebbf0..ca1524675ec330d8454a8a95ac6fcea4b2a8d345
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -51,6 -51,7 +51,7 @@@
  #include "nblib/tests/testhelpers.h"
  #include "nblib/tests/testsystems.h"
  #include "nblib/topology.h"
+ #include "nblib/util/setup.h"
  
  namespace nblib
  {
@@@ -80,12 -81,7 +81,7 @@@ TEST(NBlibTest, SpcMethanolForcesAreCor
      gmx::ArrayRef<Vec3> forces(simState.forces());
      ASSERT_NO_THROW(forceCalculator.compute(simState.coordinates(), forces));
  
-     /* Use higher-than-usual tolerance for forces. Some of the particles in the test systems are
-      * very close to each other, and, for example, the distance between the first two particles
-      * is approx. 0.13 and already has relative uncertainty around 1e-6. */
-     gmx::test::FloatingPointTolerance forceTolerance(1.0e-5, 1.0e-9, 1e-4, 1.0e-9, 1000, 1000, true);
-     Vector3DTest forcesOutputTest(forceTolerance);
+     Vector3DTest forcesOutputTest(5e-5);
      forcesOutputTest.testVectors(forces, "SPC-methanol forces");
  }
  
@@@ -121,8 -117,8 +117,8 @@@ TEST(NBlibTest, CanIntegrateSystem
      {
          gmx::ArrayRef<Vec3> forces(simState.forces());
          forceCalculator.compute(simState.coordinates(), simState.forces());
 -        EXPECT_NO_THROW(integrator.integrate(1.0, simState.coordinates(), simState.velocities(),
 -                                             simState.forces()));
 +        EXPECT_NO_THROW(integrator.integrate(
 +                1.0, simState.coordinates(), simState.velocities(), simState.forces()));
      }
  }
  
index 43e6f45522025ad80a0a4a035cbf5a83d17fb777,1d1d0a78a638b240d4f53a76e8ddf37acb352278..2bedef8887ecc158f0d914c1d4c023be3c06be21
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -162,7 -162,7 +162,7 @@@ MethanolMoleculeBuilder::MethanolMolecu
      HarmonicBondType ometBond(1.1, 1.2);
      methanol_.addInteraction(ParticleName("O2"), ParticleName("Me1"), ometBond);
  
-     DefaultAngle ochAngle(Degrees(108.52), 397.5);
+     HarmonicAngleType ochAngle(Degrees(108.52), 397.5);
      methanol_.addInteraction(ParticleName("O2"), ParticleName("Me1"), ParticleName("H3"), ochAngle);
  }
  
@@@ -180,8 -180,8 +180,8 @@@ Topology WaterTopologyBuilder::buildTop
      std::vector<std::string>  typeNames = { "Ow", "H" };
      for (const auto& name : typeNames)
      {
 -        interactions.add(ParticleTypeName(name), library.c6(ParticleName(name)),
 -                         library.c12(ParticleName(name)));
 +        interactions.add(
 +                ParticleTypeName(name), library.c6(ParticleName(name)), library.c12(ParticleName(name)));
      }
  
      // Add some molecules to the topology
@@@ -208,8 -208,8 +208,8 @@@ Topology SpcMethanolTopologyBuilder::bu
      std::vector<std::string>  typeNames = { "Ow", "H", "OMet", "CMet" };
      for (const auto& name : typeNames)
      {
 -        interactions.add(ParticleTypeName(name), library.c6(ParticleName(name)),
 -                         library.c12(ParticleName(name)));
 +        interactions.add(
 +                ParticleTypeName(name), library.c6(ParticleName(name)), library.c12(ParticleName(name)));
      }
  
      // Add some molecules to the topology
@@@ -239,8 -239,8 +239,8 @@@ ArgonTopologyBuilder::ArgonTopologyBuil
      ParticleLibrary library;
  
      ParticleTypesInteractions nbinteractions;
 -    nbinteractions.add(ParticleTypeName("Ar"), library.c6(ParticleName("Ar")),
 -                       library.c12(ParticleName("Ar")));
 +    nbinteractions.add(
 +            ParticleTypeName("Ar"), library.c6(ParticleName("Ar")), library.c12(ParticleName("Ar")));
  
      Molecule argonMolecule(MoleculeName("AR"));
      argonMolecule.addParticle(ParticleName("AR"), library.type("Ar"));
index 5d625d2fee8a9eaf02876bdb132e1b34aeebbd7b,1d1d9ab6e25eafbe2bb41a520b433b65c9640dcd..ff4d0336d929edc9eaa0e3c2af468a7d70628dfa
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 2020,2021, by the GROMACS development team, led by
   * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
   * and including many others, as listed in the AUTHORS file in the
   * top-level source directory and at http://www.gromacs.org.
  #include <gtest/gtest.h>
  
  #include "gromacs/topology/exclusionblocks.h"
+ #include "gromacs/utility/listoflists.h"
  #include "nblib/exception.h"
  #include "nblib/particletype.h"
+ #include "nblib/sequencing.hpp"
  #include "nblib/tests/testsystems.h"
  #include "nblib/topology.h"
+ #include "nblib/topologyhelpers.h"
+ #include "nblib/particlesequencer.h"
  
  namespace nblib
  {
@@@ -167,9 -171,11 +171,11 @@@ TEST(NBlibTest, TopologyThrowsIdentical
  
  TEST(NBlibTest, TopologyHasExclusions)
  {
-     WaterTopologyBuilder         waters;
-     Topology                     watersTopology = waters.buildTopology(2);
-     const gmx::ListOfLists<int>& testExclusions = watersTopology.getGmxExclusions();
+     WaterTopologyBuilder        waters;
+     Topology                    watersTopology = waters.buildTopology(2);
+     ExclusionLists<int>         exclusionLists = watersTopology.exclusionLists();
+     const gmx::ListOfLists<int> testExclusions(std::move(exclusionLists.ListRanges),
+                                                std::move(exclusionLists.ListElements));
  
      const std::vector<std::vector<int>>& refExclusions = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
                                                             { 3, 4, 5 }, { 3, 4, 5 }, { 3, 4, 5 } };
@@@ -182,10 -188,12 +188,10 @@@ TEST(NBlibTest, TopologyHasSequencing
      WaterTopologyBuilder waters;
      Topology             watersTopology = waters.buildTopology(2);
  
 -    EXPECT_EQ(0, watersTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"),
 -                                           ParticleName("Oxygen")));
 +    EXPECT_EQ(0, watersTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("Oxygen")));
      EXPECT_EQ(1, watersTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("H1")));
      EXPECT_EQ(2, watersTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("H2")));
 -    EXPECT_EQ(3, watersTopology.sequenceID(MoleculeName("SOL"), 1, ResidueName("SOL"),
 -                                           ParticleName("Oxygen")));
 +    EXPECT_EQ(3, watersTopology.sequenceID(MoleculeName("SOL"), 1, ResidueName("SOL"), ParticleName("Oxygen")));
      EXPECT_EQ(4, watersTopology.sequenceID(MoleculeName("SOL"), 1, ResidueName("SOL"), ParticleName("H1")));
      EXPECT_EQ(5, watersTopology.sequenceID(MoleculeName("SOL"), 1, ResidueName("SOL"), ParticleName("H2")));
  }
@@@ -203,9 -211,7 +209,9 @@@ TEST(NBlibTest, TopologyCanAggregateBon
  
      std::vector<HarmonicBondType> bondsTest;
      // use the expansionArray (bondsExpansion) to expand to the full list if bonds
 -    std::transform(begin(bondsExpansion), end(bondsExpansion), std::back_inserter(bondsTest),
 +    std::transform(begin(bondsExpansion),
 +                   end(bondsExpansion),
 +                   std::back_inserter(bondsTest),
                     [&bonds](size_t i) { return bonds[i]; });
  
      std::vector<HarmonicBondType> waterBonds =
@@@ -228,7 -234,7 +234,7 @@@ TEST(NBlibTest, TopologyCanSequencePair
  
      std::vector<std::tuple<Molecule, int>> molecules{ std::make_tuple(water, 2),
                                                        std::make_tuple(methanol, 1) };
-     detail::ParticleSequencer              particleSequencer;
+     ParticleSequencer                      particleSequencer;
      particleSequencer.build(molecules);
      auto pairs = detail::sequenceIDs<HarmonicBondType>(molecules, particleSequencer);
  
@@@ -263,7 -269,7 +269,7 @@@ TEST(NBlibTest, TopologySequenceIdThrow
  
      std::vector<std::tuple<Molecule, int>> molecules{ std::make_tuple(water, 2),
                                                        std::make_tuple(methanol, 1) };
-     detail::ParticleSequencer              particleSequencer;
+     ParticleSequencer                      particleSequencer;
      particleSequencer.build(molecules);
      auto pairs = detail::sequenceIDs<HarmonicBondType>(molecules, particleSequencer);
  
@@@ -349,8 -355,8 +355,8 @@@ TEST(NBlibTest, TopologyListedInteracti
  #undef SORT
      /// \endcond
  
 -    EXPECT_TRUE(std::equal(begin(interactions_reference), end(interactions_reference),
 -                           begin(interactions_test)));
 +    EXPECT_TRUE(std::equal(
 +            begin(interactions_reference), end(interactions_reference), begin(interactions_test)));
  }
  
  TEST(NBlibTest, TopologyListedInteractionsMultipleTypes)
      Molecule water    = WaterMoleculeBuilder{}.waterMolecule();
      Molecule methanol = MethanolMoleculeBuilder{}.methanolMolecule();
  
-     CubicBondType testBond(1., 1., 1.);
-     DefaultAngle  testAngle(Degrees(1), 1);
+     CubicBondType     testBond(1., 1., 1.);
+     HarmonicAngleType testAngle(Degrees(1), 1);
  
      water.addInteraction(ParticleName("H1"), ParticleName("H2"), testBond);
      water.addInteraction(ParticleName("H1"), ParticleName("Oxygen"), ParticleName("H2"), testAngle);
      auto  interactionData   = topology.getInteractionData();
      auto& harmonicBonds     = pickType<HarmonicBondType>(interactionData);
      auto& cubicBonds        = pickType<CubicBondType>(interactionData);
-     auto& angleInteractions = pickType<DefaultAngle>(interactionData);
+     auto& angleInteractions = pickType<HarmonicAngleType>(interactionData);
  
      HarmonicBondType              ohBond(1., 1.);
      HarmonicBondType              ohBondMethanol(1.01, 1.02);
      int MeH1 = topology.sequenceID(MoleculeName("MeOH"), 0, ResidueName("MeOH"), ParticleName("H3"));
  
      std::vector<CubicBondType>                   cubicBondsReference{ testBond };
 -    std::vector<InteractionIndex<CubicBondType>> cubicIndicesReference{ { std::min(H1, H2),
 -                                                                          std::max(H1, H2), 0 } };
 +    std::vector<InteractionIndex<CubicBondType>> cubicIndicesReference{
 +        { std::min(H1, H2), std::max(H1, H2), 0 }
 +    };
      EXPECT_EQ(cubicBondsReference, cubicBonds.parameters);
      EXPECT_EQ(cubicIndicesReference, cubicBonds.indices);
  
-     DefaultAngle                                methanolAngle(Degrees(108.52), 397.5);
-     std::vector<DefaultAngle>                   angleReference{ testAngle, methanolAngle };
-     std::vector<InteractionIndex<DefaultAngle>> angleIndicesReference{
+     HarmonicAngleType                                methanolAngle(Degrees(108.52), 397.5);
+     std::vector<HarmonicAngleType>                   angleReference{ testAngle, methanolAngle };
+     std::vector<InteractionIndex<HarmonicAngleType>> angleIndicesReference{
          { std::min(H1, H2), Ow, std::max(H1, H2), 0 }, { std::min(MeH1, MeO1), Me1, std::max(MeO1, MeH1), 1 }
      };
      EXPECT_EQ(angleReference, angleInteractions.parameters);
@@@ -458,7 -463,7 +464,7 @@@ TEST(NBlibTest, toGmxExclusionBlockWork
      reference.push_back(localBlock);
      reference.push_back(localBlock);
  
-     std::vector<gmx::ExclusionBlock> probe = detail::toGmxExclusionBlock(testInput);
+     std::vector<gmx::ExclusionBlock> probe = toGmxExclusionBlock(testInput);
  
      ASSERT_EQ(reference.size(), probe.size());
      for (size_t i = 0; i < reference.size(); ++i)
diff --combined api/nblib/topology.cpp
index ca5b3df8c223e96f6473be3f1a1a26b5d1d020ab,b55eaa03710388ca2048d37eb68703c17e3cfe01..acaea4999f2a25cce5fc775f78aa112c10122f55
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -42,6 -42,7 +42,7 @@@
   * \author Sebastian Keller <keller@cscs.ch>
   * \author Artem Zhmurov <zhmurov@gmail.com>
   */
+ #include <algorithm>
  #include <numeric>
  
  #include "gromacs/topology/exclusionblocks.h"
  #include "gromacs/utility/smalloc.h"
  #include "nblib/exception.h"
  #include "nblib/particletype.h"
+ #include "nblib/sequencing.hpp"
  #include "nblib/topology.h"
- #include "nblib/util/internal.h"
+ #include "nblib/util/util.hpp"
+ #include "nblib/topologyhelpers.h"
  
  namespace nblib
  {
  
  TopologyBuilder::TopologyBuilder() : numParticles_(0) {}
  
gmx::ListOfLists<int> TopologyBuilder::createExclusionsListOfLists() const
ExclusionLists<int> TopologyBuilder::createExclusionsLists() const
  {
      const auto& moleculesList = molecules_;
  
                  "No exclusions found in the " + molecule.name().value() + " molecule.";
          assert((!exclusions.empty() && message.c_str()));
  
-         std::vector<gmx::ExclusionBlock> exclusionBlockPerMolecule =
-                 detail::toGmxExclusionBlock(exclusions);
+         std::vector<gmx::ExclusionBlock> exclusionBlockPerMolecule = toGmxExclusionBlock(exclusions);
  
          // duplicate the exclusionBlockPerMolecule for the number of Molecules of (numMols)
          for (size_t i = 0; i < numMols; ++i)
          {
-             auto offsetExclusions =
-                     detail::offsetGmxBlock(exclusionBlockPerMolecule, particleNumberOffset);
+             auto offsetExclusions = offsetGmxBlock(exclusionBlockPerMolecule, particleNumberOffset);
  
 -            std::copy(std::begin(offsetExclusions), std::end(offsetExclusions),
 +            std::copy(std::begin(offsetExclusions),
 +                      std::end(offsetExclusions),
                        std::back_inserter(exclusionBlockGlobal));
  
              particleNumberOffset += molecule.numParticlesInMolecule();
          exclusionsListOfListsGlobal.pushBack(block.atomNumber);
      }
  
-     return exclusionsListOfListsGlobal;
+     std::vector<int>    listRanges(exclusionsListOfListsGlobal.listRangesView().begin(),
+                                 exclusionsListOfListsGlobal.listRangesView().end());
+     std::vector<int>    listElements(exclusionsListOfListsGlobal.elementsView().begin(),
+                                   exclusionsListOfListsGlobal.elementsView().end());
+     ExclusionLists<int> exclusionListsGlobal;
+     exclusionListsGlobal.ListRanges   = std::move(listRanges);
+     exclusionListsGlobal.ListElements = std::move(listElements);
+     return exclusionListsGlobal;
  }
  
- ListedInteractionData TopologyBuilder::createInteractionData(const detail::ParticleSequencer& particleSequencer)
+ ListedInteractionData TopologyBuilder::createInteractionData(const ParticleSequencer& particleSequencer)
  {
      ListedInteractionData interactionData;
  
  
          // combine stage 1 + 2 expansion arrays
          std::vector<size_t> expansionArray(expansionArrayStage1.size());
 -        std::transform(begin(expansionArrayStage1), end(expansionArrayStage1), begin(expansionArray),
 +        std::transform(begin(expansionArrayStage1),
 +                       end(expansionArrayStage1),
 +                       begin(expansionArray),
                         [& S2 = expansionArrayStage2](size_t S1Element) { return S2[S1Element]; });
  
          // add data about InteractionType instances
          // coordinateIndices contains the particle sequence IDs of all interaction coordinates of type <BondType>
          auto coordinateIndices = detail::sequenceIDs<InteractionType>(this->molecules_, particleSequencer);
          // zip coordinateIndices(i,j,...) + expansionArray(k) -> interactionDataElement.indices(i,j,...,k)
 -        std::transform(begin(coordinateIndices), end(coordinateIndices), begin(expansionArray),
 +        std::transform(begin(coordinateIndices),
 +                       end(coordinateIndices),
 +                       begin(expansionArray),
                         begin(interactionDataElement.indices),
                         [](auto coordinateIndex, auto interactionIndex) {
                             std::array<int, coordinateIndex.size() + 1> ret{ 0 };
@@@ -194,11 -198,9 +203,9 @@@ Topology TopologyBuilder::buildTopology
      }
      topology_.numParticles_ = numParticles_;
  
-     topology_.exclusions_ = createExclusionsListOfLists();
-     topology_.charges_    = extractParticleTypeQuantity<real>([](const auto& data, auto& map) {
-         ignore_unused(map);
-         return data.charge_;
-     });
+     topology_.exclusionLists_ = createExclusionsLists();
+     topology_.charges_        = extractParticleTypeQuantity<real>(
+             [](const auto& data, [[maybe_unused]] auto& map) { return data.charge_; });
  
      // map unique ParticleTypes to IDs
      std::unordered_map<std::string, int> nameToId;
      }
  
      topology_.particleTypeIdOfAllParticles_ =
-             extractParticleTypeQuantity<int>([&nameToId](const auto& data, auto& map) {
-                 ignore_unused(map);
+             extractParticleTypeQuantity<int>([&nameToId](const auto& data, [[maybe_unused]] auto& map) {
                  return nameToId[data.particleTypeName_];
              });
  
-     detail::ParticleSequencer particleSequencer;
+     ParticleSequencer particleSequencer;
      particleSequencer.build(molecules_);
      topology_.particleSequencer_ = std::move(particleSequencer);
  
              {
                  std::string message =
                          formatString("Missing nonbonded interaction parameters for pair {} {}",
 -                                     particleType1.first, particleType2.first);
 +                                     particleType1.first,
 +                                     particleType2.first);
                  throw InputException(message);
              }
          }
@@@ -322,9 -322,9 +328,9 @@@ CombinationRule Topology::getCombinatio
      return combinationRule_;
  }
  
gmx::ListOfLists<int> Topology::getGmxExclusions() const
ExclusionLists<int> Topology::exclusionLists() const
  {
-     return exclusions_;
+     return exclusionLists_;
  }
  
  } // namespace nblib
index 3a57ca375b4f96ed7e44b0f2359bcaded0a628de,1cad0bbcca40ce4d5596657599b16165e9a7b86a..0bb283b171ef1f0f9564d9bdaed892efcf0e968d
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
   * \author Sebastian Keller <keller@cscs.ch>
   * \author Artem Zhmurov <zhmurov@gmail.com>
   */
- #include <numeric>
+ #include <algorithm>
  
  #include "gromacs/topology/exclusionblocks.h"
- #include "gromacs/utility/smalloc.h"
- #include "nblib/exception.h"
- #include "nblib/listed_forces/transformations.h"
  #include "nblib/topologyhelpers.h"
- #include "nblib/util/internal.h"
  
  namespace nblib
  {
  
- namespace detail
- {
  std::vector<gmx::ExclusionBlock> toGmxExclusionBlock(const std::vector<std::tuple<int, int>>& tupleList)
  {
      std::vector<gmx::ExclusionBlock> ret;
@@@ -102,275 -96,11 +96,13 @@@ std::vector<gmx::ExclusionBlock> offset
      // shift particle numbers by offset
      for (auto& localBlock : inBlock)
      {
 -        std::transform(std::begin(localBlock.atomNumber), std::end(localBlock.atomNumber),
 -                       std::begin(localBlock.atomNumber), [offset](auto i) { return i + offset; });
 +        std::transform(std::begin(localBlock.atomNumber),
 +                       std::end(localBlock.atomNumber),
 +                       std::begin(localBlock.atomNumber),
 +                       [offset](auto i) { return i + offset; });
      }
  
      return inBlock;
  }
  
- int ParticleSequencer::operator()(const MoleculeName& moleculeName,
-                                   int                 moleculeNr,
-                                   const ResidueName&  residueName,
-                                   const ParticleName& particleName) const
- {
-     try
-     {
-         return data_.at(moleculeName).at(moleculeNr).at(residueName).at(particleName);
-     }
-     catch (const std::out_of_range& outOfRange)
-     {
-         // TODO: use string format function once we have it
-         if (moleculeName.value() == residueName.value())
-         {
-             printf("No particle %s in residue %s in molecule %s found\n",
-                    particleName.value().c_str(),
-                    residueName.value().c_str(),
-                    moleculeName.value().c_str());
-         }
-         else
-         {
-             printf("No particle %s in molecule %s found\n",
-                    particleName.value().c_str(),
-                    moleculeName.value().c_str());
-         }
-         throw InputException(outOfRange.what());
-     };
- }
- void ParticleSequencer::build(const std::vector<std::tuple<Molecule, int>>& moleculesList)
- {
-     int currentID = 0;
-     for (auto& molNumberTuple : moleculesList)
-     {
-         const Molecule& molecule = std::get<0>(molNumberTuple);
-         const size_t    numMols  = std::get<1>(molNumberTuple);
-         auto& moleculeMap = data_[molecule.name()];
-         for (size_t i = 0; i < numMols; ++i)
-         {
-             auto& moleculeNrMap = moleculeMap[i];
-             for (int j = 0; j < molecule.numParticlesInMolecule(); ++j)
-             {
-                 moleculeNrMap[molecule.residueName(j)][molecule.particleName(j)] = currentID++;
-             }
-         }
-     }
- }
- template<class I>
- std::tuple<std::vector<size_t>, std::vector<I>>
- collectInteractions(const std::vector<std::tuple<Molecule, int>>& molecules)
- {
-     std::vector<I>      collectedBonds;
-     std::vector<size_t> expansionArray;
-     for (auto& molNumberTuple : molecules)
-     {
-         const Molecule& molecule = std::get<0>(molNumberTuple);
-         size_t          numMols  = std::get<1>(molNumberTuple);
-         auto& interactions = pickType<I>(molecule.interactionData()).interactionTypes_;
-         std::vector<size_t> moleculeExpansion(interactions.size());
-         // assign indices to the bonds in the current molecule, continue counting from
-         // the number of bonds seen so far (=collectedBonds.size())
-         std::iota(begin(moleculeExpansion), end(moleculeExpansion), collectedBonds.size());
-         std::copy(begin(interactions), end(interactions), std::back_inserter(collectedBonds));
-         for (size_t i = 0; i < numMols; ++i)
-         {
-             std::copy(begin(moleculeExpansion), end(moleculeExpansion), std::back_inserter(expansionArray));
-         }
-     }
-     return std::make_tuple(expansionArray, collectedBonds);
- }
- /// \cond DO_NOT_DOCUMENT
- #define COLLECT_BONDS_INSTANTIATE_TEMPLATE(x)                                     \
-     template std::tuple<std::vector<size_t>, std::vector<x>> collectInteractions( \
-             const std::vector<std::tuple<Molecule, int>>&);
- MAP(COLLECT_BONDS_INSTANTIATE_TEMPLATE, SUPPORTED_LISTED_TYPES)
- /// \endcond
- namespace sequence_detail
- {
- //! Helper function to convert a tuple of strings into a particle index sequence
- template<class Tuple, class F, class... Args, size_t... Is>
- auto stringsToIndices_impl(const Tuple& tuple, std::index_sequence<Is...> is, F&& f, Args... args)
- {
-     // return std::make_tuple(f(args..., std::get<2 * Is + 1>(tuple), std::get<2 * Is>(tuple))...);
-     ignore_unused(is);
-     return std::array<int, sizeof...(Is)>{ f(
-             args..., std::get<2 * Is + 1>(tuple), std::get<2 * Is>(tuple))... };
- }
- /*! \brief
-  *  This takes a tuple<(string, string) * nCenter> from molecule
-  *  where nCenter = 2 for bonds, 3 for angles and 4 for dihedrals
-  *  each (ResidueName, ParticleName)-pair is converted to a particle sequence index
-  *  by calling the supplied function object f, containing the particleSequencer at the call site
-  *  Therefore, the return type is tuple<int * nCenter>
-  *
-  */
- template<class Tuple, class F, class... Args>
- auto stringsToIndices(const Tuple& tuple, F&& f, Args... args)
- {
-     auto is = std::make_index_sequence<std::tuple_size<Tuple>::value / 2>{};
-     return stringsToIndices_impl(tuple, is, std::forward<F>(f), args...);
- }
- //! Tuple ordering for two center interactions
- [[maybe_unused]] static std::array<int, 2> nblibOrdering(const std::array<int, 2>& t)
- {
-     // for bonds (two coordinate indices),
-     // we choose to store the lower sequence ID first. this allows for better unit tests
-     // that are agnostic to how the input was set up
-     int id1 = std::min(std::get<0>(t), std::get<1>(t));
-     int id2 = std::max(std::get<0>(t), std::get<1>(t));
-     return std::array<int, 2>{ id1, id2 };
- }
- //! Tuple ordering for three center interactions
- [[maybe_unused]] static std::array<int, 3> nblibOrdering(const std::array<int, 3>& t)
- {
-     // for angles (three coordinate indices),
-     // we choose to store the two non-center coordinate indices sorted.
-     // such that ret[0] < ret[2] always (ret = returned tuple)
-     int id1 = std::min(std::get<0>(t), std::get<2>(t));
-     int id3 = std::max(std::get<0>(t), std::get<2>(t));
-     return std::array<int, 3>{ id1, std::get<1>(t), id3 };
- }
- //! Tuple ordering for four center interactions
- [[maybe_unused]] static std::array<int, 4> nblibOrdering(const std::array<int, 4>& t)
- {
-     return t;
- }
- //! Tuple ordering for five center interactions
- [[maybe_unused]] static std::array<int, 5> nblibOrdering(const std::array<int, 5>& t)
- {
-     return t;
- }
- } // namespace sequence_detail
- //! For each interaction, translate particle identifiers (moleculeName, nr, residueName,
- //! particleName) to particle coordinate indices
- template<class B>
- std::vector<CoordinateIndex<B>> sequenceIDs(const std::vector<std::tuple<Molecule, int>>& molecules,
-                                             const detail::ParticleSequencer& particleSequencer)
- {
-     std::vector<CoordinateIndex<B>> coordinateIndices;
-     auto callSequencer = [&particleSequencer](const MoleculeName& moleculeName,
-                                               int                 i,
-                                               const ResidueName&  residueName,
-                                               const ParticleName& particleName) {
-         return particleSequencer(moleculeName, i, residueName, particleName);
-     };
-     // loop over all molecules
-     for (const auto& molNumberTuple : molecules)
-     {
-         const Molecule& molecule = std::get<0>(molNumberTuple);
-         size_t          numMols  = std::get<1>(molNumberTuple);
-         for (size_t i = 0; i < numMols; ++i)
-         {
-             auto& interactions = pickType<B>(molecule.interactionData()).interactions_;
-             for (const auto& interactionString : interactions)
-             {
-                 CoordinateIndex<B> index = sequence_detail::stringsToIndices(
-                         interactionString, callSequencer, molecule.name(), i);
-                 coordinateIndices.push_back(nblibOrdering(index));
-             }
-         }
-     }
-     return coordinateIndices;
- }
- /// \cond DO_NOT_DOCUMENT
- #define SEQUENCE_PAIR_ID_INSTANTIATE_TEMPLATE(x)             \
-     template std::vector<CoordinateIndex<x>> sequenceIDs<x>( \
-             const std::vector<std::tuple<Molecule, int>>&, const detail::ParticleSequencer&);
- MAP(SEQUENCE_PAIR_ID_INSTANTIATE_TEMPLATE, SUPPORTED_LISTED_TYPES)
- #undef SEQUENCE_PAIR_ID_INSTANTIATE_TEMPLATE
- /// \endcond
- template<class I>
- std::tuple<std::vector<size_t>, std::vector<I>> eliminateDuplicateInteractions(const std::vector<I>& aggregatedInteractions)
- {
-     std::vector<size_t> uniqueIndices(aggregatedInteractions.size());
-     std::vector<I>      uniquInteractionsInstances;
-     // if there are no interactions of type B we're done now
-     if (aggregatedInteractions.empty())
-     {
-         return std::make_tuple(uniqueIndices, uniquInteractionsInstances);
-     }
-     // create 0,1,2,... sequence
-     std::iota(begin(uniqueIndices), end(uniqueIndices), 0);
-     std::vector<std::tuple<I, size_t>> enumeratedBonds(aggregatedInteractions.size());
-     // append each interaction with its index
-     std::transform(begin(aggregatedInteractions),
-                    end(aggregatedInteractions),
-                    begin(uniqueIndices),
-                    begin(enumeratedBonds),
-                    [](I b, size_t i) { return std::make_tuple(b, i); });
-     auto sortKey = [](const auto& t1, const auto& t2) { return std::get<0>(t1) < std::get<0>(t2); };
-     // sort w.r.t bonds. the result will contain contiguous segments of identical bond instances
-     // the associated int indicates the original index of each BondType instance in the input vector
-     std::sort(begin(enumeratedBonds), end(enumeratedBonds), sortKey);
-     // initialize it1 and it2 to delimit first range of equal BondType instances
-     auto range = std::equal_range(begin(enumeratedBonds), end(enumeratedBonds), enumeratedBonds[0], sortKey);
-     auto it1 = range.first;
-     auto it2 = range.second;
-     // number of unique instances of BondType B = number of contiguous segments in enumeratedBonds =
-     //         number of iterations in the outer while loop below
-     while (it1 != end(enumeratedBonds))
-     {
-         uniquInteractionsInstances.push_back(std::get<0>(*it1));
-         // loop over all identical BondType instances;
-         for (; it1 != it2; ++it1)
-         {
-             // we note down that the BondType instance at index <interactionIndex>
-             // can be found in the uniqueBondInstances container at index <uniqueBondInstances.size()>
-             int interactionIndex            = std::get<1>(*it1);
-             uniqueIndices[interactionIndex] = uniquInteractionsInstances.size() - 1;
-         }
-         // Note it1 has been incremented and is now equal to it2
-         if (it1 != end(enumeratedBonds))
-         {
-             it2 = std::upper_bound(it1, end(enumeratedBonds), *it1, sortKey);
-         }
-     }
-     return make_tuple(uniqueIndices, uniquInteractionsInstances);
- }
- /// \cond DO_NOT_DOCUMENT
- #define ELIMINATE_DUPLICATE_BONDS_INSTANTIATE_TEMPLATE(x)                                    \
-     template std::tuple<std::vector<size_t>, std::vector<x>> eliminateDuplicateInteractions( \
-             const std::vector<x>& aggregatedBonds);
- MAP(ELIMINATE_DUPLICATE_BONDS_INSTANTIATE_TEMPLATE, SUPPORTED_LISTED_TYPES)
- #undef ELIMINATE_DUPLICATE_BONDS_INSTANTIATE_TEMPLATE
- /// \endcond
- } // namespace detail
  } // namespace nblib
diff --combined api/nblib/util/setup.cpp
index 3c087f8f976a2f03b3b4076aabd7bc609344b357,9aa85bc3b484379a5128b5cae985078d3d4808f0..a6521e9cccec601832120ff2d5691ad8aa0808f8
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -43,8 -43,7 +43,7 @@@
   * \author Artem Zhmurov <zhmurov@gmail.com>
   */
  
- #include "nblib/util/internal.h"
- #include "nblib/util/user.h"
+ #include "nblib/util/setup.h"
  #include "gromacs/random/tabulatednormaldistribution.h"
  #include "gromacs/random/threefry.h"
  #include "gromacs/utility/arrayref.h"
  namespace nblib
  {
  
- namespace detail
- {
- std::string next_token(std::string& s, const std::string& delimiter)
- {
-     std::string token = s.substr(0, s.find(delimiter));
-     std::size_t next = s.find(delimiter);
-     if (next == std::string::npos)
-         s.clear();
-     else
-         s.erase(0, next + delimiter.length());
-     return token;
- }
- } // namespace detail
  //! Generates an array of particle velocities based on the Maxwell-Boltzmann distribution
  //! using temperature, masses and a random number generator
  static std::vector<Vec3> low_mspeed(real tempi, std::vector<real> const& masses, gmx::ThreeFry2x64<>* rng)
          fprintf(debug,
                  "Velocities were taken from a Maxwell distribution\n"
                  "Initial generated temperature: %12.5e (scaled to: %12.5e)\n",
 -                temp, tempi);
 +                temp,
 +                tempi);
      }
  
      return velocities;
index 99ed210e4b2efd43f67c77b4158b4711306fab92,2f67c69f626dfac030c5cc0b5a476e7a562fcb79..bc6372c9658ed1b1fb41df9960070bcc8e2ed802
@@@ -2,7 -2,7 +2,7 @@@
  # This file is part of the GROMACS molecular simulation package.
  #
  # Copyright (c) 2014,2015,2016,2017,2018 by the GROMACS development team.
- # Copyright (c) 2019,2020, by the GROMACS development team, led by
+ # Copyright (c) 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.
@@@ -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 1)
 +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)
  
@@@ -257,13 -256,13 +257,13 @@@ if (NOT SOURCE_IS_SOURCE_DISTRIBUTION A
  endif()
  
  set(REGRESSIONTEST_VERSION "${GMX_VERSION_STRING}")
 -set(REGRESSIONTEST_BRANCH "release-2021") 
 +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 "ca12c40c20575df76b1b8b1a351cb7b0" CACHE INTERNAL "MD5 sum of the regressiontests tarball for this GROMACS version")
+ set(REGRESSIONTEST_MD5SUM "c9b6b2f0754b113efc44c738897a26f7" CACHE INTERNAL "MD5 sum of the regressiontests tarball for this GROMACS version")
  
  math(EXPR GMX_VERSION_NUMERIC
       "${GMX_VERSION_MAJOR}*10000 + ${GMX_VERSION_PATCH}")
@@@ -412,6 -411,7 +412,7 @@@ else(
              -D VERSION_CMAKEIN=${VERSION_INFO_CMAKEIN_FILE_PARTIAL}
              -D VERSION_OUT=${VERSION_INFO_CMAKE_FILE}
              -D VERSION_STRING_OF_FORK=${GMX_VERSION_STRING_OF_FORK}
+             -D SOURCE_IS_SOURCE_DISTRIBUTION=${SOURCE_IS_SOURCE_DISTRIBUTION}
              -P ${CMAKE_CURRENT_LIST_DIR}/gmxGenerateVersionInfoWithoutGit.cmake
          WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
          COMMENT "Generating release version information")
@@@ -435,7 -435,7 +436,7 @@@ set(CHECKSUM_FILE "${PROJECT_SOURCE_DIR
  # not been tampered with.
  # Note: The RUN_ALWAYS here is to regenerate the hash file only, it does not
  # mean that the target is run in all builds
- if (PYTHONINTERP_FOUND)
+ if (Python3_Interpreter_FOUND)
      # We need the full path to the directories after passing it through
      set(FULL_PATH_DIRECTORIES "")
      foreach(DIR ${SET_OF_DIRECTORIES_TO_CHECKSUM})
  else()
      add_custom_target(reference_checksum
          COMMAND ${CMAKE_COMMAND} -E echo
-         "Can not checksum files without python being available"
+         "Can not checksum files without python3 being available"
          WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
          COMMENT "Generating reference checksum of source files")
  endif()
diff --combined docs/CMakeLists.txt
index 662026c08983bd3ed8aef138d49f956b30eb6d1a,5e97749915f3a3e104df5825f4f15fc058fc9c05..be5483dbd4bcb1e47605e91c193195359a77a962
@@@ -2,7 -2,7 +2,7 @@@
  # This file is part of the GROMACS molecular simulation package.
  #
  # Copyright (c) 2014,2015,2016,2017,2018 by the GROMACS development team.
- # Copyright (c) 2019,2020, by the GROMACS development team, led by
+ # Copyright (c) 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.
@@@ -173,7 -173,7 +173,7 @@@ if (SPHINX_FOUND
          reference-manual/references.rst
          # PNG formated plot files that don't need to be converted into PNG
          # for the web page.
-         reference-manual/plots/peregrine.png
+       reference-manual/plots/GMX_logos/gmx_falcon_blue.png
          reference-manual/plots/adress.png
          reference-manual/plots/plotje.png
          reference-manual/plots/xvgr.png
          reference-manual/special/plots/equipotential.pdf
          reference-manual/special/plots/field.pdf
          reference-manual/special/plots/gaussians.pdf
+         reference-manual/special/plots/lambda-values.pdf
          reference-manual/special/plots/pulldirrel.pdf
          reference-manual/special/plots/pull.pdf
          reference-manual/special/plots/pullref.pdf
          how-to/visualize.rst
          install-guide/index.rst
          release-notes/index.rst
 +        release-notes/2022/major/highlights.rst
 +        release-notes/2022/major/features.rst
 +        release-notes/2022/major/performance.rst
 +        release-notes/2022/major/tools.rst
 +        release-notes/2022/major/bugs-fixed.rst
 +        release-notes/2022/major/removed-functionality.rst
 +        release-notes/2022/major/deprecated-functionality.rst
 +        release-notes/2022/major/portability.rst
 +        release-notes/2022/major/miscellaneous.rst
+         release-notes/2021/2021.1.rst
          release-notes/2021/major/highlights.rst
          release-notes/2021/major/features.rst
          release-notes/2021/major/performance.rst
          release-notes/2020/2020.3.rst
          release-notes/2020/2020.4.rst
          release-notes/2020/2020.5.rst
+         release-notes/2020/2020.6.rst
          release-notes/2020/major/highlights.rst
          release-notes/2020/major/features.rst
          release-notes/2020/major/performance.rst
index 44c68ce0ec0bc609d76cef6a3c375a3f32f69187,24911f03ee82e2a4d91ecc86a0ecbd23f56d3d35..bdbcabe93bf6b2a5ec2ae87cc1b48d950afcd72f
@@@ -55,9 -55,10 +55,9 @@@ Quick and dirty cluster installatio
  
  On a cluster where users are expected to be running across multiple
  nodes using MPI, make one installation similar to the above, and
 -another using ``-DGMX_MPI=on`` and which is `building only
 -mdrun`_, because that is the only component of |Gromacs| that uses
 -MPI. The latter will install a single simulation engine binary,
 -i.e. ``mdrun_mpi`` when the default suffix is used. Hence it is safe
 +another using ``-DGMX_MPI=on``.
 +The latter will install binaries and libraries named using
 +a default suffix of ``_mpi`` ie ``gmx_mpi``. Hence it is safe
  and common practice to install this into the same location where
  the non-MPI build is installed.
  
@@@ -69,11 -70,12 +69,11 @@@ using the following `CMake options`_ wi
  appropriate value instead of ``xxx`` :
  
  * ``-DCMAKE_C_COMPILER=xxx`` equal to the name of the C99 `Compiler`_ you wish to use (or the environment variable ``CC``)
- * ``-DCMAKE_CXX_COMPILER=xxx`` equal to the name of the C++98 `compiler`_ you wish to use (or the environment variable ``CXX``)
+ * ``-DCMAKE_CXX_COMPILER=xxx`` equal to the name of the C++17 `compiler`_ you wish to use (or the environment variable ``CXX``)
 -* ``-DGMX_MPI=on`` to build using `MPI support`_ (generally good to combine with `building only mdrun`_)
 +* ``-DGMX_MPI=on`` to build using `MPI support`_
  * ``-DGMX_GPU=CUDA`` to build with NVIDIA CUDA support enabled.
  * ``-DGMX_GPU=OpenCL`` to build with OpenCL_ support enabled.
  * ``-DGMX_SIMD=xxx`` to specify the level of `SIMD support`_ of the node on which |Gromacs| will run
 -* ``-DGMX_BUILD_MDRUN_ONLY=on`` for `building only mdrun`_, e.g. for compute cluster back-end nodes
  * ``-DGMX_DOUBLE=on`` to build |Gromacs| in double precision (slower, and not normally useful)
  * ``-DCMAKE_PREFIX_PATH=xxx`` to add a non-standard location for CMake to `search for libraries, headers or programs`_
  * ``-DCMAKE_INSTALL_PREFIX=xxx`` to install |Gromacs| to a `non-standard location`_ (default ``/usr/local/gromacs``)
@@@ -142,7 -144,7 +142,7 @@@ particular libstdc++ library, provide t
  ``-DGMX_GPLUSPLUS_PATH=/path/to/g++``.
  
  On Windows with the Intel compiler, the MSVC standard library is used,
- and at least MSVC 2017 15.7 is required. Load the enviroment variables with
+ and at least MSVC 2017 15.7 is required. Load the environment variables with
  vcvarsall.bat.
  
  To build with clang and llvm's libcxx standard library, use
@@@ -816,16 -818,18 +816,16 @@@ earlier hardware, because this will lea
  mdrun) that run slowly on the new hardware. Building two full
  installations and locally managing how to call the correct one
  (e.g. using a module system) is the recommended
 -approach. Alternatively, as at the moment the |Gromacs| tools do not
 -make strong use of SIMD acceleration, it can be convenient to create
 -an installation with tools portable across different x86 machines, but
 -with separate mdrun binaries for each architecture. To achieve this,
 +approach. Alternatively, one can use different suffixes to install 
 +several versions of |Gromacs| in the same location. To achieve this,
  one can first build a full installation with the
  least-common-denominator SIMD instruction set, e.g. ``-DGMX_SIMD=SSE2``,
 -then build separate mdrun binaries for each architecture present in
 +in order for simple commands like ``gmx grompp`` to work on all machines,
 +then build specialized ``gmx`` binaries for each architecture present in
  the heterogeneous environment. By using custom binary and library
 -suffixes for the mdrun-only builds, these can be installed to the
 -same location as the "generic" tools installation.
 -`Building just the mdrun binary`_ is possible by setting the
 -``-DGMX_BUILD_MDRUN_ONLY=ON`` option.
 +suffixes (with CMake variables ``-DGMX_BINARY_SUFFIX=xxx`` and
 +``-DGMX_LIBS_SUFFIX=xxx``), these can be installed to the same
 +location.
  
  Linear algebra libraries
  ~~~~~~~~~~~~~~~~~~~~~~~~
@@@ -960,6 -964,17 +960,6 @@@ supported by ``cmake`` (e.g. ``ninja``
  
  .. _building just the mdrun binary:
  
 -Building only mdrun
 -~~~~~~~~~~~~~~~~~~~
 -
 -This is now deprecated, but still supported with the ``cmake`` option
 -``-DGMX_BUILD_MDRUN_ONLY=ON``, which will build a different version of
 -``libgromacs`` and the ``mdrun`` program.  Naturally, now ``make
 -install`` installs only those products. By default, mdrun-only builds
 -will default to static linking against |Gromacs| libraries, because
 -this is generally a good idea for the targets for which an mdrun-only
 -build is desirable.
 -
  Installing |Gromacs|
  ^^^^^^^^^^^^^^^^^^^^
  
@@@ -1045,11 -1060,42 +1045,11 @@@ but then you should include a detailed 
  your hardware, and the output of ``gmx mdrun -version`` (which contains
  valuable diagnostic information in the header).
  
 -Testing for MDRUN_ONLY executables
 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 -
 -A build with ``-DGMX_BUILD_MDRUN_ONLY`` cannot be tested with
 -``make check`` from the build tree, because most of the tests
 -require a full build to run things like ``grompp``. To test such an
 -mdrun fully requires installing it to the same location as a normal
 -build of |Gromacs|, downloading the regression tests tarball manually
 -as described above, sourcing the correct ``GMXRC`` and running the
 -perl script manually. For example, from your |Gromacs| source
 -directory:
 -
 -::
 -
 -    mkdir build-normal
 -    cd build-normal
 -    # First, build and install normally to allow full testing of the standalone simulator.
 -    cmake .. -DGMX_MPI=ON -DCMAKE_INSTALL_PREFIX=/your/installation/prefix/here
 -    make -j 4
 -    make install
 -    cd ..
 -    mkdir build-mdrun-only
 -    cd build-mdrun-only
 -    # Next, build and install the GMX_BUILD_MDRUN_ONLY version (optional).
 -    cmake .. -DGMX_MPI=ON -DGMX_BUILD_MDRUN_ONLY=ON -DCMAKE_INSTALL_PREFIX=/your/installation/prefix/here
 -    make -j 4
 -    make install
 -    cd /to/your/unpacked/regressiontests
 -    source /your/installation/prefix/here/bin/GMXRC
 -    ./gmxtest.pl all -np 2
 -
  Non-standard suffix
  ~~~~~~~~~~~~~~~~~~~
  
 -If your mdrun program has been suffixed in a non-standard way, then
 -the ``./gmxtest.pl -mdrun`` option will let you specify that name to the
 +If your ``gmx`` program has been suffixed in a non-standard way, then
 +the ``./gmxtest.pl -suffix`` option will let you specify that suffix to the
  test machinery. You can use ``./gmxtest.pl -double`` to test the
  double-precision version. You can use ``./gmxtest.pl -crosscompiling``
  to stop the test harness attempting to check that the programs can
@@@ -1220,6 -1266,7 +1220,6 @@@ The recommended configuration is to us
               -DCMAKE_PREFIX_PATH=/your/fftw/installation/prefix \
               -DCMAKE_INSTALL_PREFIX=/where/gromacs/should/be/installed \
               -DGMX_MPI=ON \
 -             -DGMX_BUILD_MDRUN_ONLY=ON \
               -DGMX_RELAXED_DOUBLE_PRECISION=ON
      make
      make install
index b250914028dddb367f5f6844f9765485303a6894,195827bc559f29fa9fa757a0c8c58c262658c48b..6c43588883fa4b662edd589068fd2c116dafb5b7
@@@ -1,3 -1,5 +1,3 @@@
 -.. _anticipated-changes:
 -
  .. Note to developers!
     Please use """"""" to underline the individual entries for fixed issues in the subfolders,
     otherwise the formatting on the webpage is messed up.
@@@ -54,6 -56,24 +54,24 @@@ based upon xssp, and make it available 
  Functionality deprecated in |Gromacs| 2021
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  
+ ``mdrun -deffnm`` to be removed
+ """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+ This functionality is convenient when running very simple simulations,
+ because it permits grouping of a set of files that then differ only
+ their suffix. However, it does not work in the wider case of an
+ ``mdrun`` module (or modules) writing multiple ``.xvg`` output
+ files. The resulting filenames collide. That, and its interaction with
+ checkpointing and appending, have led to quite a few bug reports.
+ Because users can use a folder to group files (a standard mechanism
+ that they understand from experience outside of |Gromacs|), we can
+ build and test better software for them if we remove the erstwhile
+ convenience of ``mdrun -deffnm``. Please update your workflows
+ accordingly.
+ :issue:`3818`
  OpenCL to be removed as a GPU framework
  """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
  :issue:`3818` Work is underway for ports to AMD and Intel GPUs, and it
@@@ -122,6 -142,14 +140,14 @@@ Constant-acceleration M
  :issue:`1354` This has been broken for many years, and will be removed
  as nobody has been found with interest to fix it.
  
+ Reading .pdo files in ``gmx wham``
+ """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+ The pull code in |Gromacs| before version 4.0 wrote files in ``.pdo``
+ format. Analyses of such files are likely no longer relevant, and if
+ they are, using any older GROMACS version will work. ``gmx wham`` will be
+ simpler to maintain and extend if we no longer support reading
+ ``.pdo`` files.
  Functionality deprecated in |Gromacs| 2020
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  
index 90a44ee75e19e7f0aaac0cf48ab06651701a9568,52879e5d79941fbb00ef5a16268910259a97d1be..e091330c7fa1036f7419773cabcd5aa62734df36
@@@ -8,43 -8,32 +8,52 @@@ releases of |Gromacs|. Major releases c
  functionality supported, whereas patch releases contain only fixes for
  issues identified in the corresponding major releases.
  
 -Two versions of |Gromacs| are under active maintenance, the 2021
 -series and the 2020 series. In the latter, only highly conservative
 +Two versions of |Gromacs| are under active maintenance, the 2022
 +series and the 2021 series. In the latter, only highly conservative
  fixes will be made, and only to address issues that affect scientific
  correctness. Naturally, some of those releases will be made after the
 -year 2020 ends, but we keep 2019 in the name so users understand how
 +year 2021 ends, but we keep 2021 in the name so users understand how
  up to date their version is. Such fixes will also be incorporated into
 -the 2021 release series, as appropriate. Around the time the 2022
 -release is made, the 2020 series will no longer be maintained.
 +the 2022 release series, as appropriate. Around the time the 2023
 +release is made, the 2021 series will no longer be maintained.
  
  Where issue numbers are reported in these release notes, more details
  can be found on the `issue tracker`_ at that issue number.
  
 -|Gromacs| 2021 series
 +|Gromacs| 2022 series
  ---------------------
  
  .. todolist::
  
+ Patch releases
+ ^^^^^^^^^^^^^^
+ .. toctree::
+    :maxdepth: 1
+    2021/2021.1
 +Major release
 +^^^^^^^^^^^^^
 +
 +.. toctree::
 +   :maxdepth: 1
 +
 +   2022/major/highlights
 +   2022/major/features
 +   2022/major/performance
 +   2022/major/tools
 +   2022/major/bugs-fixed
 +   2022/major/deprecated-functionality
 +   2022/major/removed-functionality
 +   2022/major/portability
 +   2022/major/miscellaneous
 +
 +
 +|Gromacs| 2021 series
 +---------------------
 +
  Major release
  ^^^^^^^^^^^^^
  
     2021/major/portability
     2021/major/miscellaneous
  
 +
 +Older (unmaintained) |Gromacs| series
 +-------------------------------------------------------
 +
  |Gromacs| 2020 series
  ---------------------
  
@@@ -74,6 -59,7 +83,7 @@@ Patch release
  .. toctree::
     :maxdepth: 1
  
+    2020/2020.6
     2020/2020.5
     2020/2020.4
     2020/2020.3
@@@ -129,6 -115,9 +139,6 @@@ Major releas
     2019/major/portability
     2019/major/miscellaneous
  
 -Older (unmaintained) |Gromacs| series
 --------------------------------------------------------
 -
  |Gromacs| 2018 series
  ---------------------
  
index 030c7bb6378eaf10f3e858c6f647405044afe03f,74f140063075e9c2fe814edd9750eeada5960c83..f5ebe9462e7912b12043174c8642a9103f09d852
@@@ -252,18 -252,15 +252,15 @@@ Run contro
  
  .. mdp:: mts-level2-forces
  
-    (longrange-nonbonded nonbonded pair dihedral)
-    A list of force groups that will be evaluated only every
+    (longrange-nonbonded)
+    A list of one or more force groups that will be evaluated only every
     :mdp:`mts-level2-factor` steps. Supported entries are:
     ``longrange-nonbonded``, ``nonbonded``, ``pair``, ``dihedral``, ``angle``,
     ``pull`` and ``awh``. With ``pair`` the listed pair forces (such as 1-4)
     are selected. With ``dihedral`` all dihedrals are selected, including cmap.
     All other forces, including all restraints, are evaluated and
     integrated every step. When PME or Ewald is used for electrostatics
-    and/or LJ interactions, ``longrange-nonbonded`` has to be entered here.
-    The default value should work well for most standard atomistic simulations
-    and in particular for replacing virtual site treatment for increasing
-    the time step.
+    and/or LJ interactions, ``longrange-nonbonded`` can not be omitted here.
  
  .. mdp:: mts-level2-factor
  
@@@ -2954,6 -2951,20 +2951,6 @@@ Expanded Ensemble calculation
  Non-equilibrium MD
  ^^^^^^^^^^^^^^^^^^
  
 -.. mdp:: acc-grps
 -
 -   groups for constant acceleration (*e.g.* ``Protein Sol``) all atoms
 -   in groups Protein and Sol will experience constant acceleration as
 -   specified in the :mdp:`accelerate` line. (Deprecated)
 -
 -.. mdp:: accelerate
 -
 -   (0) [nm ps\ :sup:`-2`]
 -   acceleration for :mdp:`acc-grps`; x, y and z for each group
 -   (*e.g.* ``0.1 0.0 0.0 -0.1 0.0 0.0`` means that first group has
 -   constant acceleration of 0.1 nm ps\ :sup:`-2` in X direction, second group
 -   the opposite). (Deprecated)
 -
  .. mdp:: freezegrps
  
     Groups that are to be frozen (*i.e.* their X, Y, and/or Z position
index f5dd6c33558b909d60cd003608ab0dd4004d9878,94a37af107702f5e11b749515d1db5eec03c50d5..ae443a53772e88a12eebf707e8b2aeca0c313c90
@@@ -1,7 -1,7 +1,7 @@@
  #
  # This file is part of the GROMACS molecular simulation package.
  #
- # Copyright (c) 2019,2020, by the GROMACS development team, led by
+ # Copyright (c) 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.
@@@ -70,9 -70,9 +70,9 @@@ from .exceptions import FeatureNotAvail
  
  # TODO: Version management policy and procedures.
  _major = 0
 -_minor = 2
 +_minor = 3
  _micro = 0
- _suffix = 'b1'
+ _suffix = ''
  
  # Reference https://www.python.org/dev/peps/pep-0440/
  # and https://packaging.pypa.io/en/latest/version/
index e1246075cc436d08ccb0192e26a949ae4f99f03d,c8ae4732193e327ecac5477458d663e9ee56d1e9..7ec8697061048710ad4ffc682a6fc0d11123f61d
@@@ -1,7 -1,7 +1,7 @@@
  #
  # This file is part of the GROMACS molecular simulation package.
  #
- # Copyright (c) 2019,2020, by the GROMACS development team, led by
+ # Copyright (c) 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.
@@@ -161,7 -161,7 +161,7 @@@ setup
      name='gmxapi',
  
      # TODO: single-source version information (currently repeated in gmxapi/version.py and CMakeLists.txt)
 -    version='0.2.0',
 +    version='0.3.0a1',
      python_requires='>=3.6',
      install_requires=['networkx>=2.0',
                        'numpy>=1'],
index abcaaa8cb9ae46d9353a05f16ca8d1a0fcc7818e,1754ac7f104b1e4af40794e7b922946b7ca6cdf2..b5d68415bcca08d80dd79c748d3986025b2cf442
@@@ -1,7 -1,8 +1,8 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2015,2016,2017,2018,2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 2015,2016,2017,2018,2019, The GROMACS development team.
+  * Copyright (c) 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.
@@@ -164,7 -165,7 +165,7 @@@ void sumPmf(gmx::ArrayRef<PointState> p
      /* Need to temporarily exponentiate the log weights to sum over simulations */
      for (size_t i = 0; i < buffer.size(); i++)
      {
-         buffer[i] = pointState[i].inTargetRegion() ? std::exp(-pointState[i].logPmfSum()) : 0;
+         buffer[i] = pointState[i].inTargetRegion() ? std::exp(pointState[i].logPmfSum()) : 0;
      }
  
      sumOverSimulations(gmx::ArrayRef<double>(buffer), commRecord, multiSimComm);
      {
          if (pointState[i].inTargetRegion())
          {
-             pointState[i].setLogPmfSum(-std::log(buffer[i] * normFac));
+             pointState[i].setLogPmfSum(std::log(buffer[i] * normFac));
          }
      }
  }
@@@ -323,8 -324,8 +324,8 @@@ void BiasState::calcConvolvedPmf(const 
                  /* Add the convolved PMF weights for the neighbors of this point.
                  Note that this function only adds point within the target > 0 region.
                  Sum weights, take the logarithm last to get the free energy. */
 -                double logWeight = biasedLogWeightFromPoint(dimParams, points_, grid, neighbor,
 -                                                            biasNeighbor, point.coordValue, {}, m);
 +                double logWeight = biasedLogWeightFromPoint(
 +                        dimParams, points_, grid, neighbor, biasNeighbor, point.coordValue, {}, m);
                  freeEnergyWeights += std::exp(logWeight);
              }
          }
@@@ -455,10 -456,7 +456,10 @@@ int BiasState::warnForHistogramAnomalie
                      "If you are not certain about your settings you might want to increase your "
                      "pull force constant or "
                      "modify your sampling region.\n",
 -                    biasIndex + 1, t, pointValueString.c_str(), maxHistogramRatio);
 +                    biasIndex + 1,
 +                    t,
 +                    pointValueString.c_str(),
 +                    maxHistogramRatio);
              gmx::TextLineWrapper wrapper;
              wrapper.settings().setLineLength(c_linewidth);
              fprintf(fplog, "%s", wrapper.wrapToString(warningMessage).c_str());
@@@ -549,8 -547,8 +550,8 @@@ double BiasState::moveUmbrella(const st
                                 bool                          onlySampleUmbrellaGridpoint)
  {
      /* Generate and set a new coordinate reference value */
 -    coordState_.sampleUmbrellaGridpoint(grid, coordState_.gridpointIndex(), probWeightNeighbor,
 -                                        step, seed, indexSeed);
 +    coordState_.sampleUmbrellaGridpoint(
 +            grid, coordState_.gridpointIndex(), probWeightNeighbor, step, seed, indexSeed);
  
      if (onlySampleUmbrellaGridpoint)
      {
@@@ -635,8 -633,8 +636,8 @@@ void BiasState::getSkippedUpdateHistogr
          /* In between global updates the reference histogram size is kept constant so we trivially
             know what the histogram size was at the time of the skipped update. */
          double histogramSize = histogramSize_.histogramSize();
 -        setHistogramUpdateScaleFactors(params, histogramSize, histogramSize, weightHistScaling,
 -                                       logPmfSumScaling);
 +        setHistogramUpdateScaleFactors(
 +                params, histogramSize, histogramSize, weightHistScaling, logPmfSumScaling);
      }
      else
      {
@@@ -1043,12 -1041,8 +1044,12 @@@ bool BiasState::isSamplingRegionCovered
      /* Label each point along each dimension as covered or not. */
      for (int d = 0; d < grid.numDimensions(); d++)
      {
 -        labelCoveredPoints(checkDim[d].visited, checkDim[d].checkCovering, grid.axis(d).numPoints(),
 -                           grid.axis(d).numPointsInPeriod(), params.coverRadius()[d], checkDim[d].covered);
 +        labelCoveredPoints(checkDim[d].visited,
 +                           checkDim[d].checkCovering,
 +                           grid.axis(d).numPoints(),
 +                           grid.axis(d).numPointsInPeriod(),
 +                           params.coverRadius()[d],
 +                           checkDim[d].covered);
      }
  
      /* Now check for global covering. Each dimension needs to be covered separately.
          {
              sumOverSimulations(
                      gmx::arrayRefFromArray(checkDim[d].covered.data(), grid.axis(d).numPoints()),
 -                    commRecord, multiSimComm);
 +                    commRecord,
 +                    multiSimComm);
          }
      }
  
@@@ -1169,8 -1162,8 +1170,8 @@@ void BiasState::updateFreeEnergyAndAddS
      }
  
      /* The weighthistogram size after this update. */
 -    double newHistogramSize = histogramSize_.newHistogramSize(params, t, detectedCovering, points_,
 -                                                              weightSumCovering_, fplog);
 +    double newHistogramSize = histogramSize_.newHistogramSize(
 +            params, t, detectedCovering, points_, weightSumCovering_, fplog);
  
      /* Make the update list. Usually we try to only update local points,
       * but if the update has non-trivial or non-deterministic effects
      }
      double weightHistScalingNew;
      double logPmfsumScalingNew;
 -    setHistogramUpdateScaleFactors(params, newHistogramSize, histogramSize_.histogramSize(),
 -                                   &weightHistScalingNew, &logPmfsumScalingNew);
 +    setHistogramUpdateScaleFactors(
 +            params, newHistogramSize, histogramSize_.histogramSize(), &weightHistScalingNew, &logPmfsumScalingNew);
  
      /* Update free energy and reference weight histogram for points in the update list. */
      for (int pointIndex : *updateList)
          /* Do updates from previous update steps that were skipped because this point was at that time non-local. */
          if (params.skipUpdates())
          {
 -            pointStateToUpdate->performPreviouslySkippedUpdates(params, histogramSize_.numUpdates(),
 -                                                                weightHistScalingSkipped,
 -                                                                logPmfsumScalingSkipped);
 +            pointStateToUpdate->performPreviouslySkippedUpdates(
 +                    params, histogramSize_.numUpdates(), weightHistScalingSkipped, logPmfsumScalingSkipped);
          }
  
          /* Now do an update with new sampling data. */
 -        pointStateToUpdate->updateWithNewSampling(params, histogramSize_.numUpdates(),
 -                                                  weightHistScalingNew, logPmfsumScalingNew);
 +        pointStateToUpdate->updateWithNewSampling(
 +                params, histogramSize_.numUpdates(), weightHistScalingNew, logPmfsumScalingNew);
      }
  
      /* Only update the histogram size after we are done with the local point updates */
@@@ -1279,14 -1273,9 +1280,14 @@@ double BiasState::updateProbabilityWeig
              if (n < neighbors.size())
              {
                  const int neighbor = neighbors[n];
 -                (*weight)[n]       = biasedLogWeightFromPoint(
 -                        dimParams, points_, grid, neighbor, points_[neighbor].bias(),
 -                        coordState_.coordValue(), neighborLambdaEnergies, coordState_.gridpointIndex());
 +                (*weight)[n]       = biasedLogWeightFromPoint(dimParams,
 +                                                        points_,
 +                                                        grid,
 +                                                        neighbor,
 +                                                        points_[neighbor].bias(),
 +                                                        coordState_.coordValue(),
 +                                                        neighborLambdaEnergies,
 +                                                        coordState_.gridpointIndex());
              }
              else
              {
@@@ -1355,8 -1344,8 +1356,8 @@@ double BiasState::calcConvolvedBias(con
          {
              continue;
          }
 -        double logWeight = biasedLogWeightFromPoint(dimParams, points_, grid, neighbor,
 -                                                    points_[neighbor].bias(), coordValue, {}, point);
 +        double logWeight = biasedLogWeightFromPoint(
 +                dimParams, points_, grid, neighbor, points_[neighbor].bias(), coordValue, {}, point);
          weightSum += std::exp(logWeight);
      }
  
@@@ -1447,10 -1436,8 +1448,10 @@@ void BiasState::sampleCoordAndPmf(cons
          std::vector<double> lambdaMarginalDistribution =
                  calculateFELambdaMarginalDistribution(grid, neighbors, probWeightNeighbor);
  
 -        awh_dvec coordValueAlongLambda = { coordState_.coordValue()[0], coordState_.coordValue()[1],
 -                                           coordState_.coordValue()[2], coordState_.coordValue()[3] };
 +        awh_dvec coordValueAlongLambda = { coordState_.coordValue()[0],
 +                                           coordState_.coordValue()[1],
 +                                           coordState_.coordValue()[2],
 +                                           coordState_.coordValue()[3] };
          for (size_t i = 0; i < neighbors.size(); i++)
          {
              const int neighbor = neighbors[i];
@@@ -1560,7 -1547,8 +1561,7 @@@ void BiasState::broadcast(const t_commr
  
      gmx_bcast(points_.size() * sizeof(PointState), points_.data(), commRecord->mpi_comm_mygroup);
  
 -    gmx_bcast(weightSumCovering_.size() * sizeof(double), weightSumCovering_.data(),
 -              commRecord->mpi_comm_mygroup);
 +    gmx_bcast(weightSumCovering_.size() * sizeof(double), weightSumCovering_.data(), commRecord->mpi_comm_mygroup);
  
      gmx_bcast(sizeof(histogramSize_), &histogramSize_, commRecord->mpi_comm_mygroup);
  }
@@@ -1669,8 -1657,8 +1670,8 @@@ static void readUserPmfAndTargetDistrib
  
      if (numRows <= 0)
      {
 -        std::string mesg = gmx::formatString("%s is empty!.\n\n%s", filename.c_str(),
 -                                             correctFormatMessage.c_str());
 +        std::string mesg = gmx::formatString(
 +                "%s is empty!.\n\n%s", filename.c_str(), correctFormatMessage.c_str());
          GMX_THROW(InvalidInputError(mesg));
      }
  
          std::string mesg = gmx::formatString(
                  "%s contains too few data points (%d)."
                  "The minimum number of points is 2.",
 -                filename.c_str(), numRows);
 +                filename.c_str(),
 +                numRows);
          GMX_THROW(InvalidInputError(mesg));
      }
  
          std::string mesg = gmx::formatString(
                  "The number of columns in %s should be at least %d."
                  "\n\n%s",
 -                filename.c_str(), numColumnsMin, correctFormatMessage.c_str());
 +                filename.c_str(),
 +                numColumnsMin,
 +                correctFormatMessage.c_str());
          GMX_THROW(InvalidInputError(mesg));
      }
  
          std::string mesg = gmx::formatString(
                  "Found %d trailing zero data rows in %s. Please remove trailing empty lines and "
                  "try again.",
 -                numZeroRows, filename.c_str());
 +                numZeroRows,
 +                filename.c_str());
          GMX_THROW(InvalidInputError(mesg));
      }
  
          if (target < 0)
          {
              std::string mesg = gmx::formatString(
 -                    "Target distribution weight at point %zu (%g) in %s is negative.", m, target,
 +                    "Target distribution weight at point %zu (%g) in %s is negative.",
 +                    m,
 +                    target,
                      filename.c_str());
              GMX_THROW(InvalidInputError(mesg));
          }
      {
          std::string mesg =
                  gmx::formatString("The target weights given in column %d in %s are all 0",
 -                                  columnIndexTarget, filename.c_str());
 +                                  columnIndexTarget,
 +                                  filename.c_str());
          GMX_THROW(InvalidInputError(mesg));
      }
  
index 7c494282150c8e9887e925b9ab5037f1f5694d78,2a2b5be0622582cbea0ff25c974df0efd834807d..5be37f1de15eddf38688c85b7f1b4e1e196b6820
@@@ -1,7 -1,8 +1,8 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2015,2016,2017,2018,2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 2015,2016,2017,2018,2019, The GROMACS development team.
+  * Copyright (c) 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.
@@@ -261,11 -262,8 +262,11 @@@ void ElectricField::initOutput(FILE* fp
              }
              else
              {
 -                fpField_ = xvgropen(opt2fn("-field", nfile, fnm), "Applied electric field",
 -                                    "Time (ps)", "E (V/nm)", oenv);
 +                fpField_ = xvgropen(opt2fn("-field", nfile, fnm),
 +                                    "Applied electric field",
 +                                    "Time (ps)",
 +                                    "E (V/nm)",
 +                                    oenv);
              }
          }
      }
@@@ -316,7 -314,7 +317,7 @@@ void ElectricField::calculateForces(con
              if (fieldStrength != 0)
              {
                  // TODO: Check parallellism
-                 for (index i = 0; i != ssize(f); ++i)
+                 for (int i = 0; i < mdatoms.homenr; ++i)
                  {
                      // NOTE: Not correct with perturbed charges
                      f[i][m] += mdatoms.chargeA[i] * fieldStrength;
index c88a7526e99f43497913276698056081acfa653a,fadc3d283e1e015c38ae83c6a8378f499dfecc65..89a2d8da3b0b404e5ad9272180808895f377e015
@@@ -3,7 -3,7 +3,7 @@@
   *
   * Copyright (c) 2008,2009,2010,2011,2012 by 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.
@@@ -286,8 -286,7 +286,8 @@@ static const char* eenh_names[eenhNR] 
                                            "energy_delta_h_start_time",
                                            "energy_delta_h_start_lambda" };
  
 -static const char* ePullhNames[epullhNR] = { "pullhistory_numcoordinates", "pullhistory_numgroups",
 +static const char* ePullhNames[epullhNR] = { "pullhistory_numcoordinates",
 +                                             "pullhistory_numgroups",
                                               "pullhistory_numvaluesinxsum",
                                               "pullhistory_numvaluesinfsum" };
  
@@@ -610,26 -609,30 +610,26 @@@ static bool_t listXdrVector(XDR* xd, St
  #if !GMX_DOUBLE
                  if (cptElementType == CptElementType::real3)
                  {
 -                    pr_rvecs(list, 0, entryName(part, ecpt),
 -                             reinterpret_cast<const rvec*>(data.data()), nf / 3);
 +                    pr_rvecs(list, 0, entryName(part, ecpt), reinterpret_cast<const rvec*>(data.data()), nf / 3);
                  }
                  else
  #endif
                  {
                      /* Note: With double precision code dumping a single precision rvec will produce float iso rvec print, but that's a minor annoyance */
 -                    pr_fvec(list, 0, entryName(part, ecpt),
 -                            reinterpret_cast<const float*>(data.data()), nf, TRUE);
 +                    pr_fvec(list, 0, entryName(part, ecpt), reinterpret_cast<const float*>(data.data()), nf, TRUE);
                  }
                  break;
              case xdr_datatype_double:
  #if GMX_DOUBLE
                  if (cptElementType == CptElementType::real3)
                  {
 -                    pr_rvecs(list, 0, entryName(part, ecpt),
 -                             reinterpret_cast<const rvec*>(data.data()), nf / 3);
 +                    pr_rvecs(list, 0, entryName(part, ecpt), reinterpret_cast<const rvec*>(data.data()), nf / 3);
                  }
                  else
  #endif
                  {
                      /* Note: With single precision code dumping a double precision rvec will produce float iso rvec print, but that's a minor annoyance */
 -                    pr_dvec(list, 0, entryName(part, ecpt),
 -                            reinterpret_cast<const double*>(data.data()), nf, TRUE);
 +                    pr_dvec(list, 0, entryName(part, ecpt), reinterpret_cast<const double*>(data.data()), nf, TRUE);
                  }
                  break;
              default: GMX_RELEASE_ASSERT(false, "Data type not implemented for listing");
@@@ -747,9 -750,7 +747,9 @@@ static int doVectorLow(XDR
              {
                  gmx_fatal(FARGS,
                            "Count mismatch for state entry %s, code count is %d, file count is %d\n",
 -                          entryName(part, ecpt), nval, numElemInTheFile);
 +                          entryName(part, ecpt),
 +                          nval,
 +                          numElemInTheFile);
              }
          }
          else if (nptr != nullptr)
          if (!typesMatch)
          {
              char buf[STRLEN];
 -            sprintf(buf, "mismatch for state entry %s, code precision is %s, file precision is %s",
 -                    entryName(part, ecpt), xdr_datatype_names[xdrTypeInTheCode],
 +            sprintf(buf,
 +                    "mismatch for state entry %s, code precision is %s, file precision is %s",
 +                    entryName(part, ecpt),
 +                    xdr_datatype_names[xdrTypeInTheCode],
                      xdr_datatype_names[xdrTypeInTheFile]);
  
              /* Matching int and real should never occur, but check anyhow */
              if (xdrTypeInTheFile == xdr_datatype_int || xdrTypeInTheCode == xdr_datatype_int)
              {
                  gmx_fatal(FARGS,
 -                          "Type %s: incompatible checkpoint formats or corrupted checkpoint file.", buf);
 +                          "Type %s: incompatible checkpoint formats or corrupted checkpoint file.",
 +                          buf);
              }
          }
  
          {
              snew(vChar, numElemInTheFile * sizeOfXdrType(xdrTypeInTheFile));
          }
 -        res = xdr_vector(xd, vChar, numElemInTheFile, sizeOfXdrType(xdrTypeInTheFile),
 -                         xdrProc(xdrTypeInTheFile));
 +        res = xdr_vector(
 +                xd, vChar, numElemInTheFile, sizeOfXdrType(xdrTypeInTheFile), xdrProc(xdrTypeInTheFile));
          if (res == 0)
          {
              return -1;
@@@ -839,16 -837,16 +839,16 @@@ template<typename T
  static int
  doVector(XDR* xd, StatePart part, int ecpt, int sflags, std::vector<T>* vector, FILE* list, int numElements = -1)
  {
 -    return doVectorLow<T>(xd, part, ecpt, sflags, numElements, nullptr, nullptr, vector, list,
 -                          CptElementType::real);
 +    return doVectorLow<T>(
 +            xd, part, ecpt, sflags, numElements, nullptr, nullptr, vector, list, CptElementType::real);
  }
  
  //! \brief Read/Write an ArrayRef<real>.
  static int doRealArrayRef(XDR* xd, StatePart part, int ecpt, int sflags, gmx::ArrayRef<real> vector, FILE* list)
  {
      real* v_real = vector.data();
 -    return doVectorLow<real, std::allocator<real>>(xd, part, ecpt, sflags, vector.size(), nullptr,
 -                                                   &v_real, nullptr, list, CptElementType::real);
 +    return doVectorLow<real, std::allocator<real>>(
 +            xd, part, ecpt, sflags, vector.size(), nullptr, &v_real, nullptr, list, CptElementType::real);
  }
  
  //! Convert from view of RVec to view of real.
@@@ -879,8 -877,8 +879,8 @@@ doRvecVector(XDR* xd, StatePart part, i
          // allocator from RVec to real.
          using realAllocator =
                  typename std::allocator_traits<typename PaddedVectorOfRVecType::allocator_type>::template rebind_alloc<real>;
 -        return doVectorLow<real, realAllocator>(xd, part, ecpt, sflags, numReals, nullptr, nullptr,
 -                                                nullptr, list, CptElementType::real);
 +        return doVectorLow<real, realAllocator>(
 +                xd, part, ecpt, sflags, numReals, nullptr, nullptr, nullptr, list, CptElementType::real);
      }
  }
  
   */
  static int do_cpte_reals(XDR* xd, StatePart part, int ecpt, int sflags, int n, real** v, FILE* list)
  {
 -    return doVectorLow<real, std::allocator<real>>(xd, part, ecpt, sflags, n, nullptr, v, nullptr,
 -                                                   list, CptElementType::real);
 +    return doVectorLow<real, std::allocator<real>>(
 +            xd, part, ecpt, sflags, n, nullptr, v, nullptr, list, CptElementType::real);
  }
  
  /* This function does the same as do_cpte_reals,
   */
  static int do_cpte_n_reals(XDR* xd, StatePart part, int ecpt, int sflags, int* n, real** v, FILE* list)
  {
 -    return doVectorLow<real, std::allocator<real>>(xd, part, ecpt, sflags, -1, n, v, nullptr, list,
 -                                                   CptElementType::real);
 +    return doVectorLow<real, std::allocator<real>>(
 +            xd, part, ecpt, sflags, -1, n, v, nullptr, list, CptElementType::real);
  }
  
  static int do_cpte_real(XDR* xd, StatePart part, int ecpt, int sflags, real* r, FILE* list)
  {
 -    return doVectorLow<real, std::allocator<real>>(xd, part, ecpt, sflags, 1, nullptr, &r, nullptr,
 -                                                   list, CptElementType::real);
 +    return doVectorLow<real, std::allocator<real>>(
 +            xd, part, ecpt, sflags, 1, nullptr, &r, nullptr, list, CptElementType::real);
  }
  
  static int do_cpte_ints(XDR* xd, StatePart part, int ecpt, int sflags, int n, int** v, FILE* list)
  {
 -    return doVectorLow<int, std::allocator<int>>(xd, part, ecpt, sflags, n, nullptr, v, nullptr,
 -                                                 list, CptElementType::integer);
 +    return doVectorLow<int, std::allocator<int>>(
 +            xd, part, ecpt, sflags, n, nullptr, v, nullptr, list, CptElementType::integer);
  }
  
  static int do_cpte_int(XDR* xd, StatePart part, int ecpt, int sflags, int* i, FILE* list)
@@@ -931,8 -929,8 +931,8 @@@ static int do_cpte_bool(XDR* xd, StateP
  
  static int do_cpte_doubles(XDR* xd, StatePart part, int ecpt, int sflags, int n, double** v, FILE* list)
  {
 -    return doVectorLow<double, std::allocator<double>>(xd, part, ecpt, sflags, n, nullptr, v,
 -                                                       nullptr, list, CptElementType::real);
 +    return doVectorLow<double, std::allocator<double>>(
 +            xd, part, ecpt, sflags, n, nullptr, v, nullptr, list, CptElementType::real);
  }
  
  static int do_cpte_double(XDR* xd, StatePart part, int ecpt, int sflags, double* r, FILE* list)
@@@ -946,8 -944,8 +946,8 @@@ static int do_cpte_matrix(XDR* xd, Stat
      int   ret;
  
      vr  = &(v[0][0]);
 -    ret = doVectorLow<real, std::allocator<real>>(xd, part, ecpt, sflags, DIM * DIM, nullptr, &vr,
 -                                                  nullptr, nullptr, CptElementType::matrix3x3);
 +    ret = doVectorLow<real, std::allocator<real>>(
 +            xd, part, ecpt, sflags, DIM * DIM, nullptr, &vr, nullptr, nullptr, CptElementType::matrix3x3);
  
      if (list && ret == 0)
      {
@@@ -971,8 -969,8 +971,8 @@@ static int do_cpte_nmatrix(XDR* xd, Sta
      }
      for (i = 0; i < n; i++)
      {
 -        reti = doVectorLow<real, std::allocator<real>>(xd, part, ecpt, sflags, n, nullptr, &(v[i]),
 -                                                       nullptr, nullptr, CptElementType::matrix3x3);
 +        reti = doVectorLow<real, std::allocator<real>>(
 +                xd, part, ecpt, sflags, n, nullptr, &(v[i]), nullptr, nullptr, CptElementType::matrix3x3);
          if (list && reti == 0)
          {
              sprintf(name, "%s[%d]", entryName(part, ecpt), i);
@@@ -1002,11 -1000,8 +1002,11 @@@ static int do_cpte_matrices(XDR* xd, St
      }
      if (list == nullptr && nf != n)
      {
 -        gmx_fatal(FARGS, "Count mismatch for state entry %s, code count is %d, file count is %d\n",
 -                  entryName(part, ecpt), n, nf);
 +        gmx_fatal(FARGS,
 +                  "Count mismatch for state entry %s, code count is %d, file count is %d\n",
 +                  entryName(part, ecpt),
 +                  n,
 +                  nf);
      }
      if (list || !(sflags & (1 << ecpt)))
      {
              }
          }
      }
 -    ret = doVectorLow<real, std::allocator<real>>(xd, part, ecpt, sflags, nf * DIM * DIM, nullptr,
 -                                                  &vr, nullptr, nullptr, CptElementType::matrix3x3);
 +    ret = doVectorLow<real, std::allocator<real>>(
 +            xd, part, ecpt, sflags, nf * DIM * DIM, nullptr, &vr, nullptr, nullptr, CptElementType::matrix3x3);
      for (i = 0; i < nf; i++)
      {
          for (j = 0; j < DIM; j++)
@@@ -1085,8 -1080,7 +1085,8 @@@ static void do_cpt_header(XDR* xd, gmx_
          gmx_fatal(FARGS,
                    "Start of file magic number mismatch, checkpoint file has %d, should be %d\n"
                    "The checkpoint file is corrupted or not a checkpoint file",
 -                  magic, CPT_MAGIC1);
 +                  magic,
 +                  CPT_MAGIC1);
      }
      char fhost[255];
      if (!bRead)
      {
          gmx_fatal(FARGS,
                    "Attempting to read a checkpoint file of version %d with code of version %d\n",
 -                  contents->file_version, cpt_version);
 +                  contents->file_version,
 +                  cpt_version);
      }
      if (contents->file_version >= 13)
      {
  
      if (contents->file_version >= cptv_ModularSimulator)
      {
 -        do_cpt_bool_err(xd, "Is modular simulator checkpoint",
 -                        &contents->isModularSimulatorCheckpoint, list);
 +        do_cpt_bool_err(
 +                xd, "Is modular simulator checkpoint", &contents->isModularSimulatorCheckpoint, list);
      }
      else
      {
@@@ -1286,10 -1279,7 +1286,10 @@@ static int do_cpt_state(XDR* xd, int ff
              {
                  case estLAMBDA:
                      ret = doRealArrayRef(
 -                            xd, part, i, sflags,
 +                            xd,
 +                            part,
 +                            i,
 +                            sflags,
                              gmx::arrayRefFromArray<real>(state->lambda.data(), state->lambda.size()),
                              list);
                      break;
                      ret = do_cpte_real(xd, part, i, sflags, &state->hist.disre_initf, list);
                      break;
                  case estDISRE_RM3TAV:
 -                    ret = do_cpte_n_reals(xd, part, i, sflags, &state->hist.ndisrepairs,
 -                                          &state->hist.disre_rm3tav, list);
 +                    ret = do_cpte_n_reals(
 +                            xd, part, i, sflags, &state->hist.ndisrepairs, &state->hist.disre_rm3tav, list);
                      break;
                  case estORIRE_INITF:
                      ret = do_cpte_real(xd, part, i, sflags, &state->hist.orire_initf, list);
                      break;
                  case estORIRE_DTAV:
 -                    ret = do_cpte_n_reals(xd, part, i, sflags, &state->hist.norire_Dtav,
 -                                          &state->hist.orire_Dtav, list);
 +                    ret = do_cpte_n_reals(
 +                            xd, part, i, sflags, &state->hist.norire_Dtav, &state->hist.orire_Dtav, list);
                      break;
                  case estPULLCOMPREVSTEP:
                      ret = doVector<double>(xd, part, i, sflags, &state->pull_com_prev_step, list);
@@@ -1559,10 -1549,10 +1559,10 @@@ static int do_cpt_swapstate(XDR* xd, gm
      }
      else
      {
 -        do_cpt_n_rvecs_err(xd, "Ch0 whole x", swapstate->nat[eChan0],
 -                           *swapstate->xc_old_whole_p[eChan0], list);
 -        do_cpt_n_rvecs_err(xd, "Ch1 whole x", swapstate->nat[eChan1],
 -                           *swapstate->xc_old_whole_p[eChan1], list);
 +        do_cpt_n_rvecs_err(
 +                xd, "Ch0 whole x", swapstate->nat[eChan0], *swapstate->xc_old_whole_p[eChan0], list);
 +        do_cpt_n_rvecs_err(
 +                xd, "Ch1 whole x", swapstate->nat[eChan1], *swapstate->xc_old_whole_p[eChan1], list);
      }
  
      return 0;
@@@ -1578,7 -1568,8 +1578,7 @@@ static int do_cpt_enerhist(XDR* xd, gmx
          return ret;
      }
  
 -    GMX_RELEASE_ASSERT(enerhist != nullptr,
 -                       "With energy history, we need a valid enerhist pointer");
 +    GMX_RELEASE_ASSERT(enerhist != nullptr, "With energy history, we need a valid enerhist pointer");
  
      /* This is stored/read for backward compatibility */
      int energyHistoryNumEnergies = 0;
@@@ -1988,8 -1979,8 +1988,8 @@@ static int do_cpt_correlation_grid(XDR
  
      if (bRead)
      {
 -        initCorrelationGridHistory(corrGrid, corrGrid->numCorrelationTensors, corrGrid->tensorSize,
 -                                   corrGrid->blockDataListSize);
 +        initCorrelationGridHistory(
 +                corrGrid, corrGrid->numCorrelationTensors, corrGrid->tensorSize, corrGrid->blockDataListSize);
      }
  
      for (gmx::CorrelationBlockDataHistory& blockData : corrGrid->blockDataBuffer)
@@@ -2075,8 -2066,8 +2075,8 @@@ static int do_cpt_awh_bias(XDR* xd, gmx
                      do_cpt_step_err(xd, eawhh_names[i], &(state->numUpdates), list);
                      break;
                  case eawhhFORCECORRELATIONGRID:
 -                    ret = do_cpt_correlation_grid(xd, bRead, fflags,
 -                                                  &biasHistory->forceCorrelationGrid, list, i);
 +                    ret = do_cpt_correlation_grid(
 +                            xd, bRead, fflags, &biasHistory->forceCorrelationGrid, list, i);
                      break;
                  default: gmx_fatal(FARGS, "Unknown awh history entry %d\n", i);
              }
@@@ -2228,7 -2219,8 +2228,7 @@@ static int do_cpt_files(XDR* xd, gmx_bo
              {
                  return -1;
              }
 -            if (do_cpt_u_chars(xd, "file_checksum", outputfile.checksum.size(),
 -                               outputfile.checksum.data(), list)
 +            if (do_cpt_u_chars(xd, "file_checksum", outputfile.checksum.size(), outputfile.checksum.data(), list)
                  != 0)
              {
                  return -1;
@@@ -2325,19 -2317,18 +2325,19 @@@ void write_checkpoint_data(t_fileio
      if ((do_cpt_state(gmx_fio_getxdr(fp), state->flags, state, nullptr) < 0)
          || (do_cpt_ekinstate(gmx_fio_getxdr(fp), headerContents.flags_eks, &state->ekinstate, nullptr) < 0)
          || (do_cpt_enerhist(gmx_fio_getxdr(fp), FALSE, headerContents.flags_enh, enerhist, nullptr) < 0)
 -        || (doCptPullHist(gmx_fio_getxdr(fp), FALSE, headerContents.flagsPullHistory, pullHist,
 -                          StatePart::pullHistory, nullptr)
 +        || (doCptPullHist(gmx_fio_getxdr(fp), FALSE, headerContents.flagsPullHistory, pullHist, StatePart::pullHistory, nullptr)
              < 0)
 -        || (do_cpt_df_hist(gmx_fio_getxdr(fp), headerContents.flags_dfh, headerContents.nlambda,
 -                           &state->dfhist, nullptr)
 +        || (do_cpt_df_hist(gmx_fio_getxdr(fp), headerContents.flags_dfh, headerContents.nlambda, &state->dfhist, nullptr)
              < 0)
 -        || (do_cpt_EDstate(gmx_fio_getxdr(fp), FALSE, headerContents.nED,
 -                           observablesHistory->edsamHistory.get(), nullptr)
 +        || (do_cpt_EDstate(
 +                    gmx_fio_getxdr(fp), FALSE, headerContents.nED, observablesHistory->edsamHistory.get(), nullptr)
              < 0)
          || (do_cpt_awh(gmx_fio_getxdr(fp), FALSE, headerContents.flags_awhh, state->awhHistory.get(), nullptr) < 0)
 -        || (do_cpt_swapstate(gmx_fio_getxdr(fp), FALSE, headerContents.eSwapCoords,
 -                             observablesHistory->swapHistory.get(), nullptr)
 +        || (do_cpt_swapstate(gmx_fio_getxdr(fp),
 +                             FALSE,
 +                             headerContents.eSwapCoords,
 +                             observablesHistory->swapHistory.get(),
 +                             nullptr)
              < 0)
          || (do_cpt_files(gmx_fio_getxdr(fp), FALSE, outputfiles, nullptr, headerContents.file_version) < 0))
      {
@@@ -2433,7 -2424,7 +2433,7 @@@ static void check_match(FILE
      {
          const char msg_precision_difference[] =
                  "You are continuing a simulation with a different precision. Not matching\n"
-                 "single/double precision will lead to precision or performance loss.\n";
+                 "mixed/double precision will lead to precision or performance loss.\n";
          if (fplog)
          {
              fprintf(fplog, "%s\n", msg_precision_difference);
  
      if (reproducibilityRequested)
      {
 -        check_string(fplog, "Program name", gmx::getProgramContext().fullBinaryPath(),
 -                     headerContents.fprog, &mm);
 +        check_string(
 +                fplog, "Program name", gmx::getProgramContext().fullBinaryPath(), headerContents.fprog, &mm);
  
          check_int(fplog, "#ranks", cr->nnodes, headerContents.nnodes, &mm);
      }
@@@ -2561,24 -2552,21 +2561,24 @@@ static void read_checkpoint(const char
          gmx_fatal(FARGS,
                    "Checkpoint file is for a system of %d atoms, while the current system consists "
                    "of %d atoms",
 -                  headerContents->natoms, state->natoms);
 +                  headerContents->natoms,
 +                  state->natoms);
      }
      if (headerContents->ngtc != state->ngtc)
      {
          gmx_fatal(FARGS,
                    "Checkpoint file is for a system of %d T-coupling groups, while the current "
                    "system consists of %d T-coupling groups",
 -                  headerContents->ngtc, state->ngtc);
 +                  headerContents->ngtc,
 +                  state->ngtc);
      }
      if (headerContents->nnhpres != state->nnhpres)
      {
          gmx_fatal(FARGS,
                    "Checkpoint file is for a system of %d NH-pressure-coupling variables, while the "
                    "current system consists of %d NH-pressure-coupling variables",
 -                  headerContents->nnhpres, state->nnhpres);
 +                  headerContents->nnhpres,
 +                  state->nnhpres);
      }
  
      int nlambdaHistory = (state->dfhist ? state->dfhist->nlambda : 0);
          gmx_fatal(FARGS,
                    "Checkpoint file is for a system with %d lambda states, while the current system "
                    "consists of %d lambda states",
 -                  headerContents->nlambda, nlambdaHistory);
 +                  headerContents->nlambda,
 +                  nlambdaHistory);
      }
  
 -    init_gtc_state(state, state->ngtc, state->nnhpres,
 +    init_gtc_state(state,
 +                   state->ngtc,
 +                   state->nnhpres,
                     headerContents->nhchainlength); /* need to keep this here to keep the tpr format working */
      /* write over whatever was read; we use the number of Nose-Hoover chains from the checkpoint */
  
      {
          observablesHistory->energyHistory = std::make_unique<energyhistory_t>();
      }
 -    ret = do_cpt_enerhist(gmx_fio_getxdr(fp), TRUE, headerContents->flags_enh,
 -                          observablesHistory->energyHistory.get(), nullptr);
 +    ret = do_cpt_enerhist(
 +            gmx_fio_getxdr(fp), TRUE, headerContents->flags_enh, observablesHistory->energyHistory.get(), nullptr);
      if (ret)
      {
          cp_error();
          {
              observablesHistory->pullHistory = std::make_unique<PullHistory>();
          }
 -        ret = doCptPullHist(gmx_fio_getxdr(fp), TRUE, headerContents->flagsPullHistory,
 -                            observablesHistory->pullHistory.get(), StatePart::pullHistory, nullptr);
 +        ret = doCptPullHist(gmx_fio_getxdr(fp),
 +                            TRUE,
 +                            headerContents->flagsPullHistory,
 +                            observablesHistory->pullHistory.get(),
 +                            StatePart::pullHistory,
 +                            nullptr);
          if (ret)
          {
              cp_error();
                    "Continuing from checkpoint files written before GROMACS 4.5 is not supported");
      }
  
 -    ret = do_cpt_df_hist(gmx_fio_getxdr(fp), headerContents->flags_dfh, headerContents->nlambda,
 -                         &state->dfhist, nullptr);
 +    ret = do_cpt_df_hist(
 +            gmx_fio_getxdr(fp), headerContents->flags_dfh, headerContents->nlambda, &state->dfhist, nullptr);
      if (ret)
      {
          cp_error();
      {
          observablesHistory->edsamHistory = std::make_unique<edsamhistory_t>(edsamhistory_t{});
      }
 -    ret = do_cpt_EDstate(gmx_fio_getxdr(fp), TRUE, headerContents->nED,
 -                         observablesHistory->edsamHistory.get(), nullptr);
 +    ret = do_cpt_EDstate(
 +            gmx_fio_getxdr(fp), TRUE, headerContents->nED, observablesHistory->edsamHistory.get(), nullptr);
      if (ret)
      {
          cp_error();
      {
          observablesHistory->swapHistory = std::make_unique<swaphistory_t>(swaphistory_t{});
      }
 -    ret = do_cpt_swapstate(gmx_fio_getxdr(fp), TRUE, headerContents->eSwapCoords,
 -                           observablesHistory->swapHistory.get(), nullptr);
 +    ret = do_cpt_swapstate(
 +            gmx_fio_getxdr(fp), TRUE, headerContents->eSwapCoords, observablesHistory->swapHistory.get(), nullptr);
      if (ret)
      {
          cp_error();
@@@ -2772,19 -2753,9 +2772,19 @@@ void load_checkpoint(const char
      if (SIMMASTER(cr))
      {
          /* Read the state from the checkpoint file */
 -        read_checkpoint(fn, logfio, cr, dd_nc, ir->eI, &(ir->fepvals->init_fep_state),
 -                        &headerContents, state, observablesHistory, reproducibilityRequested,
 -                        mdModulesNotifier, modularSimulatorCheckpointData, useModularSimulator);
 +        read_checkpoint(fn,
 +                        logfio,
 +                        cr,
 +                        dd_nc,
 +                        ir->eI,
 +                        &(ir->fepvals->init_fep_state),
 +                        &headerContents,
 +                        state,
 +                        observablesHistory,
 +                        reproducibilityRequested,
 +                        mdModulesNotifier,
 +                        modularSimulatorCheckpointData,
 +                        useModularSimulator);
      }
      if (PAR(cr))
      {
@@@ -2874,15 -2845,15 +2874,15 @@@ static CheckpointHeaderContents read_ch
          cp_error();
      }
      PullHistory pullHist = {};
 -    ret = doCptPullHist(gmx_fio_getxdr(fp), TRUE, headerContents.flagsPullHistory, &pullHist,
 -                        StatePart::pullHistory, nullptr);
 +    ret                  = doCptPullHist(
 +            gmx_fio_getxdr(fp), TRUE, headerContents.flagsPullHistory, &pullHist, StatePart::pullHistory, nullptr);
      if (ret)
      {
          cp_error();
      }
  
 -    ret = do_cpt_df_hist(gmx_fio_getxdr(fp), headerContents.flags_dfh, headerContents.nlambda,
 -                         &state->dfhist, nullptr);
 +    ret = do_cpt_df_hist(
 +            gmx_fio_getxdr(fp), headerContents.flags_dfh, headerContents.nlambda, &state->dfhist, nullptr);
      if (ret)
      {
          cp_error();
@@@ -3002,14 -2973,14 +3002,14 @@@ void list_checkpoint(const char* fn, FI
      if (ret == 0)
      {
          PullHistory pullHist = {};
 -        ret = doCptPullHist(gmx_fio_getxdr(fp), TRUE, headerContents.flagsPullHistory, &pullHist,
 -                            StatePart::pullHistory, out);
 +        ret                  = doCptPullHist(
 +                gmx_fio_getxdr(fp), TRUE, headerContents.flagsPullHistory, &pullHist, StatePart::pullHistory, out);
      }
  
      if (ret == 0)
      {
 -        ret = do_cpt_df_hist(gmx_fio_getxdr(fp), headerContents.flags_dfh, headerContents.nlambda,
 -                             &state.dfhist, out);
 +        ret = do_cpt_df_hist(
 +                gmx_fio_getxdr(fp), headerContents.flags_dfh, headerContents.nlambda, &state.dfhist, out);
      }
  
      if (ret == 0)
index 6620f1d6dbce8fb2c50c914fd1e58467cd157f27,2d87034417e51a835f728d7409cd5bd5232db580..d50be6feb9237846ed867c7430a23bec01e75c31
@@@ -39,7 -39,7 +39,7 @@@
  
  /* This file is completely threadsafe - keep it that way! */
  
 -#include "tpxio.h"
 +#include "gromacs/fileio/tpxio.h"
  
  #include <cstdio>
  #include <cstdlib>
@@@ -134,8 -134,7 +134,8 @@@ enum tpx
      tpxv_StoreNonBondedInteractionExclusionGroup, /**< Store the non bonded interaction exclusion group in the topology */
      tpxv_VSite1,                                  /**< Added 1 type virtual site */
      tpxv_MTS,                                     /**< Added multiple time stepping */
 -    tpxv_Count                                    /**< the total number of tpxv versions */
 +    tpxv_RemovedConstantAcceleration, /**< Removed support for constant acceleration NEMD. */
 +    tpxv_Count                        /**< the total number of tpxv versions */
  };
  
  /*! \brief Version number of the file format written to run input
  static const int tpx_version = tpxv_Count - 1;
  
  
- /* This number should only be increased when you edit the TOPOLOGY section
-  * or the HEADER of the tpx format.
+ /*! \brief
+  * Enum keeping track of incompatible changes for older TPR versions.
+  *
+  * The enum should be updated with a new field when editing the TOPOLOGY
+  * or HEADER of the tpx format. In particular, updating ftupd or
+  * changing the fields of TprHeaderVersion often trigger such needs.
+  *
   * This way we can maintain forward compatibility too for all analysis tools
   * and/or external programs that only need to know the atom/residue names,
   * charges, and bond connectivity.
   *
   * In particular, it must be increased when adding new elements to
   * ftupd, so that old code can read new .tpr files.
-  *
-  * Updated for added field that contains the number of bytes of the tpr body, excluding the header.
   */
- static const int tpx_generation = 27;
+ enum class TpxGeneration : int
+ {
+     Initial = 26, //! First version is 26
+     AddSizeField, //! TPR header modified for writing as a block.
+     AddVSite1,    //! ftupd changed to include VSite1 type.
+     Count         //! Number of entries.
+ };
+ //! Value of Current TPR generation.
+ static const int tpx_generation = static_cast<int>(TpxGeneration::Count) - 1;
  
  /* This number should be the most recent backwards incompatible version
   * I.e., if this number is 9, we cannot read tpx version 9 with this code.
@@@ -195,6 -206,9 +207,9 @@@ typedef struc
   * obsolete t_interaction_function types. Any data read from such
   * fields is discarded. Their names have _NOLONGERUSED appended to
   * them to make things clear.
+  *
+  * When adding to or making breaking changes to reading this struct,
+  * update TpxGeneration.
   */
  static const t_ftupd ftupd[] = {
      { 70, F_RESTRBONDS },
@@@ -1549,11 -1563,7 +1564,11 @@@ static void do_inputrec(gmx::ISerialize
      {
          ir->opts.nhchainlength = 1;
      }
 -    serializer->doInt(&ir->opts.ngacc);
 +    int removedOptsNgacc = 0;
 +    if (serializer->reading() && file_version < tpxv_RemovedConstantAcceleration)
 +    {
 +        serializer->doInt(&removedOptsNgacc);
 +    }
      serializer->doInt(&ir->opts.ngfrz);
      serializer->doInt(&ir->opts.ngener);
  
          snew(ir->opts.anneal_temp, ir->opts.ngtc);
          snew(ir->opts.tau_t, ir->opts.ngtc);
          snew(ir->opts.nFreeze, ir->opts.ngfrz);
 -        snew(ir->opts.acc, ir->opts.ngacc);
          snew(ir->opts.egp_flags, ir->opts.ngener * ir->opts.ngener);
      }
      if (ir->opts.ngtc > 0)
      {
          serializer->doIvecArray(ir->opts.nFreeze, ir->opts.ngfrz);
      }
 -    if (ir->opts.ngacc > 0)
 +    if (serializer->reading() && file_version < tpxv_RemovedConstantAcceleration && removedOptsNgacc > 0)
 +    {
 +        std::vector<gmx::RVec> dummy;
 +        dummy.resize(removedOptsNgacc);
 +        serializer->doRvecArray(reinterpret_cast<rvec*>(dummy.data()), removedOptsNgacc);
 +        ir->useConstantAcceleration = std::any_of(dummy.begin(), dummy.end(), [](const gmx::RVec& vec) {
 +            return vec[XX] != 0.0 || vec[YY] != 0.0 || vec[ZZ] != 0.0;
 +        });
 +    }
 +    else
      {
 -        serializer->doRvecArray(ir->opts.acc, ir->opts.ngacc);
 +        ir->useConstantAcceleration = false;
      }
      serializer->doIntArray(ir->opts.egp_flags, ir->opts.ngener * ir->opts.ngener);
  
@@@ -2017,12 -2019,8 +2032,12 @@@ static void do_iparams(gmx::ISerializer
              serializer->doInt(&iparams->cmap.cmapB);
              break;
          default:
 -            gmx_fatal(FARGS, "unknown function type %d (%s) in %s line %d", ftype,
 -                      interaction_function[ftype].name, __FILE__, __LINE__);
 +            gmx_fatal(FARGS,
 +                      "unknown function type %d (%s) in %s line %d",
 +                      ftype,
 +                      interaction_function[ftype].name,
 +                      __FILE__,
 +                      __LINE__);
      }
  }
  
@@@ -2606,11 -2604,6 +2621,11 @@@ static void do_mtop(gmx::ISerializer* s
      }
  
      do_groups(serializer, &mtop->groups, &(mtop->symtab));
 +    if (file_version < tpxv_RemovedConstantAcceleration)
 +    {
 +        mtop->groups.groups[SimulationAtomGroupType::AccelerationUnused].clear();
 +        mtop->groups.groupNumbers[SimulationAtomGroupType::AccelerationUnused].clear();
 +    }
  
      mtop->haveMoleculeIndices = true;
  
@@@ -2689,16 -2682,10 +2704,16 @@@ static void do_tpxheader(gmx::FileIOXdr
              gmx_fatal(FARGS,
                        "Unknown precision in file %s: real is %d bytes "
                        "instead of %zu or %zu",
 -                      filename, precision, sizeof(float), sizeof(double));
 +                      filename,
 +                      precision,
 +                      sizeof(float),
 +                      sizeof(double));
          }
          gmx_fio_setprecision(fio, tpx->isDouble);
 -        fprintf(stderr, "Reading file %s, %s (%s precision)\n", filename, buf.c_str(),
 +        fprintf(stderr,
 +                "Reading file %s, %s (%s precision)\n",
 +                filename,
 +                buf.c_str(),
                  tpx->isDouble ? "double" : "single");
      }
      else
                  gmx_fatal(FARGS,
                            "tpx tag/version mismatch: reading tpx file (%s) version %d, tag '%s' "
                            "with program for tpx version %d, tag '%s'",
 -                          filename, tpx->fileVersion, fileTag.c_str(), tpx_version, tpx_tag);
 +                          filename,
 +                          tpx->fileVersion,
 +                          fileTag.c_str(),
 +                          tpx_version,
 +                          tpx_tag);
              }
          }
      }
          || ((tpx->fileVersion > tpx_version) && !TopOnlyOK) || (tpx->fileGeneration > tpx_generation)
          || tpx_version == 80) /*80 was used by both 5.0-dev and 4.6-dev*/
      {
 -        gmx_fatal(FARGS, "reading tpx file (%s) version %d with version %d program", filename,
 -                  tpx->fileVersion, tpx_version);
 +        gmx_fatal(FARGS,
 +                  "reading tpx file (%s) version %d with version %d program",
 +                  filename,
 +                  tpx->fileVersion,
 +                  tpx_version);
      }
  
      serializer->doInt(&tpx->natoms);
@@@ -3302,13 -3282,8 +3317,13 @@@ void write_tpx_state(const char* fn, co
      // TPR file for now - and thus we ask the serializer to swap if this host is little endian.
      gmx::InMemorySerializer tprBodySerializer(gmx::EndianSwapBehavior::SwapIfHostIsLittleEndian);
  
 -    do_tpx_body(&tprBodySerializer, &tpx, const_cast<t_inputrec*>(ir), const_cast<t_state*>(state),
 -                nullptr, nullptr, const_cast<gmx_mtop_t*>(mtop));
 +    do_tpx_body(&tprBodySerializer,
 +                &tpx,
 +                const_cast<t_inputrec*>(ir),
 +                const_cast<t_state*>(state),
 +                nullptr,
 +                nullptr,
 +                const_cast<gmx_mtop_t*>(mtop));
  
      std::vector<char> tprBody = tprBodySerializer.finishAndGetBuffer();
      tpx.sizeOfTprBody         = tprBody.size();
index b5d7061933bb38e31b6c25d4843a780af5c9f7e7,20db415965f52e0d511b2df7a8945d18660a1524..69e86a344e067f505a163558268f2c5e47d2ac63
@@@ -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.
@@@ -207,15 -207,9 +207,15 @@@ static void corr_print(t_corr
      out = xvgropen(fn, title, output_env_get_xvgr_tlabel(oenv), yaxis, oenv);
      if (DD)
      {
 -        fprintf(out, "# MSD gathered over %g %s with %d restarts\n", msdtime,
 -                output_env_get_time_unit(oenv).c_str(), curr->nrestart);
 -        fprintf(out, "# Diffusion constants fitted from time %g to %g %s\n", beginfit, endfit,
 +        fprintf(out,
 +                "# MSD gathered over %g %s with %d restarts\n",
 +                msdtime,
 +                output_env_get_time_unit(oenv).c_str(),
 +                curr->nrestart);
 +        fprintf(out,
 +                "# Diffusion constants fitted from time %g to %g %s\n",
 +                beginfit,
 +                endfit,
                  output_env_get_time_unit(oenv).c_str());
          for (i = 0; i < curr->ngrp; i++)
          {
              fprintf(out, "  %10g", curr->data[j][i]);
              if (bTen)
              {
 -                fprintf(out, " %10g %10g %10g %10g %10g %10g", curr->datam[j][i][XX][XX],
 -                        curr->datam[j][i][YY][YY], curr->datam[j][i][ZZ][ZZ], curr->datam[j][i][YY][XX],
 -                        curr->datam[j][i][ZZ][XX], curr->datam[j][i][ZZ][YY]);
 +                fprintf(out,
 +                        " %10g %10g %10g %10g %10g %10g",
 +                        curr->datam[j][i][XX][XX],
 +                        curr->datam[j][i][YY][YY],
 +                        curr->datam[j][i][ZZ][ZZ],
 +                        curr->datam[j][i][YY][XX],
 +                        curr->datam[j][i][ZZ][XX],
 +                        curr->datam[j][i][ZZ][YY]);
              }
          }
          fprintf(out, "\n");
@@@ -633,13 -622,15 +633,14 @@@ static void printmol(t_corr
          }
      }
      xvgrclose(out);
+     fprintf(stdout, "Wrote per-molecule output to %s\n", fn);
      do_view(oenv, fn, "-graphtype bar");
  
      /* Compute variance, stddev and error */
      Dav /= curr->nmol;
      D2av /= curr->nmol;
      VarD = D2av - gmx::square(Dav);
 -    printf("<D> = %.4f Std. Dev. = %.4f Error = %.4f\n", Dav, std::sqrt(VarD),
 -           std::sqrt(VarD / curr->nmol));
 +    printf("<D> = %.4f Std. Dev. = %.4f Error = %.4f\n", Dav, std::sqrt(VarD), std::sqrt(VarD / curr->nmol));
  
      if (fn_pdb && x)
      {
              pdbinfo[i].bfac *= scale;
          }
          write_sto_conf(fn_pdb, "molecular MSD", &top->atoms, x, nullptr, pbcType, box);
+         fprintf(stdout, "Wrote frame for -tpdb to %s\n", fn_pdb);
      }
  }
  
@@@ -702,8 -694,7 +704,8 @@@ static int corr_loop(t_corr
          fprintf(stderr,
                  "WARNING: The trajectory only contains part of the system (%d of %d atoms) and "
                  "therefore the COM motion of only this part of the system will be removed\n",
 -                natoms, top->atoms.nr);
 +                natoms,
 +                top->atoms.nr);
      }
  
      snew(x[prev], natoms);
  
          curr->nframes++;
      } while (read_next_x(oenv, status, &t, x[cur], box));
 -    fprintf(stderr, "\nUsed %d restart points spaced %g %s over %g %s\n\n", curr->nrestart,
 -            output_env_conv_time(oenv, dt), output_env_get_time_unit(oenv).c_str(),
 +    fprintf(stderr,
 +            "\nUsed %d restart points spaced %g %s over %g %s\n\n",
 +            curr->nrestart,
 +            output_env_conv_time(oenv, dt),
 +            output_env_get_time_unit(oenv).c_str(),
              output_env_conv_time(oenv, curr->time[curr->nframes - 1]),
              output_env_get_time_unit(oenv).c_str());
  
@@@ -966,25 -954,12 +968,25 @@@ static void do_corr(const char
          index_atom2mol(&gnx[0], index[0], &top->mols);
      }
  
 -    msd = std::make_unique<t_corr>(nrgrp, type, axis, dim_factor, mol_file == nullptr ? 0 : gnx[0],
 -                                   bTen, bMW, dt, top, beginfit, endfit);
 -
 -    nat_trx = corr_loop(msd.get(), trx_file, top, pbcType, mol_file ? gnx[0] != 0 : false, gnx.data(),
 -                        index, (mol_file != nullptr) ? calc1_mol : (bMW ? calc1_mw : calc1_norm),
 -                        bTen, gnx_com, index_com, dt, t_pdb, pdb_file ? &x : nullptr, box, oenv);
 +    msd = std::make_unique<t_corr>(
 +            nrgrp, type, axis, dim_factor, mol_file == nullptr ? 0 : gnx[0], bTen, bMW, dt, top, beginfit, endfit);
 +
 +    nat_trx = corr_loop(msd.get(),
 +                        trx_file,
 +                        top,
 +                        pbcType,
 +                        mol_file ? gnx[0] != 0 : false,
 +                        gnx.data(),
 +                        index,
 +                        (mol_file != nullptr) ? calc1_mol : (bMW ? calc1_mw : calc1_norm),
 +                        bTen,
 +                        gnx_com,
 +                        index_com,
 +                        dt,
 +                        t_pdb,
 +                        pdb_file ? &x : nullptr,
 +                        box,
 +                        oenv);
  
      /* Correct for the number of points */
      for (j = 0; (j < msd->ngrp); j++)
              fprintf(stderr,
                      "\nNo frame found need time tpdb = %g ps\n"
                      "Can not write %s\n\n",
 -                    t_pdb, pdb_file);
 +                    t_pdb,
 +                    pdb_file);
          }
          i             = top->atoms.nr;
          top->atoms.nr = nat_trx;
      {
          for (i1 = i0; i1 < msd->nframes && msd->time[i1] <= endfit; i1++) {}
      }
 -    fprintf(stdout, "Fitting from %g to %g %s\n\n", beginfit, endfit,
 -            output_env_get_time_unit(oenv).c_str());
 +    fprintf(stdout, "Fitting from %g to %g %s\n\n", beginfit, endfit, output_env_get_time_unit(oenv).c_str());
  
      N = i1 - i0;
      if (N <= 2)
          }
      }
      /* Print mean square displacement */
 -    corr_print(msd.get(), bTen, msd_file, "Mean Square Displacement", "MSD (nm\\S2\\N)",
 -               msd->time[msd->nframes - 1], beginfit, endfit, DD.data(), SigmaD.data(), grpname, oenv);
 +    corr_print(msd.get(),
 +               bTen,
 +               msd_file,
 +               "Mean Square Displacement",
 +               "MSD (nm\\S2\\N)",
 +               msd->time[msd->nframes - 1],
 +               beginfit,
 +               endfit,
 +               DD.data(),
 +               SigmaD.data(),
 +               grpname,
 +               oenv);
  }
  
  int gmx_msd(int argc, char* argv[])
      real              dim_factor;
      gmx_output_env_t* oenv;
  
 -    if (!parse_common_args(&argc, argv, PCA_CAN_VIEW | PCA_CAN_BEGIN | PCA_CAN_END | PCA_TIME_UNIT,
 -                           NFILE, fnm, asize(pa), pa, asize(desc), desc, 0, nullptr, &oenv))
 +    if (!parse_common_args(&argc,
 +                           argv,
 +                           PCA_CAN_VIEW | PCA_CAN_BEGIN | PCA_CAN_END | PCA_TIME_UNIT,
 +                           NFILE,
 +                           fnm,
 +                           asize(pa),
 +                           pa,
 +                           asize(desc),
 +                           desc,
 +                           0,
 +                           nullptr,
 +                           &oenv))
      {
          return 0;
      }
          gmx_fatal(FARGS, "Could not read a topology from %s. Try a tpr file instead.", tps_file);
      }
  
 -    do_corr(trx_file, ndx_file, msd_file, mol_file, pdb_file, t_pdb, ngroup, &top, pbcType, bTen,
 -            bMW, bRmCOMM, type, dim_factor, axis, dt, beginfit, endfit, oenv);
 +    do_corr(trx_file,
 +            ndx_file,
 +            msd_file,
 +            mol_file,
 +            pdb_file,
 +            t_pdb,
 +            ngroup,
 +            &top,
 +            pbcType,
 +            bTen,
 +            bMW,
 +            bRmCOMM,
 +            type,
 +            dim_factor,
 +            axis,
 +            dt,
 +            beginfit,
 +            endfit,
 +            oenv);
  
      done_top(&top);
      view_all(oenv, NFILE, fnm);
index 22422c2586f15a6a09bd23dd499488d0e7f85099,41dcdaf8421bb71b26c99fbe156e8431b1a4b0bf..6b79fa1e83e26ccd4c485e732bc7356dc8d16e11
@@@ -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.
@@@ -171,8 -171,8 +171,8 @@@ static void get_params(const char* mpin
      psr->ticklinewidth  = get_ereal(&inp, "ticklinewidth", psr->linewidth, wi);
      psr->zerolinewidth  = get_ereal(&inp, "zerolinewidth", psr->ticklinewidth, wi);
      psr->X.lineatzero   = get_eenum(&inp, "x-lineat0value", colors);
-     psr->X.major        = get_ereal(&inp, "x-major", 1, wi);
-     psr->X.minor        = get_ereal(&inp, "x-minor", 1, wi);
+     psr->X.major        = get_ereal(&inp, "x-major", -1, wi);
+     psr->X.minor        = get_ereal(&inp, "x-minor", -1, wi);
      psr->X.offset       = get_ereal(&inp, "x-firstmajor", 0.0, wi);
      psr->X.first        = (get_eenum(&inp, "x-majorat0", gmx_bools) != 0);
      psr->X.majorticklen = get_ereal(&inp, "x-majorticklen", 8.0, wi);
@@@ -422,7 -422,8 +422,7 @@@ static void draw_boxes(t_psdata* ps, re
                  /* Plot label on lowest graph only */
                  if (m == mat.begin())
                  {
 -                    ps_ctext(ps, xx, yy00 - DDD - psr->X.majorticklen - psr->X.tickfontsize * 0.8,
 -                             xtick[x], eXCenter);
 +                    ps_ctext(ps, xx, yy00 - DDD - psr->X.majorticklen - psr->X.tickfontsize * 0.8, xtick[x], eXCenter);
                  }
              }
              else if (bRmod(m->axis_x[x], psr->X.offset, psr->X.minor)
                  /* Major ticks */
                  strlength = std::max(strlength, std::strlen(ytick[y]));
                  ps_line(ps, xx00, yy, xx00 - psr->Y.majorticklen, yy);
 -                ps_ctext(ps, xx00 - psr->Y.majorticklen - DDD, yy - psr->Y.tickfontsize / 3.0,
 -                         ytick[y], eXRight);
 +                ps_ctext(ps, xx00 - psr->Y.majorticklen - DDD, yy - psr->Y.tickfontsize / 3.0, ytick[y], eXRight);
              }
              else if (bRmod(m->axis_y[y], psr->Y.offset, psr->Y.minor))
              {
      if (!mylab.empty())
      {
          ps_strfont(ps, psr->X.font, psr->X.fontsize);
 -        ps_ctext(ps, x0 + w / 2,
 +        ps_ctext(ps,
 +                 x0 + w / 2,
                   y0 - DDD - psr->X.majorticklen - psr->X.tickfontsize * FUDGE - psr->X.fontsize,
 -                 mylab, eXCenter);
 +                 mylab,
 +                 eXCenter);
      }
  }
  
@@@ -641,10 -641,8 +641,10 @@@ static std::vector<t_mapping> add_maps(
      {
          gmx_fatal(FARGS, "Not enough symbols to merge the two colormaps\n");
      }
 -    printf("Combining colormaps of %zu and %zu elements into one of %zu elements\n", map1.size(),
 -           map2.size(), map.size());
 +    printf("Combining colormaps of %zu and %zu elements into one of %zu elements\n",
 +           map1.size(),
 +           map2.size(),
 +           map.size());
      gmx::index k = 0;
      for (gmx::index j = 0; j < gmx::ssize(map1) && k < gmx::ssize(map); ++j, ++k)
      {
@@@ -816,11 -814,7 +816,11 @@@ static void ps_mat(const char
      if (psr->X.major <= 0)
      {
          tick_spacing((mat[0].flags & MAT_SPATIAL_X) ? mat[0].nx + 1 : mat[0].nx,
 -                     mat[0].axis_x.data(), psr->X.offset, 'X', &(psr->X.major), &(psr->X.minor));
 +                     mat[0].axis_x.data(),
 +                     psr->X.offset,
 +                     'X',
 +                     &(psr->X.major),
 +                     &(psr->X.minor));
      }
      if (psr->X.minor <= 0)
      {
      if (psr->Y.major <= 0)
      {
          tick_spacing((mat[0].flags & MAT_SPATIAL_Y) ? mat[0].ny + 1 : mat[0].ny,
 -                     mat[0].axis_y.data(), psr->Y.offset, 'Y', &(psr->Y.major), &(psr->Y.minor));
 +                     mat[0].axis_y.data(),
 +                     psr->Y.offset,
 +                     'Y',
 +                     &(psr->Y.major),
 +                     &(psr->Y.minor));
      }
      if (psr->Y.minor <= 0)
      {
          {
              if (elegend != elBoth)
              {
 -                leg_continuous(&out, x0 + w / 2, w / 2, DDD, legend, psr->legfontsize, psr->legfont,
 -                               leg_map, mapoffset);
 +                leg_continuous(
 +                        &out, x0 + w / 2, w / 2, DDD, legend, psr->legfontsize, psr->legfont, leg_map, mapoffset);
              }
              else
              {
                  assert(!mat2.empty());
 -                leg_bicontinuous(&out, x0 + w / 2, w, DDD, mat[0].legend, mat2[0].legend,
 -                                 psr->legfontsize, psr->legfont, map1, map2);
 +                leg_bicontinuous(
 +                        &out, x0 + w / 2, w, DDD, mat[0].legend, mat2[0].legend, psr->legfontsize, psr->legfont, map1, map2);
              }
          }
          ps_comment(&out, "Done processing");
@@@ -1082,12 -1072,8 +1082,12 @@@ static void prune_mat(gmx::ArrayRef<t_m
                         "Matrix pruning requires matrices of the same size");
      for (gmx::index i = 0; i != gmx::ssize(mat); ++i)
      {
 -        fprintf(stderr, "converting %dx%d matrix to %dx%d\n", mat[i].nx, mat[i].ny,
 -                (mat[i].nx + skip - 1) / skip, (mat[i].ny + skip - 1) / skip);
 +        fprintf(stderr,
 +                "converting %dx%d matrix to %dx%d\n",
 +                mat[i].nx,
 +                mat[i].ny,
 +                (mat[i].nx + skip - 1) / skip,
 +                (mat[i].ny + skip - 1) / skip);
          /* walk through matrix */
          int xs = 0;
          for (int x = 0; (x < mat[i].nx); x++)
@@@ -1195,11 -1181,7 +1195,11 @@@ static void write_combined_matrix(in
              gmx_fatal(FARGS,
                        "Size of frame %zd in 1st (%dx%d) and 2nd matrix (%dx%d) do"
                        " not match.\n",
 -                      k, mat1[k].nx, mat1[k].ny, mat2[k].nx, mat2[k].ny);
 +                      k,
 +                      mat1[k].nx,
 +                      mat1[k].ny,
 +                      mat2[k].nx,
 +                      mat2[k].ny);
          }
          printf("Combining two %dx%d matrices\n", mat1[k].nx, mat1[k].ny);
          rmat1 = matrix2real(&mat1[k], nullptr);
          }
          else
          {
 -            write_xpm(out, mat1[k].flags, mat1[k].title, mat1[k].legend, mat1[k].label_x,
 -                      mat1[k].label_y, mat1[k].nx, mat1[k].ny, mat1[k].axis_x.data(),
 -                      mat1[k].axis_y.data(), rmat1, rlo, rhi, white, black, &nlevels);
 +            write_xpm(out,
 +                      mat1[k].flags,
 +                      mat1[k].title,
 +                      mat1[k].legend,
 +                      mat1[k].label_x,
 +                      mat1[k].label_y,
 +                      mat1[k].nx,
 +                      mat1[k].ny,
 +                      mat1[k].axis_x.data(),
 +                      mat1[k].axis_y.data(),
 +                      rmat1,
 +                      rlo,
 +                      rhi,
 +                      white,
 +                      black,
 +                      &nlevels);
          }
      }
      gmx_ffclose(out);
@@@ -1296,11 -1265,7 +1296,11 @@@ static void do_mat(gmx::ArrayRef<t_matr
                  gmx_fatal(FARGS,
                            "WAKE UP!! Size of frame %zd in 2nd matrix file (%dx%d) does not match "
                            "size of 1st matrix (%dx%d) or the other way around.\n",
 -                          k, mat2[k].nx, mat2[k].ny, mat[k].nx, mat[k].ny);
 +                          k,
 +                          mat2[k].nx,
 +                          mat2[k].ny,
 +                          mat[k].nx,
 +                          mat[k].ny);
              }
              for (int j = 0; (j < mat[k].ny); j++)
              {
  
      if (epsfile != nullptr)
      {
 -        ps_mat(epsfile, mat, mat2, bFrame, bDiag, bFirstDiag, bTitle, bTitleOnce, bYonce, elegend,
 -               size, boxx, boxy, m2p, m2pout, mapoffset);
 +        ps_mat(epsfile, mat, mat2, bFrame, bDiag, bFirstDiag, bTitle, bTitleOnce, bYonce, elegend, size, boxx, boxy, m2p, m2pout, mapoffset);
      }
      if (xpmfile != nullptr)
      {
@@@ -1537,8 -1503,8 +1537,8 @@@ int gmx_xpm2ps(int argc, char* argv[]
                         { efEPS, "-o", nullptr, ffOPTWR },     { efXPM, "-xpm", nullptr, ffOPTWR } };
  #define NFILE asize(fnm)
  
 -    if (!parse_common_args(&argc, argv, PCA_CAN_VIEW, NFILE, fnm, NPA, pa, asize(desc), desc, 0,
 -                           nullptr, &oenv))
 +    if (!parse_common_args(
 +                &argc, argv, PCA_CAN_VIEW, NFILE, fnm, NPA, pa, asize(desc), desc, 0, nullptr, &oenv))
      {
          return 0;
      }
      fn = opt2fn("-f", NFILE, fnm);
      std::vector<t_matrix> mat, mat2;
      mat = read_xpm_matrix(fn);
 -    fprintf(stderr, "There %s %zu matri%s in %s\n", (mat.size() > 1) ? "are" : "is", mat.size(),
 -            (mat.size() > 1) ? "ces" : "x", fn);
 +    fprintf(stderr,
 +            "There %s %zu matri%s in %s\n",
 +            (mat.size() > 1) ? "are" : "is",
 +            mat.size(),
 +            (mat.size() > 1) ? "ces" : "x",
 +            fn);
      fn = opt2fn_null("-f2", NFILE, fnm);
      if (fn)
      {
          mat2 = read_xpm_matrix(fn);
 -        fprintf(stderr, "There %s %zu matri%s in %s\n", (mat2.size() > 1) ? "are" : "is",
 -                mat2.size(), (mat2.size() > 1) ? "ces" : "x", fn);
 +        fprintf(stderr,
 +                "There %s %zu matri%s in %s\n",
 +                (mat2.size() > 1) ? "are" : "is",
 +                mat2.size(),
 +                (mat2.size() > 1) ? "ces" : "x",
 +                fn);
          if (mat.size() != mat2.size())
          {
              fprintf(stderr, "Different number of matrices, using the smallest number.\n");
  
      if (ecombine && ecombine != ecHalves)
      {
 -        write_combined_matrix(ecombine, xpmfile, mat, mat2, opt2parg_bSet("-cmin", NPA, pa) ? &cmin : nullptr,
 +        write_combined_matrix(ecombine,
 +                              xpmfile,
 +                              mat,
 +                              mat2,
 +                              opt2parg_bSet("-cmin", NPA, pa) ? &cmin : nullptr,
                                opt2parg_bSet("-cmax", NPA, pa) ? &cmax : nullptr);
      }
      else
      {
 -        do_mat(mat, mat2, bFrame, bZeroLine, bDiag, bFirstDiag, bTitle, bTitleOnce, bYonce, elegend,
 -               size, boxx, boxy, epsfile, xpmfile, opt2fn_null("-di", NFILE, fnm),
 -               opt2fn_null("-do", NFILE, fnm), skip, mapoffset);
 +        do_mat(mat,
 +               mat2,
 +               bFrame,
 +               bZeroLine,
 +               bDiag,
 +               bFirstDiag,
 +               bTitle,
 +               bTitleOnce,
 +               bYonce,
 +               elegend,
 +               size,
 +               boxx,
 +               boxy,
 +               epsfile,
 +               xpmfile,
 +               opt2fn_null("-di", NFILE, fnm),
 +               opt2fn_null("-do", NFILE, fnm),
 +               skip,
 +               mapoffset);
      }
  
      view_all(oenv, NFILE, fnm);
index 0f23058fa79954d5965067f7bf6b83e8ddc770ec,6153e72b3e3646d7f415e34efcf1b02925dcb427..87bbaa5e2cc81778d732059c81c7f82699ce488c
@@@ -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.
@@@ -217,9 -217,8 +217,9 @@@ const char* get_argtp(int resnr, gmx::A
  
  const char* get_histp(int resnr, gmx::ArrayRef<const RtpRename> rr)
  {
 -    const char* expl[ehisNR] = { "H on ND1 only", "H on NE2 only", "H on ND1 and NE2",
 -                                 "Coupled to Heme" };
 +    const char* expl[ehisNR] = {
 +        "H on ND1 only", "H on NE2 only", "H on ND1 and NE2", "Coupled to Heme"
 +    };
  
      return select_res(ehisNR, resnr, hh, expl, "HISTIDINE", rr);
  }
@@@ -252,12 -251,8 +252,12 @@@ void read_rtprename(const char* fname, 
          {
              if (nc != 2 && nc != 5)
              {
 -                gmx_fatal(FARGS, "Residue renaming database '%s' has %d columns instead of %d or %d",
 -                          fname, ncol, 2, 5);
 +                gmx_fatal(FARGS,
 +                          "Residue renaming database '%s' has %d columns instead of %d or %d",
 +                          fname,
 +                          ncol,
 +                          2,
 +                          5);
              }
              ncol = nc;
          }
              gmx_fatal(FARGS,
                        "A line in residue renaming database '%s' has %d columns, while previous "
                        "lines have %d columns",
 -                      fname, nc, ncol);
 +                      fname,
 +                      nc,
 +                      ncol);
          }
  
          if (nc == 2)
@@@ -314,9 -307,7 +314,9 @@@ std::string search_resrename(gmx::Array
  
          if (newName[0] == '-')
          {
 -            gmx_fatal(FARGS, "In the chosen force field there is no residue type for '%s'%s", name,
 +            gmx_fatal(FARGS,
 +                      "In the chosen force field there is no residue type for '%s'%s",
 +                      name,
                        bStart ? (bEnd ? " as a standalone (starting & ending) residue" : " as a starting terminus")
                               : (bEnd ? " as an ending terminus" : ""));
          }
@@@ -374,8 -365,7 +374,8 @@@ void rename_resrtp(t_atoms
                  GMX_LOG(logger.info)
                          .asParagraph()
                          .appendTextFormatted("Changing rtp entry of residue %d %s to '%s'",
 -                                             pdba->resinfo[r].nr, *pdba->resinfo[r].name,
 +                                             pdba->resinfo[r].nr,
 +                                             *pdba->resinfo[r].name,
                                               newName.c_str());
              }
              pdba->resinfo[r].rtp = put_symtab(symtab, newName.c_str());
@@@ -413,43 -403,77 +413,80 @@@ void rename_pdbres(t_atoms* pdba, cons
      }
  }
  
- void rename_bb(t_atoms* pdba, const char* oldnm, const char* newnm, bool bFullCompare, t_symtab* symtab)
+ /*! \brief Rename all residues named \c oldnm to \c newnm
+  *
+  * Search for residues for which the residue name from the input
+  * configuration file matches \c oldnm, and when found choose the rtp
+  * entry and name of \c newnm.
+  *
+  * \todo Refactor this function to accept a lambda that accepts i and
+  * numMatchesFound but always produces \c newnm. Then remove
+  * renameResiduesInteractively by calling this method with suitable
+  * lambdas that capture its parameter \c rr and ignores
+  * numMatchesFound. */
+ void renameResidue(const gmx::MDLogger& logger,
+                    t_atoms*             pdba,
+                    const char*          oldnm,
+                    const char*          newnm,
+                    bool                 bFullCompare,
+                    t_symtab*            symtab)
  {
-     char* bbnm;
-     int   i;
-     for (i = 0; (i < pdba->nres); i++)
+     int numMatchesFound = 0;
+     for (int i = 0; (i < pdba->nres); i++)
      {
-         /* We have not set the rtp name yes, use the residue name */
-         bbnm = *pdba->resinfo[i].name;
-         if ((bFullCompare && (gmx::equalCaseInsensitive(bbnm, oldnm)))
-             || (!bFullCompare && strstr(bbnm, oldnm) != nullptr))
+         /* We have not set the rtp name yet, use the residue name */
+         const char* residueNameInInputConfiguration = *pdba->resinfo[i].name;
+         if ((bFullCompare && (gmx::equalCaseInsensitive(residueNameInInputConfiguration, oldnm)))
+             || (!bFullCompare && strstr(residueNameInInputConfiguration, oldnm) != nullptr))
          {
-             /* Change the rtp builing block name */
-             pdba->resinfo[i].rtp = put_symtab(symtab, newnm);
+             /* Change the rtp building block name */
+             pdba->resinfo[i].rtp  = put_symtab(symtab, newnm);
+             pdba->resinfo[i].name = pdba->resinfo[i].rtp;
+             numMatchesFound++;
          }
      }
 -                        numMatchesFound, numMatchesFound > 1 ? "s" : "", oldnm, newnm);
+     if (numMatchesFound > 0)
+     {
+         GMX_LOG(logger.info)
+                 .asParagraph()
+                 .appendTextFormatted(
+                         "Replaced %d residue%s named %s to the default %s. Use interactive "
+                         "selection of protonated residues if that is what you need.",
++                        numMatchesFound,
++                        numMatchesFound > 1 ? "s" : "",
++                        oldnm,
++                        newnm);
+     }
  }
  
- void rename_bbint(t_atoms*                       pdba,
-                   const char*                    oldnm,
-                   const char*                    gettp(int, gmx::ArrayRef<const RtpRename>),
-                   bool                           bFullCompare,
-                   t_symtab*                      symtab,
-                   gmx::ArrayRef<const RtpRename> rr)
+ /*! \brief Rename all residues named \c oldnm according to the user's
+  * interactive choice
+  *
+  * Search for residues for which the residue name from the input
+  * configuration file matches \c oldnm, and when found choose the rtp
+  * entry and name of the interactive choice from \c gettp.
+  *
+  * \todo Remove this function, per todo in \c renameResidue. */
+ void renameResidueInteractively(t_atoms*    pdba,
+                                 const char* oldnm,
+                                 const char* gettp(int, gmx::ArrayRef<const RtpRename>),
+                                 bool        bFullCompare,
+                                 t_symtab*   symtab,
+                                 gmx::ArrayRef<const RtpRename> rr)
  {
-     int         i;
-     const char* ptr;
-     char*       bbnm;
-     for (i = 0; i < pdba->nres; i++)
+     // Search for residues i for which the residue name from the input
+     // configuration file matches oldnm, so it can replaced by the rtp
+     // entry and name of newnm.
+     for (int i = 0; i < pdba->nres; i++)
      {
          /* We have not set the rtp name yet, use the residue name */
-         bbnm = *pdba->resinfo[i].name;
-         if ((bFullCompare && (strcmp(bbnm, oldnm) == 0)) || (!bFullCompare && strstr(bbnm, oldnm) != nullptr))
+         char* residueNameInInputConfiguration = *pdba->resinfo[i].name;
+         if ((bFullCompare && (strcmp(residueNameInInputConfiguration, oldnm) == 0))
+             || (!bFullCompare && strstr(residueNameInInputConfiguration, oldnm) != nullptr))
          {
-             ptr                  = gettp(i, rr);
-             pdba->resinfo[i].rtp = put_symtab(symtab, ptr);
+             const char* interactiveRtpChoice = gettp(i, rr);
+             pdba->resinfo[i].rtp             = put_symtab(symtab, interactiveRtpChoice);
+             pdba->resinfo[i].name            = pdba->resinfo[i].rtp;
          }
      }
  }
@@@ -478,8 -502,7 +515,8 @@@ void check_occupancy(t_atoms* atoms, co
                              .appendTextFormatted("Occupancy for atom %s%d-%s is %f rather than 1",
                                                   *atoms->resinfo[atoms->atom[i].resind].name,
                                                   atoms->resinfo[atoms->atom[i].resind].nr,
 -                                                 *atoms->atomname[i], atoms->pdbinfo[i].occup);
 +                                                 *atoms->atomname[i],
 +                                                 atoms->pdbinfo[i].occup);
                  }
                  if (atoms->pdbinfo[i].occup == 0)
                  {
                              "there were %d atoms with zero occupancy and %d atoms with "
                              "         occupancy unequal to one (out of %d atoms). Check your pdb "
                              "file.",
 -                            nzero, nnotone, atoms->nr);
 +                            nzero,
 +                            nnotone,
 +                            atoms->nr);
          }
          else
          {
@@@ -531,11 -552,7 +568,11 @@@ void write_posres(const char* fn, t_ato
              "\n"
              "[ position_restraints ]\n"
              "; %4s%6s%8s%8s%8s\n",
 -            "atom", "type", "fx", "fy", "fz");
 +            "atom",
 +            "type",
 +            "fx",
 +            "fy",
 +            "fz");
      for (i = 0; (i < pdba->nr); i++)
      {
          if (!is_hydrogen(*pdba->atomname[i]) && !is_dummymass(*pdba->atomname[i]))
@@@ -620,7 -637,8 +657,8 @@@ int read_pdball(const char*     inf
      return natom;
  }
  
- void process_chain(t_atoms*                       pdba,
+ void process_chain(const gmx::MDLogger&           logger,
+                    t_atoms*                       pdba,
                     gmx::ArrayRef<gmx::RVec>       x,
                     bool                           bTrpU,
                     bool                           bPheU,
      /* Rename aromatics, lys, asp and histidine */
      if (bTyrU)
      {
-         rename_bb(pdba, "TYR", "TYRU", false, symtab);
+         renameResidue(logger, pdba, "TYR", "TYRU", false, symtab);
      }
      if (bTrpU)
      {
-         rename_bb(pdba, "TRP", "TRPU", false, symtab);
+         renameResidue(logger, pdba, "TRP", "TRPU", false, symtab);
      }
      if (bPheU)
      {
-         rename_bb(pdba, "PHE", "PHEU", false, symtab);
+         renameResidue(logger, pdba, "PHE", "PHEU", false, symtab);
      }
      if (bLysMan)
      {
-         rename_bbint(pdba, "LYS", get_lystp, false, symtab, rr);
+         renameResidueInteractively(pdba, "LYS", get_lystp, false, symtab, rr);
      }
      if (bArgMan)
      {
-         rename_bbint(pdba, "ARG", get_argtp, false, symtab, rr);
+         renameResidueInteractively(pdba, "ARG", get_argtp, false, symtab, rr);
      }
      if (bGlnMan)
      {
-         rename_bbint(pdba, "GLN", get_glntp, false, symtab, rr);
+         renameResidueInteractively(pdba, "GLN", get_glntp, false, symtab, rr);
      }
      if (bAspMan)
      {
-         rename_bbint(pdba, "ASP", get_asptp, false, symtab, rr);
+         renameResidueInteractively(pdba, "ASP", get_asptp, false, symtab, rr);
      }
      else
      {
-         rename_bb(pdba, "ASPH", "ASP", false, symtab);
+         renameResidue(logger, pdba, "ASPH", "ASP", false, symtab);
      }
      if (bGluMan)
      {
-         rename_bbint(pdba, "GLU", get_glutp, false, symtab, rr);
+         renameResidueInteractively(pdba, "GLU", get_glutp, false, symtab, rr);
      }
      else
      {
-         rename_bb(pdba, "GLUH", "GLU", false, symtab);
+         renameResidue(logger, pdba, "GLUH", "GLU", false, symtab);
      }
  
      if (!bHisMan)
      }
      else
      {
-         rename_bbint(pdba, "HIS", get_histp, true, symtab, rr);
+         renameResidueInteractively(pdba, "HIS", get_histp, true, symtab, rr);
      }
  
      /* Initialize the rtp builing block names with the residue names
@@@ -748,18 -766,15 +786,18 @@@ void sort_pdbatoms(gmx::ArrayRef<const 
          atomnm                                  = *pdba->atomname[i];
          const PreprocessResidue* localPpResidue = &restp_chain[pdba->atom[i].resind];
          auto                     found =
 -                std::find_if(localPpResidue->atomname.begin(), localPpResidue->atomname.end(),
 +                std::find_if(localPpResidue->atomname.begin(),
 +                             localPpResidue->atomname.end(),
                               [&atomnm](char** it) { return gmx::equalCaseInsensitive(atomnm, *it); });
          if (found == localPpResidue->atomname.end())
          {
              std::string buf = gmx::formatString(
                      "Atom %s in residue %s %d was not found in rtp entry %s with %d atoms\n"
                      "while sorting atoms.\n%s",
 -                    atomnm, *pdba->resinfo[pdba->atom[i].resind].name,
 -                    pdba->resinfo[pdba->atom[i].resind].nr, localPpResidue->resname.c_str(),
 +                    atomnm,
 +                    *pdba->resinfo[pdba->atom[i].resind].name,
 +                    pdba->resinfo[pdba->atom[i].resind].nr,
 +                    localPpResidue->resname.c_str(),
                      localPpResidue->natom(),
                      is_hydrogen(atomnm)
                              ? "\nFor a hydrogen, this can be a different protonation state, or it\n"
@@@ -833,10 -848,7 +871,10 @@@ int remove_duplicate_atoms(t_atoms* pdb
                  GMX_LOG(logger.info)
                          .asParagraph()
                          .appendTextFormatted("deleting duplicate atom %4s  %s%4d%c",
 -                                             *pdba->atomname[i], *ri->name, ri->nr, ri->ic);
 +                                             *pdba->atomname[i],
 +                                             *ri->name,
 +                                             ri->nr,
 +                                             ri->ic);
                  if (ri->chainid && (ri->chainid != ' '))
                  {
                      printf(" ch %c", ri->chainid);
@@@ -911,11 -923,8 +949,11 @@@ void checkResidueTypeSanity(t_atoms* pd
          gmx_fatal(FARGS,
                    "The chain covering the range %s--%s does not have a consistent chain ID. "
                    "The first residue has ID '%c', while residue %s has ID '%c'.",
 -                  startResidueString.c_str(), endResidueString.c_str(), chainID0,
 -                  residueString.c_str(), chainID);
 +                  startResidueString.c_str(),
 +                  endResidueString.c_str(),
 +                  chainID0,
 +                  residueString.c_str(),
 +                  chainID);
      }
  
      // At this point all residues have the same ID. If they are also non-blank
                        "file in the GROMACS library directory. If there are other molecules "
                        "such as ligands, they should not have the same chain ID as the "
                        "adjacent protein chain since it's a separate molecule.",
 -                      startResidueString.c_str(), endResidueString.c_str(), restype0.c_str(),
 -                      residueString.c_str(), restype.c_str());
 +                      startResidueString.c_str(),
 +                      endResidueString.c_str(),
 +                      restype0.c_str(),
 +                      residueString.c_str(),
 +                      restype.c_str());
          }
      }
  }
@@@ -997,8 -1003,7 +1035,8 @@@ void find_nc_ter(t_atoms* pdba, int r0
              GMX_LOG(logger.info)
                      .asParagraph()
                      .appendTextFormatted("Identified residue %s%d as a starting terminus.",
 -                                         *pdba->resinfo[i].name, pdba->resinfo[i].nr);
 +                                         *pdba->resinfo[i].name,
 +                                         pdba->resinfo[i].nr);
              *r_start = i;
          }
          else if (gmx::equalCaseInsensitive(*startrestype, "Ion"))
                          .appendTextFormatted(
                                  "Residue %s%d has type 'Ion', assuming it is not linked into a "
                                  "chain.",
 -                                *pdba->resinfo[i].name, pdba->resinfo[i].nr);
 +                                *pdba->resinfo[i].name,
 +                                pdba->resinfo[i].nr);
              }
              if (ionNotes == 4)
              {
                                      "be catastrophic if they should in fact be linked. Please "
                                      "check your structure, "
                                      "and add %s to residuetypes.dat if this was not correct.",
 -                                    *pdba->resinfo[i].name, pdba->resinfo[i].nr, *pdba->resinfo[i].name);
 +                                    *pdba->resinfo[i].name,
 +                                    pdba->resinfo[i].nr,
 +                                    *pdba->resinfo[i].name);
                  }
                  else
                  {
                                      "and add all "
                                      "necessary residue names to residuetypes.dat if this was not "
                                      "correct.",
 -                                    *pdba->resinfo[i].name, pdba->resinfo[i].nr);
 +                                    *pdba->resinfo[i].name,
 +                                    pdba->resinfo[i].nr);
                  }
              }
              if (startWarnings == 4)
                              .appendTextFormatted(
                                      "Residue %s%d has type 'Ion', assuming it is not linked into a "
                                      "chain.",
 -                                    *pdba->resinfo[i].name, pdba->resinfo[i].nr);
 +                                    *pdba->resinfo[i].name,
 +                                    pdba->resinfo[i].nr);
                  }
                  if (ionNotes == 4)
                  {
                                      "linked. Please check your structure, and add %s to "
                                      "residuetypes.dat "
                                      "if this was not correct.",
 -                                    *pdba->resinfo[i].name, pdba->resinfo[i].nr, restype->c_str(),
 -                                    *pdba->resinfo[*r_start].name, pdba->resinfo[*r_start].nr,
 -                                    startrestype->c_str(), *pdba->resinfo[i].name);
 +                                    *pdba->resinfo[i].name,
 +                                    pdba->resinfo[i].nr,
 +                                    restype->c_str(),
 +                                    *pdba->resinfo[*r_start].name,
 +                                    pdba->resinfo[*r_start].nr,
 +                                    startrestype->c_str(),
 +                                    *pdba->resinfo[i].name);
                  }
                  if (endWarnings == 4)
                  {
          GMX_LOG(logger.info)
                  .asParagraph()
                  .appendTextFormatted("Identified residue %s%d as a ending terminus.",
 -                                     *pdba->resinfo[*r_end].name, pdba->resinfo[*r_end].nr);
 +                                     *pdba->resinfo[*r_end].name,
 +                                     pdba->resinfo[*r_end].nr);
      }
  }
  
@@@ -1276,16 -1271,9 +1314,16 @@@ void modify_chain_numbers(t_atoms* pdba
                                          "Split the chain (and introduce termini) between residue %s%d (chain id '%c', atom %d %s)\
  "
                                          "and residue %s%d (chain id '%c', atom %d %s) ? [n/y]",
 -                                        prev_resname, prev_resnum, prev_chainid, prev_atomnum,
 -                                        prev_atomname, this_resname, this_resnum, this_chainid,
 -                                        this_atomnum, this_atomname);
 +                                        prev_resname,
 +                                        prev_resnum,
 +                                        prev_chainid,
 +                                        prev_atomnum,
 +                                        prev_atomname,
 +                                        this_resname,
 +                                        this_resnum,
 +                                        this_chainid,
 +                                        this_atomnum,
 +                                        this_atomname);
  
                          if (nullptr == fgets(select, STRLEN - 1, stdin))
                          {
@@@ -1425,9 -1413,8 +1463,9 @@@ enum class VSitesType : in
      Aromatics,
      Count
  };
 -const gmx::EnumerationArray<VSitesType, const char*> c_vsitesTypeNames = { { "none", "hydrogens",
 -                                                                             "aromatics" } };
 +const gmx::EnumerationArray<VSitesType, const char*> c_vsitesTypeNames = {
 +    { "none", "hydrogens", "aromatics" }
 +};
  
  enum class WaterType : int
  {
@@@ -1452,9 -1439,8 +1490,9 @@@ enum class MergeType : in
      Interactive,
      Count
  };
 -const gmx::EnumerationArray<MergeType, const char*> c_mergeTypeNames = { { "no", "all",
 -                                                                           "interactive" } };
 +const gmx::EnumerationArray<MergeType, const char*> c_mergeTypeNames = {
 +    { "no", "all", "interactive" }
 +};
  
  } // namespace
  
@@@ -1838,12 -1824,8 +1876,12 @@@ void pdb2gmx::optionsFinished(
      }
  
      /* Force field selection, interactive or direct */
 -    choose_ff(strcmp(ff_.c_str(), "select") == 0 ? nullptr : ff_.c_str(), forcefield_,
 -              sizeof(forcefield_), ffdir_, sizeof(ffdir_), loggerOwner_->logger());
 +    choose_ff(strcmp(ff_.c_str(), "select") == 0 ? nullptr : ff_.c_str(),
 +              forcefield_,
 +              sizeof(forcefield_),
 +              ffdir_,
 +              sizeof(ffdir_),
 +              loggerOwner_->logger());
  
      if (strlen(forcefield_) > 0)
      {
@@@ -1956,20 -1938,8 +1994,20 @@@ int pdb2gmx::run(
      PbcType        pbcType;
      t_atoms        pdba_all;
      rvec*          pdbx;
 -    int natom = read_pdball(inputConfFile_.c_str(), bOutputSet_, outFile_.c_str(), &title, &pdba_all,
 -                            &pdbx, &pbcType, box, bRemoveH_, &symtab, &rt, watres, &aps, bVerbose_);
 +    int            natom = read_pdball(inputConfFile_.c_str(),
 +                            bOutputSet_,
 +                            outFile_.c_str(),
 +                            &title,
 +                            &pdba_all,
 +                            &pdbx,
 +                            &pbcType,
 +                            box,
 +                            bRemoveH_,
 +                            &symtab,
 +                            &rt,
 +                            watres,
 +                            &aps,
 +                            bVerbose_);
  
      if (natom == 0)
      {
                                      "%s) and chain starting with "
                                      "residue %s%d (chain id '%c', atom %d %s) into a single "
                                      "moleculetype (keeping termini)? [n/y]",
 -                                    prev_resname, prev_resnum, prev_chainid, prev_atomnum,
 -                                    prev_atomname, this_resname, this_resnum, this_chainid,
 -                                    this_atomnum, this_atomname);
 +                                    prev_resname,
 +                                    prev_resnum,
 +                                    prev_chainid,
 +                                    prev_atomnum,
 +                                    prev_atomname,
 +                                    this_resname,
 +                                    this_resnum,
 +                                    this_chainid,
 +                                    this_atomnum,
 +                                    this_atomname);
  
                      if (nullptr == fgets(select, STRLEN - 1, stdin))
                      {
              .appendTextFormatted(
                      "There are %d chains and %d blocks of water and "
                      "%d residues with %d atoms",
 -                    numChains - nwaterchain, nwaterchain, pdba_all.nres, natom);
 +                    numChains - nwaterchain,
 +                    nwaterchain,
 +                    pdba_all.nres,
 +                    natom);
  
      GMX_LOG(logger.info)
              .asParagraph()
      {
          GMX_LOG(logger.info)
                  .asParagraph()
 -                .appendTextFormatted("  %d '%c' %5d %6d  %s\n", i + 1,
 -                                     chains[i].chainid ? chains[i].chainid : '-', chains[i].pdba->nres,
 -                                     chains[i].pdba->nr, chains[i].bAllWat ? "(only water)" : "");
 +                .appendTextFormatted("  %d '%c' %5d %6d  %s\n",
 +                                     i + 1,
 +                                     chains[i].chainid ? chains[i].chainid : '-',
 +                                     chains[i].pdba->nres,
 +                                     chains[i].pdba->nr,
 +                                     chains[i].bAllWat ? "(only water)" : "");
      }
  
      check_occupancy(&pdba_all, inputConfFile_.c_str(), bVerbose_, logger);
              GMX_LOG(logger.info)
                      .asParagraph()
                      .appendTextFormatted("Processing chain %d '%c' (%d atoms, %d residues)",
 -                                         chain + 1, cc->chainid, natom, nres);
 +                                         chain + 1,
 +                                         cc->chainid,
 +                                         natom,
 +                                         nres);
          }
          else
          {
              GMX_LOG(logger.info)
                      .asParagraph()
 -                    .appendTextFormatted("Processing chain %d (%d atoms, %d residues)", chain + 1,
 -                                         natom, nres);
 -        }
 -
 -        process_chain(logger, pdba, x, bUnA_, bUnA_, bUnA_, bLysMan_, bAspMan_, bGluMan_, bHisMan_,
 -                      bArgMan_, bGlnMan_, angle_, distance_, &symtab, rtprename);
 +                    .appendTextFormatted(
 +                            "Processing chain %d (%d atoms, %d residues)", chain + 1, natom, nres);
 +        }
 +
-         process_chain(pdba,
++        process_chain(logger,
++                      pdba,
 +                      x,
 +                      bUnA_,
 +                      bUnA_,
 +                      bUnA_,
 +                      bLysMan_,
 +                      bAspMan_,
 +                      bGluMan_,
 +                      bHisMan_,
 +                      bArgMan_,
 +                      bGlnMan_,
 +                      angle_,
 +                      distance_,
 +                      &symtab,
 +                      rtprename);
  
          cc->chainstart[cc->nterpairs] = pdba->nres;
          j                             = 0;
          for (int i = 0; i < cc->nterpairs; i++)
          {
 -            find_nc_ter(pdba, cc->chainstart[i], cc->chainstart[i + 1], &(cc->r_start[j]),
 -                        &(cc->r_end[j]), &rt, logger);
 +            find_nc_ter(
 +                    pdba, cc->chainstart[i], cc->chainstart[i + 1], &(cc->r_start[j]), &(cc->r_end[j]), &rt, logger);
              if (cc->r_start[j] >= 0 && cc->r_end[j] >= 0)
              {
 -                if (checkChainCyclicity(pdba, pdbx, cc->r_start[j], cc->r_end[j], rtpFFDB,
 -                                        rtprename, long_bond_dist_, short_bond_dist_))
 +                if (checkChainCyclicity(
 +                            pdba, pdbx, cc->r_start[j], cc->r_end[j], rtpFFDB, rtprename, long_bond_dist_, short_bond_dist_))
                  {
                      cc->cyclicBondsIndex.push_back(cc->r_start[j]);
                      cc->cyclicBondsIndex.push_back(cc->r_end[j]);
                  {
                      if (bTerMan_ && !tdblist.empty())
                      {
 -                        sprintf(select, "Select start terminus type for %s-%d",
 -                                *pdba->resinfo[cc->r_start[i]].name, pdba->resinfo[cc->r_start[i]].nr);
 +                        sprintf(select,
 +                                "Select start terminus type for %s-%d",
 +                                *pdba->resinfo[cc->r_start[i]].name,
 +                                pdba->resinfo[cc->r_start[i]].nr);
                          cc->ntdb.push_back(choose_ter(tdblist, select));
                      }
                      else
                          cc->ntdb.push_back(tdblist[0]);
                      }
  
 -                    printf("Start terminus %s-%d: %s\n", *pdba->resinfo[cc->r_start[i]].name,
 -                           pdba->resinfo[cc->r_start[i]].nr, (cc->ntdb[i])->name.c_str());
 +                    printf("Start terminus %s-%d: %s\n",
 +                           *pdba->resinfo[cc->r_start[i]].name,
 +                           pdba->resinfo[cc->r_start[i]].nr,
 +                           (cc->ntdb[i])->name.c_str());
                      tdblist.clear();
                  }
              }
                  {
                      if (bTerMan_ && !tdblist.empty())
                      {
 -                        sprintf(select, "Select end terminus type for %s-%d",
 -                                *pdba->resinfo[cc->r_end[i]].name, pdba->resinfo[cc->r_end[i]].nr);
 +                        sprintf(select,
 +                                "Select end terminus type for %s-%d",
 +                                *pdba->resinfo[cc->r_end[i]].name,
 +                                pdba->resinfo[cc->r_end[i]].nr);
                          cc->ctdb.push_back(choose_ter(tdblist, select));
                      }
                      else
                      {
                          cc->ctdb.push_back(tdblist[0]);
                      }
 -                    printf("End terminus %s-%d: %s\n", *pdba->resinfo[cc->r_end[i]].name,
 -                           pdba->resinfo[cc->r_end[i]].nr, (cc->ctdb[i])->name.c_str());
 +                    printf("End terminus %s-%d: %s\n",
 +                           *pdba->resinfo[cc->r_end[i]].name,
 +                           pdba->resinfo[cc->r_end[i]].nr,
 +                           (cc->ctdb[i])->name.c_str());
                      tdblist.clear();
                  }
              }
          std::vector<MoleculePatchDatabase> hb_chain;
          /* lookup hackblocks and rtp for all residues */
          std::vector<PreprocessResidue> restp_chain;
 -        get_hackblocks_rtp(&hb_chain, &restp_chain, rtpFFDB, pdba->nres, pdba->resinfo, cc->nterpairs,
 -                           &symtab, cc->ntdb, cc->ctdb, cc->r_start, cc->r_end, bAllowMissing_, logger);
 +        get_hackblocks_rtp(&hb_chain,
 +                           &restp_chain,
 +                           rtpFFDB,
 +                           pdba->nres,
 +                           pdba->resinfo,
 +                           cc->nterpairs,
 +                           &symtab,
 +                           cc->ntdb,
 +                           cc->ctdb,
 +                           cc->r_start,
 +                           cc->r_end,
 +                           bAllowMissing_,
 +                           logger);
          /* ideally, now we would not need the rtp itself anymore, but do
             everything using the hb and restp arrays. Unfortunately, that
             requires some re-thinking of code in gen_vsite.c, which I won't
                  .asParagraph()
                  .appendTextFormatted(
                          "Generating any missing hydrogen atoms and/or adding termini.");
 -        add_h(&pdba, &localAtoms[chain], &x, ah, &symtab, cc->nterpairs, cc->ntdb, cc->ctdb,
 -              cc->r_start, cc->r_end, bAllowMissing_, cc->cyclicBondsIndex);
 +        add_h(&pdba,
 +              &localAtoms[chain],
 +              &x,
 +              ah,
 +              &symtab,
 +              cc->nterpairs,
 +              cc->ntdb,
 +              cc->ctdb,
 +              cc->r_start,
 +              cc->r_end,
 +              bAllowMissing_,
 +              cc->cyclicBondsIndex);
          GMX_LOG(logger.info)
                  .asParagraph()
                  .appendTextFormatted("Now there are %d residues with %d atoms", pdba->nres, pdba->nr);
              top_file2 = top_file;
          }
  
 -        pdb2top(top_file2, posre_fn.c_str(), molname.c_str(), pdba, &x, &atype, &symtab, rtpFFDB,
 -                restp_chain, hb_chain, bAllowMissing_, bVsites_, bVsiteAromatics_, ffdir_, mHmult_,
 -                ssbonds, long_bond_dist_, short_bond_dist_, bDeuterate_, bChargeGroups_, bCmap_,
 -                bRenumRes_, bRTPresname_, cc->cyclicBondsIndex, logger);
 +        pdb2top(top_file2,
 +                posre_fn.c_str(),
 +                molname.c_str(),
 +                pdba,
 +                &x,
 +                &atype,
 +                &symtab,
 +                rtpFFDB,
 +                restp_chain,
 +                hb_chain,
 +                bAllowMissing_,
 +                bVsites_,
 +                bVsiteAromatics_,
 +                ffdir_,
 +                mHmult_,
 +                ssbonds,
 +                long_bond_dist_,
 +                short_bond_dist_,
 +                bDeuterate_,
 +                bChargeGroups_,
 +                bCmap_,
 +                bRenumRes_,
 +                bRTPresname_,
 +                cc->cyclicBondsIndex,
 +                logger);
  
          if (!cc->bAllWat)
          {
                      "The topology file '%s' for the selected water "
                      "model '%s' can not be found in the force field "
                      "directory. Select a different water model.",
 -                    waterFile.c_str(), watermodel_);
 +                    waterFile.c_str(),
 +                    watermodel_);
              GMX_THROW(InconsistentInputError(message));
          }
      }
              GMX_LOG(logger.info)
                      .asParagraph()
                      .appendTextFormatted("Including chain %d in system: %d atoms %d residues",
 -                                         i + 1, chains[i].pdba->nr, chains[i].pdba->nres);
 +                                         i + 1,
 +                                         chains[i].pdba->nr,
 +                                         chains[i].pdba->nres);
          }
          for (int j = 0; (j < chains[i].pdba->nr); j++)
          {
      {
          make_new_box(atoms->nr, gmx::as_rvec_array(x.data()), box, box_space, false);
      }
 -    write_sto_conf(outputConfFile_.c_str(), title, atoms, gmx::as_rvec_array(x.data()), nullptr,
 -                   pbcType, box);
 +    write_sto_conf(
 +            outputConfFile_.c_str(), title, atoms, gmx::as_rvec_array(x.data()), nullptr, pbcType, box);
  
      done_symtab(&symtab);
      done_atom(&pdba_all);
      {
          GMX_LOG(logger.info)
                  .asParagraph()
 -                .appendTextFormatted("The %s force field and the %s water model are used.", ffname_,
 -                                     watermodel_);
 +                .appendTextFormatted(
 +                        "The %s force field and the %s water model are used.", ffname_, watermodel_);
          sfree(watermodel_);
      }
      else
index 499fbbd16b7a0d05b1c2d2427c2c2d7c8381a720,f7cb14caecc42010d5d40e86f288caa5fbd33207..9663d50d80f64e5875027c59a9edf8fc02418429
@@@ -72,6 -72,7 +72,7 @@@
  #include "gromacs/topology/mtop_util.h"
  #include "gromacs/topology/symtab.h"
  #include "gromacs/topology/topology.h"
+ #include "gromacs/utility/arrayref.h"
  #include "gromacs/utility/cstringutil.h"
  #include "gromacs/utility/exceptions.h"
  #include "gromacs/utility/fatalerror.h"
  
  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 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];
      char                     fep_lambda[efptNR][STRLEN];
      char                     lambda_weights[STRLEN];
      std::vector<std::string> pullGroupNames;
@@@ -231,6 -233,105 +232,6 @@@ static void process_interaction_modifie
      }
  }
  
 -static void checkMtsRequirement(const t_inputrec& ir, const char* param, const int nstValue, warninp_t wi)
 -{
 -    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)
 -    {
 -        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());
 -    }
 -}
 -
 -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,
  
      set_warning_line(wi, mdparin, -1);
  
 +    /* 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))
 +    {
 +        std::vector<std::string> errorMessages = gmx::checkMtsRequirements(*ir);
 +
 +        for (const auto& errorMessage : errorMessages)
 +        {
 +            warning_error(wi, errorMessage.c_str());
 +        }
 +    }
 +
      if (ir->coulombtype == eelRF_NEC_UNSUPPORTED)
      {
          sprintf(warn_buf, "%s electrostatics is no longer supported", eel_names[eelRF_NEC_UNSUPPORTED]);
                  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]);
 +                        evdw_names[ir->vdwtype],
 +                        evdw_names[evdwCUT],
 +                        eintmod_names[ir->vdw_modifier]);
                  warning_note(wi, warn_buf);
  
                  ir->vdwtype = evdwCUT;
              }
              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",
 +                        evdw_names[ir->vdwtype],
 +                        eintmod_names[ir->vdw_modifier]);
                  warning_error(wi, warn_buf);
              }
          }
  
          if (EEL_USER(ir->coulombtype))
          {
 -            sprintf(warn_buf, "Coulomb type %s is not supported with the verlet scheme",
 +            sprintf(warn_buf,
 +                    "Coulomb type %s is not supported with the verlet scheme",
                      eel_names[ir->coulombtype]);
              warning_error(wi, warn_buf);
          }
                          "Setting tcoupl from '%s' to 'no'. %s handles temperature coupling "
                          "implicitly. See the documentation for more information on which "
                          "parameters affect temperature for %s.",
 -                        etcoupl_names[ir->etc], ei_names[ir->eI], ei_names[ir->eI]);
 +                        etcoupl_names[ir->etc],
 +                        ei_names[ir->eI],
 +                        ei_names[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]);
 +                        etcoupl_names[ir->etc],
 +                        ei_names[ir->eI]);
              }
              warning_note(wi, warn_buf);
          }
          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]);
 +                ei_names[eiVVAK],
 +                ei_names[eiMD],
 +                ei_names[eiVV]);
          warning_note(wi, warn_buf);
      }
      if (!EI_DYNAMICS(ir->eI))
          {
              sprintf(warn_buf,
                      "Setting pcoupl from '%s' to 'no'. Pressure coupling does not apply to %s.",
 -                    epcoupl_names[ir->epc], ei_names[ir->eI]);
 +                    epcoupl_names[ir->epc],
 +                    ei_names[ir->eI]);
              warning_note(wi, warn_buf);
          }
          ir->epc = epcNO;
                  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->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
          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]);
 +            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,
                  "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);
      }
                  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));
  
          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));
  
  
          {
              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]);
 +                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]);
                  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[efptVDW][i],
 +                        fep->all_lambda[efptCOUL][i]);
                  CHECK((fep->sc_alpha > 0)
                        && (((fep->all_lambda[efptCOUL][i] > 0.0) && (fep->all_lambda[efptCOUL][i] < 1.0))
                            && ((fep->all_lambda[efptVDW][i] > 0.0) && (fep->all_lambda[efptVDW][i] < 1.0))));
                      "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);
          }
  
          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]);
 +                expand->equil_n_at_lam,
 +                elmceq_names[elmceqNUMATLAM]);
          CHECK((expand->equil_n_at_lam > 0) && (expand->elmceq != elmceqNUMATLAM));
  
          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]);
 +                expand->equil_samples,
 +                elmceq_names[elmceqSAMPLES]);
          CHECK((expand->equil_samples > 0) && (expand->elmceq != elmceqSAMPLES));
  
          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]);
 +                expand->equil_steps,
 +                elmceq_names[elmceqSTEPS]);
          CHECK((expand->equil_steps > 0) && (expand->elmceq != elmceqSTEPS));
  
          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]);
 +                expand->equil_samples,
 +                elmceq_names[elmceqWLDELTA]);
          CHECK((expand->equil_wl_delta > 0) && (expand->elmceq != elmceqWLDELTA));
  
          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]);
 +                expand->equil_ratio,
 +                elmceq_names[elmceqRATIO]);
          CHECK((expand->equil_ratio > 0) && (expand->elmceq != elmceqRATIO));
  
          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]);
 +                expand->equil_n_at_lam,
 +                elmceq_names[elmceqNUMATLAM]);
          CHECK((expand->equil_n_at_lam <= 0) && (expand->elmceq == elmceqNUMATLAM));
  
          sprintf(err_buf,
                  "weight-equil-number-samples (%d) must be a positive integer if "
                  "lmc-weights-equil=%s",
 -                expand->equil_samples, elmceq_names[elmceqSAMPLES]);
 +                expand->equil_samples,
 +                elmceq_names[elmceqSAMPLES]);
          CHECK((expand->equil_samples <= 0) && (expand->elmceq == elmceqSAMPLES));
  
          sprintf(err_buf,
                  "weight-equil-number-steps (%d) must be a positive integer if lmc-weights-equil=%s",
 -                expand->equil_steps, elmceq_names[elmceqSTEPS]);
 +                expand->equil_steps,
 +                elmceq_names[elmceqSTEPS]);
          CHECK((expand->equil_steps <= 0) && (expand->elmceq == elmceqSTEPS));
  
 -        sprintf(err_buf, "weight-equil-wl-delta (%f) must be > 0 if lmc-weights-equil=%s",
 -                expand->equil_wl_delta, elmceq_names[elmceqWLDELTA]);
 +        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-count-ratio (%f) must be > 0 if lmc-weights-equil=%s",
 -                expand->equil_ratio, elmceq_names[elmceqRATIO]);
 +        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, "lmc-weights-equil=%s only possible when lmc-stats = %s or lmc-stats %s",
 -                elmceq_names[elmceqWLDELTA], elamstats_names[elamstatsWL], elamstats_names[elamstatsWWL]);
 +        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-repeats (%d) must be greater than 0", expand->lmc_repeats);
          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));
          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));
  
              {
                  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);
              }
          }
          }
          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);
          }
          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);
      }
                  "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());
 +                eel_names[eelCUT],
 +                eel_names[eelUSER],
 +                c_pbcTypeNames[PbcType::No].c_str());
          CHECK(((ir->coulombtype != eelCUT) && (ir->coulombtype != eelUSER))
                || (ir->pbcType != PbcType::No) || (ir->rcoulomb != 0.0) || (ir->rvdw != 0.0));
  
  
      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.",
 +                etcoupl_names[ir->etc],
 +                ei_names[ir->eI]);
          CHECK(!(EI_VV(ir->eI)));
  
          if (ir->nstcomm > 0 && (ir->etc == etcANDERSEN))
          sprintf(err_buf,
                  "nstcomm must be 1, not %d for %s, as velocities of atoms in coupled groups are "
                  "randomized every time step",
 -                ir->nstcomm, etcoupl_names[ir->etc]);
 +                ir->nstcomm,
 +                etcoupl_names[ir->etc]);
          CHECK(ir->nstcomm > 1 && (ir->etc == etcANDERSEN));
      }
  
          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));
 +                ETCOUPLTYPE(ir->etc),
 +                ETCOUPLTYPE(etcVRESCALE));
          warning_note(wi, warn_buf);
      }
  
              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);
 +                    EPCOUPLTYPE(ir->epc),
 +                    ir->tau_p,
 +                    pcouple_min_integration_steps(ir->epc),
 +                    dt_pcoupl);
              warning(wi, warn_buf);
          }
  
          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]);
 +                eel_names[ir->coulombtype],
 +                eel_names[eelRF_ZERO]);
          warning(wi, warn_buf);
      }
  
          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",
 +            sprintf(warn_buf,
 +                    "Using epsilon-rf = epsilon-r with %s does not make sense",
                      eel_names[ir->coulombtype]);
              warning(wi, warn_buf);
          }
                      "The switching range should be 5%% or less (currently %.2f%% using a switching "
                      "range of %4f-%4f) for accurate electrostatic energies, energy conservation "
                      "will be good regardless, since ewald_rtol = %g.",
 -                    percentage, ir->rcoulomb_switch, ir->rcoulomb, ir->ewald_rtol);
 +                    percentage,
 +                    ir->rcoulomb_switch,
 +                    ir->rcoulomb,
 +                    ir->ewald_rtol);
              warning(wi, warn_buf);
          }
      }
          if (ir->coulombtype == eelPMESWITCH || ir->coulombtype == eelPMEUSER
              || ir->coulombtype == eelPMEUSERSWITCH)
          {
 -            sprintf(err_buf, "With coulombtype = %s, rcoulomb must be <= rlist",
 -                    eel_names[ir->coulombtype]);
 +            sprintf(err_buf, "With coulombtype = %s, rcoulomb must be <= rlist", eel_names[ir->coulombtype]);
              CHECK(ir->rcoulomb > ir->rlist);
          }
      }
  
          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",
 +                    eel_names[ir->coulombtype],
 +                    orderMin,
 +                    orderMax);
              warning_error(wi, warn_buf);
          }
      }
      {
          if (ir->ewald_geometry == eewg3D)
          {
 -            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(),
 +                    eewg_names[eewg3DC]);
              warning(wi, warn_buf);
          }
          /* This check avoids extra pbc coding for exclusion corrections */
      }
      if ((ir->ewald_geometry == eewg3DC) && (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",
 +                eel_names[ir->coulombtype],
 +                eewg_names[eewg3DC],
 +                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->vdw_modifier == eintmodNONE || ir->vdw_modifier == eintmodPOTSHIFT))
          {
 -            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",
 +                    evdw_names[ir->vdwtype],
 +                    eintmod_names[eintmodPOTSHIFT],
 +                    eintmod_names[eintmodNONE]);
              warning_error(wi, err_buf);
          }
      }
@@@ -1511,9 -1527,8 +1512,9 @@@ static void parse_n_real(char* str, int
          }
          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.");
          }
      }
  }
@@@ -1571,9 -1586,7 +1572,9 @@@ static void do_fep_params(t_inputrec* i
              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],
 +                      efpt_names[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 */
      }
      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))
      {
@@@ -1698,8 -1709,7 +1699,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;
@@@ -1720,14 -1730,39 +1721,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");
              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);
@@@ -1962,17 -1993,17 +1963,15 @@@ void get_ir(const char*     mdparin
      ir->useMts = (get_eeenum(&inp, "mts", yesno_names, wi) != 0);
      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);
-         ir->mtsLevels.resize(2);
-         mtsOpts.level2Forces = setStringEntry(
-                 &inp, "mts-level2-forces", "longrange-nonbonded nonbonded pair dihedral");
++        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");
      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");
  
      /* 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);
                      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]))
 +                    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",
 +                    gmx_fatal(FARGS,
 +                              "Pressure coupling type %s not implemented yet",
                                epcoupltype_names[ir->epct]);
              }
          }
      }
  
      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)
      {
      /* 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)
@@@ -2779,6 -2794,21 +2778,21 @@@ int search_string(const char* s, int ng
                s);
  }
  
+ static void atomGroupRangeValidation(int natoms, int groupIndex, const t_blocka& block)
+ {
+     /* Now go over the atoms in the group */
+     for (int j = block.index[groupIndex]; (j < block.index[groupIndex + 1]); j++)
+     {
+         int aj = block.a[j];
+         /* Range checking */
+         if ((aj < 0) || (aj >= natoms))
+         {
+             gmx_fatal(FARGS, "Invalid atom number %d in indexfile", aj + 1);
+         }
+     }
+ }
  static void do_numbering(int                        natoms,
                           SimulationGroups*          groups,
                           gmx::ArrayRef<std::string> groupsFromMdpFile,
  {
      unsigned short*   cbuf;
      AtomGroupIndices* grps = &(groups->groups[gtype]);
-     int               j, gid, aj, ognr, ntot = 0;
+     int               ntot = 0;
      const char*       title;
      char              warn_buf[STRLEN];
  
      for (int i = 0; i != groupsFromMdpFile.ssize(); ++i)
      {
          /* Lookup the group name in the block structure */
-         gid = search_string(groupsFromMdpFile[i].c_str(), block->nr, gnames);
+         const int gid = search_string(groupsFromMdpFile[i].c_str(), block->nr, gnames);
          if ((grptp != egrptpONE) || (i == 0))
          {
              grps->emplace_back(gid);
          }
+         GMX_ASSERT(block, "Can't have a nullptr block");
+         atomGroupRangeValidation(natoms, gid, *block);
          /* Now go over the atoms in the group */
-         for (j = block->index[gid]; (j < block->index[gid + 1]); j++)
+         for (int j = block->index[gid]; (j < block->index[gid + 1]); j++)
          {
-             aj = block->a[j];
-             /* Range checking */
-             if ((aj < 0) || (aj >= natoms))
-             {
-                 gmx_fatal(FARGS, "Invalid atom number %d in indexfile", aj + 1);
-             }
+             const int aj = block->a[j];
              /* Lookup up the old group number */
-             ognr = cbuf[aj];
+             const int ognr = cbuf[aj];
              if (ognr != NOGID)
              {
 -                gmx_fatal(FARGS, "Atom %d in multiple %s groups (%d and %d)", aj + 1, title,
 -                          ognr + 1, i + 1);
 +                gmx_fatal(FARGS, "Atom %d in multiple %s groups (%d and %d)", aj + 1, title, ognr + 1, i + 1);
              }
              else
              {
              warning_note(wi, warn_buf);
          }
          /* Assign all atoms currently unassigned to a rest group */
-         for (j = 0; (j < natoms); j++)
+         for (int j = 0; (j < natoms); j++)
          {
              if (cbuf[j] == NOGID)
              {
          {
              if (bVerbose)
              {
 -                fprintf(stderr, "Making dummy/rest group for %s containing %d elements\n", title,
 -                        natoms - ntot);
 +                fprintf(stderr, "Making dummy/rest group for %s containing %d elements\n", title, natoms - ntot);
              }
              /* Add group name "rest" */
              grps->emplace_back(restnm);
  
              /* Assign the rest name to all atoms not currently assigned to a group */
-             for (j = 0; (j < natoms); j++)
+             for (int j = 0; (j < natoms); j++)
              {
                  if (cbuf[j] == NOGID)
                  {
@@@ -2941,8 -2967,7 +2949,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]);
           * 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)
              {
          }
  
          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);
@@@ -3273,11 -3292,8 +3281,11 @@@ static void make_swap_groups(t_swapcoor
  
          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 ? eSwapFixedGrp_names[ig] : "Swap",
 +                    swap->grp[ig].molname,
 +                    swapg->nat);
              snew(swapg->ind, swapg->nat);
              for (i = 0; i < swapg->nat; i++)
              {
@@@ -3305,8 -3321,7 +3313,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++)
          {
@@@ -3384,8 -3399,7 +3392,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)
@@@ -3462,22 -3476,14 +3470,22 @@@ void do_index(const char
          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);
                  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);
 +                        ETCOUPLTYPE(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
      {
                  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++;
                      if (ir->opts.annealing[i] != eannNO)
                      {
                          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]),
 +                                eann_names[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]);
                          }
  
                          j = ir->opts.anneal_npoints[i] - 1;
                          if (ir->opts.annealing[i] == eannSINGLE)
                          {
 -                            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)
                              {
  
      if (ir->bPull)
      {
 -            const int gid = search_string(inputrecStrings->pullGroupNames[i].c_str(),
 -                                          defaultIndexGroups->nr, gnames);
+         for (int i = 1; i < ir->pull->ngroup; i++)
+         {
++            const int gid = search_string(
++                    inputrecStrings->pullGroupNames[i].c_str(), defaultIndexGroups->nr, gnames);
+             GMX_ASSERT(defaultIndexGroups, "Must have initialized default index groups");
+             atomGroupRangeValidation(natoms, gid, *defaultIndexGroups);
+         }
          process_pull_groups(ir->pull->group, inputrecStrings->pullGroupNames, defaultIndexGroups, gnames);
  
          checkPullCoords(ir->pull->group, ir->pull->coord);
              *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);
 -
      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);
 +    do_numbering(natoms,
 +                 groups,
 +                 vcmGroupNames,
 +                 defaultIndexGroups,
 +                 gnames,
 +                 SimulationAtomGroupType::MassCenterVelocityRemoval,
 +                 restnm,
 +                 vcmGroupNames.empty() ? egrptpALL_GENREST : egrptpPART,
 +                 bVerbose,
 +                 wi);
  
      if (ir->comm_mode != ecmNO)
      {
      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 */
      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++)
      {
@@@ -4117,8 -4059,7 +4133,8 @@@ static bool absolute_reference(const t_
                              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);
                      }
                  }
              }
@@@ -4155,8 -4096,8 +4171,8 @@@ static void check_combination_rule_diff
  
          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;
      }
@@@ -4216,8 -4157,8 +4232,8 @@@ static void check_combination_rules(con
  {
      bool bLBRulesPossible, bC6ParametersWorkWithGeometricRules, bC6ParametersWorkWithLBRules;
  
 -    check_combination_rule_differences(mtop, 0, &bC6ParametersWorkWithGeometricRules,
 -                                       &bC6ParametersWorkWithLBRules, &bLBRulesPossible);
 +    check_combination_rule_differences(
 +            mtop, 0, &bC6ParametersWorkWithGeometricRules, &bC6ParametersWorkWithLBRules, &bLBRulesPossible);
      if (ir->ljpme_combination_rule == eljpmeLB)
      {
          if (!bC6ParametersWorkWithLBRules || !bLBRulesPossible)
  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;
      ivec                      AbsRef;
      char                      warn_buf[STRLEN];
                          "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);
          }
  
                          "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,
 +                        etcoupl_names[ir->etc],
 +                        ir->nstcomm,
 +                        ir->opts.tau_t[i],
 +                        nsteps);
                  CHECK(nsteps % ir->nstcomm != 0);
              }
          }
              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",
 +                    etcoupl_names[ir->etc],
 +                    epcoupl_names[ir->epc],
 +                    "tau-p",
 +                    ir->tau_p,
 +                    "tau-t",
                      tau_t_max);
              warning(wi, message.c_str());
          }
                      "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));
 +                    EELTYPE(ir->coulombtype),
 +                    EELTYPE(eelCUT));
              warning(wi, err_buf);
          }
      }
                        "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
          && !gmx_within_tol(sys->ffparams.reppow, 12.0, 10 * GMX_DOUBLE_EPS))
      {
                              gmx_fatal(FARGS,
                                        "Can not have dynamic box while using pull geometry '%s' "
                                        "(dim %c)",
 -                                      EPULLGEOM(ir->pull->coord[c].eGeom), 'x' + m);
 +                                      EPULLGEOM(ir->pull->coord[c].eGeom),
 +                                      'x' + m);
                          }
                      }
                  }
index d72b4fa2053d81d2df7ee0a90d494733cd3e134b,36c27ceb647fe620ae0f536b2984db8cfdc67aaa..475afeea73f47456681b9433a8f4d2036340d623
@@@ -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,2017,2018 by the GROMACS development team.
-  * Copyright (c) 2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
  
  /* Must correspond to the Directive enum in grompp_impl.h */
  static gmx::EnumerationArray<Directive, const char*> directive_names = {
 -    { "defaults", "atomtypes", "bondtypes", "constrainttypes", "pairtypes", "angletypes",
 -      "dihedraltypes", "nonbond_params", "implicit_genborn_params", "implicit_surface_params",
 +    { "defaults",
 +      "atomtypes",
 +      "bondtypes",
 +      "constrainttypes",
 +      "pairtypes",
 +      "angletypes",
 +      "dihedraltypes",
 +      "nonbond_params",
 +      "implicit_genborn_params",
 +      "implicit_surface_params",
        "cmaptypes",
        /* All the directives above can not appear after moleculetype */
 -      "moleculetype", "atoms", "virtual_sites1", "virtual_sites2", "virtual_sites3",
 -      "virtual_sites4", "virtual_sitesn", "bonds", "exclusions", "pairs", "pairs_nb", "angles",
 -      "dihedrals", "constraints", "settles", "polarization", "water_polarization",
 -      "thole_polarization", "system", "molecules", "position_restraints", "angle_restraints",
 -      "angle_restraints_z", "distance_restraints", "orientation_restraints", "dihedral_restraints",
 -      "cmap", "intermolecular_interactions", "maxdirs", "invalid", "none" }
 +      "moleculetype",
 +      "atoms",
++      "virtual_sites1",
 +      "virtual_sites2",
 +      "virtual_sites3",
 +      "virtual_sites4",
 +      "virtual_sitesn",
 +      "bonds",
 +      "exclusions",
 +      "pairs",
 +      "pairs_nb",
 +      "angles",
 +      "dihedrals",
 +      "constraints",
 +      "settles",
 +      "polarization",
 +      "water_polarization",
 +      "thole_polarization",
 +      "system",
 +      "molecules",
 +      "position_restraints",
 +      "angle_restraints",
 +      "angle_restraints_z",
 +      "distance_restraints",
 +      "orientation_restraints",
 +      "dihedral_restraints",
 +      "cmap",
 +      "intermolecular_interactions",
 +      "maxdirs",
 +      "invalid",
 +      "none" }
  };
  
  int ifunc_index(Directive d, int type)
              {
                  return F_BHAM;
              }
+         case Directive::d_vsites1:
+             if (type == 1)
+             {
+                 return F_VSITE1;
+             }
+             else
+             {
+                 gmx_fatal(FARGS, "Invalid vsites1 type %d", type);
+             }
          case Directive::d_vsites2:
              switch (type)
              {
@@@ -303,21 -280,21 +313,22 @@@ void DS_Init(DirStack** DS
          // be in the same place that was valid in old versions (ie. child
          // directive of [atomtypes]) but any relevant case will
          // satisfy that.
 -        set_nec(&(necessary[Directive::d_implicit_genborn_params]), Directive::d_atomtypes,
 -                Directive::d_none);
 -        set_nec(&(necessary[Directive::d_implicit_surface_params]), Directive::d_atomtypes,
 -                Directive::d_none);
 +        set_nec(&(necessary[Directive::d_implicit_genborn_params]), Directive::d_atomtypes, Directive::d_none);
 +        set_nec(&(necessary[Directive::d_implicit_surface_params]), Directive::d_atomtypes, Directive::d_none);
          set_nec(&(necessary[Directive::d_cmaptypes]), Directive::d_atomtypes, Directive::d_none);
          set_nec(&(necessary[Directive::d_moleculetype]), Directive::d_atomtypes, Directive::d_none);
          set_nec(&(necessary[Directive::d_atoms]), Directive::d_moleculetype, Directive::d_none);
+         set_nec(&(necessary[Directive::d_vsites1]), Directive::d_atoms, Directive::d_none);
          set_nec(&(necessary[Directive::d_vsites2]), Directive::d_atoms, Directive::d_none);
          set_nec(&(necessary[Directive::d_vsites3]), Directive::d_atoms, Directive::d_none);
          set_nec(&(necessary[Directive::d_vsites4]), Directive::d_atoms, Directive::d_none);
          set_nec(&(necessary[Directive::d_vsitesn]), Directive::d_atoms, Directive::d_none);
          set_nec(&(necessary[Directive::d_bonds]), Directive::d_atoms, Directive::d_none);
 -        set_nec(&(necessary[Directive::d_exclusions]), Directive::d_bonds, Directive::d_constraints,
 -                Directive::d_settles, Directive::d_none);
 +        set_nec(&(necessary[Directive::d_exclusions]),
 +                Directive::d_bonds,
 +                Directive::d_constraints,
 +                Directive::d_settles,
 +                Directive::d_none);
          set_nec(&(necessary[Directive::d_pairs]), Directive::d_atoms, Directive::d_none);
          set_nec(&(necessary[Directive::d_pairs_nb]), Directive::d_atoms, Directive::d_none);
          set_nec(&(necessary[Directive::d_angles]), Directive::d_atoms, Directive::d_none);
          set_nec(&(necessary[Directive::d_orientation_restraints]), Directive::d_atoms, Directive::d_none);
          set_nec(&(necessary[Directive::d_dihedral_restraints]), Directive::d_atoms, Directive::d_none);
          set_nec(&(necessary[Directive::d_cmap]), Directive::d_atoms, Directive::d_none);
 -        set_nec(&(necessary[Directive::d_intermolecular_interactions]), Directive::d_molecules,
 +        set_nec(&(necessary[Directive::d_intermolecular_interactions]),
 +                Directive::d_molecules,
                  Directive::d_none);
      }
      *DS = nullptr;
index 998fc40eddde9f8f204b9f138dc680e3b86c5b6a,7c7343a7018df5fc023e34d340a0f94761d61bad..cf702b1f9178adb9c315e3127253769e6a70c7c1
@@@ -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.
@@@ -164,13 -164,8 +164,13 @@@ double check_mol(const gmx_mtop_t* mtop
              if (((m <= 0.0) || (mB <= 0.0)) && ((pt == eptAtom) || (pt == eptNucleus)))
              {
                  ri = atoms->atom[i].resind;
 -                sprintf(buf, "atom %s (Res %s-%d) has mass %g (state A) / %g (state B)\n",
 -                        *(atoms->atomname[i]), *(atoms->resinfo[ri].name), atoms->resinfo[ri].nr, m, mB);
 +                sprintf(buf,
 +                        "atom %s (Res %s-%d) has mass %g (state A) / %g (state B)\n",
 +                        *(atoms->atomname[i]),
 +                        *(atoms->resinfo[ri].name),
 +                        atoms->resinfo[ri].nr,
 +                        m,
 +                        mB);
                  warning_error(wi, buf);
              }
              else if (((m != 0) || (mB != 0)) && (pt == eptVSite))
                          "virtual site %s (Res %s-%d) has non-zero mass %g (state A) / %g (state "
                          "B)\n"
                          "     Check your topology.\n",
 -                        *(atoms->atomname[i]), *(atoms->resinfo[ri].name), atoms->resinfo[ri].nr, m, mB);
 +                        *(atoms->atomname[i]),
 +                        *(atoms->resinfo[ri].name),
 +                        atoms->resinfo[ri].nr,
 +                        m,
 +                        mB);
                  warning_error(wi, buf);
                  /* The following statements make LINCS break! */
                  /* atoms->atom[i].m=0; */
@@@ -562,10 -553,8 +562,10 @@@ static char** read_topol(const char
                          {
                              /* we should print here which directives should have
                                 been present, and which actually are */
 -                            gmx_fatal(FARGS, "%s\nInvalid order for directive %s",
 -                                      cpp_error(&handle, eCPP_SYNTAX), dir2str(newd));
 +                            gmx_fatal(FARGS,
 +                                      "%s\nInvalid order for directive %s",
 +                                      cpp_error(&handle, eCPP_SYNTAX),
 +                                      dir2str(newd));
                              /* d = Directive::d_invalid; */
                          }
  
                          case Directive::d_defaults:
                              if (bReadDefaults)
                              {
 -                                gmx_fatal(FARGS, "%s\nFound a second defaults directive.\n",
 +                                gmx_fatal(FARGS,
 +                                          "%s\nFound a second defaults directive.\n",
                                            cpp_error(&handle, eCPP_SYNTAX));
                              }
                              bReadDefaults = TRUE;
 -                            nscan = sscanf(pline, "%s%s%s%lf%lf%lf", nb_str, comb_str, genpairs,
 -                                           &fLJ, &fQQ, &fPOW);
 +                            nscan         = sscanf(
 +                                    pline, "%s%s%s%lf%lf%lf", nb_str, comb_str, genpairs, &fLJ, &fQQ, &fPOW);
                              if (nscan < 2)
                              {
                                  too_few(wi);
  
                              break;
                          case Directive::d_atomtypes:
 -                            push_at(symtab, atypes, &bondAtomType, pline, nb_funct, &nbparam,
 -                                    bGenPairs ? &pair : nullptr, wi);
 +                            push_at(symtab,
 +                                    atypes,
 +                                    &bondAtomType,
 +                                    pline,
 +                                    nb_funct,
 +                                    &nbparam,
 +                                    bGenPairs ? &pair : nullptr,
 +                                    wi);
                              break;
  
                          case Directive::d_bondtypes: // Intended to fall through
                                          || opts->couple_lam1 == ecouplamNONE
                                          || opts->couple_lam1 == ecouplamQ))
                                  {
 -                                    dcatt = add_atomtype_decoupled(symtab, atypes, &nbparam,
 -                                                                   bGenPairs ? &pair : nullptr);
 +                                    dcatt = add_atomtype_decoupled(
 +                                            symtab, atypes, &nbparam, bGenPairs ? &pair : nullptr);
                                  }
                                  ntype  = atypes->size();
                                  ncombs = (ntype * (ntype + 1)) / 2;
 -                                generate_nbparams(*combination_rule, nb_funct,
 -                                                  &(interactions[nb_funct]), atypes, wi);
 +                                generate_nbparams(
 +                                        *combination_rule, nb_funct, &(interactions[nb_funct]), atypes, wi);
                                  ncopy = copy_nbparams(nbparam, nb_funct, &(interactions[nb_funct]), ntype);
                                  GMX_LOG(logger.info)
                                          .asParagraph()
                                          .appendTextFormatted(
                                                  "Generated %d of the %d non-bonded parameter "
                                                  "combinations",
 -                                                ncombs - ncopy, ncombs);
 +                                                ncombs - ncopy,
 +                                                ncombs);
                                  free_nbparam(nbparam, ntype);
                                  if (bGenPairs)
                                  {
 -                                    gen_pairs((interactions[nb_funct]), &(interactions[F_LJ14]),
 -                                              fudgeLJ, *combination_rule);
 +                                    gen_pairs((interactions[nb_funct]),
 +                                              &(interactions[F_LJ14]),
 +                                              fudgeLJ,
 +                                              *combination_rule);
                                      ncopy = copy_nbparams(pair, nb_funct, &(interactions[F_LJ14]), ntype);
                                      GMX_LOG(logger.info)
                                              .asParagraph()
                                              .appendTextFormatted(
                                                      "Generated %d of the %d 1-4 parameter "
                                                      "combinations",
 -                                                    ncombs - ncopy, ncombs);
 +                                                    ncombs - ncopy,
 +                                                    ncombs);
                                      free_nbparam(pair, ntype);
                                  }
                                  /* Copy GBSA parameters to atomtype array? */
                              GMX_RELEASE_ASSERT(
                                      mi0,
                                      "Need to have a valid MoleculeInformation object to work on");
 -                            push_bond(d, interactions, mi0->interactions, &(mi0->atoms), atypes,
 -                                      pline, FALSE, bGenPairs, *fudgeQQ, bZero, &bWarn_copy_A_B, wi);
 +                            push_bond(d,
 +                                      interactions,
 +                                      mi0->interactions,
 +                                      &(mi0->atoms),
 +                                      atypes,
 +                                      pline,
 +                                      FALSE,
 +                                      bGenPairs,
 +                                      *fudgeQQ,
 +                                      bZero,
 +                                      &bWarn_copy_A_B,
 +                                      wi);
                              break;
                          case Directive::d_pairs_nb:
                              GMX_RELEASE_ASSERT(
                                      mi0,
                                      "Need to have a valid MoleculeInformation object to work on");
 -                            push_bond(d, interactions, mi0->interactions, &(mi0->atoms), atypes,
 -                                      pline, FALSE, FALSE, 1.0, bZero, &bWarn_copy_A_B, wi);
 +                            push_bond(d,
 +                                      interactions,
 +                                      mi0->interactions,
 +                                      &(mi0->atoms),
 +                                      atypes,
 +                                      pline,
 +                                      FALSE,
 +                                      FALSE,
 +                                      1.0,
 +                                      bZero,
 +                                      &bWarn_copy_A_B,
 +                                      wi);
                              break;
  
+                         case Directive::d_vsites1:
                          case Directive::d_vsites2:
                          case Directive::d_vsites3:
                          case Directive::d_vsites4:
                              GMX_RELEASE_ASSERT(
                                      mi0,
                                      "Need to have a valid MoleculeInformation object to work on");
 -                            push_bond(d, interactions, mi0->interactions, &(mi0->atoms), atypes,
 -                                      pline, TRUE, bGenPairs, *fudgeQQ, bZero, &bWarn_copy_A_B, wi);
 +                            push_bond(d,
 +                                      interactions,
 +                                      mi0->interactions,
 +                                      &(mi0->atoms),
 +                                      atypes,
 +                                      pline,
 +                                      TRUE,
 +                                      bGenPairs,
 +                                      *fudgeQQ,
 +                                      bZero,
 +                                      &bWarn_copy_A_B,
 +                                      wi);
                              break;
                          case Directive::d_cmap:
                              GMX_RELEASE_ASSERT(
                                      .asParagraph()
                                      .appendTextFormatted(
                                              "Excluding %d bonded neighbours molecule type '%s'",
 -                                            mi0->nrexcl, *mi0->name);
 +                                            mi0->nrexcl,
 +                                            *mi0->name);
                              sum_q(&mi0->atoms, nrcopies, &qt, &qBt);
                              if (!mi0->bProcessed)
                              {
  
                                  if (bCouple)
                                  {
 -                                    convert_moltype_couple(mi0, dcatt, *fudgeQQ, opts->couple_lam0,
 -                                                           opts->couple_lam1, opts->bCoupleIntra,
 -                                                           nb_funct, &(interactions[nb_funct]), wi);
 +                                    convert_moltype_couple(mi0,
 +                                                           dcatt,
 +                                                           *fudgeQQ,
 +                                                           opts->couple_lam0,
 +                                                           opts->couple_lam1,
 +                                                           opts->bCoupleIntra,
 +                                                           nb_funct,
 +                                                           &(interactions[nb_funct]),
 +                                                           wi);
                                  }
                                  stupid_fill_block(&mi0->mols, mi0->atoms.nr, TRUE);
                                  mi0->bProcessed = TRUE;
          }
          GMX_LOG(logger.info)
                  .asParagraph()
 -                .appendTextFormatted("Coupling %d copies of molecule type '%s'", nmol_couple,
 -                                     opts->couple_moltype);
 +                .appendTextFormatted(
 +                        "Coupling %d copies of molecule type '%s'", nmol_couple, opts->couple_moltype);
      }
  
      /* this is not very clean, but fixes core dump on empty system name */
      }
      if (fabs(qBt) > 1e-4 && !gmx_within_tol(qBt, qt, 1e-6))
      {
 -        sprintf(warn_buf, "State B has non-zero total charge: %.6f\n%s\n", qBt,
 -                floating_point_arithmetic_tip);
 +        sprintf(warn_buf, "State B has non-zero total charge: %.6f\n%s\n", qBt, floating_point_arithmetic_tip);
          warning_note(wi, warn_buf);
      }
      if (usingFullRangeElectrostatics && (fabs(qt) > 1e-4 || fabs(qBt) > 1e-4))
@@@ -1053,26 -996,10 +1054,26 @@@ char** do_top(boo
      {
          GMX_LOG(logger.info).asParagraph().appendTextFormatted("processing topology...");
      }
 -    title = read_topol(topfile, tmpfile, opts->define, opts->include, symtab, atypes, molinfo,
 -                       intermolecular_interactions, interactions, combination_rule, repulsion_power,
 -                       opts, fudgeQQ, molblock, ffParametrizedWithHBondConstraints,
 -                       ir->efep != efepNO, bZero, EEL_FULL(ir->coulombtype), wi, logger);
 +    title = read_topol(topfile,
 +                       tmpfile,
 +                       opts->define,
 +                       opts->include,
 +                       symtab,
 +                       atypes,
 +                       molinfo,
 +                       intermolecular_interactions,
 +                       interactions,
 +                       combination_rule,
 +                       repulsion_power,
 +                       opts,
 +                       fudgeQQ,
 +                       molblock,
 +                       ffParametrizedWithHBondConstraints,
 +                       ir->efep != efepNO,
 +                       bZero,
 +                       EEL_FULL(ir->coulombtype),
 +                       wi,
 +                       logger);
  
      if ((*combination_rule != eCOMB_GEOMETRIC) && (ir->vdwtype == evdwUSER))
      {
index 063fac54b5203a93da9bee709c2b6c5070e609c7,fb84d41f2b14a937e56c3c3118b0e792ffd69a1e..93021e418f9420c975d46fe9f0b45d90d17944be
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -124,7 -124,8 +124,8 @@@ static bool isDeviceFunctional(const cl
          cl::sycl::buffer<int, 1> buffer(numThreads);
          queue.submit([&](cl::sycl::handler& cgh) {
                   auto d_buffer = buffer.get_access<cl::sycl::access::mode::discard_write>(cgh);
-                  cgh.parallel_for<class DummyKernel>(numThreads, [=](cl::sycl::id<1> threadId) {
+                  cl::sycl::range<1> range{ numThreads };
+                  cgh.parallel_for<class DummyKernel>(range, [=](cl::sycl::id<1> threadId) {
                       d_buffer[threadId] = threadId.get(0);
                   });
               })
      {
          if (errorMessage != nullptr)
          {
 -            errorMessage->assign(gmx::formatString(
 -                    "Unable to run dummy kernel on device %s: %s",
 -                    syclDevice.get_info<cl::sycl::info::device::name>().c_str(), e.what()));
 +            errorMessage->assign(
 +                    gmx::formatString("Unable to run dummy kernel on device %s: %s",
 +                                      syclDevice.get_info<cl::sycl::info::device::name>().c_str(),
 +                                      e.what()));
          }
          return false;
      }
@@@ -202,7 -202,7 +203,7 @@@ std::vector<std::unique_ptr<DeviceInfor
          deviceInfos[i]->syclDevice = syclDevice;
          deviceInfos[i]->status     = checkDevice(i, *deviceInfos[i]);
          deviceInfos[i]->deviceVendor =
-                 getDeviceVendor(syclDevice.get_info<sycl::info::device::vendor>().c_str());
+                 getDeviceVendor(syclDevice.get_info<cl::sycl::info::device::vendor>().c_str());
      }
      return deviceInfos;
  }
@@@ -218,14 -218,13 +219,14 @@@ std::string getDeviceInformationString(
  
      if (!deviceExists)
      {
 -        return gmx::formatString("#%d: %s, status: %s", deviceInfo.id, "N/A",
 -                                 c_deviceStateString[deviceInfo.status]);
 +        return gmx::formatString(
 +                "#%d: %s, status: %s", deviceInfo.id, "N/A", c_deviceStateString[deviceInfo.status]);
      }
      else
      {
          return gmx::formatString(
 -                "#%d: name: %s, vendor: %s, device version: %s, status: %s", deviceInfo.id,
 +                "#%d: name: %s, vendor: %s, device version: %s, status: %s",
 +                deviceInfo.id,
                  deviceInfo.syclDevice.get_info<cl::sycl::info::device::name>().c_str(),
                  deviceInfo.syclDevice.get_info<cl::sycl::info::device::vendor>().c_str(),
                  deviceInfo.syclDevice.get_info<cl::sycl::info::device::version>().c_str(),
index ff34ad967c3f3041387c30a7c917cc6a75e9c70e,19c8762ee76d017e23e826e6ec01bddaea13a0df..d8e34936cfd6620a0ef3dc2f38bec7502ebd7644
@@@ -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.
@@@ -88,13 -88,8 +88,13 @@@ GpuBonded::Impl::Impl(const gmx_ffparam
      // This could be an async transfer (if the source is pinned), so
      // long as it uses the same stream as the kernels and we are happy
      // to consume additional pinned pages.
 -    copyToDeviceBuffer(&d_forceParams_, ffparams.iparams.data(), 0, ffparams.numTypes(),
 -                       deviceStream_, GpuApiCallBehavior::Sync, nullptr);
 +    copyToDeviceBuffer(&d_forceParams_,
 +                       ffparams.iparams.data(),
 +                       0,
 +                       ffparams.numTypes(),
 +                       deviceStream_,
 +                       GpuApiCallBehavior::Sync,
 +                       nullptr);
      vTot_.resize(F_NRE);
      allocateDeviceBuffer(&d_vTot_, F_NRE, deviceContext_);
      clearDeviceBufferAsync(&d_vTot_, 0, F_NRE, deviceStream_);
      kernelLaunchConfig_.gridSize[0]  = (fTypeRangeEnd + c_threadsPerBlock) / c_threadsPerBlock;
      kernelLaunchConfig_.gridSize[1]  = 1;
      kernelLaunchConfig_.gridSize[2]  = 1;
+     kernelLaunchConfig_.sharedMemorySize =
+             SHIFTS * sizeof(float3) + (c_threadsPerBlock / warp_size) * 3 * sizeof(float);
  }
  
  GpuBonded::Impl::~Impl()
@@@ -235,16 -232,11 +237,16 @@@ void GpuBonded::Impl::updateInteraction
          {
              t_ilist& d_iList = d_iLists_[fType];
  
 -            reallocateDeviceBuffer(&d_iList.iatoms, iList.size(), &d_iList.nr, &d_iList.nalloc,
 -                                   deviceContext_);
 +            reallocateDeviceBuffer(
 +                    &d_iList.iatoms, iList.size(), &d_iList.nr, &d_iList.nalloc, deviceContext_);
  
 -            copyToDeviceBuffer(&d_iList.iatoms, iList.iatoms.data(), 0, iList.size(), deviceStream_,
 -                               GpuApiCallBehavior::Async, nullptr);
 +            copyToDeviceBuffer(&d_iList.iatoms,
 +                               iList.iatoms.data(),
 +                               0,
 +                               iList.size(),
 +                               deviceStream_,
 +                               GpuApiCallBehavior::Async,
 +                               nullptr);
          }
          kernelParams_.fTypesOnGpu[fTypesCounter]    = fType;
          kernelParams_.numFTypeIAtoms[fTypesCounter] = iList.size();
index d3f9d4f3ac7360513940331e0377dd6aa3fef702,0253a9b9e6e04fa4f4580a666e8ad29f5a071e0b..42d24e40e1a33b2d67a3fb1783e5eef31108e618
@@@ -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.
@@@ -186,16 -186,13 +186,16 @@@ __device__ void angles_gpu(const in
          float  cos_theta;
          int    t1;
          int    t2;
 -        float  theta = bond_angle_gpu<calcVir>(gm_xq[ai], gm_xq[aj], gm_xq[ak], pbcAiuc, &r_ij,
 -                                              &r_kj, &cos_theta, &t1, &t2);
 +        float  theta = bond_angle_gpu<calcVir>(
 +                gm_xq[ai], gm_xq[aj], gm_xq[ak], pbcAiuc, &r_ij, &r_kj, &cos_theta, &t1, &t2);
  
          float va;
          float dVdt;
          harmonic_gpu(d_forceparams[type].harmonic.krA,
 -                     d_forceparams[type].harmonic.rA * CUDA_DEG2RAD_F, theta, &va, &dVdt);
 +                     d_forceparams[type].harmonic.rA * CUDA_DEG2RAD_F,
 +                     theta,
 +                     &va,
 +                     &dVdt);
  
          if (calcEner)
          {
@@@ -264,8 -261,8 +264,8 @@@ __device__ void urey_bradley_gpu(const 
          float  cos_theta;
          int    t1;
          int    t2;
 -        float  theta = bond_angle_gpu<calcVir>(gm_xq[ai], gm_xq[aj], gm_xq[ak], pbcAiuc, &r_ij,
 -                                              &r_kj, &cos_theta, &t1, &t2);
 +        float  theta = bond_angle_gpu<calcVir>(
 +                gm_xq[ai], gm_xq[aj], gm_xq[ak], pbcAiuc, &r_ij, &r_kj, &cos_theta, &t1, &t2);
  
          float va;
          float dVdt;
@@@ -466,25 -463,21 +466,25 @@@ __device__ void pdihs_gpu(const in
          int    t1;
          int    t2;
          int    t3;
 -        float  phi = dih_angle_gpu<calcVir>(gm_xq[ai], gm_xq[aj], gm_xq[ak], gm_xq[al], pbcAiuc,
 -                                           &r_ij, &r_kj, &r_kl, &m, &n, &t1, &t2, &t3);
 +        float  phi = dih_angle_gpu<calcVir>(
 +                gm_xq[ai], gm_xq[aj], gm_xq[ak], gm_xq[al], pbcAiuc, &r_ij, &r_kj, &r_kl, &m, &n, &t1, &t2, &t3);
  
          float vpd;
          float ddphi;
 -        dopdihs_gpu(d_forceparams[type].pdihs.cpA, d_forceparams[type].pdihs.phiA,
 -                    d_forceparams[type].pdihs.mult, phi, &vpd, &ddphi);
 +        dopdihs_gpu(d_forceparams[type].pdihs.cpA,
 +                    d_forceparams[type].pdihs.phiA,
 +                    d_forceparams[type].pdihs.mult,
 +                    phi,
 +                    &vpd,
 +                    &ddphi);
  
          if (calcEner)
          {
              *vtot_loc += vpd;
          }
  
 -        do_dih_fup_gpu<calcVir>(ai, aj, ak, al, ddphi, r_ij, r_kj, r_kl, m, n, gm_f, sm_fShiftLoc,
 -                                pbcAiuc, gm_xq, t1, t2, t3);
 +        do_dih_fup_gpu<calcVir>(
 +                ai, aj, ak, al, ddphi, r_ij, r_kj, r_kl, m, n, gm_f, sm_fShiftLoc, pbcAiuc, gm_xq, t1, t2, t3);
      }
  }
  
@@@ -517,8 -510,8 +517,8 @@@ __device__ void rbdihs_gpu(const in
          int    t1;
          int    t2;
          int    t3;
 -        float  phi = dih_angle_gpu<calcVir>(gm_xq[ai], gm_xq[aj], gm_xq[ak], gm_xq[al], pbcAiuc,
 -                                           &r_ij, &r_kj, &r_kl, &m, &n, &t1, &t2, &t3);
 +        float  phi = dih_angle_gpu<calcVir>(
 +                gm_xq[ai], gm_xq[aj], gm_xq[ak], gm_xq[al], pbcAiuc, &r_ij, &r_kj, &r_kl, &m, &n, &t1, &t2, &t3);
  
          /* Change to polymer convention */
          if (phi < c0)
  
          ddphi = -ddphi * sin_phi;
  
 -        do_dih_fup_gpu<calcVir>(ai, aj, ak, al, ddphi, r_ij, r_kj, r_kl, m, n, gm_f, sm_fShiftLoc,
 -                                pbcAiuc, gm_xq, t1, t2, t3);
 +        do_dih_fup_gpu<calcVir>(
 +                ai, aj, ak, al, ddphi, r_ij, r_kj, r_kl, m, n, gm_f, sm_fShiftLoc, pbcAiuc, gm_xq, t1, t2, t3);
          if (calcEner)
          {
              *vtot_loc += v;
@@@ -632,8 -625,8 +632,8 @@@ __device__ void idihs_gpu(const in
          int    t1;
          int    t2;
          int    t3;
 -        float  phi = dih_angle_gpu<calcVir>(gm_xq[ai], gm_xq[aj], gm_xq[ak], gm_xq[al], pbcAiuc,
 -                                           &r_ij, &r_kj, &r_kl, &m, &n, &t1, &t2, &t3);
 +        float  phi = dih_angle_gpu<calcVir>(
 +                gm_xq[ai], gm_xq[aj], gm_xq[ak], gm_xq[al], pbcAiuc, &r_ij, &r_kj, &r_kl, &m, &n, &t1, &t2, &t3);
  
          /* phi can jump if phi0 is close to Pi/-Pi, which will cause huge
           * force changes if we just apply a normal harmonic.
  
          float ddphi = -kA * dp;
  
 -        do_dih_fup_gpu<calcVir>(ai, aj, ak, al, -ddphi, r_ij, r_kj, r_kl, m, n, gm_f, sm_fShiftLoc,
 -                                pbcAiuc, gm_xq, t1, t2, t3);
 +        do_dih_fup_gpu<calcVir>(
 +                ai, aj, ak, al, -ddphi, r_ij, r_kj, r_kl, m, n, gm_f, sm_fShiftLoc, pbcAiuc, gm_xq, t1, t2, t3);
  
          if (calcEner)
          {
@@@ -730,11 -723,15 +730,15 @@@ template<bool calcVir, bool calcEner
  __global__ void exec_kernel_gpu(BondedCudaKernelParameters kernelParams)
  {
      assert(blockDim.y == 1 && blockDim.z == 1);
-     const int  tid          = blockIdx.x * blockDim.x + threadIdx.x;
-     float      vtot_loc     = 0;
-     float      vtotVdw_loc  = 0;
-     float      vtotElec_loc = 0;
-     __shared__ float3 sm_fShiftLoc[SHIFTS];
+     const int tid          = blockIdx.x * blockDim.x + threadIdx.x;
+     float     vtot_loc     = 0;
+     float     vtotVdw_loc  = 0;
+     float     vtotElec_loc = 0;
+     extern __shared__ char sm_dynamicShmem[];
+     char*                  sm_nextSlotPtr = sm_dynamicShmem;
+     float3*                sm_fShiftLoc   = (float3*)sm_nextSlotPtr;
+     sm_nextSlotPtr += SHIFTS * sizeof(float3);
  
      if (calcVir)
      {
              switch (fType)
              {
                  case F_BONDS:
 -                    bonds_gpu<calcVir, calcEner>(fTypeTid, &vtot_loc, numBonds, iatoms,
 -                                                 kernelParams.d_forceParams, kernelParams.d_xq,
 -                                                 kernelParams.d_f, sm_fShiftLoc, kernelParams.pbcAiuc);
 +                    bonds_gpu<calcVir, calcEner>(fTypeTid,
 +                                                 &vtot_loc,
 +                                                 numBonds,
 +                                                 iatoms,
 +                                                 kernelParams.d_forceParams,
 +                                                 kernelParams.d_xq,
 +                                                 kernelParams.d_f,
 +                                                 sm_fShiftLoc,
 +                                                 kernelParams.pbcAiuc);
                      break;
                  case F_ANGLES:
 -                    angles_gpu<calcVir, calcEner>(
 -                            fTypeTid, &vtot_loc, numBonds, iatoms, kernelParams.d_forceParams,
 -                            kernelParams.d_xq, kernelParams.d_f, sm_fShiftLoc, kernelParams.pbcAiuc);
 +                    angles_gpu<calcVir, calcEner>(fTypeTid,
 +                                                  &vtot_loc,
 +                                                  numBonds,
 +                                                  iatoms,
 +                                                  kernelParams.d_forceParams,
 +                                                  kernelParams.d_xq,
 +                                                  kernelParams.d_f,
 +                                                  sm_fShiftLoc,
 +                                                  kernelParams.pbcAiuc);
                      break;
                  case F_UREY_BRADLEY:
 -                    urey_bradley_gpu<calcVir, calcEner>(
 -                            fTypeTid, &vtot_loc, numBonds, iatoms, kernelParams.d_forceParams,
 -                            kernelParams.d_xq, kernelParams.d_f, sm_fShiftLoc, kernelParams.pbcAiuc);
 +                    urey_bradley_gpu<calcVir, calcEner>(fTypeTid,
 +                                                        &vtot_loc,
 +                                                        numBonds,
 +                                                        iatoms,
 +                                                        kernelParams.d_forceParams,
 +                                                        kernelParams.d_xq,
 +                                                        kernelParams.d_f,
 +                                                        sm_fShiftLoc,
 +                                                        kernelParams.pbcAiuc);
                      break;
                  case F_PDIHS:
                  case F_PIDIHS:
 -                    pdihs_gpu<calcVir, calcEner>(fTypeTid, &vtot_loc, numBonds, iatoms,
 -                                                 kernelParams.d_forceParams, kernelParams.d_xq,
 -                                                 kernelParams.d_f, sm_fShiftLoc, kernelParams.pbcAiuc);
 +                    pdihs_gpu<calcVir, calcEner>(fTypeTid,
 +                                                 &vtot_loc,
 +                                                 numBonds,
 +                                                 iatoms,
 +                                                 kernelParams.d_forceParams,
 +                                                 kernelParams.d_xq,
 +                                                 kernelParams.d_f,
 +                                                 sm_fShiftLoc,
 +                                                 kernelParams.pbcAiuc);
                      break;
                  case F_RBDIHS:
 -                    rbdihs_gpu<calcVir, calcEner>(
 -                            fTypeTid, &vtot_loc, numBonds, iatoms, kernelParams.d_forceParams,
 -                            kernelParams.d_xq, kernelParams.d_f, sm_fShiftLoc, kernelParams.pbcAiuc);
 +                    rbdihs_gpu<calcVir, calcEner>(fTypeTid,
 +                                                  &vtot_loc,
 +                                                  numBonds,
 +                                                  iatoms,
 +                                                  kernelParams.d_forceParams,
 +                                                  kernelParams.d_xq,
 +                                                  kernelParams.d_f,
 +                                                  sm_fShiftLoc,
 +                                                  kernelParams.pbcAiuc);
                      break;
                  case F_IDIHS:
 -                    idihs_gpu<calcVir, calcEner>(fTypeTid, &vtot_loc, numBonds, iatoms,
 -                                                 kernelParams.d_forceParams, kernelParams.d_xq,
 -                                                 kernelParams.d_f, sm_fShiftLoc, kernelParams.pbcAiuc);
 +                    idihs_gpu<calcVir, calcEner>(fTypeTid,
 +                                                 &vtot_loc,
 +                                                 numBonds,
 +                                                 iatoms,
 +                                                 kernelParams.d_forceParams,
 +                                                 kernelParams.d_xq,
 +                                                 kernelParams.d_f,
 +                                                 sm_fShiftLoc,
 +                                                 kernelParams.pbcAiuc);
                      break;
                  case F_LJ14:
 -                    pairs_gpu<calcVir, calcEner>(
 -                            fTypeTid, numBonds, iatoms, kernelParams.d_forceParams,
 -                            kernelParams.d_xq, kernelParams.d_f, sm_fShiftLoc, kernelParams.pbcAiuc,
 -                            kernelParams.electrostaticsScaleFactor, &vtotVdw_loc, &vtotElec_loc);
 +                    pairs_gpu<calcVir, calcEner>(fTypeTid,
 +                                                 numBonds,
 +                                                 iatoms,
 +                                                 kernelParams.d_forceParams,
 +                                                 kernelParams.d_xq,
 +                                                 kernelParams.d_f,
 +                                                 sm_fShiftLoc,
 +                                                 kernelParams.pbcAiuc,
 +                                                 kernelParams.electrostaticsScaleFactor,
 +                                                 &vtotVdw_loc,
 +                                                 &vtotElec_loc);
                      break;
              }
              break;
      {
          float* vtotVdw  = kernelParams.d_vTot + F_LJ14;
          float* vtotElec = kernelParams.d_vTot + F_COUL14;
-         atomicAdd(kernelParams.d_vTot + fType, vtot_loc);
-         atomicAdd(vtotVdw, vtotVdw_loc);
-         atomicAdd(vtotElec, vtotElec_loc);
+         // Stage atomic accumulation through shared memory:
+         // each warp will accumulate its own partial sum
+         // and then a single thread per warp will accumulate this to the global sum
+         int numWarps = blockDim.x / warpSize;
+         int warpId   = threadIdx.x / warpSize;
+         // Shared memory variables to hold block-local partial sum
+         float* sm_vTot = (float*)sm_nextSlotPtr;
+         sm_nextSlotPtr += numWarps * sizeof(float);
+         float* sm_vTotVdw = (float*)sm_nextSlotPtr;
+         sm_nextSlotPtr += numWarps * sizeof(float);
+         float* sm_vTotElec = (float*)sm_nextSlotPtr;
+         if (threadIdx.x % warpSize == 0)
+         {
+             // One thread per warp initializes to zero
+             sm_vTot[warpId]     = 0.;
+             sm_vTotVdw[warpId]  = 0.;
+             sm_vTotElec[warpId] = 0.;
+         }
+         __syncwarp(); // All threads in warp must wait for initialization
+         // Perform warp-local accumulation in shared memory
+         atomicAdd(sm_vTot + warpId, vtot_loc);
+         atomicAdd(sm_vTotVdw + warpId, vtotVdw_loc);
+         atomicAdd(sm_vTotElec + warpId, vtotElec_loc);
+         __syncwarp(); // Ensure all threads in warp have completed
+         if (threadIdx.x % warpSize == 0)
+         { // One thread per warp accumulates partial sum into global sum
+             atomicAdd(kernelParams.d_vTot + fType, sm_vTot[warpId]);
+             atomicAdd(vtotVdw, sm_vTotVdw[warpId]);
+             atomicAdd(vtotElec, sm_vTotElec[warpId]);
+         }
      }
      /* Accumulate shift vectors from shared memory to global memory on the first SHIFTS threads of the block. */
      if (calcVir)
@@@ -891,12 -878,8 +928,12 @@@ void GpuBonded::Impl::launchKernel(
  
      const auto kernelArgs = prepareGpuKernelArguments(kernelPtr, kernelLaunchConfig_, &kernelParams_);
  
 -    launchGpuKernel(kernelPtr, kernelLaunchConfig_, deviceStream_, nullptr,
 -                    "exec_kernel_gpu<calcVir, calcEner>", kernelArgs);
 +    launchGpuKernel(kernelPtr,
 +                    kernelLaunchConfig_,
 +                    deviceStream_,
 +                    nullptr,
 +                    "exec_kernel_gpu<calcVir, calcEner>",
 +                    kernelArgs);
  
      wallcycle_sub_stop(wcycle_, ewcsLAUNCH_GPU_BONDED);
      wallcycle_stop(wcycle_, ewcLAUNCH_GPU);
index 5c27087d4c2d4e5e1e54137c95a1916e4300e5a0,ee298f5a519c066cec1ff02ded467c2c550c5f3c..a7280159ff90d8d1498defb2986b3a329f3e18ea
@@@ -2,7 -2,7 +2,7 @@@
   * This file is part of the GROMACS molecular simulation package.
   *
   * Copyright (c) 2014,2015,2016,2017,2018 by the GROMACS development team.
-  * Copyright (c) 2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -458,36 -458,14 +458,36 @@@ real calc_one_bond(in
                 nice to account to its own subtimer, but first
                 wallcycle needs to be extended to support calling from
                 multiple threads. */
 -            v = cmap_dihs(nbn, iatoms.data() + nb0, iparams.data(), &idef.cmap_grid, x, f, fshift,
 -                          pbc, lambda[efptFTYPE], &(dvdl[efptFTYPE]), md, fcd, global_atom_index);
 +            v = cmap_dihs(nbn,
 +                          iatoms.data() + nb0,
 +                          iparams.data(),
 +                          &idef.cmap_grid,
 +                          x,
 +                          f,
 +                          fshift,
 +                          pbc,
 +                          lambda[efptFTYPE],
 +                          &(dvdl[efptFTYPE]),
 +                          md,
 +                          fcd,
 +                          global_atom_index);
          }
          else
          {
 -            v = calculateSimpleBond(ftype, nbn, iatoms.data() + nb0, iparams.data(), x, f, fshift,
 -                                    pbc, lambda[efptFTYPE], &(dvdl[efptFTYPE]), md, fcd,
 -                                    global_atom_index, flavor);
 +            v = calculateSimpleBond(ftype,
 +                                    nbn,
 +                                    iatoms.data() + nb0,
 +                                    iparams.data(),
 +                                    x,
 +                                    f,
 +                                    fshift,
 +                                    pbc,
 +                                    lambda[efptFTYPE],
 +                                    &(dvdl[efptFTYPE]),
 +                                    md,
 +                                    fcd,
 +                                    global_atom_index,
 +                                    flavor);
          }
      }
      else
          /* TODO The execution time for pairs might be nice to account
             to its own subtimer, but first wallcycle needs to be
             extended to support calling from multiple threads. */
 -        do_pairs(ftype, nbn, iatoms.data() + nb0, iparams.data(), x, f, fshift, pbc, lambda, dvdl,
 -                 md, fr, havePerturbedInteractions, stepWork, grpp, global_atom_index);
 +        do_pairs(ftype,
 +                 nbn,
 +                 iatoms.data() + nb0,
 +                 iparams.data(),
 +                 x,
 +                 f,
 +                 fshift,
 +                 pbc,
 +                 lambda,
 +                 dvdl,
 +                 md,
 +                 fr,
 +                 havePerturbedInteractions,
 +                 stepWork,
 +                 grpp,
 +                 global_atom_index);
      }
  
      if (thread == 0)
@@@ -581,25 -545,9 +581,25 @@@ static void calcBondedForces(const Inte
                  if (!ilist.empty() && ftype_is_bonded_potential(ftype))
                  {
                      ArrayRef<const int> iatoms = gmx::makeConstArrayRef(ilist.iatoms);
 -                    v = calc_one_bond(thread, ftype, idef, iatoms, idef.numNonperturbedInteractions[ftype],
 -                                      bt->workDivision, x, ft, fshift, fr, pbc_null, grpp, nrnb,
 -                                      lambda, dvdlt, md, fcd, stepWork, global_atom_index);
 +                    v                          = calc_one_bond(thread,
 +                                      ftype,
 +                                      idef,
 +                                      iatoms,
 +                                      idef.numNonperturbedInteractions[ftype],
 +                                      bt->workDivision,
 +                                      x,
 +                                      ft,
 +                                      fshift,
 +                                      fr,
 +                                      pbc_null,
 +                                      grpp,
 +                                      nrnb,
 +                                      lambda,
 +                                      dvdlt,
 +                                      md,
 +                                      fcd,
 +                                      stepWork,
 +                                      global_atom_index);
                      epot[ftype] += v;
                  }
              }
@@@ -653,20 -601,9 +653,20 @@@ void calc_listed(struct gmx_wallcycle
          /* The dummy array is to have a place to store the dhdl at other values
             of lambda, which will be thrown away in the end */
          real dvdl[efptNR] = { 0 };
 -        calcBondedForces(idef, bt, x, fr, fr->bMolPBC ? pbc : nullptr,
 -                         as_rvec_array(forceWithShiftForces.shiftForces().data()), enerd, nrnb,
 -                         lambda, dvdl, md, fcd, stepWork, global_atom_index);
 +        calcBondedForces(idef,
 +                         bt,
 +                         x,
 +                         fr,
 +                         fr->bMolPBC ? pbc : nullptr,
 +                         as_rvec_array(forceWithShiftForces.shiftForces().data()),
 +                         enerd,
 +                         nrnb,
 +                         lambda,
 +                         dvdl,
 +                         md,
 +                         fcd,
 +                         stepWork,
 +                         global_atom_index);
          wallcycle_sub_stop(wcycle, ewcsLISTED);
  
          wallcycle_sub_start(wcycle, ewcsLISTED_BUF_OPS);
@@@ -724,8 -661,7 +724,8 @@@ void calc_listed_lambda(const Interacti
  
      /* We already have the forces, so we use temp buffers here */
      std::fill(forceBufferLambda.begin(), forceBufferLambda.end(), 0.0_real);
 -    std::fill(shiftForceBufferLambda.begin(), shiftForceBufferLambda.end(),
 +    std::fill(shiftForceBufferLambda.begin(),
 +              shiftForceBufferLambda.end(),
                gmx::RVec{ 0.0_real, 0.0_real, 0.0_real });
      rvec4* f      = reinterpret_cast<rvec4*>(forceBufferLambda.data());
      rvec*  fshift = as_rvec_array(shiftForceBufferLambda.data());
  
                  gmx::StepWorkload tempFlags;
                  tempFlags.computeEnergy = true;
 -                real v = calc_one_bond(0, ftype, idef, iatomsPerturbed, iatomsPerturbed.ssize(),
 -                                       workDivision, x, f, fshift, fr, pbc_null, grpp, nrnb, lambda,
 -                                       dvdl.data(), md, fcd, tempFlags, global_atom_index);
 +                real v                  = calc_one_bond(0,
 +                                       ftype,
 +                                       idef,
 +                                       iatomsPerturbed,
 +                                       iatomsPerturbed.ssize(),
 +                                       workDivision,
 +                                       x,
 +                                       f,
 +                                       fshift,
 +                                       fr,
 +                                       pbc_null,
 +                                       grpp,
 +                                       nrnb,
 +                                       lambda,
 +                                       dvdl.data(),
 +                                       md,
 +                                       fcd,
 +                                       tempFlags,
 +                                       global_atom_index);
                  epot[ftype] += v;
              }
          }
@@@ -804,8 -724,11 +804,11 @@@ void ListedForces::calculate(struct gmx
      // Todo: replace all rvec use here with ArrayRefWithPadding
      const rvec* x = as_rvec_array(coordinates.paddedArrayRef().data());
  
+     const bool calculateRestInteractions =
+             interactionSelection_.test(static_cast<int>(ListedForces::InteractionGroup::Rest));
      t_pbc pbc_full; /* Full PBC is needed for position restraints */
-     if (haveRestraints(*fcdata))
+     if (calculateRestInteractions && haveRestraints(*fcdata))
      {
          if (!idef.il[F_POSRES].empty() || !idef.il[F_FBPOSRES].empty())
          {
          if (fcdata->orires->nr > 0)
          {
              GMX_ASSERT(!xWholeMolecules.empty(), "Need whole molecules for orienation restraints");
 -            enerd->term[F_ORIRESDEV] = calc_orires_dev(
 -                    ms, idef.il[F_ORIRES].size(), idef.il[F_ORIRES].iatoms.data(), idef.iparams.data(),
 -                    md, xWholeMolecules, x, fr->bMolPBC ? pbc : nullptr, fcdata->orires, hist);
 +            enerd->term[F_ORIRESDEV] = calc_orires_dev(ms,
 +                                                       idef.il[F_ORIRES].size(),
 +                                                       idef.il[F_ORIRES].iatoms.data(),
 +                                                       idef.iparams.data(),
 +                                                       md,
 +                                                       xWholeMolecules,
 +                                                       x,
 +                                                       fr->bMolPBC ? pbc : nullptr,
 +                                                       fcdata->orires,
 +                                                       hist);
          }
          if (fcdata->disres->nres > 0)
          {
 -            calc_disres_R_6(cr, ms, idef.il[F_DISRES].size(), idef.il[F_DISRES].iatoms.data(), x,
 -                            fr->bMolPBC ? pbc : nullptr, fcdata->disres, hist);
 +            calc_disres_R_6(cr,
 +                            ms,
 +                            idef.il[F_DISRES].size(),
 +                            idef.il[F_DISRES].iatoms.data(),
 +                            x,
 +                            fr->bMolPBC ? pbc : nullptr,
 +                            fcdata->disres,
 +                            hist);
          }
  
          wallcycle_sub_stop(wcycle, ewcsRESTRAINTS);
      }
  
 -    calc_listed(wcycle, idef, threading_.get(), x, forceOutputs, fr, pbc, enerd, nrnb, lambda, md,
 -                fcdata, global_atom_index, stepWork);
 +    calc_listed(wcycle, idef, threading_.get(), x, forceOutputs, fr, pbc, enerd, nrnb, lambda, md, fcdata, global_atom_index, stepWork);
  
      /* Check if we have to determine energy differences
       * at foreign lambda's.
                  {
                      lam_i[j] = (i == 0 ? lambda[j] : fepvals->all_lambda[j][i - 1]);
                  }
 -                calc_listed_lambda(idef, threading_.get(), x, fr, pbc, forceBufferLambda_,
 -                                   shiftForceBufferLambda_, &(enerd->foreign_grpp), enerd->foreign_term,
 -                                   dvdl, nrnb, lam_i, md, fcdata, global_atom_index);
 +                calc_listed_lambda(idef,
 +                                   threading_.get(),
 +                                   x,
 +                                   fr,
 +                                   pbc,
 +                                   forceBufferLambda_,
 +                                   shiftForceBufferLambda_,
 +                                   &(enerd->foreign_grpp),
 +                                   enerd->foreign_term,
 +                                   dvdl,
 +                                   nrnb,
 +                                   lam_i,
 +                                   md,
 +                                   fcdata,
 +                                   global_atom_index);
                  sum_epot(enerd->foreign_grpp, enerd->foreign_term);
                  const double dvdlSum = std::accumulate(std::begin(dvdl), std::end(dvdl), 0.);
                  std::fill(std::begin(dvdl), std::end(dvdl), 0.0);
index 5304ac5fe29f416e8d5f77006944a7de2dfcfedc,2d14fd6fc6280adfc5646ddfab8c1e1bf6d13955..2ed5853fc50ff5a8c5c82bf023ce187eadb9db6c
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -104,9 -104,8 +104,9 @@@ DensitySimilarityInnerProduct::DensityS
      const auto numVoxels = gradient_.asConstView().mapping().required_span_size();
      /* the gradient for the inner product measure of fit is constant and does not
       * depend on the compared density, so it is pre-computed here */
-     std::transform(begin(referenceDensity_), end(referenceDensity), begin(gradient_), [numVoxels](float x) {
 -    std::transform(begin(referenceDensity_), end(referenceDensity_), begin(gradient_),
 -                   [numVoxels](float x) { return x / numVoxels; });
++    std::transform(begin(referenceDensity_), end(referenceDensity_), begin(gradient_), [numVoxels](float x) {
 +        return x / numVoxels;
 +    });
  }
  
  real DensitySimilarityInnerProduct::similarity(density comparedDensity)
@@@ -195,12 -194,8 +195,12 @@@ real DensitySimilarityRelativeEntropy::
      {
          GMX_THROW(RangeError("Reference density and compared density need to have same extents."));
      }
 -    return std::inner_product(begin(referenceDensity_), end(referenceDensity_),
 -                              begin(comparedDensity), 0., std::plus<>(), relativeEntropyAtVoxel);
 +    return std::inner_product(begin(referenceDensity_),
 +                              end(referenceDensity_),
 +                              begin(comparedDensity),
 +                              0.,
 +                              std::plus<>(),
 +                              relativeEntropyAtVoxel);
  }
  
  DensitySimilarityMeasure::density DensitySimilarityRelativeEntropy::gradient(density comparedDensity)
      {
          GMX_THROW(RangeError("Reference density and compared density need to have same extents."));
      }
 -    std::transform(begin(referenceDensity_), end(referenceDensity_), begin(comparedDensity),
 -                   begin(gradient_), relativeEntropyGradientAtVoxel);
 +    std::transform(begin(referenceDensity_),
 +                   end(referenceDensity_),
 +                   begin(comparedDensity),
 +                   begin(gradient_),
 +                   relativeEntropyGradientAtVoxel);
      return gradient_.asConstView();
  }
  
@@@ -235,7 -227,7 +235,7 @@@ struct CrossCorrelationEvaluationHelper
      real referenceSquaredSum = 0;
      //! The sum of the squared compared density voxel values
      real comparisonSquaredSum = 0;
-     //! The covariance of the refernce and the compared density
+     //! The covariance of the reference and the compared density
      real covariance = 0;
  };
  
@@@ -275,7 -267,7 +275,7 @@@ CrossCorrelationEvaluationHelperValues 
  class CrossCorrelationGradientAtVoxel
  {
  public:
-     //! Set up the gradident calculation with pre-computed values
+     //! Set up the gradient calculation with pre-computed values
      CrossCorrelationGradientAtVoxel(const CrossCorrelationEvaluationHelperValues& preComputed) :
          prefactor_(evaluatePrefactor(preComputed.comparisonSquaredSum, preComputed.referenceSquaredSum)),
          comparisonPrefactor_(preComputed.covariance / preComputed.comparisonSquaredSum),
@@@ -352,7 -344,7 +352,7 @@@ real DensitySimilarityCrossCorrelation:
  
      // To avoid numerical instability due to large squared density value sums
      // division is re-written to avoid multiplying two large numbers
-     // as product of two seperate divisions of smaller numbers
+     // as product of two separate divisions of smaller numbers
      const real covarianceSqrt = sqrt(fabs(helperValues.covariance));
      const int  sign           = helperValues.covariance > 0 ? 1 : -1;
      return sign * (covarianceSqrt / sqrt(helperValues.referenceSquaredSum))
@@@ -369,11 -361,8 +369,11 @@@ DensitySimilarityMeasure::density Densi
      CrossCorrelationEvaluationHelperValues helperValues =
              evaluateHelperValues(referenceDensity_, comparedDensity);
  
 -    std::transform(begin(referenceDensity_), end(referenceDensity_), begin(comparedDensity),
 -                   begin(gradient_), CrossCorrelationGradientAtVoxel(helperValues));
 +    std::transform(begin(referenceDensity_),
 +                   end(referenceDensity_),
 +                   begin(comparedDensity),
 +                   begin(gradient_),
 +                   CrossCorrelationGradientAtVoxel(helperValues));
  
      return gradient_.asConstView();
  }
index 3f5e7b86c6dfd70f10e88d8270a5c4d05b184f3e,504ff512ed643c4bddced38abe476fe4c547d329..29af5cb4798acdeef71de80ef690539c5eacc8e5
  
  static const double sy_const_1[] = { 1. };
  static const double sy_const_3[] = { 0.828981543588751, -0.657963087177502, 0.828981543588751 };
 -static const double sy_const_5[] = { 0.2967324292201065, 0.2967324292201065, -0.186929716880426,
 -                                     0.2967324292201065, 0.2967324292201065 };
 +static const double sy_const_5[] = { 0.2967324292201065,
 +                                     0.2967324292201065,
 +                                     -0.186929716880426,
 +                                     0.2967324292201065,
 +                                     0.2967324292201065 };
  
  static const double* sy_const[] = { nullptr, sy_const_1, nullptr, sy_const_3, nullptr, sy_const_5 };
  
@@@ -141,12 -138,8 +141,12 @@@ void update_tcouple(int64_t           s
                  berendsen_tcoupl(inputrec, ekind, dttc, state->therm_integral);
                  break;
              case etcNOSEHOOVER:
 -                nosehoover_tcoupl(&(inputrec->opts), ekind, dttc, state->nosehoover_xi.data(),
 -                                  state->nosehoover_vxi.data(), MassQ);
 +                nosehoover_tcoupl(&(inputrec->opts),
 +                                  ekind,
 +                                  dttc,
 +                                  state->nosehoover_xi.data(),
 +                                  state->nosehoover_vxi.data(),
 +                                  MassQ);
                  break;
              case etcVRESCALE:
                  vrescale_tcoupl(inputrec, step, ekind, dttc, state->therm_integral.data());
@@@ -185,8 -178,8 +185,8 @@@ void update_pcouple_before_coordinates(
      {
          real dtpc = inputrec->nstpcouple * inputrec->delta_t;
  
 -        parrinellorahman_pcoupl(fplog, step, inputrec, dtpc, state->pres_prev, state->box,
 -                                state->box_rel, state->boxv, M, parrinellorahmanMu, bInitStep);
 +        parrinellorahman_pcoupl(
 +                fplog, step, inputrec, dtpc, state->pres_prev, state->box, state->box_rel, state->boxv, M, parrinellorahmanMu, bInitStep);
      }
  }
  
@@@ -218,54 -211,21 +218,54 @@@ void update_pcouple_after_coordinates(F
              if (do_per_step(step, inputrec->nstpcouple))
              {
                  real dtpc = inputrec->nstpcouple * dt;
 -                berendsen_pcoupl(fplog, step, inputrec, dtpc, pressure, state->box, forceVirial,
 -                                 constraintVirial, pressureCouplingMu, &state->baros_integral);
 -                berendsen_pscale(inputrec, pressureCouplingMu, state->box, state->box_rel, start,
 -                                 homenr, state->x.rvec_array(), md->cFREEZE, nrnb, scaleCoordinates);
 +                pressureCouplingCalculateScalingMatrix<epcBERENDSEN>(fplog,
 +                                                                     step,
 +                                                                     inputrec,
 +                                                                     dtpc,
 +                                                                     pressure,
 +                                                                     state->box,
 +                                                                     forceVirial,
 +                                                                     constraintVirial,
 +                                                                     pressureCouplingMu,
 +                                                                     &state->baros_integral);
 +                pressureCouplingScaleBoxAndCoordinates<epcBERENDSEN>(inputrec,
 +                                                                     pressureCouplingMu,
 +                                                                     state->box,
 +                                                                     state->box_rel,
 +                                                                     start,
 +                                                                     homenr,
 +                                                                     state->x.rvec_array(),
 +                                                                     nullptr,
 +                                                                     md->cFREEZE,
 +                                                                     nrnb,
 +                                                                     scaleCoordinates);
              }
              break;
          case (epcCRESCALE):
              if (do_per_step(step, inputrec->nstpcouple))
              {
                  real dtpc = inputrec->nstpcouple * dt;
 -                crescale_pcoupl(fplog, step, inputrec, dtpc, pressure, state->box, forceVirial,
 -                                constraintVirial, pressureCouplingMu, &state->baros_integral);
 -                crescale_pscale(inputrec, pressureCouplingMu, state->box, state->box_rel, start,
 -                                homenr, state->x.rvec_array(), state->v.rvec_array(), md->cFREEZE,
 -                                nrnb, scaleCoordinates);
 +                pressureCouplingCalculateScalingMatrix<epcCRESCALE>(fplog,
 +                                                                    step,
 +                                                                    inputrec,
 +                                                                    dtpc,
 +                                                                    pressure,
 +                                                                    state->box,
 +                                                                    forceVirial,
 +                                                                    constraintVirial,
 +                                                                    pressureCouplingMu,
 +                                                                    &state->baros_integral);
 +                pressureCouplingScaleBoxAndCoordinates<epcCRESCALE>(inputrec,
 +                                                                    pressureCouplingMu,
 +                                                                    state->box,
 +                                                                    state->box_rel,
 +                                                                    start,
 +                                                                    homenr,
 +                                                                    state->x.rvec_array(),
 +                                                                    state->v.rvec_array(),
 +                                                                    md->cFREEZE,
 +                                                                    nrnb,
 +                                                                    scaleCoordinates);
              }
              break;
          case (epcPARRINELLORAHMAN):
@@@ -358,8 -318,8 +358,8 @@@ extern gmx_bool update_randomize_veloci
         particle andersen or 2) it's massive andersen and it's tau_t/dt */
      if ((ir->etc == etcANDERSEN) || do_per_step(step, gmx::roundToInt(1.0 / rate)))
      {
 -        andersen_tcoupl(ir, step, cr, md, v, rate, upd->getAndersenRandomizeGroup(),
 -                        upd->getBoltzmanFactor());
 +        andersen_tcoupl(
 +                ir, step, cr, md, v, rate, upd->getAndersenRandomizeGroup(), upd->getBoltzmanFactor());
          return TRUE;
      }
      return FALSE;
@@@ -840,34 -800,25 +840,34 @@@ void parrinellorahman_pcoupl(FILE
      mmul_ur0(invbox, t1, mu);
  }
  
 -void berendsen_pcoupl(FILE*             fplog,
 -                      int64_t           step,
 -                      const t_inputrec* ir,
 -                      real              dt,
 -                      const tensor      pres,
 -                      const matrix      box,
 -                      const matrix      force_vir,
 -                      const matrix      constraint_vir,
 -                      matrix            mu,
 -                      double*           baros_integral)
 +//! Return compressibility factor for entry (i,j) of Berendsen / C-rescale scaling matrix
 +static inline real compressibilityFactor(int i, int j, const t_inputrec* ir, real dt)
  {
 -    real scalar_pressure, xy_pressure, p_corr_z;
 -    char buf[STRLEN];
 +    return ir->compress[i][j] * dt / ir->tau_p;
 +}
  
 -    /*
 -     *  Calculate the scaling matrix mu
 -     */
 -    scalar_pressure = 0;
 -    xy_pressure     = 0;
 +//! Details of Berendsen / C-rescale scaling matrix calculation
 +template<int pressureCouplingType>
 +static void calculateScalingMatrixImplDetail(const t_inputrec* ir,
 +                                             matrix            mu,
 +                                             real              dt,
 +                                             const matrix      pres,
 +                                             const matrix      box,
 +                                             real              scalar_pressure,
 +                                             real              xy_pressure,
 +                                             int64_t           step);
 +
 +//! Calculate Berendsen / C-rescale scaling matrix
 +template<int pressureCouplingType>
 +static void calculateScalingMatrixImpl(const t_inputrec* ir,
 +                                       matrix            mu,
 +                                       real              dt,
 +                                       const matrix      pres,
 +                                       const matrix      box,
 +                                       int64_t           step)
 +{
 +    real scalar_pressure = 0;
 +    real xy_pressure     = 0;
      for (int d = 0; d < DIM; d++)
      {
          scalar_pressure += pres[d][d] / DIM;
              xy_pressure += pres[d][d] / (DIM - 1);
          }
      }
 -    /* Pressure is now in bar, everywhere. */
 -#define factor(d, m) (ir->compress[d][m] * dt / ir->tau_p)
 -
 -    /* mu has been changed from pow(1+...,1/3) to 1+.../3, since this is
 -     * necessary for triclinic scaling
 -     */
      clear_mat(mu);
 +    calculateScalingMatrixImplDetail<pressureCouplingType>(
 +            ir, mu, dt, pres, box, scalar_pressure, xy_pressure, step);
 +}
 +
 +template<>
 +void calculateScalingMatrixImplDetail<epcBERENDSEN>(const t_inputrec* ir,
 +                                                    matrix            mu,
 +                                                    real              dt,
 +                                                    const matrix      pres,
 +                                                    const matrix      box,
 +                                                    real              scalar_pressure,
 +                                                    real              xy_pressure,
 +                                                    int64_t gmx_unused step)
 +{
 +    real p_corr_z = 0;
      switch (ir->epct)
      {
          case epctISOTROPIC:
              for (int d = 0; d < DIM; d++)
              {
 -                mu[d][d] = 1.0 - factor(d, d) * (ir->ref_p[d][d] - scalar_pressure) / DIM;
 +                mu[d][d] = 1.0 - compressibilityFactor(d, d, ir, dt) * (ir->ref_p[d][d] - scalar_pressure) / DIM;
              }
              break;
          case epctSEMIISOTROPIC:
              for (int d = 0; d < ZZ; d++)
              {
 -                mu[d][d] = 1.0 - factor(d, d) * (ir->ref_p[d][d] - xy_pressure) / DIM;
 +                mu[d][d] = 1.0 - compressibilityFactor(d, d, ir, dt) * (ir->ref_p[d][d] - xy_pressure) / DIM;
              }
 -            mu[ZZ][ZZ] = 1.0 - factor(ZZ, ZZ) * (ir->ref_p[ZZ][ZZ] - pres[ZZ][ZZ]) / DIM;
 +            mu[ZZ][ZZ] =
 +                    1.0 - compressibilityFactor(ZZ, ZZ, ir, dt) * (ir->ref_p[ZZ][ZZ] - pres[ZZ][ZZ]) / DIM;
              break;
          case epctANISOTROPIC:
              for (int d = 0; d < DIM; d++)
              {
                  for (int n = 0; n < DIM; n++)
                  {
 -                    mu[d][n] = (d == n ? 1.0 : 0.0) - factor(d, n) * (ir->ref_p[d][n] - pres[d][n]) / DIM;
 +                    mu[d][n] = (d == n ? 1.0 : 0.0)
 +                               - compressibilityFactor(d, n, ir, dt) * (ir->ref_p[d][n] - pres[d][n]) / DIM;
                  }
              }
              break;
              for (int d = 0; d < DIM - 1; d++)
              {
                  mu[d][d] = 1.0
 -                           + factor(d, d)
 +                           + compressibilityFactor(d, d, ir, dt)
                                       * (ir->ref_p[d][d] / (mu[ZZ][ZZ] * box[ZZ][ZZ])
                                          - (pres[ZZ][ZZ] + p_corr_z - xy_pressure))
                                       / (DIM - 1);
              }
              break;
          default:
 -            gmx_fatal(FARGS, "Berendsen pressure coupling type %s not supported yet\n",
 +            gmx_fatal(FARGS,
 +                      "Berendsen pressure coupling type %s not supported yet\n",
                        EPCOUPLTYPETYPE(ir->epct));
      }
 -    /* To fullfill the orientation restrictions on triclinic boxes
 -     * we will set mu_yx, mu_zx and mu_zy to 0 and correct
 -     * the other elements of mu to first order.
 -     */
 -    mu[YY][XX] += mu[XX][YY];
 -    mu[ZZ][XX] += mu[XX][ZZ];
 -    mu[ZZ][YY] += mu[YY][ZZ];
 -    mu[XX][YY] = 0;
 -    mu[XX][ZZ] = 0;
 -    mu[YY][ZZ] = 0;
 -
 -    /* Keep track of the work the barostat applies on the system.
 -     * Without constraints force_vir tells us how Epot changes when scaling.
 -     * With constraints constraint_vir gives us the constraint contribution
 -     * to both Epot and Ekin. Although we are not scaling velocities, scaling
 -     * the coordinates leads to scaling of distances involved in constraints.
 -     * This in turn changes the angular momentum (even if the constrained
 -     * distances are corrected at the next step). The kinetic component
 -     * of the constraint virial captures the angular momentum change.
 -     */
 -    for (int d = 0; d < DIM; d++)
 -    {
 -        for (int n = 0; n <= d; n++)
 -        {
 -            *baros_integral -=
 -                    2 * (mu[d][n] - (n == d ? 1 : 0)) * (force_vir[d][n] + constraint_vir[d][n]);
 -        }
 -    }
 -
 -    if (debug)
 -    {
 -        pr_rvecs(debug, 0, "PC: pres ", pres, 3);
 -        pr_rvecs(debug, 0, "PC: mu   ", mu, 3);
 -    }
 -
 -    if (mu[XX][XX] < 0.99 || mu[XX][XX] > 1.01 || mu[YY][YY] < 0.99 || mu[YY][YY] > 1.01
 -        || mu[ZZ][ZZ] < 0.99 || mu[ZZ][ZZ] > 1.01)
 -    {
 -        char buf2[22];
 -        sprintf(buf,
 -                "\nStep %s  Warning: pressure scaling more than 1%%, "
 -                "mu: %g %g %g\n",
 -                gmx_step_str(step, buf2), mu[XX][XX], mu[YY][YY], mu[ZZ][ZZ]);
 -        if (fplog)
 -        {
 -            fprintf(fplog, "%s", buf);
 -        }
 -        fprintf(stderr, "%s", buf);
 -    }
  }
  
 -void crescale_pcoupl(FILE*             fplog,
 -                     int64_t           step,
 -                     const t_inputrec* ir,
 -                     real              dt,
 -                     const tensor      pres,
 -                     const matrix      box,
 -                     const matrix      force_vir,
 -                     const matrix      constraint_vir,
 -                     matrix            mu,
 -                     double*           baros_integral)
 +template<>
 +void calculateScalingMatrixImplDetail<epcCRESCALE>(const t_inputrec* ir,
 +                                                   matrix            mu,
 +                                                   real              dt,
 +                                                   const matrix      pres,
 +                                                   const matrix      box,
 +                                                   real              scalar_pressure,
 +                                                   real              xy_pressure,
 +                                                   int64_t           step)
  {
 -    /*
 -     *  Calculate the scaling matrix mu
 -     */
 -    real scalar_pressure = 0;
 -    real xy_pressure     = 0;
 -    for (int d = 0; d < DIM; d++)
 -    {
 -        scalar_pressure += pres[d][d] / DIM;
 -        if (d != ZZ)
 -        {
 -            xy_pressure += pres[d][d] / (DIM - 1);
 -        }
 -    }
 -    clear_mat(mu);
 -
      gmx::ThreeFry2x64<64>         rng(ir->ld_seed, gmx::RandomDomain::Barostat);
      gmx::NormalDistribution<real> normalDist;
      rng.restart(step, 0);
      {
          vol *= box[d][d];
      }
 -    real gauss;
 -    real gauss2;
 -    real kt = ir->opts.ref_t[0] * BOLTZ;
 +    real gauss  = 0;
 +    real gauss2 = 0;
 +    real kt     = ir->opts.ref_t[0] * BOLTZ;
      if (kt < 0.0)
      {
          kt = 0.0;
              gauss = normalDist(rng);
              for (int d = 0; d < DIM; d++)
              {
 -                const real compressibilityFactor = ir->compress[d][d] * dt / ir->tau_p;
 -                mu[d][d] = std::exp(-compressibilityFactor * (ir->ref_p[d][d] - scalar_pressure) / DIM
 -                                    + std::sqrt(2.0 * kt * compressibilityFactor * PRESFAC / vol)
 -                                              * gauss / DIM);
 +                const real factor = compressibilityFactor(d, d, ir, dt);
 +                mu[d][d]          = std::exp(-factor * (ir->ref_p[d][d] - scalar_pressure) / DIM
 +                                    + std::sqrt(2.0 * kt * factor * PRESFAC / vol) * gauss / DIM);
              }
              break;
          case epctSEMIISOTROPIC:
              gauss2 = normalDist(rng);
              for (int d = 0; d < ZZ; d++)
              {
 -                const real compressibilityFactor = ir->compress[d][d] * dt / ir->tau_p;
 -                mu[d][d]                         = std::exp(
 -                        -compressibilityFactor * (ir->ref_p[d][d] - xy_pressure) / DIM
 -                        + std::sqrt((DIM - 1) * 2.0 * kt * compressibilityFactor * PRESFAC / vol / DIM)
 -                                  / (DIM - 1) * gauss);
 +                const real factor = compressibilityFactor(d, d, ir, dt);
 +                mu[d][d]          = std::exp(-factor * (ir->ref_p[d][d] - xy_pressure) / DIM
 +                                    + std::sqrt((DIM - 1) * 2.0 * kt * factor * PRESFAC / vol / DIM)
 +                                              / (DIM - 1) * gauss);
              }
              {
 -                const real compressibilityFactor = ir->compress[ZZ][ZZ] * dt / ir->tau_p;
 -                mu[ZZ][ZZ]                       = std::exp(
 -                        -compressibilityFactor * (ir->ref_p[ZZ][ZZ] - pres[ZZ][ZZ]) / DIM
 -                        + std::sqrt(2.0 * kt * compressibilityFactor * PRESFAC / vol / DIM) * gauss2);
 +                const real factor = compressibilityFactor(ZZ, ZZ, ir, dt);
 +                mu[ZZ][ZZ]        = std::exp(-factor * (ir->ref_p[ZZ][ZZ] - pres[ZZ][ZZ]) / DIM
 +                                      + std::sqrt(2.0 * kt * factor * PRESFAC / vol / DIM) * gauss2);
              }
              break;
          case epctSURFACETENSION:
              gauss2 = normalDist(rng);
              for (int d = 0; d < ZZ; d++)
              {
 -                const real compressibilityFactor = ir->compress[d][d] * dt / ir->tau_p;
 +                const real factor = compressibilityFactor(d, d, ir, dt);
                  /* Notice: we here use ref_p[ZZ][ZZ] as isotropic pressure and ir->ref_p[d][d] as surface tension */
                  mu[d][d] = std::exp(
 -                        -compressibilityFactor
 -                                * (ir->ref_p[ZZ][ZZ] - ir->ref_p[d][d] / box[ZZ][ZZ] - xy_pressure) / DIM
 -                        + std::sqrt(4.0 / 3.0 * kt * compressibilityFactor * PRESFAC / vol)
 -                                  / (DIM - 1) * gauss);
 +                        -factor * (ir->ref_p[ZZ][ZZ] - ir->ref_p[d][d] / box[ZZ][ZZ] - xy_pressure) / DIM
 +                        + std::sqrt(4.0 / 3.0 * kt * factor * PRESFAC / vol) / (DIM - 1) * gauss);
              }
              {
 -                const real compressibilityFactor = ir->compress[ZZ][ZZ] * dt / ir->tau_p;
 -                mu[ZZ][ZZ]                       = std::exp(
 -                        -compressibilityFactor * (ir->ref_p[ZZ][ZZ] - pres[ZZ][ZZ]) / DIM
 -                        + std::sqrt(2.0 / 3.0 * kt * compressibilityFactor * PRESFAC / vol) * gauss2);
 +                const real factor = compressibilityFactor(ZZ, ZZ, ir, dt);
 +                mu[ZZ][ZZ]        = std::exp(-factor * (ir->ref_p[ZZ][ZZ] - pres[ZZ][ZZ]) / DIM
 +                                      + std::sqrt(2.0 / 3.0 * kt * factor * PRESFAC / vol) * gauss2);
              }
              break;
          default:
 -            gmx_fatal(FARGS, "C-rescale pressure coupling type %s not supported yet\n",
 +            gmx_fatal(FARGS,
 +                      "C-rescale pressure coupling type %s not supported yet\n",
                        EPCOUPLTYPETYPE(ir->epct));
      }
 -    /* To fullfill the orientation restrictions on triclinic boxes
 +}
 +
 +template<int pressureCouplingType>
 +void pressureCouplingCalculateScalingMatrix(FILE*             fplog,
 +                                            int64_t           step,
 +                                            const t_inputrec* ir,
 +                                            real              dt,
 +                                            const tensor      pres,
 +                                            const matrix      box,
 +                                            const matrix      force_vir,
 +                                            const matrix      constraint_vir,
 +                                            matrix            mu,
 +                                            double*           baros_integral)
 +{
 +    static_assert(pressureCouplingType == epcBERENDSEN || pressureCouplingType == epcCRESCALE,
 +                  "pressureCouplingCalculateScalingMatrix is only implemented for Berendsen and "
 +                  "C-rescale pressure coupling");
 +
 +    calculateScalingMatrixImpl<pressureCouplingType>(ir, mu, dt, pres, box, step);
 +
 +    /* To fulfill the orientation restrictions on triclinic boxes
       * we will set mu_yx, mu_zx and mu_zy to 0 and correct
       * the other elements of mu to first order.
       */
          sprintf(buf,
                  "\nStep %s  Warning: pressure scaling more than 1%%, "
                  "mu: %g %g %g\n",
 -                gmx_step_str(step, buf2), mu[XX][XX], mu[YY][YY], mu[ZZ][ZZ]);
 +                gmx_step_str(step, buf2),
 +                mu[XX][XX],
 +                mu[YY][YY],
 +                mu[ZZ][ZZ]);
          if (fplog)
          {
              fprintf(fplog, "%s", buf);
      }
  }
  
 -void crescale_pscale(const t_inputrec*    ir,
 -                     const matrix         mu,
 -                     matrix               box,
 -                     matrix               box_rel,
 -                     int                  start,
 -                     int                  nr_atoms,
 -                     rvec                 x[],
 -                     rvec                 v[],
 -                     const unsigned short cFREEZE[],
 -                     t_nrnb*              nrnb,
 -                     const bool           scaleCoordinates)
 +template<int pressureCouplingType>
 +void pressureCouplingScaleBoxAndCoordinates(const t_inputrec*    ir,
 +                                            const matrix         mu,
 +                                            matrix               box,
 +                                            matrix               box_rel,
 +                                            int                  start,
 +                                            int                  nr_atoms,
 +                                            rvec                 x[],
 +                                            rvec                 v[],
 +                                            const unsigned short cFREEZE[],
 +                                            t_nrnb*              nrnb,
 +                                            const bool           scaleCoordinates)
  {
 -    ivec* nFreeze = ir->opts.nFreeze;
 -    int nthreads gmx_unused;
 -    matrix       inv_mu;
 -
 -#ifndef __clang_analyzer__
 -    nthreads = gmx_omp_nthreads_get(emntUpdate);
 -#endif
 -
 -    gmx::invertBoxMatrix(mu, inv_mu);
 +    static_assert(pressureCouplingType == epcBERENDSEN || pressureCouplingType == epcCRESCALE,
 +                  "pressureCouplingScaleBoxAndCoordinates is only implemented for Berendsen and "
 +                  "C-rescale pressure coupling");
  
 -    /* Scale the positions and the velocities */
 -    if (scaleCoordinates)
 +    ivec*  nFreeze = ir->opts.nFreeze;
 +    matrix inv_mu;
 +    if (pressureCouplingType == epcCRESCALE)
      {
 -#pragma omp parallel for num_threads(nthreads) schedule(static)
 -        for (int n = start; n < start + nr_atoms; n++)
 -        {
 -            // Trivial OpenMP region that does not throw
 -            int g;
 -
 -            if (cFREEZE == nullptr)
 -            {
 -                g = 0;
 -            }
 -            else
 -            {
 -                g = cFREEZE[n];
 -            }
 -
 -            if (!nFreeze[g][XX])
 -            {
 -                x[n][XX] = mu[XX][XX] * x[n][XX] + mu[YY][XX] * x[n][YY] + mu[ZZ][XX] * x[n][ZZ];
 -                v[n][XX] = inv_mu[XX][XX] * v[n][XX] + inv_mu[YY][XX] * v[n][YY]
 -                           + inv_mu[ZZ][XX] * v[n][ZZ];
 -            }
 -            if (!nFreeze[g][YY])
 -            {
 -                x[n][YY] = mu[YY][YY] * x[n][YY] + mu[ZZ][YY] * x[n][ZZ];
 -                v[n][YY] = inv_mu[YY][YY] * v[n][YY] + inv_mu[ZZ][YY] * v[n][ZZ];
 -            }
 -            if (!nFreeze[g][ZZ])
 -            {
 -                x[n][ZZ] = mu[ZZ][ZZ] * x[n][ZZ];
 -                v[n][ZZ] = inv_mu[ZZ][ZZ] * v[n][ZZ];
 -            }
 -        }
 +        gmx::invertBoxMatrix(mu, inv_mu);
      }
 -    /* compute final boxlengths */
 -    for (int d = 0; d < DIM; d++)
 -    {
 -        box[d][XX] = mu[XX][XX] * box[d][XX] + mu[YY][XX] * box[d][YY] + mu[ZZ][XX] * box[d][ZZ];
 -        box[d][YY] = mu[YY][YY] * box[d][YY] + mu[ZZ][YY] * box[d][ZZ];
 -        box[d][ZZ] = mu[ZZ][ZZ] * box[d][ZZ];
 -    }
 -
 -    preserve_box_shape(ir, box_rel, box);
 -
 -    /* (un)shifting should NOT be done after this,
 -     * since the box vectors might have changed
 -     */
 -    inc_nrnb(nrnb, eNR_PCOUPL, nr_atoms);
 -}
  
 -void berendsen_pscale(const t_inputrec*    ir,
 -                      const matrix         mu,
 -                      matrix               box,
 -                      matrix               box_rel,
 -                      int                  start,
 -                      int                  nr_atoms,
 -                      rvec                 x[],
 -                      const unsigned short cFREEZE[],
 -                      t_nrnb*              nrnb,
 -                      const bool           scaleCoordinates)
 -{
 -    ivec* nFreeze = ir->opts.nFreeze;
 -    int   d;
 -    int nthreads gmx_unused;
 -
 -#ifndef __clang_analyzer__
 -    nthreads = gmx_omp_nthreads_get(emntUpdate);
 -#endif
 -
 -    /* Scale the positions */
 +    /* Scale the positions and the velocities */
      if (scaleCoordinates)
      {
 -#pragma omp parallel for num_threads(nthreads) schedule(static)
 +        const int gmx_unused numThreads = gmx_omp_nthreads_get(emntUpdate);
 +#pragma omp parallel for num_threads(numThreads) schedule(static)
          for (int n = start; n < start + nr_atoms; n++)
          {
              // Trivial OpenMP region that does not throw
 -            int g;
 -
 -            if (cFREEZE == nullptr)
 -            {
 -                g = 0;
 -            }
 -            else
 +            int g = 0;
 +            if (cFREEZE != nullptr)
              {
                  g = cFREEZE[n];
              }
              if (!nFreeze[g][XX])
              {
                  x[n][XX] = mu[XX][XX] * x[n][XX] + mu[YY][XX] * x[n][YY] + mu[ZZ][XX] * x[n][ZZ];
 +                if (pressureCouplingType == epcCRESCALE)
 +                {
 +                    v[n][XX] = inv_mu[XX][XX] * v[n][XX] + inv_mu[YY][XX] * v[n][YY]
 +                               + inv_mu[ZZ][XX] * v[n][ZZ];
 +                }
              }
              if (!nFreeze[g][YY])
              {
                  x[n][YY] = mu[YY][YY] * x[n][YY] + mu[ZZ][YY] * x[n][ZZ];
 +                if (pressureCouplingType == epcCRESCALE)
 +                {
 +                    v[n][YY] = inv_mu[YY][YY] * v[n][YY] + inv_mu[ZZ][YY] * v[n][ZZ];
 +                }
              }
              if (!nFreeze[g][ZZ])
              {
                  x[n][ZZ] = mu[ZZ][ZZ] * x[n][ZZ];
 +                if (pressureCouplingType == epcCRESCALE)
 +                {
 +                    v[n][ZZ] = inv_mu[ZZ][ZZ] * v[n][ZZ];
 +                }
              }
          }
      }
      /* compute final boxlengths */
 -    for (d = 0; d < DIM; d++)
 +    for (int d = 0; d < DIM; d++)
      {
          box[d][XX] = mu[XX][XX] * box[d][XX] + mu[YY][XX] * box[d][YY] + mu[ZZ][XX] * box[d][ZZ];
          box[d][YY] = mu[YY][YY] * box[d][YY] + mu[ZZ][YY] * box[d][ZZ];
@@@ -1370,33 -1416,18 +1370,33 @@@ void trotter_update(const t_inputrec
          {
              case etrtBAROV:
              case etrtBAROV2:
 -                boxv_trotter(ir, &(state->veta), dt, state->box, ekind, vir,
 -                             enerd->term[F_PDISPCORR], MassQ);
 +                boxv_trotter(ir, &(state->veta), dt, state->box, ekind, vir, enerd->term[F_PDISPCORR], MassQ);
                  break;
              case etrtBARONHC:
              case etrtBARONHC2:
 -                NHC_trotter(opts, state->nnhpres, ekind, dt, state->nhpres_xi.data(),
 -                            state->nhpres_vxi.data(), nullptr, &(state->veta), MassQ, FALSE);
 +                NHC_trotter(opts,
 +                            state->nnhpres,
 +                            ekind,
 +                            dt,
 +                            state->nhpres_xi.data(),
 +                            state->nhpres_vxi.data(),
 +                            nullptr,
 +                            &(state->veta),
 +                            MassQ,
 +                            FALSE);
                  break;
              case etrtNHC:
              case etrtNHC2:
 -                NHC_trotter(opts, opts->ngtc, ekind, dt, state->nosehoover_xi.data(),
 -                            state->nosehoover_vxi.data(), scalefac, nullptr, MassQ, (ir->eI == eiVV));
 +                NHC_trotter(opts,
 +                            opts->ngtc,
 +                            ekind,
 +                            dt,
 +                            state->nosehoover_xi.data(),
 +                            state->nosehoover_vxi.data(),
 +                            scalefac,
 +                            nullptr,
 +                            MassQ,
 +                            (ir->eI == eiVV));
                  /* need to rescale the kinetic energies and velocities here.  Could
                     scale the velocities later, but we need them scaled in order to
                     produce the correct outputs, so we'll scale them here. */
@@@ -1550,7 -1581,8 +1550,7 @@@ init_npt_vars(const t_inputrec* ir, t_s
  
      if (EI_VV(ir->eI) && (ir->epc == epcMTTK) && (ir->etc != etcNOSEHOOVER))
      {
 -        gmx_fatal(FARGS,
 -                  "Cannot do MTTK pressure coupling without Nose-Hoover temperature control");
 +        gmx_fatal(FARGS, "Cannot do MTTK pressure coupling without Nose-Hoover temperature control");
      }
  
      init_npt_masses(ir, state, MassQ, TRUE);
@@@ -1732,7 -1764,7 +1732,7 @@@ static real energyNoseHoover(const t_in
          const double* ivxi  = &state->nosehoover_vxi[i * nh];
          const double* iQinv = &(MassQ->Qinv[i * nh]);
  
-         int  nd   = static_cast<int>(ir->opts.nrdf[i]);
+         real nd   = ir->opts.nrdf[i];
          real reft = std::max<real>(ir->opts.ref_t[i], 0);
          real kT   = BOLTZ * reft;
  
                      {
                          energy += 0.5 * gmx::square(ivxi[j]) / iQinv[j];
                          /* contribution from the thermal variable of the NH chain */
-                         int ndj;
+                         real ndj = 0;
                          if (j == 0)
                          {
                              ndj = nd;
@@@ -1795,12 -1827,8 +1795,12 @@@ static real energyPressureMTTK(const t_
              }
              if (debug)
              {
 -                fprintf(debug, "P-T-group: %10d Chain %4d ThermV: %15.8f ThermX: %15.8f", i, j,
 -                        state->nhpres_vxi[i * nh + j], state->nhpres_xi[i * nh + j]);
 +                fprintf(debug,
 +                        "P-T-group: %10d Chain %4d ThermV: %15.8f ThermX: %15.8f",
 +                        i,
 +                        j,
 +                        state->nhpres_vxi[i * nh + j],
 +                        state->nhpres_xi[i * nh + j]);
              }
          }
      }
@@@ -2021,13 -2049,8 +2021,13 @@@ void vrescale_tcoupl(const t_inputrec* 
  
              if (debug)
              {
 -                fprintf(debug, "TC: group %d: Ekr %g, Ek %g, Ek_new %g, Lambda: %g\n", i, Ek_ref,
 -                        Ek, Ek_new, ekind->tcstat[i].lambda);
 +                fprintf(debug,
 +                        "TC: group %d: Ekr %g, Ek %g, Ek_new %g, Lambda: %g\n",
 +                        i,
 +                        Ek_ref,
 +                        Ek,
 +                        Ek_new,
 +                        ekind->tcstat[i].lambda);
              }
          }
          else
  
  void rescale_velocities(const gmx_ekindata_t* ekind, const t_mdatoms* mdatoms, int start, int end, rvec v[])
  {
 -    unsigned short *cACC, *cTC;
 -    int             ga, gt, n, d;
 -    real            lg;
 -    rvec            vrel;
 -
 -    cTC = mdatoms->cTC;
 -
 +    const unsigned short*             cTC    = mdatoms->cTC;
      gmx::ArrayRef<const t_grp_tcstat> tcstat = ekind->tcstat;
  
 -    if (ekind->bNEMD)
 +    for (int n = start; n < end; n++)
      {
 -        gmx::ArrayRef<const t_grp_acc> gstat = ekind->grpstat;
 -        cACC                                 = mdatoms->cACC;
 -
 -        ga = 0;
 -        gt = 0;
 -        for (n = start; n < end; n++)
 +        int gt = 0;
 +        if (cTC)
          {
 -            if (cACC)
 -            {
 -                ga = cACC[n];
 -            }
 -            if (cTC)
 -            {
 -                gt = cTC[n];
 -            }
 -            /* Only scale the velocity component relative to the COM velocity */
 -            rvec_sub(v[n], gstat[ga].u, vrel);
 -            lg = tcstat[gt].lambda;
 -            for (d = 0; d < DIM; d++)
 -            {
 -                v[n][d] = gstat[ga].u[d] + lg * vrel[d];
 -            }
 +            gt = cTC[n];
          }
 -    }
 -    else
 -    {
 -        gt = 0;
 -        for (n = start; n < end; n++)
 +        const real lg = tcstat[gt].lambda;
 +        for (int d = 0; d < DIM; d++)
          {
 -            if (cTC)
 -            {
 -                gt = cTC[n];
 -            }
 -            lg = tcstat[gt].lambda;
 -            for (d = 0; d < DIM; d++)
 -            {
 -                v[n][d] *= lg;
 -            }
 +            v[n][d] *= lg;
          }
      }
  }
@@@ -2108,11 -2166,8 +2108,11 @@@ void update_annealing_target_temp(t_inp
                  break;
              case eannSINGLE: thist = t; break;
              default:
 -                gmx_fatal(FARGS, "Death horror in update_annealing_target_temp (i=%d/%d npoints=%d)",
 -                          i, ir->opts.ngtc, npoints);
 +                gmx_fatal(FARGS,
 +                          "Death horror in update_annealing_target_temp (i=%d/%d npoints=%d)",
 +                          i,
 +                          ir->opts.ngtc,
 +                          npoints);
          }
          /* We are doing annealing for this group if we got here,
           * and we have the (relative) time as thist.
index bdac063a161d20d127f5f82dee67b156d22b3d6e,d48016a4d64c05e82f6d380942c5dca4758d53a3..570229515be08ac9bf33ca21c770dc8d3d4133fc
@@@ -70,6 -70,8 +70,8 @@@
  #include "gromacs/utility/gmxmpi.h"
  #include "gromacs/utility/smalloc.h"
  
+ #include "expanded_internal.h"
  static void init_df_history_weights(df_history_t* dfhist, const t_expanded* expand, int nlim)
  {
      int i;
@@@ -344,20 -346,25 +346,25 @@@ static gmx_bool UpdateWeights(in
                                int64_t       step)
  {
      gmx_bool bSufficientSamples;
+     real     acceptanceWeight;
      int      i;
-     int      n0, np1, nm1, nval, min_nvalm, min_nvalp, maxc;
-     real     omega_m1_0, omega_p1_0, clam_osum;
-     real     de, de_function;
-     real     cnval, zero_sum_weights;
+     int      min_nvalm, min_nvalp, maxc;
+     real     omega_m1_0, omega_p1_0;
+     real     zero_sum_weights;
      real *omegam_array, *weightsm_array, *omegap_array, *weightsp_array, *varm_array, *varp_array,
              *dwp_array, *dwm_array;
-     real    clam_varm, clam_varp, clam_weightsm, clam_weightsp, clam_minvar;
+     real    clam_varm, clam_varp, clam_osum, clam_weightsm, clam_weightsp, clam_minvar;
      real *  lam_variance, *lam_dg;
      double* p_k;
      double  pks = 0;
-     real    chi_m1_0, chi_p1_0, chi_m2_0, chi_p2_0, chi_p1_m1, chi_p2_m1, chi_m1_p1, chi_m2_p1;
  
-     /* if we have equilibrated the weights, exit now */
+     /* Future potential todos for this function (see #3848):
+      *  - Update the names in the dhist structure to be clearer. Not done for now since this
+      *    a bugfix update and we are mininizing other code changes.
+      *  - Modularize the code some more.
+      *  - potentially merge with accelerated weight histogram functionality, since it's very similar.
+      */
+     /*  if we have equilibrated the expanded ensemble weights, we are not updating them, so exit now */
      if (dfhist->bEquil)
      {
          return FALSE;
  
      if (EWL(expand->elamstats))
      {
-         if (expand->elamstats == elamstatsWL) /* Standard Wang-Landau */
+         if (expand->elamstats == elamstatsWL) /* Using standard Wang-Landau for weight updates */
          {
              dfhist->sum_weights[fep_state] -= dfhist->wl_delta;
              dfhist->wl_histo[fep_state] += 1.0;
          }
-         else if (expand->elamstats == elamstatsWWL) /* Weighted Wang-Landau */
+         else if (expand->elamstats == elamstatsWWL)
+         /* Using weighted Wang-Landau for weight updates.
+          * Very closly equivalent to accelerated weight histogram approach
+          * applied to expanded ensemble. */
          {
              snew(p_k, nlim);
  
  
              /* then increment weights (uses count) */
              pks = 0.0;
 -            GenerateWeightedGibbsProbabilities(weighted_lamee, p_k, &pks, nlim, dfhist->wl_histo,
 -                                               dfhist->wl_delta);
 +            GenerateWeightedGibbsProbabilities(
 +                    weighted_lamee, p_k, &pks, nlim, dfhist->wl_histo, dfhist->wl_delta);
  
              for (i = 0; i < nlim; i++)
              {
      if (expand->elamstats == elamstatsBARKER || expand->elamstats == elamstatsMETROPOLIS
          || expand->elamstats == elamstatsMINVAR)
      {
-         de_function = 0; /* to get rid of warnings, but this value will not be used because of the logic */
          maxc = 2 * expand->c_range + 1;
  
          snew(lam_dg, nlim);
          snew(varm_array, maxc);
          snew(dwm_array, maxc);
  
-         /* unpack the current lambdas -- we will only update 2 of these */
+         /* unpack the values of the free energy differences and the
+          * variance in their estimates between nearby lambdas. We will
+          * only actually update 2 of these, the state we are currently
+          * at and the one we end up moving to
+          */
  
          for (i = 0; i < nlim - 1; i++)
          { /* only through the second to last */
                      gmx::square(dfhist->sum_variance[i + 1]) - gmx::square(dfhist->sum_variance[i]);
          }
  
-         /* accumulate running averages */
-         for (nval = 0; nval < maxc; nval++)
+         /* accumulate running averages of thermodynamic averages for Bennett Acceptance Ratio-based
+          * estimates of the free energy .
+          * Rather than peforming self-consistent estimation of the free energies at each step,
+          * we keep track of an array of possible different free energies (cnvals),
+          * and we self-consistently choose the best one. The one that leads to a free energy estimate
+          * that is closest to itself is the best estimate of the free energy.  It is essentially a
+          * parallellized version of self-consistent iteration.  maxc is the number of these constants. */
+         for (int nval = 0; nval < maxc; nval++)
          {
-             /* constants for later use */
-             cnval = static_cast<real>(nval - expand->c_range);
-             /* actually, should be able to rewrite it w/o exponential, for better numerical stability */
+             const real cnval = static_cast<real>(nval - expand->c_range);
+             /* Compute acceptance criterion weight to the state below this one for use in averages.
+              * Note we do not have to have just moved from that state to use this free energy
+              * estimate; these are essentially "virtual" moves. */
              if (fep_state > 0)
              {
-                 de = std::exp(cnval - (scaled_lamee[fep_state] - scaled_lamee[fep_state - 1]));
-                 if (expand->elamstats == elamstatsBARKER || expand->elamstats == elamstatsMINVAR)
-                 {
-                     de_function = 1.0 / (1.0 + de);
-                 }
-                 else if (expand->elamstats == elamstatsMETROPOLIS)
-                 {
-                     if (de < 1.0)
-                     {
-                         de_function = 1.0;
-                     }
-                     else
-                     {
-                         de_function = 1.0 / de;
-                     }
-                 }
-                 dfhist->accum_m[fep_state][nval] += de_function;
-                 dfhist->accum_m2[fep_state][nval] += de_function * de_function;
+                 const auto lambdaEnergyDifference =
+                         cnval - (scaled_lamee[fep_state] - scaled_lamee[fep_state - 1]);
+                 acceptanceWeight =
+                         gmx::calculateAcceptanceWeight(expand->elamstats, lambdaEnergyDifference);
+                 dfhist->accum_m[fep_state][nval] += acceptanceWeight;
+                 dfhist->accum_m2[fep_state][nval] += acceptanceWeight * acceptanceWeight;
              }
  
+             // Compute acceptance criterion weight to transition to the next state
              if (fep_state < nlim - 1)
              {
-                 de = std::exp(-cnval + (scaled_lamee[fep_state + 1] - scaled_lamee[fep_state]));
-                 if (expand->elamstats == elamstatsBARKER || expand->elamstats == elamstatsMINVAR)
-                 {
-                     de_function = 1.0 / (1.0 + de);
-                 }
-                 else if (expand->elamstats == elamstatsMETROPOLIS)
-                 {
-                     if (de < 1.0)
-                     {
-                         de_function = 1.0;
-                     }
-                     else
-                     {
-                         de_function = 1.0 / de;
-                     }
-                 }
-                 dfhist->accum_p[fep_state][nval] += de_function;
-                 dfhist->accum_p2[fep_state][nval] += de_function * de_function;
+                 const auto lambdaEnergyDifference =
+                         -cnval + (scaled_lamee[fep_state + 1] - scaled_lamee[fep_state]);
+                 acceptanceWeight =
+                         gmx::calculateAcceptanceWeight(expand->elamstats, lambdaEnergyDifference);
+                 dfhist->accum_p[fep_state][nval] += acceptanceWeight;
+                 dfhist->accum_p2[fep_state][nval] += acceptanceWeight * acceptanceWeight;
              }
  
-             /* Metropolis transition and Barker transition (unoptimized Bennett) acceptance weight determination */
+             /* Determination of Metropolis transition and Barker transition weights */
  
-             n0 = dfhist->n_at_lam[fep_state];
+             int numObservationsCurrentState = dfhist->n_at_lam[fep_state];
+             /* determine the number of observations above and below the current state */
+             int numObservationsLowerState = 0;
              if (fep_state > 0)
              {
-                 nm1 = dfhist->n_at_lam[fep_state - 1];
-             }
-             else
-             {
-                 nm1 = 0;
+                 numObservationsLowerState = dfhist->n_at_lam[fep_state - 1];
              }
+             int numObservationsHigherState = 0;
              if (fep_state < nlim - 1)
              {
-                 np1 = dfhist->n_at_lam[fep_state + 1];
-             }
-             else
-             {
-                 np1 = 0;
+                 numObservationsHigherState = dfhist->n_at_lam[fep_state + 1];
              }
  
-             /* logic SHOULD keep these all set correctly whatever the logic, but apparently it can't figure it out. */
-             chi_m1_0 = chi_p1_0 = chi_m2_0 = chi_p2_0 = chi_p1_m1 = chi_p2_m1 = chi_m1_p1 = chi_m2_p1 = 0;
+             /* Calculate the biases for each expanded ensemble state that minimize the total
+              * variance, as implemented in Martinez-Veracoechea and Escobedo,
+              * J. Phys. Chem. B 2008, 112, 8120-8128
+              *
+              * The variance associated with the free energy estimate between two states i and j
+              * is calculated as
+              *     Var(i,j) = {avg[xi(i->j)^2] / avg[xi(i->j)]^2 - 1} / numObservations(i->j)
+              *              + {avg[xi(j->i)^2] / avg[xi(j->i)]^2 - 1} / numObservations(j->i)
+              * where xi(i->j) is the acceptance factor / weight associated with moving from state i to j
+              * As we are calculating the acceptance factor to the neighbors every time we're visiting
+              * a state, numObservations(i->j) == numObservations(i) and numObservations(j->i) == numObservations(j)
+              */
  
-             if (n0 > 0)
+             /* Accumulation of acceptance weight averages between the current state and the
+              * states +1 (p1) and -1 (m1), averaged at current state (0)
+              */
+             real avgAcceptanceCurrentToLower  = 0;
+             real avgAcceptanceCurrentToHigher = 0;
+             /* Accumulation of acceptance weight averages quantities between states 0
+              *  and states +1 and -1, squared
+              */
+             real avgAcceptanceCurrentToLowerSquared  = 0;
+             real avgAcceptanceCurrentToHigherSquared = 0;
+             /* Accumulation of free energy quantities from lower state (m1) to current state (0) and squared */
+             real avgAcceptanceLowerToCurrent        = 0;
+             real avgAcceptanceLowerToCurrentSquared = 0;
+             /* Accumulation of free energy quantities from upper state (p1) to current state (0) and squared */
+             real avgAcceptanceHigherToCurrent        = 0;
+             real avgAcceptanceHigherToCurrentSquared = 0;
+             if (numObservationsCurrentState > 0)
              {
-                 chi_m1_0 = dfhist->accum_m[fep_state][nval] / n0;
-                 chi_p1_0 = dfhist->accum_p[fep_state][nval] / n0;
-                 chi_m2_0 = dfhist->accum_m2[fep_state][nval] / n0;
-                 chi_p2_0 = dfhist->accum_p2[fep_state][nval] / n0;
+                 avgAcceptanceCurrentToLower = dfhist->accum_m[fep_state][nval] / numObservationsCurrentState;
+                 avgAcceptanceCurrentToHigher =
+                         dfhist->accum_p[fep_state][nval] / numObservationsCurrentState;
+                 avgAcceptanceCurrentToLowerSquared =
+                         dfhist->accum_m2[fep_state][nval] / numObservationsCurrentState;
+                 avgAcceptanceCurrentToHigherSquared =
+                         dfhist->accum_p2[fep_state][nval] / numObservationsCurrentState;
              }
  
-             if ((fep_state > 0) && (nm1 > 0))
+             if ((fep_state > 0) && (numObservationsLowerState > 0))
              {
-                 chi_p1_m1 = dfhist->accum_p[fep_state - 1][nval] / nm1;
-                 chi_p2_m1 = dfhist->accum_p2[fep_state - 1][nval] / nm1;
+                 avgAcceptanceLowerToCurrent =
+                         dfhist->accum_p[fep_state - 1][nval] / numObservationsLowerState;
+                 avgAcceptanceLowerToCurrentSquared =
+                         dfhist->accum_p2[fep_state - 1][nval] / numObservationsLowerState;
              }
  
-             if ((fep_state < nlim - 1) && (np1 > 0))
+             if ((fep_state < nlim - 1) && (numObservationsHigherState > 0))
              {
-                 chi_m1_p1 = dfhist->accum_m[fep_state + 1][nval] / np1;
-                 chi_m2_p1 = dfhist->accum_m2[fep_state + 1][nval] / np1;
+                 avgAcceptanceHigherToCurrent =
+                         dfhist->accum_m[fep_state + 1][nval] / numObservationsHigherState;
+                 avgAcceptanceHigherToCurrentSquared =
+                         dfhist->accum_m2[fep_state + 1][nval] / numObservationsHigherState;
              }
-             omega_m1_0    = 0;
-             omega_p1_0    = 0;
-             clam_weightsm = 0;
-             clam_weightsp = 0;
-             clam_varm     = 0;
-             clam_varp     = 0;
+             /* These are accumulation of positive values (see definition of acceptance functions
+              * above), or of squares of positive values.
+              * We're taking this for granted in the following calculation, so make sure
+              * here that nothing weird happened. Although technically all values should be positive,
+              * because of floating point precisions, they might be numerically zero. */
+             GMX_RELEASE_ASSERT(
+                     avgAcceptanceCurrentToLower >= 0 && avgAcceptanceCurrentToLowerSquared >= 0
+                             && avgAcceptanceCurrentToHigher >= 0
+                             && avgAcceptanceCurrentToHigherSquared >= 0 && avgAcceptanceLowerToCurrent >= 0
+                             && avgAcceptanceLowerToCurrentSquared >= 0 && avgAcceptanceHigherToCurrent >= 0
+                             && avgAcceptanceHigherToCurrentSquared >= 0,
+                     "By definition, the acceptance factors should all be nonnegative.");
+             real varianceCurrentToLower   = 0;
+             real varianceCurrentToHigher  = 0;
+             real weightDifferenceToLower  = 0;
+             real weightDifferenceToHigher = 0;
+             real varianceToLower          = 0;
+             real varianceToHigher         = 0;
  
              if (fep_state > 0)
              {
-                 if (n0 > 0)
+                 if (numObservationsCurrentState > 0)
                  {
-                     omega_m1_0 = chi_m2_0 / (chi_m1_0 * chi_m1_0) - 1.0;
-                     if (nm1 > 0)
+                     /* Calculate {avg[xi(i->j)^2] / avg[xi(i->j)]^2 - 1}
+                      *
+                      * Note that if avg[xi(i->j)] == 0, also avg[xi(i->j)^2] == 0 (since the
+                      * acceptances are all positive!), and hence
+                      *     {avg[xi(i->j)^2] / avg[xi(i->j)]^2 - 1} -> 0  for  avg[xi(i->j)] -> 0
+                      * We're catching that case explicitly to avoid numerical
+                      * problems dividing by zero when the overlap between states is small (#3304)
+                      */
+                     if (avgAcceptanceCurrentToLower > 0)
                      {
-                         real omega_p1_m1 = chi_p2_m1 / (chi_p1_m1 * chi_p1_m1) - 1.0;
-                         clam_weightsm    = (std::log(chi_m1_0) - std::log(chi_p1_m1)) + cnval;
-                         clam_varm        = (1.0 / n0) * (omega_m1_0) + (1.0 / nm1) * (omega_p1_m1);
+                         varianceCurrentToLower =
+                                 avgAcceptanceCurrentToLowerSquared
+                                         / (avgAcceptanceCurrentToLower * avgAcceptanceCurrentToLower)
+                                 - 1.0;
+                     }
+                     if (numObservationsLowerState > 0)
+                     {
+                         /* Calculate {avg[xi(i->j)^2] / avg[xi(i->j)]^2 - 1}
+                          *
+                          * Note that if avg[xi(i->j)] == 0, also avg[xi(i->j)^2] == 0 (since the
+                          * acceptances are all positive!), and hence
+                          *     {avg[xi(i->j)^2] / avg[xi(i->j)]^2 - 1} -> 0  for  avg[xi(i->j)] -> 0
+                          * We're catching that case explicitly to avoid numerical
+                          * problems dividing by zero when the overlap between states is small (#3304)
+                          */
+                         real varianceLowerToCurrent = 0;
+                         if (avgAcceptanceLowerToCurrent > 0)
+                         {
+                             varianceLowerToCurrent =
+                                     avgAcceptanceLowerToCurrentSquared
+                                             / (avgAcceptanceLowerToCurrent * avgAcceptanceLowerToCurrent)
+                                     - 1.0;
+                         }
+                         /* Free energy difference to the state one state lower */
+                         /* if these either of these quantities are zero, the energies are */
+                         /* way too large for the dynamic range.  We need an alternate guesstimate */
+                         if ((avgAcceptanceCurrentToLower == 0) || (avgAcceptanceLowerToCurrent == 0))
+                         {
+                             weightDifferenceToLower =
+                                     (scaled_lamee[fep_state] - scaled_lamee[fep_state - 1]);
+                         }
+                         else
+                         {
+                             weightDifferenceToLower = (std::log(avgAcceptanceCurrentToLower)
+                                                        - std::log(avgAcceptanceLowerToCurrent))
+                                                       + cnval;
+                         }
+                         /* Variance of the free energy difference to the one state lower */
+                         varianceToLower =
+                                 (1.0 / numObservationsCurrentState) * (varianceCurrentToLower)
+                                 + (1.0 / numObservationsLowerState) * (varianceLowerToCurrent);
                      }
                  }
              }
  
              if (fep_state < nlim - 1)
              {
-                 if (n0 > 0)
+                 if (numObservationsCurrentState > 0)
                  {
-                     omega_p1_0 = chi_p2_0 / (chi_p1_0 * chi_p1_0) - 1.0;
-                     if (np1 > 0)
+                     /* Calculate {avg[xi(i->j)^2] / avg[xi(i->j)]^2 - 1}
+                      *
+                      * Note that if avg[xi(i->j)] == 0, also avg[xi(i->j)^2] == 0 (since the
+                      * acceptances are all positive!), and hence
+                      *     {avg[xi(i->j)^2] / avg[xi(i->j)]^2 - 1} -> 0  for  avg[xi(i->j)] -> 0
+                      * We're catching that case explicitly to avoid numerical
+                      * problems dividing by zero when the overlap between states is small (#3304)
+                      */
+                     if (avgAcceptanceCurrentToHigher < 0)
+                     {
+                         varianceCurrentToHigher =
+                                 avgAcceptanceCurrentToHigherSquared
+                                         / (avgAcceptanceCurrentToHigher * avgAcceptanceCurrentToHigher)
+                                 - 1.0;
+                     }
+                     if (numObservationsHigherState > 0)
                      {
-                         real omega_m1_p1 = chi_m2_p1 / (chi_m1_p1 * chi_m1_p1) - 1.0;
-                         clam_weightsp    = (std::log(chi_m1_p1) - std::log(chi_p1_0)) + cnval;
-                         clam_varp        = (1.0 / np1) * (omega_m1_p1) + (1.0 / n0) * (omega_p1_0);
+                         /* Calculate {avg[xi(i->j)^2] / avg[xi(i->j)]^2 - 1}
+                          *
+                          * Note that if avg[xi(i->j)] == 0, also avg[xi(i->j)^2] == 0 (since the
+                          * acceptances are all positive!), and hence
+                          *     {avg[xi(i->j)^2] / avg[xi(i->j)]^2 - 1} -> 0  for  avg[xi(i->j)] -> 0
+                          * We're catching that case explicitly to avoid numerical
+                          * problems dividing by zero when the overlap between states is small (#3304)
+                          */
+                         real varianceHigherToCurrent = 0;
+                         if (avgAcceptanceHigherToCurrent > 0)
+                         {
+                             varianceHigherToCurrent =
+                                     avgAcceptanceHigherToCurrentSquared
+                                             / (avgAcceptanceHigherToCurrent * avgAcceptanceHigherToCurrent)
+                                     - 1.0;
+                         }
+                         /* Free energy difference to the state one state higher */
+                         /* if these either of these quantities are zero, the energies are */
+                         /* way too large for the dynamic range.  We need an alternate guesstimate */
+                         if ((avgAcceptanceHigherToCurrent == 0) || (avgAcceptanceCurrentToHigher == 0))
+                         {
+                             weightDifferenceToHigher =
+                                     (scaled_lamee[fep_state + 1] - scaled_lamee[fep_state]);
+                         }
+                         else
+                         {
+                             weightDifferenceToHigher = (std::log(avgAcceptanceHigherToCurrent)
+                                                         - std::log(avgAcceptanceCurrentToHigher))
+                                                        + cnval;
+                         }
+                         /* Variance of the free energy difference to the one state higher */
+                         varianceToHigher =
+                                 (1.0 / numObservationsHigherState) * (varianceHigherToCurrent)
+                                 + (1.0 / numObservationsCurrentState) * (varianceCurrentToHigher);
                      }
                  }
              }
  
-             if (n0 > 0)
+             if (numObservationsCurrentState > 0)
              {
-                 omegam_array[nval] = omega_m1_0;
+                 omegam_array[nval] = varianceCurrentToLower;
              }
              else
              {
                  omegam_array[nval] = 0;
              }
-             weightsm_array[nval] = clam_weightsm;
-             varm_array[nval]     = clam_varm;
-             if (nm1 > 0)
+             weightsm_array[nval] = weightDifferenceToLower;
+             varm_array[nval]     = varianceToLower;
+             if (numObservationsLowerState > 0)
              {
-                 dwm_array[nval] = fabs((cnval + std::log((1.0 * n0) / nm1)) - lam_dg[fep_state - 1]);
+                 dwm_array[nval] =
+                         fabs((cnval + std::log((1.0 * numObservationsCurrentState) / numObservationsLowerState))
+                              - lam_dg[fep_state - 1]);
              }
              else
              {
                  dwm_array[nval] = std::fabs(cnval - lam_dg[fep_state - 1]);
              }
  
-             if (n0 > 0)
+             if (numObservationsCurrentState > 0)
              {
-                 omegap_array[nval] = omega_p1_0;
+                 omegap_array[nval] = varianceCurrentToHigher;
              }
              else
              {
                  omegap_array[nval] = 0;
              }
-             weightsp_array[nval] = clam_weightsp;
-             varp_array[nval]     = clam_varp;
-             if ((np1 > 0) && (n0 > 0))
+             weightsp_array[nval] = weightDifferenceToHigher;
+             varp_array[nval]     = varianceToHigher;
+             if ((numObservationsHigherState > 0) && (numObservationsCurrentState > 0))
              {
-                 dwp_array[nval] = fabs((cnval + std::log((1.0 * np1) / n0)) - lam_dg[fep_state]);
+                 dwp_array[nval] =
+                         fabs((cnval + std::log((1.0 * numObservationsHigherState) / numObservationsCurrentState))
+                              - lam_dg[fep_state]);
              }
              else
              {
              }
          }
  
-         /* find the C's closest to the old weights value */
+         /* find the free energy estimate closest to the guessed weight's value */
  
          min_nvalm     = FindMinimum(dwm_array, maxc);
          omega_m1_0    = omegam_array[min_nvalm];
          if (expand->elamstats == elamstatsMINVAR)
          {
              bSufficientSamples = TRUE;
-             /* make sure they are all past a threshold */
+             /* make sure the number of samples in each state are all
+              * past a user-specified threshold
+              */
              for (i = 0; i < nlim; i++)
              {
                  if (dfhist->n_at_lam[i] < expand->minvarmin)
@@@ -922,16 -1059,11 +1059,16 @@@ static int ChooseNewLambda(in
                              "Something wrong in choosing new lambda state with a Gibbs move -- "
                              "probably underflow in weight determination.\nDenominator is: "
                              "%3d%17.10e\n  i                dE        numerator          weights\n",
 -                            0, pks);
 +                            0,
 +                            pks);
                      for (ifep = minfep; ifep <= maxfep; ifep++)
                      {
 -                        loc += sprintf(&errorstr[loc], "%3d %17.10e%17.10e%17.10e\n", ifep,
 -                                       weighted_lamee[ifep], p_k[ifep], dfhist->sum_weights[ifep]);
 +                        loc += sprintf(&errorstr[loc],
 +                                       "%3d %17.10e%17.10e%17.10e\n",
 +                                       ifep,
 +                                       weighted_lamee[ifep],
 +                                       p_k[ifep],
 +                                       dfhist->sum_weights[ifep]);
                      }
                      gmx_fatal(FARGS, "%s", errorstr);
                  }
              de = weighted_lamee[lamtrial] - weighted_lamee[fep_state];
              if (expand->elmcmove == elmcmoveMETROPOLIS)
              {
-                 tprob     = 1.0;
-                 trialprob = std::exp(de);
-                 if (trialprob < tprob)
+                 tprob = 1.0;
+                 if (de < 0)
                  {
-                     tprob = trialprob;
+                     tprob = std::exp(de);
                  }
                  propose[fep_state] = 0;
                  propose[lamtrial]  = 1.0; /* note that this overwrites the above line if fep_state = ntrial, which only occurs at the ends */
              }
              else if (expand->elmcmove == elmcmoveBARKER)
              {
-                 tprob = 1.0 / (1.0 + std::exp(-de));
+                 if (de > 0) /* Numerically stable version */
+                 {
+                     tprob = 1.0 / (1.0 + std::exp(-de));
+                 }
+                 else if (de < 0)
+                 {
+                     tprob = std::exp(de) / (std::exp(de) + 1.0);
+                 }
                  propose[fep_state] = (1 - tprob);
                  propose[lamtrial] +=
                          tprob; /* we add, to account for the fact that at the end, they might be the same point */
@@@ -1113,12 -1250,8 +1255,12 @@@ void PrintFreeEnergyInfoToFile(FILE
              }
              if (expand->elamstats == elamstatsMINVAR)
              {
 -                fprintf(outfile, " %10.5f %10.5f %10.5f %10.5f", dfhist->sum_weights[ifep],
 -                        dfhist->sum_dg[ifep], dg, dv);
 +                fprintf(outfile,
 +                        " %10.5f %10.5f %10.5f %10.5f",
 +                        dfhist->sum_weights[ifep],
 +                        dfhist->sum_dg[ifep],
 +                        dg,
 +                        dv);
              }
              else
              {
  }
  
  int ExpandedEnsembleDynamics(FILE*                 log,
 -                             const t_inputrec*     ir,
 +                             t_inputrec*           ir,
                               const gmx_enerdata_t* enerd,
                               t_state*              state,
                               t_extmass*            MassQ,
      {
          if (log)
          {
 -            fprintf(log, "\nStep %" PRId64 ": Weights have equilibrated, using criteria: %s\n",
 -                    step, elmceq_names[expand->elmceq]);
 +            fprintf(log,
 +                    "\nStep %" PRId64 ": Weights have equilibrated, using criteria: %s\n",
 +                    step,
 +                    elmceq_names[expand->elmceq]);
          }
      }
  
 -    lamnew = ChooseNewLambda(nlim, expand, dfhist, fep_state, weighted_lamee, p_k,
 -                             ir->expandedvals->lmc_seed, step);
 +    lamnew = ChooseNewLambda(
 +            nlim, expand, dfhist, fep_state, weighted_lamee, p_k, ir->expandedvals->lmc_seed, step);
      /* if using simulated tempering, we need to adjust the temperatures */
      if (ir->bSimTemp && (lamnew != fep_state)) /* only need to change the temperatures if we change the state */
      {
index 05b6a4f3bede7a2412e35313c645d4c333769dc0,6c6804f1bbf9181d02ae91b2d46c68aa10a34b65..68db1fbfd7316e0b435bcc0c19ad503c82f6ef39
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2015,2016,2017,2018,2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 2015,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.
@@@ -120,15 -120,16 +120,16 @@@ gmx_bool exist_output_file(const char* 
  {
      StringOutputStream stream;
      TextWriter         writer(&stream);
-     writer.writeStringFormatted(
+     writer.writeLineFormatted(
              "Some output files listed in the checkpoint file %s are not present or not named "
              "as the output files by the current program:)",
              checkpointFilename);
-     auto settings  = writer.wrapperSettings();
-     auto oldIndent = settings.indent(), newIndent = 2;
+     auto& settings  = writer.wrapperSettings();
+     auto  oldIndent = settings.indent(), newIndent = 2;
  
      writer.writeLine("Expected output files that are present:");
      settings.setIndent(newIndent);
+     settings.setLineLength(78);
      for (const auto& outputfile : outputfiles)
      {
          if (exist_output_file(outputfile.filename, nfile, fnm))
      settings.setIndent(oldIndent);
      writer.ensureEmptyLine();
  
+     // The implementation of -deffnm does not handle properly the
+     // naming of output files that share a common suffix, such as
+     // pullx.xvg and pullf.xvg from the pull module. Such output files
+     // will be sought by the wrong name by the code that handles the
+     // restart, even though the pull module would later work out what
+     // they should have been called. Since there is a straightforward
+     // way to work around that, we help the user with that. This can
+     // be removed when gitlab issue #3875 is resolved.
+     bool missingFilesIncludedPullOutputFiles = false;
      writer.writeLine("Expected output files that are not present or named differently:");
      settings.setIndent(newIndent);
      for (const auto& outputfile : outputfiles)
          if (!exist_output_file(outputfile.filename, nfile, fnm))
          {
              writer.writeLine(outputfile.filename);
+             // If this was a pull file, then we have a known issue and
+             // work-around (See gitlab issue #3442).
+             if (!missingFilesIncludedPullOutputFiles
+                 && (contains(outputfile.filename, "pullx")
+                     || contains(outputfile.filename, "pullf")))
+             {
+                 missingFilesIncludedPullOutputFiles = true;
+             }
          }
      }
+     if (missingFilesIncludedPullOutputFiles)
+     {
+         writer.ensureEmptyLine();
+         writer.writeLineFormatted(
+                 "It appears that pull output files were not found. It is known that "
+                 "using gmx mdrun -deffnm test with pulling and later "
+                 "gmx mdrun -deffnm test -cpi will fail to consider the changed default "
+                 "filename when checking the pull output files for restarting with "
+                 "appending. You may be able to work around this by using a command like "
+                 "gmx mdrun -deffnm test -px test_pullx -pf test_pullf -cpi.");
+     }
      settings.setIndent(oldIndent);
  
+     writer.ensureEmptyLine();
      writer.writeLineFormatted(
-             R"(To keep your simulation files safe, this simulation will not restart. Either name your
- output files exactly the same as the previous simulation part (e.g. with -deffnm), or
- make sure all the output files are present (e.g. run from the same directory as the
- previous simulation part), or instruct mdrun to write new output files with mdrun -noappend.
- In the last case, you will not be able to use appending in future for this simulation.)",
+             "To keep your simulation files safe, this simulation will not restart. "
+             "Either name your output files exactly the same as the previous simulation "
+             "part (e.g. with -deffnm or explicit naming), or make sure all the output "
+             "files are present (e.g. run from the same directory as the previous simulation "
+             "part), or instruct mdrun to write new output files with mdrun -noappend. In "
+             "the last case, you will not be able to use appending in future for this "
+             "simulation.",
 -            numFilesMissing, outputfiles.size());
 +            numFilesMissing,
 +            outputfiles.size());
      GMX_THROW(InconsistentInputError(stream.toString()));
  }
  
@@@ -264,8 -295,7 +296,8 @@@ StartingBehaviorHandler chooseStartingB
      GMX_RELEASE_ASSERT(Path::extensionMatches(logFilename, ftp2ext(efLOG)),
                         formatString("The checkpoint file or its reading is broken, the first "
                                      "output file '%s' must be a log file with extension '%s'",
 -                                    logFilename, ftp2ext(efLOG))
 +                                    logFilename,
 +                                    ftp2ext(efLOG))
                                 .c_str());
  
      if (appendingBehavior != AppendingBehavior::NoAppending)
                          "Cannot restart with appending because the previous simulation part used "
                          "%s precision which does not match the %s precision used by this build "
                          "of GROMACS. Either use matching precision or use mdrun -noappend.",
 -                        precisionToString(headerContents.double_prec), precisionToString(GMX_DOUBLE))));
 +                        precisionToString(headerContents.double_prec),
 +                        precisionToString(GMX_DOUBLE))));
              }
          }
          // If the previous log filename had a part number, then we
@@@ -372,8 -401,7 +404,8 @@@ void checkOutputFile(t_fileio* fileToCh
                      "Can't read %d bytes of '%s' to compute checksum. The file "
                      "has been replaced or its contents have been modified. Cannot "
                      "do appending because of this condition.",
 -                    outputfile.checksumSize, outputfile.filename);
 +                    outputfile.checksumSize,
 +                    outputfile.filename);
              GMX_THROW(InconsistentInputError(message));
          }
      }
@@@ -515,8 -543,8 +547,8 @@@ void StartingBehaviorHandler::ensureMul
  
      auto startingBehaviors = gatherIntFromMultiSimulation(ms, static_cast<int>(startingBehavior));
      bool identicalStartingBehaviors =
 -            (std::adjacent_find(std::begin(startingBehaviors), std::end(startingBehaviors),
 -                                std::not_equal_to<>())
 +            (std::adjacent_find(
 +                     std::begin(startingBehaviors), std::end(startingBehaviors), std::not_equal_to<>())
               == std::end(startingBehaviors));
  
      const EnumerationArray<StartingBehavior, std::string> behaviorStrings = {
@@@ -541,8 -569,8 +573,8 @@@ simulations wanted the following respec
          for (index simIndex = 0; simIndex != ssize(startingBehaviors); ++simIndex)
          {
              auto behavior = static_cast<StartingBehavior>(startingBehaviors[simIndex]);
 -            message += formatString("  Simulation %6zd: %s\n", simIndex,
 -                                    behaviorStrings[behavior].c_str());
 +            message += formatString(
 +                    "  Simulation %6zd: %s\n", simIndex, behaviorStrings[behavior].c_str());
          }
          GMX_THROW(InconsistentInputError(message));
      }
      // describes the same simulation part. If those don't match, then
      // the simulation cannot proceed.
      auto simulationParts = gatherIntFromMultiSimulation(ms, headerContents->simulation_part);
 -    bool identicalSimulationParts = (std::adjacent_find(std::begin(simulationParts),
 -                                                        std::end(simulationParts), std::not_equal_to<>())
 -                                     == std::end(simulationParts));
 +    bool identicalSimulationParts =
 +            (std::adjacent_find(
 +                     std::begin(simulationParts), std::end(simulationParts), std::not_equal_to<>())
 +             == std::end(simulationParts));
  
      if (!identicalSimulationParts)
      {
index 8c8769e4a4b53760d71c549f00076d6318605370,2fa20c51293e5f92d087735018674690141f4f2b..cd2bb66391927bd78f05bc3870929512176fd802
@@@ -1,7 -1,7 +1,7 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -54,6 -54,7 +54,7 @@@ namespace gm
  {
  class StopHandler;
  class TrajectoryElement;
+ enum class StartingBehavior;
  
  /*! \internal
   * \ingroup module_modularsimulator
@@@ -228,12 -229,15 +229,15 @@@ public
  private:
      /*! \brief Constructor
       *
-      * \param callbacks  A vector of pointers to callbacks
-      * \param nstlog     The logging frequency
-      * \param initStep   The first step of the simulation
-      * \param initTime   The start time of the simulation
+      * \param callbacks         A vector of pointers to callbacks
+      * \param nstlog            The logging frequency
+      * \param initStep          The first step of the simulation
+      * \param startingBehavior  Whether this is a new simulation or restarting from checkpoint
       */
-     LoggingSignaller(std::vector<SignallerCallback> callbacks, Step nstlog, Step initStep, Time initTime);
+     LoggingSignaller(std::vector<SignallerCallback> callbacks,
+                      Step                           nstlog,
+                      Step                           initStep,
+                      StartingBehavior               startingBehavior);
  
      //! Client callbacks
      std::vector<SignallerCallback> callbacks_;
      const Step nstlog_;
      //! The initial step of the simulation
      const Step initStep_;
-     //! The initial time of the simulation
-     const Time initTime_;
+     //! How we are starting the simulation
+     const StartingBehavior startingBehavior_;
  
      //! ILastStepSignallerClient implementation
      std::optional<SignallerCallback> registerLastStepCallback() override;
@@@ -464,10 -468,9 +468,10 @@@ std::unique_ptr<EnergySignaller> Signal
      auto calculateFreeEnergyCallbacks =
              buildCallbackVector(EnergySignallerEvent::FreeEnergyCalculationStep);
      // NOLINTNEXTLINE(modernize-make-unique): make_unique does not work with private constructor
 -    return std::unique_ptr<EnergySignaller>(new EnergySignaller(
 -            std::move(calculateEnergyCallbacks), std::move(calculateVirialCallbacks),
 -            std::move(calculateFreeEnergyCallbacks), std::forward<Args>(args)...));
 +    return std::unique_ptr<EnergySignaller>(new EnergySignaller(std::move(calculateEnergyCallbacks),
 +                                                                std::move(calculateVirialCallbacks),
 +                                                                std::move(calculateFreeEnergyCallbacks),
 +                                                                std::forward<Args>(args)...));
  }
  
  //! Helper function to get the callbacks from the clients
index fc2de3a609c4f5f9cc08339cd7d97213cc5a9d69,9b90fd16c916c228bf47df2c8d41110e505c2f9b..d1e8cb5207cec9ab795767fc4c0be87d81ac81ee
@@@ -92,7 -92,7 +92,7 @@@ ModularSimulatorAlgorithm::ModularSimul
                                                       t_commrec*               cr,
                                                       const MDLogger&          mdlog,
                                                       const MdrunOptions&      mdrunOptions,
 -                                                     t_inputrec*              inputrec,
 +                                                     const t_inputrec*        inputrec,
                                                       t_nrnb*                  nrnb,
                                                       gmx_wallcycle*           wcycle,
                                                       t_forcerec*              fr,
@@@ -204,8 -204,8 +204,8 @@@ void ModularSimulatorAlgorithm::simulat
          fprintf(stderr, "starting mdrun '%s'\n", topologyName_.c_str());
          if (inputrec->nsteps >= 0)
          {
 -            timeString = formatString("%8.1f", static_cast<double>(inputrec->init_step + inputrec->nsteps)
 -                                                       * inputrec->delta_t);
 +            timeString = formatString(
 +                    "%8.1f", static_cast<double>(inputrec->init_step + inputrec->nsteps) * inputrec->delta_t);
          }
          else
          {
          }
          if (inputrec->init_step > 0)
          {
 -            fprintf(stderr, "%s steps, %s ps (continuing from step %s, %8.1f ps).\n",
 -                    gmx_step_str(inputrec->init_step + inputrec->nsteps, sbuf), timeString.c_str(),
 -                    gmx_step_str(inputrec->init_step, sbuf2), inputrec->init_step * inputrec->delta_t);
 +            fprintf(stderr,
 +                    "%s steps, %s ps (continuing from step %s, %8.1f ps).\n",
 +                    gmx_step_str(inputrec->init_step + inputrec->nsteps, sbuf),
 +                    timeString.c_str(),
 +                    gmx_step_str(inputrec->init_step, sbuf2),
 +                    inputrec->init_step * inputrec->delta_t);
          }
          else
          {
 -            fprintf(stderr, "%s steps, %s ps.\n", gmx_step_str(inputrec->nsteps, sbuf),
 -                    timeString.c_str());
 +            fprintf(stderr, "%s steps, %s ps.\n", gmx_step_str(inputrec->nsteps, sbuf), timeString.c_str());
          }
          fprintf(fplog, "\n");
      }
@@@ -307,17 -305,10 +307,17 @@@ void ModularSimulatorAlgorithm::postSte
          dd_cycles_add(cr->dd, static_cast<float>(cycles), ddCyclStep);
      }
  
 -    resetHandler_->resetCounters(
 -            step, step - inputrec->init_step, mdlog, fplog, cr, fr->nbv.get(), nrnb, fr->pmedata,
 -            pmeLoadBalanceHelper_ ? pmeLoadBalanceHelper_->loadBalancingObject() : nullptr, wcycle,
 -            walltime_accounting);
 +    resetHandler_->resetCounters(step,
 +                                 step - inputrec->init_step,
 +                                 mdlog,
 +                                 fplog,
 +                                 cr,
 +                                 fr->nbv.get(),
 +                                 nrnb,
 +                                 fr->pmedata,
 +                                 pmeLoadBalanceHelper_ ? pmeLoadBalanceHelper_->loadBalancingObject() : nullptr,
 +                                 wcycle,
 +                                 walltime_accounting);
  }
  
  void ModularSimulatorAlgorithm::populateTaskQueue()
@@@ -411,36 -402,22 +411,36 @@@ ModularSimulatorAlgorithmBuilder::Modul
      }
  
      statePropagatorData_ = std::make_unique<StatePropagatorData>(
 -            legacySimulatorData->top_global->natoms, legacySimulatorData->fplog, legacySimulatorData->cr,
 -            legacySimulatorData->state_global, legacySimulatorData->fr->nbv->useGpu(),
 -            legacySimulatorData->fr->bMolPBC, legacySimulatorData->mdrunOptions.writeConfout,
 -            opt2fn("-c", legacySimulatorData->nfile, legacySimulatorData->fnm), legacySimulatorData->inputrec,
 -            legacySimulatorData->mdAtoms->mdatoms(), legacySimulatorData->top_global);
 +            legacySimulatorData->top_global->natoms,
 +            legacySimulatorData->fplog,
 +            legacySimulatorData->cr,
 +            legacySimulatorData->state_global,
 +            legacySimulatorData->fr->nbv->useGpu(),
 +            legacySimulatorData->fr->bMolPBC,
 +            legacySimulatorData->mdrunOptions.writeConfout,
 +            opt2fn("-c", legacySimulatorData->nfile, legacySimulatorData->fnm),
 +            legacySimulatorData->inputrec,
 +            legacySimulatorData->mdAtoms->mdatoms(),
 +            legacySimulatorData->top_global);
  
      // Multi sim is turned off
      const bool simulationsShareState = false;
  
 -    energyData_ = std::make_unique<EnergyData>(
 -            statePropagatorData_.get(), freeEnergyPerturbationData_.get(), legacySimulatorData->top_global,
 -            legacySimulatorData->inputrec, legacySimulatorData->mdAtoms, legacySimulatorData->enerd,
 -            legacySimulatorData->ekind, legacySimulatorData->constr, legacySimulatorData->fplog,
 -            legacySimulatorData->fr->fcdata.get(), legacySimulatorData->mdModulesNotifier,
 -            MASTER(legacySimulatorData->cr), legacySimulatorData->observablesHistory,
 -            legacySimulatorData->startingBehavior, simulationsShareState);
 +    energyData_ = std::make_unique<EnergyData>(statePropagatorData_.get(),
 +                                               freeEnergyPerturbationData_.get(),
 +                                               legacySimulatorData->top_global,
 +                                               legacySimulatorData->inputrec,
 +                                               legacySimulatorData->mdAtoms,
 +                                               legacySimulatorData->enerd,
 +                                               legacySimulatorData->ekind,
 +                                               legacySimulatorData->constr,
 +                                               legacySimulatorData->fplog,
 +                                               legacySimulatorData->fr->fcdata.get(),
 +                                               legacySimulatorData->mdModulesNotifier,
 +                                               MASTER(legacySimulatorData->cr),
 +                                               legacySimulatorData->observablesHistory,
 +                                               legacySimulatorData->startingBehavior,
 +                                               simulationsShareState);
  }
  
  ModularSimulatorAlgorithm ModularSimulatorAlgorithmBuilder::build()
          }
      }
  
 -    ModularSimulatorAlgorithm algorithm(
 -            *(legacySimulatorData_->top_global->name), legacySimulatorData_->fplog,
 -            legacySimulatorData_->cr, legacySimulatorData_->mdlog, legacySimulatorData_->mdrunOptions,
 -            legacySimulatorData_->inputrec, legacySimulatorData_->nrnb, legacySimulatorData_->wcycle,
 -            legacySimulatorData_->fr, legacySimulatorData_->walltime_accounting);
 +    ModularSimulatorAlgorithm algorithm(*(legacySimulatorData_->top_global->name),
 +                                        legacySimulatorData_->fplog,
 +                                        legacySimulatorData_->cr,
 +                                        legacySimulatorData_->mdlog,
 +                                        legacySimulatorData_->mdrunOptions,
 +                                        legacySimulatorData_->inputrec,
 +                                        legacySimulatorData_->nrnb,
 +                                        legacySimulatorData_->wcycle,
 +                                        legacySimulatorData_->fr,
 +                                        legacySimulatorData_->walltime_accounting);
      registerWithInfrastructureAndSignallers(algorithm.signalHelper_.get());
      algorithm.statePropagatorData_        = std::move(statePropagatorData_);
      algorithm.energyData_                 = std::move(energyData_);
      algorithm.stopHandler_ = legacySimulatorData_->stopHandlerBuilder->getStopHandlerMD(
              compat::not_null<SimulationSignal*>(
                      &(*globalCommunicationHelper_.simulationSignals())[eglsSTOPCOND]),
 -            simulationsShareState, MASTER(legacySimulatorData_->cr),
 -            legacySimulatorData_->inputrec->nstlist, legacySimulatorData_->mdrunOptions.reproducible,
 -            globalCommunicationHelper_.nstglobalcomm(), legacySimulatorData_->mdrunOptions.maximumHoursToRun,
 -            legacySimulatorData_->inputrec->nstlist == 0, legacySimulatorData_->fplog,
 -            algorithm.stophandlerCurrentStep_, algorithm.stophandlerIsNSStep_,
 +            simulationsShareState,
 +            MASTER(legacySimulatorData_->cr),
 +            legacySimulatorData_->inputrec->nstlist,
 +            legacySimulatorData_->mdrunOptions.reproducible,
 +            globalCommunicationHelper_.nstglobalcomm(),
 +            legacySimulatorData_->mdrunOptions.maximumHoursToRun,
 +            legacySimulatorData_->inputrec->nstlist == 0,
 +            legacySimulatorData_->fplog,
 +            algorithm.stophandlerCurrentStep_,
 +            algorithm.stophandlerIsNSStep_,
              legacySimulatorData_->walltime_accounting);
  
      // Build reset handler
      algorithm.resetHandler_                  = std::make_unique<ResetHandler>(
              compat::make_not_null<SimulationSignal*>(
                      &(*globalCommunicationHelper_.simulationSignals())[eglsRESETCOUNTERS]),
 -            simulationsShareResetCounters, legacySimulatorData_->inputrec->nsteps,
 -            MASTER(legacySimulatorData_->cr), legacySimulatorData_->mdrunOptions.timingOptions.resetHalfway,
 -            legacySimulatorData_->mdrunOptions.maximumHoursToRun, legacySimulatorData_->mdlog,
 -            legacySimulatorData_->wcycle, legacySimulatorData_->walltime_accounting);
 +            simulationsShareResetCounters,
 +            legacySimulatorData_->inputrec->nsteps,
 +            MASTER(legacySimulatorData_->cr),
 +            legacySimulatorData_->mdrunOptions.timingOptions.resetHalfway,
 +            legacySimulatorData_->mdrunOptions.maximumHoursToRun,
 +            legacySimulatorData_->mdlog,
 +            legacySimulatorData_->wcycle,
 +            legacySimulatorData_->walltime_accounting);
  
      // Build topology holder
 -    algorithm.topologyHolder_ = topologyHolderBuilder_.build(
 -            *legacySimulatorData_->top_global, legacySimulatorData_->cr,
 -            legacySimulatorData_->inputrec, legacySimulatorData_->fr, legacySimulatorData_->mdAtoms,
 -            legacySimulatorData_->constr, legacySimulatorData_->vsite);
 +    algorithm.topologyHolder_ = topologyHolderBuilder_.build(*legacySimulatorData_->top_global,
 +                                                             legacySimulatorData_->cr,
 +                                                             legacySimulatorData_->inputrec,
 +                                                             legacySimulatorData_->fr,
 +                                                             legacySimulatorData_->mdAtoms,
 +                                                             legacySimulatorData_->constr,
 +                                                             legacySimulatorData_->vsite);
  
      // Build PME load balance helper
      if (PmeLoadBalanceHelper::doPmeLoadBalancing(legacySimulatorData_->mdrunOptions,
                                                   legacySimulatorData_->inputrec,
                                                   legacySimulatorData_->fr))
      {
 -        algorithm.pmeLoadBalanceHelper_ = std::make_unique<PmeLoadBalanceHelper>(
 -                legacySimulatorData_->mdrunOptions.verbose, algorithm.statePropagatorData_.get(),
 -                legacySimulatorData_->fplog, legacySimulatorData_->cr, legacySimulatorData_->mdlog,
 -                legacySimulatorData_->inputrec, legacySimulatorData_->wcycle, legacySimulatorData_->fr);
 +        algorithm.pmeLoadBalanceHelper_ =
 +                std::make_unique<PmeLoadBalanceHelper>(legacySimulatorData_->mdrunOptions.verbose,
 +                                                       algorithm.statePropagatorData_.get(),
 +                                                       legacySimulatorData_->fplog,
 +                                                       legacySimulatorData_->cr,
 +                                                       legacySimulatorData_->mdlog,
 +                                                       legacySimulatorData_->inputrec,
 +                                                       legacySimulatorData_->wcycle,
 +                                                       legacySimulatorData_->fr);
          registerWithInfrastructureAndSignallers(algorithm.pmeLoadBalanceHelper_.get());
      }
      // Build domdec helper
          algorithm.domDecHelper_ = std::make_unique<DomDecHelper>(
                  legacySimulatorData_->mdrunOptions.verbose,
                  legacySimulatorData_->mdrunOptions.verboseStepPrintInterval,
 -                algorithm.statePropagatorData_.get(), algorithm.freeEnergyPerturbationData_.get(),
 +                algorithm.statePropagatorData_.get(),
 +                algorithm.freeEnergyPerturbationData_.get(),
                  algorithm.topologyHolder_.get(),
                  globalCommunicationHelper_.moveCheckBondedInteractionsCallback(),
 -                globalCommunicationHelper_.nstglobalcomm(), legacySimulatorData_->fplog,
 -                legacySimulatorData_->cr, legacySimulatorData_->mdlog, legacySimulatorData_->constr,
 -                legacySimulatorData_->inputrec, legacySimulatorData_->mdAtoms, legacySimulatorData_->nrnb,
 -                legacySimulatorData_->wcycle, legacySimulatorData_->fr, legacySimulatorData_->vsite,
 -                legacySimulatorData_->imdSession, legacySimulatorData_->pull_work);
 +                globalCommunicationHelper_.nstglobalcomm(),
 +                legacySimulatorData_->fplog,
 +                legacySimulatorData_->cr,
 +                legacySimulatorData_->mdlog,
 +                legacySimulatorData_->constr,
 +                legacySimulatorData_->inputrec,
 +                legacySimulatorData_->mdAtoms,
 +                legacySimulatorData_->nrnb,
 +                legacySimulatorData_->wcycle,
 +                legacySimulatorData_->fr,
 +                legacySimulatorData_->vsite,
 +                legacySimulatorData_->imdSession,
 +                legacySimulatorData_->pull_work);
          registerWithInfrastructureAndSignallers(algorithm.domDecHelper_.get());
      }
  
      // Build trajectory element
 -    auto trajectoryElement = trajectoryElementBuilder_.build(
 -            legacySimulatorData_->fplog, legacySimulatorData_->nfile, legacySimulatorData_->fnm,
 -            legacySimulatorData_->mdrunOptions, legacySimulatorData_->cr,
 -            legacySimulatorData_->outputProvider, legacySimulatorData_->mdModulesNotifier,
 -            legacySimulatorData_->inputrec, legacySimulatorData_->top_global,
 -            legacySimulatorData_->oenv, legacySimulatorData_->wcycle,
 -            legacySimulatorData_->startingBehavior, simulationsShareState);
 +    auto trajectoryElement = trajectoryElementBuilder_.build(legacySimulatorData_->fplog,
 +                                                             legacySimulatorData_->nfile,
 +                                                             legacySimulatorData_->fnm,
 +                                                             legacySimulatorData_->mdrunOptions,
 +                                                             legacySimulatorData_->cr,
 +                                                             legacySimulatorData_->outputProvider,
 +                                                             legacySimulatorData_->mdModulesNotifier,
 +                                                             legacySimulatorData_->inputrec,
 +                                                             legacySimulatorData_->top_global,
 +                                                             legacySimulatorData_->oenv,
 +                                                             legacySimulatorData_->wcycle,
 +                                                             legacySimulatorData_->startingBehavior,
 +                                                             simulationsShareState);
      registerWithInfrastructureAndSignallers(trajectoryElement.get());
  
      // Build free energy element
      {
          checkpointHelperBuilder_.setCheckpointHandler(std::make_unique<CheckpointHandler>(
                  compat::make_not_null<SimulationSignal*>(&(*algorithm.signals_)[eglsCHKPT]),
 -                simulationsShareState, legacySimulatorData_->inputrec->nstlist == 0,
 -                MASTER(legacySimulatorData_->cr), legacySimulatorData_->mdrunOptions.writeConfout,
 +                simulationsShareState,
 +                legacySimulatorData_->inputrec->nstlist == 0,
 +                MASTER(legacySimulatorData_->cr),
 +                legacySimulatorData_->mdrunOptions.writeConfout,
                  legacySimulatorData_->mdrunOptions.checkpointOptions.period));
 -        algorithm.checkpointHelper_ = checkpointHelperBuilder_.build(
 -                legacySimulatorData_->inputrec->init_step, trajectoryElement.get(),
 -                legacySimulatorData_->fplog, legacySimulatorData_->cr,
 -                legacySimulatorData_->observablesHistory, legacySimulatorData_->walltime_accounting,
 -                legacySimulatorData_->state_global, legacySimulatorData_->mdrunOptions.writeConfout);
 +        algorithm.checkpointHelper_ =
 +                checkpointHelperBuilder_.build(legacySimulatorData_->inputrec->init_step,
 +                                               trajectoryElement.get(),
 +                                               legacySimulatorData_->fplog,
 +                                               legacySimulatorData_->cr,
 +                                               legacySimulatorData_->observablesHistory,
 +                                               legacySimulatorData_->walltime_accounting,
 +                                               legacySimulatorData_->state_global,
 +                                               legacySimulatorData_->mdrunOptions.writeConfout);
          registerWithInfrastructureAndSignallers(algorithm.checkpointHelper_.get());
      }
  
          const auto* inputrec = legacySimulatorData_->inputrec;
          addSignaller(energySignallerBuilder_.build(
                  inputrec->nstcalcenergy, inputrec->fepvals->nstdhdl, inputrec->nstpcouple));
 -        addSignaller(trajectorySignallerBuilder_.build(
 -                inputrec->nstxout, inputrec->nstvout, inputrec->nstfout,
 -                inputrec->nstxout_compressed, trajectoryElement->tngBoxOut(),
 -                trajectoryElement->tngLambdaOut(), trajectoryElement->tngBoxOutCompressed(),
 -                trajectoryElement->tngLambdaOutCompressed(), inputrec->nstenergy));
 -        addSignaller(loggingSignallerBuilder_.build(inputrec->nstlog, inputrec->init_step,
 -                                                    legacySimulatorData_->startingBehavior));
 -        addSignaller(lastStepSignallerBuilder_.build(inputrec->nsteps, inputrec->init_step,
 -                                                     algorithm.stopHandler_.get()));
 -        addSignaller(neighborSearchSignallerBuilder_.build(inputrec->nstlist, inputrec->init_step,
 -                                                           inputrec->init_t));
 +        addSignaller(trajectorySignallerBuilder_.build(inputrec->nstxout,
 +                                                       inputrec->nstvout,
 +                                                       inputrec->nstfout,
 +                                                       inputrec->nstxout_compressed,
 +                                                       trajectoryElement->tngBoxOut(),
 +                                                       trajectoryElement->tngLambdaOut(),
 +                                                       trajectoryElement->tngBoxOutCompressed(),
 +                                                       trajectoryElement->tngLambdaOutCompressed(),
 +                                                       inputrec->nstenergy));
-         addSignaller(loggingSignallerBuilder_.build(inputrec->nstlog, inputrec->init_step, inputrec->init_t));
++        addSignaller(loggingSignallerBuilder_.build(
++                inputrec->nstlog, inputrec->init_step, legacySimulatorData_->startingBehavior));
 +        addSignaller(lastStepSignallerBuilder_.build(
 +                inputrec->nsteps, inputrec->init_step, algorithm.stopHandler_.get()));
 +        addSignaller(neighborSearchSignallerBuilder_.build(
 +                inputrec->nstlist, inputrec->init_step, inputrec->init_t));
      }
  
      // Create element list
@@@ -690,9 -621,8 +691,9 @@@ ISimulatorElement* ModularSimulatorAlgo
  bool ModularSimulatorAlgorithmBuilder::elementExists(const ISimulatorElement* element) const
  {
      // Check whether element exists in element list
 -    if (std::any_of(elements_.begin(), elements_.end(),
 -                    [element](auto& existingElement) { return element == existingElement.get(); }))
 +    if (std::any_of(elements_.begin(), elements_.end(), [element](auto& existingElement) {
 +            return element == existingElement.get();
 +        }))
      {
          return true;
      }
index 584c84dc835dac0136070363bec5782a412ad9da,bedabd85c8bb7eef62d8596c6ccd25c6a526fca7..5fa2e00eec6066ff9b1eb86ab5ec53b5498fdc1d
@@@ -2,7 -2,7 +2,7 @@@
   * This file is part of the GROMACS molecular simulation package.
   *
   * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
-  * Copyright (c) 2017,2018,2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -124,7 -124,9 +124,7 @@@ static void init_nbparam(NBParamGpu
                           const nbnxn_atomdata_t::Params& nbatParams,
                           const DeviceContext&            deviceContext)
  {
 -    int ntypes;
 -
 -    ntypes = nbatParams.numTypes;
 +    const int ntypes = nbatParams.numTypes;
  
      set_cutoff_parameters(nbp, ic, listParams);
  
       * combination is rarely used. LJ force-switch with LB rule is more common,
       * but gives only 1% speed-up.
       */
 -    if (ic->vdwtype == evdwCUT)
 -    {
 -        switch (ic->vdw_modifier)
 -        {
 -            case eintmodNONE:
 -            case eintmodPOTSHIFT:
 -                switch (nbatParams.comb_rule)
 -                {
 -                    case ljcrNONE: nbp->vdwtype = evdwTypeCUT; break;
 -                    case ljcrGEOM: nbp->vdwtype = evdwTypeCUTCOMBGEOM; break;
 -                    case ljcrLB: nbp->vdwtype = evdwTypeCUTCOMBLB; break;
 -                    default:
 -                        gmx_incons(
 -                                "The requested LJ combination rule is not implemented in the CUDA "
 -                                "GPU accelerated kernels!");
 -                }
 -                break;
 -            case eintmodFORCESWITCH: nbp->vdwtype = evdwTypeFSWITCH; break;
 -            case eintmodPOTSWITCH: nbp->vdwtype = evdwTypePSWITCH; break;
 -            default:
 -                gmx_incons(
 -                        "The requested VdW interaction modifier is not implemented in the CUDA GPU "
 -                        "accelerated kernels!");
 -        }
 -    }
 -    else if (ic->vdwtype == evdwPME)
 -    {
 -        if (ic->ljpme_comb_rule == ljcrGEOM)
 -        {
 -            assert(nbatParams.comb_rule == ljcrGEOM);
 -            nbp->vdwtype = evdwTypeEWALDGEOM;
 -        }
 -        else
 -        {
 -            assert(nbatParams.comb_rule == ljcrLB);
 -            nbp->vdwtype = evdwTypeEWALDLB;
 -        }
 -    }
 -    else
 -    {
 -        gmx_incons(
 -                "The requested VdW type is not implemented in the CUDA GPU accelerated kernels!");
 -    }
 -
 -    if (ic->eeltype == eelCUT)
 -    {
 -        nbp->eeltype = eelTypeCUT;
 -    }
 -    else if (EEL_RF(ic->eeltype))
 -    {
 -        nbp->eeltype = eelTypeRF;
 -    }
 -    else if ((EEL_PME(ic->eeltype) || ic->eeltype == eelEWALD))
 -    {
 -        nbp->eeltype = nbnxn_gpu_pick_ewald_kernel_type(*ic, deviceContext.deviceInfo());
 -    }
 -    else
 -    {
 -        /* Shouldn't happen, as this is checked when choosing Verlet-scheme */
 -        gmx_incons(
 -                "The requested electrostatics type is not implemented in the CUDA GPU accelerated "
 -                "kernels!");
 -    }
 +    nbp->vdwType  = nbnxmGpuPickVdwKernelType(ic, nbatParams.comb_rule);
-     nbp->elecType = nbnxmGpuPickElectrostaticsKernelType(ic);
++    nbp->elecType = nbnxmGpuPickElectrostaticsKernelType(ic, deviceContext.deviceInfo());
  
      /* generate table for PME */
      nbp->coulomb_tab = nullptr;
 -    if (nbp->eeltype == eelTypeEWALD_TAB || nbp->eeltype == eelTypeEWALD_TAB_TWIN)
 +    if (nbp->elecType == ElecType::EwaldTab || nbp->elecType == ElecType::EwaldTabTwin)
      {
          GMX_RELEASE_ASSERT(ic->coulombEwaldTables, "Need valid Coulomb Ewald correction tables");
          init_ewald_coulomb_force_table(*ic->coulombEwaldTables, nbp, deviceContext);
      }
  
      /* set up LJ parameter lookup table */
 -    if (!useLjCombRule(nbp->vdwtype))
 +    if (!useLjCombRule(nbp->vdwType))
      {
 -        initParamLookupTable(&nbp->nbfp, &nbp->nbfp_texobj, nbatParams.nbfp.data(),
 -                             2 * ntypes * ntypes, deviceContext);
 +        initParamLookupTable(
 +                &nbp->nbfp, &nbp->nbfp_texobj, nbatParams.nbfp.data(), 2 * ntypes * ntypes, deviceContext);
      }
  
      /* set up LJ-PME parameter lookup table */
      if (ic->vdwtype == evdwPME)
      {
 -        initParamLookupTable(&nbp->nbfp_comb, &nbp->nbfp_comb_texobj, nbatParams.nbfp_comb.data(),
 -                             2 * ntypes, deviceContext);
 +        initParamLookupTable(
 +                &nbp->nbfp_comb, &nbp->nbfp_comb_texobj, nbatParams.nbfp_comb.data(), 2 * ntypes, deviceContext);
      }
  }
  
@@@ -277,13 -340,8 +277,13 @@@ void gpu_upload_shiftvec(NbnxmGpu* nb, 
      {
          static_assert(sizeof(adat->shift_vec[0]) == sizeof(nbatom->shift_vec[0]),
                        "Sizes of host- and device-side shift vectors should be the same.");
 -        copyToDeviceBuffer(&adat->shift_vec, reinterpret_cast<const float3*>(nbatom->shift_vec.data()),
 -                           0, SHIFTS, localStream, GpuApiCallBehavior::Async, nullptr);
 +        copyToDeviceBuffer(&adat->shift_vec,
 +                           reinterpret_cast<const float3*>(nbatom->shift_vec.data()),
 +                           0,
 +                           SHIFTS,
 +                           localStream,
 +                           GpuApiCallBehavior::Async,
 +                           nullptr);
          adat->bShiftVecUploaded = true;
      }
  }
@@@ -354,7 -412,7 +354,7 @@@ void gpu_init_atomdata(NbnxmGpu* nb, co
  
          allocateDeviceBuffer(&d_atdat->f, nalloc, deviceContext);
          allocateDeviceBuffer(&d_atdat->xq, nalloc, deviceContext);
 -        if (useLjCombRule(nb->nbparam->vdwtype))
 +        if (useLjCombRule(nb->nbparam->vdwType))
          {
              allocateDeviceBuffer(&d_atdat->lj_comb, nalloc, deviceContext);
          }
          nbnxn_cuda_clear_f(nb, nalloc);
      }
  
 -    if (useLjCombRule(nb->nbparam->vdwtype))
 +    if (useLjCombRule(nb->nbparam->vdwType))
      {
          static_assert(sizeof(d_atdat->lj_comb[0]) == sizeof(float2),
                        "Size of the LJ parameters element should be equal to the size of float2.");
          copyToDeviceBuffer(&d_atdat->lj_comb,
 -                           reinterpret_cast<const float2*>(nbat->params().lj_comb.data()), 0,
 -                           natoms, localStream, GpuApiCallBehavior::Async, nullptr);
 +                           reinterpret_cast<const float2*>(nbat->params().lj_comb.data()),
 +                           0,
 +                           natoms,
 +                           localStream,
 +                           GpuApiCallBehavior::Async,
 +                           nullptr);
      }
      else
      {
          static_assert(sizeof(d_atdat->atom_types[0]) == sizeof(nbat->params().type[0]),
                        "Sizes of host- and device-side atom types should be the same.");
 -        copyToDeviceBuffer(&d_atdat->atom_types, nbat->params().type.data(), 0, natoms, localStream,
 -                           GpuApiCallBehavior::Async, nullptr);
 +        copyToDeviceBuffer(&d_atdat->atom_types,
 +                           nbat->params().type.data(),
 +                           0,
 +                           natoms,
 +                           localStream,
 +                           GpuApiCallBehavior::Async,
 +                           nullptr);
      }
  
      if (bDoTime)
@@@ -422,7 -471,7 +422,7 @@@ void gpu_free(NbnxmGpu* nb
      nbparam = nb->nbparam;
  
      if ((!nbparam->coulomb_tab)
 -        && (nbparam->eeltype == eelTypeEWALD_TAB || nbparam->eeltype == eelTypeEWALD_TAB_TWIN))
 +        && (nbparam->elecType == ElecType::EwaldTab || nbparam->elecType == ElecType::EwaldTabTwin))
      {
          destroyParamLookupTable(&nbparam->coulomb_tab, nbparam->coulomb_tab_texobj);
      }
  
      delete nb->timers;
  
 -    if (!useLjCombRule(nb->nbparam->vdwtype))
 +    if (!useLjCombRule(nb->nbparam->vdwType))
      {
          destroyParamLookupTable(&nbparam->nbfp, nbparam->nbfp_texobj);
      }
  
 -    if (nbparam->vdwtype == evdwTypeEWALDGEOM || nbparam->vdwtype == evdwTypeEWALDLB)
 +    if (nbparam->vdwType == VdwType::EwaldGeom || nbparam->vdwType == VdwType::EwaldLB)
      {
          destroyParamLookupTable(&nbparam->nbfp_comb, nbparam->nbfp_comb_texobj);
      }
@@@ -528,16 -577,10 +528,16 @@@ void nbnxn_gpu_init_x_to_nbat_x(const N
      bool                bDoTime       = gpu_nbv->bDoTime;
      const int           maxNumColumns = gridSet.numColumnsMax();
  
 -    reallocateDeviceBuffer(&gpu_nbv->cxy_na, maxNumColumns * gridSet.grids().size(),
 -                           &gpu_nbv->ncxy_na, &gpu_nbv->ncxy_na_alloc, *gpu_nbv->deviceContext_);
 -    reallocateDeviceBuffer(&gpu_nbv->cxy_ind, maxNumColumns * gridSet.grids().size(),
 -                           &gpu_nbv->ncxy_ind, &gpu_nbv->ncxy_ind_alloc, *gpu_nbv->deviceContext_);
 +    reallocateDeviceBuffer(&gpu_nbv->cxy_na,
 +                           maxNumColumns * gridSet.grids().size(),
 +                           &gpu_nbv->ncxy_na,
 +                           &gpu_nbv->ncxy_na_alloc,
 +                           *gpu_nbv->deviceContext_);
 +    reallocateDeviceBuffer(&gpu_nbv->cxy_ind,
 +                           maxNumColumns * gridSet.grids().size(),
 +                           &gpu_nbv->ncxy_ind,
 +                           &gpu_nbv->ncxy_ind_alloc,
 +                           *gpu_nbv->deviceContext_);
  
      for (unsigned int g = 0; g < gridSet.grids().size(); g++)
      {
          const int* cxy_na          = grid.cxy_na().data();
          const int* cxy_ind         = grid.cxy_ind().data();
  
 -        reallocateDeviceBuffer(&gpu_nbv->atomIndices, atomIndicesSize, &gpu_nbv->atomIndicesSize,
 -                               &gpu_nbv->atomIndicesSize_alloc, *gpu_nbv->deviceContext_);
 +        reallocateDeviceBuffer(&gpu_nbv->atomIndices,
 +                               atomIndicesSize,
 +                               &gpu_nbv->atomIndicesSize,
 +                               &gpu_nbv->atomIndicesSize_alloc,
 +                               *gpu_nbv->deviceContext_);
  
          if (atomIndicesSize > 0)
          {
                  gpu_nbv->timers->xf[AtomLocality::Local].nb_h2d.openTimingRegion(deviceStream);
              }
  
 -            copyToDeviceBuffer(&gpu_nbv->atomIndices, atomIndices, 0, atomIndicesSize, deviceStream,
 -                               GpuApiCallBehavior::Async, nullptr);
 +            copyToDeviceBuffer(&gpu_nbv->atomIndices,
 +                               atomIndices,
 +                               0,
 +                               atomIndicesSize,
 +                               deviceStream,
 +                               GpuApiCallBehavior::Async,
 +                               nullptr);
  
              if (bDoTime)
              {
              }
  
              int* destPtr = &gpu_nbv->cxy_na[maxNumColumns * g];
 -            copyToDeviceBuffer(&destPtr, cxy_na, 0, numColumns, deviceStream,
 -                               GpuApiCallBehavior::Async, nullptr);
 +            copyToDeviceBuffer(
 +                    &destPtr, cxy_na, 0, numColumns, deviceStream, GpuApiCallBehavior::Async, nullptr);
  
              if (bDoTime)
              {
              }
  
              destPtr = &gpu_nbv->cxy_ind[maxNumColumns * g];
 -            copyToDeviceBuffer(&destPtr, cxy_ind, 0, numColumns, deviceStream,
 -                               GpuApiCallBehavior::Async, nullptr);
 +            copyToDeviceBuffer(
 +                    &destPtr, cxy_ind, 0, numColumns, deviceStream, GpuApiCallBehavior::Async, nullptr);
  
              if (bDoTime)
              {
index 763482badd75de8702c62632fde035a5c74964ef,a472cb437dabaf08cc4b8603cdf692614000ca85..1bcc676879c2f66ca2b16e2bbb35e21a256ba2ac
@@@ -2,6 -2,6 +2,7 @@@
   * This file is part of the GROMACS molecular simulation package.
   *
   * Copyright (c) 2014,2015,2017,2018,2019,2020, by the GROMACS development team, led by
++ * 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.
@@@ -50,8 -50,6 +51,8 @@@
  #include "gromacs/gpu_utils/gpu_macros.h"
  #include "gromacs/mdtypes/locality.h"
  
 +#include "nbnxm.h"
 +
  struct NbnxmGpu;
  struct DeviceInformation;
  struct gmx_wallclock_gpu_nbnxn_t;
@@@ -126,16 -124,6 +127,17 @@@ int gpu_min_ci_balanced(NbnxmGpu gmx_un
  GPU_FUNC_QUALIFIER
  bool gpu_is_kernel_ewald_analytical(const NbnxmGpu gmx_unused* nb) GPU_FUNC_TERM_WITH_RETURN(FALSE);
  
- enum ElecType nbnxmGpuPickElectrostaticsKernelType(const interaction_const_t gmx_unused* ic)
 +/** Return the enum value of electrostatics kernel type for given interaction parameters \p ic. */
 +GPU_FUNC_QUALIFIER
++enum ElecType nbnxmGpuPickElectrostaticsKernelType(const interaction_const_t gmx_unused* ic,
++                                                   const DeviceInformation gmx_unused& deviceInfo)
 +        GPU_FUNC_TERM_WITH_RETURN(ElecType::Count);
 +
 +/** Return the enum value of VdW kernel type for given \p ic and \p combRule. */
 +GPU_FUNC_QUALIFIER
 +enum VdwType nbnxmGpuPickVdwKernelType(const interaction_const_t gmx_unused* ic, int gmx_unused combRule)
 +        GPU_FUNC_TERM_WITH_RETURN(VdwType::Count);
 +
  /** Returns an opaque pointer to the GPU command stream
   *  Note: CUDA only.
   */
index ad7c2c8017ea3dc28684d0762ba3ede1ca5e46ef,0af6e94e6189a623768d0eb334890e2c52834b67..ba869da29a8611ea7d6ad6e7203bbbea96d0467c
@@@ -2,7 -2,7 +2,7 @@@
   * This file is part of the GROMACS molecular simulation package.
   *
   * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
-  * Copyright (c) 2017,2018,2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -58,7 -58,7 +58,8 @@@
  
  #include "nbnxm_gpu_data_mgmt.h"
  
+ #include "gromacs/hardware/device_information.h"
 +#include "gromacs/mdtypes/interaction_const.h"
  #include "gromacs/nbnxm/gpu_data_mgmt.h"
  #include "gromacs/timing/gpu_timing.h"
  #include "gromacs/utility/cstringutil.h"
@@@ -79,8 -79,8 +80,8 @@@ void init_ewald_coulomb_force_table(con
      }
  
      nbp->coulomb_tab_scale = tables.scale;
 -    initParamLookupTable(&nbp->coulomb_tab, &nbp->coulomb_tab_texobj, tables.tableF.data(),
 -                         tables.tableF.size(), deviceContext);
 +    initParamLookupTable(
 +            &nbp->coulomb_tab, &nbp->coulomb_tab_texobj, tables.tableF.data(), tables.tableF.size(), deviceContext);
  }
  
  void inline printEnvironmentVariableDeprecationMessage(bool               isEnvironmentVariableSet,
          fprintf(stderr,
                  "Environment variables GMX_CUDA_%s and GMX_OCL_%s are deprecated and will be\n"
                  "removed in release 2022, please use GMX_GPU_%s instead.",
 -                environmentVariableSuffix.c_str(), environmentVariableSuffix.c_str(),
 +                environmentVariableSuffix.c_str(),
 +                environmentVariableSuffix.c_str(),
                  environmentVariableSuffix.c_str());
      }
  }
  
- enum ElecType nbnxn_gpu_pick_ewald_kernel_type(const interaction_const_t& ic)
 -int nbnxn_gpu_pick_ewald_kernel_type(const interaction_const_t& ic,
 -                                     const DeviceInformation gmx_unused& deviceInfo)
++enum ElecType nbnxn_gpu_pick_ewald_kernel_type(const interaction_const_t& ic,
++                                               const DeviceInformation gmx_unused& deviceInfo)
  {
      bool bTwinCut = (ic.rcoulomb != ic.rvdw);
 -    int  kernel_type;
  
      /* Benchmarking/development environment variables to force the use of
         analytical or tabulated Ewald kernel. */
                  "requested through environment variables.");
      }
  
-     /* By default, use analytical Ewald
-      * TODO: tabulated does not work in OpenCL, it needs fixing, see init_nbparam() in nbnxn_ocl_data_mgmt.cpp
-      *
+     /* By default, use analytical Ewald except with CUDA on NVIDIA CC 7.0 and 8.0.
       */
-     bool bUseAnalyticalEwald = true;
+     const bool c_useTabulatedEwaldDefault =
+ #if GMX_GPU_CUDA
+             (deviceInfo.prop.major == 7 && deviceInfo.prop.minor == 0)
+             || (deviceInfo.prop.major == 8 && deviceInfo.prop.minor == 0);
+ #else
+             false;
+ #endif
+     bool bUseAnalyticalEwald = !c_useTabulatedEwaldDefault;
      if (forceAnalyticalEwald)
      {
+         bUseAnalyticalEwald = true;
          if (debug)
          {
              fprintf(debug, "Using analytical Ewald GPU kernels\n");
         forces it (use it for debugging/benchmarking only). */
      if (!bTwinCut && !forceTwinCutoffEwald)
      {
 -        kernel_type = bUseAnalyticalEwald ? eelTypeEWALD_ANA : eelTypeEWALD_TAB;
 +        return bUseAnalyticalEwald ? ElecType::EwaldAna : ElecType::EwaldTab;
      }
      else
      {
 -        kernel_type = bUseAnalyticalEwald ? eelTypeEWALD_ANA_TWIN : eelTypeEWALD_TAB_TWIN;
 +        return bUseAnalyticalEwald ? ElecType::EwaldAnaTwin : ElecType::EwaldTabTwin;
      }
 -
 -    return kernel_type;
  }
  
  void set_cutoff_parameters(NBParamGpu* nbp, const interaction_const_t* ic, const PairlistParams& listParams)
@@@ -197,7 -206,7 +205,7 @@@ void gpu_pme_loadbal_update_param(cons
  
      set_cutoff_parameters(nbp, ic, nbv->pairlistSets().params());
  
-     nbp->elecType = nbnxn_gpu_pick_ewald_kernel_type(*ic);
 -    nbp->eeltype = nbnxn_gpu_pick_ewald_kernel_type(*ic, nb->deviceContext_->deviceInfo());
++    nbp->elecType = nbnxn_gpu_pick_ewald_kernel_type(*ic, nb->deviceContext_->deviceInfo());
  
      GMX_RELEASE_ASSERT(ic->coulombEwaldTables, "Need valid Coulomb Ewald correction tables");
      init_ewald_coulomb_force_table(*ic->coulombEwaldTables, nbp, *nb->deviceContext_);
@@@ -267,10 -276,8 +275,10 @@@ void gpu_init_pairlist(NbnxmGpu* nb, co
      {
          if (d_plist->na_c != h_plist->na_ci)
          {
 -            sprintf(sbuf, "In init_plist: the #atoms per cell has changed (from %d to %d)",
 -                    d_plist->na_c, h_plist->na_ci);
 +            sprintf(sbuf,
 +                    "In init_plist: the #atoms per cell has changed (from %d to %d)",
 +                    d_plist->na_c,
 +                    h_plist->na_ci);
              gmx_incons(sbuf);
          }
      }
      // TODO most of this function is same in CUDA and OpenCL, move into the header
      const DeviceContext& deviceContext = *nb->deviceContext_;
  
 -    reallocateDeviceBuffer(&d_plist->sci, h_plist->sci.size(), &d_plist->nsci, &d_plist->sci_nalloc,
 -                           deviceContext);
 -    copyToDeviceBuffer(&d_plist->sci, h_plist->sci.data(), 0, h_plist->sci.size(), deviceStream,
 -                       GpuApiCallBehavior::Async, bDoTime ? iTimers.pl_h2d.fetchNextEvent() : nullptr);
 -
 -    reallocateDeviceBuffer(&d_plist->cj4, h_plist->cj4.size(), &d_plist->ncj4, &d_plist->cj4_nalloc,
 +    reallocateDeviceBuffer(
 +            &d_plist->sci, h_plist->sci.size(), &d_plist->nsci, &d_plist->sci_nalloc, deviceContext);
 +    copyToDeviceBuffer(&d_plist->sci,
 +                       h_plist->sci.data(),
 +                       0,
 +                       h_plist->sci.size(),
 +                       deviceStream,
 +                       GpuApiCallBehavior::Async,
 +                       bDoTime ? iTimers.pl_h2d.fetchNextEvent() : nullptr);
 +
 +    reallocateDeviceBuffer(
 +            &d_plist->cj4, h_plist->cj4.size(), &d_plist->ncj4, &d_plist->cj4_nalloc, deviceContext);
 +    copyToDeviceBuffer(&d_plist->cj4,
 +                       h_plist->cj4.data(),
 +                       0,
 +                       h_plist->cj4.size(),
 +                       deviceStream,
 +                       GpuApiCallBehavior::Async,
 +                       bDoTime ? iTimers.pl_h2d.fetchNextEvent() : nullptr);
 +
 +    reallocateDeviceBuffer(&d_plist->imask,
 +                           h_plist->cj4.size() * c_nbnxnGpuClusterpairSplit,
 +                           &d_plist->nimask,
 +                           &d_plist->imask_nalloc,
                             deviceContext);
 -    copyToDeviceBuffer(&d_plist->cj4, h_plist->cj4.data(), 0, h_plist->cj4.size(), deviceStream,
 -                       GpuApiCallBehavior::Async, bDoTime ? iTimers.pl_h2d.fetchNextEvent() : nullptr);
  
 -    reallocateDeviceBuffer(&d_plist->imask, h_plist->cj4.size() * c_nbnxnGpuClusterpairSplit,
 -                           &d_plist->nimask, &d_plist->imask_nalloc, deviceContext);
 -
 -    reallocateDeviceBuffer(&d_plist->excl, h_plist->excl.size(), &d_plist->nexcl,
 -                           &d_plist->excl_nalloc, deviceContext);
 -    copyToDeviceBuffer(&d_plist->excl, h_plist->excl.data(), 0, h_plist->excl.size(), deviceStream,
 -                       GpuApiCallBehavior::Async, bDoTime ? iTimers.pl_h2d.fetchNextEvent() : nullptr);
 +    reallocateDeviceBuffer(
 +            &d_plist->excl, h_plist->excl.size(), &d_plist->nexcl, &d_plist->excl_nalloc, deviceContext);
 +    copyToDeviceBuffer(&d_plist->excl,
 +                       h_plist->excl.data(),
 +                       0,
 +                       h_plist->excl.size(),
 +                       deviceStream,
 +                       GpuApiCallBehavior::Async,
 +                       bDoTime ? iTimers.pl_h2d.fetchNextEvent() : nullptr);
  
      if (bDoTime)
      {
@@@ -348,86 -337,7 +356,87 @@@ void gpu_reset_timings(nonbonded_verlet
  
  bool gpu_is_kernel_ewald_analytical(const NbnxmGpu* nb)
  {
 -    return ((nb->nbparam->eeltype == eelTypeEWALD_ANA) || (nb->nbparam->eeltype == eelTypeEWALD_ANA_TWIN));
 +    return ((nb->nbparam->elecType == ElecType::EwaldAna)
 +            || (nb->nbparam->elecType == ElecType::EwaldAnaTwin));
 +}
 +
- enum ElecType nbnxmGpuPickElectrostaticsKernelType(const interaction_const_t* ic)
++enum ElecType nbnxmGpuPickElectrostaticsKernelType(const interaction_const_t* ic,
++                                                   const DeviceInformation&   deviceInfo)
 +{
 +    if (ic->eeltype == eelCUT)
 +    {
 +        return ElecType::Cut;
 +    }
 +    else if (EEL_RF(ic->eeltype))
 +    {
 +        return ElecType::RF;
 +    }
 +    else if ((EEL_PME(ic->eeltype) || ic->eeltype == eelEWALD))
 +    {
-         return nbnxn_gpu_pick_ewald_kernel_type(*ic);
++        return nbnxn_gpu_pick_ewald_kernel_type(*ic, deviceInfo);
 +    }
 +    else
 +    {
 +        /* Shouldn't happen, as this is checked when choosing Verlet-scheme */
 +        GMX_THROW(gmx::InconsistentInputError(
 +                gmx::formatString("The requested electrostatics type %s (%d) is not implemented in "
 +                                  "the GPU accelerated kernels!",
 +                                  EELTYPE(ic->eeltype),
 +                                  ic->eeltype)));
 +    }
 +}
 +
 +
 +enum VdwType nbnxmGpuPickVdwKernelType(const interaction_const_t* ic, int combRule)
 +{
 +    if (ic->vdwtype == evdwCUT)
 +    {
 +        switch (ic->vdw_modifier)
 +        {
 +            case eintmodNONE:
 +            case eintmodPOTSHIFT:
 +                switch (combRule)
 +                {
 +                    case ljcrNONE: return VdwType::Cut;
 +                    case ljcrGEOM: return VdwType::CutCombGeom;
 +                    case ljcrLB: return VdwType::CutCombLB;
 +                    default:
 +                        GMX_THROW(gmx::InconsistentInputError(gmx::formatString(
 +                                "The requested LJ combination rule %s (%d) is not implemented in "
 +                                "the GPU accelerated kernels!",
 +                                enum_name(combRule, ljcrNR, c_ljcrNames),
 +                                combRule)));
 +                }
 +            case eintmodFORCESWITCH: return VdwType::FSwitch;
 +            case eintmodPOTSWITCH: return VdwType::PSwitch;
 +            default:
 +                GMX_THROW(gmx::InconsistentInputError(
 +                        gmx::formatString("The requested VdW interaction modifier %s (%d) is not "
 +                                          "implemented in the GPU accelerated kernels!",
 +                                          INTMODIFIER(ic->vdw_modifier),
 +                                          ic->vdw_modifier)));
 +        }
 +    }
 +    else if (ic->vdwtype == evdwPME)
 +    {
 +        if (ic->ljpme_comb_rule == ljcrGEOM)
 +        {
 +            assert(combRule == ljcrGEOM);
 +            return VdwType::EwaldGeom;
 +        }
 +        else
 +        {
 +            assert(combRule == ljcrLB);
 +            return VdwType::EwaldLB;
 +        }
 +    }
 +    else
 +    {
 +        GMX_THROW(gmx::InconsistentInputError(gmx::formatString(
 +                "The requested VdW type %s (%d) is not implemented in the GPU accelerated kernels!",
 +                EVDWTYPE(ic->vdwtype),
 +                ic->vdwtype)));
 +    }
  }
  
  } // namespace Nbnxm
index e0e65e562b886157dee6738d27da0672f279b68b,b417566db4fd96e10356b33e7a3877e42fc10d4f..36efd356b9ce45d7dd0e15822c50acb28c25c971
@@@ -2,7 -2,7 +2,7 @@@
   * This file is part of the GROMACS molecular simulation package.
   *
   * Copyright (c) 2012,2013,2014,2015,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.
@@@ -68,7 -68,8 +68,8 @@@ void init_ewald_coulomb_force_table(con
                                      const DeviceContext&         deviceContext);
  
  /*! \brief Selects the Ewald kernel type, analytical or tabulated, single or twin cut-off. */
- enum ElecType nbnxn_gpu_pick_ewald_kernel_type(const interaction_const_t gmx_unused& ic);
 -int nbnxn_gpu_pick_ewald_kernel_type(const interaction_const_t gmx_unused& ic,
 -                                     const DeviceInformation&              deviceInfo);
++enum ElecType nbnxn_gpu_pick_ewald_kernel_type(const interaction_const_t gmx_unused& ic,
++                                               const DeviceInformation&              deviceInfo);
  
  /*! \brief Copies all parameters related to the cut-off from ic to nbp
   */
index f37dfbe582a61db82845f8323bb25514d4cf8daf,ac99cf926d7a94b25427712ac54888df86e4bcda..290d5f669a675b8e9b5ead2c4851f2bd2412879c
@@@ -2,7 -2,7 +2,7 @@@
   * This file is part of the GROMACS molecular simulation package.
   *
   * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
-  * Copyright (c) 2017,2018,2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -124,6 -124,78 +124,6 @@@ static void init_atomdata_first(cl_atom
      ad->nalloc = -1;
  }
  
 -/*! \brief Returns the kinds of electrostatics and Vdw OpenCL
 - *  kernels that will be used.
 - *
 - * Respectively, these values are from enum eelOcl and enum
 - * evdwOcl. */
 -static void map_interaction_types_to_gpu_kernel_flavors(const interaction_const_t* ic,
 -                                                        int                        combRule,
 -                                                        int*                       gpu_eeltype,
 -                                                        int*                       gpu_vdwtype,
 -                                                        const DeviceContext&       deviceContext)
 -{
 -    if (ic->vdwtype == evdwCUT)
 -    {
 -        switch (ic->vdw_modifier)
 -        {
 -            case eintmodNONE:
 -            case eintmodPOTSHIFT:
 -                switch (combRule)
 -                {
 -                    case ljcrNONE: *gpu_vdwtype = evdwTypeCUT; break;
 -                    case ljcrGEOM: *gpu_vdwtype = evdwTypeCUTCOMBGEOM; break;
 -                    case ljcrLB: *gpu_vdwtype = evdwTypeCUTCOMBLB; break;
 -                    default:
 -                        gmx_incons(
 -                                "The requested LJ combination rule is not implemented in the "
 -                                "OpenCL GPU accelerated kernels!");
 -                }
 -                break;
 -            case eintmodFORCESWITCH: *gpu_vdwtype = evdwTypeFSWITCH; break;
 -            case eintmodPOTSWITCH: *gpu_vdwtype = evdwTypePSWITCH; break;
 -            default:
 -                gmx_incons(
 -                        "The requested VdW interaction modifier is not implemented in the GPU "
 -                        "accelerated kernels!");
 -        }
 -    }
 -    else if (ic->vdwtype == evdwPME)
 -    {
 -        if (ic->ljpme_comb_rule == ljcrGEOM)
 -        {
 -            *gpu_vdwtype = evdwTypeEWALDGEOM;
 -        }
 -        else
 -        {
 -            *gpu_vdwtype = evdwTypeEWALDLB;
 -        }
 -    }
 -    else
 -    {
 -        gmx_incons("The requested VdW type is not implemented in the GPU accelerated kernels!");
 -    }
 -
 -    if (ic->eeltype == eelCUT)
 -    {
 -        *gpu_eeltype = eelTypeCUT;
 -    }
 -    else if (EEL_RF(ic->eeltype))
 -    {
 -        *gpu_eeltype = eelTypeRF;
 -    }
 -    else if ((EEL_PME(ic->eeltype) || ic->eeltype == eelEWALD))
 -    {
 -        *gpu_eeltype = nbnxn_gpu_pick_ewald_kernel_type(*ic, deviceContext.deviceInfo());
 -    }
 -    else
 -    {
 -        /* Shouldn't happen, as this is checked when choosing Verlet-scheme */
 -        gmx_incons(
 -                "The requested electrostatics type is not implemented in the GPU accelerated "
 -                "kernels!");
 -    }
 -}
  
  /*! \brief Initializes the nonbonded parameter data structure.
   */
@@@ -135,8 -207,8 +135,8 @@@ static void init_nbparam(NBParamGpu
  {
      set_cutoff_parameters(nbp, ic, listParams);
  
 -    map_interaction_types_to_gpu_kernel_flavors(ic, nbatParams.comb_rule, &(nbp->eeltype),
 -                                                &(nbp->vdwtype), deviceContext);
 +    nbp->vdwType  = nbnxmGpuPickVdwKernelType(ic, nbatParams.comb_rule);
-     nbp->elecType = nbnxmGpuPickElectrostaticsKernelType(ic);
++    nbp->elecType = nbnxmGpuPickElectrostaticsKernelType(ic, deviceContext.deviceInfo());
  
      if (ic->vdwtype == evdwPME)
      {
      }
      /* generate table for PME */
      nbp->coulomb_tab = nullptr;
 -    if (nbp->eeltype == eelTypeEWALD_TAB || nbp->eeltype == eelTypeEWALD_TAB_TWIN)
 +    if (nbp->elecType == ElecType::EwaldTab || nbp->elecType == ElecType::EwaldTabTwin)
      {
          GMX_RELEASE_ASSERT(ic->coulombEwaldTables, "Need valid Coulomb Ewald correction tables");
          init_ewald_coulomb_force_table(*ic->coulombEwaldTables, nbp, deviceContext);
@@@ -188,11 -260,8 +188,11 @@@ static cl_kernel nbnxn_gpu_create_kerne
      kernel = clCreateKernel(nb->dev_rundata->program, kernel_name, &cl_error);
      if (CL_SUCCESS != cl_error)
      {
 -        gmx_fatal(FARGS, "Failed to create kernel '%s' for GPU #%s: OpenCL error %d", kernel_name,
 -                  nb->deviceContext_->deviceInfo().device_name, cl_error);
 +        gmx_fatal(FARGS,
 +                  "Failed to create kernel '%s' for GPU #%s: OpenCL error %d",
 +                  kernel_name,
 +                  nb->deviceContext_->deviceInfo().device_name,
 +                  cl_error);
      }
  
      return kernel;
@@@ -227,8 -296,8 +227,8 @@@ static void nbnxn_ocl_clear_e_fshift(Nb
      cl_error |= clSetKernelArg(zero_e_fshift, arg_no++, sizeof(cl_uint), &shifts);
      GMX_ASSERT(cl_error == CL_SUCCESS, ocl_get_error_string(cl_error).c_str());
  
 -    cl_error = clEnqueueNDRangeKernel(ls, zero_e_fshift, 3, nullptr, global_work_size,
 -                                      local_work_size, 0, nullptr, nullptr);
 +    cl_error = clEnqueueNDRangeKernel(
 +            ls, zero_e_fshift, 3, nullptr, global_work_size, local_work_size, 0, nullptr, nullptr);
      GMX_ASSERT(cl_error == CL_SUCCESS, ocl_get_error_string(cl_error).c_str());
  }
  
@@@ -404,13 -473,8 +404,13 @@@ void gpu_upload_shiftvec(NbnxmGpu* nb, 
      {
          GMX_ASSERT(sizeof(float) * DIM == sizeof(*nbatom->shift_vec.data()),
                     "Sizes of host- and device-side shift vectors should be the same.");
 -        copyToDeviceBuffer(&adat->shift_vec, reinterpret_cast<const float*>(nbatom->shift_vec.data()),
 -                           0, SHIFTS * DIM, deviceStream, GpuApiCallBehavior::Async, nullptr);
 +        copyToDeviceBuffer(&adat->shift_vec,
 +                           reinterpret_cast<const float*>(nbatom->shift_vec.data()),
 +                           0,
 +                           SHIFTS * DIM,
 +                           deviceStream,
 +                           GpuApiCallBehavior::Async,
 +                           nullptr);
          adat->bShiftVecUploaded = CL_TRUE;
      }
  }
@@@ -455,7 -519,7 +455,7 @@@ void gpu_init_atomdata(NbnxmGpu* nb, co
          allocateDeviceBuffer(&d_atdat->f, nalloc * DIM, deviceContext);
          allocateDeviceBuffer(&d_atdat->xq, nalloc * (DIM + 1), deviceContext);
  
 -        if (useLjCombRule(nb->nbparam->vdwtype))
 +        if (useLjCombRule(nb->nbparam->vdwType))
          {
              // Two Lennard-Jones parameters per atom
              allocateDeviceBuffer(&d_atdat->lj_comb, nalloc * 2, deviceContext);
          nbnxn_ocl_clear_f(nb, nalloc);
      }
  
 -    if (useLjCombRule(nb->nbparam->vdwtype))
 +    if (useLjCombRule(nb->nbparam->vdwType))
      {
          GMX_ASSERT(sizeof(float) == sizeof(*nbat->params().lj_comb.data()),
                     "Size of the LJ parameters element should be equal to the size of float2.");
 -        copyToDeviceBuffer(&d_atdat->lj_comb, nbat->params().lj_comb.data(), 0, 2 * natoms,
 -                           deviceStream, GpuApiCallBehavior::Async,
 +        copyToDeviceBuffer(&d_atdat->lj_comb,
 +                           nbat->params().lj_comb.data(),
 +                           0,
 +                           2 * natoms,
 +                           deviceStream,
 +                           GpuApiCallBehavior::Async,
                             bDoTime ? timers->atdat.fetchNextEvent() : nullptr);
      }
      else
      {
          GMX_ASSERT(sizeof(int) == sizeof(*nbat->params().type.data()),
                     "Sizes of host- and device-side atom types should be the same.");
 -        copyToDeviceBuffer(&d_atdat->atom_types, nbat->params().type.data(), 0, natoms, deviceStream,
 -                           GpuApiCallBehavior::Async, bDoTime ? timers->atdat.fetchNextEvent() : nullptr);
 +        copyToDeviceBuffer(&d_atdat->atom_types,
 +                           nbat->params().type.data(),
 +                           0,
 +                           natoms,
 +                           deviceStream,
 +                           GpuApiCallBehavior::Async,
 +                           bDoTime ? timers->atdat.fetchNextEvent() : nullptr);
      }
  
      if (bDoTime)
index 15eaee5503e9578049bbfad7fc7be98c957ab36b,7727fe15f6708f7c5e76e29e916525c5f2f78879..bb7bb49410625c231a8bdbd7309423eccafd5916
@@@ -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.
@@@ -335,14 -335,8 +335,14 @@@ static void apply_forces_coord(struct p
  
      if (pcrd.params.eGeom == epullgCYL)
      {
 -        apply_forces_cyl_grp(&pull->dyna[coord], pcrd.spatialData.cyl_dev, masses, forces.force01,
 -                             pcrd.scalarForce, -1, f, pull->nthreads);
 +        apply_forces_cyl_grp(&pull->dyna[coord],
 +                             pcrd.spatialData.cyl_dev,
 +                             masses,
 +                             forces.force01,
 +                             pcrd.scalarForce,
 +                             -1,
 +                             f,
 +                             pull->nthreads);
  
          /* Sum the force along the vector and the radial force */
          dvec f_tot;
  
          if (!pull->group[pcrd.params.group[0]].params.ind.empty())
          {
 -            apply_forces_grp(&pull->group[pcrd.params.group[0]], masses, forces.force01, -1, f,
 -                             pull->nthreads);
 +            apply_forces_grp(
 +                    &pull->group[pcrd.params.group[0]], masses, forces.force01, -1, f, pull->nthreads);
          }
          apply_forces_grp(&pull->group[pcrd.params.group[1]], masses, forces.force01, 1, f, pull->nthreads);
  
          if (pcrd.params.ngroup >= 4)
          {
 -            apply_forces_grp(&pull->group[pcrd.params.group[2]], masses, forces.force23, -1, f,
 -                             pull->nthreads);
 -            apply_forces_grp(&pull->group[pcrd.params.group[3]], masses, forces.force23, 1, f,
 -                             pull->nthreads);
 +            apply_forces_grp(
 +                    &pull->group[pcrd.params.group[2]], masses, forces.force23, -1, f, pull->nthreads);
 +            apply_forces_grp(&pull->group[pcrd.params.group[3]], masses, forces.force23, 1, f, pull->nthreads);
          }
          if (pcrd.params.ngroup >= 6)
          {
 -            apply_forces_grp(&pull->group[pcrd.params.group[4]], masses, forces.force45, -1, f,
 -                             pull->nthreads);
 -            apply_forces_grp(&pull->group[pcrd.params.group[5]], masses, forces.force45, 1, f,
 -                             pull->nthreads);
 +            apply_forces_grp(
 +                    &pull->group[pcrd.params.group[4]], masses, forces.force45, -1, f, pull->nthreads);
 +            apply_forces_grp(&pull->group[pcrd.params.group[5]], masses, forces.force45, 1, f, pull->nthreads);
          }
      }
  }
@@@ -449,13 -445,25 +449,25 @@@ real max_pull_distance2(const pull_coor
  
  /* This function returns the distance based on coordinates xg and xref.
   * Note that the pull coordinate struct pcrd is not modified.
+  *
+  * \param[in]  pull  The pull struct
+  * \param[in]  pcrd  The pull coordinate to compute a distance for
+  * \param[in]  pbc   The periodic boundary conditions
+  * \param[in]  xg    The coordinate of group 1
+  * \param[in]  xref  The coordinate of group 0
+  * \param[in]  groupIndex0  The index of group 0 in the pcrd->params.group
+  * \param[in]  groupIndex1  The index of group 1 in the pcrd->params.group
+  * \param[in]  max_dist2    The maximum distance squared
+  * \param[out] dr           The distance vector
   */
  static void low_get_pull_coord_dr(const struct pull_t*     pull,
                                    const pull_coord_work_t* pcrd,
                                    const t_pbc*             pbc,
-                                   dvec                     xg,
+                                   const dvec               xg,
                                    dvec                     xref,
-                                   double                   max_dist2,
+                                   const int                groupIndex0,
+                                   const int                groupIndex1,
+                                   const double             max_dist2,
                                    dvec                     dr)
  {
      const pull_group_work_t* pgrp0 = &pull->group[pcrd->params.group[0]];
          gmx_fatal(FARGS,
                    "Distance between pull groups %d and %d (%f nm) is larger than 0.49 times the "
                    "box size (%f).\n%s",
-                   pcrd->params.group[0],
-                   pcrd->params.group[1],
 -                  pcrd->params.group[groupIndex0], pcrd->params.group[groupIndex1], sqrt(dr2),
 -                  sqrt(0.98 * 0.98 * max_dist2), pcrd->params.eGeom == epullgDIR ? "You might want to consider using \"pull-geometry = direction-periodic\" instead.\n" : "");
++                  pcrd->params.group[groupIndex0],
++                  pcrd->params.group[groupIndex1],
 +                  sqrt(dr2),
 +                  sqrt(0.98 * 0.98 * max_dist2),
 +                  pcrd->params.eGeom == epullgDIR
 +                          ? "You might want to consider using \"pull-geometry = "
 +                            "direction-periodic\" instead.\n"
 +                          : "");
      }
  
      if (pcrd->params.eGeom == epullgDIRPBC)
@@@ -566,14 -568,8 +578,14 @@@ static void get_pull_coord_dr(struct pu
          }
          if (debug)
          {
 -            fprintf(debug, "pull coord %d vector: %6.3f %6.3f %6.3f normalized: %6.3f %6.3f %6.3f\n",
 -                    coord_ind, vec[XX], vec[YY], vec[ZZ], spatialData.vec[XX], spatialData.vec[YY],
 +            fprintf(debug,
 +                    "pull coord %d vector: %6.3f %6.3f %6.3f normalized: %6.3f %6.3f %6.3f\n",
 +                    coord_ind,
 +                    vec[XX],
 +                    vec[YY],
 +                    vec[ZZ],
 +                    spatialData.vec[XX],
 +                    spatialData.vec[YY],
                      spatialData.vec[ZZ]);
          }
      }
      pull_group_work_t* pgrp0 = &pull->group[pcrd->params.group[0]];
      pull_group_work_t* pgrp1 = &pull->group[pcrd->params.group[1]];
  
 -    low_get_pull_coord_dr(pull, pcrd, pbc, pgrp1->x,
 -                          pcrd->params.eGeom == epullgCYL ? pull->dyna[coord_ind].x : pgrp0->x, 0,
 -                          1, md2, spatialData.dr01);
 +    low_get_pull_coord_dr(pull,
 +                          pcrd,
 +                          pbc,
 +                          pgrp1->x,
 +                          pcrd->params.eGeom == epullgCYL ? pull->dyna[coord_ind].x : pgrp0->x,
++                          0,
++                          1,
 +                          md2,
 +                          spatialData.dr01);
  
      if (pcrd->params.ngroup >= 4)
      {
          pgrp2 = &pull->group[pcrd->params.group[2]];
          pgrp3 = &pull->group[pcrd->params.group[3]];
  
-         low_get_pull_coord_dr(pull, pcrd, pbc, pgrp3->x, pgrp2->x, md2, spatialData.dr23);
+         low_get_pull_coord_dr(pull, pcrd, pbc, pgrp3->x, pgrp2->x, 2, 3, md2, spatialData.dr23);
      }
      if (pcrd->params.ngroup >= 6)
      {
          pgrp4 = &pull->group[pcrd->params.group[4]];
          pgrp5 = &pull->group[pcrd->params.group[5]];
  
-         low_get_pull_coord_dr(pull, pcrd, pbc, pgrp5->x, pgrp4->x, md2, spatialData.dr45);
+         low_get_pull_coord_dr(pull, pcrd, pbc, pgrp5->x, pgrp4->x, 4, 5, md2, spatialData.dr45);
      }
  }
  
@@@ -635,8 -627,7 +649,8 @@@ static void low_set_pull_coord_referenc
          {
              gmx_fatal(FARGS,
                        "Pull reference distance for coordinate %d (%f) needs to be non-negative",
 -                      coord_ind + 1, value_ref);
 +                      coord_ind + 1,
 +                      value_ref);
          }
      }
      else if (pcrd->params.eGeom == epullgANGLE || pcrd->params.eGeom == epullgANGLEAXIS)
@@@ -844,12 -835,8 +858,12 @@@ do_constraint(struct pull_t* pull, t_pb
          const PullCoordSpatialData& spatialData = pcrd->spatialData;
          if (debug)
          {
 -            fprintf(debug, "Pull coord %zu dr %f %f %f\n", c, spatialData.dr01[XX],
 -                    spatialData.dr01[YY], spatialData.dr01[ZZ]);
 +            fprintf(debug,
 +                    "Pull coord %zu dr %f %f %f\n",
 +                    c,
 +                    spatialData.dr01[XX],
 +                    spatialData.dr01[YY],
 +                    spatialData.dr01[ZZ]);
          }
  
          if (pcrd->params.eGeom == epullgDIR || pcrd->params.eGeom == epullgDIRPBC)
              pgrp1 = &pull->group[pcrd->params.group[1]];
  
              /* Get the current difference vector */
 -            low_get_pull_coord_dr(pull, pcrd, pbc, rnew[pcrd->params.group[1]],
 -                                  rnew[pcrd->params.group[0]], 0, 1, -1, unc_ij);
 +            low_get_pull_coord_dr(
-                     pull, pcrd, pbc, rnew[pcrd->params.group[1]], rnew[pcrd->params.group[0]], -1, unc_ij);
++                    pull, pcrd, pbc, rnew[pcrd->params.group[1]], rnew[pcrd->params.group[0]], 0, 1, -1, unc_ij);
  
              if (debug)
              {
                          gmx_fatal(
                                  FARGS,
                                  "The pull constraint reference distance for group %zu is <= 0 (%f)",
 -                                c, pcrd->value_ref);
 +                                c,
 +                                pcrd->value_ref);
                      }
  
                      {
  
                          if (debug)
                          {
 -                            fprintf(debug, "Pull ax^2+bx+c=0: a=%e b=%e c=%e lambda=%e\n", c_a, c_b,
 -                                    c_c, lambda);
 +                            fprintf(debug, "Pull ax^2+bx+c=0: a=%e b=%e c=%e lambda=%e\n", c_a, c_b, c_c, lambda);
                          }
                      }
  
  
                  g0 = pcrd->params.group[0];
                  g1 = pcrd->params.group[1];
-                 low_get_pull_coord_dr(pull, pcrd, pbc, rnew[g1], rnew[g0], -1, tmp);
-                 low_get_pull_coord_dr(pull, pcrd, pbc, dr1, dr0, -1, tmp3);
+                 low_get_pull_coord_dr(pull, pcrd, pbc, rnew[g1], rnew[g0], 0, 1, -1, tmp);
+                 low_get_pull_coord_dr(pull, pcrd, pbc, dr1, dr0, 0, 1, -1, tmp3);
 -                fprintf(debug, "Pull cur %8.5f %8.5f %8.5f j:%8.5f %8.5f %8.5f d: %8.5f\n", rnew[g0][0],
 -                        rnew[g0][1], rnew[g0][2], rnew[g1][0], rnew[g1][1], rnew[g1][2], dnorm(tmp));
 -                fprintf(debug, "Pull ref %8s %8s %8s   %8s %8s %8s d: %8.5f\n", "", "", "", "", "",
 -                        "", pcrd->value_ref);
 -                fprintf(debug, "Pull cor %8.5f %8.5f %8.5f j:%8.5f %8.5f %8.5f d: %8.5f\n", dr0[0],
 -                        dr0[1], dr0[2], dr1[0], dr1[1], dr1[2], dnorm(tmp3));
 +                fprintf(debug,
 +                        "Pull cur %8.5f %8.5f %8.5f j:%8.5f %8.5f %8.5f d: %8.5f\n",
 +                        rnew[g0][0],
 +                        rnew[g0][1],
 +                        rnew[g0][2],
 +                        rnew[g1][0],
 +                        rnew[g1][1],
 +                        rnew[g1][2],
 +                        dnorm(tmp));
 +                fprintf(debug,
 +                        "Pull ref %8s %8s %8s   %8s %8s %8s d: %8.5f\n",
 +                        "",
 +                        "",
 +                        "",
 +                        "",
 +                        "",
 +                        "",
 +                        pcrd->value_ref);
 +                fprintf(debug,
 +                        "Pull cor %8.5f %8.5f %8.5f j:%8.5f %8.5f %8.5f d: %8.5f\n",
 +                        dr0[0],
 +                        dr0[1],
 +                        dr0[2],
 +                        dr1[0],
 +                        dr1[1],
 +                        dr1[2],
 +                        dnorm(tmp3));
              } /* END DEBUG */
  
              /* Update the COMs with dr */
                  continue;
              }
  
 -            low_get_pull_coord_dr(pull, &coord, pbc, rnew[coord.params.group[1]],
 -                                  rnew[coord.params.group[0]], 0, 1, -1, unc_ij);
 +            low_get_pull_coord_dr(
-                     pull, &coord, pbc, rnew[coord.params.group[1]], rnew[coord.params.group[0]], -1, unc_ij);
++                    pull, &coord, pbc, rnew[coord.params.group[1]], rnew[coord.params.group[0]], 0, 1, -1, unc_ij);
  
              switch (coord.params.eGeom)
              {
                              "Pull constraint not converged: "
                              "groups %d %d,"
                              "d_ref = %f, current d = %f\n",
 -                            coord.params.group[0], coord.params.group[1], coord.value_ref, dnorm(unc_ij));
 +                            coord.params.group[0],
 +                            coord.params.group[1],
 +                            coord.value_ref,
 +                            dnorm(unc_ij));
                  }
  
                  bConverged_all = FALSE;
@@@ -1360,8 -1323,9 +1374,8 @@@ static PullCoordVectorForces calculateV
              dist_32        = sqrdist_32 * inv_dist_32;
  
              /* Forces on groups 0, 1 */
 -            a_01 = pcrd.scalarForce * dist_32 / m2; /* scalarForce is -dV/dphi */
 -            dsvmul(-a_01, spatialData.planevec_m,
 -                   forces.force01); /* added sign to get force on group 1, not 0 */
 +            a_01 = pcrd.scalarForce * dist_32 / m2;                /* scalarForce is -dV/dphi */
 +            dsvmul(-a_01, spatialData.planevec_m, forces.force01); /* added sign to get force on group 1, not 0 */
  
              /* Forces on groups 4, 5 */
              a_45 = -pcrd.scalarForce * dist_32 / n2;
@@@ -1414,10 -1378,7 +1428,10 @@@ void register_external_pull_potential(s
          gmx_fatal(FARGS,
                    "Module '%s' attempted to register an external potential for pull coordinate %d "
                    "which is out of the pull coordinate range %d - %zu\n",
 -                  provider, coord_index + 1, 1, pull->coord.size());
 +                  provider,
 +                  coord_index + 1,
 +                  1,
 +                  pull->coord.size());
      }
  
      pull_coord_work_t* pcrd = &pull->coord[coord_index];
                  FARGS,
                  "Module '%s' attempted to register an external potential for pull coordinate %d "
                  "which of type '%s', whereas external potentials are only supported with type '%s'",
 -                provider, coord_index + 1, epull_names[pcrd->params.eType], epull_names[epullEXTERNAL]);
 +                provider,
 +                coord_index + 1,
 +                epull_names[pcrd->params.eType],
 +                epull_names[epullEXTERNAL]);
      }
  
      GMX_RELEASE_ASSERT(!pcrd->params.externalPotentialProvider.empty(),
          gmx_fatal(FARGS,
                    "Module '%s' attempted to register an external potential for pull coordinate %d "
                    "which expects the external potential to be provided by a module named '%s'",
 -                  provider, coord_index + 1, pcrd->params.externalPotentialProvider.c_str());
 +                  provider,
 +                  coord_index + 1,
 +                  pcrd->params.externalPotentialProvider.c_str());
      }
  
      /* Lock to avoid (extremely unlikely) simultaneous reading and writing of
          gmx_fatal(FARGS,
                    "Module '%s' attempted to register an external potential for pull coordinate %d "
                    "more than once",
 -                  provider, coord_index + 1);
 +                  provider,
 +                  coord_index + 1);
      }
  
      pcrd->bExternalPotentialProviderHasBeenRegistered = true;
@@@ -1491,8 -1446,7 +1505,8 @@@ static void check_external_potential_re
                    "No external provider for external pull potentials have been provided for %d "
                    "pull coordinates. The first coordinate without provider is number %zu, which "
                    "expects a module named '%s' to provide the external potential.",
 -                  pull->numUnregisteredExternalPotentials, c + 1,
 +                  pull->numUnregisteredExternalPotentials,
 +                  c + 1,
                    pull->coord[c].params.externalPotentialProvider.c_str());
      }
  }
@@@ -1539,8 -1493,8 +1553,8 @@@ void apply_external_pull_coord_force(st
              forceWithVirial->addVirialContribution(virial);
          }
  
 -        apply_forces_coord(pull, coord_index, pullCoordForces, masses,
 -                           as_rvec_array(forceWithVirial->force_.data()));
 +        apply_forces_coord(
 +                pull, coord_index, pullCoordForces, masses, as_rvec_array(forceWithVirial->force_.data()));
      }
  
      pull->numExternalPotentialsStillToBeAppliedThisStep--;
@@@ -1720,11 -1674,8 +1734,11 @@@ void dd_make_local_pull_groups(const t_
  
          if (debug && dd != nullptr)
          {
 -            fprintf(debug, "Our DD rank (%3d) pull #atoms>0 or master: %s, will be part %s\n", dd->rank,
 -                    gmx::boolToString(bMustParticipate), gmx::boolToString(bWillParticipate));
 +            fprintf(debug,
 +                    "Our DD rank (%3d) pull #atoms>0 or master: %s, will be part %s\n",
 +                    dd->rank,
 +                    gmx::boolToString(bMustParticipate),
 +                    gmx::boolToString(bWillParticipate));
          }
  
          if (bWillParticipate)
@@@ -1903,10 -1854,8 +1917,10 @@@ static void init_pull_group_index(FILE
          }
          else
          {
 -            gmx_fatal(FARGS, "The total%s mass of pull group %d is zero",
 -                      !pg->params.weight.empty() ? " weighted" : "", g);
 +            gmx_fatal(FARGS,
 +                      "The total%s mass of pull group %d is zero",
 +                      !pg->params.weight.empty() ? " weighted" : "",
 +                      g);
          }
      }
      if (fplog)
@@@ -1966,8 -1915,7 +1980,8 @@@ struct pull_t* init_pull(FILE
  
      for (int i = 0; i < pull_params->ngroup; ++i)
      {
 -        pull->group.emplace_back(pull_params->group[i], atomSets->add(pull_params->group[i].ind),
 +        pull->group.emplace_back(pull_params->group[i],
 +                                 atomSets->add(pull_params->group[i].ind),
                                   pull_params->bSetPbcRefToPrevStepCOM);
      }
  
                            "%d in the input is larger than that supported by the code (up to %d). "
                            "You are probably reading a tpr file generated with a newer version of "
                            "Gromacs with an binary from an older version of Gromacs.",
 -                          c + 1, pcrd->params.eGeom, epullgNR - 1);
 +                          c + 1,
 +                          pcrd->params.eGeom,
 +                          epullgNR - 1);
          }
  
          if (pcrd->params.eType == epullCONSTRAINT)
                  gmx_fatal(FARGS,
                            "Pulling of type %s can not be combined with geometry %s. Consider using "
                            "pull type %s.",
 -                          epull_names[pcrd->params.eType], epullg_names[pcrd->params.eGeom],
 +                          epull_names[pcrd->params.eType],
 +                          epullg_names[pcrd->params.eGeom],
                            epull_names[epullUMBRELLA]);
              }
  
              if (pcrd->params.eType != epullEXTERNAL)
              {
                  low_set_pull_coord_reference_value(
 -                        pcrd, c,
 -                        pcrd->params.init * pull_conversion_factor_userinput2internal(&pcrd->params));
 +                        pcrd, c, pcrd->params.init * pull_conversion_factor_userinput2internal(&pcrd->params));
              }
              else
              {
          int numRealGroups = pull->group.size() - 1;
          GMX_RELEASE_ASSERT(numRealGroups > 0,
                             "The reference absolute position pull group should always be present");
 -        fprintf(fplog, "with %zu pull coordinate%s and %d group%s\n", pull->coord.size(),
 -                pull->coord.size() == 1 ? "" : "s", numRealGroups, numRealGroups == 1 ? "" : "s");
 +        fprintf(fplog,
 +                "with %zu pull coordinate%s and %d group%s\n",
 +                pull->coord.size(),
 +                pull->coord.size() == 1 ? "" : "s",
 +                numRealGroups,
 +                numRealGroups == 1 ? "" : "s");
          if (bAbs)
          {
              fprintf(fplog, "with an absolute reference\n");
                  }
              }
              const auto& referenceGroup = pull->group[coord.params.group[0]];
 -            pull->dyna.emplace_back(referenceGroup.params, referenceGroup.atomSet,
 -                                    pull->params.bSetPbcRefToPrevStepCOM);
 +            pull->dyna.emplace_back(
 +                    referenceGroup.params, referenceGroup.atomSet, pull->params.bSetPbcRefToPrevStepCOM);
          }
      }
  
@@@ -2389,8 -2331,7 +2403,8 @@@ void preparePrevStepPullCom(const t_inp
          {
              /* Only the master rank has the checkpointed COM from the previous step */
              gmx_bcast(sizeof(double) * state->pull_com_prev_step.size(),
 -                      &state->pull_com_prev_step[0], cr->mpi_comm_mygroup);
 +                      &state->pull_com_prev_step[0],
 +                      cr->mpi_comm_mygroup);
          }
          setPrevStepPullComFromState(pull_work, state);
      }
index 2e5e15850d7aa8c2c8f2ca1eccefa4ddbfc4246e,99046af962d184b0d8b495fcded0209f3f42eab4..773a44eff9466a7795811454b80a42226a859fff
@@@ -2,7 -2,7 +2,7 @@@
   * This file is part of the GROMACS molecular simulation package.
   *
   * Copyright (c) 2020 Research Organization for Information Science and Technology (RIST).
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -174,7 -174,8 +174,7 @@@ static inline void gmx_simdcall store(s
  {
      assert(0 == (std::size_t(m) % GMX_SIMD_ALIGNMENT));
      svbool_t pg = svwhilelt_b32(0, (int32_t)GMX_SIMD_DINT32_WIDTH);
 -    svst1_s32(pg, m,
 -              svuzp1(svreinterpret_s32_s64(a.simdInternal_), svreinterpret_s32_s64(a.simdInternal_)));
 +    svst1_s32(pg, m, svuzp1(svreinterpret_s32_s64(a.simdInternal_), svreinterpret_s32_s64(a.simdInternal_)));
  }
  
  static inline SimdDInt32 gmx_simdcall simdLoadU(const std::int32_t* m, SimdDInt32Tag)
  static inline void gmx_simdcall storeU(std::int32_t* m, SimdDInt32 a)
  {
      svbool_t pg = svwhilelt_b32(0, (int32_t)GMX_SIMD_DINT32_WIDTH);
 -    svst1_s32(pg, m,
 -              svuzp1(svreinterpret_s32_s64(a.simdInternal_), svreinterpret_s32_s64(a.simdInternal_)));
 +    svst1_s32(pg, m, svuzp1(svreinterpret_s32_s64(a.simdInternal_), svreinterpret_s32_s64(a.simdInternal_)));
  }
  
  static inline SimdDInt32 gmx_simdcall setZeroDI()
@@@ -211,29 -213,29 +211,29 @@@ gmx_simdcall static inline double extra
  static inline SimdDouble gmx_simdcall operator&(SimdDouble a, SimdDouble b)
  {
      svbool_t pg = svptrue_b64();
 -    return { svreinterpret_f64_s64(svand_s64_x(pg, svreinterpret_s64_f64(a.simdInternal_),
 -                                               svreinterpret_s64_f64(b.simdInternal_))) };
 +    return { svreinterpret_f64_s64(svand_s64_x(
 +            pg, svreinterpret_s64_f64(a.simdInternal_), svreinterpret_s64_f64(b.simdInternal_))) };
  }
  
  static inline SimdDouble gmx_simdcall andNot(SimdDouble a, SimdDouble b)
  {
      svbool_t pg = svptrue_b64();
 -    return { svreinterpret_f64_s64(svbic_s64_x(pg, svreinterpret_s64_f64(b.simdInternal_),
 -                                               svreinterpret_s64_f64(a.simdInternal_))) };
 +    return { svreinterpret_f64_s64(svbic_s64_x(
 +            pg, svreinterpret_s64_f64(b.simdInternal_), svreinterpret_s64_f64(a.simdInternal_))) };
  }
  
  static inline SimdDouble gmx_simdcall operator|(SimdDouble a, SimdDouble b)
  {
      svbool_t pg = svptrue_b64();
 -    return { svreinterpret_f64_s64(svorr_s64_x(pg, svreinterpret_s64_f64(a.simdInternal_),
 -                                               svreinterpret_s64_f64(b.simdInternal_))) };
 +    return { svreinterpret_f64_s64(svorr_s64_x(
 +            pg, svreinterpret_s64_f64(a.simdInternal_), svreinterpret_s64_f64(b.simdInternal_))) };
  }
  
  static inline SimdDouble gmx_simdcall operator^(SimdDouble a, SimdDouble b)
  {
      svbool_t pg = svptrue_b64();
 -    return { svreinterpret_f64_s64(sveor_s64_x(pg, svreinterpret_s64_f64(a.simdInternal_),
 -                                               svreinterpret_s64_f64(b.simdInternal_))) };
 +    return { svreinterpret_f64_s64(sveor_s64_x(
 +            pg, svreinterpret_s64_f64(a.simdInternal_), svreinterpret_s64_f64(b.simdInternal_))) };
  }
  
  static inline SimdDouble gmx_simdcall operator+(SimdDouble a, SimdDouble b)
@@@ -347,7 -349,7 +347,7 @@@ static inline SimdDouble gmx_simdcall m
      // The result will always be correct since we mask the result with m, but
      // for debug builds we also want to make sure not to generate FP exceptions
  #ifndef NDEBUG
-     x.simdInternal_ = svsel_f64(m, x.simdInternal_, svdup_n_f64(1.0));
+     x.simdInternal_ = svsel_f64(pg, x.simdInternal_, svdup_n_f64(1.0));
  #endif
      return { svreinterpret_f64_u64(svand_n_u64_z(
              pg, svreinterpret_u64_f64(svrecpe_f64(x.simdInternal_)), 0xFFFFFFFFFFFFFFFF)) };
@@@ -389,10 -391,9 +389,10 @@@ static inline SimdDouble gmx_simdcall f
              pg, svreinterpret_s64_u64(svlsr_n_u64_x(pg, svreinterpret_u64_s64(iExponent), 52)), exponentBias);
  
  
 -    svfloat64_t result = svreinterpret_f64_s64(svorr_s64_x(
 -            pg, svand_s64_x(pg, svreinterpret_s64_f64(value.simdInternal_), mantissaMask),
 -            svreinterpret_s64_f64(half)));
 +    svfloat64_t result = svreinterpret_f64_s64(
 +            svorr_s64_x(pg,
 +                        svand_s64_x(pg, svreinterpret_s64_f64(value.simdInternal_), mantissaMask),
 +                        svreinterpret_s64_f64(half)));
  
      if (opt == MathOptimization::Safe)
      {
index 572266a9839c74f6c06e8e69ef0af5f807101a38,cffd79f15c8911cd32b896af09c8c0f209f15fa6..d3d054c97d1b17e4a9151645bec3bc73d998299b
@@@ -2,7 -2,7 +2,7 @@@
   * This file is part of the GROMACS molecular simulation package.
   *
   * Copyright (c) 2020 Research Organization for Information Science and Technology (RIST).
-  * Copyright (c) 2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -90,8 -90,7 +90,8 @@@ static inline void gmx_simdcall gatherL
      svint64_t offsets;
      svbool_t  pg = svptrue_b64();
      offsets      = svmul_n_s64_x(
 -            pg, svunpklo_s64(svld1_s32(svwhilelt_b32(0, (int32_t)GMX_SIMD_DINT32_WIDTH), offset)),
 +            pg,
 +            svunpklo_s64(svld1_s32(svwhilelt_b32(0, (int32_t)GMX_SIMD_DINT32_WIDTH), offset)),
              align * sizeof(double));
      v0->simdInternal_ = svld1_gather_s64offset_f64(pg, base, offsets);
      offsets           = svadd_n_s64_x(pg, offsets, sizeof(double));
@@@ -147,8 -146,7 +147,8 @@@ static inline void gmx_simdcall gatherL
      svint64_t offsets;
      svbool_t  pg = svptrue_b64();
      offsets      = svmul_n_s64_x(
 -            pg, svunpklo_s64(svld1_s32(svwhilelt_b32(0, (int32_t)GMX_SIMD_DINT32_WIDTH), offset)),
 +            pg,
 +            svunpklo_s64(svld1_s32(svwhilelt_b32(0, (int32_t)GMX_SIMD_DINT32_WIDTH), offset)),
              align * sizeof(double));
      v0->simdInternal_ = svld1_gather_s64offset_f64(pg, base, offsets);
      offsets           = svadd_n_s64_x(pg, offsets, sizeof(double));
@@@ -170,8 -168,7 +170,8 @@@ static inline void gmx_simdcall transpo
      svint64_t offsets;
      svbool_t  pg = svptrue_b64();
      offsets      = svmul_n_s64_x(
 -            pg, svunpklo_s64(svld1_s32(svwhilelt_b32(0, (int32_t)GMX_SIMD_DINT32_WIDTH), offset)),
 +            pg,
 +            svunpklo_s64(svld1_s32(svwhilelt_b32(0, (int32_t)GMX_SIMD_DINT32_WIDTH), offset)),
              align * sizeof(double));
      svst1_scatter_s64offset_f64(pg, base, offsets, v0.simdInternal_);
      offsets = svadd_n_s64_x(pg, offsets, sizeof(double));
@@@ -192,6 -189,7 +192,7 @@@ static inline void gmx_simdcal
      alignas(GMX_SIMD_ALIGNMENT) double tvec[3 * GMX_SIMD_DOUBLE_WIDTH];
      v = svcreate3_f64(v0.simdInternal_, v1.simdInternal_, v2.simdInternal_);
      svst3_f64(pg, tvec, v);
+ #if GMX_SIMD_DOUBLE_WIDTH >= 3
      pg = svwhilelt_b64(0, 3);
      for (int i = 0; i < GMX_SIMD_DOUBLE_WIDTH; i++)
      {
          svfloat64_t t3 = svadd_f64_x(pg, t1, t2);
          svst1_f64(pg, base + align * offset[i], t3);
      }
+ #else
+     for (std::size_t i = 0; i < GMX_SIMD_DOUBLE_WIDTH; i++)
+     {
+         for (int j = 0; j < 3; j++)
+         {
+             base[align * offset[i] + j] += tvec[i * 3 + j];
+         }
+     }
+ #endif
  }
  
  template<int align>
@@@ -213,6 -220,7 +223,7 @@@ static inline void gmx_simdcal
      alignas(GMX_SIMD_ALIGNMENT) double tvec[3 * GMX_SIMD_DOUBLE_WIDTH];
      v = svcreate3_f64(v0.simdInternal_, v1.simdInternal_, v2.simdInternal_);
      svst3_f64(pg, tvec, v);
+ #if GMX_SIMD_DOUBLE_WIDTH >= 3
      pg = svwhilelt_b64(0, 3);
      for (int i = 0; i < GMX_SIMD_DOUBLE_WIDTH; i++)
      {
          svfloat64_t t3 = svsub_f64_x(pg, t1, t2);
          svst1_f64(pg, base + align * offset[i], t3);
      }
+ #else
+     for (std::size_t i = 0; i < GMX_SIMD_DOUBLE_WIDTH; i++)
+     {
+         for (int j = 0; j < 3; j++)
+         {
+             base[align * offset[i] + j] -= tvec[i * 3 + j];
+         }
+     }
+ #endif
  }
  
  static inline void gmx_simdcall expandScalarsToTriplets(SimdDouble  scalar,
@@@ -284,11 -301,21 +304,21 @@@ static inline double gmx_simdcal
      sum[1] = svadda_f64(pg, 0.0, v1.simdInternal_);
      sum[2] = svadda_f64(pg, 0.0, v2.simdInternal_);
      sum[3] = svadda_f64(pg, 0.0, v3.simdInternal_);
-     pg     = svwhilelt_b64(0, 4);
-     _m     = svld1_f64(pg, m);
-     _s     = svld1_f64(pg, sum);
+ #if GMX_SIMD_DOUBLE_WIDTH >= 4
+     pg = svwhilelt_b64(0, 4);
+     _m = svld1_f64(pg, m);
+     _s = svld1_f64(pg, sum);
      svst1_f64(pg, m, svadd_f64_x(pg, _m, _s));
      return svadda_f64(pg, 0.0, _s);
+ #else
+     double res = 0;
+     for (int i = 0; i < 4; i++)
+     {
+         m[i] += sum[i];
+         res += sum[i];
+     }
+     return res;
+ #endif
  }
  
  static inline SimdDouble gmx_simdcall loadDualHsimd(const double* m0, const double* m1)
@@@ -360,11 -387,21 +390,21 @@@ static inline double gmx_simdcall reduc
      sum[1] = svadda_f64(pg, 0.0, v0.simdInternal_);
      sum[3] = svadda_f64(pg, 0.0, v1.simdInternal_);
  
+ #if GMX_SIMD_DOUBLE_WIDTH >= 4
      pg = svwhilelt_b64(0, 4);
      _m = svld1_f64(pg, m);
      _s = svld1_f64(pg, sum);
      svst1_f64(pg, m, svadd_f64_x(pg, _m, _s));
      return svadda_f64(pg, 0.0, _s);
+ #else
+     double res = 0;
+     for (int i = 0; i < 4; i++)
+     {
+         m[i] += sum[i];
+         res += sum[i];
+     }
+     return res;
+ #endif
  }
  
  template<int align>
@@@ -378,8 -415,7 +418,8 @@@ static inline void gmx_simdcall gatherL
      svbool_t    pg = svwhilelt_b64(0, (int32_t)GMX_SIMD_DOUBLE_WIDTH / 2);
      svfloat64_t _v0, _v1;
      offsets = svmul_n_s64_x(
 -            pg, svunpklo(svld1_s32(svwhilelt_b32(0, (int32_t)GMX_SIMD_DINT32_WIDTH / 2), offset)),
 +            pg,
 +            svunpklo(svld1_s32(svwhilelt_b32(0, (int32_t)GMX_SIMD_DINT32_WIDTH / 2), offset)),
              align * sizeof(double));
      _v0               = svld1_gather_s64offset_f64(pg, base0, offsets);
      _v1               = svld1_gather_s64offset_f64(pg, base1, offsets);
index 9312fdc0bb83e37b92d01b97b5d1d6ea0a700ff2,143a35c244cae0aee9551ce049468ea7081e627f..b1fdb52b743fa1b2e380eb418850ae3af2b7f96b
@@@ -1,7 -1,8 +1,8 @@@
  /*
   * This file is part of the GROMACS molecular simulation package.
   *
-  * Copyright (c) 2015,2016,2017,2018,2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 2015,2016,2017,2018,2019 by the GROMACS development team.
+  * Copyright (c) 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.
@@@ -508,6 -509,23 +509,23 @@@ int get_nthreads_mpi(const gmx_hw_info_
              }
          }
  
+         if (ngpu > 0 && (nrank_new % ngpu) != 0)
+         {
+             /* If we use GPUs, the number of ranks must be divisible by the number of GPUs,
+              * unless the GPUs are very different (and if they are, user should manually
+              * select the parallelization scheme).
+              * Rounding down the number of ranks, or setting it to ngpu, whichever is smaller.
+              * */
+             if (nrank_new > ngpu)
+             {
+                 nrank_new = (nrank_new / ngpu) * ngpu;
+             }
+             else
+             {
+                 nrank_new = ngpu;
+             }
+         }
          nrank = nrank_new;
  
          /* We reduced the number of tMPI ranks, which means we might violate
@@@ -609,9 -627,7 +627,9 @@@ void check_resource_division_efficiency
                      "threads per rank, which is most likely inefficient. The optimum is usually "
                      "between %d and"
                      " %d threads per rank.",
 -                    nth_omp_max, nthreads_omp_mpi_ok_min, nthreads_omp_mpi_target_max);
 +                    nth_omp_max,
 +                    nthreads_omp_mpi_ok_min,
 +                    nthreads_omp_mpi_target_max);
  
              if (bNtOmpOptionSet)
              {
                            "%s If you want to run with this setup, specify the -ntomp option. But "
                            "we suggest to "
                            "change the number of MPI ranks%s.",
 -                          buf, mpi_option);
 +                          buf,
 +                          mpi_option);
              }
          }
      }
  //! Dump a \c hw_opt to \c fp.
  static void print_hw_opt(FILE* fp, const gmx_hw_opt_t* hw_opt)
  {
 -    fprintf(fp, "hw_opt: nt %d ntmpi %d ntomp %d ntomp_pme %d gpu_id '%s' gputasks '%s'\n",
 -            hw_opt->nthreads_tot, hw_opt->nthreads_tmpi, hw_opt->nthreads_omp, hw_opt->nthreads_omp_pme,
 -            hw_opt->gpuIdsAvailable.c_str(), hw_opt->userGpuTaskAssignment.c_str());
 +    fprintf(fp,
 +            "hw_opt: nt %d ntmpi %d ntomp %d ntomp_pme %d gpu_id '%s' gputasks '%s'\n",
 +            hw_opt->nthreads_tot,
 +            hw_opt->nthreads_tmpi,
 +            hw_opt->nthreads_omp,
 +            hw_opt->nthreads_omp_pme,
 +            hw_opt->gpuIdsAvailable.c_str(),
 +            hw_opt->userGpuTaskAssignment.c_str());
  }
  
  void checkAndUpdateHardwareOptions(const gmx::MDLogger& mdlog,
                        "The total number of threads requested (%d) does not match the thread-MPI "
                        "ranks (%d) "
                        "times the OpenMP threads (%d) requested",
 -                      hw_opt->nthreads_tot, hw_opt->nthreads_tmpi, hw_opt->nthreads_omp);
 +                      hw_opt->nthreads_tot,
 +                      hw_opt->nthreads_tmpi,
 +                      hw_opt->nthreads_omp);
          }
  
          if (hw_opt->nthreads_tmpi > 0 && hw_opt->nthreads_tot % hw_opt->nthreads_tmpi != 0)
                        "The total number of threads requested (%d) is not divisible by the number "
                        "of thread-MPI "
                        "ranks requested (%d)",
 -                      hw_opt->nthreads_tot, hw_opt->nthreads_tmpi);
 +                      hw_opt->nthreads_tot,
 +                      hw_opt->nthreads_tmpi);
          }
  
          if (hw_opt->nthreads_omp > 0 && hw_opt->nthreads_tot % hw_opt->nthreads_omp != 0)
                        "The total number of threads requested (%d) is not divisible by the number "
                        "of OpenMP "
                        "threads requested (%d)",
 -                      hw_opt->nthreads_tot, hw_opt->nthreads_omp);
 +                      hw_opt->nthreads_tot,
 +                      hw_opt->nthreads_omp);
          }
      }
  
                        "You requested %d OpenMP threads with %d total threads. Choose a total "
                        "number of threads "
                        "that is a multiple of the number of OpenMP threads.",
 -                      hw_opt->nthreads_omp, hw_opt->nthreads_tot);
 +                      hw_opt->nthreads_omp,
 +                      hw_opt->nthreads_tot);
          }
  
          if (hw_opt->nthreads_tmpi > hw_opt->nthreads_tot)
                        "You requested %d thread-MPI ranks with %d total threads. Choose a total "
                        "number of "
                        "threads that is a multiple of the number of thread-MPI ranks.",
 -                      hw_opt->nthreads_tmpi, hw_opt->nthreads_tot);
 +                      hw_opt->nthreads_tmpi,
 +                      hw_opt->nthreads_tot);
          }
      }
  
index cbc9b16a60b9aa7a08f10ea4111a993a4717ba4b,03b0989476701c49570f54ca635d677a96bccceb..086fd8d29f879a7b842ec4c36d15328b0700961a
@@@ -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,2018 by the GROMACS development team.
-  * Copyright (c) 2019,2020, by the GROMACS development team, led by
+  * Copyright (c) 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.
@@@ -54,15 -54,9 +54,15 @@@ static void printHarmonicInteraction(gm
                                       const char*      r,
                                       const char*      kr)
  {
 -    writer->writeLineFormatted("%sA=%12.5e, %sA=%12.5e, %sB=%12.5e, %sB=%12.5e", r,
 -                               iparams.harmonic.rA, kr, iparams.harmonic.krA, r,
 -                               iparams.harmonic.rB, kr, iparams.harmonic.krB);
 +    writer->writeLineFormatted("%sA=%12.5e, %sA=%12.5e, %sB=%12.5e, %sB=%12.5e",
 +                               r,
 +                               iparams.harmonic.rA,
 +                               kr,
 +                               iparams.harmonic.krA,
 +                               r,
 +                               iparams.harmonic.rB,
 +                               kr,
 +                               iparams.harmonic.krB);
  }
  
  void pr_iparams(FILE* fp, t_functype ftype, const t_iparams& iparams)
@@@ -82,37 -76,25 +82,37 @@@ void printInteractionParameters(gmx::Te
          case F_ANGLES:
          case F_G96ANGLES: printHarmonicInteraction(writer, iparams, "th", "ct"); break;
          case F_CROSS_BOND_BONDS:
 -            writer->writeLineFormatted("r1e=%15.8e, r2e=%15.8e, krr=%15.8e", iparams.cross_bb.r1e,
 -                                       iparams.cross_bb.r2e, iparams.cross_bb.krr);
 +            writer->writeLineFormatted("r1e=%15.8e, r2e=%15.8e, krr=%15.8e",
 +                                       iparams.cross_bb.r1e,
 +                                       iparams.cross_bb.r2e,
 +                                       iparams.cross_bb.krr);
              break;
          case F_CROSS_BOND_ANGLES:
              writer->writeLineFormatted("r1e=%15.8e, r1e=%15.8e, r3e=%15.8e, krt=%15.8e",
 -                                       iparams.cross_ba.r1e, iparams.cross_ba.r2e,
 -                                       iparams.cross_ba.r3e, iparams.cross_ba.krt);
 +                                       iparams.cross_ba.r1e,
 +                                       iparams.cross_ba.r2e,
 +                                       iparams.cross_ba.r3e,
 +                                       iparams.cross_ba.krt);
              break;
          case F_LINEAR_ANGLES:
              writer->writeLineFormatted("klinA=%15.8e, aA=%15.8e, klinB=%15.8e, aB=%15.8e",
 -                                       iparams.linangle.klinA, iparams.linangle.aA,
 -                                       iparams.linangle.klinB, iparams.linangle.aB);
 +                                       iparams.linangle.klinA,
 +                                       iparams.linangle.aA,
 +                                       iparams.linangle.klinB,
 +                                       iparams.linangle.aB);
              break;
          case F_UREY_BRADLEY:
              writer->writeLineFormatted(
                      "thetaA=%15.8e, kthetaA=%15.8e, r13A=%15.8e, kUBA=%15.8e, thetaB=%15.8e, "
                      "kthetaB=%15.8e, r13B=%15.8e, kUBB=%15.8e",
 -                    iparams.u_b.thetaA, iparams.u_b.kthetaA, iparams.u_b.r13A, iparams.u_b.kUBA,
 -                    iparams.u_b.thetaB, iparams.u_b.kthetaB, iparams.u_b.r13B, iparams.u_b.kUBB);
 +                    iparams.u_b.thetaA,
 +                    iparams.u_b.kthetaA,
 +                    iparams.u_b.r13A,
 +                    iparams.u_b.kUBA,
 +                    iparams.u_b.thetaB,
 +                    iparams.u_b.kthetaB,
 +                    iparams.u_b.r13B,
 +                    iparams.u_b.kUBB);
              break;
          case F_QUARTIC_ANGLES:
              writer->writeStringFormatted("theta=%15.8e", iparams.qangle.theta);
              writer->ensureLineBreak();
              break;
          case F_BHAM:
 -            writer->writeLineFormatted("a=%15.8e, b=%15.8e, c=%15.8e", iparams.bham.a,
 -                                       iparams.bham.b, iparams.bham.c);
 +            writer->writeLineFormatted(
 +                    "a=%15.8e, b=%15.8e, c=%15.8e", iparams.bham.a, iparams.bham.b, iparams.bham.c);
              break;
          case F_BONDS:
          case F_G96BONDS:
          case F_MORSE:
              writer->writeLineFormatted(
                      "b0A=%15.8e, cbA=%15.8e, betaA=%15.8e, b0B=%15.8e, cbB=%15.8e, betaB=%15.8e",
 -                    iparams.morse.b0A, iparams.morse.cbA, iparams.morse.betaA, iparams.morse.b0B,
 -                    iparams.morse.cbB, iparams.morse.betaB);
 +                    iparams.morse.b0A,
 +                    iparams.morse.cbA,
 +                    iparams.morse.betaA,
 +                    iparams.morse.b0B,
 +                    iparams.morse.cbB,
 +                    iparams.morse.betaB);
              break;
          case F_CUBICBONDS:
 -            writer->writeLineFormatted("b0=%15.8e, kb=%15.8e, kcub=%15.8e", iparams.cubic.b0,
 -                                       iparams.cubic.kb, iparams.cubic.kcub);
 +            writer->writeLineFormatted("b0=%15.8e, kb=%15.8e, kcub=%15.8e",
 +                                       iparams.cubic.b0,
 +                                       iparams.cubic.kb,
 +                                       iparams.cubic.kcub);
              break;
          case F_CONNBONDS: writer->ensureEmptyLine(); break;
          case F_FENEBONDS:
              writer->writeLineFormatted(
                      "lowA=%15.8e, up1A=%15.8e, up2A=%15.8e, kA=%15.8e, lowB=%15.8e, up1B=%15.8e, "
                      "up2B=%15.8e, kB=%15.8e,",
 -                    iparams.restraint.lowA, iparams.restraint.up1A, iparams.restraint.up2A,
 -                    iparams.restraint.kA, iparams.restraint.lowB, iparams.restraint.up1B,
 -                    iparams.restraint.up2B, iparams.restraint.kB);
 +                    iparams.restraint.lowA,
 +                    iparams.restraint.up1A,
 +                    iparams.restraint.up2A,
 +                    iparams.restraint.kA,
 +                    iparams.restraint.lowB,
 +                    iparams.restraint.up1B,
 +                    iparams.restraint.up2B,
 +                    iparams.restraint.kB);
              break;
          case F_TABBONDS:
          case F_TABBONDSNC:
          case F_TABANGLES:
          case F_TABDIHS:
 -            writer->writeLineFormatted("tab=%d, kA=%15.8e, kB=%15.8e", iparams.tab.table,
 -                                       iparams.tab.kA, iparams.tab.kB);
 +            writer->writeLineFormatted(
 +                    "tab=%d, kA=%15.8e, kB=%15.8e", iparams.tab.table, iparams.tab.kA, iparams.tab.kB);
              break;
          case F_POLARIZATION:
              writer->writeLineFormatted("alpha=%15.8e", iparams.polarize.alpha);
              break;
          case F_ANHARM_POL:
              writer->writeLineFormatted("alpha=%15.8e drcut=%15.8e khyp=%15.8e",
 -                                       iparams.anharm_polarize.alpha, iparams.anharm_polarize.drcut,
 +                                       iparams.anharm_polarize.alpha,
 +                                       iparams.anharm_polarize.drcut,
                                         iparams.anharm_polarize.khyp);
              break;
          case F_THOLE_POL:
              writer->writeLineFormatted("a=%15.8e, alpha1=%15.8e, alpha2=%15.8e, rfac=%15.8e",
 -                                       iparams.thole.a, iparams.thole.alpha1, iparams.thole.alpha2,
 +                                       iparams.thole.a,
 +                                       iparams.thole.alpha1,
 +                                       iparams.thole.alpha2,
                                         iparams.thole.rfac);
              break;
          case F_WATER_POL:
              writer->writeLineFormatted(
                      "al_x=%15.8e, al_y=%15.8e, al_z=%15.8e, rOH=%9.6f, rHH=%9.6f, rOD=%9.6f",
 -                    iparams.wpol.al_x, iparams.wpol.al_y, iparams.wpol.al_z, iparams.wpol.rOH,
 -                    iparams.wpol.rHH, iparams.wpol.rOD);
 +                    iparams.wpol.al_x,
 +                    iparams.wpol.al_y,
 +                    iparams.wpol.al_z,
 +                    iparams.wpol.rOH,
 +                    iparams.wpol.rHH,
 +                    iparams.wpol.rOD);
              break;
          case F_LJ:
              writer->writeLineFormatted("c6=%15.8e, c12=%15.8e", iparams.lj.c6, iparams.lj.c12);
              break;
          case F_LJ14:
              writer->writeLineFormatted("c6A=%15.8e, c12A=%15.8e, c6B=%15.8e, c12B=%15.8e",
 -                                       iparams.lj14.c6A, iparams.lj14.c12A, iparams.lj14.c6B,
 +                                       iparams.lj14.c6A,
 +                                       iparams.lj14.c12A,
 +                                       iparams.lj14.c6B,
                                         iparams.lj14.c12B);
              break;
          case F_LJC14_Q:
              writer->writeLineFormatted("fqq=%15.8e, qi=%15.8e, qj=%15.8e, c6=%15.8e, c12=%15.8e",
 -                                       iparams.ljc14.fqq, iparams.ljc14.qi, iparams.ljc14.qj,
 -                                       iparams.ljc14.c6, iparams.ljc14.c12);
 +                                       iparams.ljc14.fqq,
 +                                       iparams.ljc14.qi,
 +                                       iparams.ljc14.qj,
 +                                       iparams.ljc14.c6,
 +                                       iparams.ljc14.c12);
              break;
          case F_LJC_PAIRS_NB:
 -            writer->writeLineFormatted("qi=%15.8e, qj=%15.8e, c6=%15.8e, c12=%15.8e", iparams.ljcnb.qi,
 -                                       iparams.ljcnb.qj, iparams.ljcnb.c6, iparams.ljcnb.c12);
 +            writer->writeLineFormatted("qi=%15.8e, qj=%15.8e, c6=%15.8e, c12=%15.8e",
 +                                       iparams.ljcnb.qi,
 +                                       iparams.ljcnb.qj,
 +                                       iparams.ljcnb.c6,
 +                                       iparams.ljcnb.c12);
              break;
          case F_PDIHS:
          case F_PIDIHS:
          case F_ANGRES:
          case F_ANGRESZ:
              writer->writeLineFormatted("phiA=%15.8e, cpA=%15.8e, phiB=%15.8e, cpB=%15.8e, mult=%d",
 -                                       iparams.pdihs.phiA, iparams.pdihs.cpA, iparams.pdihs.phiB,
 -                                       iparams.pdihs.cpB, iparams.pdihs.mult);
 +                                       iparams.pdihs.phiA,
 +                                       iparams.pdihs.cpA,
 +                                       iparams.pdihs.phiB,
 +                                       iparams.pdihs.cpB,
 +                                       iparams.pdihs.mult);
              break;
          case F_DISRES:
              writer->writeLineFormatted(
                      "label=%4d, type=%1d, low=%15.8e, up1=%15.8e, up2=%15.8e, fac=%15.8e)",
 -                    iparams.disres.label, iparams.disres.type, iparams.disres.low,
 -                    iparams.disres.up1, iparams.disres.up2, iparams.disres.kfac);
 +                    iparams.disres.label,
 +                    iparams.disres.type,
 +                    iparams.disres.low,
 +                    iparams.disres.up1,
 +                    iparams.disres.up2,
 +                    iparams.disres.kfac);
              break;
          case F_ORIRES:
              writer->writeLineFormatted(
                      "ex=%4d, label=%d, power=%4d, c=%15.8e, obs=%15.8e, kfac=%15.8e)",
 -                    iparams.orires.ex, iparams.orires.label, iparams.orires.power, iparams.orires.c,
 -                    iparams.orires.obs, iparams.orires.kfac);
 +                    iparams.orires.ex,
 +                    iparams.orires.label,
 +                    iparams.orires.power,
 +                    iparams.orires.c,
 +                    iparams.orires.obs,
 +                    iparams.orires.kfac);
              break;
          case F_DIHRES:
              writer->writeLineFormatted(
                      "phiA=%15.8e, dphiA=%15.8e, kfacA=%15.8e, phiB=%15.8e, dphiB=%15.8e, "
                      "kfacB=%15.8e",
 -                    iparams.dihres.phiA, iparams.dihres.dphiA, iparams.dihres.kfacA,
 -                    iparams.dihres.phiB, iparams.dihres.dphiB, iparams.dihres.kfacB);
 +                    iparams.dihres.phiA,
 +                    iparams.dihres.dphiA,
 +                    iparams.dihres.kfacA,
 +                    iparams.dihres.phiB,
 +                    iparams.dihres.dphiB,
 +                    iparams.dihres.kfacB);
              break;
          case F_POSRES:
              writer->writeLineFormatted(
                      "pos0A=(%15.8e,%15.8e,%15.8e), fcA=(%15.8e,%15.8e,%15.8e), "
                      "pos0B=(%15.8e,%15.8e,%15.8e), fcB=(%15.8e,%15.8e,%15.8e)",
 -                    iparams.posres.pos0A[XX], iparams.posres.pos0A[YY], iparams.posres.pos0A[ZZ],
 -                    iparams.posres.fcA[XX], iparams.posres.fcA[YY], iparams.posres.fcA[ZZ],
 -                    iparams.posres.pos0B[XX], iparams.posres.pos0B[YY], iparams.posres.pos0B[ZZ],
 -                    iparams.posres.fcB[XX], iparams.posres.fcB[YY], iparams.posres.fcB[ZZ]);
 +                    iparams.posres.pos0A[XX],
 +                    iparams.posres.pos0A[YY],
 +                    iparams.posres.pos0A[ZZ],
 +                    iparams.posres.fcA[XX],
 +                    iparams.posres.fcA[YY],
 +                    iparams.posres.fcA[ZZ],
 +                    iparams.posres.pos0B[XX],
 +                    iparams.posres.pos0B[YY],
 +                    iparams.posres.pos0B[ZZ],
 +                    iparams.posres.fcB[XX],
 +                    iparams.posres.fcB[YY],
 +                    iparams.posres.fcB[ZZ]);
              break;
          case F_FBPOSRES:
              writer->writeLineFormatted(
                      "pos0=(%15.8e,%15.8e,%15.8e), geometry=%d, r=%15.8e, k=%15.8e",
 -                    iparams.fbposres.pos0[XX], iparams.fbposres.pos0[YY], iparams.fbposres.pos0[ZZ],
 -                    iparams.fbposres.geom, iparams.fbposres.r, iparams.fbposres.k);
 +                    iparams.fbposres.pos0[XX],
 +                    iparams.fbposres.pos0[YY],
 +                    iparams.fbposres.pos0[ZZ],
 +                    iparams.fbposres.geom,
 +                    iparams.fbposres.r,
 +                    iparams.fbposres.k);
              break;
          case F_RBDIHS:
              for (int i = 0; i < NR_RBDIHS; i++)
              {
 -                writer->writeStringFormatted("%srbcA[%d]=%15.8e", i == 0 ? "" : ", ", i,
 -                                             iparams.rbdihs.rbcA[i]);
 +                writer->writeStringFormatted(
 +                        "%srbcA[%d]=%15.8e", i == 0 ? "" : ", ", i, iparams.rbdihs.rbcA[i]);
              }
              writer->ensureLineBreak();
              for (int i = 0; i < NR_RBDIHS; i++)
              {
 -                writer->writeStringFormatted("%srbcB[%d]=%15.8e", i == 0 ? "" : ", ", i,
 -                                             iparams.rbdihs.rbcB[i]);
 +                writer->writeStringFormatted(
 +                        "%srbcB[%d]=%15.8e", i == 0 ? "" : ", ", i, iparams.rbdihs.rbcB[i]);
              }
              writer->ensureLineBreak();
              break;
          case F_SETTLE:
              writer->writeLineFormatted("doh=%15.8e, dhh=%15.8e", iparams.settle.doh, iparams.settle.dhh);
              break;
+         case F_VSITE1: writer->ensureEmptyLine(); break;
          case F_VSITE2: writer->writeLineFormatted("a=%15.8e", iparams.vsite.a); break;
          case F_VSITE3:
          case F_VSITE3FD:
          case F_VSITE3OUT:
          case F_VSITE4FD:
          case F_VSITE4FDN:
 -            writer->writeLineFormatted("a=%15.8e, b=%15.8e, c=%15.8e", iparams.vsite.a,
 -                                       iparams.vsite.b, iparams.vsite.c);
 +            writer->writeLineFormatted(
 +                    "a=%15.8e, b=%15.8e, c=%15.8e", iparams.vsite.a, iparams.vsite.b, iparams.vsite.c);
              break;
          case F_VSITEN:
              writer->writeLineFormatted("n=%2d, a=%15.8e", iparams.vsiten.n, iparams.vsiten.a);
              writer->ensureLineBreak();
              break;
          default:
 -            gmx_fatal(FARGS, "unknown function type %d (%s) in %s line %d", ftype,
 -                      interaction_function[ftype].name, __FILE__, __LINE__);
 +            gmx_fatal(FARGS,
 +                      "unknown function type %d (%s) in %s line %d",
 +                      ftype,
 +                      interaction_function[ftype].name,
 +                      __FILE__,
 +                      __LINE__);
      }
  }
  
@@@ -463,9 -389,7 +464,9 @@@ void pr_idef(FILE* fp, int indent, cons
          for (i = 0; i < idef->ntypes; i++)
          {
              pr_indent(fp, indent + INDENT);
 -            fprintf(fp, "functype[%d]=%s, ", bShowNumbers ? i : -1,
 +            fprintf(fp,
 +                    "functype[%d]=%s, ",
 +                    bShowNumbers ? i : -1,
                      interaction_function[idef->functype[i]].name);
              pr_iparams(fp, idef->functype[i], idef->iparams[i]);
          }
  
          for (j = 0; (j < F_NRE); j++)
          {
 -            printIlist(fp, indent, interaction_function[j].longname, idef->functype, idef->il[j],
 -                       bShowNumbers, bShowParameters, idef->iparams);
 +            printIlist(fp,
 +                       indent,
 +                       interaction_function[j].longname,
 +                       idef->functype,
 +                       idef->il[j],
 +                       bShowNumbers,
 +                       bShowParameters,
 +                       idef->iparams);
          }
      }
  }
index ae0ee9dbcda3bebbec355eb3b059f1d00d42f660,43ba3d508862dd41bcb1c4dd08a3d2606f84d135..273ffde72182325c2a32b72739e29eeb9a9d539a
@@@ -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.
@@@ -107,6 -107,7 +107,7 @@@ std::string formatCentered(int width, c
  
  void printCopyright(gmx::TextWriter* writer)
  {
+     // Contributors sorted alphabetically by last name
      static const char* const Contributors[]  = { "Andrey Alekseenko",
                                                  "Emile Apol",
                                                  "Rossen Apostolov",
                                                  "Aldert van Buuren",
                                                  "Rudi van Drunen",
                                                  "Anton Feenstra",
+                                                 "Gilles Gouaillardet",
                                                  "Alan Gray",
                                                  "Gerrit Groenhof",
                                                  "Anca Hamuraru",
      static const char* const CopyrightText[] = {
          "Copyright (c) 1991-2000, University of Groningen, The Netherlands.",
          "Copyright (c) 2001-2019, The GROMACS development team at",
 -        "Uppsala University, Stockholm University and", "the Royal Institute of Technology, Sweden.",
 +        "Uppsala University, Stockholm University and",
 +        "the Royal Institute of Technology, Sweden.",
          "check out http://www.gromacs.org for more information."
      };
  
@@@ -283,7 -284,7 +285,7 @@@ void gmx_print_version_info(gmx::TextWr
  #if GMX_DOUBLE
      writer->writeLine("Precision:          double");
  #else
-     writer->writeLine("Precision:          single");
+     writer->writeLine("Precision:          mixed");
  #endif
      writer->writeLine(formatString("Memory model:       %u bit", static_cast<unsigned>(8 * sizeof(void*))));
  
  #if HAVE_EXTRAE
      unsigned major, minor, revision;
      Extrae_get_version(&major, &minor, &revision);
 -    writer->writeLine(formatString("Tracing support:    enabled. Using Extrae-%d.%d.%d", major,
 -                                   minor, revision));
 +    writer->writeLine(formatString(
 +            "Tracing support:    enabled. Using Extrae-%d.%d.%d", major, minor, revision));
  #else
      writer->writeLine("Tracing support:    disabled");
  #endif
       * them. Can wait for later, as the master branch has ready code to do all
       * that. */
      writer->writeLine(formatString("C compiler:         %s", BUILD_C_COMPILER));
 -    writer->writeLine(formatString("C compiler flags:   %s %s", BUILD_CFLAGS,
 -                                   CMAKE_BUILD_CONFIGURATION_C_FLAGS));
 +    writer->writeLine(formatString(
 +            "C compiler flags:   %s %s", BUILD_CFLAGS, CMAKE_BUILD_CONFIGURATION_C_FLAGS));
      writer->writeLine(formatString("C++ compiler:       %s", BUILD_CXX_COMPILER));
 -    writer->writeLine(formatString("C++ compiler flags: %s %s", BUILD_CXXFLAGS,
 -                                   CMAKE_BUILD_CONFIGURATION_CXX_FLAGS));
 +    writer->writeLine(formatString(
 +            "C++ compiler flags: %s %s", BUILD_CXXFLAGS, CMAKE_BUILD_CONFIGURATION_CXX_FLAGS));
  #ifdef HAVE_LIBMKL
      /* MKL might be used for LAPACK/BLAS even if FFTs use FFTW, so keep it separate */
 -    writer->writeLine(formatString("Linked with Intel MKL version %d.%d.%d.", __INTEL_MKL__,
 -                                   __INTEL_MKL_MINOR__, __INTEL_MKL_UPDATE__));
 +    writer->writeLine(formatString(
 +            "Linked with Intel MKL version %d.%d.%d.", __INTEL_MKL__, __INTEL_MKL_MINOR__, __INTEL_MKL_UPDATE__));
  #endif
  #if GMX_GPU_OPENCL
      writer->writeLine(formatString("OpenCL include dir: %s", OPENCL_INCLUDE_DIR));
  #endif
  #if GMX_GPU_CUDA
      writer->writeLine(formatString("CUDA compiler:      %s", CUDA_COMPILER_INFO));
 -    writer->writeLine(formatString("CUDA compiler flags:%s %s", CUDA_COMPILER_FLAGS,
 -                                   CMAKE_BUILD_CONFIGURATION_CXX_FLAGS));
 +    writer->writeLine(formatString(
 +            "CUDA compiler flags:%s %s", CUDA_COMPILER_FLAGS, CMAKE_BUILD_CONFIGURATION_CXX_FLAGS));
      writer->writeLine("CUDA driver:        " + gmx::getCudaDriverVersionString());
      writer->writeLine("CUDA runtime:       " + gmx::getCudaRuntimeVersionString());
  #endif
@@@ -425,8 -426,8 +427,8 @@@ void printBinaryInformation(TextWriter
          // necessary to read stuff above the copyright notice.
          // The line above the copyright notice puts the copyright notice is
          // context, though.
 -        writer->writeLine(formatString("%sGROMACS:      %s, version %s%s%s", prefix, name,
 -                                       gmx_version(), precisionString, suffix));
 +        writer->writeLine(formatString(
 +                "%sGROMACS:      %s, version %s%s%s", prefix, name, gmx_version(), precisionString, suffix));
      }
      const char* const binaryPath = programContext.fullBinaryPath();
      if (!gmx::isNullOrEmpty(binaryPath))
      const gmx::InstallationPrefixInfo installPrefix = programContext.installationPrefix();
      if (!gmx::isNullOrEmpty(installPrefix.path))
      {
 -        writer->writeLine(formatString("%sData prefix:  %s%s%s", prefix, installPrefix.path,
 -                                       installPrefix.bSourceLayout ? " (source tree)" : "", suffix));
 +        writer->writeLine(formatString("%sData prefix:  %s%s%s",
 +                                       prefix,
 +                                       installPrefix.path,
 +                                       installPrefix.bSourceLayout ? " (source tree)" : "",
 +                                       suffix));
      }
      const std::string workingDir = Path::getWorkingDirectory();
      if (!workingDir.empty())
      const char* const commandLine = programContext.commandLine();
      if (!gmx::isNullOrEmpty(commandLine))
      {
 -        writer->writeLine(formatString("%sCommand line:%s\n%s  %s%s", prefix, suffix, prefix,
 -                                       commandLine, suffix));
 +        writer->writeLine(formatString(
 +                "%sCommand line:%s\n%s  %s%s", prefix, suffix, prefix, commandLine, suffix));
      }
      if (settings.bExtendedInfo_)
      {
diff --combined tests/CMakeLists.txt
index e600a404bbc343e16b68b77f76296cedb47aa562,67a4990890e7a35cc5fd30be4cf500683a259951..325664ac02607f6b530d35b2af4a0d5908e0a640
@@@ -43,7 -43,7 +43,7 @@@ if(REGRESSIONTEST_DOWNLOAD
          set(REGRESSIONTEST_URL https://gitlab.com/gromacs/gromacs-regressiontests/-/archive/${REGRESSIONTEST_BRANCH}/gromacs-regressiontests-${REGRESSIONTEST_BRANCH}.tar.gz)
          # REGRESSIONTEST_PATH for dev trees is set later based on the dirname found in the tar
      else()
-         set(REGRESSIONTEST_URL http://ftp.gromacs.org/regressiontests/regressiontests-${REGRESSIONTEST_VERSION}.tar.gz)
+         set(REGRESSIONTEST_URL https://ftp.gromacs.org/regressiontests/regressiontests-${REGRESSIONTEST_VERSION}.tar.gz)
          set(REGRESSIONTEST_PATH
              "${CMAKE_CURRENT_BINARY_DIR}/regressiontests-${REGRESSIONTEST_VERSION}"
              CACHE PATH "Path to auto-downloaded regressiontests" FORCE)
@@@ -88,12 -88,13 +88,12 @@@ log: ${log}"
      set(REGRESSIONTEST_DOWNLOAD OFF CACHE BOOL "Tests already downloaded. Set to yes to download again" FORCE)
  endif()
  
 -if(REGRESSIONTEST_PATH AND (CMAKE_CROSSCOMPILING OR CMAKE_CONFIGURATION_TYPES OR GMX_BUILD_MDRUN_ONLY))
 +if(REGRESSIONTEST_PATH AND (CMAKE_CROSSCOMPILING OR CMAKE_CONFIGURATION_TYPES))
      # TODO: It would be nicer to do these checks before potentially downloading the tests.
      # Cross-compiling toolchains require us to compile both front-end and
      # back-end binaries to run gmxtest.pl.
 -    # Testing an mdrun-only builds require supporting binaries from a full build
      message(WARNING
 -        "With cross-compiling, multi-configuration generators (e.g. Visual Studio), or with mdrun-only builds, running regressiontests from build system is not supported. Please run gmxtest.pl directly.")
 +        "With cross-compiling or multi-configuration generators (e.g. Visual Studio), running regressiontests from build system is not supported. Please run gmxtest.pl directly.")
      set(REGRESSIONTEST_PATH OFF CACHE BOOL
          "With cross-compiling or multi-configuration generators, running regressiontests from build system is not supported." FORCE)
  endif()
@@@ -135,7 -136,7 +135,7 @@@ if(REGRESSIONTEST_PATH
          list(APPEND ARGS -suffix ${GMX_BINARY_SUFFIX})
      endif()
      #crosscompile is only used to disable checking whether binaries work
 -    #given that we know they are there and that mdrun might not be exectuable
 +    #given that we know they are there and that mdrun might not be executable
      #(e.g. Cray) we enable it.
      list(APPEND ARGS -crosscompile)
  
@@@ -205,14 -206,15 +205,14 @@@ if(GMX_PHYSICAL_VALIDATION
              "GMX_PHYSICAL_VALIDATION set, but physical validation script not found in ${PHYSVALTEST_SOURCE_PATH}.")
      endif()
  
 -    if(CMAKE_CROSSCOMPILING OR CMAKE_CONFIGURATION_TYPES OR GMX_BUILD_MDRUN_ONLY)
 +    if(CMAKE_CROSSCOMPILING OR CMAKE_CONFIGURATION_TYPES)
          # The following comment is copied from regression tests:
          #     Cross-compiling toolchains require us to compile both front-end and
          #     back-end binaries to run gmxtest.pl.
 -        #     Testing an mdrun-only builds require supporting binaries from a full build
          # TODO: Look into the details of this.
          # For now, turn it off - our python-gmx interface is probably not that stable for special cases anyway
          message(WARNING
 -                "With cross-compiling, multi-configuration generators (e.g. Visual Studio), or with mdrun-only builds,\
 +                "With cross-compiling or multi-configuration generators (e.g. Visual Studio),\
                  running physical validation tests from build system is not supported.\
                  Please run physicalvalidation.py directly.")
          set(GMX_PHYSICAL_VALIDATION OFF CACHE BOOL