انتقل إلى المحتوى الرئيسي

الضوضاء الكمية وتخفيف الأخطاء

ملاحظة

Toshinari Itoko (28 يونيو 2024)

تنزيل ملف PDF للمحاضرة الأصلية. لاحظ أن بعض مقاطع الكود قد تصبح قديمة لأنها صور ثابتة.

الوقت التقريبي لتشغيل هذه التجربة على وحدة المعالجة الكمية (QPU) هو 1 دقيقة و40 ثانية.

1. مقدمة

في هذا الدرس، سنتناول الضوضاء وكيفية تخفيفها على أجهزة الحوسبة الكمية. سنبدأ بفحص تأثيرات الضوضاء باستخدام محاكٍ يمكنه محاكاة الضوضاء بعدة طرق، من بينها استخدام ملفات تعريف الضوضاء من أجهزة الحوسبة الكمية الفعلية. ثم سننتقل إلى أجهزة الحوسبة الكمية الحقيقية التي تنطوي على ضوضاء متأصلة. سنستعرض تأثيرات تخفيف الأخطاء، بما في ذلك مزيج من تقنيات مثل الاستقراء عند الضوضاء الصفرية (ZNE) وتدوير البوابات (gate-twirling).

سنبدأ بتحميل بعض الحزم.

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib qiskit qiskit-aer qiskit-ibm-runtime
# !pip install qiskit qiskit_aer qiskit_ibm_runtime
# !pip install jupyter
# !pip install matplotlib pylatexenc
import qiskit

qiskit.__version__
'2.0.2'
import qiskit_aer

qiskit_aer.__version__
'0.17.1'
import qiskit_ibm_runtime

qiskit_ibm_runtime.__version__
'0.40.1'

2. المحاكاة الصاخبة بدون تخفيف للأخطاء

Qiskit Aer هو محاكٍ كلاسيكي للحوسبة الكمية. بإمكانه محاكاة التنفيذ المثالي وكذلك التنفيذ الصاخب للدوائر الكمية. يوضح هذا الدفتر كيفية تشغيل المحاكاة الصاخبة باستخدام Qiskit Aer:

  1. بناء نموذج ضوضاء
  2. بناء أداة أخذ عينات صاخبة (محاكٍ) باستخدام نموذج الضوضاء
  3. تشغيل دائرة كمية على أداة أخذ العينات الصاخبة
noise_model = NoiseModel()
...
noisy_sampler = Sampler(options={"backend_options": {"noise_model": noise_model}})
job = noisy_sampler.run([circuit])

2.1 بناء دائرة اختبار

نأخذ بعين الاعتبار دوائر بسيطة بكيوبت واحد تكرر بوابات X عدد d من المرات (d=0 ... 100) وتقيس المتمثل Z.

from qiskit.circuit import QuantumCircuit

MAX_DEPTH = 100
circuits = []
for d in range(MAX_DEPTH + 1):
circ = QuantumCircuit(1)
for _ in range(d):
circ.x(0)
circ.barrier(0)
circ.measure_all()
circuits.append(circ)

display(circuits[3].draw(output="mpl"))

Output of the previous code cell

from qiskit.quantum_info import SparsePauliOp

obs = SparsePauliOp.from_list([("Z", 1.0)])
obs
SparsePauliOp(['Z'],
coeffs=[1.+0.j])

2.2 بناء نموذج الضوضاء

لإجراء المحاكاة الصاخبة، نحتاج إلى تحديد NoiseModel. نوضح في هذا القسم كيفية بناء NoiseModel. أولاً، نحتاج إلى تعريف أخطاء كمية (أو أخطاء قراءة) لإضافتها إلى نموذج الضوضاء.

from qiskit_aer.noise.errors import (
coherent_unitary_error,
amplitude_damping_error,
ReadoutError,
)
from qiskit.circuit.library import RXGate

