/* ---------------------------------------------------------------------- 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 "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 #include #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 }; #define MAXELEMENT 103 // used elsewhere in MDI package /* ---------------------------------------------------------------------- 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) { // check requirements for LAMMPS to work with MDI as an engine if (atom->tag_enable == 0) error->all(FLERR, "MDI engine requires atom IDs"); if (atom->natoms && atom->tag_consecutive() == 0) error->all(FLERR, "MDI engine requires consecutive atom IDs"); // optional args elements = nullptr; int iarg = 0; while (iarg < narg) { if (strcmp(arg[iarg], "elements") == 0) { int ntypes = atom->ntypes; delete[] elements; elements = new int[ntypes + 1]; if (iarg + ntypes + 1 > narg) error->all(FLERR, "Illegal mdi engine command"); for (int i = 1; i <= ntypes; i++) { elements[i] = utils::inumeric(FLERR, arg[iarg + i], false, lmp); if (elements[i] < 0 || elements[i] > MAXELEMENT) error->all(FLERR, "Illegal mdi engine command"); } iarg += ntypes + 1; } else error->all(FLERR, "Illegal mdi engine command"); } // error check an MDI element does not map to multiple atom types if (elements) { int ntypes = atom->ntypes; for (int i = 1; i < ntypes; i++) for (int j = i + 1; j <= ntypes; j++) { if (elements[i] == 0 || elements[j] == 0) continue; if (elements[i] == elements[j]) error->all(FLERR, "MDI engine element cannot map to multiple types"); } } // 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"); ke = modify->add_compute(fmt::format("{} all ke", id_ke)); id_pe = utils::strdup(std::string("MDI_ENGINE") + "_pe"); pe = modify->add_compute(fmt::format("{} all pe", id_pe)); id_press = utils::strdup(std::string("MDI_ENGINE") + "_press"); press = modify->add_compute(fmt::format("{} all pressure NULL virial", id_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 = static_cast(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 a callback function with MDI used when engine runs in plugin mode // execute_command_plugin_wrapper() must be a static method MDI_Set_execute_command_func(execute_command_plugin_wrapper, 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 (true) { // 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[] elements; delete[] mdicmd; delete[] node_engine; delete[] node_driver; modify->delete_compute(id_ke); 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; } /* ---------------------------------------------------------------------- wrapper function on execute_command() invoked as callback by MDI when engine operates in plugin mode this is a static method in mdi_engine.h ---------------------------------------------------------------------- */ int MDIEngine::execute_command_plugin_wrapper(const char *command, MDI_Comm comm, void *class_obj) { auto mdi_engine = (MDIEngine *) class_obj; return mdi_engine->execute_command(command, comm); } /* ---------------------------------------------------------------------- process a single driver command called by engine_node() in loop when engine runs as stand-alone code called by execute_command_plugin_wrapper() when engine runs as plugin lib ---------------------------------------------------------------------- */ 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: Cannot confirm that command '{}' is supported", command); } 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, ">ELEMENTS") == 0) { if (!elements) error->all(FLERR, "MDI engine command did not define element list"); receive_elements(); } 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", ">ELEMENTS"); 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 = dynamic_cast(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 = dynamic_cast(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 (true) { 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 or >ELEMENTS, >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 or >ELEMENTS, >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() // create list of 1 to sys_natoms IDs // optionally set charges if specified by ">CHARGES" tagint *sys_ids; memory->create(sys_ids, sys_natoms, "mdi:sys_ids"); for (int i = 0; i < sys_natoms; i++) sys_ids[i] = i + 1; if (flag_velocities) lammps_create_atoms(lmp, sys_natoms, sys_ids, sys_types, sys_coords, sys_velocities, nullptr, 1); else lammps_create_atoms(lmp, sys_natoms, sys_ids, sys_types, sys_coords, nullptr, nullptr, 1); if (flag_charges) lammps_scatter_atoms(lmp, (char *) "q", 1, 1, sys_charges); memory->destroy(sys_ids); // 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 // 3,7,6 = xy, yz, xz tilt factors 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 an upper triangular matrix"); if (sys_cell[3] != 0.0 || sys_cell[7] != 0.0 || sys_cell[6] != 0.0) if (!domain->triclinic) error->all(FLERR, "MDI: Received cell edges are for a triclinic box, " "but LAMMPS is using an orthogonal box"); } /* ---------------------------------------------------------------------- >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; } /* ---------------------------------------------------------------------- >ELEMENTS command receive elements for each atom = atomic numbers convert to LAMMPS atom types and store in sys_types ---------------------------------------------------------------------- */ void MDIEngine::receive_elements() { actionflag = 0; flag_types = 1; int ierr = MDI_Recv(sys_types, sys_natoms, MDI_INT, mdicomm); if (ierr) error->all(FLERR, "MDI: >ELEMENTS data"); MPI_Bcast(sys_types, sys_natoms, MPI_INT, 0, world); // convert from element atomic numbers to LAMMPS atom types // use maping provided by mdi engine command int ntypes = atom->ntypes; int itype; for (int i = 0; i < sys_natoms; i++) { for (itype = 1; itype <= ntypes; itype++) { if (sys_types[i] == elements[itype]) { sys_types[i] = itype; break; } } if (itype > ntypes) error->all(FLERR, "MDI element not found in element list"); } } /* ---------------------------------------------------------------------- >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 * sys_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: 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, sys_natoms, MPI_INT, MPI_SUM, 0, world); if (comm->me == 0) { for (int iatom = 0; iatom < sys_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, sys_natoms * MDI_LABEL_LENGTH, MDI_CHAR, mdicomm); if (ierr) error->all(FLERR, "MDI: 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(); vtensor_full[0] = press->vector[0] * lmp2mdi_pressure; vtensor_full[4] = press->vector[1] * lmp2mdi_pressure; vtensor_full[8] = press->vector[2] * lmp2mdi_pressure; vtensor_full[1] = vtensor_full[3] = press->vector[3] * lmp2mdi_pressure; vtensor_full[2] = vtensor_full[6] = press->vector[4] * lmp2mdi_pressure; vtensor_full[5] = vtensor_full[7] = press->vector[5] * lmp2mdi_pressure; int ierr = MDI_Send(vtensor_full, 9, MDI_DOUBLE, mdicomm); if (ierr) error->all(FLERR, "MDI: 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, sys_natoms, MPI_DOUBLE, MPI_SUM, 0, world); int ierr = MDI_Send(buf1all, sys_natoms, MDI_DOUBLE, mdicomm); if (ierr) error->all(FLERR, "MDI: 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, sys_natoms, MPI_INT, MPI_SUM, 0, world); int ierr = MDI_Send(ibuf1all, sys_natoms, MDI_INT, mdicomm); if (ierr) error->all(FLERR, "MDI: 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 * sys_natoms, MPI_DOUBLE, MPI_SUM, 0, world); int ierr = MDI_Send(buf3all, 3 * sys_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"); auto 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"); auto 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"); auto infile = new char[nbytes + 1]; int ierr = MDI_Recv(infile, nbytes + 1, MDI_CHAR, mdicomm); if (ierr) error->all(FLERR, "MDI: INFILE data for {}", infile); 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 // MDI energy/volume = Hartree/Bohr^3, // so need to remove LAMMPS nktv2p from pressure 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; } }