Skip to main content
IBM Quantum Platform

Depuración y seguimiento

La función build() transforma un circuito anotado en un circuito modelo y un par samplex. En un circuito complejo, puede resultar difícil determinar qué casilla del circuito original se corresponde con cada barrera de la plantilla, o con cada nodo del DAG de Samplex.

Samplomatic ofrece dos herramientas de rastreo complementarias:

  • Etiquetas de barrera : Cada barrera del circuito de la plantilla lleva una etiqueta que codifica su origen. Siempre están presentes, independientemente de si debug=True está activado o no.
  • Información de rastreo en los nodos de samplex : Cuando build() se invoca con debug=True, cada nodo de samplex lleva asociado un objeto TraceInfo que lo vincula con la caja o cajas que lo generaron.

Ambas herramientas resultan más útiles cuando los recuadros incluyen una anotación Tag , que asocia una ref cadena de caracteres al recuadro. Como alternativa, también se adjuntan las referencias InjectNoise .


Casillas de etiquetas

Una anotación Tag asocia una cadena ref a un recuadro. El aparece ref entonces en las etiquetas de barrera y en la información de rastreo de los nodos de Samplex.

El siguiente circuito de ejemplo tiene tres cajas: dos cajas CX etiquetadas en pares de qubits disjuntos y una caja de medición sin etiquetar y orientada hacia la derecha. El segundo cuadro CX también incluye una anotación InjectNoise : tanto su tag «ref» como su «noise» ref aparecerán en las etiquetas de la barrera. Las dos cajas CX abarcan diferentes qubits, lo que permite al optimizador Samplex fusionar sus nodos de propagación en nodos compartidos. Esto se muestra en la sección de información de seguimiento.

from qiskit.circuit import QuantumCircuit

from samplomatic import InjectNoise, Tag, Twirl, build

circuit = QuantumCircuit(4, 4)

with circuit.box([Twirl(), Tag("cx_ab")]):
    circuit.cx(0, 1)

with circuit.box([Twirl(), InjectNoise("cx_noise"), Tag("cx_cd")]):
    circuit.cx(2, 3)

with circuit.box([Twirl(), Tag("meas_box")]):
    circuit.measure(range(4), range(4))

circuit.draw("mpl")
../_images/f6b6fe7b60a39ba897814008443fa79b66213005462ab09f49ffc89aab1f3668.png

Etiquetas de barrera en el circuito de la plantilla

Al llamar build() a la función del circuito anterior se obtiene una plantilla cuyas barreras llevan etiquetas identificativas. Cada etiqueta tiene el siguiente formato {side}{scope}@{key=value&...}:

  • Lado : L = límite de alineación a la izquierda, M = límite del contenido del recuadro interior, R = límite de alineación a la derecha.
  • Ámbito : Un índice entero (o una lista separada por guiones bajos en el caso de cuadros anidados) que distingue entre varios cuadros situados en el mismo nivel de anidación.
  • Anotaciones : Una lista de pares key=value , @precedidos por el prefijo «-» y &separados por «-», derivados de las anotaciones de la caja. La clave tag procede de una Tag anotación y inject_noise procede de una anotación InjectNoise .

Las barreras de los recuadros sin etiquetar solo incluyen el lado y el ámbito (por ejemplo, L2), sin ningún sufijo @ .

template, samplex = build(circuit)
template.draw("mpl", fold=100)
../_images/e533d70a29504005a636aa27306d3ce3b1d2488358ac6a53f0674f2934698ab8.png

Las etiquetas de barrera también se pueden extraer mediante programación:

barrier_labels = [instr.operation.label for instr in template if instr.operation.name == "barrier"]
barrier_labels
['L0@tag=cx_ab',
 'M0@tag=cx_ab',
 'R0@tag=cx_ab',
 'L1@inject_noise=cx_noise&tag=cx_cd',
 'M1@inject_noise=cx_noise&tag=cx_cd',
 'R1@inject_noise=cx_noise&tag=cx_cd',
 'L2@tag=meas_box',
 'M2@tag=meas_box',
 'R2@tag=meas_box']

