تجربة على نطاق الفائدة I
تاميا أونودِيرا (5 يوليو 2024)
تحميل ملف PDF للمحاضرة الأصلية. لاحظ أن بعض مقاطع الكود قد تكون قديمة لأنها صور ثابتة.
الوقت التقريبي للمعالج الكمومي لتشغيل هذه التجربة هو 45 ثانية.
1. مقدمة إلى ورقة الفائدة
في هذا الدرس، نُشغِّل دائرة كمومية على نطاق الفائدة تظهر في ما نسميه بشكل غير رسمي "ورقة الفائدة" المنشورة في Nature المجلد 618، 15 يونيو 2023. تتناول الورقة تطور نموذج إيزنغ ثنائي الأبعاد للمجال المستعرض بالزمن. بالتحديد، يدرسون ديناميكيات الزمن لهاميلتونيان:
حيث هو اقتران الدورانات المتجاورة مع و هو المجال المستعرض الكلي. يُحاكون ديناميكيات الدوران من حالة ابتدائية باستخدام تحليل تروتر من الرتبة الأولى لمؤثر التطور الزمني:
حيث يُجزَّأ زمن التطور إلى خطوة تروتر، و و هما بوابتا الدوران و على التوالي.
أجروا التجارب على معالج IBM Quantum® Eagle، وهو جهاز مؤلف من 127 كيوبت بتوصيل سداسي ثقيل، طبّقوا فيه تفاعلات على جميع الكيوبتات وتفاعلات على جميع أضلاع خريطة الاقتران. لاحظ أنه لا يمكن تطبيق جميع تفاعلات في آنٍ واحد بسبب "الاعتماد على البيانات". لذا يُلو ِّنون خريطة الاقتران لتجميعها في طبقات. تُعطى كل طبقة اللون نفسه، ويمكن تطبيقها بالتوازي.
بالإضافة إلى ذلك، ولبساطة التجربة، ركّزوا على الحالة .
المساهمة الجديدة للورقة هي أنهم بنوا دوائر كمومية على نطاق يتخطى محاكاة متجه الحالة، وشغّلوها على حواسيب كمومية مضوضأة، ونجحوا في استخلاص نتائج موثوقة. أي أنهم أثبتوا الفائدة العملية للحواسيب الكمومية المضوضأة. وفي سبيل ذلك، طبّقوا إسقاط إضافة الضوضاء الصفرية (ZNE) مع تضخيم الخطأ الاحتمالي (PEA) للتخفيف من أخطاء الأجهزة المضوضأة.
منذ ذلك الحين، أصبحنا نسمي مثل هذه التجارب والدوائر "ذات نطاق الفائدة".
1.1 هدفك
هدفك في هذا الدرس هو بناء دائرة كمومية على نطاق الفائدة وتشغيلها على معالج Eagle. يخرج عن نطاق هذا الكتاب استخلاص نتائج موثوقة، جزئياً لأن PEA ميزة تجريبية في Qiskit حتى وقت كتابة هذا النص، وجزئياً لأن تطبيق ZNE مع PEA سيستغرق وقتاً طويلاً.
بالتحديد، أنت مطلوب منك بناء وتشغيل الدائرة المقابلة للشكل 4b في الورقة، ورسم نقاط "غير مُخففة" خاصة بك. كما ترى، هي دائرة بـ127 كيوبت × 60 طبقة (20 خطوة تروتر) مع كمراقَب.
يبدو الأمر ضخماً؟ لا تقلق. الدروس الثلاثة الأخيرة من هذه الدورة تُمهِّد الطريق. للبداية، سنوضح تجربة أصغر حجماً تتمثل في بناء وتشغيل على جهاز افتراضي دائرة بـ27 كيوبت × 6 طبقات (2 خطوة تروتر) مع كمراقَب.
هذا كل ما في المقدمة. لننطلق في مغامرة نطاق الفائدة!
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime rustworkx
import qiskit
qiskit.__version__
'2.0.2'
#!pip install qiskit_ibm_runtime
#!pip install qiskit_aer
import matplotlib.pyplot as plt
import numpy as np
import rustworkx as rx
from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.circuit.library import YGate
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_runtime import (
QiskitRuntimeService,
fake_provider,
EstimatorV2 as Estimator,
)
from qiskit_aer import AerSimulator
service = QiskitRuntimeService()
2. التحضير
2.1 بناء RZZ(- / 2)
أولاً، لاحظ أن بوابة RZZ بشكل عام تتطلب بوابتَي .
from qiskit.circuit.library import RZZGate
θ_h = Parameter("$\\theta_h$")
qc1 = QuantumCircuit(2)
qc1.append(RZZGate(θ_h), [0, 1])
qc1.decompose(reps=1).draw("mpl")
كما ذُكر أعلاه، نُركِّز على بوابة RZZ بزاوية محددة وهي - / 2 لهذه التجربة. كما هو موضح في الورقة، يمكن تحقيقها ببوابة واحدة فقط.
qc2 = QuantumCircuit(2)
qc2.sdg([0, 1])
qc2.append(YGate().power(1 / 2), [1])
qc2.cx(0, 1)
qc2.append(YGate().power(1 / 2).adjoint(), [1])
qc2.draw("mpl")
نُعرِّف بوابة انطلاقاً من هذه الدائرة للرجوع إليها لاحقاً.
rzz = qc2.to_gate(label="RZZ")
لنجرب استخداماً عشوائياً للبوابة rzz التي عرّفناها للتو.
qc3 = QuantumCircuit(3)
qc3.append(rzz, [0, 1])
qc3.append(rzz, [0, 2])
display(qc3.draw("mpl"))
# display(qc.decompose(reps=1).draw("mpl"))
قبل المضي قُدُماً، لنتحقق من التكافؤ المنطقي بين qc1 (بوابة RZZ) للزاوية -pi/2 وبوابة rzz أو qc2 التي عرّفناها:
from qiskit.quantum_info import Operator
op1 = Operator(qc1.assign_parameters([-np.pi / 2]))
op2 = Operator(qc2)
op1.equiv(op2)
True
2.2 تلوين خريطة الاقتران
لندرس كيف نُلوِّن خريطة اقتران خلفية. هذا ضروري لتجميع تفاعلات في طبقات.
للبداية، لنعرض خريطة اقتران خلفية. لاحظ أن خرائط الاقتران هي سداسية ثقيلة لجميع أجهزة IBM Quantum الحالية.
backend = service.least_busy(operational=True, simulator=False)
backend.coupling_map.draw()

