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

مقارنة إعدادات المُحوِّل

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

الخلفية

لضمان نتائج أسرع وأكثر كفاءة، اعتبارًا من 1 مارس 2024، يجب تحويل الدوائر والمراقِبات لتستخدم فقط التعليمات التي يدعمها وحدة المعالجة الكمومية (QPU) قبل إرسالها إلى بدائيات Qiskit Runtime. نسمي هذه دوائر ومراقِبات معمارية مجموعة التعليمات (ISA). إحدى الطرق الشائعة لتحقيق ذلك هي استخدام دالة generate_preset_pass_manager في المُحوِّل. ومع ذلك، قد تختار اتباع عملية يدوية أكثر.

على سبيل المثال، قد ترغب في استهداف مجموعة فرعية محددة من Qubits على جهاز معين. تختبر هذه العملية أداء إعدادات مختلفة للمُحوِّل من خلال إتمام العملية الكاملة لإنشاء الدوائر وتحويلها وإرسالها.

المتطلبات

قبل البدء، تأكد من تثبيت ما يلي:

  • Qiskit SDK الإصدار v1.2 أو أحدث، مع دعم التصور
  • Qiskit Runtime الإصدار v0.28 أو أحدث (pip install qiskit-ibm-runtime)

الإعداد

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
# Create circuit to test transpiler on
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import GroverOperator, Diagonal

# Use Statevector object to calculate the ideal output
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_histogram
from qiskit.transpiler import PassManager

from qiskit.circuit.library import XGate
from qiskit.quantum_info import hellinger_fidelity

# Qiskit Runtime
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Batch,
SamplerV2 as Sampler,
)
from qiskit_ibm_runtime.transpiler.passes.scheduling import (
ASAPScheduleAnalysis,
PadDynamicalDecoupling,
)

الخطوة 1: تحويل المدخلات الكلاسيكية إلى مسألة كمومية

أنشئ دائرة صغيرة ليجرّب عليها المُحوِّل التحسين. يُنشئ هذا المثال دائرة تنفّذ خوارزمية Grover مع أوراكل يُعلِّم الحالة 111. بعد ذلك، قم بمحاكاة التوزيع المثالي (ما تتوقع قياسه لو شغّلت هذا على حاسوب كمومي مثالي عددًا لانهائيًا من المرات) للمقارنة لاحقًا.

# To run on hardware, select the backend with the fewest number of jobs in the queue
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
backend.name
'ibm_brisbanse'
oracle = Diagonal([1] * 7 + [-1])
qc = QuantumCircuit(3)
qc.h([0, 1, 2])
qc = qc.compose(GroverOperator(oracle))

qc.draw(output="mpl", style="iqp")

Output of the previous code cell

ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()

plot_histogram(ideal_distribution)

Output of the previous code cell

الخطوة 2: تحسين المسألة لتنفيذها على العتاد الكمومي

بعد ذلك، حوِّل الدوائر لوحدة المعالجة الكمومية. ستقارن أداء المُحوِّل مع optimization_level مضبوطًا على 0 (الأدنى) مقابل 3 (الأعلى). يقوم مستوى التحسين الأدنى بالحد الأدنى اللازم لتشغيل الدائرة على الجهاز؛ إذ يُعيِّن Qubits الدائرة إلى Qubits الجهاز ويضيف بوابات التبديل للسماح بجميع عمليات الـ Qubitين. أما مستوى التحسين الأعلى فهو أكثر ذكاءً بكثير ويستخدم الكثير من الحيل لتقليل عدد البوابات الإجمالي. بما أن بوابات متعددة الـ Qubit لها معدلات خطأ مرتفعة وتتفكك Qubits بمرور الوقت، فإن الدوائر الأقصر ينبغي أن تعطي نتائج أفضل.

تقوم الخلية التالية بتحويل qc لكلتا قيمتَي optimization_level، وتطبع عدد بوابات الـ Qubitين، وتضيف الدوائر المُحوَّلة إلى قائمة. بعض خوارزميات المُحوِّل عشوائية، لذا يُحدَّد seed لضمان إمكانية التكرار.

