מאחדים את הכול עם Qiskit Runtime
סיכום
ויקטוריה ליפינסקה מציגה סיכום סופי של מה שלמדנו עד כה.
מקורות
המאמרים הבאים מוזכרים בסרטון לעיל.
- Quantum Chemistry in the Age of Quantum Computing, Cao, et al.
- Quantum computational chemistry, McArdle, et al.
VQE עם דפוסי Qiskit
יש לנו את כל המרכיבים הנחוצים לחישוב VQE:
- Hamiltonian
- Ansatz
- אופטימייזר קלאסי
כעת נצטרך רק לשלב אותם במסגרת דפוסי Qiskit.
שלב 1: ממפים קלטים קלאסיים לבעיה קוונטית
כפי שצוין קודם, נניח כאן שכבר נוצר Hamiltonian מתאים בפורמט הנכון. אם יש לך שאלות בנושא, ראה את השיעור על Hamiltonians להנחיה. בלוק הקוד שלהלן מגדיר את הרכיבים שהוסברו בשיעורים הקודמים. כאן בחרנו לדגם את H2 מכיוון שה-Hamiltonian שלו קומפקטי מספיק לכתיבה מלאה.
# 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
אנו בוחרים Circuit מסוג 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
עכשיו אנו בונים את פונקציית העלות שלנו. היא כמובן קשורה ל-Hamiltonian, אך שונה ממנו בכך שה-Hamiltonian הוא אופרטור, ואנחנו רוצים פונקציה שמחזירה את ערך הציפייה של אותו אופרטור, תוך שימוש ב-Estimator. כמובן, היא משיגה זאת באמצעות ה-ansatz והפרמטרים הווריאציוניים, כך שכל אלה מופיעים כארגומנטים. להלן, אנו מגדירים גרסאות מעט שונות לשימוש על חומרה אמיתית או סימולטורים.
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
שלב 2: אופטימיזציה של הבעיה להרצה קוונטית
אנחנו רוצים שהקוד שלנו ירוץ ביעילות האפשרית על החומרה בה אנו משתמשים. לכן עלינו לבחור Backend כדי להתחיל את שלב האופטימיזציה. הקוד שלהלן בוחר את ה-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
אופטימיזציה של Circuit להרצה על Backend אמיתי היא נושא עשיר וקריטי. אך הוא אינו ייחודי ל-VQE. לעת עתה, נזכיר בקצרה שני מונחים חשובים:
- optimization_level: זה מתאר עד כמה ה-Circuit מותאם לפריסה של ה-Backend שנבחר. רמת האופטימיזציה הנמוכה ביותר עושה רק את המינימום הנדרש כדי להריץ את ה-Circuit על המכשיר; היא ממפה את ה-Qubit של ה-Circuit ל-Qubit של המכשיר ומוסיפה שערי החלפה כדי לאפשר את כל פעולות שני ה-Qubit. רמת האופטימיזציה הגבוהה ביותר הרבה יותר חכמה ומשתמשת בהרבה טריקים כדי להפחית את ספירת ה-Gate הכוללת. מכיוון שלשערים מרובי Qubit יש שיעורי שגיאה גבוהים ו-Qubit מאבדים קוהרנטיות עם הזמן, מעגלים קצרים יותר אמורים לתת תוצאות טובות יותר.
- Dynamical Decoupling: אנו יכולים להחיל רצף של Gates על Qubit בלתי פעילים. זה מבטל חלק מהאינטראקציות הלא רצויות עם הסביבה.
עיין בתיעוד המקושר לקבלת מידע נוסף על אופטימיזציה של Circuits. הקוד שלהלן מייצר מנהל pass manager תוך שימוש ב-preset pass managers מ-
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")
עלינו גם להחיל את מאפייני פריסת המכשיר על ה-Hamiltonian.
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])
שלב 3: ביצוע באמצעות Qiskit Primitives
לפני שאנו מריצים על החומרה שנבחרה, כדאי להשתמש בסימולטור לניפוי שגיאות ראשוני, ולעיתים לאמדן שגיאות. מסיבות אלו, נדגים בקצרה כיצד להריץ VQE על סימולטור. אך חשוב לציין כי שום מחשב קלאסי, סימולטור או GPU אינו יכול לסמלץ במדויק את הפונקציונליות המלאה של מחשב קוונטי בעל 127 Qubit עם שזירה גבוהה. בעידן הנוכחי של שימושיות קוונטית, לסימולטורים יהיה שימוש מוגבל.
זכור שעבור כל בחירת פרמטרים ב-Circuit הווריאציוני, יש לחשב ערך ציפייה (שכן זה הערך שיש למזער). כפי שאולי כבר ניחשת, הדרך היעילה ביותר לעשות זאת היא שימוש ב-Primitive של Qiskit, Estimator. נתחיל עם סימולטור מקומי, אשר ידרוש מאיתנו להשתמש בגרסה המקומית של Estimator הנקראת BackendEstimator.
בשמירת ה-Backend האמיתי שבו השתמשנו לאופטימיזציה, נוכל לייבא מודל של התנהגות הרעש של אותו מכשיר ולהשתמש בו עם הסימולטור המקומי שנבחר. כאן נשתמש ב-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, תוך מזעור פונקציית העלות באמצעות ה-Hamiltonian שנבחר, ה-ansatz, האופטימייזר הקלאסי וה-BackendEstimator שלנו, המבוסס על ה-Backend האמיתי שבחרנו לשימוש הבא. שים לב שכאן בחרנו מספר קטן יחסית לאיטרציות המקסימליות. הסיבה לכך היא שאנו פשוט משתמשים בסימולטור לניפוי שגיאות. שלבי אופטימיזציה של 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
הקוד הורץ כהלכה, אם כי לא התכנס — כפי שציפינו. נמשיך להריץ את החישוב על חומרה אמיתית, ואז נדון בפלטים. עבור Backends אמיתיים, נשתמש ב-Qiskit Runtime Estimator. נרצה להריץ זאת בתוך Session של Qiskit Runtime ובדרך כלל נרצה לציין אפשרויות עבור אותה Session.
from qiskit_ibm_runtime import QiskitRuntimeService, Session
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime.options import EstimatorOptions
בין היתר, שימוש ב-Session אומר שהמשימה שלנו תמתין בתור רק פעם אחת, בתחילה. איטרציות נוספות של האופטימייזר הקלאסי לא יתורגמו לתור נוסף. ב-Session, נוכל להגדיר רמות חוסן ואופטימיזציה. כלים אלו חשובים מספיק כדי שנכלול סקירה קצרה של כל אחד ואת חשיבותו ב-VQE, עם קישורים ללמידה נוספת:
- Runtime sessions: VQE הוא מטבעו איטרטיבי, כאשר האופטימייזר הקלאסי בוחר פרמטרים ווריאציוניים חדשים, ובכך Gatesחדשות נמצאות בשימוש, בכל ניסיון עוקב. ללא שימוש ב-sessions, הדבר עלול לגרום לזמן תור נוסף בין כל Circuit של ניסיון. עטיפת חישוב ה-VQE בתוך Session מביאה לתור ראשוני אחד בלבד לפני תחילת העבודה, ללא זמן תור נוסף בין שלבים ווריאציוניים. אסטרטגיה זו כבר שימשה בדוגמת השיעור הקודם, אך עשויה למלא תפקיד חשוב אף יותר בעת שינוי גיאומטריה. למידע נוסף על sessions, עיין בתיעוד מצבי הביצוע.
- האופטימיזציה המובנית של Estimator: ב-Estimator יש אפשרויות מובנות לאופטימיזציה של חישוב. בהקשרים רבים (כולל Estimator), ההגדרות מוגבלות ל-0 ו-1, כאשר 0 מציין ללא אופטימיזציה, ו-1 (ברירת המחדל) מציין אופטימיזציה מסוימת של ה-Circuit לחומרה שנבחרה. הקשרים אחרים מסוימים מאפשרים הגדרות של 0, 1, 2 או 3. למידע על השיטות הספציפיות המשמשות בהגדרות שונות, ראה את התיעוד. כאן נגדיר בפועל אופטימיזציה ל-0 ונשתמש ב-
skip\_transpilation = true, מכיוון שכבר עשינו Transpile ל-Circuit שלנו באמצעות ה-pass manager לעיל, בחלק האופטימיזציה. - החוסן המובנה של Estimator: כמו אופטימיזציה, ל-Estimator יש הגדרות מובנות לחוסן מול שגיאות, התואמות לגישות שונות למיתון שגיאות. כדי ללמוד על הגדרות רמת החוסן, עיין בתיעוד.
כדאי לציין שמיתון שגיאות ממלא תפקיד מורכב בהתכנסות של חישוב VQE. האופטימייזר הקלאסי מחפש במרחב הפרמטרים את הפרמטרים שממזערים את האנרגיה. כשאתה רחוק מאוד מהפרמטרים האופטימליים, שיפוע תלול עשוי להיות גלוי לאופטימייזר הקלאסי אפילו בנוכחות שגיאות. אך ככל שהחישוב מתכנס ואתה מתקרב לערכים האופטימליים, השיפוע הופך קטן יותר, ומוצף בקלות רבה יותר על ידי שגיאות. כמה מיתון שגיאות אתה רוצה להשתמש בו? באיזה שלבים בהתכנסות? אלו בחירות שעליך לעשות עבור מקרה השימוש הספציפי שלך.
עבור הרצת החומרה הראשונה הזו, הגדרנו חוסן ל-0 כדי לאפשר הרצה מהירה יחסית. לכל יישום רציני, תרצה להשתמש במיתון שגיאות. שים לב שבתא שלהלן יש שני ערכות אפשרויות: (1) אפשרויות ל-Runtime session, שכינינו "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® תחת Workloads.
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
שלב 4: עיבוד לאחר ביצוע, החזרת תוצאה בפורמט קלאסי
בואו נקדיש רגע לוודא שאנחנו מבינים את הפלטים האלה. פלט "fun" הוא הערך המינימלי שקיבלנו עבור פונקציית העלות (לא בהכרח הערך האחרון שחושב). זוהי האנרגיה הכוללת, כולל דחיית גרעין חיובית, ולכן הגדרנו גם את electron_energy.
במקרה לעיל, יש לנו הודעה שמספר הערכות הפונקציה המרבי חרג, ושמספר הערכות הפונקציה (nfev) היה 10. משמעות הדבר היא פשוט שקריטריונים אחרים להתכנסות האופטימיזציה לא התקיימו; במילים אחרות, אין סיבה לחשוב שמצאנו את אנרגיית מצב היסוד. זוהי גם משמעות success שהוא "False".
לבסוף, יש לנו x. זהו וקטור הפרמטרים הווריאציוניים. אלו הפרמטרים שנמצאו בחישוב שהניב את פונקציית העלות המינימלית (ערך ציפייה האנרגיה). שמונת הערכות הללו מתאימות לשמונת זוויות הסיבוב באותן Gates ב-ansatz שמקבלות זוויות סיבוב משתנות.
כל הכבוד! הרצת חישוב VQE על QPU של IBM Quantum!
בשיעור הבא, נראה כיצד לכוון את workstream הזה כך שיכלול משתנים ב-Hamiltonian שלך. בהקשר של בעיות כימיה קוונטית, הדבר עשוי להיות כרוך בשינוי גיאומטריה כדי לקבוע צורות של מולקולות או אתרי קשירה.
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