diff --git a/qiskit_optimization/runtime/qaoa_program.py b/qiskit_optimization/runtime/qaoa_program.py index 65f9750fe..d97dd429e 100644 --- a/qiskit_optimization/runtime/qaoa_program.py +++ b/qiskit_optimization/runtime/qaoa_program.py @@ -18,6 +18,7 @@ from qiskit import QuantumCircuit from qiskit.algorithms import MinimumEigensolverResult +from qiskit.algorithms.optimizers import Optimizer from qiskit.circuit.library import QAOAAnsatz from qiskit.opflow import OperatorBase from qiskit.providers import Provider @@ -32,7 +33,7 @@ class QAOAProgram(VQEProgram): def __init__( self, - optimizer: Optional[Dict[str, Any]] = None, + optimizer: Optional[Union[Optimizer, Dict[str, Any]]] = None, reps: int = 1, initial_state: Optional[QuantumCircuit] = None, mixer: Union[QuantumCircuit, OperatorBase] = None, @@ -46,11 +47,11 @@ def __init__( ) -> None: """ Args: - optimizer: A dictionary specifying a classical optimizer. - Currently only SPSA and QN-SPSA are supported. Per default, SPSA is used. - The dictionary must contain a key ``name`` for the name of the optimizer and may - contain additional keys for the settings. - E.g. ``{'name': 'SPSA', 'maxiter': 100}``. + optimizer: An optimizer or dictionary specifying a classical optimizer. + If a dictionary, only SPSA and QN-SPSA are supported. The dictionary must contain a + key ``name`` for the name of the optimizer and may contain additional keys for the + settings. E.g. ``{'name': 'SPSA', 'maxiter': 100}``. + Per default, SPSA is used. reps: the integer parameter :math:`p` as specified in https://arxiv.org/abs/1411.4028, Has a minimum valid value of 1. initial_state: An optional initial state to prepend the QAOA circuit with diff --git a/qiskit_optimization/runtime/vqe_program.py b/qiskit_optimization/runtime/vqe_program.py index 0fecd544f..2375dfb54 100644 --- a/qiskit_optimization/runtime/vqe_program.py +++ b/qiskit_optimization/runtime/vqe_program.py @@ -13,7 +13,7 @@ """The Qiskit Optimization VQE Quantum Program.""" -from typing import List, Callable, Optional, Any, Dict +from typing import List, Callable, Optional, Any, Dict, Union import numpy as np from qiskit import QuantumCircuit @@ -21,6 +21,7 @@ from qiskit.providers import Provider from qiskit.providers.backend import Backend from qiskit.algorithms import MinimumEigensolver, MinimumEigensolverResult, VQEResult +from qiskit.algorithms.optimizers import Optimizer, SPSA from qiskit.opflow import OperatorBase, PauliSumOp from qiskit.quantum_info import SparsePauliOp @@ -35,7 +36,7 @@ class VQEProgram(MinimumEigensolver): def __init__( self, ansatz: QuantumCircuit, - optimizer: Optional[Dict[str, Any]] = None, + optimizer: Optional[Union[Optimizer, Dict[str, Any]]] = None, initial_point: Optional[np.ndarray] = None, provider: Optional[Provider] = None, backend: Optional[Backend] = None, @@ -47,11 +48,11 @@ def __init__( """ Args: ansatz: A parameterized circuit used as Ansatz for the wave function. - optimizer: A dictionary specifying a classical optimizer. - Currently only SPSA and QN-SPSA are supported. Per default, SPSA is used. - The dictionary must contain a key ``name`` for the name of the optimizer and may - contain additional keys for the settings. - E.g. ``{'name': 'SPSA', 'maxiter': 100}``. + optimizer: An optimizer or dictionary specifying a classical optimizer. + If a dictionary, only SPSA and QN-SPSA are supported. The dictionary must contain a + key ``name`` for the name of the optimizer and may contain additional keys for the + settings. E.g. ``{'name': 'SPSA', 'maxiter': 100}``. + Per default, SPSA is used. backend: The backend to run the circuits on. initial_point: An optional initial point (i.e. initial parameter values) for the optimizer. If ``None`` a random vector is used. @@ -66,7 +67,7 @@ def __init__( steps. Per default False. """ if optimizer is None: - optimizer = {"name": "SPSA"} + optimizer = SPSA(maxiter=300) # define program name self._program_id = "vqe" @@ -120,21 +121,25 @@ def ansatz(self, ansatz: QuantumCircuit) -> None: self._ansatz = ansatz @property - def optimizer(self) -> Dict[str, Any]: + def optimizer(self) -> Union[Optimizer, Dict[str, Any]]: """Return the dictionary describing the optimizer.""" return self._optimizer @optimizer.setter - def optimizer(self, settings: Dict[str, Any]) -> None: + def optimizer(self, optimizer: Union[Optimizer, Dict[str, Any]]) -> None: """Set the optimizer.""" - if "name" not in settings.keys(): - raise ValueError( - "The settings must contain a ``name`` key specifying the type of " "the optimizer." - ) + if isinstance(optimizer, Optimizer): + self._optimizer = optimizer + else: + if "name" not in optimizer.keys(): + raise ValueError( + "The optimizer dictionary must contain a ``name`` key specifying the type " + "of the optimizer." + ) - _validate_optimizer_settings(settings) + _validate_optimizer_settings(optimizer) - self._optimizer = settings + self._optimizer = optimizer @property def backend(self) -> Optional[Backend]: @@ -218,9 +223,7 @@ def wrapped_callback(*args): return None def compute_minimum_eigenvalue( - self, - operator: OperatorBase, - aux_operators: Optional[List[Optional[OperatorBase]]] = None, + self, operator: OperatorBase, aux_operators: Optional[List[Optional[OperatorBase]]] = None ) -> MinimumEigensolverResult: """Calls the VQE Runtime to approximate the ground state of the given operator. @@ -280,11 +283,6 @@ def compute_minimum_eigenvalue( raise RuntimeError(f"The job {job.job_id()} failed unexpectedly.") from exc # re-build result from serialized return value - vqe_result = self._parse_job_result(job, result) - return vqe_result - - def _parse_job_result(self, job, result): - """Build the VQE result object from the job result.""" vqe_result = VQEProgramResult() vqe_result.job_id = job.job_id() vqe_result.cost_function_evals = result.get("cost_function_evals", None) @@ -297,6 +295,7 @@ def _parse_job_result(self, job, result): vqe_result.optimizer_evals = result.get("optimizer_evals", None) vqe_result.optimizer_time = result.get("optimizer_time", None) vqe_result.optimizer_history = result.get("optimizer_history", None) + return vqe_result @@ -309,8 +308,8 @@ class VQEProgramResult(VQEResult): def __init__(self) -> None: super().__init__() - self._job_id: Optional[str] = None - self._optimizer_history: Optional[Dict[str, Any]] = None + self._job_id = None # type: str + self._optimizer_history = None # type: Dict[str, Any] @property def job_id(self) -> str: diff --git a/releasenotes/notes/runtime-optimizers-58f30311f969f54f.yaml b/releasenotes/notes/runtime-optimizers-58f30311f969f54f.yaml new file mode 100644 index 000000000..c55a39b5e --- /dev/null +++ b/releasenotes/notes/runtime-optimizers-58f30311f969f54f.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Allow Qiskit's :class:`~qiskit.algorithms.optimizers.Optimizer` classes as input for + the ``optimizer`` in the :class:`~qiskit_optimization.runtime.VQEProgram` and + :class:`~qiskit_optimization.runtime.QAOAProgram` instead of only + dictionaries. diff --git a/test/runtime/fake_vqeruntime.py b/test/runtime/fake_vqeruntime.py index dd527d8fe..9f8f220b6 100644 --- a/test/runtime/fake_vqeruntime.py +++ b/test/runtime/fake_vqeruntime.py @@ -14,6 +14,7 @@ from typing import Dict, Any import numpy as np +from qiskit.algorithms.optimizers import Optimizer from qiskit.circuit import QuantumCircuit from qiskit.opflow import PauliSumOp from qiskit.providers import Provider @@ -59,7 +60,7 @@ def run(self, program_id, inputs, options, callback=None): "aux_operators": (list, type(None)), "ansatz": QuantumCircuit, "initial_point": (np.ndarray, str), - "optimizer": dict, + "optimizer": (Optimizer, dict), "shots": int, "measurement_error_mitigation": bool, "store_intermediate": bool, diff --git a/test/runtime/test_qaoaprogram.py b/test/runtime/test_qaoaprogram.py index be48c55ca..9e098063f 100644 --- a/test/runtime/test_qaoaprogram.py +++ b/test/runtime/test_qaoaprogram.py @@ -15,7 +15,9 @@ from test import QiskitOptimizationTestCase import unittest +from ddt import ddt, data import numpy as np +from qiskit.algorithms.optimizers import COBYLA from qiskit.providers.basicaer import QasmSimulatorPy from qiskit.opflow import I, Z @@ -24,6 +26,7 @@ from .fake_vqeruntime import FakeRuntimeProvider +@ddt class TestQAOAProgram(QiskitOptimizationTestCase): """Test the QAOA program.""" @@ -31,12 +34,15 @@ def setUp(self): super().setUp() self.provider = FakeRuntimeProvider() - def test_standard_case(self): + @data( + {"name": "SPSA", "maxiter": 100}, + COBYLA(), + ) + def test_standard_case(self, optimizer): """Test a standard use case.""" operator = Z ^ I ^ Z reps = 2 initial_point = np.random.RandomState(42).random(2 * reps) - optimizer = {"name": "SPSA", "maxiter": 100} backend = QasmSimulatorPy() qaoa = QAOAProgram( diff --git a/test/runtime/test_vqeprogram.py b/test/runtime/test_vqeprogram.py index 4bfaee945..ce426e9a3 100644 --- a/test/runtime/test_vqeprogram.py +++ b/test/runtime/test_vqeprogram.py @@ -15,7 +15,9 @@ from test import QiskitOptimizationTestCase import unittest +from ddt import ddt, data import numpy as np +from qiskit.algorithms.optimizers import COBYLA from qiskit.providers.basicaer import QasmSimulatorPy from qiskit.circuit.library import RealAmplitudes from qiskit.opflow import I, Z @@ -25,6 +27,7 @@ from .fake_vqeruntime import FakeRuntimeProvider +@ddt class TestVQEProgram(QiskitOptimizationTestCase): """Test the VQE program.""" @@ -32,12 +35,15 @@ def setUp(self): super().setUp() self.provider = FakeRuntimeProvider() - def test_standard_case(self): + @data( + {"name": "SPSA", "maxiter": 100}, + COBYLA(), + ) + def test_standard_case(self, optimizer): """Test a standard use case.""" circuit = RealAmplitudes(3) operator = Z ^ I ^ Z initial_point = np.random.RandomState(42).random(circuit.num_parameters) - optimizer = {"name": "SPSA", "maxiter": 100} backend = QasmSimulatorPy() vqe = VQEProgram(