נוסחאות מכפלת-רב להפחתת שגיאת Trotter
שימוש משוער ב-QPU: ארבע דקות על מעבד Heron r2 (הערה: זוהי הערכה בלבד. זמן הריצה שלך עשוי להשתנות.)
רקע
מדריך זה מדגים כיצד להשתמש בנוסחת מכפלת-רב (MPF) כדי להשיג שגיאת Trotter נמוכה יותר על האובזרבל שלנו בהשוואה לזו שנגרמת על ידי מעגל ה-Trotter העמוק ביותר שבאמת נריץ. MPF מפחיתים את שגיאת ה-Trotter של דינמיקת המילטוניאן דרך שילוב משוקלל של מספר ביצועי מעגל. שקול את המשימה של מציאת ערכי ציפייה של אובזרבל עבור המצב הקוונטי עם ההמילטוניאן . אפשר להשתמש בנוסחאות מכפלה (PF) כדי לקרב את אבולוציית הזמן על ידי ביצוע הדברים הבאים:
- כתוב את ההמילטוניאן כ- כאשר הם אופרטורים הרמיטיים כך שכל יוניטרי מתאים יכול להיות מיושם ביעילות על מכשיר קוונטי.
- קרב את האיברים שאינם מתחלפים זה עם זה.
אז, ה-PF מסדר ראשון (נוסחת Lie-Trotter) הוא:
שיש לו איבר שגיאה ריבועי . אפשר גם להשתמש ב-PF מסדרים גבוהים יותר (נוסחאות Lie-Trotter-Suzuki), שמתכנסים מהר יותר, ומוגדרים באופן רקורסיבי כ:
כאשר הוא הסדר של ה-PF הסימטרי ו-. עבור אבולוציות זמן ארוכות, אפשר לפצל את מרווח הזמן ל- מרווחים, הנקראים צעדי Trotter, בעלי משך ולקרב את אבולוציית הזמן בכל מרווח עם נוסחת מכפלה מסדר . לפיכך, ה-PF מסדר עבור אופרטור אבולוציית זמן על פני צעדי Trotter הוא:
כאשר איבר השגיאה פוחת עם מספר צעדי ה-Trotter והסדר של ה-PF.
בהינתן מספר שלם ונוסחת מכפלה , המצב המתפתח בזמן המקורב ניתן לקבלה מ- על ידי הפעלת איטרציות של נוסחת המכפלה .
הוא קירוב ל- עם שגיאת קירוב Trotter ||. אם אנחנו שוקלים שילוב לינארי של קירובי Trotter של :
כאשר הם מקדמי המשקל שלנו, היא מטריצת הצפיפות המתאימה למצב הטהור שהתקבל על ידי התפתחות המצב ההתחלתי עם נוסחת המכפלה, , הכוללת צעדי Trotter, ו- מאנדקס את מספר ה-PF שמרכיבים את ה-MPF. כל האיברים ב- משתמשים באותה נוסחת מכפלה כבסיס שלהם. המטרה היא לשפר את || על ידי מציאת עם אפילו נמוך יותר.
- אינו חייב להיות מצב פיזיקלי מכיוון ש- אינם חייבים להיות חיוביים. המטרה כאן היא למזער את השגיאה בערך הצפייה של האובזרבלים ולא למצוא תחליף פיזיקלי ל-.
- קובע גם את עומק המעגל וגם את רמת קירוב Trotter. ערכים קטנים יותר של מובילים למעגלים קצרים יותר, שגורמים לפחות שגיאות מעגל אך יהיו קירוב פחות מדויק למצב הרצוי.
המפתח כאן הוא ששגיאת ה-Trotter הנותרת שניתנת על ידי קטנה יותר משגיאת ה-Trotter שהיינו מקבלים פשוט על ידי שימוש בערך ה- הגדול ביותר.
אתה יכול לראות את התועלת של זה משתי נקודות מבט:
- עבור תקציב קבוע של צעדי Trotter שאתה יכול להריץ, אתה יכול לקבל תוצאות עם שגיאת Trotter שקטנה יותר בסך הכל.
- בהינתן מספר יעד כלשהו של צעדי Trotter שגדול מדי לביצוע, אתה יכול להשתמש ב-MPF כדי למצוא אוסף של מעגלים בעומק נמוך יותר להריץ שמביאים לשגיאת Trotter דומה.
דרישות
לפני שמתחילים עם מדריך זה, ודא שהדברים הבאים מותקנים:
- Qiskit SDK v1.0 ומעלה, עם תמיכה ב-visualization
- Qiskit Runtime v0.22 ומעלה (
pip install qiskit-ibm-runtime) - תוספי MPF Qiskit (
pip install qiskit_addon_mpf) - תוספי utils של Qiskit (
pip install qiskit_addon_utils) - ספריית Quimb (
pip install quimb) - ספריית Qiskit Quimb (
pip install qiskit-quimb) - Numpy v0.21 עבור תאימות בין חבילות (
pip install numpy==0.21)
חלק I. דוגמה בקנה מידה קטן
חקור את היציבות של MPF
אין הגבלה ברורה על הבחירה של מספר צעדי Trotter שמרכיבים את מצב ה-MPF . עם זאת, אלה חייבים להיבחר בזהירות כדי להימנע מאי-יציבויות בערכי הצפייה המתקבלים המחושבים מ-. כלל כללי טוב הוא לקבוע את צעד ה-Trotter הקטן ביותר כך ש-. אם אתה רוצה ללמוד יותר על זה וכיצד לבחור את ערכי ה- האחרים שלך, ראה במדריך How to choose the Trotter steps for an MPF.
בדוגמה למטה אנחנו חוקרים את היציבות של פתרון ה-MPF על ידי חישוב ערך הצפייה של המגנטיזציה לטווח של זמנים תוך שימוש במצבים שונים שהתפתחו בזמן. באופן ספציפי, אנחנו משווים את ערכי הצפייה המחושבים מכל אחת מאבולוציות הזמן המקורבות המיושמות עם צעדי ה-Trotter המתאימים ומודלי ה-MPF השונים (מקדמים סטטיים ודינמיים) עם הערכים המדויקים של האובזרבל שהתפתח בזמן. ראשית, בואו נגדיר את הפרמטרים עבור נוסחאות ה-Trotter וזמני האבולוציה
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-mpf qiskit-addon-utils qiskit-aer qiskit-ibm-runtime rustworkx scipy
import numpy as np
mpf_trotter_steps = [1, 2, 4]
order = 2
symmetric = False
trotter_times = np.arange(0.5, 1.55, 0.1)
exact_evolution_times = np.arange(trotter_times[0], 1.55, 0.05)
עבור דוגמה זו נשתמש במצב Neel כמצב ההתחלתי ובמודל Heisenberg על קו של 10 אתרים עבור ההמילטוניאן השולט באבולוציית הזמן
כאשר הוא חוזק הצימוד עבור קצוות שכן-קרוב ביותר.
from qiskit.transpiler import CouplingMap
from rustworkx.visualization import graphviz_draw
from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian
import numpy as np
L = 10
# Generate some coupling map to use for this example
coupling_map = CouplingMap.from_line(L, bidirectional=False)
graphviz_draw(coupling_map.graph, method="circo")
# Get a qubit operator describing the Heisenberg field model
hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(1.0, 1.0, 1.0),
ext_magnetic_field=(0.0, 0.0, 0.0),
)
print(hamiltonian)
SparsePauliOp(['IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII'],
coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
האובזרבל שנמדוד הוא מגנטיזציה על זוג קיוביטים באמצע השרשרת.
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp.from_sparse_list(
[("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)
print(observable)
SparsePauliOp(['IIIIZZIIII'],
coeffs=[1.+0.j])
אנחנו מגדירים מעבר טרנספילר לאסוף את סיבובי ה-XX וה-YY במעגל כשער XX+YY יחיד. זה יאפשר לנו למנף את תכונות שימור הספין של TeNPy במהלך חישוב ה-MPO, להאיץ משמעותית את החישוב.
from qiskit.circuit.library import XXPlusYYGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes.optimization.collect_and_collapse import (
CollectAndCollapse,
collect_using_filter_function,
collapse_to_operation,
)
from functools import partial
def filter_function(node):
return node.op.name in {"rxx", "ryy"}
collect_function = partial(
collect_using_filter_function,
filter_function=filter_function,
split_blocks=True,
min_block_size=1,
)
def collapse_to_xx_plus_yy(block):
param = 0.0
for node in block.data:
param += node.operation.params[0]
return XXPlusYYGate(param)
collapse_function = partial(
collapse_to_operation,
collapse_function=collapse_to_xx_plus_yy,
)
pm = PassManager()
pm.append(CollectAndCollapse(collect_function, collapse_function))
אז אנחנו יוצרים את המעגלים המיישמים את אבולוציות הזמן המקורבות של Trotter.
from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
)
from qiskit import QuantumCircuit
# Initial Neel state preparation
initial_state_circ = QuantumCircuit(L)
initial_state_circ.x([i for i in range(L) if i % 2 != 0])
all_circs = []
for total_time in trotter_times:
mpf_trotter_circs = [
generate_time_evolution_circuit(
hamiltonian,
time=total_time,
synthesis=SuzukiTrotter(reps=num_steps, order=order),
)
for num_steps in mpf_trotter_steps
]
mpf_trotter_circs = pm.run(
mpf_trotter_circs
) # Collect XX and YY into XX + YY
mpf_circuits = [
initial_state_circ.compose(circuit) for circuit in mpf_trotter_circs
]
all_circs.append(mpf_circuits)
mpf_circuits[-1].draw("mpl", fold=-1)

לאחר מכן, אנחנו מחשבים את ערכי הצפייה המתפתחים בזמן ממעגלי ה-Trotter.
from copy import deepcopy
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
aer_sim = AerSimulator()
estimator = Estimator(mode=aer_sim)
mpf_expvals_all_times, mpf_stds_all_times = [], []
for t, mpf_circuits in zip(trotter_times, all_circs):
mpf_expvals = []
circuits = [deepcopy(circuit) for circuit in mpf_circuits]
pm_sim = generate_preset_pass_manager(
backend=aer_sim, optimization_level=3
)
isa_circuits = pm_sim.run(circuits)
result = estimator.run(
[(circuit, observable) for circuit in isa_circuits], precision=0.005
).result()
mpf_expvals = [res.data.evs for res in result]
mpf_stds = [res.data.stds for res in result]
mpf_expvals_all_times.append(mpf_expvals)
mpf_stds_all_times.append(mpf_stds)
אנחנו גם מחשבים את ערכי הצפייה המדויקים להשוואה.
from scipy.linalg import expm
from qiskit.quantum_info import Statevector
exact_expvals = []
for t in exact_evolution_times:
# Exact expectation values
exp_H = expm(-1j * t * hamiltonian.to_matrix())
initial_state = Statevector(initial_state_circ).data
time_evolved_state = exp_H @ initial_state
exact_obs = (
time_evolved_state.conj()
@ observable.to_matrix()
@ time_evolved_state
).real
exact_expvals.append(exact_obs)
מקדמי MPF סטטיים
MPF סטטיים הם אלה שבהם ערכי ה- אינם תלויים בזמן האבולוציה, . בואו נשקול את ה-PF מסדר עם צעדי Trotter, זה יכול להיכתב כ:
כאשר הן מטריצות שתלויות בקומוטטורים של איברי בפירוק של ההמילטוניאן. חשוב לציין ש- עצמם אינם תלויים בזמן ובמספר צעדי ה-Trotter . לכן, אפשר לבטל איברי שגיאה מסדר נמוך יותר התורמים ל- עם בחירה קפדנית של המשקלים של השילוב הלינארי. כדי לבטל את שגיאת ה-Trotter עבור האיברים הראשונים (אלה יתנו את התרומות הגדולות ביותר מכיוון שהם מתאימים למספר הנמוך יותר של צעדי Trotter) בביטוי עבור , המקדמים חייבים לספק את המשוואות הבאות:
עם . המשוואה הראשונה מבטיחה שאין הטיה במצב הבנוי , בעוד שהמשוואה השנייה מבטיחה את הביטול של שגיאות ה-Trotter. עבור PF מסדר גבוה יותר, המשוואה השנייה הופכת ל- כאשר עבור PF סימטריים ו- אחרת, עם . השגיאה המתקבלת (ראה [1],[2]) היא אז
קביעת המקדמים הסטטיים של MPF עבור קבוצה נתונה של ערכי מסתכמת בפתרון מערכת המשוואות הלינאריות המוגדרת על ידי שתי המשוואות למעלה עבור המשתנים : . כאשר הם המקדמים שאנחנו מעוניינים בהם, היא מטריצה שתלויה ב- ובסוג ה-PF שאנו משתמשים בו (), ו- הוא וקטור של אילוצים. באופן ספציפי:
כאשר הוא ה-order, הוא אם symmetric הוא True ו- אחרת, הם ה-trotter_steps, ו- הם המשתנים לפתור עבורם. האינדקסים ו- מתחילים ב-. אנחנו יכולים גם לדמיין זאת בצורת מטריצה:
ו
לפרטים נוספים, ראה בתיעוד של מערכת המשוואות הלינאריות (LSE).
אנחנו יכולים למצוא פתרון עבור באופן אנליטי כ-; ראה לדוגמה [1] או [2]. עם זאת, הפתרון המדויק הזה יכול להיות "לא-מותנה היטב", מה שגורם לנורמות L1 גדולות מאוד של המקדמים שלנו, , שיכולות להוביל לביצועים גרועים של ה-MPF. במקום זאת, אפשר גם לקבל פתרון מקורב שממזער את נורמת ה-L1 של כדי לנסות לייעל את התנהגות ה-MPF.
הגדר את ה-LSE
כעת לאחר שבחרנו את ערכי ה- שלנו, עלינו קודם לבנות את ה-LSE, כפי שהוסבר לעיל.
המטריצה תלויה לא רק ב- אלא גם בבחירת ה-PF שלנו, במיוחד ה-order שלו.
בנוסף, אתה עשוי לקחת בחשבון האם ה-PF סימטרי או לא (ראה [1]) על ידי הגדרת symmetric=True/False.
עם זאת, זה לא נדרש, כפי שמוצג על ידי [2].
from qiskit_addon_mpf.static import setup_static_lse
lse = setup_static_lse(mpf_trotter_steps, order=order, symmetric=symmetric)
בואו נעבור דרך הערכים שנבחרו למעלה כדי לבנות את מטריצת ה- ואת וקטור ה-. עם צעדי Trotter , סדר ובחירה של צעדי Trotter לא-סימטריים (), יש לנו שאלמנטי המטריצה של מתחת לשורה הראשונה נקבעים על ידי הביטוי