Información de seguimiento en los nodos de Samplex

Al pasar el parámetro debug=True «to», se adjunta build() información de seguimiento a cada nodo «samplex». El atributo trace_info de cada nodo es un TraceInfo objeto cuyo diccionario trace_refs asocia claves de anotación (por ejemplo, "tag" y "inject_noise") a conjuntos de cadenas de referencia. Los nodos que no tienen una anotación correspondiente en el recuadro tienen trace_info=None.

Cuando draw() se ejecuta esta función en un «samplex» compilado en modo depuración, al pasar el cursor por encima de cualquier nodo del gráfico interactivo, se muestra su información en trace_refs la ventana emergente al pasar el cursor.

template, samplex = build(circuit, debug=True)
samplex.draw()

La información de rastreo también se puede consultar mediante código. Fíjate en que algunos nodos contienen referencias tanto de 'cx_ab' como de 'cx_cd'. El optimizador Samplex fusionó sus nodos de propagación paralela porque las dos cajas abarcan qubits disjuntos y comparten un predecesor común procedente de la emisión de la caja de medición «right-dressed».

for node in samplex.graph.nodes():
    if node.trace_info is not None:
        tags = node.trace_info.trace_refs.get("tag", set())
        merged = " ← merged" if len(tags) > 1 else ""
        print(f"{type(node).__name__:40s}  tags={tags}{merged}")
TwirlSamplingNode                         tags={'meas_box'}
InjectNoiseNode                           tags={'cx_cd'}
CollectZ2ToOutputNode                     tags={'cx_cd'}
TwirlSamplingNode                         tags={'cx_cd'}
CombineRegistersNode                      tags={'cx_cd'}
CollectTemplateValues                     tags={'cx_cd'}
TwirlSamplingNode                         tags={'cx_ab'}
CombineRegistersNode                      tags={'cx_ab', 'cx_cd'} ← merged
CollectTemplateValues                     tags={'cx_ab', 'cx_cd'} ← merged

Para encontrar todos los nodos que proceden de un cuadro concreto, filtra por la clave "tag" :

tag_ref = "cx_ab"
matching_nodes = [
    node
    for node in samplex.graph.nodes()
    if node.trace_info is not None and tag_ref in node.trace_info.trace_refs.get("tag", set())
]

print(f"Nodes originating from box '{tag_ref}':")
for node in matching_nodes:
    print(f"  {type(node).__name__}")
Nodes originating from box 'cx_ab':
  TwirlSamplingNode
  CombineRegistersNode
  CollectTemplateValues

El DAG de Samplex

El conjunto «samplex» devuelto por build() es un grafo acíclico dirigido (DAG) en el que los arcos representan dependencias entre registros. Es decir, una arista del nodo A al nodo B significa que B debe esperar a que A haya realizado sus operaciones en los registros virtuales compartidos antes de que se le permita a B actuar. No se trata de un orden temporal como el del grafo dirigido acíclico (DAG) de un circuito cuántico. En cambio, el gráfico va desde los nodos encargados de generar aleatorizaciones hasta los nodos encargados de sintetizarlas como resultados.

Hay tres tipos de nodos, cada uno con un estilo visual distinto en el gráfico interactivo:

Forma
Color
Tipo
Rol
EstrellaRojoSamplingNodeCrea nuevos registros virtuales a partir de una distribución o de una entrada
CírculoVerdeEvaluationNodeTransforma, combina o propaga registros virtuales
PajaritaAzul / MoradoCollectionNodeLee los registros y escribe en las salidas sample()

La ejecución se lleva a cabo en tres fases:

  1. Todos los nodos de muestreo se ejecutan primero (en paralelo).
  2. A continuación se ejecutan los nodos de evaluación, en orden topológico (en paralelo dentro de cada generación).
  3. Los nodos de recogida se ejecutan en último lugar (en paralelo).

