ตัดทอนเทอม Pauli ระหว่าง backpropagation
คู่มือนี้อธิบายวิธีกำหนดค่ากลไกการตัดทอนเทอม Pauli ที่จัดเตรียมโดยโมดูล qiskit_addon_obp.utils.truncating
Operator backpropagation (OBP) ใช้ลดความลึกของวงจรควอนตัมโดยแลกกับ observable ที่ซับซ้อนขึ้น เพื่อให้ได้ผลลัพธ์ที่มีความหมายจาก OBP โดยปกติต้องตัดทอนเทอมออกจาก observable เพื่อไม่ให้มันเติบโตใหญ่เกินไป วิธีหนึ่งที่ช่วยให้ backpropagate ลึกเข้าไปในวงจรได้มากขึ้น โดยไม่ให้ operator ใหญ่เกินไป คือการตัดทอนเทอมที่มีสัมประสิทธิ์เล็กแทนที่จะเพิ่มเข้าไปใน operator การตัดทอนเทอมอาจทำให้ต้องรันวงจรควอนตัมน้อยลง แต่ก็ทำให้เกิดความคลาดเคลื่อนในการคำนวณค่าคาดหมายสุดท้าย ซึ่งเป็นสัดส่วนกับขนาดของสัมประสิทธิ์ของเทอมที่ถูกตัดทอน
เมธอด backpropagate รับ TruncationErrorBudget แบบไม่บังคับ ซึ่งกำหนดค่าการตัดทอนเทอม Pauli ที่มีน้ำหนักต่ำของแต่ละ observable หลังจาก backpropagate แต่ละ slice สำเร็จ จำนวนเทอมที่ถูกตัดทอนขึ้นอยู่กับพารามิเตอร์การกำหนดค่าต่าง ๆ ที่ผู้ใช้ระบุ ในปัจจุบันมีกลยุทธ์การตัดทอนเพียงแบบเดียว - เมธอด truncate_binary_search เมื่อได้รับ observable และ งบประมาณ บางค่า มันจะทำการค้นหาแบบทวิภาคเหนือเทอม Pauli และสัมประสิทธิ์ภายใน observable นั้นเพื่อหาเกณฑ์ที่เหมาะสมที่สุด โดยให้ผลรวมของสัมประสิทธิ์ที่ถูกตัดทอนมีค่ามากที่สุดแต่ต่ำกว่างบประมาณ
หมายเหตุ: โดยค่าเริ่มต้น จะใช้ L1 norm ในการประเมินและจำกัดขอบเขตความคลาดเคลื่อนจากการตัดทอน อย่างไรก็ตาม การตั้งค่า p_norm ช่วยให้ระบุ Lp-norm ที่จะใช้ได้
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้การตั้งค่านั้น ดูคู่มือ Use different Lp-norms for Pauli term truncation
ตัวอย่างต่อไปนี้แสดงวิธีต่าง ๆ ในการสร้าง TruncationErrorBudget โดยใช้ฟังก์ชัน setup_budget ที่มาคู่กัน
สร้างวงจรตัวอย่าง
คู่มือนี้ใช้ circuit slice ต่อไปนี้:
# 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)