# Need to add measurements to the circuit
qc.measure_all()

# Find the correct two-qubit gate
twoQ_gates = set(["ecr", "cz", "cx"])
for gate in backend.basis_gates:
if gate in twoQ_gates:
twoQ_gate = gate

circuits = []
for optimization_level in [0, 3]:
pm = generate_preset_pass_manager(
optimization_level, backend=backend, seed_transpiler=0
)
t_qc = pm.run(qc)
print(
f"Two-qubit gates (optimization_level={optimization_level}): ",
t_qc.count_ops()[twoQ_gate],
)
circuits.append(t_qc)
Two-qubit gates (optimization_level=0):  21
Two-qubit gates (optimization_level=3): 14

بما أن بوابات CNOT عادةً ما تمتلك معدل خطأ مرتفعًا، فإن الدائرة المُحوَّلة بـ optimization_level=3 ينبغي أن تُحقق أداءً أفضل بكثير.

هناك طريقة أخرى يمكنك من خلالها تحسين الأداء وهي الفصل الديناميكي، من خلال تطبيق سلسلة من البوابات على Qubits الخاملة. يؤدي ذلك إلى إلغاء بعض التفاعلات غير المرغوب فيها مع البيئة. تضيف الخلية التالية الفصل الديناميكي إلى الدائرة المُحوَّلة بـ optimization_level=3 وتضيفها إلى القائمة.

# Get gate durations so the transpiler knows how long each operation takes
durations = backend.target.durations()

# This is the sequence we'll apply to idling qubits
dd_sequence = [XGate(), XGate()]

# Run scheduling and dynamic decoupling passes on circuit
pm = PassManager(
[
ASAPScheduleAnalysis(durations),
PadDynamicalDecoupling(durations, dd_sequence),
]
)
circ_dd = pm.run(circuits[1])

# Add this new circuit to our list
circuits.append(circ_dd)
circ_dd.draw(output="mpl", style="iqp", idle_wires=False)

Output of the previous code cell

الخطوة 3: التنفيذ باستخدام بدائيات Qiskit

في هذه المرحلة، لديك قائمة من الدوائر المُحوَّلة لوحدة المعالجة الكمومية المحددة. بعد ذلك، أنشئ نسخة من البدائي sampler وابدأ مهمة دُفعية باستخدام مدير السياق (with ...:), الذي يفتح الدُفعة ويغلقها تلقائيًا.

داخل مدير السياق، قم بأخذ عينات من الدوائر وخزِّن النتائج في result.

with Batch(backend=backend):
sampler = Sampler()
job = sampler.run(
[(circuit) for circuit in circuits], # sample all three circuits
shots=8000,
)
result = job.result()

الخطوة 4: المعالجة اللاحقة وإرجاع النتيجة بالتنسيق الكلاسيكي المطلوب

أخيرًا، ارسم النتائج من عمليات تشغيل الجهاز مقابل التوزيع المثالي. يمكنك أن ترى أن نتائج optimization_level=3 أقرب إلى التوزيع المثالي بسبب عدد البوابات الأقل، وأن optimization_level=3 + dd أقرب حتى من ذلك بفضل الفصل الديناميكي.

binary_prob = [
{
k: v / res.data.meas.num_shots
for k, v in res.data.meas.get_counts().items()
}
for res in result
]
plot_histogram(
binary_prob + [ideal_distribution],
bar_labels=False,
legend=[
"optimization_level=0",
"optimization_level=3",
"optimization_level=3 + dd",
"ideal distribution",
],
)

Output of the previous code cell

يمكنك التحقق من ذلك بحساب دقة Hellinger بين كل مجموعة نتائج والتوزيع المثالي (القيمة الأعلى أفضل، والقيمة 1 تعني دقة مثالية).

for prob in binary_prob:
print(f"{hellinger_fidelity(prob, ideal_distribution):.3f}")
0.848
0.945
0.990