Source code for pyRadPlan.optimization.solvers._scipy_solver
"""SciPy solver Class."""
import logging
from typing import Callable, Union
import array_api_compat
import numpy as np
from ...core.xp_utils.typing import Array
from scipy.optimize import minimize, Bounds
from ._base_solvers import NonLinearOptimizer
from ...core import xp_utils
logger = logging.getLogger(__name__)
[docs]
class OptimizerSciPy(NonLinearOptimizer):
"""
SciPy solver configuration class.
Attributes
----------
options : dict
Options for the solver
method : Union[str, Callable]
The solver method
"""
name = "SciPy minimize"
short_name = "scipy"
gpu_compatible = False
allow_keyboard_cancel = True
options: dict[str]
method: Union[str, Callable]
def __init__(self):
self.options = {
"disp": True,
"ftol": 1e-5,
"gtol": 1e-5,
}
self.method = "L-BFGS-B"
super().__init__()
def _callback(self, xk: np.ndarray):
if self._keyboard_listener.stop_event.is_set():
raise StopIteration("Optimization cancelled by user")
def _solve_problem(self, x0: Array) -> tuple[Array, dict]:
"""
Solve the problem.
Parameters
----------
x0 : np.ndarray
Initial guess for the decision variables.
Returns
-------
result : dict
"""
self.options.update({"maxiter": self.max_iter})
if isinstance(x0, list):
x0 = np.asarray(x0)
xp = array_api_compat.array_namespace(x0)
x0 = xp_utils.to_numpy(x0)
bounds = [xp_utils.to_numpy(xp.asarray(b)) for b in self.bounds]
bounds = Bounds(lb=bounds[0], ub=bounds[1])
device = self.device
def scipy_objective(x: Array):
return xp_utils.to_numpy(self.objective(xp_utils.from_numpy(xp, x, device=device)))
def scipy_gradient(x: Array):
return xp_utils.to_numpy(self.gradient(xp_utils.from_numpy(xp, x, device=device)))
# Initialize the SciPy solution function and its arguments
result = minimize(
x0=x0,
fun=scipy_objective,
method=self.method,
jac=scipy_gradient,
# constraints=self.constraints,
# hess=self.hessian,
tol=self.abs_obj_tol,
bounds=bounds,
callback=self._callback,
options=self.options,
)
return xp_utils.from_numpy(xp, result["x"]), result