diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b44dfff167..98794109f7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -155,7 +155,9 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/heads/stable') }} - uses: ./.github/actions/install-aqua - name: Install Dependencies - run: pip install "cvxpy>1.0.0" + run: | + pip install "cvxpy>1.0.0" + pip install scikit-quant shell: bash - name: Install cplex run: pip install cplex diff --git a/.pylintdict b/.pylintdict index 372f464a02..b529e3b844 100644 --- a/.pylintdict +++ b/.pylintdict @@ -51,6 +51,7 @@ bitstring bitstrings bksf bmod +bobyqa bohr bool boolean @@ -275,6 +276,7 @@ ij ijkl ik imag +imfil imode implicant implicants @@ -365,11 +367,13 @@ matlab matmul maxcut maxdepth +maxfail maxfev maxfun maxima maxiter maxiters +maxmp McArdle mccluskey mct @@ -536,6 +540,7 @@ quadratically quadraticconstraint quadraticprogram quandl +quant quantile quantized quantumcircuit @@ -606,8 +611,10 @@ sigmoid simonetto sj sklearn +skquant slsqp smode +snobfit spsa sqrt srange diff --git a/qiskit/aqua/components/optimizers/__init__.py b/qiskit/aqua/components/optimizers/__init__.py index 57c000288d..1c7e1afe51 100644 --- a/qiskit/aqua/components/optimizers/__init__.py +++ b/qiskit/aqua/components/optimizers/__init__.py @@ -58,6 +58,21 @@ SPSA TNC +Qiskit Aqua also provides the following optimizers, which are built-out using the optimizers from +the `scikit-quant` package. The `scikit-quant` package is not installed by default but must be +explicitly installed, if desired, by the user - the optimizers therein are provided under various +licenses so it has been made an optional install for the end user to choose whether to do so or +not. To install the `scikit-quant` dependent package you can use +`pip install qiskit-aqua[skquant]`. + +.. autosummary:: + :toctree: ../stubs/ + :nosignatures: + + BOBYQA + IMFIL + SNOBFIT + Global Optimizers ================= The global optimizers here all use NLopt for their core function and can only be @@ -101,6 +116,9 @@ from .nlopts.direct_l_rand import DIRECT_L_RAND from .nlopts.esch import ESCH from .nlopts.isres import ISRES +from .snobfit import SNOBFIT +from .bobyqa import BOBYQA +from .imfil import IMFIL __all__ = ['Optimizer', 'OptimizerSupportLevel', @@ -117,4 +135,5 @@ 'SLSQP', 'SPSA', 'TNC', - 'CRS', 'DIRECT_L', 'DIRECT_L_RAND', 'ESCH', 'ISRES'] + 'CRS', 'DIRECT_L', 'DIRECT_L_RAND', 'ESCH', 'ISRES', + 'SNOBFIT', 'BOBYQA', 'IMFIL'] diff --git a/qiskit/aqua/components/optimizers/bobyqa.py b/qiskit/aqua/components/optimizers/bobyqa.py new file mode 100644 index 0000000000..2b900a3041 --- /dev/null +++ b/qiskit/aqua/components/optimizers/bobyqa.py @@ -0,0 +1,76 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019, 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. + +"""Bound Optimization BY Quadratic Approximation (BOBYQA) optimizer.""" + + +import logging +import numpy as np +from qiskit.aqua import MissingOptionalLibraryError +from .optimizer import Optimizer, OptimizerSupportLevel + +logger = logging.getLogger(__name__) + +try: + import skquant.opt as skq + _HAS_SKQUANT = True +except ImportError: + _HAS_SKQUANT = False + + +class BOBYQA(Optimizer): + """ Bound Optimization BY Quadratic Approximation algorithm. + + BOBYQA finds local solutions to nonlinear, non-convex minimization problems with optional + bound constraints, without requirement of derivatives of the objective function. + + Uses skquant.opt installed with pip install scikit-quant. + For further detail, please refer to + https://github.com/scikit-quant/scikit-quant and https://qat4chem.lbl.gov/software. + """ + + # pylint: disable=unused-argument + def __init__(self, + maxiter: int = 1000, + ) -> None: + """ + Args: + maxiter: Maximum number of function evaluations. + + Raises: + MissingOptionalLibraryError: scikit-quant not installed + """ + if not _HAS_SKQUANT: + raise MissingOptionalLibraryError( + libname='scikit-quant', + name='BOBYQA', + pip_install='pip install qiskit-aqua[skquant]') + super().__init__() + self._maxiter = maxiter + + def get_support_level(self): + """ Returns support level dictionary. """ + return { + 'gradient': OptimizerSupportLevel.ignored, + 'bounds': OptimizerSupportLevel.required, + 'initial_point': OptimizerSupportLevel.required + } + + def optimize(self, num_vars, objective_function, gradient_function=None, + variable_bounds=None, initial_point=None): + """ Runs the optimization. """ + super().optimize(num_vars, objective_function, gradient_function, + variable_bounds, initial_point) + res, history = skq.minimize(objective_function, np.array(initial_point), + bounds=np.array(variable_bounds), budget=self._maxiter, + method="bobyqa") + return res.optpar, res.optval, len(history) diff --git a/qiskit/aqua/components/optimizers/imfil.py b/qiskit/aqua/components/optimizers/imfil.py new file mode 100644 index 0000000000..e996be9096 --- /dev/null +++ b/qiskit/aqua/components/optimizers/imfil.py @@ -0,0 +1,76 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019, 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. + +"""IMplicit FILtering (IMFIL) optimizer.""" + +import logging +from qiskit.aqua import MissingOptionalLibraryError +from .optimizer import Optimizer, OptimizerSupportLevel + +logger = logging.getLogger(__name__) + +try: + import skquant.opt as skq + _HAS_SKQUANT = True +except ImportError: + _HAS_SKQUANT = False + + +class IMFIL(Optimizer): + """IMplicit FILtering algorithm. + + Implicit filtering is a way to solve bound-constrained optimization problems for + which derivatives are not available. In comparison to methods that use interpolation to + reconstruct the function and its higher derivatives, implicit filtering builds upon + coordinate search followed by interpolation to get an approximate gradient. + + Uses skquant.opt installed with pip install scikit-quant. + For further detail, please refer to + https://github.com/scikit-quant/scikit-quant and https://qat4chem.lbl.gov/software. + """ + + # pylint: disable=unused-argument + def __init__(self, + maxiter: int = 1000, + ) -> None: + """ + Args: + maxiter: Maximum number of function evaluations. + + Raises: + MissingOptionalLibraryError: scikit-quant not installed + """ + if not _HAS_SKQUANT: + raise MissingOptionalLibraryError( + libname='scikit-quant', + name='IMFIL', + pip_install='pip install qiskit-aqua[skquant]') + super().__init__() + self._maxiter = maxiter + + def get_support_level(self): + """ Returns support level dictionary. """ + return { + 'gradient': OptimizerSupportLevel.ignored, + 'bounds': OptimizerSupportLevel.required, + 'initial_point': OptimizerSupportLevel.required + } + + def optimize(self, num_vars, objective_function, gradient_function=None, variable_bounds=None, + initial_point=None): + """ Runs the optimization. """ + super().optimize(num_vars, objective_function, gradient_function, variable_bounds, + initial_point) + res, history = skq.minimize(func=objective_function, x0=initial_point, + bounds=variable_bounds, budget=self._maxiter, + method="imfil") + return res.optpar, res.optval, len(history) diff --git a/qiskit/aqua/components/optimizers/snobfit.py b/qiskit/aqua/components/optimizers/snobfit.py new file mode 100644 index 0000000000..94296c7130 --- /dev/null +++ b/qiskit/aqua/components/optimizers/snobfit.py @@ -0,0 +1,109 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019, 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. + +"""Stable Noisy Optimization by Branch and FIT algorithm (SNOBFIT) optimizer.""" + +import logging +import numpy as np +from qiskit.aqua import MissingOptionalLibraryError +from .optimizer import Optimizer, OptimizerSupportLevel + +logger = logging.getLogger(__name__) + +try: + import skquant.opt as skq + _HAS_SKQUANT = True +except ImportError: + _HAS_SKQUANT = False + +try: + from SQSnobFit import optset + _HAS_SKSNOBFIT = True +except ImportError: + _HAS_SKSNOBFIT = False + + +class SNOBFIT(Optimizer): + """Stable Noisy Optimization by Branch and FIT algorithm. + + SnobFit is used for the optimization of derivative-free, noisy objective functions providing + robust and fast solutions of problems with continuous variables varying within bound. + + Uses skquant.opt installed with pip install scikit-quant. + For further detail, please refer to + https://github.com/scikit-quant/scikit-quant and https://qat4chem.lbl.gov/software. + """ + + # pylint: disable=unused-argument + def __init__(self, + maxiter: int = 1000, + maxfail: int = 10, + maxmp: int = None, + verbose: bool = False, + ) -> None: + """ + Args: + maxiter: Maximum number of function evaluations. + maxmp: Maximum number of model points requested for the local fit. + Default = 2 * number of parameters + 6 set to this value when None. + maxfail: Maximum number of failures to improve the solution. Stops the algorithm + after maxfail is reached. + verbose: Provide verbose (debugging) output. + + Raises: + MissingOptionalLibraryError: scikit-quant or SQSnobFit not installed + """ + if not _HAS_SKQUANT: + raise MissingOptionalLibraryError( + libname='scikit-quant', + name='SNOBFIT', + pip_install='pip install qiskit-aqua[skquant]') + if not _HAS_SKSNOBFIT: + raise MissingOptionalLibraryError( + libname='SQSnobFit', + name='SNOBFIT', + pip_install='pip install SQSnobFit') + super().__init__() + self._maxiter = maxiter + self._maxfail = maxfail + self._maxmp = maxmp + self._verbose = verbose + + def get_support_level(self): + """ Returns support level dictionary. """ + return { + 'gradient': OptimizerSupportLevel.ignored, + 'bounds': OptimizerSupportLevel.required, + 'initial_point': OptimizerSupportLevel.required + } + + def optimize(self, num_vars, objective_function, gradient_function=None, + variable_bounds=None, initial_point=None): + """ Runs the optimization. """ + super().optimize(num_vars, objective_function, gradient_function, + variable_bounds, initial_point) + snobfit_settings = { + 'maxmp': self._maxmp, + 'maxfail': self._maxfail, + 'verbose': self._verbose, + } + options = optset(optin=snobfit_settings) + # counters the error when initial point is outside the acceptable bounds + for idx, theta in enumerate(initial_point): + if abs(theta) > variable_bounds[idx][0]: + initial_point[idx] = initial_point[idx] % variable_bounds[idx][0] + elif abs(theta) > variable_bounds[idx][1]: + initial_point[idx] = initial_point[idx] % variable_bounds[idx][1] + res, history = skq.minimize(objective_function, np.array(initial_point, dtype=float), + bounds=variable_bounds, budget=self._maxiter, + method="snobfit", options=options) + return res.optpar, res.optval, len(history) diff --git a/requirements-dev.txt b/requirements-dev.txt index f73e1ea923..051f6a0b67 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -17,4 +17,4 @@ qiskit-aer qiskit-ibmq-provider mypy>=0.780 mypy-extensions>=0.4.3 -networkx>=2.2 +networkx>=2.2 \ No newline at end of file diff --git a/setup.py b/setup.py index ca85bed627..492cb64367 100644 --- a/setup.py +++ b/setup.py @@ -85,6 +85,7 @@ 'cplex': ["cplex; python_version >= '3.6' and python_version < '3.8'"], 'cvx': ['cvxpy>1.0.0,!=1.1.0,!=1.1.1,!=1.1.2'], 'pyscf': ["pyscf; sys_platform != 'win32'"], + 'skquant': ["scikit-quant"], }, zip_safe=False ) diff --git a/test/aqua/test_optimizers_scikitquant.py b/test/aqua/test_optimizers_scikitquant.py new file mode 100644 index 0000000000..110570fb72 --- /dev/null +++ b/test/aqua/test_optimizers_scikitquant.py @@ -0,0 +1,79 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. + +""" Test of scikit-quant optimizers. """ + +import unittest +from test.aqua import QiskitAquaTestCase +from qiskit import BasicAer +from qiskit.circuit.library import RealAmplitudes +from qiskit.aqua import QuantumInstance, MissingOptionalLibraryError +from qiskit.aqua.operators import WeightedPauliOperator +from qiskit.aqua.algorithms import VQE +from qiskit.aqua import aqua_globals +from qiskit.aqua.components.optimizers import BOBYQA, SNOBFIT, IMFIL + + +class TestOptimizers(QiskitAquaTestCase): + """ Test scikit-quant optimizers. """ + + def setUp(self): + """ Set the problem. """ + super().setUp() + aqua_globals.random_seed = 50 + pauli_dict = { + 'paulis': [{"coeff": {"imag": 0.0, "real": -1.052373245772859}, "label": "II"}, + {"coeff": {"imag": 0.0, "real": 0.39793742484318045}, "label": "IZ"}, + {"coeff": {"imag": 0.0, "real": -0.39793742484318045}, "label": "ZI"}, + {"coeff": {"imag": 0.0, "real": -0.01128010425623538}, "label": "ZZ"}, + {"coeff": {"imag": 0.0, "real": 0.18093119978423156}, "label": "XX"} + ] + } + self.qubit_op = WeightedPauliOperator.from_dict(pauli_dict) + + def _optimize(self, optimizer): + """ launch vqe """ + result = VQE(self.qubit_op, + RealAmplitudes(), + optimizer).run( + QuantumInstance(BasicAer.get_backend('statevector_simulator'), + seed_simulator=aqua_globals.random_seed, + seed_transpiler=aqua_globals.random_seed)) + self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=1) + + def test_bobyqa(self): + """ BOBYQA optimizer test. """ + try: + optimizer = BOBYQA(maxiter=150) + self._optimize(optimizer) + except MissingOptionalLibraryError as ex: + self.skipTest(str(ex)) + + def test_snobfit(self): + """ SNOBFIT optimizer test. """ + try: + optimizer = SNOBFIT(maxiter=100, maxfail=100, maxmp=20) + self._optimize(optimizer) + except MissingOptionalLibraryError as ex: + self.skipTest(str(ex)) + + def test_imfil(self): + """ IMFIL test. """ + try: + optimizer = IMFIL(maxiter=100) + self._optimize(optimizer) + except MissingOptionalLibraryError as ex: + self.skipTest(str(ex)) + + +if __name__ == '__main__': + unittest.main()