# Coherent (unitary) error: Over X-rotation error
# https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.coherent_unitary_error.html#qiskit_aer.noise.coherent_unitary_error
OVER_ROTATION_ANGLE = 0.05
coherent_error = coherent_unitary_error(RXGate(OVER_ROTATION_ANGLE).to_matrix())

# Incoherent error: Amplitude dumping error
# https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.amplitude_damping_error.html#qiskit_aer.noise.amplitude_damping_error
AMPLITUDE_DAMPING_PARAM = 0.02 # in [0, 1] (0: no error)
incoherent_error = amplitude_damping_error(AMPLITUDE_DAMPING_PARAM)

# Readout (measurement) error: Readout error
# https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.ReadoutError.html#qiskit_aer.noise.ReadoutError
PREP0_MEAS1 = 0.03 # P(1|0): Probability of preparing 0 and measuring 1
PREP1_MEAS0 = 0.08 # P(0|1): Probability of preparing 1 and measuring 0
readout_error = ReadoutError(
[[1 - PREP0_MEAS1, PREP0_MEAS1], [PREP1_MEAS0, 1 - PREP1_MEAS0]]
)
from qiskit_aer.noise import NoiseModel

noise_model = NoiseModel()
noise_model.add_quantum_error(coherent_error.compose(incoherent_error), "x", (0,))
noise_model.add_readout_error(readout_error, (0,))

2.3 بناء أداة أخذ عينات صاخبة باستخدام نموذج الضوضاء

from qiskit_aer.primitives import SamplerV2 as Sampler

noisy_sampler = Sampler(options={"backend_options": {"noise_model": noise_model}})

2.4 تشغيل الدوائر الكمية على أداة أخذ العينات الصاخبة

job = noisy_sampler.run(circuits, shots=400)
result = job.result()
result[0].data.meas.get_counts()
{'0': 389, '1': 11}

2.5 رسم النتائج

import matplotlib.pyplot as plt

plt.title("Noisy simulation")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
ds,
[result[d].data.meas.expectation_values(["Z"]) for d in ds],
color="gray",
linestyle="-",
)
plt.scatter(ds, [result[d].data.meas.expectation_values(["Z"]) for d in ds], marker="o")
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.ylim(-1, 1)
plt.xlabel("Circuit depth")
plt.ylabel("Measured <Z>")
plt.show()

2.6 المحاكاة المثالية

ideal_sampler = Sampler()
job_ideal = ideal_sampler.run(circuits)
result_ideal = job_ideal.result()
plt.title("Ideal simulation")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
ds,
[result_ideal[d].data.meas.expectation_values(["Z"]) for d in ds],
color="gray",
linestyle="-",
)
plt.scatter(
ds, [result_ideal[d].data.meas.expectation_values(["Z"]) for d in ds], marker="o"
)
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.xlabel("Circuit depth")
plt.ylabel("Measured <Z>")
plt.show()

Output of the previous code cell

2.7 تمرين

بتعديل الكود أدناه،

  • جرب 25 ضعف عدد اللقطات (= 10_000 لقطة) وتأكد من الحصول على رسم بياني أكثر سلاسة
  • غيّر معاملات الضوضاء (OVER_ROTATION_ANGLE أو AMPLITUDE_DAMPING_PARAM أو PREP0_MEAS1 أو PREP1_MEAS0) وانظر كيف يتغير الرسم البياني
