أمثلة وتطبيقات
في هذا الدرس، سنستعرض بعض أمثلة الخوارزميات التغايرية وكيفية تطبيقها:
- كيفية كتابة خوارزمية تغايرية مخصصة
- كيفية تطبيق خوارزمية تغايرية لإيجاد أدنى القيم الذاتية
- كيفية توظيف الخوارزميات التغايرية لحل حالات استخدام تطبيقية
لاحظ أن إطار عمل أنماط Qiskit يمكن تطبيقه على جميع المسائل التي نقدمها هنا. غير أنه تجنبًا للتكرار، سنُشير صراحةً إلى خطوات الإطار في مثال واحد فقط، يُشغَّل على عتاد حقيقي.
تعريف المسائل
تخيّل أننا نريد استخدام خوارزمية تغايرية لإيجاد القيمة الذاتية للمؤثر التالي:
يمتلك هذا المؤثر القيم الذاتية التالية:
والحالات الذاتية:
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime rustworkx scipy
from qiskit.quantum_info import SparsePauliOp
observable_1 = SparsePauliOp.from_list([("II", 2), ("XX", -2), ("YY", 3), ("ZZ", -3)])
VQE مخصص
سنستكشف أولًا كيفية بناء نموذج VQE يدويًا لإيجاد أدنى قيمة ذاتية لـ . سيستوعب هذا مجموعة من التقنيات التي تناو لناها على مدار هذه الدورة.
def cost_func_vqe(params, ansatz, hamiltonian, estimator):
"""Return estimate of energy from estimator
Parameters:
params (ndarray): Array of ansatz parameters
ansatz (QuantumCircuit): Parameterized ansatz circuit
hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
estimator (Estimator): Estimator primitive instance
Returns:
float: Energy estimate
"""
pub = (ansatz, hamiltonian, params)
cost = estimator.run([pub]).result()[0].data.evs
return cost
from qiskit.circuit.library.n_local import n_local
from qiskit import QuantumCircuit
import numpy as np
reference_circuit = QuantumCircuit(2)
reference_circuit.x(0)
variational_form = n_local(
num_qubits=2,
rotation_blocks=["rz", "ry"],
entanglement_blocks="cx",
entanglement="linear",
reps=1,
)
raw_ansatz = reference_circuit.compose(variational_form)
raw_ansatz.decompose().draw("mpl")

سنبدأ بتنقيح الكود على محاكيات محلية.
from qiskit.primitives import StatevectorEstimator as Estimator
from qiskit.primitives import StatevectorSampler as Sampler
estimator = Estimator()
sampler = Sampler()
الآن نضبط مجموعة أولية من المعاملات:
x0 = np.ones(raw_ansatz.num_parameters)
print(x0)
[1. 1. 1. 1. 1. 1. 1. 1.]
يمكننا تصغير دالة التكلفة هذه لحساب المعاملات المثلى
# SciPy minimizer routine
from scipy.optimize import minimize
import time
start_time = time.time()
result = minimize(
cost_func_vqe,
x0,
args=(raw_ansatz, observable_1, estimator),
method="COBYLA",
options={"maxiter": 1000, "disp": True},
)
end_time = time.time()
execution_time = end_time - start_time
Return from COBYLA because the trust region radius reaches its lower bound.
Number of function values = 103 Least value of F = -5.999999998357189
The corresponding X is:
[2.27483579e+00 8.37593091e-01 1.57080508e+00 5.82932911e-06
2.49973063e+00 6.41884255e-01 6.33686904e-01 6.33688223e-01]
result
message: Return from COBYLA because the trust region radius reaches its lower bound.
success: True
status: 0
fun: -5.999999998357189
x: [ 2.275e+00 8.376e-01 1.571e+00 5.829e-06 2.500e+00
6.419e-01 6.337e-01 6.337e-01]
nfev: 103
maxcv: 0.0
بما أن هذه المسألة التجريبية تستخدم كيوبتين فقط، يمكننا التحقق من ذلك باستخدام حلّال القيم الذاتية في الجبر الخطي بـ NumPy.
from numpy.linalg import eigvalsh
solution_eigenvalue = min(eigvalsh(observable_1.to_matrix()))
print(f"""Number of iterations: {result.nfev}""")
print(f"""Time (s): {execution_time}""")
print(
f"Percent error: {100*abs((result.fun - solution_eigenvalue)/solution_eigenvalue):.2e}"
)
Number of iterations: 103
Time (s): 0.4394676685333252
Percent error: 2.74e-08
كما ترى، النتيجة قريبة جدًا من القيمة المثالية.
التجريب لتحسين السرعة والدقة
إضافة حالة مرجعية
في المثال السابق لم نستخدم أي معامل مرجعي . الآن دعنا نفكر في كيفية الحصول على الحالة الذاتية المثالية . تأمل الدائرة التالية.
from qiskit import QuantumCircuit
ideal_qc = QuantumCircuit(2)
ideal_qc.h(0)
ideal_qc.cx(0, 1)
ideal_qc.draw("mpl")
يمكننا التحقق بسرعة من أن هذه الدائرة تعطينا الحالة المطلوبة.
from qiskit.quantum_info import Statevector
Statevector(ideal_qc)
Statevector([0.70710678+0.j, 0. +0.j, 0. +0.j,
0.70710678+0.j],
dims=(2, 2))
الآن بعد أن رأينا كيف تبدو الدائرة التي تُحضّر حالة الحل، يبدو من المنطقي استخدام بوابة Hadamard كدائرة مرجعية، بحيث يصبح ansatz الكامل:
reference = QuantumCircuit(2)
reference.h(0)
reference.cx(0, 1)
# Include barrier to separate reference from variational form
reference.barrier()
ref_ansatz = variational_form.decompose().compose(reference, front=True)
ref_ansatz.draw("mpl")

