/* ---------------------------------------------------------------------- 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: Gareth Tribello (Queens U, Belfast) Pablo Piaggi (EPFL) ------------------------------------------------------------------------- */ #include #include #include "atom.h" #include "comm.h" #include "update.h" #include "force.h" #include "respa.h" #include "domain.h" #include "error.h" #include "group.h" #include "fix_plumed.h" #include "universe.h" #include "compute.h" #include "modify.h" #include "pair.h" #include "timer.h" #include "plumed/wrapper/Plumed.h" #if defined(__PLUMED_DEFAULT_KERNEL) #define PLUMED_QUOTE_DIRECT(name) #name #define PLUMED_QUOTE(macro) PLUMED_QUOTE_DIRECT(macro) static char plumed_default_kernel[] = "PLUMED_KERNEL=" PLUMED_QUOTE(__PLUMED_DEFAULT_KERNEL); #endif /* -------------------------------------------------------------------- */ using namespace LAMMPS_NS; using namespace FixConst; #define INVOKED_SCALAR 1 FixPlumed::FixPlumed(LAMMPS *lmp, int narg, char **arg) : Fix(lmp, narg, arg), p(nullptr), nlocal(0), gatindex(nullptr), masses(nullptr), charges(nullptr), id_pe(nullptr), id_press(nullptr) { if (!atom->tag_enable) error->all(FLERR,"Fix plumed requires atom tags"); if (atom->tag_consecutive() == 0) error->all(FLERR,"Fix plumed requires consecutive atom IDs"); if (igroup != 0 && comm->me == 0) error->warning(FLERR,"Fix group for fix plumed is not 'all'. " "Group will be ignored."); #if defined(__PLUMED_DEFAULT_KERNEL) if (getenv("PLUMED_KERNEL") == nullptr) putenv(plumed_default_kernel); #endif p=new PLMD::Plumed; // Check API version int api_version=0; p->cmd("getApiVersion",&api_version); if ((api_version < 5) || (api_version > 7)) error->all(FLERR,"Incompatible API version for PLUMED in fix plumed. " "Only Plumed 2.4.x, 2.5.x, and 2.6.x are tested and supported."); #if !defined(MPI_STUBS) // If the -partition option is activated then enable // inter-partition communication if (universe->existflag == 1) { int me; MPI_Comm inter_comm; MPI_Comm_rank(world,&me); // Change MPI_COMM_WORLD to universe->uworld which seems more appropriate MPI_Comm_split(universe->uworld,me,0,&inter_comm); p->cmd("GREX setMPIIntracomm",&world); if (me == 0) { // The inter-partition communicator is only defined for the root in // each partition (a.k.a. world). This is due to the way in which // it is defined inside plumed. p->cmd("GREX setMPIIntercomm",&inter_comm); } p->cmd("GREX init",nullptr); } // The general communicator is independent of the existence of partitions, // if there are partitions, world is defined within each partition, // whereas if partitions are not defined then world is equal to // MPI_COMM_WORLD. // plumed does not know about LAMMPS using the MPI STUBS library and will // fail if this is called under these circumstances p->cmd("setMPIComm",&world); #endif // Set up units // LAMMPS units wrt kj/mol - nm - ps // Set up units if(strcmp(update->unit_style,"lj") == 0) { // LAMMPS units lj p->cmd("setNaturalUnits"); } else { // Conversion factor from LAMMPS energy units to kJ/mol (units of PLUMED) double energyUnits=1.0; // LAMMPS units real :: kcal/mol; if (strcmp(update->unit_style,"real") == 0) { energyUnits=4.184; // LAMMPS units metal :: eV; } else if (strcmp(update->unit_style,"metal") == 0) { energyUnits=96.48530749925792; // LAMMPS units si :: Joule; } else if (strcmp(update->unit_style,"si") == 0) { energyUnits=0.001; // LAMMPS units cgs :: erg; } else if (strcmp(update->unit_style,"cgs") == 0) { energyUnits=6.0221418e13; // LAMMPS units electron :: Hartree; } else if (strcmp(update->unit_style,"electron") == 0) { energyUnits=2625.5257; } else error->all(FLERR,"Fix plumed cannot handle your choice of units"); // Conversion factor from LAMMPS length units to nm (units of PLUMED) double lengthUnits=0.1/force->angstrom; // Conversion factor from LAMMPS time unit to ps (units of PLUMED) double timeUnits=0.001/force->femtosecond; p->cmd("setMDEnergyUnits",&energyUnits); p->cmd("setMDLengthUnits",&lengthUnits); p->cmd("setMDTimeUnits",&timeUnits); } // Read fix parameters: int next=0; for (int i=3;iexistflag == 1) { // Each replica writes an independent log file // with suffix equal to the replica id char str_num[32], logFile[1024]; sprintf(str_num,".%d",universe->iworld); strncpy(logFile,arg[i],1024-32); strcat(logFile,str_num); p->cmd("setLogFile",logFile); next=0; } else { // partition option not used p->cmd("setLogFile",arg[i]); next=0; } } else if (!strcmp(arg[i],"plumedfile")) { next=2; } else if (next==2) { p->cmd("setPlumedDat",arg[i]); next=0; } else error->all(FLERR,"Syntax error - use 'fix plumed " "plumedfile plumed.dat outfile plumed.out' "); } if (next==1) error->all(FLERR,"missing argument for outfile option"); if (next==2) error->all(FLERR,"missing argument for plumedfile option"); p->cmd("setMDEngine","LAMMPS"); if (atom->natoms > MAXSMALLINT) error->all(FLERR,"Fix plumed can only handle up to 2.1 billion atoms"); natoms=int(atom->natoms); p->cmd("setNatoms",&natoms); double dt=update->dt; p->cmd("setTimestep",&dt); virial_flag=1; thermo_virial=1; scalar_flag = 1; // This is the real initialization: p->cmd("init"); // Define compute to calculate potential energy id_pe = new char[8]; strcpy(id_pe,"plmd_pe"); char **newarg = new char*[3]; newarg[0] = id_pe; newarg[1] = (char *) "all"; newarg[2] = (char *) "pe"; modify->add_compute(3,newarg); delete [] newarg; int ipe = modify->find_compute(id_pe); c_pe = modify->compute[ipe]; // Define compute to calculate pressure tensor id_press = new char[11]; strcpy(id_press,"plmd_press"); newarg = new char*[5]; newarg[0] = id_press; newarg[1] = (char *) "all"; newarg[2] = (char *) "pressure"; newarg[3] = (char *) "NULL"; newarg[4] = (char *) "virial"; modify->add_compute(5,newarg); delete [] newarg; int ipress = modify->find_compute(id_press); c_press = modify->compute[ipress]; for (int i = 0; i < modify->nfix; i++) { const char * const check_style = modify->fix[i]->style; // There must be only one if (strcmp(check_style,"plumed") == 0) error->all(FLERR,"There must be only one instance of fix plumed"); // Avoid conflict with fixes that define internal pressure computes. // See comment in the setup method if (utils::strmatch(check_style,"^nph") || utils::strmatch(check_style,"^npt") || utils::strmatch(check_style,"^rigid/nph") || utils::strmatch(check_style,"^rigid/npt") || utils::strmatch(check_style,"^msst") || utils::strmatch(check_style,"^nphug") || utils::strmatch(check_style,"^ipi") || utils::strmatch(check_style,"^press/berendsen") || utils::strmatch(check_style,"^qbmsst")) error->all(FLERR,"Fix plumed must be defined before any other fixes, " "that compute pressure internally"); } } FixPlumed::~FixPlumed() { delete p; modify->delete_compute(id_pe); modify->delete_compute(id_press); delete[] id_pe; delete[] id_press; delete[] masses; delete[] charges; delete[] gatindex; } int FixPlumed::setmask() { // set with a bitmask how and when apply the force from plumed int mask = 0; mask |= POST_FORCE; mask |= THERMO_ENERGY; mask |= POST_FORCE_RESPA; mask |= MIN_POST_FORCE; return mask; } void FixPlumed::init() { if (utils::strmatch(update->integrate_style,"^respa")) nlevels_respa = ((Respa *) update->integrate)->nlevels; // This avoids nan pressure if compute_pressure is called // in a setup method for (int i=0;i<6;i++) virial[i] = 0.; } void FixPlumed::setup(int vflag) { // Here there is a crucial issue connected to constant pressure // simulations. The fix_nh will call the compute_pressure inside // the setup method, that is executed once and for all at the // beginning of the simulation. Since our fix has a contribution // to the virial, when this happens the variable virial must have // been calculated. In other words, the setup method of fix_plumed // has to be executed first. This creates a race condition with the // setup method of fix_nh. This is why in the constructor I check if // nh fixes have already been called. if (utils::strmatch(update->integrate_style,"^respa")) { ((Respa *) update->integrate)->copy_flevel_f(nlevels_respa-1); post_force_respa(vflag,nlevels_respa-1,0); ((Respa *) update->integrate)->copy_f_flevel(nlevels_respa-1); } else { post_force(vflag); } } void FixPlumed::min_setup(int vflag) { // This has to be checked. // For instance it might have problems with fix_box_relax post_force(vflag); } void FixPlumed::post_force(int /* vflag */) { int update_gatindex=0; if (natoms != int(atom->natoms)) error->all(FLERR,"Fix plumed does not support simulations with varying " "numbers of atoms"); // Try to find out if the domain decomposition has been updated: if (nlocal != atom->nlocal) { if (charges) delete [] charges; if (masses) delete [] masses; if (gatindex) delete [] gatindex; nlocal=atom->nlocal; gatindex=new int [nlocal]; masses=new double [nlocal]; charges=new double [nlocal]; update_gatindex=1; } else { for (int i=0;itag[i]-1) { update_gatindex=1; break; } } } MPI_Allreduce(MPI_IN_PLACE,&update_gatindex,1,MPI_INT,MPI_SUM,world); // In case it has been updated, rebuild the local mass/charges array // and tell plumed about the change: if (update_gatindex) { for (int i=0;itag[i]-1; // Get masses if (atom->rmass_flag) { for (int i=0;irmass[i]; } else { for (int i=0;imass[atom->type[i]]; } // Get charges if (atom->q_flag) { for (int i=0;iq[i]; } else { for (int i=0;icmd("setAtomsNlocal",&nlocal); p->cmd("setAtomsGatindex",gatindex); } // set up local virial/box. plumed uses full 3x3 matrices double plmd_virial[3][3]; for (int i=0;i<3;i++) for (int j=0;j<3;j++) plmd_virial[i][j]=0.0; double box[3][3]; for (int i=0;i<3;i++) for (int j=0;j<3;j++) box[i][j]=0.0; box[0][0]=domain->h[0]; box[1][1]=domain->h[1]; box[2][2]=domain->h[2]; box[2][1]=domain->h[3]; box[2][0]=domain->h[4]; box[1][0]=domain->h[5]; // Make initial of virial of this fix zero // The following line is very important, otherwise // the compute pressure will include for (int i=0;i<6;++i) virial[i] = 0.; // local variable with timestep: if (update->ntimestep > MAXSMALLINT) error->all(FLERR,"Fix plumed can only handle up to 2.1 billion timesteps"); int step=int(update->ntimestep); // pass all pointers to plumed: p->cmd("setStep",&step); int plumedStopCondition=0; p->cmd("setStopFlag",&plumedStopCondition); p->cmd("setPositions",&atom->x[0][0]); p->cmd("setBox",&box[0][0]); p->cmd("setForces",&atom->f[0][0]); p->cmd("setMasses",&masses[0]); p->cmd("setCharges",&charges[0]); p->cmd("getBias",&bias); // Pass virial to plumed // If energy is needed plmd_virial is equal to Lammps' virial // If energy is not needed plmd_virial is initialized to zero // In the first case the virial will be rescaled and an extra term will be added // In the latter case only an extra term will be added p->cmd("setVirial",&plmd_virial[0][0]); p->cmd("prepareCalc"); plumedNeedsEnergy=0; p->cmd("isEnergyNeeded",&plumedNeedsEnergy); // Pass potential energy and virial if needed double *virial_lmp; if (plumedNeedsEnergy) { // Error if tail corrections are included if (force->pair && force->pair->tail_flag && comm->me == 0) error->warning(FLERR,"Tail corrections to the pair potential included." " The energy cannot be biased correctly in this case." " Remove the tail corrections by removing the" " command: pair_modify tail yes"); // compute the potential energy double pot_energy = 0.; c_pe->compute_scalar(); pot_energy = c_pe->scalar; // Divide energy by number of processes // Plumed wants it this way int nprocs; MPI_Comm_size(world,&nprocs); pot_energy /= nprocs; p->cmd("setEnergy",&pot_energy); // Compute pressure due to the virial (no kinetic energy term!) c_press->compute_vector(); virial_lmp = c_press->vector; // Check if pressure is finite if (!std::isfinite(virial_lmp[0]) || !std::isfinite(virial_lmp[1]) || !std::isfinite(virial_lmp[2]) || !std::isfinite(virial_lmp[3]) || !std::isfinite(virial_lmp[4]) || !std::isfinite(virial_lmp[5])) error->all(FLERR,"Non-numeric virial - Plumed cannot work with that"); // Convert pressure to virial per number of MPI processes // From now on all virials are divided by the number of MPI processes double nktv2p = force->nktv2p; double inv_volume; if (domain->dimension == 3) { inv_volume = 1.0 / (domain->xprd * domain->yprd * domain->zprd); } else { inv_volume = 1.0 / (domain->xprd * domain->yprd); } for (int i=0;i<6;i++) virial_lmp[i] /= (inv_volume * nktv2p * nprocs); // Convert virial from lammps to plumed representation plmd_virial[0][0]=-virial_lmp[0]; plmd_virial[1][1]=-virial_lmp[1]; plmd_virial[2][2]=-virial_lmp[2]; plmd_virial[0][1]=-virial_lmp[3]; plmd_virial[0][2]=-virial_lmp[4]; plmd_virial[1][2]=-virial_lmp[5]; } // do the real calculation: p->cmd("performCalc"); if(plumedStopCondition) timer->force_timeout(); // retransform virial to lammps representation and assign it to this // fix's virial. If the energy is biased, Plumed is giving back the full // virial and therefore we have to subtract the initial virial i.e. virial_lmp. // The vector virial contains only the contribution added by plumed. // The calculation of the pressure will be done by a compute pressure // and will include this contribution. if (plumedNeedsEnergy) { virial[0] = -plmd_virial[0][0]-virial_lmp[0]; virial[1] = -plmd_virial[1][1]-virial_lmp[1]; virial[2] = -plmd_virial[2][2]-virial_lmp[2]; virial[3] = -plmd_virial[0][1]-virial_lmp[3]; virial[4] = -plmd_virial[0][2]-virial_lmp[4]; virial[5] = -plmd_virial[1][2]-virial_lmp[5]; } else { virial[0] = -plmd_virial[0][0]; virial[1] = -plmd_virial[1][1]; virial[2] = -plmd_virial[2][2]; virial[3] = -plmd_virial[0][1]; virial[4] = -plmd_virial[0][2]; virial[5] = -plmd_virial[1][2]; } // Ask for the computes in the next time step // such that the virial and energy are tallied. // This should be changed to something that triggers the // calculation only if plumed needs it. c_pe->addstep(update->ntimestep+1); c_press->addstep(update->ntimestep+1); } void FixPlumed::post_force_respa(int vflag, int ilevel, int /* iloop */) { if (ilevel == nlevels_respa-1) post_force(vflag); } void FixPlumed::min_post_force(int vflag) { post_force(vflag); } void FixPlumed::reset_dt() { error->all(FLERR,"Cannot change the time step when fix plumed is active"); } double FixPlumed::compute_scalar() { return bias; } int FixPlumed::modify_param(int narg, char **arg) { if (strcmp(arg[0],"pe") == 0) { if (narg < 2) error->all(FLERR,"Illegal fix_modify command"); modify->delete_compute(id_pe); delete[] id_pe; int n = strlen(arg[1]) + 1; id_pe = new char[n]; strcpy(id_pe,arg[1]); int icompute = modify->find_compute(arg[1]); if (icompute < 0) error->all(FLERR,"Could not find fix_modify potential energy ID"); c_pe = modify->compute[icompute]; if (c_pe->peflag == 0) error->all(FLERR,"Fix_modify plmd_pe ID does not compute potential energy"); if (c_pe->igroup != 0 && comm->me == 0) error->warning(FLERR,"Potential for fix PLUMED is not for group all"); return 2; } else if (strcmp(arg[0],"press") == 0) { if (narg < 2) error->all(FLERR,"Illegal fix_modify command"); modify->delete_compute(id_press); delete[] id_press; int n = strlen(arg[1]) + 1; id_press = new char[n]; strcpy(id_press,arg[1]); int icompute = modify->find_compute(arg[1]); if (icompute < 0) error->all(FLERR,"Could not find fix_modify pressure ID"); c_press = modify->compute[icompute]; if (c_press->pressflag == 0) error->all(FLERR,"Fix_modify pressure ID does not compute pressure"); if (c_press->igroup != 0 && comm->me == 0) error->warning(FLERR,"Virial for fix PLUMED is not for group all"); return 2; } return 0; } double FixPlumed::memory_usage() { return double((8+8+4)*atom->nlocal); }