Skip to main content
IBM Quantum Platform

Transpilador

La función generate_boxing_pass_manager() es una herramienta flexible y práctica para crear un esquema qiskit.transpiler.PassManager que permita agrupar las instrucciones del circuito en recuadros con anotaciones. Esto ofrece una alternativa sencilla a la agrupación y anotación manual de circuitos, y resulta útil para automatizar un flujo de trabajo, probar diferentes estrategias de agrupación de circuitos, etc.

Esta guía explica cómo utilizar generate_boxing_pass_manager() y sus argumentos. Para destacar los efectos de cada uno de los argumentos de la función, las secciones siguientes se refieren principalmente al circuito que se muestra a continuación.

from qiskit.circuit import Parameter, QuantumCircuit

circuit = QuantumCircuit(4, 7)
circuit.h(1)
circuit.h(2)
circuit.cz(1, 2)
circuit.h(1)
circuit.cx(1, 0)
circuit.cx(2, 3)
circuit.measure(range(1, 4), range(3))
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
for qubit in range(4):
    circuit.rz(Parameter(f"th_{qubit}"), qubit)
    circuit.rx(Parameter(f"phi_{qubit}"), qubit)
    circuit.rz(Parameter(f"lam_{qubit}"), qubit)
circuit.measure(range(4), range(3, 7))

circuit.draw("mpl", scale=0.8)
../_images/cf27e1ef03e51b2360c90165a4efeaa793da36083bb674774ca06c8181ef63dd.png

Agrupa las operaciones en casillas

El argumento se enable_gates puede establecer en True o False para especificar si las puertas de dos qubits deben agruparse en recuadros. Del mismo modo, enable_measures especifica si las mediciones deben agruparse o no. El siguiente fragmento de código muestra un ejemplo en el que se agrupan tanto las puertas como las mediciones.

from samplomatic.transpiler import generate_boxing_pass_manager

boxing_pass_manager = generate_boxing_pass_manager(
    enable_gates=True,
    enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)
../_images/b323dde8a5412e42359aef1831af92ed3702d2251547c663208dc084a2995276.png

Como se puede observar en la figura, el gestor de pasos crea recuadros de dos tipos: los que contienen una sola capa de puertas de dos qubits y los que contienen una sola capa de mediciones. Esta separación refleja las prácticas habituales en los protocolos de aprendizaje y mitigación del ruido, que suelen centrarse en capas de operaciones homogéneas. Las puertas de dos qubits y las mediciones se colocan en la casilla situada más a la izquierda en la que quepan, y cada puerta de un solo qubit se coloca en la misma casilla que la puerta de dos qubits o la medición a la que precede.

El siguiente código muestra un ejemplo en el que enable_gates se establece en False. Como se puede observar, las puertas de dos qubits no están agrupadas en recuadros, ni tampoco las puertas de un solo qubit que las preceden.

boxing_pass_manager = generate_boxing_pass_manager(
    enable_gates=False,
    enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)
../_images/3fc8e660b0c7eff1f5aa2e571d064f91b3eb2738c5990727e026a8ffe1101a1c.png

Elige cómo etiquetar las casillas

Todas las puertas de dos qubits y las cajas de medición del circuito devuelto tienen anotaciones con formato «left-dressed». En concreto, todos los recuadros que contienen puertas de dos qubits están marcados con un Twirl. En el caso de los cuadros de medición, los usuarios pueden elegir entre Twirl, BasisTranform (con establecido mode en "measure"), o ambos. El siguiente código genera un circuito en el que todas las casillas están giradas y, además, las casillas de medidas llevan la anotación BasisTranform.

boxing_pass_manager = generate_boxing_pass_manager(
    enable_gates=True,
    enable_measures=True,
    measure_annotations="all",
)
transpiled_circuit = boxing_pass_manager.run(circuit)

Prepara el circuito para la inyección de ruido

