There may be issues due to the fact that the NL is initialized with the LAMMPS data file, that does not have to be the same as the starting config in i-PI
514 lines
16 KiB
C++
514 lines
16 KiB
C++
/* ----------------------------------------------------------------------
|
||
LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
|
||
https://www.lammps.org/, Sandia National Laboratories
|
||
LAMMPS development team: developers@lammps.org
|
||
|
||
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: Michele Ceriotti (EPFL), Axel Kohlmeyer (Temple U)
|
||
------------------------------------------------------------------------- */
|
||
|
||
#include "fix_ipi.h"
|
||
|
||
#include "atom.h"
|
||
#include "comm.h"
|
||
#include "compute.h"
|
||
#include "domain.h"
|
||
#include "error.h"
|
||
#include "force.h"
|
||
#include "irregular.h"
|
||
#include "kspace.h"
|
||
#include "modify.h"
|
||
#include "neighbor.h"
|
||
#include "update.h"
|
||
|
||
#include <cstring>
|
||
|
||
using namespace LAMMPS_NS;
|
||
using namespace FixConst;
|
||
|
||
/******************************************************************************************
|
||
* A fix to interface LAMMPS with i-PI - A Python interface for path integral molecular dynamics
|
||
* Michele Ceriotti, EPFL (2014)
|
||
* Please cite:
|
||
* Ceriotti, M., More, J., & Manolopoulos, D. E. (2014).
|
||
* i-PI: A Python interface for ab initio path integral molecular dynamics simulations.
|
||
* Computer Physics Communications, 185, 1019–1026. doi:10.1016/j.cpc.2013.10.027
|
||
* And see [https://github.com/i-pi/i-pi] to download a version of i-PI
|
||
******************************************************************************************/
|
||
|
||
// socket interface
|
||
#ifndef _WIN32
|
||
#include <netdb.h>
|
||
#include <netinet/in.h>
|
||
#include <netinet/tcp.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/types.h>
|
||
#include <sys/un.h>
|
||
#include <unistd.h>
|
||
#else
|
||
#ifndef WIN32_LEAN_AND_MEAN
|
||
#define WIN32_LEAN_AND_MEAN
|
||
#endif
|
||
#include <io.h>
|
||
#include <windows.h>
|
||
#endif
|
||
|
||
#define MSGLEN 12
|
||
|
||
/* Utility functions to simplify the interface with POSIX sockets */
|
||
|
||
static void open_socket(int &sockfd, int inet, int port, char *host, Error *error)
|
||
/* Opens a socket.
|
||
|
||
Args:
|
||
sockfd: The id of the socket that will be created.
|
||
inet: An integer that determines whether the socket will be an inet or unix
|
||
domain socket. Gives unix if 0, inet otherwise.
|
||
port: The port number for the socket to be created. Low numbers are often
|
||
reserved for important channels, so use of numbers of 4 or more digits is
|
||
recommended.
|
||
host: The name of the host server.
|
||
error: pointer to a LAMMPS Error object
|
||
*/
|
||
{
|
||
int ai_err,flagNagle;
|
||
|
||
#ifdef _WIN32
|
||
error->one(FLERR, "i-PI socket implementation requires UNIX environment");
|
||
#else
|
||
if (inet > 0) { // creates an internet socket
|
||
|
||
// fetches information on the host
|
||
struct addrinfo hints, *res;
|
||
|
||
memset(&hints, 0, sizeof(hints));
|
||
hints.ai_socktype = SOCK_STREAM;
|
||
hints.ai_family = AF_UNSPEC;
|
||
hints.ai_flags = AI_PASSIVE;
|
||
|
||
ai_err = getaddrinfo(host, std::to_string(port).c_str(), &hints, &res);
|
||
if (ai_err != 0) error->one(FLERR, "Error fetching host data. Wrong host name?");
|
||
|
||
// creates socket
|
||
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||
if (sockfd < 0) error->one(FLERR, "Error creating socket for fix ipi");
|
||
|
||
// set TCP_NODELAY=1 to disable Nagle's algorithm as it slows down the small transactions for i-PI
|
||
flagNagle = 1;
|
||
int result_TCP = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *)&flagNagle, sizeof(int));
|
||
if (result_TCP < 0) { perror("Error setting TCP_NODELAY"); }
|
||
|
||
// makes connection
|
||
if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0)
|
||
error->one(FLERR, "Error opening INET socket: wrong port or server unreachable");
|
||
freeaddrinfo(res);
|
||
|
||
} else { // creates a unix socket
|
||
struct sockaddr_un serv_addr;
|
||
|
||
// fills up details of the socket address
|
||
memset(&serv_addr, 0, sizeof(serv_addr));
|
||
serv_addr.sun_family = AF_UNIX;
|
||
strcpy(serv_addr.sun_path, "/tmp/ipi_");
|
||
strcpy(serv_addr.sun_path + 9, host);
|
||
|
||
// creates the socket
|
||
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
if (sockfd < 0) error->one(FLERR, "Error creating socket for fix ipi");
|
||
|
||
// connects
|
||
if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
|
||
error->one(FLERR,
|
||
"Error opening UNIX socket: server may not be running "
|
||
"or the path to the socket unavailable");
|
||
}
|
||
#endif
|
||
}
|
||
|
||
static void writebuffer(int sockfd, const char *data, int len, Error *error)
|
||
/* Writes to a socket.
|
||
|
||
Args:
|
||
sockfd: The id of the socket that will be written to.
|
||
data: The data to be written to the socket.
|
||
len: The length of the data in bytes.
|
||
*/
|
||
{
|
||
int n;
|
||
|
||
n = write(sockfd, data, len);
|
||
if (n < 0) error->one(FLERR, "Error writing to socket: broken connection");
|
||
}
|
||
|
||
static void readbuffer(int sockfd, char *data, int len, Error *error)
|
||
/* Reads from a socket.
|
||
|
||
Args:
|
||
sockfd: The id of the socket that will be read from.
|
||
data: The storage array for data read from the socket.
|
||
len: The length of the data in bytes.
|
||
*/
|
||
{
|
||
int n, nr;
|
||
|
||
n = nr = read(sockfd, data, len);
|
||
|
||
while (nr > 0 && n < len) {
|
||
nr = read(sockfd, &data[n], len - n);
|
||
n += nr;
|
||
}
|
||
|
||
if (n == 0) error->one(FLERR, "Error reading from socket: broken connection");
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------- */
|
||
|
||
FixIPI::FixIPI(LAMMPS *lmp, int narg, char **arg) : Fix(lmp, narg, arg), irregular(nullptr)
|
||
{
|
||
if (narg < 5) utils::missing_cmd_args(FLERR, "fix ipi", error);
|
||
|
||
if (atom->tag_enable == 0) error->all(FLERR, "Cannot use fix ipi without atom IDs");
|
||
if (atom->tag_consecutive() == 0) error->all(FLERR, "Fix ipi requires consecutive atom IDs");
|
||
if (strcmp(update->unit_style, "lj") == 0) error->all(FLERR, "Fix ipi does not support lj units");
|
||
|
||
if ((strcmp(arg[1], "all") != 0) && (comm->me == 0))
|
||
error->warning(FLERR, "Not using group 'all' with fix ipi can result in undefined behavior");
|
||
|
||
host = strdup(arg[3]);
|
||
port = utils::inumeric(FLERR, arg[4], false, lmp);
|
||
|
||
master = (comm->me == 0) ? 1 : 0;
|
||
inet = 1;
|
||
reset_flag = 0;
|
||
firsttime = 1;
|
||
|
||
int iarg = 5;
|
||
while (iarg < narg) {
|
||
if (strcmp(arg[iarg], "unix") == 0) {
|
||
inet = 0;
|
||
++iarg;
|
||
} else if (strcmp(arg[iarg], "reset") == 0) {
|
||
reset_flag = 1;
|
||
++iarg;
|
||
} else {
|
||
error->all(FLERR, "Unknown fix ipi keyword: {}", arg[iarg]);
|
||
}
|
||
}
|
||
|
||
// sanity check
|
||
if (inet && ((port <= 1024) || (port > 65536)))
|
||
error->all(FLERR, "Invalid port for fix ipi: {}", port);
|
||
|
||
hasdata = bsize = 0;
|
||
|
||
// creates a temperature compute for all atoms
|
||
modify->add_compute("IPI_TEMP all temp");
|
||
|
||
// creates a pressure compute to extract the virial
|
||
modify->add_compute("IPI_PRESS all pressure IPI_TEMP virial");
|
||
|
||
// create instance of Irregular class
|
||
irregular = new Irregular(lmp);
|
||
|
||
// yet, we have not assigned a socket
|
||
socketflag = 0;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------- */
|
||
|
||
FixIPI::~FixIPI()
|
||
{
|
||
if (bsize) delete[] buffer;
|
||
free(host);
|
||
modify->delete_compute("IPI_TEMP");
|
||
modify->delete_compute("IPI_PRESS");
|
||
delete irregular;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------- */
|
||
|
||
int FixIPI::setmask()
|
||
{
|
||
int mask = 0;
|
||
mask |= INITIAL_INTEGRATE;
|
||
mask |= FINAL_INTEGRATE;
|
||
return mask;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------- */
|
||
|
||
void FixIPI::init()
|
||
{
|
||
//only opens socket on master process
|
||
if (master) {
|
||
if (!socketflag) open_socket(ipisock, inet, port, host, error);
|
||
} else
|
||
ipisock = 0;
|
||
// TODO: should check for success in socket opening,
|
||
// but the current open_socket routine dies brutally if unsuccessful
|
||
|
||
// tell lammps we have assigned a socket
|
||
socketflag = 1;
|
||
|
||
// asks for evaluation of PE at first step
|
||
modify->compute[modify->find_compute("thermo_pe")]->invoked_scalar = -1;
|
||
modify->addstep_compute_all(update->ntimestep + 1);
|
||
|
||
kspace_flag = (force->kspace) ? 1 : 0;
|
||
|
||
// makes sure that neighbor lists are re-built at each step
|
||
// (cannot make assumptions when cycling over beads!)
|
||
neighbor->delay = 0;
|
||
neighbor->every = 1;
|
||
}
|
||
|
||
// clang-format off
|
||
void FixIPI::initial_integrate(int /*vflag*/)
|
||
{
|
||
/* This is called at the beginning of the integration loop,
|
||
* and will be used to read positions from the socket. Then,
|
||
* everything should be updated, since there is no guarantee
|
||
* that successive snapshots will be close together (think
|
||
* of parallel tempering for instance) */
|
||
|
||
char header[MSGLEN+1];
|
||
|
||
if (hasdata)
|
||
error->all(FLERR, "i-PI got out of sync in initial_integrate and will die!");
|
||
|
||
double cellh[9], cellih[9];
|
||
int nat;
|
||
if (master) { // only read positions on master
|
||
|
||
// wait until something happens
|
||
while (true) {
|
||
// while i-PI just asks for status, signal we are ready and wait
|
||
readbuffer(ipisock, header, MSGLEN, error); header[MSGLEN]=0;
|
||
|
||
if (strcmp(header,"STATUS ") == 0)
|
||
writebuffer(ipisock,"READY ",MSGLEN, error);
|
||
else break;
|
||
}
|
||
|
||
if (strcmp(header,"EXIT ") == 0)
|
||
error->one(FLERR, "Got EXIT message from i-PI. Now leaving!");
|
||
|
||
// when i-PI signals it has positions to evaluate new forces,
|
||
// read positions and cell data
|
||
if (strcmp(header,"POSDATA ") == 0) {
|
||
readbuffer(ipisock, (char*) cellh, 9*8, error);
|
||
readbuffer(ipisock, (char*) cellih, 9*8, error);
|
||
readbuffer(ipisock, (char*) &nat, 4, error);
|
||
|
||
// allocate buffer, but only do this once.
|
||
if (bsize==0) {
|
||
bsize=3*nat;
|
||
buffer = new double[bsize];
|
||
} else if (bsize != 3*nat)
|
||
error->one(FLERR, "Number of atoms changed along the way.");
|
||
|
||
// finally read position data into buffer
|
||
readbuffer(ipisock, (char*) buffer, 8*bsize, error);
|
||
} else
|
||
error->one(FLERR, "Wrapper did not send positions, I will now die!");
|
||
}
|
||
|
||
// shares the atomic coordinates with everyone
|
||
MPI_Bcast(&nat,1,MPI_INT,0,world);
|
||
// must also allocate the buffer on the non-head nodes
|
||
if (bsize==0) {
|
||
bsize=3*nat;
|
||
buffer = new double[bsize];
|
||
}
|
||
MPI_Bcast(cellh,9,MPI_DOUBLE,0,world);
|
||
MPI_Bcast(cellih,9,MPI_DOUBLE,0,world);
|
||
MPI_Bcast(buffer,bsize,MPI_DOUBLE,0,world);
|
||
|
||
//updates atomic coordinates and cell based on the data received
|
||
double *boxhi = domain->boxhi;
|
||
double *boxlo = domain->boxlo;
|
||
double posconv;
|
||
posconv=0.52917721*force->angstrom;
|
||
boxlo[0] = -0.5*cellh[0]*posconv;
|
||
boxlo[1] = -0.5*cellh[4]*posconv;
|
||
boxlo[2] = -0.5*cellh[8]*posconv;
|
||
boxhi[0] = -boxlo[0];
|
||
boxhi[1] = -boxlo[1];
|
||
boxhi[2] = -boxlo[2];
|
||
domain->xy = cellh[1]*posconv;
|
||
domain->xz = cellh[2]*posconv;
|
||
domain->yz = cellh[5]*posconv;
|
||
|
||
// do error checks on simulation box and set small for triclinic boxes
|
||
domain->set_initial_box();
|
||
// reset global and local box using the new box dimensions
|
||
domain->reset_box();
|
||
// signal that the box has (or may have) changed
|
||
domain->box_change = 1;
|
||
|
||
// picks local atoms from the buffer
|
||
double **x = atom->x;
|
||
int *mask = atom->mask;
|
||
int nlocal = atom->nlocal;
|
||
if (igroup == atom->firstgroup) nlocal = atom->nfirst;
|
||
for (int i = 0; i < nlocal; i++) {
|
||
if (mask[i] & groupbit) {
|
||
x[i][0]=buffer[3*(atom->tag[i]-1)+0]*posconv;
|
||
x[i][1]=buffer[3*(atom->tag[i]-1)+1]*posconv;
|
||
x[i][2]=buffer[3*(atom->tag[i]-1)+2]*posconv;
|
||
}
|
||
}
|
||
|
||
// ensure atoms are in current box & update box via shrink-wrap
|
||
// has to be be done before invoking Irregular::migrate_atoms()
|
||
// since it requires atoms be inside simulation box
|
||
|
||
// folds atomic coordinates close to the origin
|
||
if (domain->triclinic) domain->x2lamda(atom->nlocal);
|
||
domain->pbc();
|
||
domain->reset_box();
|
||
// move atoms to new processors via irregular()
|
||
// only needed if migrate_check() says an atom moves to far
|
||
if (irregular->migrate_check()) irregular->migrate_atoms();
|
||
if (domain->triclinic) domain->lamda2x(atom->nlocal);
|
||
|
||
// ensures continuity of trajectories relative to the
|
||
// snapshot at neighbor list creation, minimizing the
|
||
// number of neighbor list updates
|
||
auto xhold = neighbor->get_xhold();
|
||
if (xhold != NULL && !firsttime) {
|
||
// don't wrap if xhold is not used in the NL, or the
|
||
// first call (because the NL is initialized from the
|
||
// data file that might have nothing to do with the
|
||
// current structure
|
||
for (int i = 0; i < nlocal; i++) {
|
||
if (mask[i] & groupbit) {
|
||
auto delx = x[i][0] - xhold[i][0];
|
||
auto dely = x[i][1] - xhold[i][1];
|
||
auto delz = x[i][2] - xhold[i][2];
|
||
|
||
domain->minimum_image(delx, dely, delz);
|
||
|
||
x[i][0] = xhold[i][0] + delx;
|
||
x[i][1] = xhold[i][1] + dely;
|
||
x[i][2] = xhold[i][2] + delz;
|
||
}
|
||
}
|
||
}
|
||
firsttime = 0;
|
||
|
||
// check if kspace solver is used
|
||
if (reset_flag && kspace_flag) {
|
||
// reset kspace, pair, angles, ... b/c simulation box might have changed.
|
||
// kspace->setup() is in some cases not enough since, e.g., g_ewald needs
|
||
// to be reestimated due to changes in box dimensions.
|
||
force->init();
|
||
// reset_grid() is necessary for pppm since init() is not calling
|
||
// setup() nor reset_grid() upon calling init().
|
||
if (force->kspace->pppmflag) force->kspace->reset_grid();
|
||
// other kspace styles might need too another setup()?
|
||
} else if (!reset_flag && kspace_flag) {
|
||
// original version
|
||
force->kspace->setup();
|
||
}
|
||
|
||
// compute PE. makes sure that it will be evaluated at next step
|
||
modify->compute[modify->find_compute("thermo_pe")]->invoked_scalar = -1;
|
||
modify->addstep_compute_all(update->ntimestep+1);
|
||
|
||
hasdata=1;
|
||
}
|
||
|
||
void FixIPI::final_integrate()
|
||
{
|
||
/* This is called after forces and energy have been computed. Now we only need to
|
||
* communicate them back to i-PI so that the integration can continue. */
|
||
char header[MSGLEN+1];
|
||
double vir[9], pot=0.0;
|
||
double forceconv, potconv, posconv, pressconv, posconv3;
|
||
char retstr[1024];
|
||
|
||
// conversions from LAMMPS units to atomic units, which are used by i-PI
|
||
potconv=3.1668152e-06/force->boltz;
|
||
posconv=0.52917721*force->angstrom;
|
||
posconv3=posconv*posconv*posconv;
|
||
forceconv=potconv*posconv;
|
||
pressconv=1/force->nktv2p*potconv*posconv3;
|
||
|
||
// compute for potential energy
|
||
pot=modify->compute[modify->find_compute("thermo_pe")]->compute_scalar();
|
||
pot*=potconv;
|
||
|
||
// probably useless check
|
||
if (!hasdata)
|
||
error->all(FLERR, "i-PI got out of sync in final_integrate and will die!");
|
||
|
||
int nat=bsize/3;
|
||
double **f= atom->f;
|
||
auto lbuf = new double[bsize];
|
||
|
||
// reassembles the force vector from the local arrays
|
||
int nlocal = atom->nlocal;
|
||
if (igroup == atom->firstgroup) nlocal = atom->nfirst;
|
||
for (int i = 0; i < bsize; ++i) lbuf[i]=0.0;
|
||
for (int i = 0; i < nlocal; i++) {
|
||
lbuf[3*(atom->tag[i]-1)+0]=f[i][0]*forceconv;
|
||
lbuf[3*(atom->tag[i]-1)+1]=f[i][1]*forceconv;
|
||
lbuf[3*(atom->tag[i]-1)+2]=f[i][2]*forceconv;
|
||
}
|
||
MPI_Allreduce(lbuf,buffer,bsize,MPI_DOUBLE,MPI_SUM,world);
|
||
delete[] lbuf;
|
||
|
||
for (int i = 0; i < 9; ++i) vir[i]=0.0;
|
||
|
||
int press_id = modify->find_compute("IPI_PRESS");
|
||
Compute* comp_p = modify->compute[press_id];
|
||
comp_p->compute_vector();
|
||
double myvol = domain->xprd*domain->yprd*domain->zprd/posconv3;
|
||
|
||
vir[0] = comp_p->vector[0]*pressconv*myvol;
|
||
vir[4] = comp_p->vector[1]*pressconv*myvol;
|
||
vir[8] = comp_p->vector[2]*pressconv*myvol;
|
||
vir[1] = comp_p->vector[3]*pressconv*myvol;
|
||
vir[2] = comp_p->vector[4]*pressconv*myvol;
|
||
vir[5] = comp_p->vector[5]*pressconv*myvol;
|
||
retstr[0]=0;
|
||
|
||
if (master) {
|
||
// check for new messages
|
||
while (true) {
|
||
readbuffer(ipisock, header, MSGLEN, error); header[MSGLEN]=0;
|
||
|
||
if (strcmp(header,"STATUS ") == 0)
|
||
writebuffer(ipisock,"HAVEDATA ",MSGLEN, error);
|
||
else break;
|
||
}
|
||
|
||
if (strcmp(header,"EXIT ") == 0)
|
||
error->one(FLERR, "Got EXIT message from i-PI. Now leaving!");
|
||
|
||
if (strcmp(header,"GETFORCE ") == 0) {
|
||
// return force and energy data
|
||
writebuffer(ipisock,"FORCEREADY ",MSGLEN, error);
|
||
writebuffer(ipisock,(char*) &pot,8, error);
|
||
writebuffer(ipisock,(char*) &nat,4, error);
|
||
writebuffer(ipisock,(char*) buffer, bsize*8, error);
|
||
writebuffer(ipisock,(char*) vir,9*8, error);
|
||
nat=strlen(retstr); writebuffer(ipisock,(char*) &nat,4, error);
|
||
writebuffer(ipisock,(char*) retstr, nat, error);
|
||
}
|
||
else
|
||
error->one(FLERR, "Wrapper did not ask for forces, I will now die!");
|
||
}
|
||
|
||
hasdata=0;
|
||
}
|