דלג לתוכן הראשי
פרויקט זה בשלב בטא. מצאת באג? פתח issue ב-GitHub. יש שאלה או רעיון? שאל ב-Discussions — נשמח לשמוע ממך!
מה איכות התרגום בדף זה?

קיצוץ איברי Pauli במהלך backpropagation

המדריך הזה מסביר איך להגדיר את מנגנון קיצוץ איברי ה-Pauli שמספק המודול qiskit_addon_obp.utils.truncating.

אפשר להשתמש ב-Operator backpropagation ‏(OBP) כדי להקטין את עומק המעגלים הקוונטיים במחיר observable מורכב יותר. כדי לקבל תוצאות משמעותיות מ-OBP, בדרך כלל צריך לקצץ איברים מה-observable כדי למנוע ממנו לגדול יותר מדי. דרך אחת לאפשר backpropagation עמוק יותר לתוך המעגל, תוך מניעת גידול יתר של האופרטור, היא לקצץ איברים בעלי מקדמים קטנים במקום להוסיף אותם לאופרטור. קיצוץ איברים יכול לגרום לכך שיהיו פחות מעגלים קוונטיים להרצה, אבל הוא גורם לשגיאה מסוימת בחישוב ערך התצפית הסופי, פרופורציונית לגודל המקדמים של האיברים שקוצצו.

השיטה backpropagate מקבלת TruncationErrorBudget אופציונלי, שמגדיר את קיצוץ איברי ה-Pauli בעלי המשקל הנמוך לכל observable לאחר ה-backpropagation המוצלח של כל פרוסה. כמות האיברים שמקוצצים תלויה בפרמטרי תצורה שונים שהמשתמש מציין. נכון לעכשיו קיימת רק אסטרטגיית קיצוץ אחת - השיטה truncate_binary_search. בהינתן observable ו_תקציב_ כלשהו, היא תבצע חיפוש בינארי על איברי ה-Pauli והמקדמים בתוך ה-observable כדי למצוא את הסף האופטימלי, כך שסכום המקדמים שקוצצו יהיה מרבי אך נמוך מהתקציב.

הערה: כברירת מחדל נעשה שימוש בנורמת L1 להערכה ולחסימה של שגיאת הקיצוץ; עם זאת, ההגדרה p_norm מאפשרת לציין את נורמת ה-Lp שתשמש. למידע נוסף על השימוש בהגדרה הזו, עיינו במדריך Use different Lp-norms for Pauli term truncation. הדוגמאות הבאות ממחישות דרכים שונות לבנות TruncationErrorBudget באמצעות הפונקציה הנלווית setup_budget.

בניית מעגל לדוגמה​

