diff --git a/.pylintdict b/.pylintdict index 7d314a76ee..c8dbe9da03 100644 --- a/.pylintdict +++ b/.pylintdict @@ -322,6 +322,7 @@ msq multiclass multinomial multiprocess +Nakanishi namelist nan narray @@ -337,6 +338,7 @@ neq nevals newtons's nfev +nft nk nlopt nmo @@ -521,6 +523,7 @@ timelimit timestamp tnc toctree +Todo toffoli tol tomo diff --git a/CHANGELOG.md b/CHANGELOG.md index 568f2e2fa2..fa6f556574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Changelog](http://keepachangelog.com/en/1.0.0/). Added ----- +- NFT optimizer, part of a project of Qiskit Camp Asia 2019 (#729) - Algorithm interface and result classes (#849) - Chemistry FCIDump file driver (#859) - Chemistry stack automatic Z2 symmetry reduction (#870) diff --git a/qiskit/aqua/components/optimizers/__init__.py b/qiskit/aqua/components/optimizers/__init__.py index 0e23d9839c..695861205b 100644 --- a/qiskit/aqua/components/optimizers/__init__.py +++ b/qiskit/aqua/components/optimizers/__init__.py @@ -51,6 +51,7 @@ COBYLA L_BFGS_B NELDER_MEAD + NFT P_BFGS POWELL SLSQP @@ -93,6 +94,7 @@ from .spsa import SPSA from .tnc import TNC from .aqgd import AQGD +from .nft import NFT from .nlopts.crs import CRS from .nlopts.direct_l import DIRECT_L from .nlopts.direct_l_rand import DIRECT_L_RAND @@ -106,6 +108,7 @@ 'COBYLA', 'L_BFGS_B', 'NELDER_MEAD', + 'NFT', 'P_BFGS', 'POWELL', 'SLSQP', diff --git a/qiskit/aqua/components/optimizers/nft.py b/qiskit/aqua/components/optimizers/nft.py new file mode 100644 index 0000000000..6068e12702 --- /dev/null +++ b/qiskit/aqua/components/optimizers/nft.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- + +# 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. + +"""Nakanishi-Fujii-Todo algorithm.""" + +from typing import Optional +import logging + +import numpy as np +from scipy.optimize import minimize +from scipy.optimize import OptimizeResult +from .optimizer import Optimizer + + +logger = logging.getLogger(__name__) + + +class NFT(Optimizer): + """ + Nakanishi-Fujii-Todo algorithm. + + See https://arxiv.org/abs/1903.12166 + """ + + _OPTIONS = ['maxiter', 'maxfev', 'disp', 'reset_interval'] + + # pylint: disable=unused-argument + def __init__(self, + maxiter: Optional[int] = None, + maxfev: int = 1024, + disp: bool = False, + reset_interval: int = 32) -> None: + """ + Built out using scipy framework, for details, please refer to + https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html. + + Args: + maxiter: Maximum number of iterations to perform. + maxfev: Maximum number of function evaluations to perform. + disp: disp + reset_interval: The minimum estimates directly once + in ``reset_interval`` times. + + Notes: + In this optimization method, the optimization function have to satisfy + three conditions written in [1]_. + + References: + .. [1] K. M. Nakanishi, K. Fujii, and S. Todo. 2019. + Sequential minimal optimization for quantum-classical hybrid algorithms. + arXiv preprint arXiv:1903.12166. + """ + super().__init__() + for k, v in locals().items(): + if k in self._OPTIONS: + self._options[k] = v + + def get_support_level(self): + """ return support level dictionary """ + return { + 'gradient': Optimizer.SupportLevel.ignored, + 'bounds': Optimizer.SupportLevel.ignored, + 'initial_point': Optimizer.SupportLevel.required + } + + def optimize(self, num_vars, objective_function, gradient_function=None, + variable_bounds=None, initial_point=None): + super().optimize(num_vars, objective_function, gradient_function, + variable_bounds, initial_point) + + res = minimize(objective_function, initial_point, + method=nakanishi_fujii_todo, options=self._options) + return res.x, res.fun, res.nfev + + +# pylint: disable=invalid-name +def nakanishi_fujii_todo(fun, x0, args=(), maxiter=None, maxfev=1024, + reset_interval=32, eps=1e-32, callback=None, **_): + """ + Find the global minimum of a function using the nakanishi_fujii_todo + algorithm [1]. + Args: + fun (callable): ``f(x, *args)`` + Function to be optimized. ``args`` can be passed as an optional item + in the dict ``minimizer_kwargs``. + This function must satisfy the three condition written in Ref. [1]. + x0 (ndarray): shape (n,) + Initial guess. Array of real elements of size (n,), + where 'n' is the number of independent variables. + args (tuple, optional): + Extra arguments passed to the objective function. + maxiter (int): + Maximum number of iterations to perform. + Default: None. + maxfev (int): + Maximum number of function evaluations to perform. + Default: 1024. + reset_interval (int): + The minimum estimates directly once in ``reset_interval`` times. + Default: 32. + eps (float): eps + **_ : additional options + callback (callable, optional): + Called after each iteration. + Returns: + OptimizeResult: + The optimization result represented as a ``OptimizeResult`` object. + Important attributes are: ``x`` the solution array. See + `OptimizeResult` for a description of other attributes. + Notes: + In this optimization method, the optimization function have to satisfy + three conditions written in [1]. + References: + .. [1] K. M. Nakanishi, K. Fujii, and S. Todo. 2019. + Sequential minimal optimization for quantum-classical hybrid algorithms. + arXiv preprint arXiv:1903.12166. + """ + + x0 = np.asarray(x0) + recycle_z0 = None + niter = 0 + funcalls = 0 + + while True: + + idx = niter % x0.size + + if reset_interval > 0: + if niter % reset_interval == 0: + recycle_z0 = None + + if recycle_z0 is None: + z0 = fun(np.copy(x0), *args) + funcalls += 1 + else: + z0 = recycle_z0 + + p = np.copy(x0) + p[idx] = x0[idx] + np.pi / 2 + z1 = fun(p, *args) + funcalls += 1 + + p = np.copy(x0) + p[idx] = x0[idx] - np.pi / 2 + z3 = fun(p, *args) + funcalls += 1 + + z2 = z1 + z3 - z0 + c = (z1 + z3) / 2 + a = np.sqrt((z0 - z2) ** 2 + (z1 - z3) ** 2) / 2 + b = np.arctan((z1 - z3) / ((z0 - z2) + eps * (z0 == z2))) + x0[idx] + b += 0.5 * np.pi + 0.5 * np.pi * np.sign((z0 - z2) + eps * (z0 == z2)) + + x0[idx] = b + recycle_z0 = c - a + + niter += 1 + + if callback is not None: + callback(np.copy(x0)) + + if maxfev is not None: + if funcalls >= maxfev: + break + + if maxiter is not None: + if niter >= maxiter: + break + + return OptimizeResult(fun=fun(np.copy(x0)), x=x0, nit=niter, nfev=funcalls, success=(niter > 1)) diff --git a/test/aqua/test_optimizer_nft.py b/test/aqua/test_optimizer_nft.py new file mode 100644 index 0000000000..d9ec5f439b --- /dev/null +++ b/test/aqua/test_optimizer_nft.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# 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 NFT optimizer """ + +from test.aqua import QiskitAquaTestCase +from qiskit import BasicAer + +from qiskit.aqua import QuantumInstance, aqua_globals +from qiskit.aqua.operators import WeightedPauliOperator +from qiskit.aqua.components.variational_forms import RY +from qiskit.aqua.components.optimizers import NFT +from qiskit.aqua.algorithms import VQE + + +class TestOptimizerNFT(QiskitAquaTestCase): + """ Test NFT optimizer using RY with VQE """ + + def setUp(self): + super().setUp() + self.seed = 50 + aqua_globals.random_seed = self.seed + 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 test_nft(self): + """ Test NFT optimizer by using it """ + + result = VQE(self.qubit_op, + RY(self.qubit_op.num_qubits), + NFT()).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.857275, places=6)