הפצת אופרטורים אחורה (OBP) לאומדן ערכי תוחלת
אומדן שימוש: 4 דקות על מעבד Heron r3 (הערה: זהו אומדן בלבד. זמן הריצה שלך עשוי להשתנות.)
תוצאות למידה
לאחר המעבר במדריך זה, המשתמשים צריכים להבין:
- כיצד להשתמש ב-
qiskit-addon-obpכדי להפחית את עומק המעגל הקוונטי במחיר מספר מוגדל של ביצועי מעגלים - כיצד להשתמש ב-
qiskit-addon-utilsלבניית המילטוניאנים XYZ ומעגלי התפתחות הזמן שלהם
דרישות מוקדמות
אנחנו מציעים שהמשתמשים יהיו מכירים עם הנושאים הבאים לפני המעבר במדריך זה:
- שימוש ב-Estimator primitive לחישוב ערכי תוחלת של אובזרבל
רקע
הפצת אופרטורים אחורה היא טכניקה הכוללת ספיגת פעולות מסוף מעגל קוונטי לתוך האובזרבל הנמדד, בדרך כלל מקטינה את העומק של המעגל במחיר איברים נוספים באובזרבל. המטרה היא להפיץ אחורה כמה שיותר מהמעגל מבלי לאפשר לאובזרבל לגדול יותר מדי. יישום מבוסס Qiskit זמין בתוסף OBP Qiskit. קראו את התיעוד המתאים למידע נוסף.
נבחן דוגמת מעגל שעבורו יש למדוד אובזרבל , כאשר הם אופרטורי Pauli ו- הם מקדמים. נסמן את המעגל כאוניטרי בודד שניתן לחלק באופן לוגי ל- כפי שמוצג באיור למטה.

