Source code for qiskit.algorithms.optimizers.nlopts.nloptimizer
# This code is part of Qiskit.
#
# (C) Copyright IBM 2018, 2020.
#
# 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.
"""Minimize using objective function"""
from __future__ import annotations
from collections.abc import Callable
from enum import Enum
from abc import abstractmethod
import logging
import numpy as np
from qiskit.utils import optionals as _optionals
from ..optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT
logger = logging.getLogger(__name__)
class NLoptOptimizerType(Enum):
"""NLopt Valid Optimizer"""
GN_CRS2_LM = 1
GN_DIRECT_L_RAND = 2
GN_DIRECT_L = 3
GN_ESCH = 4
GN_ISRES = 5
@_optionals.HAS_NLOPT.require_in_instance
class NLoptOptimizer(Optimizer):
"""
NLopt global optimizer base class
"""
_OPTIONS = ["max_evals"]
def __init__(self, max_evals: int = 1000) -> None: # pylint: disable=unused-argument
"""
Args:
max_evals: Maximum allowed number of function evaluations.
Raises:
MissingOptionalLibraryError: NLopt library not installed.
"""
import nlopt
super().__init__()
for k, v in list(locals().items()):
if k in self._OPTIONS:
self._options[k] = v
self._optimizer_names = {
NLoptOptimizerType.GN_CRS2_LM: nlopt.GN_CRS2_LM,
NLoptOptimizerType.GN_DIRECT_L_RAND: nlopt.GN_DIRECT_L_RAND,
NLoptOptimizerType.GN_DIRECT_L: nlopt.GN_DIRECT_L,
NLoptOptimizerType.GN_ESCH: nlopt.GN_ESCH,
NLoptOptimizerType.GN_ISRES: nlopt.GN_ISRES,
}
@abstractmethod
def get_nlopt_optimizer(self) -> NLoptOptimizerType:
"""return NLopt optimizer enum type"""
raise NotImplementedError
def get_support_level(self):
"""return support level dictionary"""
return {
"gradient": OptimizerSupportLevel.ignored,
"bounds": OptimizerSupportLevel.supported,
"initial_point": OptimizerSupportLevel.required,
}
@property
def settings(self):
return {"max_evals": self._options.get("max_evals", 1000)}
def minimize(
self,
fun: Callable[[POINT], float],
x0: POINT,
jac: Callable[[POINT], POINT] | None = None,
bounds: list[tuple[float, float]] | None = None,
) -> OptimizerResult:
import nlopt
x0 = np.asarray(x0)
if bounds is None:
bounds = [(None, None)] * x0.size
threshold = 3 * np.pi
low = [(l if l is not None else -threshold) for (l, u) in bounds]
high = [(u if u is not None else threshold) for (l, u) in bounds]
name = self._optimizer_names[self.get_nlopt_optimizer()]
opt = nlopt.opt(name, len(low))
logger.debug(opt.get_algorithm_name())
opt.set_lower_bounds(low)
opt.set_upper_bounds(high)
eval_count = 0
def wrap_objfunc_global(x, _grad):
nonlocal eval_count
eval_count += 1
return fun(x)
opt.set_min_objective(wrap_objfunc_global)
opt.set_maxeval(self._options.get("max_evals", 1000))
xopt = opt.optimize(x0)
minf = opt.last_optimum_value()
logger.debug("Global minimize found %s eval count %s", minf, eval_count)
result = OptimizerResult()
result.x = xopt
result.fun = minf
result.nfev = eval_count
return result