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) print self.d self.d += dstrip(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 # Reduce the dimensionality masked_old_x = self.old_x[:, self.gm.fixatoms_mask] masked_d = self.d[:, self.gm.fixatoms_mask] # self.gm is reduced inside its __init__() and __call__() functions masked_qlist = self.qlist[:, self.gm.fixatoms_mask] masked_glist = self.glist[:, self.gm.fixatoms_mask] fdf0 = (self.old_u, -self.old_f[:, self.gm.fixatoms_mask]) # We update everything within L_BFGS (and all other calls). L_BFGS(masked_old_x, masked_d, self.gm, masked_qlist, masked_glist, fdf0, self.big_step, self.ls_options["tolerance"] * self.tolerances["energy"], self.ls_options["iter"], self.corrections, self.scale, step) # Restore the dimensionality self.d[:, self.gm.fixatoms_mask] = masked_d self.qlist[:, self.gm.fixatoms_mask] = masked_qlist self.glist[:, self.gm.fixatoms_mask] = masked_glist else: fdf0 = (self.old_u, -self.old_f) # 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: 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.""" 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 step(self, step=None): """Does one simulation time step.""" info("\nMD STEP %d" % step, verbosity.debug) # Fetch spring constants self.nebbfgsm.kappa = self.spring["kappa"] self.neblm.kappa = self.spring["kappa"] self.ptime = self.ttime = 0 self.qtime = -time.time() if self.mode == "lbfgs": # L-BFGS Minimization # Initialize 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 info(" @GEOP: Initializing L-BFGS", verbosity.debug) fx, nebgrad = self.nebbfgsm(self.beads.q) # Set direction to direction of NEB forces self.nebbfgsm.d = -nebgrad self.nebbfgsm.xold = self.beads.q.copy() # Initialize lists of previous positions and gradients self.qlist = np.zeros( (self.corrections, len(self.beads.q.flatten()))) self.glist = np.zeros( (self.corrections, len(self.beads.q.flatten()))) else: fx, nebgrad = self.nebbfgsm(self.beads.q) # Intial gradient and gradient modulus u0, du0 = (fx, nebgrad) # Store old force self.old_f[:] = -nebgrad # Do one iteration of L-BFGS and return positions, gradient modulus, # direction, list of positions, list of gradients # self.beads.q, fx, self.nebbfgsm.d, self.qlist, self.glist = L_BFGS(self.beads.q, L_BFGS(self.beads.q, self.nebbfgsm.d, self.nebbfgsm, self.qlist, self.glist, fdf0=(u0, du0), big_step=self.big_step, tol=self.ls_options["tolerance"], itmax=self.ls_options["iter"], m=self.corrections, scale=self.scale, k=step) info(" @GEOP: Updated position list", verbosity.debug) info(" @GEOP: Updated gradient list", verbosity.debug) # x = current position - previous position. Use to determine converged minimization x = np.amax( np.absolute(np.subtract(self.beads.q, self.nebbfgsm.xold))) # Store old positions self.nebbfgsm.xold[:] = self.beads.q self.beads.q = self.nebbfgsm.dbeads.q self.forces.transfer_forces(self.nebbfgsm.dforces) info(" @GEOP: Updated bead positions", verbosity.debug) # Routine for steepest descent and conjugate gradient # TODO: CURRENTLY DOES NOT WORK. MUST BE ELIMINATED OR DEBUGGED 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 nebgrad = self.neblm(self.beads.q)[0] gradf1 = dq1 = -nebgrad # 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.old_f dq0 = self.old_d nebgrad = self.neblm(self.beads.q)[0] gradf1 = -nebgrad 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.old_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.neblm.set_dir(dstrip(self.beads.q), dq1_unit) # Reuse initial value since we have energy and forces already u0 = np.dot(-nebgrad.flatten(), dq1_unit.flatten()) u0 = np.sqrt(np.dot(u0, u0)) (x, fx) = min_brent_neb(self.neblm, fdf0=u0, 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.old_f.flatten(), self.forces.f.flatten() - self.old_f.flatten())) == 0.0))\ and (x <= self.tolerances["position"]): softexit.trigger( "Geometry optimization converged. Exiting simulation") else: info( " @GEOP: Not converged, deltaEnergy = %.8f, tol = %.8f" % ((fx - u0 / self.beads.natoms), self.tolerances["energy"]), verbosity.debug) info( " @GEOP: Not converged, force = %.8f, tol = %f" % (np.amax( np.absolute(self.forces.f)), self.tolerances["force"]), verbosity.debug) info( " @GEOP: Not converged, deltaForce = %.8f, tol = 0.00000000" % (np.sqrt( np.dot(self.forces.f.flatten() - self.old_f.flatten(), self.forces.f.flatten() - self.old_f.flatten()))), verbosity.debug) info( " @GEOP: Not converged, deltaX = %.8f, tol = %.8f" % (x, self.tolerances["position"]), verbosity.debug)