Aller au contenu principal
Ce projet est en phase bêta. Tu as trouvé un bug ? Ouvrir un ticket GitHub. Une question ou une idée ? Pose ta question dans Discussions — tes retours sont les bienvenus !
Quelle est la qualité de la traduction sur cette page ?

Démarrage rapide

Ce guide présente un exemple minimal fonctionnel du package qiskit-paulice. Nous utilisons des contrôles de Pauli spatio-temporels pour détecter les erreurs pendant l'exécution d'un circuit Clifford, puis nous post-sélectionnons uniquement les échantillons pour lesquels aucune erreur n'a été détectée. Pour un workflow de bout en bout plus explicatif qui quantifie l'amélioration de la fidélité, consulte le tutoriel approfondi.

Étapes du workflow

  1. Prépare les entrées : un circuit Clifford de charge utile, les qubits cibles/ancillaires qui hébergeront les contrôles, et un modèle de bruit.
  2. Trouve de bons contrôles de Pauli spatio-temporels et ajoute-les au circuit.
  3. Échantillonne le circuit contrôlé.
  4. Post-sélectionne uniquement les échantillons pour lesquels aucune erreur n'a été détectée.

1. Préparer les entrées​

add_pauli_checks prend un circuit Clifford se terminant par des mesures (ou, avec l'argument stabilizers, un circuit sans mesures, comme dans le guide des codes spatio-temporels), les qubits cibles qui serviront à implémenter les contrôles, et un modèle de bruit utilisé pour évaluer les contrôles candidats. Ici, nous construisons un circuit Clifford aléatoire peu profond, nous le disposons sur une chaîne 1D de qubits physiques sur ibm_boston, nous associons chaque qubit de charge utile à un qubit ancillaire voisin (afin qu'un contrôle ne nécessite aucune porte SWAP), et nous déduisons un modèle de bruit dépolarisant approximatif à partir des données de benchmark du backend.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "qiskit_paulice": "qiskit-paulice"}
_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_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 of the previous code cell

2. Trouver et ajouter des contrôles de Pauli spatio-temporels​

add_pauli_checks recherche des contrôles efficaces et de faible poids sur chaque qubit cible et les ajoute au circuit, en renvoyant une séquence d'instances CheckedCircuit. Les instances CheckedCircuit contiennent un nombre croissant de contrôles, de 0 contrôle jusqu'à un contrôle par qubit cible. Chaque contrôle améliore la capacité de détection d'erreurs au prix d'une profondeur de circuit légèrement accrue. Pour cet exemple, nous utiliserons le circuit avec un contrôle 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 of the previous code cell

3. Échantillonner le circuit contrôlé​

Avant d'échantillonner, nous transpilons le circuit contrôlé sur les qubits physiques (initial_layout = layout + ancilla_qubits) et dans l'ensemble de portes de base natif de ibm_boston. Pour émuler un QPU bruité, nous échantillonnons avec un backend stabilisateur Aer bruité dont les taux d'erreur correspondent au noise_model utilisé pour choisir les contrôles.

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")
sampled 1000 shots

4. Post-sélectionner les échantillons sans erreur détectée​

get_postselection_method associe chaque shot à son vecteur de syndrome ; nous ne conservons que les shots dont le syndrome est entièrement nul — ceux pour lesquels aucun contrôle n'a détecté d'erreur. Écarter les shots signalés retire 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. Consulte 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%})")
kept 927 of 1000 shots (93%)