วิธีปรับแต่งตัวเลือกการตั้งค่า
การคำนวณขอบเขตนั้นแม่นยำในหลักการ แต่เป็นการประมาณในทางปฏิบัติ: การวิวัฒน์เทอมความคลาดเคลื่อนแบบ Pauli ผ่าน circuit ให้ตัวดำเนินการที่มีจำนวนเทอมมากแบบเอกซ์โพเนนเชียล และการหา spectral norm ของคอมมิวเตเตอร์ที่ได้ก็มีต้นทุนสูงในตัวมันเอง ตัวเลือกด้านล่างควบคุมต้นทุนนั้น โดยแบ่ง เป็นสามกลุ่ม:
-
ตัวเลือกกลุ่ม Accuracy เปลี่ยนค่าตัวเลขของขอบเขต โดยแลกความแน่นของขอบเขตกับต้นทุน
-
กลุ่ม Scope กำหนดว่าจะคำนวณขอบเขต ใดบ้าง
-
ตัวเลือกกลุ่ม Execution เปลี่ยน วิธี ที่การคำนวณทำงาน (ไม่ใช่สิ่งที่คำนวณ)
| ตัวเลือก | ค่าเริ่มต้น | ควบคุม | ผลต่อผลลัพธ์ |
|---|---|---|---|
| Accuracy | |||
evolution_max_terms | 1_000_000 | จำนวนเทอมของตัวดำเนินการที่รอดจากการวิวัฒน์ | ต่ำลง: ถูกลง ขอบเขตหลวมขึ้น |
atol_simplify | 1e-8 | เทอมของคอมมิวเตเตอร์ใดที่ถูกทิ้งเพราะเล็กน้อยเกินไป | สูงขึ้น: ถูกลง ขอบเขตหลวมขึ้น |
eigval_max_qubits | 14 | คอมมิวเตเตอร์กว้างได้แค่ไหนจึงจะคำนวณ spectral norm ของมันได้ | ต่ำลง: ถูกลง ขอบเขตหลวมขึ้น |
atol_eigenvalue | 1e-10 | เมื่อไรที่ตัวแก้ค่าลักษณะเฉพาะแบบวนซ้ำนับว่าลู่เข้าแล้ว | สูงขึ้น: ถูกลง ขอบเขตแม่นยำน้อยลง |
| Scope | |||
max_num_boxes | None | จำนวน box ที่คำนวณขอบเขต | box ที่เกินขีดจำกัดจะคงขอบเขตธรรมดาที่ 2.0 |
| Execution | |||
timeout | None | งบเวลาจริงสำหรับการคำนวณทั้งหมด | งานที่เหลือจะคงขอบเขตธรรมดาที่ 2.0 |
num_processes | 1 | จำนวนโปรเซสทำงานที่คำนวณขอบเขตแบบขนาน | ไม่มีผลโดยตัวมันเอง |
ขอบเขตจะมีประโยชน์ก็ต่อเมื่อมัน ถูกต้อง: มันต้องไม่ประเมินผลของเทอมความคลาดเคลื่อนที่มีต่อ
observable ต่ำเกินจริง การผ่อนปรนตัวเลือกใดๆ เหล่านี้ นอกจาก atol_eigenvalue มีแต่ทำให้ขอบเขต
ใหญ่ขึ้น (อนุรักษ์นิยมขึ้น) ไม่มีทางเล็กลง ดังนั้นราคาของการตั้งค่าหลวมคือ sampling
overhead ที่มากขึ้น ไม่ใช่ค่าคาดหมายที่ผิด - atol_eigenvalue เป็นข้อยกเว้น
นิยาม circuit
การทดลองต่อไปนี้ใช้ circuit Ising แบบ Trotter และ 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
circuit = trotter_ising_circuit(
num_qubits, num_steps=4, rx_angle=np.pi / 16, rzz_angle=-np.pi / 3
)
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"noisy layers: {len(noise_model_paulis)}")
print(
f"Pauli error terms: {sum(len(p) for p in noise_model_paulis.values())}"
)
noisy layers: 2
Pauli error terms: 198
ฟังก์ชันช่วยเล็กๆ สรุปขอบเขตแต่ละชุดในบรรทัดเดียว ผลของตัวเลือกจึงปรากฏเป็น ตัวเลขไม่กี่ตัว แทนที่จะเป็นอาร์เรย์เป็นกำแพง
ทุกขอบเขตอยู่ระหว่าง 0 ถึงขอบเขตธรรมดาที่ 2.0 สรุปจะรายงานค่าเฉลี่ยของขอบเขตและ ส่วนเบี่ยงเบนมาตรฐาน ตามด้วยการแบ่งเทอมเป็นกลุ่มเหล่านี้:
- เทอมที่มีขอบเขตเป็น 0 พอดี เพราะสลับที่ได้กับ observable ที่ผ่านการวิวัฒน์
- เทอมที่คำนวณขอบเขตได้ระหว่าง 0 ถึง 2.0
- เทอมที่ค้างอยู่ที่ค่าธรรมดา 2.0
โปรดสังเกตว่า ค่าเฉลี่ยที่ต่ำกว่าคือแน่นกว่า และแน่นกว่าหมายถึง sampling overhead ที่เล็กลงในภายหลัง
การกวาดค่าตัวเลือกหนึ่งตัวจะทำเครื่องหมายค่าเริ่มต้นของตัวเลือกนั้นด้วย *:
import inspect
import time
from qiskit_addon_slc.bounds import compute_forward_bounds
def summarize(rates, label, elapsed):
"""Prints a one-line summary of a set of bounds."""
# 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:28} 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%} │ "
f"{elapsed:4.2f}s"
)
def forward(label, **kwargs):
"""Computes the forward bounds, summarizes them and returns all their rates."""
start = time.perf_counter()
bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable, **kwargs
)
elapsed = time.perf_counter() - start
rates = np.concatenate([bound.rates for bound in bounds.values()])
summarize(rates, label, elapsed)
return rates
def sweep(option, values):
"""Computes the forward bounds for each value of one option, marking the default with `*`."""
default = (
inspect.signature(compute_forward_bounds).parameters[option].default
)
for value in values:
marker = "*" if value == default else " "
forward(f"{marker} {option}={value:g}", **{option: value})
baseline = forward(" defaults")
defaults mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.29s
ความแม่นยำ
evolution_max_terms
การวิวัฒน์เทอมความคลาดเคลื่อนแบบ Pauli ผ่าน gate ที่ไม่ใช่ Clifford จะเปลี่ยนมันเป็นผลรวมของ Pauli และผลรวมนั้น
โตตามความลึกของ circuit evolution_max_terms จำกัดจำนวนเทอมเหล่านั้นที่เก็บไว้ 1-norm ของ
สิ่งที่ถูกทิ้งจะถูกบวกเข้าไปในขอบเขต จึงไม่มีอะไรสูญหายไปเงียบๆ
การกวาดค่าเริ่มจากแน่นกว่าค่าเริ่มต้นไปจนถึงการตัดอย่างสุดโต่ง การลดเพดานทำให้ การคำนวณถูกลงและขอบเขตหลวมขึ้น
sweep("evolution_max_terms", (10_000_000, 1_000_000, 1000, 100, 10, 2))
evolution_max_terms=1e+07 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.38s
* evolution_max_terms=1e+06 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.31s
evolution_max_terms=1000 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.30s
evolution_max_terms=100 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.29s
evolution_max_terms=10 mean 0.3927 ± 0.711 │ at 0: 59% computed: 31% at 2.0: 9% │ 0.28s
evolution_max_terms=2 mean 0.5245 ± 0.792 │ at 0: 59% computed: 23% at 2.0: 18% │ 0.21s
การเพิ่มเพดานเกินค่าเริ่มต้นไม่เปลี่ยนอะไรในที่นี้ สำหรับ circuit ขนาดนี้ ค่าเริ่มต้น เก็บทุกเทอมไว้อยู่แล้ว ขอบเขตจึงแน่นที่สุดเท่าที่ตัวเลือกนี้ทำได้ นั่นคือ สถานการณ์ทั่วไป การทำให้ตัวเลือก accuracy แน่นขึ้นจะคุ้มก็ต่อเมื่อค่าเริ่มต้นเป็นตัวจำกัดจริงๆ และวิธีค้นหาคือการทดลองด้านบน ลองรันซ้ำด้วยค่าที่แน่นกว่าแล้วดูว่า ขอบเขตขยับหรือไม่
คำเตือน: รูทีนการแพร่กระจายเบื้องหลังถือว่า evolution_max_terms เป็น ความจุที่ต้อง
จองล่วงหน้า ไม่ใช่เพดานที่ใช้ตรวจสอบ การส่งค่าที่ในนามไม่จำกัด เช่น
numpy.iinfo(numpy.uint).max จะขอหน่วยความจำระดับเทระไบต์จากตัวจัดสรรและทำให้โปรเซสล้มเลิก
ให้ใช้ตัวเลขที่สมเหตุสมผล ค่าเริ่มต้นหนึ่งล้านก็ใจกว้างพอแล้ว
atol_simplify
หลังจากสร้างคอมมิวเตเตอร์แล้ว เทอมที่มีสัมประสิทธิ์ต่ำกว่า atol_simplify จะถูกทิ้ง เช่นเดียวกับ
เพดานการวิวัฒน์ 1-norm ที่สูญเสียไปด้วยวิธีนี้จะถูกบวกเข้าไปในขอบเขต
sweep("atol_simplify", (0.0, 1e-8, 1e-4, 1e-2, 1e-1))
atol_simplify=0 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.32s
* atol_simplify=1e-08 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.31s
atol_simplify=0.0001 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.33s
atol_simplify=0.01 mean 0.3656 ± 0.685 │ at 0: 59% computed: 35% at 2.0: 6% │ 0.31s
atol_simplify=0.1 mean 0.3837 ± 0.697 │ at 0: 59% computed: 31% at 2.0: 10% │ 0.31s
eigval_max_qubits
ขอบเขตที่แน่นที่สุดที่มีคือ spectral norm ของคอมมิวเตเตอร์ ซึ่งต้องคำนวณค่าลักษณะเฉพาะ
สิ่งนี้แพงขึ้นเมื่อคอมมิวเตเตอร์กว้างขึ้น ดังนั้น eigval_max_qubits กำหนดความกว้าง
ที่เกินจากนั้นจะเลิกพยายามและหันไปใช้อสมการสามเหลี่ยมแทน ขอบเขตนั้น
ของสัมประสิทธิ์ ของคอมมิวเตเตอร์ ถูกต้องแต่หลวมกว่า
การตั้งเป็นศูนย์บังคับให้ใช้ทางสำรองทุกที่ ซึ่งแสดงประโยชน์ของตัวแก้ค่าลักษณะเฉพาะ ในตัวเลขเดียว
sweep("eigval_max_qubits", (20, 14, 8, 4, 2, 0))
eigval_max_qubits=20 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.31s
* eigval_max_qubits=14 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.30s
eigval_max_qubits=8 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.32s
eigval_max_qubits=4 mean 0.3742 ± 0.694 │ at 0: 59% computed: 35% at 2.0: 6% │ 0.30s
eigval_max_qubits=2 mean 0.4983 ± 0.809 │ at 0: 59% computed: 23% at 2.0: 18% │ 0.27s
eigval_max_qubits=0 mean 0.5081 ± 0.810 │ at 0: 59% computed: 21% at 2.0: 19% │ 0.27s
แม้ปิดตัวแก้ค่าลักษณะเฉพาะ ค่าเฉลี่ยก็ยังต่ำกว่าค่าธรรมดา 2.0 มาก สัดส่วน ของเทอมที่มีขอบเขตเป็น 0 ไม่เปลี่ยน: เทอมที่สลับที่ได้พอดีกับ observable ที่ผ่านการวิวัฒน์ มีคอมมิวเตเตอร์เป็นศูนย์ ซึ่งถูกจดจำได้ก่อนจะพิจารณาการคำนวณค่าลักษณะเฉพาะใดๆ ส่วน เทอมอื่น ขอบเขตจากอสมการสามเหลี่ยมถึง 2.0 สำหรับบางเทอมและถูกจำกัดไว้ที่นั่น แต่สำหรับอีกมากมัน ยังเล็กกว่า เทอมเหล่านั้นจึงคงขอบเขตที่คำนวณได้ต่ำกว่า 2.0
atol_eigenvalue
เมื่อมีการคำนวณ spectral norm จริง atol_eigenvalue คือค่าความคลาดเคลื่อนที่ยอมรับได้สำหรับการลู่เข้าของ
ตัวแก้ค่าลักษณะเฉพาะแบบวนซ้ำ แต่คอมมิวเตเตอร์ส่วนใหญ่ไม่เคยไปถึงตัวแก้นั้น:
-
คอมมิวเตเตอร์ที่กระทำกับไม่เกินสี่ qubit จะถูกทำเป็นทแยงมุมอย่างแม่นยำโดยตรง
-
คอมมิวเตเตอร์ที่กว้างกว่านั้นจะถูกลดเหลือจำนวน qubit น้อยที่สุดที่เทอม Pauli ของมันต้องใช้ก่อน และถ้ารูปแบบที่ลดแล้วกระทำกับไม่เกินแปด qubit ก็จะถูกทำเป็นทแยงมุมอย่างแม่นยำ
เฉพาะคอมมิวเตเตอร์ที่หลังการลดนั้นยังต้องใช้เกินแปด qubit เท่านั้นที่ไปถึงตัวแก้ แบบวนซ้ำ ซึ่งเป็นที่เดียวที่ความคลาดเคลื่อนที่ยอมรับได้นี้ใช้ circuit ขนาดที่ใช้ในคู่มือนี้ไม่เคยไป ถึงขั้นนั้น การกวาดค่าตัวเลือกนี้จึงให้ขอบเขตเหมือนกันทุกค่า มันมีความสำคัญสำหรับ light cone ที่กว้างบนอุปกรณ์ขนาดใหญ่
ต่างจากตัวเลือก accuracy อื่น การผ่อน atol_eigenvalue ไม่ ปลอดภัยในทิศทางหนึ่ง ตัวแก้
ค่าลักษณะเฉพาะแบบวนซ้ำเข้าใกล้ค่าลักษณะเฉพาะสุดขั้วจากด้านในของสเปกตรัม ดังนั้นค่าประมาณ
ที่ยังลู่เข้าไม่เต็มที่จะประเมิน spectral norm และขอบเขตต่ำเกินจริง ค่าเริ่มต้น
จึงถูกตั้งให้แน่นโดยเจตนาด้วยเหตุนั้น ถ้าตัวแก้ลู่เข้าไม่ได้ ทางสำรองอสมการสามเหลี่ยม
จะเข้ามาแทน ซึ่งกลับมาถูกต้องอีกครั้ง
ขอบเขต
max_num_boxes
บางครั้งขีดจำกัดที่มีประโยชน์ไม่ใช่ความแม่นยำแต่เป็นความลึก max_num_boxes หยุดการสร้างการคำนวณขอบเขต
เมื่อเกินจำนวน box ที่กำหนด และทุกเลเยอร์ที่เกินจากนั้นจะถูกกำหนดขอบเขตธรรมดา
ที่ 2.0 แทน
ขีดจำกัดนับคำสั่ง BoxOp ที่มีสัญญาณรบกวน (คำสั่งที่มีคำอธิบายประกอบ InjectNoise) แทนที่จะเป็น
เช่น ความลึกในหน่วย gate วิธีนี้ผูกมันกับโครงสร้างของ circuit ที่ผู้ใช้มองเห็น และ
หลีกเลี่ยงการพึ่งพาการอ้างอิงคำอธิบายประกอบที่สร้างอัตโนมัติ ซึ่งอาจเปลี่ยนระหว่าง
การรัน transpilation
นี่คือทางเลือกตามธรรมชาติสำหรับ circuit ที่สองครึ่งของมันจัดการด้วยขอบเขตไปข้างหน้าและย้อนกลับ ตามลำดับ: คำนวณแต่ละอย่างลึกเท่าที่จะใช้เท่านั้น
from qiskit.circuit import BoxOp
from samplomatic import InjectNoise
# The limit counts the noisy boxes encountered while iterating the circuit, not the unique ones:
# a repeated layer is one noise model but several boxes. Boxes without an `InjectNoise`
# annotation carry no error terms and do not count.
num_boxes = sum(
1
for inst in boxed_circuit.data
if isinstance(inst.operation, BoxOp)
and any(
isinstance(annotation, InjectNoise)
for annotation in inst.operation.annotations
)
)
print(
f"the circuit has {num_boxes} noisy boxes drawn from "
f"{len(noise_model_paulis)} distinct noise models\n"
)
for limit in (None, 6, 4, 2, 1):
marker = "*" if limit is None else " "
forward(f"{marker} max_num_boxes={limit}", max_num_boxes=limit)
the circuit has 9 noisy boxes drawn from 2 distinct noise models
* max_num_boxes=None mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.33s
max_num_boxes=6 mean 0.7486 ± 0.935 │ at 0: 53% computed: 18% at 2.0: 30% │ 0.18s
max_num_boxes=4 mean 1.1506 ± 0.980 │ at 0: 40% computed: 5% at 2.0: 55% │ 0.11s
max_num_boxes=2 mean 1.5707 ± 0.821 │ at 0: 21% computed: 0% at 2.0: 79% │ 0.05s
max_num_boxes=1 mean 1.8157 ± 0.579 │ at 0: 9% computed: 0% at 2.0: 91% │ 0.03s
การรันงาน
timeout
timeout คืองบเวลาจริงเป็นวินาที เมื่อหมดเวลา ขอบเขตที่คำนวณเสร็จแล้วจะถูกเก็บไว้
และทุกอย่างที่ค้างอยู่จะคงขอบเขตธรรมดาที่ 2.0 ผลลัพธ์จึงใช้ได้เสมอ เพียงแต่
หลวมขึ้นยิ่งตัดเร็ว
มันไม่ใช่เส้นตายเด็ดขาด: งานที่กำลังรันอยู่จะได้รับอนุญาตให้ทำจนเสร็จ การเรียกจึงอาจเกิน งบเวลาได้เท่ากับระยะเวลาของหนึ่งงาน
num_processes
ขอบเขตของเทอมความคลาดเคลื่อนต่างกันเป็นอิสระต่อกัน ดังนั้น num_processes กระจายมันไปยังโปรเซสทำงาน
โดยตัวมันเอง มันมีผลแค่ระยะเวลาที่การคำนวณใช้ ไม่เคยมีผลต่อผลลัพธ์
หมายเหตุ: การใช้ timeout ร่วมกับ num_processes ทำให้ผลลัพธ์ขึ้นกับจังหวะเวลา: งานใดที่
เสร็จก่อนงบเวลาหมดนั้นไม่แน่นอน ขอบเขตยังคงถูกต้อง แต่ไม่สามารถ
ทำซ้ำได้จากการรันหนึ่งไปอีกการรันหนึ่งอีกต่อไป
เริ่มต้นจากตรงไหน
ค่าเริ่มต้นถูกเลือกให้ได้ขอบเขตที่แน่นบน circuit ขนาดที่พอจ่ายไหว เมื่อ การคำนวณช้าเกินไป ลองใช้ตัวเลือกตามลำดับคร่าวๆ นี้:
-
num_processes: เพิ่มความเร็วฟรีๆ โดยไม่เสียความแม่นยำ -
max_num_boxes: ถ้าคุณรู้ล่วงหน้าว่าสนใจลึกเข้าไปใน circuit แค่ไหน -
eigval_max_qubits: ข้ามขั้นตอนที่แพงที่สุดสำหรับคอมมิวเตเตอร์ที่กว้างที่สุด ซึ่งมักเป็น ส่วนน้อยของเทอม -
evolution_max_termsและatol_simplify: สองตัวนี้กระทบความแม่นยำทั้งหมด จึงปรับ เป็นอันดับสุดท้าย โดยตรวจด้วยการกวาดค่าแบบข้างบนเพื่อดูว่าขอบเขตขยับไปแค่ไหน -
timeout: การหยุดแบบเด็ดขาดมากกว่าตัวช่วยปรับแต่ง สำหรับเมื่อการคำนวณต้องเสร็จตามเส้นตาย ไม่ว่าคุณภาพจะเป็นอย่างไร
ยิ่งขอบเขตหลวม sampling overhead ที่ compute_local_scales จะรายงานสำหรับ
ความคลาดเคลื่อนของ bias ที่กำหนดก็ยิ่งใหญ่ขึ้น overhead นั้นคือปริมาณที่ควรจับตาขณะปรับแต่ง: มันบอกคุณว่า
การประมาณมีต้นทุนจริงเท่าไร