def print_pdb(atoms, cell, filedesc=sys.stdout, title=""): """Prints an atomic configuration into a pdb formatted file. Also prints the cell parameters in standard pdb form. Note that the angles are in degrees. Args: atoms: An atoms object giving the atom positions. cell: A cell object giving the system box. filedesc: An open writable file object. Defaults to standard output. title: An optional string of max. 70 characters. """ fmt_cryst = "CRYST1%9.3f%9.3f%9.3f%7.2f%7.2f%7.2f%s%4i\n" fmt_atom = "ATOM %5i %4s%1s%3s %1s%4i%1s %8.3f%8.3f%8.3f%6.2f%6.2f %2s%2i\n" if title != "": filedesc.write("TITLE %70s\n" % (title)) a, b, c, alpha, beta, gamma = mt.h2abc_deg(cell.h) z = 1 filedesc.write(fmt_cryst % (a, b, c, alpha, beta, gamma, " P 1 ", z)) natoms = atoms.natoms qs = depstrip(atoms.q) lab = depstrip(atoms.names) for i in range(natoms): data = (i + 1, lab[i], ' ', ' 1', ' ', 1, ' ', qs[3 * i], qs[3 * i + 1], qs[3 * i + 2], 0.0, 0.0, ' ', 0) filedesc.write(fmt_atom % data) filedesc.write("END\n")
def queue(self, atoms, cell, reqid=-1): """Adds a request. Note that the pars dictionary need to be sent as a string of a standard format so that the initialisation of the driver can be done. Args: atoms: An Atoms object giving the atom positions. cell: A Cell object giving the system box. pars: An optional dictionary giving the parameters to be sent to the driver for initialisation. Defaults to {}. reqid: An optional integer that identifies requests of the same type, e.g. the bead index Returns: A list giving the status of the request of the form {'pos': An array giving the atom positions folded back into the unit cell, 'cell': Cell object giving the system box, 'pars': parameter string, 'result': holds the result as a list once the computation is done, 'status': a string labelling the status of the calculation, 'id': the id of the request, usually the bead number, 'start': the starting time for the calculation, used to check for timeouts.}. """ par_str = " " if not self.pars is None: for k, v in self.pars.items(): par_str += k + " : " + str(v) + " , " else: par_str = " " pbcpos = depstrip(atoms.q).copy() pbcpos2 = depstrip(atoms.q).copy() if self.dopbc: cell.array_pbc(pbcpos) newreq = ForceRequest({ "id": reqid, "pos": pbcpos, "cell": (depstrip(cell.h).copy(), depstrip(cell.ih).copy()), "pars": par_str, "result": None, "status": "Queued", "start": -1 }) self._threadlock.acquire() try: self.requests.append(newreq) finally: self._threadlock.release() return newreq
def print_xyz_path(beads, cell, filedesc=sys.stdout): """Prints all the bead configurations, into a xyz formatted file. Prints all the replicas for each time step separately, rather than all at once. Args: beads: A beads object giving the bead positions. cell: A cell object giving the system box. filedesc: An open writable file object. Defaults to standard output. """ a, b, c, alpha, beta, gamma = mt.h2abc_deg(cell.h) natoms = beads.natoms nbeads = beads.nbeads for j in range(nbeads): filedesc.write( "%d\n# bead: %d CELL(abcABC): %10.5f %10.5f %10.5f %10.5f %10.5f %10.5f \n" % (natoms, j, a, b, c, alpha, beta, gamma)) for i in range(natoms): qs = depstrip(beads.q) lab = depstrip(beads.names) filedesc.write( "%8s %12.5e %12.5e %12.5e\n" % (lab[i], qs[j][3 * i], qs[j][3 * i + 1], qs[j][3 * i + 2]))
def step(self, step=None): """ Does one simulation time step Attributes: ttime: The time taken in applying the thermostat steps. """ self.qtime = -time.time() info("\nMD STEP %d" % step, verbosity.debug) # Store previous forces for warning exit condition self.old_f[:] = self.forces.f dq1 = depstrip(self.forces.f) # Move direction for steepest descent dq1_unit = dq1 / np.sqrt(np.dot(dq1.flatten(), dq1.flatten())) info(" @GEOP: Determined SD direction", verbosity.debug) #Check for fixatoms if len(self.fixatoms) > 0: for dqb in dq1_unit: dqb[self.fixatoms * 3] = 0.0 dqb[self.fixatoms * 3 + 1] = 0.0 dqb[self.fixatoms * 3 + 2] = 0.0 #Set position and direction inside the mapper self.lm.set_dir(depstrip(self.beads.q), dq1_unit) # Reuse initial value since we have energy and forces already u0, du0 = (self.forces.pot.copy(), np.dot(depstrip(self.forces.f.flatten()), dq1_unit.flatten())) # Do one SD iteration; return positions and energy #(x, fx,dfx) = min_brent(self.lm, fdf0=(u0, du0), x0=0.0, #DELETE min_brent(self.lm, fdf0=(u0, du0), x0=0.0, tol=self.ls_options["tolerance"] * self.tolerances["energy"], itmax=self.ls_options["iter"], init_step=self.ls_options["step"]) info(" Number of force calls: %d" % (self.lm.fcount)) self.lm.fcount = 0 #Update positions and forces self.beads.q = self.lm.dbeads.q self.forces.transfer_forces( self.lm.dforces) #This forces the update of the forces d_x = np.absolute(np.subtract(self.beads.q, self.lm.x0)) x = np.linalg.norm(d_x) # Automatically adapt the search step for the next iteration. # Relaxes better with very small step --> multiply by factor of 0.1 or 0.01 self.ls_options["step"] = 0.1 * x * self.ls_options["adaptive"] + ( 1 - self.ls_options["adaptive"]) * self.ls_options["step"] # Exit simulation step d_x_max = np.amax(np.absolute(d_x)) self.exitstep(self.forces.pot, u0, d_x_max)
def print_pdb(atoms, cell, filedesc = sys.stdout, title=""): """Prints the atom configurations, into a pdb formatted file. Also prints the cell parameters in standard pdb form. Note that the angles are in degrees. Args: atoms: An atoms object giving the atom positions. cell: A cell object giving the system box. filedesc: An open writable file object. Defaults to standard output. title: An optional string of max. 70 characters. """ if title != "" : filedesc.write("TITLE %70s\n" % (title)) a, b, c, alpha, beta, gamma = mt.h2abc_deg(cell.h) z = 1 filedesc.write("CRYST1%9.3f%9.3f%9.3f%7.2f%7.2f%7.2f%s%4i\n" % (a, b, c, alpha, beta, gamma, " P 1 ", z)) natoms = atoms.natoms qs = depstrip(atoms.q) lab = depstrip(atoms.names) for i in range(natoms): filedesc.write("ATOM %5i %4s%1s%3s %1s%4i%1s %8.3f%8.3f%8.3f%6.2f%6.2f %2s%2i\n" % (i+1, lab[i], ' ', ' 1', ' ', 1, ' ', qs[3*i], qs[3*i+1], qs[3*i+2], 0.0, 0.0, ' ', 0)) filedesc.write("END\n")
def print_pdb_path(beads, cell, filedesc = sys.stdout): """Prints all the bead configurations, into a pdb formatted file. Prints the ring polymer springs as well as the bead positions using the CONECT command. Also prints the cell parameters in standard pdb form. Note that the angles are in degrees. Args: beads: A beads object giving the bead positions. cell: A cell object giving the system box. filedesc: An open writable file object. Defaults to standard output. """ a, b, c, alpha, beta, gamma = mt.h2abc_deg(cell.h) z = 1 #What even is this parameter? filedesc.write("CRYST1%9.3f%9.3f%9.3f%7.2f%7.2f%7.2f%s%4i\n" % (a, b, c, alpha, beta, gamma, " P 1 ", z)) natoms = beads.natoms nbeads = beads.nbeads for j in range(nbeads): for i in range(natoms): qs = depstrip(beads.q) lab = depstrip(beads.names) filedesc.write("ATOM %5i %4s%1s%3s %1s%4i%1s %8.3f%8.3f%8.3f%6.2f%6.2f %2s%2i\n" % (j*natoms+i+1, lab[i],' ',' 1',' ',1,' ', qs[j][3*i], qs[j][3*i+1], qs[j][3*i+2],0.0,0.0,' ',0)) if nbeads > 1: for i in range(natoms): filedesc.write("CONECT%5i%5i\n" % (i+1, (nbeads-1)*natoms+i+1)) for j in range(nbeads-1): for i in range(natoms): filedesc.write("CONECT%5i%5i\n" % (j*natoms+i+1, (j+1)*natoms+i+1)) filedesc.write("END\n")
def print_xyz_path(beads, cell, filedesc=sys.stdout): """Prints all the bead configurations, into a xyz formatted file. Prints all the replicas for each time step separately, rather than all at once. Args: beads: A beads object giving the bead positions. cell: A cell object giving the system box. filedesc: An open writable file object. Defaults to standard output. """ a, b, c, alpha, beta, gamma = mt.h2abc_deg(cell.h) natoms = beads.natoms nbeads = beads.nbeads for j in range(nbeads): filedesc.write( "%d\n# bead: %d CELL(abcABC): %10.5f %10.5f %10.5f %10.5f %10.5f %10.5f \n" % (natoms, j, a, b, c, alpha, beta, gamma) ) for i in range(natoms): qs = depstrip(beads.q) lab = depstrip(beads.names) filedesc.write("%8s %12.5e %12.5e %12.5e\n" % (lab[i], qs[j][3 * i], qs[j][3 * i + 1], qs[j][3 * i + 2]))
def __call__(self, x): self.dbeads.q = self.x0 + self.d * x e = self.dforces.pot # Energy g = -np.dot(depstrip(self.dforces.f).flatten(), self.d.flatten()) # Gradient return e, g
def pconstraints(self): """This removes the centre of mass contribution to the kinetic energy. Calculates the centre of mass momenta, then removes the mass weighted contribution from each atom. If the ensemble defines a thermostat, then the contribution to the conserved quantity due to this subtraction is added to the thermostat heat energy, as it is assumed that the centre of mass motion is due to the thermostat. If there is a choice of thermostats, the thermostat connected to the centroid is chosen. """ if (self.fixcom): pcom = np.zeros(3, float) na3 = self.beads.natoms * 3 nb = self.beads.nbeads p = depstrip(self.beads.p) m = depstrip(self.beads.m3)[:, 0:na3:3] M = self.beads[0].M for i in range(3): pcom[i] = p[:, i:na3:3].sum() self.ensemble.eens += np.dot(pcom, pcom) / (2.0 * M * nb) # subtracts COM velocity pcom *= 1.0 / (nb * M) for i in range(3): self.beads.p[:, i:na3:3] -= m * pcom[i] if len(self.fixatoms) > 0: for bp in self.beads.p: m = depstrip(self.beads.m) self.ensemble.eens += 0.5 * np.dot( bp[self.fixatoms * 3], bp[self.fixatoms * 3] / m[self.fixatoms]) self.ensemble.eens += 0.5 * np.dot( bp[self.fixatoms * 3 + 1], bp[self.fixatoms * 3 + 1] / m[self.fixatoms]) self.ensemble.eens += 0.5 * np.dot( bp[self.fixatoms * 3 + 2], bp[self.fixatoms * 3 + 2] / m[self.fixatoms]) bp[self.fixatoms * 3] = 0.0 bp[self.fixatoms * 3 + 1] = 0.0 bp[self.fixatoms * 3 + 2] = 0.0
def __call__(self, x): """ computes energy and gradient for optimization step determines new position (x0+d*x)""" self.fcount += 1 self.dbeads.q = self.x0 + self.d * x e = self.dforces.pot # Energy g = -np.dot(depstrip(self.dforces.f).flatten(), self.d.flatten()) # Gradient return e, g
def step(self, step=None): """Does one simulation time step.""" # thermostat is applied at the outer loop self.ttime = -time.time() self.thermostat.step() self.pconstraints() self.ttime += time.time() # bias is applied at the outer loop too self.beads.p += depstrip(self.bias.f) * (self.dt * 0.5) self.mtsprop(0, 1.0) self.beads.p += depstrip(self.bias.f) * (self.dt * 0.5) self.ttime -= time.time() self.thermostat.step() self.pconstraints() self.ttime += time.time()
def print_xyz(atoms, cell, filedesc=sys.stdout, title=""): """Prints an atomic configuration into an XYZ formatted file. Args: atoms: An atoms object giving the centroid positions. cell: A cell object giving the system box. filedesc: An open writable file object. Defaults to standard output. title: This gives a string to be appended to the comment line. """ a, b, c, alpha, beta, gamma = mt.h2abc_deg(cell.h) natoms = atoms.natoms fmt_header = "%d\n# CELL(abcABC): %10.5f %10.5f %10.5f %10.5f %10.5f %10.5f %s\n" filedesc.write(fmt_header % (natoms, a, b, c, alpha, beta, gamma, title)) # direct access to avoid unnecessary slow-down qs = depstrip(atoms.q) lab = depstrip(atoms.names) for i in range(natoms): filedesc.write("%8s %12.5e %12.5e %12.5e\n" % (lab[i], qs[3 * i], qs[3 * i + 1], qs[3 * i + 2]))
def print_xyz(atoms, cell, filedesc=sys.stdout, title=""): """Prints the centroid configurations, into a xyz formatted file. Args: atoms: An atoms object giving the centroid positions. cell: A cell object giving the system box. filedesc: An open writable file object. Defaults to standard output. title: This gives a string to be appended to the comment line. """ a, b, c, alpha, beta, gamma = mt.h2abc_deg(cell.h) natoms = atoms.natoms filedesc.write( "%d\n# CELL(abcABC): %10.5f %10.5f %10.5f %10.5f %10.5f %10.5f %s\n" % (natoms, a, b, c, alpha, beta, gamma, title) ) # direct access to avoid unnecessary slow-down qs = depstrip(atoms.q) lab = depstrip(atoms.names) for i in range(natoms): filedesc.write("%8s %12.5e %12.5e %12.5e\n" % (lab[i], qs[3 * i], qs[3 * i + 1], qs[3 * i + 2]))
def print_json(atoms, cell, filedesc=sys.stdout, title=""): """Prints an atomic configuration into an XYZ formatted file. Args: atoms: An atoms object giving the centroid positions. cell: A cell object giving the system box. filedesc: An open writable file object. Defaults to standard output. title: This gives a string to be appended to the comment line. """ a, b, c, alpha, beta, gamma = mt.h2abc_deg(cell.h) natoms = atoms.natoms # direct access to avoid unnecessary slow-down qs = depstrip(atoms.q) lab = depstrip(atoms.names) filedesc.write( json.dumps([ natoms, a, b, c, alpha, beta, gamma, title, qs.tolist(), lab.tolist() ])) filedesc.write("\n")
def queue(self, atoms, cell, pars=None, reqid=0): """Adds a request. Note that the pars dictionary need to be sent as a string of a standard format so that the initialisation of the driver can be done. Args: atoms: An Atoms object giving the atom positions. cell: A Cell object giving the system box. pars: An optional dictionary giving the parameters to be sent to the driver for initialisation. Defaults to {}. reqid: An optional integer that identifies requests of the same type, e.g. the bead index Returns: A list giving the status of the request of the form {'atoms': Atoms object giving the atom positions, 'cell': Cell object giving the system box, 'pars': parameter string, 'result': holds the result as a list once the computation is done, 'status': a string labelling the status, 'id': the id of the request, usually the bead number, 'start': the starting time for the calculation, used to check for timeouts.}. """ par_str = " " if not pars is None: for k, v in pars.items(): par_str += k + " : " + str(v) + " , " else: par_str = " " # APPLY PBC -- this is useful for codes such as LAMMPS that don't do full PBC when computing distances pbcpos = depstrip(atoms.q).copy() if self.dopbc: cell.array_pbc(pbcpos) newreq = { "pos": pbcpos, "cell": cell, "pars": par_str, "result": None, "status": "Queued", "id": reqid, "start": -1, } self.requests.append(newreq) return newreq
def queue(self, atoms, cell, pars=None, reqid=0): """Adds a request. Note that the pars dictionary need to be sent as a string of a standard format so that the initialisation of the driver can be done. Args: atoms: An Atoms object giving the atom positions. cell: A Cell object giving the system box. pars: An optional dictionary giving the parameters to be sent to the driver for initialisation. Defaults to {}. reqid: An optional integer that identifies requests of the same type, e.g. the bead index Returns: A list giving the status of the request of the form {'atoms': Atoms object giving the atom positions, 'cell': Cell object giving the system box, 'pars': parameter string, 'result': holds the result as a list once the computation is done, 'status': a string labelling the status, 'id': the id of the request, usually the bead number, 'start': the starting time for the calculation, used to check for timeouts.}. """ par_str = " " if not pars is None: for k, v in pars.items(): par_str += k + " : " + str(v) + " , " else: par_str = " " # APPLY PBC -- this is useful for codes such as LAMMPS that don't do full PBC when computing distances pbcpos = depstrip(atoms.q).copy() if self.dopbc: cell.array_pbc(pbcpos) newreq = { "pos": pbcpos, "cell": cell, "pars": par_str, "result": None, "status": "Queued", "id": reqid, "start": -1 } self.requests.append(newreq) return newreq
def step(self, step=None): """ Does one simulation time step Attributes: ttime: The time taken in applying the thermostat steps. """ self.qtime = -time.time() info("\nMD STEP %d" % step, verbosity.debug) if step == 0: info(" @GEOP: Initializing L-BFGS", verbosity.debug) self.d += depstrip(self.forces.f) / np.sqrt( np.dot(self.forces.f.flatten(), self.forces.f.flatten())) self.old_x[:] = self.beads.q self.old_u[:] = self.forces.pot self.old_f[:] = self.forces.f if len(self.fixatoms) > 0: for dqb in self.old_f: dqb[self.fixatoms * 3] = 0.0 dqb[self.fixatoms * 3 + 1] = 0.0 dqb[self.fixatoms * 3 + 2] = 0.0 fdf0 = (self.old_u, -self.old_f) #d_x,new_d, new_qlist, new_glist = L_BFGS(self.old_x, # Note that the line above is not needed anymore because we update everything # within L_BFGS (and all other calls). L_BFGS(self.old_x, self.d, self.gm, self.qlist, self.glist, fdf0, self.big_step, self.ls_options["tolerance"] * self.tolerances["energy"], self.ls_options["iter"], self.corrections, self.scale, step) info(" Number of force calls: %d" % (self.gm.fcount)) self.gm.fcount = 0 #Update positions and forces self.beads.q = self.gm.dbeads.q self.forces.transfer_forces( self.gm.dforces) #This forces the update of the forces # Exit simulation step d_x_max = np.amax(np.absolute(np.subtract(self.beads.q, self.old_x))) self.exitstep(self.forces.pot, self.old_u, d_x_max)
def step(self, step=None): """ Does one simulation time step. Attributes: qtime: The time taken in updating the positions. """ self.qtime = -time.time() info("\nMD STEP %d" % step, verbosity.debug) if step == 0: info(" @GEOP: Initializing BFGS", verbosity.debug) self.d += depstrip(self.forces.f) / np.sqrt( np.dot(self.forces.f.flatten(), self.forces.f.flatten())) self.old_x[:] = self.beads.q self.old_u[:] = self.forces.pot self.old_f[:] = self.forces.f if len(self.fixatoms) > 0: for dqb in self.old_f: dqb[self.fixatoms * 3] = 0.0 dqb[self.fixatoms * 3 + 1] = 0.0 dqb[self.fixatoms * 3 + 2] = 0.0 fdf0 = (self.old_u, -self.old_f) # Do one iteration of BFGS # The invhessian and the directions are updated inside. BFGS(self.old_x, self.d, self.gm, fdf0, self.invhessian, self.big_step, self.ls_options["tolerance"] * self.tolerances["energy"], self.ls_options["iter"]) info(" Number of force calls: %d" % (self.gm.fcount)) self.gm.fcount = 0 #Update positions and forces self.beads.q = self.gm.dbeads.q self.forces.transfer_forces( self.gm.dforces) #This forces the update of the forces # Exit simulation step d_x_max = np.amax(np.absolute(np.subtract(self.beads.q, self.old_x))) self.exitstep(self.forces.pot, self.old_u, d_x_max)
def qcstep(self, alpha=1.0): """Velocity Verlet centroid position propagator.""" self.nm.qnm[0, :] += depstrip(self.nm.pnm)[0, :] / depstrip( self.beads.m3)[0] * self.dt / alpha
def step(self, step=None): """Does one simulation time step Attributes: ptime: The time taken in updating the velocities. qtime: The time taken in updating the positions. ttime: The time taken in applying the thermostat steps. """ self.ptime = 0.0 self.ttime = 0.0 self.qtime = -time.time() info("\nMD STEP %d" % step, verbosity.debug) if step == 0: gradf1 = dq1 = depstrip(self.forces.f) # Move direction for 1st conjugate gradient step dq1_unit = dq1 / np.sqrt(np.dot(gradf1.flatten(), gradf1.flatten())) info(" @GEOP: Determined SD direction", verbosity.debug) else: gradf0 = self.old_f dq0 = self.d gradf1 = depstrip(self.forces.f) beta = np.dot( (gradf1.flatten() - gradf0.flatten()), gradf1.flatten()) / ( np.dot(gradf0.flatten(), gradf0.flatten())) dq1 = gradf1 + max(0.0, beta) * dq0 dq1_unit = dq1 / np.sqrt(np.dot(dq1.flatten(), dq1.flatten())) info(" @GEOP: Determined CG direction", verbosity.debug) # Store force and direction for next CG step self.d[:] = dq1 self.old_f[:] = gradf1 if len(self.fixatoms) > 0: for dqb in dq1_unit: dqb[self.fixatoms * 3] = 0.0 dqb[self.fixatoms * 3 + 1] = 0.0 dqb[self.fixatoms * 3 + 2] = 0.0 self.lm.set_dir(depstrip(self.beads.q), dq1_unit) # Reuse initial value since we have energy and forces already u0, du0 = (self.forces.pot.copy(), np.dot(depstrip(self.forces.f.flatten()), dq1_unit.flatten())) # Do one CG iteration; return positions and energy min_brent(self.lm, fdf0=(u0, du0), x0=0.0, tol=self.ls_options["tolerance"] * self.tolerances["energy"], itmax=self.ls_options["iter"], init_step=self.ls_options["step"]) info(" Number of force calls: %d" % (self.lm.fcount)) self.lm.fcount = 0 #Update positions and forces self.beads.q = self.lm.dbeads.q self.forces.transfer_forces( self.lm.dforces) #This forces the update of the forces d_x = np.absolute(np.subtract(self.beads.q, self.lm.x0)) x = np.linalg.norm(d_x) # Automatically adapt the search step for the next iteration. # Relaxes better with very small step --> multiply by factor of 0.1 or 0.01 self.ls_options["step"] = 0.1 * x * self.ls_options["adaptive"] + ( 1 - self.ls_options["adaptive"]) * self.ls_options["step"] # Exit simulation step d_x_max = np.amax(np.absolute(d_x)) self.exitstep(self.forces.pot, u0, d_x_max)
def pstep(self): """Velocity Verlet momenta propagator.""" self.beads.p += depstrip(self.forces.f) * (self.dt * 0.5) # also adds the bias force self.beads.p += depstrip(self.bias.f) * (self.dt * 0.5)
def step(self, step=None): """Does one simulation time step.""" self.ptime = 0.0 self.ttime = 0.0 self.qtime = -time.time() info("\nMD STEP %d" % step, verbosity.debug) if self.mode == "bfgs": # BFGS Minimization # Initialize approximate Hessian inverse to the identity and direction # to the steepest descent direction if step == 0: # or np.sqrt(np.dot(self.bfgsm.d, self.bfgsm.d)) == 0.0: <-- this part for restarting at claimed minimum (optional) info(" @GEOP: Initializing BFGS", verbosity.debug) self.bfgsm.d = depstrip(self.forces.f) / np.sqrt(np.dot(self.forces.f.flatten(), self.forces.f.flatten())) self.bfgsm.xold = self.beads.q.copy() # Current energy and forces u0 = self.forces.pot.copy() du0 = - self.forces.f # Store previous forces self.cg_old_f[:] = self.forces.f # Do one iteration of BFGS, return new point, function value, # move direction, and current Hessian to use for next iteration self.beads.q, fx, self.bfgsm.d, self.invhessian = BFGS(self.beads.q, self.bfgsm.d, self.bfgsm, fdf0=(u0, du0), invhessian=self.invhessian, max_step=self.max_step, tol=self.ls_options["tolerance"], itmax=self.ls_options["iter"]) # x = current position - previous position; use for exit tolerance x = np.amax(np.absolute(np.subtract(self.beads.q, self.bfgsm.xold))) # Store old position self.bfgsm.xold[:] = self.beads.q info(" @GEOP: Updating bead positions", verbosity.debug) elif self.mode == "lbfgs": # L-BFGS Minimization # Initialize approximate Hessian inverse to the identity and direction # to the steepest descent direction # Initialize lists of previous positions and gradient if step == 0: # or np.sqrt(np.dot(self.bfgsm.d, self.bfgsm.d)) == 0.0: <-- this part for restarting at claimed minimum (optional) info(" @GEOP: Initializing L-BFGS", verbosity.debug) self.bfgsm.d = depstrip(self.forces.f) / np.sqrt(np.dot(self.forces.f.flatten(), self.forces.f.flatten())) self.bfgsm.xold = self.beads.q.copy() self.qlist = np.zeros((self.corrections, len(self.beads.q.flatten()))) self.glist = np.zeros((self.corrections, len(self.beads.q.flatten()))) # Current energy and force u0, du0 = (self.forces.pot.copy(), - self.forces.f) # Store previous forces self.cg_old_f[:] = self.forces.f.reshape(len(self.cg_old_f)) # Do one iteration of L-BFGS, return new point, function value, # move direction, and current Hessian to use for next iteration self.beads.q, fx, self.bfgsm.d, self.qlist, self.glist = L_BFGS(self.beads.q, self.bfgsm.d, self.bfgsm, self.qlist, self.glist, fdf0=(u0, du0), max_step=self.max_step, tol=self.ls_options["tolerance"], itmax=self.ls_options["iter"], m=self.corrections, k=step) info(" @GEOP: Updated position list", verbosity.debug) info(" @GEOP: Updated gradient list", verbosity.debug) # x = current position - old position. Used for convergence tolerance x = np.amax(np.absolute(np.subtract(self.beads.q, self.bfgsm.xold))) # Store old position self.bfgsm.xold[:] = self.beads.q info(" @GEOP: Updated bead positions", verbosity.debug) # Routine for steepest descent and conjugate gradient else: if (self.mode == "sd" or step == 0): # Steepest descent minimization # gradf1 = force at current atom position # dq1 = direction of steepest descent # dq1_unit = unit vector of dq1 gradf1 = dq1 = depstrip(self.forces.f) # Move direction for steepest descent and 1st conjugate gradient step dq1_unit = dq1 / np.sqrt(np.dot(gradf1.flatten(), gradf1.flatten())) info(" @GEOP: Determined SD direction", verbosity.debug) else: # Conjugate gradient, Polak-Ribiere # gradf1: force at current atom position # gradf0: force at previous atom position # dq1 = direction to move # dq0 = previous direction # dq1_unit = unit vector of dq1 gradf0 = self.cg_old_f dq0 = self.cg_old_d gradf1 = depstrip(self.forces.f) beta = np.dot((gradf1.flatten() - gradf0.flatten()), gradf1.flatten()) / (np.dot(gradf0.flatten(), gradf0.flatten())) dq1 = gradf1 + max(0.0, beta) * dq0 dq1_unit = dq1 / np.sqrt(np.dot(dq1.flatten(), dq1.flatten())) info(" @GEOP: Determined CG direction", verbosity.debug) # Store force and direction for next CG step self.cg_old_d[:] = dq1 self.cg_old_f[:] = gradf1 if len(self.fixatoms) > 0: for dqb in dq1_unit: dqb[self.fixatoms*3] = 0.0 dqb[self.fixatoms*3+1] = 0.0 dqb[self.fixatoms*3+2] = 0.0 self.lm.set_dir(depstrip(self.beads.q), dq1_unit) # Reuse initial value since we have energy and forces already u0, du0 = (self.forces.pot.copy(), np.dot(depstrip(self.forces.f.flatten()), dq1_unit.flatten())) # Do one SD/CG iteration; return positions and energy (x, fx) = min_brent(self.lm, fdf0=(u0, du0), x0=0.0, tol=self.ls_options["tolerance"], itmax=self.ls_options["iter"], init_step=self.ls_options["step"]) # Automatically adapt the search step for the next iteration. # Relaxes better with very small step --> multiply by factor of 0.1 or 0.01 self.ls_options["step"] = 0.1 * x * self.ls_options["adaptive"] + (1 - self.ls_options["adaptive"]) * self.ls_options["step"] self.beads.q += dq1_unit * x info(" @GEOP: Updated bead positions", verbosity.debug) self.qtime += time.time() # Determine conditions for converged relaxation if ((fx - u0) / self.beads.natoms <= self.tolerances["energy"])\ and ((np.amax(np.absolute(self.forces.f)) <= self.tolerances["force"]) or (np.sqrt(np.dot(self.forces.f.flatten() - self.cg_old_f.flatten(), self.forces.f.flatten() - self.cg_old_f.flatten())) == 0.0))\ and (x <= self.tolerances["position"]): softexit.trigger("Geometry optimization converged. Exiting simulation")
def main(prefix, temp): T = float(temp) fns_pos = sorted(glob.glob(prefix + ".pos*")) fns_for = sorted(glob.glob(prefix + ".for*")) fn_out_kin = prefix + ".kin.xyz" fn_out_kod = prefix + ".kod.xyz" # check that we found the same number of positions and forces files nbeads = len(fns_pos) if nbeads != len(fns_for): print fns_pos print fns_for raise ValueError( "Mismatch between number of input files for forces and positions.") # print some information print 'temperature = {:f} K'.format(T) print print 'number of beads = {:d}'.format(nbeads) print print 'positions and forces file names:' for fn_pos, fn_for in zip(fns_pos, fns_for): print '{:s} {:s}'.format(fn_pos, fn_for) print print 'output file names:' print fn_out_kin print fn_out_kod print temp = unit_to_internal("energy", "kelvin", T) # open input and output files ipos = [open(fn, "r") for fn in fns_pos] ifor = [open(fn, "r") for fn in fns_for] ikin = open(fn_out_kin, "w") ikod = open(fn_out_kod, "w") natoms = 0 ifr = 0 while True: # print progress if ifr % 100 == 0: print '\rProcessing frame {:d}'.format(ifr), sys.stdout.flush() # load one frame try: for i in range(nbeads): ret = read_file("xyz", ipos[i]) pos = ret["atoms"] ret = read_file("xyz", ifor[i]) force = ret["atoms"] if natoms == 0: natoms = pos.natoms beads = Beads(natoms, nbeads) forces = Beads(natoms, nbeads) kcv = np.zeros((natoms, 6), float) beads[i].q = pos.q forces[i].q = force.q except EOFError: # finished reading files break # calculate kinetic energies q = depstrip(beads.q) f = depstrip(forces.q) qc = depstrip(beads.qc) kcv[:] = 0 for j in range(nbeads): for i in range(natoms): kcv[i, 0] += (q[j, i * 3 + 0] - qc[i * 3 + 0]) * f[j, i * 3 + 0] kcv[i, 1] += (q[j, i * 3 + 1] - qc[i * 3 + 1]) * f[j, i * 3 + 1] kcv[i, 2] += (q[j, i * 3 + 2] - qc[i * 3 + 2]) * f[j, i * 3 + 2] kcv[i, 3] += ( q[j, i * 3 + 0] - qc[i * 3 + 0]) * f[j, i * 3 + 1] + ( q[j, i * 3 + 1] - qc[i * 3 + 1]) * f[j, i * 3 + 0] kcv[i, 4] += ( q[j, i * 3 + 0] - qc[i * 3 + 0]) * f[j, i * 3 + 2] + ( q[j, i * 3 + 2] - qc[i * 3 + 2]) * f[j, i * 3 + 0] kcv[i, 5] += ( q[j, i * 3 + 1] - qc[i * 3 + 1]) * f[j, i * 3 + 2] + ( q[j, i * 3 + 2] - qc[i * 3 + 2]) * f[j, i * 3 + 1] kcv *= -0.5 / nbeads kcv[:, 0:3] += 0.5 * Constants.kb * temp kcv[:, 3:6] *= 0.5 # write output ikin.write( "%d\n# Centroid-virial kinetic energy estimator [a.u.] - diagonal terms: xx yy zz\n" % natoms) ikod.write( "%d\n# Centroid-virial kinetic energy estimator [a.u.] - off-diag terms: xy xz yz\n" % natoms) for i in range(natoms): ikin.write("%8s %12.5e %12.5e %12.5e\n" % (pos.names[i], kcv[i, 0], kcv[i, 1], kcv[i, 2])) ikod.write("%8s %12.5e %12.5e %12.5e\n" % (pos.names[i], kcv[i, 3], kcv[i, 4], kcv[i, 5])) ifr += 1 print '\rProcessed {:d} frames.'.format(ifr) ikin.close() ikod.close()