Opakující se kódy
Odhadované využití: méně než 10 sekund na procesoru Heron (POZNÁMKA: Jedná se pouze o odhad. Skutečná doba běhu se může lišit.)
Výsledky učení
-
Jak implementovat kód pro opravu chyb typu bit-flip pomocí dynamických circuits
-
Jak měřit stabilizátory k detekci kvantových chyb, aniž by došlo ke zničení zakódované informace
-
Jak vyhodnotit výkon kvantové korekce chyb porovnáním opravených a neopravených výsledků
Předpoklady
Pozadí
Aby bylo možné provádět kvantovou opravu chyb (QEC) v reálném čase, musíš být schopen/schopna dynamicky řídit průběh kvantového programu během spuštění, takže kvantové Gaty mohou být podmíněny výsledky měření. Tento tutoriál spouští bit-flip kód, což je velmi jednoduchá forma QEC. Demonstruje dynamický kvantový Circuit, který dokáže chránit zakódovaný Qubit před jednou chybou překlápění bitu, a poté vyhodnocuje výkon bit-flip kódu.
Můžeš využít další ancilla Qubity a provázanost k měření stabilizátorů, které netransformují zakódované kvantové informace, přičemž tě stále informují o některých třídách chyb, ke kterým mohlo dojít. Kvantový stabilizátorový kód kóduje logických Qubitů do fyzických Qubitů. Stabilizátorové kódy se kriticky zaměřují na opravu diskrétní sady chyb s podporou Pauliho grupy .
V tomto tutoriálu demonstrujeme kód bit-flip pomocí jednoduchého experimentu s kvantovou pamětí. Připravíme zakódovaný qubit v logickém stavu a poté implementujeme více cyklů prostojů (aby se nashromáždily chyby), po nichž následuje detekce a oprava chyb. Následně kvantifikujeme pravděpodobnost logické chyby jako funkci počtu cyklů (času), přičemž pravděpodobnost logické chyby je pravděpodobnost, že po finálním měření nalezneme qubity ve stavu, který neobnovuje (konkrétně stavy odpovídající chybě jsou , , a ).
Porovnáme míru chyb v závislosti na čase s mírou chyb jednotlivých, nezakódovaných qubitů a také s použitím opakovacího kódu (repetition code), kde chyby detekujeme a opravujeme pouze po finálním měření, nikoli dynamicky během circuitu.
Poznámka: opakovací kód umožňuje opravovat pouze chyby typu bit-flip, a proto se nejedná o úplný kód pro korekci chyb. Díky své jednoduchosti je ale dobrým výchozím bodem pro ilustraci toho, jak implementovat korekci chyb na kvantovém počítači. Níže uvedený paměťový experiment testuje pouze jeden typ chyby (rozpad na ) a technicky demonstruje pouze ochranu klasické informace.
Požadavky
Před zahájením tohoto tutoriálu se ujisti, že máš nainstalováno:
-
Qiskit SDK v2.0 nebo novější, s podporou vizualizace
-
Qiskit Runtime v0.40 nebo novější (
pip install qiskit-ibm-runtime)
Nastavení
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime scipy seaborn
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()
Příklad s malým simulátorem
Tento krok vynecháme, protože cílem tohoto experimentu je změřit životnost logického qubitu při reálném hardwarovém šumu, jako je útlum amplitudy během prostojů; bezšumový simulátor by neukázal žádné chyby k detekci a opravě.
Příklad s rozsáhlým hardwarem
Krok 1. Mapování klasických vstupů na kvantový problém
Výběr backendu
Abychom mohli detekovat chyby během circuitu, musíme vybrat backend, který má přístup k metodě MidCircuitMeasure (viz dokumentace).
# 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
Sestavení sekvence stabilizátorových circuits pro bit-flip implementujících více kol detekce a opravy chyb
Bit-flip kód patří mezi nejjednodušší příklady stabilizátorového kódu. Chrání stav před jednou chybou překlápění bitu (X) na libovolném z kódovacích Qubitů. Uvažuj působení chyby překlápění bitu , která zobrazuje a na libovolném z našich Qubitů; pak máme . Kód vyžaduje pět Qubitů: tři slouží ke kódování chráněného stavu ("datové Qubity") a zbývající dva jsou použity jako ancilla Qubity pro měření stabilizátoru.
Níže sestavíš circuits, které (1) připraví datové qubity ve stavu logického , poté (2) spustí více cyklů zpoždění následovaných korekcí chyb (včetně resetu syndromových qubitů) a (3) přečtou stav datových qubitů.
Otestujeme také životnost stavu bez použití korekce chyb, a to zahrnutím tří referenčních qubitů, které připravíme ve stavu , necháme nečinné a poté je přečteme.
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)

Krok 2: Optimalizace problému pro kvantové spuštění na hardwaru
Aby se zkrátila celková doba provádění úlohy, primitiva Qiskit přijímají pouze Circuity a pozorovatelné veličiny, které odpovídají instrukcím a konektivitě podporované cílovým systémem (označované jako ISA Circuity a pozorovatelné veličiny). Zjisti více o transpilaci.
Generování ISA Circuitů
Začneme nalezením počátečního layoutu (tedy výběru fyzických qubitů, na které namapujeme naše circuits) transpilací nejdelšího z našich circuits pomocí předdefinovaného pass manageru s úrovní optimalizace 3.
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]
U referenčních qubitů, se kterými budeme porovnávat naši logickou kvantovou paměť, zvolíme ty nejlepší dostupné qubity z hlediska koherenčního času útlumu amplitudy ().
# 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)

Krok 3. Spuštění pomocí primitiv Qiskit
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'
Krok 4: Post-processing a vrácení výsledku v požadovaném klasickém formátu
Nyní porovnáme míru chyb v závislosti na čase mezi logickou pamětí využívající 3qubitový opakovací kód na jedné straně a jednotlivými nezakódovanými referenčními qubity na straně druhé.
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

Jasně vidíme zlepšenou životnost stavu , a to i ve srovnání s nejlepšími (nezakódovanými) fyzickými qubity. Mějme však na paměti, že tento experiment opravuje jen jeden typ chyby, totiž bit-flip. Dokážeš životnost logického qubitu zlepšit? Mohl bys zkusit optimalizovat dobu zpoždění, zvětšit velikost opakovacího kódu nad atd.
Další kroky
Pokud tě tato práce zaujala, mohly by tě zajímat následující materiály:
-
Kurz Základy kvantové korekce chyb - dozvíš se více o kvantové korekci chyb
-
Tutoriál Detekce chyb s nízkou režií pomocí spacetime kódů - naučíš se používat Pauli checks k detekci chyb a post-selekci vzorků