Source code for dials.algorithms.symmetry.cosym.engine

"""LBFGS refinement engine for cosym analysis."""

from __future__ import annotations

import logging

import scipy.optimize

import scitbx.lbfgs
from scitbx.array_family import flex

logger = logging.getLogger(__name__)


[docs] class lbfgs_with_curvs: """Minimise a target function using the LBFGS minimiser. Implementation of an LBFGS minimiser using curvature information, according to the interface defined by :mod:`scitbx.lbfgs`. """
[docs] def __init__(self, target, coords, use_curvatures=True, termination_params=None): """Initialise an lbfgs_with_curvs object. Args: target (dials.algorithms.target.Target): The target function to minimise. coords (np.ndarray): The starting coordinates for minimisation. use_curvatures (bool): Whether or not to use curvature information in the minimisation. Defaults to True. termination_params (scitbx.lbfgs.termination_parameters): Override the default termination parameters for the minimisation. """ self.target = target self.x = flex.double(coords) self.f = None self.g = None if use_curvatures: self.diag_mode = "always" else: self.diag_mode = None self.minimizer = scitbx.lbfgs.run( target_evaluator=self, termination_params=termination_params ) self.coords = self.x.as_numpy_array()
[docs] def compute_functional_gradients_diag(self): """Compute the functional, gradients and diagonal. Returns: tuple: A tuple of the functional, gradients and diagonal, where the diagonal is the reciprocal of the curvatures. """ f, g, curvs = self.compute_functional_gradients_and_curvatures() # Curvatures of zero will cause a crash, because their inverse is taken. if not curvs.all_gt(0.0): raise RuntimeError( "Zero curvature(s) encountered during minimization, minimization stopped." ) diags = 1.0 / curvs return f, g, diags
[docs] def compute_functional_gradients_and_curvatures(self): """Compute the functional, gradients and curvatures. Returns: tuple: A tuple of the functional, gradients and curvatures. """ x = self.x.as_numpy_array() self.f = self.target.compute_functional(x) self.g = self.target.compute_gradients(x) self.c = self.target.curvatures(x) return self.f, flex.double(self.g), flex.double(self.c)
[docs] def compute_functional_and_gradients(self): """Compute the functional and gradients. Returns: tuple: A tuple of the functional and gradients. """ x = self.x.as_numpy_array() self.f = self.target.compute_functional(x) self.g = self.target.compute_gradients(x) return self.f, flex.double(self.g)
[docs] def callback_after_step(self, minimizer): """Log progress after each successful step of the minimisation.""" logger.debug("minimization step: f, iter, nfun:") logger.debug(f"{self.f} {minimizer.iter()} {minimizer.nfun()}")
[docs] def minimize_scitbx_lbfgs( target, coords, use_curvatures=True, max_iterations=100, max_calls=None ): termination_params = scitbx.lbfgs.termination_parameters( max_iterations=max_iterations, max_calls=max_calls, traditional_convergence_test=True, traditional_convergence_test_eps=0.001, drop_convergence_test_n_test_points=5, drop_convergence_test_max_drop_eps=1e-7, drop_convergence_test_iteration_coefficient=2, ) result = lbfgs_with_curvs( target, coords, use_curvatures=use_curvatures, termination_params=termination_params, ) return scipy.optimize.OptimizeResult( fun=result.f, jac=result.g, x=result.coords, nfev=result.minimizer.nfun() )
[docs] def minimize_scipy( target, coords, method="L-BFGS-B", max_iterations=None, max_calls=None ): """Thin wrapper around scipy.optimize.minimize. Args: target (dials.algorithms.target.Target): The target function to minimise. coords (np.array): The starting coordinates for minimisation. """ options = {} if max_iterations: options.update(maxiter=max_iterations) if max_calls: options.update(maxfun=max_calls) return scipy.optimize.minimize( fun=target.compute_functional, x0=coords, jac=target.compute_gradients, method=method, options=options, )