เราจะดู observable ง่าย ๆ ตัวเดียว:
from qiskit.quantum_info import SparsePauliOp
obs = SparsePauliOp("IIIIIZIIII")
กรณีที่ง่ายที่สุด: งบประมาณการตัดทอนคงที่สำหรับแต่ละ slice
งบประมาณที่ใช้ได้สำหรับการตัดทอนเทอม Pauli สามารถแตกต่างกันในแต่ละขั้นตอนของ backpropagation เพื่อทำความเข้าใจว่าสิ่งนี้ทำงานอย่างไร เราดูกรณีที่ง่ายที่สุดก่อน คืองบประมาณการตัดทอนคงที่ตามที่ผู้ใช้ระบุ
วิธีที่ตรงไปตรงมาที่สุดในการระบุงบประมาณการตัดทอนคือการใช้อาร์กิวเมนต์ max_error_per_slice
อันที่จริง นี่คือสิ่งที่ทำในบทเรียน Reducing depth of circuits with operator backpropagation การตั้งค่า max_error_per_slice เป็น float ทำให้แต่ละ slice ได้รับงบประมาณเท่ากับค่านั้น
ในตัวอย่างด้านล่าง เราตั้งค่านี้เป็น 0.001 ซึ่งรับประกันความคลาดเคลื่อนจากการตัดทอนโดยนัยไม่เกิน 0.018 หากทำ backpropagate ครบทั้ง 18 slice
สังเกตว่างบประมาณความคลาดเคลื่อนที่เหลือหลังจาก backpropagate slice หนึ่งและตัดทอนเทอมที่มีสัมประสิทธิ์เล็กแล้ว จะถูกเพิ่มเข้าไปในงบประมาณความคลาดเคลื่อนของ slice ถัดไปเสมอ
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 หลังจาก backpropagate slice ที่สาม ตั้งแต่ slice ที่สามเป็นต้นไป เรารู้ว่าเราตัดทอนอย่างน้อยหนึ่งเทอมจากแต่ละ slice ที่ backpropagate เพราะเกิดความคลาดเคลื่อนจากการตัดทอนหลังจากแต่ละ slice
-
กราฟบนขวา แสดงว่างบประมาณความคลาดเคลื่อนเพิ่มขึ้นเป็น
.003สำหรับ slice ที่สาม เราเห็นงบประมาณที่เหลือลดลงอย่างรวดเร็ว ซึ่งหมายความว่ามีเทอมถูกตัดทอนจาก observable สอดคล้องกับที่เราอนุมานได้จากกราฟบนซ้าย -
กราฟล่างซ้าย แสดงว่าเมื่อเราตัดทอนเทอมจาก observable ความคลาดเคลื่อนสะสมโดยรวมของเรา เพิ่มขึ้นอย่างต่อเนื่อง กราฟนี้ยังยืนยันด้วยว่าไม่มีเทอมใดถูกตัดทอนจนกระทั่งหลังจาก backpropagate slice ที่สามแล้ว
-
กราฟล่างขวา แสดงว่าจำนวนกลุ่ม Pauli ที่ commute กันใน observable ของเราเพิ่มขึ้นถึงขีดจำกัดที่ระบุคือ
10กราฟนี้ยังแสดงด้วยว่าการ backpropagate อีกหนึ่งเลเยอร์จะทำให้ observable ของเราเติบโตเกินขอบเขตที่ระบุ ดังที่เห็นได้จากจุดตัดกันของเส้นสีดำและสีแดง
สังเกตว่าในกราฟเหล่านี้ทั้งหมด แกน x แจงนับ slice ที่ถูก backpropagate แต่เนื่องจาก OBP ทำงานที่ปลายวงจร slice 1 จึงเป็น slice สุดท้ายจริง ๆ slice 2 คือ slice ก่อนหน้านั้น และเป็นเช่นนี้ต่อไป
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)])

ระบุงบประมาณของแต่ละ slice อย่างชัดเจน
หากคุณรู้วิธีกำหนดงบประมาณให้แต่ละ slice เพื่อให้ประสิทธิภาพของ backpropagation เหมาะสมที่สุด คุณอาจต้องการกำหนดงบประมาณให้แต่ละ slice อย่างชัดเจน เพื่อการสาธิต เราจะกำหนดงบประมาณศูนย์ให้สามslice แรก และใช้งบประมาณ .001 ต่อ slice สำหรับ slice ที่เหลือ
# 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.
การเอางบประมาณออกจากสามเลเยอร์แรกส่งผลให้ไม่มีเทอมถูกตัดทอนจนกระทั่งหลังเลเยอร์ที่สี่ ซึ่งยืนยันได้จากกราฟสามกราฟเหล่านี้ สิ่งที่อาจน่าแปลกใจเล็กน้อยคือแม้ slice ที่สี่จะไม่มีงบประมาณเหลือส่งต่อมาให้ แต่ก็ยังมีเทอมหนึ่งถูกตัดทอนโดยใช้งบประมาณ .001 ที่จัดสรรให้ ซึ่งเห็นได้ชัดจากกราฟบนซ้าย แต่ก็สังเกตได้จากกราฟบนขวา เช่นกัน เพราะเส้นโค้งงบประมาณที่เหลือแบนราบระหว่าง slice ที่สามและสี่ อีกรายละเอียดที่น่าสังเกตคือ ตั้งแต่จุดนี้เป็นต้นไปมีอย่างน้อยหนึ่งเทอมถูกตัดทอน เหมือนกับตัวอย่างก่อนหน้า
ข้อสรุปสำคัญคือ แม้เราจะเกิดความคลาดเคลื่อนจากการตัดทอนน้อยกว่าในตัวอย่างที่สองเนื่องจากการทำให้งบประมาณของบาง slice เป็นศูนย์ แต่เรายัง backpropagate ได้จำนวน slice เท่าเดิม และ observable ของเรามีจำนวนกลุ่ม Pauli ที่ commute กันเท่าเดิม เรายืนยันได้ว่าขอบเขตความคลาดเคลื่อนของเราในตัวอย่างที่สองเล็กกว่า โดยดูจากกราฟล่างซ้าย
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)])

