Wiederholungscodes
Nutzungsschätzung: weniger als 10 Sekunden auf einem Heron-Prozessor (HINWEIS: Dies ist nur eine Schätzung. Deine Laufzeit kann variieren.)
Lernziele
-
Wie man einen Bit-Flip-Fehlerkorrekturcode mit dynamischen Circuits implementiert
-
Wie man Stabilisatoren misst, um Quantenfehler zu erkennen, ohne die kodierte Information zu zerstören
-
Wie man die Leistung der Quantenfehlerkorrektur bewertet, indem man korrigierte und unkorrigierte Ergebnisse vergleicht
Voraussetzungen
Hintergrund
Um Echtzeit-Quantenfehlerkorrektur (QEC) zu ermöglichen, musst du in der Lage sein, den Quantenprogrammfluss während der Ausführung dynamisch zu steuern, sodass Quantengates von Messergebnissen abhängig gemacht werden können. Dieses Tutorial führt den Bit-Flip-Code aus, der eine sehr einfache Form von QEC darstellt. Es demonstriert einen dynamischen Quantenschaltkreis, der ein kodiertes Qubit vor einem einzelnen Bit-Flip-Fehler schützen kann, und bewertet dann die Leistung des Bit-Flip-Codes.
Du kannst zusätzliche Hilfs-Qubits und Verschränkung nutzen, um Stabilisatoren zu messen, die kodierte Quanteninformation nicht transformieren, während sie dich dennoch über einige Fehlerklassen informieren, die möglicherweise aufgetreten sind. Ein Quanten-Stabilisator-Code kodiert logische Qubits in physische Qubits. Stabilisator-Codes zielen gezielt darauf ab, eine diskrete Fehlermenge zu korrigieren, deren Träger in der Pauli-Gruppe liegt.
In diesem Tutorial demonstrieren wir den Bit-Flip-Code anhand eines einfachen Quantenspeicher-Experiments. Wir bereiten das kodierte Qubit im logischen Zustand vor und führen dann mehrere Zyklen von Leerlaufzeit (um Fehler anzusammeln) gefolgt von Fehlererkennung und -korrektur durch. Anschließend quantifizieren wir die Wahrscheinlichkeit eines logischen Fehlers als Funktion der Anzahl der Zyklen (Zeit), wobei die logische Fehlerwahrscheinlichkeit die Wahrscheinlichkeit ist, die Qubits nach der finalen Messung in einem Zustand vorzufinden, der den -Zustand nicht wiederherstellt (konkret sind die Zustände, die einem Fehler entsprechen, , , und ).
Wir werden die Fehlerrate im Vergleich zur Zeit mit den Fehlerraten einzelner, nicht kodierter Qubits vergleichen sowie mit der Verwendung des Wiederholungscodes, bei dem wir Fehler nur nach der finalen Messung erkennen und korrigieren, jedoch nicht dynamisch während des Circuits.
Hinweis: Der Wiederholungscode erlaubt nur die Korrektur von Bit-Flip-Fehlern und ist daher kein vollständiger Fehlerkorrekturcode. Aufgrund seiner Einfachheit ist er jedoch ein guter Ausgangspunkt, um zu veranschaulichen, wie man Fehlerkorrektur auf einem Quantencomputer implementiert. Das folgende Speicher-Experiment testet nur auf eine einzige Fehlerart (einen Zerfall von zu ) und demonstriert technisch nur den Schutz klassischer Information.
Anforderungen
Stelle vor Beginn dieses Tutorials sicher, dass du Folgendes installiert hast:
-
Qiskit SDK v2.0 oder neuer, mit Visualisierung-Unterstützung
-
Qiskit Runtime v0.40 oder neuer (
pip install qiskit-ibm-runtime)
Einrichtung
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "scipy": "scipy", "seaborn": "seaborn"}
_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
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
sns.set()
plt.rc("xtick", labelsize=20)
plt.rc("ytick", labelsize=20)
plt.rc("lines", linewidth=3)
plt.rc("font", size=20)
plt.rc("legend", fontsize="large")
plt.rc("axes", labelsize=20)
plt.rcParams["figure.figsize"] = 15, 6
plt.rcParams["legend.title_fontsize"] = 25
# Qiskit imports
from qiskit import (
QuantumCircuit,
QuantumRegister,
ClassicalRegister,
)
# qiskit-ibm-runtime
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit_ibm_runtime.circuit import MidCircuitMeasure
service = QiskitRuntimeService()
Kleinmaßstäbliches Simulatorbeispiel
Wir verzichten auf diesen Schritt, da das Ziel dieses Experiments darin besteht, die Lebensdauer eines logischen Qubits unter realem Hardware-Rauschen zu messen, wie z. B. Amplitudendämpfung während der Leerlaufverzögerungen; ein rauschfreier Simulator würde keine Fehler zum Erkennen und Korrigieren zeigen.
Großmaßstäbliches Hardwarebeispiel
Schritt 1: Klassische Eingaben auf ein Quantenproblem abbilden
Ein Backend auswählen
Um Fehler während des Circuits zu erkennen, müssen wir ein Backend auswählen, das Zugriff auf die Methode MidCircuitMeasure hat (siehe die Dokumentation).
# You can see all backends that support mid-circuit measurements by running the following code.
service.backends(filters=lambda b: "measure_2" in b.supported_instructions)
[<IBMBackend('ibm_pittsburgh')>,
<IBMBackend('ibm_boston')>,
<IBMBackend('ibm_kingston')>]
# Choose the least busy backend that supports mid-circuit measurements (`measure_2`).
backend = service.least_busy(
filters=lambda b: "measure_2" in b.supported_instructions,
operational=True,
simulator=False,
dynamic_circuits=True,
)
# backend = service.backend(backend_name) # alternatively, you could choose a specific backend
print(backend.name)
ibm_boston
Eine Sequenz von Bit-Flip-Stabilisator-Circuits erstellen, die mehrere Runden von Fehlererkennung und -korrektur implementieren
Der Bit-Flip-Code gehört zu den einfachsten Beispielen eines Stabilisator-Codes. Er schützt den Zustand vor einem einzelnen Bit-Flip (X)-Fehler auf einem der Kodierungs-Qubits. Betrachte die Wirkung des Bit-Flip-Fehlers , der und auf einem unserer Qubits abbildet, dann haben wir . Der Code benötigt fünf Qubits: Drei werden verwendet, um den geschützten Zustand zu kodieren (die „Daten-Qubits“), und die verbleibenden zwei dienen als Hilfs-Qubits für die Stabilisatormessung.
Im Folgenden baust du Circuits, die (1) die Daten-Qubits im logischen Zustand vorbereiten, dann (2) mehrere Zyklen einer -Verzögerung gefolgt von Fehlerkorrektur (einschließlich Reset der Syndrom-Qubits) durchführen und (3) den Zustand der Daten-Qubits auslesen.
Wir testen außerdem die Lebensdauer des -Zustands ohne Fehlerkorrektur, indem wir drei Referenz-Qubits einbeziehen, die wir im Zustand vorbereiten, im Leerlauf lassen und dann auslesen.
def build_qc(
qreg_data,
qreg_syndrome,
creg_data,
creg_syndrome,
qreg_ref=None,
creg_ref=None,
):
"""Build a typical error correction circuit"""
if qreg_ref:
return QuantumCircuit(
qreg_data,
qreg_syndrome,
creg_data,
creg_syndrome,
qreg_ref,
creg_ref,
)
else:
return QuantumCircuit(
qreg_data, qreg_syndrome, creg_data, creg_syndrome
)
def encode_bit_flip(circuit, qreg_data, qreg_ref=None) -> QuantumCircuit:
"""Encode bit-flip. This is done by simply adding a cx"""
for q in qreg_data:
circuit.x(q)
if qreg_ref:
for q in qreg_ref:
circuit.x(q)
circuit.barrier()
return circuit
def measure_syndrome_bit(
circuit, qreg_data, qreg_syndrome, creg_syndrome, qreg_ref=None
):
"""
Measure the syndrome by measuring the parity.
We reset our ancilla qubits after measuring the stabilizer
so we can reuse them for repeated stabilizer measurements.
Because we have already observed the state of the qubit,
we can write the conditional reset protocol directly to
avoid another round of qubit measurement if we used
the `reset` instruction.
"""
circuit.cx(qreg_data[0], qreg_syndrome[0])
circuit.cx(qreg_data[1], qreg_syndrome[0])
circuit.cx(qreg_data[0], qreg_syndrome[1])
circuit.cx(qreg_data[2], qreg_syndrome[1])
circuit.barrier()
for q_measure, c_measure in zip(qreg_syndrome, creg_syndrome):
circuit.append(MidCircuitMeasure(), [q_measure], [c_measure])
with circuit.if_test((creg_syndrome[0], 1)):
circuit.x(qreg_syndrome[0])
with circuit.if_test((creg_syndrome[1], 1)):
circuit.x(qreg_syndrome[1])
circuit.barrier()
return circuit
def apply_correction_bit(circuit, qreg_data, creg_syndrome):
"""We can detect where an error occurred and correct our state"""
with circuit.if_test((creg_syndrome, 3)):
circuit.x(qreg_data[0])
with circuit.if_test((creg_syndrome, 1)):
circuit.x(qreg_data[1])
with circuit.if_test((creg_syndrome, 2)):
circuit.x(qreg_data[2])
circuit.barrier()
return circuit
def apply_final_readout(
circuit, qreg_data, creg_data, qreg_ref=None, creg_ref=None
):
"""Read out the final measurements"""
circuit.barrier()
if qreg_ref:
circuit.measure(qreg_ref, creg_ref)
circuit.measure(qreg_data, creg_data)
return circuit
def build_error_correction_sequence(
num_cycles, cycles_per_circuit, nq_ref=3, delay=None
) -> QuantumCircuit:
circuits = []
reps = []
qreg_data = QuantumRegister(3, name="qdata")
qreg_syndrome = QuantumRegister(2, name="qsyndrome")
creg_data = ClassicalRegister(3, name="cdata")
creg_syndrome = ClassicalRegister(2, name="csyndrome")
qreg_ref = QuantumRegister(nq_ref, name="qreference")
creg_ref = ClassicalRegister(nq_ref, name="creference")
circuit = build_qc(
qreg_data,
qreg_syndrome,
creg_data,
creg_syndrome,
qreg_ref=qreg_ref,
creg_ref=creg_ref,
)
circuit = encode_bit_flip(circuit, qreg_data, qreg_ref=qreg_ref)
circuit_n = circuit.copy()
circuit_n = apply_final_readout(
circuit_n, qreg_data, creg_data, qreg_ref=qreg_ref, creg_ref=creg_ref
)
circuits.append(circuit_n)
reps.append(0)
for i in range(1, num_cycles + 1):
if delay:
circuit.delay(delay, unit="us")
circuit.barrier()
circuit = measure_syndrome_bit(
circuit,
qreg_data,
qreg_syndrome,
creg_syndrome,
qreg_ref=qreg_ref,
)
circuit = apply_correction_bit(circuit, qreg_data, creg_syndrome)
circuit_n = circuit.copy()
if i % cycles_per_circuit == 0:
circuit_n = apply_final_readout(
circuit_n,
qreg_data,
creg_data,
qreg_ref=qreg_ref,
creg_ref=creg_ref,
)
circuits.append(circuit_n)
reps.append(i)
return circuits, np.array(reps)
num_cycles = 40
cycles_per_circuit = 4
nq_ref = 3
circuits, rep_array = build_error_correction_sequence(
num_cycles, cycles_per_circuit, nq_ref=3, delay=5
)
circuits[1].draw(output="mpl", cregbundle=False, fold=50)

