Merge release-2019 into master
authorPaul Bauer <paul.bauer.q@gmail.com>
Wed, 31 Jul 2019 13:28:34 +0000 (15:28 +0200)
committerBerk Hess <hess@kth.se>
Thu, 1 Aug 2019 07:53:51 +0000 (09:53 +0200)
Resolved Conflicts:
cmake/gmxVersionInfo.cmake
docs/CMakeLists.txt
docs/release-notes/2019/2019.2.rst
src/external/build-fftw/CMakeLists.txt
src/gromacs/awh/read-params.cpp
src/gromacs/awh/read-params.h
src/gromacs/domdec/domdec_internal.h
src/gromacs/ewald/pme-gather.clh
src/gromacs/fileio/gmx_internal_xdr.cpp
src/gromacs/gmxana/gmx_editconf.cpp
src/gromacs/gmxpreprocess/readir.cpp
src/gromacs/listed-forces/manage-threading.cpp
src/gromacs/mdlib/broadcaststructs.cpp
src/gromacs/mdlib/membed.cpp
src/gromacs/mdlib/nbnxn_atomdata.cpp
src/gromacs/mdlib/nbnxn_atomdata.h
src/gromacs/mdlib/nbnxn_search.cpp
src/gromacs/mdlib/ns.cpp
src/gromacs/mdrun/runner.cpp
src/programs/mdrun/tests/spc-and-methanol.top

Change-Id: I2c0853732620246cade31f3a66ae23b7ba5b902b

37 files changed:
1  2 
cmake/gmxDetectAvx512FmaUnits.cmake
cmake/gmxManageNvccConfig.cmake
docs/CMakeLists.txt
docs/install-guide/index.rst
docs/reference-manual/references.rst
docs/release-notes/index.rst
docs/user-guide/mdp-options.rst
docs/user-guide/mdrun-performance.rst
src/external/build-fftw/CMakeLists.txt
src/gromacs/awh/awh.cpp
src/gromacs/awh/read_params.cpp
src/gromacs/awh/read_params.h
src/gromacs/domdec/domdec.cpp
src/gromacs/domdec/domdec_internal.h
src/gromacs/domdec/domdec_struct.h
src/gromacs/ewald/pme.cpp
src/gromacs/ewald/pme_gather.clh
src/gromacs/fileio/pdbio.cpp
src/gromacs/gmxana/gmx_wham.cpp
src/gromacs/gmxpreprocess/editconf.cpp
src/gromacs/gmxpreprocess/gmxcpp.cpp
src/gromacs/gmxpreprocess/gmxcpp.h
src/gromacs/gmxpreprocess/grompp.cpp
src/gromacs/gmxpreprocess/toppush.cpp
src/gromacs/listed_forces/manage_threading.cpp
src/gromacs/mdlib/broadcaststructs.cpp
src/gromacs/mdlib/membed.cpp
src/gromacs/mdlib/shake.cpp
src/gromacs/mdrun/legacymdrunoptions.h
src/gromacs/mdrun/minimize.cpp
src/gromacs/mdrun/runner.cpp
src/gromacs/nbnxm/pairlist.cpp
src/gromacs/pulling/pull.cpp
src/gromacs/pulling/pullutil.cpp
src/gromacs/selection/indexutil.cpp
src/gromacs/selection/sm_simple.cpp
src/programs/mdrun/tests/refdata/MdrunTest_WritesHelp.xml

index 5f278b3bff4461e09aa218dadec7d7ef58908f08,f52c1892a203b98607eb6ff54deb43261d545e0d..7836c4b2a99192442adb121e097692f3bc352675
@@@ -58,11 -58,12 +58,11 @@@ function(gmx_detect_avx_512_fma_units R
              if(SIMD_AVX_512_CXX_SUPPORTED AND GMX_X86_GCC_INLINE_ASM)
                  # Compile the detection program
  
 -                set(_compile_definitions "-I${PROJECT_SOURCE_DIR}/src -DGMX_IDENTIFY_AVX512_FMA_UNITS_STANDALONE -DSIMD_AVX_512_CXX_SUPPORTED=1 -DGMX_X86_GCC_INLINE_ASM=1 ${SIMD_AVX_512_CXX_FLAGS} ${GMX_STDLIB_CXX_FLAGS}")
 +                set(_compile_definitions "-I${PROJECT_SOURCE_DIR}/src -DGMX_IDENTIFY_AVX512_FMA_UNITS_STANDALONE -DSIMD_AVX_512_CXX_SUPPORTED=1 -DGMX_X86_GCC_INLINE_ASM=1 ${SIMD_AVX_512_CXX_FLAGS}")
                  try_compile(AVX_512_FMA_UNIT_DETECTION_COMPILED
                      "${PROJECT_BINARY_DIR}"
                      "${PROJECT_SOURCE_DIR}/src/gromacs/hardware/identifyavx512fmaunits.cpp"
                      COMPILE_DEFINITIONS "${_compile_definitions}"
 -                    LINK_LIBRARIES "${GMX_STDLIB_LIBRARIES}"
                      OUTPUT_VARIABLE AVX_512_FMA_UNIT_DETECTION_COMPILED_OUTPUT
                      COPY_FILE ${AVX_512_FMA_UNIT_DETECTION_BINARY})
                  if(NOT AVX_512_FMA_UNIT_DETECTION_COMPILED AND NOT RUN_AVX_512_FMA_UNIT_DETECTION_COMPILATION_QUIETLY)
@@@ -82,7 -83,7 +82,7 @@@
                          ERROR_QUIET)
                      if (RESULT_VAR EQUAL 0)
                          string(STRIP "${OUTPUT_VAR_TEMP}" OUTPUT_VAR)
-                         set(${RESULT} ${OUTPUT_VAR_TEMP} CACHE INTERNAL "Result of test for number of AVX-512 FMA units")
+                         set(${RESULT} ${OUTPUT_VAR} CACHE INTERNAL "Result of test for number of AVX-512 FMA units")
                      else()
                          message(STATUS "Could not identify number of AVX-512 units - detection program did run successfully")
                          set(${RESULT} -1 CACHE INTERNAL "Result of test for number of AVX-512 FMA units")
index f88297e960f2c1a52cecaf9f11a96cefe63bf250,bdf5a97dc230455813312ca26b481e18b381313c..63fe528599d4b3a43f55177cf9be19bd82c8c575
@@@ -67,10 -67,8 +67,10 @@@ if(CUDA_HOST_COMPILER_CHANGED
      set(CUDA_HOST_COMPILER_OPTIONS "")
  
      if(APPLE AND CMAKE_C_COMPILER_ID MATCHES "GNU")
 -        # Some versions of gcc-4.8 and gcc-4.9 produce errors (in particular on OS X)
 -        # if we do not use -D__STRICT_ANSI__. It is harmless, so we might as well add it for all versions.
 +        # Some versions of gcc-4.8 and gcc-4.9 have produced errors
 +        # (in particular on OS X) if we do not use
 +        # -D__STRICT_ANSI__. It is harmless, so we might as well add
 +        # it for all versions.
          list(APPEND CUDA_HOST_COMPILER_OPTIONS "-D__STRICT_ANSI__")
      endif()
  
@@@ -99,30 -97,54 +99,30 @@@ if (GMX_CUDA_TARGET_SM OR GMX_CUDA_TARG
      endforeach()
  else()
      # Set the CUDA GPU architectures to compile for:
 -    # - with CUDA >=5.0 <6.5:   CC <=3.5 is supported
 -    #     => compile sm_30, sm_35 SASS, and compute_35 PTX
 -    # - with CUDA ==6.5:        CC <=3.7 and 5.0 are supported
 -    #     => compile sm_30, sm_35, sm_37 sm_50, SASS, and compute_50 PTX
 -    # - with CUDA >=7.0         CC 5.2 is supported (5.3, Tegra X1 we don't generate code for)
 -    #     => compile sm_30, sm_35, sm_37, sm_50, & sm_52 SASS, and compute_52 PTX
 -    # - with CUDA >=8.0         CC 6.0-6.2 is supported (but we know nothing about CC 6.2, so we won't generate code or it)
 -    #     => compile sm_30, sm_35, sm_37, sm_50, sm_52, sm_60, sm_61 SASS, and compute_60 and compute_61 PTX
      # - with CUDA >=9.0         CC 7.0 is supported and CC 2.0 is no longer supported
      #     => compile sm_30, sm_35, sm_37, sm_50, sm_52, sm_60, sm_61, sm_70 SASS, and compute_70 PTX
 -    #
 -    #   Note that CUDA 6.5.19 second patch release supports cc 5.2 too, but
 -    #   CUDA_VERSION does not contain patch version and having PTX 5.0 JIT-ed is
 -    #   equally fast as compiling with sm_5.2 anyway.
 +    # - with CUDA >=10.0        CC 7.5 is supported
 +    #     => compile sm_30, sm_35, sm_37, sm_50, sm_52, sm_60, sm_61, sm_70, sm_75 SASS, and compute_75 PTX
  
      # First add flags that trigger SASS (binary) code generation for physical arch
      list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_30,code=sm_30")
      list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_35,code=sm_35")
 -
 -    if(NOT CUDA_VERSION VERSION_LESS "6.5") # >= 6.5
 -        list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_37,code=sm_37")
 -        list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_50,code=sm_50")
 -    endif()
 -    if(NOT CUDA_VERSION VERSION_LESS "7.0") # >= 7.0
 -        list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_52,code=sm_52")
 -    endif()
 -    if(NOT CUDA_VERSION VERSION_LESS "8.0") # >= 8.0
 -        list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_60,code=sm_60")
 -        list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_61,code=sm_61")
 -    endif()
 -    if(NOT CUDA_VERSION VERSION_LESS "9.0") # >= 9.0
 -        list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_70,code=sm_70")
 -    endif()
 +    list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_37,code=sm_37")
 +    list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_50,code=sm_50")
 +    list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_52,code=sm_52")
 +    list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_60,code=sm_60")
 +    list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_61,code=sm_61")
 +    list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_70,code=sm_70")
  
      # Next add flags that trigger PTX code generation for the newest supported virtual arch
      # that's useful to JIT to future architectures
 -    if(CUDA_VERSION VERSION_LESS "6.5")
 -        list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_35,code=compute_35")
 -    elseif(CUDA_VERSION VERSION_LESS "7.0")
 -        list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_50,code=compute_50")
 -    elseif(CUDA_VERSION VERSION_LESS "8.0")
 -        list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_52,code=compute_52")
 -    elseif(CUDA_VERSION VERSION_LESS "9.0")
 -        list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_60,code=compute_60")
 -        list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_61,code=compute_61")
 -    elseif(CUDA_VERSION VERSION_LESS "10.0")
 -        list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_70,code=compute_70")
 -    else() # version >= 10.0
 +    list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_35,code=compute_35")
 +    list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_50,code=compute_50")
 +    list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_52,code=compute_52")
 +    list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_60,code=compute_60")
 +    list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_61,code=compute_61")
 +    list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_70,code=compute_70")
 +    if(NOT CUDA_VERSION VERSION_LESS "10.0")
          list (APPEND GMX_CUDA_NVCC_GENCODE_FLAGS "-gencode;arch=compute_75,code=compute_75")
      endif()
  endif()
@@@ -136,23 -158,6 +136,23 @@@ if (GMX_CUDA_TARGET_COMPUTE
      set_property(CACHE GMX_CUDA_TARGET_COMPUTE PROPERTY TYPE STRING)
  endif()
  
 +# FindCUDA.cmake is unaware of the mechanism used by cmake to embed
 +# the compiler flag for the required C++ standard in the generated
 +# build files, so we have to pass it ourselves
 +if (MSVC)
 +    # We use C++14 on MSVC, but cmake does not understand the
 +    # necessary compilation option for that until version 3.10, so we
 +    # can remove this after we require that version.
 +    if (NOT CMAKE_CXX14_STANDARD_COMPILE_OPTION)
 +        set(GMX_CXX_STANDARD_COMPILE_OPTION "-std:c++14")
 +    else()
 +        set(GMX_CXX_STANDARD_COMPILE_OPTION "${CMAKE_CXX14_STANDARD_COMPILE_OPTION}")
 +    endif()
 +else()
 +    set(GMX_CXX_STANDARD_COMPILE_OPTION "${CMAKE_CXX14_STANDARD_COMPILE_OPTION}")
 +endif()
 +list(APPEND GMX_CUDA_NVCC_FLAGS "${GMX_CXX_STANDARD_COMPILE_OPTION}")
 +
  # assemble the CUDA flags
  list(APPEND GMX_CUDA_NVCC_FLAGS "${GMX_CUDA_NVCC_GENCODE_FLAGS}")
  list(APPEND GMX_CUDA_NVCC_FLAGS "-use_fast_math")
@@@ -172,7 -177,7 +172,7 @@@ gmx_check_if_changed(_cuda_nvcc_executa
  # code. Set the CMake variable GMX_NVCC_WORKS on if you want to
  # bypass this check.
  if((_cuda_nvcc_executable_or_flags_changed OR CUDA_HOST_COMPILER_CHANGED OR NOT GMX_NVCC_WORKS) AND NOT WIN32)
-     message(STATUS "Check for working NVCC/C compiler combination")
+     message(STATUS "Check for working NVCC/C++ compiler combination with nvcc '${CUDA_NVCC_EXECUTABLE}'")
      execute_process(COMMAND ${CUDA_NVCC_EXECUTABLE} -ccbin ${CUDA_HOST_COMPILER} -c ${CUDA_NVCC_FLAGS} ${CUDA_NVCC_FLAGS_${_build_type}} ${CMAKE_SOURCE_DIR}/cmake/TestCUDA.cu
          RESULT_VARIABLE _cuda_test_res
          OUTPUT_VARIABLE _cuda_test_out
          message(STATUS "${CUDA_NVCC_EXECUTABLE} standard output: '${_cuda_test_out}'")
          message(STATUS "${CUDA_NVCC_EXECUTABLE} standard error:  '${_cuda_test_err}'")
          if(${_cuda_test_err} MATCHES "nsupported")
-             message(FATAL_ERROR "NVCC/C compiler combination does not seem to be supported. CUDA frequently does not support the latest versions of the host compiler, so you might want to try an earlier C/C++ compiler version and make sure your CUDA compiler and driver are as recent as possible.")
+             message(FATAL_ERROR "NVCC/C++ compiler combination does not seem to be supported. CUDA frequently does not support the latest versions of the host compiler, so you might want to try an earlier C++ compiler version and make sure your CUDA compiler and driver are as recent as possible.")
          else()
              message(FATAL_ERROR "CUDA compiler does not seem to be functional.")
          endif()
      elseif(NOT GMX_CUDA_TEST_COMPILER_QUIETLY)
-         message(STATUS "Check for working NVCC/C compiler combination - works")
+         message(STATUS "Check for working NVCC/C++ compiler combination - works")
          set(GMX_NVCC_WORKS TRUE CACHE INTERNAL "Nvcc can compile a trivial test program")
      endif()
  endif() # GMX_CHECK_NVCC
diff --combined docs/CMakeLists.txt
index 9580f88d4a68449f0242e19e4b831ab2f41b3e02,3479a3bc2c89a31a5fb5436f4c541f78eee3c86b..07d96196939a410427989511b14a35caba20c152
@@@ -60,13 -60,18 +60,13 @@@ set(EXPECTED_DOXYGEN_VERSION 1.8.5
  
  set(EXPECTED_SPHINX_VERSION 1.6.1)
  
 -if (DEFINED PYTHON_EXECUTABLE)
 -    # Keep quiet on subsequent runs of cmake
 -    set(PythonInterp_FIND_QUIETLY ON)
 -endif()
 -find_package(PythonInterp 2.7)
 -
 -
 -if (NOT ${PYTHON_VERSION_MAJOR} EQUAL 3)
 -    find_package(Sphinx ${EXPECTED_SPHINX_VERSION} QUIET COMPONENTS pygments)
 -else()
 -    MESSAGE(STATUS "Can not build documentation with Python 3")
 +# By default, suppress output after first configuration.
 +if(SPHINX_ALREADY_SEARCHED)
 +    set(Sphinx_FIND_QUIETLY ON)
  endif()
 +find_package(Sphinx ${EXPECTED_SPHINX_VERSION} COMPONENTS pygments)
 +set(SPHINX_ALREADY_SEARCHED TRUE CACHE BOOL "True if a search for Sphinx has already been done")
 +mark_as_advanced(SPHINX_ALREADY_SEARCHED)
  
  # Even if we aren't going to make the full webpage, set up to put all
  # the documentation output in the same place, for convenience
@@@ -108,7 -113,10 +108,7 @@@ elseif (BUILD_IS_INSOURCE
      set(MANUAL_BUILD_NOT_POSSIBLE_REASON "the build is in-source")
  else()
      include(manual/UseLATEX.cmake)
 -    if(${PYTHON_VERSION_MAJOR} EQUAL 3)
 -        set(MANUAL_BUILD_IS_POSSIBLE OFF)
 -        set(MANUAL_BUILD_NOT_POSSIBLE_REASON "We can not build the documentation when using python3")
 -    elseif(NOT SPHINX_FOUND)
 +    if(NOT SPHINX_FOUND)
          set(MANUAL_BUILD_IS_POSSIBLE OFF)
          set(MANUAL_BUILD_NOT_POSSIBLE_REASON "Sphinx has not been found and is needed to create the LaTex input files")
      elseif(NOT PDFLATEX_COMPILER)
@@@ -237,7 -245,6 +237,7 @@@ if (SPHINX_FOUND
          reference-manual/analysis/rmsd.rst
          reference-manual/analysis/covariance-analysis.rst
          reference-manual/analysis/dihedral-pca.rst
 +        reference-manual/analysis/hydrogen-bonds.rst
          reference-manual/analysis/protein-related.rst
          reference-manual/analysis/interface-related.rst)
      # The image files have also been ordered by the respective
          how-to/visualize.rst
          install-guide/index.rst
          release-notes/index.rst
 +        release-notes/2020/major/highlights.rst
 +        release-notes/2020/major/features.rst
 +        release-notes/2020/major/performance.rst
 +        release-notes/2020/major/tools.rst
 +        release-notes/2020/major/bugs-fixed.rst
 +        release-notes/2020/major/removed-functionality.rst
 +        release-notes/2020/major/deprecated-functionality.rst
 +        release-notes/2020/major/portability.rst
 +        release-notes/2020/major/miscellaneous.rst
+         release-notes/2019/2019.4.rst
+         release-notes/2019/2019.3.rst
          release-notes/2019/2019.2.rst
          release-notes/2019/2019.1.rst
          release-notes/2019/major/highlights.rst
          release-notes/2019/major/deprecated-functionality.rst
          release-notes/2019/major/portability.rst
          release-notes/2019/major/miscellaneous.rst
+         release-notes/2018/2018.7.rst
          release-notes/2018/2018.6.rst
          release-notes/2018/2018.5.rst
          release-notes/2018/2018.4.rst
      add_custom_target(install-guide
          COMMAND
              ${SPHINX_EXECUTABLE}
 -            -q -E -b text
 +            -q -b text
              -w sphinx-install.log
              -d ${CMAKE_CURRENT_BINARY_DIR}/install-guide/_doctrees
              -c ${SPHINX_INPUT_DIR}
      add_custom_target(webpage-sphinx
          DEPENDS sphinx-programs
          DEPENDS sphinx-input
 -        DEPENDS sphinx-image-conversion 
 +        DEPENDS sphinx-image-conversion
 +        DEPENDS manual
          COMMAND
              ${CMAKE_COMMAND} -E make_directory ${SPHINX_INPUT_DIR}/_static
          COMMAND
              ${SPHINX_EXECUTABLE}
 -            -q -E -b html
 +            -q -b html
              -w sphinx-html.log
              -d "${SPHINX_CACHE_DIR}"
              "${SPHINX_INPUT_DIR}"
      add_custom_target(man
          COMMAND
              ${SPHINX_EXECUTABLE}
 -            -q -E -b man
 +            -q -b man
              -w sphinx-man.log
              -d ${SPHINX_CACHE_DIR}
              -t do_man
  
  else()
      set(MANUAL_BUILD_IS_POSSIBLE OFF)
-     set(MANUAL_BUILD_NOT_POSSIBLE_REASON "Sphinx version ${EXPECTED_SPHINX_VERSION} is not available")
+     set(MANUAL_BUILD_NOT_POSSIBLE_REASON "Sphinx expected minimum version ${EXPECTED_SPHINX_VERSION} is not available")
  
      add_custom_target(webpage-sphinx
          COMMAND ${CMAKE_COMMAND} -E echo
-             "HTML pages cannot be built because Sphinx version ${EXPECTED_SPHINX_VERSION} is not available"
+             "HTML pages cannot be built because Sphinx expected minimum version ${EXPECTED_SPHINX_VERSION} is not available"
          VERBATIM)
      add_custom_target(install-guide
          COMMAND ${CMAKE_COMMAND} -E echo
-             "INSTALL cannot be built because Sphinx version ${EXPECTED_SPHINX_VERSION} is not available"
+             "INSTALL cannot be built because Sphinx expected minimum version ${EXPECTED_SPHINX_VERSION} is not available"
          VERBATIM)
      add_custom_target(man
          COMMAND ${CMAKE_COMMAND} -E echo
-             "man pages cannot be built because Sphinx version ${EXPECTED_SPHINX_VERSION} is not available"
+             "man pages cannot be built because Sphinx expected minimum version ${EXPECTED_SPHINX_VERSION} is not available"
          VERBATIM)
      add_custom_target(sphinx-create-texman
          COMMAND ${CMAKE_COMMAND} -E echo
-             "Cannot prepare LaTeX input files because Sphinx version ${EXPECTED_SPHINX_VERSION} is not available"
+             "Cannot prepare LaTeX input files because Sphinx expected minimum version ${EXPECTED_SPHINX_VERSION} is not available"
          VERBATIM)
+     add_custom_target(manual
+         COMMAND ${CMAKE_COMMAND} -E echo
+             "manual cannot be built because Sphinx expected minimum version ${EXPECTED_SPHINX_VERSION} is not available")
  endif()
  
  if (MAN_PAGE_DIR)
@@@ -639,13 -642,13 +645,13 @@@ set(HTML_BUILD_NOT_POSSIBLE_REASON
  set(HTML_BUILD_WARNINGS)
  
  # Next, turn it off if any of the preconditions are unsatisified
 -if (NOT PYTHON_EXECUTABLE)
 +if (NOT PythonInterp_FOUND)
      set(HTML_BUILD_IS_POSSIBLE OFF)
      set(HTML_BUILD_NOT_POSSIBLE_REASON "Python is required")
  elseif (NOT SPHINX_FOUND)
      # Hardly anything gets built if Sphinx is not available, so don't bother.
      set(HTML_BUILD_IS_POSSIBLE OFF)
-     set(HTML_BUILD_NOT_POSSIBLE_REASON "Sphinx version ${EXPECTED_SPHINX_VERSION} is required")
+     set(HTML_BUILD_NOT_POSSIBLE_REASON "Sphinx expected minimum version ${EXPECTED_SPHINX_VERSION} is required")
  endif()
  if (NOT MANUAL_BUILD_IS_POSSIBLE)
      list(APPEND HTML_BUILD_WARNINGS
index 2b2c78eaf341e5497cc6bbba02f9f0a1f523a193,de9ea0df9f1545b78b47d153c01d3b113b83aaf0..48da20d21efdf4b9ab6668490bf0f7ec5e0a0142
@@@ -55,7 -55,7 +55,7 @@@ 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 an MPI wrapper compiler and which is `building only
 +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
@@@ -104,20 -104,20 +104,20 @@@ PowerPC including POWER8, ARM v7, ARM v
  Compiler
  ^^^^^^^^
  
 -|Gromacs| can be compiled on any platform with ANSI C99 and C++11
 +|Gromacs| can be compiled on any platform with ANSI C99 and C++14
  compilers, and their respective standard C/C++ libraries. Good
  performance on an OS and architecture requires choosing a good
  compiler. We recommend gcc, because it is free, widely available and
  frequently provides the best performance.
  
  You should strive to use the most recent version of your
 -compiler. Since we require full C++11 support the minimum supported
 +compiler. Since we require full C++14 support the minimum supported
  compiler versions are
  
 -* GNU (gcc) 4.8.1
 +* GNU (gcc) 5.1
  * Intel (icc) 17.0.1
 -* LLVM (clang) 3.3
 -* Microsoft (MSVC) 2017 (C++14 is used)
 +* LLVM (clang) 3.6
 +* Microsoft (MSVC) 2017
  
  Other compilers may work (Cray, Pathscale, older clang) but do
  not offer competitive performance. We recommend against PGI because
@@@ -131,24 -131,32 +131,24 @@@ You may also need the most recent versi
  components beside the compiler itself (e.g. assembler or linker);
  these are often shipped by your OS distribution's binutils package.
  
 -C++11 support requires adequate support in both the compiler and the
 +C++14 support requires adequate support in both the compiler and the
  C++ library. The gcc and MSVC compilers include their own standard
 -libraries and require no further configuration. For configuration of
 -other compilers, read on.
 +libraries and require no further configuration. If your vendor's
 +compiler also manages the standard library library via compiler flags,
 +these will be honored. For configuration of other compilers, read on.
  
  On Linux, both the Intel and clang compiler use the libstdc++ which
  comes with gcc as the default C++ library. For |Gromacs|, we require
 -the compiler to support libstc++ version 4.8.1 or higher. To select a
 -particular libstdc++ library, use:
 -
 -* For Intel: ``-DGMX_STDLIB_CXX_FLAGS=-gcc-name=/path/to/gcc/binary``
 -  or make sure that the correct gcc version is first in path (e.g. by
 -  loading the gcc module). It can also be useful to add
 -  ``-DCMAKE_CXX_LINK_FLAGS="-Wl,-rpath,/path/to/gcc/lib64
 -  -L/path/to/gcc/lib64"`` to ensure linking works correctly.
 -* For clang:
 -  ``-DCMAKE_CXX_FLAGS=--gcc-toolchain=/path/to/gcc/folder``. This
 -  folder should contain ``include/c++``.
 +the compiler to support libstc++ version 5.1 or higher. To select a
 +particular libstdc++ library, provide the path to g++ with
 +``-DGMX_GPLUSPLUS_PATH=/path/to/g++``.
  
  On Windows with the Intel compiler, the MSVC standard library is used,
  and at least MSVC 2017 is required. Load the enviroment variables with
  vcvarsall.bat.
  
 -To build with any compiler and clang's libcxx standard library, use
 -``-DGMX_STDLIB_CXX_FLAGS=-stdlib=libc++
 --DGMX_STDLIB_LIBRARIES='-lc++abi -lc++'``.
 +To build with clang and llvm's libcxx standard library, use
 +``-DCMAKE_CXX_FLAGS=-stdlib=libc++``.
  
  If you are running on Mac OS X, the best option is the Intel
  compiler. Both clang and gcc will work, but they produce lower
@@@ -179,7 -187,8 +179,7 @@@ GPU suppor
  
  |Gromacs| has excellent support for NVIDIA GPUs supported via CUDA.
  On Linux, NVIDIA CUDA_ toolkit with minimum version |REQUIRED_CUDA_VERSION|
 -is required, and the latest version is strongly encouraged. Using
 -Microsoft MSVC compiler requires version 9.0. NVIDIA GPUs with at
 +is required, and the latest version is strongly encouraged. NVIDIA GPUs with at
  least NVIDIA compute capability |REQUIRED_CUDA_COMPUTE_CAPABILITY| are
  required. You are strongly recommended to
  get the latest CUDA version and driver that supports your hardware, but
@@@ -223,11 -232,6 +223,11 @@@ you will need to hav
    standard, and
  * wrapper compilers that will compile code using that library.
  
 +To compile with MPI set your compiler to the normal (non-MPI) compiler
 +and add ``-DGMX_MPI=on`` to the cmake options. It is possible to set
 +the compiler to the MPI compiler wrapper but it is neither necessary
 +nor recommended.
 +
  The |Gromacs| team recommends OpenMPI_ version
  1.6 (or higher), MPICH_ version 1.4.1 (or
  higher), or your hardware vendor's MPI installation. The most recent
@@@ -236,6 -240,10 +236,10 @@@ networks might depend on accelerations 
  library. LAM-MPI_ might work, but since it has
  been deprecated for years, it is not supported.
  
+ For example, depending on your actual MPI library, use ``cmake
+ -DCMAKE_C_COMPILER=mpicc -DCMAKE_CXX_COMPILER=mpicxx -DGMX_MPI=on``.
  CMake
  ^^^^^
  
@@@ -541,9 -549,9 +545,9 @@@ lead to performance loss, e.g. on Inte
     code will work on the  AMD Bulldozer and Piledriver processors, it is significantly less
     efficient than the ``AVX_128_FMA`` choice above - do not be fooled
     to assume that 256 is better than 128 in this case.
 -6. ``AVX2_128`` AMD Zen microarchitecture processors (2017);
 +6. ``AVX2_128`` AMD Zen/Zen2 and Hygon Dhyana microarchitecture processors;
     it will enable AVX2 with 3-way fused multiply-add instructions.
 -   While the Zen microarchitecture does support 256-bit AVX2 instructions,
 +   While these microarchitectures do support 256-bit AVX2 instructions,
     hence ``AVX2_256`` is also supported, 128-bit will generally be faster,
     in particular when the non-bonded tasks run on the CPU -- hence
     the default ``AVX2_128``. With GPU offload however ``AVX2_256``
@@@ -665,8 -673,8 +669,8 @@@ best-tested and supported of these. Lin
  CPUs also works well.
  
  Experimental support is available for compiling CUDA code, both for host and
 -device, using clang (version 3.9 or later).
 -A CUDA toolkit (>= v7.0) is still required but it is used only for GPU device code
 +device, using clang (version 6.0 or later).
 +A CUDA toolkit is still required but it is used only for GPU device code
  generation and to link against the CUDA runtime library.
  The clang CUDA support simplifies compilation and provides benefits for development
  (e.g. allows the use code sanitizers in CUDA host-code).
@@@ -679,7 -687,7 +683,7 @@@ virtual architecture code is always emb
  Note that this is mainly a developer-oriented feature and it is not recommended
  for production use as the performance can be significantly lower than that
  of code compiled with nvcc (and it has also received less testing).
 -However, note that with clang 5.0 the performance gap is significantly narrowed
 +However, note that since clang 5.0 the performance gap is only moderate
  (at the time of writing, about 20% slower GPU kernels), so this version
  could be considered in non performance-critical use-cases.
  
@@@ -1257,12 -1265,11 +1261,12 @@@ much everywhere, it is important that w
  it works because we have tested it. We do test on Linux, Windows, and
  Mac with a range of compilers and libraries for a range of our
  configuration options. Every commit in our git source code repository
 -is currently tested on x86 with a number of gcc versions ranging from 4.8.1
 -through 7, versions 16 and 18 of the Intel compiler, and Clang
 -versions 3.4 through 5. For this, we use a variety of GNU/Linux
 +is currently tested on x86 with a number of gcc versions ranging from 5.1
 +through 8.1, version 19 of the Intel compiler, and Clang
 +versions 3.6 through 7. For this, we use a variety of GNU/Linux
  flavors and versions as well as recent versions of Windows. Under
  Windows, we test both MSVC 2017 and version 16 of the Intel compiler.
 +Other compiler, library, and OS versions are tested less frequently.
  For details, you can
  have a look at the `continuous integration server used by GROMACS`_,
  which runs Jenkins_.
index ec95099ea587961130cdc416f1ad7b48bdf8bf4b,1b217139a2166b69638eedbfef785d7371a3d23d..85b14b943a40ff712203a0cc0a670e8549a388df
@@@ -992,6 -992,20 +992,6 @@@ simulations on distributed memory machi
  for parallelization of particle simulations," *J. Chem. Phys.*, **124**
  [18] 184109–184109 (2006).
  
 -.. raw:: html
 -
 -   </div>
 -
 -.. raw:: html
 -
 -   <div id="ref-Tironi95">
 -
 -.. _refTironi95:
 -
 -:sup:`71` I.G. Tironi, R. Sperb, P.E. Smith, and W.F. van Gunsteren, "A
 -generalized reaction field method for molecular dynamics simulations,"
 -*J. Chem. Phys.*, **102** 5451–5459 (1995).
 -
  .. raw:: html
  
     </div>
@@@ -2538,6 -2552,21 +2538,21 @@@ molecular dynamics simulations*, (2002)
  
     </div>
  
+ .. raw:: html
+    <div id="ref-GroenhofEwaldArtefact">
+ .. _refGroenhofEwaldArtefact:
+ :sup:`181` Hub, J. S., de Groot, B. L., Grubmüller, H., Groenhof, G.,
+ "Quantifying artifacts in Ewald simulations of inhomogeneous systems with a net charge,"
+ *J. Chem. Theory Comput.*, **10**, 381–390 (2014).
+ .. raw:: html
+    </div>
  .. raw:: html
  
     </div>
index 4d7f1ec9ef4dc79412bca1b344c3261eb0dccea9,a036d333c0ac8fab9bc3202b25726bdef9f6cb28..3ed1cf3c45354ee65c9d0b47e1ccdec45b6bec3b
@@@ -8,38 -8,18 +8,38 @@@ 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 2019
 -series and the 2018 series. In the latter, only highly conservative
 +Two versions of |Gromacs| are under active maintenance, the NEXT
 +series and the 2019 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 2018 ends, but we keep 2018 in the name so users understand how
 +year 2019 ends, but we keep 2018 in the name so users understand how
  up to date their version is. Such fixes will also be incorporated into
 -the 2019 release series, as appropriate. Around the time the 2020
 -release is made, the 2018 series will no longer be maintained.
 +the NEXT release series, as appropriate. Around the time the NEXT+1
 +release is made, the 2019 series will no longer be maintained.
  
  Where issue numbers are reported in these release notes, more details
  can be found at https://redmine.gromacs.org at that issue number.
  
 +|Gromacs| NEXT series
 +---------------------
 +
 +Major release
 +^^^^^^^^^^^^^
 +
 +.. toctree::
 +   :maxdepth: 1
 +
 +   2020/major/highlights
 +   2020/major/features
 +   2020/major/performance
 +   2020/major/tools
 +   2020/major/bugs-fixed
 +   2020/major/deprecated-functionality
 +   2020/major/removed-functionality
 +   2020/major/portability
 +   2020/major/miscellaneous
 +
 +
  |Gromacs| 2019 series
  ---------------------
  
@@@ -49,6 -29,8 +49,8 @@@ Patch release
  .. toctree::
     :maxdepth: 1
  
+    2019/2019.4
+    2019/2019.3
     2019/2019.2
     2019/2019.1
  
@@@ -78,6 -60,7 +80,7 @@@ Patch release
  .. toctree::
     :maxdepth: 1
  
+    2018/2018.7
     2018/2018.6
     2018/2018.5
     2018/2018.4
index 15b8b8aaa988f2b0abd9c4f6a71797f6effbdc59,137a2718de64b468844a9ea05bd304b648b53b80..8ae3921743ec42be9b1014437929645d4614fefb
@@@ -622,6 -622,16 +622,6 @@@ Electrostatic
        :mdp:`epsilon-rf`. The dielectric constant can be set to
        infinity by setting :mdp:`epsilon-rf` =0.
  
 -   .. mdp-value:: Generalized-Reaction-Field
 -
 -      Generalized reaction field with Coulomb cut-off
 -      :mdp:`rcoulomb`, where :mdp:`rlist` >= :mdp:`rcoulomb`. The
 -      dielectric constant beyond the cut-off is
 -      :mdp:`epsilon-rf`. The ionic strength is computed from the
 -      number of charged (*i.e.* with non zero charge) charge
 -      groups. The temperature for the GRF potential is set with
 -      :mdp:`ref-t`.
 -
     .. mdp-value:: Reaction-Field-zero
  
        In |Gromacs|, normal reaction-field electrostatics with
@@@ -1065,7 -1075,7 +1065,7 @@@ Temperature couplin
     integrators, the leap-frog :mdp-value:`integrator=md` integrator
     only supports 1. Data for the NH chain variables is not printed
     to the :ref:`edr` file by default, but can be turned on with the
 -   :mdp:`print-nose-hoover-chains` option.
 +   :mdp:`print-nose-hoover-chain-variables` option.
  
  .. mdp:: print-nose-hoover-chain-variables
  
@@@ -1217,8 -1227,8 +1217,8 @@@ Pressure couplin
  
        The reference coordinates for position restraints are not
        modified. Note that with this option the virial and pressure
-       will depend on the absolute positions of the reference
-       coordinates.
+       might be ill defined, see :ref:`here <reference-manual-position-restraints>`
+       for more details.
  
     .. mdp-value:: all
  
        one COM is used, even when there are multiple molecules with
        position restraints. For calculating the COM of the reference
        coordinates in the starting configuration, periodic boundary
-       conditions are not taken into account.
+       conditions are not taken into account. Note that with this option
+       the virial and pressure might be ill defined, see
+       :ref:`here <reference-manual-position-restraints>` for more details.
  
  
  Simulated annealing
@@@ -1907,7 -1919,7 +1909,7 @@@ AWH adaptive biasin
        multidimensional and is defined by mapping each dimension to a pull coordinate index.
        This is only allowed if :mdp-value:`pull-coord1-type=external-potential` and
        :mdp:`pull-coord1-potential-provider` = ``awh`` for the concerned pull coordinate
-       indices.
+       indices. Pull geometry 'direction-periodic' is not supported by AWH.
  
  .. mdp:: awh-potential
  
     (0.0) [nm] or [rad]
     Start value of the sampling interval along this dimension. The range of allowed
     values depends on the relevant pull geometry (see :mdp:`pull-coord1-geometry`).
-    For periodic geometries :mdp:`awh1-dim1-start` greater than :mdp:`awh1-dim1-end`
+    For dihedral geometries :mdp:`awh1-dim1-start` greater than :mdp:`awh1-dim1-end`
     is allowed. The interval will then wrap around from +period/2 to -period/2.
+    For the direction geometry, the dimension is made periodic when
+    the direction is along a box vector and covers more than 95%
+    of the box length. Note that one should not apply pressure coupling
+    along a periodic dimension.
  
  .. mdp:: awh1-dim1-end
  
     (0.0) [nm] or [rad]
     End value defining the sampling interval together with :mdp:`awh1-dim1-start`.
  
- .. mdp:: awh1-dim1-period
-    (0.0) [nm] or [rad]
-    The period of this reaction coordinate, use 0 when the coordinate is not periodic.
  .. mdp:: awh1-dim1-diffusion
  
     (10\ :sup:`-5`) [nm\ :sup:`2`/ps] or [rad\ :sup:`2`/ps]
index 20311c0476d264e604358c02b49660e7ca06303b,2faed6a90379cbd4f8ed7002563a6ec574408434..6ee02260f8e693bc94881d7f85b7c01cfced3be8
@@@ -173,7 -173,9 +173,9 @@@ Parallelization scheme
  There are multiple parallelization schemes available, therefore a simulation can be run on a
  given hardware with different choices of run configuration.
  
- Core level parallelization via SIMD: SSE, AVX, etc.
+ .. _intra-core-parallelization:
+ Intra-core parallelization via SIMD: SSE, AVX, etc.
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  
  One level of performance improvement available in |Gromacs| is through the use of
@@@ -196,13 -198,20 +198,20 @@@ Thus, you need to configure and compil
  By default, the build system will detect the highest supported
  acceleration of the host where the compilation is carried out. For cross-compiling for
  a machine with a different highest SIMD instructions set, in order to set the target acceleration,
- the ``-DGMX_SIMD`` CMake option can be used. For best performance always pick the highest
(latest) SIMD instruction set supported by the target architecture (and |Gromacs|). To use a single
+ the ``-DGMX_SIMD`` CMake option can be used.
+ To use a single
  installation on multiple different machines, it is convenient to compile the analysis tools with
  the lowest common SIMD instruction set (as these rely little on SIMD acceleration), but for best
- performance :ref:`mdrun <gmx mdrun>` should be compiled separately for each machine.
+ performance :ref:`mdrun <gmx mdrun>` should be compiled be compiled separately with the
+ highest (latest) ``native`` SIMD instruction set of the target architecture (supported by |Gromacs|).
  
- .. TODO add a note on AVX throttle and its impact on MPI-parallel and GPU accelerated runs
+ Recent Intel CPU architectures bring tradeoffs between the maximum clock frequency of the
+ CPU (ie. its speed), and the width of the SIMD instructions it executes (ie its throughput
+ at a given speed). In particular, the Intel ``Skylake`` and ``Cascade Lake`` processors
+ (e.g. Xeon SP Gold/Platinum), can offer better throughput when using narrower SIMD because
+ of the better clock frequency available. Consider building :ref:`mdrun <gmx mdrun>`
+ configured with ``GMX_SIMD=AVX2_256`` instead of ``GMX_SIMD=AVX512`` for better
+ performance in GPU accelerated or highly parallel MPI runs.
  
  Process(-or) level parallelization via OpenMP
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@@ -439,6 -448,10 +448,10 @@@ behavior
      default, 0, will start one thread on each available core.
      Alternatively, :ref:`mdrun <gmx mdrun>` will honor the appropriate system
      environment variable (e.g. ``OMP_NUM_THREADS``) if set.
+     Note that the maximum number of OpenMP threads (per rank) is,
+     for efficiency reasons, limited to 64. While it is rarely beneficial to use
+     a number of threads higher than this, the GMX_OPENMP_MAX_THREADS CMake variable
+     can be used to increase the limit.
  
  ``-npme``
      The total number of ranks to dedicate to the long-ranged
@@@ -758,13 -771,22 +771,13 @@@ cases
      performance. If available, using ``-bonded gpu`` is expected
      to improve the ability of DLB to maximize performance.
  
 -``-gcom``
 -    During the simulation :ref:`gmx mdrun` must communicate between all ranks to
 -    compute quantities such as kinetic energy. By default, this
 -    happens whenever plausible, and is influenced by a lot of
 -    :ref:`mdp options. <mdp-general>` The period between communication phases
 -    must be a multiple of :mdp:`nstlist`, and defaults to
 -    the minimum of :mdp:`nstcalcenergy` and :mdp:`nstlist`.
 -    ``mdrun -gcom`` sets the number of steps that must elapse between
 -    such communication phases, which can improve performance when
 -    running on a lot of ranks. Note that this means that _e.g._
 -    temperature coupling algorithms will
 -    effectively remain at constant energy until the next
 -    communication phase. :ref:`gmx mdrun` will always honor the
 -    setting of ``mdrun -gcom``, by changing :mdp:`nstcalcenergy`,
 -    :mdp:`nstenergy`, :mdp:`nstlog`, :mdp:`nsttcouple` and/or
 -    :mdp:`nstpcouple` if necessary.
 +During the simulation :ref:`gmx mdrun` must communicate between all
 +PP ranks to compute quantities such as kinetic energy for log file
 +reporting, or perhaps temperature coupling. By default, this happens
 +whenever necessary to honor several :ref:`mdp options <mdp-general>`,
 +so that the period between communication phases is the least common
 +denominator of :mdp:`nstlist`, :mdp:`nstcalcenergy`,
 +:mdp:`nsttcouple`, and :mdp:`nstpcouple`.
  
  Note that ``-tunepme`` has more effect when there is more than one
  :term:`node`, because the cost of communication for the PP and PME
@@@ -1060,7 -1082,7 +1073,7 @@@ Known limitation
  
  **Please note again the limitations outlined below!**
  
- - Only compilation with CUDA is supported.
+ - PME GPU offload is supported on NVIDIA hardware with CUDA and AMD hardware with OpenCL.
  
  - Only a PME order of 4 is supported on GPUs.
  
@@@ -1295,9 -1317,13 +1308,13 @@@ of 2. So it can be useful go through th
  * If you have GPUs that support either CUDA or OpenCL, use them.
  
    * Configure with ``-DGMX_GPU=ON`` (add ``-DGMX_USE_OPENCL=ON`` for OpenCL).
-   * For CUDA, use the newest CUDA availabe for your GPU to take advantage of the
+   * For CUDA, use the newest CUDA available for your GPU to take advantage of the
      latest performance enhancements.
    * Use a recent GPU driver.
+   * Make sure you use an :ref:`gmx mdrun` with ``GMX_SIMD`` appropriate for the CPU
+     architecture; the log file will contain a warning note if suboptimal setting is used.
+     However, prefer ``AVX2` over ``AVX512`` in GPU or highly parallel MPI runs (for more
+     information see the :ref:`intra-core parallelization information <intra-core-parallelization>`).
    * If compiling on a cluster head node, make sure that ``GMX_SIMD``
      is appropriate for the compute nodes.
  
@@@ -1316,8 -1342,8 +1333,8 @@@ Run setu
    :ref:`gmx tune_pme` can help automate this search.
  * For massively parallel runs (also ``gmx mdrun -multidir``), or with a slow
    network, global communication can become a bottleneck and you can reduce it
 -  with ``gmx mdrun -gcom`` (note that this does affect the frequency of
 -  temperature and pressure coupling).
 +  by choosing larger periods for algorithms such as temperature and
 +  pressure coupling).
  
  Checking and improving performance
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    list to do more non-bonded computation to keep energy drift constant).
  
    * If ``Comm. energies`` takes a lot of time (a note will be printed in the log
 -    file), increase nstcalcenergy or use ``mdrun -gcom``.
 +    file), increase nstcalcenergy.
    * If all communication takes a lot of time, you might be running on too many
      cores, or you could try running combined MPI/OpenMP parallelization with 2
      or 4 OpenMP threads per MPI process.
index aada5438d667487825ea941c7010c8909824b678,e8084f204e7c64e0beb51520b95c2925e26b63f1..a0a1379a0124f0e41e0f42a6bb10c4f95e7b0fe9
@@@ -67,26 -67,22 +67,28 @@@ if(${GMX_SIMD_ACTIVE} MATCHES "^(SSE|AV
      set(_fftw_simd_support_level "--enable-sse2")
  elseif(${GMX_SIMD_ACTIVE} MATCHES "^(SSE)")
      set(_fftw_simd_support_level "--enable-sse2")
 -elseif(${GMX_SIMD_ACTIVE} MATCHES "^(AVX)")
 +elseif(${GMX_SIMD_ACTIVE} MATCHES "^(AVX)" AND NOT ${GMX_SIMD_ACTIVE} MATCHES "^(AVX_512)")
      # Testing shows FFTW configured with --enable-sse2 --enable-avx is
      # slightly faster on most architectures than --enable-sse2 alone.
      # Support for --enable-avx2 was only added in 3.3.5, but
      # configuring with it is at worst a warning, even on an earlier
      # version.
 +    # On platforms capable of AVX512 where we are building with AVX2,
 +    # enabling AVX512 risks clock-throttling the entire mdrun if
 +    # fftw happens to pick up an AVX512 kernel (which is not unlikely
 +    # as fftw tuning is known to produce highly varying results).
 +    set(_fftw_simd_support_level --enable-sse2;--enable-avx;--enable-avx2)
 +elseif(${GMX_SIMD_ACTIVE} MATCHES "^(AVX_512)")
      # MSVC, GCC < 4.9, Clang < 3.9 do not support AVX-512, so
-     # we should not enable it.
+     # we should not enable it there. FFTW does not support clang with
+     # AVX-512, so we should not enable that either.
 -if(MSVC OR (CMAKE_COMPILER_IS_GNUCC AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.9.0) OR
 -   (CMAKE_C_COMPILER_ID MATCHES "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 3.9.0) OR
 -   (CMAKE_C_COMPILER_ID MATCHES "Clang" AND ${GMX_SIMD_ACTIVE} MATCHES "^(AVX_512)"))
 -    set(_fftw_simd_support_level --enable-sse2;--enable-avx;--enable-avx2)
 -else()
 -    set(_fftw_simd_support_level --enable-sse2;--enable-avx;--enable-avx2;--enable-avx512)
 -endif()
 +    if(MSVC OR (CMAKE_COMPILER_IS_GNUCC AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.9.0) OR
-        (CMAKE_C_COMPILER_ID MATCHES "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 3.9.0))
++        (CMAKE_C_COMPILER_ID MATCHES "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 3.9.0) OR
++        (CMAKE_C_COMPILER_ID MATCHES "Clang" AND ${GMX_SIMD_ACTIVE} MATCHES "^(AVX_512)"))
 +        set(_fftw_simd_support_level --enable-sse2;--enable-avx;--enable-avx2)
 +    else()
 +        set(_fftw_simd_support_level --enable-sse2;--enable-avx;--enable-avx2;--enable-avx512)
 +    endif()
  elseif(${GMX_SIMD_ACTIVE} MATCHES "^(VSX)")
      set(_fftw_simd_support_level --enable-vsx)
  endif()
@@@ -106,16 -102,47 +108,16 @@@ set(GMX_BUILD_OWN_FFTW_MD5 8aac833c943d
      "Expected MD5 hash for the file at GMX_BUILD_OWN_FFTW_URL")
  mark_as_advanced(GMX_BUILD_OWN_FFTW_URL GMX_BUILD_OWN_FFTW_MD5)
  
 -# ExternalProject at least up to CMake 3.0 prints a confusing error message if
 -# download fails when MD5 verification is enabled.  So we manage the download
 -# ourselves so that MD5 sum is not verified there, and then pass a local file
 -# as the URL to ExternalProject.  This way, ExternalProject still verifies the
 -# MD5 sum with a proper message if that fails.
 -# TODO remove this when we require CMake 3.5+
 -set(url "${GMX_BUILD_OWN_FFTW_URL}")
 -# Determine whether we are actually downloading (this matches the conditions in
 -# ExternalProject).  ExternalProject works as expected if passed a local file.
 -set(is_download TRUE)
 -if (IS_DIRECTORY "${url}" OR "${url}" MATCHES "^file://" OR NOT "${url}" MATCHES "^[a-z]+://")
 -    set(is_download FALSE)
 -endif()
 -if (is_download)
 -    # For simplicity, don't try to extract the file name from the URL, but use
 -    # a hard-coded value.
 -    set(remote_url "${GMX_BUILD_OWN_FFTW_URL}")
 -    set(local_path "${CMAKE_CURRENT_BINARY_DIR}/fftw.tar.gz")
 -    set(url ${local_path})
 -    # Write a script to do our own download step (mimics what ExternalProject
 -    # would do, but without MD5 sum verification at this step).
 -    set(download_script ${CMAKE_CURRENT_BINARY_DIR}/fftw-download.cmake)
 -    configure_file(fftw-download.cmake.cmakein ${download_script} @ONLY)
 -endif()
 -
  # The actual build target.
  set(EXTERNAL_FFTW_BUILD_TARGET fftwBuild)
  include(ExternalProject)
  ExternalProject_add(${EXTERNAL_FFTW_BUILD_TARGET}
 -        URL "${url}" URL_MD5 ${GMX_BUILD_OWN_FFTW_MD5}
 +        URL "${GMX_BUILD_OWN_FFTW_URL}"
 +        URL_MD5 ${GMX_BUILD_OWN_FFTW_MD5}
          CONFIGURE_COMMAND <SOURCE_DIR>/configure --prefix=<INSTALL_DIR> --libdir=<INSTALL_DIR>/lib --disable-fortran
          ${GMX_BUILD_OWN_FFTW_SHARED_FLAG} ${GMX_BUILD_OWN_FFTW_OPTIMIZATION_CONFIGURATION}
          ${GMX_BUILD_OWN_FFTW_PREC}
          ${GMX_BUILD_OWN_FFTW_TARGET_HOST})
 -# Add a custom step to do our own download if that is necessary.
 -if (is_download)
 -    ExternalProject_add_step(${EXTERNAL_FFTW_BUILD_TARGET} pre-download
 -            COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/fftw-download.cmake
 -            DEPENDERS download)
 -endif()
 -
  ExternalProject_get_property(${EXTERNAL_FFTW_BUILD_TARGET} INSTALL_DIR)
  
  string(REGEX REPLACE "fftw" "fftw3" FFTW_LIBNAME ${LOWERFFTW})
diff --combined src/gromacs/awh/awh.cpp
index cf3a1576793ec2d810a92bd29274a0ee93e27c03,00b1f87072640640341ee345d627b146b11d663b..3d035c3ff8ad6eab3ed8f9bfef30e72b79b43093
  #include "gromacs/fileio/enxio.h"
  #include "gromacs/gmxlib/network.h"
  #include "gromacs/math/units.h"
 -#include "gromacs/mdtypes/awh-history.h"
 -#include "gromacs/mdtypes/awh-params.h"
 +#include "gromacs/mdrunutility/multisim.h"
 +#include "gromacs/mdtypes/awh_history.h"
 +#include "gromacs/mdtypes/awh_params.h"
  #include "gromacs/mdtypes/commrec.h"
  #include "gromacs/mdtypes/forceoutput.h"
  #include "gromacs/mdtypes/inputrec.h"
 -#include "gromacs/mdtypes/pull-params.h"
 +#include "gromacs/mdtypes/pull_params.h"
  #include "gromacs/mdtypes/state.h"
  #include "gromacs/pbcutil/pbc.h"
  #include "gromacs/pulling/pull.h"
@@@ -163,6 -162,7 +163,7 @@@ Awh::Awh(FILE                 *fplog
              const AwhDimParams &awhDimParams      = awhBiasParams.dimParams[d];
              GMX_RELEASE_ASSERT(awhDimParams.eCoordProvider == eawhcoordproviderPULL, "Currently only the pull code is supported as coordinate provider");
              const t_pull_coord &pullCoord         = inputRecord.pull->coord[awhDimParams.coordIndex];
+             GMX_RELEASE_ASSERT(pullCoord.eGeom != epullgDIRPBC, "Pull geometry 'direction-periodic' is not supported by AWH");
              double              conversionFactor  = pull_coordinate_is_angletype(&pullCoord) ? DEG2RAD : 1;
              dimParams.emplace_back(conversionFactor, awhDimParams.forceConstant, beta);
  
@@@ -422,8 -422,8 +423,8 @@@ prepareAwhModule(FILE                 *
          GMX_THROW(InvalidInputError("AWH biasing does not support shell particles."));
      }
  
 -    auto awh = compat::make_unique<Awh>(fplog, inputRecord, commRecord, multiSimRecord, *inputRecord.awhParams,
 -                                        biasInitFilename, pull_work);
 +    auto awh = std::make_unique<Awh>(fplog, inputRecord, commRecord, multiSimRecord, *inputRecord.awhParams,
 +                                     biasInitFilename, pull_work);
  
      if (startingFromCheckpoint)
      {
index a02b54fc4ac00165174f2c3d5dca001ac7771847,0000000000000000000000000000000000000000..ea4e6e219875aad3218aa75f4ca43a56dda40831
mode 100644,000000..100644
--- /dev/null
@@@ -1,783 -1,0 +1,780 @@@
-  * \param[in] pull_params      Pull parameters.
-  * \param[in] coord_ind        Pull coordinate index.
-  * \param[in] box              Box vectors.
 +/*
 + * This file is part of the GROMACS molecular simulation package.
 + *
 + * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
 + * Copyright (c) 2001-2004, The GROMACS development team.
 + * Copyright (c) 2013,2014,2015,2016,2017,2018,2019, by the GROMACS development team, led by
 + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
 + * and including many others, as listed in the AUTHORS file in the
 + * top-level source directory and at http://www.gromacs.org.
 + *
 + * GROMACS is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public License
 + * as published by the Free Software Foundation; either version 2.1
 + * of the License, or (at your option) any later version.
 + *
 + * GROMACS is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * Lesser General Public License for more details.
 + *
 + * You should have received a copy of the GNU Lesser General Public
 + * License along with GROMACS; if not, see
 + * http://www.gnu.org/licenses, or write to the Free Software Foundation,
 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
 + *
 + * If you want to redistribute modifications to GROMACS, please
 + * consider that scientific software is very special. Version
 + * control is crucial - bugs must be traceable. We will be happy to
 + * consider code for inclusion in the official distribution, but
 + * derived work must not be called official GROMACS. Details are found
 + * in the README & COPYING files - if they are missing, get the
 + * official version at http://www.gromacs.org.
 + *
 + * To help us fund GROMACS development, we humbly ask that you cite
 + * the research papers on the package. Check out http://www.gromacs.org.
 + */
 +#include "gmxpre.h"
 +
 +#include "read_params.h"
 +
 +#include "gromacs/awh/awh.h"
 +#include "gromacs/fileio/readinp.h"
 +#include "gromacs/fileio/warninp.h"
 +#include "gromacs/math/units.h"
 +#include "gromacs/math/utilities.h"
 +#include "gromacs/math/vec.h"
 +#include "gromacs/mdtypes/awh_params.h"
 +#include "gromacs/mdtypes/inputrec.h"
 +#include "gromacs/mdtypes/md_enums.h"
 +#include "gromacs/mdtypes/pull_params.h"
 +#include "gromacs/pbcutil/pbc.h"
 +#include "gromacs/pulling/pull.h"
 +#include "gromacs/random/seed.h"
 +#include "gromacs/utility/cstringutil.h"
 +#include "gromacs/utility/fatalerror.h"
 +#include "gromacs/utility/smalloc.h"
 +#include "gromacs/utility/stringutil.h"
 +
 +#include "biasparams.h"
 +#include "biassharing.h"
 +
 +namespace gmx
 +{
 +
 +const char *eawhtarget_names[eawhtargetNR+1] = {
 +    "constant", "cutoff", "boltzmann", "local-boltzmann", nullptr
 +};
 +
 +const char *eawhgrowth_names[eawhgrowthNR+1] = {
 +    "exp-linear", "linear", nullptr
 +};
 +
 +const char *eawhpotential_names[eawhpotentialNR+1] = {
 +    "convolved", "umbrella", nullptr
 +};
 +
 +const char *eawhcoordprovider_names[eawhcoordproviderNR+1] = {
 +    "pull", nullptr
 +};
 +
 +/*! \brief
 + * Read parameters of an AWH bias dimension.
 + *
 + * \param[in,out] inp        Input file entries.
 + * \param[in] prefix         Prefix for dimension parameters.
 + * \param[in,out] dimParams  AWH dimensional parameters.
 + * \param[in] pull_params    Pull parameters.
 + * \param[in,out] wi         Struct for bookeeping warnings.
 + * \param[in] bComment       True if comments should be printed.
 + */
 +static void readDimParams(std::vector<t_inpfile> *inp, const std::string &prefix,
 +                          AwhDimParams *dimParams, const pull_params_t *pull_params,
 +                          warninp_t wi, bool bComment)
 +{
 +    std::string opt;
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "The provider of the reaction coordinate, currently only pull is supported");
 +    }
 +
 +    opt = prefix + "-coord-provider";
 +    dimParams->eCoordProvider = get_eeenum(inp, opt, eawhcoordprovider_names, wi);
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "The coordinate index for this dimension");
 +    }
 +    opt = prefix + "-coord-index";
 +    int coordIndexInput;
 +    coordIndexInput = get_eint(inp, opt, 1, wi);
 +    if (coordIndexInput <  1)
 +    {
 +        gmx_fatal(FARGS, "Failed to read a valid coordinate index for %s. "
 +                  "Note that the pull coordinate indexing starts at 1.", opt.c_str());
 +    }
 +
 +    /* The pull coordinate indices start at 1 in the input file, at 0 internally */
 +    dimParams->coordIndex = coordIndexInput - 1;
 +
 +    /* The pull settings need to be consistent with the AWH settings */
 +    if (!(pull_params->coord[dimParams->coordIndex].eType == epullEXTERNAL) )
 +    {
 +        gmx_fatal(FARGS, "AWH biasing can only be  applied to pull type %s",
 +                  EPULLTYPE(epullEXTERNAL));
 +    }
 +
 +    if (dimParams->coordIndex >= pull_params->ncoord)
 +    {
 +        gmx_fatal(FARGS, "The given AWH coordinate index (%d) is larger than the number of pull coordinates (%d)",
 +                  coordIndexInput, pull_params->ncoord);
 +    }
 +    if (pull_params->coord[dimParams->coordIndex].rate != 0)
 +    {
 +        auto message = formatString("Setting pull-coord%d-rate (%g) is incompatible with AWH biasing this coordinate",
 +                                    coordIndexInput, pull_params->coord[dimParams->coordIndex].rate);
 +        warning_error(wi, message);
 +    }
 +
 +    /* Grid params for each axis */
 +    int eGeom = pull_params->coord[dimParams->coordIndex].eGeom;
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "Start and end values for each coordinate dimension");
 +    }
 +
 +    opt               = prefix + "-start";
 +    dimParams->origin = get_ereal(inp, opt, 0., wi);
 +
 +    opt            = prefix + "-end";
 +    dimParams->end = get_ereal(inp, opt, 0., wi);
 +
 +    if (gmx_within_tol(dimParams->end - dimParams->origin, 0, GMX_REAL_EPS))
 +    {
 +        auto message = formatString("The given interval length given by %s-start (%g) and %s-end (%g) is zero. "
 +                                    "This will result in only one point along this axis in the coordinate value grid.",
 +                                    prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end);
 +        warning(wi, message);
 +    }
 +    /* Check that the requested interval is in allowed range */
 +    if (eGeom == epullgDIST)
 +    {
 +        if (dimParams->origin < 0 || dimParams->end < 0)
 +        {
 +            gmx_fatal(FARGS, "%s-start (%g) or %s-end (%g) set to a negative value. With pull geometry distance coordinate values are non-negative. "
 +                      "Perhaps you want to use geometry %s instead?",
 +                      prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end, EPULLGEOM(epullgDIR));
 +        }
 +    }
 +    else if (eGeom == epullgANGLE || eGeom == epullgANGLEAXIS)
 +    {
 +        if (dimParams->origin < 0 || dimParams->end > 180)
 +        {
 +            gmx_fatal(FARGS, "%s-start (%g) and %s-end (%g) are outside of the allowed range 0 to 180 deg for pull geometries %s and %s ",
 +                      prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end, EPULLGEOM(epullgANGLE), EPULLGEOM(epullgANGLEAXIS));
 +        }
 +    }
 +    else if (eGeom == epullgDIHEDRAL)
 +    {
 +        if (dimParams->origin < -180 || dimParams->end > 180)
 +        {
 +            gmx_fatal(FARGS, "%s-start (%g) and %s-end (%g) are outside of the allowed range -180 to 180 deg for pull geometry %s. ",
 +                      prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end, EPULLGEOM(epullgDIHEDRAL));
 +        }
 +    }
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "The force constant for this coordinate (kJ/mol/nm^2 or kJ/mol/rad^2)");
 +    }
 +    opt = prefix + "-force-constant";
 +    dimParams->forceConstant = get_ereal(inp, opt, 0, wi);
 +    if (dimParams->forceConstant <= 0)
 +    {
 +        warning_error(wi, "The force AWH bias force constant should be > 0");
 +    }
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "Estimated diffusion constant (nm^2/ps or rad^2/ps)");
 +    }
 +    opt                  = prefix + "-diffusion";
 +    dimParams->diffusion = get_ereal(inp, opt, 0, wi);
 +
 +    if (dimParams->diffusion <= 0)
 +    {
 +        const double diffusion_default = 1e-5;
 +        auto         message           = formatString
 +                ("%s not explicitly set by user. You can choose to use a default "
 +                "value (%g nm^2/ps or rad^2/ps) but this may very well be "
 +                "non-optimal for your system!", opt.c_str(), diffusion_default);
 +        warning(wi, message);
 +        dimParams->diffusion = diffusion_default;
 +    }
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "Diameter that needs to be sampled around a point before it is considered covered.");
 +    }
 +    opt = prefix + "-cover-diameter";
 +    dimParams->coverDiameter = get_ereal(inp, opt, 0, wi);
 +
 +    if (dimParams->coverDiameter < 0)
 +    {
 +        gmx_fatal(FARGS, "%s (%g) cannot be negative.",
 +                  opt.c_str(), dimParams->coverDiameter);
 +    }
 +}
 +
 +/*! \brief
 + * Check consistency of input at the AWH bias level.
 + *
 + * \param[in]     awhBiasParams  AWH bias parameters.
 + * \param[in,out] wi             Struct for bookkeeping warnings.
 + */
 +static void checkInputConsistencyAwhBias(const AwhBiasParams &awhBiasParams,
 +                                         warninp_t            wi)
 +{
 +    /* Covering diameter and sharing warning. */
 +    for (int d = 0; d < awhBiasParams.ndim; d++)
 +    {
 +        double coverDiameter = awhBiasParams.dimParams[d].coverDiameter;
 +        if (awhBiasParams.shareGroup <= 0 && coverDiameter > 0)
 +        {
 +            warning(wi, "The covering diameter is only relevant to set for bias sharing simulations.");
 +        }
 +    }
 +}
 +
 +/*! \brief
 + * Read parameters of an AWH bias.
 + *
 + * \param[in,out] inp            Input file entries.
 + * \param[in,out] awhBiasParams  AWH dimensional parameters.
 + * \param[in]     prefix         Prefix for bias parameters.
 + * \param[in]     ir             Input parameter struct.
 + * \param[in,out] wi             Struct for bookeeping warnings.
 + * \param[in]     bComment       True if comments should be printed.
 + */
 +static void read_bias_params(std::vector<t_inpfile> *inp, AwhBiasParams *awhBiasParams, const std::string &prefix,
 +                             const t_inputrec *ir, warninp_t wi, bool bComment)
 +{
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "Estimated initial PMF error (kJ/mol)");
 +    }
 +
 +    std::string opt = prefix + "-error-init";
 +    /* We allow using a default value here without warning (but warn the user if the diffusion constant is not set). */
 +    awhBiasParams->errorInitial = get_ereal(inp, opt, 10, wi);
 +    if (awhBiasParams->errorInitial <= 0)
 +    {
 +        gmx_fatal(FARGS, "%s needs to be > 0.", opt.c_str());
 +    }
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "Growth rate of the reference histogram determining the bias update size: exp-linear or linear");
 +    }
 +    opt                    = prefix + "-growth";
 +    awhBiasParams->eGrowth = get_eeenum(inp, opt, eawhgrowth_names, wi);
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "Start the simulation by equilibrating histogram towards the target distribution: no or yes");
 +    }
 +    opt = prefix + "-equilibrate-histogram";
 +    awhBiasParams->equilibrateHistogram = (get_eeenum(inp, opt, yesno_names, wi) != 0);
 +    if (awhBiasParams->equilibrateHistogram && awhBiasParams->eGrowth != eawhgrowthEXP_LINEAR)
 +    {
 +        auto message = formatString("Option %s will only have an effect for histogram growth type '%s'.",
 +                                    opt.c_str(), EAWHGROWTH(eawhgrowthEXP_LINEAR));
 +        warning(wi, message);
 +    }
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "Target distribution type: constant, cutoff, boltzmann or local-boltzmann");
 +    }
 +    opt                    = prefix + "-target";
 +    awhBiasParams->eTarget = get_eeenum(inp, opt, eawhtarget_names, wi);
 +
 +    if ((awhBiasParams->eTarget == eawhtargetLOCALBOLTZMANN) &&
 +        (awhBiasParams->eGrowth == eawhgrowthEXP_LINEAR))
 +    {
 +        auto message = formatString("Target type '%s' combined with histogram growth type '%s' is not "
 +                                    "expected to give stable bias updates. You probably want to use growth type "
 +                                    "'%s' instead.",
 +                                    EAWHTARGET(eawhtargetLOCALBOLTZMANN), EAWHGROWTH(eawhgrowthEXP_LINEAR),
 +                                    EAWHGROWTH(eawhgrowthLINEAR));
 +        warning(wi, message);
 +    }
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "Boltzmann beta scaling factor for target distribution types 'boltzmann' and 'boltzmann-local'");
 +    }
 +    opt = prefix + "-target-beta-scaling";
 +    awhBiasParams->targetBetaScaling = get_ereal(inp, opt, 0, wi);
 +
 +    switch (awhBiasParams->eTarget)
 +    {
 +        case eawhtargetBOLTZMANN:
 +        case eawhtargetLOCALBOLTZMANN:
 +            if (awhBiasParams->targetBetaScaling < 0 || awhBiasParams->targetBetaScaling > 1)
 +            {
 +                gmx_fatal(FARGS, "%s = %g is not useful for target type %s.",
 +                          opt.c_str(), awhBiasParams->targetBetaScaling, EAWHTARGET(awhBiasParams->eTarget));
 +            }
 +            break;
 +        default:
 +            if (awhBiasParams->targetBetaScaling != 0)
 +            {
 +                gmx_fatal(FARGS, "Value for %s (%g) set explicitly but will not be used for target type %s.",
 +                          opt.c_str(), awhBiasParams->targetBetaScaling, EAWHTARGET(awhBiasParams->eTarget));
 +            }
 +            break;
 +    }
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "Free energy cutoff value for target distribution type 'cutoff'");
 +    }
 +    opt = prefix + "-target-cutoff";
 +    awhBiasParams->targetCutoff = get_ereal(inp, opt, 0, wi);
 +
 +    switch (awhBiasParams->eTarget)
 +    {
 +        case eawhtargetCUTOFF:
 +            if (awhBiasParams->targetCutoff <= 0)
 +            {
 +                gmx_fatal(FARGS, "%s = %g is not useful for target type %s.",
 +                          opt.c_str(), awhBiasParams->targetCutoff, EAWHTARGET(awhBiasParams->eTarget));
 +            }
 +            break;
 +        default:
 +            if (awhBiasParams->targetCutoff != 0)
 +            {
 +                gmx_fatal(FARGS, "Value for %s (%g) set explicitly but will not be used for target type %s.",
 +                          opt.c_str(), awhBiasParams->targetCutoff, EAWHTARGET(awhBiasParams->eTarget));
 +            }
 +            break;
 +    }
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "Initialize PMF and target with user data: no or yes");
 +    }
 +    opt = prefix + "-user-data";
 +    awhBiasParams->bUserData = get_eeenum(inp, opt, yesno_names, wi);
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "Group index to share the bias with, 0 means not shared");
 +    }
 +    opt = prefix + "-share-group";
 +    awhBiasParams->shareGroup = get_eint(inp, opt, 0, wi);
 +    if (awhBiasParams->shareGroup < 0)
 +    {
 +        warning_error(wi, "AWH bias share-group should be >= 0");
 +    }
 +
 +    if (bComment)
 +    {
 +        printStringNoNewline(inp, "Dimensionality of the coordinate");
 +    }
 +    opt                 = prefix + "-ndim";
 +    awhBiasParams->ndim = get_eint(inp, opt, 0, wi);
 +
 +    if (awhBiasParams->ndim <= 0 ||
 +        awhBiasParams->ndim > c_biasMaxNumDim)
 +    {
 +        gmx_fatal(FARGS, "%s (%d) needs to be > 0 and at most %d\n", opt.c_str(),  awhBiasParams->ndim, c_biasMaxNumDim);
 +    }
 +    if (awhBiasParams->ndim > 2)
 +    {
 +        warning_note(wi, "For awh-dim > 2 the estimate based on the diffusion and the initial error is currently only a rough guideline."
 +                     " You should verify its usefulness for your system before production runs!");
 +    }
 +    snew(awhBiasParams->dimParams, awhBiasParams->ndim);
 +    for (int d = 0; d < awhBiasParams->ndim; d++)
 +    {
 +        bComment = bComment && d == 0;
 +        std::string prefixdim = prefix + formatString("-dim%d", d + 1);
 +        readDimParams(inp, prefixdim, &awhBiasParams->dimParams[d], ir->pull, wi, bComment);
 +    }
 +
 +    /* Check consistencies here that cannot be checked at read time at a lower level. */
 +    checkInputConsistencyAwhBias(*awhBiasParams, wi);
 +}
 +
 +/*! \brief
 + * Check consistency of input at the AWH level.
 + *
 + * \param[in]     awhParams  AWH parameters.
 + * \param[in,out] wi         Struct for bookkeeping warnings.
 + */
 +static void checkInputConsistencyAwh(const AwhParams &awhParams,
 +                                     warninp_t        wi)
 +{
 +    /* Each pull coord can map to at most 1 AWH coord.
 +     * Check that we have a shared bias when requesting multisim sharing.
 +     */
 +    bool haveSharedBias = false;
 +    for (int k1 = 0; k1 < awhParams.numBias; k1++)
 +    {
 +        const AwhBiasParams &awhBiasParams1 = awhParams.awhBiasParams[k1];
 +
 +        if (awhBiasParams1.shareGroup > 0)
 +        {
 +            haveSharedBias = true;
 +        }
 +
 +        /* k1 is the reference AWH, k2 is the AWH we compare with (can be equal to k1) */
 +        for (int k2 = k1; k2 < awhParams.numBias; k2++)
 +        {
 +            for (int d1 = 0; d1 < awhBiasParams1.ndim; d1++)
 +            {
 +                const AwhBiasParams &awhBiasParams2 = awhParams.awhBiasParams[k2];
 +
 +                /* d1 is the reference dimension of the reference AWH. d2 is the dim index of the AWH to compare with. */
 +                for (int d2 = 0; d2 < awhBiasParams2.ndim; d2++)
 +                {
 +                    /* Give an error if (d1, k1) is different from (d2, k2) but the pull coordinate is the same */
 +                    if ( (d1 != d2 || k1 != k2) && (awhBiasParams1.dimParams[d1].coordIndex == awhBiasParams2.dimParams[d2].coordIndex) )
 +                    {
 +                        char errormsg[STRLEN];
 +                        sprintf(errormsg, "One pull coordinate (%d) cannot be mapped to two separate AWH dimensions (awh%d-dim%d and awh%d-dim%d). "
 +                                "If this is really what you want to do you will have to duplicate this pull coordinate.",
 +                                awhBiasParams1.dimParams[d1].coordIndex + 1, k1 + 1, d1 + 1, k2 + 1, d2 + 1);
 +                        gmx_fatal(FARGS, "%s", errormsg);
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    if (awhParams.shareBiasMultisim && !haveSharedBias)
 +    {
 +        warning(wi, "Sharing of biases over multiple simulations is requested, but no bias is marked as shared (share-group > 0)");
 +    }
 +
 +    /* mdrun does not support this (yet), but will check again */
 +    if (haveBiasSharingWithinSimulation(awhParams))
 +    {
 +        warning(wi, "You have shared biases within a single simulation, but mdrun does not support this (yet)");
 +    }
 +}
 +
 +AwhParams *readAndCheckAwhParams(std::vector<t_inpfile> *inp, const t_inputrec *ir, warninp_t wi)
 +{
 +    AwhParams  *awhParams;
 +    snew(awhParams, 1);
 +    std::string opt;
 +
 +    /* Parameters common for all biases */
 +
 +    printStringNoNewline(inp, "The way to apply the biasing potential: convolved or umbrella");
 +    opt                   = "awh-potential";
 +    awhParams->ePotential = get_eeenum(inp, opt, eawhpotential_names, wi);
 +
 +    printStringNoNewline(inp, "The random seed used for sampling the umbrella center in the case of umbrella type potential");
 +    opt             = "awh-seed";
 +    awhParams->seed = get_eint(inp, opt, -1, wi);
 +    if (awhParams->seed == -1)
 +    {
 +        awhParams->seed = static_cast<int>(gmx::makeRandomSeed());
 +        fprintf(stderr, "Setting the AWH bias MC random seed to %" PRId64 "\n", awhParams->seed);
 +    }
 +
 +    printStringNoNewline(inp, "Data output interval in number of steps");
 +    opt               = "awh-nstout";
 +    awhParams->nstOut = get_eint(inp, opt, 100000, wi);
 +    if (awhParams->nstOut <= 0)
 +    {
 +        auto message = formatString("Not writing AWH output with AWH (%s = %d) does not make sense",
 +                                    opt.c_str(), awhParams->nstOut);
 +        warning_error(wi, message);
 +    }
 +    /* This restriction can be removed by changing a flag of print_ebin() */
 +    if (ir->nstenergy == 0 || awhParams->nstOut % ir->nstenergy != 0)
 +    {
 +        auto message = formatString("%s (%d) should be a multiple of nstenergy (%d)",
 +                                    opt.c_str(), awhParams->nstOut, ir->nstenergy);
 +        warning_error(wi, message);
 +    }
 +
 +    printStringNoNewline(inp, "Coordinate sampling interval in number of steps");
 +    opt = "awh-nstsample";
 +    awhParams->nstSampleCoord = get_eint(inp, opt, 10, wi);
 +
 +    printStringNoNewline(inp, "Free energy and bias update interval in number of samples");
 +    opt = "awh-nsamples-update";
 +    awhParams->numSamplesUpdateFreeEnergy = get_eint(inp, opt, 10, wi);
 +    if (awhParams->numSamplesUpdateFreeEnergy <= 0)
 +    {
 +        warning_error(wi, opt + " needs to be an integer > 0");
 +    }
 +
 +    printStringNoNewline(inp, "When true, biases with share-group>0 are shared between multiple simulations");
 +    opt = "awh-share-multisim";
 +    awhParams->shareBiasMultisim = (get_eeenum(inp, opt, yesno_names, wi) != 0);
 +
 +    printStringNoNewline(inp, "The number of independent AWH biases");
 +    opt                = "awh-nbias";
 +    awhParams->numBias = get_eint(inp, opt, 1, wi);
 +    if (awhParams->numBias <= 0)
 +    {
 +        gmx_fatal(FARGS, "%s needs to be an integer > 0", opt.c_str());
 +    }
 +
 +    /* Read the parameters specific to each AWH bias */
 +    snew(awhParams->awhBiasParams, awhParams->numBias);
 +
 +    for (int k = 0; k < awhParams->numBias; k++)
 +    {
 +        bool        bComment  = (k == 0);
 +        std::string prefixawh = formatString("awh%d", k + 1);
 +        read_bias_params(inp, &awhParams->awhBiasParams[k], prefixawh, ir, wi, bComment);
 +    }
 +
 +    /* Do a final consistency check before returning */
 +    checkInputConsistencyAwh(*awhParams, wi);
 +
 +    if (ir->init_step != 0)
 +    {
 +        warning_error(wi, "With AWH init-step should be 0");
 +    }
 +
 +    return awhParams;
 +}
 +
 +/*! \brief
 + * Gets the period of a pull coordinate.
 + *
- static double get_pull_coord_period(const pull_params_t *pull_params,
-                                     int                  coord_ind,
-                                     const matrix         box)
++ * \param[in] pullCoordParams   The parameters for the pull coordinate.
++ * \param[in] pbc               The PBC setup
++ * \param[in] intervalLength    The length of the AWH interval for this pull coordinate
 + * \returns the period (or 0 if not periodic).
 + */
-     double        period;
-     t_pull_coord *pcrd_params = &pull_params->coord[coord_ind];
-     if (pcrd_params->eGeom == epullgDIRPBC)
-     {
-         /* For direction periodic, we need the pull vector to be one of the box vectors
-            (or more generally I guess it could be an integer combination of boxvectors).
-            This boxvector should to be orthogonal to the (periodic) plane spanned by the other two box vectors.
-            Here we assume that the pull vector is either x, y or z.
-          * E.g. for pull vec = (1, 0, 0) the box vector tensor should look like:
-          * | x 0 0 |
-          * | 0 a c |
-          * | 0 b d |
-          *
-            The period is then given by the box length x.
-            Note: we make these checks here for AWH and not in pull because we allow pull to be more general.
-          */
-         int m_pullvec = -1, count_nonzeros = 0;
-         /* Check that pull vec has only one component and which component it is. This component gives the relevant box vector */
-         for (int m = 0; m < DIM; m++)
-         {
-             if (pcrd_params->vec[m] != 0)
-             {
-                 m_pullvec = m;
-                 count_nonzeros++;
-             }
-         }
-         if (count_nonzeros != 1)
-         {
-             gmx_fatal(FARGS, "For AWH biasing pull coordinate %d with pull geometry %s, the pull vector needs to be parallel to "
-                       "a box vector that is parallel to either the x, y or z axis and is orthogonal to the other box vectors.",
-                       coord_ind + 1, EPULLGEOM(epullgDIRPBC));
-         }
++static double get_pull_coord_period(const t_pull_coord &pullCoordParams,
++                                    const t_pbc        &pbc,
++                                    const real          intervalLength)
 +{
-         /* Check that there is a box vec parallel to pull vec and that this boxvec is orthogonal to the other box vectors */
-         for (int m = 0; m < DIM; m++)
++    double period = 0;
 +
-             for (int n = 0; n < DIM; n++)
++    if (pullCoordParams.eGeom == epullgDIR)
++    {
++        const real margin           = 0.001;
++        // Make dims periodic when the interval covers > 95%
++        const real periodicFraction = 0.95;
++
++        // Check if the pull direction is along a box vector
++        for (int dim = 0; dim < pbc.ndim_ePBC; dim++)
 +        {
-                 if ((n != m) && (n == m_pullvec || m == m_pullvec) && box[m][n] > 0)
++            const real boxLength    = norm(pbc.box[dim]);
++            const real innerProduct = iprod(pullCoordParams.vec, pbc.box[dim]);
++            if (innerProduct >= (1 - margin)*boxLength &&
++                innerProduct <= (1 + margin)*boxLength)
 +            {
-                     gmx_fatal(FARGS, "For AWH biasing pull coordinate %d with pull geometry %s, there needs to be a box vector parallel to the pull vector that is "
-                               "orthogonal to the other box vectors.",
-                               coord_ind + 1, EPULLGEOM(epullgDIRPBC));
++                GMX_RELEASE_ASSERT(intervalLength < (1 + margin)*boxLength,
++                                   "We have checked before that interval <= period");
++                if (intervalLength > periodicFraction*boxLength)
 +                {
-         /* If this box vector only has one component as we assumed the norm should be equal to the absolute value of that component */
-         period = static_cast<double>(norm(box[m_pullvec]));
++                    period = boxLength;
 +                }
 +            }
 +        }
-     else if (pcrd_params->eGeom == epullgDIHEDRAL)
 +    }
-     else
-     {
-         period = 0;
-     }
++    else if (pullCoordParams.eGeom == epullgDIHEDRAL)
 +    {
 +        /* The dihedral angle is periodic in -180 to 180 deg */
 +        period = 360;
 +    }
-                                 const matrix box,  int ePBC,
 +
 +    return period;
 +}
 +
 +/*! \brief
 + * Checks if the given interval is defined in the correct periodic interval.
 + *
 + * \param[in] origin      Start value of interval.
 + * \param[in] end         End value of interval.
 + * \param[in] period      Period (or 0 if not periodic).
 + * \returns true if the end point values are in the correct periodic interval.
 + */
 +static bool intervalIsInPeriodicInterval(double origin, double end, double period)
 +{
 +    return (period == 0) || (std::fabs(origin) <= 0.5*period && std::fabs(end) <= 0.5*period);
 +}
 +
 +/*! \brief
 + * Checks if a value is within an interval.
 + *
 + * \param[in] origin      Start value of interval.
 + * \param[in] end         End value of interval.
 + * \param[in] period      Period (or 0 if not periodic).
 + * \param[in] value       Value to check.
 + * \returns true if the value is within the interval.
 + */
 +static bool valueIsInInterval(double origin, double end, double period, double value)
 +{
 +    bool bIn_interval;
 +
 +    if (period > 0)
 +    {
 +        if (origin < end)
 +        {
 +            /* The interval closes within the periodic interval */
 +            bIn_interval = (value >= origin) && (value <= end);
 +        }
 +        else
 +        {
 +            /* The interval wraps around the periodic boundary */
 +            bIn_interval = ((value >= origin) && (value <= 0.5*period)) || ((value >= -0.5*period) && (value <= end));
 +        }
 +    }
 +    else
 +    {
 +        bIn_interval = (value >= origin) && (value <= end);
 +    }
 +
 +    return bIn_interval;
 +}
 +
 +/*! \brief
 + * Check if the starting configuration is consistent with the given interval.
 + *
 + * \param[in]     awhParams  AWH parameters.
 + * \param[in,out] wi         Struct for bookeeping warnings.
 + */
 +static void checkInputConsistencyInterval(const AwhParams *awhParams, warninp_t wi)
 +{
 +    for (int k = 0; k < awhParams->numBias; k++)
 +    {
 +        AwhBiasParams    *awhBiasParams  = &awhParams->awhBiasParams[k];
 +        for (int d = 0; d < awhBiasParams->ndim; d++)
 +        {
 +            AwhDimParams *dimParams      = &awhBiasParams->dimParams[d];
 +            int           coordIndex     = dimParams->coordIndex;
 +            double        origin         = dimParams->origin, end = dimParams->end, period = dimParams->period;
 +            double        coordValueInit = dimParams->coordValueInit;
 +
 +            if ((period == 0) && (origin > end))
 +            {
 +                gmx_fatal(FARGS, "For the non-periodic pull coordinates awh%d-dim%d-start (%f) cannot be larger than awh%d-dim%d-end (%f)",
 +                          k + 1, d + 1, origin, k + 1, d + 1, end);
 +            }
 +
 +            /* Currently we assume symmetric periodic intervals, meaning we use [-period/2, period/2] as the reference interval.
 +               Make sure the AWH interval is within this reference interval.
 +
 +               Note: we could fairly simply allow using a  more general interval (e.g. [x, x + period]) but it complicates
 +               things slightly and I don't see that there is a great need for it. It would also mean that the interval would
 +               depend on AWH input. Also, for dihedral angles you would always want the reference interval to be -180, +180,
 +               independent of AWH parameters.
 +             */
 +            if (!intervalIsInPeriodicInterval(origin, end, period))
 +            {
 +                gmx_fatal(FARGS, "When using AWH with periodic pull coordinate geometries awh%d-dim%d-start (%.8g) and "
 +                          "awh%d-dim%d-end (%.8g) should cover at most one period (%.8g) and take values in between "
 +                          "minus half a period and plus half a period, i.e. in the interval [%.8g, %.8g].",
 +                          k + 1, d + 1, origin, k + 1, d + 1, end,
 +                          period, -0.5*period, 0.5*period);
 +
 +            }
 +
 +            /* Warn if the pull initial coordinate value is not in the grid */
 +            if (!valueIsInInterval(origin, end, period, coordValueInit))
 +            {
 +                auto message = formatString
 +                        ("The initial coordinate value (%.8g) for pull coordinate index %d falls outside "
 +                        "of the sampling nterval awh%d-dim%d-start (%.8g) to awh%d-dim%d-end (%.8g). "
 +                        "This can lead to large initial forces pulling the coordinate towards the sampling interval.",
 +                        coordValueInit, coordIndex + 1, k + 1, d + 1, origin, k + 1, d + 1, end);
 +                warning(wi, message);
 +            }
 +        }
 +    }
 +}
 +
 +void setStateDependentAwhParams(AwhParams *awhParams,
 +                                const pull_params_t *pull_params, pull_t *pull_work,
-             AwhDimParams *dimParams = &awhBiasParams->dimParams[d];
++                                const matrix box, int ePBC, const tensor &compressibility,
 +                                const t_grpopts *inputrecGroupOptions, warninp_t wi)
 +{
 +    /* The temperature is not really state depenendent but is not known
 +     * when read_awhParams is called (in get ir).
 +     * It is known first after do_index has been called in grompp.cpp.
 +     */
 +    if (inputrecGroupOptions->ref_t == nullptr ||
 +        inputrecGroupOptions->ref_t[0] <= 0)
 +    {
 +        gmx_fatal(FARGS, "AWH biasing is only supported for temperatures > 0");
 +    }
 +    for (int i = 1; i < inputrecGroupOptions->ngtc; i++)
 +    {
 +        if (inputrecGroupOptions->ref_t[i] != inputrecGroupOptions->ref_t[0])
 +        {
 +            gmx_fatal(FARGS, "AWH biasing is currently only supported for identical temperatures for all temperature coupling groups");
 +        }
 +    }
 +
 +    t_pbc          pbc;
 +    set_pbc(&pbc, ePBC, box);
 +
 +    for (int k = 0; k < awhParams->numBias; k++)
 +    {
 +        AwhBiasParams *awhBiasParams = &awhParams->awhBiasParams[k];
 +        for (int d = 0; d < awhBiasParams->ndim; d++)
 +        {
-             /* The periodiciy of the AWH grid in certain cases depends on the simulation box */
-             dimParams->period = get_pull_coord_period(pull_params, dimParams->coordIndex, box);
++            AwhDimParams       *dimParams       = &awhBiasParams->dimParams[d];
++            const t_pull_coord &pullCoordParams = pull_params->coord[dimParams->coordIndex];
 +
-             t_pull_coord *pullCoord = &pull_params->coord[dimParams->coordIndex];
-             dimParams->coordValueInit *= pull_conversion_factor_internal2userinput(pullCoord);
++            if (pullCoordParams.eGeom == epullgDIRPBC)
++            {
++                gmx_fatal(FARGS, "AWH does not support pull geometry '%s'. "
++                          "If the maximum distance between the groups is always less than half the box size, "
++                          "you can use geometry '%s' instead.",
++                          EPULLGEOM(epullgDIRPBC),
++                          EPULLGEOM(epullgDIR));
++
++            }
++
++            dimParams->period = get_pull_coord_period(pullCoordParams, pbc, dimParams->end - dimParams->origin);
++            // We would like to check for scaling, but we don't have the full inputrec available here
++            if (dimParams->period > 0 && !(pullCoordParams.eGeom == epullgANGLE ||
++                                           pullCoordParams.eGeom == epullgDIHEDRAL))
++            {
++                bool coordIsScaled = false;
++                for (int d2 = 0; d2 < DIM; d2++)
++                {
++                    if (pullCoordParams.vec[d2] != 0 && norm2(compressibility[d2]) != 0)
++                    {
++                        coordIsScaled = true;
++                    }
++                }
++                if (coordIsScaled)
++                {
++                    std::string mesg = gmx::formatString("AWH dimension %d of bias %d is periodic with pull geometry '%s', "
++                                                         "while you should are applying pressure scaling to the corresponding box vector, this is not supported.",
++                                                         d + 1, k + 1, EPULLGEOM(pullCoordParams.eGeom));
++                    warning(wi, mesg.c_str());
++                }
++            }
 +
 +            /* The initial coordinate value, converted to external user units. */
 +            dimParams->coordValueInit =
 +                get_pull_coord_value(pull_work, dimParams->coordIndex, &pbc);
 +
++            dimParams->coordValueInit *= pull_conversion_factor_internal2userinput(&pullCoordParams);
 +        }
 +    }
 +    checkInputConsistencyInterval(awhParams, wi);
 +
 +    /* Register AWH as external potential with pull to check consistency. */
 +    Awh::registerAwhWithPull(*awhParams, pull_work);
 +}
 +
 +} // namespace gmx
index fa953974ea39ff96843b45a8c5556134d6190857,ef2f8eab06df98fe93567e300711818a65c37c0b..ef2f8eab06df98fe93567e300711818a65c37c0b
@@@ -78,6 -78,7 +78,7 @@@ AwhParams *readAndCheckAwhParams(std::v
   * \param[in,out] pull_work             Pull working struct to register AWH bias in.
   * \param[in]     box                   Box vectors.
   * \param[in]     ePBC                  Periodic boundary conditions enum.
+  * \param[in]     compressibility       Compressibility matrix for pressure coupling, pass all 0 without pressure coupling
   * \param[in]     inputrecGroupOptions  Parameters for atom groups.
   * \param[in,out] wi                    Struct for bookeeping warnings.
   *
@@@ -88,6 -89,7 +89,7 @@@ void setStateDependentAwhParams(AwhPara
                                  pull_t              *pull_work,
                                  const matrix         box,
                                  int                  ePBC,
+                                 const tensor        &compressibility,
                                  const t_grpopts     *inputrecGroupOptions,
                                  warninp_t            wi);
  
index 4ba6cc34846d2cf8ea4dd16d9ce07fe844799759,7889e0383f06deb70a1e601482acf35c784ac02a..40488725e73536f38ca5c281fc9b237310276768
  #include <cstring>
  
  #include <algorithm>
 +#include <memory>
  
 -#include "gromacs/compat/make_unique.h"
  #include "gromacs/domdec/collect.h"
  #include "gromacs/domdec/dlb.h"
  #include "gromacs/domdec/dlbtiming.h"
  #include "gromacs/domdec/domdec_network.h"
  #include "gromacs/domdec/ga2la.h"
 +#include "gromacs/domdec/options.h"
  #include "gromacs/domdec/partition.h"
  #include "gromacs/gmxlib/network.h"
  #include "gromacs/gmxlib/nrnb.h"
  #include "gromacs/gpu_utils/gpu_utils.h"
  #include "gromacs/hardware/hw_info.h"
 -#include "gromacs/listed-forces/manage-threading.h"
 +#include "gromacs/listed_forces/manage_threading.h"
  #include "gromacs/math/vec.h"
  #include "gromacs/math/vectypes.h"
  #include "gromacs/mdlib/calc_verletbuf.h"
  #include "gromacs/mdlib/constr.h"
  #include "gromacs/mdlib/constraintrange.h"
 -#include "gromacs/mdlib/mdrun.h"
  #include "gromacs/mdlib/updategroups.h"
  #include "gromacs/mdlib/vsite.h"
  #include "gromacs/mdtypes/commrec.h"
  #include "gromacs/mdtypes/inputrec.h"
 +#include "gromacs/mdtypes/mdrunoptions.h"
  #include "gromacs/mdtypes/state.h"
  #include "gromacs/pbcutil/ishift.h"
  #include "gromacs/pbcutil/pbc.h"
  #include "redistribute.h"
  #include "utility.h"
  
 +// TODO remove this when moving domdec into gmx namespace
 +using gmx::DdRankOrder;
 +using gmx::DlbOption;
 +using gmx::DomdecOptions;
 +
  static const char *edlbs_names[int(DlbState::nr)] = { "off", "auto", "locked", "on", "on" };
  
  /* The size per atom group of the cggl_flag buffer in gmx_domdec_comm_t */
@@@ -207,12 -201,6 +207,12 @@@ t_block *dd_charge_groups_global(gmx_do
      return &dd->comm->cgs_gl;
  }
  
 +gmx::ArrayRef<const gmx::RangePartitioning> getUpdateGroupingPerMoleculetype(const gmx_domdec_t &dd)
 +{
 +    GMX_RELEASE_ASSERT(dd.comm, "Need a valid dd.comm");
 +    return dd.comm->updateGroupingPerMoleculetype;
 +}
 +
  void dd_store_state(gmx_domdec_t *dd, t_state *state)
  {
      int i;
@@@ -320,6 -308,8 +320,6 @@@ void dd_move_x(gmx_domdec_
  
      comm = dd->comm;
  
 -    const gmx::RangePartitioning &atomGrouping = dd->atomGrouping();
 -
      nzone   = 1;
      nat_tot = comm->atomRanges.numHomeAtoms();
      for (int d = 0; d < dd->ndim; d++)
              int                        n          = 0;
              if (!bPBC)
              {
 -                for (int g : ind.index)
 +                for (int j : ind.index)
                  {
 -                    for (int j : atomGrouping.block(g))
 -                    {
 -                        sendBuffer[n] = x[j];
 -                        n++;
 -                    }
 +                    sendBuffer[n] = x[j];
 +                    n++;
                  }
              }
              else if (!bScrew)
              {
 -                for (int g : ind.index)
 +                for (int j : ind.index)
                  {
 -                    for (int j : atomGrouping.block(g))
 +                    /* We need to shift the coordinates */
 +                    for (int d = 0; d < DIM; d++)
                      {
 -                        /* We need to shift the coordinates */
 -                        for (int d = 0; d < DIM; d++)
 -                        {
 -                            sendBuffer[n][d] = x[j][d] + shift[d];
 -                        }
 -                        n++;
 +                        sendBuffer[n][d] = x[j][d] + shift[d];
                      }
 +                    n++;
                  }
              }
              else
              {
 -                for (int g : ind.index)
 +                for (int j : ind.index)
                  {
 -                    for (int j : atomGrouping.block(g))
 -                    {
 -                        /* Shift x */
 -                        sendBuffer[n][XX] = x[j][XX] + shift[XX];
 -                        /* Rotate y and z.
 -                         * This operation requires a special shift force
 -                         * treatment, which is performed in calc_vir.
 -                         */
 -                        sendBuffer[n][YY] = box[YY][YY] - x[j][YY];
 -                        sendBuffer[n][ZZ] = box[ZZ][ZZ] - x[j][ZZ];
 -                        n++;
 -                    }
 +                    /* Shift x */
 +                    sendBuffer[n][XX] = x[j][XX] + shift[XX];
 +                    /* Rotate y and z.
 +                     * This operation requires a special shift force
 +                     * treatment, which is performed in calc_vir.
 +                     */
 +                    sendBuffer[n][YY] = box[YY][YY] - x[j][YY];
 +                    sendBuffer[n][ZZ] = box[ZZ][ZZ] - x[j][ZZ];
 +                    n++;
                  }
              }
  
@@@ -422,6 -421,8 +422,6 @@@ void dd_move_f(gmx_domdec_
  
      comm = dd->comm;
  
 -    const gmx::RangePartitioning &atomGrouping = dd->atomGrouping();
 -
      nzone   = comm->zones.n/2;
      nat_tot = comm->atomRanges.end(DDAtomRanges::Type::Zones);
      for (int d = dd->ndim-1; d >= 0; d--)
              int n = 0;
              if (!bShiftForcesNeedPbc)
              {
 -                for (int g : ind.index)
 +                for (int j : ind.index)
                  {
 -                    for (int j : atomGrouping.block(g))
 +                    for (int d = 0; d < DIM; d++)
                      {
 -                        for (int d = 0; d < DIM; d++)
 -                        {
 -                            f[j][d] += receiveBuffer[n][d];
 -                        }
 -                        n++;
 +                        f[j][d] += receiveBuffer[n][d];
                      }
 +                    n++;
                  }
              }
              else if (!bScrew)
                  /* fshift should always be defined if this function is
                   * called when bShiftForcesNeedPbc is true */
                  assert(nullptr != fshift);
 -                for (int g : ind.index)
 +                for (int j : ind.index)
                  {
 -                    for (int j : atomGrouping.block(g))
 +                    for (int d = 0; d < DIM; d++)
                      {
 -                        for (int d = 0; d < DIM; d++)
 -                        {
 -                            f[j][d] += receiveBuffer[n][d];
 -                        }
 -                        /* Add this force to the shift force */
 -                        for (int d = 0; d < DIM; d++)
 -                        {
 -                            fshift[is][d] += receiveBuffer[n][d];
 -                        }
 -                        n++;
 +                        f[j][d] += receiveBuffer[n][d];
                      }
 +                    /* Add this force to the shift force */
 +                    for (int d = 0; d < DIM; d++)
 +                    {
 +                        fshift[is][d] += receiveBuffer[n][d];
 +                    }
 +                    n++;
                  }
              }
              else
              {
 -                for (int g : ind.index)
 +                for (int j : ind.index)
                  {
 -                    for (int j : atomGrouping.block(g))
 +                    /* Rotate the force */
 +                    f[j][XX] += receiveBuffer[n][XX];
 +                    f[j][YY] -= receiveBuffer[n][YY];
 +                    f[j][ZZ] -= receiveBuffer[n][ZZ];
 +                    if (fshift)
                      {
 -                        /* Rotate the force */
 -                        f[j][XX] += receiveBuffer[n][XX];
 -                        f[j][YY] -= receiveBuffer[n][YY];
 -                        f[j][ZZ] -= receiveBuffer[n][ZZ];
 -                        if (fshift)
 +                        /* Add this force to the shift force */
 +                        for (int d = 0; d < DIM; d++)
                          {
 -                            /* Add this force to the shift force */
 -                            for (int d = 0; d < DIM; d++)
 -                            {
 -                                fshift[is][d] += receiveBuffer[n][d];
 -                            }
 +                            fshift[is][d] += receiveBuffer[n][d];
                          }
 -                        n++;
                      }
 +                    n++;
                  }
              }
          }
@@@ -549,6 -559,8 +549,6 @@@ void dd_atom_spread_real(gmx_domdec_t *
  
      comm = dd->comm;
  
 -    const gmx::RangePartitioning &atomGrouping = dd->atomGrouping();
 -
      nzone   = 1;
      nat_tot = comm->atomRanges.numHomeAtoms();
      for (int d = 0; d < dd->ndim; d++)
              DDBufferAccess<gmx::RVec> sendBufferAccess(comm->rvecBuffer, ind.nsend[nzone + 1]);
              gmx::ArrayRef<real>       sendBuffer = realArrayRefFromRvecArrayRef(sendBufferAccess.buffer);
              int                       n          = 0;
 -            for (int g : ind.index)
 +            for (int j : ind.index)
              {
 -                for (int j : atomGrouping.block(g))
 -                {
 -                    sendBuffer[n++] = v[j];
 -                }
 +                sendBuffer[n++] = v[j];
              }
  
              DDBufferAccess<gmx::RVec> receiveBufferAccess(comm->rvecBuffer2, cd->receiveInPlace ? 0 : ind.nrecv[nzone + 1]);
@@@ -607,6 -622,8 +607,6 @@@ void dd_atom_sum_real(gmx_domdec_t *dd
  
      comm = dd->comm;
  
 -    const gmx::RangePartitioning &atomGrouping = dd->atomGrouping();
 -
      nzone   = comm->zones.n/2;
      nat_tot = comm->atomRanges.end(DDAtomRanges::Type::Zones);
      for (int d = dd->ndim-1; d >= 0; d--)
                         sendBuffer, receiveBuffer);
              /* Add the received forces */
              int n = 0;
 -            for (int g : ind.index)
 +            for (int j : ind.index)
              {
 -                for (int j : atomGrouping.block(g))
 -                {
 -                    v[j] += receiveBuffer[n];
 -                    n++;
 -                }
 +                v[j] += receiveBuffer[n];
 +                n++;
              }
          }
          nzone /= 2;
@@@ -667,7 -687,7 +667,7 @@@ real dd_cutoff_multibody(const gmx_domd
      comm = dd->comm;
  
      r = -1;
 -    if (comm->bInterCGBondeds)
 +    if (comm->haveInterDomainMultiBodyBondeds)
      {
          if (comm->cutoff_mbody > 0)
          {
@@@ -1147,7 -1167,7 +1147,7 @@@ static void make_load_communicator(gmx_
              if (dd->ci[dim] == dd->master_ci[dim])
              {
                  /* This is the root process of this row */
 -                cellsizes.rowMaster  = gmx::compat::make_unique<RowMaster>();
 +                cellsizes.rowMaster  = std::make_unique<RowMaster>();
  
                  RowMaster &rowMaster = *cellsizes.rowMaster;
                  rowMaster.cellFrac.resize(ddCellFractionBufferSize(dd, dim_ind));
@@@ -1240,7 -1260,7 +1240,7 @@@ static void make_load_communicators(gmx
          fprintf(debug, "Making load communicators\n");
      }
  
-     snew(dd->comm->load,          std::max(dd->ndim, 1));
+     dd->comm->load = new domdec_load_t[std::max(dd->ndim, 1)];
      snew(dd->comm->mpi_comm_load, std::max(dd->ndim, 1));
  
      if (dd->ndim == 0)
@@@ -1750,9 -1770,9 +1750,9 @@@ static void make_dd_communicators(cons
      /* We can not use DDMASTER(dd), because dd->masterrank is set later */
      if (MASTER(cr))
      {
 -        dd->ma = gmx::compat::make_unique<AtomDistribution>(dd->nc,
 -                                                            comm->cgs_gl.nr,
 -                                                            comm->cgs_gl.index[comm->cgs_gl.nr]);
 +        dd->ma = std::make_unique<AtomDistribution>(dd->nc,
 +                                                    comm->cgs_gl.nr,
 +                                                    comm->cgs_gl.index[comm->cgs_gl.nr]);
      }
  }
  
@@@ -1914,7 -1934,7 +1914,7 @@@ static DlbState forceDlbOffOrBail(DlbSt
   */
  static DlbState determineInitialDlbState(const gmx::MDLogger &mdlog,
                                           DlbOption dlbOption, gmx_bool bRecordLoad,
 -                                         const MdrunOptions &mdrunOptions,
 +                                         const gmx::MdrunOptions &mdrunOptions,
                                           const t_inputrec *ir)
  {
      DlbState dlbState = DlbState::offCanTurnOn;
@@@ -2086,10 -2106,10 +2086,10 @@@ static void setupUpdateGroups(const gmx
           */
          int homeAtomCountEstimate =  mtop.natoms/numMpiRanksTotal;
          comm->updateGroupsCog =
 -            gmx::compat::make_unique<gmx::UpdateGroupsCog>(mtop,
 -                                                           comm->updateGroupingPerMoleculetype,
 -                                                           maxReferenceTemperature(inputrec),
 -                                                           homeAtomCountEstimate);
 +            std::make_unique<gmx::UpdateGroupsCog>(mtop,
 +                                                   comm->updateGroupingPerMoleculetype,
 +                                                   maxReferenceTemperature(inputrec),
 +                                                   homeAtomCountEstimate);
  
          /* To use update groups, the large domain-to-domain cutoff distance
           * should be compatible with the box size.
  static void set_dd_limits_and_grid(const gmx::MDLogger &mdlog,
                                     t_commrec *cr, gmx_domdec_t *dd,
                                     const DomdecOptions &options,
 -                                   const MdrunOptions &mdrunOptions,
 +                                   const gmx::MdrunOptions &mdrunOptions,
                                     const gmx_mtop_t *mtop,
                                     const t_inputrec *ir,
                                     const matrix box,
      comm->bPMELoadBalDLBLimits = FALSE;
  
      /* Allocate the charge group/atom sorting struct */
 -    comm->sort = gmx::compat::make_unique<gmx_domdec_sort_t>();
 -
 -    comm->bCGs = (ncg_mtop(mtop) < mtop->natoms);
 +    comm->sort = std::make_unique<gmx_domdec_sort_t>();
  
      /* We need to decide on update groups early, as this affects communication distances */
      comm->useUpdateGroups = false;
          setupUpdateGroups(mdlog, *mtop, *ir, cutoffMargin, cr->nnodes, comm);
      }
  
 -    comm->bInterCGBondeds = ((ncg_mtop(mtop) > gmx_mtop_num_molecules(*mtop)) ||
 -                             mtop->bIntermolecularInteractions);
 -    if (comm->bInterCGBondeds)
 -    {
 -        comm->bInterCGMultiBody = (multi_body_bondeds_count(mtop) > 0);
 -    }
 -    else
 -    {
 -        comm->bInterCGMultiBody = FALSE;
 -    }
 +    // TODO: Check whether all bondeds are within update groups
 +    comm->haveInterDomainBondeds          = (mtop->natoms > gmx_mtop_num_molecules(*mtop) ||
 +                                             mtop->bIntermolecularInteractions);
 +    comm->haveInterDomainMultiBodyBondeds = (multi_body_bondeds_count(mtop) > 0);
  
      if (comm->useUpdateGroups)
      {
       *       Note that we would need to improve the pairlist buffer case.
       */
  
 -    if (comm->bInterCGBondeds)
 +    if (comm->haveInterDomainBondeds)
      {
          if (options.minimumCommunicationRange > 0)
          {
                             !isDlbDisabled(comm),
                             options.dlbScaling,
                             comm->cellsize_limit, comm->cutoff,
 -                           comm->bInterCGBondeds);
 +                           comm->haveInterDomainBondeds);
  
          if (dd->nc[XX] == 0)
          {
          comm->slb_frac[ZZ] = get_slb_frac(mdlog, "z", dd->nc[ZZ], options.cellSizeZ);
      }
  
 -    if (comm->bInterCGBondeds && comm->cutoff_mbody == 0)
 +    if (comm->haveInterDomainBondeds && comm->cutoff_mbody == 0)
      {
          if (comm->bBondComm || !isDlbDisabled(comm))
          {
@@@ -2626,15 -2654,18 +2626,15 @@@ static void writeSettings(gmx::TextWrit
          log->writeLine();
      }
  
 -    gmx_bool bInterCGVsites = count_intercg_vsites(mtop) != 0;
 +    const bool haveInterDomainVsites =
 +        (countInterUpdategroupVsites(*mtop, comm->updateGroupingPerMoleculetype) != 0);
  
 -    if (comm->bInterCGBondeds ||
 -        bInterCGVsites ||
 +    if (comm->haveInterDomainBondeds ||
 +        haveInterDomainVsites ||
          dd->splitConstraints || dd->splitSettles)
      {
          std::string decompUnits;
 -        if (comm->bCGs)
 -        {
 -            decompUnits = "charge groups";
 -        }
 -        else if (comm->useUpdateGroups)
 +        if (comm->useUpdateGroups)
          {
              decompUnits = "atom groups";
          }
              }
          }
  
 -        if (comm->bInterCGBondeds)
 +        if (comm->haveInterDomainBondeds)
          {
              log->writeLineFormatted("%40s  %-7s %6.3f nm",
                                      "two-body bonded interactions", "(-rdd)",
                                      "multi-body bonded interactions", "(-rdd)",
                                      (comm->bBondComm || isDlbOn(dd->comm)) ? comm->cutoff_mbody : std::min(comm->cutoff, limit));
          }
 -        if (bInterCGVsites)
 +        if (haveInterDomainVsites)
          {
              log->writeLineFormatted("%40s  %-7s %6.3f nm",
                                      "virtual site constructions", "(-rcon)", limit);
@@@ -2819,7 -2850,7 +2819,7 @@@ gmx_bool dd_bonded_molpbc(const gmx_dom
       * or we use domain decomposition for each periodic dimension,
       * we do not need to take pbc into account for the bonded interactions.
       */
 -    return (ePBC != epbcNONE && dd->comm->bInterCGBondeds &&
 +    return (ePBC != epbcNONE && dd->comm->haveInterDomainBondeds &&
              !(dd->nc[XX] > 1 &&
                dd->nc[YY] > 1 &&
                (dd->nc[ZZ] > 1 || ePBC == epbcXY)));
@@@ -2922,7 -2953,7 +2922,7 @@@ static void set_dd_envvar_options(cons
  gmx_domdec_t *init_domain_decomposition(const gmx::MDLogger           &mdlog,
                                          t_commrec                     *cr,
                                          const DomdecOptions           &options,
 -                                        const MdrunOptions            &mdrunOptions,
 +                                        const gmx::MdrunOptions       &mdrunOptions,
                                          const gmx_mtop_t              *mtop,
                                          const t_inputrec              *ir,
                                          const matrix                   box,
index 46a460ee1be0259af908a35019190739e905d13c,d6bc09c20266ce867b6d9a818600bab15a03121a..bd7b57f9aa91198bcb6942ef5333eb7dfbc2e154
@@@ -59,21 -59,27 +59,27 @@@ struct t_commrec
  
  struct BalanceRegion;
  
+ //! Indices to communicate in a dimension
  struct gmx_domdec_ind_t
  {
-     /* The numbers of charge groups to send and receive for each cell
-      * that requires communication, the last entry contains the total
+     //! @{
+     /*! \brief The numbers of charge groups to send and receive for each
+      * cell that requires communication, the last entry contains the total
       * number of atoms that needs to be communicated.
       */
-     int              nsend[DD_MAXIZONE+2];
-     int              nrecv[DD_MAXIZONE+2];
-     /* The charge groups to send */
+     int              nsend[DD_MAXIZONE+2] = {};
+     int              nrecv[DD_MAXIZONE+2] = {};
+     //! @}
+     //! The charge groups to send
      std::vector<int> index;
+     //! @{
      /* The atom range for non-in-place communication */
-     int              cell2at0[DD_MAXIZONE];
-     int              cell2at1[DD_MAXIZONE];
+     int              cell2at0[DD_MAXIZONE] = {};
+     int              cell2at1[DD_MAXIZONE] = {};
+     //! @}
  };
  
+ //! Things relating to index communication
  struct gmx_domdec_comm_dim_t
  {
      /* Returns the number of grid pulses (the number of domains in the halo along this dimension) */
          return ind.size();
      }
  
-     int                           np_dlb;         /* For dlb, for use with edlbAUTO          */
-     std::vector<gmx_domdec_ind_t> ind;            /* The indices to communicate, size np     */
-     bool                          receiveInPlace; /* Can we receive data in place?            */
+     /**< For dlb, for use with edlbAUTO          */
+     int                           np_dlb = 0;
+     /**< The indices to communicate, size np     */
+     std::vector<gmx_domdec_ind_t> ind;
+     /**< Can we receive data in place?            */
+     bool                          receiveInPlace = false;
  };
  
  /*! \brief Load balancing data along a dim used on the master rank of that dim */
@@@ -92,30 -101,45 +101,45 @@@ struct RowMaste
  {
      struct Bounds
      {
-         real cellFracLowerMax; /**< State var.: max lower bound., incl. neighbors */
-         real cellFracUpperMin; /**< State var.: min upper bound., incl. neighbors */
-         real boundMin;         /**< Temp. var.: lower limit for cell boundary */
-         real boundMax;         /**< Temp. var.: upper limit for cell boundary */
+         /**< State var.: max lower bound., incl. neighbors */
+         real cellFracLowerMax = 0;
+         /**< State var.: min upper bound., incl. neighbors */
+         real cellFracUpperMin = 0;
+         /**< Temp. var.: lower limit for cell boundary */
+         real boundMin = 0;
+         /**< Temp. var.: upper limit for cell boundary */
+         real boundMax = 0;
      };
  
-     std::vector<bool>   isCellMin;    /**< Temp. var.: is this cell size at the limit */
-     std::vector<real>   cellFrac;     /**< State var.: cell boundaries, box relative */
-     std::vector<real>   oldCellFrac;  /**< Temp. var.: old cell size */
-     std::vector<Bounds> bounds;       /**< Cell bounds */
-     bool                dlbIsLimited; /**< State var.: is DLB limited in this row */
-     std::vector<real>   buf_ncd;      /**< Temp. var.  */
+     /**< Temp. var.: is this cell size at the limit */
+     std::vector<bool>   isCellMin;
+     /**< State var.: cell boundaries, box relative */
+     std::vector<real>   cellFrac;
+     /**< Temp. var.: old cell size */
+     std::vector<real>   oldCellFrac;
+     /**< Cell bounds */
+     std::vector<Bounds> bounds;
+     /**< State var.: is DLB limited in this row */
+     bool                dlbIsLimited = false;
+     /**< Temp. var.  */
+     std::vector<real>   buf_ncd;
  };
  
  /*! \brief Struct for managing cell sizes with DLB along a dimension */
  struct DDCellsizesWithDlb
  {
-     /* Cell sizes for dynamic load balancing */
-     std::unique_ptr<RowMaster> rowMaster;    /**< Cell row root struct, only available on the first rank in a row */
-     std::vector<real>          fracRow;      /**< The cell sizes, in fractions, along a row, not available on the first rank in a row */
-     real                       fracLower;    /**< The lower corner, in fractions, in triclinic space */
-     real                       fracUpper;    /**< The upper corner, in fractions, in triclinic space */
-     real                       fracLowerMax; /**< The maximum lower corner among all our neighbors */
-     real                       fracUpperMin; /**< The minimum upper corner among all our neighbors */
+     /**< Cell row root struct, only available on the first rank in a row */
+     std::unique_ptr<RowMaster> rowMaster;
+     /**< The cell sizes, in fractions, along a row, not available on the first rank in a row */
+     std::vector<real>          fracRow;
+     /**< The lower corner, in fractions, in triclinic space */
+     real                       fracLower = 0;
+     /**< The upper corner, in fractions, in triclinic space */
+     real                       fracUpper = 0;
+     /**< The maximum lower corner among all our neighbors */
+     real                       fracLowerMax = 0;
+     /**< The minimum upper corner among all our neighbors */
+     real                       fracUpperMin = 0;
  };
  
  /*! \brief Struct for compute load commuication
   */
  typedef struct
  {
-     int    nload;     /**< The number of load recordings */
-     float *load;      /**< Scan of the sum of load over dimensions */
-     float  sum;       /**< The sum of the load over the ranks up to our current dimension */
-     float  max;       /**< The maximum over the ranks contributing to \p sum */
-     float  sum_m;     /**< Like \p sum, but takes the maximum when the load balancing is limited */
-     float  cvol_min;  /**< Minimum cell volume, relative to the box */
-     float  mdf;       /**< The PP time during which PME can overlap */
-     float  pme;       /**< The PME-only rank load */
-     int    flags;     /**< Bit flags that tell if DLB was limited, per dimension */
+     /**< The number of load recordings */
+     int    nload = 0;
+     /**< Scan of the sum of load over dimensions */
+     float *load = nullptr;
+     /**< The sum of the load over the ranks up to our current dimension */
+     float  sum = 0;
+     /**< The maximum over the ranks contributing to \p sum */
+     float  max = 0;
+     /**< Like \p sum, but takes the maximum when the load balancing is limited */
+     float  sum_m = 0;
+     /**< Minimum cell volume, relative to the box */
+     float  cvol_min = 0;
+     /**< The PP time during which PME can overlap */
+     float  mdf = 0;
+     /**< The PME-only rank load */
+     float  pme = 0;
+     /**< Bit flags that tell if DLB was limited, per dimension */
+     int    flags = 0;
  } domdec_load_t;
  
  /*! \brief Data needed to sort an atom to the desired location in the local state */
  typedef struct
  {
-     int  nsc;     /**< Neighborsearch grid cell index */
-     int  ind_gl;  /**< Global atom/charge group index */
-     int  ind;     /**< Local atom/charge group index */
+     /**< Neighborsearch grid cell index */
+     int  nsc = 0;
+     /**< Global atom/charge group index */
+     int  ind_gl = 0;
+     /**< Local atom/charge group index */
+     int  ind = 0;
  } gmx_cgsort_t;
  
  /*! \brief Temporary buffers for sorting atoms */
  typedef struct
  {
-     std::vector<gmx_cgsort_t> sorted;     /**< Sorted array of indices */
-     std::vector<gmx_cgsort_t> stationary; /**< Array of stationary atom/charge group indices */
-     std::vector<gmx_cgsort_t> moved;      /**< Array of moved atom/charge group indices */
-     std::vector<int>          intBuffer;  /**< Integer buffer for sorting */
+     /**< Sorted array of indices */
+     std::vector<gmx_cgsort_t> sorted;
+     /**< Array of stationary atom/charge group indices */
+     std::vector<gmx_cgsort_t> stationary;
+     /**< Array of moved atom/charge group indices */
+     std::vector<gmx_cgsort_t> moved;
+     /**< Integer buffer for sorting */
+     std::vector<int>          intBuffer;
  } gmx_domdec_sort_t;
  
  /*! \brief Manages atom ranges and order for the local state atom vectors */
@@@ -257,25 -297,40 +297,40 @@@ enum class DlbStat
  /*! \brief The PME domain decomposition for one dimension */
  typedef struct
  {
-     int      dim;       /**< The dimension */
-     gmx_bool dim_match; /**< Tells if DD and PME dims match */
-     int      nslab;     /**< The number of PME ranks/domains in this dimension */
-     real    *slb_dim_f; /**< Cell sizes for determining the PME comm. with SLB */
-     int     *pp_min;    /**< The minimum pp node location, size nslab */
-     int     *pp_max;    /**< The maximum pp node location, size nslab */
-     int      maxshift;  /**< The maximum shift for coordinate redistribution in PME */
+     /**< The dimension */
+     int      dim = 0;
+     /**< Tells if DD and PME dims match */
+     gmx_bool dim_match = false;
+     /**< The number of PME ranks/domains in this dimension */
+     int      nslab = 0;
+     /**< Cell sizes for determining the PME comm. with SLB */
+     real    *slb_dim_f = nullptr;
+     /**< The minimum pp node location, size nslab */
+     int     *pp_min = nullptr;
+     /**< The maximum pp node location, size nslab */
+     int     *pp_max = nullptr;
+     /**< The maximum shift for coordinate redistribution in PME */
+     int      maxshift = 0;
  } gmx_ddpme_t;
  
  struct gmx_ddzone_t
  {
-     real min0;    /* The minimum bottom of this zone                        */
-     real max1;    /* The maximum top of this zone                           */
-     real min1;    /* The minimum top of this zone                           */
-     real mch0;    /* The maximum bottom communicaton height for this zone   */
-     real mch1;    /* The maximum top communicaton height for this zone      */
-     real p1_0;    /* The bottom value of the first cell in this zone        */
-     real p1_1;    /* The top value of the first cell in this zone           */
-     real dataSet; /* Bool disguised as a real, 1 when the above data has been set. 0 otherwise */
+     /**< The minimum bottom of this zone                        */
+     real min0 = 0;
+     /**< The maximum top of this zone                           */
+     real max1 = 0;
+     /**< The minimum top of this zone                           */
+     real min1 = 0;
+     /**< The maximum bottom communicaton height for this zone   */
+     real mch0 = 0;
+     /**< The maximum top communicaton height for this zone      */
+     real mch1 = 0;
+     /**< The bottom value of the first cell in this zone        */
+     real p1_0 = 0;
+     /**< The top value of the first cell in this zone           */
+     real p1_1 = 0;
+     /**< Bool disguised as a real, 1 when the above data has been set. 0 otherwise */
+     real dataSet = 0;
  };
  
  /*! \brief The number of reals in gmx_ddzone_t */
@@@ -365,14 -420,19 +420,19 @@@ class DDBufferAcces
          gmx::ArrayRef<T>  buffer;    /**< The access to the memory buffer */
  };
  
- /*! brief Temporary buffer for setting up communiation over one pulse and all zones in the halo */
+ /*! \brief Temporary buffer for setting up communiation over one pulse and all zones in the halo */
  struct dd_comm_setup_work_t
  {
-     std::vector<int>       localAtomGroupBuffer; /**< The local atom group indices to send */
-     std::vector<int>       atomGroupBuffer;      /**< Buffer for collecting the global atom group indices to send */
-     std::vector<gmx::RVec> positionBuffer;       /**< Buffer for collecting the atom group positions to send */
-     int                    nat;                  /**< The number of atoms contained in the atom groups to send */
-     int                    nsend_zone;           /**< The number of atom groups to send for the last zone */
+     /**< The local atom group indices to send */
+     std::vector<int>       localAtomGroupBuffer;
+     /**< Buffer for collecting the global atom group indices to send */
+     std::vector<int>       atomGroupBuffer;
+     /**< Buffer for collecting the atom group positions to send */
+     std::vector<gmx::RVec> positionBuffer;
+     /**< The number of atoms contained in the atom groups to send */
+     int                    nat = 0;
+     /**< The number of atom groups to send for the last zone */
+     int                    nsend_zone = 0;
  };
  
  /*! \brief Struct for domain decomposition communication
  struct gmx_domdec_comm_t // NOLINT (clang-analyzer-optin.performance.Padding)
  {
      /* PME and Cartesian communicator stuff */
-     int         npmedecompdim;     /**< The number of decomposition dimensions for PME, 0: no PME */
-     int         npmenodes;         /**< The number of ranks doing PME (PP/PME or only PME) */
-     int         npmenodes_x;       /**< The number of PME ranks/domains along x */
-     int         npmenodes_y;       /**< The number of PME ranks/domains along y */
-     gmx_bool    bCartesianPP_PME;  /**< Use Cartesian communication between PP and PME ranks */
-     ivec        ntot;              /**< Cartesian grid for combinted PP+PME ranks */
-     int         cartpmedim;        /**< The number of dimensions for the PME setup that are Cartesian */
-     int        *pmenodes;          /**< The PME ranks, size npmenodes */
-     int        *ddindex2simnodeid; /**< The Cartesian index to sim rank conversion, used with bCartesianPP_PME */
-     gmx_ddpme_t ddpme[2];          /**< The 1D or 2D PME domain decomposition setup */
+     /**< The number of decomposition dimensions for PME, 0: no PME */
+     int         npmedecompdim = 0;
+     /**< The number of ranks doing PME (PP/PME or only PME) */
+     int         npmenodes = 0;
+     /**< The number of PME ranks/domains along x */
+     int         npmenodes_x = 0;
+     /**< The number of PME ranks/domains along y */
+     int         npmenodes_y = 0;
+     /**< Use Cartesian communication between PP and PME ranks */
+     gmx_bool    bCartesianPP_PME = false;
+     /**< Cartesian grid for combinted PP+PME ranks */
+     ivec        ntot = { };
+     /**< The number of dimensions for the PME setup that are Cartesian */
+     int         cartpmedim = 0;
+     /**< The PME ranks, size npmenodes */
+     int        *pmenodes = nullptr;
+     /**< The Cartesian index to sim rank conversion, used with bCartesianPP_PME */
+     int        *ddindex2simnodeid = nullptr;
+     /**< The 1D or 2D PME domain decomposition setup */
+     gmx_ddpme_t ddpme[2];
  
      /* The DD particle-particle nodes only */
-     gmx_bool bCartesianPP;        /**< Use a Cartesian communicator for PP */
-     int     *ddindex2ddnodeid;    /**< The Cartesian index to DD rank conversion, used with bCartesianPP */
+     /**< Use a Cartesian communicator for PP */
+     gmx_bool bCartesianPP = false;
+     /**< The Cartesian index to DD rank conversion, used with bCartesianPP */
+     int     *ddindex2ddnodeid = nullptr;
  
      /* The DLB state, used for reloading old states, during e.g. EM */
-     t_block cgs_gl;               /**< The global charge groups, this defined the DD state (except for the DLB state) */
+     /**< The global charge groups, this defined the DD state (except for the DLB state) */
+     t_block cgs_gl = { };
  
      /* Charge group / atom sorting */
-     std::unique_ptr<gmx_domdec_sort_t> sort; /**< Data structure for cg/atom sorting */
+     /**< Data structure for cg/atom sorting */
+     std::unique_ptr<gmx_domdec_sort_t> sort;
  
      //! True when update groups are used
-     bool                                  useUpdateGroups;
+     bool                                  useUpdateGroups = false;
      //!  Update atom grouping for each molecule type
      std::vector<gmx::RangePartitioning>   updateGroupingPerMoleculetype;
      //! Centers of mass of local update groups
      std::unique_ptr<gmx::UpdateGroupsCog> updateGroupsCog;
  
      /* Are there charge groups? */
-     bool haveInterDomainBondeds;          /**< Are there inter-domain bonded interactions? */
-     bool haveInterDomainMultiBodyBondeds; /**< Are there inter-domain multi-body interactions? */
 -    /**< True when there are charge groups */
 -    gmx_bool bCGs = false;
 -
 -    /**< Are there inter-cg bonded interactions? */
 -    gmx_bool bInterCGBondeds = false;
 -    /**< Are there inter-cg multi-body interactions? */
 -    gmx_bool bInterCGMultiBody = false;
++    bool haveInterDomainBondeds          = false; /**< Are there inter-domain bonded interactions? */
++    bool haveInterDomainMultiBodyBondeds = false; /**< Are there inter-domain multi-body interactions? */
  
      /* Data for the optional bonded interaction atom communication range */
-     gmx_bool  bBondComm;          /**< Only communicate atoms beyond the non-bonded cut-off when they are involved in bonded interactions with non-local atoms */
-     t_blocka *cglink;             /**< Links between cg's through bonded interactions */
-     char     *bLocalCG;           /**< Local cg availability, TODO: remove when group scheme is removed */
+     /**< Only communicate atoms beyond the non-bonded cut-off when they are involved in bonded interactions with non-local atoms */
+     gmx_bool  bBondComm = false;
+     /**< Links between cg's through bonded interactions */
+     t_blocka *cglink = nullptr;
+     /**< Local cg availability, TODO: remove when group scheme is removed */
+     char     *bLocalCG = nullptr;
  
      /* The DLB state, possible values are defined above */
      DlbState dlbState;
      /* With dlbState=DlbState::offCanTurnOn, should we check if to DLB on at the next DD? */
-     gmx_bool bCheckWhetherToTurnDlbOn;
+     gmx_bool bCheckWhetherToTurnDlbOn = false;
      /* The first DD count since we are running without DLB */
      int      ddPartioningCountFirstDlbOff = 0;
  
      /* Cell sizes for static load balancing, first index cartesian */
-     real **slb_frac;
+     real **slb_frac = nullptr;
  
      /* The width of the communicated boundaries */
-     real     cutoff_mbody;        /**< Cut-off for multi-body interactions, also 2-body bonded when \p cutoff_mody > \p cutoff */
-     real     cutoff;              /**< Cut-off for non-bonded/2-body interactions */
-     rvec     cellsize_min;        /**< The minimum guaranteed cell-size, Cartesian indexing */
-     rvec     cellsize_min_dlb;    /**< The minimum guaranteed cell-size with dlb=auto */
-     real     cellsize_limit;      /**< The lower limit for the DD cell size with DLB */
-     gmx_bool bVacDLBNoLimit;      /**< Effectively no NB cut-off limit with DLB for systems without PBC? */
+     /**< Cut-off for multi-body interactions, also 2-body bonded when \p cutoff_mody > \p cutoff */
+     real     cutoff_mbody = 0;
+     /**< Cut-off for non-bonded/2-body interactions */
+     real     cutoff = 0;
+     /**< The minimum guaranteed cell-size, Cartesian indexing */
+     rvec     cellsize_min = { };
+     /**< The minimum guaranteed cell-size with dlb=auto */
+     rvec     cellsize_min_dlb = { };
+     /**< The lower limit for the DD cell size with DLB */
+     real     cellsize_limit = 0;
+     /**< Effectively no NB cut-off limit with DLB for systems without PBC? */
+     gmx_bool bVacDLBNoLimit = false;
  
      /** With PME load balancing we set limits on DLB */
-     gmx_bool bPMELoadBalDLBLimits;
+     gmx_bool bPMELoadBalDLBLimits = false;
      /** DLB needs to take into account that we want to allow this maximum
       *  cut-off (for PME load balancing), this could limit cell boundaries.
       */
-     real PMELoadBal_max_cutoff;
+     real PMELoadBal_max_cutoff = 0;
  
-     ivec tric_dir;                /**< tric_dir from \p gmx_ddbox_t is only stored here because dd_get_ns_ranges needs it */
-     rvec box0;                    /**< box lower corner, required with dim's without pbc when avoiding communication */
-     rvec box_size;                /**< box size, required with dim's without pbc when avoiding communication */
+     /**< tric_dir from \p gmx_ddbox_t is only stored here because dd_get_ns_ranges needs it */
+     ivec tric_dir = { };
+     /**< box lower corner, required with dim's without pbc and -gcom */
+     rvec box0 = { };
+     /**< box size, required with dim's without pbc and -gcom */
+     rvec box_size = { };
  
-     rvec cell_x0;                 /**< The DD cell lower corner, in triclinic space */
-     rvec cell_x1;                 /**< The DD cell upper corner, in triclinic space */
+     /**< The DD cell lower corner, in triclinic space */
+     rvec cell_x0 = { };
+     /**< The DD cell upper corner, in triclinic space */
+     rvec cell_x1 = { };
  
-     rvec old_cell_x0;             /**< The old \p cell_x0, to check cg displacements */
-     rvec old_cell_x1;             /**< The old \p cell_x1, to check cg displacements */
+     /**< The old \p cell_x0, to check cg displacements */
+     rvec old_cell_x0 = { };
+     /**< The old \p cell_x1, to check cg displacements */
+     rvec old_cell_x1 = { };
  
      /** The communication setup and charge group boundaries for the zones */
      gmx_domdec_zones_t zones;
       * cell boundaries of neighboring cells for staggered grids when using
       * dynamic load balancing.
       */
-     gmx_ddzone_t zone_d1[2];          /**< Zone limits for dim 1 with staggered grids */
-     gmx_ddzone_t zone_d2[2][2];       /**< Zone limits for dim 2 with staggered grids */
+     /**< Zone limits for dim 1 with staggered grids */
+     gmx_ddzone_t zone_d1[2];
+     /**< Zone limits for dim 2 with staggered grids */
+     gmx_ddzone_t zone_d2[2][2];
  
      /** The coordinate/force communication setup and indices */
      gmx_domdec_comm_dim_t cd[DIM];
      /** The maximum number of cells to communicate with in one dimension */
-     int                   maxpulse;
+     int                   maxpulse = 0;
  
      /** Which cg distribution is stored on the master node,
       *  stored as DD partitioning call count.
       */
-     int64_t master_cg_ddp_count;
+     int64_t master_cg_ddp_count = 0;
  
      /** The number of cg's received from the direct neighbors */
-     int  zone_ncg1[DD_MAXZONE];
+     int  zone_ncg1[DD_MAXZONE] = {0};
  
      /** The atom ranges in the local state */
      DDAtomRanges atomRanges;
      DDBuffer<gmx::RVec> rvecBuffer;
  
      /* Temporary storage for thread parallel communication setup */
-     std::vector<dd_comm_setup_work_t> dth; /**< Thread-local work data */
+     /**< Thread-local work data */
+     std::vector<dd_comm_setup_work_t> dth;
  
      /* Communication buffer only used with multiple grid pulses */
-     DDBuffer<gmx::RVec> rvecBuffer2; /**< Another rvec comm. buffer */
+     /**< Another rvec comm. buffer */
+     DDBuffer<gmx::RVec> rvecBuffer2;
  
      /* Communication buffers for local redistribution */
-     std::array<std::vector<int>, DIM*2>       cggl_flag;  /**< Charge group flag comm. buffers */
-     std::array<std::vector<gmx::RVec>, DIM*2> cgcm_state; /**< Charge group center comm. buffers */
+     /**< Charge group flag comm. buffers */
+     std::array<std::vector<int>, DIM*2>       cggl_flag;
+     /**< Charge group center comm. buffers */
+     std::array<std::vector<gmx::RVec>, DIM*2> cgcm_state;
  
      /* Cell sizes for dynamic load balancing */
      std::vector<DDCellsizesWithDlb> cellsizesWithDlb;
  
      /* Stuff for load communication */
-     gmx_bool        bRecordLoad;         /**< Should we record the load */
-     domdec_load_t  *load;                /**< The recorded load data */
-     int             nrank_gpu_shared;    /**< The number of MPI ranks sharing the GPU our rank is using */
+     /**< Should we record the load */
+     gmx_bool        bRecordLoad = false;
+     /**< The recorded load data */
+     domdec_load_t  *load = nullptr;
+     /**< The number of MPI ranks sharing the GPU our rank is using */
+     int             nrank_gpu_shared = 0;
  #if GMX_MPI
-     MPI_Comm       *mpi_comm_load;       /**< The MPI load communicator */
-     MPI_Comm        mpi_comm_gpu_shared; /**< The MPI load communicator for ranks sharing a GPU */
+     /**< The MPI load communicator */
+     MPI_Comm       *mpi_comm_load = nullptr;
+     /**< The MPI load communicator for ranks sharing a GPU */
+     MPI_Comm        mpi_comm_gpu_shared;
  #endif
  
      /* Information for managing the dynamic load balancing */
-     int            dlb_scale_lim;      /**< Maximum DLB scaling per load balancing step in percent */
+     /**< Maximum DLB scaling per load balancing step in percent */
+     int            dlb_scale_lim = 0;
  
-     BalanceRegion *balanceRegion;      /**< Struct for timing the force load balancing region */
+     /**< Struct for timing the force load balancing region */
+     BalanceRegion *balanceRegion = nullptr;
  
      /* Cycle counters over nstlist steps */
-     float  cycl[ddCyclNr];             /**< Total cycles counted */
-     int    cycl_n[ddCyclNr];           /**< The number of cycle recordings */
-     float  cycl_max[ddCyclNr];         /**< The maximum cycle count */
+     /**< Total cycles counted */
+     float  cycl[ddCyclNr] = { };
+     /**< The number of cycle recordings */
+     int    cycl_n[ddCyclNr] = { };
+     /**< The maximum cycle count */
+     float  cycl_max[ddCyclNr] = { };
      /** Flop counter (0=no,1=yes,2=with (eFlop-1)*5% noise */
-     int    eFlop;
-     double flop;                       /**< Total flops counted */
-     int    flop_n;                     /**< The number of flop recordings */
+     int    eFlop = 0;
+     /**< Total flops counted */
+     double flop = 0.0;
+     /**< The number of flop recordings */
+     int    flop_n = 0;
      /** How many times did we have load measurements */
-     int    n_load_have;
+     int    n_load_have = 0;
      /** How many times have we collected the load measurements */
-     int    n_load_collect;
+     int    n_load_collect = 0;
  
      /* Cycle count history for DLB checks */
-     float       cyclesPerStepBeforeDLB;     /**< The averaged cycles per step over the last nstlist step before turning on DLB */
-     float       cyclesPerStepDlbExpAverage; /**< The running average of the cycles per step during DLB */
-     bool        haveTurnedOffDlb;           /**< Have we turned off DLB (after turning DLB on)? */
-     int64_t     dlbSlowerPartitioningCount; /**< The DD step at which we last measured that DLB off was faster than DLB on, 0 if there was no such step */
+     /**< The averaged cycles per step over the last nstlist step before turning on DLB */
+     float       cyclesPerStepBeforeDLB = 0;
+     /**< The running average of the cycles per step during DLB */
+     float       cyclesPerStepDlbExpAverage = 0;
+     /**< Have we turned off DLB (after turning DLB on)? */
+     bool        haveTurnedOffDlb = false;
+     /**< The DD step at which we last measured that DLB off was faster than DLB on, 0 if there was no such step */
+     int64_t     dlbSlowerPartitioningCount = 0;
  
      /* Statistics for atoms */
-     double sum_nat[static_cast<int>(DDAtomRanges::Type::Number)]; /**< The atoms per range, summed over the steps */
+     /**< The atoms per range, summed over the steps */
+     double sum_nat[static_cast<int>(DDAtomRanges::Type::Number)] = { };
  
      /* Statistics for calls and times */
-     int    ndecomp;                    /**< The number of partioning calls */
-     int    nload;                      /**< The number of load recordings */
-     double load_step;                  /**< Total MD step time */
-     double load_sum;                   /**< Total PP force time */
-     double load_max;                   /**< Max \p load_sum over the ranks */
-     ivec   load_lim;                   /**< Was load balancing limited, per DD dim */
-     double load_mdf;                   /**< Total time on PP done during PME overlap time */
-     double load_pme;                   /**< Total time on our PME-only rank */
+     /**< The number of partioning calls */
+     int    ndecomp = 0;
+     /**< The number of load recordings */
+     int    nload = 0;
+     /**< Total MD step time */
+     double load_step = 0.0;
+     /**< Total PP force time */
+     double load_sum = 0.0;
+     /**< Max \p load_sum over the ranks */
+     double load_max = 0.0;
+     /**< Was load balancing limited, per DD dim */
+     ivec   load_lim = { };
+     /**< Total time on PP done during PME overlap time */
+     double load_mdf = 0.0;
+     /**< Total time on our PME-only rank */
+     double load_pme = 0.0;
  
      /** The last partition step */
-     int64_t partition_step;
+     int64_t partition_step = 0;
  
      /* Debugging */
-     int  nstDDDump;                    /**< Step interval for dumping the local+non-local atoms to pdb */
-     int  nstDDDumpGrid;                /**< Step interval for duming the DD grid to pdb */
-     int  DD_debug;                     /**< DD debug print level: 0, 1, 2 */
+     /**< Step interval for dumping the local+non-local atoms to pdb */
+     int  nstDDDump = 0;
+     /**< Step interval for duming the DD grid to pdb */
+     int  nstDDDumpGrid = 0;
+     /**< DD debug print level: 0, 1, 2 */
+     int  DD_debug = 0;
  };
  
  /*! \brief DD zone permutation
index 766089b5b328c61def2be54cc69aa2077c44ad97,01bb6f774f61f20c8d86ec557b025b5eace67124..4121a84eb5abf767a2bf185d85d54672cbdee2c3
@@@ -76,37 -76,48 +76,48 @@@ class LocalAtomSetManager
  }
  
  typedef struct {
-     int  j0;     /* j-zone start               */
-     int  j1;     /* j-zone end                 */
-     int  cg1;    /* i-charge-group end         */
-     int  jcg0;   /* j-charge-group start       */
-     int  jcg1;   /* j-charge-group end         */
-     ivec shift0; /* Minimum shifts to consider */
-     ivec shift1; /* Maximum shifts to consider */
+     /* j-zone start               */
+     int  j0 = 0;
+     /* j-zone end                 */
+     int  j1 = 0;
+     /* i-charge-group end         */
+     int  cg1 = 0;
+     /* j-charge-group start       */
+     int  jcg0 = 0;
+     /* j-charge-group end         */
+     int  jcg1 = 0;
+     /* Minimum shifts to consider */
+     ivec shift0 = { };
+     /* Maximum shifts to consider */
+     ivec shift1 = { };
  } gmx_domdec_ns_ranges_t;
  
  typedef struct {
-     rvec x0;     /* Zone lower corner in triclinic coordinates         */
-     rvec x1;     /* Zone upper corner in triclinic coordinates         */
-     rvec bb_x0;  /* Zone bounding box lower corner in Cartesian coords */
-     rvec bb_x1;  /* Zone bounding box upper corner in Cartesian coords */
+     /* Zone lower corner in triclinic coordinates         */
+     rvec x0 = { };
+     /* Zone upper corner in triclinic coordinates         */
+     rvec x1 = { };
+     /* Zone bounding box lower corner in Cartesian coords */
+     rvec bb_x0 = { };
+     /* Zone bounding box upper corner in Cartesian coords */
+     rvec bb_x1 = { };
  } gmx_domdec_zone_size_t;
  
  struct gmx_domdec_zones_t {
      /* The number of zones including the home zone */
-     int                    n;
+     int                    n = 0;
      /* The shift of the zones with respect to the home zone */
-     ivec                   shift[DD_MAXZONE];
+     ivec                   shift[DD_MAXZONE] = { };
      /* The charge group boundaries for the zones */
-     int                    cg_range[DD_MAXZONE+1];
+     int                    cg_range[DD_MAXZONE+1] = { };
      /* The number of neighbor search zones with i-particles */
-     int                    nizone;
+     int                    nizone = 0;
      /* The neighbor search charge group ranges for each i-zone */
      gmx_domdec_ns_ranges_t izone[DD_MAXIZONE];
      /* Boundaries of the zones */
      gmx_domdec_zone_size_t size[DD_MAXZONE];
      /* The cg density of the home zone */
-     real                   dens_zone0;
+     real                   dens_zone0 = 0;
  };
  
  struct gmx_ddbox_t {
@@@ -195,6 -206,14 +206,6 @@@ struct gmx_domdec_t { //NOLINT(clang-an
      int                           ncg_home = 0;
      /* Global atom group indices for the home and all non-home groups */
      std::vector<int>              globalAtomGroupIndices;
 -    /* The atom groups for the home and all non-home groups, todo: make private */
 -    gmx::RangePartitioning        atomGrouping_;
 -    const gmx::RangePartitioning &atomGrouping() const
 -    {
 -        return atomGrouping_;
 -    }
 -    /* Local atom to local atom-group index, only used for checking bondeds */
 -    std::vector<int> localAtomGroupFromAtom;
  
      /* Index from the local atoms to the global atoms, covers home and received zones */
      std::vector<int> globalAtomIndices;
index 1a9cc81276e72070ae6dfd012b621969a8811db8,c5f314b0b3a6ea955a8c7f9fcf1775135d083afc..39197ecaf3d8078b3e830d850f44b125c687a91c
@@@ -83,7 -83,7 +83,7 @@@
  #include <list>
  
  #include "gromacs/domdec/domdec.h"
 -#include "gromacs/ewald/ewald-utils.h"
 +#include "gromacs/ewald/ewald_utils.h"
  #include "gromacs/fft/parallel_3dfft.h"
  #include "gromacs/fileio/pdbio.h"
  #include "gromacs/gmxlib/network.h"
  #include "gromacs/utility/stringutil.h"
  #include "gromacs/utility/unique_cptr.h"
  
 -#include "calculate-spline-moduli.h"
 -#include "pme-gather.h"
 -#include "pme-gpu-internal.h"
 -#include "pme-grid.h"
 -#include "pme-internal.h"
 -#include "pme-redistribute.h"
 -#include "pme-solve.h"
 -#include "pme-spline-work.h"
 -#include "pme-spread.h"
 +#include "calculate_spline_moduli.h"
 +#include "pme_gather.h"
 +#include "pme_gpu_internal.h"
 +#include "pme_grid.h"
 +#include "pme_internal.h"
 +#include "pme_redistribute.h"
 +#include "pme_solve.h"
 +#include "pme_spline_work.h"
 +#include "pme_spread.h"
  
  /*! \brief Help build a descriptive message in \c error if there are
   * \c errorReasons why PME on GPU is not supported.
@@@ -176,6 -176,9 +176,9 @@@ bool pme_gpu_supports_hardware(const gm
          {
              errorReasons.emplace_back("non-AMD devices");
          }
+ #ifdef __APPLE__
+         errorReasons.emplace_back("Apple OS X operating system");
+ #endif
      }
      return addMessageIfNotSupported(errorReasons, error);
  }
@@@ -202,6 -205,10 +205,6 @@@ bool pme_gpu_supports_input(const t_inp
      {
          errorReasons.emplace_back("Lennard-Jones PME");
      }
 -    if (ir.cutoff_scheme == ecutsGROUP)
 -    {
 -        errorReasons.emplace_back("group cutoff scheme");
 -    }
      if (!EI_DYNAMICS(ir.eI))
      {
          errorReasons.emplace_back("not a dynamical integrator");
@@@ -268,7 -275,7 +271,7 @@@ gmx::PinningPolicy pme_get_pinning_poli
  const int gmxCacheLineSize = 64;
  
  //! Set up coordinate communication
 -static void setup_coordinate_communication(pme_atomcomm_t *atc)
 +static void setup_coordinate_communication(PmeAtomComm *atc)
  {
      int nslab, n, i;
      int fw, bw;
          bw = (atc->nodeid - i + nslab) % nslab;
          if (n < nslab - 1)
          {
 -            atc->node_dest[n] = fw;
 -            atc->node_src[n]  = bw;
 +            atc->slabCommSetup[n].node_dest = fw;
 +            atc->slabCommSetup[n].node_src  = bw;
              n++;
          }
          if (n < nslab - 1)
          {
 -            atc->node_dest[n] = bw;
 -            atc->node_src[n]  = fw;
 +            atc->slabCommSetup[n].node_dest = bw;
 +            atc->slabCommSetup[n].node_src  = fw;
              n++;
          }
      }
@@@ -319,79 -326,133 +322,79 @@@ static double estimate_pme_load_imbalan
      return (n1 + n2 + 3*n3)/static_cast<double>(6*pme->nkx*pme->nky*pme->nkz);
  }
  
 -/*! \brief Initialize atom communication data structure */
 -static void init_atomcomm(struct gmx_pme_t *pme, pme_atomcomm_t *atc,
 -                          int dimind, gmx_bool bSpread)
 +#ifndef DOXYGEN
 +
 +PmeAtomComm::PmeAtomComm(MPI_Comm   PmeMpiCommunicator,
 +                         const int  numThreads,
 +                         const int  pmeOrder,
 +                         const int  dimIndex,
 +                         const bool doSpread) :
 +    dimind(dimIndex),
 +    bSpread(doSpread),
 +    pme_order(pmeOrder),
 +    nthread(numThreads),
 +    spline(nthread)
  {
 -    int thread;
 -
 -    atc->dimind    = dimind;
 -    atc->nslab     = 1;
 -    atc->nodeid    = 0;
 -    atc->pd_nalloc = 0;
 -#if GMX_MPI
 -    if (pme->nnodes > 1)
 +    if (PmeMpiCommunicator != MPI_COMM_NULL)
      {
 -        atc->mpi_comm = pme->mpi_comm_d[dimind];
 -        MPI_Comm_size(atc->mpi_comm, &atc->nslab);
 -        MPI_Comm_rank(atc->mpi_comm, &atc->nodeid);
 +        mpi_comm = PmeMpiCommunicator;
 +#if GMX_MPI
 +        MPI_Comm_size(mpi_comm, &nslab);
 +        MPI_Comm_rank(mpi_comm, &nodeid);
 +#endif
      }
      if (debug)
      {
 -        fprintf(debug, "For PME atom communication in dimind %d: nslab %d rank %d\n", atc->dimind, atc->nslab, atc->nodeid);
 +        fprintf(debug, "For PME atom communication in dimind %d: nslab %d rank %d\n", dimind, nslab, nodeid);
      }
 -#endif
 -
 -    atc->bSpread   = bSpread;
 -    atc->pme_order = pme->pme_order;
  
 -    if (atc->nslab > 1)
 +    if (nslab > 1)
      {
 -        snew(atc->node_dest, atc->nslab);
 -        snew(atc->node_src, atc->nslab);
 -        setup_coordinate_communication(atc);
 -
 -        snew(atc->count_thread, pme->nthread);
 -        for (thread = 0; thread < pme->nthread; thread++)
 -        {
 -            snew(atc->count_thread[thread], atc->nslab);
 -        }
 -        atc->count = atc->count_thread[0];
 -        snew(atc->rcount, atc->nslab);
 -        snew(atc->buf_index, atc->nslab);
 -    }
 +        slabCommSetup.resize(nslab);
 +        setup_coordinate_communication(this);
  
 -    atc->nthread = pme->nthread;
 -    if (atc->nthread > 1)
 -    {
 -        snew(atc->thread_plist, atc->nthread);
 -    }
 -    snew(atc->spline, atc->nthread);
 -    for (thread = 0; thread < atc->nthread; thread++)
 -    {
 -        if (atc->nthread > 1)
 +        count_thread.resize(nthread);
 +        for (auto &countThread : count_thread)
          {
 -            snew(atc->thread_plist[thread].n, atc->nthread+2*gmxCacheLineSize);
 -            atc->thread_plist[thread].n += gmxCacheLineSize;
 +            countThread.resize(nslab);
          }
      }
 -}
  
 -/*! \brief Destroy an atom communication data structure and its child structs */
 -static void destroy_atomcomm(pme_atomcomm_t *atc)
 -{
 -    sfree(atc->pd);
 -    if (atc->nslab > 1)
 +    if (nthread > 1)
      {
 -        sfree(atc->node_dest);
 -        sfree(atc->node_src);
 -        for (int i = 0; i < atc->nthread; i++)
 -        {
 -            sfree(atc->count_thread[i]);
 -        }
 -        sfree(atc->count_thread);
 -        sfree(atc->rcount);
 -        sfree(atc->buf_index);
 -
 -        sfree(atc->x);
 -        sfree(atc->coefficient);
 -        sfree(atc->f);
 -    }
 -    sfree(atc->idx);
 -    sfree(atc->fractx);
 +        threadMap.resize(nthread);
  
 -    sfree(atc->thread_idx);
 -    for (int i = 0; i < atc->nthread; i++)
 -    {
 -        if (atc->nthread > 1)
 -        {
 -            int *n_ptr = atc->thread_plist[i].n - gmxCacheLineSize;
 -            sfree(n_ptr);
 -            sfree(atc->thread_plist[i].i);
 -        }
 -        sfree(atc->spline[i].ind);
 -        for (int d = 0; d < ZZ; d++)
 +#pragma omp parallel for num_threads(nthread) schedule(static)
 +        for (int thread = 0; thread < nthread; thread++)
          {
 -            sfree(atc->spline[i].theta[d]);
 -            sfree(atc->spline[i].dtheta[d]);
 +            try
 +            {
 +                /* Allocate buffer with padding to avoid cache polution */
 +                threadMap[thread].nBuffer.resize(nthread + 2*gmxCacheLineSize);
 +                threadMap[thread].n = threadMap[thread].nBuffer.data() + gmxCacheLineSize;
 +            }
 +            GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
          }
 -        sfree_aligned(atc->spline[i].ptr_dtheta_z);
 -        sfree_aligned(atc->spline[i].ptr_theta_z);
 -    }
 -    if (atc->nthread > 1)
 -    {
 -        sfree(atc->thread_plist);
      }
 -    sfree(atc->spline);
  }
  
 +#endif // !DOXYGEN
 +
  /*! \brief Initialize data structure for communication */
  static void
  init_overlap_comm(pme_overlap_t *  ol,
                    int              norder,
 -#if GMX_MPI
                    MPI_Comm         comm,
 -#endif
                    int              nnodes,
                    int              nodeid,
                    int              ndata,
                    int              commplainsize)
  {
      gmx_bool         bCont;
 -#if GMX_MPI
 -    MPI_Status       stat;
  
      ol->mpi_comm = comm;
 -#endif
 -
 -    ol->nnodes = nnodes;
 -    ol->nodeid = nodeid;
 +    ol->nnodes   = nnodes;
 +    ol->nodeid   = nodeid;
  
      /* Linear translation of the PME grid won't affect reciprocal space
       * calculations, so to optimize we only interpolate "upwards",
  
  #if GMX_MPI
      /* Communicate the buffer sizes to receive */
 +    MPI_Status       stat;
      for (size_t b = 0; b < ol->comm_data.size(); b++)
      {
          MPI_Sendrecv(&ol->send_size, 1, MPI_INT, ol->comm_data[b].send_id, b,
@@@ -574,6 -634,7 +577,6 @@@ static int div_round_up(int enumerator
  gmx_pme_t *gmx_pme_init(const t_commrec         *cr,
                          const NumPmeDomains     &numPmeDomains,
                          const t_inputrec        *ir,
 -                        int                      homenr,
                          gmx_bool                 bFreeEnergy_q,
                          gmx_bool                 bFreeEnergy_lj,
                          gmx_bool                 bReproducible,
      pme->nnodes_major        = numPmeDomains.x;
      pme->nnodes_minor        = numPmeDomains.y;
  
 -#if GMX_MPI
      if (numPmeDomains.x*numPmeDomains.y > 1)
      {
          pme->mpi_comm = cr->mpi_comm_mygroup;
  
 +#if GMX_MPI
          MPI_Comm_rank(pme->mpi_comm, &pme->nodeid);
          MPI_Comm_size(pme->mpi_comm, &pme->nnodes);
 +#endif
          if (pme->nnodes != numPmeDomains.x*numPmeDomains.y)
          {
              gmx_incons("PME rank count mismatch");
      {
          pme->mpi_comm = MPI_COMM_NULL;
      }
 -#endif
  
      if (pme->nnodes == 1)
      {
 -#if GMX_MPI
          pme->mpi_comm_d[0] = MPI_COMM_NULL;
          pme->mpi_comm_d[1] = MPI_COMM_NULL;
 -#endif
 -        pme->ndecompdim   = 0;
 -        pme->nodeid_major = 0;
 -        pme->nodeid_minor = 0;
 -#if GMX_MPI
 -        pme->mpi_comm_d[0] = pme->mpi_comm_d[1] = MPI_COMM_NULL;
 -#endif
 +        pme->ndecompdim    = 0;
 +        pme->nodeid_major  = 0;
 +        pme->nodeid_minor  = 0;
      }
      else
      {
          if (numPmeDomains.y == 1)
          {
 -#if GMX_MPI
              pme->mpi_comm_d[0] = pme->mpi_comm;
              pme->mpi_comm_d[1] = MPI_COMM_NULL;
 -#endif
 -            pme->ndecompdim   = 1;
 -            pme->nodeid_major = pme->nodeid;
 -            pme->nodeid_minor = 0;
 +            pme->ndecompdim    = 1;
 +            pme->nodeid_major  = pme->nodeid;
 +            pme->nodeid_minor  = 0;
  
          }
          else if (numPmeDomains.x == 1)
          {
 -#if GMX_MPI
              pme->mpi_comm_d[0] = MPI_COMM_NULL;
              pme->mpi_comm_d[1] = pme->mpi_comm;
 -#endif
 -            pme->ndecompdim   = 1;
 -            pme->nodeid_major = 0;
 -            pme->nodeid_minor = pme->nodeid;
 +            pme->ndecompdim    = 1;
 +            pme->nodeid_major  = 0;
 +            pme->nodeid_minor  = pme->nodeid;
          }
          else
          {
       * but we do need the overlap in x because of the communication order.
       */
      init_overlap_comm(&pme->overlap[0], pme->pme_order,
 -#if GMX_MPI
                        pme->mpi_comm_d[0],
 -#endif
                        pme->nnodes_major, pme->nodeid_major,
                        pme->nkx,
                        (div_round_up(pme->nky, pme->nnodes_minor)+pme->pme_order)*(pme->nkz+pme->pme_order-1));
       * extra for the offset. That's what the (+1)*pme->nkz is for.
       */
      init_overlap_comm(&pme->overlap[1], pme->pme_order,
 -#if GMX_MPI
                        pme->mpi_comm_d[1],
 -#endif
                        pme->nnodes_minor, pme->nodeid_minor,
                        pme->nky,
                        (div_round_up(pme->nkx, pme->nnodes_major)+pme->pme_order+1)*pme->nkz);
      }
  
      /* Use atc[0] for spreading */
 -    init_atomcomm(pme.get(), &pme->atc[0], numPmeDomains.x > 1 ? 0 : 1, TRUE);
 +    const int firstDimIndex   = (numPmeDomains.x > 1 ? 0 : 1);
 +    MPI_Comm  mpiCommFirstDim = (pme->nnodes > 1 ? pme->mpi_comm_d[firstDimIndex] : MPI_COMM_NULL);
 +    bool      doSpread        = true;
 +    pme->atc.emplace_back(mpiCommFirstDim, pme->nthread,
 +                          pme->pme_order,
 +                          firstDimIndex, doSpread);
      if (pme->ndecompdim >= 2)
      {
 -        init_atomcomm(pme.get(), &pme->atc[1], 1, FALSE);
 -    }
 -
 -    if (pme->nnodes == 1)
 -    {
 -        pme->atc[0].n = homenr;
 -        pme_realloc_atomcomm_things(&pme->atc[0]);
 +        const int secondDimIndex = 1;
 +        doSpread                 = false;
 +        pme->atc.emplace_back(pme->mpi_comm_d[1], pme->nthread,
 +                              pme->pme_order,
 +                              secondDimIndex, doSpread);
      }
  
 -    pme->lb_buf1       = nullptr;
 -    pme->lb_buf2       = nullptr;
 -    pme->lb_buf_nalloc = 0;
 -
      if (pme_gpu_active(pme.get()))
      {
          if (!pme->gpu)
@@@ -933,6 -1008,8 +936,6 @@@ void gmx_pme_reinit(struct gmx_pme_t **
                      real               ewaldcoeff_q,
                      real               ewaldcoeff_lj)
  {
 -    int        homenr;
 -
      // Create a copy of t_inputrec fields that are used in gmx_pme_init().
      // TODO: This would be better as just copying a sub-structure that contains
      // all the PME parameters and nothing else.
      irc.nky                    = grid_size[YY];
      irc.nkz                    = grid_size[ZZ];
  
 -    if (pme_src->nnodes == 1)
 -    {
 -        homenr = pme_src->atc[0].n;
 -    }
 -    else
 -    {
 -        homenr = -1;
 -    }
 -
      try
      {
          const gmx::MDLogger dummyLogger;
          GMX_ASSERT(pmedata, "Invalid PME pointer");
          NumPmeDomains numPmeDomains = { pme_src->nnodes_major, pme_src->nnodes_minor };
          *pmedata = gmx_pme_init(cr, numPmeDomains,
 -                                &irc, homenr, pme_src->bFEP_q, pme_src->bFEP_lj, FALSE, ewaldcoeff_q, ewaldcoeff_lj,
 +                                &irc, pme_src->bFEP_q, pme_src->bFEP_lj, FALSE, ewaldcoeff_q, ewaldcoeff_lj,
                                  pme_src->nthread, pme_src->runMode, pme_src->gpu, nullptr, nullptr, dummyLogger);
 +        /* When running PME on the CPU not using domain decomposition,
 +         * the atom data is allocated once only in gmx_pme_(re)init().
 +         */
 +        if (!pme_src->gpu && pme_src->nnodes == 1)
 +        {
 +            gmx_pme_reinit_atoms(*pmedata, pme_src->atc[0].numAtoms(), nullptr);
 +        }
          //TODO this is mostly passing around current values
      }
      GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
      /* We would like to reuse the fft grids, but that's harder */
  }
  
 -void gmx_pme_calc_energy(struct gmx_pme_t *pme, int n, rvec *x, real *q, real *V)
 +void gmx_pme_calc_energy(gmx_pme_t                      *pme,
 +                         gmx::ArrayRef<const gmx::RVec>  x,
 +                         gmx::ArrayRef<const real>       q,
 +                         real                           *V)
  {
 -    pme_atomcomm_t *atc;
      pmegrids_t     *grid;
  
      if (pme->nnodes > 1)
          gmx_incons("gmx_pme_calc_energy with free energy");
      }
  
 -    atc            = &pme->atc_energy;
 -    atc->nthread   = 1;
 -    if (atc->spline == nullptr)
 +    if (!pme->atc_energy)
      {
 -        snew(atc->spline, atc->nthread);
 +        pme->atc_energy = std::make_unique<PmeAtomComm>(MPI_COMM_NULL, 1, pme->pme_order,
 +                                                        0, true);
      }
 -    atc->nslab     = 1;
 -    atc->bSpread   = TRUE;
 -    atc->pme_order = pme->pme_order;
 -    atc->n         = n;
 -    pme_realloc_atomcomm_things(atc);
 +    PmeAtomComm *atc = pme->atc_energy.get();
 +    atc->setNumAtoms(x.ssize());
      atc->x           = x;
      atc->coefficient = q;
  
  
  /*! \brief Calculate initial Lorentz-Berthelot coefficients for LJ-PME */
  static void
 -calc_initial_lb_coeffs(struct gmx_pme_t *pme, const real *local_c6, const real *local_sigma)
 +calc_initial_lb_coeffs(gmx::ArrayRef<real>  coefficient,
 +                       const real          *local_c6,
 +                       const real          *local_sigma)
  {
 -    int  i;
 -    for (i = 0; i < pme->atc[0].n; ++i)
 +    for (gmx::index i = 0; i < coefficient.ssize(); ++i)
      {
 -        real sigma4;
 -        sigma4                     = local_sigma[i];
 -        sigma4                     = sigma4*sigma4;
 -        sigma4                     = sigma4*sigma4;
 -        pme->atc[0].coefficient[i] = local_c6[i] / sigma4;
 +        real sigma4    = local_sigma[i];
 +        sigma4         = sigma4*sigma4;
 +        sigma4         = sigma4*sigma4;
 +        coefficient[i] = local_c6[i] / sigma4;
      }
  }
  
  /*! \brief Calculate next Lorentz-Berthelot coefficients for LJ-PME */
  static void
 -calc_next_lb_coeffs(struct gmx_pme_t *pme, const real *local_sigma)
 +calc_next_lb_coeffs(gmx::ArrayRef<real>  coefficient,
 +                    const real          *local_sigma)
  {
 -    int  i;
 -
 -    for (i = 0; i < pme->atc[0].n; ++i)
 +    for (gmx::index i = 0; i < coefficient.ssize(); ++i)
      {
 -        pme->atc[0].coefficient[i] *= local_sigma[i];
 +        coefficient[i] *= local_sigma[i];
      }
  }
  
  int gmx_pme_do(struct gmx_pme_t *pme,
 -               int start,       int homenr,
 -               rvec x[],        rvec f[],
 +               gmx::ArrayRef<const gmx::RVec> coordinates,
 +               gmx::ArrayRef<gmx::RVec>       forces,
                 real chargeA[],  real chargeB[],
                 real c6A[],      real c6B[],
                 real sigmaA[],   real sigmaB[],
 -               matrix box,      const t_commrec *cr,
 +               const matrix box, const t_commrec *cr,
                 int  maxshift_x, int maxshift_y,
                 t_nrnb *nrnb,    gmx_wallcycle *wcycle,
                 matrix vir_q,    matrix vir_lj,
  {
      GMX_ASSERT(pme->runMode == PmeRunMode::CPU, "gmx_pme_do should not be called on the GPU PME run.");
  
 -    int                  d, i, j, npme, grid_index, max_grid_index;
 -    int                  n_d;
 -    pme_atomcomm_t      *atc        = nullptr;
 -    pmegrids_t          *pmegrid    = nullptr;
 -    real                *grid       = nullptr;
 -    rvec                *f_d;
 +    int                  d, npme, grid_index, max_grid_index;
 +    PmeAtomComm         &atc         = pme->atc[0];
 +    pmegrids_t          *pmegrid     = nullptr;
 +    real                *grid        = nullptr;
      real                *coefficient = nullptr;
      PmeOutput            output[2]; // The second is used for the B state with FEP
      real                 scale, lambda;
  
      if (pme->nnodes > 1)
      {
 -        atc      = &pme->atc[0];
 -        atc->npd = homenr;
 -        if (atc->npd > atc->pd_nalloc)
 +        atc.pd.resize(coordinates.ssize());
 +        for (int d = pme->ndecompdim-1; d >= 0; d--)
          {
 -            atc->pd_nalloc = over_alloc_dd(atc->npd);
 -            srenew(atc->pd, atc->pd_nalloc);
 -        }
 -        for (d = pme->ndecompdim-1; d >= 0; d--)
 -        {
 -            atc           = &pme->atc[d];
 -            atc->maxshift = (atc->dimind == 0 ? maxshift_x : maxshift_y);
 +            PmeAtomComm &atc    = pme->atc[d];
 +            atc.maxshift        = (atc.dimind == 0 ? maxshift_x : maxshift_y);
          }
      }
      else
      {
 -        atc = &pme->atc[0];
 -        /* This could be necessary for TPI */
 -        pme->atc[0].n = homenr;
 -        if (DOMAINDECOMP(cr))
 -        {
 -            pme_realloc_atomcomm_things(atc);
 -        }
 -        atc->x = x;
 -        atc->f = f;
 +        GMX_ASSERT(coordinates.ssize() == atc.numAtoms(), "We expect atc.numAtoms() coordinates");
 +        GMX_ASSERT(forces.ssize() >= atc.numAtoms(), "We need a force buffer with at least atc.numAtoms() elements");
 +
 +        atc.x = coordinates;
 +        atc.f = forces;
      }
  
      matrix scaledBox;
          pfft_setup = pme->pfft_setup[grid_index];
          switch (grid_index)
          {
 -            case 0: coefficient = chargeA + start; break;
 -            case 1: coefficient = chargeB + start; break;
 -            case 2: coefficient = c6A + start; break;
 -            case 3: coefficient = c6B + start; break;
 +            case 0: coefficient = chargeA; break;
 +            case 1: coefficient = chargeB; break;
 +            case 2: coefficient = c6A; break;
 +            case 3: coefficient = c6B; break;
          }
  
          grid = pmegrid->grid.grid;
  
          if (pme->nnodes == 1)
          {
 -            atc->coefficient = coefficient;
 +            atc.coefficient = gmx::arrayRefFromArray(coefficient, coordinates.size());
          }
          else
          {
              wallcycle_start(wcycle, ewcPME_REDISTXF);
 -            do_redist_pos_coeffs(pme, cr, start, homenr, bFirst, x, coefficient);
 +            do_redist_pos_coeffs(pme, cr, bFirst, coordinates, coefficient);
  
              wallcycle_stop(wcycle, ewcPME_REDISTXF);
          }
          if (debug)
          {
              fprintf(debug, "Rank= %6d, pme local particles=%6d\n",
 -                    cr->nodeid, atc->n);
 +                    cr->nodeid, atc.numAtoms());
          }
  
          if (flags & GMX_PME_SPREAD)
              wallcycle_start(wcycle, ewcPME_SPREAD);
  
              /* Spread the coefficients on a grid */
 -            spread_on_grid(pme, &pme->atc[0], pmegrid, bFirst, TRUE, fftgrid, bDoSplines, grid_index);
 +            spread_on_grid(pme, &atc, pmegrid, bFirst, TRUE, fftgrid, bDoSplines, grid_index);
  
              if (bFirst)
              {
 -                inc_nrnb(nrnb, eNR_WEIGHTS, DIM*atc->n);
 +                inc_nrnb(nrnb, eNR_WEIGHTS, DIM*atc.numAtoms());
              }
              inc_nrnb(nrnb, eNR_SPREADBSP,
 -                     pme->pme_order*pme->pme_order*pme->pme_order*atc->n);
 +                     pme->pme_order*pme->pme_order*pme->pme_order*atc.numAtoms());
  
              if (!pme->bUseThreads)
              {
                  wrap_periodic_pmegrid(pme, grid);
  
                  /* sum contributions to local grid from other nodes */
 -#if GMX_MPI
                  if (pme->nnodes > 1)
                  {
                      gmx_sum_qgrid_dd(pme, grid, GMX_SUM_GRID_FORWARD);
                  }
 -#endif
  
                  copy_pmegrid_to_fftgrid(pme, grid, fftgrid, grid_index);
              }
          if (bBackFFT)
          {
              /* distribute local grid to all nodes */
 -#if GMX_MPI
              if (pme->nnodes > 1)
              {
                  gmx_sum_qgrid_dd(pme, grid, GMX_SUM_GRID_BACKWARD);
              }
 -#endif
  
              unwrap_periodic_pmegrid(pme, grid);
          }
              {
                  try
                  {
 -                    gather_f_bsplines(pme, grid, bClearF, atc,
 -                                      &atc->spline[thread],
 +                    gather_f_bsplines(pme, grid, bClearF, &atc,
 +                                      &atc.spline[thread],
                                        pme->bFEP ? (grid_index % 2 == 0 ? 1.0-lambda : lambda) : 1.0);
                  }
                  GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
  
  
              inc_nrnb(nrnb, eNR_GATHERFBSP,
 -                     pme->pme_order*pme->pme_order*pme->pme_order*pme->atc[0].n);
 +                     pme->pme_order*pme->pme_order*pme->pme_order*atc.numAtoms());
              /* Note: this wallcycle region is opened above inside an OpenMP
                 region, so take care if refactoring code here. */
              wallcycle_stop(wcycle, ewcPME_GATHER);
          /* Loop over A- and B-state if we are doing FEP */
          for (fep_state = 0; fep_state < fep_states_lj; ++fep_state)
          {
 -            real *local_c6 = nullptr, *local_sigma = nullptr, *RedistC6 = nullptr, *RedistSigma = nullptr;
 +            real               *local_c6 = nullptr, *local_sigma = nullptr, *RedistC6 = nullptr, *RedistSigma = nullptr;
 +            gmx::ArrayRef<real> coefficientBuffer;
              if (pme->nnodes == 1)
              {
 -                if (pme->lb_buf1 == nullptr)
 -                {
 -                    pme->lb_buf_nalloc = pme->atc[0].n;
 -                    snew(pme->lb_buf1, pme->lb_buf_nalloc);
 -                }
 -                pme->atc[0].coefficient = pme->lb_buf1;
 +                pme->lb_buf1.resize(atc.numAtoms());
 +                coefficientBuffer = pme->lb_buf1;
                  switch (fep_state)
                  {
                      case 0:
              }
              else
              {
 -                atc = &pme->atc[0];
 +                coefficientBuffer = atc.coefficientBuffer;
                  switch (fep_state)
                  {
                      case 0:
                  }
                  wallcycle_start(wcycle, ewcPME_REDISTXF);
  
 -                do_redist_pos_coeffs(pme, cr, start, homenr, bFirst, x, RedistC6);
 -                if (pme->lb_buf_nalloc < atc->n)
 -                {
 -                    pme->lb_buf_nalloc = atc->nalloc;
 -                    srenew(pme->lb_buf1, pme->lb_buf_nalloc);
 -                    srenew(pme->lb_buf2, pme->lb_buf_nalloc);
 -                }
 -                local_c6 = pme->lb_buf1;
 -                for (i = 0; i < atc->n; ++i)
 +                do_redist_pos_coeffs(pme, cr, bFirst, coordinates, RedistC6);
 +                pme->lb_buf1.resize(atc.numAtoms());
 +                pme->lb_buf2.resize(atc.numAtoms());
 +                local_c6 = pme->lb_buf1.data();
 +                for (int i = 0; i < atc.numAtoms(); ++i)
                  {
 -                    local_c6[i] = atc->coefficient[i];
 +                    local_c6[i] = atc.coefficient[i];
                  }
  
 -                do_redist_pos_coeffs(pme, cr, start, homenr, FALSE, x, RedistSigma);
 -                local_sigma = pme->lb_buf2;
 -                for (i = 0; i < atc->n; ++i)
 +                do_redist_pos_coeffs(pme, cr, FALSE, coordinates, RedistSigma);
 +                local_sigma = pme->lb_buf2.data();
 +                for (int i = 0; i < atc.numAtoms(); ++i)
                  {
 -                    local_sigma[i] = atc->coefficient[i];
 +                    local_sigma[i] = atc.coefficient[i];
                  }
  
                  wallcycle_stop(wcycle, ewcPME_REDISTXF);
              }
 -            calc_initial_lb_coeffs(pme, local_c6, local_sigma);
 +            atc.coefficient = coefficientBuffer;
 +            calc_initial_lb_coeffs(coefficientBuffer, local_c6, local_sigma);
  
              /*Seven terms in LJ-PME with LB, grid_index < 2 reserved for electrostatics*/
              for (grid_index = 2; grid_index < 9; ++grid_index)
                  pmegrid    = &pme->pmegrid[grid_index];
                  fftgrid    = pme->fftgrid[grid_index];
                  pfft_setup = pme->pfft_setup[grid_index];
 -                calc_next_lb_coeffs(pme, local_sigma);
 +                calc_next_lb_coeffs(coefficientBuffer, local_sigma);
                  grid = pmegrid->grid.grid;
  
                  if (flags & GMX_PME_SPREAD)
                  {
                      wallcycle_start(wcycle, ewcPME_SPREAD);
                      /* Spread the c6 on a grid */
 -                    spread_on_grid(pme, &pme->atc[0], pmegrid, bFirst, TRUE, fftgrid, bDoSplines, grid_index);
 +                    spread_on_grid(pme, &atc, pmegrid, bFirst, TRUE, fftgrid, bDoSplines, grid_index);
  
                      if (bFirst)
                      {
 -                        inc_nrnb(nrnb, eNR_WEIGHTS, DIM*atc->n);
 +                        inc_nrnb(nrnb, eNR_WEIGHTS, DIM*atc.numAtoms());
                      }
  
                      inc_nrnb(nrnb, eNR_SPREADBSP,
 -                             pme->pme_order*pme->pme_order*pme->pme_order*atc->n);
 +                             pme->pme_order*pme->pme_order*pme->pme_order*atc.numAtoms());
                      if (pme->nthread == 1)
                      {
                          wrap_periodic_pmegrid(pme, grid);
                          /* sum contributions to local grid from other nodes */
 -#if GMX_MPI
                          if (pme->nnodes > 1)
                          {
                              gmx_sum_qgrid_dd(pme, grid, GMX_SUM_GRID_FORWARD);
                          }
 -#endif
                          copy_pmegrid_to_fftgrid(pme, grid, fftgrid, grid_index);
                      }
                      wallcycle_stop(wcycle, ewcPME_SPREAD);
              if (bBackFFT)
              {
                  bFirst = !pme->doCoulomb;
 -                calc_initial_lb_coeffs(pme, local_c6, local_sigma);
 +                calc_initial_lb_coeffs(coefficientBuffer, local_c6, local_sigma);
                  for (grid_index = 8; grid_index >= 2; --grid_index)
                  {
                      /* Unpack structure */
                      fftgrid    = pme->fftgrid[grid_index];
                      pfft_setup = pme->pfft_setup[grid_index];
                      grid       = pmegrid->grid.grid;
 -                    calc_next_lb_coeffs(pme, local_sigma);
 +                    calc_next_lb_coeffs(coefficientBuffer, local_sigma);
  #pragma omp parallel num_threads(pme->nthread) private(thread)
                      {
                          try
                      } /*#pragma omp parallel*/
  
                      /* distribute local grid to all nodes */
 -#if GMX_MPI
                      if (pme->nnodes > 1)
                      {
                          gmx_sum_qgrid_dd(pme, grid, GMX_SUM_GRID_BACKWARD);
                      }
 -#endif
  
                      unwrap_periodic_pmegrid(pme, grid);
  
  
  
                          inc_nrnb(nrnb, eNR_GATHERFBSP,
 -                                 pme->pme_order*pme->pme_order*pme->pme_order*pme->atc[0].n);
 +                                 pme->pme_order*pme->pme_order*pme->pme_order*pme->atc[0].numAtoms());
                      }
                      wallcycle_stop(wcycle, ewcPME_GATHER);
  
          wallcycle_start(wcycle, ewcPME_REDISTXF);
          for (d = 0; d < pme->ndecompdim; d++)
          {
 -            atc = &pme->atc[d];
 +            gmx::ArrayRef<gmx::RVec> forcesRef;
              if (d == pme->ndecompdim - 1)
              {
 -                n_d = homenr;
 -                f_d = f + start;
 +                const size_t numAtoms = coordinates.size();
 +                GMX_ASSERT(forces.size() >= numAtoms, "Need at least numAtoms forces");
 +                forcesRef = forces.subArray(0, numAtoms);
              }
              else
              {
 -                n_d = pme->atc[d+1].n;
 -                f_d = pme->atc[d+1].f;
 +                forcesRef = pme->atc[d + 1].f;
              }
              if (DOMAINDECOMP(cr))
              {
 -                dd_pmeredist_f(pme, atc, n_d, f_d,
 +                dd_pmeredist_f(pme, &pme->atc[d], forcesRef,
                                 d == pme->ndecompdim-1 && pme->bPPnode);
              }
          }
              {
                  *energy_q       = (1.0-lambda_q)*output[0].coulombEnergy_ + lambda_q*output[1].coulombEnergy_;
                  *dvdlambda_q   += output[1].coulombEnergy_ - output[0].coulombEnergy_;
 -                for (i = 0; i < DIM; i++)
 +                for (int i = 0; i < DIM; i++)
                  {
 -                    for (j = 0; j < DIM; j++)
 +                    for (int j = 0; j < DIM; j++)
                      {
                          vir_q[i][j] += (1.0-lambda_q)*output[0].coulombVirial_[i][j] +
                              lambda_q*output[1].coulombVirial_[i][j];
              {
                  *energy_lj     = (1.0-lambda_lj)*output[0].lennardJonesEnergy_ + lambda_lj*output[1].lennardJonesEnergy_;
                  *dvdlambda_lj += output[1].lennardJonesEnergy_ - output[0].lennardJonesEnergy_;
 -                for (i = 0; i < DIM; i++)
 +                for (int i = 0; i < DIM; i++)
                  {
 -                    for (j = 0; j < DIM; j++)
 +                    for (int j = 0; j < DIM; j++)
                      {
                          vir_lj[i][j] += (1.0-lambda_lj)*output[0].lennardJonesVirial_[i][j] + lambda_lj*output[1].lennardJonesVirial_[i][j];
                      }
@@@ -1738,11 -1846,19 +1741,11 @@@ void gmx_pme_destroy(gmx_pme_t *pme
      sfree(pme->cfftgrid);
      sfree(pme->pfft_setup);
  
 -    for (int i = 0; i < std::max(1, pme->ndecompdim); i++) //pme->atc[0] is always allocated
 -    {
 -        destroy_atomcomm(&pme->atc[i]);
 -    }
 -
      for (int i = 0; i < DIM; i++)
      {
          sfree(pme->bsp_mod[i]);
      }
  
 -    sfree(pme->lb_buf1);
 -    sfree(pme->lb_buf2);
 -
      sfree(pme->bufv);
      sfree(pme->bufr);
  
      delete pme;
  }
  
 -void gmx_pme_reinit_atoms(const gmx_pme_t *pme, const int nAtoms, const real *charges)
 +void gmx_pme_reinit_atoms(gmx_pme_t  *pme,
 +                          const int   numAtoms,
 +                          const real *charges)
  {
      if (pme_gpu_active(pme))
      {
 -        pme_gpu_reinit_atoms(pme->gpu, nAtoms, charges);
 +        pme_gpu_reinit_atoms(pme->gpu, numAtoms, charges);
 +    }
 +    else
 +    {
 +        pme->atc[0].setNumAtoms(numAtoms);
 +        // TODO: set the charges here as well
      }
 -    // TODO: handle the CPU case here; handle the whole t_mdatoms
  }
index 8b19f474a5f9f9d5dbf82dfee8edde8d1647c472,0000000000000000000000000000000000000000..ba3ed180fe0951e80ff0fe065cb4a029f5591d00
mode 100644,000000..100644
--- /dev/null
@@@ -1,381 -1,0 +1,385 @@@
-         /* This barrier was not needed in CUDA. Different OpenCL compilers might have different ideas
 +/*
 + * This file is part of the GROMACS molecular simulation package.
 + *
 + * Copyright (c) 2018,2019, by the GROMACS development team, led by
 + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
 + * and including many others, as listed in the AUTHORS file in the
 + * top-level source directory and at http://www.gromacs.org.
 + *
 + * GROMACS is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public License
 + * as published by the Free Software Foundation; either version 2.1
 + * of the License, or (at your option) any later version.
 + *
 + * GROMACS is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * Lesser General Public License for more details.
 + *
 + * You should have received a copy of the GNU Lesser General Public
 + * License along with GROMACS; if not, see
 + * http://www.gnu.org/licenses, or write to the Free Software Foundation,
 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
 + *
 + * If you want to redistribute modifications to GROMACS, please
 + * consider that scientific software is very special. Version
 + * control is crucial - bugs must be traceable. We will be happy to
 + * consider code for inclusion in the official distribution, but
 + * derived work must not be called official GROMACS. Details are found
 + * in the README & COPYING files - if they are missing, get the
 + * official version at http://www.gromacs.org.
 + *
 + * To help us fund GROMACS development, we humbly ask that you cite
 + * the research papers on the package. Check out http://www.gromacs.org.
 + */
 +
 +/*! \internal \file
 + *  \brief Implements PME OpenCL force gathering kernel.
 + * When including this and other PME OpenCL kernel files, plenty of common
 + * constants/macros are expected to be defined (such as "order" which is PME interpolation order).
 + * For details, please see how pme_program.cl is compiled in pme_gpu_program_impl_ocl.cpp.
 + *
 + * This file's kernels specifically expect the following definitions:
 + *
 + * - atomsPerBlock which expresses how many atoms are processed by a single work group
 + * - order which is a PME interpolation order
 + * - overwriteForces must evaluate to either true or false to specify whether the kernel
 + * overwrites or reduces into the forces buffer
 + * - wrapX and wrapY must evaluate to either true or false to specify whether the grid overlap
 + * in dimension X/Y is to be used
 + *
 + *  \author Aleksei Iupinov <a.yupinov@gmail.com>
 + */
 +
 +#include "pme_gpu_types.h"
 +#include "pme_gpu_utils.clh"
 +
 +#ifndef COMPILE_GATHER_HELPERS_ONCE
 +#define COMPILE_GATHER_HELPERS_ONCE
 +
 +/*! \brief
 + * Unrolls the dynamic index accesses to the constant grid sizes to avoid local memory operations.
 + */
 +inline float read_grid_size(const float *realGridSizeFP,
 +                            const int    dimIndex)
 +{
 +    switch (dimIndex)
 +    {
 +        case XX: return realGridSizeFP[XX];
 +        case YY: return realGridSizeFP[YY];
 +        case ZZ: return realGridSizeFP[ZZ];
 +    }
 +    assert(false);
 +    return 0.0f;
 +}
 +
 +/*! \brief Reduce the partial force contributions.
 + *
 + * FIXME: this reduction should be simplified and improved, it does 3x16 force component
 + *        reduction per 16 threads so no extra shared mem should be needed for intermediates
 + *        or passing results back.
 + *
 + * \param[out]      sm_forces          Local memory array with the output forces (rvec).
 + * \param[in]       atomIndexLocal     Local atom index
 + * \param[in]       splineIndex        Spline index
 + * \param[in]       lineIndex          Line index (same as threadLocalId)
 + * \param[in]       realGridSizeFP     Local grid size constant
 + * \param[in]       fx                 Input force partial component X
 + * \param[in]       fy                 Input force partial component Y
 + * \param[in]       fz                 Input force partial component Z
 + * \param[in,out]   sm_forceReduction  Reduction working buffer
 + * \param[in]       sm_forceTemp       Convenience pointers into \p sm_forceReduction
 + */
 +inline void reduce_atom_forces(__local float * __restrict__  sm_forces,
 +                               const int                     atomIndexLocal,
 +                               const int                     splineIndex,
 +                               const int                     lineIndex,
 +                               const float                  *realGridSizeFP,
 +                               float                         fx,
 +                               float                         fy,
 +                               float                         fz,
 +                               __local float * __restrict__  sm_forceReduction,
 +                               __local float ** __restrict__ sm_forceTemp
 +                               )
 +
 +{
 +    // TODO: implement AMD intrinsics reduction, like with shuffles in CUDA version. #2514
 +
 +    /* Number of data components and threads for a single atom */
 +#define atomDataSize threadsPerAtom
 +    // We use blockSize local memory elements to read fx, or fy, or fz, and then reduce them to fit into smemPerDim elements
 +    // All those guys are defines and not consts, because they go into the local memory array size.
 +#define blockSize (atomsPerBlock * atomDataSize)
 +#define smemPerDim warp_size
 +#define smemReserved  (DIM * smemPerDim)
 +
 +    const int numWarps  = blockSize / smemPerDim;
 +    const int minStride = max(1, atomDataSize / numWarps);
 +
 +#pragma unroll DIM
 +    for (int dimIndex = 0; dimIndex < DIM; dimIndex++)
 +    {
 +        int elementIndex = smemReserved + lineIndex;
 +        // Store input force contributions
 +        sm_forceReduction[elementIndex] = (dimIndex == XX) ? fx : (dimIndex == YY) ? fy : fz;
++
++#if !defined(_AMD_SOURCE_)
++        /* This barrier was not needed in CUDA, nor is it needed on AMD GPUs.
++         * Different OpenCL compilers might have different ideas
 +         * about #pragma unroll, though. OpenCL 2 has _attribute__((opencl_unroll_hint)).
 +         * #2519
 +         */
 +        barrier(CLK_LOCAL_MEM_FENCE);
++#endif
 +
 +        // Reduce to fit into smemPerDim (warp size)
 +#pragma unroll
 +        for (int redStride = atomDataSize >> 1; redStride > minStride; redStride >>= 1)
 +        {
 +            if (splineIndex < redStride)
 +            {
 +                sm_forceReduction[elementIndex] += sm_forceReduction[elementIndex + redStride];
 +            }
 +        }
 +        barrier(CLK_LOCAL_MEM_FENCE);
 +        // Last iteration - packing everything to be nearby, storing convenience pointer
 +        sm_forceTemp[dimIndex] = sm_forceReduction + dimIndex * smemPerDim;
 +        int redStride = minStride;
 +        if (splineIndex < redStride)
 +        {
 +            const int packedIndex = atomIndexLocal * redStride + splineIndex;
 +            sm_forceTemp[dimIndex][packedIndex] = sm_forceReduction[elementIndex] + sm_forceReduction[elementIndex + redStride];
 +        }
 +    }
 +
 +    barrier(CLK_LOCAL_MEM_FENCE);
 +
 +    assert ((blockSize / warp_size) >= DIM);
 +
 +    const int warpIndex = lineIndex / warp_size;
 +    const int dimIndex  = warpIndex;
 +
 +    // First 3 warps can now process 1 dimension each
 +    if (dimIndex < DIM)
 +    {
 +        int sourceIndex = lineIndex % warp_size;
 +#pragma unroll
 +        for (int redStride = minStride >> 1; redStride > 1; redStride >>= 1)
 +        {
 +            if (!(splineIndex & redStride))
 +            {
 +                sm_forceTemp[dimIndex][sourceIndex] += sm_forceTemp[dimIndex][sourceIndex + redStride];
 +            }
 +        }
 +
 +        const float n = read_grid_size(realGridSizeFP, dimIndex);
 +
 +        const int   atomIndex = sourceIndex / minStride;
 +        if (sourceIndex == minStride * atomIndex)
 +        {
 +            sm_forces[atomIndex * DIM + dimIndex] = (sm_forceTemp[dimIndex][sourceIndex] + sm_forceTemp[dimIndex][sourceIndex + 1]) * n;
 +        }
 +    }
 +}
 +
 +#endif //COMPILE_GATHER_HELPERS_ONCE
 +
 +/*! \brief
 + * An OpenCL kernel which gathers the atom forces from the grid.
 + * The grid is assumed to be wrapped in dimension Z.
 + * Please see the file description for additional defines which this kernel expects.
 + *
 + * \param[in]     kernelParams         All the PME GPU data.
 + * \param[in]     gm_coefficients      Atom charges/coefficients.
 + * \param[in]     gm_grid              Global 3D grid.
 + * \param[in]     gm_theta             Atom spline parameter values
 + * \param[in]     gm_dtheta            Atom spline parameter derivatives
 + * \param[in]     gm_gridlineIndices   Atom gridline indices (ivec)
 + * \param[in,out] gm_forces            Atom forces (rvec)
 + */
 +__attribute__((reqd_work_group_size(order, order, atomsPerBlock)))
 +__kernel void CUSTOMIZED_KERNEL_NAME(pme_gather_kernel)(const struct PmeOpenCLKernelParams   kernelParams,
 +                                                        __global const float * __restrict__  gm_coefficients,
 +                                                        __global const float * __restrict__  gm_grid,
 +                                                        __global const float * __restrict__  gm_theta,
 +                                                        __global const float * __restrict__  gm_dtheta,
 +                                                        __global const int * __restrict__    gm_gridlineIndices,
 +                                                        __global float * __restrict__        gm_forces
 +                                                        )
 +{
 +    /* These are the atom indices - for the shared and global memory */
 +    const int         atomIndexLocal    = get_local_id(ZZ);
 +    const int         atomIndexOffset   = get_group_id(XX) * atomsPerBlock;
 +    const int         atomIndexGlobal   = atomIndexOffset + atomIndexLocal;
 +
 +    /* Some sizes which are defines and not consts because they go into the array size */
 +    #define blockSize (atomsPerBlock * atomDataSize)
 +    assert(blockSize == (get_local_size(0) * get_local_size(1) * get_local_size(2)));
 +    #define smemPerDim warp_size
 +    #define smemReserved  (DIM * smemPerDim)
 +    #define totalSharedMemory (smemReserved + blockSize)
 +    #define gridlineIndicesSize (atomsPerBlock * DIM)
 +    #define splineParamsSize (atomsPerBlock * DIM * order)
 +
 +    __local int    sm_gridlineIndices[gridlineIndicesSize];
 +    __local float2 sm_splineParams[splineParamsSize]; /* Theta/dtheta pairs  as .x/.y */
 +
 +    /* Spline Y/Z coordinates */
 +    const int ithy = get_local_id(YY);
 +    const int ithz = get_local_id(XX);
 +
 +    const int threadLocalId = (get_local_id(2) * get_local_size(1) + get_local_id(1)) * get_local_size(0) + get_local_id(0);
 +
 +    /* These are the spline contribution indices in shared memory */
 +    const int splineIndex = (get_local_id(1) * get_local_size(0) + get_local_id(0)); /* Relative to the current particle , 0..15 for order 4 */
 +    const int lineIndex   = threadLocalId;                                           /* And to all the block's particles */
 +
 +    /* Staging the atom gridline indices, DIM * atomsPerBlock threads */
 +    const int localGridlineIndicesIndex  = threadLocalId;
 +    const int globalGridlineIndicesIndex = get_group_id(XX) * gridlineIndicesSize + localGridlineIndicesIndex;
 +    const int globalCheckIndices         = pme_gpu_check_atom_data_index(globalGridlineIndicesIndex, kernelParams.atoms.nAtoms * DIM);
 +    if ((localGridlineIndicesIndex < gridlineIndicesSize) & globalCheckIndices)
 +    {
 +        sm_gridlineIndices[localGridlineIndicesIndex] = gm_gridlineIndices[globalGridlineIndicesIndex];
 +        assert(sm_gridlineIndices[localGridlineIndicesIndex] >= 0);
 +    }
 +    /* Staging the spline parameters, DIM * order * atomsPerBlock threads */
 +    const int localSplineParamsIndex  = threadLocalId;
 +    const int globalSplineParamsIndex = get_group_id(XX) * splineParamsSize + localSplineParamsIndex;
 +    const int globalCheckSplineParams = pme_gpu_check_atom_data_index(globalSplineParamsIndex, kernelParams.atoms.nAtoms * DIM * order);
 +    if ((localSplineParamsIndex < splineParamsSize) && globalCheckSplineParams)
 +    {
 +        sm_splineParams[localSplineParamsIndex].x = gm_theta[globalSplineParamsIndex];
 +        sm_splineParams[localSplineParamsIndex].y = gm_dtheta[globalSplineParamsIndex];
 +        assert(isfinite(sm_splineParams[localSplineParamsIndex].x));
 +        assert(isfinite(sm_splineParams[localSplineParamsIndex].y));
 +    }
 +    barrier(CLK_LOCAL_MEM_FENCE);
 +
 +    float           fx = 0.0f;
 +    float           fy = 0.0f;
 +    float           fz = 0.0f;
 +
 +    const int       globalCheck = pme_gpu_check_atom_data_index(atomIndexGlobal, kernelParams.atoms.nAtoms);
 +    const int       chargeCheck = pme_gpu_check_atom_charge(gm_coefficients[atomIndexGlobal]);
 +
 +    if (chargeCheck & globalCheck)
 +    {
 +        const int    nx        = kernelParams.grid.realGridSize[XX];
 +        const int    ny        = kernelParams.grid.realGridSize[YY];
 +        const int    nz        = kernelParams.grid.realGridSize[ZZ];
 +        const int    pny       = kernelParams.grid.realGridSizePadded[YY];
 +        const int    pnz       = kernelParams.grid.realGridSizePadded[ZZ];
 +
 +        const int    atomWarpIndex = atomIndexLocal % atomsPerWarp;
 +        const int    warpIndex     = atomIndexLocal / atomsPerWarp;
 +
 +        const int    splineIndexBase = getSplineParamIndexBase(warpIndex, atomWarpIndex);
 +        const int    splineIndexY    = getSplineParamIndex(splineIndexBase, YY, ithy);
 +        const float2 tdy             = sm_splineParams[splineIndexY];
 +        const int    splineIndexZ    = getSplineParamIndex(splineIndexBase, ZZ, ithz);
 +        const float2 tdz             = sm_splineParams[splineIndexZ];
 +
 +        const int    ixBase         = sm_gridlineIndices[atomIndexLocal * DIM + XX];
 +        int          iy             = sm_gridlineIndices[atomIndexLocal * DIM + YY] + ithy;
 +        if (wrapY & (iy >= ny))
 +        {
 +            iy -= ny;
 +        }
 +        int iz  = sm_gridlineIndices[atomIndexLocal * DIM + ZZ] + ithz;
 +        if (iz >= nz)
 +        {
 +            iz -= nz;
 +        }
 +        const int constOffset    = iy * pnz + iz;
 +
 +#pragma unroll order
 +        for (int ithx = 0; (ithx < order); ithx++)
 +        {
 +            int ix = ixBase + ithx;
 +            if (wrapX & (ix >= nx))
 +            {
 +                ix -= nx;
 +            }
 +            const int     gridIndexGlobal  = ix * pny * pnz + constOffset;
 +            assert(gridIndexGlobal >= 0);
 +            const float   gridValue    = gm_grid[gridIndexGlobal];
 +            assert(isfinite(gridValue));
 +            const int     splineIndexX = getSplineParamIndex(splineIndexBase, XX, ithx);
 +            const float2  tdx          = sm_splineParams[splineIndexX];
 +            const float   fxy1         = tdz.x * gridValue;
 +            const float   fz1          = tdz.y * gridValue;
 +            fx += tdx.y * tdy.x * fxy1;
 +            fy += tdx.x * tdy.y * fxy1;
 +            fz += tdx.x * tdy.x * fz1;
 +        }
 +    }
 +
 +    // Reduction of partial force contributions
 +    __local float  sm_forces[atomsPerBlock * DIM];
 +
 +    __local float  sm_forceReduction[totalSharedMemory];
 +    __local float *sm_forceTemp[DIM];
 +
 +    reduce_atom_forces(sm_forces,
 +                       atomIndexLocal, splineIndex, lineIndex,
 +                       kernelParams.grid.realGridSizeFP,
 +                       fx, fy, fz,
 +                       sm_forceReduction,
 +                       sm_forceTemp);
 +    barrier(CLK_LOCAL_MEM_FENCE);
 +
 +    /* Calculating the final forces with no component branching, atomsPerBlock threads */
 +    const int forceIndexLocal  = threadLocalId;
 +    const int forceIndexGlobal = atomIndexOffset + forceIndexLocal;
 +    const int calcIndexCheck   = pme_gpu_check_atom_data_index(forceIndexGlobal, kernelParams.atoms.nAtoms);
 +    if ((forceIndexLocal < atomsPerBlock) & calcIndexCheck)
 +    {
 +        const float3  atomForces     = vload3(forceIndexLocal, sm_forces);
 +        const float   negCoefficient = -gm_coefficients[forceIndexGlobal];
 +        float3        result;
 +        result.x      = negCoefficient * kernelParams.current.recipBox[XX][XX] * atomForces.x;
 +        result.y      = negCoefficient * (kernelParams.current.recipBox[XX][YY] * atomForces.x +
 +                                          kernelParams.current.recipBox[YY][YY] * atomForces.y);
 +        result.z      = negCoefficient * (kernelParams.current.recipBox[XX][ZZ] * atomForces.x +
 +                                          kernelParams.current.recipBox[YY][ZZ] * atomForces.y +
 +                                          kernelParams.current.recipBox[ZZ][ZZ] * atomForces.z);
 +        vstore3(result, forceIndexLocal, sm_forces);
 +    }
 +
 +#if !defined(_AMD_SOURCE_) && !defined(_NVIDIA_SOURCE_)
 +    /* This is only here for execution of e.g. 32-sized warps on 16-wide hardware; this was gmx_syncwarp() in CUDA.
 +     * #2519
 +     */
 +    barrier(CLK_LOCAL_MEM_FENCE);
 +#endif
 +
 +    assert(atomsPerBlock <= warp_size);
 +
 +    /* Writing or adding the final forces component-wise, single warp */
 +    const int blockForcesSize = atomsPerBlock * DIM;
 +    const int numIter         = (blockForcesSize + warp_size - 1) / warp_size;
 +    const int iterThreads     = blockForcesSize / numIter;
 +    if (threadLocalId < iterThreads)
 +    {
 +#pragma unroll
 +        for (int i = 0; i < numIter; i++)
 +        {
 +            const int outputIndexLocal  = i * iterThreads + threadLocalId;
 +            const int outputIndexGlobal = get_group_id(XX) * blockForcesSize + outputIndexLocal;
 +            const int globalOutputCheck = pme_gpu_check_atom_data_index(outputIndexGlobal, kernelParams.atoms.nAtoms * DIM);
 +            if (globalOutputCheck)
 +            {
 +                const float outputForceComponent = sm_forces[outputIndexLocal];
 +                if (overwriteForces)
 +                {
 +                    gm_forces[outputIndexGlobal] = outputForceComponent;
 +                }
 +                else
 +                {
 +                    gm_forces[outputIndexGlobal] += outputForceComponent;
 +                }
 +            }
 +        }
 +    }
 +}
index 14efea694f678d1fe27bc1bea435b48b0340d3fc,e097fbfe36401b7806f31e4cd55080db997ff369..aff5e005cd74caf22ee5e3d30ca28e8e52ebee9d
@@@ -77,6 -77,7 +77,6 @@@ static const char *pdbtp[epdbNR] = 
      "CONECT"
  };
  
 -
  #define REMARK_SIM_BOX "REMARK    THIS IS A SIMULATION BOX"
  
  static void xlate_atomname_pdb2gmx(char *name)
@@@ -287,7 -288,7 +287,7 @@@ gmx_fprintf_pqr_atomline(FILE 
      res_seq_number  = res_seq_number % 10000;
  
      int n = fprintf(fp,
-                     "%s %d %s %s %c %d %8.3f %8.3f %8.3f %6.2f %6.2f\n",
+                     "%-6s%5d %-4.4s%4.4s%c%4d %8.3f %8.3f %8.3f %6.2f %6.2f\n",
                      pdbtp[record],
                      atom_seq_number,
                      atom_name,
@@@ -305,15 -306,23 +305,15 @@@ void write_pdbfile_indexed(FILE *out, c
                             const t_atoms *atoms, const rvec x[],
                             int ePBC, const matrix box, char chainid,
                             int model_nr, int nindex, const int index[],
 -                           gmx_conect conect, gmx_bool bTerSepChains,
 +                           gmx_conect conect,
                             bool usePqrFormat)
  {
 -    gmx_conect_t     *gc = static_cast<gmx_conect_t *>(conect);
 -    int               i, ii;
 -    int               resind, resnr;
 -    enum PDB_record   type;
 -    unsigned char     resic, ch;
 -    char              altloc;
 -    real              occup, bfac;
 -    gmx_bool          bOccup;
 -    int               chainnum, lastchainnum;
 -    gmx_residuetype_t*rt;
 -    const char       *p_restype;
 -    const char       *p_lastrestype;
 -
 -    gmx_residuetype_init(&rt);
 +    gmx_conect_t   *gc = static_cast<gmx_conect_t *>(conect);
 +    enum PDB_record type;
 +    char            altloc;
 +    real            occup, bfac;
 +    gmx_bool        bOccup;
 +
  
      fprintf(out, "TITLE     %s\n", (title && title[0]) ? title : gmx::bromacs().c_str());
      if (box && ( (norm2(box[XX]) != 0.0f) || (norm2(box[YY]) != 0.0f) || (norm2(box[ZZ]) != 0.0f) ) )
           * otherwise set them all to one
           */
          bOccup = TRUE;
 -        for (ii = 0; (ii < nindex) && bOccup; ii++)
 +        for (int ii = 0; (ii < nindex) && bOccup; ii++)
          {
 -            i      = index[ii];
 +            int i      = index[ii];
              bOccup = bOccup && (atoms->pdbinfo[i].occup == 0.0);
          }
      }
  
      fprintf(out, "MODEL %8d\n", model_nr > 0 ? model_nr : 1);
  
 -    lastchainnum      = -1;
 -    p_restype         = nullptr;
 -
 -    for (ii = 0; ii < nindex; ii++)
 +    ResidueType rt;
 +    for (int ii = 0; ii < nindex; ii++)
      {
 -        i             = index[ii];
 -        resind        = atoms->atom[i].resind;
 -        chainnum      = atoms->resinfo[resind].chainnum;
 -        p_lastrestype = p_restype;
 -        gmx_residuetype_get_type(rt, *atoms->resinfo[resind].name, &p_restype);
 -
 -        /* Add a TER record if we changed chain, and if either the previous or this chain is protein/DNA/RNA. */
 -        if (bTerSepChains && ii > 0 && chainnum != lastchainnum)
 -        {
 -            /* Only add TER if the previous chain contained protein/DNA/RNA. */
 -            if (gmx_residuetype_is_protein(rt, p_lastrestype) || gmx_residuetype_is_dna(rt, p_lastrestype) || gmx_residuetype_is_rna(rt, p_lastrestype))
 -            {
 -                fprintf(out, "TER\n");
 -            }
 -            lastchainnum    = chainnum;
 -        }
 -
 -        std::string resnm = *atoms->resinfo[resind].name;
 -        std::string nm    = *atoms->atomname[i];
 +        int         i             = index[ii];
 +        int         resind        = atoms->atom[i].resind;
 +        std::string resnm         = *atoms->resinfo[resind].name;
 +        std::string nm            = *atoms->atomname[i];
  
          /* rename HG12 to 2HG1, etc. */
          nm    = xlate_atomname_gmx2pdb(nm);
 -        resnr = atoms->resinfo[resind].nr;
 -        resic = atoms->resinfo[resind].ic;
 +        int           resnr = atoms->resinfo[resind].nr;
 +        unsigned char resic = atoms->resinfo[resind].ic;
 +        unsigned char ch;
          if (chainid != ' ')
          {
              ch = chainid;
      if (nullptr != gc)
      {
          /* Write conect records */
 -        for (i = 0; (i < gc->nconect); i++)
 +        for (int i = 0; (i < gc->nconect); i++)
          {
              fprintf(out, "CONECT%5d%5d\n", gc->conect[i].ai+1, gc->conect[i].aj+1);
          }
      }
 -
 -    gmx_residuetype_destroy(rt);
  }
  
  void write_pdbfile(FILE *out, const char *title, const t_atoms *atoms, const rvec x[],
 -                   int ePBC, const matrix box, char chainid, int model_nr, gmx_conect conect, gmx_bool bTerSepChains)
 +                   int ePBC, const matrix box, char chainid, int model_nr, gmx_conect conect)
  {
      int i, *index;
  
          index[i] = i;
      }
      write_pdbfile_indexed(out, title, atoms, x, ePBC, box, chainid, model_nr,
 -                          atoms->nr, index, conect, bTerSepChains, false);
 +                          atoms->nr, index, conect, false);
      sfree(index);
  }
  
@@@ -534,11 -561,11 +534,11 @@@ static void read_anisou(char line[], in
      }
  }
  
 -void get_pdb_atomnumber(const t_atoms *atoms, gmx_atomprop_t aps)
 +void get_pdb_atomnumber(const t_atoms *atoms, AtomProperties *aps)
  {
      int    i, atomnumber, len;
      size_t k;
 -    char   anm[6], anm_copy[6], *ptr;
 +    char   anm[6], anm_copy[6];
      char   nc = '\0';
      real   eval;
  
          if ((anm[0] != ' ') && ((len <= 2) || !std::isdigit(anm[2])))
          {
              anm_copy[2] = nc;
 -            if (gmx_atomprop_query(aps, epropElement, "???", anm_copy, &eval))
 +            if (aps->setAtomProperty(epropElement, "???", anm_copy, &eval))
              {
                  atomnumber    = gmx::roundToInt(eval);
                  atomNumberSet = true;
              else
              {
                  anm_copy[1] = nc;
 -                if (gmx_atomprop_query(aps, epropElement, "???", anm_copy, &eval))
 +                if (aps->setAtomProperty(epropElement, "???", anm_copy, &eval))
                  {
                      atomnumber    = gmx::roundToInt(eval);
                      atomNumberSet = true;
              }
              anm_copy[0] = anm[k];
              anm_copy[1] = nc;
 -            if (gmx_atomprop_query(aps, epropElement, "???", anm_copy, &eval))
 +            if (aps->setAtomProperty(epropElement, "???", anm_copy, &eval))
              {
                  atomnumber    = gmx::roundToInt(eval);
                  atomNumberSet = true;
              }
          }
 +        std::string buf;
          if (atomNumberSet)
          {
              atoms->atom[i].atomnumber = atomnumber;
 -            ptr = gmx_atomprop_element(aps, atomnumber);
 +            buf = aps->elementFromAtomNumber(atomnumber);
              if (debug)
              {
                  fprintf(debug, "Atomnumber for atom '%s' is %d\n",
                          anm, atomnumber);
              }
          }
 -        else
 -        {
 -            ptr = nullptr;
 -        }
 -        std::strncpy(atoms->atom[i].elem, ptr == nullptr ? "" : ptr, 4);
 +        buf.resize(3);
 +        std::strncpy(atoms->atom[i].elem, buf.c_str(), 4);
      }
  }
  
index 12d9d1d2276e4e6bb68c64725d744c161e52ed07,2bcc8e145b53ec18e2370a6931a93beeb3676410..c8175ac1b880f57dd79b21d1d33b2ecd54c7d87b
@@@ -63,7 -63,7 +63,7 @@@
  #include "gromacs/math/vec.h"
  #include "gromacs/mdtypes/inputrec.h"
  #include "gromacs/mdtypes/md_enums.h"
 -#include "gromacs/mdtypes/pull-params.h"
 +#include "gromacs/mdtypes/pull_params.h"
  #include "gromacs/mdtypes/state.h"
  #include "gromacs/pulling/pull.h"
  #include "gromacs/random/tabulatednormaldistribution.h"
@@@ -2050,7 -2050,8 +2050,8 @@@ static void read_tpr_header(const char 
          header->pcrd[i].pull_type     = ir->pull->coord[i].eType;
          header->pcrd[i].geometry      = ir->pull->coord[i].eGeom;
          header->pcrd[i].ngroup        = ir->pull->coord[i].ngroup;
-         header->pcrd[i].k             = ir->pull->coord[i].k;
+         /* Angle type coordinates are handled fully in degrees in gmx wham */
+         header->pcrd[i].k             = ir->pull->coord[i].k*pull_conversion_factor_internal2userinput(&ir->pull->coord[i]);
          header->pcrd[i].init_dist     = ir->pull->coord[i].init;
  
          copy_ivec(ir->pull->coord[i].dim, header->pcrd[i].dim);
index 089d178113058187f373a42bee990368fa68f604,0000000000000000000000000000000000000000..bbec8d7fc9a21c317e6ecb524a9cbeef2661aa69
mode 100644,000000..100644
--- /dev/null
@@@ -1,1358 -1,0 +1,1358 @@@
-     if ((bGrasp || bCONECT) && (outftp != efPDB))
 +/*
 + * This file is part of the GROMACS molecular simulation package.
 + *
 + * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
 + * Copyright (c) 2001-2004, The GROMACS development team.
 + * Copyright (c) 2013,2014,2015,2016,2017,2018,2019, by the GROMACS development team, led by
 + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
 + * and including many others, as listed in the AUTHORS file in the
 + * top-level source directory and at http://www.gromacs.org.
 + *
 + * GROMACS is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public License
 + * as published by the Free Software Foundation; either version 2.1
 + * of the License, or (at your option) any later version.
 + *
 + * GROMACS is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * Lesser General Public License for more details.
 + *
 + * You should have received a copy of the GNU Lesser General Public
 + * License along with GROMACS; if not, see
 + * http://www.gnu.org/licenses, or write to the Free Software Foundation,
 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
 + *
 + * If you want to redistribute modifications to GROMACS, please
 + * consider that scientific software is very special. Version
 + * control is crucial - bugs must be traceable. We will be happy to
 + * consider code for inclusion in the official distribution, but
 + * derived work must not be called official GROMACS. Details are found
 + * in the README & COPYING files - if they are missing, get the
 + * official version at http://www.gromacs.org.
 + *
 + * To help us fund GROMACS development, we humbly ask that you cite
 + * the research papers on the package. Check out http://www.gromacs.org.
 + */
 +#include "gmxpre.h"
 +
 +#include "editconf.h"
 +
 +#include <cmath>
 +#include <cstring>
 +
 +#include <algorithm>
 +#include <string>
 +
 +#include "gromacs/commandline/pargs.h"
 +#include "gromacs/commandline/viewit.h"
 +#include "gromacs/fileio/confio.h"
 +#include "gromacs/fileio/pdbio.h"
 +#include "gromacs/fileio/tpxio.h"
 +#include "gromacs/fileio/trxio.h"
 +#include "gromacs/gmxana/princ.h"
 +#include "gromacs/gmxlib/conformation_utilities.h"
 +#include "gromacs/math/functions.h"
 +#include "gromacs/math/units.h"
 +#include "gromacs/math/vec.h"
 +#include "gromacs/pbcutil/pbc.h"
 +#include "gromacs/pbcutil/rmpbc.h"
 +#include "gromacs/topology/atomprop.h"
 +#include "gromacs/topology/index.h"
 +#include "gromacs/topology/topology.h"
 +#include "gromacs/utility/arraysize.h"
 +#include "gromacs/utility/cstringutil.h"
 +#include "gromacs/utility/fatalerror.h"
 +#include "gromacs/utility/futil.h"
 +#include "gromacs/utility/gmxassert.h"
 +#include "gromacs/utility/smalloc.h"
 +#include "gromacs/utility/strdb.h"
 +
 +static real calc_mass(t_atoms *atoms, gmx_bool bGetMass, AtomProperties *aps)
 +{
 +    real tmass;
 +    int  i;
 +
 +    tmass = 0;
 +    for (i = 0; (i < atoms->nr); i++)
 +    {
 +        if (bGetMass)
 +        {
 +            aps->setAtomProperty(epropMass,
 +                                 std::string(*atoms->resinfo[atoms->atom[i].resind].name),
 +                                 std::string(*atoms->atomname[i]), &(atoms->atom[i].m));
 +        }
 +        tmass += atoms->atom[i].m;
 +    }
 +
 +    return tmass;
 +}
 +
 +static real calc_geom(int isize, const int *index, rvec *x, rvec geom_center, rvec minval,
 +                      rvec maxval, gmx_bool bDiam)
 +{
 +    real  diam2, d;
 +    int   ii, i, j;
 +
 +    clear_rvec(geom_center);
 +    diam2 = 0;
 +    if (isize == 0)
 +    {
 +        clear_rvec(minval);
 +        clear_rvec(maxval);
 +    }
 +    else
 +    {
 +        if (index)
 +        {
 +            ii = index[0];
 +        }
 +        else
 +        {
 +            ii = 0;
 +        }
 +        for (j = 0; j < DIM; j++)
 +        {
 +            minval[j] = maxval[j] = x[ii][j];
 +        }
 +        for (i = 0; i < isize; i++)
 +        {
 +            if (index)
 +            {
 +                ii = index[i];
 +            }
 +            else
 +            {
 +                ii = i;
 +            }
 +            rvec_inc(geom_center, x[ii]);
 +            for (j = 0; j < DIM; j++)
 +            {
 +                if (x[ii][j] < minval[j])
 +                {
 +                    minval[j] = x[ii][j];
 +                }
 +                if (x[ii][j] > maxval[j])
 +                {
 +                    maxval[j] = x[ii][j];
 +                }
 +            }
 +            if (bDiam)
 +            {
 +                if (index)
 +                {
 +                    for (j = i + 1; j < isize; j++)
 +                    {
 +                        d     = distance2(x[ii], x[index[j]]);
 +                        diam2 = std::max(d, diam2);
 +                    }
 +                }
 +                else
 +                {
 +                    for (j = i + 1; j < isize; j++)
 +                    {
 +                        d     = distance2(x[i], x[j]);
 +                        diam2 = std::max(d, diam2);
 +                    }
 +                }
 +            }
 +        }
 +        svmul(1.0 / isize, geom_center, geom_center);
 +    }
 +
 +    return std::sqrt(diam2);
 +}
 +
 +static void center_conf(int natom, rvec *x, rvec center, rvec geom_cent)
 +{
 +    int  i;
 +    rvec shift;
 +
 +    rvec_sub(center, geom_cent, shift);
 +
 +    printf("    shift       :%7.3f%7.3f%7.3f (nm)\n", shift[XX], shift[YY],
 +           shift[ZZ]);
 +
 +    for (i = 0; (i < natom); i++)
 +    {
 +        rvec_inc(x[i], shift);
 +    }
 +}
 +
 +static void scale_conf(int natom, rvec x[], matrix box, const rvec scale)
 +{
 +    int i, j;
 +
 +    for (i = 0; i < natom; i++)
 +    {
 +        for (j = 0; j < DIM; j++)
 +        {
 +            x[i][j] *= scale[j];
 +        }
 +    }
 +    for (i = 0; i < DIM; i++)
 +    {
 +        for (j = 0; j < DIM; j++)
 +        {
 +            box[i][j] *= scale[j];
 +        }
 +    }
 +}
 +
 +static void read_bfac(const char *fn, int *n_bfac, double **bfac_val, int **bfac_nr)
 +{
 +    int    i;
 +    char **bfac_lines;
 +
 +    *n_bfac = get_lines(fn, &bfac_lines);
 +    snew(*bfac_val, *n_bfac);
 +    snew(*bfac_nr, *n_bfac);
 +    fprintf(stderr, "Reading %d B-factors from %s\n", *n_bfac, fn);
 +    for (i = 0; (i < *n_bfac); i++)
 +    {
 +        sscanf(bfac_lines[i], "%d %lf", &(*bfac_nr)[i], &(*bfac_val)[i]);
 +    }
 +
 +}
 +
 +static void set_pdb_conf_bfac(int natoms, int nres, t_atoms *atoms, int n_bfac,
 +                              double *bfac, int *bfac_nr, gmx_bool peratom)
 +{
 +    real     bfac_min, bfac_max;
 +    int      i, n;
 +    gmx_bool found;
 +
 +    if (n_bfac > atoms->nres)
 +    {
 +        peratom = TRUE;
 +    }
 +
 +    bfac_max = -1e10;
 +    bfac_min = 1e10;
 +    for (i = 0; (i < n_bfac); i++)
 +    {
 +        /*    if ((bfac_nr[i]-1<0) || (bfac_nr[i]-1>=atoms->nr))
 +           gmx_fatal(FARGS,"Index of B-Factor %d is out of range: %d (%g)",
 +           i+1,bfac_nr[i],bfac[i]); */
 +        if (bfac[i] > bfac_max)
 +        {
 +            bfac_max = bfac[i];
 +        }
 +        if (bfac[i] < bfac_min)
 +        {
 +            bfac_min = bfac[i];
 +        }
 +    }
 +    while ((bfac_max > 99.99) || (bfac_min < -99.99))
 +    {
 +        fprintf(stderr,
 +                "Range of values for B-factors too large (min %g, max %g) "
 +                "will scale down a factor 10\n", bfac_min, bfac_max);
 +        for (i = 0; (i < n_bfac); i++)
 +        {
 +            bfac[i] /= 10;
 +        }
 +        bfac_max /= 10;
 +        bfac_min /= 10;
 +    }
 +    while ((std::abs(bfac_max) < 0.5) && (std::abs(bfac_min) < 0.5))
 +    {
 +        fprintf(stderr,
 +                "Range of values for B-factors too small (min %g, max %g) "
 +                "will scale up a factor 10\n", bfac_min, bfac_max);
 +        for (i = 0; (i < n_bfac); i++)
 +        {
 +            bfac[i] *= 10;
 +        }
 +        bfac_max *= 10;
 +        bfac_min *= 10;
 +    }
 +
 +    for (i = 0; (i < natoms); i++)
 +    {
 +        atoms->pdbinfo[i].bfac = 0;
 +    }
 +
 +    if (!peratom)
 +    {
 +        fprintf(stderr, "Will attach %d B-factors to %d residues\n", n_bfac,
 +                nres);
 +        for (i = 0; (i < n_bfac); i++)
 +        {
 +            found = FALSE;
 +            for (n = 0; (n < natoms); n++)
 +            {
 +                if (bfac_nr[i] == atoms->resinfo[atoms->atom[n].resind].nr)
 +                {
 +                    atoms->pdbinfo[n].bfac = bfac[i];
 +                    found                  = TRUE;
 +                }
 +            }
 +            if (!found)
 +            {
 +                gmx_warning("Residue nr %d not found\n", bfac_nr[i]);
 +            }
 +        }
 +    }
 +    else
 +    {
 +        fprintf(stderr, "Will attach %d B-factors to %d atoms\n", n_bfac,
 +                natoms);
 +        for (i = 0; (i < n_bfac); i++)
 +        {
 +            atoms->pdbinfo[bfac_nr[i] - 1].bfac = bfac[i];
 +        }
 +    }
 +}
 +
 +static void pdb_legend(FILE *out, int natoms, int nres, t_atoms *atoms, rvec x[])
 +{
 +    real bfac_min, bfac_max, xmin, ymin, zmin;
 +    int  i;
 +    int  space = ' ';
 +
 +    bfac_max = -1e10;
 +    bfac_min = 1e10;
 +    xmin     = 1e10;
 +    ymin     = 1e10;
 +    zmin     = 1e10;
 +    for (i = 0; (i < natoms); i++)
 +    {
 +        xmin     = std::min(xmin, x[i][XX]);
 +        ymin     = std::min(ymin, x[i][YY]);
 +        zmin     = std::min(zmin, x[i][ZZ]);
 +        bfac_min = std::min(bfac_min, atoms->pdbinfo[i].bfac);
 +        bfac_max = std::max(bfac_max, atoms->pdbinfo[i].bfac);
 +    }
 +    fprintf(stderr, "B-factors range from %g to %g\n", bfac_min, bfac_max);
 +    for (i = 1; (i < 12); i++)
 +    {
 +        fprintf(out,
 +                "%-6s%5d  %-4.4s%3.3s %c%4d%c   %8.3f%8.3f%8.3f%6.2f%6.2f\n",
 +                "ATOM  ", natoms + 1 + i, "CA", "LEG", space, nres + 1, space,
 +                (xmin + (i * 0.12)) * 10, ymin * 10, zmin * 10, 1.0, bfac_min
 +                + ((i - 1.0) * (bfac_max - bfac_min) / 10));
 +    }
 +}
 +
 +static void visualize_images(const char *fn, int ePBC, matrix box)
 +{
 +    t_atoms atoms;
 +    rvec   *img;
 +    char   *c, *ala;
 +    int     nat, i;
 +
 +    nat = NTRICIMG + 1;
 +    init_t_atoms(&atoms, nat, FALSE);
 +    atoms.nr = nat;
 +    snew(img, nat);
 +    /* FIXME: Constness should not be cast away */
 +    c   = const_cast<char*>("C");
 +    ala = const_cast<char*>("ALA");
 +    for (i = 0; i < nat; i++)
 +    {
 +        atoms.atomname[i]        = &c;
 +        atoms.atom[i].resind     = i;
 +        atoms.resinfo[i].name    = &ala;
 +        atoms.resinfo[i].nr      = i + 1;
 +        atoms.resinfo[i].chainid = 'A' + i / NCUCVERT;
 +    }
 +    calc_triclinic_images(box, img + 1);
 +
 +    write_sto_conf(fn, "Images", &atoms, img, nullptr, ePBC, box);
 +
 +    done_atom(&atoms);
 +    sfree(img);
 +}
 +
 +static void visualize_box(FILE *out, int a0, int r0, matrix box, const rvec gridsize)
 +{
 +    int  *edge;
 +    rvec *vert, shift;
 +    int   nx, ny, nz, nbox, nat;
 +    int   i, j, x, y, z;
 +    int   rectedge[24] =
 +    {
 +        0, 1, 1, 3, 3, 2, 0, 2, 0, 4, 1, 5, 3, 7, 2, 6, 4, 5, 5, 7, 7, 6, 6,
 +        4
 +    };
 +
 +    a0++;
 +    r0++;
 +
 +    nx   = gmx::roundToInt(gridsize[XX]);
 +    ny   = gmx::roundToInt(gridsize[YY]);
 +    nz   = gmx::roundToInt(gridsize[ZZ]);
 +    nbox = nx * ny * nz;
 +    if (TRICLINIC(box))
 +    {
 +        nat = nbox * NCUCVERT;
 +        snew(vert, nat);
 +        calc_compact_unitcell_vertices(ecenterDEF, box, vert);
 +        j = 0;
 +        for (z = 0; z < nz; z++)
 +        {
 +            for (y = 0; y < ny; y++)
 +            {
 +                for (x = 0; x < nx; x++)
 +                {
 +                    for (i = 0; i < DIM; i++)
 +                    {
 +                        shift[i] = x * box[0][i] + y * box[1][i] + z
 +                            * box[2][i];
 +                    }
 +                    for (i = 0; i < NCUCVERT; i++)
 +                    {
 +                        rvec_add(vert[i], shift, vert[j]);
 +                        j++;
 +                    }
 +                }
 +            }
 +        }
 +
 +        for (i = 0; i < nat; i++)
 +        {
 +            gmx_fprintf_pdb_atomline(out, epdbATOM, a0 + i, "C", ' ', "BOX", 'K' + i / NCUCVERT, r0 + i, ' ',
 +                                     10*vert[i][XX], 10*vert[i][YY], 10*vert[i][ZZ], 1.0, 0.0, "");
 +        }
 +
 +        edge = compact_unitcell_edges();
 +        for (j = 0; j < nbox; j++)
 +        {
 +            for (i = 0; i < NCUCEDGE; i++)
 +            {
 +                fprintf(out, "CONECT%5d%5d\n", a0 + j * NCUCVERT + edge[2 * i],
 +                        a0 + j * NCUCVERT + edge[2 * i + 1]);
 +            }
 +        }
 +
 +        sfree(vert);
 +    }
 +    else
 +    {
 +        i = 0;
 +        for (z = 0; z <= 1; z++)
 +        {
 +            for (y = 0; y <= 1; y++)
 +            {
 +                for (x = 0; x <= 1; x++)
 +                {
 +                    gmx_fprintf_pdb_atomline(out, epdbATOM, a0 + i, "C", ' ', "BOX", 'K' + i/8, r0+i, ' ',
 +                                             x * 10 * box[XX][XX], y * 10 * box[YY][YY], z * 10 * box[ZZ][ZZ], 1.0, 0.0, "");
 +                    i++;
 +                }
 +            }
 +        }
 +        for (i = 0; i < 24; i += 2)
 +        {
 +            fprintf(out, "CONECT%5d%5d\n", a0 + rectedge[i], a0 + rectedge[i + 1]);
 +        }
 +    }
 +}
 +
 +static void calc_rotmatrix(rvec principal_axis, rvec targetvec, matrix rotmatrix)
 +{
 +    rvec rotvec;
 +    real ux, uy, uz, costheta, sintheta;
 +
 +    costheta = cos_angle(principal_axis, targetvec);
 +    sintheta = std::sqrt(1.0-costheta*costheta); /* sign is always positive since 0<theta<pi */
 +
 +    /* Determine rotation from cross product with target vector */
 +    cprod(principal_axis, targetvec, rotvec);
 +    unitv(rotvec, rotvec);
 +    printf("Aligning %g %g %g to %g %g %g : xprod  %g %g %g\n",
 +           principal_axis[XX], principal_axis[YY], principal_axis[ZZ], targetvec[XX], targetvec[YY], targetvec[ZZ],
 +           rotvec[XX], rotvec[YY], rotvec[ZZ]);
 +
 +    ux              = rotvec[XX];
 +    uy              = rotvec[YY];
 +    uz              = rotvec[ZZ];
 +    rotmatrix[0][0] = ux*ux + (1.0-ux*ux)*costheta;
 +    rotmatrix[0][1] = ux*uy*(1-costheta)-uz*sintheta;
 +    rotmatrix[0][2] = ux*uz*(1-costheta)+uy*sintheta;
 +    rotmatrix[1][0] = ux*uy*(1-costheta)+uz*sintheta;
 +    rotmatrix[1][1] = uy*uy + (1.0-uy*uy)*costheta;
 +    rotmatrix[1][2] = uy*uz*(1-costheta)-ux*sintheta;
 +    rotmatrix[2][0] = ux*uz*(1-costheta)-uy*sintheta;
 +    rotmatrix[2][1] = uy*uz*(1-costheta)+ux*sintheta;
 +    rotmatrix[2][2] = uz*uz + (1.0-uz*uz)*costheta;
 +
 +    printf("Rotation matrix: \n%g %g %g\n%g %g %g\n%g %g %g\n",
 +           rotmatrix[0][0], rotmatrix[0][1], rotmatrix[0][2],
 +           rotmatrix[1][0], rotmatrix[1][1], rotmatrix[1][2],
 +           rotmatrix[2][0], rotmatrix[2][1], rotmatrix[2][2]);
 +}
 +
 +static void renum_resnr(t_atoms *atoms, int isize, const int *index,
 +                        int resnr_start)
 +{
 +    int i, resind_prev, resind;
 +
 +    resind_prev = -1;
 +    for (i = 0; i < isize; i++)
 +    {
 +        resind = atoms->atom[index == nullptr ? i : index[i]].resind;
 +        if (resind != resind_prev)
 +        {
 +            atoms->resinfo[resind].nr = resnr_start;
 +            resnr_start++;
 +        }
 +        resind_prev = resind;
 +    }
 +}
 +
 +int gmx_editconf(int argc, char *argv[])
 +{
 +    const char     *desc[] =
 +    {
 +        "[THISMODULE] converts generic structure format to [REF].gro[ref], [TT].g96[tt]",
 +        "or [REF].pdb[ref].",
 +        "[PAR]",
 +        "The box can be modified with options [TT]-box[tt], [TT]-d[tt] and",
 +        "[TT]-angles[tt]. Both [TT]-box[tt] and [TT]-d[tt]",
 +        "will center the system in the box, unless [TT]-noc[tt] is used.",
 +        "The [TT]-center[tt] option can be used to shift the geometric center",
 +        "of the system from the default of (x/2, y/2, z/2) implied by [TT]-c[tt]",
 +        "to some other value.",
 +        "[PAR]",
 +        "Option [TT]-bt[tt] determines the box type: [TT]triclinic[tt] is a",
 +        "triclinic box, [TT]cubic[tt] is a rectangular box with all sides equal",
 +        "[TT]dodecahedron[tt] represents a rhombic dodecahedron and",
 +        "[TT]octahedron[tt] is a truncated octahedron.",
 +        "The last two are special cases of a triclinic box.",
 +        "The length of the three box vectors of the truncated octahedron is the",
 +        "shortest distance between two opposite hexagons.",
 +        "Relative to a cubic box with some periodic image distance, the volume of a ",
 +        "dodecahedron with this same periodic distance is 0.71 times that of the cube, ",
 +        "and that of a truncated octahedron is 0.77 times.",
 +        "[PAR]",
 +        "Option [TT]-box[tt] requires only",
 +        "one value for a cubic, rhombic dodecahedral, or truncated octahedral box.",
 +        "[PAR]",
 +        "With [TT]-d[tt] and a [TT]triclinic[tt] box the size of the system in the [IT]x[it]-, [IT]y[it]-,",
 +        "and [IT]z[it]-directions is used. With [TT]-d[tt] and [TT]cubic[tt],",
 +        "[TT]dodecahedron[tt] or [TT]octahedron[tt] boxes, the dimensions are set",
 +        "to the diameter of the system (largest distance between atoms) plus twice",
 +        "the specified distance.",
 +        "[PAR]",
 +        "Option [TT]-angles[tt] is only meaningful with option [TT]-box[tt] and",
 +        "a triclinic box and cannot be used with option [TT]-d[tt].",
 +        "[PAR]",
 +        "When [TT]-n[tt] or [TT]-ndef[tt] is set, a group",
 +        "can be selected for calculating the size and the geometric center,",
 +        "otherwise the whole system is used.",
 +        "[PAR]",
 +        "[TT]-rotate[tt] rotates the coordinates and velocities.",
 +        "[PAR]",
 +        "[TT]-princ[tt] aligns the principal axes of the system along the",
 +        "coordinate axes, with the longest axis aligned with the [IT]x[it]-axis. ",
 +        "This may allow you to decrease the box volume,",
 +        "but beware that molecules can rotate significantly in a nanosecond.",
 +        "[PAR]",
 +        "Scaling is applied before any of the other operations are",
 +        "performed. Boxes and coordinates can be scaled to give a certain density (option",
 +        "[TT]-density[tt]). Note that this may be inaccurate in case a [REF].gro[ref]",
 +        "file is given as input. A special feature of the scaling option is that when the",
 +        "factor -1 is given in one dimension, one obtains a mirror image,",
 +        "mirrored in one of the planes. When one uses -1 in three dimensions, ",
 +        "a point-mirror image is obtained.[PAR]",
 +        "Groups are selected after all operations have been applied.[PAR]",
 +        "Periodicity can be removed in a crude manner.",
 +        "It is important that the box vectors at the bottom of your input file",
 +        "are correct when the periodicity is to be removed.",
 +        "[PAR]",
 +        "When writing [REF].pdb[ref] files, B-factors can be",
 +        "added with the [TT]-bf[tt] option. B-factors are read",
 +        "from a file with with following format: first line states number of",
 +        "entries in the file, next lines state an index",
 +        "followed by a B-factor. The B-factors will be attached per residue",
 +        "unless the number of B-factors is larger than the number of the residues or unless the",
 +        "[TT]-atom[tt] option is set. Obviously, any type of numeric data can",
 +        "be added instead of B-factors. [TT]-legend[tt] will produce",
 +        "a row of CA atoms with B-factors ranging from the minimum to the",
 +        "maximum value found, effectively making a legend for viewing.",
 +        "[PAR]",
 +        "With the option [TT]-mead[tt] a special [REF].pdb[ref] ([REF].pqr[ref])",
 +        "file for the MEAD electrostatics",
 +        "program (Poisson-Boltzmann solver) can be made. A further prerequisite",
 +        "is that the input file is a run input file.",
 +        "The B-factor field is then filled with the Van der Waals radius",
 +        "of the atoms while the occupancy field will hold the charge.",
 +        "[PAR]",
 +        "The option [TT]-grasp[tt] is similar, but it puts the charges in the B-factor",
 +        "and the radius in the occupancy.",
 +        "[PAR]",
 +        "Option [TT]-align[tt] allows alignment",
 +        "of the principal axis of a specified group against the given vector, ",
 +        "with an optional center of rotation specified by [TT]-aligncenter[tt].",
 +        "[PAR]",
 +        "Finally, with option [TT]-label[tt], [TT]editconf[tt] can add a chain identifier",
 +        "to a [REF].pdb[ref] file, which can be useful for analysis with e.g. Rasmol.",
 +        "[PAR]",
 +        "To convert a truncated octrahedron file produced by a package which uses",
 +        "a cubic box with the corners cut off (such as GROMOS), use::",
 +        "",
 +        "  gmx editconf -f in -rotate 0 45 35.264 -bt o -box veclen -o out",
 +        "",
 +        "where [TT]veclen[tt] is the size of the cubic box times [SQRT]3[sqrt]/2."
 +    };
 +    const char     *bugs[] =
 +    {
 +        "For complex molecules, the periodicity removal routine may break down, "
 +        "in that case you can use [gmx-trjconv]."
 +    };
 +    static real     dist    = 0.0;
 +    static gmx_bool bNDEF   = FALSE, bRMPBC = FALSE, bCenter = FALSE, bReadVDW =
 +        FALSE, bCONECT      = FALSE;
 +    static gmx_bool peratom = FALSE, bLegend = FALSE, bOrient = FALSE, bMead =
 +        FALSE, bGrasp       = FALSE, bSig56 = FALSE;
 +    static rvec     scale   =
 +    { 1, 1, 1 }, newbox     =
 +    { 0, 0, 0 }, newang     =
 +    { 90, 90, 90 };
 +    static real rho          = 1000.0, rvdw = 0.12;
 +    static rvec center       =
 +    { 0, 0, 0 }, translation =
 +    { 0, 0, 0 }, rotangles   =
 +    { 0, 0, 0 }, aligncenter =
 +    { 0, 0, 0 }, targetvec   =
 +    { 0, 0, 0 };
 +    static const char *btype[] =
 +    { nullptr, "triclinic", "cubic", "dodecahedron", "octahedron", nullptr },
 +    *label             = "A";
 +    static rvec visbox =
 +    { 0, 0, 0 };
 +    static int  resnr_start = -1;
 +    t_pargs
 +                pa[] =
 +    {
 +        { "-ndef", FALSE, etBOOL,
 +          { &bNDEF }, "Choose output from default index groups" },
 +        { "-visbox", FALSE, etRVEC,
 +          { visbox },
 +          "HIDDENVisualize a grid of boxes, -1 visualizes the 14 box images" },
 +        { "-bt", FALSE, etENUM,
 +          { btype }, "Box type for [TT]-box[tt] and [TT]-d[tt]" },
 +        { "-box", FALSE, etRVEC,
 +          { newbox }, "Box vector lengths (a,b,c)" },
 +        { "-angles", FALSE, etRVEC,
 +          { newang }, "Angles between the box vectors (bc,ac,ab)" },
 +        { "-d", FALSE, etREAL,
 +          { &dist }, "Distance between the solute and the box" },
 +        { "-c", FALSE, etBOOL,
 +          { &bCenter },
 +          "Center molecule in box (implied by [TT]-box[tt] and [TT]-d[tt])" },
 +        { "-center", FALSE, etRVEC,
 +          { center }, "Shift the geometrical center to (x,y,z)" },
 +        { "-aligncenter", FALSE, etRVEC,
 +          { aligncenter }, "Center of rotation for alignment" },
 +        { "-align", FALSE, etRVEC,
 +          { targetvec },
 +          "Align to target vector" },
 +        { "-translate", FALSE, etRVEC,
 +          { translation }, "Translation" },
 +        { "-rotate", FALSE, etRVEC,
 +          { rotangles },
 +          "Rotation around the X, Y and Z axes in degrees" },
 +        { "-princ", FALSE, etBOOL,
 +          { &bOrient },
 +          "Orient molecule(s) along their principal axes" },
 +        { "-scale", FALSE, etRVEC,
 +          { scale }, "Scaling factor" },
 +        { "-density", FALSE, etREAL,
 +          { &rho },
 +          "Density (g/L) of the output box achieved by scaling" },
 +        { "-pbc", FALSE, etBOOL,
 +          { &bRMPBC },
 +          "Remove the periodicity (make molecule whole again)" },
 +        { "-resnr", FALSE, etINT,
 +          { &resnr_start },
 +          " Renumber residues starting from resnr" },
 +        { "-grasp", FALSE, etBOOL,
 +          { &bGrasp },
 +          "Store the charge of the atom in the B-factor field and the radius of the atom in the occupancy field" },
 +        {
 +            "-rvdw", FALSE, etREAL,
 +            { &rvdw },
 +            "Default Van der Waals radius (in nm) if one can not be found in the database or if no parameters are present in the topology file"
 +        },
 +        { "-sig56", FALSE, etBOOL,
 +          { &bSig56 },
 +          "Use rmin/2 (minimum in the Van der Waals potential) rather than [GRK]sigma[grk]/2 " },
 +        {
 +            "-vdwread", FALSE, etBOOL,
 +            { &bReadVDW },
 +            "Read the Van der Waals radii from the file [TT]vdwradii.dat[tt] rather than computing the radii based on the force field"
 +        },
 +        { "-atom", FALSE, etBOOL,
 +          { &peratom }, "Force B-factor attachment per atom" },
 +        { "-legend", FALSE, etBOOL,
 +          { &bLegend }, "Make B-factor legend" },
 +        { "-label", FALSE, etSTR,
 +          { &label }, "Add chain label for all residues" },
 +        {
 +            "-conect", FALSE, etBOOL,
 +            { &bCONECT },
 +            "Add CONECT records to a [REF].pdb[ref] file when written. Can only be done when a topology is present"
 +        }
 +    };
 +#define NPA asize(pa)
 +
 +    FILE             *out;
 +    const char       *infile, *outfile;
 +    int               outftp, inftp, natom, i, j, n_bfac, itype, ntype;
 +    double           *bfac    = nullptr, c6, c12;
 +    int              *bfac_nr = nullptr;
 +    t_topology       *top     = nullptr;
 +    char             *grpname, *sgrpname, *agrpname;
 +    int               isize, ssize, numAlignmentAtoms;
 +    int              *index, *sindex, *aindex;
 +    rvec             *x, *v, gc, rmin, rmax, size;
 +    int               ePBC;
 +    matrix            box, rotmatrix, trans;
 +    rvec              princd, tmpvec;
 +    gmx_bool          bIndex, bSetSize, bSetAng, bDist, bSetCenter, bAlign;
 +    gmx_bool          bHaveV, bScale, bRho, bTranslate, bRotate, bCalcGeom, bCalcDiam;
 +    real              diam = 0, mass = 0, d, vdw;
 +    gmx_conect        conect;
 +    gmx_output_env_t *oenv;
 +    t_filenm          fnm[] =
 +    {
 +        { efSTX, "-f", nullptr, ffREAD },
 +        { efNDX, "-n", nullptr, ffOPTRD },
 +        { efSTO, nullptr, nullptr, ffOPTWR },
 +        { efPQR, "-mead", "mead", ffOPTWR },
 +        { efDAT, "-bf", "bfact", ffOPTRD }
 +    };
 +#define NFILE asize(fnm)
 +
 +    if (!parse_common_args(&argc, argv, PCA_CAN_VIEW, NFILE, fnm, NPA, pa,
 +                           asize(desc), desc, asize(bugs), bugs, &oenv))
 +    {
 +        return 0;
 +    }
 +    fprintf(stdout, "Note that major changes are planned in future for "
 +            "editconf, to improve usability and utility.\n");
 +
 +    bIndex     = opt2bSet("-n", NFILE, fnm) || bNDEF;
 +    bMead      = opt2bSet("-mead", NFILE, fnm);
 +    bSetSize   = opt2parg_bSet("-box", NPA, pa);
 +    bSetAng    = opt2parg_bSet("-angles", NPA, pa);
 +    bSetCenter = opt2parg_bSet("-center", NPA, pa);
 +    bDist      = opt2parg_bSet("-d", NPA, pa);
 +    bAlign     = opt2parg_bSet("-align", NPA, pa);
 +    /* Only automatically turn on centering without -noc */
 +    if ((bDist || bSetSize || bSetCenter) && !opt2parg_bSet("-c", NPA, pa))
 +    {
 +        bCenter = TRUE;
 +    }
 +    bScale     = opt2parg_bSet("-scale", NPA, pa);
 +    bRho       = opt2parg_bSet("-density", NPA, pa);
 +    bTranslate = opt2parg_bSet("-translate", NPA, pa);
 +    bRotate    = opt2parg_bSet("-rotate", NPA, pa);
 +    if (bScale && bRho)
 +    {
 +        fprintf(stderr, "WARNING: setting -density overrides -scale\n");
 +    }
 +    bScale    = bScale || bRho;
 +    bCalcGeom = bCenter || bRotate || bOrient || bScale;
 +
 +    GMX_RELEASE_ASSERT(btype[0] != nullptr, "Option setting inconsistency; btype[0] is NULL");
 +
 +    bCalcDiam = (btype[0][0] == 'c' || btype[0][0] == 'd' || btype[0][0] == 'o');
 +
 +    infile = ftp2fn(efSTX, NFILE, fnm);
 +    if (bMead)
 +    {
 +        outfile = ftp2fn(efPQR, NFILE, fnm);
 +    }
 +    else
 +    {
 +        outfile = ftp2fn(efSTO, NFILE, fnm);
 +    }
 +    outftp = fn2ftp(outfile);
 +    inftp  = fn2ftp(infile);
 +
 +    AtomProperties aps;
 +
 +    if (bMead && bGrasp)
 +    {
 +        printf("Incompatible options -mead and -grasp. Turning off -grasp\n");
 +        bGrasp = FALSE;
 +    }
-                   " when using the -grasp or -connect options\n");
++    if (bGrasp && (outftp != efPDB))
 +    {
 +        gmx_fatal(FARGS, "Output file should be a .pdb file"
-     if ((bMead || bGrasp || bCONECT) && (fn2ftp(infile) != efTPR))
++                  " when using the -grasp option\n");
 +    }
-                   " when using the -mead or -connect options\n");
++    if ((bMead || bGrasp) && (fn2ftp(infile) != efTPR))
 +    {
 +        gmx_fatal(FARGS, "Input file should be a .tpr file"
++                  " when using the -mead option\n");
 +    }
 +
 +    t_symtab  symtab;
 +    char     *name;
 +    t_atoms   atoms;
 +    open_symtab(&symtab);
 +    readConfAndAtoms(infile, &symtab, &name, &atoms, &ePBC, &x, &v, box);
 +    natom = atoms.nr;
 +    if (atoms.pdbinfo == nullptr)
 +    {
 +        snew(atoms.pdbinfo, atoms.nr);
 +    }
 +    atoms.havePdbInfo = TRUE;
 +
 +    if (fn2ftp(infile) == efPDB)
 +    {
 +        get_pdb_atomnumber(&atoms, &aps);
 +    }
 +    printf("Read %d atoms\n", atoms.nr);
 +
 +    /* Get the element numbers if available in a pdb file */
 +    if (fn2ftp(infile) == efPDB)
 +    {
 +        get_pdb_atomnumber(&atoms, &aps);
 +    }
 +
 +    if (ePBC != epbcNONE)
 +    {
 +        real vol = det(box);
 +        printf("Volume: %g nm^3, corresponds to roughly %d electrons\n",
 +               vol, 100*(static_cast<int>(vol*4.5)));
 +    }
 +
 +    if (bMead || bGrasp || bCONECT)
 +    {
 +        top = read_top(infile, nullptr);
 +    }
 +
 +    if (bMead || bGrasp)
 +    {
 +        if (atoms.nr != top->atoms.nr)
 +        {
 +            gmx_fatal(FARGS, "Atom numbers don't match (%d vs. %d)", atoms.nr, top->atoms.nr);
 +        }
 +        snew(atoms.pdbinfo, top->atoms.nr);
 +        ntype = top->idef.atnr;
 +        for (i = 0; (i < atoms.nr); i++)
 +        {
 +            /* Determine the Van der Waals radius from the force field */
 +            if (bReadVDW)
 +            {
 +                if (!aps.setAtomProperty(epropVDW,
 +                                         *top->atoms.resinfo[top->atoms.atom[i].resind].name,
 +                                         *top->atoms.atomname[i], &vdw))
 +                {
 +                    vdw = rvdw;
 +                }
 +            }
 +            else
 +            {
 +                itype = top->atoms.atom[i].type;
 +                c12   = top->idef.iparams[itype*ntype+itype].lj.c12;
 +                c6    = top->idef.iparams[itype*ntype+itype].lj.c6;
 +                if ((c6 != 0) && (c12 != 0))
 +                {
 +                    real sig6;
 +                    if (bSig56)
 +                    {
 +                        sig6 = 2*c12/c6;
 +                    }
 +                    else
 +                    {
 +                        sig6 = c12/c6;
 +                    }
 +                    vdw   = 0.5*gmx::sixthroot(sig6);
 +                }
 +                else
 +                {
 +                    vdw = rvdw;
 +                }
 +            }
 +            /* Factor of 10 for nm -> Angstroms */
 +            vdw *= 10;
 +
 +            if (bMead)
 +            {
 +                atoms.pdbinfo[i].occup = top->atoms.atom[i].q;
 +                atoms.pdbinfo[i].bfac  = vdw;
 +            }
 +            else
 +            {
 +                atoms.pdbinfo[i].occup = vdw;
 +                atoms.pdbinfo[i].bfac  = top->atoms.atom[i].q;
 +            }
 +        }
 +    }
 +    bHaveV = FALSE;
 +    for (i = 0; (i < natom) && !bHaveV; i++)
 +    {
 +        for (j = 0; (j < DIM) && !bHaveV; j++)
 +        {
 +            bHaveV = bHaveV || (v[i][j] != 0);
 +        }
 +    }
 +    printf("%selocities found\n", bHaveV ? "V" : "No v");
 +
 +    if (visbox[0] > 0)
 +    {
 +        if (bIndex)
 +        {
 +            gmx_fatal(FARGS, "Sorry, can not visualize box with index groups");
 +        }
 +        if (outftp != efPDB)
 +        {
 +            gmx_fatal(FARGS, "Sorry, can only visualize box with a pdb file");
 +        }
 +    }
 +    else if (visbox[0] == -1)
 +    {
 +        visualize_images("images.pdb", ePBC, box);
 +    }
 +
 +    /* remove pbc */
 +    if (bRMPBC)
 +    {
 +        rm_gropbc(&atoms, x, box);
 +    }
 +
 +    if (bCalcGeom)
 +    {
 +        if (bIndex)
 +        {
 +            fprintf(stderr, "\nSelect a group for determining the system size:\n");
 +            get_index(&atoms, ftp2fn_null(efNDX, NFILE, fnm),
 +                      1, &ssize, &sindex, &sgrpname);
 +        }
 +        else
 +        {
 +            ssize  = atoms.nr;
 +            sindex = nullptr;
 +        }
 +        diam = calc_geom(ssize, sindex, x, gc, rmin, rmax, bCalcDiam);
 +        rvec_sub(rmax, rmin, size);
 +        printf("    system size :%7.3f%7.3f%7.3f (nm)\n",
 +               size[XX], size[YY], size[ZZ]);
 +        if (bCalcDiam)
 +        {
 +            printf("    diameter    :%7.3f               (nm)\n", diam);
 +        }
 +        printf("    center      :%7.3f%7.3f%7.3f (nm)\n", gc[XX], gc[YY], gc[ZZ]);
 +        printf("    box vectors :%7.3f%7.3f%7.3f (nm)\n",
 +               norm(box[XX]), norm(box[YY]), norm(box[ZZ]));
 +        printf("    box angles  :%7.2f%7.2f%7.2f (degrees)\n",
 +               norm2(box[ZZ]) == 0 ? 0 :
 +               RAD2DEG*gmx_angle(box[YY], box[ZZ]),
 +               norm2(box[ZZ]) == 0 ? 0 :
 +               RAD2DEG*gmx_angle(box[XX], box[ZZ]),
 +               norm2(box[YY]) == 0 ? 0 :
 +               RAD2DEG*gmx_angle(box[XX], box[YY]));
 +        printf("    box volume  :%7.2f               (nm^3)\n", det(box));
 +    }
 +
 +    if (bRho || bOrient || bAlign)
 +    {
 +        mass = calc_mass(&atoms, !fn2bTPX(infile), &aps);
 +    }
 +
 +    if (bOrient)
 +    {
 +        int     *index;
 +        char    *grpnames;
 +
 +        /* Get a group for principal component analysis */
 +        fprintf(stderr, "\nSelect group for the determining the orientation\n");
 +        get_index(&atoms, ftp2fn_null(efNDX, NFILE, fnm), 1, &isize, &index, &grpnames);
 +
 +        /* Orient the principal axes along the coordinate axes */
 +        orient_princ(&atoms, isize, index, natom, x, bHaveV ? v : nullptr, nullptr);
 +        sfree(index);
 +        sfree(grpnames);
 +    }
 +
 +    if (bScale)
 +    {
 +        /* scale coordinates and box */
 +        if (bRho)
 +        {
 +            /* Compute scaling constant */
 +            real vol, dens;
 +
 +            vol  = det(box);
 +            dens = (mass*AMU)/(vol*NANO*NANO*NANO);
 +            fprintf(stderr, "Volume  of input %g (nm^3)\n", vol);
 +            fprintf(stderr, "Mass    of input %g (a.m.u.)\n", mass);
 +            fprintf(stderr, "Density of input %g (g/l)\n", dens);
 +            if (vol == 0 || mass == 0)
 +            {
 +                gmx_fatal(FARGS, "Cannot scale density with "
 +                          "zero mass (%g) or volume (%g)\n", mass, vol);
 +            }
 +
 +            scale[XX] = scale[YY] = scale[ZZ] = std::cbrt(dens/rho);
 +            fprintf(stderr, "Scaling all box vectors by %g\n", scale[XX]);
 +        }
 +        scale_conf(atoms.nr, x, box, scale);
 +    }
 +
 +    if (bAlign)
 +    {
 +        if (bIndex)
 +        {
 +            fprintf(stderr, "\nSelect a group that you want to align:\n");
 +            get_index(&atoms, ftp2fn_null(efNDX, NFILE, fnm),
 +                      1, &numAlignmentAtoms, &aindex, &agrpname);
 +        }
 +        else
 +        {
 +            numAlignmentAtoms = atoms.nr;
 +            snew(aindex, numAlignmentAtoms);
 +            for (i = 0; i < numAlignmentAtoms; i++)
 +            {
 +                aindex[i] = i;
 +            }
 +        }
 +        printf("Aligning %d atoms (out of %d) to %g %g %g, center of rotation %g %g %g\n", numAlignmentAtoms, natom,
 +               targetvec[XX], targetvec[YY], targetvec[ZZ],
 +               aligncenter[XX], aligncenter[YY], aligncenter[ZZ]);
 +        /*subtract out pivot point*/
 +        for (i = 0; i < numAlignmentAtoms; i++)
 +        {
 +            rvec_dec(x[aindex[i]], aligncenter);
 +        }
 +        /*now determine transform and rotate*/
 +        /*will this work?*/
 +        principal_comp(numAlignmentAtoms, aindex, atoms.atom, x, trans, princd);
 +
 +        unitv(targetvec, targetvec);
 +        printf("Using %g %g %g as principal axis\n", trans[0][2], trans[1][2], trans[2][2]);
 +        tmpvec[XX] = trans[0][2]; tmpvec[YY] = trans[1][2]; tmpvec[ZZ] = trans[2][2];
 +        calc_rotmatrix(tmpvec, targetvec, rotmatrix);
 +        /* rotmatrix finished */
 +
 +        for (i = 0; i < numAlignmentAtoms; ++i)
 +        {
 +            mvmul(rotmatrix, x[aindex[i]], tmpvec);
 +            copy_rvec(tmpvec, x[aindex[i]]);
 +        }
 +
 +        /*add pivot point back*/
 +        for (i = 0; i < numAlignmentAtoms; i++)
 +        {
 +            rvec_inc(x[aindex[i]], aligncenter);
 +        }
 +        if (!bIndex)
 +        {
 +            sfree(aindex);
 +        }
 +    }
 +
 +    if (bTranslate)
 +    {
 +        if (bIndex)
 +        {
 +            fprintf(stderr, "\nSelect a group that you want to translate:\n");
 +            get_index(&atoms, ftp2fn_null(efNDX, NFILE, fnm),
 +                      1, &ssize, &sindex, &sgrpname);
 +        }
 +        else
 +        {
 +            ssize  = atoms.nr;
 +            sindex = nullptr;
 +        }
 +        printf("Translating %d atoms (out of %d) by %g %g %g nm\n", ssize, natom,
 +               translation[XX], translation[YY], translation[ZZ]);
 +        if (sindex)
 +        {
 +            for (i = 0; i < ssize; i++)
 +            {
 +                rvec_inc(x[sindex[i]], translation);
 +            }
 +        }
 +        else
 +        {
 +            for (i = 0; i < natom; i++)
 +            {
 +                rvec_inc(x[i], translation);
 +            }
 +        }
 +    }
 +    if (bRotate)
 +    {
 +        /* Rotate */
 +        printf("Rotating %g, %g, %g degrees around the X, Y and Z axis respectively\n", rotangles[XX], rotangles[YY], rotangles[ZZ]);
 +        for (i = 0; i < DIM; i++)
 +        {
 +            rotangles[i] *= DEG2RAD;
 +        }
 +        rotate_conf(natom, x, v, rotangles[XX], rotangles[YY], rotangles[ZZ]);
 +    }
 +
 +    if (bCalcGeom)
 +    {
 +        /* recalc geometrical center and max and min coordinates and size */
 +        calc_geom(ssize, sindex, x, gc, rmin, rmax, FALSE);
 +        rvec_sub(rmax, rmin, size);
 +        if (bScale || bOrient || bRotate)
 +        {
 +            printf("new system size : %6.3f %6.3f %6.3f\n",
 +                   size[XX], size[YY], size[ZZ]);
 +        }
 +    }
 +
 +    if ((btype[0] != nullptr) && (bSetSize || bDist || (btype[0][0] == 't' && bSetAng)))
 +    {
 +        ePBC = epbcXYZ;
 +        if (!(bSetSize || bDist))
 +        {
 +            for (i = 0; i < DIM; i++)
 +            {
 +                newbox[i] = norm(box[i]);
 +            }
 +        }
 +        clear_mat(box);
 +        /* calculate new boxsize */
 +        switch (btype[0][0])
 +        {
 +            case 't':
 +                if (bDist)
 +                {
 +                    for (i = 0; i < DIM; i++)
 +                    {
 +                        newbox[i] = size[i]+2*dist;
 +                    }
 +                }
 +                if (!bSetAng)
 +                {
 +                    box[XX][XX] = newbox[XX];
 +                    box[YY][YY] = newbox[YY];
 +                    box[ZZ][ZZ] = newbox[ZZ];
 +                }
 +                else
 +                {
 +                    matrix_convert(box, newbox, newang);
 +                }
 +                break;
 +            case 'c':
 +            case 'd':
 +            case 'o':
 +                if (bSetSize)
 +                {
 +                    d = newbox[0];
 +                }
 +                else
 +                {
 +                    d = diam+2*dist;
 +                }
 +                if (btype[0][0] == 'c')
 +                {
 +                    for (i = 0; i < DIM; i++)
 +                    {
 +                        box[i][i] = d;
 +                    }
 +                }
 +                else if (btype[0][0] == 'd')
 +                {
 +                    box[XX][XX] = d;
 +                    box[YY][YY] = d;
 +                    box[ZZ][XX] = d/2;
 +                    box[ZZ][YY] = d/2;
 +                    box[ZZ][ZZ] = d*std::sqrt(2.0)/2.0;
 +                }
 +                else
 +                {
 +                    box[XX][XX] = d;
 +                    box[YY][XX] = d/3;
 +                    box[YY][YY] = d*std::sqrt(2.0)*2.0/3.0;
 +                    box[ZZ][XX] = -d/3;
 +                    box[ZZ][YY] = d*std::sqrt(2.0)/3.0;
 +                    box[ZZ][ZZ] = d*std::sqrt(6.0)/3.0;
 +                }
 +                break;
 +        }
 +    }
 +
 +    /* calculate new coords for geometrical center */
 +    if (!bSetCenter)
 +    {
 +        calc_box_center(ecenterDEF, box, center);
 +    }
 +
 +    /* center molecule on 'center' */
 +    if (bCenter)
 +    {
 +        center_conf(natom, x, center, gc);
 +    }
 +
 +    /* print some */
 +    if (bCalcGeom)
 +    {
 +        calc_geom(ssize, sindex, x, gc, rmin, rmax, FALSE);
 +        printf("new center      :%7.3f%7.3f%7.3f (nm)\n", gc[XX], gc[YY], gc[ZZ]);
 +    }
 +    if (bOrient || bScale || bDist || bSetSize)
 +    {
 +        printf("new box vectors :%7.3f%7.3f%7.3f (nm)\n",
 +               norm(box[XX]), norm(box[YY]), norm(box[ZZ]));
 +        printf("new box angles  :%7.2f%7.2f%7.2f (degrees)\n",
 +               norm2(box[ZZ]) == 0 ? 0 :
 +               RAD2DEG*gmx_angle(box[YY], box[ZZ]),
 +               norm2(box[ZZ]) == 0 ? 0 :
 +               RAD2DEG*gmx_angle(box[XX], box[ZZ]),
 +               norm2(box[YY]) == 0 ? 0 :
 +               RAD2DEG*gmx_angle(box[XX], box[YY]));
 +        printf("new box volume  :%7.2f               (nm^3)\n", det(box));
 +    }
 +
 +    if (check_box(epbcXYZ, box))
 +    {
 +        printf("\nWARNING: %s\n"
 +               "See the GROMACS manual for a description of the requirements that\n"
 +               "must be satisfied by descriptions of simulation cells.\n",
 +               check_box(epbcXYZ, box));
 +    }
 +
 +    if (bDist && btype[0][0] == 't')
 +    {
 +        if (TRICLINIC(box))
 +        {
 +            printf("\nWARNING: Your box is triclinic with non-orthogonal axes. In this case, the\n"
 +                   "distance from the solute to a box surface along the corresponding normal\n"
 +                   "vector might be somewhat smaller than your specified value %f.\n"
 +                   "You can check the actual value with g_mindist -pi\n", dist);
 +        }
 +        else if (!opt2parg_bSet("-bt", NPA, pa))
 +        {
 +            printf("\nWARNING: No boxtype specified - distance condition applied in each dimension.\n"
 +                   "If the molecule rotates the actual distance will be smaller. You might want\n"
 +                   "to use a cubic box instead, or why not try a dodecahedron today?\n");
 +        }
 +    }
 +    if (bCONECT && (outftp == efPDB) && (inftp == efTPR))
 +    {
 +        conect = gmx_conect_generate(top);
 +    }
 +    else
 +    {
 +        conect = nullptr;
 +    }
 +
 +    if (bIndex)
 +    {
 +        fprintf(stderr, "\nSelect a group for output:\n");
 +        get_index(&atoms, opt2fn_null("-n", NFILE, fnm),
 +                  1, &isize, &index, &grpname);
 +
 +        if (resnr_start >= 0)
 +        {
 +            renum_resnr(&atoms, isize, index, resnr_start);
 +        }
 +
 +        if (opt2parg_bSet("-label", NPA, pa))
 +        {
 +            for (i = 0; (i < atoms.nr); i++)
 +            {
 +                atoms.resinfo[atoms.atom[i].resind].chainid = label[0];
 +            }
 +        }
 +
 +        if (opt2bSet("-bf", NFILE, fnm) || bLegend)
 +        {
 +            gmx_fatal(FARGS, "Sorry, cannot do bfactors with an index group.");
 +        }
 +
 +        if (outftp == efPDB)
 +        {
 +            out = gmx_ffopen(outfile, "w");
 +            write_pdbfile_indexed(out, name, &atoms, x, ePBC, box, ' ', 1, isize, index, conect, FALSE);
 +            gmx_ffclose(out);
 +        }
 +        else
 +        {
 +            write_sto_conf_indexed(outfile, name, &atoms, x, bHaveV ? v : nullptr, ePBC, box, isize, index);
 +        }
 +    }
 +    else
 +    {
 +        if (resnr_start >= 0)
 +        {
 +            renum_resnr(&atoms, atoms.nr, nullptr, resnr_start);
 +        }
 +
 +        if ((outftp == efPDB) || (outftp == efPQR))
 +        {
 +            out = gmx_ffopen(outfile, "w");
 +            if (bMead)
 +            {
 +                fprintf(out, "REMARK    "
 +                        "The B-factors in this file hold atomic radii\n");
 +                fprintf(out, "REMARK    "
 +                        "The occupancy in this file hold atomic charges\n");
 +            }
 +            else if (bGrasp)
 +            {
 +                fprintf(out, "GRASP PDB FILE\nFORMAT NUMBER=1\n");
 +                fprintf(out, "REMARK    "
 +                        "The B-factors in this file hold atomic charges\n");
 +                fprintf(out, "REMARK    "
 +                        "The occupancy in this file hold atomic radii\n");
 +            }
 +            else if (opt2bSet("-bf", NFILE, fnm))
 +            {
 +                read_bfac(opt2fn("-bf", NFILE, fnm), &n_bfac, &bfac, &bfac_nr);
 +                set_pdb_conf_bfac(atoms.nr, atoms.nres, &atoms,
 +                                  n_bfac, bfac, bfac_nr, peratom);
 +            }
 +            if (opt2parg_bSet("-label", NPA, pa))
 +            {
 +                for (i = 0; (i < atoms.nr); i++)
 +                {
 +                    atoms.resinfo[atoms.atom[i].resind].chainid = label[0];
 +                }
 +            }
 +            /* Need to bypass the regular write_pdbfile because I don't want to change
 +             * all instances to include the boolean flag for writing out PQR files.
 +             */
 +            int *index;
 +            snew(index, atoms.nr);
 +            for (int i = 0; i < atoms.nr; i++)
 +            {
 +                index[i] = i;
 +            }
 +            write_pdbfile_indexed(out, name, &atoms, x, ePBC, box, ' ', -1, atoms.nr, index, conect,
 +                                  outftp == efPQR);
 +            sfree(index);
 +            if (bLegend)
 +            {
 +                pdb_legend(out, atoms.nr, atoms.nres, &atoms, x);
 +            }
 +            if (visbox[0] > 0)
 +            {
 +                visualize_box(out, bLegend ? atoms.nr+12 : atoms.nr,
 +                              bLegend ? atoms.nres = 12 : atoms.nres, box, visbox);
 +            }
 +            gmx_ffclose(out);
 +        }
 +        else
 +        {
 +            write_sto_conf(outfile, name, &atoms, x, bHaveV ? v : nullptr, ePBC, box);
 +        }
 +    }
 +    done_atom(&atoms);
 +    done_symtab(&symtab);
 +    sfree(name);
 +    if (x)
 +    {
 +        sfree(x);
 +    }
 +    if (v)
 +    {
 +        sfree(v);
 +    }
 +    do_view(oenv, outfile, nullptr);
 +    output_env_done(oenv);
 +
 +    return 0;
 +}
index a8e621ca342a16c925ce0a0cf2bc4f39e872b68f,b9cfd8a20bf846952536ddc9fe765bf14efd3404..d232b55dbf109e4f0aae681c6b3cc9999a114990
@@@ -49,7 -49,6 +49,7 @@@
  #include <algorithm>
  #include <memory>
  
 +#include <unordered_set>
  #include <sys/types.h>
  
  #include "gromacs/utility/arrayref.h"
@@@ -74,16 -73,16 +74,16 @@@ struct gmx_cp
  {
      std::shared_ptr < std::vector < t_define>>    defines;
      std::shared_ptr < std::vector < std::string>> includes;
 -
 -    FILE             *fp = nullptr;
 -    std::string       path;
 -    std::string       cwd;
 -    std::string       fn;
 -    std::string       line;
 -    int               line_nr;
 -    std::vector<int>  ifdefs;
 -    struct gmx_cpp   *child  = nullptr;
 -    struct gmx_cpp   *parent = nullptr;
 +    std::unordered_set<std::string> unmatched_defines;
 +    FILE                           *fp = nullptr;
 +    std::string                     path;
 +    std::string                     cwd;
 +    std::string                     fn;
 +    std::string                     line;
 +    int                             line_nr;
 +    std::vector<int>                ifdefs;
 +    struct gmx_cpp                 *child  = nullptr;
 +    struct gmx_cpp                 *parent = nullptr;
  };
  
  static bool is_word_end(char c)
@@@ -259,14 -258,12 +259,14 @@@ cpp_open_file(const cha
                  {
                      std::string buf = cppopts[i] + 2;
                      buf.resize(ptr - cppopts[i] - 2);
 -
                      add_define(cpp->defines.get(), buf, ptr + 1);
 +                    cpp->unmatched_defines.insert(buf);
 +
                  }
                  else
                  {
                      add_define(cpp->defines.get(), cppopts[i] + 2, "");
 +                    cpp->unmatched_defines.insert(cppopts[i] + 2);
                  }
              }
              i++;
@@@ -382,14 -379,6 +382,14 @@@ process_directive(gmx_cpp_t         *ha
              {
                  if (define.name == dval)
                  {
 +                    // erase from unmatched_defines in original handle
 +                    gmx_cpp_t root = handle;
 +                    while (root->parent != nullptr)
 +                    {
 +                        root = root->parent;
 +                    }
 +                    root->unmatched_defines.erase(dval);
 +
                      found = true;
                      break;
                  }
          {
              return eCPP_SYNTAX;
          }
+         // An include needs to be followed by either a '"' or a '<' as a first character.
+         if ((dval[0] != '"') && (dval[0] != '<'))
+         {
+             return eCPP_INVALID_INCLUDE_DELIMITER;
+         }
          for (size_t i1 = 0; i1 < dval.size(); i1++)
          {
              if ((dval[i1] == '"') || (dval[i1] == '<') || (dval[i1] == '>'))
@@@ -575,7 -569,7 +580,7 @@@ int cpp_read_line(gmx_cpp_t *handlep, i
              if (!bEOF)
              {
                  /* Something strange happened, fgets returned NULL,
-                  * but we are not at EOF.
+                  * but we are not at EOF. Maybe wrong line endings?
                   */
                  return eCPP_UNKNOWN;
              }
              }
              if (nn > 0)
              {
 +                // Need to erase  unmatched define in original handle
 +                gmx_cpp_t root = handle;
 +                while (root->parent != nullptr)
 +                {
 +                    root = root->parent;
 +                }
 +                root->unmatched_defines.erase(define.name);
 +
                  std::string  name;
                  const char  *ptr = buf;
                  const char  *ptr2;
@@@ -734,8 -720,8 +739,8 @@@ char *cpp_error(gmx_cpp_t *handlep, in
      char        buf[256];
      const char *ecpp[] = {
          "OK", "File not found", "End of file", "Syntax error", "Interrupted",
-         "Invalid file handle",
-         "File not open", "Unknown error", "Error status out of range"
+         "Invalid file handle", "Invalid delimiter for filename in #include statement",
+         "File not open", "Unknown error, perhaps your text file uses wrong line endings?", "Error status out of range"
      };
      gmx_cpp_t   handle = *handlep;
  
  
      return gmx_strdup(buf);
  }
 +
 +std::string checkAndWarnForUnusedDefines(const gmx_cpp &handle)
 +{
 +    std::string warning;
 +    if (!handle.unmatched_defines.empty())
 +    {
 +        warning = "The following macros were defined in the 'define' mdp field with the -D prefix, but "
 +            "were not used in the topology:\n";
 +        for (auto &str : handle.unmatched_defines)
 +        {
 +            warning += ("    " + str + "\n");
 +        }
 +        warning += "If you haven't made a spelling error, either use the macro you defined, "
 +            "or don't define the macro";
 +    }
 +    return warning;
 +}
index f85fe06b54287f0df10410a03ee3c3ea1869fa38,35f4b5416c70a8a6355ed9253c43e812fcb0209d..988dd1a23a59b9b7497808d018707683fff4ee18
@@@ -45,7 -45,7 +45,7 @@@ typedef struct gmx_cpp *gmx_cpp_t
  /* The possible return codes for these functions */
  enum {
      eCPP_OK, eCPP_FILE_NOT_FOUND, eCPP_EOF, eCPP_SYNTAX, eCPP_INTERRUPT,
-     eCPP_INVALID_HANDLE,
+     eCPP_INVALID_HANDLE, eCPP_INVALID_INCLUDE_DELIMITER,
      eCPP_FILE_NOT_OPEN, eCPP_UNKNOWN, eCPP_NR
  };
  
@@@ -86,8 -86,4 +86,8 @@@ void cpp_done(gmx_cpp_t handle)
   */
  char *cpp_error(gmx_cpp_t *handlep, int status);
  
 +/* Returns warning message if strings defined in mdp define section (e.g. -DFLEXIBLE)
 + * were not not found when processing the topology */
 +std::string checkAndWarnForUnusedDefines(const gmx_cpp &handle);
 +
  #endif
index 553cd9f232ac7024db4f3d1cccb82f87424c6e47,2a87cec0c4bf7200834943a65c7197fc494b99db..43a317cc211793837f3f4531f07e310b3c5bc733
  #include <cstring>
  
  #include <algorithm>
 +#include <memory>
  #include <vector>
  
  #include <sys/types.h>
  
 -#include "gromacs/awh/read-params.h"
 +#include "gromacs/awh/read_params.h"
  #include "gromacs/commandline/pargs.h"
 -#include "gromacs/compat/make_unique.h"
 -#include "gromacs/ewald/ewald-utils.h"
 +#include "gromacs/ewald/ewald_utils.h"
  #include "gromacs/ewald/pme.h"
  #include "gromacs/fft/calcgrid.h"
  #include "gromacs/fileio/confio.h"
@@@ -63,7 -63,7 +63,7 @@@
  #include "gromacs/gmxpreprocess/convparm.h"
  #include "gromacs/gmxpreprocess/gen_maxwell_velocities.h"
  #include "gromacs/gmxpreprocess/gpp_atomtype.h"
 -#include "gromacs/gmxpreprocess/grompp-impl.h"
 +#include "gromacs/gmxpreprocess/grompp_impl.h"
  #include "gromacs/gmxpreprocess/notset.h"
  #include "gromacs/gmxpreprocess/readir.h"
  #include "gromacs/gmxpreprocess/tomorse.h"
@@@ -79,9 -79,8 +79,9 @@@
  #include "gromacs/mdlib/compute_io.h"
  #include "gromacs/mdlib/constr.h"
  #include "gromacs/mdlib/perf_est.h"
 -#include "gromacs/mdlib/sim_util.h"
 -#include "gromacs/mdrunutility/mdmodules.h"
 +#include "gromacs/mdlib/qmmm.h"
 +#include "gromacs/mdlib/vsite.h"
 +#include "gromacs/mdrun/mdmodules.h"
  #include "gromacs/mdtypes/inputrec.h"
  #include "gromacs/mdtypes/md_enums.h"
  #include "gromacs/mdtypes/nblist.h"
  #include "gromacs/utility/smalloc.h"
  #include "gromacs/utility/snprintf.h"
  
 -static int rm_interactions(int ifunc, int nrmols, t_molinfo mols[])
 +/* TODO The implementation details should move to their own source file. */
 +InteractionOfType::InteractionOfType(gmx::ArrayRef<const int>  atoms,
 +                                     gmx::ArrayRef<const real> params,
 +                                     const std::string        &name)
 +    : atoms_(atoms.begin(), atoms.end()),
 +      interactionTypeName_(name)
  {
 -    int  i, n;
 +    GMX_RELEASE_ASSERT(params.size() <= forceParam_.size(),
 +                       gmx::formatString("Cannot have more parameters than the maximum number possible (%d)", MAXFORCEPARAM).c_str());
 +    auto forceParamIt = forceParam_.begin();
 +    for (const auto param : params)
 +    {
 +        *forceParamIt++ = param;
 +    }
 +    while (forceParamIt != forceParam_.end())
 +    {
 +        *forceParamIt++ = NOTSET;
 +    }
 +}
 +
 +const int &InteractionOfType::ai() const
 +{
 +    GMX_RELEASE_ASSERT(!atoms_.empty(), "Need to have at least one atom set");
 +    return atoms_[0];
 +}
 +
 +const int &InteractionOfType::aj() const
 +{
 +    GMX_RELEASE_ASSERT(atoms_.size() > 1, "Need to have at least two atoms set");
 +    return atoms_[1];
 +}
 +
 +const int &InteractionOfType::ak() const
 +{
 +    GMX_RELEASE_ASSERT(atoms_.size() > 2, "Need to have at least three atoms set");
 +    return atoms_[2];
 +}
 +
 +const int &InteractionOfType::al() const
 +{
 +    GMX_RELEASE_ASSERT(atoms_.size() > 3, "Need to have at least four atoms set");
 +    return atoms_[3];
 +}
 +
 +const int &InteractionOfType::am() const
 +{
 +    GMX_RELEASE_ASSERT(atoms_.size() > 4, "Need to have at least five atoms set");
 +    return atoms_[4];
 +}
 +
 +const real &InteractionOfType::c0() const
 +{
 +    return forceParam_[0];
 +}
  
 -    n = 0;
 +const real &InteractionOfType::c1() const
 +{
 +    return forceParam_[1];
 +}
 +
 +const real &InteractionOfType::c2() const
 +{
 +    return forceParam_[2];
 +}
 +
 +const std::string &InteractionOfType::interactionTypeName() const
 +{
 +    return interactionTypeName_;
 +}
 +
 +void InteractionOfType::sortBondAtomIds()
 +{
 +    if (aj() < ai())
 +    {
 +        std::swap(atoms_[0], atoms_[1]);
 +    }
 +}
 +
 +void InteractionOfType::sortAngleAtomIds()
 +{
 +    if (ak() < ai())
 +    {
 +        std::swap(atoms_[0], atoms_[2]);
 +    }
 +}
 +
 +void InteractionOfType::sortDihedralAtomIds()
 +{
 +    if (al() < ai())
 +    {
 +        std::swap(atoms_[0], atoms_[3]);
 +        std::swap(atoms_[1], atoms_[2]);
 +    }
 +}
 +
 +void InteractionOfType::sortAtomIds()
 +{
 +    if (isBond())
 +    {
 +        sortBondAtomIds();
 +    }
 +    else if (isAngle())
 +    {
 +        sortAngleAtomIds();
 +    }
 +    else if (isDihedral())
 +    {
 +        sortDihedralAtomIds();
 +    }
 +    else
 +    {
 +        GMX_THROW(gmx::InternalError("Can not sort parameters other than bonds, angles or dihedrals"));
 +    }
 +};
 +
 +void InteractionOfType::setForceParameter(int pos, real value)
 +{
 +    GMX_RELEASE_ASSERT(pos < MAXFORCEPARAM, "Can't set parameter beyond the maximum number of parameters");
 +    forceParam_[pos] = value;
 +}
 +
 +void MoleculeInformation::initMolInfo()
 +{
 +    init_block(&cgs);
 +    init_block(&mols);
 +    init_blocka(&excls);
 +    init_t_atoms(&atoms, 0, FALSE);
 +}
 +
 +void MoleculeInformation::partialCleanUp()
 +{
 +    done_block(&mols);
 +}
 +
 +void MoleculeInformation::fullCleanUp()
 +{
 +    done_atom (&atoms);
 +    done_block(&cgs);
 +    done_block(&mols);
 +}
 +
 +static int rm_interactions(int ifunc, gmx::ArrayRef<MoleculeInformation> mols)
 +{
 +    int n = 0;
      /* For all the molecule types */
 -    for (i = 0; i < nrmols; i++)
 +    for (auto &mol : mols)
      {
 -        n += mols[i].plist[ifunc].nr;
 -        mols[i].plist[ifunc].nr = 0;
 +        n                  += mol.interactions[ifunc].size();
 +        mol.interactions[ifunc].interactionTypes.clear();
      }
      return n;
  }
@@@ -320,10 -180,10 +320,10 @@@ static void check_eg_vs_cg(gmx_mtop_t *
              {
                  /* Get the energy group of the first atom in this charge group */
                  firstj  = astart + molt->cgs.index[cg];
 -                firsteg = getGroupType(&mtop->groups, egcENER, firstj);
 +                firsteg = getGroupType(mtop->groups, SimulationAtomGroupType::EnergyOutput, firstj);
                  for (j = molt->cgs.index[cg]+1; j < molt->cgs.index[cg+1]; j++)
                  {
 -                    eg = getGroupType(&mtop->groups, egcENER, astart+j);
 +                    eg = getGroupType(mtop->groups, SimulationAtomGroupType::EnergyOutput, astart+j);
                      if (eg != firsteg)
                      {
                          gmx_fatal(FARGS, "atoms %d and %d in charge group %d of molecule type '%s' are in different energy groups",
      }
  }
  
 -static void check_cg_sizes(const char *topfn, const t_block *cgs, warninp_t wi)
 +static void check_cg_sizes(const char *topfn, const t_block *cgs, warninp *wi)
  {
      int  maxsize, cg;
      char warn_buf[STRLEN];
      }
  }
  
 -static void check_bonds_timestep(const gmx_mtop_t *mtop, double dt, warninp_t wi)
 +static void check_bonds_timestep(const gmx_mtop_t *mtop, double dt, warninp *wi)
  {
      /* This check is not intended to ensure accurate integration,
       * rather it is to signal mistakes in the mdp settings.
  
  static void check_vel(gmx_mtop_t *mtop, rvec v[])
  {
 -    gmx_mtop_atomloop_all_t aloop;
 -    const t_atom           *atom;
 -    int                     a;
 -
 -    aloop = gmx_mtop_atomloop_all_init(mtop);
 -    while (gmx_mtop_atomloop_all_next(aloop, &a, &atom))
 +    for (const AtomProxy atomP : AtomRange(*mtop))
      {
 -        if (atom->ptype == eptShell ||
 -            atom->ptype == eptBond  ||
 -            atom->ptype == eptVSite)
 +        const t_atom &local = atomP.atom();
 +        int           i     = atomP.globalAtomNumber();
 +        if (local.ptype == eptShell ||
 +            local.ptype == eptBond  ||
 +            local.ptype == eptVSite)
          {
 -            clear_rvec(v[a]);
 +            clear_rvec(v[i]);
          }
      }
  }
  
  static void check_shells_inputrec(gmx_mtop_t *mtop,
                                    t_inputrec *ir,
 -                                  warninp_t   wi)
 +                                  warninp    *wi)
  {
 -    gmx_mtop_atomloop_all_t aloop;
 -    const t_atom           *atom;
 -    int                     a, nshells = 0;
 -    char                    warn_buf[STRLEN];
 +    int                        nshells = 0;
 +    char                       warn_buf[STRLEN];
  
 -    aloop = gmx_mtop_atomloop_all_init(mtop);
 -    while (gmx_mtop_atomloop_all_next(aloop, &a, &atom))
 +    for (const AtomProxy atomP : AtomRange(*mtop))
      {
 -        if (atom->ptype == eptShell ||
 -            atom->ptype == eptBond)
 +        const t_atom &local = atomP.atom();
 +        if (local.ptype == eptShell ||
 +            local.ptype == eptBond)
          {
              nshells++;
          }
  
  /* TODO Decide whether this function can be consolidated with
   * gmx_mtop_ftype_count */
 -static int nint_ftype(gmx_mtop_t *mtop, t_molinfo *mi, int ftype)
 +static int nint_ftype(gmx_mtop_t *mtop, gmx::ArrayRef<const MoleculeInformation> mi, int ftype)
  {
      int nint = 0;
      for (const gmx_molblock_t &molb : mtop->molblock)
      {
 -        nint += molb.nmol*mi[molb.type].plist[ftype].nr;
 +        nint += molb.nmol*mi[molb.type].interactions[ftype].size();
      }
  
      return nint;
   * in the order of use in the molblocks,
   * unused molecule types are deleted.
   */
 -static void renumber_moltypes(gmx_mtop_t *sys,
 -                              int *nmolinfo, t_molinfo **molinfo)
 +static void renumber_moltypes(gmx_mtop_t                       *sys,
 +                              std::vector<MoleculeInformation> *molinfo)
  {
 -    int       *order, norder;
 -    t_molinfo *minew;
  
 -    snew(order, *nmolinfo);
 -    norder = 0;
 +    std::vector<int> order;
      for (gmx_molblock_t &molblock : sys->molblock)
      {
 -        int i;
 -        for (i = 0; i < norder; i++)
 +        const auto found = std::find_if(order.begin(), order.end(),
 +                                        [&molblock](const auto &entry)
 +                                        { return molblock.type == entry; });
 +        if (found == order.end())
          {
 -            if (order[i] == molblock.type)
 -            {
 -                break;
 -            }
 +            /* This type did not occur yet, add it */
 +            order.push_back(molblock.type);
 +            molblock.type = order.size() - 1;
          }
 -        if (i == norder)
 +        else
          {
 -            /* This type did not occur yet, add it */
 -            order[norder] = molblock.type;
 -            /* Renumber the moltype in the topology */
 -            norder++;
 +            molblock.type = std::distance(order.begin(), found);
          }
 -        molblock.type = i;
      }
  
      /* We still need to reorder the molinfo structs */
 -    snew(minew, norder);
 -    for (int mi = 0; mi < *nmolinfo; mi++)
 +    std::vector<MoleculeInformation> minew(order.size());
 +    int index = 0;
 +    for (auto &mi : *molinfo)
      {
 -        int i;
 -        for (i = 0; i < norder; i++)
 -        {
 -            if (order[i] == mi)
 -            {
 -                break;
 -            }
 -        }
 -        if (i == norder)
 +        const auto found = std::find(order.begin(), order.end(), index);
 +        if (found != order.end())
          {
 -            done_mi(&(*molinfo)[mi]);
 +            int pos = std::distance(order.begin(), found);
 +            minew[pos] = mi;
          }
          else
          {
 -            minew[i] = (*molinfo)[mi];
 +            // Need to manually clean up memory ....
 +            mi.fullCleanUp();
          }
 +        index++;
      }
 -    sfree(order);
 -    sfree(*molinfo);
  
 -    *nmolinfo = norder;
      *molinfo  = minew;
  }
  
 -static void molinfo2mtop(int nmi, t_molinfo *mi, gmx_mtop_t *mtop)
 +static void molinfo2mtop(gmx::ArrayRef<const MoleculeInformation> mi, gmx_mtop_t *mtop)
  {
 -    mtop->moltype.resize(nmi);
 -    for (int m = 0; m < nmi; m++)
 +    mtop->moltype.resize(mi.size());
 +    int pos = 0;
 +    for (const auto &mol : mi)
      {
 -        gmx_moltype_t &molt = mtop->moltype[m];
 -        molt.name           = mi[m].name;
 -        molt.atoms          = mi[m].atoms;
 +        gmx_moltype_t &molt = mtop->moltype[pos];
 +        molt.name           = mol.name;
 +        molt.atoms          = mol.atoms;
          /* ilists are copied later */
 -        molt.cgs            = mi[m].cgs;
 -        molt.excls          = mi[m].excls;
 +        molt.cgs            = mol.cgs;
 +        molt.excls          = mol.excls;
 +        pos++;
      }
  }
  
@@@ -624,24 -499,24 +624,24 @@@ static voi
  new_status(const char *topfile, const char *topppfile, const char *confin,
             t_gromppopts *opts, t_inputrec *ir, gmx_bool bZero,
             bool bGenVel, bool bVerbose, t_state *state,
 -           gpp_atomtype_t atype, gmx_mtop_t *sys,
 -           int *nmi, t_molinfo **mi, t_molinfo **intermolecular_interactions,
 -           t_params plist[],
 +           PreprocessingAtomTypes *atypes, gmx_mtop_t *sys,
 +           std::vector<MoleculeInformation> *mi,
 +           std::unique_ptr<MoleculeInformation> *intermolecular_interactions,
 +           gmx::ArrayRef<InteractionsOfType> interactions,
             int *comb, double *reppow, real *fudgeQQ,
             gmx_bool bMorse,
 -           warninp_t wi)
 +           warninp *wi)
  {
 -    t_molinfo                  *molinfo = nullptr;
 -    std::vector<gmx_molblock_t> molblock;
 -    int                         i, nrmols, nmismatch;
 -    bool                        ffParametrizedWithHBondConstraints;
 -    char                        buf[STRLEN];
 -    char                        warn_buf[STRLEN];
 +    std::vector<gmx_molblock_t>      molblock;
 +    int                              i, nmismatch;
 +    bool                             ffParametrizedWithHBondConstraints;
 +    char                             buf[STRLEN];
 +    char                             warn_buf[STRLEN];
  
      /* TOPOLOGY processing */
      sys->name = do_top(bVerbose, topfile, topppfile, opts, bZero, &(sys->symtab),
 -                       plist, comb, reppow, fudgeQQ,
 -                       atype, &nrmols, &molinfo, intermolecular_interactions,
 +                       interactions, comb, reppow, fudgeQQ,
 +                       atypes, mi, intermolecular_interactions,
                         ir,
                         &molblock,
                         &ffParametrizedWithHBondConstraints,
              /* Add a new molblock to the topology */
              sys->molblock.push_back(molb);
          }
 -        sys->natoms += molb.nmol*molinfo[sys->molblock.back().type].atoms.nr;
 +        sys->natoms += molb.nmol*(*mi)[sys->molblock.back().type].atoms.nr;
      }
      if (sys->molblock.empty())
      {
          gmx_fatal(FARGS, "No molecules were defined in the system");
      }
  
 -    renumber_moltypes(sys, &nrmols, &molinfo);
 +    renumber_moltypes(sys, mi);
  
      if (bMorse)
      {
 -        convert_harmonics(nrmols, molinfo, atype);
 +        convert_harmonics(*mi, atypes);
      }
  
      if (ir->eDisre == edrNone)
      {
 -        i = rm_interactions(F_DISRES, nrmols, molinfo);
 +        i = rm_interactions(F_DISRES, *mi);
          if (i > 0)
          {
              set_warning_line(wi, "unknown", -1);
      }
      if (!opts->bOrire)
      {
 -        i = rm_interactions(F_ORIRES, nrmols, molinfo);
 +        i = rm_interactions(F_ORIRES, *mi);
          if (i > 0)
          {
              set_warning_line(wi, "unknown", -1);
      }
  
      /* Copy structures from msys to sys */
 -    molinfo2mtop(nrmols, molinfo, sys);
 +    molinfo2mtop(*mi, sys);
  
      gmx_mtop_finalize(sys);
  
          warning(wi, buf);
      }
  
 -    /* If using the group scheme, make sure charge groups are made whole to avoid errors
 -     * in calculating charge group size later on
 -     */
 -    if (ir->cutoff_scheme == ecutsGROUP && ir->ePBC != epbcNONE)
 -    {
 -        // Need temporary rvec for coordinates
 -        do_pbc_first_mtop(nullptr, ir->ePBC, state->box, sys, state->x.rvec_array());
 -    }
 -
      /* Do more checks, mostly related to constraints */
      if (bVerbose)
      {
          fprintf(stderr, "double-checking input for internal consistency...\n");
      }
      {
 -        bool bHasNormalConstraints = 0 < (nint_ftype(sys, molinfo, F_CONSTR) +
 -                                          nint_ftype(sys, molinfo, F_CONSTRNC));
 -        bool bHasAnyConstraints = bHasNormalConstraints || 0 < nint_ftype(sys, molinfo, F_SETTLE);
 +        bool bHasNormalConstraints = 0 < (nint_ftype(sys, *mi, F_CONSTR) +
 +                                          nint_ftype(sys, *mi, F_CONSTRNC));
 +        bool bHasAnyConstraints = bHasNormalConstraints || 0 < nint_ftype(sys, *mi, F_SETTLE);
          double_check(ir, state->box,
                       bHasNormalConstraints,
                       bHasAnyConstraints,
      if (bGenVel)
      {
          real                   *mass;
 -        gmx_mtop_atomloop_all_t aloop;
 -        const t_atom           *atom;
  
          snew(mass, state->natoms);
 -        aloop = gmx_mtop_atomloop_all_init(sys);
 -        while (gmx_mtop_atomloop_all_next(aloop, &i, &atom))
 +        for (const AtomProxy atomP : AtomRange(*sys))
          {
 -            mass[i] = atom->m;
 +            const t_atom &local = atomP.atom();
 +            int           i     = atomP.globalAtomNumber();
 +            mass[i] = local.m;
          }
  
          if (opts->seed == -1)
          stop_cm(stdout, state->natoms, mass, state->x.rvec_array(), state->v.rvec_array());
          sfree(mass);
      }
 -
 -    *nmi = nrmols;
 -    *mi  = molinfo;
  }
  
  static void copy_state(const char *slog, t_trxframe *fr,
@@@ -923,18 -811,16 +923,18 @@@ static void cont_status(const char *slo
  
      if ((ir->epc != epcNO  || ir->etc == etcNOSEHOOVER) && ener)
      {
 -        get_enx_state(ener, use_time, &sys->groups, ir, state);
 +        get_enx_state(ener, use_time, sys->groups, ir, state);
          preserve_box_shape(ir, state->box_rel, state->boxv);
      }
  }
  
 -static void read_posres(gmx_mtop_t *mtop, t_molinfo *molinfo, gmx_bool bTopB,
 +static void read_posres(gmx_mtop_t *mtop,
 +                        gmx::ArrayRef<const MoleculeInformation> molinfo,
 +                        gmx_bool bTopB,
                          const char *fn,
                          int rc_scaling, int ePBC,
                          rvec com,
 -                        warninp_t wi)
 +                        warninp *wi)
  {
      gmx_bool           *hadAtom;
      rvec               *x, *v;
      matrix              box, invbox;
      int                 natoms, npbcdim = 0;
      char                warn_buf[STRLEN];
 -    int                 a, i, ai, j, k, nat_molb;
 -    t_params           *pr, *prfb;
 +    int                 a, nat_molb;
      t_atom             *atom;
  
      snew(top, 1);
      if (rc_scaling != erscNO)
      {
          copy_mat(box, invbox);
 -        for (j = npbcdim; j < DIM; j++)
 +        for (int j = npbcdim; j < DIM; j++)
          {
              clear_rvec(invbox[j]);
              invbox[j][j] = 1;
      for (gmx_molblock_t &molb : mtop->molblock)
      {
          nat_molb = molb.nmol*mtop->moltype[molb.type].atoms.nr;
 -        pr       = &(molinfo[molb.type].plist[F_POSRES]);
 -        prfb     = &(molinfo[molb.type].plist[F_FBPOSRES]);
 -        if (pr->nr > 0 || prfb->nr > 0)
 +        const InteractionsOfType *pr   = &(molinfo[molb.type].interactions[F_POSRES]);
 +        const InteractionsOfType *prfb = &(molinfo[molb.type].interactions[F_FBPOSRES]);
 +        if (pr->size() > 0 || prfb->size() > 0)
          {
              atom = mtop->moltype[molb.type].atoms.atom;
 -            for (i = 0; (i < pr->nr); i++)
 +            for (const auto &restraint : pr->interactionTypes)
              {
 -                ai = pr->param[i].ai();
 +                int ai = restraint.ai();
                  if (ai >= natoms)
                  {
                      gmx_fatal(FARGS, "Position restraint atom index (%d) in moltype '%s' is larger than number of atoms in %s (%d).\n",
                  if (rc_scaling == erscCOM)
                  {
                      /* Determine the center of mass of the posres reference coordinates */
 -                    for (j = 0; j < npbcdim; j++)
 +                    for (int j = 0; j < npbcdim; j++)
                      {
                          sum[j] += atom[ai].m*x[a+ai][j];
                      }
                  }
              }
              /* Same for flat-bottomed posres, but do not count an atom twice for COM */
 -            for (i = 0; (i < prfb->nr); i++)
 +            for (const auto &restraint : prfb->interactionTypes)
              {
 -                ai = prfb->param[i].ai();
 +                int ai = restraint.ai();
                  if (ai >= natoms)
                  {
                      gmx_fatal(FARGS, "Position restraint atom index (%d) in moltype '%s' is larger than number of atoms in %s (%d).\n",
                  if (rc_scaling == erscCOM && !hadAtom[ai])
                  {
                      /* Determine the center of mass of the posres reference coordinates */
 -                    for (j = 0; j < npbcdim; j++)
 +                    for (int j = 0; j < npbcdim; j++)
                      {
                          sum[j] += atom[ai].m*x[a+ai][j];
                      }
              if (!bTopB)
              {
                  molb.posres_xA.resize(nat_molb);
 -                for (i = 0; i < nat_molb; i++)
 +                for (int i = 0; i < nat_molb; i++)
                  {
                      copy_rvec(x[a+i], molb.posres_xA[i]);
                  }
              else
              {
                  molb.posres_xB.resize(nat_molb);
 -                for (i = 0; i < nat_molb; i++)
 +                for (int i = 0; i < nat_molb; i++)
                  {
                      copy_rvec(x[a+i], molb.posres_xB[i]);
                  }
          {
              gmx_fatal(FARGS, "The total mass of the position restraint atoms is 0");
          }
 -        for (j = 0; j < npbcdim; j++)
 +        for (int j = 0; j < npbcdim; j++)
          {
              com[j] = sum[j]/totmass;
          }
              if (!molb.posres_xA.empty() || !molb.posres_xB.empty())
              {
                  std::vector<gmx::RVec> &xp = (!bTopB ? molb.posres_xA : molb.posres_xB);
 -                for (i = 0; i < nat_molb; i++)
 +                for (int i = 0; i < nat_molb; i++)
                  {
 -                    for (j = 0; j < npbcdim; j++)
 +                    for (int j = 0; j < npbcdim; j++)
                      {
                          if (rc_scaling == erscALL)
                          {
                              /* Convert from Cartesian to crystal coordinates */
                              xp[i][j] *= invbox[j][j];
 -                            for (k = j+1; k < npbcdim; k++)
 +                            for (int k = j+1; k < npbcdim; k++)
                              {
                                  xp[i][j] += invbox[k][j]*xp[i][k];
                              }
          if (rc_scaling == erscCOM)
          {
              /* Convert the COM from Cartesian to crystal coordinates */
 -            for (j = 0; j < npbcdim; j++)
 +            for (int j = 0; j < npbcdim; j++)
              {
                  com[j] *= invbox[j][j];
 -                for (k = j+1; k < npbcdim; k++)
 +                for (int k = j+1; k < npbcdim; k++)
                  {
                      com[j] += invbox[k][j]*com[k];
                  }
      sfree(hadAtom);
  }
  
 -static void gen_posres(gmx_mtop_t *mtop, t_molinfo *mi,
 +static void gen_posres(gmx_mtop_t *mtop,
 +                       gmx::ArrayRef<const MoleculeInformation> mi,
                         const char *fnA, const char *fnB,
                         int rc_scaling, int ePBC,
                         rvec com, rvec comB,
 -                       warninp_t wi)
 +                       warninp *wi)
  {
 -    read_posres  (mtop, mi, FALSE, fnA, rc_scaling, ePBC, com, wi);
 +    read_posres(mtop, mi, FALSE, fnA, rc_scaling, ePBC, com, wi);
      /* It is safer to simply read the b-state posres rather than trying
       * to be smart and copy the positions.
       */
      read_posres(mtop, mi, TRUE, fnB, rc_scaling, ePBC, comB, wi);
  }
  
 -static void set_wall_atomtype(gpp_atomtype_t at, t_gromppopts *opts,
 -                              t_inputrec *ir, warninp_t wi)
 +static void set_wall_atomtype(PreprocessingAtomTypes *at, t_gromppopts *opts,
 +                              t_inputrec *ir, warninp *wi)
  {
      int  i;
      char warn_buf[STRLEN];
      }
      for (i = 0; i < ir->nwall; i++)
      {
 -        ir->wall_atomtype[i] = get_atomtype_type(opts->wall_atomtype[i], at);
 +        ir->wall_atomtype[i] = at->atomTypeFromName(opts->wall_atomtype[i]);
          if (ir->wall_atomtype[i] == NOTSET)
          {
              sprintf(warn_buf, "Specified wall atom type %s is not defined", opts->wall_atomtype[i]);
      }
  }
  
 -static int nrdf_internal(t_atoms *atoms)
 +static int nrdf_internal(const t_atoms *atoms)
  {
      int i, nmass, nrdf;
  
@@@ -1220,10 -1106,10 +1220,10 @@@ interpolate1d( double           xmin
  
  
  static void
 -setup_cmap (int                    grid_spacing,
 -            int                    nc,
 -            const real *           grid,
 -            gmx_cmap_t       *     cmap_grid)
 +setup_cmap (int                       grid_spacing,
 +            int                       nc,
 +            gmx::ArrayRef<const real> grid,
 +            gmx_cmap_t          *     cmap_grid)
  {
      int                 i, j, k, ii, jj, kk, idx;
      int                 offset;
@@@ -1311,28 -1197,27 +1311,28 @@@ static void init_cmap_grid(gmx_cmap_t *
  }
  
  
 -static int count_constraints(const gmx_mtop_t *mtop, t_molinfo *mi, warninp_t wi)
 +static int count_constraints(const gmx_mtop_t                        *mtop,
 +                             gmx::ArrayRef<const MoleculeInformation> mi,
 +                             warninp                                 *wi)
  {
 -    int             count, count_mol, i;
 -    t_params       *plist;
 +    int             count, count_mol;
      char            buf[STRLEN];
  
      count = 0;
      for (const gmx_molblock_t &molb : mtop->molblock)
      {
          count_mol = 0;
 -        plist     = mi[molb.type].plist;
 +        gmx::ArrayRef<const InteractionsOfType> interactions = mi[molb.type].interactions;
  
 -        for (i = 0; i < F_NRE; i++)
 +        for (int i = 0; i < F_NRE; i++)
          {
              if (i == F_SETTLE)
              {
 -                count_mol += 3*plist[i].nr;
 +                count_mol += 3*interactions[i].size();
              }
              else if (interaction_function[i].flags & IF_CONSTRAINT)
              {
 -                count_mol += plist[i].nr;
 +                count_mol += interactions[i].size();
              }
          }
  
@@@ -1355,12 -1240,15 +1355,12 @@@ static real calc_temp(const gmx_mtop_t 
                        const t_inputrec *ir,
                        rvec             *v)
  {
 -    gmx_mtop_atomloop_all_t aloop;
 -    const t_atom           *atom;
 -    int                     a;
 -
 -    double                  sum_mv2 = 0;
 -    aloop = gmx_mtop_atomloop_all_init(mtop);
 -    while (gmx_mtop_atomloop_all_next(aloop, &a, &atom))
 +    double                     sum_mv2 = 0;
 +    for (const AtomProxy atomP : AtomRange(*mtop))
      {
 -        sum_mv2 += atom->m*norm2(v[a]);
 +        const t_atom &local = atomP.atom();
 +        int           i     = atomP.globalAtomNumber();
 +        sum_mv2 += local.m*norm2(v[i]);
      }
  
      double nrdf = 0;
  }
  
  static real get_max_reference_temp(const t_inputrec *ir,
 -                                   warninp_t         wi)
 +                                   warninp          *wi)
  {
      real         ref_t;
      int          i;
   */
  static void checkForUnboundAtoms(const gmx_moltype_t     *molt,
                                   gmx_bool                 bVerbose,
 -                                 warninp_t                wi)
 +                                 warninp                 *wi)
  {
      const t_atoms *atoms = &molt->atoms;
  
  /* Checks all moleculetypes for unbound atoms */
  static void checkForUnboundAtoms(const gmx_mtop_t     *mtop,
                                   gmx_bool              bVerbose,
 -                                 warninp_t             wi)
 +                                 warninp              *wi)
  {
      for (const gmx_moltype_t &molt : mtop->moltype)
      {
@@@ -1568,7 -1456,7 +1568,7 @@@ static bool haveDecoupledModeInMol(cons
   */
  static void checkDecoupledModeAccuracy(const gmx_mtop_t *mtop,
                                         const t_inputrec *ir,
 -                                       warninp_t         wi)
 +                                       warninp          *wi)
  {
      /* We only have issues with decoupled modes with normal MD.
       * With stochastic dynamics equipartitioning is enforced strongly.
@@@ -1654,8 -1542,10 +1654,8 @@@ static void set_verlet_buffer(const gmx
                                t_inputrec       *ir,
                                real              buffer_temp,
                                matrix            box,
 -                              warninp_t         wi)
 +                              warninp          *wi)
  {
 -    real                   rlist_1x1;
 -    int                    n_nonlin_vsite;
      char                   warn_buf[STRLEN];
  
      printf("Determining Verlet buffer for a tolerance of %g kJ/mol/ps at %g K\n", ir->verletbuf_tol, buffer_temp);
      VerletbufListSetup listSetup1x1;
      listSetup1x1.cluster_size_i = 1;
      listSetup1x1.cluster_size_j = 1;
 -    calc_verlet_buffer_size(mtop, det(box), ir, ir->nstlist, ir->nstlist - 1,
 -                            buffer_temp, &listSetup1x1,
 -                            &n_nonlin_vsite, &rlist_1x1);
 +    const real rlist_1x1 =
 +        calcVerletBufferSize(*mtop, det(box), *ir, ir->nstlist, ir->nstlist - 1,
 +                             buffer_temp, listSetup1x1);
  
      /* Set the pair-list buffer size in ir */
      VerletbufListSetup listSetup4x4 =
          verletbufGetSafeListSetup(ListSetupType::CpuNoSimd);
 -    calc_verlet_buffer_size(mtop, det(box), ir, ir->nstlist, ir->nstlist - 1,
 -                            buffer_temp, &listSetup4x4,
 -                            &n_nonlin_vsite, &ir->rlist);
 +    ir->rlist =
 +        calcVerletBufferSize(*mtop, det(box), *ir, ir->nstlist, ir->nstlist - 1,
 +                             buffer_temp, listSetup4x4);
  
 +    const int n_nonlin_vsite = countNonlinearVsites(*mtop);
      if (n_nonlin_vsite > 0)
      {
          sprintf(warn_buf, "There are %d non-linear virtual site constructions. Their contribution to the energy error is approximated. In most cases this does not affect the error significantly.", n_nonlin_vsite);
  
  int gmx_grompp(int argc, char *argv[])
  {
 -    const char            *desc[] = {
 +    const char                          *desc[] = {
          "[THISMODULE] (the gromacs preprocessor)",
          "reads a molecular topology file, checks the validity of the",
          "file, expands the topology from a molecular description to an atomic",
          "interpret the output messages before attempting to bypass them with",
          "this option."
      };
 -    t_gromppopts          *opts;
 -    int                    nmi;
 -    t_molinfo             *mi, *intermolecular_interactions;
 -    gpp_atomtype_t         atype;
 -    int                    nvsite, comb;
 -    t_params              *plist;
 -    real                   fudgeQQ;
 -    double                 reppow;
 -    const char            *mdparin;
 -    int                    ntype;
 -    bool                   bNeedVel, bGenVel;
 -    gmx_bool               have_atomnumber;
 -    gmx_output_env_t      *oenv;
 -    gmx_bool               bVerbose = FALSE;
 -    warninp_t              wi;
 -    char                   warn_buf[STRLEN];
 -
 -    t_filenm               fnm[] = {
 +    t_gromppopts                        *opts;
 +    std::vector<MoleculeInformation>     mi;
 +    std::unique_ptr<MoleculeInformation> intermolecular_interactions;
 +    int                                  nvsite, comb;
 +    real                                 fudgeQQ;
 +    double                               reppow;
 +    const char                          *mdparin;
 +    bool                                 bNeedVel, bGenVel;
 +    gmx_bool                             have_atomnumber;
 +    gmx_output_env_t                    *oenv;
 +    gmx_bool                             bVerbose = FALSE;
 +    warninp                             *wi;
 +    char                                 warn_buf[STRLEN];
 +
 +    t_filenm                             fnm[] = {
          { efMDP, nullptr,  nullptr,        ffREAD  },
          { efMDP, "-po", "mdout",     ffWRITE },
          { efSTX, "-c",  nullptr,        ffREAD  },
          warning_error(wi, warn_buf);
      }
  
 -    snew(plist, F_NRE);
 -    init_plist(plist);
 -    gmx_mtop_t sys;
 -    atype = init_atomtype();
 +    std::array<InteractionsOfType, F_NRE> interactions;
 +    gmx_mtop_t                            sys;
 +    PreprocessingAtomTypes                atypes;
      if (debug)
      {
          pr_symtab(debug, 0, "Just opened", &sys.symtab);
      t_state state;
      new_status(fn, opt2fn_null("-pp", NFILE, fnm), opt2fn("-c", NFILE, fnm),
                 opts, ir, bZero, bGenVel, bVerbose, &state,
 -               atype, &sys, &nmi, &mi, &intermolecular_interactions,
 -               plist, &comb, &reppow, &fudgeQQ,
 +               &atypes, &sys, &mi, &intermolecular_interactions,
 +               interactions, &comb, &reppow, &fudgeQQ,
                 opts->bMorse,
                 wi);
  
      for (size_t mt = 0; mt < sys.moltype.size(); mt++)
      {
          nvsite +=
 -            set_vsites(bVerbose, &sys.moltype[mt].atoms, atype, mi[mt].plist);
 +            set_vsites(bVerbose, &sys.moltype[mt].atoms, &atypes, mi[mt].interactions);
      }
      /* now throw away all obsolete bonds, angles and dihedrals: */
      /* note: constraints are ALWAYS removed */
      {
          for (size_t mt = 0; mt < sys.moltype.size(); mt++)
          {
 -            clean_vsite_bondeds(mi[mt].plist, sys.moltype[mt].atoms.nr, bRmVSBds);
 +            clean_vsite_bondeds(mi[mt].interactions, sys.moltype[mt].atoms.nr, bRmVSBds);
          }
      }
  
          gmx_mtop_remove_chargegroups(&sys);
      }
  
 -    if (count_constraints(&sys, mi, wi) && (ir->eConstrAlg == econtSHAKE))
 +    if ((count_constraints(&sys, mi, wi) != 0) && (ir->eConstrAlg == econtSHAKE))
      {
          if (ir->eI == eiCG || ir->eI == eiLBFGS)
          {
  
      /* If we are doing QM/MM, check that we got the atom numbers */
      have_atomnumber = TRUE;
 -    for (i = 0; i < get_atomtype_ntypes(atype); i++)
 +    for (int i = 0; i < gmx::ssize(atypes); i++)
      {
 -        have_atomnumber = have_atomnumber && (get_atomtype_atomnumber(i, atype) >= 0);
 +        have_atomnumber = have_atomnumber && (atypes.atomNumberFromAtomType(i) >= 0);
      }
      if (!have_atomnumber && ir->bQMMM)
      {
      }
  
      /* If we are using CMAP, setup the pre-interpolation grid */
 -    if (plist[F_CMAP].ncmap > 0)
 +    if (interactions[F_CMAP].ncmap() > 0)
      {
 -        init_cmap_grid(&sys.ffparams.cmap_grid, plist[F_CMAP].nc, plist[F_CMAP].grid_spacing);
 -        setup_cmap(plist[F_CMAP].grid_spacing, plist[F_CMAP].nc, plist[F_CMAP].cmap, &sys.ffparams.cmap_grid);
 +        init_cmap_grid(&sys.ffparams.cmap_grid, interactions[F_CMAP].cmapAngles, interactions[F_CMAP].cmakeGridSpacing);
 +        setup_cmap(interactions[F_CMAP].cmakeGridSpacing, interactions[F_CMAP].cmapAngles, interactions[F_CMAP].cmap, &sys.ffparams.cmap_grid);
      }
  
 -    set_wall_atomtype(atype, opts, ir, wi);
 +    set_wall_atomtype(&atypes, opts, ir, wi);
      if (bRenum)
      {
 -        renum_atype(plist, &sys, ir->wall_atomtype, atype, bVerbose);
 -        get_atomtype_ntypes(atype);
 +        atypes.renumberTypes(interactions, &sys, ir->wall_atomtype, bVerbose);
      }
  
      if (ir->implicit_solvent)
      }
  
      /* PELA: Copy the atomtype data to the topology atomtype list */
 -    copy_atomtype_atomtypes(atype, &(sys.atomtypes));
 +    atypes.copyTot_atomtypes(&(sys.atomtypes));
  
      if (debug)
      {
 -        pr_symtab(debug, 0, "After renum_atype", &sys.symtab);
 +        pr_symtab(debug, 0, "After atype.renumberTypes", &sys.symtab);
      }
  
      if (bVerbose)
          fprintf(stderr, "converting bonded parameters...\n");
      }
  
 -    ntype = get_atomtype_ntypes(atype);
 -    convert_params(ntype, plist, mi, intermolecular_interactions,
 -                   comb, reppow, fudgeQQ, &sys);
 +    const int ntype = atypes.size();
 +    convertInteractionsOfType(ntype, interactions, mi, intermolecular_interactions.get(),
 +                              comb, reppow, fudgeQQ, &sys);
  
      if (debug)
      {
 -        pr_symtab(debug, 0, "After convert_params", &sys.symtab);
 +        pr_symtab(debug, 0, "After converInteractionsOfType", &sys.symtab);
      }
  
      /* set ptype to VSite for virtual sites */
          pr_symtab(debug, 0, "After close", &sys.symtab);
      }
  
 -    /* make exclusions between QM atoms */
 +    /* make exclusions between QM atoms and remove charges if needed */
      if (ir->bQMMM)
      {
          if (ir->QMMMscheme == eQMMMschemenormal && ir->ns_type == ensSIMPLE)
          {
              generate_qmexcl(&sys, ir, wi, GmxQmmmMode::GMX_QMMM_ORIGINAL);
          }
 +        if (ir->QMMMscheme != eQMMMschemeoniom)
 +        {
 +            std::vector<int> qmmmAtoms = qmmmAtomIndices(*ir, sys);
 +            removeQmmmAtomCharges(&sys, qmmmAtoms);
 +        }
      }
  
      if (ir->eI == eiMimic)
          }
      }
  
 -    struct pull_t *pull = nullptr;
 +    pull_t *pull = nullptr;
  
      if (ir->bPull)
      {
  
      if (ir->bDoAwh)
      {
+         tensor compressibility = { { 0 } };
+         if (ir->epc != epcNO)
+         {
+             copy_mat(ir->compress, compressibility);
+         }
          setStateDependentAwhParams(ir->awhParams, ir->pull, pull,
-                                    state.box, ir->ePBC, &ir->opts, wi);
+                                    state.box, ir->ePBC, compressibility,
+                                    &ir->opts, wi);
      }
  
      if (ir->bPull)
  
      {
          char   warn_buf[STRLEN];
 -        double cio = compute_io(ir, sys.natoms, &sys.groups, F_NRE, 1);
 +        double cio = compute_io(ir, sys.natoms, sys.groups, F_NRE, 1);
          sprintf(warn_buf, "This run will generate roughly %.0f Mb of data", cio);
          if (cio > 2000)
          {
      write_tpx_state(ftp2fn(efTPR, NFILE, fnm), ir, &state, &sys);
  
      /* Output IMD group, if bIMD is TRUE */
 -    write_IMDgroup_to_file(ir->bIMD, ir, &state, &sys, NFILE, fnm);
 +    gmx::write_IMDgroup_to_file(ir->bIMD, ir, &state, &sys, NFILE, fnm);
  
      sfree(opts->define);
      sfree(opts->include);
      sfree(opts);
 -    done_plist(plist);
 -    sfree(plist);
 -    for (int i = 0; i < nmi; i++)
 +    for (auto &mol : mi)
      {
          // Some of the contents of molinfo have been stolen, so
 -        // done_mi can't be called.
 -        done_block(&mi[i].mols);
 -        done_plist(mi[i].plist);
 +        // fullCleanUp can't be called.
 +        mol.partialCleanUp();
      }
 -    sfree(mi);
 -    done_atomtype(atype);
      done_inputrec_strings();
      output_env_done(oenv);
  
index d49e1dfa8fb877e906a4236794456b894fbb48b6,db87f40777c115cbcea562a7ccf3f60784d467b2..bd598b7b46d0d42c1bb4565c2a384df6adfffa59
@@@ -49,7 -49,6 +49,7 @@@
  #include "gromacs/fileio/warninp.h"
  #include "gromacs/gmxpreprocess/gpp_atomtype.h"
  #include "gromacs/gmxpreprocess/gpp_bond_atomtype.h"
 +#include "gromacs/gmxpreprocess/grompp_impl.h"
  #include "gromacs/gmxpreprocess/notset.h"
  #include "gromacs/gmxpreprocess/readir.h"
  #include "gromacs/gmxpreprocess/topdirs.h"
  #include "gromacs/utility/smalloc.h"
  #include "gromacs/utility/stringutil.h"
  
 -void generate_nbparams(int comb, int ftype, t_params *plist, gpp_atomtype_t atype,
 -                       warninp_t wi)
 +void generate_nbparams(int                         comb,
 +                       int                         ftype,
 +                       InteractionsOfType         *interactions,
 +                       PreprocessingAtomTypes     *atypes,
 +                       warninp                    *wi)
  {
 -    int   i, j, k = -1, nf;
      int   nr, nrfp;
      real  c, bi, bj, ci, cj, ci0, ci1, ci2, cj0, cj1, cj2;
  
      /* Lean mean shortcuts */
 -    nr   = get_atomtype_ntypes(atype);
 +    nr   = atypes->size();
      nrfp = NRFP(ftype);
 -    snew(plist->param, nr*nr);
 -    plist->nr = nr*nr;
 +    interactions->interactionTypes.clear();
  
 +    std::array<real, MAXFORCEPARAM> forceParam = {NOTSET};
      /* Fill the matrix with force parameters */
      switch (ftype)
      {
              {
                  case eCOMB_GEOMETRIC:
                      /* Gromos rules */
 -                    for (i = k = 0; (i < nr); i++)
 +                    for (int i = 0; (i < nr); i++)
                      {
 -                        for (j = 0; (j < nr); j++, k++)
 +                        for (int j = 0; (j < nr); j++)
                          {
 -                            for (nf = 0; (nf < nrfp); nf++)
 +                            for (int nf = 0; (nf < nrfp); nf++)
                              {
 -                                ci = get_atomtype_nbparam(i, nf, atype);
 -                                cj = get_atomtype_nbparam(j, nf, atype);
 -                                c  = std::sqrt(ci * cj);
 -                                plist->param[k].c[nf]      = c;
 +                                ci             = atypes->atomNonBondedParamFromAtomType(i, nf);
 +                                cj             = atypes->atomNonBondedParamFromAtomType(j, nf);
 +                                c              = std::sqrt(ci * cj);
 +                                forceParam[nf] = c;
                              }
 +                            interactions->interactionTypes.emplace_back(InteractionOfType({}, forceParam));
                          }
                      }
                      break;
  
                  case eCOMB_ARITHMETIC:
                      /* c0 and c1 are sigma and epsilon */
 -                    for (i = k = 0; (i < nr); i++)
 +                    for (int i = 0; (i < nr); i++)
                      {
 -                        for (j = 0; (j < nr); j++, k++)
 +                        for (int j = 0; (j < nr); j++)
                          {
 -                            ci0                  = get_atomtype_nbparam(i, 0, atype);
 -                            cj0                  = get_atomtype_nbparam(j, 0, atype);
 -                            ci1                  = get_atomtype_nbparam(i, 1, atype);
 -                            cj1                  = get_atomtype_nbparam(j, 1, atype);
 -                            plist->param[k].c[0] = (fabs(ci0) + fabs(cj0))*0.5;
 +                            ci0                  = atypes->atomNonBondedParamFromAtomType(i, 0);
 +                            cj0                  = atypes->atomNonBondedParamFromAtomType(j, 0);
 +                            ci1                  = atypes->atomNonBondedParamFromAtomType(i, 1);
 +                            cj1                  = atypes->atomNonBondedParamFromAtomType(j, 1);
 +                            forceParam[0]        = (fabs(ci0) + fabs(cj0))*0.5;
                              /* Negative sigma signals that c6 should be set to zero later,
                               * so we need to propagate that through the combination rules.
                               */
                              if (ci0 < 0 || cj0 < 0)
                              {
 -                                plist->param[k].c[0] *= -1;
 +                                forceParam[0] *= -1;
                              }
 -                            plist->param[k].c[1] = std::sqrt(ci1*cj1);
 +                            forceParam[1] = std::sqrt(ci1*cj1);
 +                            interactions->interactionTypes.emplace_back(InteractionOfType({}, forceParam));
                          }
                      }
  
                      break;
                  case eCOMB_GEOM_SIG_EPS:
                      /* c0 and c1 are sigma and epsilon */
 -                    for (i = k = 0; (i < nr); i++)
 +                    for (int i = 0; (i < nr); i++)
                      {
 -                        for (j = 0; (j < nr); j++, k++)
 +                        for (int j = 0; (j < nr); j++)
                          {
 -                            ci0                  = get_atomtype_nbparam(i, 0, atype);
 -                            cj0                  = get_atomtype_nbparam(j, 0, atype);
 -                            ci1                  = get_atomtype_nbparam(i, 1, atype);
 -                            cj1                  = get_atomtype_nbparam(j, 1, atype);
 -                            plist->param[k].c[0] = std::sqrt(std::fabs(ci0*cj0));
 +                            ci0                  = atypes->atomNonBondedParamFromAtomType(i, 0);
 +                            cj0                  = atypes->atomNonBondedParamFromAtomType(j, 0);
 +                            ci1                  = atypes->atomNonBondedParamFromAtomType(i, 1);
 +                            cj1                  = atypes->atomNonBondedParamFromAtomType(j, 1);
 +                            forceParam[0]        = std::sqrt(std::fabs(ci0*cj0));
                              /* Negative sigma signals that c6 should be set to zero later,
                               * so we need to propagate that through the combination rules.
                               */
                              if (ci0 < 0 || cj0 < 0)
                              {
 -                                plist->param[k].c[0] *= -1;
 +                                forceParam[0] *= -1;
                              }
 -                            plist->param[k].c[1] = std::sqrt(ci1*cj1);
 +                            forceParam[1] = std::sqrt(ci1*cj1);
 +                            interactions->interactionTypes.emplace_back(InteractionOfType({}, forceParam));
                          }
                      }
  
                      auto message = gmx::formatString("No such combination rule %d", comb);
                      warning_error_and_exit(wi, message, FARGS);
              }
 -            if (plist->nr != k)
 -            {
 -                gmx_incons("Topology processing, generate nb parameters");
 -            }
              break;
  
          case F_BHAM:
              /* Buckingham rules */
 -            for (i = k = 0; (i < nr); i++)
 +            for (int i = 0; (i < nr); i++)
              {
 -                for (j = 0; (j < nr); j++, k++)
 +                for (int j = 0; (j < nr); j++)
                  {
 -                    ci0                  = get_atomtype_nbparam(i, 0, atype);
 -                    cj0                  = get_atomtype_nbparam(j, 0, atype);
 -                    ci2                  = get_atomtype_nbparam(i, 2, atype);
 -                    cj2                  = get_atomtype_nbparam(j, 2, atype);
 -                    bi                   = get_atomtype_nbparam(i, 1, atype);
 -                    bj                   = get_atomtype_nbparam(j, 1, atype);
 -                    plist->param[k].c[0] = std::sqrt(ci0 * cj0);
 +                    ci0                  = atypes->atomNonBondedParamFromAtomType(i, 0);
 +                    cj0                  = atypes->atomNonBondedParamFromAtomType(j, 0);
 +                    ci2                  = atypes->atomNonBondedParamFromAtomType(i, 2);
 +                    cj2                  = atypes->atomNonBondedParamFromAtomType(j, 2);
 +                    bi                   = atypes->atomNonBondedParamFromAtomType(i, 1);
 +                    bj                   = atypes->atomNonBondedParamFromAtomType(j, 1);
 +                    forceParam[0]        = std::sqrt(ci0 * cj0);
                      if ((bi == 0) || (bj == 0))
                      {
 -                        plist->param[k].c[1] = 0;
 +                        forceParam[1] = 0;
                      }
                      else
                      {
 -                        plist->param[k].c[1] = 2.0/(1/bi+1/bj);
 +                        forceParam[1] = 2.0/(1/bi+1/bj);
                      }
 -                    plist->param[k].c[2] = std::sqrt(ci2 * cj2);
 +                    forceParam[2] = std::sqrt(ci2 * cj2);
 +                    interactions->interactionTypes.emplace_back(InteractionOfType({}, forceParam));
                  }
              }
  
      }
  }
  
 -static void realloc_nb_params(gpp_atomtype_t at,
 +/*! \brief Used to temporarily store the explicit non-bonded parameter
 + * combinations, which will be copied to InteractionsOfType. */
 +struct t_nbparam
 +{
 +    //! Has this combination been set.
 +    bool     bSet;
 +    //! The non-bonded parameters
 +    real     c[4];
 +};
 +
 +static void realloc_nb_params(PreprocessingAtomTypes *atypes,
                                t_nbparam ***nbparam, t_nbparam ***pair)
  {
      /* Add space in the non-bonded parameters matrix */
 -    int atnr = get_atomtype_ntypes(at);
 +    int atnr = atypes->size();
      srenew(*nbparam, atnr);
      snew((*nbparam)[atnr-1], atnr);
      if (pair)
      }
  }
  
 +int copy_nbparams(t_nbparam **param, int ftype, InteractionsOfType *interactions, int nr)
 +{
 +    int nrfp, ncopy;
 +
 +    nrfp = NRFP(ftype);
 +
 +    ncopy = 0;
 +    for (int i = 0; i < nr; i++)
 +    {
 +        for (int j = 0; j <= i; j++)
 +        {
 +            GMX_RELEASE_ASSERT(param, "Must have valid parameters");
 +            if (param[i][j].bSet)
 +            {
 +                for (int f = 0; f < nrfp; f++)
 +                {
 +                    interactions->interactionTypes[nr*i+j].setForceParameter(f, param[i][j].c[f]);
 +                    interactions->interactionTypes[nr*j+i].setForceParameter(f, param[i][j].c[f]);
 +                }
 +                ncopy++;
 +            }
 +        }
 +    }
 +
 +    return ncopy;
 +}
 +
 +void free_nbparam(t_nbparam **param, int nr)
 +{
 +    int i;
 +
 +    GMX_RELEASE_ASSERT(param, "Must have valid parameters");
 +    for (i = 0; i < nr; i++)
 +    {
 +        GMX_RELEASE_ASSERT(param[i], "Must have valid parameters");
 +        sfree(param[i]);
 +    }
 +    sfree(param);
 +}
 +
  static void copy_B_from_A(int ftype, double *c)
  {
      int nrfpA, nrfpB, i;
      }
  }
  
 -void push_at (t_symtab *symtab, gpp_atomtype_t at, t_bond_atomtype bat,
 +void push_at (t_symtab *symtab, PreprocessingAtomTypes *at, PreprocessingBondAtomType *bondAtomType,
                char *line, int nb_funct,
                t_nbparam ***nbparam, t_nbparam ***pair,
 -              warninp_t wi)
 +              warninp *wi)
  {
      typedef struct {
          const char *entry;
          int         ptype;
      } t_xlate;
 -    t_xlate    xl[eptNR] = {
 +    t_xlate xl[eptNR] = {
          { "A",   eptAtom },
          { "N",   eptNucleus },
          { "S",   eptShell },
          { "V",   eptVSite },
      };
  
 -    int        nr, i, nfields, j, pt, nfp0 = -1;
 -    int        batype_nr, nread;
 -    char       type[STRLEN], btype[STRLEN], ptype[STRLEN];
 -    double     m, q;
 -    double     c[MAXFORCEPARAM];
 -    char       tmpfield[12][100]; /* Max 12 fields of width 100 */
 -    t_atom    *atom;
 -    t_param   *param;
 -    int        atomnr;
 -    bool       have_atomic_number;
 -    bool       have_bonded_type;
 +    int     nr, nfields, j, pt, nfp0 = -1;
 +    int     batype_nr, nread;
 +    char    type[STRLEN], btype[STRLEN], ptype[STRLEN];
 +    double  m, q;
 +    double  c[MAXFORCEPARAM];
 +    char    tmpfield[12][100]; /* Max 12 fields of width 100 */
 +    t_atom *atom;
 +    int     atomnr;
 +    bool    have_atomic_number;
 +    bool    have_bonded_type;
  
      snew(atom, 1);
 -    snew(param, 1);
  
      /* First assign input line to temporary array */
      nfields = sscanf(line, "%s%s%s%s%s%s%s%s%s%s%s%s",
              auto message = gmx::formatString("Invalid function type %d in push_at", nb_funct);
              warning_error_and_exit(wi, message, FARGS);
      }
 -    for (j = nfp0; (j < MAXFORCEPARAM); j++)
 +    for (int j = nfp0; (j < MAXFORCEPARAM); j++)
      {
          c[j] = 0.0;
      }
 +    std::array<real, MAXFORCEPARAM> forceParam;
  
      if (strlen(type) == 1 && isdigit(type[0]))
      {
      atom->q     = q;
      atom->m     = m;
      atom->ptype = pt;
 -    for (i = 0; (i < MAXFORCEPARAM); i++)
 +    for (int i = 0; i < MAXFORCEPARAM; i++)
      {
 -        param->c[i] = c[i];
 +        forceParam[i] = c[i];
      }
  
 -    if ((batype_nr = get_bond_atomtype_type(btype, bat)) == NOTSET)
 -    {
 -        add_bond_atomtype(bat, symtab, btype);
 -    }
 -    batype_nr = get_bond_atomtype_type(btype, bat);
 +    InteractionOfType interactionType({}, forceParam, "");
 +
 +    batype_nr = bondAtomType->addBondAtomType(symtab, btype);
  
 -    if ((nr = get_atomtype_type(type, at)) != NOTSET)
 +    if ((nr = at->atomTypeFromName(type)) != NOTSET)
      {
-         auto message = gmx::formatString("Overriding atomtype %s", type);
+         auto message = gmx::formatString
+                 ("Atomtype %s was defined previously (e.g. in the forcefield files), "
+                 "and has now been defined again. This could happen e.g. if you would "
+                 "use a self-contained molecule .itp file that duplicates or replaces "
+                 "the contents of the standard force-field files. You should check "
+                 "the contents of your files and remove such repetition. If you know "
+                 "you should override the previous definition, then you could choose "
+                 "to suppress this warning with -maxwarn.", type);
          warning(wi, message);
 -        if ((nr = set_atomtype(nr, at, symtab, atom, type, param, batype_nr,
 -                               atomnr)) == NOTSET)
 +        if ((nr = at->setType(nr, symtab, *atom, type, interactionType, batype_nr,
 +                              atomnr)) == NOTSET)
          {
              auto message = gmx::formatString("Replacing atomtype %s failed", type);
              warning_error_and_exit(wi, message, FARGS);
          }
      }
 -    else if ((add_atomtype(at, symtab, atom, type, param,
 -                           batype_nr, atomnr)) == NOTSET)
 +    else if ((at->addType(symtab, *atom, type, interactionType,
 +                          batype_nr, atomnr)) == NOTSET)
      {
          auto message = gmx::formatString("Adding atomtype %s failed", type);
          warning_error_and_exit(wi, message, FARGS);
          realloc_nb_params(at, nbparam, pair);
      }
      sfree(atom);
 -    sfree(param);
  }
  
  //! Return whether the contents of \c a and \c b are the same, considering also reversed order.
@@@ -578,15 -536,15 +585,15 @@@ static bool equalEitherForwardOrBackwar
              std::equal(a.begin(), a.end(), b.rbegin()));
  }
  
 -static void push_bondtype(t_params     *       bt,
 -                          const t_param *      b,
 -                          int                  nral,
 -                          int                  ftype,
 -                          bool                 bAllowRepeat,
 -                          const char *         line,
 -                          warninp_t            wi)
 +static void push_bondtype(InteractionsOfType              *bt,
 +                          const InteractionOfType         &b,
 +                          int                              nral,
 +                          int                              ftype,
 +                          bool                             bAllowRepeat,
 +                          const char                      *line,
 +                          warninp                         *wi)
  {
 -    int      nr   = bt->nr;
 +    int      nr   = bt->size();
      int      nrfp = NRFP(ftype);
  
      /* If bAllowRepeat is TRUE, we allow multiple entries as long as they
      if (bAllowRepeat && nr > 1)
      {
          isContinuationOfBlock = true;
 +        gmx::ArrayRef<const int> newParAtom = b.atoms();
 +        gmx::ArrayRef<const int> sysParAtom = bt->interactionTypes[nr - 2].atoms();
          for (int j = 0; j < nral; j++)
          {
 -            if (b->a[j] != bt->param[nr - 2].a[j])
 +            if (newParAtom[j] != sysParAtom[j])
              {
                  isContinuationOfBlock = false;
              }
      bool haveErrored = false;
      for (int i = 0; (i < nr); i++)
      {
 -        gmx::ArrayRef<const int> bParams(b->a, b->a + nral);
 -        gmx::ArrayRef<const int> testParams(bt->param[i].a, bt->param[i].a + nral);
 +        gmx::ArrayRef<const int> bParams    = b.atoms();
 +        gmx::ArrayRef<const int> testParams = bt->interactionTypes[i].atoms();
 +        GMX_RELEASE_ASSERT(bParams.size() == testParams.size(), "Number of atoms needs to be the same between parameters");
          if (equalEitherForwardOrBackward(bParams, testParams))
          {
              GMX_ASSERT(nrfp <= MAXFORCEPARAM, "This is ensured in other places, but we need this assert to keep the clang analyzer happy");
 -            const bool identicalParameters = std::equal(bt->param[i].c, bt->param[i].c + nrfp, b->c);
 +            const bool identicalParameters = std::equal(bt->interactionTypes[i].forceParam().begin(),
 +                                                        bt->interactionTypes[i].forceParam().begin() + nrfp, b.forceParam().begin());
  
              if (!bAllowRepeat || identicalParameters)
              {
                  else if (!haveWarned)
                  {
                      auto message = gmx::formatString
-                             ("Overriding %s parameters.%s",
+                             ("Bondtype %s was defined previously (e.g. in the forcefield files), "
+                             "and has now been defined again. This could happen e.g. if you would "
+                             "use a self-contained molecule .itp file that duplicates or replaces "
+                             "the contents of the standard force-field files. You should check "
+                             "the contents of your files and remove such repetition. If you know "
+                             "you should override the previous definition, then you could choose "
+                             "to suppress this warning with -maxwarn.%s",
                              interaction_function[ftype].longname,
                              (ftype == F_PDIHS) ?
                              "\nUse dihedraltype 9 to allow several multiplicity terms. Only consecutive "
                      warning(wi, message);
  
                      fprintf(stderr, "  old:                                         ");
 -                    for (int j = 0; (j < nrfp); j++)
 +                    gmx::ArrayRef<const real> forceParam = bt->interactionTypes[i].forceParam();
 +                    for (int j = 0; j < nrfp; j++)
                      {
 -                        fprintf(stderr, " %g", bt->param[i].c[j]);
 +                        fprintf(stderr, " %g", forceParam[j]);
                      }
                      fprintf(stderr, " \n  new: %s\n\n", line);
  
                  /* Overwrite the parameters with the latest ones */
                  // TODO considering improving the following code by replacing with:
                  // std::copy(b->c, b->c + nrfp, bt->param[i].c);
 -                for (int j = 0; (j < nrfp); j++)
 +                gmx::ArrayRef<const real> forceParam = b.forceParam();
 +                for (int j = 0; j < nrfp; j++)
                  {
 -                    bt->param[i].c[j] = b->c[j];
 +                    bt->interactionTypes[i].setForceParameter(j, forceParam[j]);
                  }
              }
          }
  
      if (addBondType)
      {
 -        /* alloc */
 -        pr_alloc (2, bt);
 -
          /* fill the arrays up and down */
 -        memcpy(bt->param[bt->nr].c,  b->c, sizeof(b->c));
 -        memcpy(bt->param[bt->nr].a,  b->a, sizeof(b->a));
 -        memcpy(bt->param[bt->nr+1].c, b->c, sizeof(b->c));
 +        bt->interactionTypes.emplace_back(
 +                InteractionOfType(b.atoms(), b.forceParam(), b.interactionTypeName()));
 +        /* need to store force values because they might change below */
 +        std::vector<real> forceParam(b.forceParam().begin(), b.forceParam().end());
  
          /* The definitions of linear angles depend on the order of atoms,
           * that means that for atoms i-j-k, with certain parameter a, the
           */
          if (ftype == F_LINEAR_ANGLES)
          {
 -            bt->param[bt->nr+1].c[0] = 1-bt->param[bt->nr+1].c[0];
 -            bt->param[bt->nr+1].c[2] = 1-bt->param[bt->nr+1].c[2];
 +            forceParam[0] = 1- forceParam[0];
 +            forceParam[2] = 1- forceParam[2];
          }
 -
 -        for (int j = 0; (j < nral); j++)
 +        std::vector<int>         atoms;
 +        gmx::ArrayRef<const int> oldAtoms = b.atoms();
 +        for (auto oldAtom = oldAtoms.rbegin(); oldAtom != oldAtoms.rend(); oldAtom++)
          {
 -            bt->param[bt->nr+1].a[j] = b->a[nral-1-j];
 +            atoms.emplace_back(*oldAtom);
          }
 +        bt->interactionTypes.emplace_back(InteractionOfType(atoms, forceParam, b.interactionTypeName()));
 +    }
 +}
 +
 +static std::vector<int> atomTypesFromAtomNames(const PreprocessingAtomTypes    *atomTypes,
 +                                               const PreprocessingBondAtomType *bondAtomTypes,
 +                                               gmx::ArrayRef<const char[20]>    atomNames,
 +                                               warninp                         *wi)
 +{
 +
 +    GMX_RELEASE_ASSERT(!(!atomNames.empty() && !atomTypes && !bondAtomTypes),
 +                       "Need to have either valid atomtypes or bondatomtypes object");
  
 -        bt->nr += 2;
 +    std::vector<int> atomTypesFromAtomNames;
 +    for (const auto &name : atomNames)
 +    {
 +        if (atomTypes != nullptr)
 +        {
 +            int atomType = atomTypes->atomTypeFromName(name);
 +            if (atomType == NOTSET)
 +            {
 +                auto message = gmx::formatString("Unknown atomtype %s\n", name);
 +                warning_error_and_exit(wi, message, FARGS);
 +            }
 +            atomTypesFromAtomNames.emplace_back(atomType);
 +        }
 +        else if (bondAtomTypes != nullptr)
 +        {
 +            int atomType = bondAtomTypes->bondAtomTypeFromName(name);
 +            if (atomType == NOTSET)
 +            {
 +                auto message = gmx::formatString("Unknown bond_atomtype %s\n", name);
 +                warning_error_and_exit(wi, message, FARGS);
 +            }
 +            atomTypesFromAtomNames.emplace_back(atomType);
 +        }
      }
 +    return atomTypesFromAtomNames;
  }
  
 -void push_bt(directive d, t_params bt[], int nral,
 -             gpp_atomtype_t at,
 -             t_bond_atomtype bat, char *line,
 -             warninp_t wi)
 +
 +void push_bt(Directive                                 d,
 +             gmx::ArrayRef<InteractionsOfType>         bt,
 +             int                                       nral,
 +             PreprocessingAtomTypes                   *at,
 +             PreprocessingBondAtomType                *bondAtomType,
 +             char                                     *line,
 +             warninp                                  *wi)
  {
 -    const char *formal[MAXATOMLIST+1] = {
 +    const char     *formal[MAXATOMLIST+1] = {
          "%s",
          "%s%s",
          "%s%s%s",
          "%s%s%s%s%s%s",
          "%s%s%s%s%s%s%s"
      };
 -    const char *formnl[MAXATOMLIST+1] = {
 +    const char     *formnl[MAXATOMLIST+1] = {
          "%*s",
          "%*s%*s",
          "%*s%*s%*s",
          "%*s%*s%*s%*s%*s%*s",
          "%*s%*s%*s%*s%*s%*s%*s"
      };
 -    const char *formlf = "%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf";
 -    int         i, ft, ftype, nn, nrfp, nrfpA;
 -    char        f1[STRLEN];
 -    char        alc[MAXATOMLIST+1][20];
 +    const char     *formlf = "%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf";
 +    int             i, ft, ftype, nn, nrfp, nrfpA;
 +    char            f1[STRLEN];
 +    char            alc[MAXATOMLIST+1][20];
      /* One force parameter more, so we can check if we read too many */
 -    double      c[MAXFORCEPARAM+1];
 -    t_param     p;
 +    double          c[MAXFORCEPARAM+1];
  
 -    if ((bat && at) || (!bat && !at))
 +    if ((bondAtomType && at) || (!bondAtomType && !at))
      {
 -        gmx_incons("You should pass either bat or at to push_bt");
 +        gmx_incons("You should pass either bondAtomType or at to push_bt");
      }
  
      /* Make format string (nral ints+functype) */
              }
          }
      }
 -    for (i = 0; (i < nral); i++)
 -    {
 -        if (at && ((p.a[i] = get_atomtype_type(alc[i], at)) == NOTSET))
 -        {
 -            auto message = gmx::formatString("Unknown atomtype %s\n", alc[i]);
 -            warning_error_and_exit(wi, message, FARGS);
 -        }
 -        else if (bat && ((p.a[i] = get_bond_atomtype_type(alc[i], bat)) == NOTSET))
 -        {
 -            auto message = gmx::formatString("Unknown bond_atomtype %s\n", alc[i]);
 -            warning_error_and_exit(wi, message, FARGS);
 -        }
 -    }
 -    for (i = 0; (i < nrfp); i++)
 +    std::vector<int> atomTypes = atomTypesFromAtomNames(at,
 +                                                        bondAtomType,
 +                                                        gmx::arrayRefFromArray(alc, nral),
 +                                                        wi);
 +    std::array<real, MAXFORCEPARAM> forceParam;
 +    for (int i = 0; (i < nrfp); i++)
      {
 -        p.c[i] = c[i];
 +        forceParam[i] = c[i];
      }
 -    push_bondtype (&(bt[ftype]), &p, nral, ftype, FALSE, line, wi);
 +    push_bondtype (&(bt[ftype]), InteractionOfType(atomTypes, forceParam), nral, ftype, FALSE, line, wi);
  }
  
  
 -void push_dihedraltype(directive d, t_params bt[],
 -                       t_bond_atomtype bat, char *line,
 -                       warninp_t wi)
 +void push_dihedraltype(Directive d, gmx::ArrayRef<InteractionsOfType> bt,
 +                       PreprocessingBondAtomType *bondAtomType, char *line,
 +                       warninp *wi)
  {
 -    const char  *formal[MAXATOMLIST+1] = {
 +    const char      *formal[MAXATOMLIST+1] = {
          "%s",
          "%s%s",
          "%s%s%s",
          "%s%s%s%s%s%s",
          "%s%s%s%s%s%s%s"
      };
 -    const char  *formnl[MAXATOMLIST+1] = {
 +    const char      *formnl[MAXATOMLIST+1] = {
          "%*s",
          "%*s%*s",
          "%*s%*s%*s",
          "%*s%*s%*s%*s%*s%*s",
          "%*s%*s%*s%*s%*s%*s%*s"
      };
 -    const char  *formlf[MAXFORCEPARAM] = {
 +    const char      *formlf[MAXFORCEPARAM] = {
          "%lf",
          "%lf%lf",
          "%lf%lf%lf",
          "%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf",
          "%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf",
      };
 -    int          i, ft, ftype, nn, nrfp, nrfpA, nral;
 -    char         f1[STRLEN];
 -    char         alc[MAXATOMLIST+1][20];
 -    double       c[MAXFORCEPARAM];
 -    t_param      p;
 -    bool         bAllowRepeat;
 +    int              i, ft, ftype, nn, nrfp, nrfpA, nral;
 +    char             f1[STRLEN];
 +    char             alc[MAXATOMLIST+1][20];
 +    double           c[MAXFORCEPARAM];
 +    bool             bAllowRepeat;
  
      /* This routine accepts dihedraltypes defined from either 2 or 4 atoms.
       *
          }
      }
  
 -    for (i = 0; (i < 4); i++)
 +    std::vector<int>                atoms;
 +    std::array<real, MAXFORCEPARAM> forceParam;
 +    for (int i = 0; (i < 4); i++)
      {
          if (!strcmp(alc[i], "X"))
          {
 -            p.a[i] = -1;
 +            atoms.emplace_back(-1);
          }
          else
          {
 -            if ((p.a[i] = get_bond_atomtype_type(alc[i], bat)) == NOTSET)
 +            int atomNumber;
 +            if ((atomNumber = bondAtomType->bondAtomTypeFromName(alc[i])) == NOTSET)
              {
                  auto message = gmx::formatString("Unknown bond_atomtype %s", alc[i]);
                  warning_error_and_exit(wi, message, FARGS);
              }
 +            atoms.emplace_back(atomNumber);
          }
      }
 -    for (i = 0; (i < nrfp); i++)
 +    for (int i = 0; (i < nrfp); i++)
      {
 -        p.c[i] = c[i];
 +        forceParam[i] = c[i];
      }
      /* Always use 4 atoms here, since we created two wildcard atoms
       * if there wasn't of them 4 already.
       */
 -    push_bondtype (&(bt[ftype]), &p, 4, ftype, bAllowRepeat, line, wi);
 +    push_bondtype (&(bt[ftype]), InteractionOfType(atoms, forceParam), 4, ftype, bAllowRepeat, line, wi);
  }
  
  
 -void push_nbt(directive d, t_nbparam **nbt, gpp_atomtype_t atype,
 +void push_nbt(Directive d, t_nbparam **nbt, PreprocessingAtomTypes *atypes,
                char *pline, int nb_funct,
 -              warninp_t wi)
 +              warninp *wi)
  {
      /* swap the atoms */
      const char *form3 = "%*s%*s%*s%lf%lf%lf";
      }
  
      /* Put the parameters in the matrix */
 -    if ((ai = get_atomtype_type (a0, atype)) == NOTSET)
 +    if ((ai = atypes->atomTypeFromName(a0)) == NOTSET)
      {
          auto message = gmx::formatString("Atomtype %s not found", a0);
          warning_error_and_exit(wi, message, FARGS);
      }
 -    if ((aj = get_atomtype_type (a1, atype)) == NOTSET)
 +    if ((aj = atypes->atomTypeFromName(a1)) == NOTSET)
      {
          auto message = gmx::formatString("Atomtype %s not found", a1);
          warning_error_and_exit(wi, message, FARGS);
          }
          if (!bId)
          {
-             auto message = gmx::formatString("Overriding non-bonded parameters,");
+             auto message = gmx::formatString
+                     ("Non-bonded parameters were defined previously (e.g. in the forcefield files), "
+                     "and have now been defined again. This could happen e.g. if you would "
+                     "use a self-contained molecule .itp file that duplicates or replaces "
+                     "the contents of the standard force-field files. You should check "
+                     "the contents of your files and remove such repetition. If you know "
+                     "you should override the previous definitions, then you could choose "
+                     "to suppress this warning with -maxwarn.");
              warning(wi, message);
              fprintf(stderr, "  old:");
              for (i = 0; i < nrfp; i++)
  }
  
  void
 -push_cmaptype(directive d, t_params bt[], int nral, gpp_atomtype_t at,
 -              t_bond_atomtype bat, char *line,
 -              warninp_t wi)
 +push_cmaptype(Directive                                 d,
 +              gmx::ArrayRef<InteractionsOfType>         bt,
 +              int                                       nral,
 +              PreprocessingAtomTypes                   *atomtypes,
 +              PreprocessingBondAtomType                *bondAtomType,
 +              char                                     *line,
 +              warninp                                  *wi)
  {
 -    const char  *formal = "%s%s%s%s%s%s%s%s%n";
 +    const char      *formal = "%s%s%s%s%s%s%s%s%n";
  
 -    int          i, ft, ftype, nn, nrfp, nrfpA, nrfpB;
 -    int          start, nchar_consumed;
 -    int          nxcmap, nycmap, ncmap, read_cmap, sl, nct;
 -    char         s[20], alc[MAXATOMLIST+2][20];
 -    t_param      p;
 +    int              ft, ftype, nn, nrfp, nrfpA, nrfpB;
 +    int              start, nchar_consumed;
 +    int              nxcmap, nycmap, ncmap, read_cmap, sl, nct;
 +    char             s[20], alc[MAXATOMLIST+2][20];
  
      /* Keep the compiler happy */
      read_cmap = 0;
      nrfpB  = strtol(alc[7], nullptr, 10)*strtol(alc[7], nullptr, 10);
      nrfp   = nrfpA+nrfpB;
  
 -    /* Allocate memory for the CMAP grid */
 -    bt[F_CMAP].ncmap += nrfp;
 -    srenew(bt[F_CMAP].cmap, bt[F_CMAP].ncmap);
 -
      /* Read in CMAP parameters */
      sl = 0;
 -    for (i = 0; i < ncmap; i++)
 +    for (int i = 0; i < ncmap; i++)
      {
          while (isspace(*(line+start+sl)))
          {
          }
          nn  = sscanf(line+start+sl, " %s ", s);
          sl += strlen(s);
 -        bt[F_CMAP].cmap[i+(bt[F_CMAP].ncmap)-nrfp] = strtod(s, nullptr);
 +        bt[F_CMAP].cmap.emplace_back(strtod(s, nullptr));
  
          if (nn == 1)
          {
      /* Check do that we got the number of parameters we expected */
      if (read_cmap == nrfpA)
      {
 -        for (i = 0; i < ncmap; i++)
 +        for (int i = 0; i < ncmap; i++)
          {
 -            bt[F_CMAP].cmap[i+ncmap] = bt[F_CMAP].cmap[i];
 +            bt[F_CMAP].cmap.emplace_back(bt[F_CMAP].cmap[i]);
          }
      }
      else
      /* Set grid spacing and the number of grids (we assume these numbers to be the same for all grids
       * so we can safely assign them each time
       */
 -    bt[F_CMAP].grid_spacing = nxcmap;            /* Or nycmap, they need to be equal */
 -    bt[F_CMAP].nc           = bt[F_CMAP].nc + 1; /* Since we are incrementing here, we need to subtract later, see (*****) */
 -    nct                     = (nral+1) * bt[F_CMAP].nc;
 +    bt[F_CMAP].cmakeGridSpacing = nxcmap; /* Or nycmap, they need to be equal */
 +    bt[F_CMAP].cmapAngles++;              /* Since we are incrementing here, we need to subtract later, see (*****) */
 +    nct                     = (nral+1) * bt[F_CMAP].cmapAngles;
  
 -    /* Allocate memory for the cmap_types information */
 -    srenew(bt[F_CMAP].cmap_types, nct);
 -
 -    for (i = 0; (i < nral); i++)
 +    for (int i = 0; (i < nral); i++)
      {
 -        if (at && ((p.a[i] = get_bond_atomtype_type(alc[i], bat)) == NOTSET))
 -        {
 -            auto message = gmx::formatString("Unknown atomtype %s\n", alc[i]);
 -            warning_error(wi, message);
 -        }
 -        else if (bat && ((p.a[i] = get_bond_atomtype_type(alc[i], bat)) == NOTSET))
 -        {
 -            auto message = gmx::formatString("Unknown bond_atomtype %s\n", alc[i]);
 -            warning_error(wi, message);
 -        }
 -
          /* Assign a grid number to each cmap_type */
 -        bt[F_CMAP].cmap_types[bt[F_CMAP].nct++] = get_bond_atomtype_type(alc[i], bat);
 +        GMX_RELEASE_ASSERT(bondAtomType != nullptr, "Need valid PreprocessingBondAtomType object");
 +        bt[F_CMAP].cmapAtomTypes.emplace_back(bondAtomType->bondAtomTypeFromName(alc[i]));
      }
  
      /* Assign a type number to this cmap */
 -    bt[F_CMAP].cmap_types[bt[F_CMAP].nct++] = bt[F_CMAP].nc-1; /* Since we inremented earlier, we need to subtrac here, to get the types right (****) */
 +    bt[F_CMAP].cmapAtomTypes.emplace_back(bt[F_CMAP].cmapAngles-1); /* Since we inremented earlier, we need to subtrac here, to get the types right (****) */
  
      /* Check for the correct number of atoms (again) */
 -    if (bt[F_CMAP].nct != nct)
 +    if (bt[F_CMAP].nct() != nct)
      {
 -        auto message = gmx::formatString("Incorrect number of atom types (%d) in cmap type %d\n", nct, bt[F_CMAP].nc);
 +        auto message = gmx::formatString("Incorrect number of atom types (%d) in cmap type %d\n", nct, bt[F_CMAP].cmapAngles);
          warning_error(wi, message);
      }
 -
 -    /* Is this correct?? */
 -    for (i = 0; i < MAXFORCEPARAM; i++)
 -    {
 -        p.c[i] = NOTSET;
 -    }
 +    std::vector<int> atomTypes = atomTypesFromAtomNames(atomtypes,
 +                                                        bondAtomType,
 +                                                        gmx::constArrayRefFromArray(alc, nral),
 +                                                        wi);
 +    std::array<real, MAXFORCEPARAM> forceParam = {NOTSET};
  
      /* Push the bond to the bondlist */
 -    push_bondtype (&(bt[ftype]), &p, nral, ftype, FALSE, line, wi);
 +    push_bondtype (&(bt[ftype]), InteractionOfType(atomTypes, forceParam), nral, ftype, FALSE, line, wi);
  }
  
  
@@@ -1282,7 -1230,7 +1302,7 @@@ static void push_atom_now(t_symtab *sym
                            char *resnumberic,
                            char *resname, char *name, real m0, real q0,
                            int typeB, char *ctypeB, real mB, real qB,
 -                          warninp_t wi)
 +                          warninp *wi)
  {
      int           j, resind = 0, resnr;
      unsigned char ric;
@@@ -1378,8 -1326,8 +1398,8 @@@ static void push_cg(t_block *block, in
  }
  
  void push_atom(t_symtab *symtab, t_block *cgs,
 -               t_atoms *at, gpp_atomtype_t atype, char *line, int *lastcg,
 -               warninp_t wi)
 +               t_atoms *at, PreprocessingAtomTypes *atypes, char *line, int *lastcg,
 +               warninp *wi)
  {
      int           nr, ptype;
      int           cgnumber, atomnr, type, typeB, nscan;
          return;
      }
      sscanf(id, "%d", &atomnr);
 -    if ((type  = get_atomtype_type(ctype, atype)) == NOTSET)
 +    if ((type  = atypes->atomTypeFromName(ctype)) == NOTSET)
      {
          auto message = gmx::formatString("Atomtype %s not found", ctype);
          warning_error_and_exit(wi, message, FARGS);
      }
 -    ptype = get_atomtype_ptype(type, atype);
 +    ptype = atypes->atomParticleTypeFromAtomType(type);
  
      /* Set default from type */
 -    q0    = get_atomtype_qA(type, atype);
 -    m0    = get_atomtype_massA(type, atype);
 +    q0    = atypes->atomChargeFromAtomType(type);
 +    m0    = atypes->atomMassFromAtomType(type);
      typeB = type;
      qB    = q0;
      mB    = m0;
              m0 = mB = m;
              if (nscan > 2)
              {
 -                if ((typeB = get_atomtype_type(ctypeB, atype)) == NOTSET)
 +                if ((typeB = atypes->atomTypeFromName(ctypeB)) == NOTSET)
                  {
                      auto message = gmx::formatString("Atomtype %s not found", ctypeB);
                      warning_error_and_exit(wi, message, FARGS);
                  }
 -                qB = get_atomtype_qA(typeB, atype);
 -                mB = get_atomtype_massA(typeB, atype);
 +                qB = atypes->atomChargeFromAtomType(typeB);
 +                mB = atypes->atomMassFromAtomType(typeB);
                  if (nscan > 3)
                  {
                      qB = qb;
  
      push_cg(cgs, lastcg, cgnumber, nr);
  
 -    push_atom_now(symtab, at, atomnr, get_atomtype_atomnumber(type, atype),
 +    push_atom_now(symtab, at, atomnr, atypes->atomNumberFromAtomType(type),
                    type, ctype, ptype, resnumberic,
                    resname, name, m0, q0, typeB,
                    typeB == type ? ctype : ctypeB, mB, qB, wi);
  }
  
 -void push_molt(t_symtab *symtab, int *nmol, t_molinfo **mol, char *line,
 -               warninp_t wi)
 +void push_molt(t_symtab                         *symtab,
 +               std::vector<MoleculeInformation> *mol,
 +               char                             *line,
 +               warninp                          *wi)
  {
      char       type[STRLEN];
 -    int        nrexcl, i;
 -    t_molinfo *newmol;
 +    int        nrexcl;
  
      if ((sscanf(line, "%s%d", type, &nrexcl)) != 2)
      {
      }
  
      /* Test if this moleculetype overwrites another */
 -    i    = 0;
 -    while (i < *nmol)
 +    const auto found = std::find_if(mol->begin(), mol->end(),
 +                                    [&type](const auto &m)
 +                                    { return strcmp(*(m.name), type) == 0; });
 +    if (found != mol->end())
      {
 -        if (strcmp(*((*mol)[i].name), type) == 0)
 -        {
 -            auto message = gmx::formatString("moleculetype %s is redefined", type);
 -            warning_error_and_exit(wi, message, FARGS);
 -        }
 -        i++;
 +        auto message = gmx::formatString("moleculetype %s is redefined", type);
 +        warning_error_and_exit(wi, message, FARGS);
      }
  
 -    (*nmol)++;
 -    srenew(*mol, *nmol);
 -    newmol = &((*mol)[*nmol-1]);
 -    init_molinfo(newmol);
 +    mol->emplace_back();
 +    mol->back().initMolInfo();
  
      /* Fill in the values */
 -    newmol->name     = put_symtab(symtab, type);
 -    newmol->nrexcl   = nrexcl;
 -    newmol->excl_set = FALSE;
 +    mol->back().name     = put_symtab(symtab, type);
 +    mol->back().nrexcl   = nrexcl;
 +    mol->back().excl_set = false;
  }
  
 -static bool default_nb_params(int ftype, t_params bt[], t_atoms *at,
 -                              t_param *p, int c_start, bool bB, bool bGenPairs)
 +static bool findIfAllNBAtomsMatch(gmx::ArrayRef<const int>      atomsFromParameterArray,
 +                                  gmx::ArrayRef<const int>      atomsFromCurrentParameter,
 +                                  const t_atoms                *at,
 +                                  bool                          bB)
  {
 -    int          i, j, ti, tj, ntype;
 -    bool         bFound;
 -    t_param     *pi    = nullptr;
 -    int          nr    = bt[ftype].nr;
 -    int          nral  = NRAL(ftype);
 -    int          nrfp  = interaction_function[ftype].nrfpA;
 -    int          nrfpB = interaction_function[ftype].nrfpB;
 +    if (atomsFromParameterArray.size() != atomsFromCurrentParameter.size())
 +    {
 +        return false;
 +    }
 +    else if (bB)
 +    {
 +        for (int i = 0; i < atomsFromCurrentParameter.ssize(); i++)
 +        {
 +            if (at->atom[atomsFromCurrentParameter[i]].typeB != atomsFromParameterArray[i])
 +            {
 +                return false;
 +            }
 +        }
 +        return true;
 +    }
 +    else
 +    {
 +        for (int i = 0; i < atomsFromCurrentParameter.ssize(); i++)
 +        {
 +            if (at->atom[atomsFromCurrentParameter[i]].type != atomsFromParameterArray[i])
 +            {
 +                return false;
 +            }
 +        }
 +        return true;
 +    }
 +}
 +
 +static bool default_nb_params(int ftype, gmx::ArrayRef<InteractionsOfType> bt, t_atoms *at,
 +                              InteractionOfType *p, int c_start, bool bB, bool bGenPairs)
 +{
 +    int                    ti, tj, ntype;
 +    bool                   bFound;
 +    InteractionOfType     *pi    = nullptr;
 +    int                    nr    = bt[ftype].size();
 +    int                    nral  = NRAL(ftype);
 +    int                    nrfp  = interaction_function[ftype].nrfpA;
 +    int                    nrfpB = interaction_function[ftype].nrfpB;
  
      if ((!bB && nrfp == 0) || (bB && nrfpB == 0))
      {
          GMX_ASSERT(ntype * ntype == nr, "Number of pairs of generated non-bonded parameters should be a perfect square");
          if (bB)
          {
 -            ti = at->atom[p->a[0]].typeB;
 -            tj = at->atom[p->a[1]].typeB;
 +            ti = at->atom[p->ai()].typeB;
 +            tj = at->atom[p->aj()].typeB;
 +        }
 +        else
 +        {
 +            ti = at->atom[p->ai()].type;
 +            tj = at->atom[p->aj()].type;
 +        }
 +        pi     = &(bt[ftype].interactionTypes[ntype*ti+tj]);
 +        if (pi->atoms().ssize() < nral)
 +        {
 +            /* not initialized yet with atom names */
 +            bFound = false;
          }
          else
          {
 -            ti = at->atom[p->a[0]].type;
 -            tj = at->atom[p->a[1]].type;
 +            bFound = ((ti == pi->ai()) && (tj == pi->aj()));
          }
 -        pi     = &(bt[ftype].param[ntype*ti+tj]);
 -        bFound = ((ti == pi->a[0]) && (tj == pi->a[1]));
      }
  
 +    gmx::ArrayRef<const int> paramAtoms = p->atoms();
      /* Search explicitly if we didnt find it */
      if (!bFound)
      {
 -        for (i = 0; ((i < nr) && !bFound); i++)
 +        auto foundParameter = std::find_if(bt[ftype].interactionTypes.begin(),
 +                                           bt[ftype].interactionTypes.end(),
 +                                           [&paramAtoms, &at, &bB](const auto &param)
 +                                           { return findIfAllNBAtomsMatch(param.atoms(), paramAtoms, at, bB); });
 +        if (foundParameter != bt[ftype].interactionTypes.end())
          {
 -            pi = &(bt[ftype].param[i]);
 -            if (bB)
 -            {
 -                for (j = 0; ((j < nral) &&
 -                             (at->atom[p->a[j]].typeB == pi->a[j])); j++)
 -                {
 -                    ;
 -                }
 -            }
 -            else
 -            {
 -                for (j = 0; ((j < nral) &&
 -                             (at->atom[p->a[j]].type == pi->a[j])); j++)
 -                {
 -                    ;
 -                }
 -            }
 -            bFound = (j == nral);
 +            bFound = true;
 +            pi     = &(*foundParameter);
          }
      }
  
      if (bFound)
      {
 +        gmx::ArrayRef<const real> forceParam = pi->forceParam();
          if (bB)
          {
              if (nrfp+nrfpB > MAXFORCEPARAM)
              {
                  gmx_incons("Too many force parameters");
              }
 -            for (j = c_start; (j < nrfpB); j++)
 +            for (int j = c_start; j < nrfpB; j++)
              {
 -                p->c[nrfp+j] = pi->c[j];
 +                p->setForceParameter(nrfp+j, forceParam[j]);
              }
          }
          else
          {
 -            for (j = c_start; (j < nrfp); j++)
 +            for (int j = c_start; j < nrfp; j++)
              {
 -                p->c[j] = pi->c[j];
 +                p->setForceParameter(j, forceParam[j]);
              }
          }
      }
      else
      {
 -        for (j = c_start; (j < nrfp); j++)
 +        for (int j = c_start; j < nrfp; j++)
          {
 -            p->c[j] = 0.0;
 +            p->setForceParameter(j, 0.0);
          }
      }
      return bFound;
  }
  
 -static bool default_cmap_params(t_params bondtype[],
 -                                t_atoms *at, gpp_atomtype_t atype,
 -                                t_param *p, bool bB,
 +static bool default_cmap_params(gmx::ArrayRef<InteractionsOfType> bondtype,
 +                                t_atoms *at, PreprocessingAtomTypes *atypes,
 +                                InteractionOfType *p, bool bB,
                                  int *cmap_type, int *nparam_def,
 -                                warninp_t wi)
 +                                warninp *wi)
  {
 -    int        i, nparam_found;
 +    int        nparam_found;
      int        ct;
 -    bool       bFound = FALSE;
 +    bool       bFound = false;
  
      nparam_found = 0;
      ct           = 0;
  
      /* Match the current cmap angle against the list of cmap_types */
 -    for (i = 0; i < bondtype[F_CMAP].nct && !bFound; i += 6)
 +    for (int i = 0; i < bondtype[F_CMAP].nct() && !bFound; i += 6)
      {
          if (bB)
          {
          else
          {
              if (
 -                (get_atomtype_batype(at->atom[p->a[0]].type, atype) == bondtype[F_CMAP].cmap_types[i])   &&
 -                (get_atomtype_batype(at->atom[p->a[1]].type, atype) == bondtype[F_CMAP].cmap_types[i+1]) &&
 -                (get_atomtype_batype(at->atom[p->a[2]].type, atype) == bondtype[F_CMAP].cmap_types[i+2]) &&
 -                (get_atomtype_batype(at->atom[p->a[3]].type, atype) == bondtype[F_CMAP].cmap_types[i+3]) &&
 -                (get_atomtype_batype(at->atom[p->a[4]].type, atype) == bondtype[F_CMAP].cmap_types[i+4]))
 +                (atypes->bondAtomTypeFromAtomType(at->atom[p->ai()].type) == bondtype[F_CMAP].cmapAtomTypes[i])   &&
 +                (atypes->bondAtomTypeFromAtomType(at->atom[p->aj()].type) == bondtype[F_CMAP].cmapAtomTypes[i+1]) &&
 +                (atypes->bondAtomTypeFromAtomType(at->atom[p->ak()].type) == bondtype[F_CMAP].cmapAtomTypes[i+2]) &&
 +                (atypes->bondAtomTypeFromAtomType(at->atom[p->al()].type) == bondtype[F_CMAP].cmapAtomTypes[i+3]) &&
 +                (atypes->bondAtomTypeFromAtomType(at->atom[p->am()].type) == bondtype[F_CMAP].cmapAtomTypes[i+4]))
              {
                  /* Found cmap torsion */
 -                bFound       = TRUE;
 -                ct           = bondtype[F_CMAP].cmap_types[i+5];
 +                bFound       = true;
 +                ct           = bondtype[F_CMAP].cmapAtomTypes[i+5];
                  nparam_found = 1;
              }
          }
      if (!bFound)
      {
          auto message = gmx::formatString("Unknown cmap torsion between atoms %d %d %d %d %d",
 -                                         p->a[0]+1, p->a[1]+1, p->a[2]+1, p->a[3]+1, p->a[4]+1);
 +                                         p->ai()+1, p->aj()+1, p->ak()+1, p->al()+1, p->am()+1);
          warning_error_and_exit(wi, message, FARGS);
      }
  
  /* Returns the number of exact atom type matches, i.e. non wild-card matches,
   * returns -1 when there are no matches at all.
   */
 -static int natom_match(t_param *pi,
 +static int natom_match(const InteractionOfType &pi,
                         int type_i, int type_j, int type_k, int type_l,
 -                       const gpp_atomtype* atype)
 +                       const PreprocessingAtomTypes* atypes)
  {
 -    if ((pi->ai() == -1 || get_atomtype_batype(type_i, atype) == pi->ai()) &&
 -        (pi->aj() == -1 || get_atomtype_batype(type_j, atype) == pi->aj()) &&
 -        (pi->ak() == -1 || get_atomtype_batype(type_k, atype) == pi->ak()) &&
 -        (pi->al() == -1 || get_atomtype_batype(type_l, atype) == pi->al()))
 +    if ((pi.ai() == -1 || atypes->bondAtomTypeFromAtomType(type_i) == pi.ai()) &&
 +        (pi.aj() == -1 || atypes->bondAtomTypeFromAtomType(type_j) == pi.aj()) &&
 +        (pi.ak() == -1 || atypes->bondAtomTypeFromAtomType(type_k) == pi.ak()) &&
 +        (pi.al() == -1 || atypes->bondAtomTypeFromAtomType(type_l) == pi.al()))
      {
          return
 -            (pi->ai() == -1 ? 0 : 1) +
 -            (pi->aj() == -1 ? 0 : 1) +
 -            (pi->ak() == -1 ? 0 : 1) +
 -            (pi->al() == -1 ? 0 : 1);
 +            (pi.ai() == -1 ? 0 : 1) +
 +            (pi.aj() == -1 ? 0 : 1) +
 +            (pi.ak() == -1 ? 0 : 1) +
 +            (pi.al() == -1 ? 0 : 1);
      }
      else
      {
      }
  }
  
 -static bool default_params(int ftype, t_params bt[],
 -                           t_atoms *at, gpp_atomtype_t atype,
 -                           t_param *p, bool bB,
 -                           t_param **param_def,
 -                           int *nparam_def)
 +static int findNumberOfDihedralAtomMatches(const InteractionOfType            &currentParamFromParameterArray,
 +                                           const InteractionOfType            &parameterToAdd,
 +                                           const t_atoms                      *at,
 +                                           const PreprocessingAtomTypes       *atypes,
 +                                           bool                                bB)
 +{
 +    if (bB)
 +    {
 +        return natom_match(currentParamFromParameterArray,
 +                           at->atom[parameterToAdd.ai()].typeB,
 +                           at->atom[parameterToAdd.aj()].typeB,
 +                           at->atom[parameterToAdd.ak()].typeB,
 +                           at->atom[parameterToAdd.al()].typeB, atypes);
 +    }
 +    else
 +    {
 +        return natom_match(currentParamFromParameterArray,
 +                           at->atom[parameterToAdd.ai()].type,
 +                           at->atom[parameterToAdd.aj()].type,
 +                           at->atom[parameterToAdd.ak()].type,
 +                           at->atom[parameterToAdd.al()].type, atypes);
 +    }
 +}
 +
 +static bool findIfAllParameterAtomsMatch(gmx::ArrayRef<const int>      atomsFromParameterArray,
 +                                         gmx::ArrayRef<const int>      atomsFromCurrentParameter,
 +                                         const t_atoms                *at,
 +                                         const PreprocessingAtomTypes *atypes,
 +                                         bool                          bB)
 +{
 +    if (atomsFromParameterArray.size() != atomsFromCurrentParameter.size())
 +    {
 +        return false;
 +    }
 +    else if (bB)
 +    {
 +        for (int i = 0; i < atomsFromCurrentParameter.ssize(); i++)
 +        {
 +            if (atypes->bondAtomTypeFromAtomType(
 +                        at->atom[atomsFromCurrentParameter[i]].typeB) != atomsFromParameterArray[i])
 +            {
 +                return false;
 +            }
 +        }
 +        return true;
 +    }
 +    else
 +    {
 +        for (int i = 0; i < atomsFromCurrentParameter.ssize(); i++)
 +        {
 +            if (atypes->bondAtomTypeFromAtomType(
 +                        at->atom[atomsFromCurrentParameter[i]].type) != atomsFromParameterArray[i])
 +            {
 +                return false;
 +            }
 +        }
 +        return true;
 +    }
 +}
 +
 +static std::vector<InteractionOfType>::iterator
 +defaultInteractionsOfType(int ftype, gmx::ArrayRef<InteractionsOfType> bt,
 +                          t_atoms *at, PreprocessingAtomTypes *atypes,
 +                          const InteractionOfType &p, bool bB,
 +                          int *nparam_def)
  {
 -    int          nparam_found;
 -    bool         bFound, bSame;
 -    t_param     *pi    = nullptr;
 -    t_param     *pj    = nullptr;
 -    int          nr    = bt[ftype].nr;
 -    int          nral  = NRAL(ftype);
 -    int          nrfpA = interaction_function[ftype].nrfpA;
 -    int          nrfpB = interaction_function[ftype].nrfpB;
 +    int              nparam_found;
 +    int              nrfpA = interaction_function[ftype].nrfpA;
 +    int              nrfpB = interaction_function[ftype].nrfpB;
  
      if ((!bB && nrfpA == 0) || (bB && nrfpB == 0))
      {
 -        return TRUE;
 +        return bt[ftype].interactionTypes.end();
      }
  
  
 -    bFound       = FALSE;
      nparam_found = 0;
      if (ftype == F_PDIHS || ftype == F_RBDIHS || ftype == F_IDIHS || ftype == F_PIDIHS)
      {
          int nmatch_max = -1;
 -        int i          = -1;
 -        int t;
  
          /* For dihedrals we allow wildcards. We choose the first type
           * that has the most real matches, i.e. non-wildcard matches.
           */
 -        for (t = 0; ((t < nr) && nmatch_max < 4); t++)
 -        {
 -            int      nmatch;
 -            t_param *pt;
 -
 -            pt = &(bt[ftype].param[t]);
 -            if (bB)
 +        auto prevPos = bt[ftype].interactionTypes.end();
 +        auto pos     = bt[ftype].interactionTypes.begin();
 +        while (pos != bt[ftype].interactionTypes.end() && nmatch_max < 4)
 +        {
 +            pos = std::find_if(bt[ftype].interactionTypes.begin(), bt[ftype].interactionTypes.end(),
 +                               [&p, &at, &atypes, &bB, &nmatch_max](const auto &param)
 +                               { return (findNumberOfDihedralAtomMatches(param, p, at, atypes, bB) > nmatch_max); });
 +            if (pos != bt[ftype].interactionTypes.end())
              {
 -                nmatch = natom_match(pt, at->atom[p->ai()].typeB, at->atom[p->aj()].typeB, at->atom[p->ak()].typeB, at->atom[p->al()].typeB, atype);
 -            }
 -            else
 -            {
 -                nmatch = natom_match(pt, at->atom[p->ai()].type, at->atom[p->aj()].type, at->atom[p->ak()].type, at->atom[p->al()].type, atype);
 -            }
 -            if (nmatch > nmatch_max)
 -            {
 -                nmatch_max = nmatch;
 -                i          = t;
 -                bFound     = TRUE;
 +                prevPos    = pos;
 +                nmatch_max = findNumberOfDihedralAtomMatches(*pos, p, at, atypes, bB);
              }
          }
  
 -        if (bFound)
 +        if (prevPos != bt[ftype].interactionTypes.end())
          {
 -            int j;
 -
 -            pi    = &(bt[ftype].param[i]);
              nparam_found++;
  
              /* Find additional matches for this dihedral - necessary
               * The rule in that case is that additional matches
               * HAVE to be on adjacent lines!
               */
 -            bSame = TRUE;
 -            /* Continue from current i value */
 -            for (j = i + 2; j < nr && bSame; j += 2)
 +            bool       bSame = true;
 +            //Advance iterator (like std::advance) without incrementing past end (UB)
 +            const auto safeAdvance = [](auto &it, auto n, auto end) {
 +                    it = end-it > n ? it+n : end;
 +                };
 +            /* Continue from current iterator position */
 +            auto       nextPos = prevPos;
 +            const auto endIter = bt[ftype].interactionTypes.end();
 +            safeAdvance(nextPos, 2, endIter);
 +            for (; nextPos < endIter && bSame; safeAdvance(nextPos, 2, endIter))
              {
 -                pj    = &(bt[ftype].param[j]);
 -                bSame = (pi->ai() == pj->ai() && pi->aj() == pj->aj() && pi->ak() == pj->ak() && pi->al() == pj->al());
 +                bSame = (prevPos->ai() == nextPos->ai() && prevPos->aj() == nextPos->aj() && prevPos->ak() == nextPos->ak() && prevPos->al() == nextPos->al());
                  if (bSame)
                  {
                      nparam_found++;
                  /* nparam_found will be increased as long as the numbers match */
              }
          }
 +        *nparam_def = nparam_found;
 +        return prevPos;
      }
      else   /* Not a dihedral */
      {
 -        int i, j;
 -
 -        for (i = 0; ((i < nr) && !bFound); i++)
 -        {
 -            pi = &(bt[ftype].param[i]);
 -            if (bB)
 -            {
 -                for (j = 0; ((j < nral) &&
 -                             (get_atomtype_batype(at->atom[p->a[j]].typeB, atype) == pi->a[j])); j++)
 -                {
 -                    ;
 -                }
 -            }
 -            else
 -            {
 -                for (j = 0; ((j < nral) &&
 -                             (get_atomtype_batype(at->atom[p->a[j]].type, atype) == pi->a[j])); j++)
 -                {
 -                    ;
 -                }
 -            }
 -            bFound = (j == nral);
 -        }
 -        if (bFound)
 +        gmx::ArrayRef<const int> atomParam = p.atoms();
 +        auto                     found     = std::find_if(bt[ftype].interactionTypes.begin(),
 +                                                          bt[ftype].interactionTypes.end(),
 +                                                          [&atomParam, &at, &atypes, &bB](const auto &param)
 +                                                          { return findIfAllParameterAtomsMatch(param.atoms(), atomParam, at, atypes, bB); });
 +        if (found != bt[ftype].interactionTypes.end())
          {
              nparam_found = 1;
          }
 +        *nparam_def = nparam_found;
 +        return found;
      }
 -
 -    *param_def  = pi;
 -    *nparam_def = nparam_found;
 -
 -    return bFound;
  }
  
  
  
 -void push_bond(directive d, t_params bondtype[], t_params bond[],
 -               t_atoms *at, gpp_atomtype_t atype, char *line,
 +void push_bond(Directive d, gmx::ArrayRef<InteractionsOfType> bondtype,
 +               gmx::ArrayRef<InteractionsOfType> bond,
 +               t_atoms *at, PreprocessingAtomTypes *atypes, char *line,
                 bool bBonded, bool bGenPairs, real fudgeQQ,
                 bool bZero, bool *bWarn_copy_A_B,
 -               warninp_t wi)
 +               warninp *wi)
  {
 -    const char  *aaformat[MAXATOMLIST] = {
 +    const char      *aaformat[MAXATOMLIST] = {
          "%d%d",
          "%d%d%d",
          "%d%d%d%d",
          "%d%d%d%d%d%d",
          "%d%d%d%d%d%d%d"
      };
 -    const char  *asformat[MAXATOMLIST] = {
 +    const char      *asformat[MAXATOMLIST] = {
          "%*s%*s",
          "%*s%*s%*s",
          "%*s%*s%*s%*s",
          "%*s%*s%*s%*s%*s%*s",
          "%*s%*s%*s%*s%*s%*s%*s"
      };
 -    const char  *ccformat = "%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf";
 -    int          nr, i, j, nral, nral_fmt, nread, ftype;
 -    char         format[STRLEN];
 +    const char      *ccformat = "%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf";
 +    int              nral, nral_fmt, nread, ftype;
 +    char             format[STRLEN];
      /* One force parameter more, so we can check if we read too many */
 -    double       cc[MAXFORCEPARAM+1];
 -    int          aa[MAXATOMLIST+1];
 -    t_param      param, *param_defA, *param_defB;
 -    bool         bFoundA = FALSE, bFoundB = FALSE, bDef, bPert, bSwapParity = FALSE;
 -    int          nparam_defA, nparam_defB;
 +    double           cc[MAXFORCEPARAM+1];
 +    int              aa[MAXATOMLIST+1];
 +    bool             bFoundA = FALSE, bFoundB = FALSE, bDef, bSwapParity = FALSE;
 +    int              nparam_defA, nparam_defB;
  
      nparam_defA = nparam_defB = 0;
  
      ftype = ifunc_index(d, 1);
      nral  = NRAL(ftype);
 -    for (j = 0; j < MAXATOMLIST; j++)
 +    for (int j = 0; j < nral; j++)
      {
          aa[j] = NOTSET;
      }
  
  
      /* Check for double atoms and atoms out of bounds */
 -    for (i = 0; (i < nral); i++)
 +    for (int i = 0; (i < nral); i++)
      {
          if (aa[i] < 1 || aa[i] > at->nr)
          {
                      aa[i], dir2str(d), at->nr, dir2str(d), dir2str(d));
              warning_error_and_exit(wi, message, FARGS);
          }
 -        for (j = i+1; (j < nral); j++)
 +        for (int j = i+1; (j < nral); j++)
          {
              GMX_ASSERT(j < MAXATOMLIST + 1, "Values from nral=NRAL() will satisfy this, we assert to keep gcc 4 happy");
              if (aa[i] == aa[j])
      }
  
      /* default force parameters  */
 -    for (j = 0; (j < MAXATOMLIST); j++)
 -    {
 -        param.a[j] = aa[j]-1;
 -    }
 -    for (j = 0; (j < MAXFORCEPARAM); j++)
 +    std::vector<int>  atoms;
 +    for (int j = 0; (j < nral); j++)
      {
 -        param.c[j] = 0.0;
 +        atoms.emplace_back(aa[j]-1);
      }
 +    /* need to have an empty but initialized param array for some reason */
 +    std::array<real, MAXFORCEPARAM> forceParam = {0.0};
  
      /* Get force params for normal and free energy perturbation
       * studies, as determined by types!
       */
 +    InteractionOfType param(atoms, forceParam, "");
  
 +    std::vector<InteractionOfType>::iterator foundAParameter = bondtype[ftype].interactionTypes.end();
 +    std::vector<InteractionOfType>::iterator foundBParameter = bondtype[ftype].interactionTypes.end();
      if (bBonded)
      {
 -        bFoundA = default_params(ftype, bondtype, at, atype, &param, FALSE, &param_defA, &nparam_defA);
 -        if (bFoundA)
 +        foundAParameter = defaultInteractionsOfType(ftype,
 +                                                    bondtype,
 +                                                    at,
 +                                                    atypes,
 +                                                    param,
 +                                                    FALSE,
 +                                                    &nparam_defA);
 +        if (foundAParameter != bondtype[ftype].interactionTypes.end())
          {
              /* Copy the A-state and B-state default parameters. */
              GMX_ASSERT(NRFPA(ftype)+NRFPB(ftype) <= MAXFORCEPARAM, "Bonded interactions may have at most 12 parameters");
 -            for (j = 0; (j < NRFPA(ftype)+NRFPB(ftype)); j++)
 +            gmx::ArrayRef<const real> defaultParam = foundAParameter->forceParam();
 +            for (int j = 0; (j < NRFPA(ftype)+NRFPB(ftype)); j++)
              {
 -                param.c[j] = param_defA->c[j];
 +                param.setForceParameter(j, defaultParam[j]);
              }
 +            bFoundA = true;
          }
 -        bFoundB = default_params(ftype, bondtype, at, atype, &param, TRUE, &param_defB, &nparam_defB);
 -        if (bFoundB)
 +        else if (NRFPA(ftype) == 0)
 +        {
 +            bFoundA = true;
 +        }
 +        foundBParameter = defaultInteractionsOfType(ftype,
 +                                                    bondtype,
 +                                                    at,
 +                                                    atypes,
 +                                                    param,
 +                                                    TRUE,
 +                                                    &nparam_defB);
 +        if (foundBParameter != bondtype[ftype].interactionTypes.end())
          {
              /* Copy only the B-state default parameters */
 -            for (j = NRFPA(ftype); (j < NRFP(ftype)); j++)
 +            gmx::ArrayRef<const real> defaultParam = foundBParameter->forceParam();
 +            for (int j = NRFPA(ftype); (j < NRFP(ftype)); j++)
              {
 -                param.c[j] = param_defB->c[j];
 +                param.setForceParameter(j, defaultParam[j]);
              }
 +            bFoundB = true;
 +        }
 +        else if (NRFPB(ftype) == 0)
 +        {
 +            bFoundB = true;
          }
      }
      else if (ftype == F_LJ14)
      }
      else if (ftype == F_LJC14_Q)
      {
 -        param.c[0] = fudgeQQ;
          /* Fill in the A-state charges as default parameters */
 -        param.c[1] = at->atom[param.a[0]].q;
 -        param.c[2] = at->atom[param.a[1]].q;
 +        param.setForceParameter(0, fudgeQQ);
 +        param.setForceParameter(1, at->atom[param.ai()].q);
 +        param.setForceParameter(2, at->atom[param.aj()].q);
          /* The default LJ parameters are the standard 1-4 parameters */
          bFoundA = default_nb_params(F_LJ14, bondtype, at, &param, 3, FALSE, bGenPairs);
          bFoundB = TRUE;
          if ((nread == NRFPA(ftype)) && (NRFPB(ftype) != 0))
          {
              /* We only have to issue a warning if these atoms are perturbed! */
 -            bPert = FALSE;
 -            for (j = 0; (j < nral); j++)
 +            bool                     bPert      = false;
 +            gmx::ArrayRef<const int> paramAtoms = param.atoms();
 +            for (int j = 0; (j < nral); j++)
              {
 -                bPert = bPert || PERTURBED(at->atom[param.a[j]]);
 +                bPert = bPert || PERTURBED(at->atom[paramAtoms[j]]);
              }
  
              if (bPert && *bWarn_copy_A_B)
              /* The B-state parameters correspond to the first nrfpB
               * A-state parameters.
               */
 -            for (j = 0; (j < NRFPB(ftype)); j++)
 +            for (int j = 0; (j < NRFPB(ftype)); j++)
              {
                  cc[nread++] = cc[j];
              }
              warning_error_and_exit(wi, message, FARGS);
          }
  
 -        for (j = 0; (j < nread); j++)
 +        for (int j = 0; (j < nread); j++)
          {
 -            param.c[j] = cc[j];
 +            param.setForceParameter(j, cc[j]);
          }
 -
          /* Check whether we have to use the defaults */
          if (nread == NRFP(ftype))
          {
          /* When we have multiple terms it would be very dangerous to allow perturbations to a different atom type! */
          if (ftype == F_PDIHS)
          {
 -            if ((nparam_defA != nparam_defB) || ((nparam_defA > 1 || nparam_defB > 1) && (param_defA != param_defB)))
 +            if ((nparam_defA != nparam_defB) || ((nparam_defA > 1 || nparam_defB > 1) && (foundAParameter != foundBParameter)))
              {
                  auto message =
                      gmx::formatString("Cannot automatically perturb a torsion with multiple terms to different form.\n"
              {
                  if (interaction_function[ftype].flags & IF_VSITE)
                  {
 -                    /* set them to NOTSET, will be calculated later */
 -                    for (j = 0; (j < MAXFORCEPARAM); j++)
 +                    for (int j = 0; j < MAXFORCEPARAM; j++)
                      {
 -                        param.c[j] = NOTSET;
 +                        param.setForceParameter(j, NOTSET);
                      }
 -
                      if (bSwapParity)
                      {
 -                        param.c1() = -1; /* flag to swap parity of vsite construction */
 +                        /* flag to swap parity of vsi  te construction */
 +                        param.setForceParameter(1, -1);
                      }
                  }
                  else
                      switch (ftype)
                      {
                          case F_VSITE3FAD:
 -                            param.c0() = 360 - param.c0();
 +                            param.setForceParameter(0, 360 - param.c0());
                              break;
                          case F_VSITE3OUT:
 -                            param.c2() = -param.c2();
 +                            param.setForceParameter(2, -param.c2());
                              break;
                      }
                  }
              if (!bFoundB)
              {
                  /* We only have to issue a warning if these atoms are perturbed! */
 -                bPert = FALSE;
 -                for (j = 0; (j < nral); j++)
 +                bool                     bPert      = false;
 +                gmx::ArrayRef<const int> paramAtoms = param.atoms();
 +                for (int j = 0; (j < nral); j++)
                  {
 -                    bPert = bPert || PERTURBED(at->atom[param.a[j]]);
 +                    bPert = bPert || PERTURBED(at->atom[paramAtoms[j]]);
                  }
  
                  if (bPert)
          }
      }
  
 +    gmx::ArrayRef<const real> paramValue = param.forceParam();
      if ((ftype == F_PDIHS || ftype == F_ANGRES || ftype == F_ANGRESZ)
 -        && param.c[5] != param.c[2])
 +        && paramValue[5] != paramValue[2])
      {
          auto message = gmx::formatString("%s multiplicity can not be perturbed %f!=%f",
                                           interaction_function[ftype].longname,
 -                                         param.c[2], param.c[5]);
 +                                         paramValue[2], paramValue[5]);
          warning_error_and_exit(wi, message, FARGS);
      }
  
 -    if (IS_TABULATED(ftype) && param.c[2] != param.c[0])
 +    if (IS_TABULATED(ftype) && param.c0() != param.c2())
      {
          auto message = gmx::formatString("%s table number can not be perturbed %d!=%d",
                                           interaction_function[ftype].longname,
 -                                         gmx::roundToInt(param.c[0]), gmx::roundToInt(param.c[2]));
 +                                         gmx::roundToInt(param.c0()), gmx::roundToInt(param.c0()));
          warning_error_and_exit(wi, message, FARGS);
      }
  
      /* Dont add R-B dihedrals where all parameters are zero (no interaction) */
      if (ftype == F_RBDIHS)
      {
 -        nr = 0;
 -        for (i = 0; i < NRFP(ftype); i++)
 +
 +        int nr = 0;
 +        for (int i = 0; i < NRFP(ftype); i++)
          {
 -            if (param.c[i] != 0)
 +            if (paramValue[i] != 0.0)
              {
                  nr++;
              }
      }
  
      /* Put the values in the appropriate arrays */
 -    add_param_to_list (&bond[ftype], &param);
 +    add_param_to_list (&bond[ftype], param);
  
      /* Push additional torsions from FF for ftype==9 if we have them.
       * We have already checked that the A/B states do not differ in this case,
       */
      if (bDef && ftype == F_PDIHS)
      {
 -        for (i = 1; i < nparam_defA; i++)
 +        for (int i = 1; i < nparam_defA; i++)
          {
              /* Advance pointer! */
 -            param_defA += 2;
 -            for (j = 0; (j < NRFPA(ftype)+NRFPB(ftype)); j++)
 +            foundAParameter += 2;
 +            gmx::ArrayRef<const real> forceParam = foundAParameter->forceParam();
 +            for (int j = 0; j  < (NRFPA(ftype)+NRFPB(ftype)); j++)
              {
 -                param.c[j] = param_defA->c[j];
 +                param.setForceParameter(j, forceParam[j]);
              }
              /* And push the next term for this torsion */
 -            add_param_to_list (&bond[ftype], &param);
 +            add_param_to_list (&bond[ftype], param);
          }
      }
  }
  
 -void push_cmap(directive d, t_params bondtype[], t_params bond[],
 -               t_atoms *at, gpp_atomtype_t atype, char *line,
 -               warninp_t wi)
 +void push_cmap(Directive d, gmx::ArrayRef<InteractionsOfType> bondtype,
 +               gmx::ArrayRef<InteractionsOfType> bond,
 +               t_atoms *at, PreprocessingAtomTypes *atypes, char *line,
 +               warninp *wi)
  {
      const char *aaformat[MAXATOMLIST+1] =
      {
          "%d%d%d%d%d%d%d"
      };
  
 -    int      i, j, ftype, nral, nread, ncmap_params;
 -    int      cmap_type;
 -    int      aa[MAXATOMLIST+1];
 -    bool     bFound;
 -    t_param  param;
 +    int          ftype, nral, nread, ncmap_params;
 +    int          cmap_type;
 +    int          aa[MAXATOMLIST+1];
 +    bool         bFound;
  
      ftype        = ifunc_index(d, 1);
      nral         = NRAL(ftype);
      }
  
      /* Check for double atoms and atoms out of bounds */
 -    for (i = 0; i < nral; i++)
 +    for (int i = 0; i < nral; i++)
      {
          if (aa[i] < 1 || aa[i] > at->nr)
          {
              warning_error_and_exit(wi, message, FARGS);
          }
  
 -        for (j = i+1; (j < nral); j++)
 +        for (int j = i+1; (j < nral); j++)
          {
              if (aa[i] == aa[j])
              {
      }
  
      /* default force parameters  */
 -    for (j = 0; (j < MAXATOMLIST); j++)
 +    std::vector<int> atoms;
 +    for (int j = 0; (j < nral); j++)
      {
 -        param.a[j] = aa[j]-1;
 +        atoms.emplace_back(aa[j]-1);
      }
 -    for (j = 0; (j < MAXFORCEPARAM); j++)
 -    {
 -        param.c[j] = 0.0;
 -    }
 -
 +    std::array<real, MAXFORCEPARAM>       forceParam = {0.0};
 +    InteractionOfType                     param(atoms, forceParam, "");
      /* Get the cmap type for this cmap angle */
 -    bFound = default_cmap_params(bondtype, at, atype, &param, FALSE, &cmap_type, &ncmap_params, wi);
 +    bFound = default_cmap_params(bondtype, at, atypes, &param, FALSE, &cmap_type, &ncmap_params, wi);
  
      /* We want exactly one parameter (the cmap type in state A (currently no state B) back */
      if (bFound && ncmap_params == 1)
      {
          /* Put the values in the appropriate arrays */
 -        param.c[0] = cmap_type;
 -        add_param_to_list(&bond[ftype], &param);
 +        param.setForceParameter(0, cmap_type);
 +        add_param_to_list(&bond[ftype], param);
      }
      else
      {
          /* This is essentially the same check as in default_cmap_params() done one more time */
          auto message = gmx::formatString("Unable to assign a cmap type to torsion %d %d %d %d and %d\n",
 -                                         param.a[0]+1, param.a[1]+1, param.a[2]+1, param.a[3]+1, param.a[4]+1);
 +                                         param.ai()+1, param.aj()+1, param.ak()+1, param.al()+1, param.am()+1);
          warning_error_and_exit(wi, message, FARGS);
      }
  }
  
  
  
 -void push_vsitesn(directive d, t_params bond[],
 +void push_vsitesn(Directive d, gmx::ArrayRef<InteractionsOfType> bond,
                    t_atoms *at, char *line,
 -                  warninp_t wi)
 +                  warninp *wi)
  {
 -    char   *ptr;
 -    int     type, ftype, j, n, ret, nj, a;
 -    int    *atc    = nullptr;
 -    double *weight = nullptr, weight_tot;
 -    t_param param;
 -
 -    /* default force parameters  */
 -    for (j = 0; (j < MAXATOMLIST); j++)
 -    {
 -        param.a[j] = NOTSET;
 -    }
 -    for (j = 0; (j < MAXFORCEPARAM); j++)
 -    {
 -        param.c[j] = 0.0;
 -    }
 +    char                           *ptr;
 +    int                             type, ftype, n, ret, nj, a;
 +    int                            *atc    = nullptr;
 +    double                         *weight = nullptr, weight_tot;
  
 +    std::array<real, MAXFORCEPARAM> forceParam = {0.0};
      ptr  = line;
      ret  = sscanf(ptr, "%d%n", &a, &n);
      ptr += n;
          warning_error_and_exit(wi, message, FARGS);
      }
  
 -    param.a[0] = a - 1;
 -
      sscanf(ptr, "%d%n", &type, &n);
      ptr  += n;
      ftype = ifunc_index(d, type);
 +    int firstAtom = a - 1;
  
      weight_tot = 0;
      nj         = 0;
          warning_error_and_exit(wi, "The total mass of the construting atoms is zero", FARGS);
      }
  
 -    for (j = 0; j < nj; j++)
 +    for (int j = 0; j < nj; j++)
      {
 -        param.a[1] = atc[j];
 -        param.c[0] = nj;
 -        param.c[1] = weight[j]/weight_tot;
 +        std::vector<int>  atoms = {firstAtom, atc[j]};
 +        forceParam[0] = nj;
 +        forceParam[1] = weight[j]/weight_tot;
          /* Put the values in the appropriate arrays */
 -        add_param_to_list (&bond[ftype], &param);
 +        add_param_to_list (&bond[ftype], InteractionOfType(atoms, forceParam));
      }
  
      sfree(atc);
      sfree(weight);
  }
  
 -void push_mol(int nrmols, t_molinfo mols[], char *pline, int *whichmol,
 +void push_mol(gmx::ArrayRef<MoleculeInformation> mols, char *pline, int *whichmol,
                int *nrcopies,
 -              warninp_t wi)
 +              warninp *wi)
  {
      char type[STRLEN];
  
      int nrci    = 0;
      int matchci = -1;
      int matchcs = -1;
 -    for (int i = 0; i < nrmols; i++)
 +    int i       = 0;
 +    for (const auto &mol : mols)
      {
 -        if (strcmp(type, *(mols[i].name)) == 0)
 +        if (strcmp(type, *(mol.name)) == 0)
          {
              nrcs++;
              matchcs = i;
          }
 -        if (gmx_strcasecmp(type, *(mols[i].name)) == 0)
 +        if (gmx_strcasecmp(type, *(mol.name)) == 0)
          {
              nrci++;
              matchci = i;
          }
 +        i++;
      }
  
      if (nrcs == 1)
      }
  }
  
 -void push_excl(char *line, gmx::ExclusionBlocks *b2, warninp_t wi)
 +void push_excl(char *line, gmx::ArrayRef<gmx::ExclusionBlock> b2, warninp *wi)
  {
      int  i, j;
      int  n;
          return;
      }
  
 -    if ((1 <= i) && (i <= b2->nr))
 +    if ((1 <= i) && (i <= b2.ssize()))
      {
          i--;
      }
          n = sscanf(line, format, &j);
          if (n == 1)
          {
 -            if ((1 <= j) && (j <= b2->nr))
 +            if ((1 <= j) && (j <= b2.ssize()))
              {
                  j--;
 -                srenew(b2->a[i], ++(b2->nra[i]));
 -                b2->a[i][b2->nra[i]-1] = j;
 +                b2[i].atomNumber.push_back(j);
                  /* also add the reverse exclusion! */
 -                srenew(b2->a[j], ++(b2->nra[j]));
 -                b2->a[j][b2->nra[j]-1] = i;
 +                b2[j].atomNumber.push_back(i);
                  strcat(base, "%*d");
              }
              else
              {
 -                auto message = gmx::formatString("Invalid Atomnr j: %d, b2->nr: %d\n", j, b2->nr);
 +                auto message = gmx::formatString("Invalid Atomnr j: %d, b2->nr: %zu\n", j, b2.size());
                  warning_error_and_exit(wi, message, FARGS);
              }
          }
      while (n == 1);
  }
  
 -int add_atomtype_decoupled(t_symtab *symtab, gpp_atomtype_t at,
 +int add_atomtype_decoupled(t_symtab *symtab, PreprocessingAtomTypes *at,
                             t_nbparam ***nbparam, t_nbparam ***pair)
  {
 -    t_atom  atom;
 -    t_param param;
 -    int     i, nr;
 +    t_atom      atom;
 +    int         nr;
  
      /* Define an atom type with all parameters set to zero (no interactions) */
      atom.q = 0.0;
       * this should be changed automatically later when required.
       */
      atom.ptype = eptAtom;
 -    for (i = 0; (i < MAXFORCEPARAM); i++)
 -    {
 -        param.c[i] = 0.0;
 -    }
  
 -    nr = add_atomtype(at, symtab, &atom, "decoupled", &param, -1, 0);
 +    std::array<real, MAXFORCEPARAM> forceParam = {0.0};
 +    nr = at->addType(symtab, atom, "decoupled", InteractionOfType({}, forceParam, ""), -1, 0);
  
      /* Add space in the non-bonded parameters matrix */
      realloc_nb_params(at, nbparam, pair);
      return nr;
  }
  
 -static void convert_pairs_to_pairsQ(t_params *plist,
 +static void convert_pairs_to_pairsQ(gmx::ArrayRef<InteractionsOfType> interactions,
                                      real fudgeQQ, t_atoms *atoms)
  {
 -    t_param *paramp1, *paramp2, *paramnew;
 -    int      i, j, p1nr, p2nr, p2newnr;
 -
      /* Add the pair list to the pairQ list */
 -    p1nr    = plist[F_LJ14].nr;
 -    p2nr    = plist[F_LJC14_Q].nr;
 -    p2newnr = p1nr + p2nr;
 -    snew(paramnew, p2newnr);
 +    std::vector<InteractionOfType>         paramnew;
  
 -    paramp1             = plist[F_LJ14].param;
 -    paramp2             = plist[F_LJC14_Q].param;
 +    gmx::ArrayRef<const InteractionOfType> paramp1 = interactions[F_LJ14].interactionTypes;
 +    gmx::ArrayRef<const InteractionOfType> paramp2 = interactions[F_LJC14_Q].interactionTypes;
  
      /* Fill in the new F_LJC14_Q array with the old one. NOTE:
         it may be possible to just ADD the converted F_LJ14 array
         a new sized memory structure, better just to deep copy it all.
       */
  
 -    for (i = 0; i < p2nr; i++)
 -    {
 -        /* Copy over parameters */
 -        for (j = 0; j < 5; j++) /* entries are 0-4 for F_LJC14_Q */
 -        {
 -            paramnew[i].c[j] = paramp2[i].c[j];
 -        }
  
 -        /* copy over atoms */
 -        for (j = 0; j < 2; j++)
 -        {
 -            paramnew[i].a[j] = paramp2[i].a[j];
 -        }
 +    for (const auto &param : paramp2)
 +    {
 +        paramnew.emplace_back(param);
      }
  
 -    for (i = p2nr; i < p2newnr; i++)
 +    for (const auto &param : paramp1)
      {
 -        j             = i-p2nr;
 -
 -        /* Copy over parameters */
 -        paramnew[i].c[0] = fudgeQQ;
 -        paramnew[i].c[1] = atoms->atom[paramp1[j].a[0]].q;
 -        paramnew[i].c[2] = atoms->atom[paramp1[j].a[1]].q;
 -        paramnew[i].c[3] = paramp1[j].c[0];
 -        paramnew[i].c[4] = paramp1[j].c[1];
 -
 -        /* copy over atoms */
 -        paramnew[i].a[0] = paramp1[j].a[0];
 -        paramnew[i].a[1] = paramp1[j].a[1];
 +        std::vector<real> forceParam = {
 +            fudgeQQ, atoms->atom[param.ai()].q, atoms->atom[param.aj()].q,
 +            param.c0(), param.c1()
 +        };
 +        paramnew.emplace_back(InteractionOfType(param.atoms(), forceParam, ""));
      }
  
 -    /* free the old pairlists */
 -    sfree(plist[F_LJC14_Q].param);
 -    sfree(plist[F_LJ14].param);
 -
      /* now assign the new data to the F_LJC14_Q structure */
 -    plist[F_LJC14_Q].param   = paramnew;
 -    plist[F_LJC14_Q].nr      = p2newnr;
 +    interactions[F_LJC14_Q].interactionTypes   = paramnew;
  
      /* Empty the LJ14 pairlist */
 -    plist[F_LJ14].nr    = 0;
 -    plist[F_LJ14].param = nullptr;
 +    interactions[F_LJ14].interactionTypes.clear();
  }
  
 -static void generate_LJCpairsNB(t_molinfo *mol, int nb_funct, t_params *nbp, warninp_t wi)
 +static void generate_LJCpairsNB(MoleculeInformation *mol, int nb_funct, InteractionsOfType *nbp, warninp *wi)
  {
 -    int       n, ntype, i, j, k;
 -    t_atom   *atom;
 -    t_blocka *excl;
 -    bool      bExcl;
 -    t_param   param;
 +    int           n, ntype;
 +    t_atom       *atom;
 +    t_blocka     *excl;
 +    bool          bExcl;
  
      n    = mol->atoms.nr;
      atom = mol->atoms.atom;
  
 -    ntype = static_cast<int>(std::sqrt(static_cast<double>(nbp->nr)));
 -    GMX_ASSERT(ntype * ntype == nbp->nr, "Number of pairs of generated non-bonded parameters should be a perfect square");
 -
 -    for (i = 0; i < MAXATOMLIST; i++)
 -    {
 -        param.a[i] = NOTSET;
 -    }
 -    for (i = 0; i < MAXFORCEPARAM; i++)
 -    {
 -        param.c[i] = NOTSET;
 -    }
 +    ntype = static_cast<int>(std::sqrt(static_cast<double>(nbp->size())));
 +    GMX_ASSERT(ntype * ntype == gmx::ssize(*nbp), "Number of pairs of generated non-bonded parameters should be a perfect square");
  
      /* Add a pair interaction for all non-excluded atom pairs */
      excl = &mol->excls;
 -    for (i = 0; i < n; i++)
 +    for (int i = 0; i < n; i++)
      {
 -        for (j = i+1; j < n; j++)
 +        for (int j = i+1; j < n; j++)
          {
              bExcl = FALSE;
 -            for (k = excl->index[i]; k < excl->index[i+1]; k++)
 +            for (int k = excl->index[i]; k < excl->index[i+1]; k++)
              {
                  if (excl->a[k] == j)
                  {
                              "for Van der Waals type Lennard-Jones");
                      warning_error_and_exit(wi, message, FARGS);
                  }
 -                param.a[0] = i;
 -                param.a[1] = j;
 -                param.c[0] = atom[i].q;
 -                param.c[1] = atom[j].q;
 -                param.c[2] = nbp->param[ntype*atom[i].type+atom[j].type].c[0];
 -                param.c[3] = nbp->param[ntype*atom[i].type+atom[j].type].c[1];
 -                add_param_to_list(&mol->plist[F_LJC_PAIRS_NB], &param);
 +                std::vector<int>  atoms      = {i, j};
 +                std::vector<real> forceParam = {
 +                    atom[i].q,
 +                    atom[j].q,
 +                    nbp->interactionTypes[ntype*atom[i].type+atom[j].type].c0(),
 +                    nbp->interactionTypes[ntype*atom[i].type+atom[j].type].c1()
 +                };
 +                add_param_to_list(&mol->interactions[F_LJC_PAIRS_NB], InteractionOfType(atoms, forceParam));
              }
          }
      }
@@@ -2707,7 -2624,7 +2727,7 @@@ static void set_excl_all(t_blocka *excl
  
  static void decouple_atoms(t_atoms *atoms, int atomtype_decouple,
                             int couple_lam0, int couple_lam1,
 -                           const char *mol_name, warninp_t wi)
 +                           const char *mol_name, warninp *wi)
  {
      int  i;
  
      }
  }
  
 -void convert_moltype_couple(t_molinfo *mol, int atomtype_decouple, real fudgeQQ,
 +void convert_moltype_couple(MoleculeInformation *mol, int atomtype_decouple, real fudgeQQ,
                              int couple_lam0, int couple_lam1,
 -                            bool bCoupleIntra, int nb_funct, t_params *nbp,
 -                            warninp_t wi)
 +                            bool bCoupleIntra, int nb_funct, InteractionsOfType *nbp,
 +                            warninp *wi)
  {
 -    convert_pairs_to_pairsQ(mol->plist, fudgeQQ, &mol->atoms);
 +    convert_pairs_to_pairsQ(mol->interactions, fudgeQQ, &mol->atoms);
      if (!bCoupleIntra)
      {
          generate_LJCpairsNB(mol, nb_funct, nbp, wi);
index 3ad73d5dc1aa76a8be397c46ae93d4d58f287a24,0000000000000000000000000000000000000000..d10dd20f60c52cb3d27ff479539c9bb058fc14df
mode 100644,000000..100644
--- /dev/null
@@@ -1,573 -1,0 +1,573 @@@
-         gmx_fatal(FARGS, "You are using %d OpenMP threads, which is larger than GMX_OPENMP_MAX_THREADS (%d). Decrease the number of OpenMP threads or rebuild GROMACS with a larger value for GMX_OPENMP_MAX_THREADS.",
 +/*
 + * This file is part of the GROMACS molecular simulation package.
 + *
 + * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
 + * Copyright (c) 2001-2004, The GROMACS development team.
 + * Copyright (c) 2013,2014,2015,2016,2017,2018,2019, by the GROMACS development team, led by
 + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
 + * and including many others, as listed in the AUTHORS file in the
 + * top-level source directory and at http://www.gromacs.org.
 + *
 + * GROMACS is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public License
 + * as published by the Free Software Foundation; either version 2.1
 + * of the License, or (at your option) any later version.
 + *
 + * GROMACS is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * Lesser General Public License for more details.
 + *
 + * You should have received a copy of the GNU Lesser General Public
 + * License along with GROMACS; if not, see
 + * http://www.gnu.org/licenses, or write to the Free Software Foundation,
 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
 + *
 + * If you want to redistribute modifications to GROMACS, please
 + * consider that scientific software is very special. Version
 + * control is crucial - bugs must be traceable. We will be happy to
 + * consider code for inclusion in the official distribution, but
 + * derived work must not be called official GROMACS. Details are found
 + * in the README & COPYING files - if they are missing, get the
 + * official version at http://www.gromacs.org.
 + *
 + * To help us fund GROMACS development, we humbly ask that you cite
 + * the research papers on the package. Check out http://www.gromacs.org.
 + */
 +/*! \internal \file
 + * \brief This file defines functions for managing threading of listed
 + * interactions.
 + *
 + * \author Mark Abraham <mark.j.abraham@gmail.com>
 + * \ingroup module_listed_forces
 + */
 +#include "gmxpre.h"
 +
 +#include "manage_threading.h"
 +
 +#include "config.h"
 +
 +#include <cassert>
 +#include <cinttypes>
 +#include <climits>
 +#include <cstdlib>
 +
 +#include <algorithm>
 +#include <string>
 +
 +#include "gromacs/listed_forces/gpubonded.h"
 +#include "gromacs/mdlib/gmx_omp_nthreads.h"
 +#include "gromacs/pbcutil/ishift.h"
 +#include "gromacs/topology/ifunc.h"
 +#include "gromacs/utility/exceptions.h"
 +#include "gromacs/utility/fatalerror.h"
 +#include "gromacs/utility/gmxassert.h"
 +#include "gromacs/utility/smalloc.h"
 +
 +#include "listed_internal.h"
 +#include "utilities.h"
 +
 +/*! \brief struct for passing all data required for a function type */
 +typedef struct {
 +    const t_ilist *il;    /**< pointer to t_ilist entry corresponding to ftype */
 +    int            ftype; /**< the function type index */
 +    int            nat;   /**< nr of atoms involved in a single ftype interaction */
 +} ilist_data_t;
 +
 +/*! \brief Divides listed interactions over threads
 + *
 + * This routine attempts to divide all interactions of the numType bondeds
 + * types stored in ild over the threads such that each thread has roughly
 + * equal load and different threads avoid touching the same atoms as much
 + * as possible.
 + */
 +static void divide_bondeds_by_locality(bonded_threading_t *bt,
 +                                       int                 numType,
 +                                       const ilist_data_t *ild)
 +{
 +    int nat_tot, nat_sum;
 +    int ind[F_NRE];    /* index into the ild[].il->iatoms */
 +    int at_ind[F_NRE]; /* index of the first atom of the interaction at ind */
 +    int f, t;
 +
 +    assert(numType <= F_NRE);
 +
 +    nat_tot = 0;
 +    for (f = 0; f < numType; f++)
 +    {
 +        /* Sum #bondeds*#atoms_per_bond over all bonded types */
 +        nat_tot  += ild[f].il->nr/(ild[f].nat + 1)*ild[f].nat;
 +        /* The start bound for thread 0 is 0 for all interactions */
 +        ind[f]    = 0;
 +        /* Initialize the next atom index array */
 +        assert(ild[f].il->nr > 0);
 +        at_ind[f] = ild[f].il->iatoms[1];
 +    }
 +
 +    nat_sum = 0;
 +    /* Loop over the end bounds of the nthreads threads to determine
 +     * which interactions threads 0 to nthreads shall calculate.
 +     *
 +     * NOTE: The cost of these combined loops is #interactions*numType.
 +     * This code is running single threaded (difficult to parallelize
 +     * over threads). So the relative cost of this function increases
 +     * linearly with the number of threads. Since the inner-most loop
 +     * is cheap and this is done only at DD repartitioning, the cost should
 +     * be negligble. At high thread count many other parts of the code
 +     * scale the same way, so it's (currently) not worth improving this.
 +     */
 +    for (t = 1; t <= bt->nthreads; t++)
 +    {
 +        int nat_thread;
 +
 +        /* Here we assume that the computational cost is proportional
 +         * to the number of atoms in the interaction. This is a rough
 +         * measure, but roughly correct. Usually there are very few
 +         * interactions anyhow and there are distributed relatively
 +         * uniformly. Proper and RB dihedrals are often distributed
 +         * non-uniformly, but their cost is roughly equal.
 +         */
 +        nat_thread = (nat_tot*t)/bt->nthreads;
 +
 +        while (nat_sum < nat_thread)
 +        {
 +            /* To divide bonds based on atom order, we compare
 +             * the index of the first atom in the bonded interaction.
 +             * This works well, since the domain decomposition generates
 +             * bondeds in order of the atoms by looking up interactions
 +             * which are linked to the first atom in each interaction.
 +             * It usually also works well without DD, since than the atoms
 +             * in bonded interactions are usually in increasing order.
 +             * If they are not assigned in increasing order, the balancing
 +             * is still good, but the memory access and reduction cost will
 +             * be higher.
 +             */
 +            int f_min;
 +
 +            /* Find out which of the types has the lowest atom index */
 +            f_min = 0;
 +            for (f = 1; f < numType; f++)
 +            {
 +                if (at_ind[f] < at_ind[f_min])
 +                {
 +                    f_min = f;
 +                }
 +            }
 +            assert(f_min >= 0 && f_min < numType);
 +
 +            /* Assign the interaction with the lowest atom index (of type
 +             * index f_min) to thread t-1 by increasing ind.
 +             */
 +            ind[f_min] += ild[f_min].nat + 1;
 +            nat_sum    += ild[f_min].nat;
 +
 +            /* Update the first unassigned atom index for this type */
 +            if (ind[f_min] < ild[f_min].il->nr)
 +            {
 +                at_ind[f_min] = ild[f_min].il->iatoms[ind[f_min] + 1];
 +            }
 +            else
 +            {
 +                /* We have assigned all interactions of this type.
 +                 * Setting at_ind to INT_MAX ensures this type will not be
 +                 * chosen in the for loop above during next iterations.
 +                 */
 +                at_ind[f_min] = INT_MAX;
 +            }
 +        }
 +
 +        /* Store the bonded end boundaries (at index t) for thread t-1 */
 +        for (f = 0; f < numType; f++)
 +        {
 +            bt->workDivision.setBound(ild[f].ftype, t, ind[f]);
 +        }
 +    }
 +
 +    for (f = 0; f < numType; f++)
 +    {
 +        assert(ind[f] == ild[f].il->nr);
 +    }
 +}
 +
 +//! Return whether function type \p ftype in \p idef has perturbed interactions
 +static bool ftypeHasPerturbedEntries(const t_idef  &idef,
 +                                     int            ftype)
 +{
 +    GMX_ASSERT(idef.ilsort == ilsortNO_FE || idef.ilsort == ilsortFE_SORTED,
 +               "Perturbed interations should be sorted here");
 +
 +    const t_ilist &ilist = idef.il[ftype];
 +
 +    return (idef.ilsort != ilsortNO_FE && ilist.nr_nonperturbed != ilist.nr);
 +}
 +
 +//! Divides bonded interactions over threads and GPU
 +static void divide_bondeds_over_threads(bonded_threading_t *bt,
 +                                        bool                useGpuForBondeds,
 +                                        const t_idef       &idef)
 +{
 +    ilist_data_t ild[F_NRE];
 +
 +    assert(bt->nthreads > 0);
 +
 +    bt->haveBondeds      = false;
 +    int    numType       = 0;
 +    size_t fTypeGpuIndex = 0;
 +    for (int fType = 0; fType < F_NRE; fType++)
 +    {
 +        if (!ftype_is_bonded_potential(fType))
 +        {
 +            continue;
 +        }
 +
 +        const t_ilist &il                     = idef.il[fType];
 +        int            nrToAssignToCpuThreads = il.nr;
 +
 +        if (useGpuForBondeds &&
 +            fTypeGpuIndex < gmx::fTypesOnGpu.size() &&
 +            gmx::fTypesOnGpu[fTypeGpuIndex] == fType)
 +        {
 +            fTypeGpuIndex++;
 +
 +            /* Perturbation is not implemented in the GPU bonded kernels.
 +             * But instead of doing all on the CPU, we could do only
 +             * the actually perturbed interactions on the CPU.
 +             */
 +            if (!ftypeHasPerturbedEntries(idef, fType))
 +            {
 +                /* We will assign this interaction type to the GPU */
 +                nrToAssignToCpuThreads = 0;
 +            }
 +        }
 +
 +        if (nrToAssignToCpuThreads > 0)
 +        {
 +            bt->haveBondeds = true;
 +        }
 +
 +        if (nrToAssignToCpuThreads == 0)
 +        {
 +            /* No interactions, avoid all the integer math below */
 +            for (int t = 0; t <= bt->nthreads; t++)
 +            {
 +                bt->workDivision.setBound(fType, t, 0);
 +            }
 +        }
 +        else if (bt->nthreads <= bt->max_nthread_uniform || fType == F_DISRES)
 +        {
 +            /* On up to 4 threads, load balancing the bonded work
 +             * is more important than minimizing the reduction cost.
 +             */
 +
 +            const int stride = 1 + NRAL(fType);
 +
 +            for (int t = 0; t <= bt->nthreads; t++)
 +            {
 +                /* Divide equally over the threads */
 +                int nr_t = (((nrToAssignToCpuThreads/stride)*t)/bt->nthreads)*stride;
 +
 +                if (fType == F_DISRES)
 +                {
 +                    /* Ensure that distance restraint pairs with the same label
 +                     * end up on the same thread.
 +                     */
 +                    while (nr_t > 0 && nr_t < nrToAssignToCpuThreads &&
 +                           idef.iparams[il.iatoms[nr_t]].disres.label ==
 +                           idef.iparams[il.iatoms[nr_t - stride]].disres.label)
 +                    {
 +                        nr_t += stride;
 +                    }
 +                }
 +
 +                bt->workDivision.setBound(fType, t, nr_t);
 +            }
 +        }
 +        else
 +        {
 +            /* Add this fType to the list to be distributed */
 +            int nat          = NRAL(fType);
 +            ild[numType].ftype = fType;
 +            ild[numType].il    = &il;
 +            ild[numType].nat   = nat;
 +
 +            /* The first index for the thread division is always 0 */
 +            bt->workDivision.setBound(fType, 0, 0);
 +
 +            numType++;
 +        }
 +    }
 +
 +    if (numType > 0)
 +    {
 +        divide_bondeds_by_locality(bt, numType, ild);
 +    }
 +
 +    if (debug)
 +    {
 +        int f;
 +
 +        fprintf(debug, "Division of bondeds over threads:\n");
 +        for (f = 0; f < F_NRE; f++)
 +        {
 +            if (ftype_is_bonded_potential(f) && idef.il[f].nr > 0)
 +            {
 +                int t;
 +
 +                fprintf(debug, "%16s", interaction_function[f].name);
 +                for (t = 0; t < bt->nthreads; t++)
 +                {
 +                    fprintf(debug, " %4d",
 +                            (bt->workDivision.bound(f, t + 1) -
 +                             bt->workDivision.bound(f, t))/
 +                            (1 + NRAL(f)));
 +                }
 +                fprintf(debug, "\n");
 +            }
 +        }
 +    }
 +}
 +
 +//! Construct a reduction mask for which parts (blocks) of the force array are touched on which thread task
 +static void
 +calc_bonded_reduction_mask(int                       natoms,
 +                           f_thread_t               *f_thread,
 +                           const t_idef             &idef,
 +                           int                       thread,
 +                           const bonded_threading_t &bondedThreading)
 +{
 +    static_assert(BITMASK_SIZE == GMX_OPENMP_MAX_THREADS, "For the error message below we assume these two are equal.");
 +
 +    if (bondedThreading.nthreads > BITMASK_SIZE)
 +    {
 +#pragma omp master
++        gmx_fatal(FARGS, "You are using %d OpenMP threads, which is larger than GMX_OPENMP_MAX_THREADS (%d). Decrease the number of OpenMP threads or rebuild GROMACS with a larger value for GMX_OPENMP_MAX_THREADS passed to CMake.",
 +                  bondedThreading.nthreads, GMX_OPENMP_MAX_THREADS);
 +#pragma omp barrier
 +    }
 +    GMX_ASSERT(bondedThreading.nthreads <= BITMASK_SIZE, "We need at least nthreads bits in the mask");
 +
 +    int nblock = (natoms + reduction_block_size - 1) >> reduction_block_bits;
 +
 +    if (nblock > f_thread->block_nalloc)
 +    {
 +        f_thread->block_nalloc = over_alloc_large(nblock);
 +        srenew(f_thread->mask,        f_thread->block_nalloc);
 +        srenew(f_thread->block_index, f_thread->block_nalloc);
 +        // NOTE: It seems f_thread->f does not need to be aligned
 +        sfree_aligned(f_thread->f);
 +        snew_aligned(f_thread->f,     f_thread->block_nalloc*reduction_block_size, 128);
 +    }
 +
 +    gmx_bitmask_t *mask = f_thread->mask;
 +
 +    for (int b = 0; b < nblock; b++)
 +    {
 +        bitmask_clear(&mask[b]);
 +    }
 +
 +    for (int ftype = 0; ftype < F_NRE; ftype++)
 +    {
 +        if (ftype_is_bonded_potential(ftype))
 +        {
 +            int nb = idef.il[ftype].nr;
 +            if (nb > 0)
 +            {
 +                int nat1 = interaction_function[ftype].nratoms + 1;
 +
 +                int nb0 = bondedThreading.workDivision.bound(ftype, thread);
 +                int nb1 = bondedThreading.workDivision.bound(ftype, thread + 1);
 +
 +                for (int i = nb0; i < nb1; i += nat1)
 +                {
 +                    for (int a = 1; a < nat1; a++)
 +                    {
 +                        bitmask_set_bit(&mask[idef.il[ftype].iatoms[i+a] >> reduction_block_bits], thread);
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    /* Make an index of the blocks our thread touches, so we can do fast
 +     * force buffer clearing.
 +     */
 +    f_thread->nblock_used = 0;
 +    for (int b = 0; b < nblock; b++)
 +    {
 +        if (bitmask_is_set(mask[b], thread))
 +        {
 +            f_thread->block_index[f_thread->nblock_used++] = b;
 +        }
 +    }
 +}
 +
 +void setup_bonded_threading(bonded_threading_t *bt,
 +                            int                 numAtoms,
 +                            bool                useGpuForBondeds,
 +                            const t_idef       &idef)
 +{
 +    int                 ctot = 0;
 +
 +    assert(bt->nthreads >= 1);
 +
 +    /* Divide the bonded interaction over the threads */
 +    divide_bondeds_over_threads(bt, useGpuForBondeds, idef);
 +
 +    if (!bt->haveBondeds)
 +    {
 +        /* We don't have bondeds, so there is nothing to reduce */
 +        return;
 +    }
 +
 +    /* Determine to which blocks each thread's bonded force calculation
 +     * contributes. Store this as a mask for each thread.
 +     */
 +#pragma omp parallel for num_threads(bt->nthreads) schedule(static)
 +    for (int t = 0; t < bt->nthreads; t++)
 +    {
 +        try
 +        {
 +            calc_bonded_reduction_mask(numAtoms, bt->f_t[t].get(),
 +                                       idef, t, *bt);
 +        }
 +        GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
 +    }
 +
 +    /* Reduce the masks over the threads and determine which blocks
 +     * we need to reduce over.
 +     */
 +    int nblock_tot = (numAtoms + reduction_block_size - 1) >> reduction_block_bits;
 +    /* Ensure we have sufficient space for all blocks */
 +    if (static_cast<size_t>(nblock_tot) > bt->block_index.size())
 +    {
 +        bt->block_index.resize(nblock_tot);
 +    }
 +    if (static_cast<size_t>(nblock_tot) > bt->mask.size())
 +    {
 +        bt->mask.resize(nblock_tot);
 +    }
 +    bt->nblock_used = 0;
 +    for (int b = 0; b < nblock_tot; b++)
 +    {
 +        gmx_bitmask_t *mask = &bt->mask[b];
 +
 +        /* Generate the union over the threads of the bitmask */
 +        bitmask_clear(mask);
 +        for (int t = 0; t < bt->nthreads; t++)
 +        {
 +            bitmask_union(mask, bt->f_t[t]->mask[b]);
 +        }
 +        if (!bitmask_is_zero(*mask))
 +        {
 +            bt->block_index[bt->nblock_used++] = b;
 +        }
 +
 +        if (debug)
 +        {
 +            int c = 0;
 +            for (int t = 0; t < bt->nthreads; t++)
 +            {
 +                if (bitmask_is_set(*mask, t))
 +                {
 +                    c++;
 +                }
 +            }
 +            ctot += c;
 +
 +            if (gmx_debug_at)
 +            {
 +                fprintf(debug, "block %d flags %s count %d\n",
 +                        b, to_hex_string(*mask).c_str(), c);
 +            }
 +        }
 +    }
 +    if (debug)
 +    {
 +        fprintf(debug, "Number of %d atom blocks to reduce: %d\n",
 +                reduction_block_size, bt->nblock_used);
 +        fprintf(debug, "Reduction density %.2f for touched blocks only %.2f\n",
 +                ctot*reduction_block_size/static_cast<double>(numAtoms),
 +                ctot/static_cast<double>(bt->nblock_used));
 +    }
 +}
 +
 +void tear_down_bonded_threading(bonded_threading_t *bt)
 +{
 +    delete bt;
 +}
 +
 +f_thread_t::f_thread_t(int numEnergyGroups) :
 +    grpp(numEnergyGroups)
 +{
 +    snew(fshift, SHIFTS);
 +}
 +
 +f_thread_t::~f_thread_t()
 +{
 +    sfree(mask);
 +    sfree(fshift);
 +    sfree(block_index);
 +    sfree_aligned(f);
 +}
 +
 +bonded_threading_t::bonded_threading_t(const int numThreads,
 +                                       const int numEnergyGroups) :
 +    nthreads(numThreads),
 +    nblock_used(0),
 +    haveBondeds(false),
 +    workDivision(nthreads),
 +    foreignLambdaWorkDivision(1)
 +{
 +    f_t.resize(numThreads);
 +#pragma omp parallel for num_threads(nthreads) schedule(static)
 +    for (int t = 0; t < nthreads; t++)
 +    {
 +        try
 +        {
 +            /* Note that thread 0 uses the global fshift and energy arrays,
 +             * but to keep the code simple, we initialize all data here.
 +             */
 +            f_t[t] = std::make_unique<f_thread_t>(numEnergyGroups);
 +        }
 +        GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
 +    }
 +}
 +
 +bonded_threading_t *init_bonded_threading(FILE      *fplog,
 +                                          const int  nenergrp)
 +{
 +    /* These thread local data structures are used for bondeds only.
 +     *
 +     * Note that we also use there structures when running single-threaded.
 +     * This is because the bonded force buffer uses type rvec4, whereas
 +     * the normal force buffer is uses type rvec. This leads to a little
 +     * reduction overhead, but the speed gain in the bonded calculations
 +     * of doing transposeScatterIncr/DecrU with aligment 4 instead of 3
 +     * is much larger than the reduction overhead.
 +     */
 +    bonded_threading_t *bt = new bonded_threading_t(gmx_omp_nthreads_get(emntBonded),
 +                                                    nenergrp);
 +
 +    /* The optimal value after which to switch from uniform to localized
 +     * bonded interaction distribution is 3, 4 or 5 depending on the system
 +     * and hardware.
 +     */
 +    const int max_nthread_uniform = 4;
 +    char *    ptr;
 +
 +    if ((ptr = getenv("GMX_BONDED_NTHREAD_UNIFORM")) != nullptr)
 +    {
 +        sscanf(ptr, "%d", &bt->max_nthread_uniform);
 +        if (fplog != nullptr)
 +        {
 +            fprintf(fplog, "\nMax threads for uniform bonded distribution set to %d by env.var.\n",
 +                    bt->max_nthread_uniform);
 +        }
 +    }
 +    else
 +    {
 +        bt->max_nthread_uniform = max_nthread_uniform;
 +    }
 +
 +    return bt;
 +}
index eebf97f2f475b3b25f4119fea5597e222b73f7b4,b4f428a693ad2a91a6a568557412eab087824b93..6eb325684e794c5ad1aaa75e125f169bafc5a5b6
  
  #include <cstring>
  
 -#include "gromacs/compat/make_unique.h"
 +#include <memory>
 +
  #include "gromacs/gmxlib/network.h"
  #include "gromacs/math/vec.h"
 -#include "gromacs/mdlib/mdrun.h"
  #include "gromacs/mdlib/tgroup.h"
 -#include "gromacs/mdtypes/awh-params.h"
 +#include "gromacs/mdtypes/awh_params.h"
  #include "gromacs/mdtypes/commrec.h"
  #include "gromacs/mdtypes/inputrec.h"
  #include "gromacs/mdtypes/md_enums.h"
 -#include "gromacs/mdtypes/pull-params.h"
 +#include "gromacs/mdtypes/pull_params.h"
  #include "gromacs/mdtypes/state.h"
  #include "gromacs/topology/mtop_util.h"
  #include "gromacs/topology/symtab.h"
@@@ -127,32 -127,6 +127,32 @@@ static void bc_strings(const t_commrec 
      sfree(handle);
  }
  
 +static void bc_strings_container(const t_commrec      *cr,
 +                                 t_symtab             *symtab,
 +                                 int                   nr,
 +                                 std::vector<char **> *nm)
 +{
 +    std::vector<int> handle;
 +    if (MASTER(cr))
 +    {
 +        for (int i = 0; (i < nr); i++)
 +        {
 +            handle.emplace_back(lookup_symtab(symtab, (*nm)[i]));
 +        }
 +    }
 +    block_bc(cr, nr);
 +    nblock_abc(cr, nr, &handle);
 +
 +    if (!MASTER(cr))
 +    {
 +        nm->resize(nr);
 +        for (int i = 0; (i < nr); i++)
 +        {
 +            (*nm)[i] = get_symtab_handle(symtab, handle[i]);
 +        }
 +    }
 +}
 +
  static void bc_strings_resinfo(const t_commrec *cr, t_symtab *symtab,
                                 int nr, t_resinfo *resinfo)
  {
@@@ -222,13 -196,15 +222,13 @@@ static void bc_blocka(const t_commrec *
      }
  }
  
 -static void bc_grps(const t_commrec *cr, t_grps grps[])
 +static void bc_grps(const t_commrec *cr, gmx::ArrayRef<AtomGroupIndices> grps)
  {
 -    int i;
 -
 -    for (i = 0; (i < egcNR); i++)
 +    for (auto &group : grps)
      {
 -        block_bc(cr, grps[i].nr);
 -        snew_bc(cr, grps[i].nm_ind, grps[i].nr);
 -        nblock_bc(cr, grps[i].nr, grps[i].nm_ind);
 +        int size = group.size();
 +        block_bc(cr, size);
 +        nblock_abc(cr, size, &group);
      }
  }
  
@@@ -248,17 -224,18 +248,17 @@@ static void bc_atoms(const t_commrec *c
  }
  
  static void bc_groups(const t_commrec *cr, t_symtab *symtab,
 -                      int natoms, gmx_groups_t *groups)
 +                      int natoms, SimulationGroups *groups)
  {
 -    int g, n;
 +    int n;
  
 -    bc_grps(cr, groups->grps);
 -    block_bc(cr, groups->ngrpname);
 -    bc_strings(cr, symtab, groups->ngrpname, &groups->grpname);
 -    for (g = 0; g < egcNR; g++)
 +    bc_grps(cr, groups->groups);
 +    bc_strings_container(cr, symtab, groups->groupNames.size(), &groups->groupNames);
 +    for (auto group : gmx::keysOf(groups->groups))
      {
          if (MASTER(cr))
          {
 -            if (groups->grpnr[g])
 +            if (!groups->groupNumbers[group].empty())
              {
                  n = natoms;
              }
              }
          }
          block_bc(cr, n);
 -        if (n == 0)
 -        {
 -            groups->grpnr[g] = nullptr;
 -        }
 -        else
 +        if (n != 0)
          {
 -            snew_bc(cr, groups->grpnr[g], n);
 -            nblock_bc(cr, n, groups->grpnr[g]);
 +            nblock_abc(cr, n, &groups->groupNumbers[group]);
          }
      }
      if (debug)
@@@ -698,7 -680,7 +698,7 @@@ static void bc_inputrec(const t_commre
          size_t            size;
          block_bc(cr, size);
          nblock_abc(cr, size, &buffer);
 -        gmx::InMemoryDeserializer serializer(buffer);
 +        gmx::InMemoryDeserializer serializer(buffer, false);
          inputrec->params = new gmx::KeyValueTreeObject(
                      gmx::deserializeKeyValueTree(&serializer));
      }
@@@ -836,7 -818,10 +836,10 @@@ void bcast_ir_mtop(const t_commrec *cr
      block_bc(cr, mtop->bIntermolecularInteractions);
      if (mtop->bIntermolecularInteractions)
      {
-         mtop->intermolecular_ilist = std::make_unique<InteractionLists>();
+         if (!MASTER(cr))
+         {
 -            mtop->intermolecular_ilist = gmx::compat::make_unique<InteractionLists>();
++            mtop->intermolecular_ilist = std::make_unique<InteractionLists>();
+         }
          bc_ilists(cr, mtop->intermolecular_ilist.get());
      }
  
index c3ccdafec3ec2dcc715884efdad874f00dc85b09,ad70bf90ad82598fa52ea9a64a0debf782ae7b8e..94747562493edcf54cb9b6cb096241ebc254f2b3
@@@ -250,7 -250,7 +250,7 @@@ static void get_input(const char *membe
  
  /* Obtain the maximum and minimum coordinates of the group to be embedded */
  static int init_ins_at(t_block *ins_at, t_block *rest_at, t_state *state, pos_ins_t *pos_ins,
 -                       gmx_groups_t *groups, int ins_grp_id, real xy_max)
 +                       SimulationGroups *groups, int ins_grp_id, real xy_max)
  {
      int        i, gid, c = 0;
      real       xmin, xmax, ymin, ymax, zmin, zmax;
  
      for (i = 0; i < state->natoms; i++)
      {
 -        gid = groups->grpnr[egcFREEZE][i];
 -        if (groups->grps[egcFREEZE].nm_ind[gid] == ins_grp_id)
 +        gid = groups->groupNumbers[SimulationAtomGroupType::Freeze][i];
 +        if (groups->groups[SimulationAtomGroupType::Freeze][gid] == ins_grp_id)
          {
              xmin = std::min(xmin, x[i][XX]);
              xmax = std::max(xmax, x[i][XX]);
@@@ -530,6 -530,7 +530,7 @@@ static int gen_rm_list(rm_t *rm_p, t_bl
      r_min_rad = probe_rad*probe_rad;
      gmx::RangePartitioning molecules = gmx_mtop_molecules(*mtop);
      snew(rm_p->block, molecules.numBlocks());
+     snew(rm_p->mol, molecules.numBlocks());
      nrm    = nupper = 0;
      nlower = low_up_rm;
      for (i = 0; i < ins_at->nr; i++)
  }
  
  /*remove all lipids and waters overlapping and update all important structures (e.g. state and mtop)*/
 -static void rm_group(gmx_groups_t *groups, gmx_mtop_t *mtop, rm_t *rm_p, t_state *state,
 +static void rm_group(SimulationGroups *groups, gmx_mtop_t *mtop, rm_t *rm_p, t_state *state,
                       t_block *ins_at, pos_ins_t *pos_ins)
  {
      int             j, k, n, rm, mol_id, at, block;
      rvec           *x_tmp, *v_tmp;
      int            *list;
 -    unsigned char  *new_egrp[egcNR];
 +    gmx::EnumerationArray < SimulationAtomGroupType, std::vector < unsigned char>> new_egrp;
      gmx_bool        bRM;
      int             RMmolblock;
  
      for (int i = 0; i < rm_p->nr; i++)
      {
          mol_id = rm_p->mol[i];
-         at     = molecules.block(mol_id).size();
+         at     = molecules.block(mol_id).begin();
          block  = rm_p->block[i];
          mtop->molblock[block].nmol--;
          for (j = 0; j < mtop->moltype[mtop->molblock[block].type].atoms.nr; j++)
      }
  
      mtop->natoms    -= n;
-     state_change_natoms(state, state->natoms - n);
-     snew(x_tmp, state->natoms);
-     snew(v_tmp, state->natoms);
+     /* We cannot change the size of the state datastructures here
+      * because we still access the coordinate arrays for all positions
+      * before removing the molecules we want to remove.
+      */
+     const int newStateAtomNumber = state->natoms - n;
+     snew(x_tmp, newStateAtomNumber);
+     snew(v_tmp, newStateAtomNumber);
  
 -    for (int i = 0; i < egcNR; i++)
 +    for (auto group : keysOf(groups->groupNumbers))
      {
 -        if (groups->grpnr[i] != nullptr)
 +        if (!groups->groupNumbers[group].empty())
          {
-             groups->groupNumbers[group].resize(state->natoms);
-             new_egrp[group].resize(state->natoms);
 -            groups->ngrpnr[i] = newStateAtomNumber;
 -            snew(new_egrp[i], newStateAtomNumber);
++            groups->groupNumbers[group].resize(newStateAtomNumber);
++            new_egrp[group].resize(newStateAtomNumber);
          }
      }
  
      auto x = makeArrayRef(state->x);
      auto v = makeArrayRef(state->v);
      rm = 0;
-     for (int i = 0; i < state->natoms+n; i++)
+     for (int i = 0; i < state->natoms; i++)
      {
          bRM = FALSE;
          for (j = 0; j < n; j++)
  
          if (!bRM)
          {
 -            for (j = 0; j < egcNR; j++)
 +            for (auto group : keysOf(groups->groupNumbers))
              {
 -                if (groups->grpnr[j] != nullptr)
 +                if (!groups->groupNumbers[group].empty())
                  {
 -                    new_egrp[j][i-rm] = groups->grpnr[j][i];
 +                    new_egrp[group][i-rm] = groups->groupNumbers[group][i];
                  }
              }
              copy_rvec(x[i], x_tmp[i-rm]);
              }
          }
      }
+     state_change_natoms(state, newStateAtomNumber);
      for (int i = 0; i < state->natoms; i++)
      {
          copy_rvec(x_tmp[i], x[i]);
      }
      sfree(v_tmp);
  
 -    for (int i = 0; i < egcNR; i++)
 +    for (auto group : keysOf(groups->groupNumbers))
      {
 -        if (groups->grpnr[i] != nullptr)
 +        if (!groups->groupNumbers[group].empty())
          {
 -            sfree(groups->grpnr[i]);
 -            groups->grpnr[i] = new_egrp[i];
 +            groups->groupNumbers[group] = new_egrp[group];
          }
      }
  
@@@ -961,24 -968,48 +967,24 @@@ void rescale_membed(int step_rel, gmx_m
      resize(membed->r_ins, x, membed->pos_ins, membed->fac);
  }
  
 -/* We would like gn to be const as well, but C doesn't allow this */
 -/* TODO this is utility functionality (search for the index of a
 -   string in a collection), so should be refactored and located more
 -   centrally. Also, it nearly duplicates the same string in readir.c */
 -static int search_string(const char *s, int ng, char *gn[])
 -{
 -    int i;
 -
 -    for (i = 0; (i < ng); i++)
 -    {
 -        if (gmx_strcasecmp(s, gn[i]) == 0)
 -        {
 -            return i;
 -        }
 -    }
 -
 -    gmx_fatal(FARGS,
 -              "Group %s selected for embedding was not found in the index file.\n"
 -              "Group names must match either [moleculetype] names or custom index group\n"
 -              "names, in which case you must supply an index file to the '-n' option\n"
 -              "of grompp.",
 -              s);
 -}
 -
  gmx_membed_t *init_membed(FILE *fplog, int nfile, const t_filenm fnm[], gmx_mtop_t *mtop,
                            t_inputrec *inputrec, t_state *state, t_commrec *cr, real *cpt)
  {
 -    char                     *ins, **gnames;
 -    int                       i, rm_bonded_at, fr_id, fr_i = 0, tmp_id, warn = 0;
 -    int                       ng, j, max_lip_rm, ins_grp_id, ntype, lip_rm;
 -    real                      prot_area;
 -    rvec                     *r_ins = nullptr;
 -    t_block                  *ins_at, *rest_at;
 -    pos_ins_t                *pos_ins;
 -    mem_t                    *mem_p;
 -    rm_t                     *rm_p;
 -    gmx_groups_t             *groups;
 -    gmx_bool                  bExcl = FALSE;
 -    t_atoms                   atoms;
 -    t_pbc                    *pbc;
 -    char                    **piecename = nullptr;
 -    gmx_membed_t             *membed    = nullptr;
 +    char                            *ins;
 +    int                              i, rm_bonded_at, fr_id, fr_i = 0, tmp_id, warn = 0;
 +    int                              ng, j, max_lip_rm, ins_grp_id, ntype, lip_rm;
 +    real                             prot_area;
 +    rvec                            *r_ins = nullptr;
 +    t_block                         *ins_at, *rest_at;
 +    pos_ins_t                       *pos_ins;
 +    mem_t                           *mem_p;
 +    rm_t                            *rm_p;
 +    SimulationGroups                *groups;
 +    gmx_bool                         bExcl = FALSE;
 +    t_atoms                          atoms;
 +    t_pbc                           *pbc;
 +    char                           **piecename = nullptr;
 +    gmx_membed_t                    *membed    = nullptr;
  
      /* input variables */
      real        xy_fac           = 0.5;
              *cpt = -1;
          }
          groups = &(mtop->groups);
 -        snew(gnames, groups->ngrpname);
 -        for (i = 0; i < groups->ngrpname; i++)
 +        std::vector<std::string> gnames;
 +        for (const auto &groupName : groups->groupNames)
          {
 -            gnames[i] = *(groups->grpname[i]);
 +            gnames.emplace_back(*groupName);
          }
  
          atoms = gmx_mtop_global_atoms(mtop);
          snew(mem_p, 1);
          fprintf(stderr, "\nSelect a group to embed in the membrane:\n");
          get_index(&atoms, opt2fn_null("-mn", nfile, fnm), 1, &(ins_at->nr), &(ins_at->index), &ins);
 -        ins_grp_id = search_string(ins, groups->ngrpname, gnames);
 +
 +        auto found = std::find_if(gnames.begin(), gnames.end(),
 +                                  [&ins](const auto &name)
 +                                  { return gmx::equalCaseInsensitive(ins, name); });
 +
 +        if (found == gnames.end())
 +        {
 +            gmx_fatal(FARGS,
 +                      "Group %s selected for embedding was not found in the index file.\n"
 +                      "Group names must match either [moleculetype] names or custom index group\n"
 +                      "names, in which case you must supply an index file to the '-n' option\n"
 +                      "of grompp.", ins);
 +        }
 +        else
 +        {
 +            ins_grp_id = std::distance(gnames.begin(), found);
 +        }
          fprintf(stderr, "\nSelect a group to embed %s into (e.g. the membrane):\n", ins);
          get_index(&atoms, opt2fn_null("-mn", nfile, fnm), 1, &(mem_p->mem_at.nr), &(mem_p->mem_at.index), &(mem_p->name));
  
  
          for (i = 0; i < inputrec->opts.ngfrz; i++)
          {
 -            tmp_id = mtop->groups.grps[egcFREEZE].nm_ind[i];
 +            tmp_id = mtop->groups.groups[SimulationAtomGroupType::Freeze][i];
              if (ins_grp_id == tmp_id)
              {
                  fr_id = tmp_id;
              }
          }
  
 -        ng = groups->grps[egcENER].nr;
 +        ng = groups->groups[SimulationAtomGroupType::EnergyOutput].size();
          if (ng == 1)
          {
              gmx_input("No energy groups defined. This is necessary for energy exclusion in the freeze group");
                  if (inputrec->opts.egp_flags[ng*i+j] == EGP_EXCL)
                  {
                      bExcl = TRUE;
 -                    if ( (groups->grps[egcENER].nm_ind[i] != ins_grp_id) ||
 -                         (groups->grps[egcENER].nm_ind[j] != ins_grp_id) )
 +                    if ( (groups->groups[SimulationAtomGroupType::EnergyOutput][i] != ins_grp_id) ||
 +                         (groups->groups[SimulationAtomGroupType::EnergyOutput][j] != ins_grp_id) )
                      {
                          gmx_fatal(FARGS, "Energy exclusions \"%s\" and  \"%s\" do not match the group "
 -                                  "to embed \"%s\"", *groups->grpname[groups->grps[egcENER].nm_ind[i]],
 -                                  *groups->grpname[groups->grps[egcENER].nm_ind[j]], ins);
 +                                  "to embed \"%s\"", *groups->groupNames[groups->groups[SimulationAtomGroupType::EnergyOutput][i]],
 +                                  *groups->groupNames[groups->groups[SimulationAtomGroupType::EnergyOutput][j]], ins);
                      }
                  }
              }
index fa49e80294d24b1634c6206984af6840b346e40c,79b82c8c18e2211a400ccadb03e8100043e4a135..7d8a05465e1b5f1d7921bc60a30f122276608e1a
@@@ -108,17 -108,6 +108,17 @@@ shakedata *shake_init(
      return d;
  }
  
 +void done_shake(shakedata *d)
 +{
 +    sfree(d->rij);
 +    sfree(d->half_of_reduced_mass);
 +    sfree(d->distance_squared_tolerance);
 +    sfree(d->constraint_distance_squared);
 +    sfree(d->sblock);
 +    sfree(d->scaled_lagrange_multiplier);
 +    sfree(d);
 +}
 +
  typedef struct {
      int iatom[3];
      int blocknr;
@@@ -195,7 -184,6 +195,7 @@@ make_shake_sblock_serial(shakedata *sha
      ncons = idef->il[F_CONSTR].nr/3;
  
      init_blocka(&sblocks);
 +    sfree(sblocks.index); // To solve memory leak
      gen_sblocks(nullptr, 0, md.homenr, idef, &sblocks, FALSE);
  
      /*
          fprintf(debug, "Going to sort constraints\n");
      }
  
-     qsort(sb, ncons, sizeof(*sb), pcomp);
+     std::qsort(sb, ncons, sizeof(*sb), pcomp);
  
      if (debug)
      {
      resizeLagrangianData(shaked, ncons);
  }
  
 +// TODO: Check if this code is useful. It might never be called.
  void
 -make_shake_sblock_dd(shakedata *shaked,
 -                     const t_ilist *ilcon, const t_block *cgs,
 +make_shake_sblock_dd(shakedata          *shaked,
 +                     const t_ilist      *ilcon,
                       const gmx_domdec_t *dd)
  {
      int      ncons, c, cg;
      cg              = 0;
      for (c = 0; c < ncons; c++)
      {
 -        if (c == 0 || iatom[1] >= cgs->index[cg+1])
 +        if (c == 0 || iatom[1] >= cg + 1)
          {
              shaked->sblock[shaked->nblocks++] = 3*c;
 -            while (iatom[1] >= cgs->index[cg+1])
 +            while (iatom[1] >= cg + 1)
              {
                  cg++;
              }
index 5ff0d7a32afc33b10df3525cf0285ba1e405754e,27a6de629a3ef033a86802560cbb175e748ee4c9..26b4fb93c8212d084cf09aaf0a2e44433c31f990
  
  #include "gromacs/commandline/filenm.h"
  #include "gromacs/commandline/pargs.h"
 -#include "gromacs/domdec/domdec.h"
 +#include "gromacs/domdec/options.h"
  #include "gromacs/hardware/hw_info.h"
 -#include "gromacs/mdlib/mdrun.h"
 -#include "gromacs/mdrun/logging.h"
 +#include "gromacs/mdtypes/mdrunoptions.h"
  
  #include "replicaexchange.h"
  
@@@ -111,6 -112,8 +111,6 @@@ class LegacyMdrunOption
            { efXVG, "-tpid",     "tpidist",   ffOPTWR },
            { efEDI, "-ei",       "sam",       ffOPTRD },
            { efXVG, "-eo",       "edsam",     ffOPTWR },
 -          { efXVG, "-devout",   "deviatie",  ffOPTWR },
 -          { efXVG, "-runav",    "runaver",   ffOPTWR },
            { efXVG, "-px",       "pullx",     ffOPTWR },
            { efXVG, "-pf",       "pullf",     ffOPTWR },
            { efXVG, "-ro",       "rotation",  ffOPTWR },
          //! Print a warning if any force is larger than this (in kJ/mol nm).
          real                             pforce = -1;
  
 +        //! The value of the -append option
 +        bool                             appendOption = true;
 +
          /*! \brief Output context for writing text files
           *
           * \todo Clarify initialization, ownership, and lifetime. */
          gmx_output_env_t                *oenv = nullptr;
  
 -        //! Handle to file used for logging.
 -        LogFilePtr logFileGuard = nullptr;
 -
          /*! \brief Command line options, defaults, docs and storage for them to fill. */
          /*! \{ */
 -        rvec              realddxyz                                               = {0, 0, 0};
 -        const char       *ddrank_opt_choices[static_cast<int>(DdRankOrder::nr)+1] =
 +        rvec              realddxyz                                                  = {0, 0, 0};
 +        const char       *ddrank_opt_choices[static_cast<int>(DdRankOrder::Count)+1] =
          { nullptr, "interleave", "pp_pme", "cartesian", nullptr };
 -        const char       *dddlb_opt_choices[static_cast<int>(DlbOption::nr)+1] =
 +        const char       *dddlb_opt_choices[static_cast<int>(DlbOption::Count)+1] =
          { nullptr, "auto", "no", "yes", nullptr };
 -        const char       *thread_aff_opt_choices[threadaffNR+1] =
 +        const char       *thread_aff_opt_choices[static_cast<int>(ThreadAffinity::Count) + 1] =
          { nullptr, "auto", "on", "off", nullptr };
          const char       *nbpu_opt_choices[5] =
          { nullptr, "auto", "cpu", "gpu", nullptr };
          { nullptr, "auto", "cpu", "gpu", nullptr };
          const char       *bonded_opt_choices[5] =
          { nullptr, "auto", "cpu", "gpu", nullptr };
 -        gmx_bool          bTryToAppendFiles     = TRUE;
          const char       *gpuIdsAvailable       = "";
          const char       *userGpuTaskAssignment = "";
  
          ImdOptions       &imdOptions = mdrunOptions.imdOptions;
  
 -        t_pargs           pa[48] = {
 +        t_pargs           pa[47] = {
  
              { "-dd",      FALSE, etRVEC, {&realddxyz},
                "Domain decomposition grid, 0 is optimize" },
                "HIDDENA string containing a vector of the relative sizes in the z "
                "direction of the corresponding DD cells. Only effective with static "
                "load balancing." },
 -            { "-gcom",    FALSE, etINT, {&mdrunOptions.globalCommunicationInterval},
 -              "Global communication frequency" },
              { "-nb",      FALSE, etENUM, {nbpu_opt_choices},
                "Calculate non-bonded interactions on" },
              { "-nstlist", FALSE, etINT, {&nstlist_cmdline},
                "Checkpoint interval (minutes)" },
              { "-cpnum",   FALSE, etBOOL, {&mdrunOptions.checkpointOptions.keepAndNumberCheckpointFiles},
                "Keep and number checkpoint files" },
 -            { "-append",  FALSE, etBOOL, {&bTryToAppendFiles},
 +            { "-append",  FALSE, etBOOL, {&appendOption},
                "Append to previous output files when continuing from checkpoint instead of adding the simulation part number to all file names" },
              { "-nsteps",  FALSE, etINT64, {&mdrunOptions.numStepsCommandline},
-               "Run this number of steps, overrides .mdp file option (-1 means infinite, -2 means use mdp option, smaller is invalid)" },
+               "Run this number of steps (-1 means infinite, -2 means use mdp option, smaller is invalid)" },
              { "-maxh",   FALSE, etREAL, {&mdrunOptions.maximumHoursToRun},
                "Terminate after 0.99 times this time (hours)" },
              { "-replex",  FALSE, etINT, {&replExParams.exchangeInterval},
index 3cfd3f77c65304a901359f0358b22d3c0c72eac6,427ec544461876fb6febde42c470826f4477261e..76addf8c6552c6cdcd0d8e01b39dcb23038b1119
  
  #include "gromacs/commandline/filenm.h"
  #include "gromacs/domdec/collect.h"
 +#include "gromacs/domdec/dlbtiming.h"
  #include "gromacs/domdec/domdec.h"
  #include "gromacs/domdec/domdec_struct.h"
 +#include "gromacs/domdec/mdsetup.h"
  #include "gromacs/domdec/partition.h"
  #include "gromacs/ewald/pme.h"
  #include "gromacs/fileio/confio.h"
  #include "gromacs/gmxlib/nrnb.h"
  #include "gromacs/imd/imd.h"
  #include "gromacs/linearalgebra/sparsematrix.h"
 -#include "gromacs/listed-forces/manage-threading.h"
 +#include "gromacs/listed_forces/manage_threading.h"
  #include "gromacs/math/functions.h"
  #include "gromacs/math/vec.h"
  #include "gromacs/mdlib/constr.h"
 +#include "gromacs/mdlib/dispersioncorrection.h"
 +#include "gromacs/mdlib/ebin.h"
 +#include "gromacs/mdlib/enerdata_utils.h"
 +#include "gromacs/mdlib/energyoutput.h"
  #include "gromacs/mdlib/force.h"
  #include "gromacs/mdlib/forcerec.h"
  #include "gromacs/mdlib/gmx_omp_nthreads.h"
  #include "gromacs/mdlib/md_support.h"
  #include "gromacs/mdlib/mdatoms.h"
 -#include "gromacs/mdlib/mdebin.h"
 -#include "gromacs/mdlib/mdrun.h"
 -#include "gromacs/mdlib/mdsetup.h"
 -#include "gromacs/mdlib/ns.h"
 -#include "gromacs/mdlib/shellfc.h"
 -#include "gromacs/mdlib/sim_util.h"
 +#include "gromacs/mdlib/stat.h"
  #include "gromacs/mdlib/tgroup.h"
  #include "gromacs/mdlib/trajectory_writing.h"
  #include "gromacs/mdlib/update.h"
  #include "gromacs/mdlib/vsite.h"
 +#include "gromacs/mdrunutility/handlerestart.h"
 +#include "gromacs/mdrunutility/printtime.h"
  #include "gromacs/mdtypes/commrec.h"
  #include "gromacs/mdtypes/inputrec.h"
  #include "gromacs/mdtypes/md_enums.h"
 +#include "gromacs/mdtypes/mdrunoptions.h"
  #include "gromacs/mdtypes/state.h"
  #include "gromacs/pbcutil/mshift.h"
  #include "gromacs/pbcutil/pbc.h"
  #include "gromacs/utility/logger.h"
  #include "gromacs/utility/smalloc.h"
  
 -#include "integrator.h"
 +#include "legacysimulator.h"
 +#include "shellfc.h"
  
  //! Utility structure for manipulating states during EM
  typedef struct {
@@@ -353,15 -348,19 +353,15 @@@ static void init_em(FILE *fplog
                      const gmx::MDLogger &mdlog,
                      const char *title,
                      const t_commrec *cr,
 -                    const gmx_multisim_t *ms,
 -                    gmx::IMDOutputProvider *outputProvider,
                      t_inputrec *ir,
 -                    const MdrunOptions &mdrunOptions,
 +                    gmx::ImdSession *imdSession,
 +                    pull_t *pull_work,
                      t_state *state_global, gmx_mtop_t *top_global,
 -                    em_state_t *ems, gmx_localtop_t **top,
 -                    t_nrnb *nrnb, rvec mu_tot,
 -                    t_forcerec *fr, gmx_enerdata_t **enerd,
 +                    em_state_t *ems, gmx_localtop_t *top,
 +                    t_nrnb *nrnb,
 +                    t_forcerec *fr,
                      t_graph **graph, gmx::MDAtoms *mdAtoms, gmx_global_stat_t *gstat,
 -                    gmx_vsite_t *vsite, gmx::Constraints *constr, gmx_shellfc_t **shellfc,
 -                    int nfile, const t_filenm fnm[],
 -                    gmx_mdoutf_t *outf, t_mdebin **mdebin,
 -                    gmx_wallcycle_t wcycle)
 +                    gmx_vsite_t *vsite, gmx::Constraints *constr, gmx_shellfc_t **shellfc)
  {
      real dvdl_constr;
  
      if (MASTER(cr))
      {
          state_global->ngtc = 0;
 -
 -        /* Initialize lambda variables */
 -        initialize_lambdas(fplog, ir, &(state_global->fep_state), state_global->lambda, nullptr);
      }
 -
 -    init_nrnb(nrnb);
 -
 -    /* Interactive molecular dynamics */
 -    init_IMD(ir, cr, ms, top_global, fplog, 1,
 -             MASTER(cr) ? state_global->x.rvec_array() : nullptr,
 -             nfile, fnm, nullptr, mdrunOptions);
 +    initialize_lambdas(fplog, *ir, MASTER(cr), &(state_global->fep_state), state_global->lambda, nullptr);
  
      if (ir->eI == eiNM)
      {
      auto mdatoms = mdAtoms->mdatoms();
      if (DOMAINDECOMP(cr))
      {
 -        *top = dd_init_local_top(top_global);
 +        top->useInDomainDecomp_ = true;
 +        dd_init_local_top(*top_global, top);
  
          dd_init_local_state(cr->dd, state_global, &ems->s);
  
          /* Distribute the charge groups over the nodes from the master node */
          dd_partition_system(fplog, mdlog, ir->init_step, cr, TRUE, 1,
 -                            state_global, top_global, ir,
 -                            &ems->s, &ems->f, mdAtoms, *top,
 +                            state_global, *top_global, ir, imdSession, pull_work,
 +                            &ems->s, &ems->f, mdAtoms, top,
                              fr, vsite, constr,
                              nrnb, nullptr, FALSE);
          dd_store_state(cr->dd, &ems->s);
          state_change_natoms(&ems->s, ems->s.natoms);
          ems->f.resizeWithPadding(ems->s.natoms);
  
 -        snew(*top, 1);
 -        mdAlgorithmsSetupAtomData(cr, ir, top_global, *top, fr,
 +        mdAlgorithmsSetupAtomData(cr, ir, *top_global, top, fr,
                                    graph, mdAtoms,
                                    constr, vsite, shellfc ? *shellfc : nullptr);
  
          if (vsite)
          {
 -            set_vsite_top(vsite, *top, mdatoms);
 +            set_vsite_top(vsite, top, mdatoms);
          }
      }
  
          *gstat = nullptr;
      }
  
 -    *outf = init_mdoutf(fplog, nfile, fnm, mdrunOptions, cr, outputProvider, ir, top_global, nullptr, wcycle);
 -
 -    snew(*enerd, 1);
 -    init_enerdata(top_global->groups.grps[egcENER].nr, ir->fepvals->n_lambda,
 -                  *enerd);
 -
 -    if (mdebin != nullptr)
 -    {
 -        /* Init bin for energy stuff */
 -        *mdebin = init_mdebin(mdoutf_get_fp_ene(*outf), top_global, ir, nullptr);
 -    }
 -
 -    clear_rvec(mu_tot);
      calc_shifts(ems->s.box, fr->shift_vec);
  }
  
@@@ -528,7 -549,7 +528,7 @@@ static void write_em_traj(FILE *fplog, 
      }
  
      mdoutf_write_to_trajectory_files(fplog, cr, outf, mdof_flags,
 -                                     top_global, step, static_cast<double>(step),
 +                                     top_global->natoms, step, static_cast<double>(step),
                                       &state->s, state_global, observablesHistory,
                                       state->f);
  
              /* If bX=true, x was collected to state_global in the call above */
              if (!bX)
              {
 -                gmx::ArrayRef<gmx::RVec> globalXRef = MASTER(cr) ? makeArrayRef(state_global->x) : gmx::EmptyArrayRef();
 -                dd_collect_vec(cr->dd, &state->s, makeArrayRef(state->s.x), globalXRef);
 +                auto globalXRef = MASTER(cr) ? state_global->x : gmx::ArrayRef<gmx::RVec>();
 +                dd_collect_vec(cr->dd, &state->s, state->s.x, globalXRef);
              }
          }
          else
              if (ir->ePBC != epbcNONE && !ir->bPeriodicMols && DOMAINDECOMP(cr))
              {
                  /* Make molecules whole only for confout writing */
 -                do_pbc_mtop(fplog, ir->ePBC, state->s.box, top_global,
 +                do_pbc_mtop(ir->ePBC, state->s.box, top_global,
                              state_global->x.rvec_array());
              }
  
@@@ -662,7 -683,7 +662,7 @@@ static bool do_em_step(const t_commrec 
  
              /* OpenMP does not supported unsigned loop variables */
  #pragma omp for schedule(static) nowait
 -            for (int i = 0; i < static_cast<int>(s2->cg_gl.size()); i++)
 +            for (int i = 0; i < gmx::ssize(s2->cg_gl); i++)
              {
                  s2->cg_gl[i] = s1->cg_gl[i];
              }
@@@ -708,8 -729,6 +708,8 @@@ static void em_dd_partition_system(FIL
                                     const gmx::MDLogger &mdlog,
                                     int step, const t_commrec *cr,
                                     gmx_mtop_t *top_global, t_inputrec *ir,
 +                                   gmx::ImdSession *imdSession,
 +                                   pull_t *pull_work,
                                     em_state_t *ems, gmx_localtop_t *top,
                                     gmx::MDAtoms *mdAtoms, t_forcerec *fr,
                                     gmx_vsite_t *vsite, gmx::Constraints *constr,
  {
      /* Repartition the domain decomposition */
      dd_partition_system(fplog, mdlog, step, cr, FALSE, 1,
 -                        nullptr, top_global, ir,
 +                        nullptr, *top_global, ir, imdSession, pull_work,
                          &ems->s, &ems->f,
                          mdAtoms, top, fr, vsite, constr,
                          nrnb, wcycle, FALSE);
@@@ -738,10 -757,18 +738,10 @@@ namespac
   * updated, then the member will be value initialized, which will
   * typically mean initialization to zero.
   *
 - * We only want to construct one of these with an initializer list, so
 - * we explicitly delete the default constructor. */
 + * Use a braced initializer list to construct one of these. */
  class EnergyEvaluator
  {
      public:
 -        //! We only intend to construct such objects with an initializer list.
 -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)
 -        // Aspects of the C++11 spec changed after GCC 4.8.5, and
 -        // compilation of the initializer list construction in
 -        // runner.cpp fails in GCC 4.8.5.
 -        EnergyEvaluator() = delete;
 -#endif
          /*! \brief Evaluates an energy on the state in \c ems.
           *
           * \todo In practice, the same objects mu_tot, vir, and pres
          gmx_localtop_t       *top;
          //! User input options.
          t_inputrec           *inputrec;
 +        //! The Interactive Molecular Dynamics session.
 +        gmx::ImdSession      *imdSession;
 +        //! The pull work object.
 +        pull_t               *pull_work;
          //! Manages flop accounting.
          t_nrnb               *nrnb;
          //! Manages wall cycle accounting.
@@@ -803,7 -826,7 +803,7 @@@ EnergyEvaluator::run(em_state_t *ems, r
      real     t;
      gmx_bool bNS;
      tensor   force_vir, shake_vir, ekin;
 -    real     dvdl_constr, prescorr, enercorr, dvdlcorr;
 +    real     dvdl_constr;
      real     terminate = 0;
  
      /* Set the time to the initial time, the time does not change during EM */
      if (DOMAINDECOMP(cr) && bNS)
      {
          /* Repartition the domain decomposition */
 -        em_dd_partition_system(fplog, mdlog, count, cr, top_global, inputrec,
 +        em_dd_partition_system(fplog, mdlog, count, cr, top_global, inputrec, imdSession,
 +                               pull_work,
                                 ems, top, mdAtoms, fr, vsite, constr,
                                 nrnb, wcycle);
      }
      /* do_force always puts the charge groups in the box and shifts again
       * We do not unshift, so molecules are always whole in congrad.c
       */
 -    do_force(fplog, cr, ms, inputrec, nullptr, nullptr,
 -             count, nrnb, wcycle, top, &top_global->groups,
 +    do_force(fplog, cr, ms, inputrec, nullptr, nullptr, imdSession,
 +             pull_work,
 +             count, nrnb, wcycle, top,
               ems->s.box, ems->s.x.arrayRefWithPadding(), &ems->s.hist,
               ems->f.arrayRefWithPadding(), force_vir, mdAtoms->mdatoms(), enerd, fcd,
               ems->s.lambda, graph, fr, ppForceWorkload, vsite, mu_tot, t, nullptr,
               GMX_FORCE_STATECHANGED | GMX_FORCE_ALLFORCES |
               GMX_FORCE_VIRIAL | GMX_FORCE_ENERGY |
               (bNS ? GMX_FORCE_NS : 0),
 -             DOMAINDECOMP(cr) ?
 -             DdOpenBalanceRegionBeforeForceComputation::yes :
 -             DdOpenBalanceRegionBeforeForceComputation::no,
 -             DOMAINDECOMP(cr) ?
 -             DdCloseBalanceRegionAfterForceComputation::yes :
 -             DdCloseBalanceRegionAfterForceComputation::no);
 +             DDBalanceRegionHandler(cr));
  
      /* Clear the unused shake virial and pressure */
      clear_mat(shake_vir);
          wallcycle_stop(wcycle, ewcMoveE);
      }
  
 -    /* Calculate long range corrections to pressure and energy */
 -    calc_dispcorr(inputrec, fr, ems->s.box, ems->s.lambda[efptVDW],
 -                  pres, force_vir, &prescorr, &enercorr, &dvdlcorr);
 -    enerd->term[F_DISPCORR] = enercorr;
 -    enerd->term[F_EPOT]    += enercorr;
 -    enerd->term[F_PRES]    += prescorr;
 -    enerd->term[F_DVDL]    += dvdlcorr;
 +    if (fr->dispersionCorrection)
 +    {
 +        /* Calculate long range corrections to pressure and energy */
 +        const DispersionCorrection::Correction correction =
 +            fr->dispersionCorrection->calculate(ems->s.box, ems->s.lambda[efptVDW]);
 +
 +        enerd->term[F_DISPCORR] = correction.energy;
 +        enerd->term[F_EPOT]    += correction.energy;
 +        enerd->term[F_PRES]    += correction.pressure;
 +        enerd->term[F_DVDL]    += correction.dvdl;
 +    }
 +    else
 +    {
 +        enerd->term[F_DISPCORR] = 0;
 +    }
  
      ems->epot = enerd->term[F_EPOT];
  
  } // namespace
  
  //! Parallel utility summing energies and forces
 -static double reorder_partsum(const t_commrec *cr, t_grpopts *opts, t_mdatoms *mdatoms,
 +static double reorder_partsum(const t_commrec *cr, t_grpopts *opts,
                                gmx_mtop_t *top_global,
                                em_state_t *s_min, em_state_t *s_b)
  {
      t_block       *cgs_gl;
      int            ncg, *cg_gl, *index, c, cg, i, a0, a1, a, gf, m;
      double         partsum;
 -    unsigned char *grpnrFREEZE;
  
      if (debug)
      {
      partsum     = 0;
      i           = 0;
      gf          = 0;
 -    grpnrFREEZE = top_global->groups.grpnr[egcFREEZE];
 +    gmx::ArrayRef<unsigned char> grpnrFREEZE = top_global->groups.groupNumbers[SimulationAtomGroupType::Freeze];
      for (c = 0; c < ncg; c++)
      {
          cg = cg_gl[c];
          a1 = index[cg+1];
          for (a = a0; a < a1; a++)
          {
 -            if (mdatoms->cFREEZE && grpnrFREEZE)
 +            if (!grpnrFREEZE.empty())
              {
                  gf = grpnrFREEZE[i];
              }
@@@ -1043,7 -1062,7 +1043,7 @@@ static real pr_beta(const t_commrec *cr
      else
      {
          /* We need to reorder cgs while summing */
 -        sum = reorder_partsum(cr, opts, mdatoms, top_global, s_min, s_b);
 +        sum = reorder_partsum(cr, opts, top_global, s_min, s_b);
      }
      if (PAR(cr))
      {
@@@ -1057,25 -1076,28 +1057,25 @@@ namespace gm
  {
  
  void
 -Integrator::do_cg()
 +LegacySimulator::do_cg()
  {
 -    const char       *CG = "Polak-Ribiere Conjugate Gradients";
 +    const char        *CG = "Polak-Ribiere Conjugate Gradients";
  
 -    gmx_localtop_t   *top;
 -    gmx_enerdata_t   *enerd;
 -    gmx_global_stat_t gstat;
 -    t_graph          *graph;
 -    double            tmp, minstep;
 -    real              stepsize;
 -    real              a, b, c, beta = 0.0;
 -    real              epot_repl = 0;
 -    real              pnorm;
 -    t_mdebin         *mdebin;
 -    gmx_bool          converged, foundlower;
 -    rvec              mu_tot;
 -    gmx_bool          do_log = FALSE, do_ene = FALSE, do_x, do_f;
 -    tensor            vir, pres;
 -    int               number_steps, neval = 0, nstcg = inputrec->nstcgsteep;
 -    gmx_mdoutf_t      outf;
 -    int               m, step, nminstep;
 -    auto              mdatoms = mdAtoms->mdatoms();
 +    gmx_localtop_t     top;
 +    gmx_global_stat_t  gstat;
 +    t_graph           *graph;
 +    double             tmp, minstep;
 +    real               stepsize;
 +    real               a, b, c, beta = 0.0;
 +    real               epot_repl = 0;
 +    real               pnorm;
 +    gmx_bool           converged, foundlower;
 +    rvec               mu_tot = {0};
 +    gmx_bool           do_log = FALSE, do_ene = FALSE, do_x, do_f;
 +    tensor             vir, pres;
 +    int                number_steps, neval = 0, nstcg = inputrec->nstcgsteep;
 +    int                m, step, nminstep;
 +    auto               mdatoms = mdAtoms->mdatoms();
  
      GMX_LOG(mdlog.info).asParagraph().
          appendText("Note that activating conjugate gradient energy minimization via the "
      em_state_t *s_c   = &s3;
  
      /* Init em and store the local state in s_min */
 -    init_em(fplog, mdlog, CG, cr, ms, outputProvider, inputrec, mdrunOptions,
 +    init_em(fplog, mdlog, CG, cr, inputrec, imdSession,
 +            pull_work,
              state_global, top_global, s_min, &top,
 -            nrnb, mu_tot, fr, &enerd, &graph, mdAtoms, &gstat,
 -            vsite, constr, nullptr,
 -            nfile, fnm, &outf, &mdebin, wcycle);
 +            nrnb, fr, &graph, mdAtoms, &gstat,
 +            vsite, constr, nullptr);
 +    gmx_mdoutf       *outf = init_mdoutf(fplog, nfile, fnm, mdrunOptions, cr, outputProvider, inputrec, top_global, nullptr, wcycle,
 +                                         StartingBehavior::NewSimulation);
 +    gmx::EnergyOutput energyOutput(mdoutf_get_fp_ene(outf), top_global, inputrec, pull_work, nullptr, false);
  
      /* Print to log file */
      print_em_start(fplog, cr, walltime_accounting, wcycle, CG);
  
      EnergyEvaluator energyEvaluator {
          fplog, mdlog, cr, ms,
 -        top_global, top,
 -        inputrec, nrnb, wcycle, gstat,
 +        top_global, &top,
 +        inputrec, imdSession, pull_work, nrnb, wcycle, gstat,
          vsite, constr, fcd, graph,
          mdAtoms, fr, ppForceWorkload, enerd
      };
      {
          /* Copy stuff to the energy bin for easy printing etc. */
          matrix nullBox = {};
 -        upd_mdebin(mdebin, FALSE, FALSE, static_cast<double>(step),
 -                   mdatoms->tmass, enerd, nullptr, nullptr, nullptr, nullBox,
 -                   nullptr, nullptr, vir, pres, nullptr, mu_tot, constr);
 +        energyOutput.addDataAtEnergyStep(false, false, static_cast<double>(step),
 +                                         mdatoms->tmass, enerd, nullptr, nullptr, nullptr, nullBox,
 +                                         nullptr, nullptr, vir, pres, nullptr, mu_tot, constr);
  
 -        print_ebin_header(fplog, step, step);
 -        print_ebin(mdoutf_get_fp_ene(outf), TRUE, FALSE, FALSE, fplog, step, step, eprNORMAL,
 -                   mdebin, fcd, &(top_global->groups), &(inputrec->opts), nullptr);
 +        energyOutput.printHeader(fplog, step, step);
 +        energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), TRUE, FALSE, FALSE,
 +                                           fplog, step, step,
 +                                           fcd, nullptr);
      }
  
      /* Estimate/guess the initial stepsize */
  
          if (DOMAINDECOMP(cr) && s_min->s.ddp_count < cr->dd->ddp_count)
          {
 -            em_dd_partition_system(fplog, mdlog, step, cr, top_global, inputrec,
 -                                   s_min, top, mdAtoms, fr, vsite, constr,
 +            em_dd_partition_system(fplog, mdlog, step, cr, top_global, inputrec, imdSession,
 +                                   pull_work,
 +                                   s_min, &top, mdAtoms, fr, vsite, constr,
                                     nrnb, wcycle);
          }
  
                  if (DOMAINDECOMP(cr) && s_min->s.ddp_count != cr->dd->ddp_count)
                  {
                      /* Reload the old state */
 -                    em_dd_partition_system(fplog, mdlog, -1, cr, top_global, inputrec,
 -                                           s_min, top, mdAtoms, fr, vsite, constr,
 +                    em_dd_partition_system(fplog, mdlog, -1, cr, top_global, inputrec, imdSession,
 +                                           pull_work,
 +                                           s_min, &top, mdAtoms, fr, vsite, constr,
                                             nrnb, wcycle);
                  }
  
              }
              /* Store the new (lower) energies */
              matrix nullBox = {};
 -            upd_mdebin(mdebin, FALSE, FALSE, static_cast<double>(step),
 -                       mdatoms->tmass, enerd, nullptr, nullptr, nullptr, nullBox,
 -                       nullptr, nullptr, vir, pres, nullptr, mu_tot, constr);
 +            energyOutput.addDataAtEnergyStep(false, false, static_cast<double>(step),
 +                                             mdatoms->tmass, enerd, nullptr, nullptr, nullptr, nullBox,
 +                                             nullptr, nullptr, vir, pres, nullptr, mu_tot, constr);
  
              do_log = do_per_step(step, inputrec->nstlog);
              do_ene = do_per_step(step, inputrec->nstenergy);
  
 -            /* Prepare IMD energy record, if bIMD is TRUE. */
 -            IMD_fill_energy_record(inputrec->bIMD, inputrec->imd, enerd, step, TRUE);
 +            imdSession->fillEnergyRecord(step, TRUE);
  
              if (do_log)
              {
 -                print_ebin_header(fplog, step, step);
 +                energyOutput.printHeader(fplog, step, step);
              }
 -            print_ebin(mdoutf_get_fp_ene(outf), do_ene, FALSE, FALSE,
 -                       do_log ? fplog : nullptr, step, step, eprNORMAL,
 -                       mdebin, fcd, &(top_global->groups), &(inputrec->opts), nullptr);
 +            energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), do_ene, FALSE, FALSE,
 +                                               do_log ? fplog : nullptr, step, step,
 +                                               fcd, nullptr);
          }
  
          /* Send energies and positions to the IMD client if bIMD is TRUE. */
 -        if (MASTER(cr) && do_IMD(inputrec->bIMD, step, cr, TRUE, state_global->box, state_global->x.rvec_array(), inputrec, 0, wcycle))
 +        if (MASTER(cr) && imdSession->run(step, TRUE, state_global->box, state_global->x.rvec_array(), 0))
          {
 -            IMD_send_positions(inputrec->imd);
 +            imdSession->sendPositionsAndEnergies();
          }
  
          /* Stop when the maximum force lies below tolerance.
  
      }   /* End of the loop */
  
 -    /* IMD cleanup, if bIMD is TRUE. */
 -    IMD_finalize(inputrec->bIMD, inputrec->imd);
 -
      if (converged)
      {
          step--; /* we never took that last step in this case */
          if (!do_log)
          {
              /* Write final value to log since we didn't do anything the last step */
 -            print_ebin_header(fplog, step, step);
 +            energyOutput.printHeader(fplog, step, step);
          }
          if (!do_ene || !do_log)
          {
              /* Write final energy file entries */
 -            print_ebin(mdoutf_get_fp_ene(outf), !do_ene, FALSE, FALSE,
 -                       !do_log ? fplog : nullptr, step, step, eprNORMAL,
 -                       mdebin, fcd, &(top_global->groups), &(inputrec->opts), nullptr);
 +            energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), !do_ene, FALSE, FALSE,
 +                                               !do_log ? fplog : nullptr, step, step,
 +                                               fcd, nullptr);
          }
      }
  
  
  
  void
 -Integrator::do_lbfgs()
 +LegacySimulator::do_lbfgs()
  {
      static const char *LBFGS = "Low-Memory BFGS Minimizer";
      em_state_t         ems;
 -    gmx_localtop_t    *top;
 -    gmx_enerdata_t    *enerd;
 +    gmx_localtop_t     top;
      gmx_global_stat_t  gstat;
      t_graph           *graph;
      int                ncorr, nmaxcorr, point, cp, neval, nminstep;
      real               a, b, c, maxdelta, delta;
      real               diag, Epot0;
      real               dgdx, dgdg, sq, yr, beta;
 -    t_mdebin          *mdebin;
      gmx_bool           converged;
 -    rvec               mu_tot;
 +    rvec               mu_tot = {0};
      gmx_bool           do_log, do_ene, do_x, do_f, foundlower, *frozen;
      tensor             vir, pres;
      int                start, end, number_steps;
 -    gmx_mdoutf_t       outf;
      int                i, k, m, n, gf, step;
      int                mdof_flags;
      auto               mdatoms = mdAtoms->mdatoms();
      neval = 0;
  
      /* Init em */
 -    init_em(fplog, mdlog, LBFGS, cr, ms, outputProvider, inputrec, mdrunOptions,
 +    init_em(fplog, mdlog, LBFGS, cr, inputrec, imdSession,
 +            pull_work,
              state_global, top_global, &ems, &top,
 -            nrnb, mu_tot, fr, &enerd, &graph, mdAtoms, &gstat,
 -            vsite, constr, nullptr,
 -            nfile, fnm, &outf, &mdebin, wcycle);
 +            nrnb, fr, &graph, mdAtoms, &gstat,
 +            vsite, constr, nullptr);
 +    gmx_mdoutf       *outf = init_mdoutf(fplog, nfile, fnm, mdrunOptions, cr, outputProvider, inputrec, top_global, nullptr, wcycle,
 +                                         StartingBehavior::NewSimulation);
 +    gmx::EnergyOutput energyOutput(mdoutf_get_fp_ene(outf), top_global, inputrec, pull_work, nullptr, false);
  
      start = 0;
      end   = mdatoms->homenr;
      if (vsite)
      {
          construct_vsites(vsite, state_global->x.rvec_array(), 1, nullptr,
 -                         top->idef.iparams, top->idef.il,
 +                         top.idef.iparams, top.idef.il,
                           fr->ePBC, fr->bMolPBC, cr, state_global->box);
      }
  
      neval++;
      EnergyEvaluator energyEvaluator {
          fplog, mdlog, cr, ms,
 -        top_global, top,
 -        inputrec, nrnb, wcycle, gstat,
 +        top_global, &top,
 +        inputrec, imdSession, pull_work, nrnb, wcycle, gstat,
          vsite, constr, fcd, graph,
          mdAtoms, fr, ppForceWorkload, enerd
      };
      {
          /* Copy stuff to the energy bin for easy printing etc. */
          matrix nullBox = {};
 -        upd_mdebin(mdebin, FALSE, FALSE, static_cast<double>(step),
 -                   mdatoms->tmass, enerd, nullptr, nullptr, nullptr, nullBox,
 -                   nullptr, nullptr, vir, pres, nullptr, mu_tot, constr);
 +        energyOutput.addDataAtEnergyStep(false, false, static_cast<double>(step),
 +                                         mdatoms->tmass, enerd, nullptr, nullptr, nullptr, nullBox,
 +                                         nullptr, nullptr, vir, pres, nullptr, mu_tot, constr);
  
 -        print_ebin_header(fplog, step, step);
 -        print_ebin(mdoutf_get_fp_ene(outf), TRUE, FALSE, FALSE, fplog, step, step, eprNORMAL,
 -                   mdebin, fcd, &(top_global->groups), &(inputrec->opts), nullptr);
 +        energyOutput.printHeader(fplog, step, step);
 +        energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), TRUE, FALSE, FALSE,
 +                                           fplog, step, step,
 +                                           fcd, nullptr);
      }
  
      /* Set the initial step.
          }
  
          mdoutf_write_to_trajectory_files(fplog, cr, outf, mdof_flags,
 -                                         top_global, step, static_cast<real>(step), &ems.s, state_global, observablesHistory, ems.f);
 +                                         top_global->natoms, step, static_cast<real>(step), &ems.s,
 +                                         state_global, observablesHistory, ems.f);
  
          /* Do the linesearching in the direction dx[point][0..(n-1)] */
  
                  {
                      /* Replace c endpoint with b */
                      c   = b;
-                     /* swap states b and c */
-                     swap_em_state(&sb, &sc);
+                     /* copy state b to c */
+                     *sc = *sb;
                  }
                  else
                  {
                      /* Replace a endpoint with b */
                      a   = b;
-                     /* swap states a and b */
-                     swap_em_state(&sa, &sb);
+                     /* copy state b to a */
+                     *sa = *sb;
                  }
  
                  /*
              }
              /* Store the new (lower) energies */
              matrix nullBox = {};
 -            upd_mdebin(mdebin, FALSE, FALSE, static_cast<double>(step),
 -                       mdatoms->tmass, enerd, nullptr, nullptr, nullptr, nullBox,
 -                       nullptr, nullptr, vir, pres, nullptr, mu_tot, constr);
 +            energyOutput.addDataAtEnergyStep(false, false, static_cast<double>(step),
 +                                             mdatoms->tmass, enerd, nullptr, nullptr, nullptr, nullBox,
 +                                             nullptr, nullptr, vir, pres, nullptr, mu_tot, constr);
 +
              do_log = do_per_step(step, inputrec->nstlog);
              do_ene = do_per_step(step, inputrec->nstenergy);
 +
 +            imdSession->fillEnergyRecord(step, TRUE);
 +
              if (do_log)
              {
 -                print_ebin_header(fplog, step, step);
 +                energyOutput.printHeader(fplog, step, step);
              }
 -            print_ebin(mdoutf_get_fp_ene(outf), do_ene, FALSE, FALSE,
 -                       do_log ? fplog : nullptr, step, step, eprNORMAL,
 -                       mdebin, fcd, &(top_global->groups), &(inputrec->opts), nullptr);
 +            energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), do_ene, FALSE, FALSE,
 +                                               do_log ? fplog : nullptr, step, step,
 +                                               fcd, nullptr);
          }
  
          /* Send x and E to IMD client, if bIMD is TRUE. */
 -        if (do_IMD(inputrec->bIMD, step, cr, TRUE, state_global->box, state_global->x.rvec_array(), inputrec, 0, wcycle) && MASTER(cr))
 +        if (imdSession->run(step, TRUE, state_global->box, state_global->x.rvec_array(), 0) && MASTER(cr))
          {
 -            IMD_send_positions(inputrec->imd);
 +            imdSession->sendPositionsAndEnergies();
          }
  
          // Reset stepsize in we are doing more iterations
-         stepsize = 1.0/ems.fnorm;
+         stepsize = 1.0;
  
          /* Stop when the maximum force lies below tolerance.
           * If we have reached machine precision, converged is already set to true.
  
      }   /* End of the loop */
  
 -    /* IMD cleanup, if bIMD is TRUE. */
 -    IMD_finalize(inputrec->bIMD, inputrec->imd);
 -
      if (converged)
      {
          step--; /* we never took that last step in this case */
       */
      if (!do_log) /* Write final value to log since we didn't do anythin last step */
      {
 -        print_ebin_header(fplog, step, step);
 +        energyOutput.printHeader(fplog, step, step);
      }
      if (!do_ene || !do_log) /* Write final energy file entries */
      {
 -        print_ebin(mdoutf_get_fp_ene(outf), !do_ene, FALSE, FALSE,
 -                   !do_log ? fplog : nullptr, step, step, eprNORMAL,
 -                   mdebin, fcd, &(top_global->groups), &(inputrec->opts), nullptr);
 +        energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), !do_ene, FALSE, FALSE,
 +                                           !do_log ? fplog : nullptr, step, step,
 +                                           fcd, nullptr);
      }
  
      /* Print some stuff... */
  }
  
  void
 -Integrator::do_steep()
 +LegacySimulator::do_steep()
  {
 -    const char       *SD = "Steepest Descents";
 -    gmx_localtop_t   *top;
 -    gmx_enerdata_t   *enerd;
 +    const char       *SD  = "Steepest Descents";
 +    gmx_localtop_t    top;
      gmx_global_stat_t gstat;
      t_graph          *graph;
      real              stepsize;
      real              ustep;
 -    gmx_mdoutf_t      outf;
 -    t_mdebin         *mdebin;
      gmx_bool          bDone, bAbort, do_x, do_f;
      tensor            vir, pres;
 -    rvec              mu_tot;
 +    rvec              mu_tot = {0};
      int               nsteps;
      int               count          = 0;
      int               steps_accepted = 0;
      em_state_t *s_try = &s1;
  
      /* Init em and store the local state in s_try */
 -    init_em(fplog, mdlog, SD, cr, ms, outputProvider, inputrec, mdrunOptions,
 +    init_em(fplog, mdlog, SD, cr, inputrec, imdSession,
 +            pull_work,
              state_global, top_global, s_try, &top,
 -            nrnb, mu_tot, fr, &enerd, &graph, mdAtoms, &gstat,
 -            vsite, constr, nullptr,
 -            nfile, fnm, &outf, &mdebin, wcycle);
 +            nrnb, fr, &graph, mdAtoms, &gstat,
 +            vsite, constr, nullptr);
 +    gmx_mdoutf       *outf = init_mdoutf(fplog, nfile, fnm, mdrunOptions, cr, outputProvider, inputrec, top_global, nullptr, wcycle,
 +                                         StartingBehavior::NewSimulation);
 +    gmx::EnergyOutput energyOutput(mdoutf_get_fp_ene(outf), top_global, inputrec, pull_work, nullptr, false);
  
      /* Print to log file  */
      print_em_start(fplog, cr, walltime_accounting, wcycle, SD);
      }
      EnergyEvaluator energyEvaluator {
          fplog, mdlog, cr, ms,
 -        top_global, top,
 -        inputrec, nrnb, wcycle, gstat,
 +        top_global, &top,
 +        inputrec, imdSession, pull_work, nrnb, wcycle, gstat,
          vsite, constr, fcd, graph,
          mdAtoms, fr, ppForceWorkload, enerd
      };
  
          if (MASTER(cr))
          {
 -            print_ebin_header(fplog, count, count);
 +            energyOutput.printHeader(fplog, count, count);
          }
  
          if (count == 0)
              {
                  /* Store the new (lower) energies  */
                  matrix nullBox = {};
 -                upd_mdebin(mdebin, FALSE, FALSE, static_cast<double>(count),
 -                           mdatoms->tmass, enerd, nullptr, nullptr, nullptr,
 -                           nullBox, nullptr, nullptr, vir, pres, nullptr, mu_tot, constr);
 -
 -                /* Prepare IMD energy record, if bIMD is TRUE. */
 -                IMD_fill_energy_record(inputrec->bIMD, inputrec->imd, enerd, count, TRUE);
 -
 -                print_ebin(mdoutf_get_fp_ene(outf), TRUE,
 -                           do_per_step(steps_accepted, inputrec->nstdisreout),
 -                           do_per_step(steps_accepted, inputrec->nstorireout),
 -                           fplog, count, count, eprNORMAL,
 -                           mdebin, fcd, &(top_global->groups), &(inputrec->opts), nullptr);
 +                energyOutput.addDataAtEnergyStep(false, false, static_cast<double>(count),
 +                                                 mdatoms->tmass, enerd, nullptr, nullptr, nullptr, nullBox,
 +                                                 nullptr, nullptr, vir, pres, nullptr, mu_tot, constr);
 +
 +                imdSession->fillEnergyRecord(count, TRUE);
 +
 +                const bool do_dr = do_per_step(steps_accepted, inputrec->nstdisreout);
 +                const bool do_or = do_per_step(steps_accepted, inputrec->nstorireout);
 +                energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), TRUE,
 +                                                   do_dr, do_or,
 +                                                   fplog, count, count,
 +                                                   fcd, nullptr);
                  fflush(fplog);
              }
          }
              if (DOMAINDECOMP(cr) && s_min->s.ddp_count != cr->dd->ddp_count)
              {
                  /* Reload the old state */
 -                em_dd_partition_system(fplog, mdlog, count, cr, top_global, inputrec,
 -                                       s_min, top, mdAtoms, fr, vsite, constr,
 +                em_dd_partition_system(fplog, mdlog, count, cr, top_global, inputrec, imdSession,
 +                                       pull_work,
 +                                       s_min, &top, mdAtoms, fr, vsite, constr,
                                         nrnb, wcycle);
              }
          }
  
-         /* Determine new step  */
-         stepsize = ustep/s_min->fmax;
+         // If the force is very small after finishing minimization,
+         // we risk dividing by zero when calculating the step size.
+         // So we check first if the minimization has stopped before
+         // trying to obtain a new step size.
+         if (!bDone)
+         {
+             /* Determine new step  */
+             stepsize = ustep/s_min->fmax;
+         }
  
          /* Check if stepsize is too small, with 1 nm as a characteristic length */
  #if GMX_DOUBLE
          }
  
          /* Send IMD energies and positions, if bIMD is TRUE. */
 -        if (do_IMD(inputrec->bIMD, count, cr, TRUE, state_global->box,
 -                   MASTER(cr) ? state_global->x.rvec_array() : nullptr,
 -                   inputrec, 0, wcycle) &&
 +        if (imdSession->run(count, TRUE, state_global->box,
 +                            MASTER(cr) ? state_global->x.rvec_array() : nullptr,
 +                            0) &&
              MASTER(cr))
          {
 -            IMD_send_positions(inputrec->imd);
 +            imdSession->sendPositionsAndEnergies();
          }
  
          count++;
      }   /* End of the loop  */
  
 -    /* IMD cleanup, if bIMD is TRUE. */
 -    IMD_finalize(inputrec->bIMD, inputrec->imd);
 -
      /* Print some data...  */
      if (MASTER(cr))
      {
  }
  
  void
 -Integrator::do_nm()
 +LegacySimulator::do_nm()
  {
      const char          *NM = "Normal Mode Analysis";
 -    gmx_mdoutf_t         outf;
      int                  nnodes, node;
 -    gmx_localtop_t      *top;
 -    gmx_enerdata_t      *enerd;
 +    gmx_localtop_t       top;
      gmx_global_stat_t    gstat;
      t_graph             *graph;
      tensor               vir, pres;
 -    rvec                 mu_tot;
 +    rvec                 mu_tot = {0};
      rvec                *dfdx;
      gmx_bool             bSparse; /* use sparse matrix storage format */
      size_t               sz;
      em_state_t     state_work {};
  
      /* Init em and store the local state in state_minimum */
 -    init_em(fplog, mdlog, NM, cr, ms, outputProvider, inputrec, mdrunOptions,
 +    init_em(fplog, mdlog, NM, cr, inputrec, imdSession,
 +            pull_work,
              state_global, top_global, &state_work, &top,
 -            nrnb, mu_tot, fr, &enerd, &graph, mdAtoms, &gstat,
 -            vsite, constr, &shellfc,
 -            nfile, fnm, &outf, nullptr, wcycle);
 +            nrnb, fr, &graph, mdAtoms, &gstat,
 +            vsite, constr, &shellfc);
 +    gmx_mdoutf            *outf = init_mdoutf(fplog, nfile, fnm, mdrunOptions, cr, outputProvider, inputrec, top_global, nullptr, wcycle,
 +                                              StartingBehavior::NewSimulation);
  
      std::vector<int>       atom_index = get_atom_index(top_global);
      std::vector<gmx::RVec> fneg(atom_index.size(), {0, 0, 0});
          snew(full_matrix, sz*sz);
      }
  
 -    init_nrnb(nrnb);
 -
 -
      /* Write start time and temperature */
      print_em_start(fplog, cr, walltime_accounting, wcycle, NM);
  
      cr->nnodes = 1;
      EnergyEvaluator energyEvaluator {
          fplog, mdlog, cr, ms,
 -        top_global, top,
 -        inputrec, nrnb, wcycle, gstat,
 +        top_global, &top,
 +        inputrec, imdSession, pull_work, nrnb, wcycle, gstat,
          vsite, constr, fcd, graph,
          mdAtoms, fr, ppForceWorkload, enerd
      };
                                          nullptr,
                                          step,
                                          inputrec,
 +                                        imdSession,
 +                                        pull_work,
                                          bNS,
                                          force_flags,
 -                                        top,
 +                                        &top,
                                          constr,
                                          enerd,
                                          fcd,
 -                                        &state_work.s,
 +                                        state_work.s.natoms,
 +                                        state_work.s.x.arrayRefWithPadding(),
 +                                        state_work.s.v.arrayRefWithPadding(),
 +                                        state_work.s.box,
 +                                        state_work.s.lambda,
 +                                        &state_work.s.hist,
                                          state_work.f.arrayRefWithPadding(),
                                          vir,
                                          mdatoms,
                                          nrnb,
                                          wcycle,
                                          graph,
 -                                        &top_global->groups,
                                          shellfc,
                                          fr,
                                          ppForceWorkload,
                                          t,
                                          mu_tot,
                                          vsite,
 -                                        DdOpenBalanceRegionBeforeForceComputation::no,
 -                                        DdCloseBalanceRegionAfterForceComputation::no);
 +                                        DDBalanceRegionHandler(nullptr));
                      bNS = false;
                      step++;
                  }
          /* write progress */
          if (bIsMaster && mdrunOptions.verbose)
          {
 -            fprintf(stderr, "\rFinished step %d out of %d",
 -                    static_cast<int>(std::min(atom+nnodes, atom_index.size())),
 -                    static_cast<int>(atom_index.size()));
 +            fprintf(stderr, "\rFinished step %d out of %td",
 +                    std::min<int>(atom+nnodes, atom_index.size()),
 +                    ssize(atom_index));
              fflush(stderr);
          }
      }
index 0c354711d517219d46c83c8484cf60453547ab45,370cff95f94c657027b79afd16368f194ac41918..79ebf9fd33fc50042233446d27e68de5c780b7f6
  #include <cstring>
  
  #include <algorithm>
 +#include <memory>
  
  #include "gromacs/commandline/filenm.h"
 -#include "gromacs/compat/make_unique.h"
  #include "gromacs/domdec/domdec.h"
  #include "gromacs/domdec/domdec_struct.h"
  #include "gromacs/domdec/localatomsetmanager.h"
 -#include "gromacs/ewald/ewald-utils.h"
 +#include "gromacs/domdec/partition.h"
 +#include "gromacs/ewald/ewald_utils.h"
  #include "gromacs/ewald/pme.h"
 -#include "gromacs/ewald/pme-gpu-program.h"
 +#include "gromacs/ewald/pme_gpu_program.h"
  #include "gromacs/fileio/checkpoint.h"
  #include "gromacs/fileio/gmxfio.h"
  #include "gromacs/fileio/oenv.h"
  #include "gromacs/hardware/cpuinfo.h"
  #include "gromacs/hardware/detecthardware.h"
  #include "gromacs/hardware/printhardware.h"
 -#include "gromacs/listed-forces/disre.h"
 -#include "gromacs/listed-forces/gpubonded.h"
 -#include "gromacs/listed-forces/orires.h"
 +#include "gromacs/imd/imd.h"
 +#include "gromacs/listed_forces/disre.h"
 +#include "gromacs/listed_forces/gpubonded.h"
 +#include "gromacs/listed_forces/orires.h"
  #include "gromacs/math/functions.h"
  #include "gromacs/math/utilities.h"
  #include "gromacs/math/vec.h"
  #include "gromacs/mdlib/boxdeformation.h"
 +#include "gromacs/mdlib/broadcaststructs.h"
  #include "gromacs/mdlib/calc_verletbuf.h"
 +#include "gromacs/mdlib/dispersioncorrection.h"
 +#include "gromacs/mdlib/enerdata_utils.h"
 +#include "gromacs/mdlib/force.h"
  #include "gromacs/mdlib/forcerec.h"
  #include "gromacs/mdlib/gmx_omp_nthreads.h"
  #include "gromacs/mdlib/makeconstraints.h"
  #include "gromacs/mdlib/md_support.h"
  #include "gromacs/mdlib/mdatoms.h"
 -#include "gromacs/mdlib/mdrun.h"
  #include "gromacs/mdlib/membed.h"
 -#include "gromacs/mdlib/nb_verlet.h"
 -#include "gromacs/mdlib/nbnxn_gpu_data_mgmt.h"
 -#include "gromacs/mdlib/nbnxn_search.h"
 -#include "gromacs/mdlib/nbnxn_tuning.h"
  #include "gromacs/mdlib/ppforceworkload.h"
  #include "gromacs/mdlib/qmmm.h"
  #include "gromacs/mdlib/sighandler.h"
 -#include "gromacs/mdlib/sim_util.h"
  #include "gromacs/mdlib/stophandler.h"
 -#include "gromacs/mdrun/legacymdrunoptions.h"
 -#include "gromacs/mdrun/logging.h"
 -#include "gromacs/mdrun/multisim.h"
 +#include "gromacs/mdrun/mdmodules.h"
  #include "gromacs/mdrun/simulationcontext.h"
 -#include "gromacs/mdrunutility/mdmodules.h"
 +#include "gromacs/mdrunutility/handlerestart.h"
 +#include "gromacs/mdrunutility/logging.h"
 +#include "gromacs/mdrunutility/multisim.h"
 +#include "gromacs/mdrunutility/printtime.h"
  #include "gromacs/mdrunutility/threadaffinity.h"
  #include "gromacs/mdtypes/commrec.h"
 +#include "gromacs/mdtypes/enerdata.h"
  #include "gromacs/mdtypes/fcdata.h"
  #include "gromacs/mdtypes/inputrec.h"
  #include "gromacs/mdtypes/md_enums.h"
 +#include "gromacs/mdtypes/mdrunoptions.h"
  #include "gromacs/mdtypes/observableshistory.h"
  #include "gromacs/mdtypes/state.h"
 +#include "gromacs/nbnxm/gpu_data_mgmt.h"
 +#include "gromacs/nbnxm/nbnxm.h"
 +#include "gromacs/nbnxm/pairlist_tuning.h"
  #include "gromacs/pbcutil/pbc.h"
  #include "gromacs/pulling/output.h"
  #include "gromacs/pulling/pull.h"
  #include "gromacs/taskassignment/resourcedivision.h"
  #include "gromacs/taskassignment/taskassignment.h"
  #include "gromacs/taskassignment/usergpuids.h"
 +#include "gromacs/timing/gpu_timing.h"
  #include "gromacs/timing/wallcycle.h"
 +#include "gromacs/timing/wallcyclereporting.h"
  #include "gromacs/topology/mtop_util.h"
  #include "gromacs/trajectory/trajectoryframe.h"
  #include "gromacs/utility/basenetwork.h"
  #include "gromacs/utility/smalloc.h"
  #include "gromacs/utility/stringutil.h"
  
 -#include "integrator.h"
 +#include "legacysimulator.h"
  #include "replicaexchange.h"
  
  #if GMX_FAHCORE
  namespace gmx
  {
  
 +/*! \brief Log if development feature flags are encountered
 + *
 + * The use of dev features indicated by environment variables is logged
 + * in order to ensure that runs with such featrues enabled can be identified
 + * from their log and standard output.
 + *
 + * \param[in]  mdlog        Logger object.
 + */
 +static void reportDevelopmentFeatures(const gmx::MDLogger &mdlog)
 +{
 +    const bool enableGpuBufOps       = (getenv("GMX_USE_GPU_BUFFER_OPS") != nullptr);
 +    const bool useGpuUpdateConstrain = (getenv("GMX_UPDATE_CONSTRAIN_GPU") != nullptr);
 +
 +    if (enableGpuBufOps)
 +    {
 +        GMX_LOG(mdlog.warning).asParagraph().appendTextFormatted(
 +                "NOTE: This run uses the 'GPU buffer ops' feature, enabled by the GMX_USE_GPU_BUFFER_OPS environment variable.");
 +    }
 +
 +    if (useGpuUpdateConstrain)
 +    {
 +        GMX_LOG(mdlog.warning).asParagraph().appendTextFormatted(
 +                "NOTE: This run uses the 'GPU update/constraints' feature, enabled by the GMX_UPDATE_CONSTRAIN_GPU environment variable.");
 +    }
 +}
 +
  /*! \brief Barrier for safe simultaneous thread access to mdrunner data
   *
   * Used to ensure that the master thread does not modify mdrunner during copy
@@@ -197,13 -163,13 +197,13 @@@ static void threadMpiMdrunnerAccessBarr
  
  Mdrunner Mdrunner::cloneOnSpawnedThread() const
  {
 -    auto newRunner = Mdrunner();
 +    auto newRunner = Mdrunner(std::make_unique<MDModules>());
  
      // All runners in the same process share a restraint manager resource because it is
      // part of the interface to the client code, which is associated only with the
      // original thread. Handles to the same resources can be obtained by copy.
      {
 -        newRunner.restraintManager_ = compat::make_unique<RestraintManager>(*restraintManager_);
 +        newRunner.restraintManager_ = std::make_unique<RestraintManager>(*restraintManager_);
      }
  
      // Copy original cr pointer before master thread can pass the thread barrier
      newRunner.replExParams        = replExParams;
      newRunner.pforce              = pforce;
      newRunner.ms                  = ms;
 -    newRunner.stopHandlerBuilder_ = compat::make_unique<StopHandlerBuilder>(*stopHandlerBuilder_);
 +    newRunner.startingBehavior    = startingBehavior;
 +    newRunner.stopHandlerBuilder_ = std::make_unique<StopHandlerBuilder>(*stopHandlerBuilder_);
  
      threadMpiMdrunnerAccessBarrier();
  
@@@ -274,7 -239,7 +274,7 @@@ t_commrec *Mdrunner::spawnThreads(int n
  
  #if GMX_THREAD_MPI
      /* now spawn new threads that start mdrunner_start_fn(), while
 -       the main thread returns, we set thread affinity later */
 +       the main thread returns. Thread affinity is handled later. */
      if (tMPI_Init_fn(TRUE, numThreadsToLaunch, TMPI_AFFINITY_NONE,
                       mdrunner_start_fn, static_cast<const void*>(this)) != TMPI_SUCCESS)
      {
@@@ -315,8 -280,8 +315,8 @@@ static void prepare_verlet_scheme(FIL
          ListSetupType      listType  = (makeGpuPairList ? ListSetupType::Gpu : ListSetupType::CpuSimdWhenSupported);
          VerletbufListSetup listSetup = verletbufGetSafeListSetup(listType);
  
 -        real               rlist_new;
 -        calc_verlet_buffer_size(mtop, det(box), ir, ir->nstlist, ir->nstlist - 1, -1, &listSetup, nullptr, &rlist_new);
 +        const real         rlist_new =
 +            calcVerletBufferSize(*mtop, det(box), *ir, ir->nstlist, ir->nstlist - 1, -1, listSetup);
  
          if (rlist_new != ir->rlist)
          {
@@@ -452,144 -417,10 +452,144 @@@ static TaskTarget findTaskTarget(const 
      return returnValue;
  }
  
 +//! Finish run, aggregate data to print performance info.
 +static void finish_run(FILE *fplog,
 +                       const gmx::MDLogger &mdlog,
 +                       const t_commrec *cr,
 +                       const t_inputrec *inputrec,
 +                       t_nrnb nrnb[], gmx_wallcycle_t wcycle,
 +                       gmx_walltime_accounting_t walltime_accounting,
 +                       nonbonded_verlet_t *nbv,
 +                       const gmx_pme_t *pme,
 +                       gmx_bool bWriteStat)
 +{
 +    double  delta_t  = 0;
 +    double  nbfs     = 0, mflop = 0;
 +    double  elapsed_time,
 +            elapsed_time_over_all_ranks,
 +            elapsed_time_over_all_threads,
 +            elapsed_time_over_all_threads_over_all_ranks;
 +    /* Control whether it is valid to print a report. Only the
 +       simulation master may print, but it should not do so if the run
 +       terminated e.g. before a scheduled reset step. This is
 +       complicated by the fact that PME ranks are unaware of the
 +       reason why they were sent a pmerecvqxFINISH. To avoid
 +       communication deadlocks, we always do the communication for the
 +       report, even if we've decided not to write the report, because
 +       how long it takes to finish the run is not important when we've
 +       decided not to report on the simulation performance.
 +
 +       Further, we only report performance for dynamical integrators,
 +       because those are the only ones for which we plan to
 +       consider doing any optimizations. */
 +    bool printReport = EI_DYNAMICS(inputrec->eI) && SIMMASTER(cr);
 +
 +    if (printReport && !walltime_accounting_get_valid_finish(walltime_accounting))
 +    {
 +        GMX_LOG(mdlog.warning).asParagraph().appendText("Simulation ended prematurely, no performance report will be written.");
 +        printReport = false;
 +    }
 +
 +    t_nrnb                  *nrnb_tot;
 +    std::unique_ptr<t_nrnb>  nrnbTotalStorage;
 +    if (cr->nnodes > 1)
 +    {
 +        nrnbTotalStorage = std::make_unique<t_nrnb>();
 +        nrnb_tot         = nrnbTotalStorage.get();
 +#if GMX_MPI
 +        MPI_Allreduce(nrnb->n, nrnb_tot->n, eNRNB, MPI_DOUBLE, MPI_SUM,
 +                      cr->mpi_comm_mysim);
 +#endif
 +    }
 +    else
 +    {
 +        nrnb_tot = nrnb;
 +    }
 +
 +    elapsed_time                  = walltime_accounting_get_time_since_reset(walltime_accounting);
 +    elapsed_time_over_all_threads = walltime_accounting_get_time_since_reset_over_all_threads(walltime_accounting);
 +    if (cr->nnodes > 1)
 +    {
 +#if GMX_MPI
 +        /* reduce elapsed_time over all MPI ranks in the current simulation */
 +        MPI_Allreduce(&elapsed_time,
 +                      &elapsed_time_over_all_ranks,
 +                      1, MPI_DOUBLE, MPI_SUM,
 +                      cr->mpi_comm_mysim);
 +        elapsed_time_over_all_ranks /= cr->nnodes;
 +        /* Reduce elapsed_time_over_all_threads over all MPI ranks in the
 +         * current simulation. */
 +        MPI_Allreduce(&elapsed_time_over_all_threads,
 +                      &elapsed_time_over_all_threads_over_all_ranks,
 +                      1, MPI_DOUBLE, MPI_SUM,
 +                      cr->mpi_comm_mysim);
 +#endif
 +    }
 +    else
 +    {
 +        elapsed_time_over_all_ranks                  = elapsed_time;
 +        elapsed_time_over_all_threads_over_all_ranks = elapsed_time_over_all_threads;
 +    }
 +
 +    if (printReport)
 +    {
 +        print_flop(fplog, nrnb_tot, &nbfs, &mflop);
 +    }
 +
 +    if (thisRankHasDuty(cr, DUTY_PP) && DOMAINDECOMP(cr))
 +    {
 +        print_dd_statistics(cr, inputrec, fplog);
 +    }
 +
 +    /* TODO Move the responsibility for any scaling by thread counts
 +     * to the code that handled the thread region, so that there's a
 +     * mechanism to keep cycle counting working during the transition
 +     * to task parallelism. */
 +    int nthreads_pp  = gmx_omp_nthreads_get(emntNonbonded);
 +    int nthreads_pme = gmx_omp_nthreads_get(emntPME);
 +    wallcycle_scale_by_num_threads(wcycle, thisRankHasDuty(cr, DUTY_PME) && !thisRankHasDuty(cr, DUTY_PP), nthreads_pp, nthreads_pme);
 +    auto cycle_sum(wallcycle_sum(cr, wcycle));
 +
 +    if (printReport)
 +    {
 +        auto                    nbnxn_gpu_timings = (nbv != nullptr && nbv->useGpu()) ? Nbnxm::gpu_get_timings(nbv->gpu_nbv) : nullptr;
 +        gmx_wallclock_gpu_pme_t pme_gpu_timings   = {};
 +
 +        if (pme_gpu_task_enabled(pme))
 +        {
 +            pme_gpu_get_timings(pme, &pme_gpu_timings);
 +        }
 +        wallcycle_print(fplog, mdlog, cr->nnodes, cr->npmenodes, nthreads_pp, nthreads_pme,
 +                        elapsed_time_over_all_ranks,
 +                        wcycle, cycle_sum,
 +                        nbnxn_gpu_timings,
 +                        &pme_gpu_timings);
 +
 +        if (EI_DYNAMICS(inputrec->eI))
 +        {
 +            delta_t = inputrec->delta_t;
 +        }
 +
 +        if (fplog)
 +        {
 +            print_perf(fplog, elapsed_time_over_all_threads_over_all_ranks,
 +                       elapsed_time_over_all_ranks,
 +                       walltime_accounting_get_nsteps_done_since_reset(walltime_accounting),
 +                       delta_t, nbfs, mflop);
 +        }
 +        if (bWriteStat)
 +        {
 +            print_perf(stderr, elapsed_time_over_all_threads_over_all_ranks,
 +                       elapsed_time_over_all_ranks,
 +                       walltime_accounting_get_nsteps_done_since_reset(walltime_accounting),
 +                       delta_t, nbfs, mflop);
 +        }
 +    }
 +}
 +
  int Mdrunner::mdrunner()
  {
      matrix                    box;
 -    t_nrnb                   *nrnb;
      t_forcerec               *fr               = nullptr;
      t_fcdata                 *fcd              = nullptr;
      real                      ewaldcoeff_q     = 0;
      int                       nChargePerturbed = -1, nTypePerturbed = 0;
      gmx_wallcycle_t           wcycle;
      gmx_walltime_accounting_t walltime_accounting = nullptr;
 -    int                       rc;
 -    int64_t                   reset_counters;
 -    int                       nthreads_pme = 1;
 -    gmx_membed_t *            membed       = nullptr;
 -    gmx_hw_info_t            *hwinfo       = nullptr;
 +    gmx_membed_t *            membed              = nullptr;
 +    gmx_hw_info_t            *hwinfo              = nullptr;
  
      /* CAUTION: threads may be started later on in this function, so
         cr doesn't reflect the final parallel state right now */
 -    std::unique_ptr<gmx::MDModules> mdModules(new gmx::MDModules);
      t_inputrec                      inputrecInstance;
      t_inputrec                     *inputrec = &inputrecInstance;
      gmx_mtop_t                      mtop;
      // Handle task-assignment related user options.
      EmulateGpuNonbonded emulateGpuNonbonded = (getenv("GMX_EMULATE_GPU") != nullptr ?
                                                 EmulateGpuNonbonded::Yes : EmulateGpuNonbonded::No);
 -    std::vector<int>    gpuIdsAvailable;
 -    try
 -    {
 -        gpuIdsAvailable = parseUserGpuIds(hw_opt.gpuIdsAvailable);
 -        // TODO We could put the GPU IDs into a std::map to find
 -        // duplicates, but for the small numbers of IDs involved, this
 -        // code is simple and fast.
 -        for (size_t i = 0; i != gpuIdsAvailable.size(); ++i)
 -        {
 -            for (size_t j = i+1; j != gpuIdsAvailable.size(); ++j)
 -            {
 -                if (gpuIdsAvailable[i] == gpuIdsAvailable[j])
 -                {
 -                    GMX_THROW(InvalidInputError(formatString("The string of available GPU device IDs '%s' may not contain duplicate device IDs", hw_opt.gpuIdsAvailable.c_str())));
 -                }
 -            }
 -        }
 -    }
 -    GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
  
      std::vector<int> userGpuTaskAssignment;
      try
      {
 -        userGpuTaskAssignment = parseUserGpuIds(hw_opt.userGpuTaskAssignment);
 +        userGpuTaskAssignment = parseUserTaskAssignmentString(hw_opt.userGpuTaskAssignment);
      }
      GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
      auto       nonbondedTarget = findTaskTarget(nbpu_opt);
      // to check that the old log file matches what the checkpoint file
      // expects. Otherwise, we should start to write log output now if
      // there is a file ready for it.
 -    if (logFileHandle != nullptr && !mdrunOptions.continuationOptions.appendFiles)
 +    if (logFileHandle != nullptr && startingBehavior != StartingBehavior::RestartWithAppending)
      {
          fplog = gmx_fio_getfp(logFileHandle);
      }
      gmx::LoggerOwner logOwner(buildLogger(fplog, cr));
      gmx::MDLogger    mdlog(logOwner.logger());
  
 -    // TODO The thread-MPI master rank makes a working
 -    // PhysicalNodeCommunicator here, but it gets rebuilt by all ranks
 -    // after the threads have been launched. This works because no use
 -    // is made of that communicator until after the execution paths
 -    // have rejoined. But it is likely that we can improve the way
 -    // this is expressed, e.g. by expressly running detection only the
 -    // master rank for thread-MPI, rather than relying on the mutex
 -    // and reference count.
 +    // report any development features that may be enabled by environment variables
 +    reportDevelopmentFeatures(mdlog);
 +
 +    // With thread-MPI, the communicator changes after threads are
 +    // launched, so this is rebuilt for the master rank at that
 +    // time. The non-master ranks are fine to keep the one made here.
      PhysicalNodeCommunicator physicalNodeComm(MPI_COMM_WORLD, gmx_physicalnode_id_hash());
      hwinfo = gmx_detect_hardware(mdlog, physicalNodeComm);
  
 -    gmx_print_detected_hardware(fplog, cr, ms, mdlog, hwinfo);
 +    gmx_print_detected_hardware(fplog, isMasterSimMasterRank(ms, MASTER(cr)), mdlog, hwinfo);
  
 -    std::vector<int> gpuIdsToUse;
 -    auto             compatibleGpus = getCompatibleGpus(hwinfo->gpu_info);
 -    if (gpuIdsAvailable.empty())
 -    {
 -        gpuIdsToUse = compatibleGpus;
 -    }
 -    else
 -    {
 -        for (const auto &availableGpuId : gpuIdsAvailable)
 -        {
 -            bool availableGpuIsCompatible = false;
 -            for (const auto &compatibleGpuId : compatibleGpus)
 -            {
 -                if (availableGpuId == compatibleGpuId)
 -                {
 -                    availableGpuIsCompatible = true;
 -                    break;
 -                }
 -            }
 -            if (!availableGpuIsCompatible)
 -            {
 -                gmx_fatal(FARGS, "You limited the set of compatible GPUs to a set that included ID #%d, but that ID is not for a compatible GPU. List only compatible GPUs.", availableGpuId);
 -            }
 -            gpuIdsToUse.push_back(availableGpuId);
 -        }
 -    }
 +    std::vector<int> gpuIdsToUse = makeGpuIdsToUse(hwinfo->gpu_info, hw_opt.gpuIdsAvailable);
  
 -    if (fplog != nullptr)
 -    {
 -        /* Print references after all software/hardware printing */
 -        please_cite(fplog, "Abraham2015");
 -        please_cite(fplog, "Pall2015");
 -        please_cite(fplog, "Pronk2013");
 -        please_cite(fplog, "Hess2008b");
 -        please_cite(fplog, "Spoel2005a");
 -        please_cite(fplog, "Lindahl2001a");
 -        please_cite(fplog, "Berendsen95a");
 -        writeSourceDoi(fplog);
 -    }
 +    // Print citation requests after all software/hardware printing
 +    pleaseCiteGromacs(fplog);
  
      std::unique_ptr<t_state> globalState;
  
      if (SIMMASTER(cr))
      {
          /* Only the master rank has the global state */
 -        globalState = compat::make_unique<t_state>();
 +        globalState = std::make_unique<t_state>();
  
          /* Read (nearly) all data required for the simulation */
          read_tpx_state(ftp2fn(efTPR, filenames.size(), filenames.data()), inputrec, globalState.get(), &mtop);
 -
 -        /* In rerun, set velocities to zero if present */
 -        if (doRerun && ((globalState->flags & (1 << estV)) != 0))
 -        {
 -            // rerun does not use velocities
 -            GMX_LOG(mdlog.info).asParagraph().appendText(
 -                    "Rerun trajectory contains velocities. Rerun does only evaluate "
 -                    "potential energy and forces. The velocities will be ignored.");
 -            for (int i = 0; i < globalState->natoms; i++)
 -            {
 -                clear_rvec(globalState->v[i]);
 -            }
 -            globalState->flags &= ~(1 << estV);
 -        }
 -
 -        if (inputrec->cutoff_scheme != ecutsVERLET)
 -        {
 -            if (nstlist_cmdline > 0)
 -            {
 -                gmx_fatal(FARGS, "Can not set nstlist with the group cut-off scheme");
 -            }
 -
 -            if (!compatibleGpus.empty())
 -            {
 -                GMX_LOG(mdlog.warning).asParagraph().appendText(
 -                        "NOTE: GPU(s) found, but the current simulation can not use GPUs\n"
 -                        "      To use a GPU, set the mdp option: cutoff-scheme = Verlet");
 -            }
 -        }
      }
  
      /* Check and update the hardware options for internal consistency */
 -    check_and_update_hw_opt_1(mdlog, &hw_opt, cr, domdecOptions.numPmeRanks);
 -
 -    /* Early check for externally set process affinity. */
 -    gmx_check_thread_affinity_set(mdlog, cr,
 -                                  &hw_opt, hwinfo->nthreads_hw_avail, FALSE);
 +    checkAndUpdateHardwareOptions(mdlog, &hw_opt, SIMMASTER(cr), domdecOptions.numPmeRanks);
  
      if (GMX_THREAD_MPI && SIMMASTER(cr))
      {
 -        if (domdecOptions.numPmeRanks > 0 && hw_opt.nthreads_tmpi <= 0)
 -        {
 -            gmx_fatal(FARGS, "You need to explicitly specify the number of MPI threads (-ntmpi) when using separate PME ranks");
 -        }
 -
 -        /* Since the master knows the cut-off scheme, update hw_opt for this.
 -         * This is done later for normal MPI and also once more with tMPI
 -         * for all tMPI ranks.
 -         */
 -        check_and_update_hw_opt_2(&hw_opt, inputrec->cutoff_scheme);
 -
          bool useGpuForNonbonded = false;
          bool useGpuForPme       = false;
          try
              useGpuForNonbonded = decideWhetherToUseGpusForNonbondedWithThreadMpi
                      (nonbondedTarget, gpuIdsToUse, userGpuTaskAssignment, emulateGpuNonbonded,
                      canUseGpuForNonbonded,
 -                    inputrec->cutoff_scheme == ecutsVERLET,
                      gpuAccelerationOfNonbondedIsUseful(mdlog, inputrec, GMX_THREAD_MPI),
                      hw_opt.nthreads_tmpi);
              useGpuForPme = decideWhetherToUseGpusForPmeWithThreadMpi
          // handle. If unsuitable, we will notice that during task
          // assignment.
          bool gpusWereDetected      = hwinfo->ngpu_compatible_tot > 0;
 -        bool usingVerletScheme     = inputrec->cutoff_scheme == ecutsVERLET;
          auto canUseGpuForNonbonded = buildSupportsNonbondedOnGpu(nullptr);
          useGpuForNonbonded = decideWhetherToUseGpusForNonbonded(nonbondedTarget, userGpuTaskAssignment,
                                                                  emulateGpuNonbonded,
                                                                  canUseGpuForNonbonded,
 -                                                                usingVerletScheme,
                                                                  gpuAccelerationOfNonbondedIsUseful(mdlog, inputrec, !GMX_THREAD_MPI),
                                                                  gpusWereDetected);
          useGpuForPme = decideWhetherToUseGpusForPme(useGpuForNonbonded, pmeTarget, userGpuTaskAssignment,
                                                      gpusWereDetected);
          auto canUseGpuForBonded = buildSupportsGpuBondeds(nullptr) && inputSupportsGpuBondeds(*inputrec, mtop, nullptr);
          useGpuForBonded =
 -            decideWhetherToUseGpusForBonded(useGpuForNonbonded, useGpuForPme, usingVerletScheme,
 +            decideWhetherToUseGpusForBonded(useGpuForNonbonded, useGpuForPme,
                                              bondedTarget, canUseGpuForBonded,
                                              EVDW_PME(inputrec->vdwtype),
                                              EEL_PME_EWALD(inputrec->coulombtype),
      // TODO: hide restraint implementation details from Mdrunner.
      // There is nothing unique about restraints at this point as far as the
      // Mdrunner is concerned. The Mdrunner should just be getting a sequence of
 -    // factory functions from the SimulationContext on which to call mdModules->add().
 +    // factory functions from the SimulationContext on which to call mdModules_->add().
      // TODO: capture all restraints into a single RestraintModule, passed to the runner builder.
      for (auto && restraint : restraintManager_->getRestraints())
      {
          auto module = RestraintMDModule::create(restraint,
                                                  restraint->sites());
 -        mdModules->add(std::move(module));
 +        mdModules_->add(std::move(module));
      }
  
      // TODO: Error handling
 -    mdModules->assignOptionsToModules(*inputrec->params, nullptr);
 +    mdModules_->assignOptionsToModules(*inputrec->params, nullptr);
  
      if (fplog != nullptr)
      {
  
      if (SIMMASTER(cr))
      {
 +        /* In rerun, set velocities to zero if present */
 +        if (doRerun && ((globalState->flags & (1 << estV)) != 0))
 +        {
 +            // rerun does not use velocities
 +            GMX_LOG(mdlog.info).asParagraph().appendText(
 +                    "Rerun trajectory contains velocities. Rerun does only evaluate "
 +                    "potential energy and forces. The velocities will be ignored.");
 +            for (int i = 0; i < globalState->natoms; i++)
 +            {
 +                clear_rvec(globalState->v[i]);
 +            }
 +            globalState->flags &= ~(1 << estV);
 +        }
 +
          /* now make sure the state is initialized and propagated */
          set_state_entries(globalState.get(), inputrec);
      }
      {
          if (!MASTER(cr))
          {
 -            globalState = compat::make_unique<t_state>();
 +            globalState = std::make_unique<t_state>();
          }
          broadcastStateWithoutDynamics(cr, globalState.get());
      }
          gmx_fatal(FARGS, "The .mdp file specified an energy mininization or normal mode algorithm, and these are not compatible with mdrun -rerun");
      }
  
 -    if (can_use_allvsall(inputrec, TRUE, cr, fplog) && DOMAINDECOMP(cr))
 -    {
 -        gmx_fatal(FARGS, "All-vs-all loops do not work with domain decomposition, use a single MPI rank");
 -    }
 -
      if (!(EEL_PME(inputrec->coulombtype) || EVDW_PME(inputrec->vdwtype)))
      {
          if (domdecOptions.numPmeRanks > 0)
  
      ObservablesHistory   observablesHistory = {};
  
 -    ContinuationOptions &continuationOptions = mdrunOptions.continuationOptions;
 -
 -    if (continuationOptions.startedFromCheckpoint)
 +    if (startingBehavior != StartingBehavior::NewSimulation)
      {
 -        gmx_bool bReadEkin;
 -
+         /* Check if checkpoint file exists before doing continuation.
+          * This way we can use identical input options for the first and subsequent runs...
+          */
+         if (mdrunOptions.numStepsCommandline > -2)
+         {
+             /* Temporarily set the number of steps to unmlimited to avoid
+              * triggering the nsteps check in load_checkpoint().
+              * This hack will go away soon when the -nsteps option is removed.
+              */
+             inputrec->nsteps = -1;
+         }
          load_checkpoint(opt2fn_master("-cpi", filenames.size(), filenames.data(), cr),
                          logFileHandle,
                          cr, domdecOptions.numCells,
                          inputrec, globalState.get(),
 -                        &bReadEkin, &observablesHistory,
 -                        continuationOptions.appendFiles,
 -                        continuationOptions.appendFilesOptionSet,
 +                        &observablesHistory,
                          mdrunOptions.reproducible);
  
 -        if (bReadEkin)
 -        {
 -            continuationOptions.haveReadEkin = true;
 -        }
 -
 -        if (continuationOptions.appendFiles && logFileHandle)
 +        if (startingBehavior == StartingBehavior::RestartWithAppending && logFileHandle)
          {
              // Now we can start normal logging to the truncated log file.
              fplog    = gmx_fio_getfp(logFileHandle);
          gmx_bcast(sizeof(box), box, cr);
      }
  
 -    /* Update rlist and nstlist. */
 -    if (inputrec->cutoff_scheme == ecutsVERLET)
 +    if (inputrec->cutoff_scheme != ecutsVERLET)
      {
 -        prepare_verlet_scheme(fplog, cr, inputrec, nstlist_cmdline, &mtop, box,
 -                              useGpuForNonbonded || (emulateGpuNonbonded == EmulateGpuNonbonded::Yes), *hwinfo->cpuInfo);
 +        gmx_fatal(FARGS, "This group-scheme .tpr file can no longer be run by mdrun. Please update to the Verlet scheme, or use an earlier version of GROMACS if necessary.");
      }
 +    /* Update rlist and nstlist. */
 +    prepare_verlet_scheme(fplog, cr, inputrec, nstlist_cmdline, &mtop, box,
 +                          useGpuForNonbonded || (emulateGpuNonbonded == EmulateGpuNonbonded::Yes), *hwinfo->cpuInfo);
  
      LocalAtomSetManager atomSets;
  
      fflush(stderr);
  #endif
  
 -    /* Check and update hw_opt for the cut-off scheme */
 -    check_and_update_hw_opt_2(&hw_opt, inputrec->cutoff_scheme);
 -
 +    // If mdrun -pin auto honors any affinity setting that already
 +    // exists. If so, it is nice to provide feedback about whether
 +    // that existing affinity setting was from OpenMP or something
 +    // else, so we run this code both before and after we initialize
 +    // the OpenMP support.
 +    gmx_check_thread_affinity_set(mdlog,
 +                                  &hw_opt, hwinfo->nthreads_hw_avail, FALSE);
      /* Check and update the number of OpenMP threads requested */
      checkAndUpdateRequestedNumOpenmpThreads(&hw_opt, *hwinfo, cr, ms, physicalNodeComm.size_,
                                              pmeRunMode, mtop);
                            physicalNodeComm.size_,
                            hw_opt.nthreads_omp,
                            hw_opt.nthreads_omp_pme,
 -                          !thisRankHasDuty(cr, DUTY_PP),
 -                          inputrec->cutoff_scheme == ecutsVERLET);
 +                          !thisRankHasDuty(cr, DUTY_PP));
  
 -    // Enable FP exception detection for the Verlet scheme, but not in
 +    // Enable FP exception detection, but not in
      // Release mode and not for compilers with known buggy FP
      // exception support (clang with any optimization) or suspected
      // buggy FP exception support (gcc 7.* with optimization).
  #if !defined NDEBUG && \
      !((defined __clang__ || (defined(__GNUC__) && !defined(__ICC) && __GNUC__ == 7)) \
      && defined __OPTIMIZE__)
 -    const bool bEnableFPE = inputrec->cutoff_scheme == ecutsVERLET;
 +    const bool bEnableFPE = true;
  #else
      const bool bEnableFPE = false;
  #endif
              }
              else if (nonbondedTarget == TaskTarget::Gpu)
              {
 -                gmx_fatal(FARGS, "Cannot run short-ranged nonbonded interactions on a GPU because there is none detected.");
 +                gmx_fatal(FARGS, "Cannot run short-ranged nonbonded interactions on a GPU because no GPU is detected.");
              }
              else if (bondedTarget == TaskTarget::Gpu)
              {
 -                gmx_fatal(FARGS, "Cannot run bonded interactions on a GPU because there is none detected.");
 +                gmx_fatal(FARGS, "Cannot run bonded interactions on a GPU because no GPU is detected.");
              }
          }
      }
              }
              else if (pmeTarget == TaskTarget::Gpu)
              {
 -                gmx_fatal(FARGS, "Cannot run PME on a GPU because there is none detected.");
 +                gmx_fatal(FARGS, "Cannot run PME on a GPU because no GPU is detected.");
              }
          }
      }
          }
      }
  
 -    /* getting number of PP/PME threads
 +    /* getting number of PP/PME threads on this MPI / tMPI rank.
         PME: env variable should be read only on one node to make sure it is
         identical everywhere;
       */
 -    nthreads_pme = gmx_omp_nthreads_get(emntPME);
 -
 -    int numThreadsOnThisRank;
 -    /* threads on this MPI process or TMPI thread */
 -    if (thisRankHasDuty(cr, DUTY_PP))
 -    {
 -        numThreadsOnThisRank = gmx_omp_nthreads_get(emntNonbonded);
 -    }
 -    else
 -    {
 -        numThreadsOnThisRank = nthreads_pme;
 -    }
 -
 +    const int numThreadsOnThisRank =
 +        thisRankHasDuty(cr, DUTY_PP) ? gmx_omp_nthreads_get(emntNonbonded) : gmx_omp_nthreads_get(emntPME);
      checkHardwareOversubscription(numThreadsOnThisRank, cr->nodeid,
                                    *hwinfo->hardwareTopology,
                                    physicalNodeComm, mdlog);
  
 -    if (hw_opt.thread_affinity != threadaffOFF)
 +    if (hw_opt.threadAffinity != ThreadAffinity::Off)
      {
          /* Before setting affinity, check whether the affinity has changed
           * - which indicates that probably the OpenMP library has changed it
           * since we first checked).
           */
 -        gmx_check_thread_affinity_set(mdlog, cr,
 +        gmx_check_thread_affinity_set(mdlog,
                                        &hw_opt, hwinfo->nthreads_hw_avail, TRUE);
  
          int numThreadsOnThisNode, intraNodeThreadOffset;
      {
          /* Master synchronizes its value of reset_counters with all nodes
           * including PME only nodes */
 -        reset_counters = wcycle_get_reset_counters(wcycle);
 +        int64_t reset_counters = wcycle_get_reset_counters(wcycle);
          gmx_bcast_sim(sizeof(reset_counters), &reset_counters, cr);
          wcycle_set_reset_counters(wcycle, reset_counters);
      }
      std::unique_ptr<MDAtoms>     mdAtoms;
      std::unique_ptr<gmx_vsite_t> vsite;
  
 -    snew(nrnb, 1);
 +    t_nrnb nrnb;
      if (thisRankHasDuty(cr, DUTY_PP))
      {
          /* Initiate forcerecord */
 -        fr                 = mk_forcerec();
 -        fr->forceProviders = mdModules->initForceProviders();
 +        fr                 = new t_forcerec;
 +        fr->forceProviders = mdModules_->initForceProviders();
          init_forcerec(fplog, mdlog, fr, fcd,
                        inputrec, &mtop, cr, box,
                        opt2fn("-table", filenames.size(), filenames.data()),
                        *hwinfo, nonbondedDeviceInfo,
                        useGpuForBonded,
                        FALSE,
 -                      pforce);
 +                      pforce,
 +                      wcycle);
  
          /* Initialize the mdAtoms structure.
           * mdAtoms is not filled with atom data,
                  pmedata = gmx_pme_init(cr,
                                         getNumPmeDomains(cr->dd),
                                         inputrec,
 -                                       mtop.natoms, nChargePerturbed != 0, nTypePerturbed != 0,
 +                                       nChargePerturbed != 0, nTypePerturbed != 0,
                                         mdrunOptions.reproducible,
                                         ewaldcoeff_q, ewaldcoeff_lj,
 -                                       nthreads_pme,
 +                                       gmx_omp_nthreads_get(emntPME),
                                         pmeRunMode, nullptr,
                                         pmeDeviceInfo, pmeGpuProgram.get(), mdlog);
              }
          signal_handler_install();
      }
  
 +    pull_t *pull_work = nullptr;
      if (thisRankHasDuty(cr, DUTY_PP))
      {
          /* Assumes uniform use of the number of OpenMP threads */
          if (inputrec->bPull)
          {
              /* Initialize pull code */
 -            inputrec->pull_work =
 +            pull_work =
                  init_pull(fplog, inputrec->pull, inputrec,
                            &mtop, cr, &atomSets, inputrec->fepvals->init_lambda);
              if (inputrec->pull->bXOutAverage || inputrec->pull->bFOutAverage)
              {
 -                initPullHistory(inputrec->pull_work, &observablesHistory);
 +                initPullHistory(pull_work, &observablesHistory);
              }
              if (EI_DYNAMICS(inputrec->eI) && MASTER(cr))
              {
 -                init_pull_output_files(inputrec->pull_work,
 +                init_pull_output_files(pull_work,
                                         filenames.size(), filenames.data(), oenv,
 -                                       continuationOptions);
 +                                       startingBehavior);
              }
          }
  
                                          globalState.get(),
                                          &mtop,
                                          oenv,
 -                                        mdrunOptions);
 +                                        mdrunOptions,
 +                                        startingBehavior);
          }
  
 +        t_swap *swap = nullptr;
          if (inputrec->eSwapCoords != eswapNO)
          {
              /* Initialize ion swapping code */
 -            init_swapcoords(fplog, inputrec, opt2fn_master("-swap", filenames.size(), filenames.data(), cr),
 -                            &mtop, globalState.get(), &observablesHistory,
 -                            cr, &atomSets, oenv, mdrunOptions);
 +            swap = init_swapcoords(fplog, inputrec,
 +                                   opt2fn_master("-swap", filenames.size(), filenames.data(), cr),
 +                                   &mtop, globalState.get(), &observablesHistory,
 +                                   cr, &atomSets, oenv, mdrunOptions,
 +                                   startingBehavior);
          }
  
          /* Let makeConstraints know whether we have essential dynamics constraints.
           */
          bool doEssentialDynamics = (opt2fn_null("-ei", filenames.size(), filenames.data()) != nullptr
                                      || observablesHistory.edsamHistory);
 -        auto constr              = makeConstraints(mtop, *inputrec, doEssentialDynamics,
 +        auto constr              = makeConstraints(mtop, *inputrec, pull_work, doEssentialDynamics,
                                                     fplog, *mdAtoms->mdatoms(),
 -                                                   cr, ms, nrnb, wcycle, fr->bMolPBC);
 +                                                   cr, ms, &nrnb, wcycle, fr->bMolPBC);
 +
 +        /* Energy terms and groups */
 +        gmx_enerdata_t enerd(mtop.groups.groups[SimulationAtomGroupType::EnergyOutput].size(), inputrec->fepvals->n_lambda);
 +
 +        /* Set up interactive MD (IMD) */
 +        auto imdSession = makeImdSession(inputrec, cr, wcycle, &enerd, ms, &mtop, mdlog,
 +                                         MASTER(cr) ? globalState->x.rvec_array() : nullptr,
 +                                         filenames.size(), filenames.data(), oenv, mdrunOptions.imdOptions,
 +                                         startingBehavior);
  
          if (DOMAINDECOMP(cr))
          {
          // understood.
          PpForceWorkload ppForceWorkload;
  
 -        GMX_ASSERT(stopHandlerBuilder_, "Runner must provide StopHandlerBuilder to integrator.");
 +        GMX_ASSERT(stopHandlerBuilder_, "Runner must provide StopHandlerBuilder to simulator.");
          /* Now do whatever the user wants us to do (how flexible...) */
 -        Integrator integrator {
 +        LegacySimulator simulator {
              fplog, cr, ms, mdlog, static_cast<int>(filenames.size()), filenames.data(),
              oenv,
              mdrunOptions,
 +            startingBehavior,
              vsite.get(), constr.get(),
              enforcedRotation ? enforcedRotation->getLegacyEnfrot() : nullptr,
              deform.get(),
 -            mdModules->outputProvider(),
 -            inputrec, &mtop,
 +            mdModules_->outputProvider(),
 +            inputrec, imdSession.get(), pull_work, swap, &mtop,
              fcd,
              globalState.get(),
              &observablesHistory,
 -            mdAtoms.get(), nrnb, wcycle, fr,
 +            mdAtoms.get(), &nrnb, wcycle, fr,
 +            &enerd,
              &ppForceWorkload,
              replExParams,
              membed,
              walltime_accounting,
              std::move(stopHandlerBuilder_)
          };
 -        integrator.run(inputrec->eI, doRerun);
 +        simulator.run(inputrec->eI, doRerun);
  
          if (inputrec->bPull)
          {
 -            finish_pull(inputrec->pull_work);
 +            finish_pull(pull_work);
          }
 -
 +        finish_swapcoords(swap);
      }
      else
      {
          GMX_RELEASE_ASSERT(pmedata, "pmedata was NULL while cr->duty was not DUTY_PP");
          /* do PME only */
          walltime_accounting = walltime_accounting_init(gmx_omp_nthreads_get(emntPME));
 -        gmx_pmeonly(pmedata, cr, nrnb, wcycle, walltime_accounting, inputrec, pmeRunMode);
 +        gmx_pmeonly(pmedata, cr, &nrnb, wcycle, walltime_accounting, inputrec, pmeRunMode);
      }
  
      wallcycle_stop(wcycle, ewcRUN);
       * if rerunMD, don't write last frame again
       */
      finish_run(fplog, mdlog, cr,
 -               inputrec, nrnb, wcycle, walltime_accounting,
 -               fr ? fr->nbv : nullptr,
 +               inputrec, &nrnb, wcycle, walltime_accounting,
 +               fr ? fr->nbv.get() : nullptr,
                 pmedata,
                 EI_DYNAMICS(inputrec->eI) && !isMultiSim(ms));
  
 -    // Free PME data
 +    // clean up cycle counter
 +    wallcycle_destroy(wcycle);
 +
 +// Free PME data
      if (pmedata)
      {
          gmx_pme_destroy(pmedata);
      // As soon as we destroy GPU contexts after mdrunner() exits, these lines should go.
      mdAtoms.reset(nullptr);
      globalState.reset(nullptr);
 -    mdModules.reset(nullptr);   // destruct force providers here as they might also use the GPU
 +    mdModules_.reset(nullptr);   // destruct force providers here as they might also use the GPU
  
      /* Free GPU memory and set a physical node tMPI barrier (which should eventually go away) */
 -    free_gpu_resources(fr, physicalNodeComm);
 +    free_gpu_resources(fr, physicalNodeComm, hwinfo->gpu_info);
      free_gpu(nonbondedDeviceInfo);
      free_gpu(pmeDeviceInfo);
 -    done_forcerec(fr, mtop.molblock.size(), mtop.groups.grps[egcENER].nr);
 +    done_forcerec(fr, mtop.molblock.size());
      sfree(fcd);
  
      if (doMembed)
          free_membed(membed);
      }
  
 -    gmx_hardware_info_free();
 -
      /* Does what it says */
      print_date_and_time(fplog, cr->nodeid, "Finished mdrun", gmx_gettime());
      walltime_accounting_destroy(walltime_accounting);
 -    sfree(nrnb);
  
      // Ensure log file content is written
      if (logFileHandle)
          gmx_fedisableexcept();
      }
  
 -    rc = static_cast<int>(gmx_get_stop_condition());
 +    auto rc = static_cast<int>(gmx_get_stop_condition());
  
  #if GMX_THREAD_MPI
      /* we need to join all threads. The sub-threads join when they
         wait for that. */
      if (PAR(cr) && MASTER(cr))
      {
 -        done_commrec(cr);
          tMPI_Finalize();
      }
 +    //TODO free commrec in MPI simulations
 +    done_commrec(cr);
  #endif
 -
      return rc;
  }
  
@@@ -1619,11 -1561,6 +1631,11 @@@ void Mdrunner::addPotential(std::shared
                                   std::move(name));
  }
  
 +Mdrunner::Mdrunner(std::unique_ptr<MDModules> mdModules)
 +    : mdModules_(std::move(mdModules))
 +{
 +}
 +
  Mdrunner::Mdrunner(Mdrunner &&) noexcept = default;
  
  //NOLINTNEXTLINE(performance-noexcept-move-constructor) working around GCC bug 58265
@@@ -1633,13 -1570,11 +1645,13 @@@ class Mdrunner::BuilderImplementatio
  {
      public:
          BuilderImplementation() = delete;
 -        explicit BuilderImplementation(SimulationContext* context);
 +        BuilderImplementation(std::unique_ptr<MDModules> mdModules,
 +                              SimulationContext        * context);
          ~BuilderImplementation();
  
          BuilderImplementation &setExtraMdrunOptions(const MdrunOptions &options,
 -                                                    real                forceWarningThreshold);
 +                                                    real                forceWarningThreshold,
 +                                                    StartingBehavior    startingBehavior);
  
          void addDomdec(const DomdecOptions &options);
  
          Mdrunner build();
  
      private:
 +
          // Default parameters copied from runner.h
          // \todo Clarify source(s) of default parameters.
  
          //! Print a warning if any force is larger than this (in kJ/mol nm).
          real forceWarningThreshold_ = -1;
  
 +        //! Whether the simulation will start afresh, or restart with/without appending.
 +        StartingBehavior startingBehavior_ = StartingBehavior::NewSimulation;
 +
 +        //! The modules that comprise the functionality of mdrun.
 +        std::unique_ptr<MDModules> mdModules_;
 +
          /*! \brief  Non-owning pointer to SimulationContext (owned and managed by client)
           *
           * \internal
          std::unique_ptr<StopHandlerBuilder> stopHandlerBuilder_ = nullptr;
  };
  
 -Mdrunner::BuilderImplementation::BuilderImplementation(SimulationContext* context) :
 +Mdrunner::BuilderImplementation::BuilderImplementation(std::unique_ptr<MDModules> mdModules,
 +                                                       SimulationContext        * context) :
 +    mdModules_(std::move(mdModules)),
      context_(context)
  {
      GMX_ASSERT(context_, "Bug found. It should not be possible to construct builder without a valid context.");
  Mdrunner::BuilderImplementation::~BuilderImplementation() = default;
  
  Mdrunner::BuilderImplementation &
 -Mdrunner::BuilderImplementation::setExtraMdrunOptions(const MdrunOptions &options,
 -                                                      real                forceWarningThreshold)
 +Mdrunner::BuilderImplementation::setExtraMdrunOptions(const MdrunOptions    &options,
 +                                                      const real             forceWarningThreshold,
 +                                                      const StartingBehavior startingBehavior)
  {
      mdrunOptions_          = options;
      forceWarningThreshold_ = forceWarningThreshold;
 +    startingBehavior_      = startingBehavior;
      return *this;
  }
  
@@@ -1775,19 -1699,17 +1787,19 @@@ void Mdrunner::BuilderImplementation::a
  
  void Mdrunner::BuilderImplementation::addMultiSim(gmx_multisim_t* multisim)
  {
 -    multisim_ = compat::make_unique<gmx_multisim_t*>(multisim);
 +    multisim_ = std::make_unique<gmx_multisim_t*>(multisim);
  }
  
  Mdrunner Mdrunner::BuilderImplementation::build()
  {
 -    auto newRunner = Mdrunner();
 +    auto newRunner = Mdrunner(std::move(mdModules_));
  
      GMX_ASSERT(context_, "Bug found. It should not be possible to call build() without a valid context.");
  
 -    newRunner.mdrunOptions    = mdrunOptions_;
 -    newRunner.domdecOptions   = domdecOptions_;
 +    newRunner.mdrunOptions          = mdrunOptions_;
 +    newRunner.pforce                = forceWarningThreshold_;
 +    newRunner.startingBehavior      = startingBehavior_;
 +    newRunner.domdecOptions         = domdecOptions_;
  
      // \todo determine an invariant to check or confirm that all gmx_hw_opt_t objects are valid
      newRunner.hw_opt          = hardwareOptions_;
          GMX_THROW(gmx::APIError("MdrunnerBuilder::addBondedTaskAssignment() is required before build()"));
      }
  
 -    newRunner.restraintManager_ = compat::make_unique<gmx::RestraintManager>();
 +    newRunner.restraintManager_ = std::make_unique<gmx::RestraintManager>();
  
      if (stopHandlerBuilder_)
      {
      }
      else
      {
 -        newRunner.stopHandlerBuilder_ = compat::make_unique<StopHandlerBuilder>();
 +        newRunner.stopHandlerBuilder_ = std::make_unique<StopHandlerBuilder>();
      }
  
      return newRunner;
@@@ -1910,19 -1832,17 +1922,19 @@@ void Mdrunner::BuilderImplementation::a
      stopHandlerBuilder_ = std::move(builder);
  }
  
 -MdrunnerBuilder::MdrunnerBuilder(compat::not_null<SimulationContext*> context) :
 -    impl_ {gmx::compat::make_unique<Mdrunner::BuilderImplementation>(context)}
 +MdrunnerBuilder::MdrunnerBuilder(std::unique_ptr<MDModules>           mdModules,
 +                                 compat::not_null<SimulationContext*> context) :
 +    impl_ {std::make_unique<Mdrunner::BuilderImplementation>(std::move(mdModules), context)}
  {
  }
  
  MdrunnerBuilder::~MdrunnerBuilder() = default;
  
 -MdrunnerBuilder &MdrunnerBuilder::addSimulationMethod(const MdrunOptions &options,
 -                                                      real                forceWarningThreshold)
 +MdrunnerBuilder &MdrunnerBuilder::addSimulationMethod(const MdrunOptions    &options,
 +                                                      real                   forceWarningThreshold,
 +                                                      const StartingBehavior startingBehavior)
  {
 -    impl_->setExtraMdrunOptions(options, forceWarningThreshold);
 +    impl_->setExtraMdrunOptions(options, forceWarningThreshold, startingBehavior);
      return *this;
  }
  
index ee890ba7a4367913cf86180efbde8d697f35d90c,0000000000000000000000000000000000000000..144f194cb5d9630337142beda35b25415e007392
mode 100644,000000..100644
--- /dev/null
@@@ -1,4338 -1,0 +1,4355 @@@
-     int ncjTotal = 0;
-     for (auto &src : srcSet)
-     {
-         ncjTotal += src.ncjInUse;
-     }
 +/*
 + * This file is part of the GROMACS molecular simulation package.
 + *
 + * Copyright (c) 2012,2013,2014,2015,2016,2017,2018,2019, by the GROMACS development team, led by
 + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
 + * and including many others, as listed in the AUTHORS file in the
 + * top-level source directory and at http://www.gromacs.org.
 + *
 + * GROMACS is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public License
 + * as published by the Free Software Foundation; either version 2.1
 + * of the License, or (at your option) any later version.
 + *
 + * GROMACS is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * Lesser General Public License for more details.
 + *
 + * You should have received a copy of the GNU Lesser General Public
 + * License along with GROMACS; if not, see
 + * http://www.gnu.org/licenses, or write to the Free Software Foundation,
 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
 + *
 + * If you want to redistribute modifications to GROMACS, please
 + * consider that scientific software is very special. Version
 + * control is crucial - bugs must be traceable. We will be happy to
 + * consider code for inclusion in the official distribution, but
 + * derived work must not be called official GROMACS. Details are found
 + * in the README & COPYING files - if they are missing, get the
 + * official version at http://www.gromacs.org.
 + *
 + * To help us fund GROMACS development, we humbly ask that you cite
 + * the research papers on the package. Check out http://www.gromacs.org.
 + */
 +
 +#include "gmxpre.h"
 +
 +#include "pairlist.h"
 +
 +#include "config.h"
 +
 +#include <cassert>
 +#include <cmath>
 +#include <cstring>
 +
 +#include <algorithm>
 +
 +#include "gromacs/domdec/domdec_struct.h"
 +#include "gromacs/gmxlib/nrnb.h"
 +#include "gromacs/math/functions.h"
 +#include "gromacs/math/utilities.h"
 +#include "gromacs/math/vec.h"
 +#include "gromacs/mdlib/gmx_omp_nthreads.h"
 +#include "gromacs/mdtypes/group.h"
 +#include "gromacs/mdtypes/md_enums.h"
 +#include "gromacs/nbnxm/atomdata.h"
 +#include "gromacs/nbnxm/gpu_data_mgmt.h"
 +#include "gromacs/nbnxm/nbnxm_geometry.h"
 +#include "gromacs/nbnxm/nbnxm_simd.h"
 +#include "gromacs/pbcutil/ishift.h"
 +#include "gromacs/pbcutil/pbc.h"
 +#include "gromacs/simd/simd.h"
 +#include "gromacs/simd/vector_operations.h"
 +#include "gromacs/topology/block.h"
 +#include "gromacs/utility/exceptions.h"
 +#include "gromacs/utility/fatalerror.h"
 +#include "gromacs/utility/gmxomp.h"
 +#include "gromacs/utility/smalloc.h"
 +
 +#include "clusterdistancekerneltype.h"
 +#include "gridset.h"
 +#include "pairlistset.h"
 +#include "pairlistsets.h"
 +#include "pairlistwork.h"
 +#include "pairsearch.h"
 +
 +using namespace gmx;                        // TODO: Remove when this file is moved into gmx namespace
 +
 +using BoundingBox   = Nbnxm::BoundingBox;   // TODO: Remove when refactoring this file
 +using BoundingBox1D = Nbnxm::BoundingBox1D; // TODO: Remove when refactoring this file
 +
 +using Grid          = Nbnxm::Grid;          // TODO: Remove when refactoring this file
 +
 +// Convience alias for partial Nbnxn namespace usage
 +using InteractionLocality = Nbnxm::InteractionLocality;
 +
 +/* We shift the i-particles backward for PBC.
 + * This leads to more conditionals than shifting forward.
 + * We do this to get more balanced pair lists.
 + */
 +constexpr bool c_pbcShiftBackward = true;
 +
 +/* Layout for the nonbonded NxN pair lists */
 +enum class NbnxnLayout
 +{
 +    NoSimd4x4, // i-cluster size 4, j-cluster size 4
 +    Simd4xN,   // i-cluster size 4, j-cluster size SIMD width
 +    Simd2xNN,  // i-cluster size 4, j-cluster size half SIMD width
 +    Gpu8x8x8   // i-cluster size 8, j-cluster size 8 + super-clustering
 +};
 +
 +#if GMX_SIMD
 +/* Returns the j-cluster size */
 +template <NbnxnLayout layout>
 +static constexpr int jClusterSize()
 +{
 +    static_assert(layout == NbnxnLayout::NoSimd4x4 || layout == NbnxnLayout::Simd4xN || layout == NbnxnLayout::Simd2xNN, "Currently jClusterSize only supports CPU layouts");
 +
 +    return layout == NbnxnLayout::Simd4xN ? GMX_SIMD_REAL_WIDTH : (layout == NbnxnLayout::Simd2xNN ? GMX_SIMD_REAL_WIDTH/2 : c_nbnxnCpuIClusterSize);
 +}
 +
 +/*! \brief Returns the j-cluster index given the i-cluster index.
 + *
 + * \tparam    jClusterSize      The number of atoms in a j-cluster
 + * \tparam    jSubClusterIndex  The j-sub-cluster index (0/1), used when size(j-cluster) < size(i-cluster)
 + * \param[in] ci                The i-cluster index
 + */
 +template <int jClusterSize, int jSubClusterIndex>
 +static inline int cjFromCi(int ci)
 +{
 +    static_assert(jClusterSize == c_nbnxnCpuIClusterSize/2 || jClusterSize == c_nbnxnCpuIClusterSize || jClusterSize == c_nbnxnCpuIClusterSize*2, "Only j-cluster sizes 2, 4 and 8 are currently implemented");
 +
 +    static_assert(jSubClusterIndex == 0 || jSubClusterIndex == 1,
 +                  "Only sub-cluster indices 0 and 1 are supported");
 +
 +    if (jClusterSize == c_nbnxnCpuIClusterSize/2)
 +    {
 +        if (jSubClusterIndex == 0)
 +        {
 +            return ci << 1;
 +        }
 +        else
 +        {
 +            return ((ci + 1) << 1) - 1;
 +        }
 +    }
 +    else if (jClusterSize == c_nbnxnCpuIClusterSize)
 +    {
 +        return ci;
 +    }
 +    else
 +    {
 +        return ci >> 1;
 +    }
 +}
 +
 +/*! \brief Returns the j-cluster index given the i-cluster index.
 + *
 + * \tparam    layout            The pair-list layout
 + * \tparam    jSubClusterIndex  The j-sub-cluster index (0/1), used when size(j-cluster) < size(i-cluster)
 + * \param[in] ci                The i-cluster index
 + */
 +template <NbnxnLayout layout, int jSubClusterIndex>
 +static inline int cjFromCi(int ci)
 +{
 +    constexpr int clusterSize = jClusterSize<layout>();
 +
 +    return cjFromCi<clusterSize, jSubClusterIndex>(ci);
 +}
 +
 +/* Returns the nbnxn coordinate data index given the i-cluster index */
 +template <NbnxnLayout layout>
 +static inline int xIndexFromCi(int ci)
 +{
 +    constexpr int clusterSize = jClusterSize<layout>();
 +
 +    static_assert(clusterSize == c_nbnxnCpuIClusterSize/2 || clusterSize == c_nbnxnCpuIClusterSize || clusterSize == c_nbnxnCpuIClusterSize*2, "Only j-cluster sizes 2, 4 and 8 are currently implemented");
 +
 +    if (clusterSize <= c_nbnxnCpuIClusterSize)
 +    {
 +        /* Coordinates are stored packed in groups of 4 */
 +        return ci*STRIDE_P4;
 +    }
 +    else
 +    {
 +        /* Coordinates packed in 8, i-cluster size is half the packing width */
 +        return (ci >> 1)*STRIDE_P8 + (ci & 1)*(c_packX8 >> 1);
 +    }
 +}
 +
 +/* Returns the nbnxn coordinate data index given the j-cluster index */
 +template <NbnxnLayout layout>
 +static inline int xIndexFromCj(int cj)
 +{
 +    constexpr int clusterSize = jClusterSize<layout>();
 +
 +    static_assert(clusterSize == c_nbnxnCpuIClusterSize/2 || clusterSize == c_nbnxnCpuIClusterSize || clusterSize == c_nbnxnCpuIClusterSize*2, "Only j-cluster sizes 2, 4 and 8 are currently implemented");
 +
 +    if (clusterSize == c_nbnxnCpuIClusterSize/2)
 +    {
 +        /* Coordinates are stored packed in groups of 4 */
 +        return (cj >> 1)*STRIDE_P4 + (cj & 1)*(c_packX4 >> 1);
 +    }
 +    else if (clusterSize == c_nbnxnCpuIClusterSize)
 +    {
 +        /* Coordinates are stored packed in groups of 4 */
 +        return cj*STRIDE_P4;
 +    }
 +    else
 +    {
 +        /* Coordinates are stored packed in groups of 8 */
 +        return cj*STRIDE_P8;
 +    }
 +}
 +#endif //GMX_SIMD
 +
 +
 +void nbnxn_init_pairlist_fep(t_nblist *nl)
 +{
 +    nl->type        = GMX_NBLIST_INTERACTION_FREE_ENERGY;
 +    nl->igeometry   = GMX_NBLIST_GEOMETRY_PARTICLE_PARTICLE;
 +    /* The interaction functions are set in the free energy kernel fuction */
 +    nl->ivdw        = -1;
 +    nl->ivdwmod     = -1;
 +    nl->ielec       = -1;
 +    nl->ielecmod    = -1;
 +
 +    nl->maxnri      = 0;
 +    nl->maxnrj      = 0;
 +    nl->nri         = 0;
 +    nl->nrj         = 0;
 +    nl->iinr        = nullptr;
 +    nl->gid         = nullptr;
 +    nl->shift       = nullptr;
 +    nl->jindex      = nullptr;
 +    nl->jjnr        = nullptr;
 +    nl->excl_fep    = nullptr;
 +
 +}
 +
 +static void init_buffer_flags(nbnxn_buffer_flags_t *flags,
 +                              int                   natoms)
 +{
 +    flags->nflag = (natoms + NBNXN_BUFFERFLAG_SIZE - 1)/NBNXN_BUFFERFLAG_SIZE;
 +    if (flags->nflag > flags->flag_nalloc)
 +    {
 +        flags->flag_nalloc = over_alloc_large(flags->nflag);
 +        srenew(flags->flag, flags->flag_nalloc);
 +    }
 +    for (int b = 0; b < flags->nflag; b++)
 +    {
 +        bitmask_clear(&(flags->flag[b]));
 +    }
 +}
 +
 +/* Returns the pair-list cutoff between a bounding box and a grid cell given an atom-to-atom pair-list cutoff
 + *
 + * Given a cutoff distance between atoms, this functions returns the cutoff
 + * distance2 between a bounding box of a group of atoms and a grid cell.
 + * Since atoms can be geometrically outside of the cell they have been
 + * assigned to (when atom groups instead of individual atoms are assigned
 + * to cells), this distance returned can be larger than the input.
 + */
 +static real
 +listRangeForBoundingBoxToGridCell(real                    rlist,
 +                                  const Grid::Dimensions &gridDims)
 +{
 +    return rlist + gridDims.maxAtomGroupRadius;
 +
 +}
 +/* Returns the pair-list cutoff between a grid cells given an atom-to-atom pair-list cutoff
 + *
 + * Given a cutoff distance between atoms, this functions returns the cutoff
 + * distance2 between two grid cells.
 + * Since atoms can be geometrically outside of the cell they have been
 + * assigned to (when atom groups instead of individual atoms are assigned
 + * to cells), this distance returned can be larger than the input.
 + */
 +static real
 +listRangeForGridCellToGridCell(real                    rlist,
 +                               const Grid::Dimensions &iGridDims,
 +                               const Grid::Dimensions &jGridDims)
 +{
 +    return rlist + iGridDims.maxAtomGroupRadius + jGridDims.maxAtomGroupRadius;
 +}
 +
 +/* Determines the cell range along one dimension that
 + * the bounding box b0 - b1 sees.
 + */
 +template<int dim>
 +static void get_cell_range(real b0, real b1,
 +                           const Grid::Dimensions &jGridDims,
 +                           real d2, real rlist, int *cf, int *cl)
 +{
 +    real listRangeBBToCell2 = gmx::square(listRangeForBoundingBoxToGridCell(rlist, jGridDims));
 +    real distanceInCells    = (b0 - jGridDims.lowerCorner[dim])*jGridDims.invCellSize[dim];
 +    *cf                     = std::max(static_cast<int>(distanceInCells), 0);
 +
 +    while (*cf > 0 &&
 +           d2 + gmx::square((b0 - jGridDims.lowerCorner[dim]) - (*cf - 1 + 1)*jGridDims.cellSize[dim]) < listRangeBBToCell2)
 +    {
 +        (*cf)--;
 +    }
 +
 +    *cl = std::min(static_cast<int>((b1 - jGridDims.lowerCorner[dim])*jGridDims.invCellSize[dim]), jGridDims.numCells[dim] - 1);
 +    while (*cl < jGridDims.numCells[dim] - 1 &&
 +           d2 + gmx::square((*cl + 1)*jGridDims.cellSize[dim] - (b1 - jGridDims.lowerCorner[dim])) < listRangeBBToCell2)
 +    {
 +        (*cl)++;
 +    }
 +}
 +
 +/* Reference code calculating the distance^2 between two bounding boxes */
 +/*
 +   static float box_dist2(float bx0, float bx1, float by0,
 +                       float by1, float bz0, float bz1,
 +                       const BoundingBox *bb)
 +   {
 +    float d2;
 +    float dl, dh, dm, dm0;
 +
 +    d2 = 0;
 +
 +    dl  = bx0 - bb->upper.x;
 +    dh  = bb->lower.x - bx1;
 +    dm  = std::max(dl, dh);
 +    dm0 = std::max(dm, 0.0f);
 +    d2 += dm0*dm0;
 +
 +    dl  = by0 - bb->upper.y;
 +    dh  = bb->lower.y - by1;
 +    dm  = std::max(dl, dh);
 +    dm0 = std::max(dm, 0.0f);
 +    d2 += dm0*dm0;
 +
 +    dl  = bz0 - bb->upper.z;
 +    dh  = bb->lower.z - bz1;
 +    dm  = std::max(dl, dh);
 +    dm0 = std::max(dm, 0.0f);
 +    d2 += dm0*dm0;
 +
 +    return d2;
 +   }
 + */
 +
 +#if !NBNXN_SEARCH_BB_SIMD4
 +
 +/*! \brief Plain C code calculating the distance^2 between two bounding boxes in xyz0 format
 + *
 + * \param[in] bb_i  First bounding box
 + * \param[in] bb_j  Second bounding box
 + */
 +static float clusterBoundingBoxDistance2(const BoundingBox &bb_i,
 +                                         const BoundingBox &bb_j)
 +{
 +    float dl   = bb_i.lower.x - bb_j.upper.x;
 +    float dh   = bb_j.lower.x - bb_i.upper.x;
 +    float dm   = std::max(dl, dh);
 +    float dm0  = std::max(dm, 0.0f);
 +    float d2   = dm0*dm0;
 +
 +    dl         = bb_i.lower.y - bb_j.upper.y;
 +    dh         = bb_j.lower.y - bb_i.upper.y;
 +    dm         = std::max(dl, dh);
 +    dm0        = std::max(dm, 0.0f);
 +    d2        += dm0*dm0;
 +
 +    dl         = bb_i.lower.z - bb_j.upper.z;
 +    dh         = bb_j.lower.z - bb_i.upper.z;
 +    dm         = std::max(dl, dh);
 +    dm0        = std::max(dm, 0.0f);
 +    d2        += dm0*dm0;
 +
 +    return d2;
 +}
 +
 +#else /* NBNXN_SEARCH_BB_SIMD4 */
 +
 +/*! \brief 4-wide SIMD code calculating the distance^2 between two bounding boxes in xyz0 format
 + *
 + * \param[in] bb_i  First bounding box, should be aligned for 4-wide SIMD
 + * \param[in] bb_j  Second bounding box, should be aligned for 4-wide SIMD
 + */
 +static float clusterBoundingBoxDistance2(const BoundingBox &bb_i,
 +                                         const BoundingBox &bb_j)
 +{
 +    // TODO: During SIMDv2 transition only some archs use namespace (remove when done)
 +    using namespace gmx;
 +
 +    const Simd4Float bb_i_S0 = load4(bb_i.lower.ptr());
 +    const Simd4Float bb_i_S1 = load4(bb_i.upper.ptr());
 +    const Simd4Float bb_j_S0 = load4(bb_j.lower.ptr());
 +    const Simd4Float bb_j_S1 = load4(bb_j.upper.ptr());
 +
 +    const Simd4Float dl_S    = bb_i_S0 - bb_j_S1;
 +    const Simd4Float dh_S    = bb_j_S0 - bb_i_S1;
 +
 +    const Simd4Float dm_S    = max(dl_S, dh_S);
 +    const Simd4Float dm0_S   = max(dm_S, simd4SetZeroF());
 +
 +    return dotProduct(dm0_S, dm0_S);
 +}
 +
 +/* Calculate bb bounding distances of bb_i[si,...,si+3] and store them in d2 */
 +template <int boundingBoxStart>
 +static inline void gmx_simdcall
 +clusterBoundingBoxDistance2_xxxx_simd4_inner(const float      *bb_i,
 +                                             float            *d2,
 +                                             const Simd4Float  xj_l,
 +                                             const Simd4Float  yj_l,
 +                                             const Simd4Float  zj_l,
 +                                             const Simd4Float  xj_h,
 +                                             const Simd4Float  yj_h,
 +                                             const Simd4Float  zj_h)
 +{
 +    constexpr int    stride = c_packedBoundingBoxesDimSize;
 +
 +    const int        shi  = boundingBoxStart*Nbnxm::c_numBoundingBoxBounds1D*DIM;
 +
 +    const Simd4Float zero = setZero();
 +
 +    const Simd4Float xi_l = load4(bb_i + shi + 0*stride);
 +    const Simd4Float yi_l = load4(bb_i + shi + 1*stride);
 +    const Simd4Float zi_l = load4(bb_i + shi + 2*stride);
 +    const Simd4Float xi_h = load4(bb_i + shi + 3*stride);
 +    const Simd4Float yi_h = load4(bb_i + shi + 4*stride);
 +    const Simd4Float zi_h = load4(bb_i + shi + 5*stride);
 +
 +    const Simd4Float dx_0 = xi_l - xj_h;
 +    const Simd4Float dy_0 = yi_l - yj_h;
 +    const Simd4Float dz_0 = zi_l - zj_h;
 +
 +    const Simd4Float dx_1 = xj_l - xi_h;
 +    const Simd4Float dy_1 = yj_l - yi_h;
 +    const Simd4Float dz_1 = zj_l - zi_h;
 +
 +    const Simd4Float mx   = max(dx_0, dx_1);
 +    const Simd4Float my   = max(dy_0, dy_1);
 +    const Simd4Float mz   = max(dz_0, dz_1);
 +
 +    const Simd4Float m0x  = max(mx, zero);
 +    const Simd4Float m0y  = max(my, zero);
 +    const Simd4Float m0z  = max(mz, zero);
 +
 +    const Simd4Float d2x  = m0x * m0x;
 +    const Simd4Float d2y  = m0y * m0y;
 +    const Simd4Float d2z  = m0z * m0z;
 +
 +    const Simd4Float d2s  = d2x + d2y;
 +    const Simd4Float d2t  = d2s + d2z;
 +
 +    store4(d2 + boundingBoxStart, d2t);
 +}
 +
 +/* 4-wide SIMD code for nsi bb distances for bb format xxxxyyyyzzzz */
 +static void
 +clusterBoundingBoxDistance2_xxxx_simd4(const float *bb_j,
 +                                       const int    nsi,
 +                                       const float *bb_i,
 +                                       float       *d2)
 +{
 +    constexpr int    stride = c_packedBoundingBoxesDimSize;
 +
 +    // TODO: During SIMDv2 transition only some archs use namespace (remove when done)
 +    using namespace gmx;
 +
 +    const Simd4Float xj_l = Simd4Float(bb_j[0*stride]);
 +    const Simd4Float yj_l = Simd4Float(bb_j[1*stride]);
 +    const Simd4Float zj_l = Simd4Float(bb_j[2*stride]);
 +    const Simd4Float xj_h = Simd4Float(bb_j[3*stride]);
 +    const Simd4Float yj_h = Simd4Float(bb_j[4*stride]);
 +    const Simd4Float zj_h = Simd4Float(bb_j[5*stride]);
 +
 +    /* Here we "loop" over si (0,stride) from 0 to nsi with step stride.
 +     * But as we know the number of iterations is 1 or 2, we unroll manually.
 +     */
 +    clusterBoundingBoxDistance2_xxxx_simd4_inner<0>(bb_i, d2,
 +                                                    xj_l, yj_l, zj_l,
 +                                                    xj_h, yj_h, zj_h);
 +    if (stride < nsi)
 +    {
 +        clusterBoundingBoxDistance2_xxxx_simd4_inner<stride>(bb_i, d2,
 +                                                             xj_l, yj_l, zj_l,
 +                                                             xj_h, yj_h, zj_h);
 +    }
 +}
 +
 +#endif /* NBNXN_SEARCH_BB_SIMD4 */
 +
 +
 +/* Returns if any atom pair from two clusters is within distance sqrt(rlist2) */
 +static inline gmx_bool
 +clusterpair_in_range(const NbnxnPairlistGpuWork &work,
 +                     int si,
 +                     int csj, int stride, const real *x_j,
 +                     real rlist2)
 +{
 +#if !GMX_SIMD4_HAVE_REAL
 +
 +    /* Plain C version.
 +     * All coordinates are stored as xyzxyz...
 +     */
 +
 +    const real *x_i = work.iSuperClusterData.x.data();
 +
 +    for (int i = 0; i < c_nbnxnGpuClusterSize; i++)
 +    {
 +        int i0 = (si*c_nbnxnGpuClusterSize + i)*DIM;
 +        for (int j = 0; j < c_nbnxnGpuClusterSize; j++)
 +        {
 +            int  j0 = (csj*c_nbnxnGpuClusterSize + j)*stride;
 +
 +            real d2 = gmx::square(x_i[i0  ] - x_j[j0  ]) + gmx::square(x_i[i0+1] - x_j[j0+1]) + gmx::square(x_i[i0+2] - x_j[j0+2]);
 +
 +            if (d2 < rlist2)
 +            {
 +                return TRUE;
 +            }
 +        }
 +    }
 +
 +    return FALSE;
 +
 +#else /* !GMX_SIMD4_HAVE_REAL */
 +
 +    /* 4-wide SIMD version.
 +     * The coordinates x_i are stored as xxxxyyyy..., x_j is stored xyzxyz...
 +     * Using 8-wide AVX(2) is not faster on Intel Sandy Bridge and Haswell.
 +     */
 +    static_assert(c_nbnxnGpuClusterSize == 8 || c_nbnxnGpuClusterSize == 4,
 +                  "A cluster is hard-coded to 4/8 atoms.");
 +
 +    Simd4Real   rc2_S      = Simd4Real(rlist2);
 +
 +    const real *x_i        = work.iSuperClusterData.xSimd.data();
 +
 +    int         dim_stride = c_nbnxnGpuClusterSize*DIM;
 +    Simd4Real   ix_S0      = load4(x_i + si*dim_stride + 0*GMX_SIMD4_WIDTH);
 +    Simd4Real   iy_S0      = load4(x_i + si*dim_stride + 1*GMX_SIMD4_WIDTH);
 +    Simd4Real   iz_S0      = load4(x_i + si*dim_stride + 2*GMX_SIMD4_WIDTH);
 +
 +    Simd4Real   ix_S1, iy_S1, iz_S1;
 +    if (c_nbnxnGpuClusterSize == 8)
 +    {
 +        ix_S1      = load4(x_i + si*dim_stride + 3*GMX_SIMD4_WIDTH);
 +        iy_S1      = load4(x_i + si*dim_stride + 4*GMX_SIMD4_WIDTH);
 +        iz_S1      = load4(x_i + si*dim_stride + 5*GMX_SIMD4_WIDTH);
 +    }
 +    /* We loop from the outer to the inner particles to maximize
 +     * the chance that we find a pair in range quickly and return.
 +     */
 +    int j0 = csj*c_nbnxnGpuClusterSize;
 +    int j1 = j0 + c_nbnxnGpuClusterSize - 1;
 +    while (j0 < j1)
 +    {
 +        Simd4Real jx0_S, jy0_S, jz0_S;
 +        Simd4Real jx1_S, jy1_S, jz1_S;
 +
 +        Simd4Real dx_S0, dy_S0, dz_S0;
 +        Simd4Real dx_S1, dy_S1, dz_S1;
 +        Simd4Real dx_S2, dy_S2, dz_S2;
 +        Simd4Real dx_S3, dy_S3, dz_S3;
 +
 +        Simd4Real rsq_S0;
 +        Simd4Real rsq_S1;
 +        Simd4Real rsq_S2;
 +        Simd4Real rsq_S3;
 +
 +        Simd4Bool wco_S0;
 +        Simd4Bool wco_S1;
 +        Simd4Bool wco_S2;
 +        Simd4Bool wco_S3;
 +        Simd4Bool wco_any_S01, wco_any_S23, wco_any_S;
 +
 +        jx0_S = Simd4Real(x_j[j0*stride+0]);
 +        jy0_S = Simd4Real(x_j[j0*stride+1]);
 +        jz0_S = Simd4Real(x_j[j0*stride+2]);
 +
 +        jx1_S = Simd4Real(x_j[j1*stride+0]);
 +        jy1_S = Simd4Real(x_j[j1*stride+1]);
 +        jz1_S = Simd4Real(x_j[j1*stride+2]);
 +
 +        /* Calculate distance */
 +        dx_S0            = ix_S0 - jx0_S;
 +        dy_S0            = iy_S0 - jy0_S;
 +        dz_S0            = iz_S0 - jz0_S;
 +        dx_S2            = ix_S0 - jx1_S;
 +        dy_S2            = iy_S0 - jy1_S;
 +        dz_S2            = iz_S0 - jz1_S;
 +        if (c_nbnxnGpuClusterSize == 8)
 +        {
 +            dx_S1            = ix_S1 - jx0_S;
 +            dy_S1            = iy_S1 - jy0_S;
 +            dz_S1            = iz_S1 - jz0_S;
 +            dx_S3            = ix_S1 - jx1_S;
 +            dy_S3            = iy_S1 - jy1_S;
 +            dz_S3            = iz_S1 - jz1_S;
 +        }
 +
 +        /* rsq = dx*dx+dy*dy+dz*dz */
 +        rsq_S0           = norm2(dx_S0, dy_S0, dz_S0);
 +        rsq_S2           = norm2(dx_S2, dy_S2, dz_S2);
 +        if (c_nbnxnGpuClusterSize == 8)
 +        {
 +            rsq_S1           = norm2(dx_S1, dy_S1, dz_S1);
 +            rsq_S3           = norm2(dx_S3, dy_S3, dz_S3);
 +        }
 +
 +        wco_S0           = (rsq_S0 < rc2_S);
 +        wco_S2           = (rsq_S2 < rc2_S);
 +        if (c_nbnxnGpuClusterSize == 8)
 +        {
 +            wco_S1           = (rsq_S1 < rc2_S);
 +            wco_S3           = (rsq_S3 < rc2_S);
 +        }
 +        if (c_nbnxnGpuClusterSize == 8)
 +        {
 +            wco_any_S01      = wco_S0 || wco_S1;
 +            wco_any_S23      = wco_S2 || wco_S3;
 +            wco_any_S        = wco_any_S01 || wco_any_S23;
 +        }
 +        else
 +        {
 +            wco_any_S = wco_S0 || wco_S2;
 +        }
 +
 +        if (anyTrue(wco_any_S))
 +        {
 +            return TRUE;
 +        }
 +
 +        j0++;
 +        j1--;
 +    }
 +
 +    return FALSE;
 +
 +#endif /* !GMX_SIMD4_HAVE_REAL */
 +}
 +
 +/* Returns the j-cluster index for index cjIndex in a cj list */
 +static inline int nblCj(gmx::ArrayRef<const nbnxn_cj_t> cjList,
 +                        int                             cjIndex)
 +{
 +    return cjList[cjIndex].cj;
 +}
 +
 +/* Returns the j-cluster index for index cjIndex in a cj4 list */
 +static inline int nblCj(gmx::ArrayRef<const nbnxn_cj4_t> cj4List,
 +                        int                              cjIndex)
 +{
 +    return cj4List[cjIndex/c_nbnxnGpuJgroupSize].cj[cjIndex & (c_nbnxnGpuJgroupSize - 1)];
 +}
 +
 +/* Returns the i-interaction mask of the j sub-cell for index cj_ind */
 +static unsigned int nbl_imask0(const NbnxnPairlistGpu *nbl, int cj_ind)
 +{
 +    return nbl->cj4[cj_ind/c_nbnxnGpuJgroupSize].imei[0].imask;
 +}
 +
 +NbnxnPairlistCpu::NbnxnPairlistCpu() :
 +    na_ci(c_nbnxnCpuIClusterSize),
 +    na_cj(0),
 +    rlist(0),
 +    ncjInUse(0),
 +    nci_tot(0),
 +    work(std::make_unique<NbnxnPairlistCpuWork>())
 +{
 +}
 +
 +NbnxnPairlistGpu::NbnxnPairlistGpu(gmx::PinningPolicy pinningPolicy) :
 +    na_ci(c_nbnxnGpuClusterSize),
 +    na_cj(c_nbnxnGpuClusterSize),
 +    na_sc(c_gpuNumClusterPerCell*c_nbnxnGpuClusterSize),
 +    rlist(0),
 +    sci({}, {pinningPolicy}),
 +    cj4({}, {pinningPolicy}),
 +    excl({}, {pinningPolicy}),
 +    nci_tot(0),
 +    work(std::make_unique<NbnxnPairlistGpuWork>())
 +{
 +    static_assert(c_nbnxnGpuNumClusterPerSupercluster == c_gpuNumClusterPerCell,
 +                  "The search code assumes that the a super-cluster matches a search grid cell");
 +
 +    static_assert(sizeof(cj4[0].imei[0].imask)*8 >= c_nbnxnGpuJgroupSize*c_gpuNumClusterPerCell,
 +                  "The i super-cluster cluster interaction mask does not contain a sufficient number of bits");
 +
 +    static_assert(sizeof(excl[0])*8 >= c_nbnxnGpuJgroupSize*c_gpuNumClusterPerCell, "The GPU exclusion mask does not contain a sufficient number of bits");
 +
 +    // We always want a first entry without any exclusions
 +    excl.resize(1);
 +}
 +
 +// TODO: Move to pairlistset.cpp
 +PairlistSet::PairlistSet(const Nbnxm::InteractionLocality  locality,
 +                         const PairlistParams             &pairlistParams) :
 +    locality_(locality),
 +    params_(pairlistParams)
 +{
 +    isCpuType_ =
 +        (params_.pairlistType == PairlistType::Simple4x2 ||
 +         params_.pairlistType == PairlistType::Simple4x4 ||
 +         params_.pairlistType == PairlistType::Simple4x8);
 +    // Currently GPU lists are always combined
 +    combineLists_ = !isCpuType_;
 +
 +    const int numLists = gmx_omp_nthreads_get(emntNonbonded);
 +
 +    if (!combineLists_ &&
 +        numLists > NBNXN_BUFFERFLAG_MAX_THREADS)
 +    {
 +        gmx_fatal(FARGS, "%d OpenMP threads were requested. Since the non-bonded force buffer reduction is prohibitively slow with more than %d threads, we do not allow this. Use %d or less OpenMP threads.",
 +                  numLists, NBNXN_BUFFERFLAG_MAX_THREADS, NBNXN_BUFFERFLAG_MAX_THREADS);
 +    }
 +
 +    if (isCpuType_)
 +    {
 +        cpuLists_.resize(numLists);
 +        if (numLists > 1)
 +        {
 +            cpuListsWork_.resize(numLists);
 +        }
 +    }
 +    else
 +    {
 +        /* Only list 0 is used on the GPU, use normal allocation for i>0 */
 +        gpuLists_.emplace_back(gmx::PinningPolicy::PinnedIfSupported);
 +        /* Lists 0 to numLists are use for constructing lists in parallel
 +         * on the CPU using numLists threads (and then merged into list 0).
 +         */
 +        for (int i = 1; i < numLists; i++)
 +        {
 +            gpuLists_.emplace_back(gmx::PinningPolicy::CannotBePinned);
 +        }
 +    }
 +    if (params_.haveFep)
 +    {
 +        fepLists_.resize(numLists);
 +
 +        /* Execute in order to avoid memory interleaving between threads */
 +#pragma omp parallel for num_threads(numLists) schedule(static)
 +        for (int i = 0; i < numLists; i++)
 +        {
 +            try
 +            {
 +                /* We used to allocate all normal lists locally on each thread
 +                 * as well. The question is if allocating the object on the
 +                 * master thread (but all contained list memory thread local)
 +                 * impacts performance.
 +                 */
 +                fepLists_[i] = std::make_unique<t_nblist>();
 +                nbnxn_init_pairlist_fep(fepLists_[i].get());
 +            }
 +            GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
 +        }
 +    }
 +}
 +
 +/* Print statistics of a pair list, used for debug output */
 +static void print_nblist_statistics(FILE                   *fp,
 +                                    const NbnxnPairlistCpu &nbl,
 +                                    const Nbnxm::GridSet   &gridSet,
 +                                    const real              rl)
 +{
 +    const Grid             &grid = gridSet.grids()[0];
 +    const Grid::Dimensions &dims = grid.dimensions();
 +
 +    fprintf(fp, "nbl nci %zu ncj %d\n",
 +            nbl.ci.size(), nbl.ncjInUse);
 +    const int    numAtomsJCluster = grid.geometry().numAtomsJCluster;
 +    const double numAtomsPerCell  = nbl.ncjInUse/static_cast<double>(grid.numCells())*numAtomsJCluster;
 +    fprintf(fp, "nbl na_cj %d rl %g ncp %d per cell %.1f atoms %.1f ratio %.2f\n",
 +            nbl.na_cj, rl, nbl.ncjInUse, nbl.ncjInUse/static_cast<double>(grid.numCells()),
 +            numAtomsPerCell,
 +            numAtomsPerCell/(0.5*4.0/3.0*M_PI*rl*rl*rl*grid.numCells()*numAtomsJCluster/(dims.gridSize[XX]*dims.gridSize[YY]*dims.gridSize[ZZ])));
 +
 +    fprintf(fp, "nbl average j cell list length %.1f\n",
 +            0.25*nbl.ncjInUse/std::max(static_cast<double>(nbl.ci.size()), 1.0));
 +
 +    int cs[SHIFTS] = { 0 };
 +    int npexcl     = 0;
 +    for (const nbnxn_ci_t &ciEntry : nbl.ci)
 +    {
 +        cs[ciEntry.shift & NBNXN_CI_SHIFT] +=
 +            ciEntry.cj_ind_end - ciEntry.cj_ind_start;
 +
 +        int j = ciEntry.cj_ind_start;
 +        while (j < ciEntry.cj_ind_end &&
 +               nbl.cj[j].excl != NBNXN_INTERACTION_MASK_ALL)
 +        {
 +            npexcl++;
 +            j++;
 +        }
 +    }
 +    fprintf(fp, "nbl cell pairs, total: %zu excl: %d %.1f%%\n",
 +            nbl.cj.size(), npexcl, 100*npexcl/std::max(static_cast<double>(nbl.cj.size()), 1.0));
 +    for (int s = 0; s < SHIFTS; s++)
 +    {
 +        if (cs[s] > 0)
 +        {
 +            fprintf(fp, "nbl shift %2d ncj %3d\n", s, cs[s]);
 +        }
 +    }
 +}
 +
 +/* Print statistics of a pair lists, used for debug output */
 +static void print_nblist_statistics(FILE                   *fp,
 +                                    const NbnxnPairlistGpu &nbl,
 +                                    const Nbnxm::GridSet   &gridSet,
 +                                    const real              rl)
 +{
 +    const Grid             &grid = gridSet.grids()[0];
 +    const Grid::Dimensions &dims = grid.dimensions();
 +
 +    fprintf(fp, "nbl nsci %zu ncj4 %zu nsi %d excl4 %zu\n",
 +            nbl.sci.size(), nbl.cj4.size(), nbl.nci_tot, nbl.excl.size());
 +    const int    numAtomsCluster = grid.geometry().numAtomsICluster;
 +    const double numAtomsPerCell = nbl.nci_tot/static_cast<double>(grid.numClusters())*numAtomsCluster;
 +    fprintf(fp, "nbl na_c %d rl %g ncp %d per cell %.1f atoms %.1f ratio %.2f\n",
 +            nbl.na_ci, rl, nbl.nci_tot, nbl.nci_tot/static_cast<double>(grid.numClusters()),
 +            numAtomsPerCell,
 +            numAtomsPerCell/(0.5*4.0/3.0*M_PI*rl*rl*rl*grid.numClusters()*numAtomsCluster/(dims.gridSize[XX]*dims.gridSize[YY]*dims.gridSize[ZZ])));
 +
 +    double sum_nsp  = 0;
 +    double sum_nsp2 = 0;
 +    int    nsp_max  = 0;
 +    int    c[c_gpuNumClusterPerCell + 1] = { 0 };
 +    for (const nbnxn_sci_t &sci : nbl.sci)
 +    {
 +        int nsp = 0;
 +        for (int j4 = sci.cj4_ind_start; j4 < sci.cj4_ind_end; j4++)
 +        {
 +            for (int j = 0; j < c_nbnxnGpuJgroupSize; j++)
 +            {
 +                int b = 0;
 +                for (int si = 0; si < c_gpuNumClusterPerCell; si++)
 +                {
 +                    if (nbl.cj4[j4].imei[0].imask & (1U << (j*c_gpuNumClusterPerCell + si)))
 +                    {
 +                        b++;
 +                    }
 +                }
 +                nsp += b;
 +                c[b]++;
 +            }
 +        }
 +        sum_nsp  += nsp;
 +        sum_nsp2 += nsp*nsp;
 +        nsp_max   = std::max(nsp_max, nsp);
 +    }
 +    if (!nbl.sci.empty())
 +    {
 +        sum_nsp  /= nbl.sci.size();
 +        sum_nsp2 /= nbl.sci.size();
 +    }
 +    fprintf(fp, "nbl #cluster-pairs: av %.1f stddev %.1f max %d\n",
 +            sum_nsp, std::sqrt(sum_nsp2 - sum_nsp*sum_nsp), nsp_max);
 +
 +    if (!nbl.cj4.empty())
 +    {
 +        for (int b = 0; b <= c_gpuNumClusterPerCell; b++)
 +        {
 +            fprintf(fp, "nbl j-list #i-subcell %d %7d %4.1f\n",
 +                    b, c[b], 100.0*c[b]/size_t {nbl.cj4.size()*c_nbnxnGpuJgroupSize});
 +        }
 +    }
 +}
 +
 +/* Returns a pointer to the exclusion mask for j-cluster-group \p cj4 and warp \p warp
 + * Generates a new exclusion entry when the j-cluster-group uses
 + * the default all-interaction mask at call time, so the returned mask
 + * can be modified when needed.
 + */
 +static nbnxn_excl_t *get_exclusion_mask(NbnxnPairlistGpu *nbl,
 +                                        int               cj4,
 +                                        int               warp)
 +{
 +    if (nbl->cj4[cj4].imei[warp].excl_ind == 0)
 +    {
 +        /* No exclusions set, make a new list entry */
 +        const size_t oldSize = nbl->excl.size();
 +        GMX_ASSERT(oldSize >= 1, "We should always have entry [0]");
 +        /* Add entry with default values: no exclusions */
 +        nbl->excl.resize(oldSize + 1);
 +        nbl->cj4[cj4].imei[warp].excl_ind = oldSize;
 +    }
 +
 +    return &nbl->excl[nbl->cj4[cj4].imei[warp].excl_ind];
 +}
 +
 +static void set_self_and_newton_excls_supersub(NbnxnPairlistGpu *nbl,
 +                                               int cj4_ind, int sj_offset,
 +                                               int i_cluster_in_cell)
 +{
 +    nbnxn_excl_t *excl[c_nbnxnGpuClusterpairSplit];
 +
 +    /* Here we only set the set self and double pair exclusions */
 +
 +    /* Reserve extra elements, so the resize() in get_exclusion_mask()
 +     * will not invalidate excl entries in the loop below
 +     */
 +    nbl->excl.reserve(nbl->excl.size() + c_nbnxnGpuClusterpairSplit);
 +    for (int w = 0; w < c_nbnxnGpuClusterpairSplit; w++)
 +    {
 +        excl[w] = get_exclusion_mask(nbl, cj4_ind, w);
 +    }
 +
 +    /* Only minor < major bits set */
 +    for (int ej = 0; ej < nbl->na_ci; ej++)
 +    {
 +        int w = (ej>>2);
 +        for (int ei = ej; ei < nbl->na_ci; ei++)
 +        {
 +            excl[w]->pair[(ej & (c_nbnxnGpuJgroupSize-1))*nbl->na_ci + ei] &=
 +                ~(1U << (sj_offset*c_gpuNumClusterPerCell + i_cluster_in_cell));
 +        }
 +    }
 +}
 +
 +/* Returns a diagonal or off-diagonal interaction mask for plain C lists */
 +static unsigned int get_imask(gmx_bool rdiag, int ci, int cj)
 +{
 +    return (rdiag && ci == cj ? NBNXN_INTERACTION_MASK_DIAG : NBNXN_INTERACTION_MASK_ALL);
 +}
 +
 +/* Returns a diagonal or off-diagonal interaction mask for cj-size=2 */
 +gmx_unused static unsigned int get_imask_simd_j2(gmx_bool rdiag, int ci, int cj)
 +{
 +    return (rdiag && ci*2 == cj ? NBNXN_INTERACTION_MASK_DIAG_J2_0 :
 +            (rdiag && ci*2+1 == cj ? NBNXN_INTERACTION_MASK_DIAG_J2_1 :
 +             NBNXN_INTERACTION_MASK_ALL));
 +}
 +
 +/* Returns a diagonal or off-diagonal interaction mask for cj-size=4 */
 +gmx_unused static unsigned int get_imask_simd_j4(gmx_bool rdiag, int ci, int cj)
 +{
 +    return (rdiag && ci == cj ? NBNXN_INTERACTION_MASK_DIAG : NBNXN_INTERACTION_MASK_ALL);
 +}
 +
 +/* Returns a diagonal or off-diagonal interaction mask for cj-size=8 */
 +gmx_unused static unsigned int get_imask_simd_j8(gmx_bool rdiag, int ci, int cj)
 +{
 +    return (rdiag && ci == cj*2 ? NBNXN_INTERACTION_MASK_DIAG_J8_0 :
 +            (rdiag && ci == cj*2+1 ? NBNXN_INTERACTION_MASK_DIAG_J8_1 :
 +             NBNXN_INTERACTION_MASK_ALL));
 +}
 +
 +#if GMX_SIMD
 +#if GMX_SIMD_REAL_WIDTH == 2
 +#define get_imask_simd_4xn  get_imask_simd_j2
 +#endif
 +#if GMX_SIMD_REAL_WIDTH == 4
 +#define get_imask_simd_4xn  get_imask_simd_j4
 +#endif
 +#if GMX_SIMD_REAL_WIDTH == 8
 +#define get_imask_simd_4xn  get_imask_simd_j8
 +#define get_imask_simd_2xnn get_imask_simd_j4
 +#endif
 +#if GMX_SIMD_REAL_WIDTH == 16
 +#define get_imask_simd_2xnn get_imask_simd_j8
 +#endif
 +#endif
 +
 +/* Plain C code for checking and adding cluster-pairs to the list.
 + *
 + * \param[in]     gridj               The j-grid
 + * \param[in,out] nbl                 The pair-list to store the cluster pairs in
 + * \param[in]     icluster            The index of the i-cluster
 + * \param[in]     jclusterFirst       The first cluster in the j-range
 + * \param[in]     jclusterLast        The last cluster in the j-range
 + * \param[in]     excludeSubDiagonal  Exclude atom pairs with i-index > j-index
 + * \param[in]     x_j                 Coordinates for the j-atom, in xyz format
 + * \param[in]     rlist2              The squared list cut-off
 + * \param[in]     rbb2                The squared cut-off for putting cluster-pairs in the list based on bounding box distance only
 + * \param[in,out] numDistanceChecks   The number of distance checks performed
 + */
 +static void
 +makeClusterListSimple(const Grid               &jGrid,
 +                      NbnxnPairlistCpu *        nbl,
 +                      int                       icluster,
 +                      int                       jclusterFirst,
 +                      int                       jclusterLast,
 +                      bool                      excludeSubDiagonal,
 +                      const real * gmx_restrict x_j,
 +                      real                      rlist2,
 +                      float                     rbb2,
 +                      int * gmx_restrict        numDistanceChecks)
 +{
 +    const BoundingBox * gmx_restrict bb_ci = nbl->work->iClusterData.bb.data();
 +    const real * gmx_restrict        x_ci  = nbl->work->iClusterData.x.data();
 +
 +    gmx_bool                         InRange;
 +
 +    InRange = FALSE;
 +    while (!InRange && jclusterFirst <= jclusterLast)
 +    {
 +        real d2  = clusterBoundingBoxDistance2(bb_ci[0], jGrid.jBoundingBoxes()[jclusterFirst]);
 +        *numDistanceChecks += 2;
 +
 +        /* Check if the distance is within the distance where
 +         * we use only the bounding box distance rbb,
 +         * or within the cut-off and there is at least one atom pair
 +         * within the cut-off.
 +         */
 +        if (d2 < rbb2)
 +        {
 +            InRange = TRUE;
 +        }
 +        else if (d2 < rlist2)
 +        {
 +            int cjf_gl = jGrid.cellOffset() + jclusterFirst;
 +            for (int i = 0; i < c_nbnxnCpuIClusterSize && !InRange; i++)
 +            {
 +                for (int j = 0; j < c_nbnxnCpuIClusterSize; j++)
 +                {
 +                    InRange = InRange ||
 +                        (gmx::square(x_ci[i*STRIDE_XYZ+XX] - x_j[(cjf_gl*c_nbnxnCpuIClusterSize+j)*STRIDE_XYZ+XX]) +
 +                         gmx::square(x_ci[i*STRIDE_XYZ+YY] - x_j[(cjf_gl*c_nbnxnCpuIClusterSize+j)*STRIDE_XYZ+YY]) +
 +                         gmx::square(x_ci[i*STRIDE_XYZ+ZZ] - x_j[(cjf_gl*c_nbnxnCpuIClusterSize+j)*STRIDE_XYZ+ZZ]) < rlist2);
 +                }
 +            }
 +            *numDistanceChecks += c_nbnxnCpuIClusterSize*c_nbnxnCpuIClusterSize;
 +        }
 +        if (!InRange)
 +        {
 +            jclusterFirst++;
 +        }
 +    }
 +    if (!InRange)
 +    {
 +        return;
 +    }
 +
 +    InRange = FALSE;
 +    while (!InRange && jclusterLast > jclusterFirst)
 +    {
 +        real d2  = clusterBoundingBoxDistance2(bb_ci[0], jGrid.jBoundingBoxes()[jclusterLast]);
 +        *numDistanceChecks += 2;
 +
 +        /* Check if the distance is within the distance where
 +         * we use only the bounding box distance rbb,
 +         * or within the cut-off and there is at least one atom pair
 +         * within the cut-off.
 +         */
 +        if (d2 < rbb2)
 +        {
 +            InRange = TRUE;
 +        }
 +        else if (d2 < rlist2)
 +        {
 +            int cjl_gl = jGrid.cellOffset() + jclusterLast;
 +            for (int i = 0; i < c_nbnxnCpuIClusterSize && !InRange; i++)
 +            {
 +                for (int j = 0; j < c_nbnxnCpuIClusterSize; j++)
 +                {
 +                    InRange = InRange ||
 +                        (gmx::square(x_ci[i*STRIDE_XYZ+XX] - x_j[(cjl_gl*c_nbnxnCpuIClusterSize+j)*STRIDE_XYZ+XX]) +
 +                         gmx::square(x_ci[i*STRIDE_XYZ+YY] - x_j[(cjl_gl*c_nbnxnCpuIClusterSize+j)*STRIDE_XYZ+YY]) +
 +                         gmx::square(x_ci[i*STRIDE_XYZ+ZZ] - x_j[(cjl_gl*c_nbnxnCpuIClusterSize+j)*STRIDE_XYZ+ZZ]) < rlist2);
 +                }
 +            }
 +            *numDistanceChecks += c_nbnxnCpuIClusterSize*c_nbnxnCpuIClusterSize;
 +        }
 +        if (!InRange)
 +        {
 +            jclusterLast--;
 +        }
 +    }
 +
 +    if (jclusterFirst <= jclusterLast)
 +    {
 +        for (int jcluster = jclusterFirst; jcluster <= jclusterLast; jcluster++)
 +        {
 +            /* Store cj and the interaction mask */
 +            nbnxn_cj_t cjEntry;
 +            cjEntry.cj   = jGrid.cellOffset() + jcluster;
 +            cjEntry.excl = get_imask(excludeSubDiagonal, icluster, jcluster);
 +            nbl->cj.push_back(cjEntry);
 +        }
 +        /* Increase the closing index in the i list */
 +        nbl->ci.back().cj_ind_end = nbl->cj.size();
 +    }
 +}
 +
 +#ifdef GMX_NBNXN_SIMD_4XN
 +#include "gromacs/nbnxm/pairlist_simd_4xm.h"
 +#endif
 +#ifdef GMX_NBNXN_SIMD_2XNN
 +#include "gromacs/nbnxm/pairlist_simd_2xmm.h"
 +#endif
 +
 +/* Plain C or SIMD4 code for making a pair list of super-cell sci vs scj.
 + * Checks bounding box distances and possibly atom pair distances.
 + */
 +static void make_cluster_list_supersub(const Grid         &iGrid,
 +                                       const Grid         &jGrid,
 +                                       NbnxnPairlistGpu   *nbl,
 +                                       const int           sci,
 +                                       const int           scj,
 +                                       const bool          excludeSubDiagonal,
 +                                       const int           stride,
 +                                       const real         *x,
 +                                       const real          rlist2,
 +                                       const float         rbb2,
 +                                       int                *numDistanceChecks)
 +{
 +    NbnxnPairlistGpuWork &work   = *nbl->work;
 +
 +#if NBNXN_BBXXXX
 +    const float          *pbb_ci = work.iSuperClusterData.bbPacked.data();
 +#else
 +    const BoundingBox    *bb_ci  = work.iSuperClusterData.bb.data();
 +#endif
 +
 +    assert(c_nbnxnGpuClusterSize == iGrid.geometry().numAtomsICluster);
 +    assert(c_nbnxnGpuClusterSize == jGrid.geometry().numAtomsICluster);
 +
 +    /* We generate the pairlist mainly based on bounding-box distances
 +     * and do atom pair distance based pruning on the GPU.
 +     * Only if a j-group contains a single cluster-pair, we try to prune
 +     * that pair based on atom distances on the CPU to avoid empty j-groups.
 +     */
 +#define PRUNE_LIST_CPU_ONE 1
 +#define PRUNE_LIST_CPU_ALL 0
 +
 +#if PRUNE_LIST_CPU_ONE
 +    int  ci_last = -1;
 +#endif
 +
 +    float *d2l = work.distanceBuffer.data();
 +
 +    for (int subc = 0; subc < jGrid.numClustersPerCell()[scj]; subc++)
 +    {
 +        const int    cj4_ind   = work.cj_ind/c_nbnxnGpuJgroupSize;
 +        const int    cj_offset = work.cj_ind - cj4_ind*c_nbnxnGpuJgroupSize;
 +        const int    cj        = scj*c_gpuNumClusterPerCell + subc;
 +
 +        const int    cj_gl     = jGrid.cellOffset()*c_gpuNumClusterPerCell + cj;
 +
 +        int          ci1;
 +        if (excludeSubDiagonal && sci == scj)
 +        {
 +            ci1 = subc + 1;
 +        }
 +        else
 +        {
 +            ci1 = iGrid.numClustersPerCell()[sci];
 +        }
 +
 +#if NBNXN_BBXXXX
 +        /* Determine all ci1 bb distances in one call with SIMD4 */
 +        const int offset = packedBoundingBoxesIndex(cj) + (cj & (c_packedBoundingBoxesDimSize - 1));
 +        clusterBoundingBoxDistance2_xxxx_simd4(jGrid.packedBoundingBoxes().data() + offset,
 +                                               ci1, pbb_ci, d2l);
 +        *numDistanceChecks += c_nbnxnGpuClusterSize*2;
 +#endif
 +
 +        int          npair = 0;
 +        unsigned int imask = 0;
 +        /* We use a fixed upper-bound instead of ci1 to help optimization */
 +        for (int ci = 0; ci < c_gpuNumClusterPerCell; ci++)
 +        {
 +            if (ci == ci1)
 +            {
 +                break;
 +            }
 +
 +#if !NBNXN_BBXXXX
 +            /* Determine the bb distance between ci and cj */
 +            d2l[ci]             = clusterBoundingBoxDistance2(bb_ci[ci], jGrid.jBoundingBoxes()[cj]);
 +            *numDistanceChecks += 2;
 +#endif
 +            float d2 = d2l[ci];
 +
 +#if PRUNE_LIST_CPU_ALL
 +            /* Check if the distance is within the distance where
 +             * we use only the bounding box distance rbb,
 +             * or within the cut-off and there is at least one atom pair
 +             * within the cut-off. This check is very costly.
 +             */
 +            *numDistanceChecks += c_nbnxnGpuClusterSize*c_nbnxnGpuClusterSize;
 +            if (d2 < rbb2 ||
 +                (d2 < rlist2 &&
 +                 clusterpair_in_range(work, ci, cj_gl, stride, x, rlist2)))
 +#else
 +            /* Check if the distance between the two bounding boxes
 +             * in within the pair-list cut-off.
 +             */
 +            if (d2 < rlist2)
 +#endif
 +            {
 +                /* Flag this i-subcell to be taken into account */
 +                imask |= (1U << (cj_offset*c_gpuNumClusterPerCell + ci));
 +
 +#if PRUNE_LIST_CPU_ONE
 +                ci_last = ci;
 +#endif
 +
 +                npair++;
 +            }
 +        }
 +
 +#if PRUNE_LIST_CPU_ONE
 +        /* If we only found 1 pair, check if any atoms are actually
 +         * within the cut-off, so we could get rid of it.
 +         */
 +        if (npair == 1 && d2l[ci_last] >= rbb2 &&
 +            !clusterpair_in_range(work, ci_last, cj_gl, stride, x, rlist2))
 +        {
 +            imask &= ~(1U << (cj_offset*c_gpuNumClusterPerCell + ci_last));
 +            npair--;
 +        }
 +#endif
 +
 +        if (npair > 0)
 +        {
 +            /* We have at least one cluster pair: add a j-entry */
 +            if (static_cast<size_t>(cj4_ind) == nbl->cj4.size())
 +            {
 +                nbl->cj4.resize(nbl->cj4.size() + 1);
 +            }
 +            nbnxn_cj4_t *cj4   = &nbl->cj4[cj4_ind];
 +
 +            cj4->cj[cj_offset] = cj_gl;
 +
 +            /* Set the exclusions for the ci==sj entry.
 +             * Here we don't bother to check if this entry is actually flagged,
 +             * as it will nearly always be in the list.
 +             */
 +            if (excludeSubDiagonal && sci == scj)
 +            {
 +                set_self_and_newton_excls_supersub(nbl, cj4_ind, cj_offset, subc);
 +            }
 +
 +            /* Copy the cluster interaction mask to the list */
 +            for (int w = 0; w < c_nbnxnGpuClusterpairSplit; w++)
 +            {
 +                cj4->imei[w].imask |= imask;
 +            }
 +
 +            nbl->work->cj_ind++;
 +
 +            /* Keep the count */
 +            nbl->nci_tot += npair;
 +
 +            /* Increase the closing index in i super-cell list */
 +            nbl->sci.back().cj4_ind_end =
 +                (nbl->work->cj_ind + c_nbnxnGpuJgroupSize - 1)/c_nbnxnGpuJgroupSize;
 +        }
 +    }
 +}
 +
 +/* Returns how many contiguous j-clusters we have starting in the i-list */
 +template <typename CjListType>
 +static int numContiguousJClusters(const int                       cjIndexStart,
 +                                  const int                       cjIndexEnd,
 +                                  gmx::ArrayRef<const CjListType> cjList)
 +{
 +    const int firstJCluster = nblCj(cjList, cjIndexStart);
 +
 +    int       numContiguous = 0;
 +
 +    while (cjIndexStart + numContiguous < cjIndexEnd &&
 +           nblCj(cjList, cjIndexStart + numContiguous) == firstJCluster + numContiguous)
 +    {
 +        numContiguous++;
 +    }
 +
 +    return numContiguous;
 +}
 +
 +/*! \internal
 + * \brief Helper struct for efficient searching for excluded atoms in a j-list
 + */
 +struct JListRanges
 +{
 +    /*! \brief Constructs a j-list range from \p cjList with the given index range */
 +    template <typename CjListType>
 +    JListRanges(int                             cjIndexStart,
 +                int                             cjIndexEnd,
 +                gmx::ArrayRef<const CjListType> cjList);
 +
 +    int cjIndexStart; //!< The start index in the j-list
 +    int cjIndexEnd;   //!< The end index in the j-list
 +    int cjFirst;      //!< The j-cluster with index cjIndexStart
 +    int cjLast;       //!< The j-cluster with index cjIndexEnd-1
 +    int numDirect;    //!< Up to cjIndexStart+numDirect the j-clusters are cjFirst + the index offset
 +};
 +
 +#ifndef DOXYGEN
 +template <typename CjListType>
 +JListRanges::JListRanges(int                             cjIndexStart,
 +                         int                             cjIndexEnd,
 +                         gmx::ArrayRef<const CjListType> cjList) :
 +    cjIndexStart(cjIndexStart),
 +    cjIndexEnd(cjIndexEnd)
 +{
 +    GMX_ASSERT(cjIndexEnd > cjIndexStart, "JListRanges should only be called with non-empty lists");
 +
 +    cjFirst   = nblCj(cjList, cjIndexStart);
 +    cjLast    = nblCj(cjList, cjIndexEnd - 1);
 +
 +    /* Determine how many contiguous j-cells we have starting
 +     * from the first i-cell. This number can be used to directly
 +     * calculate j-cell indices for excluded atoms.
 +     */
 +    numDirect = numContiguousJClusters(cjIndexStart, cjIndexEnd, cjList);
 +}
 +#endif // !DOXYGEN
 +
 +/* Return the index of \p jCluster in the given range or -1 when not present
 + *
 + * Note: This code is executed very often and therefore performance is
 + *       important. It should be inlined and fully optimized.
 + */
 +template <typename CjListType>
 +static inline int
 +findJClusterInJList(int                              jCluster,
 +                    const JListRanges               &ranges,
 +                    gmx::ArrayRef<const CjListType>  cjList)
 +{
 +    int index;
 +
 +    if (jCluster < ranges.cjFirst + ranges.numDirect)
 +    {
 +        /* We can calculate the index directly using the offset */
 +        index = ranges.cjIndexStart + jCluster - ranges.cjFirst;
 +    }
 +    else
 +    {
 +        /* Search for jCluster using bisection */
 +        index           = -1;
 +        int rangeStart  = ranges.cjIndexStart + ranges.numDirect;
 +        int rangeEnd    = ranges.cjIndexEnd;
 +        int rangeMiddle;
 +        while (index == -1 && rangeStart < rangeEnd)
 +        {
 +            rangeMiddle = (rangeStart + rangeEnd) >> 1;
 +
 +            const int clusterMiddle = nblCj(cjList, rangeMiddle);
 +
 +            if (jCluster == clusterMiddle)
 +            {
 +                index      = rangeMiddle;
 +            }
 +            else if (jCluster < clusterMiddle)
 +            {
 +                rangeEnd   = rangeMiddle;
 +            }
 +            else
 +            {
 +                rangeStart = rangeMiddle + 1;
 +            }
 +        }
 +    }
 +
 +    return index;
 +}
 +
 +// TODO: Get rid of the two functions below by renaming sci to ci (or something better)
 +
 +/* Return the i-entry in the list we are currently operating on */
 +static nbnxn_ci_t *getOpenIEntry(NbnxnPairlistCpu *nbl)
 +{
 +    return &nbl->ci.back();
 +}
 +
 +/* Return the i-entry in the list we are currently operating on */
 +static nbnxn_sci_t *getOpenIEntry(NbnxnPairlistGpu *nbl)
 +{
 +    return &nbl->sci.back();
 +}
 +
 +/* Set all atom-pair exclusions for a simple type list i-entry
 + *
 + * Set all atom-pair exclusions from the topology stored in exclusions
 + * as masks in the pair-list for simple list entry iEntry.
 + */
 +static void
 +setExclusionsForIEntry(const Nbnxm::GridSet &gridSet,
 +                       NbnxnPairlistCpu     *nbl,
 +                       gmx_bool              diagRemoved,
 +                       int                   na_cj_2log,
 +                       const nbnxn_ci_t     &iEntry,
 +                       const t_blocka       &exclusions)
 +{
 +    if (iEntry.cj_ind_end == iEntry.cj_ind_start)
 +    {
 +        /* Empty list: no exclusions */
 +        return;
 +    }
 +
 +    const JListRanges        ranges(iEntry.cj_ind_start, iEntry.cj_ind_end, gmx::makeConstArrayRef(nbl->cj));
 +
 +    const int                iCluster = iEntry.ci;
 +
 +    gmx::ArrayRef<const int> cell        = gridSet.cells();
 +    gmx::ArrayRef<const int> atomIndices = gridSet.atomIndices();
 +
 +    /* Loop over the atoms in the i-cluster */
 +    for (int i = 0; i < nbl->na_ci; i++)
 +    {
 +        const int iIndex = iCluster*nbl->na_ci + i;
 +        const int iAtom  = atomIndices[iIndex];
 +        if (iAtom >= 0)
 +        {
 +            /* Loop over the topology-based exclusions for this i-atom */
 +            for (int exclIndex = exclusions.index[iAtom]; exclIndex < exclusions.index[iAtom + 1]; exclIndex++)
 +            {
 +                const int jAtom = exclusions.a[exclIndex];
 +
 +                if (jAtom == iAtom)
 +                {
 +                    /* The self exclusion are already set, save some time */
 +                    continue;
 +                }
 +
 +                /* Get the index of the j-atom in the nbnxn atom data */
 +                const int jIndex = cell[jAtom];
 +
 +                /* Without shifts we only calculate interactions j>i
 +                 * for one-way pair-lists.
 +                 */
 +                if (diagRemoved && jIndex <= iIndex)
 +                {
 +                    continue;
 +                }
 +
 +                const int jCluster = (jIndex >> na_cj_2log);
 +
 +                /* Could the cluster se be in our list? */
 +                if (jCluster >= ranges.cjFirst && jCluster <= ranges.cjLast)
 +                {
 +                    const int index =
 +                        findJClusterInJList(jCluster, ranges,
 +                                            gmx::makeConstArrayRef(nbl->cj));
 +
 +                    if (index >= 0)
 +                    {
 +                        /* We found an exclusion, clear the corresponding
 +                         * interaction bit.
 +                         */
 +                        const int innerJ     = jIndex - (jCluster << na_cj_2log);
 +
 +                        nbl->cj[index].excl &= ~(1U << ((i << na_cj_2log) + innerJ));
 +                    }
 +                }
 +            }
 +        }
 +    }
 +}
 +
 +/* Add a new i-entry to the FEP list and copy the i-properties */
 +static inline void fep_list_new_nri_copy(t_nblist *nlist)
 +{
 +    /* Add a new i-entry */
 +    nlist->nri++;
 +
 +    assert(nlist->nri < nlist->maxnri);
 +
 +    /* Duplicate the last i-entry, except for jindex, which continues */
 +    nlist->iinr[nlist->nri]   = nlist->iinr[nlist->nri-1];
 +    nlist->shift[nlist->nri]  = nlist->shift[nlist->nri-1];
 +    nlist->gid[nlist->nri]    = nlist->gid[nlist->nri-1];
 +    nlist->jindex[nlist->nri] = nlist->nrj;
 +}
 +
 +/* Rellocate FEP list for size nl->maxnri, TODO: replace by C++ */
 +static void reallocate_nblist(t_nblist *nl)
 +{
 +    if (gmx_debug_at)
 +    {
 +        fprintf(debug, "reallocating neigborlist (ielec=%d, ivdw=%d, igeometry=%d, type=%d), maxnri=%d\n",
 +                nl->ielec, nl->ivdw, nl->igeometry, nl->type, nl->maxnri);
 +    }
 +    srenew(nl->iinr,   nl->maxnri);
 +    srenew(nl->gid,    nl->maxnri);
 +    srenew(nl->shift,  nl->maxnri);
 +    srenew(nl->jindex, nl->maxnri+1);
 +}
 +
 +/* For load balancing of the free-energy lists over threads, we set
 + * the maximum nrj size of an i-entry to 40. This leads to good
 + * load balancing in the worst case scenario of a single perturbed
 + * particle on 16 threads, while not introducing significant overhead.
 + * Note that half of the perturbed pairs will anyhow end up in very small lists,
 + * since non perturbed i-particles will see few perturbed j-particles).
 + */
 +const int max_nrj_fep = 40;
 +
 +/* Exclude the perturbed pairs from the Verlet list. This is only done to avoid
 + * singularities for overlapping particles (0/0), since the charges and
 + * LJ parameters have been zeroed in the nbnxn data structure.
 + * Simultaneously make a group pair list for the perturbed pairs.
 + */
 +static void make_fep_list(gmx::ArrayRef<const int>  atomIndices,
 +                          const nbnxn_atomdata_t   *nbat,
 +                          NbnxnPairlistCpu         *nbl,
 +                          gmx_bool                  bDiagRemoved,
 +                          nbnxn_ci_t               *nbl_ci,
 +                          real gmx_unused           shx,
 +                          real gmx_unused           shy,
 +                          real gmx_unused           shz,
 +                          real gmx_unused           rlist_fep2,
 +                          const Grid               &iGrid,
 +                          const Grid               &jGrid,
 +                          t_nblist                 *nlist)
 +{
 +    int      ci, cj_ind_start, cj_ind_end, cja, cjr;
 +    int      nri_max;
 +    int      gid_i = 0, gid_j, gid;
 +    int      egp_shift, egp_mask;
 +    int      gid_cj = 0;
 +    int      ind_i, ind_j, ai, aj;
 +    int      nri;
 +    gmx_bool bFEP_i, bFEP_i_all;
 +
 +    if (nbl_ci->cj_ind_end == nbl_ci->cj_ind_start)
 +    {
 +        /* Empty list */
 +        return;
 +    }
 +
 +    ci = nbl_ci->ci;
 +
 +    cj_ind_start = nbl_ci->cj_ind_start;
 +    cj_ind_end   = nbl_ci->cj_ind_end;
 +
 +    /* In worst case we have alternating energy groups
 +     * and create #atom-pair lists, which means we need the size
 +     * of a cluster pair (na_ci*na_cj) times the number of cj's.
 +     */
 +    nri_max = nbl->na_ci*nbl->na_cj*(cj_ind_end - cj_ind_start);
 +    if (nlist->nri + nri_max > nlist->maxnri)
 +    {
 +        nlist->maxnri = over_alloc_large(nlist->nri + nri_max);
 +        reallocate_nblist(nlist);
 +    }
 +
 +    const int numAtomsJCluster = jGrid.geometry().numAtomsJCluster;
 +
 +    const nbnxn_atomdata_t::Params &nbatParams = nbat->params();
 +
 +    const int ngid = nbatParams.nenergrp;
 +
 +    /* TODO: Consider adding a check in grompp and changing this to an assert */
 +    const int numBitsInEnergyGroupIdsForAtomsInJCluster = sizeof(gid_cj)*8;
 +    if (ngid*numAtomsJCluster > numBitsInEnergyGroupIdsForAtomsInJCluster)
 +    {
 +        gmx_fatal(FARGS, "The Verlet scheme with %dx%d kernels and free-energy only supports up to %zu energy groups",
 +                  iGrid.geometry().numAtomsICluster, numAtomsJCluster,
 +                  (sizeof(gid_cj)*8)/numAtomsJCluster);
 +    }
 +
 +    egp_shift = nbatParams.neg_2log;
 +    egp_mask  = (1 << egp_shift) - 1;
 +
 +    /* Loop over the atoms in the i sub-cell */
 +    bFEP_i_all = TRUE;
 +    for (int i = 0; i < nbl->na_ci; i++)
 +    {
 +        ind_i = ci*nbl->na_ci + i;
 +        ai    = atomIndices[ind_i];
 +        if (ai >= 0)
 +        {
 +            nri                  = nlist->nri;
 +            nlist->jindex[nri+1] = nlist->jindex[nri];
 +            nlist->iinr[nri]     = ai;
 +            /* The actual energy group pair index is set later */
 +            nlist->gid[nri]      = 0;
 +            nlist->shift[nri]    = nbl_ci->shift & NBNXN_CI_SHIFT;
 +
 +            bFEP_i = iGrid.atomIsPerturbed(ci - iGrid.cellOffset(), i);
 +
 +            bFEP_i_all = bFEP_i_all && bFEP_i;
 +
 +            if (nlist->nrj + (cj_ind_end - cj_ind_start)*nbl->na_cj > nlist->maxnrj)
 +            {
 +                nlist->maxnrj = over_alloc_small(nlist->nrj + (cj_ind_end - cj_ind_start)*nbl->na_cj);
 +                srenew(nlist->jjnr,     nlist->maxnrj);
 +                srenew(nlist->excl_fep, nlist->maxnrj);
 +            }
 +
 +            if (ngid > 1)
 +            {
 +                gid_i = (nbatParams.energrp[ci] >> (egp_shift*i)) & egp_mask;
 +            }
 +
 +            for (int cj_ind = cj_ind_start; cj_ind < cj_ind_end; cj_ind++)
 +            {
 +                unsigned int fep_cj;
 +
 +                cja = nbl->cj[cj_ind].cj;
 +
 +                if (numAtomsJCluster == jGrid.geometry().numAtomsICluster)
 +                {
 +                    cjr    = cja - jGrid.cellOffset();
 +                    fep_cj = jGrid.fepBits(cjr);
 +                    if (ngid > 1)
 +                    {
 +                        gid_cj = nbatParams.energrp[cja];
 +                    }
 +                }
 +                else if (2*numAtomsJCluster == jGrid.geometry().numAtomsICluster)
 +                {
 +                    cjr    = cja - jGrid.cellOffset()*2;
 +                    /* Extract half of the ci fep/energrp mask */
 +                    fep_cj = (jGrid.fepBits(cjr >> 1) >> ((cjr & 1)*numAtomsJCluster)) & ((1 << numAtomsJCluster) - 1);
 +                    if (ngid > 1)
 +                    {
 +                        gid_cj = nbatParams.energrp[cja >> 1] >> ((cja & 1)*numAtomsJCluster*egp_shift) & ((1 << (numAtomsJCluster*egp_shift)) - 1);
 +                    }
 +                }
 +                else
 +                {
 +                    cjr    = cja - (jGrid.cellOffset() >> 1);
 +                    /* Combine two ci fep masks/energrp */
 +                    fep_cj = jGrid.fepBits(cjr*2) + (jGrid.fepBits(cjr*2 + 1) << jGrid.geometry().numAtomsICluster);
 +                    if (ngid > 1)
 +                    {
 +                        gid_cj = nbatParams.energrp[cja*2] + (nbatParams.energrp[cja*2+1] << (jGrid.geometry().numAtomsICluster*egp_shift));
 +                    }
 +                }
 +
 +                if (bFEP_i || fep_cj != 0)
 +                {
 +                    for (int j = 0; j < nbl->na_cj; j++)
 +                    {
 +                        /* Is this interaction perturbed and not excluded? */
 +                        ind_j = cja*nbl->na_cj + j;
 +                        aj    = atomIndices[ind_j];
 +                        if (aj >= 0 &&
 +                            (bFEP_i || (fep_cj & (1 << j))) &&
 +                            (!bDiagRemoved || ind_j >= ind_i))
 +                        {
 +                            if (ngid > 1)
 +                            {
 +                                gid_j = (gid_cj >> (j*egp_shift)) & egp_mask;
 +                                gid   = GID(gid_i, gid_j, ngid);
 +
 +                                if (nlist->nrj > nlist->jindex[nri] &&
 +                                    nlist->gid[nri] != gid)
 +                                {
 +                                    /* Energy group pair changed: new list */
 +                                    fep_list_new_nri_copy(nlist);
 +                                    nri = nlist->nri;
 +                                }
 +                                nlist->gid[nri] = gid;
 +                            }
 +
 +                            if (nlist->nrj - nlist->jindex[nri] >= max_nrj_fep)
 +                            {
 +                                fep_list_new_nri_copy(nlist);
 +                                nri = nlist->nri;
 +                            }
 +
 +                            /* Add it to the FEP list */
 +                            nlist->jjnr[nlist->nrj]     = aj;
 +                            nlist->excl_fep[nlist->nrj] = (nbl->cj[cj_ind].excl >> (i*nbl->na_cj + j)) & 1;
 +                            nlist->nrj++;
 +
 +                            /* Exclude it from the normal list.
 +                             * Note that the charge has been set to zero,
 +                             * but we need to avoid 0/0, as perturbed atoms
 +                             * can be on top of each other.
 +                             */
 +                            nbl->cj[cj_ind].excl &= ~(1U << (i*nbl->na_cj + j));
 +                        }
 +                    }
 +                }
 +            }
 +
 +            if (nlist->nrj > nlist->jindex[nri])
 +            {
 +                /* Actually add this new, non-empty, list */
 +                nlist->nri++;
 +                nlist->jindex[nlist->nri] = nlist->nrj;
 +            }
 +        }
 +    }
 +
 +    if (bFEP_i_all)
 +    {
 +        /* All interactions are perturbed, we can skip this entry */
 +        nbl_ci->cj_ind_end = cj_ind_start;
 +        nbl->ncjInUse     -= cj_ind_end - cj_ind_start;
 +    }
 +}
 +
 +/* Return the index of atom a within a cluster */
 +static inline int cj_mod_cj4(int cj)
 +{
 +    return cj & (c_nbnxnGpuJgroupSize - 1);
 +}
 +
 +/* Convert a j-cluster to a cj4 group */
 +static inline int cj_to_cj4(int cj)
 +{
 +    return cj/c_nbnxnGpuJgroupSize;
 +}
 +
 +/* Return the index of an j-atom within a warp */
 +static inline int a_mod_wj(int a)
 +{
 +    return a & (c_nbnxnGpuClusterSize/c_nbnxnGpuClusterpairSplit - 1);
 +}
 +
 +/* As make_fep_list above, but for super/sub lists. */
 +static void make_fep_list(gmx::ArrayRef<const int>  atomIndices,
 +                          const nbnxn_atomdata_t   *nbat,
 +                          NbnxnPairlistGpu         *nbl,
 +                          gmx_bool                  bDiagRemoved,
 +                          const nbnxn_sci_t        *nbl_sci,
 +                          real                      shx,
 +                          real                      shy,
 +                          real                      shz,
 +                          real                      rlist_fep2,
 +                          const Grid               &iGrid,
 +                          const Grid               &jGrid,
 +                          t_nblist                 *nlist)
 +{
 +    int                nri_max;
 +    int                c_abs;
 +    int                ind_i, ind_j, ai, aj;
 +    int                nri;
 +    gmx_bool           bFEP_i;
 +    real               xi, yi, zi;
 +    const nbnxn_cj4_t *cj4;
 +
 +    const int          numJClusterGroups = nbl_sci->numJClusterGroups();
 +    if (numJClusterGroups == 0)
 +    {
 +        /* Empty list */
 +        return;
 +    }
 +
 +    const int sci           = nbl_sci->sci;
 +
 +    const int cj4_ind_start = nbl_sci->cj4_ind_start;
 +    const int cj4_ind_end   = nbl_sci->cj4_ind_end;
 +
 +    /* Here we process one super-cell, max #atoms na_sc, versus a list
 +     * cj4 entries, each with max c_nbnxnGpuJgroupSize cj's, each
 +     * of size na_cj atoms.
 +     * On the GPU we don't support energy groups (yet).
 +     * So for each of the na_sc i-atoms, we need max one FEP list
 +     * for each max_nrj_fep j-atoms.
 +     */
 +    nri_max = nbl->na_sc*nbl->na_cj*(1 + (numJClusterGroups*c_nbnxnGpuJgroupSize)/max_nrj_fep);
 +    if (nlist->nri + nri_max > nlist->maxnri)
 +    {
 +        nlist->maxnri = over_alloc_large(nlist->nri + nri_max);
 +        reallocate_nblist(nlist);
 +    }
 +
 +    /* Loop over the atoms in the i super-cluster */
 +    for (int c = 0; c < c_gpuNumClusterPerCell; c++)
 +    {
 +        c_abs = sci*c_gpuNumClusterPerCell + c;
 +
 +        for (int i = 0; i < nbl->na_ci; i++)
 +        {
 +            ind_i = c_abs*nbl->na_ci + i;
 +            ai    = atomIndices[ind_i];
 +            if (ai >= 0)
 +            {
 +                nri                  = nlist->nri;
 +                nlist->jindex[nri+1] = nlist->jindex[nri];
 +                nlist->iinr[nri]     = ai;
 +                /* With GPUs, energy groups are not supported */
 +                nlist->gid[nri]      = 0;
 +                nlist->shift[nri]    = nbl_sci->shift & NBNXN_CI_SHIFT;
 +
 +                bFEP_i = iGrid.atomIsPerturbed(c_abs - iGrid.cellOffset()*c_gpuNumClusterPerCell, i);
 +
 +                xi = nbat->x()[ind_i*nbat->xstride+XX] + shx;
 +                yi = nbat->x()[ind_i*nbat->xstride+YY] + shy;
 +                zi = nbat->x()[ind_i*nbat->xstride+ZZ] + shz;
 +
 +                const int nrjMax = nlist->nrj + numJClusterGroups*c_nbnxnGpuJgroupSize*nbl->na_cj;
 +                if (nrjMax > nlist->maxnrj)
 +                {
 +                    nlist->maxnrj = over_alloc_small(nrjMax);
 +                    srenew(nlist->jjnr,     nlist->maxnrj);
 +                    srenew(nlist->excl_fep, nlist->maxnrj);
 +                }
 +
 +                for (int cj4_ind = cj4_ind_start; cj4_ind < cj4_ind_end; cj4_ind++)
 +                {
 +                    cj4 = &nbl->cj4[cj4_ind];
 +
 +                    for (int gcj = 0; gcj < c_nbnxnGpuJgroupSize; gcj++)
 +                    {
 +                        if ((cj4->imei[0].imask & (1U << (gcj*c_gpuNumClusterPerCell + c))) == 0)
 +                        {
 +                            /* Skip this ci for this cj */
 +                            continue;
 +                        }
 +
 +                        const int cjr =
 +                            cj4->cj[gcj] - jGrid.cellOffset()*c_gpuNumClusterPerCell;
 +
 +                        if (bFEP_i || jGrid.clusterIsPerturbed(cjr))
 +                        {
 +                            for (int j = 0; j < nbl->na_cj; j++)
 +                            {
 +                                /* Is this interaction perturbed and not excluded? */
 +                                ind_j = (jGrid.cellOffset()*c_gpuNumClusterPerCell + cjr)*nbl->na_cj + j;
 +                                aj    = atomIndices[ind_j];
 +                                if (aj >= 0 &&
 +                                    (bFEP_i || jGrid.atomIsPerturbed(cjr, j)) &&
 +                                    (!bDiagRemoved || ind_j >= ind_i))
 +                                {
 +                                    int           excl_pair;
 +                                    unsigned int  excl_bit;
 +                                    real          dx, dy, dz;
 +
 +                                    const int     jHalf = j/(c_nbnxnGpuClusterSize/c_nbnxnGpuClusterpairSplit);
 +                                    nbnxn_excl_t *excl  =
 +                                        get_exclusion_mask(nbl, cj4_ind, jHalf);
 +
 +                                    excl_pair = a_mod_wj(j)*nbl->na_ci + i;
 +                                    excl_bit  = (1U << (gcj*c_gpuNumClusterPerCell + c));
 +
 +                                    dx = nbat->x()[ind_j*nbat->xstride+XX] - xi;
 +                                    dy = nbat->x()[ind_j*nbat->xstride+YY] - yi;
 +                                    dz = nbat->x()[ind_j*nbat->xstride+ZZ] - zi;
 +
 +                                    /* The unpruned GPU list has more than 2/3
 +                                     * of the atom pairs beyond rlist. Using
 +                                     * this list will cause a lot of overhead
 +                                     * in the CPU FEP kernels, especially
 +                                     * relative to the fast GPU kernels.
 +                                     * So we prune the FEP list here.
 +                                     */
 +                                    if (dx*dx + dy*dy + dz*dz < rlist_fep2)
 +                                    {
 +                                        if (nlist->nrj - nlist->jindex[nri] >= max_nrj_fep)
 +                                        {
 +                                            fep_list_new_nri_copy(nlist);
 +                                            nri = nlist->nri;
 +                                        }
 +
 +                                        /* Add it to the FEP list */
 +                                        nlist->jjnr[nlist->nrj]     = aj;
 +                                        nlist->excl_fep[nlist->nrj] = (excl->pair[excl_pair] & excl_bit) ? 1 : 0;
 +                                        nlist->nrj++;
 +                                    }
 +
 +                                    /* Exclude it from the normal list.
 +                                     * Note that the charge and LJ parameters have
 +                                     * been set to zero, but we need to avoid 0/0,
 +                                     * as perturbed atoms can be on top of each other.
 +                                     */
 +                                    excl->pair[excl_pair] &= ~excl_bit;
 +                                }
 +                            }
 +
 +                            /* Note that we could mask out this pair in imask
 +                             * if all i- and/or all j-particles are perturbed.
 +                             * But since the perturbed pairs on the CPU will
 +                             * take an order of magnitude more time, the GPU
 +                             * will finish before the CPU and there is no gain.
 +                             */
 +                        }
 +                    }
 +                }
 +
 +                if (nlist->nrj > nlist->jindex[nri])
 +                {
 +                    /* Actually add this new, non-empty, list */
 +                    nlist->nri++;
 +                    nlist->jindex[nlist->nri] = nlist->nrj;
 +                }
 +            }
 +        }
 +    }
 +}
 +
 +/* Set all atom-pair exclusions for a GPU type list i-entry
 + *
 + * Sets all atom-pair exclusions from the topology stored in exclusions
 + * as masks in the pair-list for i-super-cluster list entry iEntry.
 + */
 +static void
 +setExclusionsForIEntry(const Nbnxm::GridSet &gridSet,
 +                       NbnxnPairlistGpu     *nbl,
 +                       gmx_bool              diagRemoved,
 +                       int gmx_unused        na_cj_2log,
 +                       const nbnxn_sci_t    &iEntry,
 +                       const t_blocka       &exclusions)
 +{
 +    if (iEntry.numJClusterGroups() == 0)
 +    {
 +        /* Empty list */
 +        return;
 +    }
 +
 +    /* Set the search ranges using start and end j-cluster indices.
 +     * Note that here we can not use cj4_ind_end, since the last cj4
 +     * can be only partially filled, so we use cj_ind.
 +     */
 +    const JListRanges ranges(iEntry.cj4_ind_start*c_nbnxnGpuJgroupSize,
 +                             nbl->work->cj_ind,
 +                             gmx::makeConstArrayRef(nbl->cj4));
 +
 +    GMX_ASSERT(nbl->na_ci == c_nbnxnGpuClusterSize, "na_ci should match the GPU cluster size");
 +    constexpr int            c_clusterSize      = c_nbnxnGpuClusterSize;
 +    constexpr int            c_superClusterSize = c_nbnxnGpuNumClusterPerSupercluster*c_nbnxnGpuClusterSize;
 +
 +    const int                iSuperCluster = iEntry.sci;
 +
 +    gmx::ArrayRef<const int> atomIndices   = gridSet.atomIndices();
 +    gmx::ArrayRef<const int> cell          = gridSet.cells();
 +
 +    /* Loop over the atoms in the i super-cluster */
 +    for (int i = 0; i < c_superClusterSize; i++)
 +    {
 +        const int iIndex = iSuperCluster*c_superClusterSize + i;
 +        const int iAtom  = atomIndices[iIndex];
 +        if (iAtom >= 0)
 +        {
 +            const int iCluster = i/c_clusterSize;
 +
 +            /* Loop over the topology-based exclusions for this i-atom */
 +            for (int exclIndex = exclusions.index[iAtom]; exclIndex < exclusions.index[iAtom + 1]; exclIndex++)
 +            {
 +                const int jAtom = exclusions.a[exclIndex];
 +
 +                if (jAtom == iAtom)
 +                {
 +                    /* The self exclusions are already set, save some time */
 +                    continue;
 +                }
 +
 +                /* Get the index of the j-atom in the nbnxn atom data */
 +                const int jIndex = cell[jAtom];
 +
 +                /* Without shifts we only calculate interactions j>i
 +                 * for one-way pair-lists.
 +                 */
 +                /* NOTE: We would like to use iIndex on the right hand side,
 +                 * but that makes this routine 25% slower with gcc6/7.
 +                 * Even using c_superClusterSize makes it slower.
 +                 * Either of these changes triggers peeling of the exclIndex
 +                 * loop, which apparently leads to far less efficient code.
 +                 */
 +                if (diagRemoved && jIndex <= iSuperCluster*nbl->na_sc + i)
 +                {
 +                    continue;
 +                }
 +
 +                const int jCluster = jIndex/c_clusterSize;
 +
 +                /* Check whether the cluster is in our list? */
 +                if (jCluster >= ranges.cjFirst && jCluster <= ranges.cjLast)
 +                {
 +                    const int index =
 +                        findJClusterInJList(jCluster, ranges,
 +                                            gmx::makeConstArrayRef(nbl->cj4));
 +
 +                    if (index >= 0)
 +                    {
 +                        /* We found an exclusion, clear the corresponding
 +                         * interaction bit.
 +                         */
 +                        const unsigned int pairMask = (1U << (cj_mod_cj4(index)*c_gpuNumClusterPerCell + iCluster));
 +                        /* Check if the i-cluster interacts with the j-cluster */
 +                        if (nbl_imask0(nbl, index) & pairMask)
 +                        {
 +                            const int innerI = (i      & (c_clusterSize - 1));
 +                            const int innerJ = (jIndex & (c_clusterSize - 1));
 +
 +                            /* Determine which j-half (CUDA warp) we are in */
 +                            const int     jHalf = innerJ/(c_clusterSize/c_nbnxnGpuClusterpairSplit);
 +
 +                            nbnxn_excl_t *interactionMask =
 +                                get_exclusion_mask(nbl, cj_to_cj4(index), jHalf);
 +
 +                            interactionMask->pair[a_mod_wj(innerJ)*c_clusterSize + innerI] &= ~pairMask;
 +                        }
 +                    }
 +                }
 +            }
 +        }
 +    }
 +}
 +
 +/* Make a new ci entry at the back of nbl->ci */
 +static void addNewIEntry(NbnxnPairlistCpu *nbl, int ci, int shift, int flags)
 +{
 +    nbnxn_ci_t ciEntry;
 +    ciEntry.ci            = ci;
 +    ciEntry.shift         = shift;
 +    /* Store the interaction flags along with the shift */
 +    ciEntry.shift        |= flags;
 +    ciEntry.cj_ind_start  = nbl->cj.size();
 +    ciEntry.cj_ind_end    = nbl->cj.size();
 +    nbl->ci.push_back(ciEntry);
 +}
 +
 +/* Make a new sci entry at index nbl->nsci */
 +static void addNewIEntry(NbnxnPairlistGpu *nbl, int sci, int shift, int gmx_unused flags)
 +{
 +    nbnxn_sci_t sciEntry;
 +    sciEntry.sci           = sci;
 +    sciEntry.shift         = shift;
 +    sciEntry.cj4_ind_start = nbl->cj4.size();
 +    sciEntry.cj4_ind_end   = nbl->cj4.size();
 +
 +    nbl->sci.push_back(sciEntry);
 +}
 +
 +/* Sort the simple j-list cj on exclusions.
 + * Entries with exclusions will all be sorted to the beginning of the list.
 + */
 +static void sort_cj_excl(nbnxn_cj_t *cj, int ncj,
 +                         NbnxnPairlistCpuWork *work)
 +{
 +    work->cj.resize(ncj);
 +
 +    /* Make a list of the j-cells involving exclusions */
 +    int jnew = 0;
 +    for (int j = 0; j < ncj; j++)
 +    {
 +        if (cj[j].excl != NBNXN_INTERACTION_MASK_ALL)
 +        {
 +            work->cj[jnew++] = cj[j];
 +        }
 +    }
 +    /* Check if there are exclusions at all or not just the first entry */
 +    if (!((jnew == 0) ||
 +          (jnew == 1 && cj[0].excl != NBNXN_INTERACTION_MASK_ALL)))
 +    {
 +        for (int j = 0; j < ncj; j++)
 +        {
 +            if (cj[j].excl == NBNXN_INTERACTION_MASK_ALL)
 +            {
 +                work->cj[jnew++] = cj[j];
 +            }
 +        }
 +        for (int j = 0; j < ncj; j++)
 +        {
 +            cj[j] = work->cj[j];
 +        }
 +    }
 +}
 +
 +/* Close this simple list i entry */
 +static void closeIEntry(NbnxnPairlistCpu    *nbl,
 +                        int gmx_unused       sp_max_av,
 +                        gmx_bool gmx_unused  progBal,
 +                        float gmx_unused     nsp_tot_est,
 +                        int gmx_unused       thread,
 +                        int gmx_unused       nthread)
 +{
 +    nbnxn_ci_t &ciEntry = nbl->ci.back();
 +
 +    /* All content of the new ci entry have already been filled correctly,
 +     * we only need to sort and increase counts or remove the entry when empty.
 +     */
 +    const int jlen = ciEntry.cj_ind_end - ciEntry.cj_ind_start;
 +    if (jlen > 0)
 +    {
 +        sort_cj_excl(nbl->cj.data() + ciEntry.cj_ind_start, jlen, nbl->work.get());
 +
 +        /* The counts below are used for non-bonded pair/flop counts
 +         * and should therefore match the available kernel setups.
 +         */
 +        if (!(ciEntry.shift & NBNXN_CI_DO_COUL(0)))
 +        {
 +            nbl->work->ncj_noq += jlen;
 +        }
 +        else if ((ciEntry.shift & NBNXN_CI_HALF_LJ(0)) ||
 +                 !(ciEntry.shift & NBNXN_CI_DO_LJ(0)))
 +        {
 +            nbl->work->ncj_hlj += jlen;
 +        }
 +    }
 +    else
 +    {
 +        /* Entry is empty: remove it  */
 +        nbl->ci.pop_back();
 +    }
 +}
 +
 +/* Split sci entry for load balancing on the GPU.
 + * Splitting ensures we have enough lists to fully utilize the whole GPU.
 + * With progBal we generate progressively smaller lists, which improves
 + * load balancing. As we only know the current count on our own thread,
 + * we will need to estimate the current total amount of i-entries.
 + * As the lists get concatenated later, this estimate depends
 + * both on nthread and our own thread index.
 + */
 +static void split_sci_entry(NbnxnPairlistGpu *nbl,
 +                            int nsp_target_av,
 +                            gmx_bool progBal, float nsp_tot_est,
 +                            int thread, int nthread)
 +{
 +    int nsp_max;
 +
 +    if (progBal)
 +    {
 +        float nsp_est;
 +
 +        /* Estimate the total numbers of ci's of the nblist combined
 +         * over all threads using the target number of ci's.
 +         */
 +        nsp_est = (nsp_tot_est*thread)/nthread + nbl->nci_tot;
 +
 +        /* The first ci blocks should be larger, to avoid overhead.
 +         * The last ci blocks should be smaller, to improve load balancing.
 +         * The factor 3/2 makes the first block 3/2 times the target average
 +         * and ensures that the total number of blocks end up equal to
 +         * that of equally sized blocks of size nsp_target_av.
 +         */
 +        nsp_max = static_cast<int>(nsp_target_av*(nsp_tot_est*1.5/(nsp_est + nsp_tot_est)));
 +    }
 +    else
 +    {
 +        nsp_max = nsp_target_av;
 +    }
 +
 +    const int cj4_start = nbl->sci.back().cj4_ind_start;
 +    const int cj4_end   = nbl->sci.back().cj4_ind_end;
 +    const int j4len     = cj4_end - cj4_start;
 +
 +    if (j4len > 1 && j4len*c_gpuNumClusterPerCell*c_nbnxnGpuJgroupSize > nsp_max)
 +    {
 +        /* Modify the last ci entry and process the cj4's again */
 +
 +        int nsp        = 0;
 +        int nsp_sci    = 0;
 +        int nsp_cj4_e  = 0;
 +        int nsp_cj4    = 0;
 +        for (int cj4 = cj4_start; cj4 < cj4_end; cj4++)
 +        {
 +            int nsp_cj4_p = nsp_cj4;
 +            /* Count the number of cluster pairs in this cj4 group */
 +            nsp_cj4   = 0;
 +            for (int p = 0; p < c_gpuNumClusterPerCell*c_nbnxnGpuJgroupSize; p++)
 +            {
 +                nsp_cj4 += (nbl->cj4[cj4].imei[0].imask >> p) & 1;
 +            }
 +
 +            /* If adding the current cj4 with nsp_cj4 pairs get us further
 +             * away from our target nsp_max, split the list before this cj4.
 +             */
 +            if (nsp > 0 && nsp_max - nsp < nsp + nsp_cj4 - nsp_max)
 +            {
 +                /* Split the list at cj4 */
 +                nbl->sci.back().cj4_ind_end = cj4;
 +                /* Create a new sci entry */
 +                nbnxn_sci_t sciNew;
 +                sciNew.sci           = nbl->sci.back().sci;
 +                sciNew.shift         = nbl->sci.back().shift;
 +                sciNew.cj4_ind_start = cj4;
 +                nbl->sci.push_back(sciNew);
 +
 +                nsp_sci              = nsp;
 +                nsp_cj4_e            = nsp_cj4_p;
 +                nsp                  = 0;
 +            }
 +            nsp += nsp_cj4;
 +        }
 +
 +        /* Put the remaining cj4's in the last sci entry */
 +        nbl->sci.back().cj4_ind_end = cj4_end;
 +
 +        /* Possibly balance out the last two sci's
 +         * by moving the last cj4 of the second last sci.
 +         */
 +        if (nsp_sci - nsp_cj4_e >= nsp + nsp_cj4_e)
 +        {
 +            GMX_ASSERT(nbl->sci.size() >= 2, "We expect at least two elements");
 +            nbl->sci[nbl->sci.size() - 2].cj4_ind_end--;
 +            nbl->sci[nbl->sci.size() - 1].cj4_ind_start--;
 +        }
 +    }
 +}
 +
 +/* Clost this super/sub list i entry */
 +static void closeIEntry(NbnxnPairlistGpu *nbl,
 +                        int nsp_max_av,
 +                        gmx_bool progBal, float nsp_tot_est,
 +                        int thread, int nthread)
 +{
 +    nbnxn_sci_t &sciEntry = *getOpenIEntry(nbl);
 +
 +    /* All content of the new ci entry have already been filled correctly,
 +     * we only need to, potentially, split or remove the entry when empty.
 +     */
 +    int j4len = sciEntry.numJClusterGroups();
 +    if (j4len > 0)
 +    {
 +        /* We can only have complete blocks of 4 j-entries in a list,
 +         * so round the count up before closing.
 +         */
 +        int ncj4          = (nbl->work->cj_ind + c_nbnxnGpuJgroupSize - 1)/c_nbnxnGpuJgroupSize;
 +        nbl->work->cj_ind = ncj4*c_nbnxnGpuJgroupSize;
 +
 +        if (nsp_max_av > 0)
 +        {
 +            /* Measure the size of the new entry and potentially split it */
 +            split_sci_entry(nbl, nsp_max_av, progBal, nsp_tot_est,
 +                            thread, nthread);
 +        }
 +    }
 +    else
 +    {
 +        /* Entry is empty: remove it  */
 +        nbl->sci.pop_back();
 +    }
 +}
 +
 +/* Syncs the working array before adding another grid pair to the GPU list */
 +static void sync_work(NbnxnPairlistCpu gmx_unused *nbl)
 +{
 +}
 +
 +/* Syncs the working array before adding another grid pair to the GPU list */
 +static void sync_work(NbnxnPairlistGpu *nbl)
 +{
 +    nbl->work->cj_ind   = nbl->cj4.size()*c_nbnxnGpuJgroupSize;
 +}
 +
 +/* Clears an NbnxnPairlistCpu data structure */
 +static void clear_pairlist(NbnxnPairlistCpu *nbl)
 +{
 +    nbl->ci.clear();
 +    nbl->cj.clear();
 +    nbl->ncjInUse      = 0;
 +    nbl->nci_tot       = 0;
 +    nbl->ciOuter.clear();
 +    nbl->cjOuter.clear();
 +
 +    nbl->work->ncj_noq = 0;
 +    nbl->work->ncj_hlj = 0;
 +}
 +
 +/* Clears an NbnxnPairlistGpu data structure */
 +static void clear_pairlist(NbnxnPairlistGpu *nbl)
 +{
 +    nbl->sci.clear();
 +    nbl->cj4.clear();
 +    nbl->excl.resize(1);
 +    nbl->nci_tot = 0;
 +}
 +
 +/* Clears a group scheme pair list */
 +static void clear_pairlist_fep(t_nblist *nl)
 +{
 +    nl->nri = 0;
 +    nl->nrj = 0;
 +    if (nl->jindex == nullptr)
 +    {
 +        snew(nl->jindex, 1);
 +    }
 +    nl->jindex[0] = 0;
 +}
 +
 +/* Sets a simple list i-cell bounding box, including PBC shift */
 +static inline void set_icell_bb_simple(gmx::ArrayRef<const BoundingBox> bb,
 +                                       int ci,
 +                                       real shx, real shy, real shz,
 +                                       BoundingBox *bb_ci)
 +{
 +    bb_ci->lower.x = bb[ci].lower.x + shx;
 +    bb_ci->lower.y = bb[ci].lower.y + shy;
 +    bb_ci->lower.z = bb[ci].lower.z + shz;
 +    bb_ci->upper.x = bb[ci].upper.x + shx;
 +    bb_ci->upper.y = bb[ci].upper.y + shy;
 +    bb_ci->upper.z = bb[ci].upper.z + shz;
 +}
 +
 +/* Sets a simple list i-cell bounding box, including PBC shift */
 +static inline void set_icell_bb(const Grid &iGrid,
 +                                int ci,
 +                                real shx, real shy, real shz,
 +                                NbnxnPairlistCpuWork *work)
 +{
 +    set_icell_bb_simple(iGrid.iBoundingBoxes(), ci, shx, shy, shz,
 +                        &work->iClusterData.bb[0]);
 +}
 +
 +#if NBNXN_BBXXXX
 +/* Sets a super-cell and sub cell bounding boxes, including PBC shift */
 +static void set_icell_bbxxxx_supersub(gmx::ArrayRef<const float> bb,
 +                                      int ci,
 +                                      real shx, real shy, real shz,
 +                                      float *bb_ci)
 +{
 +    constexpr int cellBBStride = packedBoundingBoxesIndex(c_gpuNumClusterPerCell);
 +    constexpr int pbbStride    = c_packedBoundingBoxesDimSize;
 +    const int     ia           = ci*cellBBStride;
 +    for (int m = 0; m < cellBBStride; m += c_packedBoundingBoxesSize)
 +    {
 +        for (int i = 0; i < pbbStride; i++)
 +        {
 +            bb_ci[m + 0*pbbStride + i] = bb[ia + m + 0*pbbStride + i] + shx;
 +            bb_ci[m + 1*pbbStride + i] = bb[ia + m + 1*pbbStride + i] + shy;
 +            bb_ci[m + 2*pbbStride + i] = bb[ia + m + 2*pbbStride + i] + shz;
 +            bb_ci[m + 3*pbbStride + i] = bb[ia + m + 3*pbbStride + i] + shx;
 +            bb_ci[m + 4*pbbStride + i] = bb[ia + m + 4*pbbStride + i] + shy;
 +            bb_ci[m + 5*pbbStride + i] = bb[ia + m + 5*pbbStride + i] + shz;
 +        }
 +    }
 +}
 +#endif
 +
 +/* Sets a super-cell and sub cell bounding boxes, including PBC shift */
 +gmx_unused static void set_icell_bb_supersub(gmx::ArrayRef<const BoundingBox> bb,
 +                                             int ci,
 +                                             real shx, real shy, real shz,
 +                                             BoundingBox *bb_ci)
 +{
 +    for (int i = 0; i < c_gpuNumClusterPerCell; i++)
 +    {
 +        set_icell_bb_simple(bb, ci*c_gpuNumClusterPerCell+i,
 +                            shx, shy, shz,
 +                            &bb_ci[i]);
 +    }
 +}
 +
 +/* Sets a super-cell and sub cell bounding boxes, including PBC shift */
 +gmx_unused static void set_icell_bb(const Grid &iGrid,
 +                                    int ci,
 +                                    real shx, real shy, real shz,
 +                                    NbnxnPairlistGpuWork *work)
 +{
 +#if NBNXN_BBXXXX
 +    set_icell_bbxxxx_supersub(iGrid.packedBoundingBoxes(), ci, shx, shy, shz,
 +                              work->iSuperClusterData.bbPacked.data());
 +#else
 +    set_icell_bb_supersub(iGrid.iBoundingBoxes(), ci, shx, shy, shz,
 +                          work->iSuperClusterData.bb.data());
 +#endif
 +}
 +
 +/* Copies PBC shifted i-cell atom coordinates x,y,z to working array */
 +static void icell_set_x_simple(int ci,
 +                               real shx, real shy, real shz,
 +                               int stride, const real *x,
 +                               NbnxnPairlistCpuWork::IClusterData *iClusterData)
 +{
 +    const int ia = ci*c_nbnxnCpuIClusterSize;
 +
 +    for (int i = 0; i < c_nbnxnCpuIClusterSize; i++)
 +    {
 +        iClusterData->x[i*STRIDE_XYZ+XX] = x[(ia+i)*stride+XX] + shx;
 +        iClusterData->x[i*STRIDE_XYZ+YY] = x[(ia+i)*stride+YY] + shy;
 +        iClusterData->x[i*STRIDE_XYZ+ZZ] = x[(ia+i)*stride+ZZ] + shz;
 +    }
 +}
 +
 +static void icell_set_x(int ci,
 +                        real shx, real shy, real shz,
 +                        int stride, const real *x,
 +                        const ClusterDistanceKernelType kernelType,
 +                        NbnxnPairlistCpuWork *work)
 +{
 +    switch (kernelType)
 +    {
 +#if GMX_SIMD
 +#ifdef GMX_NBNXN_SIMD_4XN
 +        case ClusterDistanceKernelType::CpuSimd_4xM:
 +            icell_set_x_simd_4xn(ci, shx, shy, shz, stride, x, work);
 +            break;
 +#endif
 +#ifdef GMX_NBNXN_SIMD_2XNN
 +        case ClusterDistanceKernelType::CpuSimd_2xMM:
 +            icell_set_x_simd_2xnn(ci, shx, shy, shz, stride, x, work);
 +            break;
 +#endif
 +#endif
 +        case ClusterDistanceKernelType::CpuPlainC:
 +            icell_set_x_simple(ci, shx, shy, shz, stride, x, &work->iClusterData);
 +            break;
 +        default:
 +            GMX_ASSERT(false, "Unhandled case");
 +            break;
 +    }
 +}
 +
 +/* Copies PBC shifted super-cell atom coordinates x,y,z to working array */
 +static void icell_set_x(int ci,
 +                        real shx, real shy, real shz,
 +                        int stride, const real *x,
 +                        ClusterDistanceKernelType gmx_unused kernelType,
 +                        NbnxnPairlistGpuWork *work)
 +{
 +#if !GMX_SIMD4_HAVE_REAL
 +
 +    real * x_ci = work->iSuperClusterData.x.data();
 +
 +    int    ia = ci*c_gpuNumClusterPerCell*c_nbnxnGpuClusterSize;
 +    for (int i = 0; i < c_gpuNumClusterPerCell*c_nbnxnGpuClusterSize; i++)
 +    {
 +        x_ci[i*DIM + XX] = x[(ia+i)*stride + XX] + shx;
 +        x_ci[i*DIM + YY] = x[(ia+i)*stride + YY] + shy;
 +        x_ci[i*DIM + ZZ] = x[(ia+i)*stride + ZZ] + shz;
 +    }
 +
 +#else /* !GMX_SIMD4_HAVE_REAL */
 +
 +    real * x_ci = work->iSuperClusterData.xSimd.data();
 +
 +    for (int si = 0; si < c_gpuNumClusterPerCell; si++)
 +    {
 +        for (int i = 0; i < c_nbnxnGpuClusterSize; i += GMX_SIMD4_WIDTH)
 +        {
 +            int io = si*c_nbnxnGpuClusterSize + i;
 +            int ia = ci*c_gpuNumClusterPerCell*c_nbnxnGpuClusterSize + io;
 +            for (int j = 0; j < GMX_SIMD4_WIDTH; j++)
 +            {
 +                x_ci[io*DIM + j + XX*GMX_SIMD4_WIDTH] = x[(ia + j)*stride + XX] + shx;
 +                x_ci[io*DIM + j + YY*GMX_SIMD4_WIDTH] = x[(ia + j)*stride + YY] + shy;
 +                x_ci[io*DIM + j + ZZ*GMX_SIMD4_WIDTH] = x[(ia + j)*stride + ZZ] + shz;
 +            }
 +        }
 +    }
 +
 +#endif /* !GMX_SIMD4_HAVE_REAL */
 +}
 +
 +static real minimum_subgrid_size_xy(const Grid &grid)
 +{
 +    const Grid::Dimensions &dims = grid.dimensions();
 +
 +    if (grid.geometry().isSimple)
 +    {
 +        return std::min(dims.cellSize[XX], dims.cellSize[YY]);
 +    }
 +    else
 +    {
 +        return std::min(dims.cellSize[XX]/c_gpuNumClusterPerCellX,
 +                        dims.cellSize[YY]/c_gpuNumClusterPerCellY);
 +    }
 +}
 +
 +static real effective_buffer_1x1_vs_MxN(const Grid &iGrid,
 +                                        const Grid &jGrid)
 +{
 +    const real eff_1x1_buffer_fac_overest = 0.1;
 +
 +    /* Determine an atom-pair list cut-off buffer size for atom pairs,
 +     * to be added to rlist (including buffer) used for MxN.
 +     * This is for converting an MxN list to a 1x1 list. This means we can't
 +     * use the normal buffer estimate, as we have an MxN list in which
 +     * some atom pairs beyond rlist are missing. We want to capture
 +     * the beneficial effect of buffering by extra pairs just outside rlist,
 +     * while removing the useless pairs that are further away from rlist.
 +     * (Also the buffer could have been set manually not using the estimate.)
 +     * This buffer size is an overestimate.
 +     * We add 10% of the smallest grid sub-cell dimensions.
 +     * Note that the z-size differs per cell and we don't use this,
 +     * so we overestimate.
 +     * With PME, the 10% value gives a buffer that is somewhat larger
 +     * than the effective buffer with a tolerance of 0.005 kJ/mol/ps.
 +     * Smaller tolerances or using RF lead to a smaller effective buffer,
 +     * so 10% gives a safe overestimate.
 +     */
 +    return eff_1x1_buffer_fac_overest*(minimum_subgrid_size_xy(iGrid) +
 +                                       minimum_subgrid_size_xy(jGrid));
 +}
 +
 +/* Estimates the interaction volume^2 for non-local interactions */
 +static real nonlocal_vol2(const struct gmx_domdec_zones_t *zones, const rvec ls, real r)
 +{
 +    real cl, ca, za;
 +    real vold_est;
 +    real vol2_est_tot;
 +
 +    vol2_est_tot = 0;
 +
 +    /* Here we simply add up the volumes of 1, 2 or 3 1D decomposition
 +     * not home interaction volume^2. As these volumes are not additive,
 +     * this is an overestimate, but it would only be significant in the limit
 +     * of small cells, where we anyhow need to split the lists into
 +     * as small parts as possible.
 +     */
 +
 +    for (int z = 0; z < zones->n; z++)
 +    {
 +        if (zones->shift[z][XX] + zones->shift[z][YY] + zones->shift[z][ZZ] == 1)
 +        {
 +            cl = 0;
 +            ca = 1;
 +            za = 1;
 +            for (int d = 0; d < DIM; d++)
 +            {
 +                if (zones->shift[z][d] == 0)
 +                {
 +                    cl += 0.5*ls[d];
 +                    ca *= ls[d];
 +                    za *= zones->size[z].x1[d] - zones->size[z].x0[d];
 +                }
 +            }
 +
 +            /* 4 octants of a sphere */
 +            vold_est  = 0.25*M_PI*r*r*r*r;
 +            /* 4 quarter pie slices on the edges */
 +            vold_est += 4*cl*M_PI/6.0*r*r*r;
 +            /* One rectangular volume on a face */
 +            vold_est += ca*0.5*r*r;
 +
 +            vol2_est_tot += vold_est*za;
 +        }
 +    }
 +
 +    return vol2_est_tot;
 +}
 +
 +/* Estimates the average size of a full j-list for super/sub setup */
 +static void get_nsubpair_target(const Nbnxm::GridSet      &gridSet,
 +                                const InteractionLocality  iloc,
 +                                const real                 rlist,
 +                                const int                  min_ci_balanced,
 +                                int                       *nsubpair_target,
 +                                float                     *nsubpair_tot_est)
 +{
 +    /* The target value of 36 seems to be the optimum for Kepler.
 +     * Maxwell is less sensitive to the exact value.
 +     */
 +    const int           nsubpair_target_min = 36;
 +    real                r_eff_sup, vol_est, nsp_est, nsp_est_nl;
 +
 +    const Grid         &grid = gridSet.grids()[0];
 +
 +    /* We don't need to balance list sizes if:
 +     * - We didn't request balancing.
 +     * - The number of grid cells >= the number of lists requested,
 +     *   since we will always generate at least #cells lists.
 +     * - We don't have any cells, since then there won't be any lists.
 +     */
 +    if (min_ci_balanced <= 0 || grid.numCells() >= min_ci_balanced || grid.numCells() == 0)
 +    {
 +        /* nsubpair_target==0 signals no balancing */
 +        *nsubpair_target  = 0;
 +        *nsubpair_tot_est = 0;
 +
 +        return;
 +    }
 +
 +    gmx::RVec               ls;
 +    const int               numAtomsCluster = grid.geometry().numAtomsICluster;
 +    const Grid::Dimensions &dims            = grid.dimensions();
 +
 +    ls[XX] = dims.cellSize[XX]/c_gpuNumClusterPerCellX;
 +    ls[YY] = dims.cellSize[YY]/c_gpuNumClusterPerCellY;
 +    ls[ZZ] = numAtomsCluster/(dims.atomDensity*ls[XX]*ls[YY]);
 +
 +    /* The formulas below are a heuristic estimate of the average nsj per si*/
 +    r_eff_sup = rlist + nbnxn_get_rlist_effective_inc(numAtomsCluster, ls);
 +
 +    if (!gridSet.domainSetup().haveMultipleDomains ||
 +        gridSet.domainSetup().zones->n == 1)
 +    {
 +        nsp_est_nl = 0;
 +    }
 +    else
 +    {
 +        nsp_est_nl =
 +            gmx::square(dims.atomDensity/numAtomsCluster)*
 +            nonlocal_vol2(gridSet.domainSetup().zones, ls, r_eff_sup);
 +    }
 +
 +    if (iloc == InteractionLocality::Local)
 +    {
 +        /* Sub-cell interacts with itself */
 +        vol_est  = ls[XX]*ls[YY]*ls[ZZ];
 +        /* 6/2 rectangular volume on the faces */
 +        vol_est += (ls[XX]*ls[YY] + ls[XX]*ls[ZZ] + ls[YY]*ls[ZZ])*r_eff_sup;
 +        /* 12/2 quarter pie slices on the edges */
 +        vol_est += 2*(ls[XX] + ls[YY] + ls[ZZ])*0.25*M_PI*gmx::square(r_eff_sup);
 +        /* 4 octants of a sphere */
 +        vol_est += 0.5*4.0/3.0*M_PI*gmx::power3(r_eff_sup);
 +
 +        /* Estimate the number of cluster pairs as the local number of
 +         * clusters times the volume they interact with times the density.
 +         */
 +        nsp_est = grid.numClusters()*vol_est*dims.atomDensity/numAtomsCluster;
 +
 +        /* Subtract the non-local pair count */
 +        nsp_est -= nsp_est_nl;
 +
 +        /* For small cut-offs nsp_est will be an underesimate.
 +         * With DD nsp_est_nl is an overestimate so nsp_est can get negative.
 +         * So to avoid too small or negative nsp_est we set a minimum of
 +         * all cells interacting with all 3^3 direct neighbors (3^3-1)/2+1=14.
 +         * This might be a slight overestimate for small non-periodic groups of
 +         * atoms as will occur for a local domain with DD, but for small
 +         * groups of atoms we'll anyhow be limited by nsubpair_target_min,
 +         * so this overestimation will not matter.
 +         */
 +        nsp_est = std::max(nsp_est, grid.numClusters()*14._real);
 +
 +        if (debug)
 +        {
 +            fprintf(debug, "nsp_est local %5.1f non-local %5.1f\n",
 +                    nsp_est, nsp_est_nl);
 +        }
 +    }
 +    else
 +    {
 +        nsp_est = nsp_est_nl;
 +    }
 +
 +    /* Thus the (average) maximum j-list size should be as follows.
 +     * Since there is overhead, we shouldn't make the lists too small
 +     * (and we can't chop up j-groups) so we use a minimum target size of 36.
 +     */
 +    *nsubpair_target  = std::max(nsubpair_target_min,
 +                                 roundToInt(nsp_est/min_ci_balanced));
 +    *nsubpair_tot_est = static_cast<int>(nsp_est);
 +
 +    if (debug)
 +    {
 +        fprintf(debug, "nbl nsp estimate %.1f, nsubpair_target %d\n",
 +                nsp_est, *nsubpair_target);
 +    }
 +}
 +
 +/* Debug list print function */
 +static void print_nblist_ci_cj(FILE                   *fp,
 +                               const NbnxnPairlistCpu &nbl)
 +{
 +    for (const nbnxn_ci_t &ciEntry : nbl.ci)
 +    {
 +        fprintf(fp, "ci %4d  shift %2d  ncj %3d\n",
 +                ciEntry.ci, ciEntry.shift,
 +                ciEntry.cj_ind_end - ciEntry.cj_ind_start);
 +
 +        for (int j = ciEntry.cj_ind_start; j < ciEntry.cj_ind_end; j++)
 +        {
 +            fprintf(fp, "  cj %5d  imask %x\n",
 +                    nbl.cj[j].cj,
 +                    nbl.cj[j].excl);
 +        }
 +    }
 +}
 +
 +/* Debug list print function */
 +static void print_nblist_sci_cj(FILE                   *fp,
 +                                const NbnxnPairlistGpu &nbl)
 +{
 +    for (const nbnxn_sci_t &sci : nbl.sci)
 +    {
 +        fprintf(fp, "ci %4d  shift %2d  ncj4 %2d\n",
 +                sci.sci, sci.shift,
 +                sci.numJClusterGroups());
 +
 +        int ncp = 0;
 +        for (int j4 = sci.cj4_ind_start; j4 < sci.cj4_ind_end; j4++)
 +        {
 +            for (int j = 0; j < c_nbnxnGpuJgroupSize; j++)
 +            {
 +                fprintf(fp, "  sj %5d  imask %x\n",
 +                        nbl.cj4[j4].cj[j],
 +                        nbl.cj4[j4].imei[0].imask);
 +                for (int si = 0; si < c_gpuNumClusterPerCell; si++)
 +                {
 +                    if (nbl.cj4[j4].imei[0].imask & (1U << (j*c_gpuNumClusterPerCell + si)))
 +                    {
 +                        ncp++;
 +                    }
 +                }
 +            }
 +        }
 +        fprintf(fp, "ci %4d  shift %2d  ncj4 %2d ncp %3d\n",
 +                sci.sci, sci.shift,
 +                sci.numJClusterGroups(),
 +                ncp);
 +    }
 +}
 +
 +/* Combine pair lists *nbl generated on multiple threads nblc */
 +static void combine_nblists(gmx::ArrayRef<const NbnxnPairlistGpu>  nbls,
 +                            NbnxnPairlistGpu                      *nblc)
 +{
 +    int nsci  = nblc->sci.size();
 +    int ncj4  = nblc->cj4.size();
 +    int nexcl = nblc->excl.size();
 +    for (auto &nbl : nbls)
 +    {
 +        nsci  += nbl.sci.size();
 +        ncj4  += nbl.cj4.size();
 +        nexcl += nbl.excl.size();
 +    }
 +
 +    /* Resize with the final, combined size, so we can fill in parallel */
 +    /* NOTE: For better performance we should use default initialization */
 +    nblc->sci.resize(nsci);
 +    nblc->cj4.resize(ncj4);
 +    nblc->excl.resize(nexcl);
 +
 +    /* Each thread should copy its own data to the combined arrays,
 +     * as otherwise data will go back and forth between different caches.
 +     */
 +#if GMX_OPENMP && !(defined __clang_analyzer__)
 +    int nthreads = gmx_omp_nthreads_get(emntPairsearch);
 +#endif
 +
 +#pragma omp parallel for num_threads(nthreads) schedule(static)
 +    for (int n = 0; n < nbls.ssize(); n++)
 +    {
 +        try
 +        {
 +            /* Determine the offset in the combined data for our thread.
 +             * Note that the original sizes in nblc are lost.
 +             */
 +            int sci_offset  = nsci;
 +            int cj4_offset  = ncj4;
 +            int excl_offset = nexcl;
 +
 +            for (int i = n; i < nbls.ssize(); i++)
 +            {
 +                sci_offset  -= nbls[i].sci.size();
 +                cj4_offset  -= nbls[i].cj4.size();
 +                excl_offset -= nbls[i].excl.size();
 +            }
 +
 +            const NbnxnPairlistGpu &nbli = nbls[n];
 +
 +            for (size_t i = 0; i < nbli.sci.size(); i++)
 +            {
 +                nblc->sci[sci_offset + i]                = nbli.sci[i];
 +                nblc->sci[sci_offset + i].cj4_ind_start += cj4_offset;
 +                nblc->sci[sci_offset + i].cj4_ind_end   += cj4_offset;
 +            }
 +
 +            for (size_t j4 = 0; j4 < nbli.cj4.size(); j4++)
 +            {
 +                nblc->cj4[cj4_offset + j4]                   = nbli.cj4[j4];
 +                nblc->cj4[cj4_offset + j4].imei[0].excl_ind += excl_offset;
 +                nblc->cj4[cj4_offset + j4].imei[1].excl_ind += excl_offset;
 +            }
 +
 +            for (size_t j4 = 0; j4 < nbli.excl.size(); j4++)
 +            {
 +                nblc->excl[excl_offset + j4] = nbli.excl[j4];
 +            }
 +        }
 +        GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
 +    }
 +
 +    for (auto &nbl : nbls)
 +    {
 +        nblc->nci_tot += nbl.nci_tot;
 +    }
 +}
 +
 +static void balance_fep_lists(gmx::ArrayRef < std::unique_ptr < t_nblist>> fepLists,
 +                              gmx::ArrayRef<PairsearchWork>                work)
 +{
 +    const int numLists = fepLists.ssize();
 +
 +    if (numLists == 1)
 +    {
 +        /* Nothing to balance */
 +        return;
 +    }
 +
 +    /* Count the total i-lists and pairs */
 +    int nri_tot = 0;
 +    int nrj_tot = 0;
 +    for (const auto &list : fepLists)
 +    {
 +        nri_tot += list->nri;
 +        nrj_tot += list->nrj;
 +    }
 +
 +    const int nrj_target = (nrj_tot + numLists - 1)/numLists;
 +
 +    GMX_ASSERT(gmx_omp_nthreads_get(emntNonbonded) == numLists,
 +               "We should have as many work objects as FEP lists");
 +
 +#pragma omp parallel for schedule(static) num_threads(numLists)
 +    for (int th = 0; th < numLists; th++)
 +    {
 +        try
 +        {
 +            t_nblist *nbl = work[th].nbl_fep.get();
 +
 +            /* Note that here we allocate for the total size, instead of
 +             * a per-thread esimate (which is hard to obtain).
 +             */
 +            if (nri_tot > nbl->maxnri)
 +            {
 +                nbl->maxnri = over_alloc_large(nri_tot);
 +                reallocate_nblist(nbl);
 +            }
 +            if (nri_tot > nbl->maxnri || nrj_tot > nbl->maxnrj)
 +            {
 +                nbl->maxnrj = over_alloc_small(nrj_tot);
 +                srenew(nbl->jjnr, nbl->maxnrj);
 +                srenew(nbl->excl_fep, nbl->maxnrj);
 +            }
 +
 +            clear_pairlist_fep(nbl);
 +        }
 +        GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
 +    }
 +
 +    /* Loop over the source lists and assign and copy i-entries */
 +    int       th_dest = 0;
 +    t_nblist *nbld    = work[th_dest].nbl_fep.get();
 +    for (int th = 0; th < numLists; th++)
 +    {
 +        const t_nblist *nbls = fepLists[th].get();
 +
 +        for (int i = 0; i < nbls->nri; i++)
 +        {
 +            int nrj;
 +
 +            /* The number of pairs in this i-entry */
 +            nrj = nbls->jindex[i+1] - nbls->jindex[i];
 +
 +            /* Decide if list th_dest is too large and we should procede
 +             * to the next destination list.
 +             */
 +            if (th_dest + 1 < numLists && nbld->nrj > 0 &&
 +                nbld->nrj + nrj - nrj_target > nrj_target - nbld->nrj)
 +            {
 +                th_dest++;
 +                nbld = work[th_dest].nbl_fep.get();
 +            }
 +
 +            nbld->iinr[nbld->nri]  = nbls->iinr[i];
 +            nbld->gid[nbld->nri]   = nbls->gid[i];
 +            nbld->shift[nbld->nri] = nbls->shift[i];
 +
 +            for (int j = nbls->jindex[i]; j < nbls->jindex[i+1]; j++)
 +            {
 +                nbld->jjnr[nbld->nrj]     = nbls->jjnr[j];
 +                nbld->excl_fep[nbld->nrj] = nbls->excl_fep[j];
 +                nbld->nrj++;
 +            }
 +            nbld->nri++;
 +            nbld->jindex[nbld->nri] = nbld->nrj;
 +        }
 +    }
 +
 +    /* Swap the list pointers */
 +    for (int th = 0; th < numLists; th++)
 +    {
 +        fepLists[th].swap(work[th].nbl_fep);
 +
 +        if (debug)
 +        {
 +            fprintf(debug, "nbl_fep[%d] nri %4d nrj %4d\n",
 +                    th,
 +                    fepLists[th]->nri,
 +                    fepLists[th]->nrj);
 +        }
 +    }
 +}
 +
 +/* Returns the next ci to be processes by our thread */
 +static gmx_bool next_ci(const Grid &grid,
 +                        int nth, int ci_block,
 +                        int *ci_x, int *ci_y,
 +                        int *ci_b, int *ci)
 +{
 +    (*ci_b)++;
 +    (*ci)++;
 +
 +    if (*ci_b == ci_block)
 +    {
 +        /* Jump to the next block assigned to this task */
 +        *ci   += (nth - 1)*ci_block;
 +        *ci_b  = 0;
 +    }
 +
 +    if (*ci >= grid.numCells())
 +    {
 +        return FALSE;
 +    }
 +
 +    while (*ci >= grid.firstCellInColumn(*ci_x*grid.dimensions().numCells[YY] + *ci_y + 1))
 +    {
 +        *ci_y += 1;
 +        if (*ci_y == grid.dimensions().numCells[YY])
 +        {
 +            *ci_x += 1;
 +            *ci_y  = 0;
 +        }
 +    }
 +
 +    return TRUE;
 +}
 +
 +/* Returns the distance^2 for which we put cell pairs in the list
 + * without checking atom pair distances. This is usually < rlist^2.
 + */
 +static float boundingbox_only_distance2(const Grid::Dimensions &iGridDims,
 +                                        const Grid::Dimensions &jGridDims,
 +                                        real                    rlist,
 +                                        gmx_bool                simple)
 +{
 +    /* If the distance between two sub-cell bounding boxes is less
 +     * than this distance, do not check the distance between
 +     * all particle pairs in the sub-cell, since then it is likely
 +     * that the box pair has atom pairs within the cut-off.
 +     * We use the nblist cut-off minus 0.5 times the average x/y diagonal
 +     * spacing of the sub-cells. Around 40% of the checked pairs are pruned.
 +     * Using more than 0.5 gains at most 0.5%.
 +     * If forces are calculated more than twice, the performance gain
 +     * in the force calculation outweighs the cost of checking.
 +     * Note that with subcell lists, the atom-pair distance check
 +     * is only performed when only 1 out of 8 sub-cells in within range,
 +     * this is because the GPU is much faster than the cpu.
 +     */
 +    real bbx, bby;
 +    real rbb2;
 +
 +    bbx = 0.5*(iGridDims.cellSize[XX] + jGridDims.cellSize[XX]);
 +    bby = 0.5*(iGridDims.cellSize[YY] + jGridDims.cellSize[YY]);
 +    if (!simple)
 +    {
 +        bbx /= c_gpuNumClusterPerCellX;
 +        bby /= c_gpuNumClusterPerCellY;
 +    }
 +
 +    rbb2 = std::max(0.0, rlist - 0.5*std::sqrt(bbx*bbx + bby*bby));
 +    rbb2 = rbb2 * rbb2;
 +
 +#if !GMX_DOUBLE
 +    return rbb2;
 +#else
 +    return (float)((1+GMX_FLOAT_EPS)*rbb2);
 +#endif
 +}
 +
 +static int get_ci_block_size(const Grid &iGrid,
 +                             const bool  haveMultipleDomains,
 +                             const int   numLists)
 +{
 +    const int ci_block_enum      = 5;
 +    const int ci_block_denom     = 11;
 +    const int ci_block_min_atoms = 16;
 +    int       ci_block;
 +
 +    /* Here we decide how to distribute the blocks over the threads.
 +     * We use prime numbers to try to avoid that the grid size becomes
 +     * a multiple of the number of threads, which would lead to some
 +     * threads getting "inner" pairs and others getting boundary pairs,
 +     * which in turns will lead to load imbalance between threads.
 +     * Set the block size as 5/11/ntask times the average number of cells
 +     * in a y,z slab. This should ensure a quite uniform distribution
 +     * of the grid parts of the different thread along all three grid
 +     * zone boundaries with 3D domain decomposition. At the same time
 +     * the blocks will not become too small.
 +     */
 +    GMX_ASSERT(iGrid.dimensions().numCells[XX] > 0, "Grid can't be empty");
 +    GMX_ASSERT(numLists > 0, "We need at least one list");
 +    ci_block = (iGrid.numCells()*ci_block_enum)/(ci_block_denom*iGrid.dimensions().numCells[XX]*numLists);
 +
 +    const int numAtomsPerCell = iGrid.geometry().numAtomsPerCell;
 +
 +    /* Ensure the blocks are not too small: avoids cache invalidation */
 +    if (ci_block*numAtomsPerCell < ci_block_min_atoms)
 +    {
 +        ci_block = (ci_block_min_atoms + numAtomsPerCell - 1)/numAtomsPerCell;
 +    }
 +
 +    /* Without domain decomposition
 +     * or with less than 3 blocks per task, divide in nth blocks.
 +     */
 +    if (!haveMultipleDomains || numLists*3*ci_block > iGrid.numCells())
 +    {
 +        ci_block = (iGrid.numCells() + numLists - 1)/numLists;
 +    }
 +
 +    if (ci_block > 1 && (numLists - 1)*ci_block >= iGrid.numCells())
 +    {
 +        /* Some threads have no work. Although reducing the block size
 +         * does not decrease the block count on the first few threads,
 +         * with GPUs better mixing of "upper" cells that have more empty
 +         * clusters results in a somewhat lower max load over all threads.
 +         * Without GPUs the regime of so few atoms per thread is less
 +         * performance relevant, but with 8-wide SIMD the same reasoning
 +         * applies, since the pair list uses 4 i-atom "sub-clusters".
 +         */
 +        ci_block--;
 +    }
 +
 +    return ci_block;
 +}
 +
 +/* Returns the number of bits to right-shift a cluster index to obtain
 + * the corresponding force buffer flag index.
 + */
 +static int getBufferFlagShift(int numAtomsPerCluster)
 +{
 +    int bufferFlagShift = 0;
 +    while ((numAtomsPerCluster << bufferFlagShift) < NBNXN_BUFFERFLAG_SIZE)
 +    {
 +        bufferFlagShift++;
 +    }
 +
 +    return bufferFlagShift;
 +}
 +
 +static bool pairlistIsSimple(const NbnxnPairlistCpu gmx_unused &pairlist)
 +{
 +    return true;
 +}
 +
 +static bool pairlistIsSimple(const NbnxnPairlistGpu gmx_unused &pairlist)
 +{
 +    return false;
 +}
 +
 +static void
 +makeClusterListWrapper(NbnxnPairlistCpu                *nbl,
 +                       const Grid gmx_unused           &iGrid,
 +                       const int                        ci,
 +                       const Grid                      &jGrid,
 +                       const int                        firstCell,
 +                       const int                        lastCell,
 +                       const bool                       excludeSubDiagonal,
 +                       const nbnxn_atomdata_t          *nbat,
 +                       const real                       rlist2,
 +                       const real                       rbb2,
 +                       const ClusterDistanceKernelType  kernelType,
 +                       int                             *numDistanceChecks)
 +{
 +    switch (kernelType)
 +    {
 +        case ClusterDistanceKernelType::CpuPlainC:
 +            makeClusterListSimple(jGrid,
 +                                  nbl, ci, firstCell, lastCell,
 +                                  excludeSubDiagonal,
 +                                  nbat->x().data(),
 +                                  rlist2, rbb2,
 +                                  numDistanceChecks);
 +            break;
 +#ifdef GMX_NBNXN_SIMD_4XN
 +        case ClusterDistanceKernelType::CpuSimd_4xM:
 +            makeClusterListSimd4xn(jGrid,
 +                                   nbl, ci, firstCell, lastCell,
 +                                   excludeSubDiagonal,
 +                                   nbat->x().data(),
 +                                   rlist2, rbb2,
 +                                   numDistanceChecks);
 +            break;
 +#endif
 +#ifdef GMX_NBNXN_SIMD_2XNN
 +        case ClusterDistanceKernelType::CpuSimd_2xMM:
 +            makeClusterListSimd2xnn(jGrid,
 +                                    nbl, ci, firstCell, lastCell,
 +                                    excludeSubDiagonal,
 +                                    nbat->x().data(),
 +                                    rlist2, rbb2,
 +                                    numDistanceChecks);
 +            break;
 +#endif
 +        default:
 +            GMX_ASSERT(false, "Unhandled kernel type");
 +    }
 +}
 +
 +static void
 +makeClusterListWrapper(NbnxnPairlistGpu                     *nbl,
 +                       const Grid &gmx_unused                iGrid,
 +                       const int                             ci,
 +                       const Grid                           &jGrid,
 +                       const int                             firstCell,
 +                       const int                             lastCell,
 +                       const bool                            excludeSubDiagonal,
 +                       const nbnxn_atomdata_t               *nbat,
 +                       const real                            rlist2,
 +                       const real                            rbb2,
 +                       ClusterDistanceKernelType gmx_unused  kernelType,
 +                       int                                  *numDistanceChecks)
 +{
 +    for (int cj = firstCell; cj <= lastCell; cj++)
 +    {
 +        make_cluster_list_supersub(iGrid, jGrid,
 +                                   nbl, ci, cj,
 +                                   excludeSubDiagonal,
 +                                   nbat->xstride, nbat->x().data(),
 +                                   rlist2, rbb2,
 +                                   numDistanceChecks);
 +    }
 +}
 +
 +static int getNumSimpleJClustersInList(const NbnxnPairlistCpu &nbl)
 +{
 +    return nbl.cj.size();
 +}
 +
 +static int getNumSimpleJClustersInList(const gmx_unused NbnxnPairlistGpu &nbl)
 +{
 +    return 0;
 +}
 +
 +static void incrementNumSimpleJClustersInList(NbnxnPairlistCpu *nbl,
 +                                              int               ncj_old_j)
 +{
 +    nbl->ncjInUse += nbl->cj.size() - ncj_old_j;
 +}
 +
 +static void incrementNumSimpleJClustersInList(NbnxnPairlistGpu gmx_unused *nbl,
 +                                              int              gmx_unused  ncj_old_j)
 +{
 +}
 +
 +static void checkListSizeConsistency(const NbnxnPairlistCpu &nbl,
 +                                     const bool              haveFreeEnergy)
 +{
 +    GMX_RELEASE_ASSERT(static_cast<size_t>(nbl.ncjInUse) == nbl.cj.size() || haveFreeEnergy,
 +                       "Without free-energy all cj pair-list entries should be in use. "
 +                       "Note that subsequent code does not make use of the equality, "
 +                       "this check is only here to catch bugs");
 +}
 +
 +static void checkListSizeConsistency(const NbnxnPairlistGpu gmx_unused &nbl,
 +                                     bool gmx_unused                    haveFreeEnergy)
 +{
 +    /* We currently can not check consistency here */
 +}
 +
 +/* Set the buffer flags for newly added entries in the list */
 +static void setBufferFlags(const NbnxnPairlistCpu &nbl,
 +                           const int               ncj_old_j,
 +                           const int               gridj_flag_shift,
 +                           gmx_bitmask_t          *gridj_flag,
 +                           const int               th)
 +{
 +    if (gmx::ssize(nbl.cj) > ncj_old_j)
 +    {
 +        int cbFirst = nbl.cj[ncj_old_j].cj >> gridj_flag_shift;
 +        int cbLast  = nbl.cj.back().cj >> gridj_flag_shift;
 +        for (int cb = cbFirst; cb <= cbLast; cb++)
 +        {
 +            bitmask_init_bit(&gridj_flag[cb], th);
 +        }
 +    }
 +}
 +
 +static void setBufferFlags(const NbnxnPairlistGpu gmx_unused &nbl,
 +                           int gmx_unused                     ncj_old_j,
 +                           int gmx_unused                     gridj_flag_shift,
 +                           gmx_bitmask_t gmx_unused          *gridj_flag,
 +                           int gmx_unused                     th)
 +{
 +    GMX_ASSERT(false, "This function should never be called");
 +}
 +
 +/* Generates the part of pair-list nbl assigned to our thread */
 +template <typename T>
 +static void nbnxn_make_pairlist_part(const Nbnxm::GridSet &gridSet,
 +                                     const Grid &iGrid,
 +                                     const Grid &jGrid,
 +                                     PairsearchWork *work,
 +                                     const nbnxn_atomdata_t *nbat,
 +                                     const t_blocka &exclusions,
 +                                     real rlist,
 +                                     const PairlistType pairlistType,
 +                                     int ci_block,
 +                                     gmx_bool bFBufferFlag,
 +                                     int nsubpair_max,
 +                                     gmx_bool progBal,
 +                                     float nsubpair_tot_est,
 +                                     int th, int nth,
 +                                     T *nbl,
 +                                     t_nblist *nbl_fep)
 +{
 +    int               na_cj_2log;
 +    matrix            box;
 +    real              rl_fep2 = 0;
 +    float             rbb2;
 +    int               ci_b, ci, ci_x, ci_y, ci_xy;
 +    ivec              shp;
 +    real              bx0, bx1, by0, by1, bz0, bz1;
 +    real              bz1_frac;
 +    real              d2cx, d2z, d2z_cx, d2z_cy, d2zx, d2zxy, d2xy;
 +    int               cxf, cxl, cyf, cyf_x, cyl;
 +    int               numDistanceChecks;
 +    int               gridi_flag_shift = 0, gridj_flag_shift = 0;
 +    gmx_bitmask_t    *gridj_flag       = nullptr;
 +    int               ncj_old_i, ncj_old_j;
 +
 +    if (jGrid.geometry().isSimple != pairlistIsSimple(*nbl) ||
 +        iGrid.geometry().isSimple != pairlistIsSimple(*nbl))
 +    {
 +        gmx_incons("Grid incompatible with pair-list");
 +    }
 +
 +    sync_work(nbl);
 +    GMX_ASSERT(nbl->na_ci == jGrid.geometry().numAtomsICluster,
 +               "The cluster sizes in the list and grid should match");
 +    nbl->na_cj = JClusterSizePerListType[pairlistType];
 +    na_cj_2log = get_2log(nbl->na_cj);
 +
 +    nbl->rlist  = rlist;
 +
 +    if (bFBufferFlag)
 +    {
 +        /* Determine conversion of clusters to flag blocks */
 +        gridi_flag_shift = getBufferFlagShift(nbl->na_ci);
 +        gridj_flag_shift = getBufferFlagShift(nbl->na_cj);
 +
 +        gridj_flag       = work->buffer_flags.flag;
 +    }
 +
 +    gridSet.getBox(box);
 +
 +    const bool            haveFep = gridSet.haveFep();
 +
 +    const real            rlist2  = nbl->rlist*nbl->rlist;
 +
 +    // Select the cluster pair distance kernel type
 +    const ClusterDistanceKernelType kernelType =
 +        getClusterDistanceKernelType(pairlistType, *nbat);
 +
 +    if (haveFep && !pairlistIsSimple(*nbl))
 +    {
 +        /* Determine an atom-pair list cut-off distance for FEP atom pairs.
 +         * We should not simply use rlist, since then we would not have
 +         * the small, effective buffering of the NxN lists.
 +         * The buffer is on overestimate, but the resulting cost for pairs
 +         * beyond rlist is neglible compared to the FEP pairs within rlist.
 +         */
 +        rl_fep2 = nbl->rlist + effective_buffer_1x1_vs_MxN(iGrid, jGrid);
 +
 +        if (debug)
 +        {
 +            fprintf(debug, "nbl_fep atom-pair rlist %f\n", rl_fep2);
 +        }
 +        rl_fep2 = rl_fep2*rl_fep2;
 +    }
 +
 +    const Grid::Dimensions &iGridDims = iGrid.dimensions();
 +    const Grid::Dimensions &jGridDims = jGrid.dimensions();
 +
 +    rbb2 = boundingbox_only_distance2(iGridDims, jGridDims, nbl->rlist, pairlistIsSimple(*nbl));
 +
 +    if (debug)
 +    {
 +        fprintf(debug, "nbl bounding box only distance %f\n", std::sqrt(rbb2));
 +    }
 +
 +    const bool isIntraGridList = (&iGrid == &jGrid);
 +
 +    /* Set the shift range */
 +    for (int d = 0; d < DIM; d++)
 +    {
 +        /* Check if we need periodicity shifts.
 +         * Without PBC or with domain decomposition we don't need them.
 +         */
 +        if (d >= ePBC2npbcdim(gridSet.domainSetup().ePBC) ||
 +            gridSet.domainSetup().haveMultipleDomainsPerDim[d])
 +        {
 +            shp[d] = 0;
 +        }
 +        else
 +        {
 +            const real listRangeCellToCell =
 +                listRangeForGridCellToGridCell(rlist, iGrid.dimensions(), jGrid.dimensions());
 +            if (d == XX &&
 +                box[XX][XX] - fabs(box[YY][XX]) - fabs(box[ZZ][XX]) < listRangeCellToCell)
 +            {
 +                shp[d] = 2;
 +            }
 +            else
 +            {
 +                shp[d] = 1;
 +            }
 +        }
 +    }
 +    const bool bSimple = pairlistIsSimple(*nbl);
 +    gmx::ArrayRef<const BoundingBox> bb_i;
 +#if NBNXN_BBXXXX
 +    gmx::ArrayRef<const float>       pbb_i;
 +    if (bSimple)
 +    {
 +        bb_i  = iGrid.iBoundingBoxes();
 +    }
 +    else
 +    {
 +        pbb_i = iGrid.packedBoundingBoxes();
 +    }
 +#else
 +    /* We use the normal bounding box format for both grid types */
 +    bb_i  = iGrid.iBoundingBoxes();
 +#endif
 +    gmx::ArrayRef<const BoundingBox1D> bbcz_i  = iGrid.zBoundingBoxes();
 +    gmx::ArrayRef<const int>           flags_i = iGrid.clusterFlags();
 +    gmx::ArrayRef<const BoundingBox1D> bbcz_j  = jGrid.zBoundingBoxes();
 +    int                                cell0_i = iGrid.cellOffset();
 +
 +    if (debug)
 +    {
 +        fprintf(debug, "nbl nc_i %d col.av. %.1f ci_block %d\n",
 +                iGrid.numCells(), iGrid.numCells()/static_cast<double>(iGrid.numColumns()), ci_block);
 +    }
 +
 +    numDistanceChecks = 0;
 +
 +    const real listRangeBBToJCell2 = gmx::square(listRangeForBoundingBoxToGridCell(rlist, jGrid.dimensions()));
 +
 +    /* Initially ci_b and ci to 1 before where we want them to start,
 +     * as they will both be incremented in next_ci.
 +     */
 +    ci_b = -1;
 +    ci   = th*ci_block - 1;
 +    ci_x = 0;
 +    ci_y = 0;
 +    while (next_ci(iGrid, nth, ci_block, &ci_x, &ci_y, &ci_b, &ci))
 +    {
 +        if (bSimple && flags_i[ci] == 0)
 +        {
 +            continue;
 +        }
 +
 +        ncj_old_i = getNumSimpleJClustersInList(*nbl);
 +
 +        d2cx = 0;
 +        if (!isIntraGridList && shp[XX] == 0)
 +        {
 +            if (bSimple)
 +            {
 +                bx1 = bb_i[ci].upper.x;
 +            }
 +            else
 +            {
 +                bx1 = iGridDims.lowerCorner[XX] + (ci_x+1)*iGridDims.cellSize[XX];
 +            }
 +            if (bx1 < jGridDims.lowerCorner[XX])
 +            {
 +                d2cx = gmx::square(jGridDims.lowerCorner[XX] - bx1);
 +
 +                if (d2cx >= listRangeBBToJCell2)
 +                {
 +                    continue;
 +                }
 +            }
 +        }
 +
 +        ci_xy = ci_x*iGridDims.numCells[YY] + ci_y;
 +
 +        /* Loop over shift vectors in three dimensions */
 +        for (int tz = -shp[ZZ]; tz <= shp[ZZ]; tz++)
 +        {
 +            const real shz = tz*box[ZZ][ZZ];
 +
 +            bz0 = bbcz_i[ci].lower + shz;
 +            bz1 = bbcz_i[ci].upper + shz;
 +
 +            if (tz == 0)
 +            {
 +                d2z = 0;
 +            }
 +            else if (tz < 0)
 +            {
 +                d2z = gmx::square(bz1);
 +            }
 +            else
 +            {
 +                d2z = gmx::square(bz0 - box[ZZ][ZZ]);
 +            }
 +
 +            d2z_cx = d2z + d2cx;
 +
 +            if (d2z_cx >= rlist2)
 +            {
 +                continue;
 +            }
 +
 +            bz1_frac = bz1/iGrid.numCellsInColumn(ci_xy);
 +            if (bz1_frac < 0)
 +            {
 +                bz1_frac = 0;
 +            }
 +            /* The check with bz1_frac close to or larger than 1 comes later */
 +
 +            for (int ty = -shp[YY]; ty <= shp[YY]; ty++)
 +            {
 +                const real shy = ty*box[YY][YY] + tz*box[ZZ][YY];
 +
 +                if (bSimple)
 +                {
 +                    by0 = bb_i[ci].lower.y + shy;
 +                    by1 = bb_i[ci].upper.y + shy;
 +                }
 +                else
 +                {
 +                    by0 = iGridDims.lowerCorner[YY] + (ci_y    )*iGridDims.cellSize[YY] + shy;
 +                    by1 = iGridDims.lowerCorner[YY] + (ci_y + 1)*iGridDims.cellSize[YY] + shy;
 +                }
 +
 +                get_cell_range<YY>(by0, by1,
 +                                   jGridDims,
 +                                   d2z_cx, rlist,
 +                                   &cyf, &cyl);
 +
 +                if (cyf > cyl)
 +                {
 +                    continue;
 +                }
 +
 +                d2z_cy = d2z;
 +                if (by1 < jGridDims.lowerCorner[YY])
 +                {
 +                    d2z_cy += gmx::square(jGridDims.lowerCorner[YY] - by1);
 +                }
 +                else if (by0 > jGridDims.upperCorner[YY])
 +                {
 +                    d2z_cy += gmx::square(by0 - jGridDims.upperCorner[YY]);
 +                }
 +
 +                for (int tx = -shp[XX]; tx <= shp[XX]; tx++)
 +                {
 +                    const int  shift              = XYZ2IS(tx, ty, tz);
 +
 +                    const bool excludeSubDiagonal = (isIntraGridList && shift == CENTRAL);
 +
 +                    if (c_pbcShiftBackward && isIntraGridList && shift > CENTRAL)
 +                    {
 +                        continue;
 +                    }
 +
 +                    const real shx = tx*box[XX][XX] + ty*box[YY][XX] + tz*box[ZZ][XX];
 +
 +                    if (bSimple)
 +                    {
 +                        bx0 = bb_i[ci].lower.x + shx;
 +                        bx1 = bb_i[ci].upper.x + shx;
 +                    }
 +                    else
 +                    {
 +                        bx0 = iGridDims.lowerCorner[XX] + (ci_x  )*iGridDims.cellSize[XX] + shx;
 +                        bx1 = iGridDims.lowerCorner[XX] + (ci_x+1)*iGridDims.cellSize[XX] + shx;
 +                    }
 +
 +                    get_cell_range<XX>(bx0, bx1,
 +                                       jGridDims,
 +                                       d2z_cy, rlist,
 +                                       &cxf, &cxl);
 +
 +                    if (cxf > cxl)
 +                    {
 +                        continue;
 +                    }
 +
 +                    addNewIEntry(nbl, cell0_i+ci, shift, flags_i[ci]);
 +
 +                    if ((!c_pbcShiftBackward || excludeSubDiagonal) &&
 +                        cxf < ci_x)
 +                    {
 +                        /* Leave the pairs with i > j.
 +                         * x is the major index, so skip half of it.
 +                         */
 +                        cxf = ci_x;
 +                    }
 +
 +                    set_icell_bb(iGrid, ci, shx, shy, shz,
 +                                 nbl->work.get());
 +
 +                    icell_set_x(cell0_i+ci, shx, shy, shz,
 +                                nbat->xstride, nbat->x().data(),
 +                                kernelType,
 +                                nbl->work.get());
 +
 +                    for (int cx = cxf; cx <= cxl; cx++)
 +                    {
 +                        d2zx = d2z;
 +                        if (jGridDims.lowerCorner[XX] + cx*jGridDims.cellSize[XX] > bx1)
 +                        {
 +                            d2zx += gmx::square(jGridDims.lowerCorner[XX] + cx*jGridDims.cellSize[XX] - bx1);
 +                        }
 +                        else if (jGridDims.lowerCorner[XX] + (cx+1)*jGridDims.cellSize[XX] < bx0)
 +                        {
 +                            d2zx += gmx::square(jGridDims.lowerCorner[XX] + (cx+1)*jGridDims.cellSize[XX] - bx0);
 +                        }
 +
 +                        if (isIntraGridList &&
 +                            cx == 0 &&
 +                            (!c_pbcShiftBackward || shift == CENTRAL) &&
 +                            cyf < ci_y)
 +                        {
 +                            /* Leave the pairs with i > j.
 +                             * Skip half of y when i and j have the same x.
 +                             */
 +                            cyf_x = ci_y;
 +                        }
 +                        else
 +                        {
 +                            cyf_x = cyf;
 +                        }
 +
 +                        for (int cy = cyf_x; cy <= cyl; cy++)
 +                        {
 +                            const int columnStart = jGrid.firstCellInColumn(cx*jGridDims.numCells[YY] + cy);
 +                            const int columnEnd   = jGrid.firstCellInColumn(cx*jGridDims.numCells[YY] + cy + 1);
 +
 +                            d2zxy = d2zx;
 +                            if (jGridDims.lowerCorner[YY] + cy*jGridDims.cellSize[YY] > by1)
 +                            {
 +                                d2zxy += gmx::square(jGridDims.lowerCorner[YY] + cy*jGridDims.cellSize[YY] - by1);
 +                            }
 +                            else if (jGridDims.lowerCorner[YY] + (cy + 1)*jGridDims.cellSize[YY] < by0)
 +                            {
 +                                d2zxy += gmx::square(jGridDims.lowerCorner[YY] + (cy + 1)*jGridDims.cellSize[YY] - by0);
 +                            }
 +                            if (columnStart < columnEnd && d2zxy < listRangeBBToJCell2)
 +                            {
 +                                /* To improve efficiency in the common case
 +                                 * of a homogeneous particle distribution,
 +                                 * we estimate the index of the middle cell
 +                                 * in range (midCell). We search down and up
 +                                 * starting from this index.
 +                                 *
 +                                 * Note that the bbcz_j array contains bounds
 +                                 * for i-clusters, thus for clusters of 4 atoms.
 +                                 * For the common case where the j-cluster size
 +                                 * is 8, we could step with a stride of 2,
 +                                 * but we do not do this because it would
 +                                 * complicate this code even more.
 +                                 */
 +                                int midCell = columnStart + static_cast<int>(bz1_frac*(columnEnd - columnStart));
 +                                if (midCell >= columnEnd)
 +                                {
 +                                    midCell = columnEnd - 1;
 +                                }
 +
 +                                d2xy = d2zxy - d2z;
 +
 +                                /* Find the lowest cell that can possibly
 +                                 * be within range.
 +                                 * Check if we hit the bottom of the grid,
 +                                 * if the j-cell is below the i-cell and if so,
 +                                 * if it is within range.
 +                                 */
 +                                int downTestCell = midCell;
 +                                while (downTestCell >= columnStart &&
 +                                       (bbcz_j[downTestCell].upper >= bz0 ||
 +                                        d2xy + gmx::square(bbcz_j[downTestCell].upper - bz0) < rlist2))
 +                                {
 +                                    downTestCell--;
 +                                }
 +                                int firstCell = downTestCell + 1;
 +
 +                                /* Find the highest cell that can possibly
 +                                 * be within range.
 +                                 * Check if we hit the top of the grid,
 +                                 * if the j-cell is above the i-cell and if so,
 +                                 * if it is within range.
 +                                 */
 +                                int upTestCell = midCell + 1;
 +                                while (upTestCell < columnEnd &&
 +                                       (bbcz_j[upTestCell].lower <= bz1 ||
 +                                        d2xy + gmx::square(bbcz_j[upTestCell].lower - bz1) < rlist2))
 +                                {
 +                                    upTestCell++;
 +                                }
 +                                int lastCell = upTestCell - 1;
 +
 +#define NBNXN_REFCODE 0
 +#if NBNXN_REFCODE
 +                                {
 +                                    /* Simple reference code, for debugging,
 +                                     * overrides the more complex code above.
 +                                     */
 +                                    firstCell = columnEnd;
 +                                    lastCell  = -1;
 +                                    for (int k = columnStart; k < columnEnd; k++)
 +                                    {
 +                                        if (d2xy + gmx::square(bbcz_j[k*NNBSBB_D + 1] - bz0) < rlist2 &&
 +                                            k < firstCell)
 +                                        {
 +                                            firstCell = k;
 +                                        }
 +                                        if (d2xy + gmx::square(bbcz_j[k*NNBSBB_D] - bz1) < rlist2 &&
 +                                            k > lastCell)
 +                                        {
 +                                            lastCell = k;
 +                                        }
 +                                    }
 +                                }
 +#endif
 +
 +                                if (isIntraGridList)
 +                                {
 +                                    /* We want each atom/cell pair only once,
 +                                     * only use cj >= ci.
 +                                     */
 +                                    if (!c_pbcShiftBackward || shift == CENTRAL)
 +                                    {
 +                                        firstCell = std::max(firstCell, ci);
 +                                    }
 +                                }
 +
 +                                if (firstCell <= lastCell)
 +                                {
 +                                    GMX_ASSERT(firstCell >= columnStart && lastCell < columnEnd, "The range should reside within the current grid column");
 +
 +                                    /* For f buffer flags with simple lists */
 +                                    ncj_old_j = getNumSimpleJClustersInList(*nbl);
 +
 +                                    makeClusterListWrapper(nbl,
 +                                                           iGrid, ci,
 +                                                           jGrid, firstCell, lastCell,
 +                                                           excludeSubDiagonal,
 +                                                           nbat,
 +                                                           rlist2, rbb2,
 +                                                           kernelType,
 +                                                           &numDistanceChecks);
 +
 +                                    if (bFBufferFlag)
 +                                    {
 +                                        setBufferFlags(*nbl, ncj_old_j, gridj_flag_shift,
 +                                                       gridj_flag, th);
 +                                    }
 +
 +                                    incrementNumSimpleJClustersInList(nbl, ncj_old_j);
 +                                }
 +                            }
 +                        }
 +                    }
 +
 +                    /* Set the exclusions for this ci list */
 +                    setExclusionsForIEntry(gridSet,
 +                                           nbl,
 +                                           excludeSubDiagonal,
 +                                           na_cj_2log,
 +                                           *getOpenIEntry(nbl),
 +                                           exclusions);
 +
 +                    if (haveFep)
 +                    {
 +                        make_fep_list(gridSet.atomIndices(), nbat, nbl,
 +                                      excludeSubDiagonal,
 +                                      getOpenIEntry(nbl),
 +                                      shx, shy, shz,
 +                                      rl_fep2,
 +                                      iGrid, jGrid, nbl_fep);
 +                    }
 +
 +                    /* Close this ci list */
 +                    closeIEntry(nbl,
 +                                nsubpair_max,
 +                                progBal, nsubpair_tot_est,
 +                                th, nth);
 +                }
 +            }
 +        }
 +
 +        if (bFBufferFlag && getNumSimpleJClustersInList(*nbl) > ncj_old_i)
 +        {
 +            bitmask_init_bit(&(work->buffer_flags.flag[(iGrid.cellOffset() + ci) >> gridi_flag_shift]), th);
 +        }
 +    }
 +
 +    work->ndistc = numDistanceChecks;
 +
 +    checkListSizeConsistency(*nbl, haveFep);
 +
 +    if (debug)
 +    {
 +        fprintf(debug, "number of distance checks %d\n", numDistanceChecks);
 +
 +        print_nblist_statistics(debug, *nbl, gridSet, rlist);
 +
 +        if (haveFep)
 +        {
 +            fprintf(debug, "nbl FEP list pairs: %d\n", nbl_fep->nrj);
 +        }
 +    }
 +}
 +
 +static void reduce_buffer_flags(gmx::ArrayRef<PairsearchWork>  searchWork,
 +                                int                            nsrc,
 +                                const nbnxn_buffer_flags_t    *dest)
 +{
 +    for (int s = 0; s < nsrc; s++)
 +    {
 +        gmx_bitmask_t * flag = searchWork[s].buffer_flags.flag;
 +
 +        for (int b = 0; b < dest->nflag; b++)
 +        {
 +            bitmask_union(&(dest->flag[b]), flag[b]);
 +        }
 +    }
 +}
 +
 +static void print_reduction_cost(const nbnxn_buffer_flags_t *flags, int nout)
 +{
 +    int           nelem, nkeep, ncopy, nred, out;
 +    gmx_bitmask_t mask_0;
 +
 +    nelem = 0;
 +    nkeep = 0;
 +    ncopy = 0;
 +    nred  = 0;
 +    bitmask_init_bit(&mask_0, 0);
 +    for (int b = 0; b < flags->nflag; b++)
 +    {
 +        if (bitmask_is_equal(flags->flag[b], mask_0))
 +        {
 +            /* Only flag 0 is set, no copy of reduction required */
 +            nelem++;
 +            nkeep++;
 +        }
 +        else if (!bitmask_is_zero(flags->flag[b]))
 +        {
 +            int c = 0;
 +            for (out = 0; out < nout; out++)
 +            {
 +                if (bitmask_is_set(flags->flag[b], out))
 +                {
 +                    c++;
 +                }
 +            }
 +            nelem += c;
 +            if (c == 1)
 +            {
 +                ncopy++;
 +            }
 +            else
 +            {
 +                nred += c;
 +            }
 +        }
 +    }
 +
 +    fprintf(debug, "nbnxn reduction: #flag %d #list %d elem %4.2f, keep %4.2f copy %4.2f red %4.2f\n",
 +            flags->nflag, nout,
 +            nelem/static_cast<double>(flags->nflag),
 +            nkeep/static_cast<double>(flags->nflag),
 +            ncopy/static_cast<double>(flags->nflag),
 +            nred/static_cast<double>(flags->nflag));
 +}
 +
 +/* Copies the list entries from src to dest when cjStart <= *cjGlobal < cjEnd.
 + * *cjGlobal is updated with the cj count in src.
 + * When setFlags==true, flag bit t is set in flag for all i and j clusters.
 + */
 +template<bool setFlags>
 +static void copySelectedListRange(const nbnxn_ci_t * gmx_restrict srcCi,
 +                                  const NbnxnPairlistCpu * gmx_restrict src,
 +                                  NbnxnPairlistCpu * gmx_restrict dest,
 +                                  gmx_bitmask_t *flag,
 +                                  int iFlagShift, int jFlagShift, int t)
 +{
 +    const int ncj = srcCi->cj_ind_end - srcCi->cj_ind_start;
 +
 +    dest->ci.push_back(*srcCi);
 +    dest->ci.back().cj_ind_start = dest->cj.size();
 +    dest->ci.back().cj_ind_end   = dest->cj.size() + ncj;
 +
 +    if (setFlags)
 +    {
 +        bitmask_init_bit(&flag[srcCi->ci >> iFlagShift], t);
 +    }
 +
 +    for (int j = srcCi->cj_ind_start; j < srcCi->cj_ind_end; j++)
 +    {
 +        dest->cj.push_back(src->cj[j]);
 +
 +        if (setFlags)
 +        {
 +            /* NOTE: This is relatively expensive, since this
 +             * operation is done for all elements in the list,
 +             * whereas at list generation this is done only
 +             * once for each flag entry.
 +             */
 +            bitmask_init_bit(&flag[src->cj[j].cj >> jFlagShift], t);
 +        }
 +    }
 +}
 +
++#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ == 7
++/* Avoid gcc 7 avx512 loop vectorization bug (actually only needed with -mavx512f) */
++#pragma GCC push_options
++#pragma GCC optimize ("no-tree-vectorize")
++#endif
++
++/* Returns the number of cluster pairs that are in use summed over all lists */
++static int countClusterpairs(gmx::ArrayRef<const NbnxnPairlistCpu> pairlists)
++{
++    /* gcc 7 with -mavx512f can miss the contributions of 16 consecutive
++     * elements to the sum calculated in this loop. Above we have disabled
++     * loop vectorization to avoid this bug.
++     */
++    int ncjTotal = 0;
++    for (const auto &pairlist : pairlists)
++    {
++        ncjTotal += pairlist.ncjInUse;
++    }
++    return ncjTotal;
++}
++
++#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ == 7
++#pragma GCC pop_options
++#endif
++
 +/* This routine re-balances the pairlists such that all are nearly equally
 + * sized. Only whole i-entries are moved between lists. These are moved
 + * between the ends of the lists, such that the buffer reduction cost should
 + * not change significantly.
 + * Note that all original reduction flags are currently kept. This can lead
 + * to reduction of parts of the force buffer that could be avoided. But since
 + * the original lists are quite balanced, this will only give minor overhead.
 + */
 +static void rebalanceSimpleLists(gmx::ArrayRef<const NbnxnPairlistCpu> srcSet,
 +                                 gmx::ArrayRef<NbnxnPairlistCpu>       destSet,
 +                                 gmx::ArrayRef<PairsearchWork>         searchWork)
 +{
-     int ncjTotalNew = 0;
-     for (auto &dest : destSet)
-     {
-         ncjTotalNew += dest.ncjInUse;
-     }
++    const int ncjTotal  = countClusterpairs(srcSet);
 +    const int numLists  = srcSet.ssize();
 +    const int ncjTarget = (ncjTotal + numLists - 1)/numLists;
 +
 +#pragma omp parallel num_threads(numLists)
 +    {
 +        int t       = gmx_omp_get_thread_num();
 +
 +        int cjStart = ncjTarget* t;
 +        int cjEnd   = ncjTarget*(t + 1);
 +
 +        /* The destination pair-list for task/thread t */
 +        NbnxnPairlistCpu &dest = destSet[t];
 +
 +        clear_pairlist(&dest);
 +        dest.na_cj = srcSet[0].na_cj;
 +
 +        /* Note that the flags in the work struct (still) contain flags
 +         * for all entries that are present in srcSet->nbl[t].
 +         */
 +        gmx_bitmask_t *flag       = searchWork[t].buffer_flags.flag;
 +
 +        int            iFlagShift = getBufferFlagShift(dest.na_ci);
 +        int            jFlagShift = getBufferFlagShift(dest.na_cj);
 +
 +        int            cjGlobal   = 0;
 +        for (int s = 0; s < numLists && cjGlobal < cjEnd; s++)
 +        {
 +            const NbnxnPairlistCpu *src = &srcSet[s];
 +
 +            if (cjGlobal + src->ncjInUse > cjStart)
 +            {
 +                for (gmx::index i = 0; i < gmx::ssize(src->ci) && cjGlobal < cjEnd; i++)
 +                {
 +                    const nbnxn_ci_t *srcCi = &src->ci[i];
 +                    int               ncj   = srcCi->cj_ind_end - srcCi->cj_ind_start;
 +                    if (cjGlobal >= cjStart)
 +                    {
 +                        /* If the source list is not our own, we need to set
 +                         * extra flags (the template bool parameter).
 +                         */
 +                        if (s != t)
 +                        {
 +                            copySelectedListRange
 +                            <true>
 +                                (srcCi, src, &dest,
 +                                flag, iFlagShift, jFlagShift, t);
 +                        }
 +                        else
 +                        {
 +                            copySelectedListRange
 +                            <false>
 +                                (srcCi, src,
 +                                &dest, flag, iFlagShift, jFlagShift, t);
 +                        }
 +                    }
 +                    cjGlobal += ncj;
 +                }
 +            }
 +            else
 +            {
 +                cjGlobal += src->ncjInUse;
 +            }
 +        }
 +
 +        dest.ncjInUse = dest.cj.size();
 +    }
 +
 +#ifndef NDEBUG
++    const int ncjTotalNew = countClusterpairs(destSet);
 +    GMX_RELEASE_ASSERT(ncjTotalNew == ncjTotal, "The total size of the lists before and after rebalancing should match");
 +#endif
 +}
 +
 +/* Returns if the pairlists are so imbalanced that it is worth rebalancing. */
 +static bool checkRebalanceSimpleLists(gmx::ArrayRef<const NbnxnPairlistCpu> lists)
 +{
 +    int numLists = lists.ssize();
 +    int ncjMax   = 0;
 +    int ncjTotal = 0;
 +    for (int s = 0; s < numLists; s++)
 +    {
 +        ncjMax    = std::max(ncjMax, lists[s].ncjInUse);
 +        ncjTotal += lists[s].ncjInUse;
 +    }
 +    if (debug)
 +    {
 +        fprintf(debug, "Pair-list ncjMax %d ncjTotal %d\n", ncjMax, ncjTotal);
 +    }
 +    /* The rebalancing adds 3% extra time to the search. Heuristically we
 +     * determined that under common conditions the non-bonded kernel balance
 +     * improvement will outweigh this when the imbalance is more than 3%.
 +     * But this will, obviously, depend on search vs kernel time and nstlist.
 +     */
 +    const real rebalanceTolerance = 1.03;
 +
 +    return numLists*ncjMax > ncjTotal*rebalanceTolerance;
 +}
 +
 +/* Perform a count (linear) sort to sort the smaller lists to the end.
 + * This avoids load imbalance on the GPU, as large lists will be
 + * scheduled and executed first and the smaller lists later.
 + * Load balancing between multi-processors only happens at the end
 + * and there smaller lists lead to more effective load balancing.
 + * The sorting is done on the cj4 count, not on the actual pair counts.
 + * Not only does this make the sort faster, but it also results in
 + * better load balancing than using a list sorted on exact load.
 + * This function swaps the pointer in the pair list to avoid a copy operation.
 + */
 +static void sort_sci(NbnxnPairlistGpu *nbl)
 +{
 +    if (nbl->cj4.size() <= nbl->sci.size())
 +    {
 +        /* nsci = 0 or all sci have size 1, sorting won't change the order */
 +        return;
 +    }
 +
 +    NbnxnPairlistGpuWork &work = *nbl->work;
 +
 +    /* We will distinguish differences up to double the average */
 +    const int m = (2*nbl->cj4.size())/nbl->sci.size();
 +
 +    /* Resize work.sci_sort so we can sort into it */
 +    work.sci_sort.resize(nbl->sci.size());
 +
 +    std::vector<int> &sort = work.sortBuffer;
 +    /* Set up m + 1 entries in sort, initialized at 0 */
 +    sort.clear();
 +    sort.resize(m + 1, 0);
 +    /* Count the entries of each size */
 +    for (const nbnxn_sci_t &sci : nbl->sci)
 +    {
 +        int i = std::min(m, sci.numJClusterGroups());
 +        sort[i]++;
 +    }
 +    /* Calculate the offset for each count */
 +    int s0  = sort[m];
 +    sort[m] = 0;
 +    for (int i = m - 1; i >= 0; i--)
 +    {
 +        int s1  = sort[i];
 +        sort[i] = sort[i + 1] + s0;
 +        s0      = s1;
 +    }
 +
 +    /* Sort entries directly into place */
 +    gmx::ArrayRef<nbnxn_sci_t> sci_sort = work.sci_sort;
 +    for (const nbnxn_sci_t &sci : nbl->sci)
 +    {
 +        int i = std::min(m, sci.numJClusterGroups());
 +        sci_sort[sort[i]++] = sci;
 +    }
 +
 +    /* Swap the sci pointers so we use the new, sorted list */
 +    std::swap(nbl->sci, work.sci_sort);
 +}
 +
 +//! Prepares CPU lists produced by the search for dynamic pruning
 +static void prepareListsForDynamicPruning(gmx::ArrayRef<NbnxnPairlistCpu> lists);
 +
 +void
 +PairlistSet::constructPairlists(const Nbnxm::GridSet          &gridSet,
 +                                gmx::ArrayRef<PairsearchWork>  searchWork,
 +                                nbnxn_atomdata_t              *nbat,
 +                                const t_blocka                *excl,
 +                                const int                      minimumIlistCountForGpuBalancing,
 +                                t_nrnb                        *nrnb,
 +                                SearchCycleCounting           *searchCycleCounting)
 +{
 +    const real         rlist    = params_.rlistOuter;
 +
 +    int                nsubpair_target;
 +    float              nsubpair_tot_est;
 +    int                ci_block;
 +    gmx_bool           progBal;
 +    int                np_tot, np_noq, np_hlj, nap;
 +
 +    const int          numLists = (isCpuType_ ? cpuLists_.size() : gpuLists_.size());
 +
 +    if (debug)
 +    {
 +        fprintf(debug, "ns making %d nblists\n", numLists);
 +    }
 +
 +    nbat->bUseBufferFlags = (nbat->out.size() > 1);
 +    /* We should re-init the flags before making the first list */
 +    if (nbat->bUseBufferFlags && locality_ == InteractionLocality::Local)
 +    {
 +        init_buffer_flags(&nbat->buffer_flags, nbat->numAtoms());
 +    }
 +
 +    int nzi;
 +    if (locality_ == InteractionLocality::Local)
 +    {
 +        /* Only zone (grid) 0 vs 0 */
 +        nzi = 1;
 +    }
 +    else
 +    {
 +        nzi = gridSet.domainSetup().zones->nizone;
 +    }
 +
 +    if (!isCpuType_ && minimumIlistCountForGpuBalancing > 0)
 +    {
 +        get_nsubpair_target(gridSet, locality_, rlist, minimumIlistCountForGpuBalancing,
 +                            &nsubpair_target, &nsubpair_tot_est);
 +    }
 +    else
 +    {
 +        nsubpair_target  = 0;
 +        nsubpair_tot_est = 0;
 +    }
 +
 +    /* Clear all pair-lists */
 +    for (int th = 0; th < numLists; th++)
 +    {
 +        if (isCpuType_)
 +        {
 +            clear_pairlist(&cpuLists_[th]);
 +        }
 +        else
 +        {
 +            clear_pairlist(&gpuLists_[th]);
 +        }
 +
 +        if (params_.haveFep)
 +        {
 +            clear_pairlist_fep(fepLists_[th].get());
 +        }
 +    }
 +
 +    const gmx_domdec_zones_t *ddZones = gridSet.domainSetup().zones;
 +
 +    for (int zi = 0; zi < nzi; zi++)
 +    {
 +        const Grid &iGrid = gridSet.grids()[zi];
 +
 +        int                 zj0;
 +        int                 zj1;
 +        if (locality_ == InteractionLocality::Local)
 +        {
 +            zj0 = 0;
 +            zj1 = 1;
 +        }
 +        else
 +        {
 +            zj0 = ddZones->izone[zi].j0;
 +            zj1 = ddZones->izone[zi].j1;
 +            if (zi == 0)
 +            {
 +                zj0++;
 +            }
 +        }
 +        for (int zj = zj0; zj < zj1; zj++)
 +        {
 +            const Grid &jGrid = gridSet.grids()[zj];
 +
 +            if (debug)
 +            {
 +                fprintf(debug, "ns search grid %d vs %d\n", zi, zj);
 +            }
 +
 +            searchCycleCounting->start(enbsCCsearch);
 +
 +            ci_block = get_ci_block_size(iGrid, gridSet.domainSetup().haveMultipleDomains, numLists);
 +
 +            /* With GPU: generate progressively smaller lists for
 +             * load balancing for local only or non-local with 2 zones.
 +             */
 +            progBal = (locality_ == InteractionLocality::Local || ddZones->n <= 2);
 +
 +#pragma omp parallel for num_threads(numLists) schedule(static)
 +            for (int th = 0; th < numLists; th++)
 +            {
 +                try
 +                {
 +                    /* Re-init the thread-local work flag data before making
 +                     * the first list (not an elegant conditional).
 +                     */
 +                    if (nbat->bUseBufferFlags && ((zi == 0 && zj == 0)))
 +                    {
 +                        init_buffer_flags(&searchWork[th].buffer_flags, nbat->numAtoms());
 +                    }
 +
 +                    if (combineLists_ && th > 0)
 +                    {
 +                        GMX_ASSERT(!isCpuType_, "Can only combine GPU lists");
 +
 +                        clear_pairlist(&gpuLists_[th]);
 +                    }
 +
 +                    PairsearchWork &work = searchWork[th];
 +
 +                    work.cycleCounter.start();
 +
 +                    t_nblist *fepListPtr = (fepLists_.empty() ? nullptr : fepLists_[th].get());
 +
 +                    /* Divide the i cells equally over the pairlists */
 +                    if (isCpuType_)
 +                    {
 +                        nbnxn_make_pairlist_part(gridSet, iGrid, jGrid,
 +                                                 &work, nbat, *excl,
 +                                                 rlist,
 +                                                 params_.pairlistType,
 +                                                 ci_block,
 +                                                 nbat->bUseBufferFlags,
 +                                                 nsubpair_target,
 +                                                 progBal, nsubpair_tot_est,
 +                                                 th, numLists,
 +                                                 &cpuLists_[th],
 +                                                 fepListPtr);
 +                    }
 +                    else
 +                    {
 +                        nbnxn_make_pairlist_part(gridSet, iGrid, jGrid,
 +                                                 &work, nbat, *excl,
 +                                                 rlist,
 +                                                 params_.pairlistType,
 +                                                 ci_block,
 +                                                 nbat->bUseBufferFlags,
 +                                                 nsubpair_target,
 +                                                 progBal, nsubpair_tot_est,
 +                                                 th, numLists,
 +                                                 &gpuLists_[th],
 +                                                 fepListPtr);
 +                    }
 +
 +                    work.cycleCounter.stop();
 +                }
 +                GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
 +            }
 +            searchCycleCounting->stop(enbsCCsearch);
 +
 +            np_tot = 0;
 +            np_noq = 0;
 +            np_hlj = 0;
 +            for (int th = 0; th < numLists; th++)
 +            {
 +                inc_nrnb(nrnb, eNR_NBNXN_DIST2, searchWork[th].ndistc);
 +
 +                if (isCpuType_)
 +                {
 +                    const NbnxnPairlistCpu &nbl = cpuLists_[th];
 +                    np_tot += nbl.cj.size();
 +                    np_noq += nbl.work->ncj_noq;
 +                    np_hlj += nbl.work->ncj_hlj;
 +                }
 +                else
 +                {
 +                    const NbnxnPairlistGpu &nbl = gpuLists_[th];
 +                    /* This count ignores potential subsequent pair pruning */
 +                    np_tot += nbl.nci_tot;
 +                }
 +            }
 +            if (isCpuType_)
 +            {
 +                nap      = cpuLists_[0].na_ci*cpuLists_[0].na_cj;
 +            }
 +            else
 +            {
 +                nap      = gmx::square(gpuLists_[0].na_ci);
 +            }
 +            natpair_ljq_ = (np_tot - np_noq)*nap - np_hlj*nap/2;
 +            natpair_lj_  = np_noq*nap;
 +            natpair_q_   = np_hlj*nap/2;
 +
 +            if (combineLists_ && numLists > 1)
 +            {
 +                GMX_ASSERT(!isCpuType_, "Can only combine GPU lists");
 +
 +                searchCycleCounting->start(enbsCCcombine);
 +
 +                combine_nblists(gmx::constArrayRefFromArray(&gpuLists_[1], numLists - 1),
 +                                &gpuLists_[0]);
 +
 +                searchCycleCounting->stop(enbsCCcombine);
 +            }
 +        }
 +    }
 +
 +    if (isCpuType_)
 +    {
 +        if (numLists > 1 && checkRebalanceSimpleLists(cpuLists_))
 +        {
 +            rebalanceSimpleLists(cpuLists_, cpuListsWork_, searchWork);
 +
 +            /* Swap the sets of pair lists */
 +            cpuLists_.swap(cpuListsWork_);
 +        }
 +    }
 +    else
 +    {
 +        /* Sort the entries on size, large ones first */
 +        if (combineLists_ || gpuLists_.size() == 1)
 +        {
 +            sort_sci(&gpuLists_[0]);
 +        }
 +        else
 +        {
 +#pragma omp parallel for num_threads(numLists) schedule(static)
 +            for (int th = 0; th < numLists; th++)
 +            {
 +                try
 +                {
 +                    sort_sci(&gpuLists_[th]);
 +                }
 +                GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
 +            }
 +        }
 +    }
 +
 +    if (nbat->bUseBufferFlags)
 +    {
 +        reduce_buffer_flags(searchWork, numLists, &nbat->buffer_flags);
 +    }
 +
 +    if (gridSet.haveFep())
 +    {
 +        /* Balance the free-energy lists over all the threads */
 +        balance_fep_lists(fepLists_, searchWork);
 +    }
 +
 +    if (isCpuType_)
 +    {
 +        /* This is a fresh list, so not pruned, stored using ci.
 +         * ciOuter is invalid at this point.
 +         */
 +        GMX_ASSERT(cpuLists_[0].ciOuter.empty(), "ciOuter is invalid so it should be empty");
 +    }
 +
 +    /* If we have more than one list, they either got rebalancing (CPU)
 +     * or combined (GPU), so we should dump the final result to debug.
 +     */
 +    if (debug)
 +    {
 +        if (isCpuType_ && cpuLists_.size() > 1)
 +        {
 +            for (auto &cpuList : cpuLists_)
 +            {
 +                print_nblist_statistics(debug, cpuList, gridSet, rlist);
 +            }
 +        }
 +        else if (!isCpuType_ && gpuLists_.size() > 1)
 +        {
 +            print_nblist_statistics(debug, gpuLists_[0], gridSet, rlist);
 +        }
 +    }
 +
 +    if (debug)
 +    {
 +        if (gmx_debug_at)
 +        {
 +            if (isCpuType_)
 +            {
 +                for (auto &cpuList : cpuLists_)
 +                {
 +                    print_nblist_ci_cj(debug, cpuList);
 +                }
 +            }
 +            else
 +            {
 +                print_nblist_sci_cj(debug, gpuLists_[0]);
 +            }
 +        }
 +
 +        if (nbat->bUseBufferFlags)
 +        {
 +            print_reduction_cost(&nbat->buffer_flags, numLists);
 +        }
 +    }
 +
 +    if (params_.useDynamicPruning && isCpuType_)
 +    {
 +        prepareListsForDynamicPruning(cpuLists_);
 +    }
 +}
 +
 +void
 +PairlistSets::construct(const InteractionLocality  iLocality,
 +                        PairSearch                *pairSearch,
 +                        nbnxn_atomdata_t          *nbat,
 +                        const t_blocka            *excl,
 +                        const int64_t              step,
 +                        t_nrnb                    *nrnb)
 +{
 +    pairlistSet(iLocality).constructPairlists(pairSearch->gridSet(), pairSearch->work(),
 +                                              nbat, excl, minimumIlistCountForGpuBalancing_,
 +                                              nrnb, &pairSearch->cycleCounting_);
 +
 +    if (iLocality == Nbnxm::InteractionLocality::Local)
 +    {
 +        outerListCreationStep_ = step;
 +    }
 +    else
 +    {
 +        GMX_RELEASE_ASSERT(outerListCreationStep_ == step,
 +                           "Outer list should be created at the same step as the inner list");
 +    }
 +
 +    /* Special performance logging stuff (env.var. GMX_NBNXN_CYCLE) */
 +    if (iLocality == InteractionLocality::Local)
 +    {
 +        pairSearch->cycleCounting_.searchCount_++;
 +    }
 +    if (pairSearch->cycleCounting_.recordCycles_ &&
 +        (!pairSearch->gridSet().domainSetup().haveMultipleDomains || iLocality == InteractionLocality::NonLocal) &&
 +        pairSearch->cycleCounting_.searchCount_ % 100 == 0)
 +    {
 +        pairSearch->cycleCounting_.printCycles(stderr, pairSearch->work());
 +    }
 +}
 +
 +void
 +nonbonded_verlet_t::constructPairlist(const Nbnxm::InteractionLocality  iLocality,
 +                                      const t_blocka                   *excl,
 +                                      int64_t                           step,
 +                                      t_nrnb                           *nrnb)
 +{
 +    pairlistSets_->construct(iLocality, pairSearch_.get(), nbat.get(), excl,
 +                             step, nrnb);
 +
 +    if (useGpu())
 +    {
 +        /* Launch the transfer of the pairlist to the GPU.
 +         *
 +         * NOTE: The launch overhead is currently not timed separately
 +         */
 +        Nbnxm::gpu_init_pairlist(gpu_nbv,
 +                                 pairlistSets().pairlistSet(iLocality).gpuList(),
 +                                 iLocality);
 +    }
 +}
 +
 +static void prepareListsForDynamicPruning(gmx::ArrayRef<NbnxnPairlistCpu> lists)
 +{
 +    /* TODO: Restructure the lists so we have actual outer and inner
 +     *       list objects so we can set a single pointer instead of
 +     *       swapping several pointers.
 +     */
 +
 +    for (auto &list : lists)
 +    {
 +        /* The search produced a list in ci/cj.
 +         * Swap the list pointers so we get the outer list is ciOuter,cjOuter
 +         * and we can prune that to get an inner list in ci/cj.
 +         */
 +        GMX_RELEASE_ASSERT(list.ciOuter.empty() && list.cjOuter.empty(),
 +                           "The outer lists should be empty before preparation");
 +
 +        std::swap(list.ci, list.ciOuter);
 +        std::swap(list.cj, list.cjOuter);
 +    }
 +}
index 35a917ef8b29b31d5ba720bcb5620ba7746f2733,be8e9d1dc11dde0089631f72e2c8b8ffa7aaf580..1b0663bb1d919289b68bdc6da2c5d70251e60f35
@@@ -46,9 -46,9 +46,9 @@@
  #include <cstdlib>
  
  #include <algorithm>
 +#include <memory>
  
  #include "gromacs/commandline/filenm.h"
 -#include "gromacs/compat/make_unique.h"
  #include "gromacs/domdec/domdec_struct.h"
  #include "gromacs/domdec/localatomset.h"
  #include "gromacs/domdec/localatomsetmanager.h"
@@@ -59,6 -59,7 +59,6 @@@
  #include "gromacs/math/vec.h"
  #include "gromacs/math/vectypes.h"
  #include "gromacs/mdlib/gmx_omp_nthreads.h"
 -#include "gromacs/mdlib/mdrun.h"
  #include "gromacs/mdtypes/commrec.h"
  #include "gromacs/mdtypes/forceoutput.h"
  #include "gromacs/mdtypes/inputrec.h"
@@@ -1078,7 -1079,7 +1078,7 @@@ static void do_constraint(struct pull_
          /* update the atom positions */
          auto localAtomIndices = pgrp->atomSet.localIndex();
          copy_dvec(dr, tmp);
 -        for (gmx::index j = 0; j < localAtomIndices.size(); j++)
 +        for (gmx::index j = 0; j < localAtomIndices.ssize(); j++)
          {
              ii = localAtomIndices[j];
              if (!pgrp->localWeights.empty())
@@@ -1375,7 -1376,7 +1375,7 @@@ void register_external_pull_potential(s
      GMX_RELEASE_ASSERT(pull != nullptr, "register_external_pull_potential called before init_pull");
      GMX_RELEASE_ASSERT(provider != nullptr, "register_external_pull_potential called with NULL as provider name");
  
 -    if (coord_index < 0 || coord_index >= static_cast<int>(pull->coord.size()))
 +    if (coord_index < 0 || coord_index >= gmx::ssize(pull->coord))
      {
          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());
@@@ -1452,7 -1453,7 +1452,7 @@@ void apply_external_pull_coord_force(st
  {
      pull_coord_work_t *pcrd;
  
 -    GMX_ASSERT(coord_index >= 0 && coord_index < static_cast<int>(pull->coord.size()), "apply_external_pull_coord_force called with coord_index out of range");
 +    GMX_ASSERT(coord_index >= 0 && coord_index < gmx::ssize(pull->coord), "apply_external_pull_coord_force called with coord_index out of range");
  
      if (pull->comm.bParticipate)
      {
@@@ -1597,7 -1598,7 +1597,7 @@@ void dd_make_local_pull_groups(const t_
  
      for (pull_group_work_t &group : pull->group)
      {
-         if (group.epgrppbc == epgrppbcCOS || !group.globalWeights.empty())
+         if (!group.globalWeights.empty())
          {
              group.localWeights.resize(group.atomSet.numAtomsLocal());
              for (size_t i = 0; i < group.atomSet.numAtomsLocal(); ++i)
@@@ -1728,9 -1729,9 +1728,9 @@@ static void init_pull_group_index(FILE 
                               ir->eI == eiBD);
  
      /* In parallel, store we need to extract localWeights from weights at DD time */
 -    std::vector<real>  &weights = ((cr && PAR(cr)) ? pg->globalWeights : pg->localWeights);
 +    std::vector<real>         &weights = ((cr && PAR(cr)) ? pg->globalWeights : pg->localWeights);
  
 -    const gmx_groups_t *groups  = &mtop->groups;
 +    const SimulationGroups    &groups  = mtop->groups;
  
      /* Count frozen dimensions and (weighted) mass */
      int    nfrozen = 0;
              for (int d = 0; d < DIM; d++)
              {
                  if (pulldim_con[d] == 1 &&
 -                    ir->opts.nFreeze[getGroupType(groups, egcFREEZE, ii)][d])
 +                    ir->opts.nFreeze[getGroupType(groups, SimulationAtomGroupType::Freeze, ii)][d])
                  {
                      nfrozen++;
                  }
              }
              else
              {
 -                if (groups->grpnr[egcTC] == nullptr)
 +                if (groups.groupNumbers[SimulationAtomGroupType::TemperatureCoupling].empty())
                  {
                      mbd = ir->delta_t/ir->opts.tau_t[0];
                  }
                  else
                  {
 -                    mbd = ir->delta_t/ir->opts.tau_t[groups->grpnr[egcTC][ii]];
 +                    mbd = ir->delta_t/ir->opts.tau_t[groups.groupNumbers[SimulationAtomGroupType::TemperatureCoupling][ii]];
                  }
              }
              w                   *= m/mbd;
@@@ -1895,7 -1896,7 +1895,7 @@@ init_pull(FILE *fplog, const pull_param
              if (group.epgrppbc == epgrppbcREFAT || group.epgrppbc == epgrppbcPREVSTEPCOM)
              {
                  /* pbcAtomSet consists of a single atom */
 -                group.pbcAtomSet = gmx::compat::make_unique<gmx::LocalAtomSet>(atomSets->add({&group.params.pbcatom, &group.params.pbcatom + 1}));
 +                group.pbcAtomSet = std::make_unique<gmx::LocalAtomSet>(atomSets->add({&group.params.pbcatom, &group.params.pbcatom + 1}));
              }
          }
      }
@@@ -2265,15 -2266,13 +2265,15 @@@ static void destroy_pull(struct pull_t 
      delete pull;
  }
  
 -void preparePrevStepPullCom(const t_inputrec *ir, const t_mdatoms *md, t_state *state, const t_state *state_global, const t_commrec *cr, bool startingFromCheckpoint)
 +void preparePrevStepPullCom(const t_inputrec *ir, pull_t *pull_work,
 +                            const t_mdatoms *md, t_state *state, const t_state *state_global,
 +                            const t_commrec *cr, bool startingFromCheckpoint)
  {
      if (!ir->pull || !ir->pull->bSetPbcRefToPrevStepCOM)
      {
          return;
      }
 -    allocStatePrevStepPullCom(state, ir->pull_work);
 +    allocStatePrevStepPullCom(state, pull_work);
      if (startingFromCheckpoint)
      {
          if (MASTER(cr))
              /* 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);
          }
 -        setPrevStepPullComFromState(ir->pull_work, state);
 +        setPrevStepPullComFromState(pull_work, state);
      }
      else
      {
          t_pbc pbc;
          set_pbc(&pbc, ir->ePBC, state->box);
 -        initPullComFromPrevStep(cr, ir->pull_work, md, &pbc, state->x.rvec_array());
 -        updatePrevStepPullCom(ir->pull_work, state);
 +        initPullComFromPrevStep(cr, pull_work, md, &pbc, state->x.rvec_array());
 +        updatePrevStepPullCom(pull_work, state);
      }
  }
  
index af6b2ed5d0e9ea388b89f9fd2db415bc1bd8c3c1,40c2247f93a9624a75026b0ec9a6848a01674666..95ed32a279753bb062838b1aa18913aa630eac79
@@@ -251,7 -251,7 +251,7 @@@ static void make_cyl_refgrps(const t_co
              pdyna.dv.resize(localAtomIndices.size());
  
              /* loop over all atoms in the main ref group */
 -            for (gmx::index indexInSet = 0; indexInSet < localAtomIndices.size(); indexInSet++)
 +            for (gmx::index indexInSet = 0; indexInSet < localAtomIndices.ssize(); indexInSet++)
              {
                  int    atomIndex = localAtomIndices[indexInSet];
                  rvec   dx;
@@@ -579,6 -579,15 +579,15 @@@ void pull_calc_coms(const t_commrec *cr
      {
          pull_group_work_t *pgrp      = &pull->group[g];
  
+         /* Cosine-weighted COMs behave different from all other weighted COMs
+          * in the sense that the weights depend on instantaneous coordinates,
+          * not on pre-set weights. Thus we resize the local weight buffer here.
+          */
+         if (pgrp->epgrppbc == epgrppbcCOS)
+         {
+             pgrp->localWeights.resize(pgrp->atomSet.localIndex().size());
+         }
          auto               comBuffer =
              gmx::arrayRefFromArray(comm->comBuffer.data() + g*c_comBufferStride, c_comBufferStride);
  
@@@ -867,7 -876,7 +876,7 @@@ static bool pullGroupObeysPbcRestrictio
      }
  
      auto localAtomIndices = group.atomSet.localIndex();
 -    for (gmx::index indexInSet = 0; indexInSet < localAtomIndices.size(); indexInSet++)
 +    for (gmx::index indexInSet = 0; indexInSet < localAtomIndices.ssize(); indexInSet++)
      {
          rvec dx;
          pbc_dx(&pbc, x[localAtomIndices[indexInSet]], x_pbc, dx);
@@@ -957,7 -966,7 +966,7 @@@ bool pullCheckPbcWithinGroup(const pull
      {
          return true;
      }
 -    GMX_ASSERT(groupNr < static_cast<int>(pull.group.size()), "groupNr is out of range");
 +    GMX_ASSERT(groupNr < gmx::ssize(pull.group), "groupNr is out of range");
  
      /* Check PBC if the group uses a PBC reference atom treatment. */
      const pull_group_work_t &group = pull.group[groupNr];
@@@ -1016,7 -1025,7 +1025,7 @@@ void updatePrevStepPullCom(struct pull_
      }
  }
  
 -void allocStatePrevStepPullCom(t_state *state, pull_t *pull)
 +void allocStatePrevStepPullCom(t_state *state, const pull_t *pull)
  {
      if (!pull)
      {
index b514513fd9e479f138423c30aa58838b83818df7,6fee64af98322e74123b5f5f328b7df5947ad222..23911799c27671beb6e14a88f41ba7a1ff64ff32
  struct gmx_ana_indexgrps_t
  {
      //! Initializes an empty set of groups.
 -    explicit gmx_ana_indexgrps_t(int nr) : nr(nr), g(nullptr)
 +    explicit gmx_ana_indexgrps_t(int nr)
 +        : g(nr)
      {
          names.reserve(nr);
 -        snew(g, nr);
      }
      ~gmx_ana_indexgrps_t()
      {
 -        for (int i = 0; i < nr; ++i)
 +        for (auto &indexGrp : g)
          {
 -            gmx_ana_index_deinit(&g[i]);
 +            gmx_ana_index_deinit(&indexGrp);
          }
 -        sfree(g);
      }
  
 -    /** Number of index groups. */
 -    int                       nr;
 +
      /** Array of index groups. */
 -    gmx_ana_index_t          *g;
 +    std::vector<gmx_ana_index_t> g;
      /** Group names. */
 -    std::vector<std::string>  names;
 +    std::vector<std::string>     names;
  };
  
  /*!
@@@ -177,6 -179,32 +177,6 @@@ gmx_ana_indexgrps_free(gmx_ana_indexgrp
      delete g;
  }
  
 -/*!
 - * \param[out] g     Index group structure.
 - * \returns    true if \p g is empty, i.e., has 0 index groups.
 - */
 -bool
 -gmx_ana_indexgrps_is_empty(gmx_ana_indexgrps_t *g)
 -{
 -    return g->nr == 0;
 -}
 -
 -/*!
 - * \param[in]  g     Index groups structure.
 - * \param[in]  n     Index group number to get.
 - * \returns    Pointer to the \p n'th index group in \p g.
 - *
 - * The returned pointer should not be freed.
 - */
 -gmx_ana_index_t *
 -gmx_ana_indexgrps_get_grp(gmx_ana_indexgrps_t *g, int n)
 -{
 -    if (n < 0 || n >= g->nr)
 -    {
 -        return nullptr;
 -    }
 -    return &g->g[n];
 -}
  
  /*!
   * \param[out] dest     Output structure.
@@@ -190,7 -218,7 +190,7 @@@ gmx_ana_indexgrps_extract(gmx_ana_index
                            gmx_ana_indexgrps_t *src, int n)
  {
      destName->clear();
 -    if (n < 0 || n >= src->nr)
 +    if (n < 0 || n >= gmx::index(src->g.size()))
      {
          dest->isize = 0;
          return false;
@@@ -222,12 -250,12 +222,12 @@@ gmx_ana_indexgrps_find(gmx_ana_index_t 
      const char **names;
  
      destName->clear();
 -    snew(names, src->nr);
 -    for (int i = 0; i < src->nr; ++i)
 +    snew(names, src->g.size());
 +    for (size_t i = 0; i < src->g.size(); ++i)
      {
          names[i] = src->names[i].c_str();
      }
 -    int n = find_group(const_cast<char *>(name), src->nr,
 +    int n = find_group(const_cast<char *>(name), src->g.size(),
                         const_cast<char **>(names));
      sfree(names);
      if (n < 0)
  void
  gmx_ana_indexgrps_print(gmx::TextWriter *writer, gmx_ana_indexgrps_t *g, int maxn)
  {
 -    for (int i = 0; i < g->nr; ++i)
 +    for (int i = 0; i < gmx::ssize(g->g); ++i)
      {
          writer->writeString(gmx::formatString(" Group %2d \"%s\" ",
                                                i, g->names[i].c_str()));
@@@ -885,21 -913,23 +885,23 @@@ gmx_ana_index_make_block(t_blocka *t, c
                      {
                          int            molnr, atnr_mol;
                          mtopGetMolblockIndex(top, ai, &molb, &molnr, &atnr_mol);
-                         const t_atoms &mol_atoms = top->moltype[top->molblock[molb].type].atoms;
-                         int            last_atom = atnr_mol + 1;
+                         const t_atoms &mol_atoms    = top->moltype[top->molblock[molb].type].atoms;
+                         int            last_atom    = atnr_mol + 1;
+                         const int      currentResid = mol_atoms.atom[atnr_mol].resind;
                          while (last_atom < mol_atoms.nr
-                                && mol_atoms.atom[last_atom].resind == id)
+                                && mol_atoms.atom[last_atom].resind == currentResid)
                          {
                              ++last_atom;
                          }
                          int first_atom = atnr_mol - 1;
                          while (first_atom >= 0
-                                && mol_atoms.atom[first_atom].resind == id)
+                                && mol_atoms.atom[first_atom].resind == currentResid)
                          {
                              --first_atom;
                          }
-                         int first_mol_atom = top->moleculeBlockIndices[molb].globalAtomStart;
-                         first_mol_atom += molnr*top->moleculeBlockIndices[molb].numAtomsPerMolecule;
+                         const MoleculeBlockIndices &molBlock = top->moleculeBlockIndices[molb];
+                         int first_mol_atom                   = molBlock.globalAtomStart;
+                         first_mol_atom += molnr*molBlock.numAtomsPerMolecule;
                          first_atom      = first_mol_atom + first_atom + 1;
                          last_atom       = first_mol_atom + last_atom - 1;
                          for (int j = first_atom; j <= last_atom; ++j)
                      }
                      case INDEX_MOL:
                      {
-                         size_t molb = 0;
-                         while (molb + 1 < top->molblock.size() && id >= top->moleculeBlockIndices[molb].moleculeIndexStart)
-                         {
-                             ++molb;
-                         }
+                         int                         molnr, atnr_mol;
+                         mtopGetMolblockIndex(top, ai, &molb, &molnr, &atnr_mol);
                          const MoleculeBlockIndices &blockIndices  = top->moleculeBlockIndices[molb];
                          const int                   atomStart     = blockIndices.globalAtomStart + (id - blockIndices.moleculeIndexStart)*blockIndices.numAtomsPerMolecule;
                          for (int j = 0; j < blockIndices.numAtomsPerMolecule; ++j)
index 6e3440d9c3bcc62033b8795e71dc13b673f852dc,bc2a3534228f6597bc925dc491cf6cec1d736bbe..70a1a51b916cd40fe0c5ff51da7fba4a54732a37
@@@ -51,7 -51,7 +51,7 @@@
  #include "gromacs/utility/gmxassert.h"
  
  #include "selmethod.h"
 -#include "selmethod-impl.h"
 +#include "selmethod_impl.h"
  
  /** Evaluates the \p all selection keyword. */
  static void
@@@ -220,7 -220,7 +220,7 @@@ static const char *const help_resindex[
      "[TT]resnr[tt] selects atoms using the residue numbering in the input",
      "file. [TT]resid[tt] is synonym for this keyword for VMD compatibility.",
      "",
-     "[TT]resindex N[tt] selects the [TT]N[tt]th residue starting from the",
+     "[TT]resindex N[tt] selects the [TT]N[tt] th residue starting from the",
      "beginning of the input file. This is useful for uniquely identifying",
      "residues if there are duplicate numbers in the input file (e.g., in",
      "multiple chains).",
index 9b9d517aa68068b3e9a627ae66544aa1f4bfd2b3,0000000000000000000000000000000000000000..6255e0195fa68a9d668715d4fd0d769a8506860a
mode 100644,000000..100644
--- /dev/null
@@@ -1,296 -1,0 +1,296 @@@
-            Run this number of steps, overrides .mdp file option (-1 means
-            infinite, -2 means use mdp option, smaller is invalid)
 +<?xml version="1.0"?>
 +<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
 +<ReferenceData>
 +  <String Name="Help string">SYNOPSIS
 +
 +gmx [-s [&lt;.tpr&gt;]] [-cpi [&lt;.cpt&gt;]] [-table [&lt;.xvg&gt;]] [-tablep [&lt;.xvg&gt;]]
 +    [-tableb [&lt;.xvg&gt; [...]]] [-rerun [&lt;.xtc/.trr/...&gt;]] [-ei [&lt;.edi&gt;]]
 +    [-multidir [&lt;dir&gt; [...]]] [-awh [&lt;.xvg&gt;]] [-membed [&lt;.dat&gt;]]
 +    [-mp [&lt;.top&gt;]] [-mn [&lt;.ndx&gt;]] [-o [&lt;.trr/.cpt/...&gt;]] [-x [&lt;.xtc/.tng&gt;]]
 +    [-cpo [&lt;.cpt&gt;]] [-c [&lt;.gro/.g96/...&gt;]] [-e [&lt;.edr&gt;]] [-g [&lt;.log&gt;]]
 +    [-dhdl [&lt;.xvg&gt;]] [-field [&lt;.xvg&gt;]] [-tpi [&lt;.xvg&gt;]] [-tpid [&lt;.xvg&gt;]]
 +    [-eo [&lt;.xvg&gt;]] [-px [&lt;.xvg&gt;]] [-pf [&lt;.xvg&gt;]] [-ro [&lt;.xvg&gt;]]
 +    [-ra [&lt;.log&gt;]] [-rs [&lt;.log&gt;]] [-rt [&lt;.log&gt;]] [-mtx [&lt;.mtx&gt;]]
 +    [-if [&lt;.xvg&gt;]] [-swap [&lt;.xvg&gt;]] [-deffnm &lt;string&gt;] [-xvg &lt;enum&gt;]
 +    [-dd &lt;vector&gt;] [-ddorder &lt;enum&gt;] [-npme &lt;int&gt;] [-nt &lt;int&gt;] [-ntmpi &lt;int&gt;]
 +    [-ntomp &lt;int&gt;] [-ntomp_pme &lt;int&gt;] [-pin &lt;enum&gt;] [-pinoffset &lt;int&gt;]
 +    [-pinstride &lt;int&gt;] [-gpu_id &lt;string&gt;] [-gputasks &lt;string&gt;] [-[no]ddcheck]
 +    [-rdd &lt;real&gt;] [-rcon &lt;real&gt;] [-dlb &lt;enum&gt;] [-dds &lt;real&gt;] [-nb &lt;enum&gt;]
 +    [-nstlist &lt;int&gt;] [-[no]tunepme] [-pme &lt;enum&gt;] [-pmefft &lt;enum&gt;]
 +    [-bonded &lt;enum&gt;] [-[no]v] [-pforce &lt;real&gt;] [-[no]reprod] [-cpt &lt;real&gt;]
 +    [-[no]cpnum] [-[no]append] [-nsteps &lt;int&gt;] [-maxh &lt;real&gt;] [-replex &lt;int&gt;]
 +    [-nex &lt;int&gt;] [-reseed &lt;int&gt;]
 +
 +DESCRIPTION
 +
 +[THISMODULE] is the main computational chemistry engine within GROMACS.
 +Obviously, it performs Molecular Dynamics simulations, but it can also perform
 +Stochastic Dynamics, Energy Minimization, test particle insertion or
 +(re)calculation of energies. Normal mode analysis is another option. In this
 +case mdrun builds a Hessian matrix from single conformation. For usual Normal
 +Modes-like calculations, make sure that the structure provided is properly
 +energy-minimized. The generated matrix can be diagonalized by [gmx-nmeig].
 +
 +The mdrun program reads the run input file (-s) and distributes the topology
 +over ranks if needed. mdrun produces at least four output files. A single log
 +file (-g) is written. The trajectory file (-o), contains coordinates,
 +velocities and optionally forces. The structure file (-c) contains the
 +coordinates and velocities of the last step. The energy file (-e) contains
 +energies, the temperature, pressure, etc, a lot of these things are also
 +printed in the log file. Optionally coordinates can be written to a compressed
 +trajectory file (-x).
 +
 +The option -dhdl is only used when free energy calculation is turned on.
 +
 +Running mdrun efficiently in parallel is a complex topic, many aspects of
 +which are covered in the online User Guide. You should look there for
 +practical advice on using many of the options available in mdrun.
 +
 +ED (essential dynamics) sampling and/or additional flooding potentials are
 +switched on by using the -ei flag followed by an .edi file. The .edi file can
 +be produced with the make_edi tool or by using options in the essdyn menu of
 +the WHAT IF program. mdrun produces a .xvg output file that contains
 +projections of positions, velocities and forces onto selected eigenvectors.
 +
 +When user-defined potential functions have been selected in the .mdp file the
 +-table option is used to pass mdrun a formatted table with potential
 +functions. The file is read from either the current directory or from the
 +GMXLIB directory. A number of pre-formatted tables are presented in the GMXLIB
 +dir, for 6-8, 6-9, 6-10, 6-11, 6-12 Lennard-Jones potentials with normal
 +Coulomb. When pair interactions are present, a separate table for pair
 +interaction functions is read using the -tablep option.
 +
 +When tabulated bonded functions are present in the topology, interaction
 +functions are read using the -tableb option. For each different tabulated
 +interaction type used, a table file name must be given. For the topology to
 +work, a file name given here must match a character sequence before the file
 +extension. That sequence is: an underscore, then a 'b' for bonds, an 'a' for
 +angles or a 'd' for dihedrals, and finally the matching table number index
 +used in the topology. Note that, these options are deprecated, and in future
 +will be available via grompp.
 +
 +The options -px and -pf are used for writing pull COM coordinates and forces
 +when pulling is selected in the .mdp file.
 +
 +The option -membed does what used to be g_membed, i.e. embed a protein into a
 +membrane. This module requires a number of settings that are provided in a
 +data file that is the argument of this option. For more details in membrane
 +embedding, see the documentation in the user guide. The options -mn and -mp
 +are used to provide the index and topology files used for the embedding.
 +
 +The option -pforce is useful when you suspect a simulation crashes due to too
 +large forces. With this option coordinates and forces of atoms with a force
 +larger than a certain value will be printed to stderr. It will also terminate
 +the run when non-finite forces are present.
 +
 +Checkpoints containing the complete state of the system are written at regular
 +intervals (option -cpt) to the file -cpo, unless option -cpt is set to -1. The
 +previous checkpoint is backed up to state_prev.cpt to make sure that a recent
 +state of the system is always available, even when the simulation is
 +terminated while writing a checkpoint. With -cpnum all checkpoint files are
 +kept and appended with the step number. A simulation can be continued by
 +reading the full state from file with option -cpi. This option is intelligent
 +in the way that if no checkpoint file is found, GROMACS just assumes a normal
 +run and starts from the first step of the .tpr file. By default the output
 +will be appending to the existing output files. The checkpoint file contains
 +checksums of all output files, such that you will never loose data when some
 +output files are modified, corrupt or removed. There are three scenarios with
 +-cpi:
 +
 +* no files with matching names are present: new output files are written
 +
 +* all files are present with names and checksums matching those stored in the
 +checkpoint file: files are appended
 +
 +* otherwise no files are modified and a fatal error is generated
 +
 +With -noappend new output files are opened and the simulation part number is
 +added to all output file names. Note that in all cases the checkpoint file
 +itself is not renamed and will be overwritten, unless its name does not match
 +the -cpo option.
 +
 +With checkpointing the output is appended to previously written output files,
 +unless -noappend is used or none of the previous output files are present
 +(except for the checkpoint file). The integrity of the files to be appended is
 +verified using checksums which are stored in the checkpoint file. This ensures
 +that output can not be mixed up or corrupted due to file appending. When only
 +some of the previous output files are present, a fatal error is generated and
 +no old output files are modified and no new output files are opened. The
 +result with appending will be the same as from a single run. The contents will
 +be binary identical, unless you use a different number of ranks or dynamic
 +load balancing or the FFT library uses optimizations through timing.
 +
 +With option -maxh a simulation is terminated and a checkpoint file is written
 +at the first neighbor search step where the run time exceeds -maxh*0.99 hours.
 +This option is particularly useful in combination with setting nsteps to -1
 +either in the mdp or using the similarly named command line option (although
 +the latter is deprecated). This results in an infinite run, terminated only
 +when the time limit set by -maxh is reached (if any) or upon receiving a
 +signal.
 +
 +Interactive molecular dynamics (IMD) can be activated by using at least one of
 +the three IMD switches: The -imdterm switch allows one to terminate the
 +simulation from the molecular viewer (e.g. VMD). With -imdwait, mdrun pauses
 +whenever no IMD client is connected. Pulling from the IMD remote can be turned
 +on by -imdpull. The port mdrun listens to can be altered by -imdport.The file
 +pointed to by -if contains atom indices and forces if IMD pulling is used.
 +
 +OPTIONS
 +
 +Options to specify input files:
 +
 + -s      [&lt;.tpr&gt;]           (topol.tpr)
 +           Portable xdr run input file
 + -cpi    [&lt;.cpt&gt;]           (state.cpt)      (Opt.)
 +           Checkpoint file
 + -table  [&lt;.xvg&gt;]           (table.xvg)      (Opt.)
 +           xvgr/xmgr file
 + -tablep [&lt;.xvg&gt;]           (tablep.xvg)     (Opt.)
 +           xvgr/xmgr file
 + -tableb [&lt;.xvg&gt; [...]]     (table.xvg)      (Opt.)
 +           xvgr/xmgr file
 + -rerun  [&lt;.xtc/.trr/...&gt;]  (rerun.xtc)      (Opt.)
 +           Trajectory: xtc trr cpt gro g96 pdb tng
 + -ei     [&lt;.edi&gt;]           (sam.edi)        (Opt.)
 +           ED sampling input
 + -multidir [&lt;dir&gt; [...]]    (rundir)         (Opt.)
 +           Run directory
 + -awh    [&lt;.xvg&gt;]           (awhinit.xvg)    (Opt.)
 +           xvgr/xmgr file
 + -membed [&lt;.dat&gt;]           (membed.dat)     (Opt.)
 +           Generic data file
 + -mp     [&lt;.top&gt;]           (membed.top)     (Opt.)
 +           Topology file
 + -mn     [&lt;.ndx&gt;]           (membed.ndx)     (Opt.)
 +           Index file
 +
 +Options to specify output files:
 +
 + -o      [&lt;.trr/.cpt/...&gt;]  (traj.trr)
 +           Full precision trajectory: trr cpt tng
 + -x      [&lt;.xtc/.tng&gt;]      (traj_comp.xtc)  (Opt.)
 +           Compressed trajectory (tng format or portable xdr format)
 + -cpo    [&lt;.cpt&gt;]           (state.cpt)      (Opt.)
 +           Checkpoint file
 + -c      [&lt;.gro/.g96/...&gt;]  (confout.gro)
 +           Structure file: gro g96 pdb brk ent esp
 + -e      [&lt;.edr&gt;]           (ener.edr)
 +           Energy file
 + -g      [&lt;.log&gt;]           (md.log)
 +           Log file
 + -dhdl   [&lt;.xvg&gt;]           (dhdl.xvg)       (Opt.)
 +           xvgr/xmgr file
 + -field  [&lt;.xvg&gt;]           (field.xvg)      (Opt.)
 +           xvgr/xmgr file
 + -tpi    [&lt;.xvg&gt;]           (tpi.xvg)        (Opt.)
 +           xvgr/xmgr file
 + -tpid   [&lt;.xvg&gt;]           (tpidist.xvg)    (Opt.)
 +           xvgr/xmgr file
 + -eo     [&lt;.xvg&gt;]           (edsam.xvg)      (Opt.)
 +           xvgr/xmgr file
 + -px     [&lt;.xvg&gt;]           (pullx.xvg)      (Opt.)
 +           xvgr/xmgr file
 + -pf     [&lt;.xvg&gt;]           (pullf.xvg)      (Opt.)
 +           xvgr/xmgr file
 + -ro     [&lt;.xvg&gt;]           (rotation.xvg)   (Opt.)
 +           xvgr/xmgr file
 + -ra     [&lt;.log&gt;]           (rotangles.log)  (Opt.)
 +           Log file
 + -rs     [&lt;.log&gt;]           (rotslabs.log)   (Opt.)
 +           Log file
 + -rt     [&lt;.log&gt;]           (rottorque.log)  (Opt.)
 +           Log file
 + -mtx    [&lt;.mtx&gt;]           (nm.mtx)         (Opt.)
 +           Hessian matrix
 + -if     [&lt;.xvg&gt;]           (imdforces.xvg)  (Opt.)
 +           xvgr/xmgr file
 + -swap   [&lt;.xvg&gt;]           (swapions.xvg)   (Opt.)
 +           xvgr/xmgr file
 +
 +Other options:
 +
 + -deffnm &lt;string&gt;
 +           Set the default filename for all file options
 + -xvg    &lt;enum&gt;             (xmgrace)
 +           xvg plot formatting: xmgrace, xmgr, none
 + -dd     &lt;vector&gt;           (0 0 0)
 +           Domain decomposition grid, 0 is optimize
 + -ddorder &lt;enum&gt;            (interleave)
 +           DD rank order: interleave, pp_pme, cartesian
 + -npme   &lt;int&gt;              (-1)
 +           Number of separate ranks to be used for PME, -1 is guess
 + -nt     &lt;int&gt;              (0)
 +           Total number of threads to start (0 is guess)
 + -ntmpi  &lt;int&gt;              (0)
 +           Number of thread-MPI ranks to start (0 is guess)
 + -ntomp  &lt;int&gt;              (0)
 +           Number of OpenMP threads per MPI rank to start (0 is guess)
 + -ntomp_pme &lt;int&gt;           (0)
 +           Number of OpenMP threads per MPI rank to start (0 is -ntomp)
 + -pin    &lt;enum&gt;             (auto)
 +           Whether mdrun should try to set thread affinities: auto, on, off
 + -pinoffset &lt;int&gt;           (0)
 +           The lowest logical core number to which mdrun should pin the first
 +           thread
 + -pinstride &lt;int&gt;           (0)
 +           Pinning distance in logical cores for threads, use 0 to minimize
 +           the number of threads per physical core
 + -gpu_id &lt;string&gt;
 +           List of unique GPU device IDs available to use
 + -gputasks &lt;string&gt;
 +           List of GPU device IDs, mapping each PP task on each node to a
 +           device
 + -[no]ddcheck               (yes)
 +           Check for all bonded interactions with DD
 + -rdd    &lt;real&gt;             (0)
 +           The maximum distance for bonded interactions with DD (nm), 0 is
 +           determine from initial coordinates
 + -rcon   &lt;real&gt;             (0)
 +           Maximum distance for P-LINCS (nm), 0 is estimate
 + -dlb    &lt;enum&gt;             (auto)
 +           Dynamic load balancing (with DD): auto, no, yes
 + -dds    &lt;real&gt;             (0.8)
 +           Fraction in (0,1) by whose reciprocal the initial DD cell size will
 +           be increased in order to provide a margin in which dynamic load
 +           balancing can act while preserving the minimum cell size.
 + -nb     &lt;enum&gt;             (auto)
 +           Calculate non-bonded interactions on: auto, cpu, gpu
 + -nstlist &lt;int&gt;             (0)
 +           Set nstlist when using a Verlet buffer tolerance (0 is guess)
 + -[no]tunepme               (yes)
 +           Optimize PME load between PP/PME ranks or GPU/CPU (only with the
 +           Verlet cut-off scheme)
 + -pme    &lt;enum&gt;             (auto)
 +           Perform PME calculations on: auto, cpu, gpu
 + -pmefft &lt;enum&gt;             (auto)
 +           Perform PME FFT calculations on: auto, cpu, gpu
 + -bonded &lt;enum&gt;             (auto)
 +           Perform bonded calculations on: auto, cpu, gpu
 + -[no]v                     (no)
 +           Be loud and noisy
 + -pforce &lt;real&gt;             (-1)
 +           Print all forces larger than this (kJ/mol nm)
 + -[no]reprod                (no)
 +           Try to avoid optimizations that affect binary reproducibility
 + -cpt    &lt;real&gt;             (15)
 +           Checkpoint interval (minutes)
 + -[no]cpnum                 (no)
 +           Keep and number checkpoint files
 + -[no]append                (yes)
 +           Append to previous output files when continuing from checkpoint
 +           instead of adding the simulation part number to all file names
 + -nsteps &lt;int&gt;              (-2)
++           Run this number of steps (-1 means infinite, -2 means use mdp
++           option, smaller is invalid)
 + -maxh   &lt;real&gt;             (-1)
 +           Terminate after 0.99 times this time (hours)
 + -replex &lt;int&gt;              (0)
 +           Attempt replica exchange periodically with this period (steps)
 + -nex    &lt;int&gt;              (0)
 +           Number of random exchanges to carry out each exchange interval (N^3
 +           is one suggestion).  -nex zero or not specified gives neighbor
 +           replica exchange.
 + -reseed &lt;int&gt;              (-1)
 +           Seed for replica exchange, -1 is generate a seed
 +</String>
 +</ReferenceData>