/* ---------------------------------------------------------------------- LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator http://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 author: Aidan Thompson (SNL) ------------------------------------------------------------------------- */ #include "math.h" #include "stdio.h" #include "stdlib.h" #include "string.h" #include "pair_brenner.h" #include "atom.h" #include "neighbor.h" #include "neigh_list.h" #include "neigh_request.h" #include "force.h" #include "comm.h" #include "memory.h" #include "error.h" using namespace LAMMPS_NS; #define MAXLINE 1024 #define DELTA 4 /* ---------------------------------------------------------------------- */ PairBrenner::PairBrenner(LAMMPS *lmp) : Pair(lmp) { single_enable = 0; one_coeff = 1; PI = 4.0*atan(1.0); PI2 = 2.0*atan(1.0); PI4 = atan(1.0); nelements = 0; elements = NULL; nparams = 0; maxparam = 0; params = NULL; elem2param = NULL; } /* ---------------------------------------------------------------------- check if allocated, since class can be destructed when incomplete ------------------------------------------------------------------------- */ PairBrenner::~PairBrenner() { if (elements) for (int i = 0; i < nelements; i++) delete [] elements[i]; delete [] elements; memory->sfree(params); memory->destroy_3d_int_array(elem2param); if (allocated) { memory->destroy_2d_int_array(setflag); memory->destroy_2d_double_array(cutsq); delete [] map; } } /* ---------------------------------------------------------------------- */ void PairBrenner::compute(int eflag, int vflag) { int i,j,k,ii,jj,kk,inum,jnum; int itag,jtag,itype,jtype,ktype,iparam_ij,iparam_ijk,iparam_jik; double xtmp,ytmp,ztmp,delx,dely,delz,evdwl,fpair; double rsq,rsq1,rsq2; double delr1[3],delr2[3],fi[3],fj[3],fk[3],dri[3],drj[3],drk[3]; double Gijkfcik_dri[3],Gijkfcik_drj[3],Gijkfcik_drk[3]; double Gijkfcik,prefactor; int *ilist,*jlist,*numneigh,**firstneigh; evdwl = 0.0; if (eflag || vflag) ev_setup(eflag,vflag); else evflag = vflag_fdotr = 0; double **x = atom->x; double **f = atom->f; int *tag = atom->tag; int *type = atom->type; int nlocal = atom->nlocal; int newton_pair = force->newton_pair; inum = list->inum; ilist = list->ilist; numneigh = list->numneigh; firstneigh = list->firstneigh; // loop over full neighbor list of my atoms for (ii = 0; ii < inum; ii++) { i = ilist[ii]; itag = tag[i]; itype = map[type[i]]; xtmp = x[i][0]; ytmp = x[i][1]; ztmp = x[i][2]; // two-body interactions, skip half of them jlist = firstneigh[i]; jnum = numneigh[i]; for (jj = 0; jj < jnum; jj++) { j = jlist[jj]; 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; else if (x[j][2] == ztmp && x[j][1] < ytmp) continue; else if (x[j][2] == ztmp && x[j][1] == ytmp && x[j][0] < xtmp) continue; } jtype = map[type[j]]; delx = xtmp - x[j][0]; dely = ytmp - x[j][1]; delz = ztmp - x[j][2]; rsq = delx*delx + dely*dely + delz*delz; iparam_ij = elem2param[itype][jtype][jtype]; if (rsq > params[iparam_ij].cutsq) continue; repulsive(¶ms[iparam_ij],rsq,fpair,eflag,evdwl); f[i][0] += delx*fpair; f[i][1] += dely*fpair; f[i][2] += delz*fpair; f[j][0] -= delx*fpair; f[j][1] -= dely*fpair; f[j][2] -= delz*fpair; if (evflag) ev_tally(i,j,nlocal,newton_pair, evdwl,0.0,fpair,delx,dely,delz); } // three-body interactions // skip immediately if I-J is not within cutoff for (jj = 0; jj < jnum; jj++) { j = jlist[jj]; jtype = map[type[j]]; iparam_ij = elem2param[itype][jtype][jtype]; delr1[0] = x[j][0] - xtmp; delr1[1] = x[j][1] - ytmp; delr1[2] = x[j][2] - ztmp; rsq1 = delr1[0]*delr1[0] + delr1[1]*delr1[1] + delr1[2]*delr1[2]; if (rsq1 > params[iparam_ij].cutsq) continue; Gijkfcik = 0.0; for (kk = 0; kk < jnum; kk++) { if (jj == kk) continue; k = jlist[kk]; ktype = map[type[k]]; iparam_ijk = elem2param[itype][jtype][ktype]; delr2[0] = x[k][0] - xtmp; delr2[1] = x[k][1] - ytmp; delr2[2] = x[k][2] - ztmp; rsq2 = delr2[0]*delr2[0] + delr2[1]*delr2[1] + delr2[2]*delr2[2]; if (rsq2 > params[iparam_ijk].cutsq) continue; Gijkfcik += gf(¶ms[iparam_ijk],rsq1,rsq2,delr1,delr2); } // pairwise force due to gf force_gf(¶ms[iparam_ij],rsq1,Gijkfcik,fpair,prefactor,eflag,evdwl); f[i][0] += delr1[0]*fpair; f[i][1] += delr1[1]*fpair; f[i][2] += delr1[2]*fpair; f[j][0] -= delr1[0]*fpair; f[j][1] -= delr1[1]*fpair; f[j][2] -= delr1[2]*fpair; if (evflag) ev_tally(i,j,nlocal,newton_pair, evdwl,0.0,-fpair,-delr1[0],-delr1[1],-delr1[2]); // attractive term via loop over k for (kk = 0; kk < jnum; kk++) { if (jj == kk) continue; k = jlist[kk]; ktype = map[type[k]]; iparam_ijk = elem2param[itype][jtype][ktype]; delr2[0] = x[k][0] - xtmp; delr2[1] = x[k][1] - ytmp; delr2[2] = x[k][2] - ztmp; rsq2 = delr2[0]*delr2[0] + delr2[1]*delr2[1] + delr2[2]*delr2[2]; if (rsq2 > params[iparam_ijk].cutsq) continue; attractive(¶ms[iparam_ijk],prefactor, rsq1,rsq2,delr1,delr2,fi,fj,fk); f[i][0] += fi[0]; f[i][1] += fi[1]; f[i][2] += fi[2]; f[j][0] += fj[0]; f[j][1] += fj[1]; f[j][2] += fj[2]; f[k][0] += fk[0]; f[k][1] += fk[1]; f[k][2] += fk[2]; if (vflag_atom) v_tally3(i,j,k,fj,fk,delr1,delr2); } } } if (vflag_fdotr) virial_compute(); } /* ---------------------------------------------------------------------- */ void PairBrenner::allocate() { allocated = 1; int n = atom->ntypes; setflag = memory->create_2d_int_array(n+1,n+1,"pair:setflag"); cutsq = memory->create_2d_double_array(n+1,n+1,"pair:cutsq"); map = new int[n+1]; } /* ---------------------------------------------------------------------- global settings ------------------------------------------------------------------------- */ void PairBrenner::settings(int narg, char **arg) { if (narg != 0) error->all("Illegal pair_style command"); } /* ---------------------------------------------------------------------- set coeffs for one or more type pairs ------------------------------------------------------------------------- */ void PairBrenner::coeff(int narg, char **arg) { int i,j,n; if (!allocated) allocate(); if (narg != 3 + atom->ntypes) error->all("Incorrect args for pair coefficients"); // insure I,J args are * * if (strcmp(arg[0],"*") != 0 || strcmp(arg[1],"*") != 0) error->all("Incorrect args for pair coefficients"); // read args that map atom types to elements in potential file // map[i] = which element the Ith atom type is, -1 if NULL // nelements = # of unique elements // elements = list of element names if (elements) { for (i = 0; i < nelements; i++) delete [] elements[i]; delete [] elements; } elements = new char*[atom->ntypes]; for (i = 0; i < atom->ntypes; i++) elements[i] = NULL; nelements = 0; for (i = 3; i < narg; i++) { if (strcmp(arg[i],"NULL") == 0) { map[i-2] = -1; continue; } for (j = 0; j < nelements; j++) if (strcmp(arg[i],elements[j]) == 0) break; map[i-2] = j; if (j == nelements) { n = strlen(arg[i]) + 1; elements[j] = new char[n]; strcpy(elements[j],arg[i]); nelements++; } } // read potential file and initialize potential parameters read_file(arg[2]); setup(); // clear setflag since coeff() called once with I,J = * * n = atom->ntypes; for (int i = 1; i <= n; i++) for (int j = i; j <= n; j++) setflag[i][j] = 0; // set setflag i,j for type pairs where both are mapped to elements int count = 0; for (int i = 1; i <= n; i++) for (int j = i; j <= n; j++) if (map[i] >= 0 && map[j] >= 0) { setflag[i][j] = 1; count++; } if (count == 0) error->all("Incorrect args for pair coefficients"); } /* ---------------------------------------------------------------------- init specific to this pair style ------------------------------------------------------------------------- */ void PairBrenner::init_style() { if (atom->tag_enable == 0) error->all("Pair style Brenner requires atom IDs"); if (force->newton_pair == 0) error->all("Pair style Brenner requires newton pair on"); // need a full neighbor list int irequest = neighbor->request(this); neighbor->requests[irequest]->half = 0; neighbor->requests[irequest]->full = 1; } /* ---------------------------------------------------------------------- init for one type pair i,j and corresponding j,i ------------------------------------------------------------------------- */ double PairBrenner::init_one(int i, int j) { if (setflag[i][j] == 0) error->all("All pair coeffs are not set"); return cutmax; } /* ---------------------------------------------------------------------- */ void PairBrenner::read_file(char *file) { int params_per_line = 13; char **words = new char*[params_per_line+1]; if (params) delete [] params; params = NULL; nparams = 0; // open file on proc 0 FILE *fp; if (comm->me == 0) { fp = fopen(file,"r"); if (fp == NULL) { char str[128]; sprintf(str,"Cannot open Brenner potential file %s",file); error->one(str); } } // read each line out of file, skipping blank lines or leading '#' // store line of params if all 3 element tags are in element list int n,nwords,ielement,jelement,kelement; char line[MAXLINE],*ptr; int eof = 0; while (1) { if (comm->me == 0) { ptr = fgets(line,MAXLINE,fp); if (ptr == NULL) { eof = 1; fclose(fp); } else n = strlen(line) + 1; } MPI_Bcast(&eof,1,MPI_INT,0,world); if (eof) break; MPI_Bcast(&n,1,MPI_INT,0,world); MPI_Bcast(line,n,MPI_CHAR,0,world); // strip comment, skip line if blank if (ptr = strchr(line,'#')) *ptr = '\0'; nwords = atom->count_words(line); if (nwords == 0) continue; // concatenate additional lines until have params_per_line words while (nwords < params_per_line) { n = strlen(line); if (comm->me == 0) { ptr = fgets(&line[n],MAXLINE-n,fp); if (ptr == NULL) { eof = 1; fclose(fp); } else n = strlen(line) + 1; } MPI_Bcast(&eof,1,MPI_INT,0,world); if (eof) break; MPI_Bcast(&n,1,MPI_INT,0,world); MPI_Bcast(line,n,MPI_CHAR,0,world); if (ptr = strchr(line,'#')) *ptr = '\0'; nwords = atom->count_words(line); } if (nwords != params_per_line) error->all("Incorrect format in Brenner potential file"); // words = ptrs to all words in line nwords = 0; words[nwords++] = strtok(line," \t\n\r\f"); while (words[nwords++] = strtok(NULL," \t\n\r\f")) continue; // ielement,jelement,kelement = 1st args // if all 3 args are in element list, then parse this line // else skip to next line for (ielement = 0; ielement < nelements; ielement++) if (strcmp(words[0],elements[ielement]) == 0) break; if (ielement == nelements) continue; for (jelement = 0; jelement < nelements; jelement++) if (strcmp(words[1],elements[jelement]) == 0) break; if (jelement == nelements) continue; for (kelement = 0; kelement < nelements; kelement++) if (strcmp(words[2],elements[kelement]) == 0) break; if (kelement == nelements) continue; // load up parameter settings and error check their values if (nparams == maxparam) { maxparam += DELTA; params = (Param *) memory->srealloc(params,maxparam*sizeof(Param), "pair:params"); } params[nparams].ielement = ielement; params[nparams].jelement = jelement; params[nparams].kelement = kelement; params[nparams].Re = atof(words[3]); params[nparams].De = atof(words[4]); params[nparams].Beta = atof(words[5]); params[nparams].S = atof(words[6]); params[nparams].Detat = atof(words[7]); params[nparams].R1 = atof(words[8]); params[nparams].R2 = atof(words[9]); params[nparams].Azero = atof(words[10]); params[nparams].Czero = atof(words[11]); params[nparams].Dzero = atof(words[12]); if ( params[nparams].Re < 0.0 || params[nparams].De < 0.0 || params[nparams].Beta < 0.0 || params[nparams].S < 0.0 || params[nparams].Detat < 0.0 || params[nparams].R1 < 0.0 || params[nparams].R2 < 0.0 || params[nparams].Azero < 0.0 || params[nparams].Czero < 0.0 ||params[nparams].Dzero < 0.0 || params[nparams].R1 > params[nparams].R2 ) error->all("Illegal Brenner parameter"); nparams++; } delete [] words; } /* ---------------------------------------------------------------------- */ void PairBrenner::setup() { int i,j,k,m,n; // set elem2param for all element triplet combinations // must be a single exact match to lines read from file // do not allow for ACB in place of ABC if (elem2param) memory->destroy_3d_int_array(elem2param); elem2param = memory->create_3d_int_array(nelements,nelements,nelements, "pair:elem2param"); for (i = 0; i < nelements; i++) for (j = 0; j < nelements; j++) for (k = 0; k < nelements; k++) { n = -1; for (m = 0; m < nparams; m++) { if (i == params[m].ielement && j == params[m].jelement && k == params[m].kelement) { if (n >= 0) error->all("Potential file has duplicate entry"); n = m; } } if (n < 0) error->all("Potential file is missing an entry"); elem2param[i][j][k] = n; } // compute parameter values derived from inputs for (m = 0; m < nparams; m++) { params[m].cut = params[m].R2; params[m].cutsq = params[m].cut*params[m].cut; } // set cutmax to max of all params cutmax = 0.0; for (m = 0; m < nparams; m++) if (params[m].cut > cutmax) cutmax = params[m].cut; } /* ---------------------------------------------------------------------- */ void PairBrenner::repulsive(Param *param, double rsq, double &fforce, int eflag, double &eng) { double r,tmp_fc,tmp_fc_d,tmp_exp; r = sqrt(rsq); tmp_fc = bre_fc(r,param); tmp_fc_d = bre_fc_d(r,param); tmp_exp = exp(-param->Beta*(r-param->Re)*pow(2.0*param->S,0.5)); fforce = -(param->De/(param->S-1.0))*tmp_exp*(tmp_fc_d-tmp_fc*param->Beta*pow(2.0*param->S,0.5))/r; if (eflag) eng = (tmp_fc * param->De / (param->S-1.0)) * tmp_exp; } /* ---------------------------------------------------------------------- */ double PairBrenner::gf(Param *param, double rsqij, double rsqik, double *delrij, double *delrik) { double rij,rik,costheta; rij = sqrt(rsqij); rik = sqrt(rsqik); costheta = (delrij[0]*delrik[0] + delrij[1]*delrik[1] + delrij[2]*delrik[2]) / (rij*rik); return bre_fc(rik,param) * bre_gijk(costheta,param); } /* ---------------------------------------------------------------------- */ void PairBrenner::force_gf(Param *param, double rsq, double Gijkfcik, double &fforce, double &prefactor, int eflag, double &eng) { double r,Va,Va_d,bij; r = sqrt(rsq); Va = bre_va(r,param); Va_d = bre_va_d(r,param); bij=pow(1.0 + Gijkfcik, -param->Detat); fforce = -0.5 * bij*Va_d / r; prefactor = -0.5 * Va * bre_bij_d(Gijkfcik,param); if (eflag) eng = -0.5 * bij*Va; } /* ---------------------------------------------------------------------- attractive term use param_ij cutoff for rij test use param_ijk cutoff for rik test ------------------------------------------------------------------------- */ void PairBrenner::attractive(Param *param, double prefactor, double rsqij, double rsqik, double *delrij, double *delrik, double *fi, double *fj, double *fk) { double rij_hat[3],rik_hat[3]; double rij,rijinv,rik,rikinv; rij = sqrt(rsqij); rijinv = 1.0/rij; vec3_scale(rijinv,delrij,rij_hat); rik = sqrt(rsqik); rikinv = 1.0/rik; vec3_scale(rikinv,delrik,rik_hat); bre_gfterm_d(prefactor,rij_hat,rij,rik_hat,rik,fi,fj,fk,param); } /* ---------------------------------------------------------------------- */ double PairBrenner::bre_fc(double r, Param *param) { double bre_R1 = param->R1; double bre_R2 = param->R2; if (r < bre_R1) return 1.0; if (r > bre_R2) return 0.0; return 0.5*(1.0 + cos(PI *(r - bre_R1)/(bre_R2-bre_R1))); } /* ---------------------------------------------------------------------- */ double PairBrenner::bre_fc_d(double r, Param *param) { double bre_R1 = param->R1; double bre_R2 = param->R2; if (r < bre_R1) return 0.0; if (r > bre_R2) return 0.0; return -0.5*(PI/(bre_R2-bre_R1)) * sin(PI*(r - bre_R1)/(bre_R2-bre_R1)); } /* ---------------------------------------------------------------------- */ double PairBrenner::bre_va(double r, Param *param) { if (r > param->R2) return 0.0; return bre_fc(r,param)*param->De*(param->S/(param->S-1.0))*exp(-param->Beta*(r-param->Re)*pow(2.0/param->S,0.5)); } /* ---------------------------------------------------------------------- */ double PairBrenner::bre_va_d(double r, Param *param) { double bre_fij_d = bre_fc_d(r, param); double bre_fij = bre_fc(r, param); if (r > param->R2) return 0.0; return param->De*param->S/(param->S-1.0)*exp(-pow(2.0/param->S,0.5)*param->Beta*(r-param->Re)) *(bre_fij_d-param->Beta*pow(2.0/param->S,0.5)*bre_fij); } /* ---------------------------------------------------------------------- */ double PairBrenner::bre_bij(double Gf, Param *param) { return pow((1.0 + Gf), -param->Detat); }; /* ---------------------------------------------------------------------- */ double PairBrenner::bre_bij_d(double gf, Param *param) { return 0.5 * param->Detat * pow((1.0 + gf), -param->Detat-1.0); }; /* ---------------------------------------------------------------------- */ double PairBrenner::bre_gijk(double costheta, Param *param) { double bre_c0 = param->Czero; double bre_d0 = param->Dzero; double bre_a0 = param->Azero; return bre_a0*(1.0 + pow(bre_c0/bre_d0,2.0) - pow(bre_c0,2.0) / (pow(bre_d0,2.0) + pow(1.0 + costheta,2.0))); }; /* ---------------------------------------------------------------------- */ double PairBrenner::bre_gijk_d(double costheta, Param *param) { double bre_c0 = param->Czero; double bre_d0 = param->Dzero; double bre_a0 = param->Azero; return bre_a0*(1.0 + pow(bre_c0/bre_d0,2.0) - pow(bre_c0,2.0) / (pow(bre_d0,2.0) + pow(1.0 + costheta,2.0))); }; /* ---------------------------------------------------------------------- */ void PairBrenner::bre_gfterm_d(double prefactor, double *rij_hat, double rij, double *rik_hat, double rik, double *dri, double *drj, double *drk, Param *param) { double gijk,gijk_d,ex_delr,ex_delr_d,fc,dfc,cos_theta,tmp; double dcosdri[3],dcosdrj[3],dcosdrk[3]; fc = bre_fc(rik,param); dfc = bre_fc_d(rik,param); cos_theta = vec3_dot(rij_hat,rik_hat); gijk = bre_gijk(cos_theta,param); gijk_d = bre_gijk_d(cos_theta,param); costheta_d(rij_hat,rij,rik_hat,rik,dcosdri,dcosdrj,dcosdrk); // compute the derivative wrt Ri // dri = -dfc*gijk*rik_hat; // dri += fc*gijk_d*dcosdri; vec3_scale(-dfc*gijk,rik_hat,dri); vec3_scaleadd(fc*gijk_d,dcosdri,dri,dri); vec3_scale(prefactor,dri,dri); // compute the derivative wrt Rj // drj = fc*gijk_d*dcosdrj; vec3_scale(fc*gijk_d,dcosdrj,drj); vec3_scale(prefactor,drj,drj); // compute the derivative wrt Rk // drk = dfc*gijk*rik_hat; // drk += fc*gijk_d*dcosdrk; vec3_scale(dfc*gijk,rik_hat,drk); vec3_scaleadd(fc*gijk_d,dcosdrk,drk,drk); vec3_scale(prefactor,drk,drk); } /* ---------------------------------------------------------------------- */ void PairBrenner::costheta_d(double *rij_hat, double rij, double *rik_hat, double rik, double *dri, double *drj, double *drk) { // first element is devative wrt Ri, second wrt Rj, third wrt Rk double cos_theta = vec3_dot(rij_hat,rik_hat); vec3_scaleadd(-cos_theta,rij_hat,rik_hat,drj); vec3_scale(1.0/rij,drj,drj); vec3_scaleadd(-cos_theta,rik_hat,rij_hat,drk); vec3_scale(1.0/rik,drk,drk); vec3_add(drj,drk,dri); vec3_scale(-1.0,dri,dri); }; /* ---------------------------------------------------------------------- vector routines ------------------------------------------------------------------------- */ /* double PairBrenner::vec3_dot(double *x, double *y) { return x[0]*y[0] + x[1]*y[1] + x[2]*y[2]; } void PairBrenner::vec3_add(double *x, double *y, double *z) { z[0] = x[0]+y[0]; z[1] = x[1]+y[1]; z[2] = x[2]+y[2]; } void PairBrenner::vec3_scale(double k, double *x, double *y) { y[0] = k*x[0]; y[1] = k*x[1]; y[2] = k*x[2]; } void PairBrenner::vec3_scaleadd(double k, double *x, double *y, double *z) { z[0] = k*x[0]+y[0]; z[1] = k*x[1]+y[1]; z[2] = k*x[2]+y[2]; } */