Schritt 2: Problem für die Quantenhardware-Ausführung optimieren
Um die Gesamtausführungszeit des Jobs zu reduzieren, akzeptieren Qiskit-Primitive nur Circuits und Observablen, die den vom Zielsystem unterstützten Anweisungen und der Konnektivität entsprechen (bezeichnet als Instruction-Set-Architecture (ISA)-Circuits und -Observablen). Erfahre mehr über Transpilation.
ISA-Circuits generieren
Wir beginnen damit, ein anfängliches Layout zu finden (das heißt, eine Auswahl physischer Qubits, auf die unsere Circuits abgebildet werden), indem wir den längsten unserer Circuits mit dem voreingestellten Pass-Manager bei Optimierungsstufe 3 transpilieren.
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
isa_circuit_ref = pm.run(circuits[-1])
init_layout = isa_circuit_ref.layout.initial_index_layout(
filter_ancillas=True
)
print(init_layout)
[56, 44, 62, 43, 63, 22, 48, 67]
Für die Referenz-Qubits, mit denen wir unseren logischen Quantenspeicher vergleichen, wählen wir die verfügbaren Qubits mit der besten Kohärenzzeit für Amplitudendämpfung () aus.
# get all qubits ordered by T1
t1_data = []
for i in range(backend.num_qubits):
try:
t1_us = backend.properties().t1(i) * 1e6
except Exception:
t1_us = 0.0
t1_data.append((i, t1_us))
t1_data_sorted = sorted(t1_data, key=lambda x: x[1], reverse=True)
# exclude the qubits we have already mapped the error correcting code to
t1_data_sorted = [
t1_data for t1_data in t1_data_sorted if t1_data[0] not in init_layout[:5]
]
# use the best qubits in terms of T1 for the reference qubits
init_layout = init_layout[:5] + [t1_data[0] for t1_data in t1_data_sorted[:3]]
print(init_layout)
[56, 44, 62, 43, 63, 143, 131, 31]
# These are the resulting T1 times
properties = backend.properties()
print("Amplitude damping decoherence times for code data qubits:")
for q in init_layout[:3]:
t1 = properties.t1(q)
print(f"qubit {q}: T1 = {t1 * 1e6:.0f} mus")
print("\nAmplitude damping decoherence times for reference qubits:")
for q in init_layout[-3:]:
t1 = properties.t1(q)
print(f"qubit {q}: T1 = {t1 * 1e6:.0f} mus")
Amplitude damping decoherence times code data qubits:
qubit 56: T1 = 322 mus
qubit 44: T1 = 263 mus
qubit 62: T1 = 290 mus
Amplitude damping decoherence times reference qubits:
qubit 143: T1 = 442 mus
qubit 131: T1 = 410 mus
qubit 31: T1 = 401 mus
# now we transpile all circuits to this initial layout; this way each circuit is run on the same qubits and we can make a fair comparison
pm = generate_preset_pass_manager(
backend=backend,
optimization_level=3,
initial_layout=init_layout,
)
isa_circuits = pm.run(circuits)
isa_circuits[1].draw("mpl", cregbundle=False, fold=50)

