Zum Hauptinhalt springen
Dieses Projekt befindet sich in der Beta-Phase. Einen Fehler gefunden? GitHub Issue erstellen. Frage oder Idee? In Discussions fragen — wir freuen uns über dein Feedback!
Wie ist die Übersetzungsqualität auf dieser Seite?

So interpretierst du die Visualisierungen der Shaded lightcones

Das Paket bietet zwei Möglichkeiten, einen Shaded lightcone zu betrachten:

  • draw_shaded_lightcone legt die Schranken über das Circuit-Diagramm. Das eignet sich am besten für Circuits auf einer 1D- Kette von Qubits, bei denen Circuit-Diagramm und physisches Layout übereinstimmen.

  • animate_shaded_lightcone projiziert die Schranken auf die Kopplungskarte der QPU und geht den Circuit Schicht für Schicht durch. Das eignet sich am besten, wenn die Qubit-Konnektivität mehr als eine 1D-Linie ist und ein Circuit- Diagramm nicht mehr vermittelt, welche Qubits Nachbarn sind.

Beide Visualisierungen zeigen dieselben zugrunde liegenden Zahlen. Dieser Leitfaden erklärt, was diese Zahlen bedeuten, und geht auf die Details ein, die sich leicht falsch lesen lassen.

Einrichtung​

Verwende denselben Trotter-Ising-Circuit und dieselbe Observable wie im Schnellstart-Leitfaden.

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

Jede verrauschte Box wird nach der Schranke für die dort auftretenden Fehler schattiert: Hell bedeutet eine große Schranke, und eine Box, die im Hintergrund verblasst, bedeutet, dass ihre Fehler wenig Einfluss auf die Observable haben. Diese verblassten Boxen sind die Kandidaten, die aus dem Rauschmodell der Fehlerminderung entfernt werden können.

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

Wichtige Hinweise​

1. Jede Zahl ist eine Summe über die Fehlerterme, die einen Träger teilen. Ohne Filter werden die Schranken von jedem Fehlerterm, der auf dasselbe Qubit oder dasselbe Qubit-Paar wirkt, zu einem Wert addiert: alle drei Pauli-Typen auf einem Qubit und alle neun auf einem Paar. Das hat zwei Folgen. Ein Wert kann 2.0 überschreiten, obwohl das keine einzelne Termschranke kann (eine Box mit der Beschriftung 4.5 besteht aus mehreren sich addierenden Termen, nicht aus einer verletzten Schranke). Die Abbildung kann dir nicht sagen, ob ein großer Wert von einem Pauli- Typ oder von mehreren vergleichbaren stammt; der nächste Abschnitt zeigt, wie du sie trennst.

2. Die Farbskala beginnt bei 2.0 und wächst mit den Daten. Die Skala reicht von 0 bis zum Eintrag "max_bound" in den Metadaten des überlagerten Circuits, der das Größere aus 2.0 und dem größten gezeigten Wert ist. Ungefilterte Summen überschreiten oft 2.0, sodass sich die Skala dehnt, um sie aufzunehmen, und dieselbe Farbe in verschiedenen Abbildungen für verschiedene Werte stehen kann. Wenn jeder Wert klein ist, bleibt die Skala bei 2.0 und alles sieht einheitlich dunkel aus (das ist die Skala, die die absolute Größe widerspiegelt, kein Darstellungsfehler).

Die Pauli-Typen trennen​

Verwende pauli_filter, um jeweils einen Typ zu sehen. Eine ganze Zahl wählt ein Pauli-Gewicht; ein String wählt einen bestimmten Pauli, abgeglichen gegen jeden Rauschterm reduziert auf seinen eigenen Nicht-Identitäts-Träger. So wählt "X" jeden Ein-Qubit-XX-Fehler aus, unabhängig davon, auf welches Qubit er wirkt, und "XX" die Gewicht-2-Fehler (eine Bezeichnung in voller Breite wie "XII" passt auf nichts).

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}

Die ungefilterte Zeile ist die Summe der drei darunterliegenden Pauli-Typen. Das Filtern ist der einzige Weg zu erkennen, ob ein großer ungefilterter Wert von einem dominanten Fehlertyp oder von mehreren vergleichbaren stammt. Das ist wichtig, weil die Fehlerminderung pro Term wirkt, nicht pro Stelle.

Wenn du denselben Filter an draw_shaded_lightcone übergibst, wird er im Abbildungstitel festgehalten, sodass eine gefilterte Abbildung nicht mit einer ungefilterten verwechselt werden kann:

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

Output of the previous code cell

Diese Abbildung sieht ganz anders aus als die ungefilterte oben, und nicht nur, weil sie weniger Werte zeigt. Jeder Platzhalter in diesen Abbildungen steht für einen Träger: ein einzelnes Qubit oder ein Paar von Qubits. Ohne Filter zeigt eine Box einen Platzhalter auf jedem Qubit und auf jedem Paar benachbarter Qubits, sodass sie sich über mehrere Spalten ausbreitet. Mit "X" bleiben nur die Ein-Qubit-Träger übrig, und jede Box schrumpft auf eine einzige Spalte.