El diseño de Graphviz refleja esto. Los nodos de muestreo aparecen en la parte superior y los de recogida, en la parte inferior.


Información al pasar el cursor

Cada nodo de la visualización interactiva muestra una descripción emergente al pasar el cursor por encima. La descripción emergente contiene la siguiente información:

  • Nombre de la clase del nodo y su índice entero en el grafo.
  • Registrar los manifiestos que el nodo instancia, de los que lee, en los que escribe y que elimina. Estos te indican cómo fluyen los datos entre los nodos.
  • Detalles específicos de cada nodo, como el tipo de distribución de los nodos de muestreo, el operando de los nodos de multiplicación, los índices de los parámetros de plantilla de los nodos de colección, etc.
  • Referencias de trazado (solo cuando debug=True), que son las claves de anotación y las cadenas de referencia que vinculan el nodo con sus recuadros de origen.

Hacer clic en el gráfico y, a continuación, pasar el cursor por encima de cada nodo es la forma más rápida de comprender qué hace un nodo concreto sin tener que leer el código fuente.


Resumen de Samplex

Antes de analizar la visualización, print(samplex) ofrece un breve resumen escrito sobre el número de nodos, los datos de entrada necesarios y los resultados esperados.

template, samplex = build(circuit)
print(samplex)
Samplex(<17 nodes>)
  Inputs:
  - 'pauli_lindblad_maps.cx_noise' <PauliLindbladMap>: A PauliLindblad map acting on 2
      qubits, with 'num_terms_cx_noise' terms.

  Outputs:
    * 'measurement_flips.c' <bool['num_randomizations', 1, 4]>: Bit-flip corrections for
        measurement twirling.
    * 'parameter_values' <float32['num_randomizations', 24]>: Parameter values valid for an
        associated template circuit.
    * 'pauli_signs' <bool['num_randomizations', 1]>: Signs from sampled Pauli Lindblad
        maps, where boolean values represent the parity of the number of non-trivial factors in the
        sampled error that arise from negative rates. In other words, in order to implement basic
        PEC, the sign used to correct expectation values should be ``(-1)**bool_value``. The order
        matches the iteration order of boxes in the original circuit with noise injection
        annotations.

Revisar los registros

La llamada keep_registers=True a sample() conserva los VirtualRegister objetos intermedios que siguen activos al final del muestreo y los almacena en outputs.metadata["registers"]. Cada registro es una matriz de tipo « 2D » (de forma (num_subsystems, num_randomizations), con posibles dimensiones adicionales al final que definen la forma de la puerta) de elementos de grupo virtual.

Esto resulta útil para comprobar que las puertas virtuales se han combinado correctamente, o para examinar las muestras de Pauli o unitarias sin procesar antes de que se sinteticen en ángulos de rotación.

from qiskit.quantum_info import PauliLindbladMap

outputs = samplex.sample(
    {"pauli_lindblad_maps.cx_noise": PauliLindbladMap.identity(2)},
    num_randomizations=3,
    keep_registers=True,
)
for name, reg in outputs.metadata["registers"].items():
    print(f"{name}: type={reg.TYPE.value}, shape={reg.virtual_gates.shape}")
lhs_0: type=pauli, shape=(4, 3)
rhs_0: type=pauli, shape=(4, 3)
lhs_3: type=pauli, shape=(2, 3)
rhs_3: type=pauli, shape=(2, 3)
inject_noise_1: type=pauli, shape=(2, 3)
sign_1: type=z2, shape=(1, 3)
lhs_7: type=pauli, shape=(2, 3)
rhs_7: type=pauli, shape=(2, 3)
meas_prop_6: type=pauli, shape=(4, 3)
collect_10: type=u2, shape=(2, 3, 2, 2)
meas_prop_z2a_6: type=z2, shape=(4, 3)
collect_9: type=u2, shape=(4, 3, 2, 2)
collect_5: type=u2, shape=(2, 3, 2, 2)

