スピードリミットを使って上界を厳密化する方法
2量子ビットゲートの回路では、情報は任意に速く伝わることはできません。終端の数レイヤー前に、オブザーバブルの台から遠い量子ビットで発生したエラーは、オブザーバブルに到達する経路を持ちません。tighten_with_speed_limitは、この幾何学的な議論(リーブ・ロビンソン上界と同じ直感)を、より厳密な順方向の上界に変換します。
このステップは低コストで、順方向の上界がすでに必要としていたもの以外は何も必要とせず、上界をより厳密にすることしかできません。精度とのトレードオフを検討する必要はありません。
最初に答えるべき問いは*「いつ役に立つのか」*です。完全に収束した順方向の上界の集合に対しては、役に立つことはあまりないからです。
セットアップ
クイックスタートガイドと同じトロッター化されたイジング回路を使用し、オブザーバブルはチェーンの中央の単一量子ビットに置きます。1次元の幾何構造により、効果を考えやすくなります。影響は、1レイヤーにつき1つの結合ずつチェーンをたどらなければなりません。
# 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
注目すべきは最初の行です。完全に計算された順方向の上界では、スピードリミットは何も変えません。これは失敗ではありません。演算子の発展によって、ライトコーンがすでに実質的に見つかっているため、上界がすでに幾何学的な議論で証明できるものと同等以上に厳密だったことを意味します。
改善が現れるのは、上界が完全には計算されていない場合です。
-
eigval_max_qubits=0は三角不等式によるフォールバックを強制するため、上界はもはや交換子の実際のスペクトルノルムを反映しません。スピードリミットは、その損失の一部を取り戻します。 -
max_num_boxes=2は、ほとんどのレイヤーを自明な上界である2.0(情報を何も持たない上界)のままにします。ここではスピードリミットが有効で、平均値をおよそ3分の1に減らします。
どの場合でも、「緩くなった」列はゼロです。悪化する項は1つもなく、これがこのステップを無条件に適用しても安全である根拠になります。
サンプリングオーバーヘッドで得られるもの
上界が厳密になると、所与のバイアス許容度に対して緩和する必要があるエラー項が少なくなります。これが、ハードウェア上で重要になる数値です。
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
使うタイミング
順方向の上界が完全には収束していない場合は常に適用してください(基本的には、設定ガイドのいずれかのオプションを使って、速度と引き換えに精度を犠牲にした場合です)。上界の計算に比べて低コストで、何も悪化させることがなく、max_num_boxesやeigval_max_qubitsの制限によって失われたものの多くを取り戻せます。
完全に計算された上界では何も起こらないため、無条件に適用しても問題はありません。
2つの制限があります。
-
順方向の上界にのみ適用されます。逆方向の上界は、エラーがオブザーバブルではなく初期状態に与える影響に関するものなので、この議論は当てはまりません。
-
オブザーバブルは単一のパウリ項でなければなりません。複数の項を持つオブザーバブルの場合は、パウリごとに1回ずつ呼び出し、結果は自分で組み合わせてください。オブザーバブルが単一のパウリ項でない場合は
NotImplementedErrorが発生します。
標準的なワークフローでは、クイックスタートガイドに示されているように、順方向の上界を計算してから逆方向の上界とマージするまでの間に置きます。