Source code for modopt.core.approximate_hessians.bfgs_scipy

import numpy as np

from modopt import ApproximateHessian

from scipy.optimize import BFGS


[docs]class BFGSScipy(ApproximateHessian): """ Broyden-Fletcher-Goldfarb-Shanno (BFGS) Hessian update using SciPy's BFGS implementation. Parameters ---------- nx : int Number of optimization variables. store_hessian : bool, default=True Store the Hessian approximation. store_inverse : bool, default=False Store the inverse Hessian approximation. min_curvature : float, default=0.0 Curvature below which ``exception_strategy`` is triggered. Default is `1e-8` when ``exception_strategy = 'skip_update'`` and `0.2` when ``exception_strategy = 'damp_update'``. exception_strategy : {'skip_update', 'damp_update'}, default='damp_update' Strategy to proceed when the ``min_curvature`` condition is violated. - `'skip_update'`: Skip the update and keep the previous approximation. - `'damp_update'`: Interpolate between the computed BFGS update and the previous approximation. init_scale : {float, 'auto', np.ndarray}, default='auto' Initial scaling for the Hessian approximation. - `float`: Use ``init_scale*np.eye(nx)`` as the initial approximation. - `'auto'`: Use an automatic heuristic to compute the initial scaling factor. - `np.ndarray`: Use the provided array as the initial approximation. The array must be of shape `(nx, nx)`, symmetric, and positive definite. Note that this option only works for SciPy >= 1.14.0. Attributes ---------- B_k : np.ndarray Hessian approximation of shape `(nx, nx)`. Available only if ``store_hessian`` is ``True``. M_k : np.ndarray Inverse Hessian approximation of shape `(nx, nx)`. Available only if ``store_inverse`` is ``True``. """ def initialize(self): self.options.declare('store_hessian', default=True, types=bool) self.options.declare('store_inverse', default=False, types=bool) self.options.declare('exception_strategy', default='damp_update', values=['skip_update', 'damp_update']) self.options.declare('min_curvature', default=0.0, types=float) self.options.declare('init_scale', default='auto', types=(float, str, np.ndarray)) def setup(self, ): if self.options['min_curvature'] == 0.0: if self.options['exception_strategy'] == 'skip_update': self.options['min_curvature'] = 1e-8 else: self.options['min_curvature'] = 0.2 if self.options['store_hessian']: self.B = BFGS( exception_strategy=self.options['exception_strategy'], min_curvature=self.options['min_curvature'], init_scale=self.options['init_scale']) approx_type = 'hess' self.B.initialize(self.options['nx'], approx_type) if self.options['store_inverse']: self.M = BFGS( exception_strategy=self.options['exception_strategy'], min_curvature=self.options['min_curvature'], init_scale=self.options['init_scale']) approx_type = 'inv_hess' self.M.initialize(self.options['nx'], approx_type)
[docs] def update(self, d, w): """ Update the stored Hessian approximation `B_k` or its inverse `M_k` using the BFGS formula. The update is performed using the step ``d`` and the gradient difference ``w``. Parameters ---------- d : np.ndarray Step taken in the optimization space. w : np.ndarray Gradient difference along the step ``d``. """ if self.options['store_hessian']: self.B.update(d, w) self.B_k = self.B.get_matrix() if self.options['store_inverse']: self.M.update(d, w) self.M_k = self.M.get_matrix()