بالنسبة لهذه الدائرة الجديدة، يمكن بلوغ الحل المثالي حين تكون جميع المعاملات مساوية لـ ، وهذا يؤكد أن اختيار الدائرة المرجعية كان معقولاً.
الآن دعنا نقارن عدد تقييمات دالة التكلفة، وعدد تكرارات المُحسِّن، والوقت المستغرق، بما كانت عليه في المحاولة السابقة.
import time
start_time = time.time()
ref_result = minimize(
cost_func_vqe, x0, args=(ref_ansatz, observable_1, estimator), method="COBYLA"
)
end_time = time.time()
execution_time = end_time - start_time
باستخدام المعاملات المثلى لحساب القيمة الذاتية الدنيا:
experimental_min_eigenvalue_ref = cost_func_vqe(
ref_result.x, ref_ansatz, observable_1, estimator
)
print(experimental_min_eigenvalue_ref)
-5.999999996759607
print("ADDED REFERENCE STATE:")
print(f"""Number of iterations: {ref_result.nfev}""")
print(f"""Time (s): {execution_time}""")
print(
f"Percent error: {100*abs((experimental_min_eigenvalue_ref - solution_eigenvalue)/solution_eigenvalue):.2e}"
)
ADDED REFERENCE STATE:
Number of iterations: 127
Time (s): 0.5620882511138916
Percent error: 5.40e-08
قد يُحقق هذا تحسينًا في السرعة أو الدقة أو لا، وذلك اعتمادًا على نظامك المحدد في هذا المثال الصغير جدًا. الفكرة الأساسية هي أن البدء بحالات مرجعية مستوحاة فيزيائيًا يكتسب أهمية متزايدة في تحسين السرعة والدقة كلما تضخّمت المسائل.
تغيير نقطة البداية
الآن بعد أن رأينا تأثير إضافة الحالة المرجعية، سنستعرض ما يحدث حين نختار نقاط بداية مختلفة . سنستخدم تحديدًا و .
تذكّر أنه كما نوقش عند تقديم الحالة المرجعية، يُعثر على الحل المثالي حين تكون جميع المعاملات ، لذا ينبغي لنقطة البداية الأولى أن تستلزم تقييمات أقل.
import time
start_time = time.time()
x0 = [0, 0, 0, 0, 6, 0, 0, 0]
x0_1_result = minimize(
cost_func_vqe, x0, args=(raw_ansatz, observable_1, estimator), method="COBYLA"
)
end_time = time.time()
execution_time = end_time - start_time
print("INITIAL POINT 1:")
print(f"""Number of iterations: {x0_1_result.nfev}""")
print(f"""Time (s): {execution_time}""")
INITIAL POINT 1:
Number of iterations: 108
Time (s): 0.4492197036743164
ضبط نقطة البداية إلى :
import time
start_time = time.time()
x0 = 6 * np.ones(raw_ansatz.num_parameters)
x0_2_result = minimize(
cost_func_vqe, x0, args=(raw_ansatz, observable_1, estimator), method="COBYLA"
)
end_time = time.time()
execution_time = end_time - start_time
print("INITIAL POINT 2:")
print(f"""Number of iterations: {x0_2_result.nfev}""")
print(f"""Time (s): {execution_time}""")
INITIAL POINT 2:
Number of iterations: 107
Time (s): 0.40889453887939453
من خلال التجريب بنقاط بداية مختلفة، قد تتمكن من تحقيق التقارب بشكل أسرع ومع تقييمات أقل للدوال.
التجريب بمُحسِّنات مختلفة
يمكننا ضبط المُحسِّن باستخدام وسيطة method الخاصة بـ minimize في SciPy، ويمكن الاطلاع على مزيد من الخيارات هنا. استخدمنا في الأصل مُحسِّنًا مقيَّدًا (COBYLA). في هذا المثال، سنستكشف استخدام مُحسِّن غير مقيَّد (BFGS) بدلاً منه.
import time
start_time = time.time()
result = minimize(
cost_func_vqe, x0, args=(raw_ansatz, observable_1, estimator), method="BFGS"
)
end_time = time.time()
execution_time = end_time - start_time
print("CHANGED TO BFGS OPTIMIZER:")
print(f"""Number of iterations: {result.nfev}""")
print(f"""Time (s): {execution_time}""")
CHANGED TO BFGS OPTIMIZER:
Number of iterations: 117
Time (s): 0.31656408309936523
مثال VQD
هنا نُطبّق إطار عمل Qiskit patterns بشكل صريح.
الخطوة 1: تحويل المدخلات الكلاسيكية إلى مسألة كمومية
بدلًا من البحث عن أصغر قيمة ذاتية فقط للمؤثرات، سنبحث هنا عن جميع القيم الـ (حيث ).
تذكّر أن دوال تكلفة VQD هي: