def perform_aimd(world, mm_comm, qm_comm): me = world.Get_rank() # receive number of atoms from the MM engine mdi.MDI_Send_command("<NATOMS", mm_comm) natoms = mdi.MDI_Recv(1, mdi.MDI_INT, mm_comm) natoms = world.bcast(natoms, root=0) # allocate arrays for coordinates and forces coords = np.zeros(3 * natoms, dtype=np.float64) forces = np.zeros(3 * natoms, dtype=np.float64) # MM engine initializes a new MD simulation mdi.MDI_Send_command("@INIT_MD", mm_comm) # ----------------- # compute initial forces for Verlet timestepping # and initial energy for output on step 0 # ----------------- # MM engine proceeds to @FORCES node in setup() mdi.MDI_Send_command("@FORCES", mm_comm) # get coords from MM engine mdi.MDI_Send_command("<COORDS", mm_comm) mdi.MDI_Recv(3 * natoms, mdi.MDI_DOUBLE, mm_comm, buf=coords) world.Bcast(coords, root=0) # send coords to QM engine mdi.MDI_Send_command(">COORDS", qm_comm) mdi.MDI_Send(coords, 3 * natoms, mdi.MDI_DOUBLE, qm_comm) # get QM potential energy mdi.MDI_Send_command("<PE", qm_comm) qm_pe = mdi.MDI_Recv(1, mdi.MDI_DOUBLE, qm_comm) qm_pe = world.bcast(qm_pe, root=0) # get forces from QM engine mdi.MDI_Send_command("<FORCES", qm_comm) mdi.MDI_Recv(3 * natoms, mdi.MDI_DOUBLE, qm_comm, buf=forces) world.Bcast(forces, root=0) # send forces to MM engine mdi.MDI_Send_command(">FORCES", mm_comm) mdi.MDI_Send(forces, 3 * natoms, mdi.MDI_DOUBLE, mm_comm) # get MM kinetic energy mdi.MDI_Send_command("<KE", mm_comm) mm_ke = mdi.MDI_Recv(1, mdi.MDI_DOUBLE, mm_comm) mm_ke = world.bcast(mm_ke, root=0) # output by driver # normalize energies by atom count if me == 0: print("Step %d: MM energy %g, QM energy %g, Total energy %g" % \ (0,mm_ke/natoms,qm_pe/natoms,(mm_ke+qm_pe)/natoms)) # ----------------- # timestepping loop # ----------------- for istep in range(nsteps): # MM engine proceeds to @FORCES node mdi.MDI_Send_command("@FORCES", mm_comm) # get coords from MM engine mdi.MDI_Send_command("<COORDS", mm_comm) mdi.MDI_Recv(3 * natoms, mdi.MDI_DOUBLE, mm_comm, buf=coords) world.Bcast(coords, root=0) # send coords to QM engine mdi.MDI_Send_command(">COORDS", qm_comm) mdi.MDI_Send(coords, 3 * natoms, mdi.MDI_DOUBLE, qm_comm) # get QM potential energy mdi.MDI_Send_command("<PE", qm_comm) qm_pe = mdi.MDI_Recv(1, mdi.MDI_DOUBLE, qm_comm) qm_pe = world.bcast(qm_pe, root=0) # get forces from QM engine mdi.MDI_Send_command("<FORCES", qm_comm) mdi.MDI_Recv(3 * natoms, mdi.MDI_DOUBLE, qm_comm, buf=forces) world.Bcast(forces, root=0) # send forces to MM engine mdi.MDI_Send_command(">FORCES", mm_comm) mdi.MDI_Send(forces, 3 * natoms, mdi.MDI_DOUBLE, mm_comm) # MM engine proceeds to @ENDSTEP node # so that KE will be for fully updated velocity mdi.MDI_Send_command("@ENDSTEP", mm_comm) # get MM kinetic energy mdi.MDI_Send_command("<KE", mm_comm) mm_ke = mdi.MDI_Recv(1, mdi.MDI_DOUBLE, mm_comm) mm_ke = world.bcast(mm_ke, root=0) # output by driver # normalize energies by atom count if me == 0: print("Step %d: MM energy %g, QM energy %g, Total energy %g" % \ (istep+1,mm_ke/natoms,qm_pe/natoms,(mm_ke+qm_pe)/natoms)) # send EXIT to each engine mdi.MDI_Send_command("EXIT", mm_comm) mdi.MDI_Send_command("EXIT", qm_comm)
def perform_tasks(world, mdicomm, dummy): me = world.Get_rank() nprocs = world.Get_size() # allocate vectors for per-atom types, coords, vels, forces natoms = nx * ny * nz atypes = np.zeros(natoms, dtype=np.int) coords = np.zeros(3 * natoms, dtype=np.float64) vels = np.zeros(3 * natoms, dtype=np.float64) forces = np.zeros(3 * natoms, dtype=np.float64) atypes[:] = 1 # initialize RN generator random.seed(seed) # loop over sequence of calculations for icalc in range(ncalc): # define simulation box onerho = rho + (random.random() - 0.5) * rhodelta sigma = pow(1.0 / onerho, 1.0 / 3.0) xlo = ylo = zlo = 0.0 xhi = nx * sigma yhi = ny * sigma zhi = nz * sigma # send simulation box to engine vec = [xhi - xlo, 0.0, 0.0] + [0.0, yhi - ylo, 0.0 ] + [0.0, 0.0, zhi - zlo] mdi.MDI_Send_command(">CELL", mdicomm) mdi.MDI_Send(vec, 9, mdi.MDI_DOUBLE, mdicomm) # create atoms on perfect lattice m = 0 for k in range(nz): for j in range(ny): for i in range(nx): coords[m] = i * sigma coords[m + 1] = j * sigma coords[m + 2] = k * sigma m += 3 # perturb lattice for m in range(3 * natoms): coords[m] += 2.0 * random.random() * delta - delta # define initial velocities for m in range(3 * natoms): vels[m] = random.random() - 0.5 tcurrent = 0.0 for m in range(3 * natoms): tcurrent += vels[m] * vels[m] tcurrent /= 3 * (natoms - 1) factor = math.sqrt(tinitial / tcurrent) for m in range(3 * natoms): vels[m] *= factor # send atoms and their properties to engine mdi.MDI_Send_command(">NATOMS", mdicomm) mdi.MDI_Send(natoms, 1, mdi.MDI_INT, mdicomm) mdi.MDI_Send_command(">TYPES", mdicomm) mdi.MDI_Send(atypes, natoms, mdi.MDI_INT, mdicomm) mdi.MDI_Send_command(">COORDS", mdicomm) mdi.MDI_Send(coords, 3 * natoms, mdi.MDI_DOUBLE, mdicomm) mdi.MDI_Send_command(">VELOCITIES", mdicomm) mdi.MDI_Send(vels, 3 * natoms, mdi.MDI_DOUBLE, mdicomm) # eval or run or minimize if mode == "eval": pass elif mode == "run": mdi.MDI_Send_command(">NSTEPS", mdicomm) mdi.MDI_Send(nsteps, 1, mdi.MDI_INT, mdicomm) mdi.MDI_Send_command("MD", mdicomm) elif mode == "min": mdi.MDI_Send_command(">TOLERANCE", mdicomm) params = [tol, tol, 1000.0, 1000.0] mdi.MDI_Send(params, 4, mdi.MDI_DOUBLE, mdicomm) mdi.MDI_Send_command("OPTG", mdicomm) # request potential energy mdi.MDI_Send_command("<PE", mdicomm) pe = mdi.MDI_Recv(1, mdi.MDI_DOUBLE, mdicomm) pe = world.bcast(pe, root=0) # request virial tensor mdi.MDI_Send_command("<STRESS", mdicomm) virial = mdi.MDI_Recv(9, mdi.MDI_DOUBLE, mdicomm) virial = world.bcast(virial, root=0) # request forces mdi.MDI_Send_command("<FORCES", mdicomm) mdi.MDI_Recv(3 * natoms, mdi.MDI_DOUBLE, mdicomm, buf=forces) world.Bcast(forces, root=0) # final output from each calculation # pressure = trace of virial tensor, no kinetic component aveeng = pe / natoms pressure = (virial[0] + virial[4] + virial[8]) / 3.0 m = 0 fx = fy = fz = 0.0 for i in range(natoms): fx += forces[m] fy += forces[m + 1] fz += forces[m + 2] m += 3 fx /= natoms fy /= natoms fz /= natoms line = "Calc %d: eng %7.5g pressure %7.5g aveForce %7.5g %7.5g %7.5g" % \ (icalc+1,aveeng,pressure,fx,fy,fz) if me == 0: print(line) # send EXIT command to engine # in plugin mode, removes the plugin library mdi.MDI_Send_command("EXIT", mdicomm) # return needed for plugin mode return 0
if not mdiarg: error() # LAMMPS engines are stand-alone codes # world = MPI communicator for just this driver # invoke perform_tasks() directly if not plugin: mdi.MDI_Init(mdiarg) world = mdi.MDI_MPI_get_world_comm() # connect to 2 engines, determine which is MM vs QM mdicomm1 = mdi.MDI_Accept_Communicator() mdicomm2 = mdi.MDI_Accept_Communicator() mdi.MDI_Send_command("<NAME", mdicomm1) name1 = mdi.MDI_Recv(mdi.MDI_NAME_LENGTH, mdi.MDI_CHAR, mdicomm1) name1 = world.bcast(name1, root=0) mdi.MDI_Send_command("<NAME", mdicomm2) name2 = mdi.MDI_Recv(mdi.MDI_NAME_LENGTH, mdi.MDI_CHAR, mdicomm2) name2 = world.bcast(name2, root=0) if name1 == "MM" and name2 == "QM": mm_comm = mdicomm1 qm_comm = mdicomm2 elif name1 == "QM" and name2 == "MM": mm_comm = mdicomm2 qm_comm = mdicomm1 else: error("Two engines have invalid names")