ระบุงบประมาณแบบวนซ้ำ
หากคุณมีวงจรที่มีรูปแบบซ้ำได้ เช่น วงจร Trotter คุณอาจต้องการระบุงบประมาณสำหรับชุดย่อยของ slice ที่ซ้ำนั้น และให้ใช้งบประมาณนั้นกับการทำซ้ำของ slice เหล่านั้นทั้งหมดที่ตามมา
โดยเฉพาะ วงจรตัวอย่างที่เราใช้มีหกslice ที่ซ้ำสามครั้ง รวมเป็น 18 slice เราจะกำหนดงบประมาณศูนย์ให้เลเยอร์ qubit เดี่ยวและเลเยอร์ RYY โดยไม่มีเกณฑ์ที่แน่ชัด และกำหนดงบประมาณ .003 ให้แต่ละเลเยอร์ RXX เราจะดูว่าการระบุงบประมาณเป็นลำดับความยาว 6 ทำให้งบประมาณถูกนำไปใช้กับทั้ง 18 slice แบบวนซ้ำอย่างไร
สังเกตอีกครั้งว่า slice ถูก backpropagate ในลำดับย้อนกลับ (คือเริ่มจากท้ายสุด) ดังนั้นรายการแรกในงบประมาณแบบวนซ้ำของเราจึงถูกใช้กับ slice สุดท้าย รายการที่สองใช้กับ slice ก่อนหน้านั้น และเป็นเช่นนี้ต่อไป
# 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.
ดังที่เห็นในภาพบนซ้ายและล่างซ้าย ไม่มีการตัดทอนในสี่ slice แรก เพราะไม่ได้รับการจัดสรรงบประมาณความคลาดเคลื่อน slice ที่ห้าและหกมีบางเทอมถูกตัดทอนเมื่อมีงบประมาณพร้อมใช้
ดังที่เห็นในภาพบนซ้าย เกิดความคลาดเคลื่อนค่อนข้างมากหลังจาก backpropagate slice ที่เจ็ด แม้ slice นั้นจะไม่ได้รับการจัดสรรงบประมาณ เพราะมีการใช้เพียงราว .002 จากงบประมาณรวม .006 ที่จัดสรรให้ slice ที่ห้าและหก ส่วนที่เหลือจึงถูกส่งต่อไปยัง slice ที่เจ็ดและถูกใช้ไปเกือบหมด ดังที่เห็นในภาพบนขวา
ภาพบนซ้ายและบนขวาแสดงว่างบประมาณที่เหลือเล็กน้อยถูกใช้หมดระหว่าง slice ที่ 8-10 และงบประมาณใหม่พร้อมใช้ใน slice ที่ 11 ตามที่คาดไว้ ภาพบนซ้าย บนขวา และล่างซ้ายทั้งหมดแสดงพฤติกรรมแบบวนซ้ำของอาร์กิวเมนต์ max_error_per_slice เมื่อความยาวน้อยกว่าจำนวน slice พฤติกรรมแบบวนซ้ำนี้จะดำเนินต่อไปจนครบทุก slice แต่เกณฑ์หยุด max_qwc_groups ถูกถึงหลังจาก backpropagate 13 slice ดังที่เห็นในภาพล่างขวา
อีกสิ่งที่น่าสนใจคือจำนวนกลุ่ม Pauli ลดลงจริงหลังจากงบประมาณรอบที่สองพร้อมใช้ที่ slice ที่ 11 เพราะมีกลุ่มที่มีสัมประสิทธิ์เล็กซึ่งไม่สามารถตัดทอนได้จนกว่าจะมีงบประมาณเพิ่มหลังจาก backpropagate slice ที่ 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)])

