Files
lammps/doc/src/Developer_grid.rst
Jacob Gissinger 9f6dbf6609 doc pages 949-1220
2024-08-17 00:28:57 -04:00

846 lines
39 KiB
ReStructuredText

Use of distributed grids within style classes
---------------------------------------------
.. versionadded:: 22Dec2022
The LAMMPS source code includes two classes which facilitate the
creation and use of distributed grids. These are the Grid2d and
Grid3d classes in the src/grid2d.cpp.h and src/grid3d.cpp.h files
respectively. As the names imply, they are used for 2d or 3d
simulations, as defined by the :doc:`dimension <dimension>` command.
The :doc:`Howto_grid <Howto_grid>` page gives an overview of how
distributed grids are defined from a user perspective, lists LAMMPS
commands which use them, and explains how grid cell data is referenced
from an input script. Please read that page first as it motivates the
coding details discussed here.
This doc page is for users who wish to write new styles (input script
commands) which use distributed grids. There are a variety of
material models and analysis methods which use atoms (or
coarse-grained particles) and grids in tandem.
A *distributed* grid means each processor owns a subset of the grid
cells. In LAMMPS, the subset for each processor will be a sub-block
of grid cells with low and high index bounds in each dimension of the
grid. The union of the sub-blocks across all processors is the global
grid.
More specifically, a grid point is defined for each cell (by default
the center point), and a processor owns a grid cell if its point is
within the processor's spatial subdomain. The union of processor
subdomains is the global simulation box. If a grid point is on the
boundary of two subdomains, the lower processor owns the grid cell. A
processor may also store copies of ghost cells which surround its
owned cells.
----------
Style commands
^^^^^^^^^^^^^^
Style commands which can define and use distributed grids include the
:doc:`compute <compute>`, :doc:`fix <fix>`, :doc:`pair <pair_style>`,
and :doc:`kspace <kspace_style>` styles. If you wish grid cell data
to persist across timesteps, then use a fix. If you wish grid cell
data to be accessible by other commands, then use a fix or compute.
Currently in LAMMPS, the :doc:`pair_style amoeba <pair_amoeba>`,
:doc:`kspace_style pppm <kspace_style>`, and :doc:`kspace_style msm
<kspace_style>` commands use distributed grids but do not require
either of these capabilities; they thus create and use distributed
grids internally. Note that a pair style which needs grid cell data
to persist could be coded to work in tandem with a fix style which
provides that capability.
The *size* of a grid is specified by the number of grid cells in each
dimension of the simulation domain. In any dimension the size can be
any value >= 1. Thus a 10x10x1 grid for a 3d simulation is
effectively a 2d grid, where each grid cell spans the entire
z-dimension. A 1x100x1 grid for a 3d simulation is effectively a 1d
grid, where grid cells are a series of thin xz slabs in the
y-dimension. It is even possible to define a 1x1x1 3d grid, though it
may be inefficient to use it in a computational sense.
Note that the choice of grid size is independent of the number of
processors or their layout in a grid of processor subdomains which
overlays the simulations domain. Depending on the distributed grid
size, a single processor may own many 1000s or no grid cells.
A command can define multiple grids, each of a different size. Each
grid is an instantiation of the Grid2d or Grid3d class.
The command also defines what data it will store for each grid it
creates and it allocates the multidimensional array(s) needed to
store the data. No grid cell data is stored within the Grid2d or
Grid3d classes.
If a single value per grid cell is needed, the data array will have
the same dimension as the grid, i.e. a 2d array for a 2d grid,
likewise for 3d. If multiple values per grid cell are needed, the
data array will have one more dimension than the grid, i.e. a 3d array
for a 2d grid, or 4d array for a 3d grid. A command can choose to
define multiple data arrays for each grid it defines.
----------
Grid data allocation and access
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The simplest way for a command to allocate and access grid cell data
is to use the *create_offset()* methods provided by the Memory class.
Arguments for these methods can be values returned by the
*setup_grid()* method (described below), which define the extent of
the grid cells (owned+ghost) the processor owns. These 4 methods
allocate memory for 2d (first two) and 3d (second two) grid data. The
two methods that end in "_offset" allocate an array which stores a single
value per grid cell. The two that end in "_last" allocate an array
which stores *Nvalues* per grid cell.
.. code-block:: c++
// single value per cell for a 2d grid = 2d array
memory->create2d_offset(data2d_one, nylo_out, nyhi_out,
nxlo_out, nxhi_out, "data2d_one");
// nvalues per cell for a 2d grid = 3d array
memory->create3d_offset_last(data2d_multi, nylo_out, nyhi_out,
nxlo_out, nxhi_out, nvalues, "data2d_multi");
// single value per cell for a 3d grid = 3d array
memory->create3d_offset(data3d_one, nzlo_out, nzhi_out, nylo_out,
nyhi_out, nxlo_out, nxhi_out, "data3d_one");
// nvalues per cell for a 3d grid = 4d array
memory->create4d_offset_last(data3d_multi, nzlo_out, nzhi_out, nylo_out,
nyhi_out, nxlo_out, nxhi_out, nvalues,
"data3d_multi");
Note that these multidimensional arrays are allocated as contiguous
chunks of memory where the x-index of the grid varies fastest, then y,
and the z-index slowest. For multiple values per grid cell, the
Nvalues are contiguous, so their index varies even faster than the
x-index.
The key point is that the "offset" methods create arrays which are
indexed by the range of indices which are the bounds of the sub-block
of the global grid owned by this processor. This means loops like
these can be written in the caller code to loop over owned grid cells,
where the "i" loop bounds are the range of owned grid cells for the
processor. These are the bounds returned by the *setup_grid()*
method:
.. code-block:: c++
for (int iy = iylo; iy <= iyhi; iy++)
for (int ix = ixlo; ix <= ixhi; ix++)
data2d_one[iy][ix] = 0.0;
for (int iy = iylo; iy <= iyhi; iy++)
for (int ix = ixlo; ix <= ixhi; ix++)
for (int m = 0; m < nvalues; m++)
data2d_multi[iy][ix][m] = 0.0;
for (int iz = izlo; iz <= izhi; iz++)
for (int iy = iylo; iy <= iyhi; iy++)
for (int ix = ixlo; ix <= ixhi; ix++)
data3d_one[iz][iy][ix] = 0.0;
for (int iz = izlo; iz <= izhi; iz++)
for (int iy = iylo; iy <= iyhi; iy++)
for (int ix = ixlo; ix <= ixhi; ix++)
for (int m = 0; m < nvalues; m++)
data3d_multi[iz][iy][ix][m] = 0.0;
Simply replacing the "i" bounds with "o" bounds, also returned by the
*setup_grid()* method, would alter this code to loop over owned+ghost
cells (the entire allocated grid).
----------
Grid class constructors
^^^^^^^^^^^^^^^^^^^^^^^
The following subsections describe the public methods of the Grid3d
class which a style command can invoke. The Grid2d methods are
similar; simply remove arguments which refer to the z-dimension.
There are 2 constructors which can be used. They differ in the extra
i/o xyz lo/hi arguments:
.. code-block:: c++
Grid3d(class LAMMPS *lmp, MPI_Comm gcomm, int gnx, int gny, int gnz)
Grid3d(class LAMMPS *lmp, MPI_Comm gcomm, int gnx, int gny, int gnz,
int ixlo, int ixhi, int iylo, int iyhi, int izlo, int izhi,
int oxlo, int oxhi, int oylo, int oyhi, int ozlo, int ozhi)
Both constructors take the LAMMPS instance pointer and a communicator
over which the grid will be distributed. Typically this is the
*world* communicator the LAMMPS instance is using. The
:doc:`kspace_style msm <kspace_style>` command creates a series of
grids, each of different size, which are partitioned across different
sub-communicators of processors. Both constructors are also passed
the global grid size: *gnx* by *gny* by *gnz*.
The first constructor is used when the caller wants the Grid class to
partition the global grid across processors; the Grid class defines
which grid cells each processor owns and also which it stores as ghost
cells. A subsequent call to *setup_grid()*, discussed below, returns
this info to the caller.
The second constructor allows the caller to define the extent of owned
and ghost cells, and pass them to the Grid class. The 6 arguments
which start with "i" are the inclusive lower and upper index bounds of
the owned (inner) grid cells this processor owns in each of the 3
dimensions within the global grid. Owned grid cells are indexed from
0 to N-1 in each dimension.
The 6 arguments which start with "o" are the inclusive bounds of the
owned+ghost (outer) grid cells it stores. If the ghost cells are on
the other side of a periodic boundary, then these indices may be < 0
or >= N in any dimension, so that oxlo <= ixlo and ixhi >= ixhi is
always the case.
For example, if Nx = 100, then a processor might pass ixlo=50,
ixhi=60, oxlo=48, oxhi=62 to the Grid class. Or ixlo=0, ixhi=10,
oxlo=-2, oxhi=13. If a processor owns no grid cells in a dimension,
then the ihi value should be specified as one less than the ilo value.
Note that the only reason to use the second constructor is if the
logic for assigning ghost cells is too complex for the Grid class to
compute, using the various set() methods described next. Currently
only the kspace_style pppm/electrode and kspace_style msm commands use
the second constructor.
----------
Grid class set methods
^^^^^^^^^^^^^^^^^^^^^^
The following methods affect how the Grid class computes which owned
and ghost cells are assigned to each processor. *Set_shift_grid()* is
the only method which influences owned cell assignment; all the rest
influence ghost cell assignment. These methods are only used with the
first constructor; they are ignored if the second constructor is used.
These methods must be called before the *setup_grid()* method is
invoked, because they influence its operation.
.. code-block:: c++
void set_shift_grid(double shift);
void set_distance(double distance);
void set_stencil_atom(int lo, int hi);
void set_shift_atom(double shift_lo, double shift_hi);
void set_stencil_grid(int lo, int hi);
void set_zfactor(double factor);
Processors own a grid cell if a point within the grid cell is inside
the processor's subdomain. By default this is the center point of the
grid cell. The *set_shift_grid()* method can change this. The *shift*
argument is a value from 0.0 to 1.0 (inclusive) which is the offset of
the point within the grid cell in each dimension. The default is 0.5
for the center of the cell. A value of 0.0 is the lower left corner
point; a value of 1.0 is the upper right corner point. There is
typically no need to change the default as it is optimal for
minimizing the number of ghost cells needed.
If a processor maps its particles to grid cells, it needs to allow for
its particles being outside its subdomain between reneighboring. The
*distance* argument of the *set_distance()* method sets the furthest
distance outside a processor's subdomain which a particle can move.
Typically this is half the neighbor skin distance, assuming
reneighboring is done appropriately. This distance is used in
determining how many ghost cells a processor needs to store to enable
its particles to be mapped to grid cells. The default value is 0.0.
Some commands, like the :doc:`kspace_style pppm <kspace_style>`
command, map values (charge in the case of PPPM) to a stencil of grid
cells beyond the grid cell the particle is in. The stencil extent may
be different in the low and high directions. The *set_stencil_atom()*
method defines the maximum values of those 2 extents, assumed to be
the same in each of the 3 dimensions. Both the lo and hi values are
specified as positive integers. The default values are both 0.
Some commands, like the :doc:`kspace_style pppm <kspace_style>`
command, shift the position of an atom when mapping it to a grid cell,
based on the size of the stencil used to map values to the grid
(charge in the case of PPPM). The lo and hi arguments of the
*set_shift_atom()* method are the minimum shift in the low direction
and the maximum shift in the high direction, assumed to be the same in
each of the 3 dimensions. The shifts should be fractions of a grid
cell size with values between 0.0 and 1.0 inclusive. The default
values are both 0.0. See the src/pppm.cpp file for examples of these
lo/hi values for regular and staggered grids.
Some methods like the :doc:`fix ttm/grid <fix_ttm>` command, perform
finite difference kinds of operations on the grid, to diffuse electron
heat in the case of the two-temperature model (TTM). This operation
uses ghost grid values beyond the owned grid values the processor
updates. The *set_stencil_grid()* method defines the extent of this
stencil in both directions, assumed to be the same in each of the 3
dimensions. Both the lo and hi values are specified as positive
integers. The default values are both 0.
The kspace_style pppm commands allow a grid to be defined which
overlays a volume which extends beyond the simulation box in the z
dimension. This is for the purpose of modeling a 2d-periodic slab
(non-periodic in z) as if it were a larger 3d periodic system,
extended (with empty space) in the z dimension. The
:doc:`kspace_modify slab <kspace_modify>` command is used to specify
the ratio of the larger volume to the simulation volume; a volume
ratio of ~3 is typical. For this kind of model, the PPPM caller sets
the global grid size *gnz* ~3x larger than it would be otherwise.
This same ratio is passed by the PPPM caller as the *factor* argument
to the Grid class via the *set_zfactor()* method (*set_yfactor()* for
2d grids). The Grid class will then assign ownership of the 1/3 of
grid cells that overlay the simulation box to the processors which
also overlay the simulation box. The remaining 2/3 of the grid cells
are assigned to processors whose subdomains are adjacent to the upper
z boundary of the simulation box.
----------
Grid class setup_grid method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The *setup_grid()* method is called after the first constructor
(above) to partition the grid across processors, which determines
which grid cells each processor owns. It also calculates how many
ghost grid cells in each dimension and each direction each processor
needs to store.
Note that this method is NOT called if the second constructor above is
used. In that case, the caller assigns owned and ghost cells to each
processor.
Also note that this method must be invoked after any *set_*()* methods have
been used, since they can influence the assignment of owned and ghost
cells.
.. code-block:: c++
void setup_grid(int &ixlo, int &ixhi, int &iylo, int &iyhi, int &izlo, int &izhi,
int &oxlo, int &oxhi, int &oylo, int &oyhi, int &ozlo, int &ozhi)
The 6 return arguments which start with "i" are the inclusive lower
and upper index bounds of the owned (inner) grid cells this processor
owns in each of the 3 dimensions within the global grid. Owned grid
cells are indexed from 0 to N-1 in each dimension.
The 6 return arguments which start with "o" are the inclusive bounds of
the owned+ghost cells it owns. If the ghost cells are on the other
side of a periodic boundary, then these indices may be < 0 or >= N in
any dimension, so that oxlo <= ixlo and ixhi >= ixhi is always the
case.
----------
More grid class set methods
^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following 2 methods can be used to override settings made by the
constructors above. If used, they must be called called before the
*setup_comm()* method is invoked, since it uses the settings that
these methods override. In LAMMPS these methods are called by by the
:doc:`kspace_style msm <kspace_style>` command for the grids it
instantiates using the 2nd constructor above.
.. code-block:: c++
void set_proc_neighs(int pxlo, int pxhi, int pylo, int pyhi, int pzlo, int pzhi)
void set_caller_grid(int fxlo, int fxhi, int fylo, int fyhi, int fzlo, int fzhi)
The *set_proc_neighs()* method sets the processor IDs of the 6
neighboring processors for each processor. Normally these would match
the processor grid neighbors which LAMMPS creates to overlay the
simulation box (the default). However, MSM excludes non-participating
processors from coarse grid communication when less processors are
used. This method allows MSM to override the default values.
The *set_caller_grid()* method species the size of the data arrays the
caller allocates. Normally these would match the extent of the ghost
grid cells (the default). However the MSM caller allocates a larger
data array (more ghost cells) for its finest-level grid, for use in
other operations besides owned/ghost cell communication. This method
allows MSM to override the default values.
----------
Grid class get methods
^^^^^^^^^^^^^^^^^^^^^^
The following methods allow the caller to query the settings for a
specific grid, whether it created the grid or another command created
it.
.. code-block:: c++
void get_size(int &nxgrid, int &nygrid, int &nzgrid);
void get_bounds_owned(int &xlo, int &xhi, int &ylo, int &yhi, int &zlo, int &zhi)
void get_bounds_ghost(int &xlo, int &xhi, int &ylo, int &yhi, int &zlo, int &zhi)
The *get_size()* method returns the size of the global grid in each dimension.
The *get_bounds_owned()* method return the inclusive index bounds of
the grid cells this processor owns. The values range from 0 to N-1 in
each dimension. These values are the same as the "i" values returned
by *setup_grid()*.
The *get_bounds_ghost()* method return the inclusive index bounds of
the owned+ghost grid cells this processor stores. The owned cell
indices range from 0 to N-1, so these indices may be less than 0 or
greater than or equal to N in each dimension. These values are the
same as the "o" values returned by *setup_grid()*.
----------
Grid class owned/ghost communication
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If needed by the command, the following methods setup and perform
communication of grid data to/from neighboring processors. The
*forward_comm()* method sends owned grid cell data to the
corresponding ghost grid cells on other processors. The
*reverse_comm()* method sends ghost grid cell data to the
corresponding owned grid cells on another processor. The caller can
choose to sum ghost grid cell data to the owned grid cell or simply
copy it.
.. code-block:: c++
void setup_comm(int &nbuf1, int &nbuf2)
void forward_comm(int caller, void *ptr, int which, int nper, int nbyte,
void *buf1, void *buf2, MPI_Datatype datatype);
void reverse_comm(int caller, void *ptr, int which, int nper, int nbyte,
void *buf1, void *buf2, MPI_Datatype datatype)
int ghost_adjacent();
The *setup_comm()* method must be called one time before performing
*forward* or *reverse* communication (multiple times if needed). It
returns two integers, which should be used to allocate two buffers.
The *nbuf1* and *nbuf2* values are the number of grid cells whose data
will be stored in two buffers by the Grid class when *forward* or
*reverse* communication is performed. The caller should thus allocate
them to a size large enough to hold all the data used in any single
forward or reverse communication operation it performs. Note that the
caller may allocate and communicate multiple data arrays for a grid it
instantiates. This size includes the bytes needed for the data type
of the grid data it stores, e.g. double precision values.
The *forward_comm()* and *reverse_comm()* methods send grid cell data
from owned to ghost cells, or ghost to owned cells, respectively, as
described above. The *caller* argument should be one of these values
-- Grid3d::COMPUTE, Grid3d::FIX, Grid3d::KSPACE, Grid3d::PAIR --
depending on the style of the caller class. The *ptr* argument is the
"this" pointer to the caller class. These two arguments are used to
call back to pack()/unpack() functions in the caller class, as
explained below.
The *which* argument is a flag the caller can set which is passed to
the caller's pack()/unpack() methods. This allows a single callback
method to pack/unpack data for several different flavors of
forward/reverse communication, e.g. operating on different grids or
grid data.
The *nper* argument is the number of values per grid cell to be
communicated. The *nbyte* argument is the number of bytes per value,
e.g. 8 for double-precision values. The *buf1* and *buf2* arguments
are the two allocated buffers described above. So long as they are
allocated for the maximum size communication, they can be re-used for
any *forward_comm()/reverse_comm()* call. The *datatype* argument is
the MPI_Datatype setting, which should match the buffer allocation and
the *nbyte* argument. E.g. MPI_DOUBLE for buffers storing double
precision values.
To use the *forward_grid()* method, the caller must provide two
callback functions; likewise for use of the *reverse_grid()* methods.
These are the 4 functions, their arguments are all the same.
.. code-block:: c++
void pack_forward_grid(int which, void *vbuf, int nlist, int *list);
void unpack_forward_grid(int which, void *vbuf, int nlist, int *list);
void pack_reverse_grid(int which, void *vbuf, int nlist, int *list);
void unpack_reverse_grid(int which, void *vbuf, int nlist, int *list);
The *which* argument is set to the *which* value of the
*forward_comm()* or *reverse_comm()* calls. It allows the pack/unpack
function to select what data values to pack/unpack. *Vbuf* is the
buffer to pack/unpack the data to/from. It is a void pointer so that
the caller can cast it to whatever data type it chooses, e.g. double
precision values. *Nlist* is the number of grid cells to pack/unpack
and *list* is a vector (nlist in length) of offsets to where the data
for each grid cell resides in the caller's data arrays, which is best
illustrated with an example from the src/EXTRA-FIX/fix_ttm_grid.cpp
class which stores the scalar electron temperature for 3d system in a
3d grid (one value per grid cell):
.. code-block:: c++
void FixTTMGrid::pack_forward_grid(int /*which*/, void *vbuf, int nlist, int *list)
{
auto buf = (double *) vbuf;
double *src = &T_electron[nzlo_out][nylo_out][nxlo_out];
for (int i = 0; i < nlist; i++) buf[i] = src[list[i]];
}
In this case, the *which* argument is not used, *vbuf* points to a
buffer of doubles, and the electron temperature is stored by the
FixTTMGrid class in a 3d array of owned+ghost cells called T_electron.
That array is allocated by the *memory->create_3d_offset()* method
described above so that the first grid cell it stores is indexed as
T_electron[nzlo_out][nylo_out][nxlo_out]. The *nlist* values in
*list* are integer offsets from that first grid cell. Setting *src*
to the address of the first cell allows those offsets to be used to
access the temperatures to pack into the buffer.
Here is a similar portion of code from the src/fix_ave_grid.cpp class
which can store two kinds of data, a scalar count of atoms in a grid
cell, and one or more grid-cell-averaged atom properties. The code
from its *unpack_reverse_grid()* function for 2d grids and multiple
per-atom properties per grid cell (*nvalues*) is shown here:
.. code-block:: c++
void FixAveGrid::unpack_reverse_grid(int /*which*/, void *vbuf, int nlist, int *list)
{
auto buf = (double *) vbuf;
double *count,*data,*values;
count = &count2d[nylo_out][nxlo_out];
data = &array2d[nylo_out][nxlo_out][0];
m = 0;
for (i = 0; i < nlist; i++) {
count[list[i]] += buf[m++];
values = &data[nvalues*list[i]];
for (j = 0; j < nvalues; j++)
values[j] += buf[m++];
}
}
Both the count and the multiple values per grid cell are communicated
in *vbuf*. Note that *data* is now a pointer to the first value in
the first grid cell. And *values* points to where the first value in
*data* is for an offset of grid cells, calculated by multiplying
*nvalues* by *list[i]*. Finally, because this is reverse
communication, the communicated buffer values are summed to the caller
values.
The *ghost_adjacent()* method returns a 1 if every processor can
perform the necessary owned/ghost communication with only its nearest
neighbor processors (4 in 2d, 6 in 3d). It returns a 0 if any
processor's ghost cells extend further than nearest neighbor
processors.
This can be checked by callers who have the option to change the
global grid size to ensure more efficient nearest-neighbor-only
communication if they wish. In this case, they instantiate a grid of
a given size (resolution), then invoke *setup_comm()* followed by
*ghost_adjacent()*. If the ghost cells are not adjacent, they destroy
the grid instance and start over with a higher-resolution grid.
Several of the :doc:`kspace_style pppm <kspace_style>` command
variants have this option.
----------
Grid class remap methods for load balancing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following methods are used when a load-balancing operation,
triggered by the :doc:`balance <balance>` or :doc:`fix balance
<fix_balance>` commands, changes the partitioning of the simulation
domain into processor subdomains.
In order to work with load-balancing, any style command (compute, fix,
pair, or kspace style) which allocates a grid and stores per-grid data
should define a *reset_grid()* method; it takes no arguments. It will
be called by the two balance commands after they have reset processor
subdomains and migrated atoms (particles) to new owning processors.
The *reset_grid()* method will typically perform some or all of the
following operations. See the src/fix_ave_grid.cpp and
src/EXTRA_FIX/fix_ttm_grid.cpp files for examples of *reset_grid()*
methods, as well as the *pack_remap_grid()* and *unpack_remap_grid()*
functions.
First, the *reset_grid()* method can instantiate new grid(s) of the
same global size, then call *setup_grid()* to partition them via the
new processor subdomains. At this point, it can invoke the
*identical()* method which compares the owned and ghost grid cell
index bounds between two grids, the old grid passed as a pointer
argument, and the new grid whose *identical()* method is being called.
It returns 1 if the indices match on all processors, otherwise 0. If
they all match, then the new grids can be deleted; the command can
continue to use the old grids.
If not, then the command should allocate new grid data array(s) which
depend on the new partitioning. If the command does not need to
persist its grid data from the old partitioning to the new one, then
the command can simply delete the old data array(s) and grid
instance(s). It can then return.
If the grid data does need to persist, then the data for each grid
needs to be "remapped" from the old grid partitioning to the new grid
partitioning. The *setup_remap()* and *remap()* methods are used for
that purpose.
.. code-block:: c++
int identical(Grid3d *old);
void setup_remap(Grid3d *old, int &nremap_buf1, int &nremap_buf2)
void remap(int caller, void *ptr, int which, int nper, int nbyte,
void *buf1, void *buf2, MPI_Datatype datatype)
The arguments to these methods are identical to those for
the *setup_comm()* and *forward_comm()* or *reverse_comm()* methods.
However the returned *nremap_buf1* and *nremap2_buf* values will be
different than the *nbuf1* and *nbuf2* values. They should be used to
allocate two different remap buffers, separate from the owned/ghost
communication buffers.
To use the *remap()* method, the caller must provide two
callback functions:
.. code-block:: c++
void pack_remap_grid(int which, void *vbuf, int nlist, int *list);
void unpack_remap_grid(int which, void *vbuf, int list, int *list);
Their arguments are identical to those for the *pack_forward_grid()*
and *unpack_forward_grid()* callback functions (or the reverse
variants) discussed above. Normally, both these methods pack/unpack
all the data arrays for a given grid. The *which* argument of the
*remap()* method sets the *which* value for the pack/unpack functions.
If the command instantiates multiple grids (of different sizes), it
can be used within the pack/unpack methods to select which grid's data
is being remapped.
Note that the *pack_remap_grid()* function must copy values from the
OLD grid data arrays into the *vbuf* buffer. The *unpack_remap_grid()*
function must copy values from the *vbuf* buffer into the NEW grid
data arrays.
After the remap operation for grid cell data has been performed, the
*reset_grid()* method can deallocate the two remap buffers it created,
and can then exit.
----------
Grid class I/O methods
^^^^^^^^^^^^^^^^^^^^^^
There are two I/O methods in the Grid classes which can be used to
read and write grid cell data to files. The caller can decide on the
precise format of each file, e.g. whether header lines are prepended
or comment lines are allowed. Fundamentally, the file should contain
one line per grid cell for the entire global grid. Each line should
contain identifying info as to which grid cell it is, e.g. a unique
grid cell ID or the ix,iy,iz indices of the cell within a 3d grid.
The line should also contain one or more data values which are stored
within the grid data arrays created by the command
For grid cell IDs, the LAMMPS convention is that the IDs run from 1 to
N, where N = Nx * Ny for 2d grids and N = Nx * Ny * Nz for 3d grids.
The x-index of the grid cell varies fastest, then y, and the z-index
varies slowest. So for a 10x10x10 grid the cell IDs from 901-1000
would be in the top xy layer of the z dimension.
The *read_file()* method does something simple. It reads a chunk of
consecutive lines from the file and passes them back to the caller to
process. The caller provides a *unpack_read_grid()* function for this
purpose. The function checks the grid cell ID or indices and only
stores grid cell data for the grid cells it owns.
The *write_file()* method does something slightly more complex. Each
processor packs the data for its owned grid cells into a buffer. The
caller provides a *pack_write_grid()* function for this purpose. The
*write_file()* method then loops over all processors and each sends
its buffer one at a time to processor 0, along with the 3d (or 2d)
index bounds of its grid cell data within the global grid. Processor
0 calls back to the *unpack_write_grid()* function provided by the
caller with the buffer. The function writes one line per grid cell to
the file.
See the src/EXTRA_FIX/fix_ttm_grid.cpp file for examples of now both
these methods are used to read/write electron temperature values
from/to a file, as well as for implementations of the the pack/unpack
functions described below.
Here are the details of the two I/O methods and the 3 callback
functions. See the src/fix_ave_grid.cpp file for examples of all of
them.
.. code-block:: c++
void read_file(int caller, void *ptr, FILE *fp, int nchunk, int maxline)
void write_file(int caller, void *ptr, int which,
int nper, int nbyte, MPI_Datatype datatype
The *caller* argument in both methods should be one of these values --
Grid3d::COMPUTE, Grid3d::FIX, Grid3d::KSPACE, Grid3d::PAIR --
depending on the style of the caller class. The *ptr* argument in
both methods is the "this" pointer to the caller class. These 2
arguments are used to call back to pack()/unpack() functions in the
caller class, as explained below.
For the *read_file()* method, the *fp* argument is a file pointer to
the file to be read from, opened on processor 0 by the caller.
*Nchunk* is the number of lines to read per chunk, and *maxline* is
the maximum number of characters per line. The Grid class will
allocate a buffer for storing chunks of lines based on these values.
For the *write_file()* method, the *which* argument is a flag the
caller can set which is passed back to the caller's pack()/unpack()
methods. If the command instantiates multiple grids (of different
sizes), this flag can be used within the pack/unpack methods to select
which grid's data is being written out (presumably to different
files). the *nper* argument is the number of values per grid cell to
be written out. The *nbyte* argument is the number of bytes per
value, e.g. 8 for double-precision values. The *datatype* argument is
the MPI_Datatype setting, which should match the *nbyte* argument.
E.g. MPI_DOUBLE for double precision values.
To use the *read_grid()* method, the caller must provide one callback
function. To use the *write_grid()* method, it provides two callback
functions:
.. code-block:: c++
int unpack_read_grid(int nlines, char *buffer)
void pack_write_grid(int which, void *vbuf)
void unpack_write_grid(int which, void *vbuf, int *bounds)
For *unpack_read_grid()* the *nlines* argument is the number of lines
of character data read from the file and contained in *buffer*. The
lines each include a newline character at the end. When the function
processes the lines, it may choose to skip some of them (header or
comment lines). It returns an integer count of the number of grid
cell lines it processed. This enables the Grid class *read_file()*
method to know when it has read the correct number of lines.
For *pack_write_grid()* and *unpack_write_grid()*, the *vbuf* argument
is the buffer to pack/unpack data to/from. It is a void pointer so
that the caller can cast it to whatever data type it chooses,
e.g. double precision values. the *which* argument is set to the
*which* value of the *write_file()* method. It allows the caller to
choose which grid data to operate on.
For *unpack_write_grid()*, the *bounds* argument is a vector of 4 or 6
integer grid indices (4 for 2d, 6 for 3d). They are the
xlo,xhi,ylo,yhi,zlo,zhi index bounds of the portion of the global grid
which the *vbuf* holds owned grid cell data values for. The caller
should loop over the values in *vbuf* with a double loop (2d) or
triple loop (3d), similar to the code snippets listed above. The
x-index varies fastest, then y, and the z-index slowest. If there are
multiple values per grid cell, the index for those values varies
fastest of all. The caller can add the x,y,z indices of the grid cell
(or the corresponding grid cell ID) to the data value(s) written as
one line to the output file.
----------
Style class grid access methods
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A style command can enable its grid cell data to be accessible from
other commands. For example :doc:`fix ave/grid <fix_ave_grid>` or
:doc:`dump grid <dump>` or :doc:`dump grid/vtk <dump>`. Those
commands access the grid cell data by using a *grid reference* in
their input script syntax, as described on the :doc:`Howto_grid
<Howto_grid>` doc page. They look like this:
* c_ID:gname:dname
* c_ID:gname:dname[I]
* f_ID:gname:dname
* f_ID:gname:dname[I]
Each grid command instantiates has a unique *gname*, defined by the
command. Likewise each grid cell data structure (scalar or vector)
associated with a grid has a unique *dname*, also defined by the
command.
To provide access to its grid cell data, a style command needs to
implement the following 4 methods:
.. code-block:: c++
int get_grid_by_name(const std::string &name, int &dim);
void *get_grid_by_index(int index);
int get_griddata_by_name(int igrid, const std::string &name, int &ncol);
void *get_griddata_by_index(int index);
Currently only computes and fixes can implement these methods. If it
does so, the compute of fix should also set the variable
*pergrid_flag* to 1. See any of the compute or fix commands which set
"pergrid_flag = 1" for examples of how these 4 functions can be
implemented.
The *get_grid_by_name()* method takes a grid name as input and returns
two values. The *dim* argument is returned as 2 or 3 for the
dimensionality of the grid. The function return is a grid index from
0 to G-1 where *G* is the number of grids the command instantiates. A
value of -1 is returned if the grid name is not recognized.
The *get_grid_by_index()* method is called after the
*get_grid_by_name()* method, using the grid index it returned as its
argument. This method will return a pointer to the Grid2d or Grid3d
class. The caller can use this to query grid attributes, such as the
global size of the grid, to ensure it is of the expected size.
The *get_griddata_by_name()* method takes a grid index *igrid* and a
data name as input. It returns two values. The *ncol* argument is
returned as a 0 if the grid data is a single value (scalar) per grid
cell, or an integer M > 0 if there are M values (vector) per grid
cell. Note that even if M = 1, it is still a 1-length vector, not a
scalar. The function return is a data index from 0 to D-1 where *D*
is the number of data sets associated with that grid by the command.
A value of -1 is returned if the data name is not recognized.
The *get_griddata_by_index()* method is called after the
*get_griddata_by_name()* method, using the data index it returned as
its argument. This method will return a pointer to the
multidimensional array which stores the requested data.
As in the discussion above of the Memory class *create_offset()*
methods, the dimensionality of the array associated with the returned
pointer depends on whether it is a 2d or 3d grid and whether there is
a single or multiple values stored for each grid cell:
* single value per cell for a 2d grid = 2d array pointer
* multiple values per cell for a 2d grid = 3d array pointer
* single value per cell for a 3d grid = 3d array pointer
* multiple values per cell for a 3d grid = 4d array pointer
The caller will typically access the data by casting the void pointer
to the corresponding array pointer and using nested loops in x,y,z
between owned or ghost index bounds returned by the
*get_bounds_owned()* or *get_bounds_ghost()* methods to index into the
array. Example code snippets with this logic were listed above,
----------
Final notes
^^^^^^^^^^^
Finally, here are some additional issues to pay attention to for
writing any style command which uses distributed grids via the Grid2d
or Grid3d class.
The command destructor should delete all instances of the Grid class,
any buffers it allocated for forward/reverse or remap communication,
and any data arrays it allocated to store grid cell data.
If a command is intended to work for either 2d or 3d simulations, then
it should have logic to instantiate either 2d or 3d grids and their
associated data arrays, depending on the dimension of the simulation
box. The :doc:`fix ave/grid <fix_ave_grid>` command is an example of
such a command.
When a command maps its particles to the grid and updates grid cell
values, it should check that it is not updating or accessing a grid
cell value outside the range of its owned+ghost cells, and generate an
error message if that is the case. This could happen, for example, if
a particle has moved further than half the neighbor skin distance,
because the neighbor list update criterion are not adequate to prevent
it from happening. See the src/KSPACE/pppm.cpp file and its
*particle_map()* method for an example of this kind of error check.