OVER_ROTATION_ANGLE = 0.05
coherent_error = coherent_unitary_error(RXGate(OVER_ROTATION_ANGLE).to_matrix())
AMPLITUDE_DAMPING_PARAM = 0.02 # in [0, 1] (0: no error)
incoherent_error = amplitude_damping_error(AMPLITUDE_DAMPING_PARAM)
PREP0_MEAS1 = 0.1 # P(1|0): Probability of preparing 0 and measuring 1
PREP1_MEAS0 = 0.05 # P(0|1): Probability of preparing 1 and measuring 0
readout_error = ReadoutError(
[[1 - PREP0_MEAS1, PREP0_MEAS1], [PREP1_MEAS0, 1 - PREP1_MEAS0]]
)
noise_model = NoiseModel()
noise_model.add_quantum_error(coherent_error.compose(incoherent_error), "x", (0,))
noise_model.add_readout_error(readout_error, (0,))
options = {
"backend_options": {"noise_model": noise_model},
}
noisy_sampler = Sampler(options=options)
job = noisy_sampler.run(circuits, shots=400)
result = job.result()
plt.title("Noisy simulation")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
ds,
[result[d].data.meas.expectation_values(["Z"]) for d in ds],
marker="o",
linestyle="-",
)
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.ylim(-1, 1)
plt.xlabel("Depth")
plt.ylabel("Measured <Z>")
plt.show()

Output of the previous code cell

2.8 محاكاة أكثر واقعية للضوضاء

from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import SamplerV2 as Sampler, QiskitRuntimeService

service = QiskitRuntimeService()
real_backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
) # Eagle
<IBMBackend('ibm_strasbourg')>
aer = AerSimulator.from_backend(real_backend)
noisy_sampler = Sampler(mode=aer)
job = noisy_sampler.run(circuits)
result = job.result()
plt.title("Noisy simulation with noise model from real backend")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
ds,
[result[d].data.meas.expectation_values(["Z"]) for d in ds],
marker="o",
linestyle="-",
)
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.ylim(-1, 1)
plt.xlabel("Depth")
plt.ylabel("Measured <Z>")
plt.show()

Output of the previous code cell

3. الحوسبة الكمومية الحقيقية مع تخفيف الأخطاء

في هذا الجزء، نوضح كيفية الحصول على نتائج مُخففة الأخطاء (قيم التوقع) باستخدام Qiskit Estimator. نأخذ دوائر Trotterized مكونة من 6 كيوبتات لمحاكاة التطور الزمني لنموذج Ising أحادي البُعد، ونرى كيف يتدرج الخطأ بالنسبة لعدد خطوات الوقت.

backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
) # Eagle
backend
<IBMBackend('ibm_strasbourg')>
NUM_QUBITS = 6
NUM_TIME_STEPS = list(range(8))
RX_ANGLE = 0.1
RZZ_ANGLE = 0.1

3.1 بناء الدوائر

# Build circuits with different number of time steps
circuits = []
for n_steps in NUM_TIME_STEPS:
circ = QuantumCircuit(NUM_QUBITS)
for i in range(n_steps):
# rx layer
for q in range(NUM_QUBITS):
circ.rx(RX_ANGLE, q)
# 1st rzz layer
for q in range(1, NUM_QUBITS - 1, 2):
circ.rzz(RZZ_ANGLE, q, q + 1)
# 2nd rzz layer
for q in range(0, NUM_QUBITS - 1, 2):
circ.rzz(RZZ_ANGLE, q, q + 1)
circ.barrier() # need not to optimize the circuit
# Uncompute stage
for i in range(n_steps):
for q in range(0, NUM_QUBITS - 1, 2):
circ.rzz(-RZZ_ANGLE, q, q + 1)
for q in range(1, NUM_QUBITS - 1, 2):
circ.rzz(-RZZ_ANGLE, q, q + 1)
for q in range(NUM_QUBITS):
circ.rx(-RX_ANGLE, q)
circuits.append(circ)

لمعرفة الناتج المثالي مسبقًا، نستخدم دوائر compute-uncompute التي تتكون من مرحلة أولى يُطبَّق فيها الدائر الأصلي UU، ومرحلة ثانية يُعكس فيها UU^\dagger. لاحظ أن الناتج المثالي لمثل هذه الدوائر سيكون بشكل بديهي هو الحالة المدخلة 000000|000000\rangle، التي لها قيم توقع تافهة لأي مراقبات Pauli، مثلاً IIIIIZ=1\langle IIIIIZ \rangle = 1.

