start document describing the implementation of a pair style
This commit is contained in:
@ -523,6 +523,8 @@ The following options are available.
|
||||
These should help to make source and documentation files conforming
|
||||
to some the coding style preferences of the LAMMPS developers.
|
||||
|
||||
.. _clang-format:
|
||||
|
||||
Clang-format support
|
||||
--------------------
|
||||
|
||||
|
||||
@ -6,250 +6,9 @@ be extended by writing new classes that derive from existing
|
||||
parent classes in LAMMPS. Here, some specific coding
|
||||
details are provided for writing code for LAMMPS.
|
||||
|
||||
Writing a new fix style
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Writing fixes is a flexible way of extending LAMMPS. Users can
|
||||
implement many things using fixes:
|
||||
|
||||
- changing particles attributes (positions, velocities, forces, etc.). Examples: FixNVE, FixFreeze.
|
||||
- reading/writing data. Example: FixRestart.
|
||||
- adding or modifying properties due to geometry. Example: FixWall.
|
||||
- interacting with other subsystems or external code: Examples: FixTTM, FixExternal, FixLATTE
|
||||
- saving information for analysis or future use (previous positions,
|
||||
for instance). Examples: Fix AveTime, FixStoreState.
|
||||
|
||||
|
||||
All fixes are derived from the Fix base class and must have a
|
||||
constructor with the signature: ``FixPrintVel(class LAMMPS *, int, char **)``.
|
||||
|
||||
Every fix must be registered in LAMMPS by writing the following lines
|
||||
of code in the header before include guards:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
#ifdef FIX_CLASS
|
||||
// clang-format off
|
||||
FixStyle(print/vel,FixPrintVel);
|
||||
// clang-format on
|
||||
#else
|
||||
/* the definition of the FixPrintVel class comes here */
|
||||
...
|
||||
#endif
|
||||
|
||||
Where ``print/vel`` is the style name of your fix in the input script and
|
||||
``FixPrintVel`` is the name of the class. The header file would be called
|
||||
``fix_print_vel.h`` and the implementation file ``fix_print_vel.cpp``.
|
||||
These conventions allow LAMMPS to automatically integrate it into the
|
||||
executable when compiling and associate your new fix class with the designated
|
||||
keyword when it parses the input script.
|
||||
|
||||
Let's write a simple fix which will print the average velocity at the end
|
||||
of each timestep. First of all, implement a constructor:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
FixPrintVel::FixPrintVel(LAMMPS *lmp, int narg, char **arg)
|
||||
: Fix(lmp, narg, arg)
|
||||
{
|
||||
if (narg < 4)
|
||||
error->all(FLERR,"Illegal fix print/vel command");
|
||||
|
||||
nevery = utils::inumeric(FLERR,arg[3],false,lmp);
|
||||
if (nevery <= 0)
|
||||
error->all(FLERR,"Illegal fix print/vel command");
|
||||
}
|
||||
|
||||
In the constructor you should parse your fix arguments which are
|
||||
specified in the script. All fixes have pretty much the same syntax:
|
||||
``fix <fix-ID> <fix group> <fix name> <fix arguments ...>``. The
|
||||
first 3 parameters are parsed by Fix base class constructor, while
|
||||
``<fix arguments>`` should be parsed by you. In our case, we need to
|
||||
specify how often we want to print an average velocity. For instance,
|
||||
once in 50 timesteps: ``fix 1 print/vel 50``. There is a special variable
|
||||
in the Fix class called ``nevery`` which specifies how often the method
|
||||
``end_of_step()`` is called. Thus all we need to do is just set it up.
|
||||
|
||||
The next method we need to implement is ``setmask()``:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
int FixPrintVel::setmask()
|
||||
{
|
||||
int mask = 0;
|
||||
mask |= FixConst::END_OF_STEP;
|
||||
return mask;
|
||||
}
|
||||
|
||||
Here the user specifies which methods of your fix should be called
|
||||
during execution. The constant ``END_OF_STEP`` corresponds to the
|
||||
``end_of_step()`` method. The most important available methods that
|
||||
are called during a timestep and the order in which they are called
|
||||
are shown in the previous section.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
void FixPrintVel::end_of_step()
|
||||
{
|
||||
// for add3, scale3
|
||||
using namespace MathExtra;
|
||||
|
||||
double** v = atom->v;
|
||||
int nlocal = atom->nlocal;
|
||||
double localAvgVel[4]; // 4th element for particles count
|
||||
memset(localAvgVel, 0, 4 * sizeof(double));
|
||||
for (int particleInd = 0; particleInd < nlocal; ++particleInd) {
|
||||
add3(localAvgVel, v[particleInd], localAvgVel);
|
||||
}
|
||||
localAvgVel[3] = nlocal;
|
||||
double globalAvgVel[4];
|
||||
memset(globalAvgVel, 0, 4 * sizeof(double));
|
||||
MPI_Allreduce(localAvgVel, globalAvgVel, 4, MPI_DOUBLE, MPI_SUM, world);
|
||||
scale3(1.0 / globalAvgVel[3], globalAvgVel);
|
||||
if ((comm->me == 0) && screen) {
|
||||
fmt::print(screen,"{}, {}, {}\n",
|
||||
globalAvgVel[0], globalAvgVel[1], globalAvgVel[2]);
|
||||
}
|
||||
}
|
||||
|
||||
In the code above, we use MathExtra routines defined in
|
||||
``math_extra.h``. There are bunch of math functions to work with
|
||||
arrays of doubles as with math vectors. It is also important to note
|
||||
that LAMMPS code should always assume to be run in parallel and that
|
||||
atom data is thus distributed across the MPI ranks. Thus you can
|
||||
only process data from local atoms directly and need to use MPI library
|
||||
calls to combine or exchange data. For serial execution, LAMMPS
|
||||
comes bundled with the MPI STUBS library that contains the MPI library
|
||||
function calls in dummy versions that only work for a single MPI rank.
|
||||
|
||||
In this code we use an instance of Atom class. This object is stored
|
||||
in the Pointers class (see ``pointers.h``) which is the base class of
|
||||
the Fix base class. This object contains references to various class
|
||||
instances (the original instances are created and held by the LAMMPS
|
||||
class) with all global information about the simulation system.
|
||||
Data from the Pointers class is available to all classes inherited from
|
||||
it using protected inheritance. Hence when you write you own class,
|
||||
which is going to use LAMMPS data, don't forget to inherit from Pointers
|
||||
or pass an Pointer to it to all functions that need access. When writing
|
||||
fixes we inherit from class Fix which is inherited from Pointers so
|
||||
there is no need to inherit from it directly.
|
||||
|
||||
The code above computes average velocity for all particles in the
|
||||
simulation. Yet you have one unused parameter in fix call from the
|
||||
script: ``group_name``. This parameter specifies the group of atoms
|
||||
used in the fix. So we should compute average for all particles in the
|
||||
simulation only if ``group_name == "all"``, but it can be any group.
|
||||
The group membership information of an atom is contained in the *mask*
|
||||
property of and atom and the bit corresponding to a given group is
|
||||
stored in the groupbit variable which is defined in Fix base class:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
for (int i = 0; i < nlocal; ++i) {
|
||||
if (atom->mask[i] & groupbit) {
|
||||
// Do all job here
|
||||
}
|
||||
}
|
||||
|
||||
Class Atom encapsulates atoms positions, velocities, forces, etc. User
|
||||
can access them using particle index. Note, that particle indexes are
|
||||
usually changed every few timesteps because of neighbor list rebuilds
|
||||
and spatial sorting (to improve cache efficiency).
|
||||
|
||||
Let us consider another Fix example: We want to have a fix which stores
|
||||
atoms position from previous time step in your fix. The local atoms
|
||||
indexes may not be valid on the next iteration. In order to handle
|
||||
this situation there are several methods which should be implemented:
|
||||
|
||||
- ``double memory_usage()``: return how much memory the fix uses (optional)
|
||||
- ``void grow_arrays(int)``: do reallocation of the per particle arrays in your fix
|
||||
- ``void copy_arrays(int i, int j, int delflag)``: copy i-th per-particle
|
||||
information to j-th. Used when atom sorting is performed. if delflag is set
|
||||
and atom j owns a body, move the body information to atom i.
|
||||
- ``void set_arrays(int i)``: sets i-th particle related information to zero
|
||||
|
||||
Note, that if your class implements these methods, it must call add calls of
|
||||
add_callback and delete_callback to constructor and destructor. Since we want
|
||||
to store positions of atoms from previous timestep, we need to add
|
||||
``double** xold`` to the header file. Than add allocation code
|
||||
to the constructor:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
FixSavePos::FixSavePos(LAMMPS *lmp, int narg, char **arg), xold(nullptr)
|
||||
{
|
||||
//...
|
||||
memory->create(xold, atom->nmax, 3, "FixSavePos:x");
|
||||
atom->add_callback(0);
|
||||
}
|
||||
|
||||
FixSavePos::~FixSavePos() {
|
||||
atom->delete_callback(id, 0);
|
||||
memory->destroy(xold);
|
||||
}
|
||||
|
||||
Implement the aforementioned methods:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
double FixSavePos::memory_usage()
|
||||
{
|
||||
int nmax = atom->nmax;
|
||||
double bytes = 0.0;
|
||||
bytes += nmax * 3 * sizeof(double);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void FixSavePos::grow_arrays(int nmax)
|
||||
{
|
||||
memory->grow(xold, nmax, 3, "FixSavePos:xold");
|
||||
}
|
||||
|
||||
void FixSavePos::copy_arrays(int i, int j, int delflag)
|
||||
{
|
||||
memcpy(xold[j], xold[i], sizeof(double) * 3);
|
||||
}
|
||||
|
||||
void FixSavePos::set_arrays(int i)
|
||||
{
|
||||
memset(xold[i], 0, sizeof(double) * 3);
|
||||
}
|
||||
|
||||
int FixSavePos::pack_exchange(int i, double *buf)
|
||||
{
|
||||
int m = 0;
|
||||
buf[m++] = xold[i][0];
|
||||
buf[m++] = xold[i][1];
|
||||
buf[m++] = xold[i][2];
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
int FixSavePos::unpack_exchange(int nlocal, double *buf)
|
||||
{
|
||||
int m = 0;
|
||||
xold[nlocal][0] = buf[m++];
|
||||
xold[nlocal][1] = buf[m++];
|
||||
xold[nlocal][2] = buf[m++];
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
Now, a little bit about memory allocation. We use the Memory class which
|
||||
is just a bunch of template functions for allocating 1D and 2D
|
||||
arrays. So you need to add include ``memory.h`` to have access to them.
|
||||
|
||||
Finally, if you need to write/read some global information used in
|
||||
your fix to the restart file, you might do it by setting flag
|
||||
``restart_global = 1`` in the constructor and implementing methods void
|
||||
``write_restart(FILE *fp)`` and ``void restart(char *buf)``.
|
||||
If, in addition, you want to write the per-atom property to restart
|
||||
files additional settings and functions are needed:
|
||||
|
||||
- a fix flag indicating this needs to be set ``restart_peratom = 1;``
|
||||
- ``atom->add_callback()`` and ``atom->delete_callback()`` must be called
|
||||
a second time with the final argument set to 1 instead of 0 (indicating
|
||||
restart processing instead of per-atom data memory management).
|
||||
- the functions ``void pack_restart(int i, double *buf)`` and
|
||||
``void unpack_restart(int nlocal, int nth)`` need to be implemented
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
Developer_write_pair
|
||||
Developer_write_fix
|
||||
|
||||
246
doc/src/Developer_write_fix.rst
Normal file
246
doc/src/Developer_write_fix.rst
Normal file
@ -0,0 +1,246 @@
|
||||
Writing a new fix style
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Writing fixes is a flexible way of extending LAMMPS. Users can
|
||||
implement many things using fixes:
|
||||
|
||||
- changing particles attributes (positions, velocities, forces, etc.). Examples: FixNVE, FixFreeze.
|
||||
- reading/writing data. Example: FixRestart.
|
||||
- adding or modifying properties due to geometry. Example: FixWall.
|
||||
- interacting with other subsystems or external code: Examples: FixTTM, FixExternal, FixLATTE
|
||||
- saving information for analysis or future use (previous positions,
|
||||
for instance). Examples: Fix AveTime, FixStoreState.
|
||||
|
||||
All fixes are derived from the Fix base class and must have a
|
||||
constructor with the signature: ``FixPrintVel(class LAMMPS *, int, char **)``.
|
||||
|
||||
Every fix must be registered in LAMMPS by writing the following lines
|
||||
of code in the header before include guards:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
#ifdef FIX_CLASS
|
||||
// clang-format off
|
||||
FixStyle(print/vel,FixPrintVel);
|
||||
// clang-format on
|
||||
#else
|
||||
/* the definition of the FixPrintVel class comes here */
|
||||
...
|
||||
#endif
|
||||
|
||||
Where ``print/vel`` is the style name of your fix in the input script and
|
||||
``FixPrintVel`` is the name of the class. The header file would be called
|
||||
``fix_print_vel.h`` and the implementation file ``fix_print_vel.cpp``.
|
||||
These conventions allow LAMMPS to automatically integrate it into the
|
||||
executable when compiling and associate your new fix class with the designated
|
||||
keyword when it parses the input script.
|
||||
|
||||
Let's write a simple fix which will print the average velocity at the end
|
||||
of each timestep. First of all, implement a constructor:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
FixPrintVel::FixPrintVel(LAMMPS *lmp, int narg, char **arg)
|
||||
: Fix(lmp, narg, arg)
|
||||
{
|
||||
if (narg < 4)
|
||||
error->all(FLERR,"Illegal fix print/vel command");
|
||||
|
||||
nevery = utils::inumeric(FLERR,arg[3],false,lmp);
|
||||
if (nevery <= 0)
|
||||
error->all(FLERR,"Illegal fix print/vel command");
|
||||
}
|
||||
|
||||
In the constructor you should parse your fix arguments which are
|
||||
specified in the script. All fixes have pretty much the same syntax:
|
||||
``fix <fix-ID> <fix group> <fix name> <fix arguments ...>``. The
|
||||
first 3 parameters are parsed by Fix base class constructor, while
|
||||
``<fix arguments>`` should be parsed by you. In our case, we need to
|
||||
specify how often we want to print an average velocity. For instance,
|
||||
once in 50 timesteps: ``fix 1 print/vel 50``. There is a special variable
|
||||
in the Fix class called ``nevery`` which specifies how often the method
|
||||
``end_of_step()`` is called. Thus all we need to do is just set it up.
|
||||
|
||||
The next method we need to implement is ``setmask()``:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
int FixPrintVel::setmask()
|
||||
{
|
||||
int mask = 0;
|
||||
mask |= FixConst::END_OF_STEP;
|
||||
return mask;
|
||||
}
|
||||
|
||||
Here the user specifies which methods of your fix should be called
|
||||
during execution. The constant ``END_OF_STEP`` corresponds to the
|
||||
``end_of_step()`` method. The most important available methods that
|
||||
are called during a timestep and the order in which they are called
|
||||
are shown in the previous section.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
void FixPrintVel::end_of_step()
|
||||
{
|
||||
// for add3, scale3
|
||||
using namespace MathExtra;
|
||||
|
||||
double** v = atom->v;
|
||||
int nlocal = atom->nlocal;
|
||||
double localAvgVel[4]; // 4th element for particles count
|
||||
memset(localAvgVel, 0, 4 * sizeof(double));
|
||||
for (int particleInd = 0; particleInd < nlocal; ++particleInd) {
|
||||
add3(localAvgVel, v[particleInd], localAvgVel);
|
||||
}
|
||||
localAvgVel[3] = nlocal;
|
||||
double globalAvgVel[4];
|
||||
memset(globalAvgVel, 0, 4 * sizeof(double));
|
||||
MPI_Allreduce(localAvgVel, globalAvgVel, 4, MPI_DOUBLE, MPI_SUM, world);
|
||||
scale3(1.0 / globalAvgVel[3], globalAvgVel);
|
||||
if ((comm->me == 0) && screen) {
|
||||
fmt::print(screen,"{}, {}, {}\n",
|
||||
globalAvgVel[0], globalAvgVel[1], globalAvgVel[2]);
|
||||
}
|
||||
}
|
||||
|
||||
In the code above, we use MathExtra routines defined in
|
||||
``math_extra.h``. There are bunch of math functions to work with
|
||||
arrays of doubles as with math vectors. It is also important to note
|
||||
that LAMMPS code should always assume to be run in parallel and that
|
||||
atom data is thus distributed across the MPI ranks. Thus you can
|
||||
only process data from local atoms directly and need to use MPI library
|
||||
calls to combine or exchange data. For serial execution, LAMMPS
|
||||
comes bundled with the MPI STUBS library that contains the MPI library
|
||||
function calls in dummy versions that only work for a single MPI rank.
|
||||
|
||||
In this code we use an instance of Atom class. This object is stored
|
||||
in the Pointers class (see ``pointers.h``) which is the base class of
|
||||
the Fix base class. This object contains references to various class
|
||||
instances (the original instances are created and held by the LAMMPS
|
||||
class) with all global information about the simulation system.
|
||||
Data from the Pointers class is available to all classes inherited from
|
||||
it using protected inheritance. Hence when you write you own class,
|
||||
which is going to use LAMMPS data, don't forget to inherit from Pointers
|
||||
or pass an Pointer to it to all functions that need access. When writing
|
||||
fixes we inherit from class Fix which is inherited from Pointers so
|
||||
there is no need to inherit from it directly.
|
||||
|
||||
The code above computes average velocity for all particles in the
|
||||
simulation. Yet you have one unused parameter in fix call from the
|
||||
script: ``group_name``. This parameter specifies the group of atoms
|
||||
used in the fix. So we should compute average for all particles in the
|
||||
simulation only if ``group_name == "all"``, but it can be any group.
|
||||
The group membership information of an atom is contained in the *mask*
|
||||
property of and atom and the bit corresponding to a given group is
|
||||
stored in the groupbit variable which is defined in Fix base class:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
for (int i = 0; i < nlocal; ++i) {
|
||||
if (atom->mask[i] & groupbit) {
|
||||
// Do all job here
|
||||
}
|
||||
}
|
||||
|
||||
Class Atom encapsulates atoms positions, velocities, forces, etc. User
|
||||
can access them using particle index. Note, that particle indexes are
|
||||
usually changed every few timesteps because of neighbor list rebuilds
|
||||
and spatial sorting (to improve cache efficiency).
|
||||
|
||||
Let us consider another Fix example: We want to have a fix which stores
|
||||
atoms position from previous time step in your fix. The local atoms
|
||||
indexes may not be valid on the next iteration. In order to handle
|
||||
this situation there are several methods which should be implemented:
|
||||
|
||||
- ``double memory_usage()``: return how much memory the fix uses (optional)
|
||||
- ``void grow_arrays(int)``: do reallocation of the per particle arrays in your fix
|
||||
- ``void copy_arrays(int i, int j, int delflag)``: copy i-th per-particle
|
||||
information to j-th. Used when atom sorting is performed. if delflag is set
|
||||
and atom j owns a body, move the body information to atom i.
|
||||
- ``void set_arrays(int i)``: sets i-th particle related information to zero
|
||||
|
||||
Note, that if your class implements these methods, it must call add calls of
|
||||
add_callback and delete_callback to constructor and destructor. Since we want
|
||||
to store positions of atoms from previous timestep, we need to add
|
||||
``double** xold`` to the header file. Than add allocation code
|
||||
to the constructor:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
FixSavePos::FixSavePos(LAMMPS *lmp, int narg, char **arg), xold(nullptr)
|
||||
{
|
||||
//...
|
||||
memory->create(xold, atom->nmax, 3, "FixSavePos:x");
|
||||
atom->add_callback(0);
|
||||
}
|
||||
|
||||
FixSavePos::~FixSavePos() {
|
||||
atom->delete_callback(id, 0);
|
||||
memory->destroy(xold);
|
||||
}
|
||||
|
||||
Implement the aforementioned methods:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
double FixSavePos::memory_usage()
|
||||
{
|
||||
int nmax = atom->nmax;
|
||||
double bytes = 0.0;
|
||||
bytes += nmax * 3 * sizeof(double);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void FixSavePos::grow_arrays(int nmax)
|
||||
{
|
||||
memory->grow(xold, nmax, 3, "FixSavePos:xold");
|
||||
}
|
||||
|
||||
void FixSavePos::copy_arrays(int i, int j, int delflag)
|
||||
{
|
||||
memcpy(xold[j], xold[i], sizeof(double) * 3);
|
||||
}
|
||||
|
||||
void FixSavePos::set_arrays(int i)
|
||||
{
|
||||
memset(xold[i], 0, sizeof(double) * 3);
|
||||
}
|
||||
|
||||
int FixSavePos::pack_exchange(int i, double *buf)
|
||||
{
|
||||
int m = 0;
|
||||
buf[m++] = xold[i][0];
|
||||
buf[m++] = xold[i][1];
|
||||
buf[m++] = xold[i][2];
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
int FixSavePos::unpack_exchange(int nlocal, double *buf)
|
||||
{
|
||||
int m = 0;
|
||||
xold[nlocal][0] = buf[m++];
|
||||
xold[nlocal][1] = buf[m++];
|
||||
xold[nlocal][2] = buf[m++];
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
Now, a little bit about memory allocation. We use the Memory class which
|
||||
is just a bunch of template functions for allocating 1D and 2D
|
||||
arrays. So you need to add include ``memory.h`` to have access to them.
|
||||
|
||||
Finally, if you need to write/read some global information used in
|
||||
your fix to the restart file, you might do it by setting flag
|
||||
``restart_global = 1`` in the constructor and implementing methods void
|
||||
``write_restart(FILE *fp)`` and ``void restart(char *buf)``.
|
||||
If, in addition, you want to write the per-atom property to restart
|
||||
files additional settings and functions are needed:
|
||||
|
||||
- a fix flag indicating this needs to be set ``restart_peratom = 1;``
|
||||
- ``atom->add_callback()`` and ``atom->delete_callback()`` must be called
|
||||
a second time with the final argument set to 1 instead of 0 (indicating
|
||||
restart processing instead of per-atom data memory management).
|
||||
- the functions ``void pack_restart(int i, double *buf)`` and
|
||||
``void unpack_restart(int nlocal, int nth)`` need to be implemented
|
||||
|
||||
308
doc/src/Developer_write_pair.rst
Normal file
308
doc/src/Developer_write_pair.rst
Normal file
@ -0,0 +1,308 @@
|
||||
Writing new pair styles
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Pair styles are at the core of most simulations with LAMMPS since they
|
||||
are used to compute the forces (plus energy and virial contributions, if
|
||||
needed) on atoms for pairs of atoms within a given cutoff. This is
|
||||
often the dominant computation in LAMMPS and sometimes even the only
|
||||
one. Pair styles can be grouped in multiple categories:
|
||||
|
||||
#. simple pairwise additive interactions of point particles
|
||||
(e.g. :doc:`Lennard-Jones <pair_lj>`, :doc:`Morse <pair_morse>`,
|
||||
:doc:`Buckingham <pair_buck>`)
|
||||
#. pairwise additive interactions of point particles with added
|
||||
:doc:`Coulomb <pair_coul>` interactions
|
||||
#. manybody interactions of point particles (e.g. :doc:`EAM <pair_eam>`,
|
||||
:doc:`Tersoff <pair_tersoff>`)
|
||||
#. complex interactions that include additional per-atom properties
|
||||
(e.g. Discrete Element Models (DEM), Peridynamics, Ellipsoids,
|
||||
Smoothed Particle Hydrodynamics (SPH))
|
||||
|
||||
In the text below we will discuss aspects of implementing pair styles in
|
||||
LAMMPS by looking at representative case studies. The design of LAMMPS
|
||||
allows to focus on the essentials, which is to compute the forces (and
|
||||
energies or virial contributions), enter and manage the global settings
|
||||
as well as the potential parameters, and the pair style specific parts
|
||||
of reading and writing restart and data files. Most of the complex
|
||||
tasks like management of the atom positions, domain decomposition and
|
||||
boundaries, or neighbor list creation are handled transparently by other
|
||||
parts of the LAMMPS code.
|
||||
|
||||
As shown on the page for :doc:`writing or extending pair styles
|
||||
<Modify_pair>` for the implementation of a pair style a new class must
|
||||
be written that is either directly or indirectly derived from the
|
||||
``Pair`` class. In that derived class there are three *required*
|
||||
methods in addition to the constructor that must be implemented since
|
||||
they are "pure" in the base class: ``Pair::compute()``,
|
||||
``Pair::settings()``, ``Pair::coeff()``. All other methods are optional
|
||||
and have default implementations in the base class (most of which do
|
||||
nothing), but they may need to be overridden depending on the
|
||||
requirements of the model.
|
||||
|
||||
Case 1: a pairwise additive model
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In this section we will describe the complete procedure of adding
|
||||
a simple pair style to LAMMPS: an empirical model that can be used
|
||||
to model liquid mercury.
|
||||
|
||||
Model and general considerations
|
||||
""""""""""""""""""""""""""""""""
|
||||
|
||||
The functional form of the model according to :ref:`(Bomont) <Bomont2>`
|
||||
consists of a repulsive Born-Mayer exponential term and a temperature
|
||||
dependent, attractive Gaussian term.
|
||||
|
||||
.. math::
|
||||
|
||||
E = A_0 \exp \left( -\alpha r \right) - A_1 \exp\left[ -\beta \left(r - r_0 \right)^2 \right]
|
||||
|
||||
For the application to mercury the following parameters are listed:
|
||||
|
||||
- :math:`A_0 = 8.2464 \times 10^{13} \; \textrm{eV}`
|
||||
- :math:`\alpha = 12.48 \; \AA^{-1}`
|
||||
- :math:`\beta = 0.44 \; \AA^{-2}`
|
||||
- :math:`r_0 = 3.56 \; \AA`
|
||||
- :math:`A_1` is temperature dependent and can be determined from
|
||||
:math:`A_1 = a_0 + a_1 T + a_2 T^2` with:
|
||||
|
||||
- :math:`a_0 = 1.97475 \times 10^{-2} \; \textrm{eV}`
|
||||
- :math:`a_1 = 8.40841 \times 10^{-5} \; \textrm{eV/K}`
|
||||
- :math:`a_2 = -2.58717 \times 10^{-8} \; \textrm{eV/K}^{-2}`
|
||||
|
||||
With the optional cutoff, this means we have a total of 5 or 6
|
||||
parameters for each pair of atom types. In addition we need to get the
|
||||
temperature and a default cutoff value as global settings.
|
||||
|
||||
Because of the combination of Born-Mayer with a Gaussian, the pair style
|
||||
shall be named "born/gauss" and thus the class name would be
|
||||
``PairBornGauss`` and the source files ``pair_born_gauss.h`` and
|
||||
``pair_born_gauss.cpp``. Since this is a rather uncommon potential, it
|
||||
shall be added to the :ref:`EXTRA-PAIR <PKG-EXTRA-PAIR>` package. For
|
||||
the implementation, we will use :doc:`pair style morse <pair_morse>` as
|
||||
template.
|
||||
|
||||
Header file
|
||||
"""""""""""
|
||||
|
||||
The first segment of any LAMMPS source should be the copyright and
|
||||
license statement. Note the marker in the first line to indicate to
|
||||
editors like emacs that this file is a C++ source, even though the .h
|
||||
extension suggests a C source.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
/* -*- c++ -*- ----------------------------------------------------------
|
||||
LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
|
||||
https://www.lammps.org/, Sandia National Laboratories
|
||||
LAMMPS development team: developers@lammps.org
|
||||
|
||||
Copyright (2003) Sandia Corporation. Under the terms of Contract
|
||||
DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
|
||||
certain rights in this software. This software is distributed under
|
||||
the GNU General Public License.
|
||||
|
||||
See the README file in the top-level LAMMPS directory.
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
Every pair style must be registered in LAMMPS by writing the following
|
||||
lines of code in the second part of the header before the include guards
|
||||
for the class definition:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
#ifdef PAIR_CLASS
|
||||
// clang-format off
|
||||
PairStyle(born/gauss,PairBornGauss);
|
||||
// clang-format on
|
||||
#else
|
||||
|
||||
/* the definition of the PairBornGauss class (see below) is inserted here */
|
||||
|
||||
#endif
|
||||
|
||||
This second segment of the header file will be included by the ``Force``
|
||||
class in ``force.cpp`` to build a map of "factory functions" that will
|
||||
create an instance of these classes and return a pointer to it. The map
|
||||
connects the name of the pair style, "born/gauss", to the name of the
|
||||
class, ``PairBornGauss``. The list of header files to include is
|
||||
automatically updated by the build system, so the presence of the file
|
||||
in the ``src/EXTRA-PAIR`` folder and the enabling of the EXTRA-PAIR
|
||||
package will trigger that LAMMPS includes the new pair style when it is
|
||||
(re-)compiled. The "// clang-format" format comments are needed so that
|
||||
running :ref:`clang-format <clang-format>` on the file will not insert
|
||||
blanks between "born", "/", and "gauss" which would break the
|
||||
``PairStyle`` macro.
|
||||
|
||||
The third segment of the header is the actual class definition of the
|
||||
``PairBornGauss`` class. This has the prototypes for all member
|
||||
functions that will be implemented by this pair style. This includes a
|
||||
number of optional functions. All functions that were labeled in the
|
||||
base class as "virtual" must be given the "override" property as it is
|
||||
done in the code shown below. This helps to detect unexpected
|
||||
mismatches as compile errors in case the signature of a function is
|
||||
changed in the base class. For example, if this change would add an
|
||||
optional argument with a default value, then all existing source code
|
||||
calling the function would not need changes and still compile, but the
|
||||
function in the derived class would no longer override the one in the
|
||||
base class due to the different number of arguments and the behavior of
|
||||
the pair style is thus changed in an unintended way. Using "override"
|
||||
prevents such issues.
|
||||
|
||||
Also variables and arrays for storing global settings and potential
|
||||
parameters are defined. Since those are internal to the class, they are
|
||||
placed after a "protected:" label.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
#ifndef LMP_PAIR_BORN_GAUSS_H
|
||||
#define LMP_PAIR_BORN_GAUSS_H
|
||||
|
||||
#include "pair.h"
|
||||
|
||||
namespace LAMMPS_NS {
|
||||
|
||||
class PairBornGauss : public Pair {
|
||||
public:
|
||||
PairBornGauss(class LAMMPS *);
|
||||
~PairBornGauss() override;
|
||||
|
||||
void compute(int, int) override;
|
||||
void settings(int, char **) override;
|
||||
void coeff(int, char **) override;
|
||||
double init_one(int, int) override;
|
||||
|
||||
void write_restart(FILE *) override;
|
||||
void read_restart(FILE *) override;
|
||||
void write_restart_settings(FILE *) override;
|
||||
void read_restart_settings(FILE *) override;
|
||||
void write_data(FILE *) override;
|
||||
void write_data_all(FILE *) override;
|
||||
|
||||
double single(int, int, int, int, double, double, double, double &) override;
|
||||
void *extract(const char *, int &) override;
|
||||
|
||||
protected:
|
||||
double cut_global, temperature;
|
||||
double **cut;
|
||||
double **biga0, **alpha, **biga1, **beta, **r0;
|
||||
double **a0, **a1, **a2;
|
||||
double **offset;
|
||||
|
||||
virtual void allocate();
|
||||
};
|
||||
} // namespace LAMMPS_NS
|
||||
#endif
|
||||
|
||||
Some details of the class definition will be discussed later.
|
||||
|
||||
Implementation file
|
||||
"""""""""""""""""""
|
||||
|
||||
We move on to the implementation in the ``pair_born_gauss.cpp`` file.
|
||||
This file also starts with the LAMMPS copyright and license header.
|
||||
Below is typically the space where comments may be added with additional
|
||||
information about this specific file, the author(s) and affiliation(s)
|
||||
and email address(es) so the author can be easily contacted in case
|
||||
there are questions about the implementation later. Since the file(s)
|
||||
may be around for a long time, it is beneficial to use some kind of
|
||||
"permanent" email address, if possible.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
/* ----------------------------------------------------------------------
|
||||
LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
|
||||
https://www.lammps.org/, Sandia National Laboratories
|
||||
LAMMPS development team: developers@lammps.org
|
||||
|
||||
Copyright (2003) Sandia Corporation. Under the terms of Contract
|
||||
DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
|
||||
certain rights in this software. This software is distributed under
|
||||
the GNU General Public License.
|
||||
|
||||
See the README file in the top-level LAMMPS directory.
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
// Contributing author: Axel Kohlmeyer, Temple University, akohlmey@gmail.com
|
||||
|
||||
#include "pair_born_gauss.h"
|
||||
|
||||
#include "atom.h"
|
||||
#include "comm.h"
|
||||
#include "error.h"
|
||||
#include "fix.h"
|
||||
#include "force.h"
|
||||
#include "memory.h"
|
||||
#include "neigh_list.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
using namespace LAMMPS_NS;
|
||||
|
||||
The second section of the implementation file has various include
|
||||
statements. The include file for the class header has to come first,
|
||||
then LAMMPS classes (sorted alphabetically) and system headers and
|
||||
others, if needed. Note the standardized C++ notation for headers of
|
||||
C-library functions. The final statement of this segment imports the
|
||||
``LAMMPS_NS::`` namespace globally for this file. This way, all LAMMPS
|
||||
specific functions and classes do not have to be prefixed with
|
||||
``LAMMPS_NS::``.
|
||||
|
||||
Constructor and destructor (required)
|
||||
"""""""""""""""""""""""""""""""""""""
|
||||
|
||||
The first two functions in the implementation source file are typically
|
||||
the constructor and the destructor.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
PairBornGauss::PairBornGauss(LAMMPS *lmp) : Pair(lmp), temp(nullptr)
|
||||
{
|
||||
single_enable = 1;
|
||||
respa_enable = 0;
|
||||
writedata = 1;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
PairBornGauss::~PairBornGauss()
|
||||
{
|
||||
}
|
||||
|
||||
In the constructor you should parse your fix arguments which are
|
||||
specified in the script. All fixes have pretty much the same syntax:
|
||||
``fix <fix-ID> <fix group> <fix name> <fix arguments ...>``. The
|
||||
first 3 parameters are parsed by Fix base class constructor, while
|
||||
``<fix arguments>`` should be parsed by you. In our case, we need to
|
||||
specify how often we want to print an average velocity. For instance,
|
||||
once in 50 timesteps: ``fix 1 print/vel 50``. There is a special variable
|
||||
in the Fix class called ``nevery`` which specifies how often the method
|
||||
``end_of_step()`` is called. Thus all we need to do is just set it up.
|
||||
|
||||
Settings and coefficients (required)
|
||||
""""""""""""""""""""""""""""""""""""
|
||||
|
||||
The next method we need to implement is ``setmask()``:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
int FixPrintVel::setmask()
|
||||
{
|
||||
int mask = 0;
|
||||
mask |= FixConst::END_OF_STEP;
|
||||
return mask;
|
||||
}
|
||||
|
||||
Computing forces from the neighbor list (required)
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
xxxxxadfasdf
|
||||
|
||||
--------------
|
||||
|
||||
.. _Bomont2:
|
||||
|
||||
**(Bomont)** Bomont, Bretonnet, J. Chem. Phys. 124, 054504 (2006)
|
||||
Reference in New Issue
Block a user