תחילת העבודה עם נוסחאות מרובות-מכפלה (MPF)
תחילת העבודה עם נוסחאות מרובות-מכפלה (MPF)
Package versions
The code on this page was developed using the following requirements. We recommend using these versions or newer.
qiskit[all]~=2.3.0
qiskit-addon-utils~=0.3.0
qiskit-addon-mpf~=0.3.0
scipy~=1.16.3
מדריך זה מדגים כיצד להשתמש בחבילה qiskit-addon-mpf, תוך שימוש באבולוציה בזמן של מודל איזינג כדוגמה. באמצעות חבילה זו תוכל לבנות נוסחת מרובות-מכפלה (MPF) שמשיגה שגיאת טרוטר נמוכה יותר במדידות של ערכי ציפייה. הכלים המסופקים מאפשרים לך לקבוע את משקולות ה-MPF שנבחר, שאותן ניתן להשתמש בהן כדי לשלב מחדש את ערכי הציפייה המוערכים ממספר Circuit-ים של אבולוציה בזמן, כל אחד עם מספר שונה של צעדי טרוטר.
התחל בהתבוננות בהמילטוניאן של מודל איזינג עם 10 אתרים:
כאשר הוא עוצמת הצימוד ו- הוא עוצמת השדה המגנטי החיצוני. להגדרת הבעיה, האובסרבבל שיימדד יהיה המגנטיזציה הכוללת של המערכת
קטע הקוד שלהלן מכין את ההמילטוניאן של שרשרת האיזינג באמצעות חבילת qiskit-addon-utils, ומגדיר את האובסרבבל שיימדד.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-mpf qiskit-addon-utils scipy
from qiskit.transpiler import CouplingMap
from qiskit.synthesis import SuzukiTrotter
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives import StatevectorEstimator
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_addon_utils.problem_generators import (
generate_xyz_hamiltonian,
generate_time_evolution_circuit,
)
from qiskit_addon_mpf.costs import (
setup_exact_problem,
setup_sum_of_squares_problem,
)
from qiskit_addon_mpf.static import setup_static_lse
from scipy.linalg import expm
import numpy as np
# Generate some coupling map to use for this example
coupling_map = CouplingMap.from_line(10, bidirectional=False)
# Get a qubit operator describing the Ising field model
hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(0.0, 0.0, 1.0),
ext_magnetic_field=(0.4, 0.0, 0.0),
)
print(f"Hamiltonian:\n {hamiltonian}\n")
L = coupling_map.size()
observable = SparsePauliOp.from_sparse_list(
[("Z", [i], 1 / L / 2) for i in range(L)], num_qubits=L
)
print(f"Observable:\n {observable}")
Hamiltonian:
SparsePauliOp(['IIIIIIIZZI', 'IIIIIZZIII', 'IIIZZIIIII', 'IZZIIIIIII', 'IIIIIIIIZZ', 'IIIIIIZZII', 'IIIIZZIIII', 'IIZZIIIIII', 'ZZIIIIIIII', 'IIIIIIIIIX', 'IIIIIIIIXI', 'IIIIIIIXII', 'IIIIIIXIII', 'IIIIIXIIII', 'IIIIXIIIII', 'IIIXIIIIII', 'IIXIIIIIII', 'IXIIIIIIII', 'XIIIIIIIII'],
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, 0.4+0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j,
0.4+0.j, 0.4+0.j, 0.4+0.j])
Observable:
SparsePauliOp(['IIIIIIIIIZ', 'IIIIIIIIZI', 'IIIIIIIZII', 'IIIIIIZIII', 'IIIIIZIIII', 'IIIIZIIIII', 'IIIZIIIIII', 'IIZIIIIIII', 'IZIIIIIIII', 'ZIIIIIIIII'],
coeffs=[0.05+0.j, 0.05+0.j, 0.05+0.j, 0.05+0.j, 0.05+0.j, 0.05+0.j, 0.05+0.j,
0.05+0.j, 0.05+0.j, 0.05+0.j])
כעת תכין את ה-MPF. הבחירה הראשונה היא האם המקדמים יהיו סטטיים (בלתי תלויים בזמן) או דינמיים; מדריך זה משתמש ב-MPF סטטי. הבחירה הבאה היא קבוצת ערכי . זה קובע כמה איברים יהיו ב-MPF, וכן כמה צעדי טרוטר כל איבר משתמש בהם לסימולציה של האבולוציה בזמן. בחירת ערכי היא תהליך היוריסטי, ולכן עליך להשיג סט משלך של ערכי "טובים". קרא הנחיות לאיתור סט ערכים טוב בסוף דף תחילת העבודה.
לאחר מכן, ברגע שנקבעו ערכי , ניתן להכין את מערכת המשוואות לפתרון. המטריצה נקבעת גם היא על ידי נוסחת המכפלה שתשמש. הבחירות כאן הן סדרה, שמוגדרת כ- בדוגמה זו, וכן האם נוסחת המכפלה צריכה להיות סימטרית, שמוגדרת כ-True בדוגמה זו. קטע הקוד שלהלן בוחר זמן כולל לאבולוציה של המערכת, ערכי לשימוש, ואת מערכת המשוואות לפתרון באמצעות המתודה qiskit_addon_mpf.static.setup_static_lse.
time = 8.0
trotter_steps = (8, 12, 19)
lse = setup_static_lse(trotter_steps, order=2, symmetric=True)
print(lse)
LSE(A=array([[1.00000000e+00, 1.00000000e+00, 1.00000000e+00],
[1.56250000e-02, 6.94444444e-03, 2.77008310e-03],
[2.44140625e-04, 4.82253086e-05, 7.67336039e-06]]), b=array([1., 0., 0.]))
לאחר שמערכת המשוואות הליניאריות אותחלה, ניתן לפתור אותה באופן מדויק או באמצעות מודל קירוב המשתמש בסכום ריבועים (או נורמת פרובניוס עבור מקדמים דינמיים; ראה הפנייה ל-API למידע נוסף). הבחירה להשתמש במודל קירוב מתעוררת בדרך כלל כאשר נורמת המקדמים עבור קבוצת ערכי שנבחרה נחשבת גבוהה מדי ולא ניתן לבחור קבוצה שונה של ערכי . מדריך זה מדגים את שניהם להשוואת התוצאות.
model_exact, coeffs_exact = setup_exact_problem(lse)
model_approx, coeffs_approx = setup_sum_of_squares_problem(
lse, max_l1_norm=3.0
)
model_exact.solve()
model_approx.solve()
print(f"Exact solution: {coeffs_exact.value}")
print(f"Approximate solution: {coeffs_approx.value}")
Exact solution: [ 0.17239057 -1.19447005 2.02207947]
Approximate solution: [-0.40454257 0.57553173 0.8290123 ]
לאובייקט LSE יש גם מתודה LSE.solve() שפותרת את מערכת המשוואות באופן מדויק. הסיבה שנעשה שימוש ב-setup_exact_problem() במדריך זה היא להדגים את הממשק שמספקות השיטות הקירוביות האחרות.
הגדרה וביצוע של Circuit-ים של טרוטר
כעת שהמקדמים התקבלו, השלב האחרון הוא ליצור את Circuit-ים של אבולוציה בזמן עבור הסדר וקבוצת הצעדים שנבחרו של ה-MPF. חבילת qiskit-addon-utils יכולה לזרז תהליך זה.
circuits = []
for k in trotter_steps:
circ = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(order=2, reps=k),
time=time,
)
circuits.append(circ)
circuits[0].draw("mpl", fold=-1)
circuits[1].draw("mpl", fold=-1)
circuits[2].draw("mpl", fold=-1)
לאחר שה-Circuit-ים הללו נבנו, תוכל לבצע להם Transpile ולהריץ אותם על QPU. בדוגמה זו נשתמש באחד מהסימולטורים ללא רעש כדי להדגים כיצד שגיאת טרוטר מצטמצמת.
backend = GenericBackendV2(num_qubits=10)
transpiler = generate_preset_pass_manager(
optimization_level=2, backend=backend
)
transpiled_circuits = [transpiler.run(circ) for circ in circuits]
estimator = StatevectorEstimator()
job = estimator.run([(circ, observable) for circ in transpiled_circuits])
result = job.result()
mpf_evs = [res.data.evs for res in result]
print(mpf_evs)
[array(0.23799162), array(0.35754312), array(0.38649906)]
שחזור תוצאות
כעת שה-Circuit-ים הורצו, שחזור התוצאות פשוט למדי. כפי שצוין בדף סקירת MPF, האובסרבבל שלנו משוחזר דרך הסכום המשוקלל
כאשר הם המקדמים שמצאנו ו- הוא האומדן של האובסרבבל עבור כל Circuit. לאחר מכן נוכל להשוות את התוצאות שהתקבלו עם הערך המדויק באמצעות חבילת scipy.linalg.
exp_H = expm(-1j * time * hamiltonian.to_matrix())
initial_state = np.zeros(exp_H.shape[0])
initial_state[0] = 1.0
time_evolved_state = exp_H @ initial_state
exact_obs = (
time_evolved_state.conj() @ observable.to_matrix() @ time_evolved_state
)
# Print out the different observable measurements
print(f"Exact value: {exact_obs.real}")
print(f"PF with 19 steps: {mpf_evs[-1]}")
print(f"MPF using exact solution: {mpf_evs @ coeffs_exact.value}")
print(f"MPF using approximate solution: {mpf_evs @ coeffs_approx.value}")
Exact value: 0.4006024248789992
PF with 19 steps: 0.3864990619977402
MPF using exact solution: 0.3954847855979902
MPF using approximate solution: 0.4299121425348959
כאן ניתן לראות שה-MPF הצליח להפחית את שגיאת טרוטר בהשוואה לזו שהתקבלה עם PF בודד עם . עם זאת, המודל הקירובי הניב ערך ציפייה גרוע יותר מהמודל המדויק. הדבר ממחיש את החשיבות של שימוש בקריטריוני התכנסות מחמירים על המודל הקירובי ואיתור קבוצת "טובה".