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

الجمع بين كل شيء مع Qiskit Runtime

الملخص

تقدم فيكتوريا ليبينسكا مراجعة ختامية لما تعلمناه حتى الآن.

المراجع

المقالات التالية مُشار إليها في الفيديو أعلاه.

VQE مع أنماط Qiskit

لدينا الآن جميع المكونات اللازمة لإجراء حساب VQE:

  • الهاميلتوني
  • الأنساتز
  • المحسِّن الكلاسيكي

كل ما يتبقى هو دمجها معًا ضمن إطار أنماط Qiskit.

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

كما أشرنا سابقًا، سنفترض هنا أن هاميلتوني بالصيغة المناسبة قد جرى إنشاؤه مسبقًا. إن كانت لديك تساؤلات حول ذلك، راجع درس الهاميلتوني للاسترشاد. يقوم الكود الموجود في الخلية أدناه بضبط المكونات الموضحة في الدروس السابقة. اخترنا نمذجة H2 لأن هاميلتونيه مضغوط بما يكفي لكتابته صراحةً.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-aer qiskit-ibm-runtime scipy
# General imports
import numpy as np
from qiskit.quantum_info import SparsePauliOp

# Hamiltonian obtained from a previous lesson

H = SparsePauliOp(
[
"IIII",
"IIIZ",
"IZII",
"IIZI",
"ZIII",
"IZIZ",
"IIZZ",
"ZIIZ",
"IZZI",
"ZZII",
"ZIZI",
"YYYY",
"XXYY",
"YYXX",
"XXXX",
],
coeffs=[
-0.09820182 + 0.0j,
-0.1740751 + 0.0j,
-0.1740751 + 0.0j,
0.2242933 + 0.0j,
0.2242933 + 0.0j,
0.16891402 + 0.0j,
0.1210099 + 0.0j,
0.16631441 + 0.0j,
0.16631441 + 0.0j,
0.1210099 + 0.0j,
0.17504456 + 0.0j,
0.04530451 + 0.0j,
0.04530451 + 0.0j,
0.04530451 + 0.0j,
0.04530451 + 0.0j,
],
)

nuclear_repulsion = 0.7199689944489797

نختار دائرة efficient_su2 والمحسِّن COBYLA للبدء.

# Pre-defined ansatz circuit
from qiskit.circuit.library import efficient_su2

# SciPy minimizer routine
from scipy.optimize import minimize

# Plotting functions

# Random initial state and efficient_su2 ansatz
ansatz = efficient_su2(H.num_qubits, su2_gates=["rx"], entanglement="linear", reps=1)
x0 = 2 * np.pi * np.random.random(ansatz.num_parameters)
print(ansatz.decompose().depth())
ansatz.decompose().draw("mpl")
5

Output of the previous code cell

الآن نبني دالة التكلفة. ترتبط هذه الدالة طبعًا بالهاميلتوني، لكنها تختلف عنه في أن الهاميلتوني عامل، بينما نريد دالة تُعيد قيمة التوقع لذلك العامل باستخدام Estimator. وبالطبع، تعتمد هذه الدالة على الأنساتز والمعاملات التغييرية، لذا تظهر جميعها كحجج. في ما يلي نعرِّف نسختين مختلفتين قليلًا للاستخدام على الأجهزة الفعلية أو المحاكيات.

def cost_func(params, ansatz, H, estimator):
pub = (ansatz, [H], [params])
result = estimator.run(pubs=[pub]).result()
energy = result[0].data.evs[0]
return energy

# def cost_func_sim(params, ansatz, H, estimator):
# energy = estimator.run(ansatz, H, parameter_values=params).result().values[0]
# return energy

الخطوة الثانية: تحسين المسألة لتنفيذها كموميًا

نريد تشغيل الكود بكفاءة قصوى على الأجهزة التي نستخدمها. لذا علينا اختيار خلفية (backend) لبدء خطوة التحسين. الكود أدناه يختار الخلفية الأقل ازدحامًا المتاحة لك.

# To run on hardware, select the backend with the fewest number of jobs in the queue
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService(channel="ibm_quantum_platform")
backend = service.least_busy(operational=True, simulator=False)
backend.name

