Files
lammps/src/USER-MISC/fix_orient_eco.cpp
2020-06-09 15:14:49 -04:00

587 lines
22 KiB
C++

/* ----------------------------------------------------------------------
LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
http://lammps.sandia.gov, Sandia National Laboratdir_veces
Steve Plimpton, sjplimp@sandia.gov
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 authors: Adrian A. Schratt and Volker Mohles (ICAMS)
------------------------------------------------------------------------- */
#include "fix_orient_eco.h"
#include <mpi.h>
#include <cmath>
#include <cstring>
#include "atom.h"
#include "citeme.h"
#include "comm.h"
#include "domain.h"
#include "error.h"
#include "force.h"
#include "math_const.h"
#include "memory.h"
#include "neigh_list.h"
#include "neigh_request.h"
#include "neighbor.h"
#include "pair.h"
#include "respa.h"
#include "update.h"
#include "utils.h"
#include "fmt/format.h"
using namespace LAMMPS_NS;
using namespace FixConst;
using namespace MathConst;
static const char cite_fix_orient_eco[] =
"fix orient/eco command:\n\n"
"@Article{Schratt20,\n"
" author = {A. A. Schratt, V. Mohles},\n"
" title = {Efficient calculation of the ECO driving force for atomistic simulations of grain boundary motion},\n"
" journal = {Computational Materials Science},\n"
" volume = {182},\n"
" year = {2020},\n"
" pages = {109774},\n"
" doi = {j.commatsci.2020.109774},\n"
" url = {https://doi.org/10.1016/j.commatsci.2020.109774}\n"
"}\n\n";
struct FixOrientECO::Nbr {
double duchi; // potential derivative
double real_phi[2][3]; // real part of wave function
double imag_phi[2][3]; // imaginary part of wave function
};
/* ---------------------------------------------------------------------- */
FixOrientECO::FixOrientECO(LAMMPS *lmp, int narg, char **arg) :
Fix(lmp, narg, arg),
dir_filename(NULL), order(NULL), nbr(NULL), list(NULL)
{
if (lmp->citeme) lmp->citeme->add(cite_fix_orient_eco);
// get rank of this processor
MPI_Comm_rank(world, &me);
// check for illegal command
if (narg != 7) error->all(FLERR, "Illegal fix orient/eco command");
// set fix flags
scalar_flag = 1; // computes scalar
global_freq = 1; // values can be computed at every timestep
extscalar = 1; // scalar scales with # of atoms
peratom_flag = 1; // quantities are per atom quantities
size_peratom_cols = 2; // # of per atom quantities
peratom_freq = 1; //
// parse input parameters
u_0 = force->numeric(FLERR, arg[3]);
sign = (u_0 >= 0.0 ? 1 : -1);
eta = force->numeric(FLERR, arg[4]);
r_cut = force->numeric(FLERR, arg[5]);
// read reference orientations from file
// work on rank 0 only
int n = strlen(arg[6]) + 1;
dir_filename = new char[n];
strcpy(dir_filename, arg[6]);
if (me == 0) {
char line[IMGMAX];
char *result;
int count;
FILE *infile = force->open_potential(dir_filename);
if (infile == NULL)
error->one(FLERR,fmt::format("Cannot open fix orient/eco file {}: {}",
dir_filename, utils::getsyserror()));
for (int i = 0; i < 6; ++i) {
result = fgets(line, IMGMAX, infile);
if (!result) error->one(FLERR, "Fix orient/eco file read failed");
count = sscanf(line, "%lg %lg %lg", &dir_vec[i][0], &dir_vec[i][1], &dir_vec[i][2]);
if (count != 3) error->one(FLERR, "Fix orient/eco file read failed");
}
fclose(infile);
// calculate reciprocal lattice vectors
get_reciprocal();
squared_cutoff = r_cut * r_cut;
inv_squared_cutoff = 1.0 / squared_cutoff;
half_u = 0.5 * u_0;
inv_eta = 1.0 / eta;
}
// initializations
MPI_Bcast(&dir_vec[0][0], 18, MPI_DOUBLE, 0, world); // communicate direct lattice vectors
MPI_Bcast(&reciprocal_vectors[0][0][0], 18, MPI_DOUBLE, 0, world); // communicate reciprocal vectors
MPI_Bcast(&squared_cutoff, 1, MPI_DOUBLE, 0, world); // communicate squared cutoff radius
MPI_Bcast(&inv_squared_cutoff, 1, MPI_DOUBLE, 0, world); // communicate inverse squared cutoff radius
MPI_Bcast(&half_u, 1, MPI_DOUBLE, 0, world); // communicate half potential energy
MPI_Bcast(&inv_eta, 1, MPI_DOUBLE, 0, world); // communicate inverse threshold
// set comm size needed by this Fix
if (u_0 != 0) comm_forward = sizeof(Nbr) / sizeof(double);
added_energy = 0.0; // initial energy
nmax = atom->nmax;
nbr = (Nbr *) memory->smalloc(nmax * sizeof(Nbr), "orient/eco:nbr");
memory->create(order, nmax, 2, "orient/eco:order");
array_atom = order;
// zero the array since a variable may access it before first run
for (int i = 0; i < atom->nlocal; ++i) order[i][0] = order[i][1] = 0.0;
}
/* ---------------------------------------------------------------------- */
FixOrientECO::~FixOrientECO() {
memory->destroy(order);
memory->sfree(nbr);
delete[] dir_filename;
}
/* ---------------------------------------------------------------------- */
int FixOrientECO::setmask() {
int mask = 0;
mask |= POST_FORCE;
mask |= THERMO_ENERGY;
mask |= POST_FORCE_RESPA;
return mask;
}
/* ---------------------------------------------------------------------- */
void FixOrientECO::init() {
// get this processors rank
MPI_Comm_rank(world, &me);
// compute normalization factor
int neigh = get_norm();
if (me == 0) {
utils::logmesg(lmp,fmt::format(" fix orient/eco: cutoff={} norm_fac={} "
"neighbors={}\n", r_cut, norm_fac, neigh));
}
inv_norm_fac = 1.0 / norm_fac;
// check parameters
if (r_cut > force->pair->cutforce) {
error->all(FLERR, "Cutoff radius used by fix orient/eco must be smaller than force cutoff");
}
// communicate normalization factor
MPI_Bcast(&norm_fac, 1, MPI_DOUBLE, 0, world);
MPI_Bcast(&inv_norm_fac, 1, MPI_DOUBLE, 0, world);
if (strstr(update->integrate_style, "respa")) {
ilevel_respa = ((Respa *) update->integrate)->nlevels - 1;
if (respa_level >= 0) ilevel_respa = MIN(respa_level, ilevel_respa);
}
// need a full neighbor list
// perpetual list, built whenever re-neighboring occurs
int irequest = neighbor->request(this, instance_me);
neighbor->requests[irequest]->pair = 0;
neighbor->requests[irequest]->fix = 1;
neighbor->requests[irequest]->half = 0;
neighbor->requests[irequest]->full = 1;
}
/* ---------------------------------------------------------------------- */
void FixOrientECO::init_list(int /* id */, NeighList *ptr) {
list = ptr;
}
/* ---------------------------------------------------------------------- */
void FixOrientECO::setup(int vflag) {
if (strstr(update->integrate_style, "verlet"))
post_force(vflag);
else {
((Respa *) update->integrate)->copy_flevel_f(ilevel_respa);
post_force_respa(vflag,ilevel_respa, 0);
((Respa *) update->integrate)->copy_f_flevel(ilevel_respa);
}
}
/* ---------------------------------------------------------------------- */
void FixOrientECO::post_force(int /* vflag */) {
// set local variables
int ii, i, jj, j; // loop variables and atom IDs
int k; // variable to loop over 3 reciprocal directions
int lambda; // variable to loop over 2 crystals
int dim; // variable to loop over 3 spatial components
double dx, dy, dz; // stores current interatomic vector
double squared_distance; // stores current squared distance
double chi; // stores current order parameter
double weight; // stores current weight function
double scalar_product; // stores current scalar product
double omega; // phase of sine transition
double omega_pre = MY_PI2 * inv_eta; // prefactor for omega
double duchi_pre = half_u * MY_PI * inv_eta * inv_norm_fac; // prefactor for duchi
double sin_om; // stores the value of the sine transition
// initialize added energy at this step
added_energy = 0.0;
// set local pointers and neighbor lists
double **x = atom->x;
double **f = atom->f;
int *mask = atom->mask;
int nall = atom->nlocal + atom->nghost;
int inum = list->inum;
int jnum;
int *ilist = list->ilist;
int *jlist;
int *numneigh = list->numneigh;
int **firstneigh = list->firstneigh;
// insure nbr and order data structures are adequate size
if (nall > nmax) {
nmax = nall;
memory->destroy(nbr);
memory->destroy(order);
nbr = (Nbr *) memory->smalloc(nmax * sizeof(Nbr), "orient/eco:nbr");
memory->create(order, nmax, 2, "orient/eco:order");
array_atom = order;
}
// loop over owned atoms and compute order parameter
for (ii = 0; ii < inum; ++ii) {
i = ilist[ii];
jlist = firstneigh[i];
jnum = numneigh[i];
// initializations
chi = 0.0;
for (k = 0; k < 3; ++k) {
nbr[i].real_phi[0][k] = nbr[i].real_phi[1][k] = 0.0;
nbr[i].imag_phi[0][k] = nbr[i].imag_phi[1][k] = 0.0;
}
// loop over all neighbors of atom i
for (jj = 0; jj < jnum; ++jj) {
j = jlist[jj];
j &= NEIGHMASK;
// vector difference
dx = x[i][0] - x[j][0];
dy = x[i][1] - x[j][1];
dz = x[i][2] - x[j][2];
squared_distance = dx * dx + dy * dy + dz * dz;
if (squared_distance < squared_cutoff) {
squared_distance *= inv_squared_cutoff;
weight = squared_distance * (squared_distance - 2.0) + 1.0;
for (lambda = 0; lambda < 2; ++lambda) {
for (k = 0; k < 3; ++k) {
scalar_product = reciprocal_vectors[lambda][k][0] * dx + reciprocal_vectors[lambda][k][1] * dy + reciprocal_vectors[lambda][k][2] * dz;
nbr[i].real_phi[lambda][k] += weight * cos(scalar_product);
nbr[i].imag_phi[lambda][k] += weight * sin(scalar_product);
}
}
}
}
// collect contributions
for (k = 0; k < 3; ++k) {
chi += (nbr[i].real_phi[0][k] * nbr[i].real_phi[0][k] + nbr[i].imag_phi[0][k] * nbr[i].imag_phi[0][k] -
nbr[i].real_phi[1][k] * nbr[i].real_phi[1][k] - nbr[i].imag_phi[1][k] * nbr[i].imag_phi[1][k]);
}
chi *= inv_norm_fac;
order[i][0] = chi;
// compute normalized order parameter
// and potential energy
if (chi > eta) {
added_energy += half_u;
nbr[i].duchi = 0.0;
order[i][1] = sign;
} else if (chi < -eta) {
added_energy -= half_u;
nbr[i].duchi = 0.0;
order[i][1] = -sign;
} else {
omega = omega_pre * chi;
sin_om = sin(omega);
added_energy += half_u * sin_om;
nbr[i].duchi = duchi_pre * cos(omega);
order[i][1] = sign * sin_om;
}
// compute product with potential derivative
for (k = 0; k < 3; ++k) {
for (lambda = 0; lambda < 2; ++lambda) {
nbr[i].real_phi[lambda][k] *= nbr[i].duchi;
nbr[i].imag_phi[lambda][k] *= nbr[i].duchi;
}
}
}
// set additional local variables
double gradient_ii_cos[2][3][3]; // gradient ii cosine term
double gradient_ii_sin[2][3][3]; // gradient ii sine term
double gradient_ij_vec[2][3][3]; // gradient ij vector term
double gradient_ij_sca[2][3]; // gradient ij scalar term
double weight_gradient_prefactor; // gradient prefactor
double weight_gradient[3]; // gradient of weight
double cos_scalar_product; // cosine of scalar product
double sin_scalar_product; // sine of scalar product
double gcos_scalar_product; // gradient weight function * cosine of scalar product
double gsin_scalar_product; // gradient weight function * sine of scalar product
// compute force only if synthetic
// potential is not zero
if (u_0 != 0.0) {
// communicate to acquire nbr data for ghost atoms
comm->forward_comm_fix(this);
// loop over all atoms
for (ii = 0; ii < inum; ++ii) {
i = ilist[ii];
jlist = firstneigh[i];
jnum = numneigh[i];
const bool no_boundary_atom = (nbr[i].duchi == 0.0);
// skip atoms not in group
if (!(mask[i] & groupbit)) continue;
// initializations
for (k = 0; k < 3; ++k) {
for (lambda = 0; lambda < 2; ++lambda) {
for (dim = 0; dim < 3; ++dim) {
gradient_ii_cos[lambda][k][dim] = 0.0;
gradient_ii_sin[lambda][k][dim] = 0.0;
gradient_ij_vec[lambda][k][dim] = 0.0;
}
gradient_ij_sca[lambda][k] = 0.0;
}
}
// loop over all neighbors of atom i
// for those within squared_cutoff, compute force
for (jj = 0; jj < jnum; ++jj) {
j = jlist[jj];
j &= NEIGHMASK;
// do not compute force on atom i if it is far from boundary
if ((nbr[j].duchi == 0.0) && no_boundary_atom) continue;
// vector difference
dx = x[i][0] - x[j][0];
dy = x[i][1] - x[j][1];
dz = x[i][2] - x[j][2];
squared_distance = dx * dx + dy * dy + dz * dz;
if (squared_distance < squared_cutoff) {
// compute force on atom i
// need weight and its gradient
squared_distance *= inv_squared_cutoff;
weight = squared_distance * (squared_distance - 2.0) + 1.0;
weight_gradient_prefactor = 4.0 * (squared_distance - 1.0) * inv_squared_cutoff;
weight_gradient[0] = weight_gradient_prefactor * dx;
weight_gradient[1] = weight_gradient_prefactor * dy;
weight_gradient[2] = weight_gradient_prefactor * dz;
// (1) compute scalar product and sine and cosine of it
// (2) compute product of sine and cosine with gradient of weight function
// (3) compute gradient_ii_cos and gradient_ii_sin by summing up result of (2)
// (4) compute gradient_ij_vec and gradient_ij_sca
for (lambda = 0; lambda < 2; ++lambda) {
for (k = 0; k < 3; ++k) {
scalar_product = reciprocal_vectors[lambda][k][0] * dx + reciprocal_vectors[lambda][k][1] * dy + reciprocal_vectors[lambda][k][2] * dz;
cos_scalar_product = cos(scalar_product);
sin_scalar_product = sin(scalar_product);
for (dim = 0; dim < 3; ++dim) {
gradient_ii_cos[lambda][k][dim] += (gcos_scalar_product = weight_gradient[dim] * cos_scalar_product);
gradient_ii_sin[lambda][k][dim] += (gsin_scalar_product = weight_gradient[dim] * sin_scalar_product);
gradient_ij_vec[lambda][k][dim] += (nbr[j].real_phi[lambda][k] * gcos_scalar_product - nbr[j].imag_phi[lambda][k] * gsin_scalar_product);
}
gradient_ij_sca[lambda][k] += weight * (nbr[j].real_phi[lambda][k] * sin_scalar_product + nbr[j].imag_phi[lambda][k] * cos_scalar_product);
}
}
}
}
// sum contributions
for (k = 0; k < 3; ++k) {
for (dim = 0; dim < 3; ++dim) {
f[i][dim] -= (nbr[i].real_phi[0][k] * gradient_ii_cos[0][k][dim] + nbr[i].imag_phi[0][k] * gradient_ii_sin[0][k][dim] + gradient_ij_vec[0][k][dim] + reciprocal_vectors[1][k][dim] * gradient_ij_sca[1][k]);
f[i][dim] += (nbr[i].real_phi[1][k] * gradient_ii_cos[1][k][dim] + nbr[i].imag_phi[1][k] * gradient_ii_sin[1][k][dim] + gradient_ij_vec[1][k][dim] + reciprocal_vectors[0][k][dim] * gradient_ij_sca[0][k]);
}
}
}
}
}
/* ---------------------------------------------------------------------- */
void FixOrientECO::post_force_respa(int vflag, int ilevel, int /* iloop */) {
if (ilevel == ilevel_respa) post_force(vflag);
}
/* ---------------------------------------------------------------------- */
double FixOrientECO::compute_scalar() {
double added_energy_total;
MPI_Allreduce(&added_energy, &added_energy_total, 1, MPI_DOUBLE, MPI_SUM, world);
return added_energy_total;
}
/* ---------------------------------------------------------------------- */
int FixOrientECO::pack_forward_comm(int n, int *list, double *buf, int /*pbc_flag*/, int */*pbc*/) {
int i, j;
int m = 0;
for (i = 0; i < n; ++i) {
j = list[i];
*((Nbr*)&buf[m]) = nbr[j];
m += sizeof(Nbr)/sizeof(double);
}
return m;
}
/* ---------------------------------------------------------------------- */
void FixOrientECO::unpack_forward_comm(int n, int first, double *buf) {
int i;
int last = first + n;
int m = 0;
for (i = first; i < last; ++i) {
nbr[i] = *((Nbr*) &buf[m]);
m += sizeof(Nbr) / sizeof(double);
}
}
/* ----------------------------------------------------------------------
memory usage of local atom-based arrays
------------------------------------------------------------------------- */
double FixOrientECO::memory_usage() {
double bytes = nmax * sizeof(Nbr);
bytes += 2 * nmax * sizeof(double);
return bytes;
}
/* ----------------------------------------------------------------------
reciprocal lattice vectors from file input
------------------------------------------------------------------------- */
void FixOrientECO::get_reciprocal() {
// volume of unit cell A
double vol = 0.5 * (dir_vec[0][0] * (dir_vec[1][1] * dir_vec[2][2] - dir_vec[2][1] * dir_vec[1][2]) + dir_vec[1][0] * (dir_vec[2][1] * dir_vec[0][2] - dir_vec[0][1] * dir_vec[2][2]) + dir_vec[2][0] * (dir_vec[0][1] * dir_vec[1][2] - dir_vec[1][1] * dir_vec[0][2])) / MY_PI;
double i_vol = 1.0 / vol;
// grain A: reciprocal_vectors 0
reciprocal_vectors[0][0][0] = (dir_vec[1][1] * dir_vec[2][2] - dir_vec[2][1] * dir_vec[1][2]) * i_vol;
reciprocal_vectors[0][0][1] = (dir_vec[1][2] * dir_vec[2][0] - dir_vec[2][2] * dir_vec[1][0]) * i_vol;
reciprocal_vectors[0][0][2] = (dir_vec[1][0] * dir_vec[2][1] - dir_vec[2][0] * dir_vec[1][1]) * i_vol;
// grain A: reciprocal_vectors 1
reciprocal_vectors[0][1][0] = (dir_vec[2][1] * dir_vec[0][2] - dir_vec[0][1] * dir_vec[2][2]) * i_vol;
reciprocal_vectors[0][1][1] = (dir_vec[2][2] * dir_vec[0][0] - dir_vec[0][2] * dir_vec[2][0]) * i_vol;
reciprocal_vectors[0][1][2] = (dir_vec[2][0] * dir_vec[0][1] - dir_vec[0][0] * dir_vec[2][1]) * i_vol;
// grain A: reciprocal_vectors 2
reciprocal_vectors[0][2][0] = (dir_vec[0][1] * dir_vec[1][2] - dir_vec[1][1] * dir_vec[0][2]) * i_vol;
reciprocal_vectors[0][2][1] = (dir_vec[0][2] * dir_vec[1][0] - dir_vec[1][2] * dir_vec[0][0]) * i_vol;
reciprocal_vectors[0][2][2] = (dir_vec[0][0] * dir_vec[1][1] - dir_vec[1][0] * dir_vec[0][1]) * i_vol;
// volume of unit cell B
vol = 0.5 * (dir_vec[3][0] * (dir_vec[4][1] * dir_vec[5][2] - dir_vec[5][1] * dir_vec[4][2]) + dir_vec[4][0] * (dir_vec[5][1] * dir_vec[3][2] - dir_vec[3][1] * dir_vec[5][2]) + dir_vec[5][0] * (dir_vec[3][1] * dir_vec[4][2] - dir_vec[4][1] * dir_vec[3][2])) / MY_PI;
i_vol = 1.0 / vol;
// grain B: reciprocal_vectors 0
reciprocal_vectors[1][0][0] = (dir_vec[4][1] * dir_vec[5][2] - dir_vec[5][1] * dir_vec[4][2]) * i_vol;
reciprocal_vectors[1][0][1] = (dir_vec[4][2] * dir_vec[5][0] - dir_vec[5][2] * dir_vec[4][0]) * i_vol;
reciprocal_vectors[1][0][2] = (dir_vec[4][0] * dir_vec[5][1] - dir_vec[5][0] * dir_vec[4][1]) * i_vol;
// grain B: reciprocal_vectors 1
reciprocal_vectors[1][1][0] = (dir_vec[5][1] * dir_vec[3][2] - dir_vec[3][1] * dir_vec[5][2]) * i_vol;
reciprocal_vectors[1][1][1] = (dir_vec[5][2] * dir_vec[3][0] - dir_vec[3][2] * dir_vec[5][0]) * i_vol;
reciprocal_vectors[1][1][2] = (dir_vec[5][0] * dir_vec[3][1] - dir_vec[3][0] * dir_vec[5][1]) * i_vol;
// grain B: reciprocal_vectors 2
reciprocal_vectors[1][2][0] = (dir_vec[3][1] * dir_vec[4][2] - dir_vec[4][1] * dir_vec[3][2]) * i_vol;
reciprocal_vectors[1][2][1] = (dir_vec[3][2] * dir_vec[4][0] - dir_vec[4][2] * dir_vec[3][0]) * i_vol;
reciprocal_vectors[1][2][2] = (dir_vec[3][0] * dir_vec[4][1] - dir_vec[4][0] * dir_vec[3][1]) * i_vol;
}
/* ----------------------------------------------------------------------
normalization factor
------------------------------------------------------------------------- */
int FixOrientECO::get_norm() {
// set up local variables
double delta[3]; // relative position
double squared_distance; // squared distance of atoms i and j
double weight; // weight function for atoms i and j
double wsum = 0.0; // sum of all weight functions
double scalar_product; // scalar product
double reesum[3] = {0.0, 0.0, 0.0}; // sum of real part
double imesum[3] = {0.0, 0.0, 0.0}; // sum of imaginary part
int max_co = 4; // will produce wrong results for rcut > 3 * lattice constant
int neigh = 0; // count number of neighbors used
// loop over ideal lattice positions
int i, k, idx[3];
for (idx[0] = -max_co; idx[0] <= max_co; ++idx[0]) {
for (idx[1] = -max_co; idx[1] <= max_co; ++idx[1]) {
for (idx[2] = -max_co; idx[2] <= max_co; ++idx[2]) {
// distance of atoms
for (i = 0; i < 3; ++i) {
delta[i] = dir_vec[0][i] * idx[0] + dir_vec[1][i] * idx[1] + dir_vec[2][i] * idx[2];
}
squared_distance = delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2];
// check if atom is within cutoff region
if ((squared_distance != 0.0) and (squared_distance < squared_cutoff)) {
++neigh;
squared_distance *= inv_squared_cutoff;
// weight
weight = squared_distance * (squared_distance - 2.0) + 1.0;
wsum += weight;
// three reciprocal directions
for (k = 0; k < 3; ++k) {
scalar_product = reciprocal_vectors[1][k][0] * delta[0] + reciprocal_vectors[1][k][1] * delta[1] + reciprocal_vectors[1][k][2] * delta[2];
reesum[k] += weight * cos(scalar_product);
imesum[k] -= weight * sin(scalar_product);
}
}
}
}
}
// compute normalization
norm_fac = 3.0 * wsum * wsum;
for (k = 0; k < 3; ++k) {
norm_fac -= reesum[k] * reesum[k] + imesum[k] * imesum[k];
}
return neigh;
}