Repository: Qiskit/qiskit-optimization
Pull request #214 (2021-07-29 13:43:05)

Allow Qiskit Optimizers in the runtime programs
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add it in the CHANGELOG file under Unreleased section.
⚠️ If your pull request fixes an open issue, please link to the issue.

✅ I have added the tests to cover my changes.
✅ I have updated the documentation accordingly.
✅ I have read the CONTRIBUTING document.
-->

### Summary

Follow up on Qiskit/qiskit-nature#290. Allow Qiskit Optimizers objects as ``optimizers`` in the runtime programs instead of only dictionaries.






woodsp-ibm: Can you add a reno too please

Patch:
diff --git a/qiskit_optimization/runtime/qaoa_program.py b/qiskit_optimization/runtime/qaoa_program.py
index 65f9750..d97dd42 100644
--- a/qiskit_optimization/runtime/qaoa_program.py
+++ b/qiskit_optimization/runtime/qaoa_program.py
@@ -18,6 +18,7 @@ import numpy as np
 
 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 @@ class QAOAProgram(VQEProgram):
     ) -> 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 0fecd54..2375dfb 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.exceptions import QiskitError
 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 @@ class VQEProgram(MinimumEigensolver):
         """
         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 @@ class VQEProgram(MinimumEigensolver):
                 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 @@ class VQEProgram(MinimumEigensolver):
         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 @@ class VQEProgram(MinimumEigensolver):
             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 @@ class VQEProgram(MinimumEigensolver):
             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 @@ class VQEProgram(MinimumEigensolver):
         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 0000000..c55a39b
--- /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.


Test patch:
diff --git a/test/runtime/fake_vqeruntime.py b/test/runtime/fake_vqeruntime.py
index dd527d8..9f8f220 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 @@ class FakeVQERuntime:
             "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 be48c55..9e09806 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 qiskit_optimization.runtime import QAOAProgram, VQEProgramResult
 from .fake_vqeruntime import FakeRuntimeProvider
 
 
+@ddt
 class TestQAOAProgram(QiskitOptimizationTestCase):
     """Test the QAOA program."""
 
@@ -31,12 +34,15 @@ class TestQAOAProgram(QiskitOptimizationTestCase):
         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 4bfaee9..ce426e9 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 qiskit_optimization.runtime import VQEProgram, VQEProgramResult
 from .fake_vqeruntime import FakeRuntimeProvider
 
 
+@ddt
 class TestVQEProgram(QiskitOptimizationTestCase):
     """Test the VQE program."""
 
@@ -32,12 +35,15 @@ class TestVQEProgram(QiskitOptimizationTestCase):
         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(