/* ---------------------------------------------------------------------- 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: Tony Sheh (U Michigan), Trung Dac Nguyen (U Michigan) references: Kamberaj et al., J. Chem. Phys. 122, 224114 (2005) Miller et al., J Chem Phys. 116, 8649-8659 ------------------------------------------------------------------------- */ #include "math.h" #include "stdio.h" #include "stdlib.h" #include "string.h" #include "fix_rigid_nvt.h" #include "atom.h" #include "domain.h" #include "update.h" #include "modify.h" #include "group.h" #include "comm.h" #include "force.h" #include "output.h" #include "memory.h" #include "error.h" using namespace LAMMPS_NS; /* ---------------------------------------------------------------------- */ FixRigidNVT::FixRigidNVT(LAMMPS *lmp, int narg, char **arg) : FixRigid(lmp, narg, arg) { // other settings are made by FixRigid parent scalar_flag = 1; restart_global = 1; extscalar = 1; // error checking // convert input period to frequency if (tempflag == 0) error->all("Did not set temp for fix rigid/nvt"); if (t_start < 0.0 || t_stop <= 0.0) error->all("Target temperature for fix rigid/nvt cannot be 0.0"); if (t_period <= 0.0) error->all("Fix rigid/nvt period must be > 0.0"); t_freq = 1.0 / t_period; if (t_chain < 1) error->all("Illegal fix_modify command"); if (t_iter < 1) error->all("Illegal fix_modify command"); if (t_order != 3 && t_order != 5) error->all("Fix_modify order must be 3 or 5"); allocate_chain(); allocate_order(); conjqm = memory->create_2d_double_array(nbody,4,"nve_rigid:conjqm"); // one-time initialize of thermostat variables eta_t[0] = eta_r[0] = 0.0; eta_dot_t[0] = eta_dot_r[0] = 0.0; f_eta_t[0] = f_eta_r[0] = 0.0; for (int i = 1; i < t_chain; i++) { eta_t[i] = eta_r[i] = 0.0; eta_dot_t[i] = eta_dot_r[i] = 0.0; } } /* ---------------------------------------------------------------------- */ FixRigidNVT::~FixRigidNVT() { deallocate_chain(); deallocate_order(); memory->destroy_2d_double_array(conjqm); } /* ---------------------------------------------------------------------- */ void FixRigidNVT::init() { FixRigid::init(); // initialize thermostats // set timesteps, constants // store Yoshida-Suzuki integrator parameters dtv = update->dt; dtf = 0.5 * update->dt * force->ftm2v; dtq = 0.5 * update->dt; boltz = force->boltz; nf_t = nf_r = domain->dimension * nbody; for (int ibody = 0; ibody < nbody; ibody++) for (int k = 0; k < domain->dimension; k++) if (fabs(inertia[ibody][k]) < 1e-6) nf_r--; // see Table 1 Kamberaj et al. if (t_order == 3) { w[0] = 1.0 / (2.0 - pow(2.0, 1.0/3.0)); w[1] = 1.0 - 2.0*w[0]; w[2] = w[0]; } else if (t_order == 5) { w[0] = 1.0 / (4.0 - pow(4.0, 1.0/3.0)); w[1] = w[0]; w[2] = 1.0 - 4.0 * w[0]; w[3] = w[0]; w[4] = w[0]; } // initialize thermostat settings t_target = t_start; double kt = boltz * t_target; double t_mass = kt / (t_freq*t_freq); q_t[0] = nf_t * t_mass; q_r[0] = nf_r * t_mass; for (int i = 1; i < t_chain; i++) q_t[i] = q_r[i] = t_mass; // initialize thermostat chain positions, velocites, forces for (int i = 1; i < t_chain; i++) { f_eta_t[i] = q_t[i-1] * eta_dot_t[i-1] * eta_dot_t[i-1] - kt; f_eta_r[i] = q_r[i-1] * eta_dot_r[i-1] * eta_dot_r[i-1] - kt; } } /* ---------------------------------------------------------------------- */ void FixRigidNVT::setup(int vflag) { FixRigid::setup(vflag); t_target = t_start; double mbody[3]; for (int ibody = 0; ibody < nbody; ibody++) { matvec_rows(ex_space[ibody],ey_space[ibody],ez_space[ibody], angmom[ibody],mbody); quatvec(quat[ibody],mbody,conjqm[ibody]); conjqm[ibody][0] *= 2.0; conjqm[ibody][1] *= 2.0; conjqm[ibody][2] *= 2.0; conjqm[ibody][3] *= 2.0; } } /* ---------------------------------------------------------------------- perform preforce velocity Verlet integration see Kamberaj paper for step references ------------------------------------------------------------------------- */ void FixRigidNVT::initial_integrate(int vflag) { double tmp,akin_t,akin_r,scale_t,scale_r; double dtfm,mbody[3],tbody[3],fquat[4]; double dtf2 = dtf * 2.0; double delta = update->ntimestep - update->beginstep; delta /= update->endstep - update->beginstep; t_target = t_start + delta * (t_stop - t_start); // intialize velocity scale for translation and rotation akin_t = akin_r = 0.0; tmp = -1.0 * dtq * eta_dot_t[0]; scale_t = exp(tmp); tmp = -1.0 * dtq * eta_dot_r[0]; scale_r = exp(tmp); for (int ibody = 0; ibody < nbody; ibody++) { // step 1.1 - update vcm by 1/2 step dtfm = dtf / masstotal[ibody]; vcm[ibody][0] += dtfm * fcm[ibody][0] * fflag[ibody][0]; vcm[ibody][1] += dtfm * fcm[ibody][1] * fflag[ibody][1]; vcm[ibody][2] += dtfm * fcm[ibody][2] * fflag[ibody][2]; vcm[ibody][0] *= scale_t; vcm[ibody][1] *= scale_t; vcm[ibody][2] *= scale_t; tmp = vcm[ibody][0]*vcm[ibody][0] + vcm[ibody][1]*vcm[ibody][1] + vcm[ibody][2]*vcm[ibody][2]; akin_t += masstotal[ibody]*tmp; // step 1.2 - update xcm by full step xcm[ibody][0] += dtv * vcm[ibody][0]; xcm[ibody][1] += dtv * vcm[ibody][1]; xcm[ibody][2] += dtv * vcm[ibody][2]; torque[ibody][0] *= tflag[ibody][0]; torque[ibody][1] *= tflag[ibody][1]; torque[ibody][2] *= tflag[ibody][2]; // step 1.3 - apply torque (body coords) to quaternion momentum matvec_rows(ex_space[ibody],ey_space[ibody],ez_space[ibody], torque[ibody],tbody); quatvec(quat[ibody],tbody,fquat); conjqm[ibody][0] += dtf2 * fquat[0]; conjqm[ibody][1] += dtf2 * fquat[1]; conjqm[ibody][2] += dtf2 * fquat[2]; conjqm[ibody][3] += dtf2 * fquat[3]; conjqm[ibody][0] *= scale_r; conjqm[ibody][1] *= scale_r; conjqm[ibody][2] *= scale_r; conjqm[ibody][3] *= scale_r; // step 1.4 to 1.8 - use no_squish rotate to update p (i.e. conjqm) and q no_squish_rotate(3,conjqm[ibody],quat[ibody],inertia[ibody],dtq); no_squish_rotate(2,conjqm[ibody],quat[ibody],inertia[ibody],dtq); no_squish_rotate(1,conjqm[ibody],quat[ibody],inertia[ibody],dtv); no_squish_rotate(2,conjqm[ibody],quat[ibody],inertia[ibody],dtq); no_squish_rotate(3,conjqm[ibody],quat[ibody],inertia[ibody],dtq); // update the exyz_space from new quaternion // transform p back to angmom // update angular velocity exyz_from_q(quat[ibody],ex_space[ibody],ey_space[ibody], ez_space[ibody]); invquatvec(quat[ibody],conjqm[ibody],mbody); matvec_cols(ex_space[ibody],ey_space[ibody],ez_space[ibody], mbody,angmom[ibody]); angmom[ibody][0] *= 0.5; angmom[ibody][1] *= 0.5; angmom[ibody][2] *= 0.5; omega_from_angmom(angmom[ibody],ex_space[ibody],ey_space[ibody], ez_space[ibody],inertia[ibody],omega[ibody]); akin_r += angmom[ibody][0]*omega[ibody][0] + angmom[ibody][1]*omega[ibody][1] + angmom[ibody][2]*omega[ibody][2]; } // update thermostat chains update_nhcp(akin_t,akin_r); // virial setup before call to set_xv if (vflag) v_setup(vflag); else evflag = 0; // set coords/orient and velocity/rotation of atoms in rigid bodies // from quarternion and omega set_xv(); } /* ---------------------------------------------------------------------- */ void FixRigidNVT::final_integrate() { int i,ibody; double tmp,scale_t,scale_r,akin_t,akin_r; double dtfm,xy,xz,yz; // compute velocity scales for translation and rotation tmp = -1.0 * dtq * eta_dot_t[0]; scale_t = exp(tmp); tmp = -1.0 * dtq * eta_dot_r[0]; scale_r = exp(tmp); // sum over atoms to get force and torque on rigid body int *image = atom->image; double **x = atom->x; double **f = atom->f; int nlocal = atom->nlocal; double xprd = domain->xprd; double yprd = domain->yprd; double zprd = domain->zprd; if (triclinic) { xy = domain->xy; xz = domain->xz; yz = domain->yz; } int xbox,ybox,zbox; double xunwrap,yunwrap,zunwrap,dx,dy,dz; for (ibody = 0; ibody < nbody; ibody++) for (i = 0; i < 6; i++) sum[ibody][i] = 0.0; for (i = 0; i < nlocal; i++) { if (body[i] < 0) continue; ibody = body[i]; sum[ibody][0] += f[i][0]; sum[ibody][1] += f[i][1]; sum[ibody][2] += f[i][2]; xbox = (image[i] & 1023) - 512; ybox = (image[i] >> 10 & 1023) - 512; zbox = (image[i] >> 20) - 512; if (triclinic == 0) { xunwrap = x[i][0] + xbox*xprd; yunwrap = x[i][1] + ybox*yprd; zunwrap = x[i][2] + zbox*zprd; } else { xunwrap = x[i][0] + xbox*xprd + ybox*xy + zbox*xz; yunwrap = x[i][1] + ybox*yprd + zbox*yz; zunwrap = x[i][2] + zbox*zprd; } dx = xunwrap - xcm[ibody][0]; dy = yunwrap - xcm[ibody][1]; dz = zunwrap - xcm[ibody][2]; sum[ibody][3] += dy*f[i][2] - dz*f[i][1]; sum[ibody][4] += dz*f[i][0] - dx*f[i][2]; sum[ibody][5] += dx*f[i][1] - dy*f[i][0]; } // extended particles add their torque to torque of body if (extended) { double **torque_one = atom->torque; for (i = 0; i < nlocal; i++) { if (body[i] < 0) continue; ibody = body[i]; if (eflags[i] & TORQUE) { sum[ibody][3] += torque_one[i][0]; sum[ibody][4] += torque_one[i][1]; sum[ibody][5] += torque_one[i][2]; } } } MPI_Allreduce(sum[0],all[0],6*nbody,MPI_DOUBLE,MPI_SUM,world); double mbody[3],tbody[3],fquat[4]; double dtf2 = dtf * 2.0; for (ibody = 0; ibody < nbody; ibody++) { fcm[ibody][0] = all[ibody][0]; fcm[ibody][1] = all[ibody][1]; fcm[ibody][2] = all[ibody][2]; torque[ibody][0] = all[ibody][3]; torque[ibody][1] = all[ibody][4]; torque[ibody][2] = all[ibody][5]; // 2.5-2.6 update vcm by 1/2 step dtfm = dtf / masstotal[ibody]; vcm[ibody][0] *= scale_t; vcm[ibody][1] *= scale_t; vcm[ibody][2] *= scale_t; vcm[ibody][0] += dtfm * fcm[ibody][0] * fflag[ibody][0]; vcm[ibody][1] += dtfm * fcm[ibody][1] * fflag[ibody][1]; vcm[ibody][2] += dtfm * fcm[ibody][2] * fflag[ibody][2]; // 2.1-2.4 update conjqm, angular momentum and angular velocity // apply body torque flags torque[ibody][0] *= tflag[ibody][0]; torque[ibody][1] *= tflag[ibody][1]; torque[ibody][2] *= tflag[ibody][2]; // convert torque to the body frame matvec_rows(ex_space[ibody],ey_space[ibody],ez_space[ibody], torque[ibody],tbody); // compute "force" for quaternion quatvec(quat[ibody],tbody,fquat); // update the conjugate quaternion momentum (conjqm) conjqm[ibody][0] = scale_r * conjqm[ibody][0] + dtf2 * fquat[0]; conjqm[ibody][1] = scale_r * conjqm[ibody][1] + dtf2 * fquat[1]; conjqm[ibody][2] = scale_r * conjqm[ibody][2] + dtf2 * fquat[2]; conjqm[ibody][3] = scale_r * conjqm[ibody][3] + dtf2 * fquat[3]; // compute angular momentum in the body frame then convert to the space-fixed frame invquatvec(quat[ibody],conjqm[ibody],mbody); matvec_cols(ex_space[ibody],ey_space[ibody],ez_space[ibody], mbody,angmom[ibody]); angmom[ibody][0] *= 0.5; angmom[ibody][1] *= 0.5; angmom[ibody][2] *= 0.5; // compute new angular velocity omega_from_angmom(angmom[ibody],ex_space[ibody],ey_space[ibody], ez_space[ibody],inertia[ibody],omega[ibody]); } // set velocity/rotation of atoms in rigid bodies // virial is already setup from initial_integrate set_v(); } /* ---------------------------------------------------------------------- */ void FixRigidNVT::update_nhcp(double akin_t, double akin_r) { int i,j,k; double kt,gfkt_t,gfkt_r,tmp,ms,s,s2; kt = boltz * t_target; gfkt_t = nf_t * kt; gfkt_r = nf_r * kt; akin_t *= force->mvv2e; akin_r *= force->mvv2e; // update thermostat masses double t_mass = boltz * t_target / (t_freq * t_freq); q_t[0] = nf_t * t_mass; q_r[0] = nf_r * t_mass; for (i = 1; i < t_chain; i++) q_t[i] = q_r[i] = t_mass; // update order/timestep dependent coefficients for (i = 0; i < t_order; i++) { wdti1[i] = w[i] * dtv / t_iter; wdti2[i] = wdti1[i] / 2.0; wdti4[i] = wdti1[i] / 4.0; } // update force of thermostats coupled to particles f_eta_t[0] = (akin_t - gfkt_t) / q_t[0]; f_eta_r[0] = (akin_r - gfkt_r) / q_r[0]; // multiple timestep iteration for (i = 0; i < t_iter; i++) { for (j = 0; j < t_order; j++) { // update thermostat velocities half step eta_dot_t[t_chain-1] += wdti2[j] * f_eta_t[t_chain-1]; eta_dot_r[t_chain-1] += wdti2[j] * f_eta_r[t_chain-1]; for (k = 1; k < t_chain; k++) { tmp = wdti4[j] * eta_dot_t[t_chain-k]; ms = maclaurin_series(tmp); s = exp(-1.0 * tmp); s2 = s * s; eta_dot_t[t_chain-k-1] = eta_dot_t[t_chain-k-1] * s2 + wdti2[j] * f_eta_t[t_chain-k-1] * s * ms; tmp = wdti4[j] * eta_dot_r[t_chain-k]; ms = maclaurin_series(tmp); s = exp(-1.0 * tmp); s2 = s * s; eta_dot_r[t_chain-k-1] = eta_dot_r[t_chain-k-1] * s2 + wdti2[j] * f_eta_r[t_chain-k-1] * s * ms; } // update thermostat positions a full step for (k = 0; k < t_chain; k++) { eta_t[k] += wdti1[j] * eta_dot_t[k]; eta_r[k] += wdti1[j] * eta_dot_r[k]; } // update thermostat forces for (k = 1; k < t_chain; k++) { f_eta_t[k] = q_t[k-1] * eta_dot_t[k-1] * eta_dot_t[k-1] - kt; f_eta_t[k] /= q_t[k]; f_eta_r[k] = q_r[k-1] * eta_dot_r[k-1] * eta_dot_r[k-1] - kt; f_eta_r[k] /= q_r[k]; } // update thermostat velocities a full step for (k = 0; k < t_chain-1; k++) { tmp = wdti4[j] * eta_dot_t[k+1]; ms = maclaurin_series(tmp); s = exp(-1.0 * tmp); s2 = s * s; eta_dot_t[k] = eta_dot_t[k] * s2 + wdti2[j] * f_eta_t[k] * s * ms; tmp = q_t[k] * eta_dot_t[k] * eta_dot_t[k] - kt; f_eta_t[k+1] = tmp / q_t[k+1]; tmp = wdti4[j] * eta_dot_r[k+1]; ms = maclaurin_series(tmp); s = exp(-1.0 * tmp); s2 = s * s; eta_dot_r[k] = eta_dot_r[k] * s2 + wdti2[j] * f_eta_r[k] * s * ms; tmp = q_r[k] * eta_dot_r[k] * eta_dot_r[k] - kt; f_eta_r[k+1] = tmp / q_r[k+1]; } eta_dot_t[t_chain-1] += wdti2[j] * f_eta_t[t_chain-1]; eta_dot_r[t_chain-1] += wdti2[j] * f_eta_r[t_chain-1]; } } } /* ---------------------------------------------------------------------- pack entire state of Fix into one write ------------------------------------------------------------------------- */ void FixRigidNVT::write_restart(FILE *fp) { int n = 0; double *list = new double[1+6*t_chain]; list[n++] = t_chain; for (int i = 0; i < t_chain; i++) { list[n++] = eta_t[i]; list[n++] = eta_r[i]; list[n++] = eta_dot_t[i]; list[n++] = eta_dot_r[i]; list[n++] = f_eta_t[i]; list[n++] = f_eta_r[i]; } if (comm->me == 0) { int size = (1 + 6*t_chain)*sizeof(double); fwrite(&size,sizeof(int),1,fp); fwrite(list,sizeof(double),1+6*t_chain,fp); } delete list; } /* ---------------------------------------------------------------------- Compute the kinetic energy in the extended Hamiltonian: the conserved quantity is the sum of the returned energy and potential energy. -----------------------------------------------------------------------*/ double FixRigidNVT::compute_scalar() { int i,k,ibody; double kt = boltz * t_target; double energy,ke_t,ke_q,tmp,Pkq[4]; // compute the kinetic parts of H_NVE in Kameraj et al. (JCP 2005, pp 224114) // translational kinetic energy ke_t = 0.0; for (ibody = 0; ibody < nbody; ibody++) { ke_t += 0.5 * masstotal[ibody] * (vcm[ibody][0]*vcm[ibody][0] + vcm[ibody][1]*vcm[ibody][1] + vcm[ibody][2]*vcm[ibody][2]); } // rotational rotational energy ke_q = 0.0; for (ibody = 0; ibody < nbody; ibody++) { for (k = 1; k < 4; k++) { if (k == 1) { Pkq[0] = -quat[ibody][1]; Pkq[1] = quat[ibody][0]; Pkq[2] = quat[ibody][3]; Pkq[3] = -quat[ibody][2]; } else if (k == 2) { Pkq[0] = -quat[ibody][2]; Pkq[1] = -quat[ibody][3]; Pkq[2] = quat[ibody][0]; Pkq[3] = quat[ibody][1]; } else if (k == 3) { Pkq[0] = -quat[ibody][3]; Pkq[1] = quat[ibody][2]; Pkq[2] = -quat[ibody][1]; Pkq[3] = quat[ibody][0]; } tmp = conjqm[ibody][0]*Pkq[0] + conjqm[ibody][1]*Pkq[1] + conjqm[ibody][2]*Pkq[2] + conjqm[ibody][3]*Pkq[3]; tmp *= tmp; if (fabs(inertia[ibody][k-1]) < 1e-6) tmp *= 0.0; else tmp /= (8.0 * inertia[ibody][k-1]); ke_q += tmp; } } energy = ke_t + ke_q; // thermostat chain energy: from equation 12 in Kameraj et al. (JCP 2005) energy += kt * (nf_t * eta_t[0] + nf_r * eta_r[0]); for (i = 1; i < t_chain; i++) energy += kt * (eta_t[i] + eta_r[i]); for (i = 0; i < t_chain; i++) { energy += 0.5 * q_t[i] * (eta_dot_t[i] * eta_dot_t[i]); energy += 0.5 * q_r[i] * (eta_dot_r[i] * eta_dot_r[i]); } return energy; } /* ---------------------------------------------------------------------- use state info from restart file to restart the Fix ------------------------------------------------------------------------- */ void FixRigidNVT::restart(char *buf) { int n = 0; double *list = (double *) buf; int t_chain_prev = static_cast (list[n++]); if (t_chain_prev != t_chain) error->all("Cannot restart fix rigid/nvt with different # of chains"); for (int i = 0; i < t_chain; i++) { eta_t[i] = list[n++]; eta_r[i] = list[n++]; eta_dot_t[i] = list[n++]; eta_dot_r[i] = list[n++]; f_eta_t[i] = list[n++]; f_eta_r[i] = list[n++]; } } /* ---------------------------------------------------------------------- */ void FixRigidNVT::reset_target(double t_new) { t_start = t_stop = t_new; } /* ---------------------------------------------------------------------- */ void FixRigidNVT::allocate_chain() { q_t = new double[t_chain]; q_r = new double[t_chain]; eta_t = new double[t_chain]; eta_r = new double[t_chain]; eta_dot_t = new double[t_chain]; eta_dot_r = new double[t_chain]; f_eta_t = new double[t_chain]; f_eta_r = new double[t_chain]; } /* ---------------------------------------------------------------------- */ void FixRigidNVT::allocate_order() { w = new double[t_order]; wdti1 = new double[t_order]; wdti2 = new double[t_order]; wdti4 = new double[t_order]; } /* ---------------------------------------------------------------------- */ void FixRigidNVT::deallocate_chain() { delete [] q_t; delete [] q_r; delete [] eta_t; delete [] eta_r; delete [] eta_dot_t; delete [] eta_dot_r; delete [] f_eta_t; delete [] f_eta_r; } /* ---------------------------------------------------------------------- */ void FixRigidNVT::deallocate_order() { delete [] w; delete [] wdti1; delete [] wdti2; delete [] wdti4; }