המדריך הזה משתמש בפרוסות המעגל הבאות:

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "qiskit": "qiskit", "qiskit_addon_obp": "qiskit-addon-obp", "qiskit_addon_utils": "qiskit-addon-utils", "rustworkx": "rustworkx"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import rustworkx.generators
from qiskit.synthesis import LieTrotter
from qiskit_addon_utils.problem_generators import (
PauliOrderStrategy,
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types

# Generate a linear chain of 10 qubits
linear_chain = rustworkx.generators.path_graph(10)

# Use an arbitrary XY model
hamiltonian = generate_xyz_hamiltonian(
linear_chain,
coupling_constants=(0.05, 0.02, 0.0),
ext_magnetic_field=(0.02, 0.08, 0.0),
pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,
)
# Evolve for some time
circuit = generate_time_evolution_circuit(
hamiltonian, synthesis=LieTrotter(reps=3), time=2.0
)
# slice the circuit by gate type
slices = slice_by_gate_types(circuit)

# for visualization purposes, recombine the slices with barriers between them and draw the resulting circuit
combine_slices(slices, include_barriers=True).draw("mpl", fold=50, scale=0.6)

Output of the previous code cell

נתבונן ב-observable פשוט אחד:

from qiskit.quantum_info import SparsePauliOp

obs = SparsePauliOp("IIIIIZIIII")

המקרה הפשוט ביותר: תקציב קיצוץ קבוע לכל פרוסה​

התקציב הזמין לקיצוץ איברי Pauli יכול להשתנות בכל צעד של ה-backpropagation. כדי להבין איך זה עובד, נבחן קודם את המקרה הפשוט ביותר של תקציב קיצוץ קבוע כפי שהמשתמש מציין.

הדרך הישירה ביותר לציין את תקציב הקיצוץ היא באמצעות הארגומנט max_error_per_slice. למעשה, כך נעשה במדריך Reducing depth of circuits with operator backpropagation. הגדרת max_error_per_slice ל-float גורמת להקצאת תקציב השווה לערך הזה לכל פרוסה. בדוגמה שלהלן אנחנו מגדירים את הערך הזה ל-0.001, מה שמבטיח שגיאת קיצוץ משתמעת של לכל היותר 0.018, אם בוצע backpropagation לכל 18 הפרוסות.

שימו לב שכל תקציב שגיאה שנותר לאחר backpropagation של פרוסה וקיצוץ איברים בעלי מקדמים קטנים יתווסף תמיד לתקציב השגיאה של הפרוסה הבאה.

from qiskit_addon_obp.utils.truncating import setup_budget

truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.simplify import OperatorBudget

op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
New observable contains 29 terms and 10 commuting groups.

בהמשך נשתמש במופע OBPMetadata ובכלים שמספק המודול visualization כדי להמחיש את תהליך ה-backpropagation.

  • הגרף השמאלי העליון מראה שיש לנו מספיק תקציב כדי להתחיל לקצץ איברי observable אחרי ה-backpropagation של הפרוסה השלישית. החל מהפרוסה השלישית, אנחנו יודעים שמקצצים לפחות איבר אחד מכל פרוסה שעוברת backpropagation, כי נגרמת לנו שגיאת קיצוץ כלשהי אחרי כל פרוסה.

  • הגרף הימני העליון מראה שתקציב השגיאה גדל ל-.003 בפרוסה השלישית. אנחנו רואים ירידה חדה בתקציב שנותר, כלומר קוצצו איברים מה-observable. זה עולה בקנה אחד עם מה שהסקנו מהגרף השמאלי העליון.

  • הגרף השמאלי התחתון מראה שככל שאנחנו מקצצים איברים מה-observable, השגיאה המצטברת הכוללת שלנו גדלה באופן מונוטוני. הגרף הזה גם מאשר שלא קוצצו איברים עד אחרי ה-backpropagation של הפרוסה השלישית.

  • הגרף הימני התחתון מראה שמספר קבוצות ה-Pauli המתחלפות ב-observable שלנו גדל עד לגבול שהוגדר, 10. הגרף הזה גם מראה איך backpropagation של שכבה אחת נוספת היה גורם ל-observable שלנו לחרוג מהגבול שהוגדר, כפי שאפשר לראות מהצטלבות הקו השחור והאדום.

שימו לב שבכל הגרפים האלה ציר ה-x מונה את הפרוסות שעוברות backpropagation, אבל מכיוון ש-OBP פועל על סוף המעגל, slice 1 היא הפרוסה האחרונה ממש, slice 2 היא זו שלפניה, וכן הלאה.

from matplotlib import pyplot as plt
from qiskit_addon_obp.utils.visualization import (
plot_accumulated_error,
plot_left_over_error_budget,
plot_num_qwc_groups,
plot_slice_errors,
)

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

ציון תקציב פרוסה במפורש​

אם אתם יודעים איך להקצות תקציב לכל פרוסה כך שביצועי ה-backpropagation יהיו אופטימליים, ייתכן שתרצו להקצות תקציב במפורש לכל פרוסה. לצורך ההדגמה, נקצה לשלוש הפרוסות הראשונות תקציב אפס ונשתמש בתקציב של .001 לכל אחת מהפרוסות הנותרות.

# Zero out the first 3 slices' budgets
max_error_per_slice = [0.0] * 3 + [0.001] * (len(slices) - 3)

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
New observable contains 32 terms and 10 commuting groups.

הסרת התקציב משלוש השכבות הראשונות גרמה לכך שלא קוצצו איברים עד אחרי השכבה הרביעית, כפי שאפשר לאשר בשלושה מהגרפים האלה. מה שעשוי להפתיע במקצת הוא שלמרות שלפרוסה הרביעית לא הועבר תקציב עודף, איבר אחד עדיין קוצץ באמצעות התקציב שהוקצה, .001. זה ברור מהגרף השמאלי העליון, אבל אפשר לראות זאת גם בגרף הימני העליון, כי עקומת התקציב שנותר משתטחת בין הפרוסות שלוש וארבע. פרט בולט נוסף הוא שמנקודה זו ואילך קוצץ לפחות איבר אחד, בדיוק כמו בדוגמה הקודמת.

המסקנה העיקרית היא שאף על פי שבדוגמה השנייה נגרמה פחות שגיאת קיצוץ בגלל איפוס התקציבים של חלק מהפרוסות, הצלחנו לבצע backpropagation לאותו מספר פרוסות, וה-observable שלנו מכיל אותו מספר קבוצות Pauli מתחלפות. אפשר לאשר שהחסם על השגיאה שלנו קטן יותר בדוגמה השנייה על ידי עיון בגרף השמאלי התחתון.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

ציון התקציב באופן מחזורי​

אם יש לכם מעגל עם דפוס חוזר כלשהו, כמו מעגל Trotter, ייתכן שתרצו לציין תקציב לתת-קבוצה החוזרת הזו של פרוסות ולהשתמש בתקציב הזה בכל החזרות הבאות של הפרוסות האלה.

ליתר דיוק, למעגל הדוגמה שלנו יש שש פרוסות שחוזרות שלוש פעמים, בסך הכול 18 פרוסות. נקצה באופן שרירותי תקציב אפס לשכבות של קיוביט בודד ול-RYY, ותקציב .003 לכל אחת משכבות ה-RXX. נראה איך ציון התקציב כרצף באורך 6 גורם להחלת התקציב על כל 18 הפרוסות באופן מחזורי.

שוב, שימו לב שה-backpropagation של הפרוסות מתבצע בסדר הפוך (כלומר, מהסוף). לכן הערך הראשון בתקציב המחזורי שלנו משמש למעשה לפרוסה האחרונה, הערך השני לפרוסה שלפניה, וכן הלאה.

# Specify a length-6 per-slice budget.
# This will be cycled over three times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=inf, p_norm=1)
op_budget = OperatorBudget(max_qwc_groups=20)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 13 circuit slices.
New observable contains 49 terms and 14 commuting groups.

כפי שנראה בתמונות השמאלית העליונה והשמאלית התחתונה, לא בוצע קיצוץ בארבע הפרוסות הראשונות מכיוון שלא הוקצה להן תקציב שגיאה. בפרוסות חמש ושש קוצצו חלק מהאיברים כשהתקציב התפנה.

כפי שנראה בתמונה השמאלית העליונה, נגרמה שגיאה גדולה יחסית אחרי ה-backpropagation של פרוסה שבע, אף על פי שלפרוסה הזו לא הוקצה תקציב. זה בגלל שרק כ-.002 מתוך התקציב הכולל של .006 שהוקצה לפרוסות חמש ושש נוצל, ולכן היתרה הועברה לפרוסה שבע ונוצלה ברובה, כפי שנראה בתמונה הימנית העליונה.

התמונות השמאלית העליונה והימנית העליונה מראות שכמות התקציב הקטנה שנותרה מתכלה בין פרוסות 8-10, ותקציב חדש מתפנה בפרוסה 11, כמצופה. התמונות השמאלית העליונה, הימנית העליונה והשמאלית התחתונה כולן מדגימות את ההתנהגות המחזורית של הארגומנט max_error_per_slice כשאורכו קטן ממספר הפרוסות. ההתנהגות המחזורית הזו הייתה נמשכת בכל הפרוסות, אבל תנאי העצירה max_qwc_groups הושג אחרי backpropagation של 13 פרוסות, כפי שנראה בתמונה הימנית התחתונה.

מעניין גם שמספר קבוצות ה-Pauli אכן יורד אחרי שהסבב השני של התקציב מתפנה בפרוסה 11. זה בגלל שהיו קבוצות בעלות מקדמים קטנים שלא ניתן היה לקצץ עד שהיה יותר תקציב אחרי ה-backpropagation של פרוסה 11, ולכן הן הצטברו ב-observable במשך כמה איטרציות. המקרה הזה גם מדגיש ש-max_qwc_groups חייב להיות מועבר כדי שהאלגוריתם יסתיים.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

הגבלת השגיאה הכוללת​

בנוסף לציון תקציב השגיאה לכל פרוסה, אפשר לציין את כמות השגיאה המרבית שתיגרם מקיצוץ. ברגע שמגיעים למגבלה הזו, לא יבוצע עוד קיצוץ; עם זאת, ה-backpropagation ימשיך עד שה-observable יהפוך לגדול מדי ואחד מתנאי העצירה יתקיים.

בהמשך נריץ מחדש את הניסוי שלמעלה, עם מגבלה על max_error_total כך שתקציב השגיאה מתכלה אחרי ה-backpropagation של פרוסה שבע.

# Specify a length-6 per-slice budget.
# This will be cycled over 3 times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice, max_error_total=0.006
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=0.006, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 10 circuit slices.
New observable contains 67 terms and 20 commuting groups.

כמצופה, שגיאת הקיצוץ שלנו מגיעה לתקרה של .006 (תרשים שמאלי תחתון). בולט שבהרצה הזו לא הצלחנו לבצע backpropagation לפרוסה 11. זה בגלל שלא היה לנו מספיק תקציב לקצץ איברים ומספר קבוצות ה-Pauli המתחלפות חרג מהגבול של 20, כפי שנראה בתמונה הימנית התחתונה.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

ייתכן שנרצה פשוט להגביל את התקציב הכולל באמצעות max_error_total בלי לציין max_error_per_slice.

truncation_error_budget = setup_budget(max_error_total=0.018)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.018], max_error_total=0.018, p_norm=1)

