Détection d'erreurs à faible surcoût avec des codes spatio-temporels
Estimation d'utilisation : 10 secondes sur un processeur Heron r3 (REMARQUE : il s'agit d'une estimation uniquement. Votre temps d'exécution peut varier.)
Introduction
Low-overhead error detection with spacetime codes [1] par Simon Martiel et Ali Javadi-Abhari propose de synthétiser des vérifications spatio-temporelles à faible poids et tenant compte de la connectivité pour les circuits dominés par Clifford, puis de post-sélectionner sur ces vérifications pour détecter les défaillances avec beaucoup moins de surcoût qu'une correction d'erreur complète et moins de coups que l'atténuation d'erreur standard.
Cet article propose une méthode novatrice pour la détection d'erreurs dans les circuits quantiques (en particulier les circuits de Clifford) qui trouve un équilibre entre la correction d'erreur complète et les techniques d'atténuation plus légères. L'idée clé consiste à utiliser des codes spatio-temporels pour générer des "vérifications" à travers le circuit capables de détecter les erreurs, avec un surcoût en qubits et en portes nettement inférieur à celui d'une correction d'erreur tolérante aux fautes complète. Les auteurs conçoivent des algorithmes efficaces pour sélectionner des vérifications à faible poids (impliquant peu de qubits), compatibles avec la connectivité physique du dispositif et couvrant de grandes régions temporelles et spatiales du circuit. Ils démontrent l'approche sur des circuits comportant jusqu'à 50 qubits logiques et environ 2450 portes CZ, atteignant des gains de fidélité physique-logique allant jusqu'à 236x. Notez également qu'à mesure que les circuits incluent davantage d'opérations non-Clifford, le nombre de vérifications valides diminue exponentiellement, indiquant que la méthode fonctionne mieux pour les circuits dominés par Clifford. Dans l'ensemble, à court terme, la détection d'erreurs via des codes spatio-temporels peut offrir une voie pratique et à faible surcoût pour améliorer la fiabilité du matériel quantique.
Cette technique de détection d'erreurs repose sur la notion de vérifications de Pauli cohérentes et se base sur les travaux Single-shot error mitigation by coherent Pauli checks [2] de van den Berg et al.
Plus récemment, l'article Big cats: entanglement in 120 qubits and beyond [3] de Javadi-Abhari et al., rapporte la création d'un état de Greenberger-Horne-Zeilinger (GHZ) de 120 qubits, le plus grand état intriqué multipartite réalisé à ce jour sur une plateforme à qubits supraconducteurs. En utilisant un compilateur tenant compte du matériel, une détection d'erreurs à faible surcoût et une technique de "décomputation temporaire" pour réduire le bruit, les chercheurs ont atteint une fidélité de 0,56 ± 0,03 avec environ 28% d'efficacité de post-sélection. Le travail démontre un intrication authentique à travers les 120 qubits, validant plusieurs méthodes de certification de fidélité, et marque une référence majeure pour le matériel quantique évolutif.
Ce tutoriel s'appuie sur ces idées, vous guidant à travers l'implémentation de l'algorithme de détection d'erreurs d'abord sur un circuit de Clifford aléatoire à petite échelle, puis à travers la tâche de préparation d'un état GHZ, pour vous aider à expérimenter la détection d'erreurs sur vos propres circuits quantiques.
Prérequis
Avant de commencer ce tutoriel, assurez-vous d'avoir installé les éléments suivants :
- Qiskit SDK v2.0 ou ultérieur, avec le support de visualisation
- Qiskit Runtime v0.40 ou ultérieur (
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)
Configuration
# Added by doQumentation — installs packages not in the Binder environment
%pip install -q qiskit-device-benchmarking
# 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
Premier exemple
Pour démontrer cette méthode, nous commençons par construire un simple circuit de Clifford. Notre objectif est d'être capable de détecter quand certains types d'erreurs se produisent dans ce circuit, afin de pouvoir écarter les résultats de mesure erronés. Dans la terminologie de détection d'erreurs, on parle également de notre circuit de charge utile.
circ = random_clifford(num_qubits=2, seed=11).to_circuit()
circ.draw("mpl")
Notre objectif est d'insérer une vérification de Pauli cohérente dans ce circuit de charge utile. Mais avant de faire cela, nous séparons ce circuit en couches. Cela sera utile plus tard lors de l'insertion de portes de Pauli entre elles.
# 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()
Nous sommes maintenant prêts à ajouter des vérifications de Pauli cohérentes dans le circuit de charge utile. Pour ce faire, nous devons construire une "vérification valide" et l'insérer dans le circuit. Une "vérification" dans ce cas est un opérateur capable de signaler si une erreur s'est produite dans le circuit en effectuant une mesure sur un qubit auxiliaire. Elle est considérée comme une vérification valide lorsque les opérateurs supplémentaires insérés dans le circuit quantique ne modifient pas logiquement le circuit original.
Cette vérification est capable de détecter les types d'erreurs qui anticommutent avec elle, et la vérification déclenchera une mesure de l'état dans le qubit auxiliaire au lieu de par rétroaction de phase. Par conséquent, nous serons en mesure d'écarter les mesures où une erreur a été signalée.
En général, les vérifications de Pauli cohérentes sont des opérateurs de Pauli contrôlés insérés dans des "fils" - des emplacements spatio-temporels entre les portes. Le qubit auxiliaire responsable de signaler l'erreur est le qubit de contrôle.
Ci-dessous, nous construisons une vérification valide pour le circuit de Clifford que nous avons créé ci-dessus. Nous pouvons démontrer que cette vérification ne change pas le fonctionnement du circuit en montrant que lorsque ces vérifications de Pauli sont propagées vers l'avant du circuit, elles s'annulent mutuellement. Cela est facilement démontré car un opérateur de Pauli à travers une porte de Clifford est un autre opérateur de Pauli.
En général, on peut utiliser une heuristique de décodage telle que décrite dans [1] pour identifier les vérifications valides. Pour les besoins de notre exemple initial, nous pouvons également construire des vérifications valides en utilisant des conditions analytiques de multiplication de portes de Pauli et de Clifford.
# 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')
Comme nous pouvons le voir, nous avons une vérification valide, puisque les opérateurs de Pauli insérés ont simplement le même effet qu'un opérateur d'identité sur le circuit. Nous pouvons maintenant insérer ces vérifications dans le circuit avec un qubit auxiliaire. Ce qubit auxiliaire, ou qubit de vérification, commence dans l'état . Il inclut les versions contrôlées des opérations de Pauli décrites ci-dessus et est finalement mesuré dans la base . Ce qubit de vérification est maintenant capable de capturer les erreurs dans le circuit de charge utile sans le modifier logiquement. C'est parce que certains types de bruit dans le circuit de charge utile modifieront l'état du qubit de vérification, et il sera mesuré "1" au lieu de "0" en cas d'erreur de ce type.
# 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")
Si le qubit de vérification est mesuré à "0", nous conservons cette mesure. S'il est mesuré à "1", cela signifie qu'une erreur s'est produite dans le circuit de charge utile et nous écartons cette mesure.
# 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)
Notez qu'avec un simulateur idéal, le qubit de vérification ne détecte aucune erreur. Nous introduisons maintenant un modèle de bruit dans la simulation et voyons comment le qubit de vérification capture les erreurs.
# 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)
Comme nous pouvons le voir, certaines mesures ont capturé l'erreur en signalant le qubit de vérification comme "1", qui sont visibles dans les quatre dernières colonnes. Ces coups sont écartés. Remarque : Le qubit auxiliaire peut également introduire de nouvelles erreurs dans le circuit. Pour réduire l'effet de cela, nous pouvons insérer des vérifications imbriquées avec des qubits auxiliaires supplémentaires dans le circuit quantique.
Exemple du monde réel : Préparer un état GHZ sur du matériel réel
Étape 1 : Mapper les entrées classiques vers un problème quantique
Nous démontrons maintenant une tâche importante pour les algorithmes de calcul quantique, qui est la préparation d'un état GHZ. Nous démontrerons comment faire cela sur un backend réel en utilisant la détection d'erreurs.
# Set optional seed for reproducibility
SEED = 1
if SEED:
np.random.seed(SEED)
L'algorithme de détection d'erreurs pour la préparation de l'état GHZ respecte la topologie matérielle. Nous commençons par sélectionner le matériel souhaité.
# 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()
Un état GHZ sur qubits est défini comme