/* ---------------------------------------------------------------------- LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator https://lammps.sandia.gov/, Sandia National Laboratories 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: Axel Kohlmeyer (Temple U), Venkatesh Botu, James Chapman (Lawrence Livermore National Lab) ------------------------------------------------------------------------- */ #include "pair_agni.h" #include "atom.h" #include "citeme.h" #include "comm.h" #include "force.h" #include "error.h" #include "math_const.h" #include "math_special.h" #include "memory.h" #include "neigh_list.h" #include "neigh_request.h" #include "neighbor.h" #include "tokenizer.h" #include "potential_file_reader.h" #include #include using namespace LAMMPS_NS; using namespace MathSpecial; static const char cite_pair_agni[] = "pair agni command:\n\n" "@article{huan2019jpc,\n" " author = {Huan, T. and Batra, R. and Chapman, J. and Kim, C. and Chandrasekaran, A. and Ramprasad, Rampi},\n" " journal = {J. Phys. Chem. C},\n" " volume = {121},\n" " number = {34},\n" " pages = {20715},\n" " year = {2019},\n" "}\n\n"; #define MAXLINE 10240 #define MAXWORD 40 /* ---------------------------------------------------------------------- */ PairAGNI::PairAGNI(LAMMPS *lmp) : Pair(lmp) { if (lmp->citeme) lmp->citeme->add(cite_pair_agni); single_enable = 0; restartinfo = 0; one_coeff = 1; manybody_flag = 1; atomic_feature_version = 0; centroidstressflag = CENTROID_NOTAVAIL; no_virial_fdotr_compute = 1; params = nullptr; cutmax = 0.0; } /* ---------------------------------------------------------------------- check if allocated, since class can be destructed when incomplete ------------------------------------------------------------------------- */ PairAGNI::~PairAGNI() { if (params) { for (int i = 0; i < nparams; ++i) { memory->destroy(params[i].eta); memory->destroy(params[i].alpha); memory->destroy(params[i].xU); } memory->destroy(params); params = nullptr; } memory->destroy(elem1param); if (allocated) { memory->destroy(setflag); memory->destroy(cutsq); } } /* ---------------------------------------------------------------------- */ void PairAGNI::compute(int eflag, int vflag) { int i,j,k,ii,jj,inum,jnum,itype; double xtmp,ytmp,ztmp,delx,dely,delz; double rsq; int *ilist,*jlist,*numneigh,**firstneigh; ev_init(eflag,vflag); double **x = atom->x; double **f = atom->f; int *type = atom->type; inum = list->inum; ilist = list->ilist; numneigh = list->numneigh; firstneigh = list->firstneigh; double fxtmp,fytmp,fztmp; double *Vx, *Vy, *Vz; // loop over full neighbor list of my atoms for (ii = 0; ii < inum; ii++) { i = ilist[ii]; itype = map[type[i]]; xtmp = x[i][0]; ytmp = x[i][1]; ztmp = x[i][2]; fxtmp = fytmp = fztmp = 0.0; const Param &iparam = params[elem1param[itype]]; Vx = new double[iparam.numeta]; Vy = new double[iparam.numeta]; Vz = new double[iparam.numeta]; memset(Vx,0,iparam.numeta*sizeof(double)); memset(Vy,0,iparam.numeta*sizeof(double)); memset(Vz,0,iparam.numeta*sizeof(double)); jlist = firstneigh[i]; jnum = numneigh[i]; for (jj = 0; jj < jnum; jj++) { j = jlist[jj]; j &= NEIGHMASK; delx = xtmp - x[j][0]; dely = ytmp - x[j][1]; delz = ztmp - x[j][2]; rsq = delx*delx + dely*dely + delz*delz; if ((rsq > 0.0) && (rsq < iparam.cutsq)) { const double r = sqrt(rsq); const double cF = 0.5*(cos((MathConst::MY_PI*r)/iparam.cut)+1.0); const double wX = cF*delx/r; const double wY = cF*dely/r; const double wZ = cF*delz/r; for (k = 0; k < iparam.numeta; ++k) { double e = 0.0; if (atomic_feature_version == AGNI_VERSION_1) e = exp(-(iparam.eta[k]*rsq)); else if (atomic_feature_version == AGNI_VERSION_2) e = (1.0 / (square(iparam.eta[k]) * iparam.gwidth * sqrt(MathConst::MY_2PI))) * exp(-(square(r - iparam.eta[k])) / (2.0 * square(iparam.gwidth))); Vx[k] += wX*e; Vy[k] += wY*e; Vz[k] += wZ*e; } } } for (j = 0; j < iparam.numtrain; ++j) { double kx = 0.0; double ky = 0.0; double kz = 0.0; for (int k = 0; k < iparam.numeta; ++k) { const double xu = iparam.xU[k][j]; kx += square(Vx[k] - xu); ky += square(Vy[k] - xu); kz += square(Vz[k] - xu); } const double e = -0.5/(square(iparam.sigma)); fxtmp += iparam.alpha[j]*exp(kx*e); fytmp += iparam.alpha[j]*exp(ky*e); fztmp += iparam.alpha[j]*exp(kz*e); } fxtmp += iparam.b; fytmp += iparam.b; fztmp += iparam.b; f[i][0] += fxtmp; f[i][1] += fytmp; f[i][2] += fztmp; if (evflag) ev_tally_xyz_full(i,0.0,0.0,fxtmp,fytmp,fztmp,delx,dely,delz); delete [] Vx; delete [] Vy; delete [] Vz; } if (vflag_fdotr) virial_fdotr_compute(); } /* ---------------------------------------------------------------------- */ void PairAGNI::allocate() { allocated = 1; int n = atom->ntypes; memory->create(setflag,n+1,n+1,"pair:setflag"); memory->create(cutsq,n+1,n+1,"pair:cutsq"); map = new int[n+1]; } /* ---------------------------------------------------------------------- global settings ------------------------------------------------------------------------- */ void PairAGNI::settings(int narg, char ** /* arg */) { if (narg != 0) error->all(FLERR,"Illegal pair_style command"); } /* ---------------------------------------------------------------------- set coeffs for one or more type pairs ------------------------------------------------------------------------- */ void PairAGNI::coeff(int narg, char **arg) { if (!allocated) allocate(); map_element2type(narg-3,arg+3); if (nelements != 1) error->all(FLERR,"Cannot handle multi-element systems with this potential"); // read potential file and initialize potential parameters read_file(arg[2]); setup_params(); } /* ---------------------------------------------------------------------- init specific to this pair style ------------------------------------------------------------------------- */ void PairAGNI::init_style() { // need a full neighbor list int irequest = neighbor->request(this,instance_me); neighbor->requests[irequest]->half = 0; neighbor->requests[irequest]->full = 1; } /* ---------------------------------------------------------------------- init for one type pair i,j and corresponding j,i ------------------------------------------------------------------------- */ double PairAGNI::init_one(int i, int j) { if (setflag[i][j] == 0) error->all(FLERR,"All pair coeffs are not set"); return cutmax; } /* ---------------------------------------------------------------------- */ void PairAGNI::read_file(char *filename) { int i,j,k,curparam,wantdata,fp_counter; if (params) { for (int i = 0; i < nparams; ++i) { memory->destroy(params[i].eta); memory->destroy(params[i].alpha); memory->destroy(params[i].xU); } memory->destroy(params); params = nullptr; } nparams = 0; fp_counter = 0; wantdata = -1; // read potential file if (comm->me == 0) { PotentialFileReader reader(lmp, filename, "agni", unit_convert_flag); char * line; while ((line = reader.next_line())) { try { ValueTokenizer values(line); if (wantdata == -1) { std::string tag = values.next_string(); if (tag == "n_elements") { nparams = values.next_int(); if ((nparams < 1) || params) // sanity check error->all(FLERR,"Invalid AGNI potential file"); params = memory->create(params,nparams,"pair:params"); memset(params,0,nparams*sizeof(Param)); curparam = -1; wantdata = -1; } else if (tag == "interaction") { for (j = 0; j < nparams; ++j) { std::string element = values.next_string(); if (element == elements[params[j].ielement]) curparam = j; } } else if (tag == "element") { for (j = 0; j < nparams; ++j) { std::string element = values.next_string(); for (k = 0; k < nelements; ++k) if (element == elements[k]) break; if (k == nelements) error->all(FLERR,"No suitable parameters for requested element found"); else params[j].ielement = k; } } else if (tag == "generation") { atomic_feature_version = values.next_int(); if (atomic_feature_version != AGNI_VERSION_1 && atomic_feature_version != AGNI_VERSION_2) error->all(FLERR,"Incompatible AGNI potential file version"); } else if (tag == "eta") { params[curparam].numeta = values.count() - 1; memory->create(params[curparam].eta,params[curparam].numeta,"agni:eta"); for (j = 0; j < params[curparam].numeta; j++) params[curparam].eta[j] = values.next_double(); } else if (tag == "gwidth") { params[curparam].gwidth = values.next_double(); } else if (tag == "Rc") { params[curparam].cut = values.next_double(); } else if (tag == "n_train") { params[curparam].numtrain = values.next_int(); memory->create(params[curparam].alpha,params[curparam].numtrain,"agni:alpha"); memory->create(params[curparam].xU,params[curparam].numtrain,params[curparam].numtrain,"agni:xU"); } else if (tag == "sigma") { params[curparam].sigma = values.next_double(); } else if (tag == "b") { params[curparam].b = values.next_double(); } else if (tag == "endVar") { if (atomic_feature_version == AGNI_VERSION_1) params[curparam].gwidth = 0.0; wantdata = curparam; curparam = -1; } else error->warning(FLERR,fmt::format("Ignoring unknown tag '{}' " "in AGNI potential file.",tag)); } else { if (params && wantdata >=0) { if ((int)values.count() == params[wantdata].numeta + 2) { for (k = 0; k < params[wantdata].numeta; ++k) params[wantdata].xU[k][fp_counter] = values.next_double(); values.next_double(); // ignore params[wantdata].alpha[fp_counter] = values.next_double(); fp_counter++; } else if ((int)values.count() == params[wantdata].numeta + 3) { values.next_double(); // ignore for (k = 0; k < params[wantdata].numeta; ++k) params[wantdata].xU[k][fp_counter] = values.next_double(); values.next_double(); // ignore params[wantdata].alpha[fp_counter] = values.next_double(); fp_counter++; } else error->all(FLERR,"Invalid AGNI potential file"); } } } catch (TokenizerException &e) { error->one(FLERR, e.what()); } } } MPI_Bcast(&nparams, 1, MPI_INT, 0, world); MPI_Bcast(&atomic_feature_version, 1, MPI_INT, 0, world); if (comm->me != 0) { params = memory->create(params,nparams,"pair:params"); memset(params,0,nparams*sizeof(Param)); } MPI_Bcast(params, nparams*sizeof(Param), MPI_BYTE, 0, world); for (i = 0; i < nparams; i++) { if (comm->me != 0) { memory->create(params[i].alpha,params[i].numtrain,"agni:alpha"); memory->create(params[i].eta,params[i].numeta,"agni:eta"); memory->create(params[i].xU,params[i].numeta,params[i].numtrain,"agni:xU"); } MPI_Bcast(params[i].alpha, params[i].numtrain, MPI_DOUBLE, 0, world); MPI_Bcast(params[i].eta, params[i].numeta, MPI_DOUBLE, 0, world); MPI_Bcast(¶ms[i].xU[0][0],params[i].numtrain*params[i].numeta,MPI_DOUBLE,0,world); } } /* ---------------------------------------------------------------------- */ void PairAGNI::setup_params() { int i,m,n; double rtmp; // set elem1param for all elements memory->destroy(elem1param); memory->create(elem1param,nelements,"pair:elem1param"); for (i = 0; i < nelements; i++) { n = -1; for (m = 0; m < nparams; m++) { if (i == params[m].ielement) { if (n >= 0) error->all(FLERR,"Potential file has duplicate entry"); n = m; } } if (n < 0) error->all(FLERR,"Potential file is missing an entry"); elem1param[i] = n; } // compute parameter values derived from inputs // set cutsq using shortcut to reduce neighbor list for accelerated // calculations. cut must remain unchanged as it is a potential parameter // (cut = a*sigma) cutmax = 0.0; for (m = 0; m < nparams; m++) { rtmp = params[m].cut; params[m].cutsq = rtmp * rtmp; if (rtmp > cutmax) cutmax = rtmp; } }