Esto te inject_noise_targets permite especificar qué casillas deben recibir una anotación InjectNoise . Por ejemplo, el siguiente código genera un circuito en el que los recuadros de las puertas de dos qubits tienen una InjectNoise anotación, pero los recuadros de medición no.

boxing_pass_manager = generate_boxing_pass_manager(
    enable_gates=True,
    enable_measures=True,
    inject_noise_targets="gates",
)
transpiled_circuit = boxing_pass_manager.run(circuit)

Si un circuito contiene dos o más casillas que son equivalentes en lo que respecta a las puertas de un solo qubit, todas ellas se anotan con una anotación InjectNoise que tiene el mismo valor ref. Por lo tanto, el número de ref«s» distintos en el circuito resultante es igual al número de casillas distintas.

El valor de inject_noise_strategy influye en las anotaciones InjectNoise . Son posibles los valores siguientes:

  • modifier_ref='': Se recomienda cuando no es necesario modificar los mapas de ruido antes de extraer muestras de ellos.
  • modifier_ref=ref: Se recomienda cuando es necesario escalar todos los mapas de ruido de forma uniforme con el mismo factor.
  • Un valor único de modifier_ref: Se recomienda cuando es necesario escalar cada mapa de ruido con un factor diferente.

El siguiente código genera un circuito en el que las casillas de las puertas de dos qubits tienen una InjectNoise anotación con valores modifier_ref únicos.

boxing_pass_manager = generate_boxing_pass_manager(
    enable_gates=True,
    enable_measures=True,
    inject_noise_targets="gates",
    inject_noise_strategy="individual_modification",
)
transpiled_circuit = boxing_pass_manager.run(circuit)

Elige una estrategia de giro

El gestor de pases de empaquetado comienza seleccionando qué puertas de entrelazado e instrucciones de medición deben aparecer en las mismas casillas. Una vez hecho esto, los recuadros no ocuparán, por lo general, todo el ancho; es probable que incluyan solo un subconjunto de los qubits que están activos en el circuito o presentes en el objetivo del backend. En las configuraciones típicas del gestor de pases de caja, dado que todos los qubits sobre los que actúa una instrucción de caja se someten a un proceso de «twirling» (incluso aquellos sobre los que no actúa ninguna instrucción dentro de la caja), el uso de cajas de anchura parcial afecta a los efectos de adaptación al ruido que produce el «twirling». Los qubits inactivos no giran y pueden, por ejemplo, acumular errores coherentes mientras se aplica el campo. El ofrece twirling_strategy opciones para ampliar el número de qubits que posee cada casilla, modificando así el número de qubits inactivos:

  • "active" (por defecto) no amplía los recuadros a ningún qubit inactivo.
  • "active_accum" amplía todos los recuadros a aquellos qubits sobre los que ya ha actuado una instrucción anterior del circuito. Esto resulta útil si quieres mantener los qubits al inicio del circuito en su estado fundamental durante el mayor tiempo posible, evitando hacerlos girar hasta que se activen.
  • "active_circuit" amplía todos los recuadros a aquellos qubits sobre los que actúa cualquier instrucción del circuito. Esto resulta útil si se dispone de un circuito estructurado con capas de entrelazamiento repetidas y se desea minimizar el número de casillas únicas, ya que es necesario aprender el ruido de cada una de ellas.
  • "all" amplía todos los recuadros a todos los qubits de todos los registros cuánticos del circuito. Esto resulta útil como extensión de aquellos casos en los "active_circuit" que se desea que todos los qubits de la QPU estén siempre en estado «twirled».

En el siguiente ejemplo, se aplica cada una de las estrategias al circuito base y, a continuación, se representan los resultados.

circuit = QuantumCircuit(5, 2)
circuit.h(range(4))
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.barrier()
circuit.h(range(4))
circuit.measure([1, 2], [0, 1])

circuit.draw("mpl", scale=0.6)
../_images/cdf089891dabe1650f1303c8c747aa8cf03cdde4dfc42c772fe2264bd7a49a1b.png
import matplotlib.pyplot as plt