Schritt 3: Mit Qiskit-Primitiven ausführen
sampler = Sampler(mode=backend)
sampler.options.environment.job_tags = ["TUT-REPCODE"]
sampler.options.max_execution_time = 600 # this workload is expected to be under 10s, but it is generally a good habit to set a max execution time (here 600s = 10m)
job = sampler.run(isa_circuits, shots=1000)
print(job.job_id())
job.status()
'DONE'
Schritt 4: Nachbearbeiten und Ergebnis im gewünschten klassischen Format zurückgeben
Wir vergleichen nun den zeitlichen Verlauf der Fehlerraten des logischen Speichers, der den 3-Qubit-Wiederholungscode verwendet, einerseits mit dem der einzelnen nicht kodierten Referenz-Qubits andererseits.
results = job.result()
def correct_counts(counts_dict):
"""
Corrects the measured logical qubit encoded in the repetition code using majority vote
"""
result = {"000": 0, "111": 0}
for bitstring, count in counts_dict.items():
key = "111" if bitstring.count("1") > 1 else "000"
result[key] += count
return result
accuracy = [] # logical qubit
accuracies_ref = np.zeros(
(len(results), nq_ref)
) # accuracies on individual reference qubits
for n, pub_result in enumerate(results):
# logical accuracy (one minus error probability) for active error correction with repetition code
counts = pub_result.data.cdata.get_counts()
shots = sum(counts.values())
counts_corrected = correct_counts(counts)
accuracy.append(counts_corrected.get("111", 0) / shots)
# accuracy for individual physical reference qubits without any error correction
for i in range(nq_ref):
counts = pub_result.data.creference.slice_bits(i).get_counts()
accuracies_ref[n, i] = counts.get("1", 0) / shots
accuracy = np.array(accuracy)
def error_proba_t1(N, t1):
"""
Exponential fitting function for amplitude damping vs. number of cycles
"""
t_cycle = 7.3e-6 # approximate time per cycle = 5 mus delay + 2.3 mus for error correction
return 1 - np.exp(-t_cycle * N / t1)
fig, ax = plt.subplots(1, 1, figsize=(15, 5))
ax.plot(
rep_array, (1.0 - accuracy) * 100.0, "ko-", linewidth=3, label="rep code"
)
for i in range(nq_ref):
accuracy_1q = accuracies_ref[:, i]
if i == 0:
ax.plot(
rep_array,
(1.0 - accuracy_1q) * 100.0,
"go-",
linewidth=1,
label="1q reference",
)
else:
ax.plot(rep_array, (1.0 - accuracy_1q) * 100.0, "go-", linewidth=1)
params_bf, pcov = curve_fit(
error_proba_t1, rep_array, 1.0 - accuracy, bounds=([0, 5e-3])
)
t1_bf = params_bf[0]
print(f"Best-fit effective T1 = {t1_bf * 1e6:.0f} us")
error_prob_bf = error_proba_t1(
rep_array, t1_bf
) # np.array([1 - np.exp(-t_cycle * nt/t1) for nt in rep_array])
ax.plot(rep_array, error_prob_bf * 100.0, "k--", linewidth=1)
ax.set_xlabel("error correction rounds")
ax.set_ylabel("error [%]")
ax.set_ylim(bottom=0)
ax.legend(fontsize=15);
Best-fit effective T1 = 1397 us

Wir sehen deutlich eine verbesserte Lebensdauer des -Zustands, selbst im Vergleich zu den besten (nicht kodierten) physischen Qubits. Bedenke jedoch, dass dieses Experiment nur eine Fehlerart korrigiert, nämlich Bit-Flips. Kannst du die Lebensdauer des logischen Qubits verbessern? Du könntest zum Beispiel die Optimierung der Verzögerungszeit oder die Vergrößerung des Wiederholungscodes über hinaus untersuchen usw.
Nächste Schritte
Wenn du diese Arbeit interessant findest, könnten dich die folgenden Materialien interessieren:
-
Kurs „Grundlagen der Quantenfehlerkorrektur“ – erfahre mehr über Quantenfehlerkorrektur
-
Tutorial „Fehlererkennung mit geringem Overhead mittels Spacetime-Codes“ – lerne, wie man mit Pauli-Checks Fehler erkennt und Stichproben nachselektiert