Source code for qiskit_optimization.algorithms.slsqp_optimizer

# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2020, 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""The SLSQP optimizer wrapped to be used within Qiskit optimization module."""
from __future__ import annotations
from typing import cast, Any

import numpy as np
from scipy.optimize import fmin_slsqp

from .multistart_optimizer import MultiStartOptimizer
from .optimization_algorithm import OptimizationResultStatus, OptimizationResult
from ..exceptions import QiskitOptimizationError
from ..problems import Variable
from ..problems.constraint import Constraint
from ..problems.quadratic_program import QuadraticProgram
from ..converters import MaximizeToMinimize


[docs] class SlsqpOptimizationResult(OptimizationResult): """ SLSQP optimization result, defines additional properties that may be returned by the optimizer. """ def __init__( # pylint: disable=too-many-positional-arguments self, x: list[float] | np.ndarray, fval: float, variables: list[Variable], status: OptimizationResultStatus, fx: np.ndarray | None = None, its: int | None = None, imode: int | None = None, smode: str | None = None, ) -> None: """ Constructs a result object with properties specific to SLSQP. Args: x: The solution of the problem fval: The value of the objective function of the solution variables: A list of variables defined in the problem fx: The value of the objective function being optimized, may be different from ``fval`` its: The number of iterations. imode: The exit mode from the optimizer (see the documentation of ``scipy.optimize.fmin_slsqp``). smode: Message describing the exit mode from the optimizer. status: the termination status of the optimization algorithm. """ super().__init__(x, fval, variables, status, None) self._fx = fx self._its = its self._imode = imode self._smode = smode # pylint:disable=invalid-name @property def fx(self) -> np.ndarray | None: """Returns the final value of the objective function being actually optimized.""" return self._fx @property def its(self) -> int | None: """Returns the number of iterations""" return self._its @property def imode(self) -> int | None: """Returns the exit mode from the optimizer.""" return self._imode @property def smode(self) -> str | None: """Returns message describing the exit mode from the optimizer.""" return self._smode
[docs] class SlsqpOptimizer(MultiStartOptimizer): """The SciPy SLSQP optimizer wrapped as a Qiskit :class:`OptimizationAlgorithm`. This class provides a wrapper for ``scipy.optimize.fmin_slsqp`` (https://docs.scipy.org/doc/scipy-0.13.0/reference/generated/scipy.optimize.fmin_slsqp.html) to be used within the optimization module. The arguments for ``fmin_slsqp`` are passed via the constructor. Examples: >>> from qiskit_optimization.problems import QuadraticProgram >>> from qiskit_optimization.algorithms import SlsqpOptimizer >>> problem = QuadraticProgram() >>> # specify problem here >>> x = problem.continuous_var(name="x") >>> y = problem.continuous_var(name="y") >>> problem.maximize(linear=[2, 0], quadratic=[[-1, 2], [0, -2]]) >>> optimizer = SlsqpOptimizer() >>> result = optimizer.solve(problem) """ # pylint: disable=redefined-builtin def __init__( # pylint: disable=too-many-positional-arguments self, iter: int = 100, acc: float = 1.0e-6, iprint: int = 0, trials: int = 1, clip: float = 100.0, full_output: bool = False, ) -> None: """Initializes the SlsqpOptimizer. This initializer takes the algorithmic parameters of SLSQP and stores them for later use of ``fmin_slsqp`` when :meth:`solve` is invoked. This optimizer can be applied to find a (local) optimum for problems consisting of only continuous variables. Args: iter: The maximum number of iterations. acc: Requested accuracy. iprint: The verbosity of fmin_slsqp : - iprint <= 0 : Silent operation - iprint == 1 : Print summary upon completion (default) - iprint >= 2 : Print status of each iterate and summary trials: The number of trials for multi-start method. The first trial is solved with the initial guess of zero. If more than one trial is specified then initial guesses are uniformly drawn from ``[lowerbound, upperbound]`` with potential clipping. clip: Clipping parameter for the initial guesses in the multi-start method. If a variable is unbounded then the lower bound and/or upper bound are replaced with the ``-clip`` or ``clip`` values correspondingly for the initial guesses. full_output: If ``False``, return only the minimizer of func (default). Otherwise, output final objective function and summary information. """ super().__init__(trials, clip) self._iter = iter self._acc = acc self._iprint = iprint self._trials = trials self._clip = clip self._full_output = full_output
[docs] def get_compatibility_msg(self, problem: QuadraticProgram) -> str: """Checks whether a given problem can be solved with this optimizer. Checks whether the given problem is compatible, i.e., whether the problem contains only continuous variables, and otherwise, returns a message explaining the incompatibility. Args: problem: The optimization problem to check compatibility. Returns: Returns a string describing the incompatibility. """ # check whether there are variables of type other than continuous if len(problem.variables) > problem.get_num_continuous_vars(): return "The SLSQP optimizer supports only continuous variables" return ""
[docs] def solve(self, problem: QuadraticProgram) -> OptimizationResult: """Tries to solves the given problem using the optimizer. Runs the optimizer to try to solve the optimization problem. Args: problem: The problem to be solved. Returns: The result of the optimizer applied to the problem. Raises: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ self._verify_compatibility(problem) # we deal with minimization in the optimizer, so turn the problem to minimization max2min = MaximizeToMinimize() original_problem = problem problem = self._convert(problem, max2min) # initialize constraints and bounds slsqp_bounds = [] slsqp_eq_constraints = [] slsqp_ineq_constraints = [] # add lower/upper bound constraints for variable in problem.variables: lowerbound = variable.lowerbound upperbound = variable.upperbound slsqp_bounds.append((lowerbound, upperbound)) # pylint: disable=no-member # add linear and quadratic constraints for constraint in cast(list[Constraint], problem.linear_constraints) + cast( list[Constraint], problem.quadratic_constraints ): rhs = constraint.rhs sense = constraint.sense if sense == Constraint.Sense.EQ: slsqp_eq_constraints += [lambda x, rhs=rhs, c=constraint: rhs - c.evaluate(x)] elif sense == Constraint.Sense.LE: slsqp_ineq_constraints += [lambda x, rhs=rhs, c=constraint: rhs - c.evaluate(x)] elif sense == Constraint.Sense.GE: slsqp_ineq_constraints += [lambda x, rhs=rhs, c=constraint: c.evaluate(x) - rhs] else: raise QiskitOptimizationError("Unsupported constraint type!") # actual minimization function to be called by multi_start_solve def _minimize(x_0: np.ndarray) -> tuple[np.ndarray, Any]: output = fmin_slsqp( problem.objective.evaluate, x_0, eqcons=slsqp_eq_constraints, ieqcons=slsqp_ineq_constraints, bounds=slsqp_bounds, fprime=problem.objective.evaluate_gradient, iter=self._iter, acc=self._acc, iprint=self._iprint, full_output=self._full_output, ) if self._full_output: x, *rest = output else: x, rest = output, None return np.asarray(x), rest # actual optimization goes here result = self.multi_start_solve(_minimize, problem) # eventually convert back minimization to maximization result = self._interpret( x=result.x, problem=original_problem, converters=max2min, raw_results=result.raw_results ) if self._full_output: return SlsqpOptimizationResult( x=result.x, fval=result.fval, variables=result.variables, status=self._get_feasibility_status(problem, result.x), fx=result.raw_results[0], its=result.raw_results[1], imode=result.raw_results[2], smode=result.raw_results[3], ) else: return SlsqpOptimizationResult( x=result.x, fval=result.fval, variables=result.variables, status=self._get_feasibility_status(problem, result.x), )