plt.figure(figsize=(9, 6))
for idx, strategy in enumerate(["active", "active_accum", "active_circuit", "all"]):
    ax = plt.subplot(2, 2, idx + 1)
    pm = generate_boxing_pass_manager(twirling_strategy=strategy, remove_barriers="never")
    transpiled_circuit = pm.run(circuit)
    fig = transpiled_circuit.draw("mpl", scale=0.8, ax=ax)
    ax.set_title(f"twirling_strategy='{strategy}'")

plt.tight_layout()
../_images/b810ecc4c65822cf60b3d652792a4089c5f931ad1e8320eccdd89bc50a08ba6d.png

Especifica cómo abordar las barreras

Por defecto, las barreras se eliminan del circuito una vez que todas las puertas de entrelazamiento y las instrucciones de medición se han incluido en el recuadro según las instrucciones, incluidas sus extensiones debidas a la selección twirling_strategy , pero antes de que se añadan las puertas de un solo qubit al recuadro situado inmediatamente a su derecha, si es posible.

Por ejemplo, el siguiente circuito contiene una barrera:

circuit_with_barrier = QuantumCircuit(4)
circuit_with_barrier.h(range(4))
circuit_with_barrier.cz(0, 1)
circuit_with_barrier.barrier()
circuit_with_barrier.cz(2, 3)
circuit_with_barrier.measure_all()

circuit_with_barrier.draw("mpl", scale=0.8)
../_images/f2343807597155ab5301205803954b95d29ba0d7370440bd861439fd83d1a193.png

El comportamiento por defecto impide que las dos puertas de entrelazamiento acaben en la misma casilla, aunque permite que las puertas de un solo qubit sean absorbidas por la siguiente casilla disponible a pesar de la barrera.

boxing_pass_manager = generate_boxing_pass_manager()
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
../_images/7e0812ac6b3f2405c765338ab25c2e9179165a7257ef1d996bea99f94cb39ca6.png

Al establecer el valor en remove_barriers se 'never' conservan las barreras, de modo que las puertas de un solo qubit quedan limitadas.

boxing_pass_manager = generate_boxing_pass_manager(remove_barriers="never")

transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
../_images/6b57e625509dd8ca55fbd96994398b03ac2fe6f76f21535db9bd042d4c68c987.png

Por último, si se eliminan las barreras de inmediato, los enredadores ya no se ven obligados a acabar en cajas separadas.

boxing_pass_manager = generate_boxing_pass_manager(remove_barriers="immediately")

transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
../_images/9a44c59a05a44a3e8f0ce9b7edd3ccc2896c6a68fb00ed110a7e225f52a7ca73.png

Cómo funciona la estratificación

El gestor de pases de boxeo ejecuta dos pases clave de forma consecutiva:

  1. GroupGatesIntoBoxes — Organiza las puertas entrelazadas en grupos. En esta fase se omiten las puertas de un solo qubit; solo se agrupan las puertas de dos qubits.
  2. AbsorbSingleQubitGates — Incorpora cada puerta de un solo qubit en la casilla válida más cercana a su derecha.

Durante la estratificación, GroupGatesIntoBoxes recorre el circuito DAG en orden topológico y coloca cada puerta de dos qubits en el primer grupo cuyos qubits aún no estén ocupados. Hay tres tipos de instrucciones que actúan como delimitadores y vacían el grupo actual de sus qubits, lo que obliga a que las puertas posteriores aplicadas a esos qubits pasen a formar parte de un nuevo grupo:

  • Barreras
  • Instrucciones de la caja actual
  • Mediciones

Las puertas de un solo qubit no son delimitadores: no influyen en la agrupación y se deja que AbsorbSingleQubitGates se encargue de ello. Actualmente no se realiza ningún análisis de conmutatividad; la agrupación respeta tal cual el orden topológico del circuito.

¿Por qué remove_barriers="after_stratification" es el valor predeterminado recomendado?

El modo predeterminado es "after_stratification" porque combina dos ventajas:

  • Las barreras limitan la forma en que se agrupan las puertas de entrelazamiento (actúan como delimitadores durante la estratificación).
  • Las barreras se eliminan antes de las ejecuciones AbsorbSingleQubitGates , por lo que las puertas de un solo qubit pueden seguir pasando por las posiciones donde antes se encontraban las barreras y ser absorbidas por la caja más cercana.

Por el contrario:

  • "never" mantiene barreras a lo largo de todo el recorrido, lo que atrapa las puertas de un solo qubit entre una barrera y una caja, impidiendo su absorción y dejando rotaciones sin desenredar en el circuito.
  • "immediately" elimina las barreras antes de la estratificación, por lo que no influyen en la agrupación.
  • "finally" Resulta útil cuando se desean barreras en el circuito de salida (por ejemplo, para la visualización), pero las puertas de un solo qubit situadas entre una barrera y una caja no se absorben.

Circuitos ISA de caja con barreras

Los circuitos con una estructura repetitiva —como las simulaciones hamiltonianas «trotterizadas», los ansatzes variacionales y las secuencias de amplificación de errores— suelen crearse mediante programación y, a continuación, transpilarse a formato ISA. Añadir bloques manualmente a un circuito transpilado resulta engorroso, por lo que el flujo de trabajo habitual es el siguiente:

  1. Construye el circuito lógico, insertando barreras en los límites de las capas que quieras que se conviertan en límites de las cajas.
  2. Compilar en circuitos ISA. El transpilador de Qiskit conserva las barreras mediante el enrutamiento y la optimización.
  3. Compite en generate_boxing_pass_manager() el circuito ISA.

Las barreras son necesarias cuando el extremo de una capa no cubre todos los qubits. De lo contrario, el algoritmo codicioso colocaría las puertas de la siguiente capa —en esos qubits «libres»— en el mismo grupo. A modo de ejemplo concreto, consideremos una simulación ZZ de Heisenberg en dos pasos en un anillo de 5 qubits. Cada paso de Trotter tiene tres capas de unión: uniones pares (0,1), (2,3), uniones impares (1,2), (3,4)y la unión envolvente (4,0). Tras la capa de enlaces impares, los qubits 0 y 4 quedan libres. Sin una barrera, el enlace envolvente (4,0) se agrupa de forma «codiciosa» con los enlaces del siguiente paso de Trotter que utilizan esos qubits libres, mezclando puertas de diferentes pasos en la misma caja.

from qiskit.circuit import Parameter, QuantumCircuit

n = 5  # 5-qubit ring: bonds (0,1), (1,2), (2,3), (3,4), (4,0)
dt = Parameter("dt")
heisenberg = QuantumCircuit(n)

for step in range(2):
    # Layer 1: even bonds (0,1) and (2,3)
    for idx in range(0, n - 1, 2):
        heisenberg.rzz(dt, idx, idx + 1)
    heisenberg.barrier()
    # Layer 2: odd bonds (1,2) and (3,4) -- qubits 0 and 4 are now free
    for idx in range(1, n - 1, 2):
        heisenberg.rzz(dt, idx, idx + 1)
    heisenberg.barrier()
    # Layer 3: wrap bond (4,0) -- both qubits were free after layer 2
    heisenberg.rzz(dt, n - 1, 0)
    if step < 1:
        heisenberg.barrier()  # separates the two Trotter steps

heisenberg.measure_all()

heisenberg.draw("mpl", scale=0.7)
../_images/172fa55c9f02964fef32fc045a50b86971126be728602b3f1e4b2437df2580b7.png

El siguiente código compila el circuito a formato ISA, suponiendo que el dispositivo subyacente tiene CX como entrelazador nativo. Ten en cuenta que las barreras se conservan tras la transpilación y siguen siendo visibles en el circuito de salida.

from qiskit.transpiler import generate_preset_pass_manager

preset_pm = generate_preset_pass_manager(
    basis_gates=["rz", "sx", "cx"],
    coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]],
    optimization_level=1,
)
isa_circuit = preset_pm.run(heisenberg)
isa_circuit.draw("mpl", scale=0.5, fold=200)
../_images/e9a3fbd6ab1a91051c5cd0273d92ecebc6ae8ebbc370c30bc55daee1fd28bab7.png

A continuación, aplica el gestor de pases de boxing al circuito ISA. Cada una de las tres capas de enlaces se convierte en una caja independiente en cada paso de Trotter.

boxing_pm = generate_boxing_pass_manager()
boxed_isa = boxing_pm.run(isa_circuit)
boxed_isa.draw("mpl", scale=0.5, fold=100)
../_images/b59e3c047264b3340d9af0f1ca74ad9f7d4c1705d4a5176e441346db9b678213.png

Incorporar el gestor de pasadas de Samplomatic a los gestores de pasadas predefinidos de Qiskit

El código que aparece a continuación muestra cómo incorporar generate_boxing_pass_manager() en un gestor de pasadas predefinido de Qiskit. El circuito transpilado es ISA y contiene recuadros.

from qiskit.transpiler import generate_preset_pass_manager

preset_pass_manager = generate_preset_pass_manager(
    basis_gates=["rz", "sx", "cx"],
    coupling_map=[[0, 1], [1, 2]],
    optimization_level=0,
)
boxing_pass_manager = generate_boxing_pass_manager()

# Run the boxing pass manager after the scheduling stage
preset_pass_manager.post_scheduling = boxing_pass_manager

circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(0, 2)
circuit.measure_all()

transpiled_circuit = preset_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)
../_images/fe943b034588a5354a87abcf51b8d805c36f4f2b30a73a39c5886bce9e156b04.png

Este patrón también se integra de forma natural con el flujo de trabajo de ISA Boxing basado en barreras. El gestor de pases de boxeo se ejecuta como un paso posterior a la programación, una vez que las barreras del circuito lógico original han superado la transpilación, de modo que guían la estratificación tal y como se espera.

preset_pm = generate_preset_pass_manager(
    basis_gates=["rz", "sx", "cx"],
    coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]],
    optimization_level=1,
)
preset_pm.post_scheduling = generate_boxing_pass_manager()

boxed_heisenberg = preset_pm.run(heisenberg)
boxed_heisenberg.draw("mpl", scale=0.5, fold=100)
../_images/4905bf1df63cc83fa581ed006a93beb709be504a4f59fc7f663f4efde62da175.png

Revisar y modificar las anotaciones

Tras el auto-boxing, es posible que quieras revisar las anotaciones de determinadas casillas o modificarlas. Por ejemplo, para añadir ruido, eliminar anotaciones o modificar un grupo giratorio. Samplomatic ofrece un conjunto de funciones de utilidad en samplomatic.utils para este fin. Todas ellas devuelven un nuevo circuito sin modificar el original.

extend_annotations() añade una o varias anotaciones a cada recuadro del circuito.

from samplomatic import InjectNoise, Twirl
from samplomatic.utils import extend_annotations

boxed = generate_boxing_pass_manager().run(circuit_with_barrier)

# Add InjectNoise to every box
with_noise = extend_annotations(boxed, InjectNoise("my_noise_ref"))
with_noise.draw("mpl", scale=0.8)
../_images/7e0812ac6b3f2405c765338ab25c2e9179165a7257ef1d996bea99f94cb39ca6.png

filter_annotations() conserva únicamente las anotaciones de los tipos especificados y elimina el resto. replace_annotations() aplica una función a cada anotación; dicha función devuelve una lista de anotaciones para sustituir en su lugar (devuelve «to [] remove» para eliminar, una lista de un solo elemento para sustituir o una lista de varios elementos para ampliar).

from samplomatic.utils import filter_annotations, replace_annotations

# Keep only Twirl annotations, drop everything else
twirl_only = filter_annotations(with_noise, Twirl)

