Перейти до основного вмісту
Цей проєкт у бета-версії. Знайшов(-ла) помилку? Відкрити issue на GitHub. Є запитання чи ідея? Запитати в Discussions — будемо раді твоєму відгуку!
Яка якість перекладу на цій сторінці?

Коди повторення

Оцінка використання: менше 10 секунд на процесорі Heron (ПРИМІТКА: Це лише оцінка. Твій час виконання може відрізнятися.)

Результати навчання​

  • Як реалізувати код виправлення помилок типу bit-flip за допомогою динамічних схем

  • Як вимірювати стабілізатори для виявлення квантових помилок без руйнування закодованої інформації

  • Як оцінити продуктивність квантового виправлення помилок, порівнюючи виправлені та невиправлені результати

Передумови​

Загальна інформація​

Щоб забезпечити квантове виправлення помилок (QEC) у реальному часі, тобі потрібна можливість динамічно контролювати потік квантової програми під час виконання, щоб квантові вентилі могли залежати від результатів вимірювань. Цей посібник запускає код перевертання біта, який є дуже простою формою QEC. Він демонструє динамічну квантову схему, яка може захистити закодований кубіт від однієї помилки перевертання біта, а потім оцінює продуктивність коду перевертання біта.

Ти можеш використовувати додаткові допоміжні кубіти та заплутаність для вимірювання стабілізаторів, які не перетворюють закодовану квантову інформацію, водночас інформуючи тебе про деякі класи помилок, які могли статися. Квантовий код стабілізатора кодує kk логічних кубітів у nn фізичних кубітів. Стабілізаторні коди насамперед спрямовані на виправлення дискретного набору помилок, носій яких належить групі Паулі Πn\Pi^n.

У цьому туторіалі ми демонструємо код перевертання біта за допомогою простого експерименту з квантовою пам'яттю. Ми підготуємо закодований кубіт у логічному стані ∣1ˉ⟩≡∣111⟩|\bar{1}\rangle \equiv |111\rangle, а потім реалізуємо кілька циклів простою (для накопичення помилок), за якими йде виявлення та виправлення помилок. Далі ми кількісно оцінюємо ймовірність логічної помилки як функцію кількості циклів (часу), де ймовірність логічної помилки — це ймовірність опинитися в стані, який не відновлює ∣1ˉ⟩|\bar{1}\rangle після фінального вимірювання (конкретно, станами, що відповідають помилці, є ∣000⟩|000\rangle, ∣001⟩|001\rangle, ∣010⟩|010\rangle та ∣100⟩|100\rangle).

Ми порівняємо частоту помилок у часі з частотами помилок окремих незакодованих кубітів, а також із використанням коду повторення, де ми виявляємо та виправляємо помилки лише після фінального вимірювання, а не динамічно під час виконання схеми.

Примітка: код повторення дозволяє виправляти лише помилки типу bit-flip, а тому не є повноцінним кодом виправлення помилок. Однак завдяки своїй простоті він є гарною відправною точкою для ілюстрації того, як реалізувати виправлення помилок на квантовому комп'ютері. Наведений нижче експеримент із пам'яттю перевіряє лише один тип помилки (розпад ∣1ˉ⟩|\bar{1}\rangle у ∣0ˉ⟩|\bar{0}\rangle) і технічно демонструє лише захист класичної інформації.

Вимоги​

Перед початком цього посібника переконайся, що у тебе встановлено наступне:

  • Qiskit SDK v2.0 або новіший, з підтримкою візуалізації

  • Qiskit Runtime v0.40 або новіший (pip install qiskit-ibm-runtime)

Налаштування​

# 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()

Приклад на малому симуляторі​

Ми відмовимося від цього кроку, оскільки мета цього експерименту — виміряти час життя логічного кубіта в умовах реального апаратного шуму, наприклад амплітудного затухання під час затримок простою; симулятор без шуму не показав би жодних помилок для виявлення та виправлення.

Приклад на великомасштабному апаратному забезпеченні​

Крок 1. Відображення класичних входів на квантову задачу​

Вибір бекенда​

Щоб виявляти помилки під час виконання схеми, нам потрібно обрати бекенд, який має доступ до методу MidCircuitMeasure (див. документацію).

# 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

Побудова послідовності стабілізаторних схем bit-flip, що реалізують кілька раундів виявлення та виправлення помилок​

Код перевертання біта є одним з найпростіших прикладів коду стабілізатора. Він захищає стан від однієї помилки перевертання біта (X) на будь-якому з кодувальних кубітів. Розглянемо дію помилки перевертання біта XX, яка відображає ∣0⟩→∣1⟩|0\rangle \rightarrow |1\rangle і ∣1⟩→∣0⟩|1\rangle \rightarrow |0\rangle на будь-якому з наших кубітів, тоді ми маємо ϵ={E0,E1,E2}={IIX,IXI,XII}\epsilon = \{E_0, E_1, E_2 \} = \{IIX, IXI, XII\}. Код вимагає п'яти кубітів: три використовуються для кодування захищеного стану ("кубіти даних"), а два інші використовуються як допоміжні для вимірювання стабілізатора.

Нижче ти побудуєш схеми, які (1) готують кубіти даних у логічному стані ∣1ˉ⟩|\bar{1} \rangle, потім (2) виконують кілька циклів затримки 5μs5 \mu s, за якими йде виправлення помилок (включно зі скиданням синдромних кубітів), і (3) зчитують стан кубітів даних.

Ми також перевіримо час життя стану ∣1⟩|1\rangle без використання виправлення помилок, включивши три еталонні кубіти, які ми готуємо в стані ∣1⟩|1\rangle, залишаємо в спокої, а потім зчитуємо.

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)

Output of the previous code cell

Крок 2. Оптимізація задачі для виконання на квантовому обладнанні​

Щоб зменшити загальний час виконання завдання, примітиви Qiskit приймають лише схеми та спостережувані величини, що відповідають інструкціям і зв'язності, які підтримує цільова система (які називаються схемами та спостережуваними величинами архітектури набору інструкцій (ISA)). Дізнайся більше про транспіляцію.

Генерація ISA-схем​

Ми почнемо з пошуку початкового розташування (тобто вибору фізичних кубітів, на які буде відображено наші схеми), транспілюючи найдовшу з наших схем за допомогою готового менеджера пасів з рівнем оптимізації 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]

Для еталонних кубітів, з якими ми порівнюватимемо нашу логічну квантову пам'ять, ми оберемо найкращі доступні кубіти за часом когерентності амплітудного затухання (T1T_1).

# 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)

Output of the previous code cell

Крок 3. Виконання за допомогою примітивів 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'

Крок 4: постобробка та повернення результату в бажаному класичному форматі​

Тепер ми порівняємо частоти помилок у часі між логічною пам'яттю, що використовує 3-кубітний код повторення, з одного боку, та окремими незакодованими еталонними кубітами — з іншого.

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

Output of the previous code cell

Ми чітко бачимо покращений час життя стану ∣1ˉ⟩|\bar{1}\rangle, навіть порівняно з найкращими (незакодованими) фізичними кубітами. Однак пам'ятай, що цей експеримент виправляє лише один тип помилки, а саме bit-flip. Чи можеш ти покращити час життя логічного кубіта? Можливо, варто дослідити оптимізацію часу затримки, збільшення розміру коду повторення понад n=3n=3 тощо.

Наступні кроки​

Якщо ця робота здалася тобі цікавою, тебе можуть зацікавити такі матеріали:

Рекомендації