تقدير الطور الكمي باستخدام وظائف Qiskit من Q-CTRL
تقدير الاستخدام: 40 ثانية على معالج Heron r2. (ملاحظة: هذا تقدير فحسب. قد يختلف وقت التشغيل الفعلي.)
الخلفية النظرية
تقدير الطور الكمي (QPE) هو خوارزمية أساسية في الحوسبة الكمية، وتُشكّل الأساس لكثير من التطبيقات المهمة كخوارزمية Shor، وتقدير طاقة الحالة الأرضية في الكيمياء الكمية، ومسائل القيم الذاتية. تُقدّر خوارزمية QPE الطور المرتبط بالحالة الذاتية لمؤثر وحدوي، وفق العلاقة:
وتحدده بدقة باستخدام كيوبت للعدّ [1]. يُهيّأ هذا المؤثر بوضع هذه الكيوبتات في التراكب، ثم تطبيق قوى مُتحكَّم بها من ، ثم استخدام تحويل فورييه الكمي العكسي (QFT) لاستخراج الطور إلى نتائج قياس مُشفَّرة ثنائيًا. ينتج عن ذلك توزيع احتمالي مُتمركز حول سلاسل البتات التي تقترب كسورها الثنائية من . في الحالة المثالية، تتوافق نتيجة القياس الأكثر احتمالًا مباشرةً مع التمثيل الثنائي للطور، في حين يتناقص احتمال النتائج الأخرى بسرعة مع زيادة عدد كيوبتات العدّ. غير أن تشغيل دوائر QPE العميقة على الأجهزة الفعلية يُفضي إلى تحديات: إذ يجعل العدد الكبير من الكيوبتات وعمليات التشابك الخوارزميةَ حساسةً للغاية لفقدان الترابط وأخطاء البوابات. ينجم عن ذلك توزيعات سلاسل بتات موسّعة ومزاحة، تحجب الطور الذاتي الحقيقي. وبالتالي، قد لا تعود سلسلة البتات ذات الاحتمال الأعلى مقابلةً للتمثيل الثنائي الصحيح لـ .
في هذا البرنامج التعليمي، نعرض تطبيقًا لخوارزمية QPE باستخدام أدوات كبت الأخطاء وإدارة الأداء Fire Opal من Q-CTRL، المُقدَّمة كوظيفة Qiskit (انظر توثيق Fire Opal). تُطبّق Fire Opal تحسينات متقدمة تلقائيًا، تشمل: الإزالة الديناميكية للتشابك، وتحسينات تخطيط الكيوبتات، وتقنيات كبت الأخطاء، مما يُفضي إلى نتائج ذات دقة أعلى. تُقرّب هذه التحسينات توزيعات سلاسل البتات على الأجهزة الفعلية من تلك التي تُحصَّل في المحاكاة الخالية من الضوضاء، بحيث يمكنك التعرف على الطور الذاتي الصحيح بموثوقية حتى في ظل تأثيرات الضوضاء.
المتطلبات
قبل البدء بهذا البرنامج التعليمي، تأكد من تثبيت ما يلي:
- Qiskit SDK الإصدار v1.4 أو أحدث، مع دعم التصور المرئي
- Qiskit Runtime الإصدار v0.40 أو أحدث (
pip install qiskit-ibm-runtime) - Qiskit Functions Catalog الإصدار v0.9.0 (
pip install qiskit-ibm-catalog) - Fire Opal SDK الإصدار v9.0.2 أو أحدث (
pip install fire-opal) - Q-CTRL Visualizer الإصدار v8.0.2 أو أحدث (
pip install qctrl-visualizer)
الإعداد
أولًا، قم بالمصادقة باستخدام مفتاح API الخاص بك على IBM Quantum. ثم اختر وظيفة Qiskit على النحو التالي. (يفترض هذا الكود أنك قد حفظت حسابك مسبقًا في بيئتك المحلية.)
# Added by doQumentation — installs packages not in the Binder environment
%pip install -q qctrlvisualizer
from qiskit import QuantumCircuit
import numpy as np
import matplotlib.pyplot as plt
import qiskit
from qiskit import qasm2
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
import qctrlvisualizer as qv
from qiskit_ibm_catalog import QiskitFunctionsCatalog
plt.style.use(qv.get_qctrl_style())
catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")
# Access Function
perf_mgmt = catalog.load("q-ctrl/performance-management")
الخطوة 1: تحويل المدخلات الكلاسيكية إلى مسألة كمية
في هذا البرنامج التعليمي، نُوضّح تطبيق QPE لاستعادة الطور الذاتي لمؤثر وحدوي أحادي الكيوبت معلوم. المؤثر الوحدوي الذي نريد تقدير طوره هو بوابة الطور أحادية الكيوبت المُطبَّقة على ا لكيوبت الهدف:
نُهيّئ حالته الذاتية . ونظرًا لأن هي متجه ذاتي لـ بقيمة ذاتية ، فإن الطور الذاتي المراد تقديره هو:
نضع ، وبذلك يكون الطور الحقيقي . تُنفّذ دائرة QPE القوى المُتحكَّم بها بتطبيق دورانات طور مُتحكَّم بها بزوايا ، ثم تُطبّق QFT العكسي على سجل العدّ وتقيسه. تتمركز سلاسل البتات الناتجة حول التمثيل الثنائي لـ .
تستخدم الدائرة كيوبت للعدّ (لضبط دقة التقدير) إضافةً إلى كيوبت هدف واحد. نبدأ بتعريف اللبنات الأساسية اللازمة لتطبيق QPE: تحويل فورييه الكمي (QFT) وعكسه، ودوال مساعدة لتحويل الطور بين الكسور العشرية والثنائية، ودوال مساعدة أخرى لتطبيع الأعداد الخام إلى احتمالات لمقارنة نتائج المحاكاة مع نتائج الأجهزة الفعلية.
def inverse_quantum_fourier_transform(quantum_circuit, number_of_qubits):
"""
Apply an inverse Quantum Fourier Transform the first `number_of_qubits` qubits in the
`quantum_circuit`.
"""
for qubit in range(number_of_qubits // 2):
quantum_circuit.swap(qubit, number_of_qubits - qubit - 1)
for j in range(number_of_qubits):
for m in range(j):
quantum_circuit.cp(-np.pi / float(2 ** (j - m)), m, j)
quantum_circuit.h(j)
return quantum_circuit
def bitstring_count_to_probabilities(data, shot_count):
"""
This function turns an unsorted dictionary of bitstring counts into a sorted dictionary
of probabilities.
"""
# Turn the bitstring counts into probabilities.
probabilities = {
bitstring: bitstring_count / shot_count
for bitstring, bitstring_count in data.items()
}
sorted_probabilities = dict(
sorted(probabilities.items(), key=lambda x: x[1], reverse=True)
)
return sorted_probabilities
الخطوة 2: تحسين المسألة لتنفيذها على الأجهزة الكمية
نبني دائرة QPE بتهيئة كيوبتات العدّ في التراكب، وتطبيق دورانات الطور المُتحكَّم بها لترميز الطور الذاتي الهدف، ثم الانتهاء بـ QFT العكسي قبل القياس.
def quantum_phase_estimation_benchmark_circuit(
number_of_counting_qubits, phase
):
"""
Create the circuit for quantum phase estimation.
Parameters
----------
number_of_counting_qubits : The number of qubits in the circuit.
phase : The desired phase.
Returns
-------
QuantumCircuit
The quantum phase estimation circuit for `number_of_counting_qubits` qubits.
"""
qc = QuantumCircuit(
number_of_counting_qubits + 1, number_of_counting_qubits
)
target = number_of_counting_qubits
# |1> eigenstate for the single-qubit phase gate
qc.x(target)
# Hadamards on counting register
for q in range(number_of_counting_qubits):
qc.h(q)
# ONE controlled phase per counting qubit: cp(phase * 2**k)
for k in range(number_of_counting_qubits):
qc.cp(phase * (1 << k), k, target)
qc.barrier()
# Inverse QFT on counting register
inverse_quantum_fourier_transform(qc, number_of_counting_qubits)
qc.barrier()
for q in range(number_of_counting_qubits):
qc.measure(q, q)
return qc
الخطوة 3: التنفيذ باستخدام Qiskit primitives
نحدد عدد اللقطات والكيوبتات للتجربة، ونُرمّز الطور الهدف باستخدام رقمًا ثنائيًا. بهذه المعاملات، نبني دائرة QPE التي ستُنفَّذ على المحاكاة، والأجهزة الافتراضية، والأجهزة المُحسَّنة بـ Fire Opal.
shot_count = 10000
num_qubits = 35
phase = (1 / 6) * 2 * np.pi
circuits_quantum_phase_estimation = (
quantum_phase_estimation_benchmark_circuit(
number_of_counting_qubits=num_qubits, phase=phase
)
)
تشغيل محاكاة MPS
أولًا، نُولّد توزيعًا مرجعيًا باستخدام محاكي matrix_product_state ونُحوّل الأعداد إلى احتمالات مُعيَّرة للمقارنة لاحقًا مع نتائج الأجهزة الفعلية.
# Run the algorithm on the IBM Aer simulator.
aer_simulator = AerSimulator(method="matrix_product_state")
# Transpile the circuits for the simulator.
transpiled_circuits = qiskit.transpile(
circuits_quantum_phase_estimation, aer_simulator
)
simulated_result = (
aer_simulator.run(transpiled_circuits, shots=shot_count)
.result()
.get_counts()
)
simulated_result_probabilities = []
simulated_result_probabilities.append(
bitstring_count_to_probabilities(
simulated_result,
shot_count=shot_count,
)
)
التشغيل على الأجهزة الفعلية
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
isa_circuits = pm.run(circuits_quantum_phase_estimation)
# Run the algorithm with IBM default.
sampler = Sampler(backend)
# Run all circuits using Qiskit Runtime.
ibm_default_job = sampler.run([isa_circuits], shots=shot_count)
التشغيل على الأجهزة الفعلية مع Fire Opal
# Run the circuit using the sampler
fire_opal_job = perf_mgmt.run(
primitive="sampler",
pubs=[qasm2.dumps(circuits_quantum_phase_estimation)],
backend_name=backend.name,
options={"default_shots": shot_count},
)
الخطوة 4: المعالجة اللاحقة وإعادة النتيجة بالصيغة الكلاسيكية المطلوبة
# Retrieve results.
ibm_default_result = ibm_default_job.result()
ibm_default_probabilities = []
for idx, pub_result in enumerate(ibm_default_result):
ibm_default_probabilities.append(
bitstring_count_to_probabilities(
pub_result.data.c0.get_counts(),
shot_count=shot_count,
)
)
fire_opal_result = fire_opal_job.result()
fire_opal_probabilities = []
for idx, pub_result in enumerate(fire_opal_result):
fire_opal_probabilities.append(
bitstring_count_to_probabilities(
pub_result.data.c0.get_counts(),
shot_count=shot_count,
)
)
data = {
"simulation": simulated_result_probabilities,
"default": ibm_default_probabilities,
"fire_opal": fire_opal_probabilities,
}
def plot_distributions(
data,
number_of_counting_qubits,
top_k=None,
by="prob",
shot_count=None,
):
def nrm(d):
s = sum(d.values())
return {k: (v / s if s else 0.0) for k, v in d.items()}
def as_float(d):
return {k: float(v) for k, v in d.items()}
def to_space(d):
if by == "prob":
return nrm(as_float(d))
else:
if shot_count and 0.99 <= sum(d.values()) <= 1.01:
return {
k: v * float(shot_count) for k, v in as_float(d).items()
}
else:
return as_float(d)
def topk(d, k):
items = sorted(d.items(), key=lambda kv: kv[1], reverse=True)
return items[: (k or len(d))]
phase = "1/6"
sim = to_space(data["simulation"])
dft = to_space(data["default"])
qct = to_space(data["fire_opal"])
correct = max(sim, key=sim.get) if sim else None
print("Correct result:", correct)
sim_items = topk(sim, top_k)
dft_items = topk(dft, top_k)
qct_items = topk(qct, top_k)
sim_keys, y_sim = zip(*sim_items) if sim_items else ([], [])
dft_keys, y_dft = zip(*dft_items) if dft_items else ([], [])
qct_keys, y_qct = zip(*qct_items) if qct_items else ([], [])
fig, axes = plt.subplots(3, 1, layout="constrained")
ylab = "Probabilities"
def panel(ax, keys, ys, title, color):
x = np.arange(len(keys))
bars = ax.bar(x, ys, color=color)
ax.set_title(title)
ax.set_ylabel(ylab)
ax.set_xticks(x)
ax.set_xticklabels(keys, rotation=90)
ax.set_xlabel("Bitstrings")
if correct in keys:
i = keys.index(correct)
bars[i].set_edgecolor("black")
bars[i].set_linewidth(2)
return max(ys, default=0.0)
c_sim, c_dft, c_qct = (
qv.QCTRL_STYLE_COLORS[5],
qv.QCTRL_STYLE_COLORS[1],
qv.QCTRL_STYLE_COLORS[0],
)
m1 = panel(axes[0], list(sim_keys), list(y_sim), "Simulation", c_sim)
m2 = panel(axes[1], list(dft_keys), list(y_dft), "Default", c_dft)
m3 = panel(axes[2], list(qct_keys), list(y_qct), "Q-CTRL", c_qct)
for ax, m in zip(axes, (m1, m2, m3)):
ax.set_ylim(0, 1.05 * (m or 1.0))
for ax in axes:
ax.label_outer()
fig.suptitle(
rf"{number_of_counting_qubits} counting qubits, $2\pi\varphi$={phase}"
)
fig.set_size_inches(20, 10)
plt.show()
experiment_index = 0
phase_index = 0
distributions = {
"simulation": data["simulation"][phase_index],
"default": data["default"][phase_index],
"fire_opal": data["fire_opal"][phase_index],
}
plot_distributions(
distributions, num_qubits, top_k=100, by="prob", shot_count=shot_count
)
Correct result: 00101010101010101010101010101010101

تُحدّد المحاكاة الأساسَ المرجعي للطور الذاتي الصحيح. تُظهر تشغيلات الأجهزة الافتراضية ضوضاءً تحجب هذه النتيجة، إذ تنتشر الاحتمالية عبر سلاسل بتات خاطئة كثيرة. أما مع إدارة أداء Q-CTRL، فيصبح التوزيع أكثر حدةً وتُستعاد النتيجة الصحيحة، مما يُتيح ت طبيق QPE بشكل موثوق على هذا النطاق.
المراجع
[1] المحاضرة 7: تقدير الطور والتحليل إلى عوامل. IBM Quantum Learning - أساسيات الخوارزميات الكمية. تاريخ الاسترجاع: 3 أكتوبر 2025.
استطلاع البرنامج التعليمي
يُرجى تخصيص دقيقة لتقديم ملاحظاتك حول هذا البرنامج التعليمي. ستساعدنا آراؤك في تحسين محتوانا وتجربة المستخدم.