# MDI driver to perform a series of independent calculations # using LAMMPS (or a QM code with MDI support) as an MDI engine, # the engine can be either a standalone code or a plugin library # Syntax: python3 series_driver.py switch arg switch arg ... # possible switches: # -mdi "-role DRIVER ..." # required switch # example for stand-alone mode: # -mdi "-role DRIVER -name sequence -method TCP -port 8021" # example for plugin mode: # -mdi "-role DRIVER -name sequemce -method LINK # -plugin_path /home/sjplimp/lammps/src/" # -plugin name # name of plugin library, only when using plugin mode # -plugin_args arglist # args to add when launching plugin library, only when using plugin mode # enclose arglist in quotes if multiple words # -n 3 # number of calculations to perform, default = 3 # -mode eval/run/min # style of calculations: single snapshot evals, dynamics, minimization # default = run # -size Nx Ny Nz # cubic lattice, default = 4 4 4 # -rho 0.75 0.1 # reduced density and random variation thereof, default = 0.75 0.1 # -delta 0.1 # randomly perturb atoms initially by this distance, default 0.1 # -nsteps 100 # number of timesteps in dynamics runs, default = 100 # -temp 1.0 # initial temperature in dynamics runs, default = 1.0 # -tol 0.001 # tolerance for minimizations, default = 0.001 # -seed 12345 # random number seed > 0, default = 12345 import sys,math,random import mdi import numpy as np from mpi4py import MPI # error message def error(txt=None): if txt: raise Exception(txt) raise Exception("Syntax: python3 series_driver.py switch arg switch arg ...") # loop over all the tasks to exchange MDI Sends/Receives with the engine # for standalone mode, this is called by main program below # for plugin mode, this is a callback function invoked by MDI 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(" narg: error() mdiarg = args[iarg+1] iarg += 2 elif args[iarg] == "-plugin": if iarg+2 > narg: error() plugin = args[iarg+1] iarg += 2 elif args[iarg] == "-plugin_args": if iarg+2 > narg: error() plugin_args = args[iarg+1] iarg += 2 elif args[iarg] == "-n": if iarg+2 > narg: error() ncalc = int(args[iarg+1]) iarg += 2 elif args[iarg] == "-mode": if iarg+2 > narg: error() mode = args[iarg+1] if mode != "eval" and mode != "run" and mode != "min": error() iarg += 2 elif args[iarg] == "-size": if iarg+4 > narg: error() nx = int(args[iarg+1]) ny = int(args[iarg+2]) nz = int(args[iarg+3]) if nx <= 0 or ny <= 0 or nz <= 0: error() iarg += 4 elif args[iarg] == "-rho": if iarg+3 > narg: error() rho = float(args[iarg+1]) rhodelta = float(args[iarg+2]) if rho-rhodelta <= 0.0: error() iarg += 3 elif args[iarg] == "-delta": if iarg+2 > narg: error() delta = float(args[iarg+1]) if delta < 0.0: error() iarg += 2 elif args[iarg] == "-nsteps": if iarg+2 > narg: error() nsteps = int(args[iarg+1]) if nsteps < 0: error() iarg += 2 elif args[iarg] == "-temp": if iarg+2 > narg: error() tinitial = float(args[iarg+1]) if tinitial < 0.0: error() iarg += 2 elif args[iarg] == "-tol": if iarg+2 > narg: error() tol = float(args[iarg+1]) if tol < 0.0: error() iarg += 2 elif args[iarg] == "-seed": if iarg+2 > narg: error() seed = int(args[iarg+1]) if seed <= 0: error() iarg += 2 else: error() if not mdiarg: error() # LAMMPS engine is a stand-alone code # 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 engine mdicomm = mdi.MDI_Accept_Communicator() perform_tasks(world,mdicomm,None) # LAMMPS engine is a plugin library # launch plugin # MDI will call back to perform_tasks() if plugin: mdi.MDI_Init(mdiarg) world = MPI.COMM_WORLD plugin_args += " -mdi \"-role ENGINE -name lammps -method LINK\"" mdi.MDI_Launch_plugin(plugin,plugin_args,world,perform_tasks,None)