How to tighten bounds with a speed limit
ข้อมูลใน circuit ของ two-qubit gate ไม่สามารถแผ่ไปเร็วเท่าใดก็ได้ ข้อผิดพลาดที่เกิดขึ้น
หลาย layer ก่อนจุดสิ้นสุด บน qubit ที่อยู่ไกลจาก support ของ observable ไม่มีทางไปถึงมันได้
tighten_with_speed_limit เปลี่ยนข้อโต้แย้งเชิงเรขาคณิตนี้ (สัญชาตญาณเดียวกับ
ขอบเขต Lieb-Robinson) ให้เป็น forward bound ที่แคบลง
ขั้นตอนนี้ราคาถูก ไม่ต้องใช้อะไรเกินกว่าที่ forward bound ต้องการอยู่แล้ว และมีแต่ทำให้ ขอบเขตแคบลงเท่านั้น ไม่มีการแลกกับความแม่นยำให้ต้องชั่งใจ
คำถามที่ควรตอบก่อนคือ "เมื่อไหร่มันช่วยได้?" เพราะมันมักไม่ช่วยบนชุด forward bound ที่ลู่เข้าเต็มที่แล้ว
การตั้งค่า
ใช้ circuit Ising แบบ Trotterized เดียวกับคู่มือ quickstart โดยให้ observable อยู่บน qubit ตัวเดียวตรงกลาง chain รูปทรง 1D ทำให้เหตุผลถึงผลกระทบได้ง่าย: อิทธิพลต้อง เดินทางผ่าน chain ทีละพันธะต่อ layer
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_slc": "qiskit-addon-slc", "samplomatic": "samplomatic"}
_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 import QuantumCircuit
from qiskit.quantum_info import Pauli
from qiskit_addon_slc.bounds import (
compute_forward_bounds,
tighten_with_speed_limit,
)
from qiskit_addon_slc.utils import generate_noise_model_paulis
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.utils import find_unique_box_instructions
def trotter_ising_circuit(num_qubits, num_steps, rx_angle, rzz_angle):
"""Trotterized transverse-field Ising evolution on a 1D chain."""
circuit = QuantumCircuit(num_qubits)
for _ in range(num_steps):
circuit.rx(rx_angle, range(num_qubits))
circuit.barrier()
for start in (0, 1): # even then odd bonds
for i in range(start, num_qubits - 1, 2):
circuit.rzz(rzz_angle, i, i + 1)
circuit.barrier()
return circuit
num_qubits = 10
circuit = trotter_ising_circuit(
num_qubits, num_steps=4, rx_angle=np.pi / 16, rzz_angle=-np.pi / 3
)
# A single-qubit observable in the middle of the chain.
observable = Pauli("I" * num_qubits).compose("Z", [num_qubits // 2])
boxing_pass = generate_boxing_pass_manager(
inject_noise_targets="all",
inject_noise_strategy="individual_modification",
inject_noise_site="after",
twirling_strategy="active",
remove_barriers="never",
)
boxed_circuit = boxing_pass.run(circuit)
noise_model_paulis = generate_noise_model_paulis(
find_unique_box_instructions(boxed_circuit)
)
def compare(label, bounds):
"""Tightens a set of bounds and prints what changed."""
tightened = tighten_with_speed_limit(
bounds, boxed_circuit, noise_model_paulis, observable
)
before = np.concatenate([bound.rates for bound in bounds.values()])
after = np.concatenate([bound.rates for bound in tightened.values()])
print(
f"{label:30} mean {before.mean():.4f} -> {after.mean():.4f} "
f"tightened {np.count_nonzero(after < before - 1e-12):4} of {len(before)} "
f"loosened {np.count_nonzero(after > before + 1e-12)}"
)
print(
f"observable: Z on qubit {num_qubits // 2}, circuit depth {circuit.depth()}"
)
observable: Z on qubit 5, circuit depth 12
เมื่อไหร่ที่ช่วยได้ และเมื่อไหร่ที่ไม่ช่วย
tighten_with_speed_limit รับขอบเขตที่คุณมีอยู่แล้วและคืนสำเนาที่แคบลง ขอบเขตเดิมของคุณจะไม่ถูกแก้ไข ผลที่ได้มากน้อยแค่ไหนขึ้นอยู่กับว่าอินพุตหลวมเพียงใด
# 1. Fully computed forward bounds.
full_bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable
)
compare("fully computed", full_bounds)
# 2. Bounds where the eigenvalue step was skipped, leaving the looser triangle inequality.
loose_bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable, eigval_max_qubits=0
)
compare("triangle inequality only", loose_bounds)
# 3. Bounds computed only for the first few boxes, the rest left at the trivial bound of 2.0.
shallow_bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable, max_num_boxes=2
)
compare("max_num_boxes=2", shallow_bounds)
fully computed mean 0.3639 -> 0.3639 tightened 0 of 792 loosened 0
triangle inequality only mean 0.5081 -> 0.4842 tightened 105 of 792 loosened 0
max_num_boxes=2 mean 1.5707 -> 0.5284 tightened 450 of 792 loosened 0
บรรทัดแรกคือบรรทัดที่ควรสังเกต บน forward bound ที่คำนวณเต็มที่ speed limit ไม่เปลี่ยนอะไรเลย นั่นไม่ใช่ความล้มเหลว มันหมายความว่าขอบเขตแคบอย่างน้อยเท่าที่ข้อโต้แย้งเชิงเรขาคณิตพิสูจน์ได้อยู่แล้ว เพราะการวิวัฒน์ของ operator ได้ค้นพบ light cone ด้วยตัวเองโดยปริยาย
ผลที่ได้ปรากฏในที่ซึ่งขอบเขตไม่ได้คำนวณเต็มที่:
-
eigval_max_qubits=0บังคับให้ใช้ fallback แบบ triangle-inequality ขอบเขตจึงไม่สะท้อน spectral norm จริงของ commutator อีกต่อไป speed limit กู้คืนส่วนหนึ่งของส่วนที่เสียไปนั้นได้ -
max_num_boxes=2ทิ้งส่วนใหญ่ของ layer ไว้ที่ขอบเขต trivial เท่ากับ 2.0 (ขอบเขตที่ไม่มี ข้อมูลเลย) ตรงนี้ speed limit ช่วยได้ โดยลดค่าเฉลี่ยลงประมาณ สามเท่า
ในทุกกรณีคอลัมน์ "loosened" เป็นศูนย์ ไม่มีเทอมไหนแย่ลงเลย ซึ่งเป็นการรับประกันที่ทำให้ ขั้นตอนนี้ปลอดภัยที่จะใช้โดยไม่มีเงื่อนไข
สิ่งที่ได้ในแง่ของ sampling overhead
ขอบเขตที่แคบลงหมายถึงมี error term ที่ต้องลดน้อยลงสำหรับ bias tolerance ที่กำหนด ซึ่งเป็นตัวเลขที่สำคัญบนฮาร์ดแวร์
from qiskit.quantum_info import PauliLindbladMap, QubitSparsePauliList
from qiskit_addon_slc.bounds import compute_local_scales
# Stand-in for rates that a noise-learning experiment would measure on hardware.
rng = np.random.default_rng(42)
noise_rates = {
layer: PauliLindbladMap.from_components(
rng.random(len(terms)) * 5e-3,
QubitSparsePauliList.from_sparse_list(
terms.to_sparse_list(), terms.num_qubits
),
)
for layer, terms in noise_model_paulis.items()
}
tightened_shallow = tighten_with_speed_limit(
shallow_bounds, boxed_circuit, noise_model_paulis, observable
)
print(
f"{'bounds':30} {'overhead':>9} {'mitigated':>10} {'residual bias':>14}"
)
for label, bounds in (
("max_num_boxes=2", shallow_bounds),
("max_num_boxes=2 + speed limit", tightened_shallow),
("fully computed", full_bounds),
):
scales, cost, bias = compute_local_scales(
boxed_circuit, bounds, noise_rates, bias_tolerance=0.05
)
mitigated = sum(int(np.count_nonzero(scale)) for scale in scales.values())
total = sum(len(scale) for scale in scales.values())
print(
f"{label:30} {cost:9.4f} {f'{mitigated}/{total}':>10} {bias:>14.4f}"
)
bounds overhead mitigated residual bias
max_num_boxes=2 420.2823 617/792 0.0490
max_num_boxes=2 + speed limit 7.8511 226/792 0.0485
fully computed 5.4031 188/792 0.0494
ควรใช้เมื่อไหร่
ใช้กับ forward bound เมื่อใดก็ตามที่ขอบเขตเหล่านั้นยังลู่เข้าไม่เต็มที่ (โดยพื้นฐานคือ
เมื่อใดที่คุณแลกความแม่นยำกับความเร็วด้วยตัวเลือกใด ๆ ใน
คู่มือการตั้งค่า) มันถูกเมื่อเทียบกับการคำนวณขอบเขต
ไม่ทำให้อะไรแย่ลง และกู้คืนส่วนใหญ่ของสิ่งที่ขีดจำกัด max_num_boxes หรือ eigval_max_qubits
เสียไปได้
บนขอบเขตที่คำนวณเต็มที่มันไม่ทำอะไร จึงไม่เป็นไรที่จะใช้โดยไม่มีเงื่อนไข
ข้อจำกัดสองข้อ:
-
ใช้ได้กับ forward bound เท่านั้น backward bound เกี่ยวกับผลของข้อผิดพลาดต่อสถานะเริ่มต้น ไม่ใช่ต่อ observable ข้อโต้แย้งจึงใช้ต่อไม่ได้
-
observable ต้องเป็น Pauli term เดียว สำหรับ observable ที่มีหลายเทอม ให้เรียกฟังก์ชันนี้ หนึ่งครั้งต่อ Pauli แล้วรวมผลลัพธ์เอง ถ้า observable ไม่ใช่ Pauli term เดียว จะเกิด
NotImplementedError
ใน workflow มาตรฐาน มันอยู่ระหว่างการคำนวณ forward bound กับการรวมเข้ากับ backward bound ดังที่คู่มือ quickstartแสดง