لتلوين خريطة الاقتران، نستخدم rustworkx، وهي حزمة Python للتعامل مع الرسوم البيانية والشبكات المعقدة. تُوفر خوارزميات تلوين متعددة، وهي جميعها إرشادية وبالتالي لا تضمن إيجاد تلوين أدنى.
مع ذلك، بما أن الرسوم البيانية السداسية الثقيلة ثنائية التقسيم، نختار graph_bipartite_edge_color، الذي ينبغي أن يجد تلويناً أدنى لهذه الرسوم.
def color_coupling_map(backend):
graph = backend.coupling_map.graph
undirected_graph = graph.to_undirected(multigraph=False)
edge_color_map = rx.graph_bipartite_edge_color(undirected_graph)
if edge_color_map is None:
edge_color_map = rx.graph_greedy_edge_color(undirected_graph)
# build a map from color to a list of edges
edge_index_map = undirected_graph.edge_index_map()
color_edges_map = {color: [] for color in edge_color_map.values()}
for edge_index, color in edge_color_map.items():
color_edges_map[color].append(
(edge_index_map[edge_index][0], edge_index_map[edge_index][1])
)
return edge_color_map, color_edges_map
ينبغي رسم الرسوم البيانية السداسية الثقيلة بثلاثة ألوان. لنتحقق من ذلك لخريطة الاقتران أعلاه.
edge_color_map, color_edges_map = color_coupling_map(backend)
print(
f"{backend.name}, {backend.num_qubits}-qubit device, {len(color_edges_map.keys())} colors assigned."
)
ibm_strasbourg, 127-qubit device, 3 colors assigned.
صحيح!
للمتعة، لنرسم خريطة الاقتران بالألوان التي حصلنا عليها، مستخدمين ميزة تصوير rustworkx.
color_str_map = {0: "green", 1: "red", 2: "blue"}
undirected_graph = backend.coupling_map.graph.to_undirected(multigraph=False)
for i in undirected_graph.edge_indices():
undirected_graph.get_edge_data_by_index(i)["color"] = color_str_map[
edge_color_map[i]
]
rx.visualization.graphviz_draw(
undirected_graph, method="neato", edge_attr_fn=lambda edge: {"color": edge["color"]}
)

