/* ---------------------------------------------------------------------- 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. ------------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- Contributing author: Taylor Barnes (MolSSI) MolSSI Driver Interface (MDI) support for LAMMPS ------------------------------------------------------------------------- */ #include "mdi_engine.h" #include #include #include "atom.h" #include "comm.h" #include "compute.h" #include "domain.h" #include "error.h" #include "fix_mdi_engine.h" #include "force.h" #include "group.h" #include "input.h" #include "integrate.h" #include "irregular.h" #include "library.h" #include "library_mdi.h" #include "memory.h" #include "min.h" #include "modify.h" #include "neighbor.h" #include "output.h" #include "thermo.h" #include "timer.h" #include "update.h" #include using namespace LAMMPS_NS; enum{NATIVE,REAL,METAL}; // LAMMPS units which MDI supports enum{DEFAULT,MD,OPT}; // top-level MDI engine modes // per-atom data which engine commands access enum{TYPE,CHARGE,MASS,COORD,VELOCITY,FORCE,ADDFORCE}; /* ---------------------------------------------------------------------- trigger LAMMPS to start acting as an MDI engine either in standalone mode or plugin mode MDI_Init() for standalone mode is in main.cpp MDI_Init() for plugin mode is in library_mdi.cpp::MDI_Plugin_init_lammps() endlessly loop over receiving commands from driver and responding when EXIT command is received, mdi engine command exits ---------------------------------------------------------------------- */ MDIEngine::MDIEngine(LAMMPS *lmp, int narg, char **arg) : Pointers(lmp) { if (narg) error->all(FLERR,"Illegal mdi engine command"); // check requirements for LAMMPS to work with MDI as an engine if (atom->tag_enable == 0) error->all(FLERR,"Cannot use MDI engine without atom IDs"); if (atom->natoms && atom->tag_consecutive() == 0) error->all(FLERR,"MDI engine requires consecutive atom IDs"); // confirm LAMMPS is being run as an engine int role; MDI_Get_role(&role); if (role != MDI_ENGINE) error->all(FLERR,"Must invoke LAMMPS as an MDI engine to use mdi engine"); // root = 1 for proc 0, otherwise 0 root = (comm->me == 0) ? 1 : 0; // MDI setup mdicmd = new char[MDI_COMMAND_LENGTH]; node_engine = new char[MDI_COMMAND_LENGTH]; strncpy(node_engine,"@DEFAULT",MDI_COMMAND_LENGTH); node_driver = new char[MDI_COMMAND_LENGTH]; strncpy(node_driver,"\0",MDI_COMMAND_LENGTH); // create computes for KE. PE, pressure // pressure compute only calculates virial, no kinetic term id_ke = utils::strdup(std::string("MDI_ENGINE") + "_ke"); modify->add_compute(fmt::format("{} all ke", id_ke)); id_pe = utils::strdup(std::string("MDI_ENGINE") + "_pe"); modify->add_compute(fmt::format("{} all pe", id_pe)); id_press = utils::strdup(std::string("MDI_ENGINE") + "_press"); modify->add_compute(fmt::format("{} all pressure NULL virial", id_press)); int icompute_ke = modify->find_compute(id_ke); int icompute_pe = modify->find_compute(id_pe); int icompute_press = modify->find_compute(id_press); ke = modify->compute[icompute_ke]; pe = modify->compute[icompute_pe]; press = modify->compute[icompute_press]; //pe = modify->get_compute_by_id("thermo_pe"); //press = modify->get_compute_by_id("thermo_press"); // irregular class used if >COORDS change dramatically irregular = new Irregular(lmp); // set unit conversion factors if (strcmp(update->unit_style, "real") == 0) lmpunits = REAL; else if (strcmp(update->unit_style, "metal") == 0) lmpunits = METAL; else lmpunits = NATIVE; unit_conversions(); // internal state of engine flag_natoms = 0; flag_types = flag_charges = flag_coords = flag_velocities = 0; flag_cell = flag_cell_displ = 0; sys_types = nullptr; sys_coords = sys_velocities = nullptr; sys_charges = nullptr; buf1 = buf1all = nullptr; buf3 = buf3all = nullptr; ibuf1 = ibuf1all = nullptr; maxatom = 0; sys_natoms = atom->natoms; reallocate(); nsteps = 0; etol = ftol = 1.0e-6; niterate = -1; max_eval = std::numeric_limits::max(); nbytes = -1; actionflag = 0; // define MDI commands that LAMMPS engine recognizes mdi_commands(); // register the execute_command function with MDI // only used when engine runs in plugin mode MDI_Set_execute_command_func(lammps_execute_mdi_command,this); // one-time operation to establish a connection with the driver MDI_Accept_communicator(&mdicomm); if (mdicomm <= 0) error->all(FLERR,"Unable to connect to MDI driver"); // endless engine loop, responding to driver commands mode = DEFAULT; node_match = true; exit_command = false; while (1) { // top-level mdi engine only recognizes three nodes // DEFAULT, INIT_MD, INIT_OPTG engine_node("@DEFAULT"); // MDI commands for dynamics or minimization if (strcmp(mdicmd,"@INIT_MD") == 0) { mdi_md(); if (exit_command) break; } else if (strcmp(mdicmd,"@INIT_OPTG") == 0) { mdi_optg(); if (exit_command) break; } else if (exit_command) { break; } else error->all(FLERR, fmt::format("MDI engine exited with invalid command: {}", mdicmd)); } // clean up delete [] mdicmd; delete [] node_engine; delete [] node_driver; modify->delete_compute(id_pe); modify->delete_compute(id_press); delete [] id_ke; delete [] id_pe; delete [] id_press; delete irregular; // delete buffers deallocate(); } /* ---------------------------------------------------------------------- engine is now at this MDI node loop over received commands so long as driver is also at this node return when not the case or EXIT command received ---------------------------------------------------------------------- */ void MDIEngine::engine_node(const char *node) { int ierr; // do not process commands if engine and driver request are not the same strncpy(node_engine,node,MDI_COMMAND_LENGTH); if (strcmp(node_driver,"\0") != 0 && strcmp(node_driver,node_engine) != 0) node_match = false; // respond to commands from the driver while (!exit_command && node_match) { // read the next command from the driver // all procs call this, but only proc 0 receives the command ierr = MDI_Recv_command(mdicmd,mdicomm); if (ierr) error->all(FLERR,"MDI: Unable to receive command from driver"); // broadcast command to the other MPI tasks MPI_Bcast(mdicmd,MDI_COMMAND_LENGTH,MPI_CHAR,0,world); // execute the command execute_command(mdicmd,mdicomm); // check if driver request is now different than engine node if (strcmp(node_driver,"\0") != 0 && strcmp(node_driver,node_engine) != 0) node_match = false; } // node exit was triggered so reset node_match node_match = true; } /* ---------------------------------------------------------------------- process a single driver command called by engine_node() in loop also called by MDI itself via lib::lammps_execute_mdi_command() when LAMMPS is running as a plugin ---------------------------------------------------------------------- */ int MDIEngine::execute_command(const char *command, MDI_Comm mdicomm) { int ierr; // confirm this command is supported at this node // otherwise is error int command_exists; if (root) { ierr = MDI_Check_command_exists(node_engine,command,MDI_COMM_NULL, &command_exists); if (ierr) error->one(FLERR, "MDI: Unable to check whether current command is supported"); } MPI_Bcast(&command_exists,1,MPI_INT,0,world); if (!command_exists) error->all(FLERR,"MDI: Received command {} unsupported by engine node {}", command,node_engine); // --------------------------------------- // respond to MDI standard commands // receives first, sends second, node commands third // --------------------------------------- if (strcmp(command,">CELL") == 0) { receive_cell(); } else if (strcmp(command,">CELL_DISPL") == 0) { receive_cell_displ(); } else if (strcmp(command,">CHARGES") == 0) { receive_charges(); } else if (strcmp(command,">COORDS") == 0) { receive_coords(); } else if (strcmp(command,">FORCES") == 0) { receive_double3(FORCE); } else if (strcmp(command,">+FORCES") == 0) { receive_double3(ADDFORCE); } else if (strcmp(command,">NATOMS") == 0) { receive_natoms(); } else if (strcmp(command,">NSTEPS") == 0) { receive_nsteps(); } else if (strcmp(command,">TOLERANCE") == 0) { receive_tolerance(); } else if (strcmp(command,">TYPES") == 0) { receive_types(); } else if (strcmp(command,">VELOCITIES") == 0) { if (strcmp(node_engine,"@DEFAULT") == 0) receive_velocities(); else receive_double3(VELOCITY); // ----------------------------------------------- } else if (strcmp(command,"<@") == 0) { ierr = MDI_Send(node_engine,MDI_NAME_LENGTH,MDI_CHAR,mdicomm); if (ierr) error->all(FLERR,"MDI: <@ data"); } else if (strcmp(command,"all(FLERR,"MDI: MDI engine is already performing a simulation"); mode = MD; strncpy(node_driver,command,MDI_COMMAND_LENGTH); node_match = false; } else if (strcmp(command,"@INIT_OPTG") == 0) { if (mode != DEFAULT) error->all(FLERR,"MDI: MDI engine is already performing a simulation"); mode = OPT; strncpy(node_driver,command,MDI_COMMAND_LENGTH); node_match = false; } else if (strcmp(command,"@") == 0) { strncpy(node_driver,"\0",MDI_COMMAND_LENGTH); node_match = false; } else if (strcmp(command,"@DEFAULT") == 0) { mode = DEFAULT; strncpy(node_driver,command,MDI_COMMAND_LENGTH); node_match = false; } else if (strcmp(command,"@COORDS") == 0) { strncpy(node_driver,command,MDI_COMMAND_LENGTH); node_match = false; } else if (strcmp(command,"@FORCES") == 0) { strncpy(node_driver,command,MDI_COMMAND_LENGTH); node_match = false; } else if (strcmp(command,"@ENDSTEP") == 0) { strncpy(node_driver,command,MDI_COMMAND_LENGTH); node_match = false; // exit command } else if (strcmp(command,"EXIT") == 0) { exit_command = true; // ------------------------------------------------------- // custom LAMMPS commands // ------------------------------------------------------- } else if (strcmp(command,"NBYTES") == 0) { nbytes_command(); } else if (strcmp(command,"COMMAND") == 0) { single_command(); } else if (strcmp(command,"COMMANDS") == 0) { many_commands(); } else if (strcmp(command,"INFILE") == 0) { infile(); } else if (strcmp(command,"all(FLERR,"MDI: Unknown command {} received from driver",command); } return 0; } /* ---------------------------------------------------------------------- define which MDI commands the LAMMPS engine recognizes at each node both standard MDI commands and custom LAMMPS commands max length for a command is currently 11 chars ---------------------------------------------------------------------- */ void MDIEngine::mdi_commands() { // default node, MDI standard commands MDI_Register_node("@DEFAULT"); MDI_Register_command("@DEFAULT","<@"); MDI_Register_command("@DEFAULT","CELL"); MDI_Register_command("@DEFAULT",">CELL_DISPL"); MDI_Register_command("@DEFAULT",">CHARGES"); MDI_Register_command("@DEFAULT",">COORDS"); MDI_Register_command("@DEFAULT",">NATOMS"); MDI_Register_command("@DEFAULT",">NSTEPS"); MDI_Register_command("@DEFAULT",">TOLERANCE"); MDI_Register_command("@DEFAULT",">TYPES"); MDI_Register_command("@DEFAULT",">VELOCITIES"); MDI_Register_command("@DEFAULT","MD"); MDI_Register_command("@DEFAULT","OPTG"); MDI_Register_command("@DEFAULT","@INIT_MD"); MDI_Register_command("@DEFAULT","@INIT_OPTG"); MDI_Register_command("@DEFAULT","EXIT"); // default node, custom commands added by LAMMPS MDI_Register_command("@DEFAULT","NBYTES"); MDI_Register_command("@DEFAULT","COMMAND"); MDI_Register_command("@DEFAULT","COMMANDS"); MDI_Register_command("@DEFAULT","INFILE"); MDI_Register_command("@DEFAULT","COORDS"); MDI_Register_command("@COORDS",">VELOCITIES"); MDI_Register_command("@COORDS","@"); MDI_Register_command("@COORDS","@DEFAULT"); MDI_Register_command("@COORDS","@COORDS"); MDI_Register_command("@COORDS","@FORCES"); MDI_Register_command("@COORDS","@ENDSTEP"); MDI_Register_command("@COORDS","EXIT"); // node at POST_FORCE location in timestep // only used if fix MDI/ENGINE is instantiated // two register callbacks allow LAMMPS to interact more easily // with drivers which don't know LAMMPS control flow MDI_Register_node("@FORCES"); MDI_Register_callback("@FORCES",">FORCES"); MDI_Register_callback("@FORCES",">+FORCES"); MDI_Register_command("@FORCES","<@"); MDI_Register_command("@FORCES","FORCES"); MDI_Register_command("@FORCES",">+FORCES"); MDI_Register_command("@FORCES",">VELOCITIES"); MDI_Register_command("@FORCES","@"); MDI_Register_command("@FORCES","@DEFAULT"); MDI_Register_command("@FORCES","@COORDS"); MDI_Register_command("@FORCES","@FORCES"); MDI_Register_command("@FORCES","@ENDSTEP"); MDI_Register_command("@FORCES","EXIT"); // node at END_OF_STEP location in timestep // only used if fix MDI/ENGINE is instantiated MDI_Register_node("@ENDSTEP"); MDI_Register_command("@ENDSTEP","<@"); MDI_Register_command("@ENDSTEP","add_fix("MDI_ENGINE_INTERNAL all MDI/ENGINE"); FixMDIEngine *mdi_fix = (FixMDIEngine *) modify->get_fix_by_id("MDI_ENGINE_INTERNAL"); mdi_fix->mdi_engine = this; // initialize LAMMPS and setup() the simulation update->whichflag = 1; timer->init_timeout(); update->nsteps = 1; update->beginstep = update->firststep = update->ntimestep; update->endstep = update->laststep = update->ntimestep + update->nsteps; lmp->init(); // engine is now at @INIT_MD node // any @ command from driver will start the simulation engine_node("@INIT_MD"); if (strcmp(mdicmd,"EXIT") == 0) return; // run one step at a time forever // driver triggers exit with @ command other than @COORDS,@FORCES,@ENDSTEP update->integrate->setup(1); timer->init(); timer->barrier_start(); while (true) { update->nsteps += 1; update->laststep += 1; update->endstep = update->laststep; output->next = update->ntimestep + 1; update->integrate->run(1); if (strcmp(mdicmd,"@COORDS") != 0 && strcmp(mdicmd,"@FORCES") != 0 && strcmp(mdicmd,"@ENDSTEP") != 0) break; } timer->barrier_stop(); update->integrate->cleanup(); modify->delete_fix("MDI_ENGINE_INTERNAL"); // clear flags flag_natoms = flag_types = 0; flag_cell = flag_cell_displ = flag_charges = flag_coords = flag_velocities = 0; } /* ---------------------------------------------------------------------- run MD simulation for >NSTEPS ---------------------------------------------------------------------- */ void MDIEngine::md() { // create or update system if requested prior to MD command int flag_create = flag_natoms | flag_types; if (flag_create) create_system(); else { if (flag_cell || flag_cell_displ) adjust_box(); if (flag_charges) adjust_charges(); if (flag_coords) adjust_coords(); if (flag_velocities) adjust_velocities(); } // initialize LAMMPS and setup() the simulation // run the simulation for nsteps // clean up update->whichflag = 1; timer->init_timeout(); update->nsteps = nsteps; update->beginstep = update->firststep = update->ntimestep; update->endstep = update->laststep = update->ntimestep + update->nsteps; lmp->init(); update->integrate->setup(1); timer->init(); timer->barrier_start(); update->integrate->run(nsteps); timer->barrier_stop(); update->integrate->cleanup(); // clear flags flag_natoms = flag_types = 0; flag_cell = flag_cell_displ = flag_charges = flag_coords = flag_velocities = 0; actionflag = 1; } /* ---------------------------------------------------------------------- perform minimization one iteration at a time, controlled by driver ---------------------------------------------------------------------- */ void MDIEngine::mdi_optg() { // create or update system if requested prior to @INIT_OPTG int flag_create = flag_natoms | flag_types; if (flag_create) create_system(); else { if (flag_cell || flag_cell_displ) adjust_box(); if (flag_charges) adjust_charges(); if (flag_coords) adjust_coords(); if (flag_velocities) adjust_velocities(); } // add an instance of fix MDI/ENGINE // delete the instance before this method returns modify->add_fix("MDI_ENGINE_INTERNAL all MDI/ENGINE"); FixMDIEngine *mdi_fix = (FixMDIEngine *) modify->get_fix_by_id("MDI_ENGINE_INTERNAL"); mdi_fix->mdi_engine = this; // initialize LAMMPS and setup() the simulation update->whichflag = 2; timer->init_timeout(); update->nsteps = (niterate >= 0) ? niterate : max_eval; update->beginstep = update->firststep = update->ntimestep; update->endstep = update->laststep = update->firststep + update->nsteps; update->etol = 0.0; update->ftol = 0.0; update->max_eval = std::numeric_limits::max(); lmp->init(); // engine is now at @INIT_OPTG node // any @ command from driver will start the minimization engine_node("@INIT_OPTG"); if (strcmp(mdicmd,"EXIT") == 0) return; // run one iteration at a time forever // driver triggers exit with @ command other than @COORDS,@FORCES // two issues with running in this mode: // @COORDS and @FORCES are not just triggered per min iteration // but also for line search eng/force evals // if driver triggers exit on step that is not multiple of thermo output // then energy/virial not computed, and minimize->setup(); timer->init(); timer->barrier_start(); while (1) { update->minimize->run(1); if (strcmp(mdicmd,"@COORDS") != 0 && strcmp(mdicmd,"@FORCES") != 0) break; } timer->barrier_stop(); update->minimize->cleanup(); modify->delete_fix("MDI_ENGINE_INTERNAL"); // clear flags flag_natoms = flag_types = 0; flag_cell = flag_cell_displ = flag_charges = flag_coords = flag_velocities = 0; } /* ---------------------------------------------------------------------- perform minimization to convergence using >TOLERANCE settings ---------------------------------------------------------------------- */ void MDIEngine::optg() { // create or update system if requested prior to OPTG command int flag_create = flag_natoms | flag_types; if (flag_create) create_system(); else { if (flag_cell || flag_cell_displ) adjust_box(); if (flag_charges) adjust_charges(); if (flag_coords) adjust_coords(); if (flag_velocities) adjust_velocities(); } // initialize LAMMPS and setup() the simulation // run the minimization using 4 >TOLERANCE parameters // clean up update->whichflag = 2; timer->init_timeout(); update->nsteps = (niterate >= 0) ? niterate : max_eval; update->beginstep = update->firststep = update->ntimestep; update->endstep = update->laststep = update->firststep + update->nsteps; update->etol = etol; update->ftol = ftol; update->max_eval = max_eval; lmp->init(); update->minimize->setup(); timer->init(); timer->barrier_start(); update->minimize->run(niterate); timer->barrier_stop(); update->minimize->cleanup(); // clear flags flag_natoms = flag_types = 0; flag_cell = flag_cell_displ = flag_charges = flag_coords = flag_velocities = 0; actionflag = 1; } /* ---------------------------------------------------------------------- evaluate() invoked by migrate_check() and migrate_atoms() if needed // this can only be done if comm->style is not tiled // also requires atoms be in box and lamda coords (for triclinic) // finally invoke setup_minimal(1) to trigger exchange() & reneigh() // NOTE: what this logic still lacks for comm->style tiled, // is when to invoke migrate_atoms() if necessary, not easy to detect if (flag_create || neighbor->ago < 0) { update->whichflag = 1; lmp->init(); update->integrate->setup(1); update->whichflag = 0; } else if (flag_other) { update->ntimestep++; pe->addstep(update->ntimestep); press->addstep(update->ntimestep); int nflag = neighbor->decide(); if (nflag == 0) { comm->forward_comm(); update->integrate->setup_minimal(0); modify->clearstep_compute(); output->thermo->compute(1); } else { if (!comm->style) { if (domain->triclinic) domain->x2lamda(atom->nlocal); domain->pbc(); domain->reset_box(); if (irregular->migrate_check()) irregular->migrate_atoms(); if (domain->triclinic) domain->lamda2x(atom->nlocal); } update->integrate->setup_minimal(1); modify->clearstep_compute(); output->thermo->compute(1); } modify->addstep_compute(update->ntimestep+1); } // clear flags that trigger next eval flag_natoms = flag_types = 0; flag_cell = flag_cell_displ = flag_charges = flag_coords = flag_velocities = 0; } /* ---------------------------------------------------------------------- create a new system >CELL, >NATOMS, >TYPES, >COORDS commands are required >CELL_DISPL, >CHARGES, >VELOCITIES commands are optional ---------------------------------------------------------------------- */ void MDIEngine::create_system() { // check requirements if (flag_cell == 0 || flag_natoms == 0 || flag_types == 0 || flag_coords == 0) error->all(FLERR, "MDI create_system requires >CELL, >NATOMS, >TYPES, >COORDS " "MDI commands"); // remove all existing atoms via delete_atoms command lmp->input->one("delete_atoms group all"); // invoke lib->reset_box() double boxlo[3],boxhi[3]; double xy,yz,xz; if (flag_cell_displ) { boxlo[0] = sys_cell_displ[0]; boxlo[1] = sys_cell_displ[1]; boxlo[2] = sys_cell_displ[2]; } else { boxlo[0] = boxlo[1] = boxlo[2] = 0.0; } boxhi[0] = boxlo[0] + sys_cell[0]; boxhi[1] = boxlo[1] + sys_cell[4]; boxhi[2] = boxlo[2] + sys_cell[8]; xy = sys_cell[3]; yz = sys_cell[7]; xz = sys_cell[6]; lammps_reset_box(lmp,boxlo,boxhi,xy,yz,xz); // invoke lib->create_atoms() // optionally set charges if specified by ">CHARGES" if (flag_velocities) int natoms = lammps_create_atoms(lmp,sys_natoms,NULL,sys_types, sys_coords,sys_velocities,NULL,1); else int natoms = lammps_create_atoms(lmp,sys_natoms,NULL,sys_types, sys_coords,NULL,NULL,1); if (flag_charges) lammps_scatter_atoms(lmp,(char *) "q",1,1,sys_charges); // new system update->ntimestep = 0; } /* ---------------------------------------------------------------------- adjust simulation box ---------------------------------------------------------------------- */ void MDIEngine::adjust_box() { // convert atoms to lamda coords before changing box domain->x2lamda(atom->nlocal); // if >CELL command received, // convert celldata to new boxlo, boxhi, and tilt factors if (flag_cell) { domain->boxhi[0] = sys_cell[0] + domain->boxlo[0]; domain->boxhi[1] = sys_cell[4] + domain->boxlo[1]; domain->boxhi[2] = sys_cell[8] + domain->boxlo[2]; domain->xy = sys_cell[3]; domain->xz = sys_cell[6]; domain->yz = sys_cell[7]; } // if >CELL_DISPL command received, // convert cell_displ to new boxlo and boxhi if (flag_cell_displ) { double old_boxlo[3]; old_boxlo[0] = domain->boxlo[0]; old_boxlo[1] = domain->boxlo[1]; old_boxlo[2] = domain->boxlo[2]; domain->boxlo[0] = sys_cell_displ[0]; domain->boxlo[1] = sys_cell_displ[1]; domain->boxlo[2] = sys_cell_displ[2]; domain->boxhi[0] += domain->boxlo[0] - old_boxlo[0]; domain->boxhi[1] += domain->boxlo[1] - old_boxlo[1]; domain->boxhi[2] += domain->boxlo[2] - old_boxlo[2]; } // reset all Domain variables that depend on box size/shape // convert atoms from lamda coords back to new box coords domain->set_global_box(); domain->set_local_box(); domain->lamda2x(atom->nlocal); } /* ---------------------------------------------------------------------- overwrite charges ---------------------------------------------------------------------- */ void MDIEngine::adjust_charges() { double *q = atom->q; tagint *tag = atom->tag; int nlocal = atom->nlocal; int ilocal; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; q[i] = sys_charges[ilocal]; } } /* ---------------------------------------------------------------------- overwrite coords ---------------------------------------------------------------------- */ void MDIEngine::adjust_coords() { double **x = atom->x; tagint *tag = atom->tag; int nlocal = atom->nlocal; int ilocal; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; x[i][0] = sys_coords[3*ilocal+0]; x[i][1] = sys_coords[3*ilocal+1]; x[i][2] = sys_coords[3*ilocal+2]; } } /* ---------------------------------------------------------------------- overwrite velocities ---------------------------------------------------------------------- */ void MDIEngine::adjust_velocities() { double **v = atom->v; tagint *tag = atom->tag; int nlocal = atom->nlocal; int ilocal; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; v[i][0] = sys_velocities[3*ilocal+0]; v[i][1] = sys_velocities[3*ilocal+1]; v[i][2] = sys_velocities[3*ilocal+2]; } } // ---------------------------------------------------------------------- // ----------------------------------------------------------------------/ // MDI ">" driver commands that send data // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- /* ---------------------------------------------------------------------- >CELL command reset simulation box edge vectors in conjunction with >CELL_DISPL this can change box arbitrarily can be done to create a new box can be done incrementally during MD or OPTG ---------------------------------------------------------------------- */ void MDIEngine::receive_cell() { actionflag = 0; flag_cell = 1; int ierr = MDI_Recv(sys_cell,9,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR, "MDI: >CELL data"); MPI_Bcast(sys_cell,9,MPI_DOUBLE,0,world); for (int icell = 0; icell < 9; icell++) sys_cell[icell] *= mdi2lmp_length; // error check that edge vectors match LAMMPS triclinic requirement if (sys_cell[1] != 0.0 || sys_cell[2] != 0.0 || sys_cell[5] != 0.0) error->all(FLERR,"MDI: Received cell edges are not LAMMPS compatible"); } /* ---------------------------------------------------------------------- >CELL_DISPL command reset simulation box lower left corner in conjunction with >CELL this can change box arbitrarily can be done to create a new box can be done incrementally during MD or OPTG ---------------------------------------------------------------------- */ void MDIEngine::receive_cell_displ() { actionflag = 0; flag_cell_displ = 1; int ierr = MDI_Recv(sys_cell_displ,3,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: >CELL_DISPLS data"); MPI_Bcast(sys_cell_displ,3,MPI_DOUBLE,0,world); for (int icell = 0; icell < 3; icell++) sys_cell_displ[icell] *= mdi2lmp_length; } /* ---------------------------------------------------------------------- >CHARGES command ---------------------------------------------------------------------- */ void MDIEngine::receive_charges() { actionflag = 0; flag_charges = 1; int ierr = MDI_Recv(sys_charges,sys_natoms,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: >CHARGES data"); MPI_Bcast(sys_charges,sys_natoms,MPI_DOUBLE,0,world); } /* ---------------------------------------------------------------------- >COORDS command ---------------------------------------------------------------------- */ void MDIEngine::receive_coords() { actionflag = 0; flag_coords = 1; int n = 3*sys_natoms; int ierr = MDI_Recv(sys_coords,n,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: >COORDS data"); MPI_Bcast(sys_coords,n,MPI_DOUBLE,0,world); for (int i = 0; i < n; i++) sys_coords[i] * mdi2lmp_length; } /* ---------------------------------------------------------------------- >NATOMS command natoms cannot exceed 32-bit int for use with MDI ---------------------------------------------------------------------- */ void MDIEngine::receive_natoms() { actionflag = 0; flag_natoms = 1; int ierr = MDI_Recv(&sys_natoms,1,MDI_INT,mdicomm); if (ierr) error->all(FLERR,"MDI: >NATOMS data"); MPI_Bcast(&sys_natoms,1,MPI_INT,0,world); if (sys_natoms < 0) error->all(FLERR,"MDI received natoms < 0"); reallocate(); } /* ---------------------------------------------------------------------- >NSTEPS command receive nsteps for timestepping ---------------------------------------------------------------------- */ void MDIEngine::receive_nsteps() { int ierr = MDI_Recv(&nsteps,1,MDI_INT,mdicomm); if (ierr) error->all(FLERR,"MDI: >NSTEPS data"); MPI_Bcast(&nsteps,1,MPI_INT,0,world); if (nsteps < 0) error->all(FLERR,"MDI received nsteps < 0"); } /* ---------------------------------------------------------------------- >TOLERANCE command receive 4 minimization tolerance params ---------------------------------------------------------------------- */ void MDIEngine::receive_tolerance() { double params[4]; int ierr = MDI_Recv(params,4,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: >TOLERANCE data"); MPI_Bcast(params,4,MPI_INT,0,world); etol = params[0]; ftol = params[1]; niterate = static_cast (params[2]); max_eval = static_cast (params[3]); if (etol < 0.0 || ftol < 0.0 || niterate < 0 || max_eval < 0) error->all(FLERR,"MDI received invalid toleranace parameters"); } /* ---------------------------------------------------------------------- >TYPES command ---------------------------------------------------------------------- */ void MDIEngine::receive_types() { actionflag = 0; flag_types = 1; int ierr = MDI_Recv(sys_types,sys_natoms,MDI_INT,mdicomm); if (ierr) error->all(FLERR,"MDI: >TYPES data"); MPI_Bcast(sys_types,sys_natoms,MPI_INT,0,world); } /* ---------------------------------------------------------------------- >VELOCITIES command ---------------------------------------------------------------------- */ void MDIEngine::receive_velocities() { actionflag = 0; flag_velocities = 1; int n = 3*sys_natoms; int ierr = MDI_Recv(sys_velocities,n,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: >VELOCITIES data"); MPI_Bcast(sys_velocities,n,MPI_DOUBLE,0,world); for (int i = 0; i < n; i++) sys_coords[i] * mdi2lmp_velocity; } /* ---------------------------------------------------------------------- receive vector of 3 doubles for all atoms atoms are ordered by atomID, 1 to Natoms used by >FORCES command ---------------------------------------------------------------------- */ void MDIEngine::receive_double3(int which) { int n = 3*atom->natoms; int ierr = MDI_Recv(buf3,n,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: tag; int nlocal = atom->nlocal; int ilocal; if (which == FORCE) { double **f = atom->f; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; f[i][0] = buf3[3*ilocal+0] * mdi2lmp_force; f[i][1] = buf3[3*ilocal+1] * mdi2lmp_force; f[i][2] = buf3[3*ilocal+2] * mdi2lmp_force; } } else if (which == ADDFORCE) { double **f = atom->f; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; f[i][0] += buf3[3*ilocal+0] * mdi2lmp_force; f[i][1] += buf3[3*ilocal+1] * mdi2lmp_force; f[i][2] += buf3[3*ilocal+2] * mdi2lmp_force; } } else if (which == VELOCITY) { double **v = atom->v; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; v[i][0] = buf3[3*ilocal+0] * mdi2lmp_velocity; v[i][1] = buf3[3*ilocal+1] * mdi2lmp_velocity; v[i][2] = buf3[3*ilocal+2] * mdi2lmp_velocity; } } } // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- // MDI "<" driver commands that request data // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- /* ---------------------------------------------------------------------- boxhi[0] - domain->boxlo[0]; celldata[1] = 0.0; celldata[2] = 0.0; celldata[3] = domain->xy; celldata[4] = domain->boxhi[1] - domain->boxlo[1]; celldata[5] = 0.0; celldata[6] = domain->xz; celldata[7] = domain->yz; celldata[8] = domain->boxhi[2] - domain->boxlo[2]; for (int icell = 0; icell < 9; icell++) celldata[icell] *= lmp2mdi_length; int ierr = MDI_Send(celldata,9,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: boxlo[0]; celldata[1] = domain->boxlo[1]; celldata[2] = domain->boxlo[2]; for (int icell = 0; icell < 3; icell++) celldata[icell] *= lmp2mdi_length; int ierr = MDI_Send(celldata,3,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: compute_scalar(); double kinetic_energy = ke->compute_scalar(); double total_energy = potential_energy + kinetic_energy; total_energy *= lmp2mdi_energy; int ierr = MDI_Send(&total_energy,1,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: natoms * MDI_LABEL_LENGTH]; memset(labels,' ',atom->natoms * MDI_LABEL_LENGTH); memset(ibuf1,0,atom->natoms*sizeof(int)); // use atomID to index into ordered ibuf1 tagint *tag = atom->tag; int *type = atom->type; int nlocal = atom->nlocal; int ilocal; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; ibuf1[ilocal] = type[i]; } MPI_Reduce(ibuf1,ibuf1all,atom->natoms,MPI_INT,MPI_SUM,0,world); if (comm->me == 0) { for (int iatom = 0; iatom < atom->natoms; iatom++) { std::string label = std::to_string(ibuf1all[iatom]); int label_len = std::min(int(label.length()), MDI_LABEL_LENGTH); strncpy(&labels[iatom * MDI_LABEL_LENGTH], label.c_str(), label_len); } } int ierr = MDI_Send(labels,atom->natoms*MDI_LABEL_LENGTH,MDI_CHAR,mdicomm); if (ierr) error->all(FLERR,"MDI: (atom->natoms); int ierr = MDI_Send(&natoms,1,MDI_INT,mdicomm); if (ierr != 0) error->all(FLERR,"MDI: compute_scalar(); potential_energy *= lmp2mdi_energy; int ierr = MDI_Send(&potential_energy,1,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: compute_vector(); for (int i = 0; i < 6; i++) vtensor[i] = press->vector[i] * lmp2mdi_pressure; int ierr = MDI_Send(vtensor,6,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: natoms*sizeof(double)); // use atomID to index into ordered buf1 tagint *tag = atom->tag; int nlocal = atom->nlocal; int ilocal; if (which == CHARGE) { double *q = atom->q; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; buf1[ilocal] = q[i]; } } else if (which == MASS) { double *mass = atom->mass; double *rmass = atom->rmass; if (rmass) { for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; buf1[ilocal] = rmass[i]; } } else { int *type = atom->type; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; buf1[ilocal] = mass[type[i]]; } } } MPI_Reduce(buf1,buf1all,atom->natoms,MPI_DOUBLE,MPI_SUM,0,world); int ierr = MDI_Send(buf1all,atom->natoms,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: natoms*sizeof(int)); // use atomID to index into ordered ibuf1 tagint *tag = atom->tag; int nlocal = atom->nlocal; int ilocal; if (which == TYPE) { int *type = atom->type; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; ibuf1[ilocal] = type[i]; } } MPI_Reduce(ibuf1,ibuf1all,atom->natoms,MPI_INT,MPI_SUM,0,world); int ierr = MDI_Send(ibuf1all,atom->natoms,MDI_INT,mdicomm); if (ierr) error->all(FLERR,"MDI: natoms*sizeof(double)); // use atomID to index into ordered buf3 tagint *tag = atom->tag; int nlocal = atom->nlocal; int ilocal; if (which == COORD) { double **x = atom->x; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; buf3[3*ilocal+0] = x[i][0] * lmp2mdi_length; buf3[3*ilocal+1] = x[i][1] * lmp2mdi_length; buf3[3*ilocal+2] = x[i][2] * lmp2mdi_length; } } else if (which == FORCE) { double **f = atom->f; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; buf3[3*ilocal+0] = f[i][0] * lmp2mdi_force; buf3[3*ilocal+1] = f[i][1] * lmp2mdi_force; buf3[3*ilocal+2] = f[i][2] * lmp2mdi_force; } } else if (which == VELOCITY) { double **v = atom->v; for (int i = 0; i < nlocal; i++) { ilocal = static_cast (tag[i]) - 1; buf3[3*ilocal+0] = v[i][0] * lmp2mdi_velocity; buf3[3*ilocal+1] = v[i][1] * lmp2mdi_velocity; buf3[3*ilocal+2] = v[i][2] * lmp2mdi_velocity; } } MPI_Reduce(buf3,buf3all,3*atom->natoms,MPI_DOUBLE,MPI_SUM,0,world); int ierr = MDI_Send(buf3all,3*atom->natoms,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: all(FLERR,"MDI: NBYTES data"); MPI_Bcast(&nbytes,1,MPI_INT,0,world); } /* ---------------------------------------------------------------------- COMMAND command store received value as string of length nbytes invoke as a LAMMPS command ---------------------------------------------------------------------- */ void MDIEngine::single_command() { if (nbytes < 0) error->all(FLERR,"MDI: COMMAND nbytes has not been set"); char *cmd = new char[nbytes+1]; int ierr = MDI_Recv(cmd,nbytes+1,MDI_CHAR,mdicomm); if (ierr) error->all(FLERR,"MDI: COMMAND data"); MPI_Bcast(cmd,nbytes+1,MPI_CHAR,0,world); cmd[nbytes] = '\0'; lammps_command(lmp,cmd); delete [] cmd; } /* ---------------------------------------------------------------------- COMMANDS command store received value as multi-line string of length nbytes invoke as multiple LAMMPS commands ---------------------------------------------------------------------- */ void MDIEngine::many_commands() { if (nbytes < 0) error->all(FLERR,"MDI: COMMANDS nbytes has not been set"); char *cmds = new char[nbytes+1]; int ierr = MDI_Recv(cmds, nbytes+1, MDI_CHAR, mdicomm); if (ierr) error->all(FLERR,"MDI: COMMANDS data"); MPI_Bcast(cmds,nbytes+1,MPI_CHAR,0,world); cmds[nbytes] = '\0'; lammps_commands_string(lmp,cmds); delete [] cmds; } /* ---------------------------------------------------------------------- INFILE command store received value as infile of length length_param invoke as a LAMMPS input script ---------------------------------------------------------------------- */ void MDIEngine::infile() { if (nbytes < 0) error->all(FLERR,"MDI: INFILE nbytes has not been set"); char *infile = new char[nbytes+1]; int ierr = MDI_Recv(infile,nbytes+1,MDI_CHAR,mdicomm); if (ierr) error->all(FLERR,"MDI: INFILE data"); MPI_Bcast(infile,nbytes+1,MPI_CHAR,0,world); infile[nbytes] = '\0'; lammps_file(lmp,infile); delete [] infile; } /* ---------------------------------------------------------------------- compute_scalar(); kinetic_energy *= lmp2mdi_energy; int ierr = MDI_Send(&kinetic_energy,1,MDI_DOUBLE,mdicomm); if (ierr) error->all(FLERR,"MDI: MAXSMALLINT) error->all(FLERR,"Natoms too large to use with mdi engine"); maxatom = sys_natoms; deallocate(); allocate(); } void MDIEngine::deallocate() { memory->destroy(sys_types); memory->destroy(sys_coords); memory->destroy(sys_velocities); memory->destroy(sys_charges); memory->destroy(ibuf1); memory->destroy(buf1); memory->destroy(buf3); memory->destroy(ibuf1all); memory->destroy(buf1all); memory->destroy(buf3all); } void MDIEngine::allocate() { memory->create(sys_types,maxatom,"mdi:sys_types"); memory->create(sys_coords,3*maxatom,"mdi:sys_coords"); memory->create(sys_velocities,3*maxatom,"mdi:sys_velocities"); memory->create(sys_charges,maxatom,"mdi:sys_charges"); memory->create(ibuf1,maxatom,"mdi:ibuf1"); memory->create(buf1,maxatom,"mdi:buf1"); memory->create(buf3,3*maxatom,"mdi:buf3"); memory->create(ibuf1all,maxatom,"mdi:ibuf1all"); memory->create(buf1all,maxatom,"mdi:buf1all"); memory->create(buf3all,3*maxatom,"mdi:buf3all"); } /* ---------------------------------------------------------------------- MDI to/from LAMMPS conversion factors ------------------------------------------------------------------------- */ void MDIEngine::unit_conversions() { double angstrom_to_bohr,kelvin_to_hartree,ev_to_hartree,second_to_aut; MDI_Conversion_factor("angstrom","bohr",&angstrom_to_bohr); MDI_Conversion_factor("kelvin_energy","hartree",&kelvin_to_hartree); MDI_Conversion_factor("electron_volt","hartree",&ev_to_hartree); MDI_Conversion_Factor("second","atomic_unit_of_time",&second_to_aut); // length units mdi2lmp_length = 1.0; lmp2mdi_length = 1.0; if (lmpunits == REAL || lmpunits == METAL) { lmp2mdi_length = angstrom_to_bohr; mdi2lmp_length = 1.0 / angstrom_to_bohr; } // energy units mdi2lmp_energy = 1.0; lmp2mdi_energy = 1.0; if (lmpunits == REAL) { lmp2mdi_energy = kelvin_to_hartree / force->boltz; mdi2lmp_energy = force->boltz / kelvin_to_hartree; } else if (lmpunits == METAL) { lmp2mdi_energy = ev_to_hartree; mdi2lmp_energy = 1.0 / ev_to_hartree; } // force units = energy/length mdi2lmp_force = 1.0; lmp2mdi_force = 1.0; if (lmpunits == REAL) { lmp2mdi_force = (kelvin_to_hartree / force->boltz) / angstrom_to_bohr; mdi2lmp_force = 1.0 / lmp2mdi_force; } else if (lmpunits == METAL) { lmp2mdi_force = ev_to_hartree / angstrom_to_bohr; mdi2lmp_force = angstrom_to_bohr / ev_to_hartree; } // pressure or stress units = force/area = energy/volume mdi2lmp_pressure = 1.0; lmp2mdi_pressure = 1.0; if (lmpunits == REAL) { lmp2mdi_pressure = (kelvin_to_hartree / force->boltz) / (angstrom_to_bohr * angstrom_to_bohr * angstrom_to_bohr) / force->nktv2p; mdi2lmp_pressure = 1.0 / lmp2mdi_pressure; } else if (lmpunits == METAL) { lmp2mdi_pressure = ev_to_hartree / (angstrom_to_bohr * angstrom_to_bohr * angstrom_to_bohr) / force->nktv2p; mdi2lmp_pressure = 1.0 / lmp2mdi_pressure; } // velocity units = distance/time mdi2lmp_velocity = 1.0; lmp2mdi_velocity = 1.0; if (lmpunits == REAL) { lmp2mdi_velocity = angstrom_to_bohr / (1.0e-15 * second_to_aut); mdi2lmp_velocity = 1.0 / lmp2mdi_velocity; } else if (lmpunits == METAL) { lmp2mdi_velocity = angstrom_to_bohr / (1.0e-12 * second_to_aut); mdi2lmp_velocity = 1.0 / lmp2mdi_velocity; } }