تحسين الدائرة للتشغيل على خلفية حقيقية موضوع غني وجوهري، لكنه ليس خاصًا بـ VQE. في الوقت الحالي، سنكتفي بتذكيرك بمصطلحين مهمين:

  • optimization_level: يصف مدى ملاءمة الدائرة لتخطيط الخلفية المختارة. أدنى مستوى تحسين يقوم فقط بالحد الأدنى اللازم لتشغيل الدائرة على الجهاز؛ يُعيِّن كيوبتات الدائرة على كيوبتات الجهاز ويضيف بوابات المبادلة اللازمة. أعلى مستوى تحسين أكثر ذكاءً ويستخدم حيلًا عديدة لتقليل عدد البوابات الإجمالي. نظرًا لأن بوابات الكيوبتات المتعددة لها معدلات خطأ مرتفعة وتتفكك الكيوبتات مع الزمن، فإن الدوائر الأقصر تُعطي نتائج أفضل.
  • الإزالة الديناميكية للاقتران: يمكننا تطبيق سلسلة من البوابات على الكيوبتات الخاملة، مما يُلغي بعض التفاعلات غير المرغوب فيها مع البيئة. راجع التوثيق المرتبط لمزيد من المعلومات حول تحسين الدوائر. الكود أدناه يولِّد مدير التمريرات باستخدام مديري التمريرات المُسبقة من qiskit.transpiler.
from qiskit.transpiler import PassManager
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.transpiler.passes import (
ALAPScheduleAnalysis,
PadDynamicalDecoupling,
ConstrainedReschedule,
)
from qiskit.circuit.library import XGate

target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
pm.scheduling = PassManager(
[
ALAPScheduleAnalysis(target=target),
ConstrainedReschedule(
acquire_alignment=target.acquire_alignment,
pulse_alignment=target.pulse_alignment,
target=target,
),
PadDynamicalDecoupling(
target=target,
dd_sequence=[XGate(), XGate()],
pulse_alignment=target.pulse_alignment,
),
]
)

# Use the pass manager and draw the resulting circuit
ansatz_isa = pm.run(ansatz)
ansatz_isa.draw(output="mpl", idle_wires=False, style="iqp")

Output of the previous code cell

يجب أيضًا تطبيق خصائص تخطيط الجهاز على الهاميلتوني.

hamiltonian_isa = H.apply_layout(ansatz_isa.layout)
hamiltonian_isa
SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYYYII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYXXII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXYYII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXXXII'],
coeffs=[-0.09820182+0.j, -0.1740751 +0.j, -0.1740751 +0.j, 0.2242933 +0.j,
0.2242933 +0.j, 0.16891402+0.j, 0.1210099 +0.j, 0.16631441+0.j,
0.16631441+0.j, 0.1210099 +0.j, 0.17504456+0.j, 0.04530451+0.j,
0.04530451+0.j, 0.04530451+0.j, 0.04530451+0.j])

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

قبل التنفيذ على الأجهزة الفعلية، من الحكمة استخدام محاكٍ لاختبار أولي سريع، وأحيانًا لتقدير الأخطاء. لهذه الأسباب، نعرض باختصار كيفية تشغيل VQE على محاكٍ. لكن تجدر الإشارة إلى أن لا حاسوب كلاسيكي ولا محاكٍ ولا GPU يستطيع محاكاة كامل وظائف حاسوب كمومي عالي التشابك بـ 127 كيوبت. في عصر النفعية الكمومية الحالي، ستكون للمحاكيات قيمة محدودة.

تذكَّر أنه عند كل اختيار للمعاملات في الدائرة التغييرية، يجب حساب قيمة التوقع (إذ هي القيمة المُراد تقليلها). وكما خمَّنت على الأرجح، أكفأ طريقة لذلك هي استخدام الأداة الأولية Estimator في Qiskit. سنبدأ باستخدام محاكٍ محلي، مما يستلزم استخدام النسخة المحلية من Estimator المسماة BackendEstimator.

نحتفظ بالخلفية الفعلية التي استخدمناها للتحسين، ويمكننا استيراد نموذج سلوك الضجيج لذلك الجهاز واستخدامه مع المحاكٍ المحلي الذي نختاره. هنا سنستخدم aer_simulator_statevector.

# We will start by using a local simulator
from qiskit_aer import AerSimulator

# Import an estimator, this time from qiskit (we will import from Runtime for real hardware)
from qiskit.primitives import BackendEstimatorV2

# generate a simulator that mimics the real quantum system
backend_sim = AerSimulator.from_backend(backend)
estimator = BackendEstimatorV2(backend=backend_sim)

حان الوقت أخيرًا لتنفيذ VQE، مع تقليل دالة التكلفة باستخدام الهاميلتوني والأنساتز والمحسِّن الكلاسيكي وBackendEstimator المرتكز على الخلفية الفعلية التي اخترناها للاستخدام لاحقًا. لاحظ أننا اخترنا عددًا صغيرًا نسبيًا للتكرارات القصوى، إذ نستخدم المحاكٍ فقط لأغراض الاختبار. خطوات تحسين VQE غالبًا ما تتطلب مئات التكرارات للتقارب.

res = minimize(
cost_func,
x0,
args=(ansatz_isa, hamiltonian_isa, estimator),
method="cobyla",
options={"maxiter": 10, "disp": True},
)

print(getattr(res, "fun") - nuclear_repulsion)
print(res)
Return from COBYLA because the objective function has been evaluated MAXFUN times.
Number of function values = 10 Least value of F = -0.11556938907226563
The corresponding X is:
[4.11796514 4.52126324 0.69570423 4.12781503 6.55507846 1.80713073
0.9645473 6.23812214]

-0.8355383835212453
message: Return from COBYLA because the objective function has been evaluated MAXFUN times.
success: False
status: 3
fun: -0.11556938907226563
x: [ 4.118e+00 4.521e+00 6.957e-01 4.128e+00 6.555e+00
1.807e+00 9.645e-01 6.238e+00]
nfev: 10
maxcv: 0.0

نجح تنفيذ هذا الكود، رغم أنه لم يتقارب كما توقعنا. سنمضي قُدُمًا لتشغيل الحساب على الأجهزة الفعلية، ثم نناقش المخرجات. للخلفيات الفعلية، سنستخدم Qiskit Runtime Estimator. سنرغب في تنفيذ هذا ضمن جلسة Qiskit Runtime وتحديد خيارات لتلك الجلسة.

from qiskit_ibm_runtime import QiskitRuntimeService, Session
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime.options import EstimatorOptions

من بين ما يوفره استخدام الجلسة، أن وظيفتنا ستنتظر في قائمة الانتظار مرة واحدة فقط عند البداية. التكرارات اللاحقة للمحسِّن الكلاسيكي لن تُضاف إلى قائمة الانتظار. داخل الجلسة، يمكننا ضبط مستويات المرونة والتحسين. هذه الأدوات مهمة بما يكفي لنضمِّن مراجعة موجزة لكل منها وأهميتها في VQE مع روابط لتعلم المزيد:

  • جلسات Runtime: VQE تكراري بطبيعته، إذ يختار المحسِّن الكلاسيكي معاملات تغييرية جديدة، ومن ثمَّ بوابات جديدة، في كل تجربة تالية. دون استخدام الجلسات، قد يُفضي ذلك إلى وقت انتظار إضافي في قائمة الانتظار بين كل دائرة تجريبية. يؤدي تضمين حساب VQE داخل جلسة إلى انتظار واحد في قائمة الانتظار قبل بدء الوظيفة، دون أي وقت انتظار إضافي بين خطوات VQE. استُخدمت هذه الاستراتيجية بالفعل في مثال الدرس السابق، لكنها قد تؤدي دورًا أهم عند تغيير الهندسة الجزيئية. لمزيد من المعلومات حول الجلسات، راجع توثيق أوضاع التنفيذ.
  • التحسين المدمج في Estimator: تتوفر في Estimator خيارات مدمجة لتحسين الحساب. في كثير من السياقات (بما في ذلك Estimator)، تقتصر الإعدادات على 0 و1، حيث يعني 0 عدم التحسين، و1 (الافتراضي) يعني بعض التحسين لدائرتك على الأجهزة المختارة. بعض السياقات الأخرى تتيح إعدادات 0 أو 1 أو 2 أو 3. لمزيد من المعلومات حول الطرق المستخدمة في الإعدادات المختلفة، راجع التوثيق. هنا، سنضبط التحسين على 0 ونستخدم skip_transpilation = true، لأننا أجرينا الترجمة مسبقًا باستخدام مدير التمريرات في قسم التحسين.
  • المرونة المدمجة في Estimator: كما هو الحال مع التحسين، توفر Estimator إعدادات مدمجة للمرونة في مواجهة الأخطاء، تقابل مناهج مختلفة لتخفيف الأخطاء. للتعرف على إعدادات مستوى المرونة، راجع التوثيق.

