انتقل إلى المحتوى الرئيسي
هذا المشروع في مرحلة تجريبية. وجدت خطأ؟ افتح مشكلة على GitHub. لديك سؤال أو فكرة؟ اسأل في Discussions — نحب سماع ملاحظاتك!
كيف جودة الترجمة في هذه الصفحة؟

رموز التكرار

تقدير الاستخدام: أقل من 10 ثوانٍ على معالج Heron (ملاحظة: هذا تقدير فقط. قد يختلف وقت التشغيل الفعلي.)

مخرجات التعلم​

  • كيفية تنفيذ كود تصحيح خطأ انقلاب البت (bit-flip) باستخدام الدوائر الديناميكية

  • كيفية قياس المُثبِّتات (stabilizers) للكشف عن الأخطاء الكمومية دون تدمير المعلومات المشفرة

  • كيفية تقييم أداء تصحيح الأخطاء الكمومية من خلال مقارنة النتائج المصححة وغير المصححة

المتطلبات الأساسية​

الخلفية​

لتمكين تصحيح الأخطاء الكمومية (QEC) في الوقت الفعلي، تحتاج إلى القدرة على التحكم الديناميكي في تدفق البرنامج الكمومي أثناء التنفيذ، بحيث يمكن تشريط البوابات الكمومية على نتائج القياس. يُشغّل هذا البرنامج التعليمي كود انقلاب البت، وهو شكل بسيط جداً من أشكال QEC. يوضح دائرة كمومية ديناميكية قادرة على حماية كيوبت مُشفَّر من خطأ انقلاب بت واحد، ثم يُقيّم أداء كود انقلاب البت.

يمكنك استغلال كيوبتات أنسيلا إضافية والتشابك الكمومي لقياس المثبّتات التي لا تحوّل المعلومات الكمومية المشفرة، مع إبلاغك في الوقت ذاته ببعض فئات الأخطاء التي ربما حدثت. يُشفّر كود المثبّت الكمومي kk كيوبتاً منطقياً في nn كيوبتاً فيزيائياً. تركّز أكواد المثبّت بشكل حاسم على تصحيح مجموعة منفصلة من الأخطاء تقع ضمن زمرة باولي Πn\Pi^n.

في هذا البرنامج التعليمي، نوضح كود انقلاب البت باستخدام تجربة ذاكرة كمومية بسيطة. سنحضّر الـ Qubit المشفر في الحالة المنطقية ∣1ˉ⟩≡∣111⟩|\bar{1}\rangle \equiv |111\rangle ثم ننفذ دورات متعددة من وقت الخمول (لتراكم الأخطاء) تليها عملية الكشف عن الأخطاء وتصحيحها. بعد ذلك نقيس احتمال حدوث خطأ منطقي كدالة لعدد الدورات (الزمن)، حيث احتمال الخطأ المنطقي هو احتمال إيجاد الـ Qubits في حالة لا تستعيد ∣1ˉ⟩|\bar{1}\rangle بعد القياس النهائي (على وجه التحديد، الحالات المقابلة لخطأ هي ∣000⟩|000\rangle و ∣001⟩|001\rangle و ∣010⟩|010\rangle و ∣100⟩|100\rangle).

سنقارن معدل الخطأ مقابل الزمن بمعدلات خطأ الـ Qubits الفردية غير المشفرة، وأيضًا باستخدام رمز التكرار حيث نكتشف ونصحح الأخطاء فقط بعد القياس النهائي، وليس ديناميكيًا أثناء الدارة.

ملاحظة: يسمح كود التكرار فقط بتصحيح أخطاء انقلاب البت، وبالتالي فهو ليس كود تصحيح أخطاء كاملًا. ومع ذلك، ونظرًا لبساطته، فهو نقطة انطلاق جيدة لتوضيح كيفية تنفيذ تصحيح الأخطاء على حاسوب كمومي. تختبر تجربة الذاكرة أدناه نوعًا واحدًا فقط من الأخطاء (انحلال ∣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()

مثال محاكي صغير النطاق​

سنتخطى هذه الخطوة لأن هدف هذه التجربة هو قياس عمر Qubit منطقي تحت ضوضاء العتاد الحقيقية، مثل تخميد السعة (amplitude damping) أثناء تأخيرات الخمول؛ فمحاكي خالٍ من الضوضاء لن يُظهر أي أخطاء للكشف عنها وتصحيحها.

مثال عتاد واسع النطاق​

الخطوة 1. تعيين المدخلات الكلاسيكية إلى مسألة كمومية​

اختيار Backend​

للكشف عن الأخطاء أثناء الدارة، نحتاج إلى اختيار Backend لديه إمكانية الوصول إلى الطريقة 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 stabilizer) تنفذ جولات متعددة من الكشف عن الأخطاء وتصحيحها​

يُعدّ كود انقلاب البت من أبسط أمثلة كود المثبّت. يحمي الحالة من خطأ انقلاب بت واحد (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) تحضّر Qubits البيانات في الحالة المنطقية ∣1ˉ⟩|\bar{1} \rangle، ثم (2) تنفذ دورات متعددة من تأخير مدته 5μs5 \mu s تليها عملية تصحيح الأخطاء (بما في ذلك إعادة تعيين Qubits المتلازمة (syndrome))، و(3) تقرأ حالة Qubits البيانات.

سنختبر أيضًا عمر الحالة ∣1⟩|1\rangle دون استخدام تصحيح الأخطاء من خلال تضمين ثلاثة Qubits مرجعية نحضّرها في الحالة ∣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)). تعرّف على المزيد حول الترجمة (transpilation).

توليد دوائر ISA​

سنبدأ بإيجاد تخطيط أولي (initial layout) (أي اختيار Qubits فيزيائية لتعيين دوائرنا إليها) عن طريق ترجمة (transpile) أطول دوائرنا باستخدام مدير التمريرات (pass manager) المُعد مسبقًا بمستوى تحسين 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]

بالنسبة إلى الـ Qubits المرجعية التي سنقارن بها ذاكرتنا الكمومية المنطقية، سنختارها لتكون أفضل الـ Qubits المتاحة من حيث زمن تماسك تخميد السعة (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: المعالجة اللاحقة وإعادة النتيجة بالصيغة الكلاسيكية المطلوبة​

سنقارن الآن معدلات الخطأ مقابل الزمن بين الذاكرة المنطقية باستخدام رمز التكرار ثلاثي الـ Qubit من جهة، والـ Qubits المرجعية الفردية غير المشفرة من جهة أخرى.

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، حتى بالمقارنة مع أفضل الـ Qubits الفيزيائية (غير المشفرة). ومع ذلك، ضع في اعتبارك أن هذه التجربة تصحح نوعًا واحدًا فقط من الأخطاء، وهو انقلاب البت. هل يمكنك تحسين عمر الـ Qubit المنطقي؟ قد ترغب في استكشاف تحسين وقت التأخير، أو توسيع حجم كود التكرار إلى ما بعد n=3n=3، وما إلى ذلك.

الخطوات التالية​

إذا وجدت هذا العمل مثيرًا للاهتمام، فقد تهتم بالمواد التالية:

التوصيات