Nota
Esta página fue generada a partir de docs/tutorials/12_qaoa_runtime.ipynb.
QAOA Runtime¶
El runtime de Qiskit es un modelo de ejecución que nos permite ejecutar un programa completo en el backend. Aquí, discutimos cómo ejecutar el algoritmo QAOA en el runtime de Qiskit. Discutiremos varias de las características que esta primera versión de QAOA Runtime pone a disposición.
[1]:
from qiskit import IBMQ
IBMQ.load_account()
provider = IBMQ.get_provider(hub="ibm-q", group="open", project="main")
Preliminares¶
Primero, cargamos el programa e inspeccionamos sus argumentos para saber qué entradas espera. También investigamos los tipos de retorno para entender qué se nos devolverá. Obtenemos el programa QAOA del proveedor de la siguiente manera.
[2]:
program_id = "qaoa"
qaoa_program = provider.runtime.program(program_id)
Los argumentos para el runtime se obtienen de la siguiente manera.
[3]:
print(f"Program name: {qaoa_program.name}, Program id: {qaoa_program.program_id}")
print(qaoa_program.parameters())
Program name: qaoa, Program id: qaoa
ParameterNamespace (Values):
| Name | Value | Type | Required | Description |
-------------------------------------------------------------------------
| optimizati | None | integer | False | The optimization level to run if the swap strategies are not used. This value is 1 by default. This is an integer. |
| use_initia | None | boolean | False | 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_ | None | boolean | False | A boolean on whether or not to use a pulse-efficient transpilation. This flag is set to False by default. |
| measuremen | None | boolean | False | Whether to apply measurement error mitigation in form of a tensored measurement fitter to the measurements. Defaults to False. |
| reps | None | integer | False | The number of QAOA repetitions, i.e. the QAOA depth typically labeled p. This value defaults to 1. This is an integer. |
| alpha | None | number | False | 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. |
| aux_operat | None | array | False | A list of operators to be evaluated at the final, optimized state. This must be a List[PauliSumOp]. |
| initial_po | None | ['array', 's| False | Initial parameters of the ansatz. Can be an array or the string ``'random'`` to choose random initial parameters. The type must be numpy.ndarray or str. |
| shots | None | integer | False | The integer number of shots used for each circuit evaluation. Defaults to 1024. |
| operator | None | object | True | The cost Hamiltonian, consisting of Pauli I and Z operators, whose smallest eigenvalue we're trying to find. The type must be a PauliSumOp. |
| optimizer | None | object | False | The classical optimizer used to update the parameters in each iteration. Per default, SPSA with automatic calibration of the learning rate is used. The type must be a qiskit.algorithms.optimizers.Optimizer. |
| use_swap_s | None | boolean | False | A boolean on whether or not to use swap strategies when transpiling. This flag is set to True by default. If this is False then the standard transpiler with the given optimization level will run. |
Aquí, vemos los diferentes argumentos de entrada en los que ahora elaboramos.
operator es, con mucho, el argumento más importante, ya que representa el operador de costo que queremos minimizar. Para QAOA, esto corresponde a un
PauliSumOp
. Cada vez que haya un término cuadrático entre dos variables de decisión en el problema que deseamos minimizar, habrá un términoZZ
correspondiente en elPauliSumOp
. Por ejemplo, el operadorPauliSumOp.from_list([("ZIZ", 1), ("IZZ", -1), ("ZZI", 1)])
puede corresponder a un problema de Maxcut sobre un triángulo donde un borde tiene un peso negativo.optimizer representa el optimizador utilizado en la optimización clásica de ciclo cerrado. Esto se establecerá de forma predeterminada en SPSA para hacer frente al ruido del hardware.
reps este número entero es el número de capas del QAOA.
initial_point son los valores iniciales para \(\gamma\) y \(\beta\). Esta es una lista en el siguiente orden \([\gamma_1, \beta_1, \gamma_2, \beta_2, ...]\). Por lo tanto, la longitud de esta lista debe ser el doble de la profundidad de QAOA (es decir, el parámetro
reps
).shots es el número de iteraciones a ejecutar para cada circuito.
alpha es el \(\alpha\) de la optimización CVaR [1]. Si \(\alpha\) es menor que uno, entonces solo la mejor fracción de \(\alpha\) de iteraciones se retendrá en la optimización, es decir, esto corresponde al uso de
CVaRExpectation(alpha, PauliExpectation())
que se puede encontrar enqiskit.opflow
.measurement_error_mitigation determina si se debe utilizar la mitigación de errores de lectura. Si es
True
, el algoritmo empleado usará elTensoredMeasFitter
.Si use_swap_strategies es
True
, el programa QAOA transpilará el circuito QAOA con estrategias de intercambio dedicadas que tienen en cuenta la naturaleza conmutativa de los operadoresZZ
en QAOA y el mapa de acoplamiento del backend en el que el programa se ejecutará. Si esta opción es False, entonces el programa QAOA usará por defecto el transpilador estándar con nivel de optimización 1. Este nivel de optimización se puede elegir utilizando la opción de entrada optimization_level.Si use_pulse_efficient se establece en
True
, se ejecutará una transpilación eficiente en pulsos en cada iteración de QAOA [2]. Esto recopila bloques de dos qubits y aplica la descomposición KAK de Cartan para obtener una representaciónRZXGate
del circuito. A continuación, se utilizan pulsos de resonancia cruzada escalados. Se proporcionan más detalles en el Apéndice A más abajo.Si use_initial_mapping es
True
, se ejecutará un algoritmo heurístico para permutar los operadores de Pauli en el operador de costo para que se ajuste mejor el mapa de acoplamiento del dispositivo. Esta permutación solo se aplica si la opciónuse_swap_strategies
está configurada enTrue
y solo tiene sentido para problemas que no están completamente conectados.optimization_level Este es el nivel de optimización del transpilador Qiskit si no se utilizan estrategias de intercambio. El valor predeterminado es 1.
Referencias¶
[1] P. Kl. Barkoutsos, G. Nannicini, A. Robert, I. Tavernelli, and S. Woerner, Improving Variational Quantum Optimization using CVaR, Quantum 4, 256 (2020).
[2] N. Earnest, C. Tornow, and D. J. Egger, Pulse-efficient circuit transpilation for quantum applications on cross-resonance-based hardware, Phys. Rev. Research 3, 043088 (2021).
Ejecución del programa QAOA runtime¶
Ahora mostramos cómo usar el programa QAOA runtime con un ejemplo de cinco qubits.
Aquí, mostramos cómo llamar directamente al programa en la nube. Sin embargo, Qiskit Optimization proporciona el QAOAClient
que se adapta perfectamente al flujo de trabajo de Qiskit Optimization y se puede utilizar como un reemplazo directo de tu algoritmo local de QAOA
. Este QAOAClient
se analiza a continuación en este tutorial.
[4]:
import numpy as np
from qiskit.tools import job_monitor
from qiskit.opflow import PauliSumOp, Z, I
from qiskit.algorithms.optimizers import SPSA
# Define the cost operator to run.
op = (
(Z ^ Z ^ I ^ I ^ I)
- (I ^ I ^ Z ^ Z ^ I)
+ (I ^ I ^ Z ^ I ^ Z)
- (Z ^ I ^ Z ^ I ^ I)
- (I ^ Z ^ Z ^ I ^ I)
+ (I ^ Z ^ I ^ Z ^ I)
+ (I ^ I ^ I ^ Z ^ Z)
)
# SPSA helps deal with noisy environments.
optimizer = SPSA(maxiter=100)
# We will run a depth two QAOA.
reps = 2
# The initial point for the optimization, chosen at random.
initial_point = np.random.random(2 * reps)
# The backend that will run the programm.
options = {"backend_name": "ibmq_qasm_simulator"}
# The inputs of the program as described above.
runtime_inputs = {
"operator": op,
"reps": reps,
"optimizer": optimizer,
"initial_point": initial_point,
"shots": 2**13,
# Set to True when running on real backends to reduce circuit
# depth by leveraging swap strategies. If False the
# given optimization_level (default is 1) will be used.
"use_swap_strategies": False,
# Set to True when optimizing sparse problems.
"use_initial_mapping": False,
# Set to true when using echoed-cross-resonance hardware.
"use_pulse_efficient": False,
}
Ahora, ejecutamos el programa usando el proveedor.
[5]:
job = provider.runtime.run(
program_id=program_id,
options=options,
inputs=runtime_inputs,
)
[6]:
job_monitor(job)
Job Status: job has successfully run
[7]:
print(f"Job id: {job.job_id()}")
print(f"Job status: {job.status()}")
Job id: c9qh9mekcirf2adkkhvg
Bob status: JobStatus.DONE
Una vez que el trabajo (job) se ha ejecutado con éxito, podemos recuperar el resultado del trabajo. El objeto de resultado contiene información sobre la optimización. Para mantener la coherencia, también contiene las entradas al programa QAOA a las que se puede acceder a través de result["inputs"]
. El valor debajo de "inputs"
es un diccionario con las claves descritas anteriormente.
[8]:
result = job.result()
De particular interés es la energía que se midió en el hardware en cada iteración, así como el estado propio devuelto. Para este ejemplo, suponemos que la energía del operador de costo que minimizamos corresponde a una instancia de un problema de MaxCut.
[9]:
from collections import defaultdict
def op_adj_mat(op: PauliSumOp) -> np.array:
"""Extract the adjacency matrix from the op."""
adj_mat = np.zeros((op.num_qubits, op.num_qubits))
for pauli, coeff in op.primitive.to_list():
idx = tuple([i for i, c in enumerate(pauli[::-1]) if c == "Z"]) # index of Z
adj_mat[idx[0], idx[1]], adj_mat[idx[1], idx[0]] = np.real(coeff), np.real(coeff)
return adj_mat
def get_cost(bit_str: str, adj_mat: np.array) -> float:
"""Return the cut value of the bit string."""
n, x = len(bit_str), [int(bit) for bit in bit_str[::-1]]
cost = 0
for i in range(n):
for j in range(n):
cost += adj_mat[i, j] * x[i] * (1 - x[j])
return cost
def get_cut_distribution(result) -> dict:
"""Extract the cut distribution from the result.
Returns:
A dict of cut value: probability.
"""
adj_mat = op_adj_mat(PauliSumOp.from_list(result["inputs"]["operator"]))
state_results = []
for bit_str, amp in result["eigenstate"].items():
state_results.append((bit_str, get_cost(bit_str, adj_mat), amp**2 * 100))
vals = defaultdict(int)
for res in state_results:
vals[res[1]] += res[2]
return dict(vals)
[10]:
import matplotlib.pyplot as plt
cut_vals = get_cut_distribution(result)
fig, axs = plt.subplots(1, 2, figsize=(14, 5))
axs[0].plot(result["optimizer_history"]["energy"])
axs[1].bar(list(cut_vals.keys()), list(cut_vals.values()))
axs[0].set_xlabel("Energy evaluation number")
axs[0].set_ylabel("Energy")
axs[1].set_xlabel("Cut value")
axs[1].set_ylabel("Probability")
[10]:
Text(0, 0.5, 'Probability')

Qiskit optimization¶
La funcionalidad discutida anteriormente se implementa en Qiskit-optimization donde se puede acceder al QAOA-runtime a través del módulo runtime
. Ahora mostramos cómo usar este módulo con el QAOAClient
.
[11]:
from qiskit_optimization.runtime import QAOAClient
from qiskit_optimization.algorithms import MinimumEigenOptimizer
from qiskit_optimization import QuadraticProgram
[12]:
qubo = QuadraticProgram()
qubo.binary_var("x")
qubo.binary_var("y")
qubo.binary_var("z")
qubo.minimize(linear=[1, -2, 3], quadratic={("x", "y"): 1, ("x", "z"): -1, ("y", "z"): 2})
print(qubo.prettyprint())
Problem name:
Minimize
x*y - x*z + 2*y*z + x - 2*y + 3*z
Subject to
No constraints
Binary variables (3)
x y z
[13]:
qaoa_mes = QAOAClient(
provider=provider, backend=provider.get_backend("ibmq_qasm_simulator"), reps=2, alpha=0.75
)
qaoa = MinimumEigenOptimizer(qaoa_mes)
[14]:
result = qaoa.solve(qubo)
print(result.prettyprint())
objective function value: -2.0
variable values: x=0.0, y=1.0, z=0.0
status: SUCCESS
Apéndice A: Transpilación de pulso eficiente¶
El programa de QAOA runtime puede aprovechar una transpilación de pulso eficiente, la cual minimiza el número de pulsos de un solo qubit y hace uso de la metodología de escalado de resonancia cruzada aplicable a las compuertas de resonancia cruzada con eco de IBM Quantum Systems. Por completez, aquí mostramos el administrador de pases que está implementado.
[15]:
from qiskit.transpiler import PassManager
from qiskit.circuit.library.standard_gates.equivalence_library import (
StandardEquivalenceLibrary as std_eqlib,
)
from qiskit.transpiler.passes import (
Collect2qBlocks,
ConsolidateBlocks,
UnrollCustomDefinitions,
BasisTranslator,
Optimize1qGatesDecomposition,
)
from qiskit.transpiler.passes.calibration.builders import RZXCalibrationBuilderNoEcho
from qiskit.transpiler.passes.optimization.echo_rzx_weyl_decomposition import (
EchoRZXWeylDecomposition,
)
from qiskit.test.mock import FakeBelem
[16]:
backend = FakeBelem()
El pase de pulso eficiente se implementa utilizando pases de transpilador de Qiskit. Requiere conocimiento del backend habilitado por pulsos para las compuertas de resonancia cruzada escaladas adjuntas a las RZXGate
s obtenidas de la descomposición KAK. Estas compuertas escaladas se basan en los planificadores (schedules) de las compuertas CNOT calibradas.
[17]:
inst_map = backend.defaults().instruction_schedule_map
channel_map = backend.configuration().qubit_channel_mapping
rzx_basis = ["rzx", "rz", "x", "sx"]
pulse_efficient = PassManager(
[
# Consolidate consecutive two-qubit operations.
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=["rz", "sx", "x", "rxx"]),
# Rewrite circuit in terms of Weyl-decomposed echoed RZX gates.
EchoRZXWeylDecomposition(backend.defaults().instruction_schedule_map),
# Attach scaled CR pulse schedules to the RZX gates.
RZXCalibrationBuilderNoEcho(
instruction_schedule_map=inst_map, qubit_channel_mapping=channel_map
),
# Simplify single-qubit gates.
UnrollCustomDefinitions(std_eqlib, rzx_basis),
BasisTranslator(std_eqlib, rzx_basis),
Optimize1qGatesDecomposition(rzx_basis),
]
)
Para demostrar el pase, construimos un circuito arbitrario con bloques de compuertas de dos qubits.
[18]:
from qiskit import QuantumCircuit
circ = QuantumCircuit(3)
circ.h([0, 1, 2])
circ.rzx(0.5, 0, 1)
circ.swap(0, 1)
circ.cx(2, 1)
circ.rz(0.4, 1)
circ.cx(2, 1)
circ.rx(1.23, 2)
circ.cx(2, 1)
circ.draw("mpl")
[18]:

[19]:
pulse_efficient.run(circ).draw("mpl", fold=False)
[19]:

[20]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright
Version Information
Qiskit Software | Version |
---|---|
qiskit-terra | 0.21.0.dev0+cffbb84 |
qiskit-aer | 0.10.4 |
qiskit-ibmq-provider | 0.19.1 |
qiskit-optimization | 0.4.0 |
System information | |
Python version | 3.10.4 |
Python compiler | GCC 11.2.0 |
Python build | main, Apr 2 2022 09:04:19 |
OS | Linux |
CPUs | 4 |
Memory (Gb) | 14.577533721923828 |
Fri May 06 21:31:31 2022 JST |
This code is a part of Qiskit
© Copyright IBM 2017, 2022.
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.