הפצת אופרטורים אחורה סופגת את האוניטרי לתוך האובזרבל על ידי התפתחותו כ-. במילים אחרות, חלק מהחישוב מבוצע קלאסית באמצעות ההתפתחות של האובזרבל מ- ל-. ניתן כעת לנסח מחדש את הבעיה המקורית כמדידת האובזרבל עבור המעגל החדש בעומק נמוך יותר שהאוניטרי שלו הוא .
האוניטרי מיוצג כמספר פרוסות . קיימות דרכים מרובות להגדרת פרוסה. לדוגמה, במעגל הדוגמה לעיל, כל שכבה של וכל שכבה של שערי יכולה להיחשב כפרוסה בודדת. הפצה אחורה כוללת חישוב של באופן קלאסי. כל פרוסה יכולה להיות מיוצגת כ-, כאשר הוא Pauli של -קיוביטים ו- הוא סקלר. קל לוודא ש:
בדוגמה לעיל, אם , אז עלינו להריץ שני מעגלים קוונטיים, במקום אחד, כדי לחשב את ערך התוחלת. לכן, הפצה אחורה עשויה להגדיל את מספר האיברים באובזרבל, מה שמוביל למספר גבוה יותר של ביצועי מעגלים. דרך אחת לאפשר הפצה אחורה עמוקה יותר לתוך המעגל, תוך מניעת האופרטור מלגדול יותר מדי, היא לקטום איברים עם מקדמים קטנים, במקום להוסיף אותם לאופרטור. לדוגמה, בדוגמה לעיל, אפשר לבחור לקטום את האיבר הכולל בתנאי ש- קטן מספיק. קיטום איברים יכול להביא למעגלים קוונטיים פחותים לביצוע, אך פעולה זו גורמת לשגיאה מסוימת בחישוב ערך התוחלת הסופי פרופורציונלית לגודל המקדמים של האיברים הקטומים.
דרישות
לפני תחילת מדריך זה, וודא שהדברים הבאים מותקנים:
- Qiskit SDK v2.0 ואילך, עם תמיכה ב-visualization
- Qiskit Runtime v0.22 ואילך (
pip install qiskit-ibm-runtime) - תוסף OBP Qiskit 0.3 ואילך (
pip install qiskit-addon-obp) - כלי עזר לתוספי Qiskit 0.3 ואילך (
pip install qiskit-addon-utils)
הגדרה
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-obp qiskit-addon-utils qiskit-ibm-runtime rustworkx
import numpy as np
import matplotlib.pyplot as plt
from qiskit.primitives import StatevectorEstimator
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import CouplingMap
from qiskit.synthesis import LieTrotter
from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
)
from qiskit_addon_utils.slicing import slice_by_depth, combine_slices
from qiskit_addon_obp.utils.simplify import OperatorBudget
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.truncating import setup_budget
from rustworkx.visualization import graphviz_draw
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2, EstimatorOptions
דוגמת סימולטור בקנה מידה קטן
מדריך זה מממש תבנית Qiskit לסימולציית הדינמיקה הקוונטית של שרשרת ספין היזנברג באמצעות תוסף OBP Qiskit. שימו לב שבסימולטור ללא רעש, ערך התוחלת שהתקבל עם ובלי הפצה אחורה יהיה זהה.
שלב 1: מיפוי קלטים קלאסיים לבעיה קוונטית
מיפוי ההתפתחות בזמן של מודל היזנברג קוונטי לניסוי קוונטי
ראשית, נשתמש בפונקציה generate_xyz_hamiltonian מ-qiskit-addon-utils ליצירת המילטוניאן דמוי היזנברג על גרף קישוריות נתון. גרף זה יכול להיות או rustworkx.PyGraph או CouplingMap. להלן, נשתמש ב-CouplingMap שרשרת ליניארית של 10 קיוביטים.
num_qubits = 10
layout = [(i - 1, i) for i in range(1, num_qubits)]
# Instantiate a CouplingMap object
coupling_map = CouplingMap(layout)
graphviz_draw(coupling_map.graph, method="circo")
לאחר מכן, אנחנו יוצרים אופרטור Pauli המדגמן המילטוניאן XYZ היזנברג:
כאשר הוא הגרף של מפת הקישוריות. עבור מדריך זה, השתמשנו ב- בערכים בהתאמה, ו- בערכים בהתאמה.
# Get a qubit operator describing the Heisenberg XYZ model
hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(np.pi / 8, np.pi / 4, np.pi / 2),
ext_magnetic_field=(np.pi / 3, np.pi / 6, np.pi / 9),
)
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', 'IIIIIIIIIX', 'IIIIIIIIIY', 'IIIIIIIIIZ', 'IIIIIIIIXI', 'IIIIIIIIYI', 'IIIIIIIIZI', 'IIIIIIIXII', 'IIIIIIIYII', 'IIIIIIIZII', 'IIIIIIXIII', 'IIIIIIYIII', 'IIIIIIZIII', 'IIIIIXIIII', 'IIIIIYIIII', 'IIIIIZIIII', 'IIIIXIIIII', 'IIIIYIIIII', 'IIIIZIIIII', 'IIIXIIIIII', 'IIIYIIIIII', 'IIIZIIIIII', 'IIXIIIIIII', 'IIYIIIIIII', 'IIZIIIIIII', 'IXIIIIIIII', 'IYIIIIIIII', 'IZIIIIIIII', 'XIIIIIIIII', 'YIIIIIIIII', 'ZIIIIIIIII'],
coeffs=[0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j,
0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j,
1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j,
0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j,
0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j,
1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j,
0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 1.04719755+0.j,
0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,
0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j,
1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j,
0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,
0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j,
1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j,
0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,
0.34906585+0.j])
מאופרטור הקיוביטים, אנחנו יכולים ליצור מעגל קוונטי המדגמן את התפתחותו בזמן. השתמשנו ב-generate_time_evolution_circuit עם פירוק Lie Trotter לבניית מעגל התפתחות הזמן.
circuit = generate_time_evolution_circuit(
hamiltonian,
time=0.2,
synthesis=LieTrotter(reps=2),
)
circuit.draw("mpl", style="iqp", fold=-1)