Puedes consultar el contenido del estado final de un registro concreto. En el siguiente ejemplo, el registro es de tipo PauliRegister. Consulta el API documentation(/apidocs/index)__ para obtener más información sobre el formato de almacenamiento.

print("lhs_0:", outputs.metadata["registers"]["lhs_0"])
outputs.metadata["registers"]["lhs_0"].virtual_gates
lhs_0: PauliRegister(<4, 3>)
array([[0, 0, 1],
       [3, 1, 1],
       [3, 0, 0],
       [1, 0, 0]], dtype=uint8)

Cómo se asignan los nodos de Samplex a los parámetros de la plantilla

La matriz outputs["parameter_values"] devuelta por sample() tiene forma (num_randomizations, N), donde N coincide con len(template.parameters). Cada columna se corresponde con un parámetro del circuito modelo; en la i-ésima columna se introduce template.parameters[i].

Los nodos de colección CollectTemplateValues constituyen el enlace entre los registros virtuales y los parámetros de plantilla. Cada uno de estos nodos contiene información de índice que registra en qué columnas de la matriz de salida escribe. Esto permite rastrear qué subsistemas de puertas virtuales controlan qué parámetros de plantilla.

Consulta la guía « {Samplex inputs and outputs}samplex-io » para aprender a vincular los valores de los parámetros muestreados al circuito de la plantilla y a realizar experimentos.

from samplomatic.samplex.nodes import CollectTemplateValues

template, samplex = build(circuit, debug=True)
for node in samplex.graph.nodes():
    if isinstance(node, CollectTemplateValues):
        tags = node.trace_info.trace_refs.get("tag", set()) if node.trace_info else set()
        print(f"tags={tags}  →  template param indices: {node.template_idxs.tolist()}")
tags={'cx_cd'}  →  template param indices: [[6, 7, 8], [9, 10, 11]]
tags={'cx_ab', 'cx_cd'}  →  template param indices: [[12, 13, 14], [15, 16, 17], [18, 19, 20], [21, 22, 23]]
tags=set()  →  template param indices: [[0, 1, 2], [3, 4, 5]]

Etiquetado automático

Al utilizar generate_boxing_pass_manager(), el parámetro add_tags añade automáticamente Tag anotaciones a todos los recuadros. Hay tres modos disponibles:

  • "unique_instance": Asigna valores secuenciales ref (t0, t1, y así sucesivamente) a las casillas siguiendo el orden del circuito. Cada cuadro recibe un identificador único, ref independientemente de su estructura.
  • "unique_box": Calcula un hash estructural del contenido de cada recuadro y asigna el mismo hash a ref todos los recuadros estructuralmente equivalentes. Esto resulta útil para agrupar los recuadros por tipo en lugar de por posición.
  • "noise_ref": Copia la anotación InjectNoise de ref cada recuadro y solo etiqueta los recuadros que tengan una. Esto resulta útil cuando ya existen referencias de ruido significativas.

En el siguiente ejemplo se aplican "unique_instance" y "unique_box" a un circuito cuyas dos cajas CX son estructuralmente equivalentes. Con "unique_instance", cada uno recibe un distinto ref, mientras que con "unique_box", comparten uno.

from samplomatic.transpiler import generate_boxing_pass_manager

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

# unique_instance: every box gets a distinct tag ref
pm = generate_boxing_pass_manager(add_tags="unique_instance")
boxed = pm.run(base_circuit)
template, _ = build(boxed)

print("unique_instance barrier labels:")
for instr in template:
    if instr.operation.name == "barrier" and instr.operation.label:
        print(f"  {instr.operation.label}")
unique_instance barrier labels:
  L0@tag=t0
  M0@tag=t0
  R0@tag=t0
  L1@tag=t1
  M1@tag=t1
  R1@tag=t1
  L2@tag=t2
  M2@tag=t2
  R2@tag=t2
¿Le ha resultado útil esta página?
Informe de un error, de una errata o solicite contenido en GitHub.