3. حل التطور الزمني بتروتر لنموذج إيزنغ ثنائي الأبعاد
لنُعرِّف روتيناً لبناء دائرة ورقة الفائدة لتطور نموذج إيزنغ ثنائي الأبعاد بالزمن. يأخذ الروتين ثلاثة معاملات: خلفية، وعدداً صحيحاً يشير إلى عدد خطوات تروتر، ومتغير منطقي يتحكم في إدراج الحواجز.
def get_utility_circuit(backend, num_steps: int, barrier: bool = False):
num_qubits = backend.num_qubits
_, color_edges_map = color_coupling_map(backend)
θ_h = Parameter("$\\theta_h$")
qc = QuantumCircuit(num_qubits)
for i in range(num_steps):
qc.rx(θ_h, range(num_qubits))
for _, edge_list in color_edges_map.items():
for edge in edge_list:
qc.append(rzz, edge)
if barrier:
qc.barrier()
return qc
لاحظ أننا أجرينا بالفعل يدوياً رسم الكيوبتات وتوجيهها للدائرة المبنية. لذا، عند نقل الدائرة لاحقاً، لا ينبغي (ولا يجب) أن نطلب من المُحوِّل القيام برسم الكيوبتات وتوجيهها. كما ستلاحظ قريباً، نستدعيه بمستوى تحسين 1 وطريقة تخطيط "trivial".
بعد ذلك نُعرِّف روتيناً بسيطاً للحصول على معلومات عن الدائرة المبنية للفحص السريع.
def get_circuit_info(qc: QuantumCircuit, reps: int = 0):
qc0 = qc.decompose(reps=reps)
return (
f"{qc0.num_qubits} qubits × {qc0.depth(lambda x: x.operation.num_qubits == 2)} layers ({qc0.depth()}-depth)"
+ ", "
+ f"""Gate breakdown: {", ".join([f"{k.upper()} {v}" for k, v in qc0.count_ops().items()])}"""
)
لنجرب هذه الروتينات. يجب أن ترى دائرة بـ27 كيوبت × 15 طبقة (5 خطوات تروتر). بما أن الجهاز الافتراضي لديه 28 ضلعاً، يجب أن يكون هناك 28*5 بوابات تشابك.
backend = fake_provider.FakeTorontoV2()
num_steps = 5
qc = get_utility_circuit(backend, num_steps, True)
display(qc.draw(output="mpl", fold=-1))
print(get_circuit_info(qc, reps=0))
print(get_circuit_info(qc, reps=1))

