Skip to main content
IBM Quantum Platform

Comparar la configuración del transpilador

  • El código de esta página se ha desarrollado teniendo en cuenta los siguientes requisitos. Recomendamos utilizar estas versiones o posteriores.

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

Los distintos ajustes del transpilador ofrecen diferentes tipos de optimización del circuito, a menudo a costa de un mayor tiempo de procesamiento clásico. Esta guía describe paso a paso todo el proceso de creación, transpilación y envío de circuitos para demostrar cómo evaluar el rendimiento de diferentes configuraciones.

Ten en cuenta que el mismo ajuste podría mejorar los resultados de un circuito y, al mismo tiempo, perjudicar a otro. Asegúrate de revisar los circuitos transpilados resultantes antes de ejecutarlos en hardware real.


Configurar y crear un circuito de prueba

# 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 pequeño circuito para que el transpilador intente optimizarlo. Este ejemplo crea un circuito que ejecuta el algoritmo de Grover con un oráculo que marca el estado 111. A continuación, simule la distribución ideal (lo que esperaría medir si ejecutara esto en un ordenador cuántico perfecto un número infinito de veces) para compararlo 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:

Output of the previous code cell
ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()

plot_histogram(ideal_distribution)

Output:

Output of the previous code cell

Transpilar

A continuación, transpile los circuitos para la QPU. Compararás el rendimiento del transpilador con optimization_level establecido en 0 (el más bajo) frente a 3 (el más alto). El nivel de optimización más bajo hace lo mínimo necesario para que el circuito funcione en el dispositivo; asigna los qubits del circuito a los qubits del dispositivo y añade puertas de intercambio para permitir todas las operaciones de dos qubits. El nivel de optimización más alto es mucho más inteligente y utiliza 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 se descoheren con el tiempo, los circuitos más cortos deberían dar mejores resultados.

Important

En este ejemplo se utiliza un IBM Quantum®, pero puedes probarlo en cualquier QPU compatible con Qiskit. Tus resultados pueden ser diferentes.

La siguiente celda transpilada qc para ambos valores de optimization_level, imprime el número de puertas de dos qubits y añade los circuitos transpilados a una lista. Algunos de los algoritmos del transpilador son aleatorios, por lo que establece una semilla para garantizar la 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

Output:

'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)

Output:

Two-qubit gates (optimization_level=0):  21
Two-qubit gates (optimization_level=3):  12

Dado que los CNOT suelen tener una alta tasa de error, el circuito transpilado con optimization_level=3 debería funcionar mucho mejor.

Otra forma de mejorar el rendimiento es mediante el desacoplamiento dinámico, aplicando una secuencia de puertas a los qubits inactivos. Esto elimina algunas interacciones no deseadas con el entorno. La siguiente celda añade un desacoplamiento dinámico al circuito compilado con optimization_level=3 y lo añade 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:

Output of the previous code cell

Realiza el circuito

En este momento, dispones de una lista de circuitos compilados con diferentes configuraciones. A continuación, ejecuta estos circuitos utilizando la primitiva «Sampler» y guarda 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

Por último, representa gráficamente los resultados de las mediciones del dispositivo en comparación con la distribución ideal. Se puede observar que los resultados con optimization_level=3 se acercan más a la distribución ideal debido al menor número de puertas, y optimization_level=3 + dd se acerca aún más gracias 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:

Output of the previous code cell

Puedes confirmarlo calculando la fidelidad de Hellinger entre cada conjunto de resultados y la distribución ideal (cuanto más alta, mejor, y 1 es la fidelidad perfecta).

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

Output:

0.985
0.989
0.988

Próximos pasos

Recomendaciones
¿Le ha resultado útil esta página?
Informe de un error, errata o solicite contenidos en GitHub .