Vai al contenuto principale
Questo progetto è in fase beta. Hai trovato un bug? Apri una issue su GitHub. Hai una domanda o un'idea? Chiedi su GitHub Discussions — apprezziamo il tuo feedback!
Com'è la qualità della traduzione su questa pagina?

Come interpretare le visualizzazioni dei shaded lightcone

Il pacchetto offre due modi per osservare un shaded lightcone:

  • draw_shaded_lightcone sovrappone i limiti al diagramma del circuito. È ideale per circuiti su una catena 1D di qubit, dove il diagramma del circuito e il layout fisico coincidono.

  • animate_shaded_lightcone proietta i limiti sulla mappa di accoppiamento della QPU e scorre il circuito livello per livello. È ideale quando la connettività dei qubit è più di una linea 1D, dove un diagramma del circuito non riesce più a trasmettere quali qubit siano vicini.

Entrambe le visualizzazioni mostrano gli stessi numeri sottostanti. Questa guida spiega cosa significano quei numeri e chiarisce i dettagli che si fraintendono facilmente.

Configurazione​

Usa lo stesso circuito di Ising con Trotter e la stessa osservabile della guida rapida.

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

Ogni box rumoroso è ombreggiato in base al limite sugli errori che vi si verificano: luminoso significa un limite grande, e un box che sfuma nello sfondo significa che i suoi errori hanno poco effetto sull'osservabile. Quei box sfumati sono i candidati da eliminare dal modello di rumore mitigato.

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

Note importanti​

1. Ogni numero è una somma sui termini di errore che condividono un supporto. Senza un filtro, i limiti di ogni termine di errore che agisce sullo stesso qubit, o sulla stessa coppia di qubit, vengono sommati in un unico valore: tutti e tre i tipi di Pauli su un qubit, e tutti e nove su una coppia. Questo ha due conseguenze. Un valore può superare 2.0, anche se il limite di nessun singolo termine può (un box etichettato 4.5 è la somma di più termini, non un limite violato). La figura non può dirti se un valore grande proviene da un solo tipo di Pauli o da diversi di entità comparabile; la sezione successiva mostra come separarli.

2. La scala di colori parte da 2.0 e cresce con i dati. La scala va da 0 alla voce "max_bound" dei metadati del circuito sovrapposto, che è il maggiore tra 2.0 e il valore più grande mostrato. Le somme non filtrate spesso superano 2.0, quindi la scala si estende per contenerle, e lo stesso colore può rappresentare valori diversi in figure diverse. Quando tutti i valori sono piccoli, la scala resta a 2.0 e tutto appare uniformemente scuro (è la scala che riflette la dimensione assoluta, non un difetto di rendering).

Separare i tipi di Pauli​

Usa pauli_filter per vedere un tipo alla volta. Un intero seleziona un peso di Pauli; una stringa seleziona uno specifico Pauli, confrontato con ogni termine di rumore ridotto al proprio supporto non identità. Quindi "X" seleziona ogni errore XX a singolo qubit, indipendentemente dal qubit su cui agisce, e "XX" seleziona quelli di peso 2 (un'etichetta a larghezza piena come "XII" non corrisponde a nulla).

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 riga non filtrata è la somma dei tre tipi di Pauli sottostanti. Filtrare è l'unico modo per capire se un valore non filtrato grande proviene da un tipo di errore dominante o da diversi di entità comparabile. Questo conta, perché la mitigazione agisce per termine, non per sito.

Passare lo stesso filtro a draw_shaded_lightcone lo registra nel titolo della figura, così una figura filtrata non può essere scambiata per una non filtrata:

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

Output of the previous code cell

Questa figura appare piuttosto diversa da quella non filtrata qui sopra, e non solo perché mostra meno valori. Ogni segnaposto in queste figure rappresenta un supporto: un singolo qubit, o una coppia di qubit. Senza un filtro, un box mostra un segnaposto su ogni qubit e su ogni coppia di qubit vicini, quindi si estende su più colonne. Con "X" restano solo i supporti a singolo qubit, e ogni box si restringe a una sola colonna.

Nessuna delle due immagini mostra però ciò che PEC applica davvero. I segnaposto sono un modo per visualizzare i limiti, non gate anti-rumore. Durante l'esperimento, ogni box i cui termini di errore sono mitigati viene trattato come un singolo livello di gate a due qubit, e i Pauli anti-rumore campionati per i suoi termini di errore vengono composti in un unico Pauli che si applica a quel livello nel suo insieme.

animate_shaded_lightcone​

Su un dispositivo la cui connettività non è una linea 1D, un diagramma del circuito non mostra più quali qubit sono vicini, quindi la diffusione spaziale del lightcone diventa impossibile da leggere. animate_shaded_lightcone disegna invece la coupling map della QPU e anima i limiti su di essa, un box rumoroso per fotogramma.

Richiede una CouplingMap, che dispone su una griglia incorporando il grafo del dispositivo, quindi gestisce sia la connettività heavy-hex sia quella quadrata.

Nota: In questa documentazione renderizzata l'animazione è statica. Il cursore e il pulsante di riproduzione funzionano solo quando il notebook viene eseguito in modo interattivo, per esempio in 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

Leggere l'animazione​

Ogni qubit è un grafico a torta, con una fetta per ogni tipo di Pauli. Il marcatore di un qubit è diviso in tre fette per i suoi errori XX, YY e ZZ, ciascuna colorata in base al proprio limite. È il filtraggio della sezione precedente integrato nella figura (non serve pauli_filter per distinguere i tipi).

Gli archi riportano i limiti di errore a due qubit. La linea tra due qubit accoppiati è colorata in base al limite sugli errori di peso 2 che agiscono su quella coppia.

Il grigio chiaro significa assenza di dati, non un limite piccolo. Una fetta o un arco viene disegnato in grigio chiaro quando il modello di rumore del box non ha alcun termine di errore per esso, per esempio sui qubit su cui il box non agisce. Viene disegnato in grigio anziché scuro per non confonderlo con un limite pari a zero. I box oltre il limite max_num_boxes sono diversi: hanno il limite banale di 2.0, quindi sono disegnati all'estremo luminoso della scala.

Il cursore scorre i box, non il tempo in modo uniforme. C'è un fotogramma per ogni box rumoroso, nell'ordine del circuito (o in ordine inverso con reverse=True, che ripercorre il lightcone all'indietro a partire dall'osservabile). Guardarlo in ordine inverso è di solito la direzione più intuitiva: il cono cresce verso l'esterno a partire dai qubit misurati. Ogni fotogramma è etichettato con l'identificatore del suo box, la stessa chiave con cui il dizionario dei limiti memorizza quel box, quindi il cursore può servire anche per saltare a un box particolare e consultarne i numeri.

Nota: La disposizione dei qubit viene ricavata dal grafo della coupling map incorporandolo in un reticolo quadrato. Una coupling map che non può essere incorporata (perché è disconnessa, o troppo densamente connessa per una griglia) solleva un ValueError invece di disegnare un'immagine fuorviante.

Quale usare​

draw_shaded_lightconeanimate_shaded_lightcone
geometriail diagramma del circuitola coupling map della QPU
ideale percatene 1Dqualsiasi connettività
tipi di Paulisommati, salvo filtrosempre separati, come fette di torta
tempotutti i livelli insiemeun livello per fotogramma
outputuna figura matplotlibuna figura plotly

Per una catena 1D la figura statica è più facile da cogliere a colpo d'occhio e si riproduce in un articolo. Oltre l'1D l'animazione è l'unica delle due che mostra fedelmente la struttura spaziale.