كيفية تضييق الحدود بحد السرعة
لا تنتشر المعلومات في دارة من البوابات ثنائية الكيوبت بسرعة اعتباطية. فالخطأ الذي يقع قبل النهاية بعدة طبقات، على كيوبت بعيد عن دعم المرصود، لا يملك مساراً للوصول إليه. تحوّل tighten_with_speed_limit هذه الحجة الهندسية (وهي الحدس نفسه وراء حدود Lieb-Robinson) إلى حدود أمامية أضيق.
هذه الخطوة رخيصة، ولا تحتاج إلى شيء يتجاوز ما تطلبته الحدود الأمامية أصلاً، ولا يمكنها إلا أن تجعل الحدود أضيق. لا توجد مقايضة على حساب الدقة يجب وزنها.
السؤال الجدير بالإجابة أولاً هو "متى تفيد؟"، لأنها لا تفيد كثيراً عادةً مع مجموعة حدود أمامية متقاربة بالكامل.
الإعداد
استخدم دارة Ising ذات تقسيم تروتر نفسها الواردة في دليل البدء السريع، مع مرصود على كيوبت واحد في منتصف السلسلة. تجعل الهندسة أحادية البُعد التفكير في التأثير سهلاً: فالتأثير عليه أن يعبر السلسلة رابطة واحدة في كل طبقة.
# 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 (حد لا يحمل أي معلومات). هنا يكون حد السرعة مفيداً، إذ يخفض المتوسط بنحو ثلاثة أضعاف.
في كل الحالات يكون عمود "ما ازداد ارتخاءً" صفراً. لا يُجعل أي حد أسوأ أبداً، وهذا هو الضمان الذي يجعل الخطوة آمنة للتطبيق دون شروط.
ما تقدّمه في تكلفة أخذ العيّنات
تعني الحدود الأضيق عدداً أقل من حدود الخطأ التي تحتاج إلى تخفيف عند تفاوت انحياز معيّن، وهذا هو الرقم المهم على العتاد.
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.
على الحدود المحسوبة بالكامل لا تفعل شيئاً، لذا لا ضرر في تطبيقها دون شروط.
هناك قيدان:
-
تنطبق على الحدود الأمامية فقط. تتعلق الحدود الخلفية بتأثير الخطأ في الحالة الابتدائية وليس في المرصود، لذلك لا تنتقل الحجة إليها.
-
يجب أن يكون المرصود حد باولي واحداً. بالنسبة إلى مرصود ذي عدة حدود، استدعِها مرة لكل حد باولي ثم ادمج النتائج بنفسك. إذا لم يكن المرصود حد باولي واحداً، فإنها تُطلق
NotImplementedError.
في سير العمل المعتاد، مكانها بين حساب الحدود الأمامية ودمجها مع الحدود الخلفية، كما يوضح دليل البدء السريع.