27 qubits × 15 layers (20-depth), Gate breakdown: CIRCUIT-165 140, RX 135, BARRIER 5
27 qubits × 15 layers (60-depth), Gate breakdown: SDG 280, UNITARY 280, CX 140, R 135, BARRIER 5
4. حل نسخة 27 كيوبت من المسألة
نوضح الآن نسخة أصغر حجماً من تجربة الفائدة. سنبني دائرة بـ27 كيوبت × 6 طبقات (2 خطوة تروتر) مع كمراقَب، ونشغّلها على AerSimulator وجهاز افتراضي.
بالطبع، نتبع سير عملنا الرباعي "نمط Qiskit"، الذي يتكون من: الربط، والتحسين، والتنفيذ، والمعالجة اللاحقة. بالتحديد:
- ربط المدخلات الكلاسيكية بحوسبة كمومية.
- تحسين الدوائر للحوسبة الكمومية.
- تنفيذ الدوائر باستخدام البدائيات.
- المعالجة اللاحقة وإعادة النتائج بتنسيق كلاسيكي.
في ما يلي، لدينا خطوة الربط لإنشاء دائرة لتجربة أصغر حجماً. ثم لدينا مجموعة واحدة من التحسين والتنفيذ لـAerSimulator وأخرى لجهاز افتراضي. أخيراً، لدينا خطوة المعالجة اللاحقة لرسم النتائج.
4.1 الخطوة 1: الربط
backend = fake_provider.FakeTorontoV2() # a 27 qubit fake device.
num_steps = 2
qc = get_utility_circuit(backend, num_steps)
obs = SparsePauliOp.from_sparse_list(
[("Z", [13], 1)], num_qubits=backend.num_qubits
) # Falcon
angles = [
0,
0.1,
0.2,
0.3,
0.4,
0.5,
0.6,
0.7,
0.8,
1.0,
np.pi / 2,
] # We try 11 angles for theta_h.
4.2 الخطوتان 2 و3: التحسين والتنفيذ (المحاكي)
backend_sim = AerSimulator()
transpiled_qc_sim = transpile(
qc, backend_sim, optimization_level=1, layout_method="trivial"
)
transpiled_obs_sim = obs.apply_layout(layout=transpiled_qc_sim.layout)
print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_sim, reps=1))
27 qubits × 6 layers (23-depth), Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (16-depth), Gate breakdown: U3 80, CX 56, R 54, U1 32, U 28
شغّل أحد المستخدمين الخلية التالية على MacBook Pro بمعالج Intel Core i7 رباعي النواة بتردد 2.3 غيغاهرتز مزوّد بـ32 غيغابايت من ذاكرة RAM من نوع 3LPDDR4X، يعمل بنظام macOS 14.5. استغرق 161 مللي ثانية في الوقت الفعلي. قد يختلف الأمر قليلاً من جهاز لآخر.
%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_sim)
pub = (transpiled_qc_sim, transpiled_obs_sim, params)
result_sim = estimator.run([pub]).result()
CPU times: user 231 ms, sys: 186 ms, total: 417 ms
Wall time: 111 ms
4.3 الخطوتان 2 و3: التحسين والتنفيذ (الجهاز الافتراضي)
backend_fake = fake_provider.FakeTorontoV2()
transpiled_qc_fake = transpile(
qc, backend_fake, optimization_level=1, layout_method="trivial"
)
transpiled_obs_fake = obs.apply_layout(layout=transpiled_qc_fake.layout)
print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_fake, reps=1))
27 qubits × 6 layers (23-depth), Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (49-depth), Gate breakdown: SDG 324, U1 274, H 162, CX 56, U3 14
عندما شغّل المستخدم ذاته الخلية التالية بنفس البيئة، استغرق الأمر دقيقتين و19 ثانية في الوقت الفعلي. تنفيذ دائرة على جهاز افتراضي يستدعي محاكاة مضوضأة تستغرق وقتاً أطول بكثير من المحاكاة الدقيقة. ننصحك بعدم تشغيل دائرة أكبر (مثل 27 كيوبت × 9 طبقات بـ3 خطوات تروتر) على جهاز افتراضي.
%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_fake)
pub = (transpiled_qc_fake, transpiled_obs_fake, params)
result_fake = estimator.run([pub]).result()
CPU times: user 4min 42s, sys: 9.35 s, total: 4min 51s
Wall time: 38.3 s
4.4 الخطوة 4: المعالجة اللاحقة
نرسم النتائج من المحاكاتين الدقيقة والمضوضأة. ستلاحظ التأثيرات الشديدة للضوضاء على FakeToronto.
plt.plot(angles, result_fake[0].data.evs, "o", label="Fake Device")
plt.plot(angles, result_sim[0].data.evs, "o", label="AerSimulator")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{13} \\rangle$")
plt.legend()
plt.show()
5. حل نسخة 127 كيوبت من المسألة
هدفك هو تشغيل تجربة نطاق الفائدة كما ذُكر في البداية. ستُنشئ وتُشغِّل دائرة بـ127 كيوبت و60 طبقة (20 خطوة تروتر) مع كمراقَب. ننصحك بمحاولة القيام بذلك بنفسك، مستعيناً بكود نسخة 27 كيوبت حيثما يناسب. لكن الحل مُقدَّم هنا.
الحل:
5.1 الخطوة 1: الربط
# backend_map = service.backend("ibm_brisbane")
backend_map = service.least_busy(operational=True, simulator=False)
num_steps = 20
qc = get_utility_circuit(backend_map, num_steps)
obs = SparsePauliOp.from_sparse_list(
[("Z", [62], 1)], num_qubits=backend_map.num_qubits
) # Eagle
angles = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1.0, np.pi / 2]
5.2 الخطوتان 2 و3: التحسين والتنفيذ
نلاحظ أن خريطة اقتران معالج Eagle تحتوي على 144 ضلعاً.
# backend = service.backend("ibm_brisbane")
backend = backend_map
transpiled_qc = transpile(qc, backend, optimization_level=1, layout_method="trivial")
transpiled_obs = obs.apply_layout(layout=transpiled_qc.layout)
print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc))
156 qubits × 60 layers (221-depth), Gate breakdown: SDG 7040, UNITARY 7040, CX 3520, R 3120
156 qubits × 60 layers (201-depth), Gate breakdown: RZ 11933, SX 6240, CZ 3520
params = [[p] for p in angles]
estimator = Estimator(mode=backend)
pub = (transpiled_qc, transpiled_obs, params)
job = estimator.run([pub])
job_id = job.job_id()
print(f"job id={job_id}")
job id=d1479n6qf56g0081sxa0
5.3 المعالجة اللاحقة
نُوفِّر القيم لنقاط "المُخففة" في الشكل 4b من ورقة الفائدة. ارسمها جنباً إلى جنب مع نتائجك.
result_paper = [
1.0171,
1.0044,
0.9563,
0.9602,
0.8394,
0.8120,
0.5466,
0.4556,
0.1953,
0.0141,
0.0117,
]
# REPLACE WITH YOUR OWN JOB ID
job = service.job(job_id)
plt.plot(angles, job.result()[0].data.evs, "o", label=f"{job.backend().name}")
plt.plot(angles, result_paper, "o", label="Utility Paper")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{62} \\rangle$")
plt.legend()
plt.show()
هل نتائجك مشابهة للنتائج "غير المُخففة" في الشكل 4b؟ قد تكون مختلفة جداً، تبعاً للجهاز وحالته وقت التجربة. لا تقلق بشأن النتائج نفسها. ما سنتحقق منه هو ما إذا كنت قد كتبت الكود بصورة صحيحة. إذا فعلت ذلك، فتهانينا، لقد وصلت إلى نقطة الانطلاق في حقبة الفائدة.
كما في ورقة الفائدة، أبدى العلماء حول العالم براعة هائلة في استخلاص نتائج ذات معنى رغم وجود الضوضاء. الهدف النهائي لهذا الجهد الجماعي هو التفوق الكمومي: حالة تستطيع فيها الحواسيب الكمومية حل بعض المسائل المفيدة في الصناعة بصورة أسرع، أو بدقة أعلى، أو بتكلفة أقل مقارنةً بالحواسيب الكلاسيكية. من غير المرجح أن يكون ذلك حدثاً واحداً، بل هو على الأرجح حقبة تزداد فيها صعوبة إعادة إنتاج الكلاسيكيين للنتائج الكمومية تدريجياً، حتى تصبح في نقطة ما فترة التقدم الكمومي تلك بالغة الأهمية. شيء واحد واضح بشأن التفوق الكمومي: لن نصل إليه إلا من خلال تجارب نطاق الفائدة. إذا أسفرت هذه الدورة عن انضمامك إلى هذا المسعى، المليء بالتحدي والمتعة، فسنكون في غاية السعادة.
المرجع
- Kim, Y., Eddins, A., Anand, S. et al. Evidence for the utility of quantum computing before fault tolerance. Nature 618, 500–505 (2023). https://doi.org/10.1038/s41586-023-06096-3