הפלט של התא שלמעלה עשוי להפתיע במקצת כי per_slice_budget מוגדר ל-max_error_total. זה מציין שכל התקציב הזמין יצרך באופן חמדני. אפשר לחשוב על זה כך: כל התקציב זמין לכל פרוסה (כי אנחנו עוברים בלולאה על ה-per_slice_budget). אבל כל תקציב שכבר נוצל מנוכה מהתקציב הזמין באותה נקודה באלגוריתם.

op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 9 circuit slices.
New observable contains 25 terms and 9 commuting groups.

התמונה הימנית העליונה מראה איך כל תקציב השגיאה של .018 מועמד לרשות הפרוסה הראשונה. לא מבוצע קיצוץ עד הפרוסה השלישית, ולכן התקציב שנותר נשאר קבוע. התקציב יורד באופן מונוטוני, כי התקציב המלא מועמד לרשות כל פרוסה שעוברת backpropagation עד שהוא מתכלה.

בולט שהניסוי הזה הניב שתי פרוסות פחות שעברו backpropagation בהשוואה לניסוי הראשון במחברת הזו, שכמעט זהה לו. זה מדגים שבבעיות מסוימות חלוקה שווה של התקציב עשויה להיות אופטימלית. בבעיות אחרות, מתן אפשרות לפרוסות לנצל באופן חמדני את התקציב המלא עשוי להניב ביצועים טובים יותר.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

