התחלה מהירה
המדריך הזה מדגים דוגמה מינימלית עובדת של החבילה qiskit-addon-obp. אנחנו משתמשים ב-operator backpropagation (OBP) כדי להקטין את העומק של מעגל קוונטי על ידי ספיגת שערים אחרונים לתוך ה-observable.
ערך תצפית אינו משתנה אם מקלפים בלוק שערים אחרון מהמעגל ומצמידים אליו את ה-observable באופן קלאסי. OBP מיישם זאת שוב ושוב, ומעריך חלק מהמעגל באופן קלאסי כך שרק מעגל רדוד יותר רץ על החומרה. המחיר הוא שכל שער שנספג יכול להרחיב את ה-observable ליותר איברי Pauli, ולכן צריך לאזן בין העומק שנחסך לבין הגידול ב-observable.
כדי לראות דוגמאות לבניית תהליכי עבודה ריאליסטיים עם הכלי הזה והרצה על חומרה קוונטית, אפשר לעיין במדריכים ב-IBM Quantum Platform (OBP Tutorial).
הכנת קלטים ל-OBP
OBP מקבל כקלט רשימה של פרוסות מעגל ו-observable. הוא מבצע backpropagation לפרוסות אחת אחת, מסוף המעגל אל תוך ה-observable, ומקטין את עומק המעגל במחיר איברי Pauli נוספים ב-observable. כאן אנחנו מייצרים מעגל התפתחות בזמן של מודל Heisenberg עם 10 קיוביטים וחותכים אותו לפרוסות לפי סוג השער.
להלן נצייר את המעגל המקורי, ואחר כך את אותו מעגל משולב מחדש עם barriers שמסמנים את גבולות הפרוסות — כל פרוסה היא יחידה שאפשר לספוג לתוך ה-observable בצעד backpropagation אחד.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_obp": "qiskit-addon-obp", "qiskit_addon_utils": "qiskit-addon-utils"}
_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 numpy as np
from qiskit.quantum_info import SparsePauliOp
from qiskit.synthesis import LieTrotter
from qiskit.transpiler import CouplingMap
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types
# Generate a circuit to reduce
coupling_map = CouplingMap.from_heavy_hex(3, bidirectional=False)
reduced_coupling_map = coupling_map.reduce(
[0, 13, 1, 14, 10, 16, 5, 12, 8, 18]
)
hamiltonian = generate_xyz_hamiltonian(
reduced_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),
)
circuit = generate_time_evolution_circuit(
hamiltonian,
time=0.2,
synthesis=LieTrotter(reps=2),
)
# Slice the circuit and define an observable
slices = slice_by_gate_types(circuit)
observable = SparsePauliOp("IIIIIIIIIZ")
print(f"Original circuit depth: {circuit.depth()}")
print(f"Number of slices: {len(slices)}")
print(f"Observable terms: {len(observable)}")
Original circuit depth: 18
Number of slices: 18
Observable terms: 1
# Recombine the slices with barriers to make the slice boundaries visible
sliced_circuit = combine_slices(slices, include_barriers=True)
print("Original circuit:")
display(circuit.draw("mpl", scale=0.6, fold=-1))
print("Sliced circuit (recombined with barriers for visualization)")
sliced_circuit.draw("mpl", scale=0.6, fold=-1)
Original circuit:

Sliced circuit (recombined with barriers for visualization)

הקטנת עומק המעגל עם OBP
אנחנו קוראים ל-backpropagate כדי לספוג פרוסות לתוך ה-observable. הפונקציה מחזירה את ה-observable המורחב, את פרוסות המעגל שלא בוצע דרכן backpropagation, ומטא-נתונים על התהליך.
אם לא מגבילים, ה-observable יכול לגדול לכיוון איברי Pauli. ה-operator_budget מגביל את הגידול הזה — כאן אנחנו מאפשרים לכל היותר 8 קבוצות qubit-wise commuting, מה שקובע בערך את מספר ה-shots הנדרש להערכת ה-observable על ה-QPU. ה-backpropagation נעצר ברגע שספיגת הפרוסה הבאה תחרוג מהתקציב, וזה בדיוק מה שקורה להלן: רק 7 מתוך 18 הפרוסות נספגות לפני שה-observable ממלא את כל 8 הקבוצות המתחלפות והתהליך נעצר.
כדי להעמיק יותר לתוך המעגל, אפשר להשתמש בארגומנט מילת המפתח truncation_error_budget של backpropagate כדי להשמיט מה-observable איברי Pauli בעלי מקדם קטן תוך כדי גדילתו. זה מרסן את הגידול של ה-observable בתמורה לשגיאה שפרופורציונית לגודל איברי ה-Pauli שקוצצו. שני התקציבים משלימים זה את זה ואפשר להשתמש בהם יחד: operator_budget שולט בגודל ה-observable, ואילו truncation_error_budget שולט בשגיאה שנגרמת מהשמטת איברים.
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.simplify import OperatorBudget
max_qwc_groups = 8
bp_obs, remaining_slices, metadata = backpropagate(
observable,
slices,
operator_budget=OperatorBudget(max_qwc_groups=max_qwc_groups),
)
reduced_circuit = combine_slices(remaining_slices)
num_groups = len(bp_obs.group_commuting(qubit_wise=True))
print(
f"Backpropagated {metadata.num_backpropagated_slices} of {len(slices)} slices."
)
print(
f"Reduced circuit depth: {reduced_circuit.depth()} (was {circuit.depth()})"
)
print(f"Observable grew from {len(observable)} to {len(bp_obs)} Pauli terms.")
print(
f"Filled {num_groups} of {max_qwc_groups} commuting groups, exhausting the budget."
)
Backpropagated 7 of 18 slices.
Reduced circuit depth: 11 (was 18)
Observable grew from 1 to 18 Pauli terms.
Filled 8 of 8 commuting groups, exhausting the budget.
reduced_circuit.draw("mpl", scale=0.6, fold=-1)