Saltar al contenido principal
Este proyecto está en fase beta. ¿Encontraste un error? Abre un issue en GitHub. ¿Tienes una pregunta o una idea? Pregunta en Discussions — ¡nos encantaría tu opinión!
¿Cómo es la calidad de la traducción en esta página?

Cómo interpretar las visualizaciones de shaded lightcone

El paquete ofrece dos formas de ver un shaded lightcone:

  • draw_shaded_lightcone superpone las cotas al diagrama del circuito. Es lo mejor para circuitos sobre una cadena 1D de qubits, donde el diagrama del circuito y la disposición física coinciden.

  • animate_shaded_lightcone proyecta las cotas sobre el mapa de acoplamiento de la QPU y recorre el circuito capa por capa. Es lo mejor cuando la conectividad de los qubits es más que una línea 1D, donde un diagrama de circuito deja de transmitir qué qubits son vecinos.

Ambas visualizaciones muestran los mismos números subyacentes. Esta guía explica qué significan esos números y detalla los aspectos que son fáciles de malinterpretar.

Configuración​

Usa el mismo circuito de Ising con Trotter y el mismo observable que la guía de inicio rápido.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_slc": "qiskit-addon-slc", "samplomatic": "samplomatic"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Pauli, PauliLindbladMap, QubitSparsePauliList
from qiskit_addon_slc.bounds import (
compute_backward_bounds,
compute_forward_bounds,
merge_bounds,
)
from qiskit_addon_slc.utils import generate_noise_model_paulis
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.utils import find_unique_box_instructions

def trotter_ising_circuit(num_qubits, num_steps, rx_angle, rzz_angle):
"""Trotterized transverse-field Ising evolution on a 1D chain."""
circuit = QuantumCircuit(num_qubits)
for _ in range(num_steps):
circuit.rx(rx_angle, range(num_qubits))
circuit.barrier()
for start in (0, 1): # even then odd bonds
for i in range(start, num_qubits - 1, 2):
circuit.rzz(rzz_angle, i, i + 1)
circuit.barrier()
return circuit

num_qubits = 10
circuit = trotter_ising_circuit(
num_qubits, num_steps=4, rx_angle=np.pi / 16, rzz_angle=-np.pi / 3
)
observable = Pauli("I" * num_qubits).compose("Z", [num_qubits // 2])

boxing_pass = generate_boxing_pass_manager(
inject_noise_targets="all",
inject_noise_strategy="individual_modification",
inject_noise_site="after",
twirling_strategy="active",
remove_barriers="never",
)
boxed_circuit = boxing_pass.run(circuit)
noise_model_paulis = generate_noise_model_paulis(
find_unique_box_instructions(boxed_circuit)
)

forward_bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable
)
backward_bounds = compute_backward_bounds(boxed_circuit, noise_model_paulis)

rng = np.random.default_rng(42)
noise_rates = {
layer: PauliLindbladMap.from_components(
rng.random(len(terms)) * 5e-3,
QubitSparsePauliList.from_sparse_list(
terms.to_sparse_list(), terms.num_qubits
),
)
for layer, terms in noise_model_paulis.items()
}
merged_bounds = merge_bounds(
boxed_circuit, forward_bounds, backward_bounds, noise_rates
)
print(f"observable: Z on qubit {num_qubits // 2}")
Optimal spacetime partitioning not implemented! Just partitioning list of noisy boxes.
observable: Z on qubit 5

draw_shaded_lightcone​

Cada box ruidoso se sombrea según la cota de los errores que ocurren en él: brillante significa una cota grande, y un box que se desvanece en el fondo significa que sus errores tienen poco efecto en el observable. Esos boxes desvanecidos son los candidatos para eliminarse del modelo de ruido mitigado.

from qiskit_addon_slc.visualization import draw_shaded_lightcone

draw_shaded_lightcone(
boxed_circuit, merged_bounds, noise_model_paulis, fold=-1, scale=0.5
)

Output of the previous code cell

Notas importantes​

1. Cada número es una suma sobre los términos de error que comparten un soporte. Sin filtro, las cotas de todos los términos de error que actúan sobre el mismo qubit, o sobre el mismo par de qubits, se suman en un solo valor: los tres tipos de Pauli en un qubit y los nueve en un par. Eso tiene dos consecuencias. Un valor puede superar 2.0, aunque la cota de ningún término individual pueda (un box etiquetado 4.5 son varios términos que se suman, no una cota violada). La figura no puede decirte si un valor grande proviene de un solo tipo de Pauli o de varios comparables; la siguiente sección muestra cómo separarlos.

2. La escala de color empieza en 2.0 y crece con los datos. La escala va de 0 a la entrada "max_bound" de los metadatos del circuito superpuesto, que es el mayor entre 2.0 y el mayor valor mostrado. Las sumas sin filtrar suelen superar 2.0, así que la escala se estira para acomodarlas, y el mismo color puede representar valores distintos en figuras distintas. Cuando todos los valores son pequeños, la escala se queda en 2.0 y todo se ve uniformemente oscuro (eso es la escala reflejando el tamaño absoluto, no un fallo de renderizado).

Separar los tipos de Pauli​

Usa pauli_filter para ver un tipo cada vez. Un entero selecciona un peso de Pauli; una cadena selecciona un Pauli específico, comparado con cada término de ruido reducido a su propio soporte no identidad. Así, "X" selecciona todo error XX de un solo qubit, sin importar sobre qué qubit actúe, y "XX" selecciona los de peso 2 (una etiqueta de ancho completo como "XII" no coincide con nada).

from qiskit_addon_slc.visualization import accumulate_filtered_bounds

box_id = next(iter(merged_bounds))
print(f"single-qubit supports of box {box_id!r}:\n")
print(f"{'filter':>8} bounds per qubit")
for pauli_filter in (None, 1, "X", "Y", "Z"):
filtered = accumulate_filtered_bounds(
boxed_circuit, merged_bounds, noise_model_paulis, pauli_filter
)
per_qubit = {
support[0]: round(float(bound), 3)
for support, bound in sorted(filtered[box_id].items())
if len(support) == 1
}
print(f"{pauli_filter!s:>8} {per_qubit}")
single-qubit supports of box 'm0':

filter bounds per qubit
None {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.312, 4: 1.355, 5: 5.111, 6: 1.506, 7: 0.124, 8: 0.019, 9: 0.0}
1 {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.312, 4: 1.355, 5: 5.111, 6: 1.506, 7: 0.124, 8: 0.019, 9: 0.0}
X {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.153, 4: 0.577, 5: 2.0, 6: 0.702, 7: 0.056, 8: 0.01, 9: 0.0}
Y {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.148, 4: 0.607, 5: 2.0, 6: 0.631, 7: 0.057, 8: 0.01, 9: 0.0}
Z {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.011, 4: 0.172, 5: 1.111, 6: 0.172, 7: 0.011, 8: 0.0, 9: 0.0}

La fila sin filtrar es la suma de los tres tipos de Pauli que hay debajo. Filtrar es la única manera de saber si un valor grande sin filtrar proviene de un tipo de error dominante o de varios comparables. Eso importa, porque la mitigación actúa por término, no por sitio.

Pasar el mismo filtro a draw_shaded_lightcone lo registra en el título de la figura, de modo que una figura filtrada no pueda confundirse con una sin filtrar:

draw_shaded_lightcone(
boxed_circuit,
merged_bounds,
noise_model_paulis,
pauli_filter="X",
fold=-1,
scale=0.5,
)

Output of the previous code cell

Esta figura se ve bastante distinta de la no filtrada de arriba, y no solo porque muestre menos valores. Cada marcador de posición de estas figuras representa un soporte: un solo qubit o un par de qubits. Sin filtro, un box muestra un marcador de posición en cada qubit y en cada par de qubits vecinos, así que se extiende por varias columnas. Con "X", solo quedan los soportes de un solo qubit, y cada box se reduce a una sola columna.

Sin embargo, ninguna de las dos imágenes muestra lo que PEC realmente aplica. Los marcadores de posición son una forma de mostrar cotas, no compuertas anti-ruido. Durante el experimento, cada caja cuyos términos de error se mitigan se trata como una única capa de compuertas de dos qubits, y los Paulis anti-ruido muestreados para sus términos de error se componen en un único Pauli que se aplica a esa capa en su conjunto.

animate_shaded_lightcone​

En un dispositivo cuya conectividad no es una línea 1D, un diagrama de circuito ya no muestra qué qubits son vecinos, por lo que resulta imposible leer la extensión espacial del cono de luz. animate_shaded_lightcone dibuja en su lugar el mapa de acoplamiento de la QPU y anima las cotas sobre él, una caja ruidosa por fotograma.

Necesita un CouplingMap, que dispone en una cuadrícula incrustando el grafo del dispositivo, por lo que admite tanto la conectividad heavy-hex como la cuadrada.

Nota: En esta documentación renderizada la animación es estática. El control deslizante y el botón de reproducción solo funcionan cuando el notebook se ejecuta de forma interactiva, por ejemplo en Jupyter.

from qiskit import transpile
from qiskit.transpiler import CouplingMap
from qiskit_addon_slc.visualization import animate_shaded_lightcone

# A small square-lattice device stands in for a real backend here.
coupling_map = CouplingMap.from_grid(3, 4)

# Lay the chain onto a connected path of physical qubits, snaking through the grid, so the
# transpiler inserts no SWAPs: a SWAP-laden circuit is far more expensive to bound and harder to
# read.
initial_layout = [0, 1, 2, 3, 7, 6, 5, 4, 8, 9]
device_circuit = transpile(
circuit,
coupling_map=coupling_map,
initial_layout=initial_layout,
optimization_level=0,
)
boxed_device_circuit = boxing_pass.run(device_circuit)
device_noise_paulis = generate_noise_model_paulis(
find_unique_box_instructions(boxed_device_circuit),
coupling_map=coupling_map,
circuit=boxed_device_circuit,
)
# The same observable, on the physical qubit the middle of the chain was laid onto.
device_observable = Pauli("I" * device_circuit.num_qubits).compose(
"Z", [initial_layout[num_qubits // 2]]
)
device_bounds = compute_forward_bounds(
boxed_device_circuit, device_noise_paulis, device_observable
)

animate_shaded_lightcone(boxed_device_circuit, device_bounds, coupling_map)

Output of the previous code cell

Cómo leer la animación​

Cada qubit es un gráfico circular, con una porción por tipo de Pauli. El marcador de un qubit se divide en tres porciones para sus errores XX, YY y ZZ, cada una sombreada según su propia cota. Es el filtrado de la sección anterior incorporado en la figura (no hace falta pauli_filter para distinguir los tipos).

Las aristas llevan las cotas de error de dos qubits. La línea entre dos qubits acoplados se sombrea según la cota de los errores de peso 2 que actúan sobre ese par.

El gris claro significa que no hay datos, no una cota pequeña. Una porción o una arista se dibuja en gris claro cuando el modelo de ruido de la caja no tiene ningún término de error para ella, por ejemplo en qubits sobre los que la caja no actúa. Se dibuja en gris en lugar de oscuro para que no se confunda con una cota de cero. Las cajas más allá de un límite max_num_boxes son diferentes: llevan la cota trivial de 2.0, por lo que se dibujan en el extremo brillante de la escala.

El control deslizante avanza por cajas, no por el tiempo de manera uniforme. Hay un fotograma por caja ruidosa, en orden del circuito (o en orden inverso con reverse=True, que traza el cono de luz hacia atrás desde el observable). Verlo en orden inverso suele ser la dirección más intuitiva: el cono crece hacia fuera desde los qubits medidos. Cada fotograma lleva la etiqueta del identificador de su caja, la misma clave con la que el diccionario de cotas almacena esa caja, de modo que el control deslizante también sirve para saltar a una caja concreta y consultar sus números.

Nota: La disposición de los qubits se recupera del grafo del mapa de acoplamiento incrustándolo en una red cuadrada. Un mapa de acoplamiento que no se puede incrustar (porque está desconectado o tiene una conectividad demasiado densa para una cuadrícula) lanza un ValueError en lugar de dibujar una imagen engañosa.

Cuál usar​

draw_shaded_lightconeanimate_shaded_lightcone
geometríael diagrama del circuitoel mapa de acoplamiento de la QPU
ideal paracadenas 1Dcualquier conectividad
tipos de Paulisumados, salvo que se filtrensiempre separados, como porciones circulares
tiempotodas las capas a la vezuna capa por fotograma
salidauna figura de matplotlibuna figura de plotly

Para una cadena 1D, la figura estática es más fácil de asimilar de un vistazo y se reproduce en un artículo. Más allá de 1D, la animación es la única de las dos que muestra fielmente la estructura espacial.