# Print the circuit with 2 time steps
circuits[2].draw(output="mpl")

Output of the previous code cell

ملاحظة: كما هو موضح أعلاه، ستحتوي الدائرة ذات kk خطوة زمنية على 4k4k طبقات من البوابات ثنائية الكيوبت.

obs = SparsePauliOp.from_sparse_list([("Z", [0], 1.0)], num_qubits=NUM_QUBITS)
obs
SparsePauliOp(['IIIIIZ'],
coeffs=[1.+0.j])

3.2 تحويل الدوائر

نحوّل الدوائر للخلفية مع التحسين (optimization_level=1).

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuits = pm.run(circuits)
display(isa_circuits[2].draw("mpl", idle_wires=False, fold=-1))

Output of the previous code cell

3.3 التنفيذ باستخدام Estimator (مع مستويات مرونة مختلفة)

ضبط مستوى المرونة (estimator.options.resilience_level) هو أسهل طريقة لتطبيق تخفيف الأخطاء عند استخدام Qiskit Estimator. يدعم Estimator مستويات المرونة التالية (اعتبارًا من 2024/06/28). راجع المزيد من التفاصيل في دليل ضبط تخفيف الأخطاء.

image.png

from qiskit_ibm_runtime import Batch
from qiskit_ibm_runtime import EstimatorV2 as Estimator

jobs = []
job_ids = []
with Batch(backend=backend):
for resilience_level in [0, 1, 2]:
estimator = Estimator()
estimator.options.resilience_level = resilience_level
job = estimator.run(
[(circ, obs.apply_layout(circ.layout)) for circ in isa_circuits]
)
job_ids.append(job.job_id())
print(f"Job ID (rl={resilience_level}): {job.job_id()}")
jobs.append(job)
Job ID (rl=0): d146vcnmya70008emprg
Job ID (rl=1): d146vdnqf56g0081sva0
Job ID (rl=2): d146ven5z6q00087c61g
# check job status
for job in jobs:
print(job.status())
DONE
DONE
DONE
# REPLACE WITH YOUR OWN JOB IDS
jobs = [service.job(job_id) for job_id in job_ids]
# Get results
results = [job.result() for job in jobs]

3.4 رسم النتائج

plt.title("Error mitigation with different resilience levels")
labels = ["0 (No mitigation)", "1 (TREX)", "2 (ZNE + Gate twirling)"]
steps = NUM_TIME_STEPS
for result, label in zip(results, labels):
plt.errorbar(
x=steps,
y=[result[s].data.evs for s in steps],
yerr=[result[s].data.stds for s in steps],
marker="o",
linestyle="-",
capsize=4,
label=label,
)
plt.hlines(
1.0, min(steps), max(steps), linestyle="dashed", label="Ideal", colors="black"
)
plt.xlabel("Time steps")
plt.ylabel("Mitigated <IIIIIZ>")
plt.legend()
plt.show()

Output of the previous code cell

4. (اختياري) تخصيص خيارات تخفيف الأخطاء

يمكننا تخصيص تطبيق تقنيات تخفيف الأخطاء عبر الخيارات كما هو موضح أدناه.

# TREX
estimator.options.twirling.enable_measure = True
estimator.options.twirling.num_randomizations = "auto"
estimator.options.twirling.shots_per_randomization = "auto"

# Gate twirling
estimator.options.twirling.enable_gates = True
# ZNE
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = [1, 3, 5]
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")

# Dynamical decoupling
estimator.options.dynamical_decoupling.enable = True # Default: False
estimator.options.dynamical_decoupling.sequence_type = "XX"

# Other options
estimator.options.default_shots = 10_000

راجع الأدلة والمرجع التالية للاطلاع على تفاصيل خيارات تخفيف الأخطاء.