จำกัดความคลาดเคลื่อนรวม
นอกจากการระบุงบประมาณความคลาดเคลื่อนต่อ slice แล้ว คุณยังระบุปริมาณความคลาดเคลื่อนสูงสุดที่จะเกิดจากการตัดทอนได้ เมื่อถึงขีดจำกัดนั้น จะไม่มีการตัดทอนอีก อย่างไรก็ตาม backpropagation จะดำเนินต่อไปจนกว่า observable จะใหญ่เกินไปและเข้าเกณฑ์หยุดข้อใดข้อหนึ่ง
ถัดไป เรารันการทดลองข้างต้นอีกครั้ง โดยตั้งค่าขีดจำกัดที่ max_error_total เพื่อให้งบประมาณความคลาดเคลื่อนถูกใช้หมดหลังจาก backpropagate slice ที่เจ็ด
# 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 (แผนภูมิล่างซ้าย) ที่น่าสังเกตคือในการรันนี้เราไม่สามารถ backpropagate slice ที่ 11 ได้ เพราะเรามีงบประมาณไม่พอที่จะตัดทอนเทอม และจำนวนกลุ่ม Pauli ที่ commute กันเพิ่มขึ้นเกินขีดจำกัด 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)])

อาจเป็นที่ต้องการที่จะจำกัดงบประมาณโดยรวมด้วย 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 ซึ่งบ่งบอกว่างบประมาณที่มีทั้งหมดจะถูกใช้แบบโลภ
คุณคิดได้ในแง่นี้: งบประมาณทั้งหมดพร้อมใช้สำหรับแต่ละ slice (เพราะเราวนซ้ำเหนือ 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 ทั้งหมดถูกทำให้พร้อมใช้สำหรับ slice แรกอย่างไร ไม่มีการตัดทอนจนถึง slice ที่สาม ดังนั้นงบประมาณที่เหลือจึงคงที่ งบประมาณลดลงอย่างต่อเนื่อง เพราะงบประมาณเต็มจำนวนพร้อมใช้สำหรับแต่ละ slice ที่ถูก backpropagate จนกว่าจะถูกใช้หมด
ที่น่าสังเกตคือการทดลองนี้ได้ slice ที่ backpropagate น้อยกว่าการทดลองแรกใน notebook นี้สองslice ทั้งที่แทบเหมือนกัน สิ่งนี้แสดงให้เห็นว่าสำหรับบางปัญหา การกระจายงบประมาณอย่างเท่าเทียมอาจเหมาะสมที่สุด ส่วนบางปัญหา การปล่อยให้ slice ใช้งบประมาณเต็มจำนวนแบบโลภอาจให้ประสิทธิภาพดีกว่า
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)])

