Cara memperketat bound dengan speed limit
Informasi dalam circuit gate dua qubit tidak bisa menyebar secepat-cepatnya tanpa batas. Error yang terjadi beberapa layer sebelum akhir, pada qubit yang jauh dari support observable, tidak punya jalur untuk mencapainya. tighten_with_speed_limit mengubah argumen geometris itu (intuisi yang sama dengan batas Lieb-Robinson) menjadi bound maju yang lebih ketat.
Langkah ini murah, tidak membutuhkan apa pun di luar yang sudah diperlukan bound maju, dan hanya bisa membuat bound lebih ketat. Tidak ada trade-off akurasi yang perlu dipertimbangkan.
Pertanyaan yang layak dijawab lebih dulu adalah "kapan ini membantu?", karena langkah ini tidak sering membantu pada kumpulan bound maju yang sudah konvergen sepenuhnya.
Penyiapan
Gunakan circuit Ising Trotterisasi yang sama dengan panduan quickstart, dengan observable pada satu qubit di tengah rantai. Geometri 1D membuat efeknya mudah dipikirkan: sebuah pengaruh harus menelusuri rantai satu ikatan per 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
Kapan membantu, dan kapan tidak
tighten_with_speed_limit mengambil bound yang sudah kamu punya dan mengembalikan salinan yang diperketat; bound aslimu tidak pernah diubah. Seberapa besar keuntungannya sepenuhnya bergantung pada seberapa longgar input-nya.
# 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
Baris pertama adalah yang perlu diperhatikan. Pada bound maju yang dihitung penuh, speed limit tidak mengubah apa pun. Itu bukan kegagalan. Artinya bound-nya sudah sama ketatnya dengan yang bisa dibuktikan argumen geometris, karena evolusi operator secara efektif sudah menemukan light cone-nya sendiri.
Keuntungannya muncul di tempat bound-nya tidak dihitung penuh:
-
eigval_max_qubits=0memaksa fallback pertidaksamaan segitiga, sehingga bound tidak lagi mencerminkan norma spektral komutator yang sebenarnya. Speed limit memulihkan sebagian dari kehilangan itu. -
max_num_boxes=2membiarkan sebagian besar layer pada bound trivial 2.0 (bound yang tidak menyandi informasi apa pun). Di sini, speed limit sangat membantu, memotong rata-ratanya sekitar tiga kali lipat.
Dalam setiap kasus kolom "loosened" bernilai nol. Tidak ada suku yang pernah dibuat lebih buruk, dan itulah jaminan yang membuat langkah ini aman diterapkan tanpa syarat.
Yang didapat dalam overhead sampling
Bound yang lebih ketat berarti lebih sedikit suku error yang perlu dimitigasi untuk toleransi bias tertentu, dan angka itulah yang penting di hardware.
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
Kapan memakainya
Terapkan pada bound maju kapan pun bound tersebut belum konvergen sepenuhnya (pada dasarnya, setiap kali kamu menukar akurasi dengan kecepatan memakai opsi apa pun di panduan konfigurasi). Langkah ini murah dibandingkan komputasi bound, tidak bisa membuat apa pun lebih buruk, dan memulihkan sebagian besar yang dikorbankan oleh batas max_num_boxes atau eigval_max_qubits.
Pada bound yang dihitung penuh, langkah ini no-op, jadi tidak ada ruginya menerapkannya tanpa syarat.
Dua keterbatasan:
-
Hanya berlaku untuk bound maju. Bound mundur menyangkut pengaruh error terhadap state awal, bukan terhadap observable, jadi argumennya tidak berlaku.
-
Observable harus berupa satu suku Pauli. Untuk observable dengan beberapa suku, panggil fungsi ini sekali per Pauli dan gabungkan hasilnya sendiri. Jika observable bukan satu suku Pauli, fungsi ini memunculkan
NotImplementedError.
Dalam alur kerja standar, langkah ini ditempatkan antara menghitung bound maju dan menggabungkannya dengan bound mundur, seperti yang ditunjukkan panduan quickstart.