דלג לתוכן הראשי
פרויקט זה בשלב בטא. מצאת באג? פתח issue ב-GitHub. יש שאלה או רעיון? שאל ב-Discussions — נשמח לשמוע ממך!
מה איכות התרגום בדף זה?

קודי חזרה

הערכת שימוש: פחות מ-10 שניות על מעבד Heron (הערה: זוהי הערכה בלבד. זמן הריצה שלך עשוי להשתנות.)

תוצאות למידה​

  • איך ליישם קוד תיקון שגיאות bit-flip באמצעות מעגלים דינמיים

  • איך למדוד מייצבים כדי לזהות שגיאות קוונטיות מבלי להרוס מידע מקודד

  • איך להעריך את הביצועים של תיקון שגיאות קוונטי על ידי השוואת תוצאות מתוקנות ולא מתוקנות

דרישות מוקדמות​

רקע​

כדי לאפשר תיקון שגיאות קוונטיות (QEC) בזמן אמת, עליך להיות מסוגל לשלוט באופן דינמי בזרימת תוכנית קוונטית במהלך הביצוע, כך שניתן יהיה להתנות שערים קוונטיים בתוצאות מדידה. מדריך זה מריץ את קוד היפוך הביט, שהוא צורה פשוטה מאוד של QEC. הוא מדגים מעגל קוונטי דינמי שיכול להגן על קיוביט מקודד מפני שגיאת היפוך ביט בודדת, ולאחר מכן מעריך את ביצועי קוד היפוך הביט.

ניתן לנצל קיוביטי ancilla נוספים ושזירה כדי למדוד מייצבים שאינם משנים את המידע הקוונטי המקודד, תוך כדי שהם עדיין מודיעים לך על סוגים מסוימים של שגיאות שעלולות להתרחש. קוד מייצב קוונטי מקודד kk קיוביטים לוגיים לתוך nn קיוביטים פיזיים. קודי מייצבים מתמקדים באופן קריטי בתיקון קבוצת שגיאות בדידה עם תמיכה מקבוצת Pauli Π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).

נשווה את שיעור השגיאה מול זמן לשיעורי השגיאה של קיוביטים בודדים לא מקודדים, וגם לשימוש בקוד החזרה שבו אנו רק מזהים ומתקנים שגיאות לאחר המדידה הסופית, אך לא באופן דינמי במהלך ה-Circuit.

הערה: קוד החזרה מאפשר רק תיקון של שגיאות bit-flip ולכן אינו קוד תיקון שגיאות מלא. עם זאת, בשל פשטותו, זוהי נקודת התחלה טובה להדגמת אופן יישום תיקון שגיאות במחשב קוונטי. ניסוי הזיכרון להלן בודק רק סוג שגיאה אחד (∣1ˉ⟩|\bar{1}\rangle שדועך ל-∣0ˉ⟩|\bar{0}\rangle) ומבחינה טכנית מדגים רק את ההגנה על מידע קלאסי.

דרישות​

לפני תחילת מדריך זה, ודא שהדברים הבאים מותקנים:

  • Qiskit SDK גרסה 2.0 ומעלה, עם תמיכה ב-visualization

  • Qiskit Runtime גרסה 0.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. מיפוי קלטים קלאסיים לבעיה קוונטית​

בחר Backend​

כדי לזהות שגיאות במהלך ה-Circuit, עלינו לבחור 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 המיישמים מספר סבבים של זיהוי ותיקון שגיאות​

קוד היפוך הביט הוא אחת הדוגמאות הפשוטות ביותר לקוד מייצב. הוא מגן על המצב מפני שגיאת היפוך ביט (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\}. הקוד דורש חמישה קיוביטים: שלושה משמשים לקידוד המצב המוגן (ה-"data qubits"), והשניים הנותרים משמשים כ-ancilla למדידת מייצבים.

בהמשך תבנה מעגלים ש(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 ואובזרבלים). למד עוד על transpilation.

יצירת מעגלי ISA​

נתחיל במציאת פריסה ראשונית (כלומר, בחירת קיוביטים פיזיים למיפוי המעגלים שלנו) על ידי ביצוע Transpile לארוך שבמעגלים שלנו באמצעות מנהל המעברים המוגדר מראש עם רמת אופטימיזציה 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-flips. האם תוכל לשפר את אורך החיים של הקיוביט הלוגי? כדאי לך לחקור אופטימיזציה של זמן העיכוב, הגדלת גודל קוד החזרה מעבר ל-n=3n=3, וכן הלאה.

השלבים הבאים​

אם מצאת עבודה זו מעניינת, ייתכן שיעניין אותך החומר הבא:

המלצות