הגבלת מספר הפרוסות שעוברות backpropagation והשגיאה הכוללת יחד​

אם אתם לא רוצים לפזר את תקציב השגיאה לאורך כל המעגל אבל גם לא רוצים לנצל אותו באופן חמדני, אפשר לציין את מספר הפרוסות שבכוונתכם לבצע להן backpropagation (num_slices), יחד עם תקציב שגיאה כולל (max_error_total). כך יחולק תקציב השגיאה באופן אחיד (בהתאם ל-p_norm) בין פרוסות הקלט.

כאן נגביל את מספר הפרוסות שאולי נבצע להן backpropagation ל-12, ונשמור על אותו תקציב שגיאה כולל.

num_slices = 12

truncation_error_budget = setup_budget(
max_error_total=0.018, num_slices=num_slices, p_norm=1
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0014999999999999998], max_error_total=0.018, p_norm=1)

עכשיו ננסה לבצע backpropagation ל-12 הפרוסות שהקצינו להן תקציב בשלב הקודם. נעשה זאת פשוט על ידי העברת 12 הפרוסות האחרונות במעגל שלנו. עם p_norm=1, לכל אחת מ-12 הפרוסות יש תקציב זמין של 0.018 / num_slices = 0.0015.

bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices[-num_slices:],
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)

