// clang-format off /* ---------------------------------------------------------------------- LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator https://www.lammps.org/ 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. ------------------------------------------------------------------------- */ #include "pair_amoeba.h" #include "atom.h" #include "comm.h" #include "domain.h" #include "error.h" #include "fix_store_peratom.h" #include "neigh_list.h" #include using namespace LAMMPS_NS; enum{NOFRAME,ZONLY,ZTHENX,BISECTOR,ZBISECT,THREEFOLD}; // ---------------------------------------------------------------------- // utility methods used by different parts of the AMOEBA force field // ---------------------------------------------------------------------- /* ---------------------------------------------------------------------- kmpole performs one-time assignment of xyzaxis multipole neighbors to each owned atom any of the values can be 0 if not used yaxis can later be negative due to chkpole() also sets polaxe and pole[13] multipole for each owned atom ------------------------------------------------------------------------- */ void PairAmoeba::kmpole() { bool path; int i,j,k,m,j12,k12,m12,k13,m13,flag; int iframe,nframe; int itype,jtype,ktype,mtype,xtype,ytype,ztype; tagint jneigh,kneigh,mneigh; // DEBUG vectors tagint bondneigh[12]; tagint angleneigh[36]; amtype = atom->ivector[index_amtype]; int *polaxe = atom->ivector[index_polaxe]; double **xyzaxis = atom->darray[index_xyzaxis]; double **pole = fixpole->astore; int **nspecial = atom->nspecial; tagint **special = atom->special; int nlocal = atom->nlocal; int nmissing = 0; for (i = 0; i < nlocal; i++) { itype = amtype[i]; nframe = nmultiframe[itype]; // flag is used to prevent matching multiple times // only first match is used flag = 0; // create a sorted version of bond/angle neighs from special[][] // NOTE: this is to try and do it identically to Tinker // b/c I think in Tinker, which case is seen first can depend on atom order for (j = 0; j < nspecial[i][0]; j++) bondneigh[j] = special[i][j]; for (m = 0; m < nspecial[i][0]; m++) { tagint smallest = MAXTAGINT; for (j = m; j < nspecial[i][0]; j++) { if (bondneigh[j] < smallest) { smallest = bondneigh[j]; k = j; bondneigh[k] = bondneigh[m]; bondneigh[m] = smallest; } } } for (j = nspecial[i][0]; j < nspecial[i][1]; j++) angleneigh[j] = special[i][j]; for (m = nspecial[i][0]; m < nspecial[i][1]; m++) { tagint smallest = MAXTAGINT; for (j = m; j < nspecial[i][1]; j++) { if (angleneigh[j] < smallest) { smallest = angleneigh[j]; k = j; } angleneigh[k] = angleneigh[m]; angleneigh[m] = smallest; } } // assign xyz axis and fpole via only 1-2 connected atoms for (iframe = 0; iframe < nframe; iframe++) { xtype = xpole[itype][iframe]; ytype = ypole[itype][iframe]; ztype = zpole[itype][iframe]; for (j12 = 0; j12 < nspecial[i][0]; j12++) { jneigh = bondneigh[j12]; j = atom->map(jneigh); if (j < 0) error->one(FLERR,"AMOEBA kmpole() could not find bond partner"); jtype = amtype[j]; if (jtype == ztype) { for (k12 = 0; k12 < nspecial[i][0]; k12++) { if (k12 == j12) continue; kneigh = bondneigh[k12]; k = atom->map(kneigh); if (k < 0) error->one(FLERR,"AMOEBA kmpole() could not find bond partner"); ktype = amtype[k]; if (ktype == xtype) { if (ytype == 0 && !flag) { flag = 1; xyzaxis[i][2] = ubuf(jneigh).d; xyzaxis[i][0] = ubuf(kneigh).d; xyzaxis[i][1] = 0.0; polaxe[i] = mpaxis[itype][iframe]; for (j = 0; j < 13; j++) pole[i][j] = fpole[itype][iframe][j]; } else { for (m12 = 0; m12 < nspecial[i][0]; m12++) { if (m12 == j12 || m12 == k12) continue; mneigh = bondneigh[m12]; m = atom->map(mneigh); if (m < 0) error->one(FLERR,"AMOEBA kmpole() could not find bond partner"); mtype = amtype[m]; if (mtype == ytype && !flag) { flag = 1; xyzaxis[i][2] = ubuf(jneigh).d; xyzaxis[i][0] = ubuf(kneigh).d; xyzaxis[i][1] = ubuf(mneigh).d; polaxe[i] = mpaxis[itype][iframe]; for (j = 0; j < 13; j++) pole[i][j] = fpole[itype][iframe][j]; } } } } } } } } if (flag) continue; // assign xyz axis via 1-2 and 1-3 connected atoms for (iframe = 0; iframe < nframe; iframe++) { xtype = xpole[itype][iframe]; ytype = ypole[itype][iframe]; ztype = zpole[itype][iframe]; for (j12 = 0; j12 < nspecial[i][0]; j12++) { jneigh = bondneigh[j12]; j = atom->map(jneigh); if (j < 0) error->one(FLERR,"AMOEBA kmpole() could not find bond partner"); jtype = amtype[j]; if (jtype == ztype) { for (k13 = nspecial[i][0]; k13 < nspecial[i][1]; k13++) { kneigh = angleneigh[k13]; k = atom->map(kneigh); if (k < 0) error->one(FLERR,"AMOEBA kmpole() could not find bond partner"); ktype = amtype[k]; path = false; for (m12 = 0; m12 < nspecial[k][0]; m12++) if (special[k][m12] == jneigh) path = true; if (!path) continue; if (ktype == xtype) { if (ytype == 0 && !flag) { flag = 1; xyzaxis[i][2] = ubuf(jneigh).d; xyzaxis[i][0] = ubuf(kneigh).d; xyzaxis[i][1] = 0.0; polaxe[i] = mpaxis[itype][iframe]; for (j = 0; j < 13; j++) pole[i][j] = fpole[itype][iframe][j]; } else { for (m13 = nspecial[i][0]; m13 < nspecial[i][1]; m13++) { if (m13 == k13) continue; mneigh = angleneigh[m13]; m = atom->map(mneigh); if (m < 0) error->one(FLERR,"AMOEBA kmpole() could not find bond partner"); mtype = amtype[m]; path = false; for (m12 = 0; m12 < nspecial[m][0]; m12++) if (special[m][m12] == jneigh) path = true; if (!path) continue; if (mtype == ytype && !flag) { flag = 1; xyzaxis[i][2] = ubuf(jneigh).d; xyzaxis[i][0] = ubuf(kneigh).d; xyzaxis[i][1] = ubuf(mneigh).d; polaxe[i] = mpaxis[itype][iframe]; for (j = 0; j < 13; j++) pole[i][j] = fpole[itype][iframe][j]; } } } } } } } } if (flag) continue; // assign xyz axis via only a z-defining atom for (iframe = 0; iframe < nframe; iframe++) { xtype = xpole[itype][iframe]; ytype = ypole[itype][iframe]; ztype = zpole[itype][iframe]; for (j12 = 0; j12 < nspecial[i][0]; j12++) { jneigh = bondneigh[j12]; j = atom->map(jneigh); if (j < 0) error->one(FLERR,"AMOEBA kmpole() could not find bond partner"); jtype = amtype[j]; if (jtype == ztype) { if (xtype == 0 && !flag) { flag = 1; xyzaxis[i][2] = ubuf(jneigh).d; xyzaxis[i][0] = xyzaxis[i][1] = 0.0; polaxe[i] = mpaxis[itype][iframe]; for (j = 0; j < 13; j++) pole[i][j] = fpole[itype][iframe][j]; } } } } if (flag) continue; // assign xyz axis via no connected atoms for (iframe = 0; iframe < nframe; iframe++) { xtype = xpole[itype][iframe]; ytype = ypole[itype][iframe]; ztype = zpole[itype][iframe]; if (ztype == 0 && !flag) { flag = 1; xyzaxis[i][2] = xyzaxis[i][1] = xyzaxis[i][0] = 0.0; polaxe[i] = mpaxis[itype][iframe]; for (j = 0; j < 13; j++) pole[i][j] = fpole[itype][iframe][j]; } } if (flag) continue; // flag error if could not assign xyz axis nmissing++; } // error check on missing settings int nmissing_all; MPI_Allreduce(&nmissing,&nmissing_all,1,MPI_INT,MPI_SUM,world); if (nmissing_all) error->all(FLERR, "Pair amoeba: {} multipole settings missing\n", nmissing_all); } /* ---------------------------------------------------------------------- chkpole inverts atomic multipole moments as necessary at sites with chiral local reference frame definitions called every timestep for each atom I ------------------------------------------------------------------------- */ void PairAmoeba::chkpole(int i) { bool check; int ib,ic,id; double xad,yad,zad; double xbd,ybd,zbd; double xcd,ycd,zcd; double c1,c2,c3,vol; double **pole = fixpole->astore; int *polaxe = atom->ivector[index_polaxe]; double **xyzaxis = atom->darray[index_xyzaxis]; tagint yaxisID = (tagint) ubuf(xyzaxis[i][1]).i; // test for chirality inversion // if not, return check = true; if (polaxe[i] != ZTHENX) check = false; if (yaxisID == 0) check = false; if (!check) return; ib = zaxis2local[i]; ic = xaxis2local[i]; id = yaxis2local[i]; // compute the signed parallelpiped volume at chiral site double **x = atom->x; xad = x[i][0] - x[id][0]; yad = x[i][1] - x[id][1]; zad = x[i][2] - x[id][2]; xbd = x[ib][0] - x[id][0]; ybd = x[ib][1] - x[id][1]; zbd = x[ib][2] - x[id][2]; xcd = x[ic][0] - x[id][0]; ycd = x[ic][1] - x[id][1]; zcd = x[ic][2] - x[id][2]; c1 = ybd*zcd - zbd*ycd; c2 = ycd*zad - zcd*yad; c3 = yad*zbd - zad*ybd; vol = xad*c1 + xbd*c2 + xcd*c3; // invert atomic multipole components involving the y-axis // flip sign in permanent yaxis, not yaxis2local if ((yaxisID < 0 && vol > 0.0) || (yaxisID > 0 && vol < 0.0)) { xyzaxis[i][1] = -ubuf(yaxisID).d; pole[i][2] = -pole[i][2]; pole[i][5] = -pole[i][5]; pole[i][7] = -pole[i][7]; pole[i][9] = -pole[i][9]; pole[i][11] = -pole[i][11]; } } /* ---------------------------------------------------------------------- rotmat finds the rotation matrix that rotates the local coordinate system into the global frame at a multipole site called every timestep for each atom I ------------------------------------------------------------------------- */ void PairAmoeba::rotmat(int i) { int ix,iy,iz; double r,dot; double xi,yi,zi; double dx,dy,dz; double dx1,dy1,dz1; double dx2,dy2,dz2; double dx3,dy3,dz3; int *polaxe = atom->ivector[index_polaxe]; // get coordinates and frame definition for the multipole site double **x = atom->x; xi = x[i][0]; yi = x[i][1]; zi = x[i][2]; iz = zaxis2local[i]; ix = xaxis2local[i]; iy = yaxis2local[i]; // use the identity matrix as the default rotation matrix rotate[0][0] = 1.0; rotate[0][1] = 0.0; rotate[0][2] = 0.0; rotate[2][0] = 0.0; rotate[2][1] = 0.0; rotate[2][2] = 1.0; // z-only frame rotation matrix elements for z-axis only if (polaxe[i] == ZONLY) { dx = x[iz][0] - xi; dy = x[iz][1] - yi; dz = x[iz][2] - zi; r = sqrt(dx*dx + dy*dy + dz*dz); rotate[2][0] = dx / r; rotate[2][1] = dy / r; rotate[2][2] = dz / r; dx = 1.0; dy = 0.0; dz = 0.0; dot = rotate[2][0]; if (fabs(dot) > 0.866) { dx = 0.0; dy = 1.0; dot = rotate[2][1]; } dx = dx - dot*rotate[2][0]; dy = dy - dot*rotate[2][1]; dz = dz - dot*rotate[2][2]; r = sqrt(dx*dx + dy*dy + dz*dz); rotate[0][0] = dx / r; rotate[0][1] = dy / r; rotate[0][2] = dz / r; // z-then-x frame rotation matrix elements for z- and x-axes } else if (polaxe[i] == ZTHENX) { dx = x[iz][0] - xi; dy = x[iz][1] - yi; dz = x[iz][2] - zi; r = sqrt(dx*dx + dy*dy + dz*dz); rotate[2][0] = dx / r; rotate[2][1] = dy / r; rotate[2][2] = dz / r; dx = x[ix][0] - xi; dy = x[ix][1] - yi; dz = x[ix][2] - zi; dot = dx*rotate[2][0] + dy*rotate[2][1] + dz*rotate[2][2]; dx = dx - dot*rotate[2][0]; dy = dy - dot*rotate[2][1]; dz = dz - dot*rotate[2][2]; r = sqrt(dx*dx + dy*dy + dz*dz); rotate[0][0] = dx / r; rotate[0][1] = dy / r; rotate[0][2] = dz / r; // bisector frame rotation matrix elements for z- and x-axes } else if (polaxe[i] == BISECTOR) { dx = x[iz][0] - xi; dy = x[iz][1] - yi; dz = x[iz][2] - zi; r = sqrt(dx*dx + dy*dy + dz*dz); dx1 = dx / r; dy1 = dy / r; dz1 = dz / r; dx = x[ix][0] - xi; dy = x[ix][1] - yi; dz = x[ix][2] - zi; r = sqrt(dx*dx + dy*dy + dz*dz); dx2 = dx / r; dy2 = dy / r; dz2 = dz / r; dx = dx1 + dx2; dy = dy1 + dy2; dz = dz1 + dz2; r = sqrt(dx*dx + dy*dy + dz*dz); rotate[2][0] = dx / r; rotate[2][1] = dy / r; rotate[2][2] = dz / r; dot = dx2*rotate[2][0] + dy2*rotate[2][1] + dz2*rotate[2][2]; dx = dx2 - dot*rotate[2][0]; dy = dy2 - dot*rotate[2][1]; dz = dz2 - dot*rotate[2][2]; r = sqrt(dx*dx + dy*dy + dz*dz); rotate[0][0] = dx / r; rotate[0][1] = dy / r; rotate[0][2] = dz / r; // z-bisect frame rotation matrix elements for z- and x-axes } else if (polaxe[i] == ZBISECT) { dx = x[iz][0] - xi; dy = x[iz][1] - yi; dz = x[iz][2] - zi; r = sqrt(dx*dx + dy*dy + dz*dz); rotate[2][0] = dx / r; rotate[2][1] = dy / r; rotate[2][2] = dz / r; dx = x[ix][0] - xi; dy = x[ix][1] - yi; dz = x[ix][2] - zi; r = sqrt(dx*dx + dy*dy + dz*dz); dx1 = dx / r; dy1 = dy / r; dz1 = dz / r; dx = x[iy][0] - xi; dy = x[iy][1] - yi; dz = x[iy][2] - zi; r = sqrt(dx*dx + dy*dy + dz*dz); dx2 = dx / r; dy2 = dy / r; dz2 = dz / r; dx = dx1 + dx2; dy = dy1 + dy2; dz = dz1 + dz2; r = sqrt(dx*dx + dy*dy + dz*dz); dx = dx / r; dy = dy / r; dz = dz / r; dot = dx*rotate[2][0] + dy*rotate[2][1] + dz*rotate[2][2]; dx = dx - dot*rotate[2][0]; dy = dy - dot*rotate[2][1]; dz = dz - dot*rotate[2][2]; r = sqrt(dx*dx + dy*dy + dz*dz); rotate[0][0] = dx / r; rotate[0][1] = dy / r; rotate[0][2] = dz / r; // 3-fold frame rotation matrix elements for z- and x-axes } else if (polaxe[i] == THREEFOLD) { dx = x[iz][0] - xi; dy = x[iz][1] - yi; dz = x[iz][2] - zi; r = sqrt(dx*dx + dy*dy + dz*dz); dx1 = dx / r; dy1 = dy / r; dz1 = dz / r; dx = x[ix][0] - xi; dy = x[ix][1] - yi; dz = x[ix][2] - zi; r = sqrt(dx*dx + dy*dy + dz*dz); dx2 = dx / r; dy2 = dy / r; dz2 = dz / r; dx = x[iy][0] - xi; dy = x[iy][1] - yi; dz = x[iy][2] - zi; r = sqrt(dx*dx + dy*dy + dz*dz); dx3 = dx / r; dy3 = dy / r; dz3 = dz / r; dx = dx1 + dx2 + dx3; dy = dy1 + dy2 + dy3; dz = dz1 + dz2 + dz3; r = sqrt(dx*dx + dy*dy + dz*dz); rotate[2][0] = dx / r; rotate[2][1] = dy / r; rotate[2][2] = dz / r; dot = dx2*rotate[2][0] + dy2*rotate[2][1] + dz2*rotate[2][2]; dx = dx2 - dot*rotate[2][0]; dy = dy2 - dot*rotate[2][1]; dz = dz2 - dot*rotate[2][2]; r = sqrt(dx*dx + dy*dy + dz*dz); rotate[0][0] = dx / r; rotate[0][1] = dy / r; rotate[0][2] = dz / r; } // finally, find rotation matrix elements for the y-axis rotate[1][0] = rotate[0][2]*rotate[2][1] - rotate[0][1]*rotate[2][2]; rotate[1][1] = rotate[0][0]*rotate[2][2] - rotate[0][2]*rotate[2][0]; rotate[1][2] = rotate[0][1]*rotate[2][0] - rotate[0][0]*rotate[2][1]; } /* ---------------------------------------------------------------------- rotsite rotates the local frame atomic multipoles at a specified site into the global coordinate frame by applying a rotation matrix called every timestep for each atom Isite ------------------------------------------------------------------------- */ void PairAmoeba::rotsite(int isite) { int i,j,k,m; double mp[3][3]; double rp[3][3]; double **pole = fixpole->astore; // monopoles have the same value in any coordinate frame rpole[isite][0] = pole[isite][0]; // rotate the dipoles to the global coordinate frame for (i = 1; i <= 3; i++) { rpole[isite][i] = 0.0; for (j = 1; j <= 3; j++) rpole[isite][i] += pole[isite][j] * rotate[j-1][i-1]; } // rotate the quadrupoles to the global coordinate frame k = 4; for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { mp[j][i] = pole[isite][k]; rp[j][i] = 0.0; k++; } } for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { if (j < i) rp[j][i] = rp[i][j]; else { for (k = 0; k < 3; k++) for (m = 0; m < 3; m++) rp[j][i] += rotate[k][i]*rotate[m][j] * mp[m][k]; } } } k = 4; for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { rpole[isite][k] = rp[j][i]; k++; } } } /* ---------------------------------------------------------------------- scan standard neighbor list and make it compatible with 1-5 neighbors if IJ entry is a 1-2,1-3,1-4 neighbor then adjust offset to SBBITS15 else scan special15 to see if a 1-5 neighbor and adjust offset to SBBITS15 else do nothing to IJ entry ------------------------------------------------------------------------- */ void PairAmoeba::add_onefive_neighbors() { int i,j,k,ii,jj,which,inum,jnum,n15; tagint jtag; int *ilist,*jlist,*numneigh,**firstneigh; tagint *list15; // test for overflow of reduced allowed size of neighbor list if (atom->nlocal + atom->nghost > NEIGHMASK15) error->one(FLERR,"Pair amoeba neighbor list overflow"); // neigh list inum = list->inum; ilist = list->ilist; numneigh = list->numneigh; firstneigh = list->firstneigh; // reset special neighbor flags to include 1-5 neighbors tagint *tag = atom->tag; int *nspecial15 = atom->nspecial15; tagint **special15 = atom->special15; for (ii = 0; ii < inum; ii++) { i = ilist[ii]; n15 = nspecial15[i]; list15 = special15[i]; jlist = firstneigh[i]; jnum = numneigh[i]; for (jj = 0; jj < jnum; jj++) { j = jlist[jj]; which = j >> SBBITS & 3; j &= NEIGHMASK; jtag = tag[j]; if (!which) { for (k = 0; k < n15; k++) { if (list15[k] == jtag) { which = 4; break; } } } if (which) jlist[jj] = j ^ (which << SBBITS15); } } } /* ---------------------------------------------------------------------- update local indices of hydrogen neighbors for owned and ghost atoms red2local = used for offset of hydrogen positions in Vdwl term called on reneighboring steps, only for AMOEBA ------------------------------------------------------------------------- */ void PairAmoeba::find_hydrogen_neighbors() { int index; tagint id; // grab current ptr for redID // redID[i] = atom ID that atom I is bonded to // red2local[i] = local index of that atom // for furthest away ghost atoms, bond partner can be missing // in that case red2local = -1, but only an error if accessed in hal() double *redID = atom->dvector[index_redID]; int nlocal = atom->nlocal; int nall = nlocal + atom->nghost; for (int i = 0; i < nall; i++) { if (redID[i] == 0.0) red2local[i] = i; else { id = (tagint) ubuf(redID[i]).i; index = atom->map(id); if (index >= 0) index = domain->closest_image(i,index); red2local[i] = index; } } } /* ---------------------------------------------------------------------- update local indices of bond topology neighbors for owned atoms xyz axis2local = used for multipole orientation called on reneighboring steps ------------------------------------------------------------------------- */ void PairAmoeba::find_multipole_neighbors() { int index; tagint xaxisID,yaxisID,zaxisID; // grab current pts for xaxis,yaxis,zaxis // xyzaxis[i] = atom IDs that atom I uses for its multipole orientation // can be zero if not used, in which case set local index to self // yaxis can be negative, in which case use absolute value double **xyzaxis = atom->darray[index_xyzaxis]; int nlocal = atom->nlocal; int nmissing = 0; for (int i = 0; i < nlocal; i++) { xaxisID = (tagint) ubuf(xyzaxis[i][0]).i; yaxisID = (tagint) ubuf(xyzaxis[i][1]).i; zaxisID = (tagint) ubuf(xyzaxis[i][2]).i; if (xaxisID) { index = atom->map(xaxisID); if (index == -1) nmissing++; else { index = domain->closest_image(i,index); xaxis2local[i] = index; } } else xaxis2local[i] = i; if (yaxisID) { if (xyzaxis[i][1] < 0) yaxisID = -yaxisID; index = atom->map(yaxisID); if (index == -1) nmissing++; else { index = domain->closest_image(i,index); yaxis2local[i] = index; } } else yaxis2local[i] = i; if (zaxisID) { index = atom->map(zaxisID); if (index == -1) nmissing++; else { index = domain->closest_image(i,index); zaxis2local[i] = index; } } else zaxis2local[i] = i; } // error check on missing neighbors int nmissing_all; MPI_Allreduce(&nmissing,&nmissing_all,1,MPI_INT,MPI_SUM,world); if (nmissing_all) error->all(FLERR, "Pair amoeba: {} multipole neighbors missing\n", nmissing_all); } /* ---------------------------------------------------------------------- torque2force takes the torque values on a single site defined by a local coordinate frame and converts to Cartesian forces on the original site and sites specifying the local frame, also gives the x,y,z-force components needed for virial computation force distribution for the 3-fold local frame by Chao Lu, Ponder Lab, Washington University, July 2016 literature reference: P. L. Popelier and A. J. Stone, "Formulae for the First and Second Derivatives of Anisotropic Potentials with Respect to Geometrical Parameters", Molecular Physics, 82, 411-425 (1994) C. Segui, L. G. Pedersen and T. A. Darden, "Towards an Accurate Representation of Electrostatics in Classical Force Fields: Efficient Implementation of Multipolar Interactions in Biomolecular Simulations", Journal of Chemical Physics, 120, 73-87 (2004) i = single site = local atom index trq = torque on that atom frc xyz = returned xyz force components f = force vector for local atoms, multiple atoms will be updated ------------------------------------------------------------------------- */ void PairAmoeba::torque2force(int i, double *trq, double *frcx, double *frcy, double *frcz, double **f) { int j; int ia,ib,ic,id; int axetyp; double du,dv,dw,dot; double usiz,vsiz,wsiz; double psiz,rsiz,ssiz; double t1siz,t2siz; double uvsiz,uwsiz,vwsiz; double ursiz,ussiz; double vssiz,wssiz; double delsiz,dphiddel; double uvcos,urcos; double vscos,wscos; double upcos,vpcos,wpcos; double rwcos,rucos,rvcos; double ut1cos,ut2cos; double uvsin,ursin; double vssin,wssin; double rwsin,rusin,rvsin; double ut1sin,ut2sin; double dphidu,dphidv,dphidw; double dphidr,dphids; double u[3],v[3],w[3]; double p[3],r[3],s[3]; double t1[3],t2[3]; double uv[3],uw[3],vw[3]; double ur[3],us[3]; double vs[3],ws[3]; double del[3],eps[3]; double **x = atom->x; // zero out force components on local frame-defining atoms for (j = 0; j < 3; j++) { frcz[j] = 0.0; frcx[j] = 0.0; frcy[j] = 0.0; } // get the local frame type and the frame-defining atoms int *polaxe = atom->ivector[index_polaxe]; axetyp = polaxe[i]; if (axetyp == NOFRAME) return; ia = zaxis2local[i]; ib = i; ic = xaxis2local[i]; id = yaxis2local[i]; // construct the three rotation axes for the local frame u[0] = x[ia][0] - x[ib][0]; u[1] = x[ia][1] - x[ib][1]; u[2] = x[ia][2] - x[ib][2]; usiz = sqrt(u[0]*u[0] + u[1]*u[1] + u[2]*u[2]); if (axetyp != ZONLY) { v[0] = x[ic][0] - x[ib][0]; v[1] = x[ic][1] - x[ib][1]; v[2] = x[ic][2] - x[ib][2]; vsiz = sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); } else { v[0] = 1.0; v[1] = 0.0; v[2] = 0.0; vsiz = 1.0; dot = u[0] / usiz; if (fabs(dot) > 0.866) { v[0] = 0.0; v[1] = 1.0; } } if (axetyp == ZBISECT || axetyp == THREEFOLD) { w[0] = x[id][0] - x[ib][0]; w[1] = x[id][1] - x[ib][1]; w[2] = x[id][2] - x[ib][2]; } else { w[0] = u[1]*v[2] - u[2]*v[1]; w[1] = u[2]*v[0] - u[0]*v[2]; w[2] = u[0]*v[1] - u[1]*v[0]; } wsiz = sqrt(w[0]*w[0] + w[1]*w[1] + w[2]*w[2]); for (j = 0; j < 3; j++) { u[j] /= usiz; v[j] /= vsiz; w[j] /= wsiz; } // build some additional axes needed for the Z-Bisect method if (axetyp == ZBISECT) { r[0] = v[0] + w[0]; r[1] = v[1] + w[1]; r[2] = v[2] + w[2]; rsiz = sqrt(r[0]*r[0] + r[1]*r[1] + r[2]*r[2]); s[0] = u[1]*r[2] - u[2]*r[1]; s[1] = u[2]*r[0] - u[0]*r[2]; s[2] = u[0]*r[1] - u[1]*r[0]; ssiz = sqrt(s[0]*s[0] + s[1]*s[1] + s[2]*s[2]); for (j = 0; j < 3; j++) { r[j] /= rsiz; s[j] /= ssiz; } } // find the perpendicular and angle for each pair of axes uv[0] = v[1]*u[2] - v[2]*u[1]; uv[1] = v[2]*u[0] - v[0]*u[2]; uv[2] = v[0]*u[1] - v[1]*u[0]; uvsiz = sqrt(uv[0]*uv[0] + uv[1]*uv[1] + uv[2]*uv[2]); uw[0] = w[1]*u[2] - w[2]*u[1]; uw[1] = w[2]*u[0] - w[0]*u[2]; uw[2] = w[0]*u[1] - w[1]*u[0]; uwsiz = sqrt(uw[0]*uw[0] + uw[1]*uw[1] + uw[2]*uw[2]); vw[0] = w[1]*v[2] - w[2]*v[1]; vw[1] = w[2]*v[0] - w[0]*v[2]; vw[2] = w[0]*v[1] - w[1]*v[0]; vwsiz = sqrt(vw[0]*vw[0] + vw[1]*vw[1] + vw[2]*vw[2]); for (j = 0; j < 3; j++) { uv[j] /= uvsiz; uw[j] /= uwsiz; vw[j] /= vwsiz; } if (axetyp == ZBISECT) { ur[0] = r[1]*u[2] - r[2]*u[1]; ur[1] = r[2]*u[0] - r[0]*u[2]; ur[2] = r[0]*u[1] - r[1]*u[0]; ursiz = sqrt(ur[0]*ur[0] + ur[1]*ur[1] + ur[2]*ur[2]); us[0] = s[1]*u[2] - s[2]*u[1]; us[1] = s[2]*u[0] - s[0]*u[2]; us[2] = s[0]*u[1] - s[1]*u[0]; ussiz = sqrt(us[0]*us[0] + us[1]*us[1] + us[2]*us[2]); vs[0] = s[1]*v[2] - s[2]*v[1]; vs[1] = s[2]*v[0] - s[0]*v[2]; vs[2] = s[0]*v[1] - s[1]*v[0]; vssiz = sqrt(vs[0]*vs[0] + vs[1]*vs[1] + vs[2]*vs[2]); ws[0] = s[1]*w[2] - s[2]*w[1]; ws[1] = s[2]*w[0] - s[0]*w[2]; ws[2] = s[0]*w[1] - s[1]*w[0]; wssiz = sqrt(ws[0]*ws[0] + ws[1]*ws[1] + ws[2]*ws[2]); for (j = 0; j < 3; j++) { ur[j] /= ursiz; us[j] /= ussiz; vs[j] /= vssiz; ws[j] /= wssiz; } } // get sine and cosine of angles between the rotation axes uvcos = u[0]*v[0] + u[1]*v[1] + u[2]*v[2]; uvsin = sqrt(1.0 - uvcos*uvcos); if (axetyp == ZBISECT) { urcos = u[0]*r[0] + u[1]*r[1] + u[2]*r[2]; ursin = sqrt(1.0 - urcos*urcos); vscos = v[0]*s[0] + v[1]*s[1] + v[2]*s[2]; vssin = sqrt(1.0 - vscos*vscos); wscos = w[0]*s[0] + w[1]*s[1] + w[2]*s[2]; wssin = sqrt(1.0 - wscos*wscos); } // compute the projection of v and w onto the ru-plane if (axetyp == ZBISECT) { for (j = 0; j < 3; j++) { t1[j] = v[j] - s[j]*vscos; t2[j] = w[j] - s[j]*wscos; } t1siz = sqrt(t1[0]*t1[0] + t1[1]*t1[1] + t1[2]*t1[2]); t2siz = sqrt(t2[0]*t2[0] + t2[1]*t2[1] + t2[2]*t2[2]); for (j = 0; j < 3; j++) { t1[j] /= t1siz; t2[j] /= t2siz; } ut1cos = u[0]*t1[0] + u[1]*t1[1] + u[2]*t1[2]; ut1sin = sqrt(1.0 - ut1cos*ut1cos); ut2cos = u[0]*t2[0] + u[1]*t2[1] + u[2]*t2[2]; ut2sin = sqrt(1.0 - ut2cos*ut2cos); } // negative of dot product of torque with unit vectors gives // result of infinitesimal rotation along these vectors dphidu = -trq[0]*u[0] - trq[1]*u[1] - trq[2]*u[2]; dphidv = -trq[0]*v[0] - trq[1]*v[1] - trq[2]*v[2]; dphidw = -trq[0]*w[0] - trq[1]*w[1] - trq[2]*w[2]; if (axetyp == ZBISECT) { dphidr = -trq[0]*r[0] - trq[1]*r[1] - trq[2]*r[2]; dphids = -trq[0]*s[0] - trq[1]*s[1] - trq[2]*s[2]; } // force distribution for the Z-Only local coordinate method if (axetyp == ZONLY) { for (j = 0; j < 3; j++) { du = uv[j]*dphidv/(usiz*uvsin) + uw[j]*dphidw/usiz; f[ia][j] -= du; f[ib][j] += du; frcz[j] += du; } // force distribution for the Z-then-X local coordinate method } else if (axetyp == ZTHENX) { for (j = 0; j < 3; j++) { du = uv[j]*dphidv/(usiz*uvsin) + uw[j]*dphidw/usiz; dv = -uv[j]*dphidu/(vsiz*uvsin); f[ia][j] -= du; f[ic][j] -= dv; f[ib][j] += du + dv; frcz[j] += du; frcx[j] += dv; } // force distribution for the Bisector local coordinate method } else if (axetyp == BISECTOR) { for (j = 0; j < 3; j++) { du = uv[j]*dphidv/(usiz*uvsin) + 0.5*uw[j]*dphidw/usiz; dv = -uv[j]*dphidu/(vsiz*uvsin) + 0.5*vw[j]*dphidw/vsiz; f[ia][j] -= du; f[ic][j] -= dv; f[ib][j] += du + dv; frcz[j] += du; frcx[j] += dv; } // force distribution for the Z-Bisect local coordinate method } else if (axetyp == ZBISECT) { for (j = 0; j < 3; j++) { du = ur[j]*dphidr/(usiz*ursin) + us[j]*dphids/usiz; dv = (vssin*s[j]-vscos*t1[j])*dphidu / (vsiz*(ut1sin+ut2sin)); dw = (wssin*s[j]-wscos*t2[j])*dphidu / (wsiz*(ut1sin+ut2sin)); f[ia][j] -= du; f[ic][j] -= dv; f[id][j] -= dw; f[ib][j] += du + dv + dw; frcz[j] += du; frcx[j] += dv; frcy[j] += dw; } // force distribution for the 3-Fold local coordinate method } else if (axetyp == THREEFOLD) { p[0] = u[0] + v[0] + w[0]; p[1] = u[1] + v[1] + w[1]; p[2] = u[2] + v[2] + w[2]; psiz = sqrt(p[0]*p[0] + p[1]*p[1] + p[2]*p[2]) ; for (j = 0; j < 3; j++) p[j] /= psiz; wpcos = w[0]*p[0] + w[1]*p[1] + w[2]*p[2]; upcos = u[0]*p[0] + u[1]*p[1] + u[2]*p[2]; vpcos = v[0]*p[0] + v[1]*p[1] + v[2]*p[2]; r[0] = u[0] + v[0]; r[1] = u[1] + v[1]; r[2] = u[2] + v[2]; rsiz = sqrt(r[0]*r[0] + r[1]*r[1] + r[2]*r[2]); for (j = 0; j < 3; j++) r[j] /= rsiz; rwcos = r[0]*w[0] + r[1]*w[1] + r[2]*w[2]; rwsin = sqrt(1.0 - rwcos*rwcos); dphidr = -trq[0]*r[0] - trq[1]*r[1] - trq[2]*r[2]; del[0] = r[1]*w[2] - r[2]*w[1]; del[1] = r[2]*w[0] - r[0]*w[2] ; del[2] = r[0]*w[1] - r[1]*w[0]; delsiz = sqrt(del[0]*del[0] + del[1]*del[1] + del[2]*del[2]); for (j = 0; j < 3; j++) del[j] /= delsiz; dphiddel = -trq[0]*del[0] - trq[1]*del[1] - trq[2]*del[2]; eps[0] = del[1]*w[2] - del[2]*w[1]; eps[1] = del[2]*w[0] - del[0]*w[2]; eps[2] = del[0]*w[1] - del[1]*w[0]; for (j = 0; j < 3; j++) { dw = del[j]*dphidr/(wsiz*rwsin) + eps[j]*dphiddel*wpcos/(wsiz*psiz); f[id][j] -= dw; f[ib][j] += dw; frcy[j] += dw; } r[0] = v[0] + w[0]; r[1] = v[1] + w[1]; r[2] = v[2] + w[2]; rsiz = sqrt(r[0]*r[0] + r[1]*r[1] + r[2]*r[2]); for (j = 0; j < 3; j++) r[j] /= rsiz; rucos = r[0]*u[0] + r[1]*u[1] + r[2]*u[2]; rusin = sqrt(1.0 - rucos*rucos) ; dphidr = -trq[0]*r[0] - trq[1]*r[1] - trq[2]*r[2]; del[0] = r[1]*u[2] - r[2]*u[1]; del[1] = r[2]*u[0] - r[0]*u[2]; del[2] = r[0]*u[1] - r[1]*u[0]; delsiz = sqrt(del[0]*del[0] + del[1]*del[1] + del[2]*del[2]); for (j = 0; j < 3; j++) del[j] /= delsiz; dphiddel = -trq[0]*del[0] - trq[1]*del[1] - trq[2]*del[2]; eps[0] = del[1]*u[2] - del[2]*u[1]; eps[1] = del[2]*u[0] - del[0]*u[2]; eps[2] = del[0]*u[1] - del[1]*u[0]; for (j = 0; j < 3; j++) { du = del[j]*dphidr/(usiz*rusin) + eps[j]*dphiddel*upcos/(usiz*psiz); f[ia][j] -= du; f[ib][j] += du; frcz[j] += du; } r[0] = u[0] + w[0]; r[1] = u[1] + w[1]; r[2] = u[2] + w[2]; rsiz = sqrt(r[0]*r[0] + r[1]*r[1] + r[2]*r[2]); for (j = 0; j < 3; j++) r[j] /= rsiz; rvcos = r[0]*v[0] + r[1]*v[1] + r[2]*v[2] ; rvsin = sqrt(1.0 - rvcos*rvcos); dphidr = -trq[0]*r[0] - trq[1]*r[1] - trq[2]*r[2]; del[0] = r[1]*v[2] - r[2]*v[1]; del[1] = r[2]*v[0] - r[0]*v[2]; del[2] = r[0]*v[1] - r[1]*v[0]; delsiz = sqrt(del[0]*del[0] + del[1]*del[1] + del[2]*del[2]); for (j = 0; j < 3; j++) del[j] /= delsiz; dphiddel = -trq[0]*del[0] - trq[1]*del[1] - trq[2]*del[2]; eps[0] = del[1]*v[2] - del[2]*v[1]; eps[1] = del[2]*v[0] - del[0]*v[2]; eps[2] = del[0]*v[1] - del[1]*v[0]; for (j = 0; j < 3; j++) { dv = del[j]*dphidr/(vsiz*rvsin) + eps[j]*dphiddel*vpcos/(vsiz*psiz); f[ic][j] -= dv; f[ib][j] += dv; frcx[j] += dv; } } }