def test_bfgs_multiply_empty_lists(): size = 10 s_list = list() y_list = list() forces = np.ones(size) Hf = bfgs_multiply(s_list, y_list, forces) np.testing.assert_allclose(Hf, forces)
def optimize(self): prev_coords = self.coords[-1] prev_forces = self.forces[-1] step = bfgs_multiply(self.sigmas, self.grad_diffs, prev_forces) step = self.scale_by_max_step(step) new_coords = prev_coords + self.alpha * step coords_tmp = prev_coords.copy() forces_tmp = prev_forces.copy() self.geometry.coords = new_coords if self.is_cos and self.align: rot_vecs, rot_vec_lists, _ = fit_rigid( self.geometry, (prev_coords, prev_forces), vector_lists=(self.sigmas, self.grad_diffs)) prev_coords, prev_forces = rot_vecs # self.sigmas, self.grad_diffs = rot_vec_lists rot_sigmas, rot_grad_diffs = rot_vec_lists # if sigs: # import pdb; pdb.set_trace() np.testing.assert_allclose(np.linalg.norm(rot_sigmas), np.linalg.norm(self.sigmas)) np.testing.assert_allclose(np.linalg.norm(rot_grad_diffs), np.linalg.norm(self.grad_diffs)) self.sigmas = rot_sigmas self.grad_diffs = rot_grad_diffs new_forces = self.geometry.forces new_energy = self.geometry.energy skip = self.backtrack(new_forces, prev_forces) print("alpha", self.alpha) if skip: self.geometry.coords = coords_tmp return None sigma = new_coords - prev_coords self.sigmas.append(sigma) grad_diff = prev_forces - new_forces self.grad_diffs.append(grad_diff) # if len(self.sigmas) == self.keep_last: # import pdb; pdb.set_trace() self.sigmas = self.sigmas[-self.keep_last:] self.grad_diffs = self.grad_diffs[-self.keep_last:] # Because we add the step later on we restore the original # coordinates and set the appropriate energies and forces. self.geometry.coords = prev_coords self.geometry.forces = new_forces self.geometry.energy = new_energy self.forces.append(new_forces) self.energies.append(new_energy) return step
def optimize(self): forces = self.geometry.forces energy = self.geometry.energy self.forces.append(forces) self.energies.append(energy) norm = np.linalg.norm(forces) if not self.is_cos: self.log(f"Current energy={energy:.6f}") self.log(f"norm(forces)={norm:.6f}") # Steepest descent fallback step = forces # Check for preconditioner update if (self.precon_update and self.cur_cycle > 0 and self.cur_cycle % self.precon_update == 0): self.precon_getter = precon_getter(self.geometry, c_stab=self.c_stab) # Construct preconditoner if requested P = None if self.precon: P = self.precon_getter(self.geometry.coords) self.log("Calculated new preconditioner P") step = spsolve(P, forces) if self.cur_cycle > 0: self.grad_diffs.append(-forces - -self.forces[-2]) self.steps_.append(self.steps[-1]) step = bfgs_multiply(self.steps_, self.grad_diffs, forces, P=P) step_dir = step / np.linalg.norm(step) if self.line_search_cls is not None: kwargs = { "geometry": self.geometry, "p": step_dir, "f0": energy, "g0": -forces, "alpha_init": self.alpha_init, } line_search = self.line_search_cls(**kwargs) line_search_result = line_search.run() alpha = line_search_result.alpha step = alpha * step_dir else: max_element = np.abs(step).max() if max_element > self.max_step_element: step *= self.max_step_element / max_element return step
def optimize(self): if self.is_cos and self.align: rot_vecs, rot_vec_lists, _ = fit_rigid( self.geometry, vector_lists=(self.steps, self.forces, self.coord_diffs, self.grad_diffs)) rot_steps, rot_forces, rot_coord_diffs, rot_grad_diffs = rot_vec_lists self.steps = rot_steps self.forces = rot_forces self.coord_diffs = rot_coord_diffs self.grad_diffs = rot_grad_diffs forces = self.geometry.forces self.forces.append(forces) energy = self.geometry.energy self.energies.append(energy) norm = np.linalg.norm(forces) self.log(f"norm(forces)={norm:.6f}") if self.cur_cycle > 0: y = self.forces[-2] - forces s = self.steps[-1] if self.double_damp: s, y = double_damp(s, y, s_list=self.coord_diffs, y_list=self.grad_diffs) self.grad_diffs.append(y) self.coord_diffs.append(s) # Drop superfluous oldest vectors self.coord_diffs = self.coord_diffs[-self.keep_last:] self.grad_diffs = self.grad_diffs[-self.keep_last:] step = bfgs_multiply(self.coord_diffs, self.grad_diffs, forces, beta=self.beta, gamma_mult=self.gamma_mult, logger=self.logger) step = scale_by_max_step(step, self.max_step) return step
def double_damp(s, y, H=None, s_list=None, y_list=None, mu_1=0.2, mu_2=0.2, logger=None): """Double damped step 's' and gradient differences 'y'. H is the inverse Hessian! See [6]. Potentially updates s and y. y is only updated if mu_2 is not None. Parameters ---------- s : np.array, shape (N, ), floats Coordiante differences/step. y : np.array, shape (N, ), floats Gradient differences H : np.array, shape (N, N), floats, optional Inverse Hessian. s_list : list of nd.array, shape (K, N), optional List of K previous steps. If no H is supplied and prev_ys is given the matrix-vector product Hy will be calculated through the two-loop LBFGS-recursion. y_list : list of nd.array, shape (K, N), optional List of K previous gradient differences. See s_list. mu_1 : float, optional Parameter for 's' damping. mu_2 : float, optional Parameter for 'y' damping. logger : logging.Logger, optional Logger to be used. Returns ------- s : np.array, shape (N, ), floats Damped coordiante differences/step. y : np.array, shape (N, ), floats Damped gradient differences """ sy = s.dot(y) # Calculate Hy directly if H is not None: Hy = H.dot(y) # Calculate Hy via BFGS_multiply as in LBFGS else: Hy = bfgs_multiply(s_list, y_list, y, logger=logger) yHy = y.dot(Hy) theta_1 = 1 damped_s = "" if sy < mu_1 * yHy: theta_1 = (1 - mu_1) * yHy / (yHy - sy) s = theta_1 * s + (1 - theta_1) * Hy if theta_1 < 1.: damped_s = ", damped s" msg = f"damped BFGS\n\ttheta_1={theta_1:.4f} {damped_s}" # Double damping damped_y = "" if mu_2 is not None: sy = s.dot(y) ss = s.dot(s) theta_2 = 1 if sy < mu_2 * ss: theta_2 = (1 - mu_2) * ss / (ss - sy) y = theta_2 * y + (1 - theta_2) * s if theta_2 < 1.: damped_y = ", damped y" msg = "double " + msg + f"\n\ttheta_2={theta_2:.4f} {damped_y}" if logger is not None: logger.debug(msg.capitalize()) return s, y
def optimize(self): new_image_inds = self.geometry.new_image_inds string_size_changed = len(new_image_inds) > 0 if self.align and string_size_changed and self.is_cart_opt: procrustes(self.geometry) self.log("Aligned string.") forces = self.geometry.forces self.energies.append(self.geometry.energy) self.forces.append(forces) cur_size = self.geometry.string_size add_to_list = ( self.keep_last > 0 # Only add to s_list and y_list if we want to keep and self.cur_cycle > 0 # cycles and if we can actually calculate differences. and (not self. lbfgs_when_full # Add when LBFGS is allowed before fully grown. or self.lbfgs_when_full and self.geometry.fully_grown and not string_size_changed # Don't add when string has to be fully grown # but grew fully in this cycle. )) if add_to_list: inds = list(range(cur_size)) try: y = self.forces[-2] - forces s = self.coords[-1] - self.coords[-2] # Will be raised when the string grew in the previous cycle. except ValueError: cur_forces = np.delete(forces.reshape(cur_size, -1), new_image_inds, axis=0).flatten() y = self.forces[-2] - cur_forces cur_coords = np.delete(self.coords[-1].reshape(cur_size, -1), new_image_inds, axis=0).flatten() s = self.coords[-2] - cur_coords inds = np.delete(inds, new_image_inds) if self.double_damp: s, y = double_damp(s, y, s_list=self.s_list, y_list=self.y_list) self.s_list.append(s) self.y_list.append(y) self.inds.append(inds) # Drop oldest vectors self.s_list = self.s_list[-self.keep_last:] self.y_list = self.y_list[-self.keep_last:] self.inds = self.inds[-self.keep_last:] # Results in steepest descent step for empty s_list & y_list step = bfgs_multiply(self.s_list, self.y_list, forces, gamma_mult=self.gamma_mult, inds=self.inds, cur_size=cur_size, logger=self.logger) # When keep_last == 0 or LBFGS is not yet enabled then s_list and y_list will # be empty and step will be a simple SD step. We try to improve it via CG. if ((self.keep_last == 0 and self.cur_cycle > 0 and not string_size_changed) and (len(self.s_list) == 0 and len(self.y_list) == 0)): prev_forces = self.forces[-2] # Fletcher-Reeves beta = forces.dot(forces) / prev_forces.dot(prev_forces) # Polar-Ribiere # beta = forces.dot(forces - prev_forces) / prev_forces.dot(prev_forces) beta = min(beta, 1) step = forces + beta * self.steps[-1] self.log(f"Conjugate gradient correction, β={beta:.6f}") if self.scale_step == "global": step = scale_by_max_step(step, self.max_step) elif self.scale_step == "per_image": for image_step in step.reshape(len(self.geometry.images), -1): scale_by_max_step(image_step, self.max_step) else: raise Exception("Invalid scale_step={self.scale_step}!") return step