1195 lines
46 KiB
ReStructuredText
1195 lines
46 KiB
ReStructuredText
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 or *only* the Coulomb 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)
|
|
#. special purpose pair styles that may not even compute forces like
|
|
:doc:`pair_style zero <pair_zero>` and :doc:`pair_style tracker
|
|
<pair_tracker>`, or are a wrapper for multiple kinds of interactions
|
|
like :doc:`pair_style hybrid <pair_hybrid>`, :doc:`pair_style list <pair_list>`,
|
|
and :doc:`pair_style kim <pair_kim>`
|
|
|
|
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 developers 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>`, in order to implement a new pair style, a new class must
|
|
be written that is either directly or indirectly derived from the
|
|
``Pair`` class. If that class is directly derived from ``Pair``, there
|
|
are three methods that *must* be re-implemented, since they are "pure"
|
|
in the base class: ``Pair::compute()``, ``Pair::settings()``,
|
|
``Pair::coeff()``. In addition a custom constructor is needed. 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 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. Additionally, we need to input a
|
|
default cutoff value as a global setting.
|
|
|
|
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 (this is a convention inherited from the
|
|
very beginning of the C++ version of LAMMPS).
|
|
|
|
.. 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 including the following
|
|
lines of code in the second part of the header after the copyright
|
|
message and 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``. Before including the headers, the ``PAIR_CLASS``
|
|
define is set and the ``PairStyle(name,class)`` macro defined as needed.
|
|
|
|
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
|
|
:doc:`a few required and a number of optional functions <Modify_pair>`.
|
|
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.
|
|
|
|
The "override" property helps to detect unexpected mismatches because
|
|
compilation will stop with an error in case the signature of a function
|
|
is changed in the base class without also changing it in all derived
|
|
classes. 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" keyword prevents
|
|
such issues.
|
|
|
|
.. 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;
|
|
|
|
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++
|
|
|
|
protected:
|
|
double cut_global;
|
|
double **cut;
|
|
double **biga0, **alpha, **biga1, **beta, **r0;
|
|
double **a0, **a1, **a2;
|
|
double **offset;
|
|
|
|
virtual void allocate();
|
|
};
|
|
} // namespace LAMMPS_NS
|
|
#endif
|
|
|
|
Implementation file
|
|
"""""""""""""""""""
|
|
|
|
We move on to the implementation of the ``PairBornGauss`` class in the
|
|
``pair_born_gauss.cpp`` file. This file also starts with a LAMMPS
|
|
copyright and license header. Below that notice is typically the space
|
|
where comments may be added with additional information about this
|
|
specific file, the author(s), affiliation(s), and email address(es).
|
|
This way the contributing author(s) can be easily contacted, when
|
|
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 a block of LAMMPS classes (sorted alphabetically) followed by a
|
|
block of system headers and others, if needed. Note the standardized
|
|
C++ notation for headers of C-library functions (``cmath`` instead of
|
|
``math.h``). 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.
|
|
|
|
Pair styles are different from most classes in LAMMPS that define a
|
|
"style", as their constructor only uses the LAMMPS class instance
|
|
pointer as argument, but **not** the command line arguments of the
|
|
:doc:`pair_style command <pair_style>`. Instead, those arguments are
|
|
processed in the ``Pair::settings()`` function (or rather the version in
|
|
the derived class). The constructor is the place where global defaults
|
|
are set and specifically flags are set about which optional features of
|
|
a pair style are available.
|
|
|
|
.. code-block:: c++
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
PairBornGauss::PairBornGauss(LAMMPS *lmp) : Pair(lmp)
|
|
{
|
|
writedata = 1;
|
|
}
|
|
|
|
The `writedata = 1;` statement indicates that the pair style is capable
|
|
of writing the current pair coefficient parameters to data files. That
|
|
is, the class implements specific versions for ``Pair::data()`` and
|
|
``Pair::data_all()``. Other statements that could be added here would
|
|
be `single_enable = 1;` or `respa_enable = 0;` to indicate that the
|
|
``Pair::single()`` function is present and the
|
|
``Pair::compute_(inner|middle|outer)`` are not, but those are also the
|
|
default settings and already set in the base class.
|
|
|
|
In the destructor, we need to delete all memory that was allocated by the
|
|
pair style, usually to hold force field parameters that were entered
|
|
with the :doc:`pair_coeff command <pair_coeff>`. Most of those array
|
|
pointers will need to be declared in the derived class header, but some
|
|
(e.g. setflag, cutsq) are already declared in the base class.
|
|
|
|
.. code-block:: c++
|
|
|
|
PairBornGauss::~PairBornGauss()
|
|
{
|
|
if (allocated) {
|
|
memory->destroy(setflag);
|
|
memory->destroy(cutsq);
|
|
memory->destroy(cut);
|
|
memory->destroy(biga0);
|
|
memory->destroy(alpha);
|
|
memory->destroy(biga1);
|
|
memory->destroy(beta);
|
|
memory->destroy(r0);
|
|
memory->destroy(offset);
|
|
}
|
|
}
|
|
|
|
|
|
Settings and coefficients (required)
|
|
""""""""""""""""""""""""""""""""""""
|
|
|
|
To enter the global pair style settings and the pair style parameters,
|
|
the functions ``Pair::settings()`` and ``Pair::coeff()`` need to be
|
|
re-implemented. The arguments to the ``settings()`` function are the
|
|
arguments given to the :doc:`pair_style command <pair_style>`.
|
|
Normally, those would already be processed as part of the constructor,
|
|
but moving this to a separate function allows to change global settings
|
|
like the default cutoff without having to reissue all pair_coeff
|
|
commands or re-read the ``Pair Coeffs`` sections from the data file.
|
|
In the ``settings()`` function, also the arrays for storing parameters,
|
|
to define cutoffs, track with pairs of parameters have been explicitly
|
|
set are allocated and, if needed, initialized. In this case, the memory
|
|
allocation and initialization is moved to a function ``allocate()``.
|
|
|
|
.. code-block:: c++
|
|
|
|
/* ----------------------------------------------------------------------
|
|
allocate all arrays
|
|
------------------------------------------------------------------------- */
|
|
|
|
void PairBornGauss::allocate()
|
|
{
|
|
allocated = 1;
|
|
int np1 = atom->ntypes + 1;
|
|
|
|
memory->create(setflag, np1, np1, "pair:setflag");
|
|
for (int i = 1; i < np1; i++)
|
|
for (int j = i; j < np1; j++) setflag[i][j] = 0;
|
|
|
|
memory->create(cutsq, np1, np1, "pair:cutsq");
|
|
memory->create(cut, np1, np1, "pair:cut");
|
|
memory->create(biga0, np1, np1, "pair:biga0");
|
|
memory->create(alpha, np1, np1, "pair:alpha");
|
|
memory->create(biga1, np1, np1, "pair:biga1");
|
|
memory->create(beta, np1, np1, "pair:beta");
|
|
memory->create(r0, np1, np1, "pair:r0");
|
|
memory->create(offset, np1, np1, "pair:offset");
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
global settings
|
|
------------------------------------------------------------------------- */
|
|
|
|
void PairBornGauss::settings(int narg, char **arg)
|
|
{
|
|
if (narg != 1) error->all(FLERR, "Pair style bond/gauss must have exactly one argument");
|
|
cut_global = utils::numeric(FLERR, arg[0], false, lmp);
|
|
|
|
// reset per-type pair cutoffs that have been explicitly set previously
|
|
|
|
if (allocated) {
|
|
for (int i = 1; i <= atom->ntypes; i++)
|
|
for (int j = i; j <= atom->ntypes; j++)
|
|
if (setflag[i][j]) cut[i][j] = cut_global;
|
|
}
|
|
}
|
|
|
|
The arguments to the ``coeff()`` function are the arguments to the
|
|
:doc:`pair_coeff command <pair_coeff>`. The function is also called
|
|
when processing the ``Pair Coeffs`` or ``PairIJ Coeffs`` sections of
|
|
data files. In the case of the ``Pair Coeffs`` section there is only
|
|
one atom type per line and thus the first argument is duplicated. Since
|
|
the atom type arguments of the :doc:`pair_coeff command <pair_coeff>`
|
|
may be a range (e.g. \*\ 3 for atom types 1, 2, and 3), the
|
|
corresponding arguments are passed to the :cpp:func:`utils::bounds()
|
|
<LAMMPS_NS::utils::bounds>` function which will then return the low
|
|
and high end of the range. Note that the ``setflag`` array is set to 1
|
|
for all pairs of atom types processed by this call. This information is
|
|
later used in the ``init_one()`` function to determine if any coefficients
|
|
are missing and, if supported by the potential, generate those missing
|
|
coefficients from the selected mixing rule.
|
|
|
|
.. code-block:: c++
|
|
|
|
/* ----------------------------------------------------------------------
|
|
set coeffs for one or more type pairs
|
|
------------------------------------------------------------------------- */
|
|
|
|
void PairBornGauss::coeff(int narg, char **arg)
|
|
{
|
|
if (narg < 7 || narg > 8) error->all(FLERR, "Incorrect args for pair coefficients");
|
|
if (!allocated) allocate();
|
|
|
|
int ilo, ihi, jlo, jhi;
|
|
utils::bounds(FLERR, arg[0], 1, atom->ntypes, ilo, ihi, error);
|
|
utils::bounds(FLERR, arg[1], 1, atom->ntypes, jlo, jhi, error);
|
|
|
|
double biga0_one = utils::numeric(FLERR, arg[2], false, lmp);
|
|
double alpha_one = utils::numeric(FLERR, arg[3], false, lmp);
|
|
double biga1_one = utils::numeric(FLERR, arg[4], false, lmp);
|
|
double beta_one = utils::numeric(FLERR, arg[5], false, lmp);
|
|
double r0_one = utils::numeric(FLERR, arg[6], false, lmp);
|
|
double cut_one = cut_global;
|
|
if (narg == 10) cut_one = utils::numeric(FLERR, arg[7], false, lmp);
|
|
|
|
int count = 0;
|
|
for (int i = ilo; i <= ihi; i++) {
|
|
for (int j = MAX(jlo, i); j <= jhi; j++) {
|
|
biga0[i][j] = biga0_one;
|
|
alpha[i][j] = alpha_one;
|
|
biga1[i][j] = biga1_one;
|
|
beta[i][j] = beta_one;
|
|
r0[i][j] = r0_one;
|
|
cut[i][j] = cut_one;
|
|
setflag[i][j] = 1;
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (count == 0) error->all(FLERR, "Incorrect args for pair coefficients");
|
|
}
|
|
|
|
Initialization
|
|
""""""""""""""
|
|
|
|
The ``init_one()`` function is called during the :doc:`"init" phase
|
|
<Developer_flow>` of a simulation. This is where potential parameters
|
|
are checked for completeness, derived parameters computed (e.g. the
|
|
"offset" of the potential energy at the cutoff distance for use with the
|
|
:doc:`pair_modify shift yes <pair_modify>` command). If a pair style
|
|
supports generating "mixed" parameters (i.e. where both atoms of a pair
|
|
have a different atom type) using a "mixing rule" from the parameters of
|
|
the type with itself, this is the place to compute and store those mixed
|
|
values. The *born/gauss* pair style does not support mixing, so we only
|
|
check for completeness. Another purpose of the ``init_one()`` function
|
|
is to symmetrize the potential parameter arrays. The return value of
|
|
the function is the cutoff for the given pair of atom types. This
|
|
information is used by the neighbor list code to determine the largest
|
|
cutoff and then build the neighbor lists accordingly.
|
|
|
|
.. code-block:: c++
|
|
|
|
/* ----------------------------------------------------------------------
|
|
init for one type pair i,j and corresponding j,i
|
|
------------------------------------------------------------------------- */
|
|
|
|
double PairBornGauss::init_one(int i, int j)
|
|
{
|
|
if (setflag[i][j] == 0) error->all(FLERR, "All pair coeffs are not set");
|
|
|
|
if (offset_flag) {
|
|
double dr = cut[i][j] - r0[i][j];
|
|
offset[i][j] =
|
|
biga0[i][j] * exp(-alpha[i][j] * cut[i][j]) - biga1[i][j] * exp(-beta[i][j] * dr * dr);
|
|
} else
|
|
offset[i][j] = 0.0;
|
|
|
|
biga0[j][i] = biga0[i][j];
|
|
alpha[j][i] = alpha[i][j];
|
|
biga1[j][i] = biga1[i][j];
|
|
beta[j][i] = beta[i][j];
|
|
r0[j][i] = r0[i][j];
|
|
offset[j][i] = offset[i][j];
|
|
|
|
return cut[i][j];
|
|
}
|
|
|
|
|
|
Computing forces from the neighbor list (required)
|
|
""""""""""""""""""""""""""""""""""""""""""""""""""
|
|
|
|
The ``compute()`` function is the "workhorse" of a pair style. This is where
|
|
we have the nested loops all pairs of particles from the neighbor list to
|
|
compute forces and - if needed - energy and virial.
|
|
|
|
The first part is to define some variables for later use and store
|
|
cached copies of data or pointers we need to access frequently. Also,
|
|
this is a good place to call ``Pair::ev_init()``, which initializes
|
|
several flags derived from the `eflag` and `vflag` parameters signaling
|
|
whether energy and virial need to be tallied and whether only globally
|
|
or also per-atom.
|
|
|
|
.. code-block:: c++
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void PairBornGauss::compute(int eflag, int vflag)
|
|
{
|
|
int i, j, ii, jj, inum, jnum, itype, jtype;
|
|
double xtmp, ytmp, ztmp, delx, dely, delz, evdwl, fpair;
|
|
double rsq, r, dr, aexp, bexp, factor_lj;
|
|
int *ilist, *jlist, *numneigh, **firstneigh;
|
|
|
|
evdwl = 0.0;
|
|
ev_init(eflag, vflag);
|
|
|
|
double **x = atom->x;
|
|
double **f = atom->f;
|
|
int *type = atom->type;
|
|
int nlocal = atom->nlocal;
|
|
double *special_lj = force->special_lj;
|
|
int newton_pair = force->newton_pair;
|
|
|
|
inum = list->inum;
|
|
ilist = list->ilist;
|
|
numneigh = list->numneigh;
|
|
firstneigh = list->firstneigh;
|
|
|
|
The outer loop (index *i*) is over local atoms of our sub-domain.
|
|
Typically, the value of `inum` (the number of neighbor lists) is the
|
|
same as the number of local atoms (= atoms *owned* but this sub-domain).
|
|
But when the pair style is used as a sub-style of a :doc:`hybrid pair
|
|
style <pair_hybrid>` or neighbor list entries are removed with
|
|
:doc:`neigh_modify exclude <neigh_modify>` this number may be
|
|
smaller. The array ``list->ilist`` has the (local) indices of the atoms
|
|
for which neighbor lists have been created. Then ``list->numneigh`` is
|
|
an `inum` sized array with the number of entries of each list of
|
|
neighbors and ``list->firstneigh`` is a list of pointers to those lists.
|
|
|
|
For efficiency reasons, cached copies of some properties of the outer
|
|
loop atoms are also initialized.
|
|
|
|
.. code-block:: c++
|
|
|
|
// loop over neighbors of my atoms
|
|
|
|
for (ii = 0; ii < inum; ii++) {
|
|
i = ilist[ii];
|
|
xtmp = x[i][0];
|
|
ytmp = x[i][1];
|
|
ztmp = x[i][2];
|
|
itype = type[i];
|
|
jlist = firstneigh[i];
|
|
jnum = numneigh[i];
|
|
|
|
The inner loop (index *j*) processes the neighbor lists. The neighbor
|
|
list code encodes in the upper 2 bits whether a pair is a regular pair
|
|
of neighbor (= 0) or a pair of 1-2 (= 1), 1-3 (= 2), or 1-4 (= 3)
|
|
:doc:`"special" neighbor <special_bonds>`. The ``sbmask()`` inline
|
|
function extracts those bits and converts them into a number. This
|
|
number is used to look up the corresponding scaling factor for the
|
|
non-bonded interaction from the ``force->special_lj`` array and to store
|
|
it in `factor_lj`. Due to adding the additional bits, the value of *j*
|
|
would be out of range when accessing data from per-atom arrays, so we
|
|
apply the NEIGHMASK constant to mask them out. This step *must* be done
|
|
even if a pair style does not use special bond scaling. Otherwise there
|
|
could be a segmentation fault.
|
|
|
|
With the corrected *j* index it is now possible to compute the distance of
|
|
the pair. For efficiency reasons, the square root is only taken *after*
|
|
the check for the cutoff (which has been stored as squared cutoff by the
|
|
``Pair`` base class). For some pair styles, like the 12-6 Lennard-Jones
|
|
potential, computing the square root can be avoided entirely.
|
|
|
|
.. code-block:: c++
|
|
|
|
for (jj = 0; jj < jnum; jj++) {
|
|
j = jlist[jj];
|
|
factor_lj = special_lj[sbmask(j)];
|
|
j &= NEIGHMASK;
|
|
|
|
delx = xtmp - x[j][0];
|
|
dely = ytmp - x[j][1];
|
|
delz = ztmp - x[j][2];
|
|
rsq = delx * delx + dely * dely + delz * delz;
|
|
jtype = type[j];
|
|
|
|
The following block of code is the actual application of the model
|
|
potential to compute the force. Note, that *fpair* is the pair-wise
|
|
force divided by the distance, as this simplifies the projection of the
|
|
x-, y-, and z-components of the force vector by simply multiplying with
|
|
the respective distances in those directions.
|
|
|
|
.. code-block:: c++
|
|
|
|
if (rsq < cutsq[itype][jtype]) {
|
|
r = sqrt(rsq);
|
|
dr = r - r0[itype][jtype];
|
|
aexp = biga0[itype][jtype] * exp(-alpha[itype][jtype] * r);
|
|
bexp = biga1[itype][jtype] * exp(-beta[itype][jtype] * dr * dr);
|
|
fpair = alpha[itype][jtype] * aexp;
|
|
fpair -= 2.0 * beta[itype][jtype] * dr * bexp;
|
|
fpair *= factor_lj / r;
|
|
|
|
In the next block, the force is added to the per-atom force arrays. This
|
|
pair style uses a "half" neighbor list (each pair is listed only once)
|
|
so we take advantage of the fact that :math:` \vec{F}_{ij} =
|
|
-\vec{F}_{ji}`, i.e. apply Newton's third law. The force is *always*
|
|
stored when the atom is a "local" atom. Index *i* atoms are always "local"
|
|
(i.e. *i* < nlocal); index *j* atoms may be "ghost" atoms (*j* >= nlocal).
|
|
|
|
Depending on the settings used with the :doc:`newton command <newton>`,
|
|
those pairs are are only listed once globally (newton_pair == 1), then
|
|
forces must be stored even with ghost atoms and after all forces
|
|
are computed a "reverse communication" is performed to add those ghost
|
|
atom forces to their corresponding local atoms. If the setting is disabled,
|
|
then the extra communication is skipped, since for pairs straddling
|
|
sub-domain boundaries, the forces are computed twice and only stored with
|
|
the local atoms in the domain that *own* it.
|
|
|
|
.. code-block:: c++
|
|
|
|
f[i][0] += delx * fpair;
|
|
f[i][1] += dely * fpair;
|
|
f[i][2] += delz * fpair;
|
|
if (newton_pair || j < nlocal) {
|
|
f[j][0] -= delx * fpair;
|
|
f[j][1] -= dely * fpair;
|
|
f[j][2] -= delz * fpair;
|
|
}
|
|
|
|
The ``ev_tally()`` function tallies global or per-atom energy and
|
|
virial. For typical MD simulations, the potential energy is merely a
|
|
diagnostic and only needed on output. Similarly, the pressure may only
|
|
be computed for (infrequent) thermodynamic output. For all timesteps
|
|
where this information is not needed either, `eflag` or `evflag` are
|
|
zero and the computation and call to the tally function skipped. Note
|
|
that evdwl is initialized to zero at the beginning of the function, so
|
|
that it still is valid to access it, even if the energy is not computed
|
|
(e.g. when only the virial is needed).
|
|
|
|
.. code-block:: c++
|
|
|
|
if (eflag) evdwl = factor_lj * (aexp - bexp - offset[itype][jtype]);
|
|
if (evflag) ev_tally(i, j, nlocal, newton_pair, evdwl, 0.0, fpair, delx, dely, delz);
|
|
}
|
|
}
|
|
}
|
|
|
|
If only the global virial is needed and no energy, then calls to
|
|
``ev_tally()`` can be avoided altogether and the global virial can be
|
|
computed more efficiently from the dot product of the total per-atom
|
|
force vector and the position vector of the corresponding atom,
|
|
:math:`\vec{F}\cdot\vec{r}`. This has to be done *after* all pair-wise
|
|
forces are computed and *before* the reverse communication to collect
|
|
data from ghost atom, since the position has to be the position that was
|
|
used to compute the force, i.e. *not* the "local" position if that ghost
|
|
atom is a periodic copy.
|
|
|
|
.. code-block:: c++
|
|
|
|
if (vflag_fdotr) virial_fdotr_compute();
|
|
}
|
|
|
|
|
|
Computing force and energy for a single pair
|
|
""""""""""""""""""""""""""""""""""""""""""""
|
|
|
|
Certain features in LAMMPS utilize computing interactions between
|
|
individual pairs of atoms only and the (optional) ``single()`` function
|
|
is needed to support those features (e.g. for tabulation of force and
|
|
energy with :doc:`pair_write <pair_write>`). This is a repetition of
|
|
the force kernel in the ``compute()`` function, but only for a single
|
|
pair of atoms, where the (squared) distance is provided as parameter
|
|
(so it may not even be an existing distance between two specific atoms).
|
|
The energy is returned as the return value of the function and the force
|
|
as the `fforce` reference. Note, that is, same as *fpair* in he
|
|
``compute()`` function, the magnitude of the force along the vector
|
|
between the two atoms *divided* by the distance.
|
|
|
|
The ``single()`` function is optional. The member variable
|
|
`single_enable` should be set to 0 in the constructor, if it is not
|
|
implemented (its default value is 1).
|
|
|
|
.. code-block:: c++
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
double PairBornGauss::single(int /*i*/, int /*j*/, int itype, int jtype, double rsq,
|
|
double /*factor_coul*/, double factor_lj, double &fforce)
|
|
{
|
|
double r, dr, aexp, bexp;
|
|
|
|
r = sqrt(rsq);
|
|
dr = r - r0[itype][jtype];
|
|
aexp = biga0[itype][jtype] * exp(-alpha[itype][jtype] * r);
|
|
bexp = biga1[itype][jtype] * exp(-beta[itype][jtype] * dr * dr);
|
|
|
|
fforce = factor_lj * (alpha[itype][jtype] * aexp - 2.0 * dr * beta[itype][jtype] * bexp) / r;
|
|
return factor_lj * (aexp - bexp - offset[itype][jtype]);
|
|
}
|
|
|
|
|
|
Reading and writing of restart files
|
|
""""""""""""""""""""""""""""""""""""
|
|
|
|
Support for writing and reading binary restart files is provided by the
|
|
following four functions. Writing is only done by MPI processor rank 0.
|
|
The output of global (not related to atom types) settings is delegated
|
|
to the ``write_restart_settings()`` function. Implementing the
|
|
functions to read and write binary restart files is optional. The
|
|
member variable `restartinfo` should be set to 0 in the constructor, if
|
|
they are not implemented (its default value is 1).
|
|
|
|
.. code-block:: c++
|
|
|
|
/* ----------------------------------------------------------------------
|
|
proc 0 writes to restart file
|
|
------------------------------------------------------------------------- */
|
|
|
|
void PairBornGauss::write_restart(FILE *fp)
|
|
{
|
|
write_restart_settings(fp);
|
|
|
|
int i, j;
|
|
for (i = 1; i <= atom->ntypes; i++) {
|
|
for (j = i; j <= atom->ntypes; j++) {
|
|
fwrite(&setflag[i][j], sizeof(int), 1, fp);
|
|
if (setflag[i][j]) {
|
|
fwrite(&biga0[i][j], sizeof(double), 1, fp);
|
|
fwrite(&alpha[i][j], sizeof(double), 1, fp);
|
|
fwrite(&biga1[i][j], sizeof(double), 1, fp);
|
|
fwrite(&beta[i][j], sizeof(double), 1, fp);
|
|
fwrite(&r0[i][j], sizeof(double), 1, fp);
|
|
fwrite(&cut[i][j], sizeof(double), 1, fp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
proc 0 writes to restart file
|
|
------------------------------------------------------------------------- */
|
|
|
|
void PairBornGauss::write_restart_settings(FILE *fp)
|
|
{
|
|
fwrite(&cut_global, sizeof(double), 1, fp);
|
|
fwrite(&offset_flag, sizeof(int), 1, fp);
|
|
fwrite(&mix_flag, sizeof(int), 1, fp);
|
|
}
|
|
|
|
Similarly, on reading, only MPI processor rank 0 has opened the restart
|
|
file and will read the data. The data is then distributed across all
|
|
parallel processes using calls to ``MPI_Bcast()``. Before reading atom
|
|
type specific data, the corresponding storage needs to be allocated.
|
|
Order and number or storage size of items read must be exactly the same
|
|
as when writing, or else the data will be read incorrectly.
|
|
|
|
Reading uses the :cpp:func:`utils::sfread <LAMMPS_NS::utils::sfread>`
|
|
utility function to detect read errors and short reads, so that LAMMPS
|
|
can abort if that happens, e.g. when the restart file is corrupted.
|
|
|
|
.. code-block:: c++
|
|
|
|
/* ----------------------------------------------------------------------
|
|
proc 0 reads from restart file, bcasts
|
|
------------------------------------------------------------------------- */
|
|
|
|
void PairBornGauss::read_restart(FILE *fp)
|
|
{
|
|
read_restart_settings(fp);
|
|
|
|
allocate();
|
|
|
|
int i, j;
|
|
int me = comm->me;
|
|
for (i = 1; i <= atom->ntypes; i++) {
|
|
for (j = i; j <= atom->ntypes; j++) {
|
|
if (me == 0) utils::sfread(FLERR, &setflag[i][j], sizeof(int), 1, fp, nullptr, error);
|
|
MPI_Bcast(&setflag[i][j], 1, MPI_INT, 0, world);
|
|
if (setflag[i][j]) {
|
|
if (me == 0) {
|
|
utils::sfread(FLERR, &biga0[i][j], sizeof(double), 1, fp, nullptr, error);
|
|
utils::sfread(FLERR, &alpha[i][j], sizeof(double), 1, fp, nullptr, error);
|
|
utils::sfread(FLERR, &biga1[i][j], sizeof(double), 1, fp, nullptr, error);
|
|
utils::sfread(FLERR, &beta[i][j], sizeof(double), 1, fp, nullptr, error);
|
|
utils::sfread(FLERR, &r0[i][j], sizeof(double), 1, fp, nullptr, error);
|
|
utils::sfread(FLERR, &cut[i][j], sizeof(double), 1, fp, nullptr, error);
|
|
}
|
|
MPI_Bcast(&biga0[i][j], 1, MPI_DOUBLE, 0, world);
|
|
MPI_Bcast(&alpha[i][j], 1, MPI_DOUBLE, 0, world);
|
|
MPI_Bcast(&biga1[i][j], 1, MPI_DOUBLE, 0, world);
|
|
MPI_Bcast(&beta[i][j], 1, MPI_DOUBLE, 0, world);
|
|
MPI_Bcast(&r0[i][j], 1, MPI_DOUBLE, 0, world);
|
|
MPI_Bcast(&cut[i][j], 1, MPI_DOUBLE, 0, world);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
proc 0 reads from restart file, bcasts
|
|
------------------------------------------------------------------------- */
|
|
|
|
void PairBornGauss::read_restart_settings(FILE *fp)
|
|
{
|
|
if (comm->me == 0) {
|
|
utils::sfread(FLERR, &cut_global, sizeof(double), 1, fp, nullptr, error);
|
|
utils::sfread(FLERR, &offset_flag, sizeof(int), 1, fp, nullptr, error);
|
|
utils::sfread(FLERR, &mix_flag, sizeof(int), 1, fp, nullptr, error);
|
|
}
|
|
MPI_Bcast(&cut_global, 1, MPI_DOUBLE, 0, world);
|
|
MPI_Bcast(&offset_flag, 1, MPI_INT, 0, world);
|
|
MPI_Bcast(&mix_flag, 1, MPI_INT, 0, world);
|
|
}
|
|
|
|
Writing coefficients to data files
|
|
""""""""""""""""""""""""""""""""""
|
|
|
|
The ``write_data()`` and ``write_data_all()`` functions are optional and
|
|
write out the current state of the :doc:`pair_coeff
|
|
settings<pair_coeff>` as "Pair Coeffs" or "PairIJ Coeffs" sections to a
|
|
data file when using the :doc:`write_data command <write_data>`. The
|
|
``write_data()`` only writes out the diagonal elements of the pair
|
|
coefficient matrix, as that is required for the format of the "Pair
|
|
Coeffs" section. It is called when the "pair" option of the
|
|
:doc:`write_data command <write_data>` is "ii" (the default). This is
|
|
suitable for force fields where *all* off-diagonal terms of the pair
|
|
coefficient matrix are generated from mixing. If explicit settings for
|
|
off-diagonal elements were made, LAMMPS will print a warning, as those
|
|
would be lost. To avoid this, the "pair ij" option of :doc:`write_data
|
|
<write_data>` can be used which will trigger calling the
|
|
``write_data_all()`` function instead, which will write out all settings
|
|
of the pair coefficient matrix (regardless of whether they were
|
|
originally created from mixing or not).
|
|
|
|
The member variable `writedata` should be set to 1 in the constructor,
|
|
if they are implemented (the default value is 0).
|
|
|
|
.. code-block:: c++
|
|
|
|
/* ----------------------------------------------------------------------
|
|
proc 0 writes to data file
|
|
------------------------------------------------------------------------- */
|
|
|
|
void PairBornGauss::write_data(FILE *fp)
|
|
{
|
|
for (int i = 1; i <= atom->ntypes; i++)
|
|
fprintf(fp, "%d %g %g %g %g %g\n", i, biga0[i][i], alpha[i][i], biga1[i][i], beta[i][i],
|
|
r0[i][i]);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
proc 0 writes all pairs to data file
|
|
------------------------------------------------------------------------- */
|
|
|
|
void PairBornGauss::write_data_all(FILE *fp)
|
|
{
|
|
for (int i = 1; i <= atom->ntypes; i++)
|
|
for (int j = i; j <= atom->ntypes; j++)
|
|
fprintf(fp, "%d %d %g %g %g %g %g %g\n", i, j, biga0[i][j], alpha[i][j], biga1[i][j],
|
|
beta[i][j], r0[i][j], cut[i][j]);
|
|
}
|
|
|
|
|
|
Give access to internal data
|
|
""""""""""""""""""""""""""""
|
|
|
|
The purpose of the ``extract()`` function is to allow access to internal data
|
|
of the pair style from other parts of LAMMPS. One application is to use
|
|
:doc:`fix adapt <fix_adapt>` to gradually change potential parameters during
|
|
a run. Here, we implement access to the pair coefficient matrix parameters.
|
|
|
|
.. code-block:: c++
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void *PairBornGauss::extract(const char *str, int &dim)
|
|
{
|
|
dim = 2;
|
|
if (strcmp(str, "biga0") == 0) return (void *) biga0;
|
|
if (strcmp(str, "biga1") == 0) return (void *) biga1;
|
|
if (strcmp(str, "r0") == 0) return (void *) r0;
|
|
return nullptr;
|
|
}
|
|
|
|
Since the mercury potential, for which we have implemented the
|
|
born/gauss pair style, has a temperature dependent parameter "biga1", we
|
|
can automatically adapt the potential based on the Taylor-MacLaurin
|
|
expansion for "biga1" when performing a simulation with a temperature
|
|
ramp. LAMMPS commands for that application are given below:
|
|
|
|
.. code-block:: LAMMPS
|
|
|
|
variable tlo index 300.0
|
|
variable thi index 600.0
|
|
variable temp equal ramp(v_tlo,v_thi)
|
|
variable biga1 equal (-2.58717e-8*v_temp+8.40841e-5)*v_temp+1.97475e-2
|
|
|
|
fix 1 all nvt temp ${tlo} ${thi} 0.1
|
|
fix 2 all adapt 1 pair born/gauss biga1 * * v_biga1
|
|
|
|
Case 2: a many-body potential
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Since there is a detailed description of the purpose and general layout
|
|
of a pair style in the previous case, we will focus on where the
|
|
implementation of a typical many-body potential *differs* from a
|
|
pair-wise additive potential. We will use the implementation of the
|
|
Tersoff potential as :doc:`pair_style tersoff <pair_tersoff>` as an
|
|
example.
|
|
|
|
Constructor
|
|
"""""""""""
|
|
|
|
In the constructor several :doc:`pair style flags <Modify_pair>` must
|
|
be set differently for many-body potentials:
|
|
|
|
- the potential is not pair-wise additive, so the ``single()`` function
|
|
cannot be used. This is indicated by setting the `single_enable`
|
|
member variable to 0 (default value is 1)
|
|
- the for many-body potentials are usually not written to :doc:`binary
|
|
restart files <write_restart>`. This is indicated by setting the member
|
|
variable `restartinfo` to 0 (default is 1)
|
|
- many-body potentials typically read *all* parameters from a file which
|
|
stores parameters indexed with a string (e.g. the element). For this
|
|
only a single :doc:`pair_coeff \* \* <pair_coeff>` command is allowed.
|
|
This requirement is set and checked for, when the member variable
|
|
`one_coeff` is set to 1 (default value is 0)
|
|
- many-body potentials can produce incorrect results if pairs of atoms
|
|
are excluded from the neighbor list, e.g. explicitly by
|
|
:doc:`neigh_modify exclude <neigh_modify>` or implicitly through
|
|
defining bonds, angles, etc. and having a :doc:`special_bonds setting
|
|
<special_bonds>` that is not "special_bonds lj/coul 1.0 1.0 1.0".
|
|
LAMMPS will check for this and print a suitable warning, when the
|
|
member variable `manybody_flag` is set to 1 (default value is 0).
|
|
|
|
.. code-block:: c++
|
|
|
|
PairTersoff::PairTersoff(LAMMPS *lmp) : Pair(lmp)
|
|
{
|
|
single_enable = 0;
|
|
restartinfo = 0;
|
|
one_coeff = 1;
|
|
manybody_flag = 1;
|
|
|
|
Neighbor list request
|
|
"""""""""""""""""""""
|
|
|
|
For computing the three-body interactions of the Tersoff potential a
|
|
"full" neighbor list (both atoms of a pair are listed in each other's
|
|
neighbor list) is required. By default a "half" neighbor list is
|
|
requested (each pair is listed only once) in. The request is made in
|
|
the ``init_style()`` function. A more in-depth discussion of neighbor
|
|
lists in LAMMPS and how to request them is in :ref:`this section of the
|
|
documentation <request-neighbor-list>`
|
|
|
|
Also, additional conditions must be met for some global settings and
|
|
this is being checked for in the ``init_style()`` function, too.
|
|
|
|
.. code-block:: c++
|
|
|
|
/* ----------------------------------------------------------------------
|
|
init specific to this pair style
|
|
------------------------------------------------------------------------- */
|
|
|
|
void PairTersoff::init_style()
|
|
{
|
|
if (atom->tag_enable == 0)
|
|
error->all(FLERR,"Pair style Tersoff requires atom IDs");
|
|
if (force->newton_pair == 0)
|
|
error->all(FLERR,"Pair style Tersoff requires newton pair on");
|
|
|
|
// need a full neighbor list
|
|
|
|
neighbor->add_request(this,NeighConst::REQ_FULL);
|
|
}
|
|
|
|
Computing forces from the neighbor list
|
|
"""""""""""""""""""""""""""""""""""""""
|
|
|
|
Computing forces for a many-body potential is usually more complex than
|
|
for a pair-wise additive potential and there are multiple component.
|
|
For Tersoff, there is a pair-wise additive two-body term (two nested
|
|
loops over indices *i* and *j*) and a three-body term (three nested
|
|
loops over indices *i*, *j*, and *k*). Since the neighbor list has
|
|
all neighbors up to the maximum cutoff (for the two-body term), but
|
|
the three-body interactions have a significantly shorter cutoff, also
|
|
a "short neighbor list" is constructed at the same time while computing
|
|
the two-body and looping over the neighbor list for the first time.
|
|
|
|
.. code-block:: c++
|
|
|
|
if (rsq < cutshortsq) {
|
|
neighshort[numshort++] = j;
|
|
if (numshort >= maxshort) {
|
|
maxshort += maxshort/2;
|
|
memory->grow(neighshort,maxshort,"pair:neighshort");
|
|
}
|
|
}
|
|
|
|
For the two-body term, only a half neighbor list would be needed, even
|
|
though we have requested a full list (for the three-body loops).
|
|
Rather than computing all interactions twice, we skip over half of
|
|
the entries. This is done in slightly complex way to make certain
|
|
the same choice is made across all subdomains and so that there is
|
|
no load imbalance introduced.
|
|
|
|
.. code-block:: c++
|
|
|
|
jtag = tag[j];
|
|
if (itag > jtag) {
|
|
if ((itag+jtag) % 2 == 0) continue;
|
|
} else if (itag < jtag) {
|
|
if ((itag+jtag) % 2 == 1) continue;
|
|
} else {
|
|
if (x[j][2] < x[i][2]) continue;
|
|
if (x[j][2] == ztmp && x[j][1] < ytmp) continue;
|
|
if (x[j][2] == ztmp && x[j][1] == ytmp && x[j][0] < xtmp) continue;
|
|
}
|
|
|
|
For the three-body term, there is one additional nested loop and it uses
|
|
the "short" neighbor list, accumulated previously.
|
|
|
|
.. code-block:: c++
|
|
|
|
// three-body interactions
|
|
// skip immediately if I-J is not within cutoff
|
|
double fjxtmp,fjytmp,fjztmp;
|
|
|
|
for (jj = 0; jj < numshort; jj++) {
|
|
j = neighshort[jj];
|
|
jtype = map[type[j]];
|
|
|
|
[...]
|
|
|
|
for (kk = 0; kk < numshort; kk++) {
|
|
if (jj == kk) continue;
|
|
k = neighshort[kk];
|
|
ktype = map[type[k]];
|
|
|
|
[...]
|
|
}
|
|
[...]
|
|
|
|
|
|
Reading potential parameters
|
|
""""""""""""""""""""""""""""
|
|
|
|
For the Tersoff potential, the parameters are listed in a file and associated
|
|
with triples for elements. Thus, the ``coeff()`` function has to do three
|
|
tasks, each of which is delegated to a function:
|
|
|
|
#. map elements to atom types. Those follow the potential file name in the
|
|
command line arguments and are processed by the ``map_element2type()`` function.
|
|
#. read and parse the potential parameter file in the ``read_file()`` function.
|
|
#. Build data structures where the original and derived parameters are
|
|
indexed by all possible triples of atom types and thus can be looked
|
|
up quickly in the loops for the force computation
|
|
|
|
.. code-block:: c++
|
|
|
|
void PairTersoff::coeff(int narg, char **arg)
|
|
{
|
|
if (!allocated) allocate();
|
|
|
|
map_element2type(narg-3,arg+3);
|
|
|
|
// read potential file and initialize potential parameters
|
|
|
|
read_file(arg[2]);
|
|
setup_params();
|
|
}
|
|
|
|
|
|
Case 3: a potential requiring communication
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
For some models, the interactions between atoms depends on properties of
|
|
their environment which have to be computed *before* the the forces can
|
|
be computed. Since LAMMPS is designed to run in parallel using a
|
|
:doc:`domain decomposition strategy <Developer_par_part>`, not all
|
|
information of the atoms may be directly available and thus
|
|
communication steps may be need to collect data from ghost atoms of
|
|
neighboring subdomains or send data to ghost atoms for application
|
|
during the pairwise computation. For this we will look at how the
|
|
embedding term of the :doc:`embedded atom potential EAM <pair_eam>` is
|
|
implemented in LAMMPS.
|
|
|
|
Allocating additional per-atom storage
|
|
""""""""""""""""""""""""""""""""""""""
|
|
|
|
First suitable (local) per-atom arrays (`rho`, `fp`, `numforce`) are
|
|
allocated. These have to be large enough to include ghost atoms, are not
|
|
used outside the ``compute()`` function and are re-initialized to zero
|
|
once per timestep.
|
|
|
|
.. code-block:: c++
|
|
|
|
if (atom->nmax > nmax) {
|
|
memory->destroy(rho);
|
|
memory->destroy(fp);
|
|
memory->destroy(numforce);
|
|
nmax = atom->nmax;
|
|
memory->create(rho,nmax,"pair:rho");
|
|
memory->create(fp,nmax,"pair:fp");
|
|
memory->create(numforce,nmax,"pair:numforce");
|
|
}
|
|
|
|
Reverse communication
|
|
"""""""""""""""""""""
|
|
|
|
Then a first loop over all pairs (*i* and *j*) is performed, where data
|
|
is stored in the `rho` representing the electron density at the site of
|
|
*i* contributed from all neighbors *j*. Since the EAM pair style uses
|
|
a half neighbor list (for efficiency reasons), a reverse communication is
|
|
needed to collect the contributions to `rho` from ghost atoms (only if
|
|
:doc:`newton on <newton>` is set for pair styles).
|
|
|
|
.. code-block:: c++
|
|
|
|
if (newton_pair) comm->reverse_comm(this);
|
|
|
|
To support the reverse communication two functions must be defined:
|
|
``pack_reverse_comm()`` that copy relevant data into a buffer for ghost
|
|
atoms and ``unpac_reverse_comm()`` and take the collected data and add
|
|
it to the `rho` array for the corresponding local atoms that match the
|
|
ghost atoms. In order to allocate sufficiently sized buffers, a flag
|
|
must be set in the pair style constructor. Since in this case a single
|
|
double precision number is communicated per atom, the `comm_reverse`
|
|
member variable is set to 1 (default is 0 = no reverse communication).
|
|
|
|
.. code-block:: c++
|
|
|
|
int PairEAM::pack_reverse_comm(int n, int first, double *buf)
|
|
{
|
|
int i,m,last;
|
|
|
|
m = 0;
|
|
last = first + n;
|
|
for (i = first; i < last; i++) buf[m++] = rho[i];
|
|
return m;
|
|
}
|
|
|
|
void PairEAM::unpack_reverse_comm(int n, int *list, double *buf)
|
|
{
|
|
int i,j,m;
|
|
|
|
m = 0;
|
|
for (i = 0; i < n; i++) {
|
|
j = list[i];
|
|
rho[j] += buf[m++];
|
|
}
|
|
}
|
|
|
|
Forward communication
|
|
"""""""""""""""""""""
|
|
|
|
From the density array `rho`, the derivative of the embedding energy
|
|
`fp` is computed. The computation is only done for "local" atoms, but
|
|
for the force computation, that property also is needed on ghost atoms.
|
|
For that a forward communication is needed.
|
|
|
|
.. code-block:: c++
|
|
|
|
comm->forward_comm(this);
|
|
|
|
Similar to the reverse communication, this requires to implement a
|
|
``pack_forward_comm()`` and an ``unpack_forward_comm()`` function.
|
|
Since there is one double precision number per atom that needs to be
|
|
communicated, we must set the `comm_forward` member variable to 1
|
|
(default is 0 = no forward communication).
|
|
|
|
.. code-block:: c++
|
|
|
|
int PairEAM::pack_forward_comm(int n, int *list, double *buf, int pbc_flag, int *pbc)
|
|
{
|
|
int i,j,m;
|
|
|
|
m = 0;
|
|
for (i = 0; i < n; i++) {
|
|
j = list[i];
|
|
buf[m++] = fp[j];
|
|
}
|
|
return m;
|
|
}
|
|
|
|
void PairEAM::unpack_forward_comm(int n, int first, double *buf)
|
|
{
|
|
int i,m,last;
|
|
|
|
m = 0;
|
|
last = first + n;
|
|
for (i = first; i < last; i++) fp[i] = buf[m++];
|
|
}
|
|
|
|
--------------
|
|
|
|
.. _Bomont2:
|
|
|
|
**(Bomont)** Bomont, Bretonnet, J. Chem. Phys. 124, 054504 (2006)
|