Nota
Esta página fue generada a partir de docs/tutorials/user-transpiled-circuits.ipynb.
Envío de circuitos transpilados por el usuario usando primitivas¶
Para obtener el mejor rendimiento de tus circuitos, el servicio Qiskit Runtime pasará todos los circuitos por el transpilador de Qiskit antes de ejecutarlos. Si bien esto suele ser algo bueno, a veces es posible que queramos deshabilitarlo pasando el argumento skip_transpilation=True
a la primitiva que estamos usando.
Por ejemplo, podemos saber más que el transpilador en algunos casos, o querer apuntar a un subconjunto específico de qubits en un dispositivo específico. En este tutorial, deshabilitaremos la transpilación automática para probar el rendimiento de diferentes configuraciones del transpilador. Este ejemplo te guiará a través del proceso completo de creación, transpilación y envío de circuitos.
Transpilación de circuitos para dispositivos IBM Quantum¶
En la siguiente celda de código, creamos un pequeño circuito que nuestro transpilador intentará optimizar. En este ejemplo creamos un circuito que realiza el algoritmo de Grover, con un oráculo que marca el estado 111
. Luego simulamos la distribución ideal (lo que esperaríamos medir si ejecutáramos esto en una computadora cuántica perfecta, un número infinito de veces) para compararlo más tarde.
[1]:
# Create circuit to test transpiler on
from qiskit import QuantumCircuit
from qiskit.circuit.library import GroverOperator, Diagonal
oracle = Diagonal([1] * 7 + [-1])
qc = QuantumCircuit(3)
qc.h([0, 1, 2])
qc = qc.compose(GroverOperator(oracle))
# Use Statevector object to calculate the ideal output
from qiskit.quantum_info import Statevector
ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()
from qiskit.visualization import plot_histogram
plot_histogram(ideal_distribution)
[1]:
A continuación, debemos elegir un backend para el cual transpilar. En la siguiente celda, creamos una instancia de servicio, que usaremos para iniciar una sesión y obtener el objeto de backend, que contiene información para el transpilador. Dado que el proceso de transpilación depende del dispositivo, le pediremos al servicio runtime un dispositivo específico por su nombre. En este ejemplo, utilizaremos ibm_algiers
, que solo está disponible a través de IBM Cloud.
[2]:
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
backend = service.backend("ibm_algiers")
A continuación, transpilamos los circuitos para nuestro backend. Vamos a comparar el rendimiento del transpilador con optimization_level
establecido en 0
(el más bajo) contra 3
(el más alto). El nivel de optimización más bajo solo hace lo mínimo necesario para que el circuito funcione en el dispositivo, asigna los qubits del circuito a los qubits del dispositivo y agrega compuertas de intercambio (swap) para permitir todas las operaciones de 2 qubits. El nivel de optimización más alto es mucho más inteligente y utiliza muchos trucos para reducir el número total de compuertas. Dado que las compuertas multi-qubit tienen altas tasas de error y los qubits pierden coherencia con el tiempo, los circuitos más cortos deberían dar mejores resultados.
En la siguiente celda, transpilamos qc
para ambos valores de optimization_level
, imprimimos el número de compuertas CNOT y agregamos los circuitos transpilados a una lista. Algunos de los algoritmos del transpilador son aleatorios, por lo que establecemos una semilla para la reproducibilidad.
[3]:
# Need to add measurements to the circuit
qc.measure_all()
from qiskit import transpile
circuits = []
for optimization_level in [0, 3]:
t_qc = transpile(qc, backend, optimization_level=optimization_level, seed_transpiler=0)
print(f"CNOTs (optimization_level={optimization_level}): ", t_qc.count_ops()["cx"])
circuits.append(t_qc)
CNOTs (optimization_level=0): 27
CNOTs (optimization_level=3): 12
Dado que las CNOT suelen tener una alta tasa de error, el circuito transpilado con optimization_level=3
debería funcionar mucho mejor.
Otra forma en que podemos mejorar el rendimiento es a través del desacoplamiento dinámico, donde aplicamos una secuencia de compuertas a los qubits inactivos. Esto cancela algunas interacciones no deseadas con el medio ambiente. En la siguiente celda, agregamos desacoplamiento dinámico al circuito transpilado con optimization_level=3
y lo agregamos a nuestra lista.
[4]:
from qiskit.transpiler import PassManager, InstructionDurations
from qiskit.transpiler.passes import ASAPSchedule, DynamicalDecoupling
from qiskit.circuit.library import XGate
# Get gate durations so the transpiler knows how long each operation takes
durations = InstructionDurations.from_backend(backend)
# This is the sequence we'll apply to idling qubits
dd_sequence = [XGate(), XGate()]
# Run scheduling and dynamic decoupling passes on circuit
pm = PassManager([ASAPSchedule(durations), DynamicalDecoupling(durations, dd_sequence)])
circ_dd = pm.run(circuits[1])
# Add this new circuit to our list
circuits.append(circ_dd)
Ejecución de circuitos transpilados por el usuario utilizando Qiskit Runtime¶
En este punto, tenemos una lista de circuitos (llamados circuits
) transpilados para ibm_algiers
. En la siguiente celda, creamos una instancia de la primitiva sampler e iniciamos una sesión usando el administrador de contexto (with ...:
), que automáticamente abre y cierra la sesión por nosotros. Aquí es donde pasamos el argumento skip_transpilation=True
.
Dentro del administrador de contexto, muestreamos los circuitos y almacenamos los resultados en result
.
[5]:
from qiskit_ibm_runtime import Sampler, Session
with Session(service=service, backend=backend):
sampler = Sampler()
job = sampler.run(
circuits=circuits, # sample all three circuits
skip_transpilation=True,
shots=8000,
)
result = job.result()
Finalmente, podemos graficar los resultados de las ejecuciones del dispositivo contra la distribución ideal. Puedes ver que los resultados con optimization_level=3
están más cerca de la distribución ideal debido al conteo de compuertas más bajo, y optimization_level=3 + dd
está aún más cerca debido al desacoplamiento dinámico que aplicamos.
[6]:
from qiskit.visualization import plot_histogram
binary_prob = [quasi_dist.binary_probabilities() for quasi_dist in result.quasi_dists]
plot_histogram(
binary_prob + [ideal_distribution],
bar_labels=False,
legend=[
"optimization_level=0",
"optimization_level=3",
"optimization_level=3 + dd",
"ideal distribution",
],
)
[6]:
Podemos confirmar esto calculando la fidelidad de Hellinger entre cada conjunto de resultados y la distribución ideal (más alto es mejor, y 1 es fidelidad perfecta).
[7]:
from qiskit.quantum_info import hellinger_fidelity
for counts in result.quasi_dists:
print(f"{hellinger_fidelity(counts.binary_probabilities(), ideal_distribution):.3f}")
0.927
0.938
0.951
[8]:
import qiskit_ibm_runtime
qiskit_ibm_runtime.version.get_version_info()
[8]:
'0.4.0'
[9]:
from qiskit.tools.jupyter import *
%qiskit_version_table
%qiskit_copyright
Version Information
Qiskit Software | Version |
---|---|
qiskit-terra | 0.20.0 |
qiskit-aer | 0.9.1 |
qiskit-ignis | 0.7.0 |
qiskit-ibmq-provider | 0.18.3 |
qiskit-aqua | 0.9.5 |
qiskit | 0.34.0 |
qiskit-nature | 0.3.0 |
qiskit-finance | 0.2.1 |
qiskit-optimization | 0.2.3 |
qiskit-machine-learning | 0.2.1 |
System information | |
Python version | 3.9.10 |
Python compiler | Clang 13.0.0 (clang-1300.0.29.3) |
Python build | main, Jan 15 2022 11:48:00 |
OS | Darwin |
CPUs | 8 |
Memory (Gb) | 32.0 |
Wed Apr 13 19:59:49 2022 BST |
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.