Fehlererkennung mit geringem Overhead durch Raumzeit-Codes
Schätzung der Laufzeit: 10 Sekunden auf einem Heron-r3-Prozessor (HINWEIS: Dies ist nur eine Schätzung. Deine tatsächliche Laufzeit kann abweichen.)
Einführung
Low-overhead error detection with spacetime codes [1] von Simon Martiel und Ali Javadi-Abhari schlägt vor, gewichtsarme, konnektivitätsbewusste Raumzeit-Prüfungen für Clifford-dominierte Circuits zu synthetisieren und dann anhand dieser Prüfungen durch Post-Selektion Fehler abzufangen – mit weit weniger Overhead als bei der vollständigen Fehlerkorrektur und mit weniger Shots als bei der Standardfehlerminderung.
Diese Arbeit schlägt eine neuartige Methode zur Fehlererkennung in Quantencircuits (insbesondere Clifford-Circuits) vor, die eine Balance zwischen vollständiger Fehlerkorrektur und leichtgewichtigeren Minderungstechniken herstellt. Die Kernidee besteht darin, Raumzeit-Codes zu verwenden, um Prüfungen über den Circuit hinweg zu erzeugen, die in der Lage sind, Fehler zu erkennen, mit deutlich geringerem Qubit- und Gate-Overhead als bei der vollständigen fehlertoleranten Fehlerkorrektur. Die Autoren entwickeln effiziente Algorithmen, um Prüfungen auszuwählen, die gewichtsarm (wenige Qubits beteiligend), kompatibel mit der physikalischen Konnektivität des Geräts und in der Lage sind, große zeitliche und räumliche Bereiche des Circuits abzudecken. Sie demonstrieren den Ansatz an Circuits mit bis zu 50 logischen Qubits und ~2450 CZ-Gates und erzielen Treue-Verbesserungen von physikalisch zu logisch von bis zu 236×. Es ist auch zu beachten, dass mit zunehmenden Nicht-Clifford-Operationen die Anzahl gültiger Prüfungen exponentiell abnimmt, was zeigt, dass die Methode am besten für Clifford-dominierte Circuits funktioniert. Insgesamt bietet die Fehlererkennung über Raumzeit-Codes kurzfristig einen praktischen, weniger aufwändigen Weg zur Verbesserung der Zuverlässigkeit von Quantenhardware.
Diese Fehlererkennungstechnik stützt sich auf das Konzept kohärenter Pauli-Prüfungen und basiert auf der Arbeit Single-shot error mitigation by coherent Pauli checks [2] von van den Berg et al.
Jüngst berichtet die Arbeit Big cats: entanglement in 120 qubits and beyond [3] von Javadi-Abhari et al. von der Erzeugung eines 120-Qubit-Greenberger-Horne-Zeilinger-(GHZ-)Zustands, des bislang größten mehrpartigen verschränkten Zustands auf einer supraleitenden Qubit-Plattform. Mit einem hardwarebewussten Compiler, Fehlererkennung mit geringem Overhead und einer „temporären Unberechnung"-Technik zur Rauschreduzierung erzielten die Forscher eine Treue von 0,56 ± 0,03 bei einer Post-Selektionseffizienz von etwa 28 %. Die Arbeit demonstriert echte Verschränkung über alle 120 Qubits hinweg, validiert mehrere Treuezertifizierungsmethoden und setzt einen wichtigen Maßstab für skalierbare Quantenhardware.
Dieses Tutorial baut auf diesen Ideen auf und führt dich durch die Implementierung des Fehlererkennungsalgorithmus – zunächst auf einem kleinen zufälligen Clifford-Circuit und anschließend durch die Aufgabe der GHZ-Zustandsvorbereitung – um dir zu helfen, Fehlererkennung auf deinen eigenen Quantencircuits auszuprobieren.
Voraussetzungen
Stelle vor dem Start dieses Tutorials sicher, dass Folgendes installiert ist:
- Qiskit SDK v2.0 oder höher, mit Visualisierungsunterstützung
- Qiskit Runtime v0.40 oder höher (
pip install qiskit-ibm-runtime) - Qiskit Aer v0.17.2 (
pip install qiskit-aer) - Qiskit Device Benchmarking (
pip install "qiskit-device-benchmarking @ git+https://github.com/qiskit-community/qiskit-device-benchmarking.git") - NumPy v2.3.2 (
pip install numpy) - Matplotlib v3.10.7 (
pip install matplotlib)
Setup
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-device-benchmarking qiskit-ibm-runtime
# Standard library imports
from collections import defaultdict, deque
from functools import partial
# External libraries
import matplotlib.pyplot as plt
import numpy as np
# Qiskit
from qiskit import ClassicalRegister, QuantumCircuit
from qiskit.circuit import Delay
from qiskit.circuit.library import RZGate, XGate
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.quantum_info import Pauli, random_clifford
from qiskit.transpiler import AnalysisPass, PassManager
from qiskit.transpiler.passes import (
ALAPScheduleAnalysis,
CollectAndCollapse,
PadDelay,
PadDynamicalDecoupling,
RemoveBarriers,
)
from qiskit.transpiler.passes.optimization.collect_and_collapse import (
collect_using_filter_function,
collapse_to_operation,
)
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.visualization import plot_histogram
# Qiskit Aer
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel, ReadoutError, depolarizing_error
# Qiskit IBM Runtime
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
# Qiskit Device Benchmarking
from qiskit_device_benchmarking.utilities.gate_map import plot_gate_map
Erstes Beispiel
Um diese Methode zu demonstrieren, beginnen wir mit der Konstruktion eines einfachen Clifford-Circuits. Unser Ziel ist es, erkennen zu können, wenn bestimmte Fehlertypen in diesem Circuit auftreten, sodass wir fehlerhafte Messergebnisse verwerfen können. In der Fehlererkennungsterminologie wird dies auch als unser Nutzlast-Circuit bezeichnet.
circ = random_clifford(num_qubits=2, seed=11).to_circuit()
circ.draw("mpl")
Unser Ziel ist es, eine kohärente Pauli-Prüfung in diesen Nutzlast-Circuit einzufügen. Bevor wir das tun, unterteilen wir diesen Circuit in Schichten. Das wird später beim Einfügen von Pauli-Gates zwischen den Schichten nützlich sein.
# Separate circuit into layers
dag = circuit_to_dag(circ)
circ_layers = []
for layer in dag.layers():
layer_as_circuit = dag_to_circuit(layer["graph"])
circ_layers.append(layer_as_circuit)
# Create subplots
fig, (ax1, ax2, ax3, ax4, ax5) = plt.subplots(1, 5, figsize=(10, 4))
# Draw circuits on respective axes
circ_layers[0].draw(output="mpl", ax=ax1)
circ_layers[1].draw(output="mpl", ax=ax2)
circ_layers[2].draw(output="mpl", ax=ax3)
circ_layers[3].draw(output="mpl", ax=ax4)
circ_layers[4].draw(output="mpl", ax=ax5)
# Adjust layout to prevent overlap
plt.tight_layout()
plt.show()
Jetzt sind wir bereit, kohärente Pauli-Prüfungen in den Nutzlast-Circuit einzufügen. Dazu müssen wir eine „gültige Prüfung" konstruieren und in den Circuit einfügen. Eine „Prüfung" ist in diesem Fall ein Operator, der signalisieren kann, ob ein Fehler im Circuit aufgetreten ist, indem er eine Messung auf einem Ancilla-Qubit vornimmt. Sie gilt als gültige Prüfung, wenn die zusätzlich in den Quantencircuit eingefügten Operatoren den ursprünglichen Circuit logisch nicht verändern.
Diese Prüfung ist in der Lage, Fehlertypen zu erkennen, die mit ihr antikommutieren. In diesem Fall löst die Prüfung durch Phasen-Kickback eine Messung des -Zustands im Ancilla-Qubit statt aus. Daher können wir Messungen, bei denen ein Fehler signalisiert wurde, verwerfen.
Im Allgemeinen sind kohärente Pauli-Prüfungen kontrollierte Pauli-Operatoren, die in „Leitungen" – Raumzeit-Positionen zwischen Gates – eingefügt werden. Das Ancilla-Qubit, das für die Fehlersignalisierung zuständig ist, ist das Steuer-Qubit.
Nachfolgend konstruieren wir eine gültige Prüfung für den zuvor erstellten Clifford-Circuit. Wir können zeigen, dass diese Prüfung die Circuit-Operation nicht verändert, indem wir demonstrieren, dass sich diese Pauli-Prüfungen beim Weiterpropagieren an den Anfang des Circuits gegenseitig aufheben. Dies lässt sich leicht zeigen, da ein Pauli-Operator durch ein Clifford-Gate zu einem anderen Pauli-Operator wird.
Im Allgemeinen kann man eine Dekodierheuristik wie in [1] beschrieben verwenden, um gültige Prüfungen zu identifizieren. Für unser anfängliches Beispiel können wir gültige Prüfungen auch durch analytische Pauli- und Clifford-Gate-Multiplikationsbedingungen konstruieren.
# Define a valid check
pauli_1 = Pauli("ZI")
pauli_2 = Pauli("XZ")
circ_1 = circ_layers[0].compose(circ_layers[1])
circ_1.draw("mpl")
pauli_1_ev = pauli_1.evolve(circ_1, frame="h")
pauli_1_ev
Pauli('-ZI')
circ_2 = circ.copy()
circ_2.draw("mpl")
pauli_2_ev = pauli_2.evolve(circ_2, frame="h")
pauli_2_ev
Pauli('-ZI')
pauli_1_ev.dot(pauli_2_ev)
Pauli('II')
Wie wir sehen, haben wir eine gültige Prüfung, da die eingefügten Pauli-Operatoren denselben Effekt wie ein Identitätsoperator auf den Circuit haben. Wir können diese Prüfungen jetzt mit einem Ancilla-Qubit in den Circuit einfügen. Dieses Ancilla-Qubit, auch Prüf-Qubit genannt, startet im -Zustand. Es enthält die kontrollierten Versionen der oben beschriebenen Pauli-Operationen und wird schließlich in der -Basis gemessen. Dieses Prüf-Qubit kann nun Fehler im Nutzlast-Circuit erfassen, ohne ihn logisch zu verändern. Das liegt daran, dass bestimmte Rauscharten im Nutzlast-Circuit den Zustand des Prüf-Qubits verändern und es im Fehlerfall als „1" statt „0" gemessen wird.
# New circuit with 3 qubits (2 payload + 1 ancilla for check)
circ_meas = QuantumCircuit(3)
circ_meas.h(0)
circ_meas.compose(circ_layers[0], [1, 2], inplace=True)
circ_meas.compose(circ_layers[1], [1, 2], inplace=True)
circ_meas.cz(0, 2)
circ_meas.compose(circ_layers[2], [1, 2], inplace=True)
circ_meas.compose(circ_layers[3], [1, 2], inplace=True)
circ_meas.compose(circ_layers[4], [1, 2], inplace=True)
circ_meas.cz(0, 1)
circ_meas.cx(0, 2)
circ_meas.h(0)
# Add measurement to payload qubits
c0 = ClassicalRegister(2, name="c0")
circ_meas.add_register(c0)
circ_meas.measure(1, c0[0])
circ_meas.measure(2, c0[1])
# Add measurement to check qubit
c1 = ClassicalRegister(1, name="c1")
circ_meas.add_register(c1)
circ_meas.measure(0, c1[0])
# Visualize the final circuit with the inserted checks
circ_meas.draw("mpl")
Wird das Prüf-Qubit als „0" gemessen, behalten wir diese Messung. Wird es als „1" gemessen, bedeutet das, dass im Nutzlast-Circuit ein Fehler aufgetreten ist, und wir verwerfen diese Messung.
# Noiseless simulation using stabilizer method
sim_stab = AerSimulator(method="stabilizer")
res = sim_stab.run(circ_meas, shots=1000).result()
counts_noiseless = res.get_counts()
print(f"Stabilizer simulation result: {counts_noiseless}")
Stabilizer simulation result: {'0 11': 523, '0 01': 477}
# Plot the noiseless results
# Note that the first bit in the key corresponds to the check qubit
plot_histogram(counts_noiseless)
Mit einem idealen Simulator erkennt das Prüf-Qubit keine Fehler. Wir führen jetzt ein Rauschmodell in die Simulation ein und beobachten, wie das Prüf-Qubit Fehler erfasst.
# Qiskit Aer noise model
noise = NoiseModel()
p2 = 0.003 # two-qubit depolarizing per CZ
p1 = 0.001 # one-qubit depolarizing per 1q Clifford
pr = 0.01 # readout bit-flip probability
# 1q depolarizing on common 1q gates
e1 = depolarizing_error(p1, 1)
for g1 in ["id", "rz", "sx", "x", "h", "s"]:
noise.add_all_qubit_quantum_error(e1, g1)
# 2q depolarizing on CZ
e2 = depolarizing_error(p2, 2)
noise.add_all_qubit_quantum_error(e2, "cz")
# Readout error on measure
ro = ReadoutError([[1 - pr, pr], [pr, 1 - pr]])
noise.add_all_qubit_readout_error(ro)
# Qiskit Aer simulation with noise model
aer = AerSimulator(method="automatic", seed_simulator=43210)
job = aer.run(circ_meas, shots=1000, noise_model=noise)
result = job.result()
counts_noisy = result.get_counts()
print(f"Noise model simulation result: {counts_noisy}")
Noise model simulation result: {'1 01': 5, '0 11': 478, '1 11': 6, '1 00': 2, '1 10': 1, '0 01': 500, '0 00': 5, '0 10': 3}
plot_histogram(counts_noisy)
Wie wir sehen, haben einige Messungen den Fehler erkannt, indem das Prüf-Qubit als „1" markiert wurde – diese sind in den letzten vier Spalten sichtbar. Diese Shots werden verworfen. Hinweis: Das Ancilla-Qubit kann auch neue Fehler in den Circuit einbringen. Um diesen Effekt zu verringern, können wir verschachtelte Prüfungen mit zusätzlichen Ancilla-Qubits in den Quantencircuit einfügen.
Praxisbeispiel: GHZ-Zustand auf echter Hardware vorbereiten
Schritt 1: Klassische Eingaben auf ein Quantenproblem abbilden
Jetzt demonstrieren wir eine bedeutende Aufgabe für Quantencomputeralgorithmen: die Vorbereitung eines GHZ-Zustands. Wir zeigen, wie dies auf einem echten Backend unter Verwendung von Fehlererkennung durchgeführt wird.
# Set optional seed for reproducibility
SEED = 1
if SEED:
np.random.seed(SEED)
Der Fehlererkennungsalgorithmus für die GHZ-Zustandsvorbereitung berücksichtigt die Hardware-Topologie. Wir beginnen mit der Auswahl der gewünschten Hardware.
# This is used to run on real hardware
service = QiskitRuntimeService()
# Choose a backend to build GHZ on
backend_name = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
)
backend = service.backend(backend_name)
coupling_map = backend.target.build_coupling_map()
Ein GHZ-Zustand auf Qubits ist definiert als
Ein sehr naiver Ansatz zur Vorbereitung des GHZ-Zustands wäre, ein Wurzel-Qubit mit einem anfänglichen Hadamard-Gate zu wählen, das das Qubit in einen gleichmäßigen Superpositionszustand versetzt, und dann dieses Qubit mit jedem anderen Qubit zu verschränken. Dies ist kein guter Ansatz, da er weitreichende und tiefe CNOT-Wechselwirkungen erfordert. In diesem Tutorial verwenden wir mehrere Techniken zusammen mit Fehlererkennung, um den GHZ-Zustand zuverlässig auf echter Hardware vorzubereiten.