Aller au contenu principal
Ce projet est en phase bêta. Tu as trouvé un bug ? Ouvrir un ticket GitHub. Une question ou une idée ? Pose ta question dans Discussions — tes retours sont les bienvenus !
Quelle est la qualité de la traduction sur cette page ?

Comment interpréter les visualisations de cônes de lumière ombrés

Le paquet offre deux façons d'observer un cône de lumière ombré :

  • draw_shaded_lightcone superpose les bornes au diagramme du circuit. C'est idéal pour les circuits sur une chaîne 1D de qubits, où le diagramme du circuit et la disposition physique concordent.

  • animate_shaded_lightcone projette les bornes sur la carte de couplage du QPU et parcourt le circuit couche par couche. C'est idéal lorsque la connectivité des qubits dépasse une simple ligne 1D, là où un diagramme de circuit ne montre plus quels qubits sont voisins.

Les deux visualisations montrent les mêmes nombres sous-jacents. Ce guide explique ce que ces nombres signifient et détaille les points faciles à mal interpréter.

Configuration​

Utilise le même circuit d'Ising trotterisé et la même observable que le guide de démarrage rapide.

# 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​

Chaque boîte bruitée est ombrée selon la borne des erreurs qui s'y produisent : brillante signifie une grande borne, et une boîte qui s'estompe dans l'arrière-plan signifie que ses erreurs ont peu d'effet sur l'observable. Ces boîtes estompées sont les candidates à l'exclusion du modèle de bruit atténué.

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

Remarques importantes​

1. Chaque nombre est une somme sur les termes d'erreur partageant un support. Sans filtre, les bornes de tous les termes d'erreur agissant sur le même qubit, ou sur la même paire de qubits, sont additionnées en une seule valeur : tous les trois types de Pauli sur un qubit, et les neuf sur une paire. Cela a deux conséquences. Une valeur peut dépasser 2.0, même si la borne d'aucun terme isolé ne le peut (une boîte étiquetée 4.5 est la somme de plusieurs termes, pas une borne violée). La figure ne peut pas te dire si une grande valeur provient d'un seul type de Pauli ou de plusieurs comparables ; la section suivante montre comment les séparer.

2. L'échelle de couleurs commence à 2.0 et croît avec les données. L'échelle va de 0 à l'entrée "max_bound" des métadonnées du circuit superposé, qui est la plus grande valeur entre 2.0 et la plus grande valeur affichée. Les sommes non filtrées dépassent souvent 2.0, donc l'échelle s'étire pour les contenir, et la même couleur peut représenter des valeurs différentes selon les figures. Lorsque toutes les valeurs sont petites, l' échelle reste à 2.0 et tout paraît uniformément sombre (c'est l'échelle qui reflète la taille absolue, pas un défaut de rendu).

Séparer les types de Pauli​

Utilise pauli_filter pour voir un type à la fois. Un entier sélectionne un poids de Pauli ; une chaîne sélectionne un Pauli précis, mis en correspondance avec chaque terme de bruit réduit à son propre support non identité. Ainsi "X" sélectionne chaque erreur XX à un qubit, quel que soit le qubit sur lequel elle agit, et "XX" sélectionne celles de poids 2 (une étiquette de pleine largeur comme "XII" ne correspond à rien).

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 ligne non filtrée est la somme des trois types de Pauli en dessous. Le filtrage est le seul moyen de savoir si une grande valeur non filtrée provient d'un type d'erreur dominant ou de plusieurs comparables. Cela compte, parce que l'atténuation agit par terme, et non par site.

Passer le même filtre à draw_shaded_lightcone l'enregistre dans le titre de la figure, de sorte qu'une figure filtrée ne puisse pas être confondue avec une figure non filtrée :

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

Output of the previous code cell

Cette figure paraît assez différente de la figure non filtrée ci-dessus, et pas seulement parce qu'elle montre moins de valeurs. Chaque espace réservé de ces figures représente un support : un seul qubit, ou une paire de qubits. Sans filtre, une boîte affiche un espace réservé sur chaque qubit et sur chaque paire de qubits voisins, donc elle s'étend sur plusieurs colonnes. Avec "X", seuls les supports à un qubit subsistent, et chaque boîte se réduit à une seule colonne.

Aucune des deux images ne montre toutefois ce que PEC applique réellement. Les espaces réservés sont un moyen d'afficher les bornes, pas des portes anti-bruit. Pendant l'expérience, chaque boîte dont les termes d'erreur sont atténués est traitée comme une seule couche de portes à deux qubits, et les Paulis anti-bruit échantillonnés pour ses termes d'erreur sont composés en un seul Pauli appliqué à cette couche dans son ensemble.

animate_shaded_lightcone​

Sur un appareil dont la connectivité n'est pas une ligne 1D, un diagramme de circuit n'indique plus quels qubits sont voisins, si bien que l'étendue spatiale du cône de lumière devient impossible à lire. animate_shaded_lightcone dessine à la place la carte de couplage du QPU et y anime les bornes, une boîte bruitée par image.

Elle a besoin d'un CouplingMap, qu'elle dispose sur une grille en plongeant le graphe de l'appareil, de sorte qu'elle gère à la fois la connectivité heavy-hex et la connectivité carrée.

Remarque : Dans cette documentation rendue, l'animation est statique. Le curseur et le bouton de lecture ne fonctionnent que lorsque le notebook s'exécute de manière interactive, par exemple dans 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

Lire l'animation​

Chaque qubit est un diagramme circulaire, une part par type de Pauli. Le marqueur d'un qubit est divisé en trois parts pour ses erreurs XX, YY et ZZ, chacune ombrée selon sa propre borne. C'est le filtrage de la section précédente intégré à la figure (pas besoin de pauli_filter pour distinguer les types).

Les arêtes portent les bornes d'erreur à deux qubits. La ligne entre deux qubits couplés est ombrée selon la borne sur les erreurs de poids 2 agissant sur cette paire.

Le gris clair signifie l'absence de données, pas une petite borne. Une part ou une arête est dessinée en gris clair lorsque le modèle de bruit de la boîte n'a pas de terme d'erreur pour elle, par exemple pour les qubits sur lesquels la boîte n'agit pas. Elle est dessinée en gris plutôt qu'en foncé afin de ne pas être confondue avec une borne nulle. Les boîtes au-delà d'une limite max_num_boxes sont différentes : elles portent la borne triviale de 2.0, elles sont donc dessinées à l'extrémité vive de l'échelle.

Le curseur parcourt les boîtes, pas le temps de façon uniforme. Il y a une image par boîte bruitée, dans l'ordre du circuit (ou dans l'ordre inverse avec reverse=True, qui remonte le cône de lumière à partir de l'observable). Le regarder à l'envers est généralement le sens le plus intuitif : le cône s'étend vers l'extérieur à partir des qubits mesurés. Chaque image est étiquetée avec l'identifiant de sa boîte, la même clé sous laquelle le dictionnaire des bornes stocke cette boîte, si bien que le curseur permet aussi de sauter à une boîte précise et de retrouver ses valeurs.

Remarque : La disposition des qubits est retrouvée à partir du graphe de la carte de couplage en le plongeant dans un réseau carré. Une carte de couplage qui ne se plonge pas (parce qu'elle est déconnectée, ou trop densément connectée pour une grille) lève une ValueError plutôt que de dessiner une image trompeuse.

Lequel utiliser​

draw_shaded_lightconeanimate_shaded_lightcone
géométriele diagramme du circuitla carte de couplage du QPU
idéal pourles chaînes 1Dtoute connectivité
types de Paulisommés, sauf si filtréstoujours séparés, en parts de diagramme circulaire
tempstoutes les couches à la foisune couche par image
sortieune figure matplotlibune figure plotly

Pour une chaîne 1D, la figure statique est plus facile à saisir d'un coup d'œil et se reproduit dans un article. Au-delà de 1D, l'animation est la seule des deux qui montre fidèlement la structure spatiale.