שלב 2: אופטימיזציה של הבעיה לביצוע על חומרה קוונטית
יצירת פרוסות מעגל להפצה אחורה
הפונקציה backpropagate מפיצה אחורה פרוסות מעגל שלמות בכל פעם. לכן, הבחירה כיצד לפרוס יכולה להשפיע על מידת הביצועים של ההפצה האחורה עבור בעיה נתונה. כאן, נקבץ שערים מאותו סוג לפרוסות באמצעות פונקציית slice_by_depth.
לדיון מפורט יותר על חיתוך מעגלים, בדוק את מדריך הוראות זה של חבילת qiskit-addon-utils.
slices = slice_by_depth(circuit, max_slice_depth=1)
print(f"Separated the circuit into {len(slices)} slices.")
Separated the circuit into 18 slices.
הגבלת עד כמה האופרטור יכול לגדול במהלך הפצה אחורה
במהלך הפצה אחורה, מספר האיברים באופרטור יתקרב בדרך כלל ל- במהירות, כאשר הוא מספר הפרוסות. כאשר שני איברים באופרטור אינם קומוטטיביים מבחינת קיוביטים, אנחנו זקוקים למעגלים נפרדים כדי לקבל את ערכי התוחלת המתאימים להם. לדוגמה, אם יש לנו אובזרבל של שני קיוביטים , אז מכיוון ש-, מדידה בבסיס בודד מספיקה לחישוב ערכי התוחלת עבור שני איברים אלה. עם זאת, אנטי-קומוטטיבי עם שני האיברים האחרים, לכן אנחנו זקוקים למדידת בסיס נפרדת כדי לחשב את ערך התוחלת של . במילים אחרות, אנחנו זקוקים לשני מעגלים במקום אחד כדי לחשב . ככל שמספר האיברים באופרטור גדל, יש אפשרות שמספר ביצועי המעגלים הנדרשים גם גדל.
ניתן להגביל את גודל האופרטור על ידי ציון הארגומנט operator_budget של הפונקציה backpropagate, המקבל מופע OperatorBudget.
כדי לשלוט בכמות המשאבים הנוספים (מספר ביצועי מעגלים, ולפיכך זמן QPU הנדרש) המוקצים, אנחנו מגבילים את המספר המרבי של קבוצות Pauli קומוטטיביות מבחינת קיוביטים שהאובזרבל המופץ אחורה רשאי להחזיק. כאן אנחנו מציינים שההפצה האחורה צריכה להיפסק כאשר מספר קבוצות Pauli הקומוטטיביות מבחינת קיוביטים באופרטור גדל מעבר לשמונה.
op_budget = OperatorBudget(max_qwc_groups=8)
הפצת פרוסות אחורה מהמעגל
ראשית נציין את האובזרבל כ-, כאשר הוא מספר הקיוביטים. נפיץ אחורה פרוסות ממעגל התפתחות הזמן עד שהאיברים באובזרבל לא יכולים עוד להיות משולבים לשמונה או פחות קבוצות Pauli קומוטטיביות מבחינת קיוביטים.
observable = SparsePauliOp.from_sparse_list(
[("Z", [i], 1 / num_qubits) for i in range(num_qubits)],
num_qubits=num_qubits,
)
observable
SparsePauliOp(['IIIIIIIIIZ', 'IIIIIIIIZI', 'IIIIIIIZII', 'IIIIIIZIII', 'IIIIIZIIII', 'IIIIZIIIII', 'IIIZIIIIII', 'IIZIIIIIII', 'IZIIIIIIII', 'ZIIIIIIIII'],
coeffs=[0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j,
0.1+0.j, 0.1+0.j])
להלן תראה שהפצנו אחורה שש פרוסות, והאיברים שולבו לשש ולא שמונה קבוצות. זה מרמז שהפצת פרוסה נוספת אחורה תגרום למספר קבוצות Pauli לחרוג משמונה. אנחנו יכולים לאמת שזה המקרה על ידי בדיקת המטא-דאטה המוחזרת. שים לב גם שבחלק זה טרנספורמציית המעגל מדויקת. כלומר, אף אחד מהאיברים של האובזרבל החדש לא נקטם. המעגל המופץ אחורה והאופרטור המופץ אחורה נותנים את התוצאה המדויקת כמו המעגל והאופרטור המקוריים.
# Backpropagate slices onto the observable
bp_obs, remaining_slices, metadata = backpropagate(
observable, slices, operator_budget=op_budget
)
# Recombine the slices remaining after backpropagation
bp_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {metadata.num_backpropagated_slices} slices.")
print(
f"New observable has {len(bp_obs.paulis)} terms, which can be combined into "
f"{len(bp_obs.group_commuting(qubit_wise=True))} groups."
)
print(
f"Note that backpropagating one more slice would result in "
f"{metadata.backpropagation_history[-1].num_paulis[0]} terms "
f"across {metadata.backpropagation_history[-1].num_qwc_groups} groups."
)
print("The remaining circuit after backpropagation looks as follows:")
bp_circuit.draw("mpl", fold=-1, scale=0.6)
Backpropagated 6 slices.
New observable has 60 terms, which can be combined into 6 groups.
Note that backpropagating one more slice would result in 114 terms across 12 groups.
The remaining circuit after backpropagation looks as follows:
עבור הדוגמה בקנה מידה קטן על סימולטור, לא נשתמש בקיטום. הסיבה היא שבהיעדר רעש, המעגל עם ובלי הפצה אחורה מביא לאותה תוצאה, וקיטום מחמיר את התוצאה בשל ההקרבה הנוספת.
טרנספילציה של המעגלים לסט שערי הבסיס
כעת אנחנו מבצעים טרנספילציה הן של המעגל המקורי והן של המעגל המופץ אחורה לסט שערי הבסיס של ה-backend. אין צורך לבצע טרנספילציה על ה-backend האמיתי מכיוון שאנחנו הולכים להריץ על סימולטור עבור המופע הקטן.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
)
print(backend)
<IBMBackend('ibm_kingston')>
pm_basis = generate_preset_pass_manager(
optimization_level=3, basis_gates=backend.configuration().basis_gates
)
isa_circuit = pm_basis.run(circuit)
isa_bp_circuit = pm_basis.run(bp_circuit)
שלב 3: ביצוע באמצעות primitives של Qiskit
ראשית, אנחנו יוצרים שני Primitive Unified Blocs (PUBs) המתאימים למעגל המקורי ולמעגל המופץ אחורה. לאחר מכן אנחנו מבצעים את ה-PUBs על Estimator אידיאלי כדי לקבל את ערכי התוחלת.
pubs = [(isa_circuit, observable), (isa_bp_circuit, bp_obs)]
rng = np.random.default_rng()
estimator = StatevectorEstimator(seed=rng)
job = estimator.run(pubs)
שלב 4: עיבוד לאחר מכן והחזרת תוצאה לפורמט קלאסי רצוי
כעת אנחנו מקבלים את ערכי התוחלת של המעגלים המקורי והמופץ אחורה.
primitive_result = job.result()
circuit_expval = primitive_result[0].data.evs.item()
bp_circuit_expval = primitive_result[1].data.evs.item()
methods = [
"No backpropagation",
"Backpropagation",
]
values = [circuit_expval, bp_circuit_expval]
ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
ax.set_ylim([0.6, 0.92])
ax.set_ylabel(r"$M_Z$", fontsize=12)
Text(0, 0.5, '$M_Z$')
כצפוי, שני ערכי התוחלת מסכימים. מכיוון שאנחנו מריצים על סימולטור statevector ללא רעש, הפצה אחורה היא טרנספורמציה מדויקת של זוג המעגל-אובזרבל, ולכן זרימות העבודה המקוריות והמופצות אחורה חייבות לייצר את אותה ערך של . היתרון של הפצה אחורה מתגלה רק על חומרה רועשת, שם המעגל המופץ אחורה הקצר יותר צובר פחות שגיאה, כפי שמוצג בדוגמת החומרה בקנה מידה גדול להלן.
דוגמת חומרה בקנה מידה גדול
בעת פיתוח ניסוי, שימושי להתחיל עם מעגל קטן להקל על ויזואליזציות וסימולציות. כעת אנחנו בוחנים הפצת אופרטורים אחורה עבור המילטוניאן היזנברג בן 50 קיוביטים עם אותו סט ערכים עבור הפרמטרים ו- ואותו אובזרבל , אך עבור ארבעה צעדי Trotter. ערך התוחלת האידיאלי בקנה מידה זה לא ניתן לחישוב בשיטת כוח גס, ולכן אנחנו משתמשים ברשת טנזור ומקבלים את ערך התוחלת האידיאלי להיות .
יחד עם הפצה אחורה, בדוגמה בקנה מידה גדול זו, אנחנו גם מציגים הפצה אחורה עם קיטום. באופן אידיאלי אנחנו רוצים להפיץ אחורה כמה שיותר כדי להפחית את עומק המעגל האפקטיבי. עם זאת, לעיתים קרובות זה מוביל למספר גדול של איברים לא-קומוטטיביים באובזרבל המעודכן, מה שמגדיל את עלות הקוונטום. לכן, אנחנו יכולים להסיר איברי אובזרבל עם מקדמים קטנים באמצעות טכניקה הנקראת קיטום. בעוד שקיטום מאפשר יותר הפצה על ידי הפחתת מספר האיברים באובזרבל המעודכן, הוא גם מכניס שגיאת קירוב מסוימת. לכן, יש צורך להגביל את הקיטום בתוך גבולות מסוימים כך שגיאת ההקרבה לא תגבר על הפחתת הרעש שהתקבלה מהפצה אחורה עמוקה יותר.
כדי להגביל את כמות הקיטום, אנחנו מקצים תקציב שגיאה לכל פרוסה וגם את תקציב השגיאה הכולל על פני כל המעגל המופץ אחורה באמצעות הפונקציה setup_budget. זה מבטיח שהקיטום נשלט עבור כל פרוסה וגם עבור המעגל כולו. ראו גם מדריך זה לדרכים אחרות של הקצאת התקציב.
num_qubits = 50
layout = [(i - 1, i) for i in range(1, num_qubits)]
# Instantiate a CouplingMap object
coupling_map = CouplingMap(layout)
hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(np.pi / 8, np.pi / 4, np.pi / 2),
ext_magnetic_field=(np.pi / 3, np.pi / 6, np.pi / 9),
)
# Generate a time evolution circuit for the Hamiltonian
circuit = generate_time_evolution_circuit(
hamiltonian,
time=0.2,
synthesis=LieTrotter(reps=4),
)
# Define the observable to measure
observable = SparsePauliOp.from_sparse_list(
[("Z", [i], 1 / num_qubits) for i in range(num_qubits)],
num_qubits,
)
slices = slice_by_depth(circuit, max_slice_depth=1)
# Define the maximum number of qwc groups allowed in the
# backpropagated observable,
# and the truncation error budget
op_budget = OperatorBudget(max_qwc_groups=15)
truncation_error_budget = setup_budget(
max_error_total=0.03, max_error_per_slice=0.005
)
# First backpropagation without truncation
bp_obs, remaining_slices, metadata = backpropagate(
observable, slices, operator_budget=op_budget
)
bp_circuit = combine_slices(remaining_slices)
# Now backpropagate with truncation, using the same operator budget and
# the defined truncation error budget
bp_obs_trunc, remaining_slices_trunc, metadata = backpropagate(
observable,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
bp_circuit_trunc = combine_slices(
remaining_slices_trunc, include_barriers=False
)
# Now we transpile the original circuit and the two backpropagated circuits,
# and apply the layout to the corresponding observables
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
isa_circuit = pm.run(circuit)
isa_bp_circuit = pm.run(bp_circuit)
isa_bp_circuit_trunc = pm.run(bp_circuit_trunc)
isa_observable = observable.apply_layout(isa_circuit.layout)
isa_bp_observable = bp_obs.apply_layout(isa_bp_circuit.layout)
isa_bp_observable_trunc = bp_obs_trunc.apply_layout(
isa_bp_circuit_trunc.layout
)
# Compare the 2-qubit depth of each transpiled circuit to see how much
# depth backpropagation saved
print(
f"2-qubit depth without backpropagation: "
f"{isa_circuit.depth(lambda x: x.operation.num_qubits == 2)}"
)
print(
f"2-qubit depth with backpropagation: "
f"{isa_bp_circuit.depth(lambda x: x.operation.num_qubits == 2)}"
)
print(
f"2-qubit depth with backpropagation and truncation: "
f"{isa_bp_circuit_trunc.depth(lambda x: x.operation.num_qubits == 2)}"
)
pubs = [
(isa_circuit, isa_observable),
(isa_bp_circuit, isa_bp_observable),
(isa_bp_circuit_trunc, isa_bp_observable_trunc),
]
# Now we instantiate the Estimator primitive for the hardware with
# ZNE and measurement error
# mitigation and compute the three circuits and observables
options = EstimatorOptions()
options.default_precision = 0.01
options.resilience_level = 2
options.resilience.zne.noise_factors = [1, 1.2, 1.4]
options.resilience.zne.extrapolator = ["linear"]
estimator = EstimatorV2(mode=backend, options=options)
estimator.options.environment.job_tags = ["TUT_OBP"]
job = estimator.run(pubs)
# Retrieve the results and the standard deviations
result_no_bp = job.result()[0].data.evs.item()
result_bp = job.result()[1].data.evs.item()
result_bp_trunc = job.result()[2].data.evs.item()
std_no_bp = job.result()[0].data.stds.item()
std_bp = job.result()[1].data.stds.item()
std_bp_trunc = job.result()[2].data.stds.item()
2-qubit depth without backpropagation: 24
2-qubit depth with backpropagation: 20
2-qubit depth with backpropagation and truncation: 18
print(f"Expectation value without backpropagation: {result_no_bp}")
print(f"Backpropagated expectation value: {result_bp}")
print(f"Backpropagated expectation value with truncation: {result_bp_trunc}")
Expectation value without backpropagation: 0.9543907942381811
Backpropagated expectation value: 0.9445337385406468
Backpropagated expectation value with truncation: 0.934050286970965
# Plot the results
methods = [
"No backpropagation",
"Backpropagation",
"Backpropagation w/ truncation",
]
values = [result_no_bp, result_bp, result_bp_trunc]
error_bars = [std_no_bp, std_bp, std_bp_trunc]
ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.errorbar(methods, values, yerr=error_bars, fmt="o", color="r", capsize=5)
plt.axhline(0.89)
ax.set_ylim([0.8, 0.98])
plt.text(0.25, 0.895, "Exact result")
ax.set_ylabel(r"$M_Z$", fontsize=12)
Text(0, 0.5, '$M_Z$')
צעדים הבאים
אם מצאת עבודה זו מעניינת, ייתכן שיעניינו אותך החומרים הבאים:
- קירוב קומפילציה קוונטית למעגלי התפתחות זמן
- נוסחאות רב-מוצרים להפחתת שגיאת Trotter
pauli-prop, חבילה מואצת ב-Rust להפצת Pauli, עם הדרכות המכסות OBP, הערכת ערך-ציפייה קלאסית, וסימולציה רועשת