Source code for qiskit_optimization.algorithms.multistart_optimizer

# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2020, 2023.
#
# 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.

"""Defines an abstract class for multi start optimizers. A multi start optimizer is an optimizer
that may run minimization algorithm for the several time with different initial guesses to achieve
better results. This implementation is suitable for local optimizers."""

import logging
import time
from abc import ABC
from typing import Callable, Tuple, Any, Optional

import numpy as np
from scipy.stats import uniform

from ..problems.quadratic_program import QuadraticProgram
from ..infinity import INFINITY
from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult
from ..converters import MaximizeToMinimize

logger = logging.getLogger(__name__)


# we disable a warning: "Method 'a method' is abstract in class 'OptimizationAlgorithm' but
# is not overridden (abstract-method) since this class is not intended for instantiation
# pylint: disable=W0223
[docs]class MultiStartOptimizer(OptimizationAlgorithm, ABC): """ An abstract class that implements multi start optimization and should be sub-classed by other optimizers. """ def __init__(self, trials: int = 1, clip: float = 100.0) -> None: """ Constructs an instance of this optimizer. Args: 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. Raises: ValueError: if the variable trials has a value smaller than 1. """ super().__init__() if trials < 1: raise ValueError(f"Number of trials should be 1 or higher, but was {trials}") self._trials = trials self._clip = clip
[docs] def multi_start_solve( self, minimize: Callable[[np.ndarray], Tuple[np.ndarray, Any]], problem: QuadraticProgram, ) -> OptimizationResult: """Applies a multi start method given a local optimizer. Args: minimize: A callable object that minimizes the problem specified problem: A problem to solve Returns: The result of the multi start algorithm applied to the problem. """ fval_sol = INFINITY x_sol: Optional[np.ndarray] = None rest_sol: Optional[Tuple] = None # we deal with minimization in the optimizer, so turn the problem to minimization max2min = MaximizeToMinimize() original_problem = problem problem = self._convert(problem, max2min) # Implementation of multi-start optimizer for trial in range(self._trials): x_0 = np.zeros(problem.get_num_vars()) if trial > 0: for i, var in enumerate(problem.variables): lowerbound = var.lowerbound if var.lowerbound > -INFINITY else -self._clip upperbound = var.upperbound if var.upperbound < INFINITY else self._clip x_0[i] = uniform.rvs(lowerbound, (upperbound - lowerbound)) # run optimization t_0 = time.time() x, rest = minimize(x_0) logger.debug("minimize done in: %s seconds", str(time.time() - t_0)) fval = problem.objective.evaluate(x) # we minimize the objective if fval < fval_sol: fval_sol = fval x_sol = x rest_sol = rest # eventually convert back minimization to maximization return self._interpret( x_sol, problem=original_problem, converters=max2min, raw_results=rest_sol )
@property def trials(self) -> int: """Returns the number of trials for this optimizer. Returns: The number of trials. """ return self._trials @trials.setter def trials(self, trials: int) -> None: """Sets the number of trials. Args: trials: The number of trials to set. Raises: ValueError: if the variable trials have a value smaller than 1. """ if trials < 1: raise ValueError(f"Number of trials should be 1 or higher, but was {trials}") self._trials = trials @property def clip(self) -> float: """Returns the clip value for this optimizer. Returns: The clip value. """ return self._clip @clip.setter def clip(self, clip: float) -> None: """Sets the clip value. Args: clip: The clip value to set. """ self._clip = clip