נוסחאות מכפלת-רב להפחתת שגיאת 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 לא-סימטריים (), יש לנו שאלמנטי המטריצה של מתחת לשורה הראשונה נקבעים על ידי הביטוי , באופן ספציפי:
או בצורת מטריצה:
זה אפשרי לראות על ידי בדיקת האובייקט lse:
lse.A
array([[1. , 1. , 1. ],
[1. , 0.25 , 0.0625 ],
[1. , 0.125 , 0.015625]])
בעוד שלוקטור האילוצים יש את האלמנטים הבאים:
לפיכך,
ובאופן דומה ב-lse:
lse.b
array([1., 0., 0.])
לאובייקט lse יש מתודות למציאת המקדמים הסטטיים המספקים את מערכת המשוואות.
mpf_coeffs = lse.solve()
print(
f"The static coefficients associated with the ansatze are: {mpf_coeffs}"
)
The static coefficients associated with the ansatze are: [ 0.04761905 -0.57142857 1.52380952]
ייעל עבור באמצעות מודל מדויק
כחלופה לחישוב , אתה יכול גם להשתמש ב-setup_exact_model כדי לבנות מופע cvxpy.Problem שמשתמש ב-LSE כאילוצים ושהפתרון האופטימלי שלו ייתן .
בסעיף הבא, יהיה ברור מדוע הממשק הזה קיים.
from qiskit_addon_mpf.costs import setup_exact_problem
model_exact, coeffs_exact = setup_exact_problem(lse)
model_exact.solve()
print(coeffs_exact.value)
[ 0.04761905 -0.57142857 1.52380952]
כאינדיקטור האם MPF שנבנה עם מקדמים אלה ייתן תוצאות טובות, אנחנו יכולים להשתמש בנורמת L1 (ראה גם [1]).
print(
"L1 norm of the exact coefficients:",
np.linalg.norm(coeffs_exact.value, ord=1),
) # ord specifies the norm. ord=1 is for L1
L1 norm of the exact coefficients: 2.1428571428556378
ייעל עבור באמצעות מודל מקורב
יכול לקרות שנורמת ה-L1 עבור קבוצת ערכי שנבחרה נחשבת גבוהה מדי. אם זה המקרה ואתה לא יכול לבחור קבוצה שונה של ערכי , אתה יכול להשתמש בפתרון מקורב ל-LSE במקום בפתרון מדויק.
כדי לעשות זאת, פשוט השתמש ב-setup_approximate_model כדי לבנות מופע cvxpy.Problem שונה, שמגביל את נורמת ה-L1 לסף שנבחר תוך מזעור ההפרש של ו-.
from qiskit_addon_mpf.costs import setup_sum_of_squares_problem
model_approx, coeffs_approx = setup_sum_of_squares_problem(
lse, max_l1_norm=1.5
)
model_approx.solve()
print(coeffs_approx.value)
print(
"L1 norm of the approximate coefficients:",
np.linalg.norm(coeffs_approx.value, ord=1),
)
[-1.10294118e-03 -2.48897059e-01 1.25000000e+00]
L1 norm of the approximate coefficients: 1.5
שים לב שיש לך חופש מלא לגבי כיצד לפתור את בעיית האופטימיזציה הזו, מה שאומר שאתה יכול לשנות את פותר האופטימיזציה, את ספי ההתכנסות שלו, וכן הלאה. בדוק את המדריך המתאים על How to use the approximate model.
מקדמי MPF דינמיים
בסעיף הקודם, הצגנו MPF סטטי שמשפר על קירוב ה-Trotter הסטנדרטי. עם זאת, הגרסה הסטטית הזו אינה בהכרח ממזערת את שגיאת הקירוב. באופן קונקרטי, ה-MPF הסטטי, המסומן , אינו ההיטל האופטימלי של על תת-המרחב הנפרש על ידי מצבי נוסחת-המכפלה .
כדי לטפל בכך, אנו שוקלים MPF דינמי (שהוצג ב-[2] והוכח ניסיונית ב-[3]) שכן ממזער את שגיאת הקירוב בנורמת Frobenius. באופן פורמלי, אנו מתמקדים במזעור
ביחס למקדמים מסוימים בכל זמן . המקרין האופטימלי בנורמת Frobenius הוא אז , ואנו קוראים ל- ה-MPF הדינמי. הצבת ההגדרות למעלה: