Skip to main content
IBM Quantum Platform

Etapas del transpilador

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

    qiskit[all]~=2.5.0
    

Esta página describe las etapas del pipeline de transpilación preconstruido en el SDK de Qiskit. Hay seis etapas:

  1. init
  2. layout
  3. routing
  4. translation
  5. optimization
  6. scheduling

La función generate_preset_pass_manager crea un gestor de pases por etapas preestablecido compuesto por estas etapas. Los pases específicos que componen cada etapa dependen de los argumentos pasados a generate_preset_pass_manager. optimization_level es un argumento posicional que debe especificarse; es un número entero que puede ser 0, 1, 2 ó 3. Los valores más altos indican una optimización más intensa pero más costosa (véase Transpilación por defecto y opciones de configuración ).

La forma recomendada de transpilar un circuito es crear un administrador de pases por etapas preestablecido y luego ejecutar ese administrador de pases en el circuito, como se describe en Transpilar con administradores de pases. Sin embargo, una alternativa más sencilla pero menos personalizable es utilizar la función transpile función Esta función acepta el circuito directamente como argumento. Al igual que con generate_preset_pass_manager, los pases de transpilador específicos utilizados dependen de los argumentos, como optimization_level, pasados a transpile. De hecho, internamente la función transpile llama a generate_preset_pass_manager para crear un gestor de pases por etapas preestablecido y lo ejecuta en el circuito.


Etapa inicial

Esta primera etapa hace muy poco por defecto y es principalmente útil si desea incluir sus propias optimizaciones iniciales. Dado que la mayoría de los algoritmos de trazado y encaminamiento sólo están diseñados para trabajar con puertas de uno o dos qubits, esta etapa también se utiliza para traducir cualquier puerta que opere con más de dos qubits, en puertas que sólo operen con uno o dos qubits.

Para más información sobre cómo implementar tus propias optimizaciones iniciales para esta etapa, consulta la sección sobre plugins y personalización de gestores de pases.


Etapa de diseño

La siguiente etapa consiste en la configuración o la conectividad del backend al que se enviará el circuito. En general, los circuitos cuánticos son entidades abstractas cuyos qubits son representaciones «virtuales» o «lógicas» de los qubits reales que se utilizan en los cálculos. Para ejecutar una secuencia de puertas, es necesaria una correspondencia biunívoca entre los qubits «virtuales» y los qubits «físicos» de un dispositivo cuántico real. Esta correspondencia se almacena como un Layout objeto y forma parte de las restricciones definidas en la arquitectura del conjunto de instrucciones (ISA) de un backend.

Esta imagen muestra cómo se asignan los qubits desde la representación esquemática a un diagrama que representa cómo están conectados los qubits en la QPU. Asignación
de qubits

La elección del mapeado es extremadamente importante para minimizar el número de operaciones SWAP necesarias para mapear el circuito de entrada en la topología del dispositivo y garantizar que se utilizan los qubits mejor calibrados. Dada la importancia de esta etapa, los gestores de pases prefijados prueban distintos métodos para encontrar la mejor disposición. Normalmente, esto implica dos pasos: primero, intentar encontrar una disposición "perfecta" (una disposición que no requiera ninguna operación SWAP) y, después, una pasada heurística que intenta encontrar la mejor disposición a utilizar si no se puede encontrar una disposición perfecta. Para este primer paso se suelen utilizar dos sitios web: Passes :

  • TrivialLayout:Asigna de forma ingenua cada qubit virtual al mismo qubit físico numerado en el dispositivo (es decir, [0,1,1,3] -> [0,1,1,3] ). Este es un comportamiento histórico que solo se utiliza en optimzation_level=1 para intentar encontrar un diseño perfecto. Si falla, se intenta a continuación VF2Layout .
  • VF2Layout: Se trata de un AnalysisPass que selecciona un trazado ideal tratando esta etapa como un problema de isomorfismo de subgrafos, resuelto por el algoritmo VF2++. Si se encuentra más de un trazado, se ejecuta una heurística de puntuación para seleccionar el trazado con el error medio más bajo.

A continuación, para la etapa heurística, se utilizan dos pasadas por defecto:

  • DenseLayout: Busca el subgrafo del dispositivo con mayor conectividad y que tenga el mismo número de qubits que el circuito (se utiliza para el nivel de optimización 1 si hay operaciones de flujo de control (como IfElseOp ) presentes en el circuito).
  • SabreLayout: Este paso selecciona un diseño partiendo de un diseño aleatorio inicial y ejecutando repetidamente el SabreSwap algoritmo. Este paso solo se utiliza en los niveles de optimización 1, 2 y 3 si no se encuentra un diseño perfecto mediante el VF2Layout paso. Para obtener más detalles sobre este algoritmo, consulte el artículo « arXiv:1809.02573 » (El algoritmo de optimización de la red de control de la red de control de la red de control de la red de control de la red de control de la red de control de la red de control de la red de

Etapa de enrutamiento

Para implementar una puerta de dos qubits entre qubits que no están conectados directamente en un dispositivo cuántico, se deben insertar una o más puertas SWAP en el circuito para mover los estados de los qubits hasta que sean adyacentes en el mapa de puertas del dispositivo. Cada puerta SWAP representa una operación cara y ruidosa de realizar. Por tanto, encontrar el número mínimo de puertas SWAP necesarias para mapear un circuito en un dispositivo dado es un paso importante en el proceso de transpilación. En aras de la eficacia, esta etapa suele calcularse por defecto junto con la etapa de maquetación, pero son lógicamente distintas. La etapa de diseño selecciona los qubits hardware que se van a utilizar, mientras que la etapa de enrutamiento inserta la cantidad adecuada de puertas SWAP para ejecutar los circuitos utilizando el diseño seleccionado.

Sin embargo, encontrar el mapa SWAP óptimo es difícil. De hecho, es un problema NP-difícil y, por tanto, prohibitivamente caro de calcular para todos los dispositivos cuánticos y circuitos de entrada, salvo los más pequeños. Para evitarlo, Qiskit utiliza un algoritmo heurístico estocástico llamado SabreSwap para calcular una asignación SWAP buena, aunque no necesariamente óptima. El uso de un método estocástico significa que no se garantiza que los circuitos generados sean los mismos en repetidas ejecuciones. De hecho, al ejecutar repetidamente el mismo circuito se produce una distribución de la profundidad del circuito y del número de compuertas a la salida. Es por esta razón que muchos usuarios optan por ejecutar la función de enrutamiento (o la totalidad de StagedPassManager) muchas veces y seleccionar los circuitos de menor profundidad de la distribución de salidas.

Por ejemplo, tomemos un circuito GHZ de 15 qubits ejecutado 100 veces, utilizando un "mal" (desconectado) initial_layout.

import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.providers.fake_provider import GenericBackendV2

backend = GenericBackendV2(15)


ghz = QuantumCircuit(15)
ghz.h(0)
ghz.cx(0, range(1, 15))

depths = []
for seed in range(100):
    pass_manager = generate_preset_pass_manager(
        optimization_level=1,
        backend=backend,
        layout_method="trivial",  # Fixed layout mapped in circuit order
        seed_transpiler=seed,  # For reproducible results
    )
    depths.append(pass_manager.run(ghz).depth())

plt.figure(figsize=(8, 6))
plt.hist(depths, align="left", color="#AC557C")
plt.xlabel("Depth", fontsize=14)
plt.ylabel("Counts", fontsize=14)

Output:

Text(0, 0.5, 'Counts')
Output of the previous code cell

Esta amplia distribución demuestra lo difícil que es para el mapeador SWAP calcular la mejor asignación. Para entenderlo mejor, veamos tanto el circuito que se ejecuta como los qubits elegidos en el hardware.

ghz.draw("mpl", idle_wires=False)

Output:

Output of the previous code cell
from qiskit.visualization import plot_circuit_layout

# Plot the hardware graph and indicate which hardware qubits were chosen to run the circuit
transpiled_circ = pass_manager.run(ghz)
plot_circuit_layout(transpiled_circ, backend)

Output:

Output of the previous code cell

Como puedes ver, este circuito tiene que ejecutar una puerta de dos qubits entre los qubits 0 y 14, que están muy separados en el gráfico de conectividad. Por lo tanto, para ejecutar este circuito es necesario insertar puertas SWAP para ejecutar todas las puertas de dos qubits utilizando el paso SabreSwap .

Obsérvese también que el algoritmo SabreSwap es diferente del método SabreLayout más amplio de la etapa anterior. Por defecto, SabreLayout ejecuta tanto el trazado como el enrutado, y devuelve el circuito transformado. Esto se hace por algunas razones técnicas concretas que se especifican en la página de referencia de la API del pase.


Etapa de traducción

Al escribir un circuito cuántico, puedes utilizar cualquier puerta cuántica (operación unitaria) que desees, junto con un conjunto de operaciones que no sean puertas, como la medición de qubits o las instrucciones de reinicio. Sin embargo, la mayoría de los dispositivos cuánticos solo admiten de forma nativa unas pocas operaciones de puertas cuánticas y operaciones que no son de puertas. Estas puertas nativas forman parte de la definición de la ISA de un destino, y esta etapa del preajuste PassManagers traduce (o despliega ) las puertas especificadas en un circuito a las puertas de base nativa de un backend determinado. Este es un paso importante, ya que permite que el circuito sea ejecutado por el backend, pero suele provocar un aumento de la profundidad y del número de puertas lógicas.

Cabe destacar dos casos especiales que ayudan a ilustrar lo que hace esta etapa.

  1. Si una puerta SWAP no es una puerta nativa del backend de destino, se necesitan tres puertas CNOT:
print("native gates:" + str(sorted(backend.operation_names)))
qc = QuantumCircuit(2)
qc.swap(0, 1)
qc.decompose().draw("mpl")

Output:

native gates:['cx', 'delay', 'id', 'measure', 'reset', 'rz', 'sx', 'x']
Output of the previous code cell

Como producto de tres puertas CNOT, un SWAP es una operación cara de realizar en dispositivos cuánticos ruidosos. Sin embargo, estas operaciones suelen ser necesarias para incrustar un circuito en las limitadas conectividades de puerta de muchos dispositivos. Por tanto, minimizar el número de puertas SWAP en un circuito es un objetivo primordial en el proceso de transpilación.

  1. Una puerta Toffoli, o puerta controlada-controlada-no (ccx), es una puerta de tres qubits. Dado que nuestro conjunto de puertas base sólo incluye puertas de uno y dos qubits, esta operación debe descomponerse. Sin embargo, es bastante costoso:
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.decompose().draw("mpl")

Output:

Output of the previous code cell

Por cada puerta Toffoli de un circuito cuántico, el hardware puede ejecutar hasta seis puertas CNOT y un puñado de puertas de un solo qubit. Este ejemplo demuestra que cualquier algoritmo que haga uso de múltiples puertas Toffoli acabará siendo un circuito con una gran profundidad y, por tanto, se verá sensiblemente afectado por el ruido.


Etapa de optimización

Esta etapa se centra en la descomposición de los circuitos cuánticos en el conjunto de puertas base del dispositivo de destino, y debe luchar contra el aumento de profundidad de las etapas de diseño y enrutamiento. Afortunadamente, existen muchas rutinas para optimizar los circuitos combinando o eliminando puertas. En algunos casos, estos métodos son tan eficaces que los circuitos de salida tienen menor profundidad que los de entrada, incluso después de la disposición y el encaminamiento a la topología de hardware. En otros casos, no se puede hacer mucho, y el cálculo puede ser difícil de realizar en dispositivos ruidosos. En esta fase empiezan a diferenciarse los distintos niveles de optimización.

Además, esta etapa también ejecuta algunas comprobaciones finales para asegurarse de que todas las instrucciones del circuito están compuestas por las puertas base disponibles en el backend de destino.

El siguiente ejemplo, que utiliza un estado GHZ, muestra los efectos de diferentes niveles de optimización en la profundidad del circuito y el número de puertas.

Note

El resultado de la transpilación varía debido al mapeador estocástico SWAP. Por lo tanto, es probable que los números que aparecen a continuación cambien cada vez que ejecute el código.

15-qubit GHZ state
15-qubit GHZ state before transpilation

El siguiente código construye un estado GHZ de 15 qubits y compara la optimization_levels de transpilación en términos de profundidad de circuito resultante, recuento de puertas y recuento de puertas multi-qubit.

ghz = QuantumCircuit(15)
ghz.h(0)
ghz.cx(0, range(1, 15))

depths = []
gate_counts = []
multiqubit_gate_counts = []
levels = [str(x) for x in range(4)]
for level in range(4):
    pass_manager = generate_preset_pass_manager(
        optimization_level=level,
        backend=backend,
        seed_transpiler=1234,
    )
    circ = pass_manager.run(ghz)
    depths.append(circ.depth())
    gate_counts.append(sum(circ.count_ops().values()))
    multiqubit_gate_counts.append(circ.count_ops()["cx"])

fig, (ax1, ax2) = plt.subplots(2, 1)
ax1.bar(levels, depths, label="Depth")
ax1.set_xlabel("Optimization Level")
ax1.set_ylabel("Depth")
ax1.set_title("Output Circuit Depth")
ax2.bar(levels, gate_counts, label="Number of Circuit Operations")
ax2.bar(levels, multiqubit_gate_counts, label="Number of CX gates")
ax2.set_xlabel("Optimization Level")
ax2.set_ylabel("Number of gates")
ax2.legend()
ax2.set_title("Number of output circuit gates")
fig.tight_layout()
plt.show()

Output:

Output of the previous code cell

Planificación

Esta última etapa sólo se ejecuta si se solicita explícitamente (de forma similar a la etapa Init) y no se ejecuta por defecto (aunque se puede especificar un método estableciendo el argumento scheduling_method al llamar a generate_preset_pass_manager). La etapa de programación se suele utilizar una vez que el circuito se ha traducido a la base de destino, se ha asignado al dispositivo y se ha optimizado. Estos pases se centran en contabilizar todos los tiempos muertos de un circuito. A un alto nivel, el paso de programación se puede considerar como la inserción explícita de instrucciones de retardo para tener en cuenta el tiempo de inactividad entre las ejecuciones de las puertas y para inspeccionar cuánto tiempo se ejecutará el circuito en el backend.

A continuación se muestra un ejemplo:

ghz = QuantumCircuit(5)
ghz.h(0)
ghz.cx(0, range(1, 5))


# Use fake backend
backend = GenericBackendV2(5)

# Run with optimization level 3 and 'asap' scheduling pass
pass_manager = generate_preset_pass_manager(
    optimization_level=3,
    backend=backend,
    scheduling_method="asap",
    seed_transpiler=1234,
)


circ = pass_manager.run(ghz)
circ.draw(output="mpl", idle_wires=False)

Output:

Output of the previous code cell
Circuito con instrucciones de retardo

El transpilador insertó instrucciones Delay para tener en cuenta el tiempo de inactividad de cada qubit. Para hacernos una mejor idea de la temporización del circuito también podemos mirarlo con la función timeline.draw() :

timeline.draw (): vista del mismo circuito

La programación de un circuito consta de dos partes: el análisis y la asignación de restricciones, seguidos de una pasada de relleno. La primera parte requiere ejecutar un pase de análisis de programación (por defecto es ALAPSchedulingAnalysis), que analiza el circuito y registra la hora de inicio de cada instrucción del circuito en una programación. Una vez que el circuito tiene una programación inicial, se pueden ejecutar pasadas adicionales para tener en cuenta cualquier restricción de tiempo en el backend de destino. Por último, un pase de relleno como PadDelay o PadDynamicalDecoupling puede ejecutarse.


Próximos pasos

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