วิธีคำนวณขอบเขตแบบกำหนดเอง
compute_forward_bounds และ compute_backward_bounds เป็น wrapper เพื่อความสะดวก ทั้งสองสร้างจาก
สามส่วนเดียวกัน ซึ่ง API สาธารณะเปิดให้ใช้:
-
ฟังก์ชัน norm ซึ่งกำหนดขอบเขตของเทอมความคลาดเคลื่อนแบบ Pauli ที่ผ่านการวิวัฒน์หนึ่งเทอม
-
light cone ซึ่งกำหนดจุดเริ่มต้นของสิ่งที่เทอมความคลาดเคลื่อนถูกวิวัฒน์ไปหา
-
compute_boundsซึ่งวนซ้ำไปตาม circuit และกระจายงานต่อเทอม
การประกอบมันเองหมายความว่าคุณสามารถกำหนดขอบเขตของสิ่งที่ wrapper ไม่ครอบคลุมได้ คู่มือนี้ อธิบายกรณีหนึ่งที่คุ้มค่าจะทำ: mirror circuit ซึ่งขอบเขตย้อนกลับปกติ หลวมโดยไม่จำเป็น
wrapper ทำอะไรจริงๆ
wrapper ทั้งสองต่างกันในสามจุดที่ระบุไว้ข้างต้น:
compute_forward_bounds | compute_backward_bounds | |
|---|---|---|
| circuit | ตามที่ให้ | remove_measure(circuit).inverse() |
| ฟังก์ชัน norm | time_evolved_norm_forward เทียบกับ observable | time_evolved_norm_backward เทียบกับสถานะเริ่มต้น |
| light cone | LightCone.initialize_from_pauli เริ่มจาก observable | LightCone.initialize_from_measurements เริ่มจากทุก qubit ที่ใช้งาน |
backwards | False | True |
ขอบเขตไปข้างหน้าถามว่า "ความคลาดเคลื่อนนี้เปลี่ยน observable ที่ฉันกำลังจะวัดได้มากแค่ไหน?" และ ตอบด้วย spectral norm ของคอมมิวเตเตอร์ ขอบเขตย้อนกลับถามว่า "ความคลาดเคลื่อนนี้ เปลี่ยนสถานะที่ฉันเริ่มต้นมาได้มากแค่ไหน?" และตอบด้วย nuclear norm โดยถือว่าสถานะนั้นคือ สถานะศูนย์ทั้งหมดบนทุก qubit ที่ใช้งาน สองคำถามนี้ต่างกัน และแต่ละอันเป็นคำถามที่ถูกต้องสำหรับ ความคลาดเคลื่อนในครึ่งของ circuit ของมันเอง
กรณี mirror circuit
mirror circuit ใช้ยูนิทารีบางตัวแล้วย้อนมันกลับ: สิ่งเหล่านี้ปรากฏเมื่อใดก็ตามที่ ผลลัพธ์ในอุดมคติต้องรู้อย่างแม่นยำ (โดยทั่วไปคือ Loschmidt echoes, randomized benchmarking และ การทดลองตรวจสอบ)
mirror circuit มีสมบัติที่ขอบเขตย้อนกลับทั่วไปใช้ประโยชน์ไม่ได้ เพราะครึ่งหลัง เป็นอินเวิร์สของครึ่งแรก การวิวัฒน์ความคลาดเคลื่อนย้อนกลับไปยังสถานะเริ่มต้น ผ่าน gate เดียวกับ การวิวัฒน์มันไปข้างหน้าถึง observable สองคำถามนี้ตรงกัน ดังนั้น ขอบเขตแบบไปข้างหน้าที่คมกว่าจึงใช้ได้กับทิศทางย้อนกลับด้วย
# 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.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
# The circuit of the quickstart guide...
forward_half = trotter_ising_circuit(
num_qubits, num_steps=4, rx_angle=np.pi / 16, rzz_angle=-np.pi / 3
)
# ...followed by its inverse. The ideal result is the initial state.
circuit = forward_half.compose(forward_half.inverse())
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)
)
print(f"mirror circuit: {num_qubits} qubits, depth {circuit.depth()}")
print(f"noisy layers: {len(noise_model_paulis)}")
mirror circuit: 10 qubits, depth 24
noisy layers: 4
ประกอบขอบเขตแบบกำหนดเอง
สูตรนี้คงสิ่งที่ compute_backward_bounds ทำกับ circuit ไว้ (ตัดการวัดออก
กลับด้าน และวนซ้ำด้วย backwards=True) และแทนที่อีกสองส่วน: ฟังก์ชัน norm
แบบไปข้างหน้า และ light cone ที่เริ่มจาก observable ด้วย LightCone.initialize_from_pauli
เหมือนกับที่ compute_forward_bounds เริ่มของมันเอง แต่ละการแทนที่คือหนึ่งบรรทัด
from functools import partial
from qiskit_addon_slc.bounds import (
LightCone,
compute_bounds,
time_evolved_norm_forward,
)
from qiskit_addon_slc.utils import remove_measure
def compute_mirror_backward_bounds(
circuit,
noise_model_paulis,
/,
observable,
*,
evolution_max_terms=1_000_000,
**kwargs,
):
"""Bounds the backward direction of a mirror circuit with a forward-style bound.
Valid only for circuits whose second half inverts the first: the backward evolution then
traverses the same gates as the forward one, and reproduces the forward bounds.
"""
# As `compute_backward_bounds` does: strip measurements and invert, so that walking the circuit
# backwards from its end becomes walking the inverted circuit forwards.
circuit = remove_measure(circuit).inverse()
# The substitution: the forward norm function, bounding against the observable rather than
# against the initial state.
norm_fn = partial(
time_evolved_norm_forward,
observable=observable,
evolution_max_terms=evolution_max_terms,
)
# Seed the light cone from the observable, not from every active qubit.
light_cone = LightCone.initialize_from_pauli(circuit, observable)
return compute_bounds(
circuit,
noise_model_paulis,
light_cone,
norm_fn,
backwards=True,
**kwargs,
)
ได้ประโยชน์เท่าไร
เปรียบเทียบกับขอบเขตย้อนกลับมาตรฐานบน circuit เดียวกัน สรุปแบบเดียวกับใน คู่มือการตั้งค่า: ค่าเฉลี่ยของขอบเขตและส่วนเบี่ยงเบนมาตรฐาน ตามด้วยสัดส่วนของเทอมที่มีขอบเขตเป็น 0 ที่คำนวณขอบเขตได้ระหว่างนั้น และที่ค้างอยู่ที่ ค่าธรรมดา 2.0
from qiskit_addon_slc.bounds import (
compute_backward_bounds,
compute_forward_bounds,
)
forward_bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable
)
stock_backward = compute_backward_bounds(boxed_circuit, noise_model_paulis)
custom_backward = compute_mirror_backward_bounds(
boxed_circuit, noise_model_paulis, observable
)
def summarize(label, bounds):
"""Prints a one-line summary of a set of bounds."""
rates = np.concatenate([bound.rates for bound in bounds.values()])
# The bounds are floating-point results, so a term counts as bounded by 0, or as left at the
# trivial 2.0, when it is within rounding error of that value.
exact = np.isclose(rates, 0.0, rtol=0.0, atol=1e-12)
trivial = np.isclose(rates, 2.0, rtol=0.0, atol=1e-12)
computed = ~exact & ~trivial
print(
f"{label:18} mean {rates.mean():.4f} ± {rates.std():.3f} │ "
f"at 0: {exact.mean():4.0%} computed: {computed.mean():4.0%} "
f"at 2.0: {trivial.mean():4.0%}"
)
summarize("forward", forward_bounds)
summarize("backward (stock)", stock_backward)
summarize("backward (custom)", custom_backward)
forward mean 0.4323 ± 0.679 │ at 0: 38% computed: 61% at 2.0: 1%
backward (stock) mean 1.7218 ± 0.592 │ at 0: 0% computed: 95% at 2.0: 5%
backward (custom) mean 0.4323 ± 0.679 │ at 0: 38% computed: 61% at 2.0: 1%
ขอบเขตย้อนกลับมาตรฐานอยู่ใกล้ค่าธรรมดา 2.0 ซึ่งแทบไม่ให้ข้อมูล บน mirror circuit ทุกความคลาดเคลื่อน "ใกล้สถานะเริ่มต้น" ในความหมายที่ขอบเขตนั้นวัด ดังนั้น มันจึงแยกแยะระหว่างเทอมไม่ได้ ขอบเขตแบบกำหนดเองแน่นกว่าหลายเท่าและกำหนดขอบเขตของหลายเทอมเป็น 0
เพื่อตรวจสอบการทำงาน สามารถเทียบทั้งสองทีละ box ได้ สำหรับทุก box ขอบเขตย้อนกลับ แบบกำหนดเองออกมาเหมือนกันกับขอบเขตไปข้างหน้าของ box เดียวกัน จนถึงความคลาดเคลื่อนจากการปัดเศษ ทั้งสองกำหนดขอบเขตความคลาดเคลื่อนเดียวกันที่จุดเดียวกันใน circuit และบน mirror circuit gate หลังจุดนั้นหักล้างกลายเป็นอินเวิร์สของ gate ก่อนหน้าจุดนั้น การคำนวณทั้งสองจึงประเมิน ปริมาณเดียวกันจากปลายด้านตรงข้าม:
matches = sum(
np.allclose(forward_bounds[box_id].rates, custom_backward[box_id].rates)
for box_id in forward_bounds
)
print(f"{matches} of {len(forward_bounds)} boxes match the forward bounds")
worst = max(
np.abs(forward_bounds[box_id].rates - custom_backward[box_id].rates).max()
for box_id in forward_bounds
)
print(f"largest disagreement across all terms: {worst:.2e}")
16 of 16 boxes match the forward bounds
largest disagreement across all terms: 6.00e-15
หมายเหตุ: ความเท่ากันนี้เป็นสมบัติของโครงสร้างกระจก ไม่ใช่ของเทคนิค บน circuit ที่ไม่ผกผันตัวเอง การแทนที่แบบเดียวกันยังรันได้ แต่ไม่ได้คำนวณขอบเขตที่ ถูกต้อง สำหรับทิศทางย้อนกลับอีกต่อไป: มันตอบคำถามไปข้างหน้าเกี่ยวกับครึ่งหนึ่งของ circuit ที่คำถามนั้น ใช้ไม่ได้ ใช้วิธีนี้เฉพาะเมื่อครึ่งหลังของ circuit ของคุณเป็นอินเวิร์สของ ครึ่งแรก
ป้อนเข้าสู่เวิร์กโฟลว์ส่วนที่เหลือ
ผลลัพธ์เป็นดิกชันนารี Bounds ธรรมดา ทุกอย่างที่ตามมาจึงไม่เปลี่ยน
บน mirror circuit ก็ไม่มีเหตุผลที่จะคำนวณขอบเขตใดๆ สำหรับ circuit ทั้งหมดด้วย
ขอบเขตไปข้างหน้าจำเป็นเฉพาะสำหรับ box ของ นับจากท้าย และขอบเขต
ย้อนกลับเฉพาะสำหรับ box ของ นับจากต้น ดังนั้น max_num_boxes จำกัดแต่ละอย่าง
ให้เหลือครึ่งหนึ่งของ box ที่มีสัญญาณรบกวน และ merge_bounds รวมสองครึ่งเข้าด้วยกัน
นี่คือวิธีที่ขอบเขตถูกใช้ในทางปฏิบัติ และหมายความว่าการเปรียบเทียบทีละ box ข้างต้นไม่ สามารถทำได้อีก: มันต้องใช้ทั้งสองขอบเขตสำหรับทุก box มันเป็นการตรวจสอบการทำงานสำหรับคู่มือนี้ ไม่ใช่ขั้นตอนของเวิร์กโฟลว์
from qiskit.quantum_info import PauliLindbladMap, QubitSparsePauliList
from qiskit_addon_slc.bounds import compute_local_scales, merge_bounds
# 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()
}
# Compute each bound for its own half of the mirror circuit only. `forward_bounds` has an entry
# for every noisy box, which are the boxes `max_num_boxes` counts.
half = len(forward_bounds) // 2
half_forward = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable, max_num_boxes=half
)
half_stock = compute_backward_bounds(
boxed_circuit, noise_model_paulis, max_num_boxes=half
)
half_custom = compute_mirror_backward_bounds(
boxed_circuit, noise_model_paulis, observable, max_num_boxes=half
)
print(
f"each bound computed for {half} of {len(forward_bounds)} noisy boxes\n"
)
print(f"{'bounds':34} {'overhead':>12} {'residual bias':>14}")
for label, bounds in (
(
"forward + stock backward",
merge_bounds(boxed_circuit, half_forward, half_stock, noise_rates),
),
(
"forward + custom backward",
merge_bounds(boxed_circuit, half_forward, half_custom, noise_rates),
),
("forward only, whole circuit", forward_bounds),
):
_, cost, bias = compute_local_scales(
boxed_circuit, bounds, noise_rates, bias_tolerance=0.05
)
print(f"{label:34} {cost:12.4f} {bias:14.4f}")
Optimal spacetime partitioning not implemented! Just partitioning list of noisy boxes.
Optimal spacetime partitioning not implemented! Just partitioning list of noisy boxes.
each bound computed for 8 of 16 noisy boxes
bounds overhead residual bias
forward + stock backward 29848.0583 0.0498
forward + custom backward 465.4028 0.0498
forward only, whole circuit 465.4028 0.0498
ด้วยขอบเขตย้อนกลับมาตรฐาน sampling overhead ใหญ่กว่ามาก merge_bounds ใช้
ขอบเขตย้อนกลับจนถึงจุดสลับหนึ่งจุดใน circuit และใช้ขอบเขตไปข้างหน้าตั้งแต่จุดนั้นเป็นต้นไป
โดยเลือกจุดที่ทำให้ bias รวมน้อยที่สุด เมื่อคำนวณแต่ละขอบเขตสำหรับครึ่งเดียว จุดนั้น
ตกอยู่กลาง circuit ดังนั้น box ของ ถูกครอบคลุมด้วยขอบเขตย้อนกลับเท่านั้น
ขอบเขตย้อนกลับมาตรฐานใกล้ค่าธรรมดา 2.0 ที่นั่น ซึ่งดัน overhead ให้สูงขึ้น อันแบบ
กำหนดเองแน่นเท่ากับที่ขอบเขตไปข้างหน้าจะเป็น overhead จึงเท่ากับของ
ขอบเขตไปข้างหน้าแบบสมบูรณ์
นั่นทำให้เกิดคำถามชัดเจนว่าจะเสียเวลาทำไม ในเมื่อขอบเขตไปข้างหน้าแบบสมบูรณ์อย่างเดียวก็ให้ ผลเดียวกัน คำตอบคือต้นทุน ขอบเขตไปข้างหน้าสำหรับ box ช่วงต้นของ circuit วิวัฒน์ความคลาดเคลื่อนของมัน ผ่านส่วนที่เหลือของ แล้วผ่าน ทั้งหมด ซึ่งเป็นการวิวัฒน์ที่ยาวและกว้างที่สุด ใน circuit ตัวดำเนินการที่ผ่านการวิวัฒน์สามารถโตแบบเอกซ์โพเนนเชียลตามจำนวน gate ที่ไม่ใช่ Clifford ที่มันผ่าน และการคำนวณ spectral norm ของคอมมิวเตเตอร์ก็แพงขึ้นแบบเอกซ์โพเนนเชียลเมื่อ light cone กว้างขึ้น ในแนวทางครึ่งต่อครึ่ง ไม่มีความคลาดเคลื่อนใดถูกวิวัฒน์ผ่านเกินครึ่งหนึ่งของ circuit
การประหยัดจึงเพิ่มตามความลึกของ circuit ที่สำคัญกว่านั้น ขอบเขตไปข้างหน้าแบบสมบูรณ์
จะคำนวณไม่ได้นานก่อนที่แบบครึ่งทางจะทำไม่ได้ เมื่อการวิวัฒน์ของ box ช่วงต้น
เกิน evolution_max_terms หรือ eigval_max_qubits ขอบเขตของมันจะถูกตัดทอนหรือถอยกลับ
ไปใช้อสมการสามเหลี่ยม และในที่สุดก็แพงเกินกว่าจะคำนวณ ไม่ว่าจุดนั้น
จะอยู่ที่ใด แนวทางครึ่งต่อครึ่งต้องการแค่ให้มันตกอยู่ที่ไหนสักแห่งในครึ่งแรกของ circuit
ขอบเขตไปข้างหน้าต้องไปไม่เกินกลาง circuit และบน mirror circuit ขอบเขต
ย้อนกลับสำหรับ box ของ วิวัฒน์ความคลาดเคลื่อนของมันย้อนกลับไปถึงจุดเริ่มต้นผ่าน circuit เท่ากับที่ขอบเขต
ไปข้างหน้าสำหรับ box ที่ตรงกันของ ทำไปถึงจุดจบ ดังนั้นทั้งสองจึงหมดแรง
ที่ความลึกเดียวกัน
หมายเหตุ: ทำไมต้องคำนวณขอบเขตย้อนกลับสำหรับ box ของ ในเมื่อขอบเขตไปข้างหน้าของ box ของ ดูเหมือนควรมีตัวเลขเดียวกัน? เพราะ box ไม่ได้เป็นภาพสะท้อน ของกันและกัน แต่ละ box ฉีดสัญญาณรบกวนที่ด้านหนึ่งของ gate ของมัน และเลเยอร์อินเวิร์สของ เป็น box แยกต่างหากที่มีโมเดลสัญญาณรบกวนของตัวเอง ดังนั้น box ที่สะท้อน box ของ เห็นความคลาดเคลื่อนของมันที่จุดต่างกันใน circuit ด้วยเหตุนี้ ขอบเขตของทั้งสองจึงตรงกันเพียงบางส่วน และ ขอบเขตสำหรับ box ของ ต้องคำนวณสำหรับ box เหล่านั้นเอง
การเขียนขอบเขตแบบกำหนดเองอื่นๆ
การแทนที่สามอย่างเดียวกันครอบคลุมกรณีอื่นๆ:
-
norm ที่ต่างออกไป
time_evolved_norm_forwardรับcomm_norm_orderการส่งค่าอื่นใด นอกจาก2จะข้ามตัวแก้ค่าลักษณะเฉพาะและคำนวณ norm นั้นโดยตรง -
light cone ที่ต่างออกไป
LightCone.initialize_from_pauliเริ่ม light cone จาก Pauli ใดก็ได้ และคอนสตรักเตอร์LightConeรับ qubit ที่จะติดตามและการดำเนินการที่ใช้เริ่มมัน ดังนั้น สามารถคำนวณขอบเขตเทียบกับตัวดำเนินการใดๆ ที่คุณแสดงด้วยวิธีนั้นได้ -
ฟังก์ชัน norm ที่ต่างไปทั้งหมด
compute_boundsต้องการเพียง callable: ที่รับPauliและRotationGatesแล้วคืนCommutatorBoundsอะไรก็ตามที่ตรงตามซิกเนเจอร์นั้น ใช้ได้ รวมถึงขอบเขตรูปแบบปิดที่เฉพาะกับตระกูล circuit ของคุณ
ไม่ว่าคุณจะแทนที่ด้วยอะไร คุณต้องพิสูจน์ว่าผลลัพธ์เป็นขอบเขตบนที่แท้จริง การประเมินผลของเทอมความคลาดเคลื่อนต่ำเกินไปไม่ได้ทำให้เกิดความล้มเหลวที่มองเห็นได้ มันทำให้ได้ค่าคาดหมาย ที่ผิดอย่างเงียบๆ เพราะเทอมนั้นจะถูกทิ้งจากโมเดลสัญญาณรบกวนที่ผ่านการลดผลกระทบ