Saltar al contenido principal

Comparar ajustes del transpilador

Package versions

El código de esta página fue desarrollado con los siguientes requisitos. Recomendamos usar estas versiones o más recientes.

qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1

Las diferentes configuraciones del transpilador proporcionan distintos tipos de optimización al circuito, a menudo a costa de un mayor tiempo de procesamiento clásico. Esta guía recorre el proceso completo de creación, transpilación y envío de circuitos para demostrar cómo probar el rendimiento de diversas configuraciones.

Ten en cuenta que la misma configuración podría mejorar los resultados de un circuito mientras perjudica otro. Asegúrate de inspeccionar los circuitos transpilados resultantes antes de ejecutarlos en hardware real.

Configurar y crear el circuito de ejemplo

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
# Create circuit to test transpiler on
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import grover_operator, DiagonalGate

# Use Statevector object to calculate the ideal output
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_histogram
from qiskit.transpiler import PassManager

from qiskit.circuit.library import XGate
from qiskit.quantum_info import hellinger_fidelity

Crea un circuito pequeño para que el transpilador intente optimizar. Este ejemplo crea un circuito que lleva a cabo el algoritmo de Grover con un oráculo que marca el estado 111. A continuación, simula la distribución ideal (lo que esperarías medir si ejecutaras esto en una computadora cuántica perfecta un número infinito de veces) para compararla más adelante.

oracle = DiagonalGate([1] * 7 + [-1])
qc = QuantumCircuit(3)
qc.h([0, 1, 2])
qc = qc.compose(grover_operator(oracle))

qc.draw(output="mpl", style="iqp")

Output of the previous code cell

ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()

plot_histogram(ideal_distribution)

Output of the previous code cell

Transpilar

A continuación, transpila los circuitos para la QPU. Compararás el rendimiento del transpilador con el optimization_level configurado en 0 (el más bajo) versus 3 (el más alto). El nivel de optimización más bajo hace el mínimo fundamental necesario para lograr que el circuito se ejecute en el dispositivo; mapea los qubits del circuito a los qubits del dispositivo y añade puertas SWAP para permitir todas las operaciones de dos qubits. El nivel de optimización más alto es mucho más inteligente y usa muchos trucos para reducir el recuento total de puertas. Dado que las puertas de múltiples qubits tienen altas tasas de error y los qubits pierden coherencia a lo largo del tiempo, los circuitos más cortos deberían dar resultados mejores.

important

Este ejemplo usa hardware de IBM Quantum®, pero puedes probarlo con cualquier QPU compatible con Qiskit. Los resultados podrían ser diferentes.

La siguiente celda transpila qc para ambos valores de optimization_level, imprime la cantidad de puertas de dos qubits, e inserta los circuitos transpilados en una lista. Algunos de los algoritmos del transpilador son aleatorios, por lo que establece una semilla para fines de reproducibilidad.

# Use Qiskit Runtime to run jobs on hardware
from qiskit_ibm_runtime import (
QiskitRuntimeService,
SamplerV2 as Sampler,
)
# Select the backend with the fewest number of jobs in the queue
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
backend.name
'ibm_marrakesh'
# Need to add measurements to the circuit
qc.measure_all()

# Find the correct two-qubit gate
twoQ_gates = set(["ecr", "cz", "cx"])
for gate in backend.basis_gates:
if gate in twoQ_gates:
twoQ_gate = gate

circuits = []
for optimization_level in [0, 3]:
pm = generate_preset_pass_manager(
optimization_level, backend=backend, seed_transpiler=0
)
t_qc = pm.run(qc)
print(
f"Two-qubit gates (optimization_level={optimization_level}): ",
t_qc.count_ops()[twoQ_gate],
)
circuits.append(t_qc)
Two-qubit gates (optimization_level=0):  21
Two-qubit gates (optimization_level=3): 12

Dado que las CNOT usualmente tienen una alta tasa de error, el circuito transpilado con optimization_level=3 debería de rendir mucho mejor.

Otra forma en que puedes mejorar el rendimiento es mediante el desacoplamiento dinámico, aplicando una secuencia de puertas a los qubits inactivos. Esto cancela algunas interacciones no deseadas con el entorno. La siguiente celda de código añade desacoplamiento dinámico al circuito transpilado con optimization_level=3 y lo agrega a la lista.

from qiskit_ibm_runtime.transpiler.passes.scheduling import (
ASAPScheduleAnalysis,
PadDynamicalDecoupling,
)

# Get gate durations so the transpiler knows how long each operation takes
durations = backend.target.durations()

# This is the sequence we'll apply to idling qubits
dd_sequence = [XGate(), XGate()]

# Run scheduling and dynamic decoupling passes on circuit
pm = PassManager(
[
ASAPScheduleAnalysis(durations),
PadDynamicalDecoupling(durations, dd_sequence),
]
)
circ_dd = pm.run(circuits[1])

# Add this new circuit to our list
circuits.append(circ_dd)
circ_dd.draw(output="mpl", style="iqp", idle_wires=False)

Output of the previous code cell

Ejecutar el circuito

En este punto, tienes una lista de circuitos transpilados con diferentes configuraciones. A continuación, ejecuta estos circuitos usando la primitiva Sampler y almacena los resultados en result.

sampler = Sampler(backend)
job = sampler.run(
[(circuit) for circuit in circuits], # sample all three circuits
shots=8000,
)
result = job.result()

Ver resultados

Finalmente, grafica los resultados de las ejecuciones del dispositivo contra la distribución ideal. Puedes observar que los resultados con optimization_level=3 están más cercanos a la distribución ideal debido al menor recuento de puertas, y optimization_level=3 + dd es aún más cercano debido al desacoplamiento dinámico.

binary_prob = [
{
k: v / res.data.meas.num_shots
for k, v in res.data.meas.get_counts().items()
}
for res in result
]
plot_histogram(
binary_prob + [ideal_distribution],
bar_labels=False,
legend=[
"optimization_level=0",
"optimization_level=3",
"optimization_level=3 + dd",
"ideal distribution",
],
)

Output of the previous code cell

Puedes confirmar esto calculando la fidelidad de Hellinger entre cada conjunto de resultados y la distribución ideal (un valor más alto es mejor, y 1 indica fidelidad perfecta).

for prob in binary_prob:
print(f"{hellinger_fidelity(prob, ideal_distribution):.3f}")
0.985
0.989
0.988

Próximos pasos

Recomendaciones