From db9b59c269983581f8983e7cc602b887657654c7 Mon Sep 17 00:00:00 2001 From: Karl Hammond Date: Sun, 2 Oct 2022 20:32:42 -0500 Subject: [PATCH] Implemented scatter_atoms and scatter_atoms_subset + unit tests + documentation + typos/edits --- doc/src/Fortran.rst | 141 ++++++++-- fortran/README | 8 +- fortran/lammps.f90 | 161 ++++++++++- src/library.cpp | 16 +- .../fortran/test_fortran_gather_scatter.f90 | 113 ++++++-- unittest/fortran/wrap_gather_scatter.cpp | 251 +++++++++++------- 6 files changed, 520 insertions(+), 170 deletions(-) diff --git a/doc/src/Fortran.rst b/doc/src/Fortran.rst index 12056df995..a62378819f 100644 --- a/doc/src/Fortran.rst +++ b/doc/src/Fortran.rst @@ -1,14 +1,14 @@ The ``LIBLAMMPS`` Fortran Module ******************************** -The ``LIBLAMMPS`` module provides an interface to call LAMMPS from a -Fortran code. It is based on the LAMMPS C-library interface and -requires a Fortran 2003 compatible compiler to be compiled. It is +The ``LIBLAMMPS`` module provides an interface to call LAMMPS from Fortran. +It is based on the LAMMPS C library interface and +requires a Fortran 2003-compatible compiler to be compiled. It is designed to be self-contained and not require any support functions -written in C, C++, or Fortran. +written in C, C++, or Fortran other than those in the C library interface. While C libraries have a defined binary interface (ABI) and can thus be -used from multiple compiler versions from different vendors for as long +used from multiple compiler versions from different vendors as long as they are compatible with the hosting operating system, the same is not true for Fortran programs. Thus, the LAMMPS Fortran module needs to be compiled alongside the code using it from the source code in @@ -49,7 +49,7 @@ folder of the LAMMPS distribution. .. note:: A contributed (and more complete!) Fortran interface that more - closely resembles the C-library interface is available in the + closely resembles the C library interface is available in the ``examples/COUPLE/fortran2`` folder. Please see the ``README`` file in that folder for more information about it and how to contact its author and maintainer. @@ -62,8 +62,8 @@ Creating or deleting a LAMMPS object With the Fortran interface, the creation of a :cpp:class:`LAMMPS ` instance is included in the constructor for creating the :f:func:`lammps` derived type. To import the definition of -that type and its type-bound procedures, you need to add a ``USE -LIBLAMMPS`` statement. Internally, it will call either +that type and its type-bound procedures, you need to add a ``USE LIBLAMMPS`` +statement. Internally, it will call either :cpp:func:`lammps_open_fortran` or :cpp:func:`lammps_open_no_mpi` from the C library API to create the class instance. All arguments are optional and :cpp:func:`lammps_mpi_init` will be called automatically @@ -178,11 +178,13 @@ Accessing system properties The C library interface allows the :doc:`extraction of different kinds of information ` about the active simulation -instance and also---in some cases---to apply modifications to it. In -some cases, the C library interface makes pointers to internal data -structures accessible; when accessing them through the library interfaces, -special care is needed to avoid data corruption and crashes. Please see -the documentation of the individual type-bound procedures for details. +instance and also---in some cases---to apply modifications to it, and the +Fortran interface provides access to the same data using Fortran-style, +C-interoperable data types. In some cases, the Fortran library interface makes +pointers to internal LAMMPS data structures accessible; when accessing them +through the library interfaces, special care is needed to avoid data corruption +and crashes. Please see the documentation of the individual type-bound +procedures for details. Below is an example demonstrating some of the possible uses. @@ -258,6 +260,11 @@ of the contents of the ``LIBLAMMPS`` Fortran interface to LAMMPS. :f function extract_compute: :f:func:`extract_compute` :f function extract_fix: :f:func:`extract_fix` :f function extract_variable: :f:func:`extract_variable` + :f subroutine gather_atoms: :f:func:`gather_atoms` + :f subroutine gather_atoms_concat: :f:func:`gather_atoms_concat` + :f subroutine gather_atoms_subset: :f:func:`gather_atoms_subset` + :f subroutine scatter_atoms: :f:func:`scatter_atoms` + :f subroutine scatter_atoms_subset: :f:func:`scatter_atoms_subset` :f function version: :f:func:`version` :f subroutine flush_buffers: :f:func:`flush_buffers` :f function is_running: :f:func:`is_running` @@ -1041,9 +1048,8 @@ Procedures Bound to the lammps Derived Type This function returns the values of the variables, not pointers to them. Vectors pointing to *atom*-style variables should be of type - ``REAL(c_double)``, be of rank 1 (i.e., ``DIMENSION(:)``), and either have - the ``ALLOCATABLE`` attribute or be long enough to contain the data without - reallocation. + ``REAL(c_double)``, be of rank 1 (i.e., ``DIMENSION(:)``), and have the + ``ALLOCATABLE`` attribute. .. note:: @@ -1088,7 +1094,7 @@ Procedures Bound to the lammps Derived Type -------- -.. f:function:: gather_atoms(name, count, data) +.. f:subroutine:: gather_atoms(name, count, data) This function calls :c:func:`lammps_gather_atoms` to gather the named atom-based entity for all atoms on all processors and return it in the @@ -1135,10 +1141,11 @@ Procedures Bound to the lammps Derived Type x(1:3,1:size(xdata)/3) => xdata You can then access the *y*\ -component of atom 3 with ``x(2,3)``. + See the note about array index order at :f:func:`extract_atom`. -------- -.. f:function:: gather_atoms_concat(name, count, data) +.. f:subroutine:: gather_atoms_concat(name, count, data) This function calls :c:func:`lammps_gather_atoms_concat` to gather the named atom-based entity for all atoms on all processors and return it in the @@ -1166,6 +1173,100 @@ Procedures Bound to the lammps Derived Type -------- +.. f:subroutine:: gather_atoms_subset(name, count, ids, data) + + This function calls :c:func:`lammps_gather_atoms_subset` to gather the named + atom-based entity for the atoms in the array *ids* from all processors and + return it in the vector *data*. + + .. versionadded: TBD + + This subroutine gathers data for the requested atom IDs and stores them in a + one-dimensional array allocated by the user. The data will be ordered by + atom ID, but there is no requirement that the IDs be consecutive. If you + wish to return a similar array for *all* the atoms, use + :f:func:`gather_atoms` or :f:func:`gather_atoms_concat`. + + The *data* array will be in groups of *count* values, sorted by atom ID + in the same order as the array *ids* (e.g., if *name* is *x*, *count* = 3, + and *ids* is [100, 57, 210], then *data* might look like + [x(1,100), x(2,100), x(3,100), x(1,57), x(2,57), x(3,57), x(1,210), + :math:`\dots`]; *ids* must be provided by the user, and *data* must be + of rank 1 (i.e., ``DIMENSION(:)``) and have the ``ALLOCATABLE`` attribute. + + :p character(len=\*) name: desired quantity (e.g., *x* or *mask*) + :p integer(c_int) count: number of per-atom values you expect per atom + (e.g., 1 for *type*, *mask*, or *charge*; 3 for *x*, *v*, or *f*). Use + *count* = 3 with *image* if you want a single image flag unpacked into + *x*/*y*/*z* components. + :p integer(c_int) ids [dimension(:)]: atom IDs corresponding to the atoms + to be gathered + :p real(c_double) data [dimension(:),allocatable]: array into which to store + the data. Array *must* have the ``ALLOCATABLE`` attribute and be of rank 1 + (i.e., ``DIMENSION(:)``). If this array is already allocated, it will be + reallocated to fit the length of the incoming data. + +-------- + +.. f:subroutine:: scatter_atoms(name, data) + + This function calls :c:func:`lammps_scatter_atoms` to scatter the named + atom-based entities in *data* to all processors. + + .. versionadded:: TBD + + This subroutine takes data stored in a one-dimensional array supplied by the + user and scatters them to all atoms on all processors. The data must be + ordered by atom ID, with the requirement that the IDs be consecutive. + Use :f:func:`scatter_atoms_subset` to scatter data for some (or all) + atoms, in any order. + + The *data* array needs to be ordered in groups of *count* values, sorted by + atom ID (e.g., if *name* is *x* and *count* = 3, then + *data* = [x(1,1) x(2,1) x(3,1) x(1,2) x(2,2) x(3,2) x(1,3) :math:`\dots`]; + *data* must be of length (*count* :math:`\times` *natoms*). + + :p character(len=\*) name: quantity to be scattered (e.g., *x* or *charge*) + :p polymorphic data [dimension(:)]: per-atom values packed in a one-dimensional array + containing the data to be scattered. This array must have length *natoms* + (e.g., for *type* or *charge*) or length *natoms*\ :math:`\times 3` + (e.g., for *x* or *f*). The array *data* must be rank 1 (i.e., + ``DIMENSION(:)``) and be of type ``INTEGER(c_int)`` (e.g., for *mask* or + *type*) or of type ``REAL(c_double)`` (e.g., for *x* or *charge* or *f*). + +-------- + +.. f:subroutine:: scatter_atoms_subset(name, ids, data) + + This function calls :c:func:`lammps_scatter_atoms_subset` to scatter the + named atom-based entities in *data* to all processors. + + .. versionadded:: TBD + + This subroutine takes data stored in a one-dimensional array supplied by the + user and scatters them to a subset of atoms on all processors. The array + *data* contains data associated with atom IDs, but there is no requirement + that the IDs be consecutive, as they are provided in a separate array, + *ids*. Use :f:func:`scatter_atoms` to scatter data for all atoms, in order. + + The *data* array needs to be organized in groups of 1 or 3 values, + depending on which quantity is being scattered, with the groups in the same + order as the array *ids*. For example, if you want *data* to be the array + [x(1,1) x(2,1) x(3,1) x(1,100) x(2,100) x(3,100) x(1,57) x(2,57) x(3,57)], + then *ids* would be [1 100 57] and *name* would be *x*. + + :p character(len=\*) name: quantity to be scattered (e.g., *x* or *charge*) + :p integer(c_int) ids [dimension(:)]: atom IDs corresponding to the atoms + being scattered + :p polymorphic data [dimension(:)]: per-atom values packed into a + one-dimensional array containing the data to be scattered. This array must + have either the same length as *ids* (for *mask*, *type*, etc.) or three + times its length (for *x*, *f*, etc.); the array must be rank 1 + and be of type ``INTEGER(c_int)`` (e.g., for *mask* or *type*) or of type + ``REAL(c_double)`` (e.g., *charge*, *x*, or *f*). + +-------- + .. f:function:: version() This method returns the numeric LAMMPS version like @@ -1183,8 +1284,8 @@ Procedures Bound to the lammps Derived Type .. versionadded:: TBD A suitable buffer has to be provided. The assembled text will be truncated - to not overflow this buffer. The string is typically a few hundred bytes - long. + so as not to overflow this buffer. The string is typically a few hundred + bytes long. -------- diff --git a/fortran/README b/fortran/README index 57d163e197..6a19cd7dc2 100644 --- a/fortran/README +++ b/fortran/README @@ -1,9 +1,9 @@ -This directory contains Fortran code which interface LAMMPS as a library -and allows the LAMMPS library interface to be invoked from Fortran codes. -It requires a Fortran compiler that supports the Fortran 2003 standard. +This directory contains Fortran code that acts as an interface to LAMMPS as a +library and allows the LAMMPS library interface to be invoked from Fortran +code. It requires a Fortran compiler that supports the Fortran 2003 standard. This interface is based on and supersedes the previous Fortran interfaces -in the examples/COUPLE/fortran* folders, but is fully supported by the +in the examples/COUPLE/fortran* folders, but it is fully supported by the LAMMPS developers and included in the documentation and unit testing. Details on this Fortran interface and how to build programs using it diff --git a/fortran/lammps.f90 b/fortran/lammps.f90 index ad90d45aa4..8fad0d43e5 100644 --- a/fortran/lammps.f90 +++ b/fortran/lammps.f90 @@ -114,7 +114,14 @@ MODULE LIBLAMMPS PROCEDURE, PRIVATE :: lmp_gather_atoms_subset_double GENERIC :: gather_atoms_subset => lmp_gather_atoms_subset_int, & lmp_gather_atoms_subset_double + PROCEDURE, PRIVATE :: lmp_scatter_atoms_int, lmp_scatter_atoms_double + GENERIC :: scatter_atoms => lmp_scatter_atoms_int, & + lmp_scatter_atoms_double ! + PROCEDURE, PRIVATE :: lmp_scatter_atoms_subset_int + PROCEDURE, PRIVATE :: lmp_scatter_atoms_subset_double + GENERIC :: scatter_atoms_subset => lmp_scatter_atoms_subset_int, & + lmp_scatter_atoms_subset_double PROCEDURE :: version => lmp_version PROCEDURE,NOPASS :: get_os_info => lmp_get_os_info PROCEDURE,NOPASS :: config_has_mpi_support => lmp_config_has_mpi_support @@ -428,9 +435,20 @@ MODULE LIBLAMMPS INTEGER(c_int), VALUE :: type, count, ndata END SUBROUTINE lammps_gather_atoms_subset - !SUBROUTINE lammps_scatter_atoms + SUBROUTINE lammps_scatter_atoms(handle, name, type, count, data) BIND(C) + IMPORT :: c_ptr, c_int + IMPLICIT NONE + TYPE(c_ptr), VALUE :: handle, name, data + INTEGER(c_int), VALUE :: type, count + END SUBROUTINE lammps_scatter_atoms - !SUBROUTINE lammps_scatter_atoms_subset + SUBROUTINE lammps_scatter_atoms_subset(handle, name, type, count, & + ndata, ids, data) BIND(C) + IMPORT :: c_ptr, c_int + IMPLICIT NONE + TYPE(c_ptr), VALUE :: handle, name, ids, data + INTEGER(c_int), VALUE :: count, ndata, type + END SUBROUTINE lammps_scatter_atoms_subset !SUBROUTINE lammps_gather_bonds @@ -1212,8 +1230,8 @@ CONTAINS TYPE(c_ptr) :: Cdata, Cname INTEGER(c_int) :: natoms INTEGER(c_int), PARAMETER :: Ctype = 0_c_int - REAL(C_double) :: dnatoms - CHARACTER(LEN=80) :: error_msg + REAL(c_double) :: dnatoms + CHARACTER(LEN=100) :: error_msg IF ( count /= 1 .AND. count /= 3 ) THEN CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, 'gather_atoms& @@ -1247,7 +1265,7 @@ CONTAINS INTEGER(c_int) :: natoms INTEGER(c_int), PARAMETER :: Ctype = 1_c_int REAL(C_double) :: dnatoms - CHARACTER(LEN=80) :: error_msg + CHARACTER(LEN=100) :: error_msg IF ( count /= 1 .AND. count /= 3 ) THEN CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, 'gather_atoms& @@ -1281,7 +1299,7 @@ CONTAINS INTEGER(c_int) :: natoms INTEGER(c_int), PARAMETER :: Ctype = 0_c_int REAL(C_double) :: dnatoms - CHARACTER(LEN=80) :: error_msg + CHARACTER(LEN=100) :: error_msg IF ( count /= 1 .AND. count /= 3 ) THEN CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, & @@ -1316,7 +1334,7 @@ CONTAINS INTEGER(c_int) :: natoms INTEGER(c_int), PARAMETER :: Ctype = 1_c_int REAL(C_double) :: dnatoms - CHARACTER(LEN=80) :: error_msg + CHARACTER(LEN=100) :: error_msg IF ( count /= 1 .AND. count /= 3 ) THEN CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, & @@ -1351,11 +1369,12 @@ CONTAINS INTEGER(c_int) :: ndata TYPE(c_ptr) :: Cdata, Cname, Cids INTEGER(c_int), PARAMETER :: Ctype = 0_c_int - CHARACTER(LEN=80) :: error_msg + CHARACTER(LEN=100) :: error_msg IF ( count /= 1 .AND. count /= 3 ) THEN - CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, 'gather_atoms& - & requires "count" to be 1 or 3 [Fortran/gather_atoms]') + CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, & + 'gather_atoms_subset requires "count" to be 1 or 3 & + &[Fortran/gather_atoms]') END IF ndata = SIZE(ids, KIND=c_int) @@ -1381,11 +1400,12 @@ CONTAINS INTEGER(c_int) :: ndata TYPE(c_ptr) :: Cdata, Cname, Cids INTEGER(c_int), PARAMETER :: Ctype = 1_c_int - CHARACTER(LEN=80) :: error_msg + CHARACTER(LEN=100) :: error_msg IF ( count /= 1 .AND. count /= 3 ) THEN - CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, 'gather_atoms& - & requires "count" to be 1 or 3 [Fortran/gather_atoms]') + CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, & + 'gather_atoms_subset requires "count" to be 1 or 3 & + &[Fortran/gather_atoms]') END IF ndata = SIZE(ids, KIND=c_int) @@ -1400,6 +1420,121 @@ CONTAINS CALL lammps_free(Cname) END SUBROUTINE lmp_gather_atoms_subset_double + ! equivalent function to lammps_scatter_atoms (for integers) + SUBROUTINE lmp_scatter_atoms_int(self, name, data) + CLASS(lammps), INTENT(IN) :: self + CHARACTER(LEN=*), INTENT(IN) :: name + INTEGER(c_int), DIMENSION(:), TARGET :: data + INTEGER(c_int) :: natoms, Ccount + INTEGER(c_int), PARAMETER :: Ctype = 0_c_int + TYPE(c_ptr) :: Cname, Cdata + REAL(c_double) :: dnatoms + CHARACTER(LEN=100) :: error_msg + + dnatoms = lmp_get_natoms(self) + IF ( dnatoms > HUGE(1_c_int) ) THEN + WRITE(error_msg,'(A,1X,I0,1X,A)') & + 'Cannot use library function scatter_atoms with more than', & + HUGE(0_c_int), 'atoms [Fortran/scatter_atoms]' + CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, error_msg) + END IF + natoms = NINT(dnatoms, c_int) + + Cname = f2c_string(name) + Cdata = C_LOC(data(1)) + Ccount = SIZE(data) / natoms + + IF ( Ccount /= 1 .AND. Ccount /= 3 ) THEN + CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, & + 'lammps_scatter_atoms requires either 1 or 3 data per atom') + END IF + CALL lammps_scatter_atoms(self%handle, Cname, Ctype, Ccount, Cdata) + CALL lammps_free(Cname) + END SUBROUTINE lmp_scatter_atoms_int + + ! equivalent function to lammps_scatter_atoms (for doubles) + SUBROUTINE lmp_scatter_atoms_double(self, name, data) + CLASS(lammps), INTENT(IN) :: self + CHARACTER(LEN=*), INTENT(IN) :: name + REAL(c_double), DIMENSION(:), TARGET :: data + INTEGER(c_int) :: natoms, Ccount + INTEGER(c_int), PARAMETER :: Ctype = 1_c_int + TYPE(c_ptr) :: Cname, Cdata + REAL(c_double) :: dnatoms + CHARACTER(LEN=100) :: error_msg + + dnatoms = lmp_get_natoms(self) + IF ( dnatoms > HUGE(1_c_int) ) THEN + WRITE(error_msg,'(A,1X,I0,1X,A)') & + 'Cannot use library function scatter_atoms with more than', & + HUGE(0_c_int), 'atoms [Fortran/scatter_atoms]' + CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, error_msg) + END IF + natoms = NINT(dnatoms, c_int) + + Cname = f2c_string(name) + Cdata = C_LOC(data(1)) + Ccount = SIZE(data) / natoms + + IF ( Ccount /= 1 .AND. Ccount /= 3 ) THEN + CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, & + 'scatter_atoms requires either 1 or 3 data per atom & + &[Fortran/scatter_atoms]') + END IF + CALL lammps_scatter_atoms(self%handle, Cname, Ctype, Ccount, Cdata) + CALL lammps_free(Cname) + END SUBROUTINE lmp_scatter_atoms_double + + SUBROUTINE lmp_scatter_atoms_subset_int(self, name, ids, data) + CLASS(lammps), INTENT(IN) :: self + CHARACTER(LEN=*), INTENT(IN) :: name + INTEGER(c_int), DIMENSION(:), TARGET :: ids + INTEGER(c_int), DIMENSION(:), TARGET :: data + INTEGER(c_int), PARAMETER :: Ctype = 0_c_int + INTEGER(c_int) :: Cndata, Ccount + TYPE(c_ptr) :: Cdata, Cname, Cids + CHARACTER(LEN=100) :: error_msg + + Cndata = SIZE(ids, KIND=c_int) + Ccount = SIZE(data, KIND=c_int) / Cndata + IF ( Ccount /= 1 .AND. Ccount /= 3 ) THEN + CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, & + 'scatter_atoms_subset requires either 1 or 3 data per atom') + END IF + + Cname = f2c_string(name) + Cdata = C_LOC(data(1)) + Cids = C_LOC(ids) + CALL lammps_scatter_atoms_subset(self%handle, Cname, Ctype, Ccount, & + Cndata, Cids, Cdata) + CALL lammps_free(Cname) + END SUBROUTINE lmp_scatter_atoms_subset_int + + SUBROUTINE lmp_scatter_atoms_subset_double(self, name, ids, data) + CLASS(lammps), INTENT(IN) :: self + CHARACTER(LEN=*), INTENT(IN) :: name + INTEGER(c_int), DIMENSION(:), TARGET :: ids + REAL(c_double), DIMENSION(:), TARGET :: data + INTEGER(c_int), PARAMETER :: Ctype = 1_c_int + INTEGER(c_int) :: Cndata, Ccount + TYPE(c_ptr) :: Cdata, Cname, Cids + CHARACTER(LEN=100) :: error_msg + + Cndata = SIZE(ids, KIND=c_int) + Ccount = SIZE(data, KIND=c_int) / Cndata + IF ( Ccount /= 1 .AND. Ccount /= 3 ) THEN + CALL lmp_error(self, LMP_ERROR_ALL + LMP_ERROR_WORLD, & + 'scatter_atoms_subset requires either 1 or 3 data per atom') + END IF + + Cname = f2c_string(name) + Cdata = C_LOC(data(1)) + Cids = C_LOC(ids) + CALL lammps_scatter_atoms_subset(self%handle, Cname, Ctype, Ccount, & + Cndata, Cids, Cdata) + CALL lammps_free(Cname) + END SUBROUTINE lmp_scatter_atoms_subset_double + ! equivalent function to lammps_version INTEGER FUNCTION lmp_version(self) CLASS(lammps), INTENT(IN) :: self diff --git a/src/library.cpp b/src/library.cpp index 0851980207..fe16d1e518 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -2551,10 +2551,12 @@ return a similar array for *all* the atoms, use :cpp:func:`lammps_gather_atoms` or :cpp:func:`lammps_gather_atoms_concat`. The *data* array will be in groups of *count* values, sorted by atom ID -(e.g., if *name* is *x* and *count* = 3, then *data* might look like -x[100][0], x[100][1], x[100][2], x[101][0], x[101][1], x[101][2], x[102][0], -:math:`\dots`); *data* must be pre-allocated by the caller to length (*count* -:math:`\times` *ndata*). +in the same order as the array *ids* (e.g., if *name* is *x*, *count* = 3, and +*ids* is {100, 57, 210}, then *data* might look like {x[100][0], x[100][1], +x[100][2], x[57][0], x[57][1], x[57][2], x[210][0], :math:`\dots`); +*ids* must be provided by the user with length *ndata*, and +*data* must be pre-allocated by the caller to length +(*count* :math:`\times` *ndata*). \endverbatim * @@ -2601,7 +2603,7 @@ void lammps_gather_atoms_subset(void *handle, char *name, int type, int count, if (lmp->atom->map_style == Atom::MAP_NONE) flag = 1; if (flag) { if (lmp->comm->me == 0) - lmp->error->warning(FLERR,"Library error in lammps_gather_atoms_subset"); + lmp->error->warning(FLERR,"Library error in lammps_gather_atoms_subset: atoms must have mappable ids"); return; } @@ -2757,7 +2759,7 @@ void lammps_scatter_atoms(void *handle, char *name, int type, int count, void *d if (lmp->atom->map_style == Atom::MAP_NONE) flag = 1; if (flag) { if (lmp->comm->me == 0) - lmp->error->warning(FLERR,"Library error in lammps_scatter_atoms"); + lmp->error->warning(FLERR,"Library error in lammps_scatter_atoms: ids must exist, be consecutive, and be mapped"); return; } @@ -2908,7 +2910,7 @@ void lammps_scatter_atoms_subset(void *handle, char *name, int type, int count, if (lmp->atom->map_style == Atom::MAP_NONE) flag = 1; if (flag) { if (lmp->comm->me == 0) - lmp->error->warning(FLERR,"Library error in lammps_scatter_atoms_subset"); + lmp->error->warning(FLERR,"Library error in lammps_scatter_atoms_subset: atoms must have mapped ids"); return; } diff --git a/unittest/fortran/test_fortran_gather_scatter.f90 b/unittest/fortran/test_fortran_gather_scatter.f90 index 86870721ba..dd9182afaa 100644 --- a/unittest/fortran/test_fortran_gather_scatter.f90 +++ b/unittest/fortran/test_fortran_gather_scatter.f90 @@ -33,39 +33,39 @@ SUBROUTINE f_lammps_setup_gather_scatter () BIND(C) CALL lmp%commands_list(more_input) END SUBROUTINE f_lammps_setup_gather_scatter -FUNCTION f_lammps_gather_mask (i) BIND(C) +FUNCTION f_lammps_gather_atoms_mask (i) BIND(C) USE, INTRINSIC :: ISO_C_BINDING, ONLY : c_int USE LIBLAMMPS USE keepstuff, ONLY : lmp IMPLICIT NONE INTEGER(c_int), INTENT(IN), VALUE :: i - INTEGER(c_int) :: f_lammps_gather_mask + INTEGER(c_int) :: f_lammps_gather_atoms_mask INTEGER(c_int), DIMENSION(:), ALLOCATABLE :: mask CALL lmp%gather_atoms('mask', 1_c_int, mask) - f_lammps_gather_mask = mask(i) -END FUNCTION f_lammps_gather_mask + f_lammps_gather_atoms_mask = mask(i) +END FUNCTION f_lammps_gather_atoms_mask -FUNCTION f_lammps_gather_position (i) BIND(C) +FUNCTION f_lammps_gather_atoms_position (i) BIND(C) USE, INTRINSIC :: ISO_C_BINDING, ONLY : c_int, c_double USE LIBLAMMPS USE keepstuff, ONLY : lmp IMPLICIT NONE INTEGER(c_int), INTENT(IN), VALUE :: i - REAL(c_double) :: f_lammps_gather_position + REAL(c_double) :: f_lammps_gather_atoms_position REAL(c_double), DIMENSION(:), ALLOCATABLE :: positions CALL lmp%gather_atoms('x', 3_c_int, positions) - f_lammps_gather_position = positions(i) -END FUNCTION f_lammps_gather_position + f_lammps_gather_atoms_position = positions(i) +END FUNCTION f_lammps_gather_atoms_position -FUNCTION f_lammps_gather_concat_mask (i) BIND(C) +FUNCTION f_lammps_gather_atoms_concat_mask (i) BIND(C) USE, INTRINSIC :: ISO_C_BINDING, ONLY : c_int USE LIBLAMMPS USE keepstuff, ONLY : lmp IMPLICIT NONE INTEGER(c_int), INTENT(IN), VALUE :: i - INTEGER(c_int) :: f_lammps_gather_concat_mask + INTEGER(c_int) :: f_lammps_gather_atoms_concat_mask INTEGER(c_int), DIMENSION(:), ALLOCATABLE :: mask, tag INTEGER :: j @@ -73,20 +73,20 @@ FUNCTION f_lammps_gather_concat_mask (i) BIND(C) CALL lmp%gather_atoms_concat('id', 1_c_int, tag) DO j = 1, SIZE(tag) IF ( tag(j) == i ) THEN - f_lammps_gather_concat_mask = mask(j) + f_lammps_gather_atoms_concat_mask = mask(j) RETURN END IF END DO - f_lammps_gather_concat_mask = -1 -END FUNCTION f_lammps_gather_concat_mask + f_lammps_gather_atoms_concat_mask = -1 +END FUNCTION f_lammps_gather_atoms_concat_mask -FUNCTION f_lammps_gather_concat_position (xyz, id) BIND(C) +FUNCTION f_lammps_gather_atoms_concat_position (xyz, id) BIND(C) USE, INTRINSIC :: ISO_C_BINDING, ONLY : c_int, c_double USE LIBLAMMPS USE keepstuff, ONLY : lmp IMPLICIT NONE INTEGER(c_int), INTENT(IN), VALUE :: id, xyz - REAL(c_double) :: f_lammps_gather_concat_position + REAL(c_double) :: f_lammps_gather_atoms_concat_position REAL(c_double), DIMENSION(:), ALLOCATABLE :: positions INTEGER(c_int), DIMENSION(:), ALLOCATABLE :: tag INTEGER :: j @@ -95,18 +95,18 @@ FUNCTION f_lammps_gather_concat_position (xyz, id) BIND(C) CALL lmp%gather_atoms_concat('id', 1_c_int, tag) DO j = 1, SIZE(tag) IF ( tag(j) == id ) THEN - f_lammps_gather_concat_position = positions((j-1)*3 + xyz) + f_lammps_gather_atoms_concat_position = positions((j-1)*3 + xyz) END IF END DO -END FUNCTION f_lammps_gather_concat_position +END FUNCTION f_lammps_gather_atoms_concat_position -FUNCTION f_lammps_gather_subset_mask (i) BIND(C) +FUNCTION f_lammps_gather_atoms_subset_mask (i) BIND(C) USE, INTRINSIC :: ISO_C_BINDING, ONLY : c_int USE LIBLAMMPS USE keepstuff, ONLY : lmp IMPLICIT NONE INTEGER(c_int), INTENT(IN), VALUE :: i - INTEGER(c_int) :: f_lammps_gather_subset_mask + INTEGER(c_int) :: f_lammps_gather_atoms_subset_mask INTEGER(c_int), DIMENSION(:), ALLOCATABLE :: mask INTEGER :: j INTEGER(c_int), DIMENSION(*), PARAMETER :: tag = [3,2] @@ -114,20 +114,20 @@ FUNCTION f_lammps_gather_subset_mask (i) BIND(C) CALL lmp%gather_atoms_subset('mask', 1_c_int, tag, mask) DO j = 1, SIZE(tag) IF ( tag(j) == i ) THEN - f_lammps_gather_subset_mask = mask(j) + f_lammps_gather_atoms_subset_mask = mask(j) RETURN END IF END DO - f_lammps_gather_subset_mask = -1 -END FUNCTION f_lammps_gather_subset_mask + f_lammps_gather_atoms_subset_mask = -1 +END FUNCTION f_lammps_gather_atoms_subset_mask -FUNCTION f_lammps_gather_subset_position (xyz,id) BIND(C) +FUNCTION f_lammps_gather_atoms_subset_position (xyz,id) BIND(C) USE, INTRINSIC :: ISO_C_BINDING, ONLY : c_int, c_double USE LIBLAMMPS USE keepstuff, ONLY : lmp IMPLICIT NONE INTEGER(c_int), INTENT(IN), VALUE :: id, xyz - REAL(c_double) :: f_lammps_gather_subset_position + REAL(c_double) :: f_lammps_gather_atoms_subset_position REAL(c_double), DIMENSION(:), ALLOCATABLE :: positions INTEGER(c_int), DIMENSION(*), PARAMETER :: tag = [3,2] INTEGER :: j @@ -135,9 +135,68 @@ FUNCTION f_lammps_gather_subset_position (xyz,id) BIND(C) CALL lmp%gather_atoms_subset('x', 3_c_int, tag, positions) DO j = 1, SIZE(tag) IF ( tag(j) == id ) THEN - f_lammps_gather_subset_position = positions((j-1)*3 + xyz) + f_lammps_gather_atoms_subset_position = positions((j-1)*3 + xyz) RETURN END IF END DO - f_lammps_gather_subset_position = -1.0D0 -END FUNCTION f_lammps_gather_subset_position + f_lammps_gather_atoms_subset_position = -1.0D0 +END FUNCTION f_lammps_gather_atoms_subset_position + +SUBROUTINE f_lammps_scatter_atoms_masks() BIND(C) + USE, INTRINSIC :: ISO_C_BINDING, ONLY : c_int, c_double + USE LIBLAMMPS + USE keepstuff, ONLY : lmp + IMPLICIT NONE + INTEGER(c_int), DIMENSION(:), ALLOCATABLE :: masks + INTEGER(c_int) :: swap + + CALL lmp%gather_atoms('mask', 1_c_int, masks) + + ! swap masks of atoms 1 and 3 + swap=masks(1) + masks(1) = masks(3) + masks(3) = swap + + CALL lmp%scatter_atoms('mask', masks) ! push the swap back to LAMMPS +END SUBROUTINE f_lammps_scatter_atoms_masks + +SUBROUTINE f_lammps_scatter_atoms_positions() BIND(C) + USE, INTRINSIC :: ISO_C_BINDING, ONLY : c_int, c_double + USE LIBLAMMPS + USE keepstuff, ONLY : lmp + IMPLICIT NONE + INTEGER(c_int), DIMENSION(:), ALLOCATABLE :: tags + REAL(c_double), DIMENSION(:), ALLOCATABLE, TARGET :: xvec + REAL(c_double), DIMENSION(:,:), POINTER :: x + REAL(c_double) :: swap(3) + + CALL lmp%gather_atoms('id',1_c_int,tags) + CALL lmp%gather_atoms('x',3_c_int,xvec) + x(1:3,1:SIZE(xvec)/3) => xvec + + ! swap positions of atoms 1 and 3 + swap=x(:,1) + x(:,1) = x(:,3) + x(:,3) = swap + + CALL lmp%scatter_atoms('x', xvec) ! push the swap back to LAMMPS +END SUBROUTINE f_lammps_scatter_atoms_positions + +SUBROUTINE f_lammps_scatter_atoms_subset_mask() BIND(C) + USE, INTRINSIC :: ISO_C_BINDING, ONLY : c_int, c_double + USE LIBLAMMPS + USE keepstuff, ONLY : lmp + IMPLICIT NONE + INTEGER(c_int), DIMENSION(:), ALLOCATABLE :: all_masks + INTEGER(c_int), DIMENSION(*), PARAMETER :: tags = [3,1] + INTEGER(c_int), DIMENSION(2) :: masks + INTEGER(c_int) :: swap + + CALL lmp%gather_atoms('mask', 1_c_int, all_masks) + + ! swap masks of atoms 1 and 3 in the new array (because 'tags' is reversed) + masks(1) = all_masks(1) + masks(2) = all_masks(3) + + CALL lmp%scatter_atoms_subset('mask', tags, masks) ! push the swap to LAMMPS +END SUBROUTINE f_lammps_scatter_atoms_subset_mask diff --git a/unittest/fortran/wrap_gather_scatter.cpp b/unittest/fortran/wrap_gather_scatter.cpp index 4fd733a167..96397b7681 100644 --- a/unittest/fortran/wrap_gather_scatter.cpp +++ b/unittest/fortran/wrap_gather_scatter.cpp @@ -15,126 +15,179 @@ extern "C" { void *f_lammps_with_args(); void f_lammps_close(); void f_lammps_setup_gather_scatter(); -int f_lammps_gather_mask(int); -double f_lammps_gather_position(int); -int f_lammps_gather_concat_mask(int); -double f_lammps_gather_concat_position(int,int); -int f_lammps_gather_subset_mask(int); -double f_lammps_gather_subset_position(int,int); +int f_lammps_gather_atoms_mask(int); +double f_lammps_gather_atoms_position(int); +int f_lammps_gather_atoms_concat_mask(int); +double f_lammps_gather_atoms_concat_position(int,int); +int f_lammps_gather_atoms_subset_mask(int); +double f_lammps_gather_atoms_subset_position(int,int); +void f_lammps_scatter_atoms_masks(); +void f_lammps_scatter_atoms_positions(); } class LAMMPS_gather_scatter : public ::testing::Test { protected: - LAMMPS_NS::LAMMPS *lmp; - LAMMPS_gather_scatter() = default; - ~LAMMPS_gather_scatter() override = default; + LAMMPS_NS::LAMMPS *lmp; + LAMMPS_gather_scatter() = default; + ~LAMMPS_gather_scatter() override = default; - void SetUp() override - { - ::testing::internal::CaptureStdout(); - lmp = (LAMMPS_NS::LAMMPS *)f_lammps_with_args(); - std::string output = ::testing::internal::GetCapturedStdout(); - EXPECT_STREQ(output.substr(0, 8).c_str(), "LAMMPS ("); - } - void TearDown() override - { - ::testing::internal::CaptureStdout(); - f_lammps_close(); - std::string output = ::testing::internal::GetCapturedStdout(); - EXPECT_STREQ(output.substr(0, 16).c_str(), "Total wall time:"); - lmp = nullptr; - } + void SetUp() override + { + ::testing::internal::CaptureStdout(); + lmp = (LAMMPS_NS::LAMMPS *)f_lammps_with_args(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0, 8).c_str(), "LAMMPS ("); + } + void TearDown() override + { + ::testing::internal::CaptureStdout(); + f_lammps_close(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0, 16).c_str(), "Total wall time:"); + lmp = nullptr; + } }; -TEST_F(LAMMPS_gather_scatter, gather_masks) +TEST_F(LAMMPS_gather_scatter, gather_atoms_masks) { - f_lammps_setup_gather_scatter(); - EXPECT_EQ(f_lammps_gather_mask(1), 1); - EXPECT_EQ(f_lammps_gather_mask(2), 1); - EXPECT_EQ(f_lammps_gather_mask(3), 1); - lammps_command(lmp, "group special id 1"); - lammps_command(lmp, "group other id 2"); - lammps_command(lmp, "group spiffy id 3"); - EXPECT_EQ(f_lammps_gather_mask(1), 3); - EXPECT_EQ(f_lammps_gather_mask(2), 5); - EXPECT_EQ(f_lammps_gather_mask(3), 9); - lammps_command(lmp, "group other id 1"); - EXPECT_EQ(f_lammps_gather_mask(1), 7); - EXPECT_EQ(f_lammps_gather_mask(2), 5); - EXPECT_EQ(f_lammps_gather_mask(3), 9); + f_lammps_setup_gather_scatter(); + EXPECT_EQ(f_lammps_gather_atoms_mask(1), 1); + EXPECT_EQ(f_lammps_gather_atoms_mask(2), 1); + EXPECT_EQ(f_lammps_gather_atoms_mask(3), 1); + lammps_command(lmp, "group special id 1"); + lammps_command(lmp, "group other id 2"); + lammps_command(lmp, "group spiffy id 3"); + EXPECT_EQ(f_lammps_gather_atoms_mask(1), 3); + EXPECT_EQ(f_lammps_gather_atoms_mask(2), 5); + EXPECT_EQ(f_lammps_gather_atoms_mask(3), 9); + lammps_command(lmp, "group other id 1"); + EXPECT_EQ(f_lammps_gather_atoms_mask(1), 7); + EXPECT_EQ(f_lammps_gather_atoms_mask(2), 5); + EXPECT_EQ(f_lammps_gather_atoms_mask(3), 9); }; -TEST_F(LAMMPS_gather_scatter, gather_positions) +TEST_F(LAMMPS_gather_scatter, gather_atoms_positions) { - f_lammps_setup_gather_scatter(); - EXPECT_EQ(f_lammps_gather_position(1), 1.0); - EXPECT_EQ(f_lammps_gather_position(2), 1.0); - EXPECT_EQ(f_lammps_gather_position(3), 1.5); - EXPECT_EQ(f_lammps_gather_position(4), 0.2); - EXPECT_EQ(f_lammps_gather_position(5), 0.1); - EXPECT_EQ(f_lammps_gather_position(6), 0.1); - EXPECT_EQ(f_lammps_gather_position(7), 0.5); - EXPECT_EQ(f_lammps_gather_position(8), 0.5); - EXPECT_EQ(f_lammps_gather_position(9), 0.5); + f_lammps_setup_gather_scatter(); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_position(1), 1.0); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_position(2), 1.0); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_position(3), 1.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_position(4), 0.2); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_position(5), 0.1); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_position(6), 0.1); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_position(7), 0.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_position(8), 0.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_position(9), 0.5); }; -TEST_F(LAMMPS_gather_scatter, gather_masks_concat) +TEST_F(LAMMPS_gather_scatter, gather_atoms_concat_masks) { - f_lammps_setup_gather_scatter(); - EXPECT_EQ(f_lammps_gather_concat_mask(1), 1); - EXPECT_EQ(f_lammps_gather_concat_mask(2), 1); - EXPECT_EQ(f_lammps_gather_concat_mask(3), 1); - lammps_command(lmp, "group special id 1"); - lammps_command(lmp, "group other id 2"); - lammps_command(lmp, "group spiffy id 3"); - EXPECT_EQ(f_lammps_gather_concat_mask(1), 3); - EXPECT_EQ(f_lammps_gather_concat_mask(2), 5); - EXPECT_EQ(f_lammps_gather_concat_mask(3), 9); - lammps_command(lmp, "group other id 1"); - EXPECT_EQ(f_lammps_gather_concat_mask(1), 7); - EXPECT_EQ(f_lammps_gather_concat_mask(2), 5); - EXPECT_EQ(f_lammps_gather_concat_mask(3), 9); + f_lammps_setup_gather_scatter(); + EXPECT_EQ(f_lammps_gather_atoms_concat_mask(1), 1); + EXPECT_EQ(f_lammps_gather_atoms_concat_mask(2), 1); + EXPECT_EQ(f_lammps_gather_atoms_concat_mask(3), 1); + lammps_command(lmp, "group special id 1"); + lammps_command(lmp, "group other id 2"); + lammps_command(lmp, "group spiffy id 3"); + EXPECT_EQ(f_lammps_gather_atoms_concat_mask(1), 3); + EXPECT_EQ(f_lammps_gather_atoms_concat_mask(2), 5); + EXPECT_EQ(f_lammps_gather_atoms_concat_mask(3), 9); + lammps_command(lmp, "group other id 1"); + EXPECT_EQ(f_lammps_gather_atoms_concat_mask(1), 7); + EXPECT_EQ(f_lammps_gather_atoms_concat_mask(2), 5); + EXPECT_EQ(f_lammps_gather_atoms_concat_mask(3), 9); }; -TEST_F(LAMMPS_gather_scatter, gather_positions_concat) +TEST_F(LAMMPS_gather_scatter, gather_atoms_concat_positions) { - f_lammps_setup_gather_scatter(); - EXPECT_EQ(f_lammps_gather_concat_position(1,1), 1.0); - EXPECT_EQ(f_lammps_gather_concat_position(2,1), 1.0); - EXPECT_EQ(f_lammps_gather_concat_position(3,1), 1.5); - EXPECT_EQ(f_lammps_gather_concat_position(1,2), 0.2); - EXPECT_EQ(f_lammps_gather_concat_position(2,2), 0.1); - EXPECT_EQ(f_lammps_gather_concat_position(3,2), 0.1); - EXPECT_EQ(f_lammps_gather_concat_position(1,3), 0.5); - EXPECT_EQ(f_lammps_gather_concat_position(2,3), 0.5); - EXPECT_EQ(f_lammps_gather_concat_position(3,3), 0.5); + f_lammps_setup_gather_scatter(); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(1,1), 1.0); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(2,1), 1.0); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(3,1), 1.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(1,2), 0.2); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(2,2), 0.1); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(3,2), 0.1); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(1,3), 0.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(2,3), 0.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(3,3), 0.5); }; -TEST_F(LAMMPS_gather_scatter, gather_masks_subset) +TEST_F(LAMMPS_gather_scatter, gather_atoms_subset_masks) { - f_lammps_setup_gather_scatter(); - EXPECT_EQ(f_lammps_gather_subset_mask(2), 1); - EXPECT_EQ(f_lammps_gather_subset_mask(3), 1); - lammps_command(lmp, "group special id 1"); - lammps_command(lmp, "group other id 2"); - lammps_command(lmp, "group spiffy id 3"); - EXPECT_EQ(f_lammps_gather_subset_mask(2), 5); - EXPECT_EQ(f_lammps_gather_subset_mask(3), 9); - lammps_command(lmp, "group other id 3"); - EXPECT_EQ(f_lammps_gather_subset_mask(2), 5); - EXPECT_EQ(f_lammps_gather_subset_mask(3), 13); + f_lammps_setup_gather_scatter(); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_subset_mask(2), 1); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_subset_mask(3), 1); + lammps_command(lmp, "group special id 1"); + lammps_command(lmp, "group other id 2"); + lammps_command(lmp, "group spiffy id 3"); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_subset_mask(2), 5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_subset_mask(3), 9); + lammps_command(lmp, "group other id 3"); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_subset_mask(2), 5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_subset_mask(3), 13); }; -TEST_F(LAMMPS_gather_scatter, gather_positions_subset) +TEST_F(LAMMPS_gather_scatter, gather_atoms_subset_positions) { - f_lammps_setup_gather_scatter(); -// EXPECT_EQ(f_lammps_gather_subset_position(1,1), 1.0); -// EXPECT_EQ(f_lammps_gather_subset_position(2,1), 1.0); -// EXPECT_EQ(f_lammps_gather_subset_position(3,1), 1.5); - EXPECT_EQ(f_lammps_gather_subset_position(1,2), 0.2); - EXPECT_EQ(f_lammps_gather_subset_position(2,2), 0.1); - EXPECT_EQ(f_lammps_gather_subset_position(3,2), 0.1); - EXPECT_EQ(f_lammps_gather_subset_position(1,3), 0.5); - EXPECT_EQ(f_lammps_gather_subset_position(2,3), 0.5); - EXPECT_EQ(f_lammps_gather_subset_position(3,3), 0.5); + f_lammps_setup_gather_scatter(); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_subset_position(1,2), 0.2); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_subset_position(2,2), 0.1); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_subset_position(3,2), 0.1); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_subset_position(1,3), 0.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_subset_position(2,3), 0.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_subset_position(3,3), 0.5); +}; + +TEST_F(LAMMPS_gather_scatter, scatter_atoms_masks) +{ + f_lammps_setup_gather_scatter(); + lammps_command(lmp, "group special id 1"); + lammps_command(lmp, "group other id 2"); + lammps_command(lmp, "group spiffy id 3"); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_mask(1), 3); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_mask(2), 5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_mask(3), 9); + f_lammps_scatter_atoms_masks(); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_mask(1), 9); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_mask(2), 5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_mask(3), 3); +}; + +TEST_F(LAMMPS_gather_scatter, scatter_atoms_positions) +{ + f_lammps_setup_gather_scatter(); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(1,1), 1.0); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(2,1), 1.0); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(3,1), 1.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(1,2), 0.2); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(2,2), 0.1); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(3,2), 0.1); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(1,3), 0.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(2,3), 0.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(3,3), 0.5); + f_lammps_scatter_atoms_positions(); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(1,3), 1.0); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(2,3), 1.0); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(3,3), 1.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(1,2), 0.2); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(2,2), 0.1); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(3,2), 0.1); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(1,1), 0.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(2,1), 0.5); + EXPECT_DOUBLE_EQ(f_lammps_gather_atoms_concat_position(3,1), 0.5); +}; + +TEST_F(LAMMPS_gather_scatter, scatter_atoms_subset_mask) +{ + f_lammps_setup_gather_scatter(); + EXPECT_EQ(f_lammps_gather_atoms_mask(1), 1); + EXPECT_EQ(f_lammps_gather_atoms_mask(3), 1); + lammps_command(lmp, "group special id 1"); + lammps_command(lmp, "group other id 2"); + lammps_command(lmp, "group spiffy id 3"); + EXPECT_EQ(f_lammps_gather_atoms_mask(1), 3); + EXPECT_EQ(f_lammps_gather_atoms_mask(3), 9); + f_lammps_scatter_atoms_masks(); + EXPECT_EQ(f_lammps_gather_atoms_mask(1), 9); + EXPECT_EQ(f_lammps_gather_atoms_mask(3), 3); };