Sari la conținutul principal
Acest proiect este în versiune beta. Ai găsit o eroare? Deschide un issue pe GitHub. Ai o întrebare sau o idee? Întreabă în Discussions — ne-ar plăcea să aflăm părerea ta!
Cum este calitatea traducerii pe această pagină?

Cum să strângi limitele cu o limită de viteză

Informația dintr-un circuit de porți cu doi qubiți nu se poate răspândi arbitrar de repede. O eroare care apare cu mai multe straturi înainte de final, pe un qubit departe de suportul observabilei, nu are nicio cale de a-l atinge. tighten_with_speed_limit transformă acest argument geometric (aceeași intuiție din spatele limitelor Lieb-Robinson) în limite înainte mai strânse.

Pasul este ieftin, nu are nevoie de nimic în afară de ceea ce cereau deja limitele înainte și poate doar să facă limitele mai strânse. Nu există niciun compromis de acuratețe de luat în calcul.

Întrebarea la care merită să răspundem mai întâi este „când ajută?”, deoarece nu ajută adesea pe un set de limite înainte complet convergente.

Configurare​

Folosește același circuit Ising Trotterizat ca în ghidul quickstart, cu observabila pe un singur qubit din mijlocul lanțului. Geometria 1D face efectul ușor de înțeles: o influență trebuie să parcurgă lanțul câte o legătură pe strat.

# 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

Când ajută și când nu​

tighten_with_speed_limit preia limitele pe care le ai deja și returnează o copie strânsă; limitele tale originale nu sunt modificate niciodată. Cât câștigă depinde în întregime de cât de lejere erau cele de intrare.

# 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

Prima linie este cea de observat. Pe limitele înainte calculate complet, limita de viteză nu schimbă nimic. Nu este un eșec. Înseamnă că limitele erau deja cel puțin la fel de strânse cât poate dovedi argumentul geometric, deoarece evoluția operatorului a descoperit efectiv conul de lumină singură.

Câștigurile apar acolo unde limitele nu au fost calculate complet:

  • eigval_max_qubits=0 forțează varianta de rezervă cu inegalitatea triunghiului, astfel încât limitele nu mai reflectă norma spectrală reală a comutatorului. Limita de viteză recuperează o parte din această pierdere.

  • max_num_boxes=2 lasă majoritatea straturilor la limita trivială de 2.0 (o limită care nu codifică nicio informație). Aici, limita de viteză ajută, reducând media de aproximativ trei ori.

În fiecare caz coloana „loosened” este zero. Niciun termen nu este înrăutățit vreodată, ceea ce este garanția care face pasul sigur de aplicat necondiționat.

Ce aduce în overhead-ul de eșantionare​

Limite mai strânse înseamnă mai puțini termeni de eroare care trebuie atenuați pentru o toleranță dată a părtinirii, ceea ce este numărul care contează pe 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

Când să-l folosești​

Aplică-l limitelor înainte ori de câte ori acestea nu sunt complet convergente (în esență, ori de câte ori ai sacrificat acuratețea pentru viteză folosind oricare dintre opțiunile din ghidul de configurare). Este ieftin în comparație cu calculul limitelor, nu poate înrăutăți nimic și recuperează mare parte din ce a cedat o limită max_num_boxes sau eigval_max_qubits.

Pe limite calculate complet nu face nimic, deci nu este niciun rău în a-l aplica necondiționat.

Două limitări:

  • Se aplică doar limitelor înainte. Limitele înapoi privesc efectul erorii asupra stării inițiale, nu asupra observabilei, deci argumentul nu se transferă.

  • Observabila trebuie să fie un singur termen Pauli. Pentru o observabilă cu mai mulți termeni, apelează aceasta o dată pentru fiecare Pauli și combină rezultatele singur. Dacă observabila nu este un singur termen Pauli, generează un NotImplementedError.

În fluxul de lucru standard, își are locul între calculul limitelor înainte și îmbinarea lor cu limitele înapoi, așa cum arată ghidul quickstart.