Skip to content

Commit

Permalink
QAOA runtime (#288)
Browse files Browse the repository at this point in the history
* * Amended the runtime clients for the new QAOA runtime.

* * More flexible provider.run interface.

* * Spelling.

* * Args consistency error.

* * Spelling.

* * Updated tests to function with the new QAOA client.

* * Refactor.

* * Fix text.

* Update qiskit_optimization/runtime/qaoa_client.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* * Expose options to users.

* Update qiskit_optimization/runtime/vqe_client.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* * Docstring and error message.

* * Fix refactor bugs.

* * removed _send_job.

* * Added the runtime tutorial.

* * Tutorial.

* * Tutorial

* fix spell and do not run tutorial

* * Removed the results inputs.

* * Black NB

* * Tutorial name and title consistency.

* * Properties.

* * Consistency.

* * Update to tutorial.

* * Reno.

* * RNG initial point and spelling.

* more detailed reno

* Apply suggestions from code review

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* * Reverted rng seed.

* * Test on reps.

* * rng, docs, alpha check.

* * Init point doc.

Co-authored-by: Manoel Marques <Manoel.Marques@ibm.com>
Co-authored-by: Julien Gacon <gaconju@gmail.com>
  • Loading branch information
3 people committed Dec 16, 2021
1 parent 2a46fcd commit 68e197d
Show file tree
Hide file tree
Showing 10 changed files with 922 additions and 50 deletions.
10 changes: 10 additions & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ args
arxiv
autosummary
backend
barkoutsos
benchmarking
bitstring
bitstrings
Expand All @@ -20,6 +21,7 @@ boolean
boyd
bravyi
catol
cartan
chu
chvátal
cobyla
Expand Down Expand Up @@ -112,6 +114,7 @@ minimizer
minimumeigenoptimizer
multiset
mypy
nannicini
natively
ndarray
ndarrays
Expand Down Expand Up @@ -165,6 +168,7 @@ rhoend
rhs
rightarrow
rng
robert
ronagh
rtype
runtime
Expand All @@ -190,13 +194,19 @@ subspaces
sys
subproblem
summands
tavernelli
terra
th
toctree
todok
tol
tornow
traceback
tradeoff
transpilation
transpile
transpiler
transpiling
travelling
troyer
tsplib
Expand Down
649 changes: 649 additions & 0 deletions docs/tutorials/12_qaoa_runtime.ipynb

Large diffs are not rendered by default.

129 changes: 115 additions & 14 deletions qiskit_optimization/runtime/qaoa_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,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
from qiskit.providers.backend import Backend
Expand All @@ -38,12 +36,17 @@ def __init__(
initial_state: Optional[QuantumCircuit] = None,
mixer: Union[QuantumCircuit, OperatorBase] = None,
initial_point: Optional[np.ndarray] = None,
alpha: float = 1.0,
provider: Optional[Provider] = None,
backend: Optional[Backend] = None,
shots: int = 1024,
measurement_error_mitigation: bool = False,
callback: Optional[Callable[[int, np.ndarray, float, float], None]] = None,
store_intermediate: bool = False,
use_swap_strategies: bool = False,
use_initial_mapping: bool = False,
use_pulse_efficient: bool = False,
optimization_level: Optional[int] = None,
) -> None:
"""
Args:
Expand All @@ -60,7 +63,11 @@ def __init__(
as well as warm-starting the optimization as introduced
in http://arxiv.org/abs/2009.10095.
initial_point: An optional initial point (i.e. initial parameter values)
for the optimizer. If ``None`` a random vector is used.
for the optimizer. If ``None`` a random vector is chosen in the :class:`VQE`
class in Qiskit terra using a uniform distribution.
alpha: The fraction of top measurement samples to be used for the expectation value
(CVaR expectation). Defaults to 1, i.e. using all samples to construct the
expectation value. This value must be contained in the interval [0, 1].
provider: The provider.
backend: The backend to run the circuits on.
shots: The number of shots to be used
Expand All @@ -72,7 +79,30 @@ def __init__(
ansatz, the evaluated mean and the evaluated standard deviation.
store_intermediate: Whether or not to store intermediate values of the optimization
steps. Per default False.
use_swap_strategies: A boolean on whether or not to use swap strategies when
transpiling. If this is False then the standard transpiler with the given
optimization level will run.
use_initial_mapping: A boolean flag that, if set to True (the default is False), runs
a heuristic algorithm to permute the Paulis in the cost operator to better fit the
coupling map and the swap strategy. This is only needed when the optimization
problem is sparse and when using swap strategies to transpile.
use_pulse_efficient: A boolean on whether or not to use a pulse-efficient transpilation.
If this flag is set to False by default. See https://arxiv.org/abs/2105.01063.
optimization_level: The transpiler optimization level to run if the swap strategies are
not used. This value defaults to 1 in the QAOA runtime.
Raises:
QiskitOptimizationError: if reps is smaller than 1.
QiskitOptimizationError: if alpha is not in the interval [0, 1].
QiskitOptimizationError: if optimization_level is not None and use_swap_strategies
is True.
"""
if reps < 1:
raise QiskitOptimizationError(f"reps must be greater than 0, received {reps}.")

if alpha < 0 or alpha > 1:
raise QiskitOptimizationError(f"alpha must range from 0 to 1. Received {alpha}.")

super().__init__(
ansatz=None,
optimizer=optimizer,
Expand All @@ -87,6 +117,69 @@ def __init__(
self._initial_state = initial_state
self._mixer = mixer
self._reps = reps
self._use_swap_strategies = use_swap_strategies
self._use_initial_mapping = use_initial_mapping
self._use_pulse_efficient = use_pulse_efficient
self._alpha = alpha
self._program_id = "qaoa"

# Use the setter to check consistency with other settings.
self.optimization_level = optimization_level

@property
def use_swap_strategies(self) -> bool:
"""Whether or not the transpilation will use the swap strategies."""
return self._use_swap_strategies

@use_swap_strategies.setter
def use_swap_strategies(self, use_swap_strategies: bool) -> None:
"""Set to True to use swap strategies in the transpilation."""
self._use_swap_strategies = use_swap_strategies

@property
def use_initial_mapping(self) -> bool:
"""If True run a permutation of the decision variables to better fit the device."""
return self._use_initial_mapping

@use_initial_mapping.setter
def use_initial_mapping(self, use_initial_mapping: bool) -> None:
"""If True run a permutation of the decision variables to better fit the device."""
self._use_initial_mapping = use_initial_mapping

@property
def use_pulse_efficient(self) -> bool:
"""If True then a pulse-efficient transpiler pass will run to scale the CR gates."""
return self._use_pulse_efficient

@use_pulse_efficient.setter
def use_pulse_efficient(self, use_pulse_efficient: bool) -> None:
"""If True then a pulse-efficient transpiler pass will run to scale the CR gates."""
self._use_pulse_efficient = use_pulse_efficient

@property
def alpha(self) -> float:
"""The fraction of best shots to keep."""
return self._alpha

@alpha.setter
def alpha(self, alpha: float) -> None:
"""Set the fraction of best shots to keep."""
self._alpha = alpha

@property
def optimization_level(self) -> Optional[int]:
"""Return the optimization level to use if swap strategies are not used."""
return self._optimization_level

@optimization_level.setter
def optimization_level(self, optimization_level: Optional[int] = None):
"""Set the optimization level."""
if optimization_level is not None and self.use_swap_strategies:
raise QiskitOptimizationError(
"optimization_level cannot be set if use_swap_strategies is True."
)

self._optimization_level = optimization_level

@property
def ansatz(self) -> Optional[QuantumCircuit]:
Expand Down Expand Up @@ -147,15 +240,23 @@ def reps(self, reps: int) -> None:
"""
self._reps = reps

def compute_minimum_eigenvalue(
self,
operator: OperatorBase,
aux_operators: Optional[List[Optional[OperatorBase]]] = None,
) -> MinimumEigensolverResult:
self._ansatz = QAOAAnsatz(
operator,
reps=self.reps,
initial_state=self.initial_state,
mixer_operator=self.mixer,
def program_inputs(
self, operator: OperatorBase, aux_operators: Optional[List[Optional[OperatorBase]]] = None
) -> Dict[str, Any]:
"""Return the QAOA program inputs"""
inputs = super().program_inputs(operator, aux_operators)

# QAOA doesn't set the ansatz
del inputs["ansatz"]

inputs.update(
{
"reps": self._reps,
"use_pulse_efficient": self._use_pulse_efficient,
"use_swap_strategies": self._use_swap_strategies,
"use_initial_mapping": self._use_initial_mapping,
"alpha": self._alpha,
}
)
return super().compute_minimum_eigenvalue(operator, aux_operators)

return inputs
22 changes: 11 additions & 11 deletions qiskit_optimization/runtime/qaoa_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,17 @@ def __init__(
)

super().__init__(
optimizer,
reps,
initial_state,
mixer,
initial_point,
provider,
backend,
shots,
measurement_error_mitigation,
callback,
store_intermediate,
optimizer=optimizer,
reps=reps,
initial_state=initial_state,
mixer=mixer,
initial_point=initial_point,
provider=provider,
backend=backend,
shots=shots,
measurement_error_mitigation=measurement_error_mitigation,
callback=callback,
store_intermediate=store_intermediate,
)

def compute_minimum_eigenvalue(
Expand Down
46 changes: 30 additions & 16 deletions qiskit_optimization/runtime/vqe_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,33 @@ def wrapped_callback(*args):
else:
return None

def program_inputs(
self, operator: OperatorBase, aux_operators: Optional[List[Optional[OperatorBase]]] = None
) -> Dict[str, Any]:
"""Return the inputs for the runtime program.
Sub-classes can override this method to add their own inputs.
"""
return {
"operator": operator,
"aux_operators": aux_operators,
"ansatz": self.ansatz,
"optimizer": self.optimizer,
"initial_point": self.initial_point,
"shots": self.shots,
"measurement_error_mitigation": self.measurement_error_mitigation,
"store_intermediate": self.store_intermediate,
}

def _send_job(self, inputs: Dict[str, Any], options: Dict[str, Any]):
"""Submit a runtime job to the program ``self.program_id``."""
return self.provider.runtime.run(
program_id=self.program_id,
inputs=inputs,
options=options,
callback=self._wrap_vqe_callback(),
)

def compute_minimum_eigenvalue(
self, operator: OperatorBase, aux_operators: Optional[List[Optional[OperatorBase]]] = None
) -> MinimumEigensolverResult:
Expand Down Expand Up @@ -263,27 +290,14 @@ def compute_minimum_eigenvalue(
aux_operators = [_convert_to_paulisumop(aux_op) for aux_op in aux_operators]

# combine the settings with the given operator to runtime inputs
inputs = {
"operator": operator,
"aux_operators": aux_operators,
"ansatz": self.ansatz,
"optimizer": self.optimizer,
"initial_point": self.initial_point,
"shots": self.shots,
"measurement_error_mitigation": self.measurement_error_mitigation,
"store_intermediate": self.store_intermediate,
}
inputs = self.program_inputs(operator, aux_operators)

# define runtime options
options = {"backend_name": self.backend.name()}

# send job to runtime and return result
job = self.provider.runtime.run(
program_id=self.program_id,
inputs=inputs,
options=options,
callback=self._wrap_vqe_callback(),
)
job = self._send_job(inputs, options)

# print job ID if something goes wrong
try:
result = job.result()
Expand Down
40 changes: 40 additions & 0 deletions releasenotes/notes/qaoa-runtime-client-afa2453fc325d9bd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
features:
- |
Added the runtime client :class:`~qiskit_optimization.runtime.QAOAClient` to execute the
QAOA algorithm on Qiskit runtime. This runtime program leverages QAOA dedicated transpiler
passes such as swap strategies and pulse-efficient transpiler passes for cross-resonance based hardware.
Both these optimizations can significantly reduce circuit depth and improve execution time and
results.
Further, the QAOA runtime also allows using CVaR expectation values, which can improve the
performance of ground state calculations in optimization settings.
The client can for instance be used as
.. code-block:: python
from qiskit import IBMQ
from qiskit.algorithms.optimizers import COBYLA
from qiskit.opflow import I, Z
from qiskit_optimization.runtime import QAOAClient
# get the provider and backend we use to run the program
IBMQ.load_account()
provider = IBMQ.get_provider(hub="ibm-q", group="open", project="main")
backend = provider.get_backend("ibmq_qasm_simulator")
# define diagonal Hamiltonian whose minimum eigenvalue we want to find
op = (Z ^ Z ^ I ^ I ^ I) - (I ^ I ^ Z ^ Z ^ I)
# set up the client and solve the problem
client = QAOAClient(
reps=2, # use p=2 repetitions in the QAOA ansatz
optimizer=COBYLA(),
alpha=0.75, # use CVaR expectation with 75% of the best readouts
provider=provider,
backend=backend
)
result = client.compute_minimum_eigenvalue(op)
See also the new QAOA Runtime tutorial in ``docs/tutorials/12_qaoa_runtime.ipynb`` for more
details.
7 changes: 3 additions & 4 deletions test/algorithms/test_min_eigen_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
QiskitOptimizationTestCase,
requires_extra_library,
)
from test.runtime.fake_vqeruntime import FakeRuntimeProvider
from test.runtime.fake_vqeruntime import FakeVQERuntimeProvider, FakeQAOARuntimeProvider

import numpy as np
from ddt import data, ddt
Expand Down Expand Up @@ -357,7 +357,6 @@ def test_runtime(self, subroutine):
"""Test vqe and qaoa runtime"""
optimizer = {"name": "SPSA", "maxiter": 100}
backend = QasmSimulatorPy()
provider = FakeRuntimeProvider()

if subroutine == "vqe":
ry_ansatz = TwoLocal(5, "ry", "cz", reps=3, entanglement="full")
Expand All @@ -367,7 +366,7 @@ def test_runtime(self, subroutine):
optimizer=optimizer,
initial_point=initial_point,
backend=backend,
provider=provider,
provider=FakeVQERuntimeProvider(),
)
else:
reps = 2
Expand All @@ -377,7 +376,7 @@ def test_runtime(self, subroutine):
reps=reps,
initial_point=initial_point,
backend=backend,
provider=provider,
provider=FakeQAOARuntimeProvider(),
)

opt = MinimumEigenOptimizer(solver)
Expand Down
Loading

0 comments on commit 68e197d

Please sign in to comment.