تجدر الإشارة إلى أن تخفيف الأخطاء يلعب دورًا دقيقًا في تقارب حساب VQE. يبحث المحسِّن الكلاسيكي في فضاء المعاملات عن تلك التي تقلل الطاقة. عندما تكون بعيدًا عن المعاملات المثلى، قد يظهر تدرج حاد للمحسِّن الكلاسيكي حتى في وجود الأخطاء. لكن مع اقتراب الحساب من التقارب وقيم مثلى، يصغر التدرج ويسهل إخفاؤه بالأخطاء. كم من تخفيف للأخطاء تريد استخدامه؟ وفي أي نقاط من التقارب؟ هذه خيارات تعتمد على حالة الاستخدام المحددة.

في هذا التشغيل الأول على الأجهزة الفعلية، ضبطنا المرونة على 0 لتسهيل تشغيل سريع نسبيًا. لأي تطبيق جدي، ستحتاج إلى استخدام تخفيف الأخطاء. لاحظ أن في الخلية أدناه مجموعتين من الخيارات: (1) خيارات جلسة Runtime التي أسميناها "session_options"، و(2) خيارات المحسِّن الكلاسيكي التي أسميناها "options" هنا.

estimator_options = EstimatorOptions(resilience_level=0, default_shots=2000)
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)

res = minimize(
cost_func,
x0,
args=(ansatz_isa, hamiltonian_isa, estimator),
method="cobyla",
options={"maxiter": 10, "disp": True},
)
Return from COBYLA because the objective function has been evaluated MAXFUN times.
Number of function values = 10 Least value of F = -0.11691688904
The corresponding X is:
[5.11796514 5.52126324 0.69570423 5.12781503 6.55507846 1.80713073
1.9645473 6.23812214]

يمكنك متابعة تقدم وظيفتك على منصة IBM Quantum® ضمن أعباء العمل.

print(getattr(res, "fun") - nuclear_repulsion)
print(res)
-0.8368858834889796
message: Return from COBYLA because the objective function has been evaluated MAXFUN times.
success: False
status: 3
fun: -0.11691688904
x: [ 5.118e+00 5.521e+00 6.957e-01 5.128e+00 6.555e+00
1.807e+00 1.965e+00 6.238e+00]
nfev: 10
maxcv: 0.0

الخطوة الرابعة: المعالجة البعدية وإعادة النتيجة بصيغة كلاسيكية

دعنا نتأكد من فهمنا لهذه المخرجات. مخرج "fun" هو القيمة الدنيا التي حصلنا عليها لدالة التكلفة (وليس بالضرورة آخر قيمة محسوبة). هذه هي الطاقة الكلية، بما في ذلك طاقة التنافر النووي الموجبة، ولهذا عرَّفنا أيضًا electron_energy.

في الحالة أعلاه، نرى رسالة تفيد بتجاوز الحد الأقصى لتقييمات الدالة، وأن عدد تقييمات الدالة (nfev) كان 10. هذا يعني فقط أن المعايير الأخرى للتقارب لم تتحقق؛ بمعنى آخر، لا يوجد ما يدل على أننا وجدنا طاقة الحالة الأساسية. هذا أيضًا ما تعنيه قيمة success "False".

أخيرًا، لدينا x. هذا هو متجه المعاملات التغييرية. تمثل هذه المعاملات القيم المستخدمة في الحساب الذي أعطى أدنى دالة تكلفة (قيمة توقع الطاقة). تقابل هذه القيم الثماني زوايا الدوران الثماني في البوابات في الأنساتز التي تأخذ زوايا دوران متغيرة.

تهانينا! لقد نفَّذتَ حساب VQE على وحدة معالجة كمومية من IBM Quantum!

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

import qiskit
import qiskit_ibm_runtime

print(qiskit.version.get_version_info())
print(qiskit_ibm_runtime.version.get_version_info())
2.1.0
0.40.1