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)
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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
Example #5
0
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