المحاكاة الكمومية
Yukio Kawashima (May 30, 2024)
تنزيل ملف PDF للمحاضرة الأصلية. لاحظ أن بعض مقاطع الكود قد تصبح قديمة لأنها صور ثابتة.
الوقت التقريبي للتشغيل على QPU هو 7 ثوانٍ.
(هذا الـ notebook مأخوذ في معظمه من notebook تعليمي أصبح مهجوراً الآن في Qiskit Algorithms.)
1. مقدمة
التروترة (Trotterization) هي تقنية لمحاكاة التطور الزمني الآني، وتعتمد على تطبيق متتابع لبوابات كمومية مختارة لتقريب التطور الزمني للنظام خلال شريحة زمنية صغيرة. انطلاقاً من معادلة شرودنغر، يأخذ التطور الزمني لنظام في الحالة الابتدائية الصيغة:
حيث هو الهاميلتوني المستقل عن الزمن الذي يحكم النظام. نأخذ بعين الاعتبار هاميلتونياً يمكن كتابته على شكل مجموع موزون لحدود باولي ، إذ يمثل جداءً تنسوري لعوامل باولي تؤثر على كيوبت. قد تتبادل هذه الحدود مع بعضها أو لا تتبادل. بمعرفة الحالة عند ، كيف نحصل على حالة النظام عند وقت لاحق باستخدام حاسوب كمومي؟ يمكن فهم أسّي العامل بسهولة من خلال متسلسلة تايلور:
بعض الأسسيات البسيطة جداً، مثل ، يمكن تطبيقها بسهولة على الحواسيب الكمومية باستخدام مجموعة صغيرة من البوابات الكمومية. أما معظم الهاميلتونيات ذات الاهتمام العملي فلا تحتوي على حدٍّ واحد، بل على حدود عديدة. لاحظ ما يحدث إذا كان :
حين يتبادل و، نحصل على الحالة المألوفة (التي تنطبق أيضاً على الأعداد والمتغيرات و أدناه):
لكن حين لا يتبادل العاملان، لا يمكن إعادة ترتيب الحدود في متسلسلة تايلور لتبسيطها بهذه الطريقة. لذا، يُعدّ تعبير الهاميلتونيات المعقدة بشكل بوابات كمومية تحدياً قائماً.
أحد الحلول هو أخذ زمن صغير جداً ، بحيث يهيمن الحد الأول في توسّع تايلور. تحت هذا الافتراض:
بالطبع، قد نحتاج إلى تطوير حالتنا لفترة زمنية أطول. يتحقق ذلك باستخدام خطوات زمنية صغيرة متعددة. هذه العملية تُسمى التروترة:
هنا هي الشريحة الزمنية (خطوة التطور) التي نختارها. ونتيجةً لذلك، تُنشأ بوابة تُطبَّق مرة. كلما صغرت الخطوة الزمنية، كان التقريب أدق. غير أن ذلك يُفضي إلى دوائر أعمق، مما يعني في التطبيق الفعلي تراكماً أكبر للأخطاء (وهو مصدر قلق لا يُستهان به على الأجهزة الكمومية الحديثة).
سندرس اليوم التطور الزمني لـنموذج إيزينج على شبكات خطية بحجمي و موقع. تتكون هذه الشبكات من مصفوفة من الدورانات التي تتفاعل فقط مع جيرانها الأقرب. يمكن لهذه الدورانات أن تتخذ اتجاهين: و، يقابلان مغنطة قدرها و على التوالي.
حيث يصف طاقة التفاعل، و شدة المجال الخارجي (في الاتجاه x أعلاه، لكننا سنعدّل ذلك لاحقاً). لنكتب هذا التعبير باستخدام مصفوفات باولي، مع الأخذ بعين الاعتبار أن المجال الخارجي يصنع زاوية مع الاتجاه المستعرض،
يتميز هذا الهاميلتوني بأنه يتيح لنا دراسة تأثيرات المجال الخارجي بسهولة. في الأساس الحسابي، سيُرمَّز النظام على النحو التالي:
| الحالة الكمومية | تمثيل الدوران |
|---|---|
سنبدأ باستكشاف التطور الزمني لهذا النظام الكمومي، وتحديداً سنُبصِّر التطور الزمني لخصائص معينة كالمغنطة.
1.1 المتطلبات
قبل البدء بهذا الكورس، تأكد من تثبيت المكتبات التالية:
- Qiskit SDK v1.2 أو أحدث (
pip install qiskit) - Qiskit Runtime v0.30 أو أحدث (
pip install qiskit-ibm-runtime) - Numpy v1.24.1 أو أحدث < 2 (
pip install numpy)
1.2 استيراد المكتبات
لاحظ أن بعض المكتبات التي قد تكون مفيدة (مثل MatrixExponential وQDrift) مُدرجة حتى لو لم تُستخدم في هذا الـ notebook الحالي. يمكنك تجربتها إن كان لديك وقت!
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime
# Check the version of Qiskit
import qiskit
qiskit.__version__
'2.0.2'
# Import the qiskit library
import numpy as np
import matplotlib.pylab as plt
import warnings
from qiskit import QuantumCircuit
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.primitives import StatevectorEstimator
from qiskit.quantum_info import Statevector, SparsePauliOp
from qiskit.synthesis import (
SuzukiTrotter,
LieTrotter,
)
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2
warnings.filterwarnings("ignore")
2. صياغة المسألة
2.1 تعريف هاميلتوني إيزينج ذي المجال المستعرض
سنأخذ هنا نموذج إيزينج أحادي البُعد ذا المجال المستعرض.
أولاً، سننشئ دالة تأخذ معاملات النظام و و و، وتعيد هاميلتونياتنا كـ SparsePauliOp. إن SparsePauliOp هو تمثيل متناثر لعامل كمومي بدلالة حدود Pauli الموزونة.
def get_hamiltonian(nqubits, J, h, alpha):
# List of Hamiltonian terms as 3-tuples containing
# (1) the Pauli string,
# (2) the qubit indices corresponding to the Pauli string,
# (3) the coefficient.
ZZ_tuples = [("ZZ", [i, i + 1], -J) for i in range(0, nqubits - 1)]
Z_tuples = [("Z", [i], -h * np.sin(alpha)) for i in range(0, nqubits)]
X_tuples = [("X", [i], -h * np.cos(alpha)) for i in range(0, nqubits)]
# We create the Hamiltonian as a SparsePauliOp, via the method
# `from_sparse_list`, and multiply by the interaction term.
hamiltonian = SparsePauliOp.from_sparse_list(
[*ZZ_tuples, *Z_tuples, *X_tuples], num_qubits=nqubits
)
return hamiltonian.simplify()
تعريف الهاميلتوني
النظام الذي سندرسه الآن بحجم ، مع و و كمثال.
n_qubits = 6
hamiltonian = get_hamiltonian(nqubits=n_qubits, J=0.2, h=1.2, alpha=np.pi / 8.0)
hamiltonian
SparsePauliOp(['IIIIZZ', 'IIIZZI', 'IIZZII', 'IZZIII', 'ZZIIII', 'IIIIIZ', 'IIIIZI', 'IIIZII', 'IIZIII', 'IZIIII', 'ZIIIII', 'IIIIIX', 'IIIIXI', 'IIIXII', 'IIXIII', 'IXIIII', 'XIIIII'],
coeffs=[-0.2 +0.j, -0.2 +0.j, -0.2 +0.j, -0.2 +0.j,
-0.2 +0.j, -0.45922012+0.j, -0.45922012+0.j, -0.45922012+0.j,
-0.45922012+0.j, -0.45922012+0.j, -0.45922012+0.j, -1.10865544+0.j,
-1.10865544+0.j, -1.10865544+0.j, -1.10865544+0.j, -1.10865544+0.j,
-1.10865544+0.j])
2.2 ضبط معاملات محاكاة التطور الزمني
سننظر هنا في ثلاث تقنيات مختلفة للتروترة:
- Lie–Trotter (الرتبة الأولى)
- Suzuki–Trotter من الرتبة الثانية
- Suzuki–Trotter من الرتبة الرابعة
سيُستخدم الأخيران في التمرين وفي الملحق.
num_timesteps = 60
evolution_time = 30.0
dt = evolution_time / num_timesteps
product_formula_lt = LieTrotter()
product_formula_st2 = SuzukiTrotter(order=2)
product_formula_st4 = SuzukiTrotter(order=4)
2.3 إعداد الدائرة الكمومية 1 (الحالة الابتدائية)
أنشئ حالة ابتدائية. سنبدأ هنا بتهيئة دوران .
initial_circuit = QuantumCircuit(n_qubits)
initial_circuit.prepare_state("001100")
# Change reps and see the difference when you decompose the circuit
initial_circuit.decompose(reps=1).draw("mpl")
2.4 إعداد الدائرة الكمومية 2 (دائرة لخطوة زمنية واحدة)
سنبني هنا دائرة لخطوة زمنية واحدة باستخدام Lie–Trotter.
صيغة جداء لي (الرتبة الأولى) مُنفَّذة في LieTrotter. تتكون الصيغة من الدرجة الأولى من التقريب المذكور في المقدمة، حيث يُقرَّب الأسّي للمصفوفة لمجموع بجداء من الأسسيات للمصفوفات:
كما ذكرنا سابقاً، الدوائر العميقة جداً تؤدي إلى تراكم الأخطاء وتُسبب مشكلات للحواسيب الكمومية الحديثة. نظراً لأن بوابات الكيوبتين تمتلك معدلات أخطاء أعلى من بوابات الكيوبت الواحد، فإن عمق دائرة الكيوبتين يعدّ من الأهمية بمكان. ما يهم فعلاً هو عمق دائرة ال كيوبتين بعد عملية الـ transpilation (لأن ذلك هو الدائرة التي ينفذها الحاسوب الكمومي فعلاً). لكن دعنا نتعوّد على إحصاء العمليات في هذه الدائرة، حتى الآن باستخدام المحاكي.
single_step_evolution_gates_lt = PauliEvolutionGate(
hamiltonian, dt, synthesis=product_formula_lt
)
single_step_evolution_lt = QuantumCircuit(n_qubits)
single_step_evolution_lt.append(
single_step_evolution_gates_lt, single_step_evolution_lt.qubits
)
print(
f"""
Trotter step with Lie-Trotter
-----------------------------
Depth: {single_step_evolution_lt.decompose(reps=3).depth()}
Gate count: {len(single_step_evolution_lt.decompose(reps=3))}
Nonlocal gate count: {single_step_evolution_lt.decompose(reps=3).num_nonlocal_gates()}
Gate breakdown: {", ".join([f"{k.upper()}: {v}" for k, v in single_step_evolution_lt.decompose(reps=3).count_ops().items()])}
"""
)
single_step_evolution_lt.decompose(reps=3).draw("mpl", fold=-1)
Trotter step with Lie-Trotter
-----------------------------
Depth: 17
Gate count: 27
Nonlocal gate count: 10
Gate breakdown: U3: 12, CX: 10, U1: 5
2.5 تحديد العوامل المراد قياسها
لنعرّف عامل المغنطة ، وعامل متوسط ارتباط الدوران .
magnetization = (
SparsePauliOp.from_sparse_list(
[("Z", [i], 1.0) for i in range(0, n_qubits)], num_qubits=n_qubits
)
/ n_qubits
)
correlation = SparsePauliOp.from_sparse_list(
[("ZZ", [i, i + 1], 1.0) for i in range(0, n_qubits - 1)], num_qubits=n_qubits
) / (n_qubits - 1)
print("magnetization : ", magnetization)
print("correlation : ", correlation)
magnetization : SparsePauliOp(['IIIIIZ', 'IIIIZI', 'IIIZII', 'IIZIII', 'IZIIII', 'ZIIIII'],
coeffs=[0.16666667+0.j, 0.16666667+0.j, 0.16666667+0.j, 0.16666667+0.j,
0.16666667+0.j, 0.16666667+0.j])
correlation : SparsePauliOp(['IIIIZZ', 'IIIZZI', 'IIZZII', 'IZZIII', 'ZZIIII'],
coeffs=[0.2+0.j, 0.2+0.j, 0.2+0.j, 0.2+0.j, 0.2+0.j])
2.6 تنفيذ محاكاة التطور الزمني
سنتابع الطاقة (القيمة المتوقعة للهاميلتوني)، والمغنطة (القيمة المتوقعة لعامل المغنطة)، ومتوسط ارتباط الدوران (القيمة المتوقعة لعامل الارتباط). يُقدّر StatevectorEstimator (EstimatorV2) في Qiskit القيم المتوقعة للمراقبات، .
# Initiate the circuit
evolved_state = QuantumCircuit(initial_circuit.num_qubits)
# Start from the initial spin configuration
evolved_state.append(initial_circuit, evolved_state.qubits)
# Initiate Estimator (V2)
estimator = StatevectorEstimator()
# Set number of shots
shots = 10000
# Translate the precision required from the number of shots
precision = np.sqrt(1 / shots)
energy_list = []
mag_list = []
corr_list = []
# Estimate expectation values for t=0.0
job = estimator.run(
[(evolved_state, [hamiltonian, magnetization, correlation])], precision=precision
)
# Get estimated expectation values
evs = job.result()[0].data.evs
energy_list.append(evs[0])
mag_list.append(evs[1])
corr_list.append(evs[2])
# Start time evolution
for n in range(num_timesteps):
# Expand the circuit to describe delta-t
evolved_state.append(single_step_evolution_gates_lt, evolved_state.qubits)
# Estimate expectation values at delta-t
job = estimator.run(
[(evolved_state, [hamiltonian, magnetization, correlation])],
precision=precision,
)
# Retrieve results (expectation values)
evs = job.result()[0].data.evs
energy_list.append(evs[0])
mag_list.append(evs[1])
corr_list.append(evs[2])
# Transform the list of expectation values (at each time step) to arrays
energy_array = np.array(energy_list)
mag_array = np.array(mag_list)
corr_array = np.array(corr_list)
2.7 رسم التطور الزمني للمراقبات
سنرسم القيم المتوقعة التي قسناها مقابل الزمن.
fig, axes = plt.subplots(3, sharex=True)
times = np.linspace(0, evolution_time, num_timesteps + 1) # includes initial state
axes[0].plot(
times,
energy_array,
label="First order",
marker="x",
c="darkmagenta",
ls="-",
lw=0.8,
)
axes[1].plot(
times, mag_array, label="First order", marker="x", c="darkmagenta", ls="-", lw=0.8
)
axes[2].plot(
times, corr_array, label="First order", marker="x", c="darkmagenta", ls="-", lw=0.8
)
axes[0].set_ylabel("Energy")
axes[1].set_ylabel("Magnetization")
axes[2].set_ylabel("Mean spin correlation")
axes[2].set_xlabel("Time")
fig.suptitle("Observable evolution")
Text(0.5, 0.98, 'Observable evolution')
