Implemented scatter_atoms and scatter_atoms_subset + unit tests + documentation + typos/edits

This commit is contained in:
Karl Hammond
2022-10-02 20:32:42 -05:00
parent c5c21bb36c
commit db9b59c269
6 changed files with 520 additions and 170 deletions

View File

@ -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
<LAMMPS_NS::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 <Library_properties>` 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.
--------

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -15,12 +15,14 @@ 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 {
@ -46,95 +48,146 @@ protected:
}
};
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);
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_mask(1), 3);
EXPECT_EQ(f_lammps_gather_mask(2), 5);
EXPECT_EQ(f_lammps_gather_mask(3), 9);
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_mask(1), 7);
EXPECT_EQ(f_lammps_gather_mask(2), 5);
EXPECT_EQ(f_lammps_gather_mask(3), 9);
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);
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);
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_concat_mask(1), 3);
EXPECT_EQ(f_lammps_gather_concat_mask(2), 5);
EXPECT_EQ(f_lammps_gather_concat_mask(3), 9);
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_concat_mask(1), 7);
EXPECT_EQ(f_lammps_gather_concat_mask(2), 5);
EXPECT_EQ(f_lammps_gather_concat_mask(3), 9);
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);
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);
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_EQ(f_lammps_gather_subset_mask(2), 5);
EXPECT_EQ(f_lammps_gather_subset_mask(3), 9);
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_EQ(f_lammps_gather_subset_mask(2), 5);
EXPECT_EQ(f_lammps_gather_subset_mask(3), 13);
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);
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);
};