מכיוון שהעברנו תת-קבוצה מהפרוסות שלנו (בקוד slices[-num_slices:]) ל-backpropagate, אנחנו חייבים לשלב את הפרוסות שנותרו אחרי ה-backpropagation עם הפרוסות שמעולם לא נשלחו ל-backpropagation (בקוד slices[:-num_slices]).

אחרי שאיחדנו את כל הפרוסות הנותרות, אפשר להשתמש ב-combine_slices כדי לייצר את ה-QuantumCircuit בעל העומק המופחת. בהמשך נבדוק כמה פרוסות עברו backpropagation לעומת כמה גדל ה-observable שלנו.

# Recombine the slices remaining after backprop with the rest of the original circuit
reduced_circuit = combine_slices(slices[:-num_slices] + remaining_slices)

print(f"Backpropagated {num_slices - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 12 circuit slices.
New observable contains 29 terms and 9 commuting groups.

הגרפים מראים שהצלחנו לבצע backpropagation לכל 12 הפרוסות, תוך שמירה על ה-observable שלנו מתחת ל-10 קבוצות Pauli מתחלפות. אפשר גם לראות ששימוש ב-num_slices יחד עם max_error_total גורם לחלוקה של התקציב בין הפרוסות, ותקציב שלא נוצל מועבר שוב לפרוסה הבאה. זה בולט במיוחד בגרף למעלה מימין, כי התקציב גם נצרך וגם מתמלא מחדש לאורך ה-backpropagation.

ראוי לציין ששיטה זו של חלוקת השגיאה נתנה את התוצאות הטובות ביותר (יותר פרוסות שעברו backpropagation) בהשוואה לניסוי הראשון במחברת הזו ולדוגמה שמיד לפני זו. בכל הדוגמאות האלה הקצינו תקציב שגיאה של .018, אבל ה-backpropagation התנהג אחרת בהתאם לאופן שבו התקציב חולק.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

עבודה עם כמה observables​

השיטה qiskit_addon_obp.backpropagate מאפשרת להעביר סדרה של observables. זה מפשט את תהליך העבודה כשיש כמה observables מטרה.

אנחנו מציינים את זה במפורש כאן כדי ללמד אותך איך אסטרטגיית הקיצוץ מטפלת במקרה כזה. לצורך הדוגמה הזו, נוסיף observable נוסף לזה שבו השתמשנו עד עכשיו:

obs = [SparsePauliOp("IIIIIZIIII"), SparsePauliOp("IIIIIXIIII")]

לבסוף, נחזור על הניסוי הראשון מהמדריך הזה, אבל עם שני observables שאליהם נבצע backpropagation של המעגל.

בדוגמה הזו, זה לא משפיע על מספר הפרוסות שאפשר היה לבצע להן backpropagation. עם זאת, אפשר לראות ששני ה-observables הובילו למספרים שונים של איברי Pauli ושל קבוצות מתחלפות.

truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"The new first observable contains {len(bp_obs[0])} terms and {len(bp_obs[0].group_commuting(qubit_wise=True))} commuting groups."
)
print(
f"The new second observable contains {len(bp_obs[1])} terms and {len(bp_obs[1].group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
The new first observable contains 29 terms and 10 commuting groups.
The new second observable contains 23 terms and 8 commuting groups.

הגרפים שלהלן עוזרים להמחיש איך אלגוריתם ה-backpropagation מטפל בכמה observables.

ראשית, הגרפים למעלה משמאל, למעלה מימין ולמטה משמאל מראים שתקציב הקיצוץ של האיברים נקבע בנפרד לכל observable. במילים אחרות, לשני ה-observables יש אפשרות לקצץ איברים בהנחה של שגיאה של 0.001 לכל פרוסה שעברה backpropagation. בגלל האופי השונה של ה-observables, התוצאה היא צריכה שונה של התקציב. בדוגמה הזו יש ביניהם הרבה חפיפה, וזה לא תמיד קורה.

הגרף למטה מימין ממחיש ש-max_qwc_groups לוקח בחשבון את כל ה-observables. כלומר, האיברים של כל ה-observables מקובצים יחד כדי להגיע למספר סופי אחד של קבוצות מתחלפות ברמת ה-qubit, והוא מושווה ל-max_qwc_paulis. אותו דבר נעשה עבור הסף max_paulis (שלא נדון במחברת הזו), שמאפשר להגביל את מספר איברי Pauli בכל ה-observables יחד.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell