סימולציה קוונטית
Yukio Kawashima (May 30, 2024)
הורד את ה-PDF של ההרצאה המקורית. שים לב שחלק מקטעי הקוד עשויים להיות מיושנים מכיוון שאלו תמונות סטטיות.
זמן QPU משוער להרצת הניסוי הוא 7 שניות.
(מחברת זו לקוחה ברובה ממחברת הדרכה שהוצאה משימוש עבור Qiskit Algorithms.)
1. מבוא
כטכניקת אבולוציה בזמן אמת, טרוטריזציה מורכבת מהפעלה עוקבת של Gate קוונטי אחד או יותר, שנבחרו לקירוב האבולוציה בזמן של מערכת עבור פרוסת זמן. בעקבות משוואת שרדינגר, האבולוציה בזמן של מערכת שנמצאת בתחילה במצב לובשת את הצורה:
כאשר הוא ההמילטוניאן הבלתי תלוי בזמן המנהל את המערכת. אנו מתייחסים להמילטוניאן שניתן לכתוב כסכום משוקלל של איברי פאולי , כאשר מייצג מכפלה טנזורית של איברי פאולי הפועלים על Qubits. בפרט, ייתכן שאיברי פאולי אלו מתחלפים זה עם זה, וייתכן שלא. בהינתן מצב בזמן , כיצד מקבלים את מצב המערכת בזמן מאוחר יותר באמצעות מחשב קוונטי? את האקספוננט של אופרטור ניתן להבין בצורה הקלה ביותר דרך טור טיילור שלו:
אקספוננטים בסיסיים מאוד, כמו , ניתנים לממוש בקלות במחשבים קוונטיים באמצעות קבוצה קומפקטית של Gates קוונטיים. לרוב ההמילטוניאנים המעניינים לא יהיה רק איבר בודד, אלא הרבה איברים. שימו לב למה שקורה כאשר :
כאשר ו- מתחלפים, יש לנו את המקרה המוכר (שתקף גם למספרים, ולמשתנים ו- להלן):
אבל כאשר אופרטורים אינם מתחלפים, לא ניתן לסדר מחדש את האיברים בטור טיילור כדי לפשט בדרך זו. לפיכך, ביטוי המילטוניאנים מסובכים בעזרת Gates קוונטיים הוא אתגר.
פתרון אחד הוא לשקול זמן קצר מאוד , כך שהאיבר מסדר ראשון בפיתוח טיילור שולט. תחת הנחה זו:
כמובן, ייתכן שנצטרך לאפשר למערכת להתפתח על פני זמן ארוך יותר. זה מושג על ידי שימוש בצעדים קטנים רבים בזמן. תהליך זה נקרא טרוטריזציה:
כאן הוא פרוסת הזמן (צעד האבולוציה) שאנו בוחרים. כתוצאה מכך, נוצר Gate שמופעל פעמים. צעד זמן קטן יותר מוביל לקירוב מדויק יותר. עם זאת, הדבר גם מוביל ל-Circuits עמוקים יותר, אשר בפועל גורמים לצבירת שגיאות גדולה יותר (דאגה לא מבוטלת על מכשירים קוונטיים מהדור הנוכחי).
היום נלמד את האבולוציה בזמן של מודל איזינג על סריגים לינאריים עם ו- אתרים. סריגים אלו מורכבים ממערך של ספינים המקיימים אינטראקציה רק עם שכניהם הקרובים ביותר. לספינים אלו יכולות להיות שתי אוריינטציות: ו-, המתאימות למגנטיזציה של ו- בהתאמה.
כאשר מתאר את אנרגיית האינטראקציה, ו- את עצמת השדה החיצוני (בכיוון ה-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) נכללות למרות שאינן בשימוש במחברת הנוכחית. אתם יכולים לנסות אותן אם יש לכם זמן!
# 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 הכנת ה-Circuit הקוונטי 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 הכנת ה-Circuit הקוונטי 2 (Circuit יחיד לאבולוציה בזמן)
אנו בונים כאן Circuit עבור צעד זמן בודד תוך שימוש ב-Lie–Trotter.
נוסחת המכפלה של Lie (סדר ראשון) מממושת במחלקה LieTrotter. נוסחה מסדר ראשון מורכבת מהקירוב שצוין במבוא, שבו האקספוננט המטריצי של סכום מקורב על ידי מכפלה של אקספוננטים מטריציים:
כפי שצוין קודם, Circuits עמוקים מאוד מובילים לצבירת שגיאות, וגורמים לבעיות עבור מחשבים קוונטיים מודרניים. מכיוון ש-Gates דו-Qubit מכילים שיעורי שגיאות גבוהים יותר מ-Gates חד-Qubit, כמות מעניינת במיוחד היא עומק ה-Circuit הדו-Qubit. מה שחשוב באמת הוא עומק ה-Circuit הדו-Qubit לאחר Transpilation (מכיוון שזהו ה-Circuit שהמחשב הקוונטי מבצע בפועל). אבל בואו נרגיל את עצמנו לספור את הפעולות עבור ה-Circuit הזה, גם כעת בשימוש בסימולטור.
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')

