Démarrage rapide
qiskit-paulice Ce guide présente un exemple minimal fonctionnel du paquet. Nous utilisons des contrôles de Pauli spatio-temporels pour détecter les erreurs lors de l'exécution d'un circuit de Clifford, puis nous ne sélectionnons a posteriori que les échantillons pour lesquels aucune erreur n'a été détectée. Pour découvrir un workflow complet et détaillé qui permet de quantifier l'amélioration de la fidélité, consultez le tutoriel approfondi.
Étapes du flux de travail
- Préparez les données d'entrée : un circuit de charge utile de Clifford, les qubits cibles/auxiliaires destinés à accueillir les contrôles, ainsi qu'un modèle de bruit.
- Trouvez de bons contrôleurs de Pauli dans l'espace-temps et ajoutez-les au circuit.
- Testez le circuit vérifié.
- Ne sélectionnez que les échantillons pour lesquels aucune erreur n'a été détectée.
1. Préparer les données d'entrée
add_pauli_checks prend comme entrée un circuit de Clifford comportant au moins une mesure terminale, les qubits cibles qui seront utilisés pour mettre en œuvre les vérifications, ainsi qu'un modèle de bruit servant à évaluer les vérifications candidates. ibm_bostonNous construisons ici un circuit de Clifford aléatoire peu profond, nous le disposons sur une chaîne d' 1D s de qubits physiques, nous associons chaque qubit de charge utile à un qubit auxiliaire voisin (de sorte qu'une vérification ne nécessite aucune porte SWAP), et nous déduisons un modèle approximatif de bruit dépolarisant à partir des données de référence du backend.
import numpy as np
from qiskit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_paulice.layout import get_check_qubits
from qiskit_paulice.noise_models import NoiseModel
# Backend and a 1D chain of physical qubits to run on
backend = QiskitRuntimeService().backend("ibm_boston")
layout = [68, 69, 78, 89, 90, 91, 98, 111, 112, 113, 119, 133]
# A shallow brickwork random Clifford payload circuit
rng = np.random.default_rng(1764)
circuit = QuantumCircuit(len(layout))
circuit.h(range(circuit.num_qubits))
for d in range(4):
for i in range(d % 2, circuit.num_qubits - 1, 2):
circuit.cz(i, i + 1)
for q in range(circuit.num_qubits):
if rng.integers(0, 2):
circuit.sx(q)
if rng.integers(0, 2):
circuit.s(q)
circuit.measure_all()
# Pair each payload qubit with a neighboring ancilla to host a check (no SWAPs needed)
target_qubits, ancilla_qubits = get_check_qubits(backend.coupling_map, layout)
# A rough depolarizing noise model from backend benchmark data, used to score checks
noise_model = NoiseModel.from_backend(
backend, layout, uniform_gate_noise=True
)
circuit.draw("mpl", fold=-1)Output:
2. Rechercher et ajouter les contrôles de Pauli dans l'espace-temps
add_pauli_checks recherche des contrôles efficaces et de faible poids pour chaque qubit cible, les ajoute au circuit et renvoie une séquence CheckedCircuit d'instances. Les CheckedCircuit instances comportent un nombre croissant de vérifications, allant de 0 vérifications jusqu’à une vérification par qubit cible. Chaque vérification améliore la capacité de détection des erreurs, au prix d'une légère augmentation de la profondeur du circuit. Dans cet exemple, nous utiliserons le circuit comportant une vérification sur chaque qubit cible.
from qiskit_paulice import add_pauli_checks
# The circuit has virtual qubits, so we specify our target qubits with virtual indices
target_qubits_v = [layout.index(q) for q in target_qubits]
# Add spacetime Pauli checks
checked_circuit = add_pauli_checks(circuit, target_qubits_v, noise_model)[-1]
checked_circuit.circuit.draw("mpl", fold=-1, scale=0.4, idle_wires=False)Output:
3. Effectuer un test sur le circuit vérifié
ibm_bostonAvant de procéder à l'échantillonnage, nous transpilons le circuit vérifié sur des qubits physiques (initial_layout = layout + ancilla_qubits) et dans l'ensemble de portes de base natif de. Pour simuler un QPU sujet au bruit, nous effectuons un échantillonnage à l'aide d'un backend de stabilisateur Aer sujet au bruit, dont les taux d'erreur correspondent à ceux noise_model utilisés pour sélectionner les vérifications.
from qiskit import transpile
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel as AerNoiseModel
from qiskit_aer.noise import ReadoutError, depolarizing_error
# Transpile once: lay the checked circuit out on our qubits and into the native basis
isa_circuit = transpile(
checked_circuit.circuit,
backend,
initial_layout=layout + ancilla_qubits,
optimization_level=0,
)
# Build an Aer noise model matching the depolarizing model used to pick checks
aer_noise = AerNoiseModel()
aer_noise.add_all_qubit_quantum_error(
depolarizing_error(noise_model.gate_noise, 2), ["cz"]
)
p = noise_model.readout_noise
aer_noise.add_all_qubit_readout_error(ReadoutError([[1 - p, p], [p, 1 - p]]))
simulator = AerSimulator(method="stabilizer", noise_model=aer_noise)
counts = (
simulator.run(isa_circuit, shots=1000, seed_simulator=1764)
.result()
.get_counts()
)
print(f"sampled {sum(counts.values())} shots")Output:
sampled 1000 shots
4. Échantillons sélectionnés a posteriori ne présentant aucune erreur détectée
get_postselection_method associe chaque trame à son vecteur de syndrome; nous ne conservons que les trames dont le vecteur de syndrome est entièrement composé de zéros — c'est-à-dire celles pour lesquelles aucun contrôle n'a détecté d'erreur. Le fait d'écarter les images signalées permet d'éliminer les erreurs détectées de la distribution restante, ce qui améliore sa fidélité au prix d'un taux d'échantillonnage plus faible. Consultez le tutoriel pour une comparaison quantitative de la fidélité.
# Keep only the shots in which no check reported an error
ps_fn = checked_circuit.get_postselection_method()
counts_postselected = {
bs: n for bs, n in counts.items() if not ps_fn(bs).any()
}
kept, total = sum(counts_postselected.values()), sum(counts.values())
print(f"kept {kept} of {total} shots ({kept / total:.0%})")Output:
kept 927 of 1000 shots (93%)