จำกัดจำนวน slice ที่ backpropagate และความคลาดเคลื่อนรวมไปพร้อมกัน
หากคุณไม่ต้องการกระจายงบประมาณความคลาดเคลื่อนไปตลอดทั้งวงจร แต่ก็ไม่ต้องการใช้มันอย่างโลภเช่นกัน คุณสามารถระบุจำนวน slice ที่ตั้งใจจะ backpropagate (num_slices) พร้อมกับงบประมาณความคลาดเคลื่อนรวม (max_error_total) วิธีนี้จะกระจายงบประมาณความคลาดเคลื่อนอย่างสม่ำเสมอ (ตาม p_norm) ไปยัง slice อินพุต
ที่นี่ เราจะจำกัดจำนวน slice ที่อาจ backpropagate ไว้ที่ 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)
ตอนนี้เราจะลอง backpropagate 12 slice ที่เราจัดสรรงบประมาณไว้ในขั้นตอนก่อนหน้า เราทำโดยส่ง 12 slice สุดท้ายในวงจรของเราเข้าไป เมื่อ p_norm=1 แต่ละ slice จาก 12 slice มีงบประมาณพร้อมใช้ 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,
)
เนื่องจากเราส่งชุดย่อยของ slice (ในโค้ด slices[-num_slices:]) ให้ backpropagate เราต้องรวม slice ที่เหลือหลัง backpropagation เข้ากับ slice ที่ไม่เคยถูกส่งไป backpropagate (ในโค้ด slices[:-num_slices])
เมื่อเรารวม slice ที่เหลือทั้งหมดแล้ว เราสามารถใช้ combine_slices เพื่อสร้าง QuantumCircuit ที่ลดความลึกแล้ว ถัดไป เราตรวจดูว่ามี slice ถูก backpropagate กี่อัน เทียบกับขนาดที่ 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.
กราฟแสดงว่าเรา backpropagate ครบทั้ง 12 slice สำเร็จ โดยที่ observable ยังอยู่ในกลุ่ม Pauli ที่ commute กันไม่เกิน 10 กลุ่ม เรายังเห็นด้วยว่าการใช้ num_slices ร่วมกับ max_error_total ทำให้งบประมาณถูกกระจายไปตาม slice ต่าง ๆ และงบที่เหลือไม่ได้ใช้จะถูกส่งต่อไปยัง slice ถัดไปอีกครั้ง เห็นได้ชัดที่สุดในกราฟ ขวาบน เพราะงบถูกใช้ไปและถูกเติมกลับเข้ามาตลอดการ backpropagation
น่าสังเกตว่าวิธีกระจายค่าความคลาดเคลื่อนแบบนี้ให้ผลลัพธ์ดีที่สุด (backpropagate ได้หลาย slice กว่า) เมื่อเทียบกับการทดลองแรกใน notebook นี้และตัวอย่างก่อนหน้านี้ ทุกตัวอย่างจัดสรรงบความคลาดเคลื่อน .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)])

ทำงานกับ observable หลายตัว
เมธอด qiskit_addon_obp.backpropagate ให้เราส่งลำดับของ observable เข้าไปได้ ซึ่งทำให้ workflow ง่ายขึ้นเมื่อมี observable เป้าหมายหลายตัว
เราเน้นเรื่องนี้ไว้ตรงนี้เพื่อสอนว่ากลยุทธ์การตัดทอน (truncation) จัดการกรณีแบบนี้อย่างไร สำหรับตัวอย่างนี้ เราเพิ่ม observable อีกหนึ่งตัวเข้ากับตัวที่ใช้มาจนถึงตอนนี้:
obs = [SparsePauliOp("IIIIIZIIII"), SparsePauliOp("IIIIIXIIII")]
สุดท้าย เราทำการทดลองแรกจากบทเรียนนี้ซ้ำอีกครั้ง แต่ใช้ observable สองตัวเพื่อ backpropagate circuit เข้าไป
ในตัวอย่างนี้ จำนวน slice ที่ backpropagate ได้ไม่เปลี่ยน อย่างไรก็ตาม เราเห็นว่า observable ทั้งสองให้จำนวนเทอม Pauli และจำนวนกลุ่มที่ commute กันต่างกัน
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 จัดการกับ observable หลายตัวอย่างไร
ก่อนอื่น กราฟ ซ้ายบน, ขวาบน และ ซ้ายล่าง แสดงว่างบสำหรับการตัดทอนเทอมถูกกำหนดแยกกันสำหรับแต่ละ observable กล่าวคือ observable ทั้งสองตัดทอนเทอมได้โดยสมมติค่าความคลาดเคลื่อน 0.001 ต่อ slice ที่ backpropagate
เนื่องจาก observable มีลักษณะต่างกัน การใช้งบจึงต่างกันไปด้วย ในตัวอย่างนี้ทั้งสองมีส่วนที่ทับซ้อนกันมาก ซึ่งไม่ได้เกิดขึ้นเสมอไป
กราฟ ขวาล่าง แสดงว่า max_qwc_groups พิจารณา observable ทั้งหมด ร่วมกัน นั่นคือเทอมของทุก observable ถูกจัดกลุ่มรวมกันเพื่อให้ได้จำนวนกลุ่มที่ commute แบบ qubit-wise สุดท้ายเพียงค่าเดียว ซึ่งนำไปเทียบกับ max_qwc_paulis เกณฑ์ max_paulis (ไม่ได้กล่าวถึงใน notebook นี้) ก็ทำแบบเดียวกัน โดยให้คุณกำหนดขีดจำกัดจำนวนเทอม Pauli รวมของทุก observable ได้
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)])