Keines der beiden Bilder zeigt jedoch, was PEC tatsächlich anwendet. Die Platzhalter sind eine Möglichkeit, Schranken darzustellen, keine Anti-Rausch-Gates. Während des Experiments wird jede Box, deren Fehlerterme mitigiert werden, als einzelne Schicht von Zwei-Qubit-Gates behandelt, und die für ihre Fehlerterme gesampelten Anti-Rausch-Paulis werden zu einem einzigen Pauli zusammengesetzt, das auf diese Schicht als Ganzes angewendet wird.

animate_shaded_lightcone​

Auf einem Gerät, dessen Konnektivität keine 1D-Linie ist, zeigt ein Circuit-Diagramm nicht mehr, welche Qubits benachbart sind, sodass sich die räumliche Ausbreitung des Lightcones nicht mehr ablesen lässt. animate_shaded_lightcone zeichnet stattdessen die Coupling Map der QPU und animiert die Schranken darüber, eine verrauschte Box pro Frame.

Die Funktion benötigt eine CouplingMap, die sie durch Einbettung des Gerätegraphen auf einem Gitter anordnet; sie unterstützt daher sowohl Heavy-Hex- als auch quadratische Konnektivität.

Hinweis: In dieser gerenderten Dokumentation ist die Animation statisch. Der Schieberegler und die Wiedergabetaste funktionieren nur, wenn das Notebook interaktiv ausgeführt wird, zum Beispiel 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

Die Animation lesen​

Jedes Qubit ist ein Kreisdiagramm, ein Segment pro Pauli-Typ. Der Marker eines Qubits ist in drei Segmente für seine XX-, YY- und ZZ-Fehler unterteilt, die jeweils nach ihrer eigenen Schranke eingefärbt sind. Das ist die Filterung aus dem vorherigen Abschnitt, direkt in die Abbildung eingebaut (ohne dass pauli_filter nötig ist, um die Typen zu unterscheiden).

Kanten tragen die Zwei-Qubit-Fehlerschranken. Die Linie zwischen zwei gekoppelten Qubits ist nach der Schranke für Fehler mit Gewicht 2 eingefärbt, die auf dieses Paar wirken.

Hellgrau bedeutet keine Daten, nicht eine kleine Schranke. Ein Segment oder eine Kante wird hellgrau gezeichnet, wenn das Rauschmodell der Box dafür keinen Fehlerterm enthält, zum Beispiel bei Qubits, auf denen die Box nicht wirkt. Es wird grau statt dunkel gezeichnet, damit es nicht mit einer Schranke von null verwechselt werden kann. Boxen jenseits einer max_num_boxes-Grenze sind anders: Sie tragen die triviale Schranke von 2.0 und werden daher am hellen Ende der Skala gezeichnet.

Der Schieberegler springt von Box zu Box, nicht gleichmäßig durch die Zeit. Es gibt einen Frame pro verrauschter Box, in Circuit-Reihenfolge (oder in umgekehrter Reihenfolge mit reverse=True, was den Lightcone von der Observablen aus rückwärts verfolgt). Die umgekehrte Richtung ist meist die intuitivere: Der Kegel wächst von den gemessenen Qubits nach außen. Jeder Frame ist mit der Kennung seiner Box beschriftet, also demselben Schlüssel, unter dem das Schranken-Dictionary diese Box speichert; mit dem Schieberegler lässt sich daher auch gezielt zu einer bestimmten Box springen und ihre Zahlen nachschlagen.

Hinweis: Das Qubit-Layout wird aus dem Graphen der Coupling Map durch Einbettung in ein quadratisches Gitter rekonstruiert. Eine Coupling Map, die sich nicht einbetten lässt (weil sie unzusammenhängend oder für ein Gitter zu dicht verbunden ist), löst einen ValueError aus, statt ein irreführendes Bild zu zeichnen.

Was wann verwenden​

draw_shaded_lightconeanimate_shaded_lightcone
Geometriedas Circuit-Diagrammdie Coupling Map der QPU
am besten für1D-Kettenbeliebige Konnektivität
Pauli-Typensummiert, sofern nicht gefiltertimmer getrennt, als Kreissegmente
Zeitalle Schichten gleichzeitigeine Schicht pro Frame
Ausgabeeine matplotlib-Abbildungeine plotly-Abbildung

Für eine 1D-Kette ist die statische Abbildung auf einen Blick leichter zu erfassen und lässt sich in einer Veröffentlichung reproduzieren. Jenseits von 1D ist die Animation die einzige der beiden, die die räumliche Struktur getreu zeigt.