# Replace the twirling group on every Twirl annotation
updated = replace_annotations(
    twirl_only, lambda a: [Twirl(group="local_c1")] if isinstance(a, Twirl) else [a]
)

La función map_annotations() de uso general acepta cualquier objeto ejecutable que asigne una lista de anotaciones a una nueva lista, lo que te permite tener un control total sobre las transformaciones de cada cuadro. strip_annotations() es una función de conveniencia que elimina todas las anotaciones de cada cuadro.

Para realizar cambios masivos, como añadir InjectNoise a todas las cajas de compuertas, el inject_noise_targets parámetro de generate_boxing_pass_manager() es la opción más concisa. Estas herramientas resultan útiles para realizar modificaciones selectivas por casilla que no se pueden llevar a cabo mediante los parámetros del gestor de pasadas.


Construir un circuito

Cada gestor de pases creado por generate_boxing_pass_manager() devuelve circuitos que pueden convertirse correctamente en un par de plantillas/samplex mediante build(). Por ejemplo, el siguiente código llama a la función build() en un circuito generado por un gestor de pases de boxeo.

from samplomatic import build

boxing_pass_manager = generate_boxing_pass_manager(
    enable_gates=True,
    enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)

template, samplex = build(transpiled_circuit)

Para garantizar que cualquier circuito transpilado se pueda compilar correctamente, los gestores de pasadas saben cómo incluir bloques adicionales cuando es necesario. Por ejemplo, consideremos el circuito que se muestra a continuación, que termina con un qubit no medido 0.

circuit_with_unmeasured_qubit = QuantumCircuit(4, 3)
circuit_with_unmeasured_qubit.cz(0, 1)
circuit_with_unmeasured_qubit.cz(2, 3)
for qubit in range(4):
    circuit_with_unmeasured_qubit.rz(Parameter(f"th_{qubit}"), qubit)
    circuit_with_unmeasured_qubit.rx(Parameter(f"phi_{qubit}"), qubit)
    circuit_with_unmeasured_qubit.rz(Parameter(f"lam_{qubit}"), qubit)
circuit_with_unmeasured_qubit.measure(range(1, 4), range(3))

circuit_with_unmeasured_qubit.draw("mpl", scale=0.8)
../_images/0452779799470d2deb4d32ce19f5aa28dfe8055a064e67c0e8e5684389845b0f.png

Si se dibujaran recuadros con orientación hacia la izquierda alrededor de las puertas y las medidas, se obtendría un circuito con puertas virtuales sin recoger en el qubit 0, y la ejecución build() de este circuito daría lugar a un error. Para evitarlo, los gestores de pases devueltos por pueden generate_boxing_pass_manager() añadir recuadros con alineación a la derecha que actúen como colectores. Por ejemplo, en el código siguiente, el qubit 0 termina en un recuadro con el borde derecho que recoge la puerta virtual no recogida. Las puertas de un solo qubit que actúan sobre el qubit también 0 se colocan dentro de la caja con el fin de minimizar la profundidad del circuito resultante.

boxing_pass_manager = generate_boxing_pass_manager(
    enable_gates=True,
    enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit_with_unmeasured_qubit)
transpiled_circuit.draw("mpl", scale=0.8)
../_images/711ee3c014edf6f70ede5dfa849494286e96870b3bd70a2a89f687d83cc5bfc1.png

En otro ejemplo, se añade una caja «correctamente estructurada» para recopilar las puertas virtuales que, de otro modo, no se recopilarían debido a que las mediciones no están estructuradas en cajas.

boxing_pass_manager = generate_boxing_pass_manager(
    enable_gates=True,
    enable_measures=False,
)
transpiled_circuit = boxing_pass_manager.run(circuit_with_unmeasured_qubit)
transpiled_circuit.draw("mpl", scale=0.8)
../_images/d59d0c0af3c47f474a964a455533b99e09a65364454ec04ced7581e6afac73ab.png
¿Le ha resultado útil esta página?
Informe de un error, de una errata o solicite contenido en GitHub.