3. תרגיל 1. ביצוע סימולציה באמצעות Suzuki–Trotter מסדר שני
עכשיו בואו ננסה לבצע סימולציה עם Suzuki–Trotter מסדר שני בהמשך לדוגמה של Lie–Trotter שראינו למעלה.
ניתן להשתמש ב-Suzuki-Trotter מסדר שני ב-Qiskit באמצעות מחלקת SuzukiTrotter. בשימוש בנוסחה זו, פירוק מסדר שני הוא:
3.1 בניית Circuit לצעד זמן יחיד
השתמש ב-product_formula_st2 (SuzukiTrotter(order=2)) ובנה Circuit לצעד זמן יחיד עם Suzuki–Trotter מסדר שני. כמו כן, ספור את מספר ה-Gate-ים ואת עומק ה-Circuit והשווה עם Lie–Trotter.
# Modify the line below (Use PauliEvolutionGate)
single_step_evolution_gates_st2 = PauliEvolutionGate(
hamiltonian, dt, synthesis=product_formula_st2
)
single_step_evolution_st2 = QuantumCircuit(n_qubits)
single_step_evolution_st2.append(
single_step_evolution_gates_st2, single_step_evolution_st2.qubits
)
# Let us print some stats
print(
f"""
Trotter step with second-order Suzuki-Trotter
-----------------------------
Depth: {single_step_evolution_st2.decompose(reps=3).depth()}
Gate count: {len(single_step_evolution_st2.decompose(reps=3))}
Nonlocal gate count: {single_step_evolution_st2.decompose(reps=3).num_nonlocal_gates()}
Gate breakdown: {", ".join([f"{k.upper()}: {v}" for k, v in single_step_evolution_st2.decompose(reps=3).count_ops().items()])}
"""
)
single_step_evolution_st2.decompose(reps=2).draw("mpl", fold=-1)
Trotter step with second-order Suzuki-Trotter
-----------------------------
Depth: 34
Gate count: 53
Nonlocal gate count: 20
Gate breakdown: U3: 23, CX: 20, U1: 10
3.2 ביצוע סימולציית התפתחות בזמן
בצע התפתחות בזמן עם Suzuki–Trotter מסדר שני.
# 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_st2 = []
mag_list_st2 = []
corr_list_st2 = []
# 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_st2.append(evs[0])
mag_list_st2.append(evs[1])
corr_list_st2.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_st2, 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_st2.append(evs[0])
mag_list_st2.append(evs[1])
corr_list_st2.append(evs[2])
# Transform the list of expectation values (at each time step) to arrays
energy_array_st2 = np.array(energy_list_st2)
mag_array_st2 = np.array(mag_list_st2)
corr_array_st2 = np.array(corr_list_st2)
3.3 הצגת תוצאות Suzuki–Trotter מסדר שני
axes[0].plot(
times,
energy_array_st2,
label="Second Order",
marker="x",
c="limegreen",
ls="-",
lw=0.8,
)
axes[1].plot(
times,
mag_array_st2,
label="Second Order",
marker="x",
c="limegreen",
ls="-",
lw=0.8,
)
axes[2].plot(
times,
corr_array_st2,
label="Second Order",
marker="x",
c="limegreen",
ls="-",
lw=0.8,
)
# Replace the legend
# legend.remove()
legend = fig.legend(
*axes[0].get_legend_handles_labels(),
bbox_to_anchor=(1.0, 0.5),
loc="center left",
framealpha=0.5,
)
fig
