# 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 COBYLA 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_cobyla
from .multistart_optimizer import MultiStartOptimizer
from .optimization_algorithm import OptimizationResult
from ..exceptions import QiskitOptimizationError
from ..infinity import INFINITY
from ..problems.constraint import Constraint
from ..problems.quadratic_program import QuadraticProgram
from ..converters import MaximizeToMinimize
[docs]
class CobylaOptimizer(MultiStartOptimizer):
"""The SciPy COBYLA optimizer wrapped as a Qiskit :class:`OptimizationAlgorithm`.
This class provides a wrapper for ``scipy.optimize.fmin_cobyla``
(https://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.optimize.fmin_cobyla.html)
to be used within the optimization module.
The arguments for ``fmin_cobyla`` are passed via the constructor.
Examples:
>>> from qiskit_optimization.problems import QuadraticProgram
>>> from qiskit_optimization.algorithms import CobylaOptimizer
>>> problem = QuadraticProgram()
>>> # specify problem here
>>> optimizer = CobylaOptimizer()
>>> result = optimizer.solve(problem)
"""
def __init__( # pylint: disable=too-many-positional-arguments
self,
rhobeg: float = 1.0,
rhoend: float = 1e-4,
maxfun: int = 1000,
disp: int | None = None,
catol: float = 2e-4,
trials: int = 1,
clip: float = 100.0,
) -> None:
"""Initializes the CobylaOptimizer.
This initializer takes the algorithmic parameters of COBYLA and stores them for later use
of ``fmin_cobyla`` when :meth:`solve` is invoked.
This optimizer can be applied to find a (local) optimum for problems consisting of only
continuous variables.
Args:
rhobeg: Reasonable initial changes to the variables.
rhoend: Final accuracy in the optimization (not precisely guaranteed).
This is a lower bound on the size of the trust region.
disp: Controls the frequency of output; 0 implies no output.
Feasible values are {0, 1, 2, 3}.
maxfun: Maximum number of function evaluations.
catol: Absolute tolerance for constraint violations.
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.
"""
super().__init__(trials, clip)
self._rhobeg = rhobeg
self._rhoend = rhoend
self._maxfun = maxfun
self._disp = disp
self._catol = catol
[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 COBYLA 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 list
constraints = []
# add lower/upper bound constraints
for i, variable in enumerate(problem.variables):
lowerbound = variable.lowerbound
upperbound = variable.upperbound
if lowerbound > -INFINITY:
def lb_constraint(x, l_b=lowerbound, j=i):
return x[j] - l_b
constraints += [lb_constraint]
if upperbound < INFINITY:
def ub_constraint(x, u_b=upperbound, j=i):
return u_b - x[j]
constraints += [ub_constraint]
# 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:
constraints += [
lambda x, rhs=rhs, c=constraint: rhs - c.evaluate(x),
lambda x, rhs=rhs, c=constraint: c.evaluate(x) - rhs,
]
elif sense == Constraint.Sense.LE:
constraints += [lambda x, rhs=rhs, c=constraint: rhs - c.evaluate(x)]
elif sense == Constraint.Sense.GE:
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]:
x = fmin_cobyla(
problem.objective.evaluate,
x_0,
constraints,
rhobeg=self._rhobeg,
rhoend=self._rhoend,
maxfun=self._maxfun,
disp=self._disp,
catol=self._catol,
)
return x, None
result = self.multi_start_solve(_minimize, problem)
# eventually convert back minimization to maximization
return self._interpret(
x=result.x, problem=original_problem, converters=max2min, raw_results=result.raw_results
)