Jak ladit konfigurační možnosti
Výpočet mezí je v principu přesný, ale v praxi přibližný: vyvíjení členu Pauliho chyby obvodem vytváří operátor s exponenciálně mnoha členy a nalezení spektrální normy výsledného komutátoru je samo o sobě nákladné. Níže uvedené možnosti řídí tyto náklady. Dělí se do tří skupin:
-
Možnosti Přesnost mění numerickou hodnotu mezí a vyměňují těsnost za náklady.
-
Rozsah určuje, které meze se počítají.
-
Možnosti Provádění mění, jak výpočet probíhá (nikoli co počítá).
| Možnost | Výchozí | Řídí | Vliv na výsledek |
|---|---|---|---|
| Přesnost | |||
evolution_max_terms | 1_000_000 | Kolik členů operátoru přežije vyvíjení | Nižší: levnější, volnější meze |
atol_simplify | 1e-8 | Které členy komutátoru se zahodí jako zanedbatelné | Vyšší: levnější, volnější meze |
eigval_max_qubits | 14 | Jak široký může být komutátor, aby se spočítala jeho spektrální norma | Nižší: levnější, volnější meze |
atol_eigenvalue | 1e-10 | Kdy se iterativní řešič vlastních hodnot považuje za zkonvergovaný | Vyšší: levnější, méně přesné meze |
| Rozsah | |||
max_num_boxes | None | Pro kolik boxů se počítají meze | Boxy za limitem si ponechají triviální mez 2.0 |
| Provádění | |||
timeout | None | Časový rozpočet (reálný čas) pro celý výpočet | Zbývající práce si ponechá triviální mez 2.0 |
num_processes | 1 | Kolik pracovních procesů počítá meze paralelně | Samo o sobě žádný |
Mez je užitečná, jen pokud je platná: nikdy nesmí podhodnotit vliv členu chyby na
pozorovatelnou. Uvolnění kterékoli z těchto možností kromě atol_eigenvalue může meze pouze
zvětšit (konzervativnější), nikdy zmenšit. Cena volného nastavení je tedy větší režie
vzorkování, nikoli chybná střední hodnota - výjimkou je atol_eigenvalue.
Definice obvodu
Následující experimenty používají stejný trotterizovaný Isingův obvod a pozorovatelnou jako rychlý úvod. Při této velikosti má každá možnost viditelný efekt, přičemž každý výpočet stále trvá méně než sekundu.
# 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
Malá pomocná funkce shrnuje každou sadu mezí do jednoho řádku, takže efekt možnosti se projeví jako pár čísel, a ne jako zeď polí.
Každá mez leží mezi 0 a triviální mezí 2.0. Souhrn uvádí průměrnou mez a její směrodatnou odchylku, a poté jak se členy dělí do těchto skupin:
- Členy s mezí přesně 0, protože komutují s vyvinutou pozorovatelnou
- Členy s vypočítanou mezí mezi 0 a 2.0
- Členy ponechané na triviální 2.0
Všimni si, že nižší průměr je těsnější a těsnější znamená menší režii vzorkování později.
Průchod jednou možností označuje výchozí hodnotu možnosti znakem *:
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
Přesnost
evolution_max_terms
Vyvíjení členu Pauliho chyby skrz non-Cliffordova hradla jej mění na součet Pauliho členů a tento součet
roste s hloubkou obvodu. evolution_max_terms omezuje, kolik z těchto členů se ponechá. 1-norma
toho, co se zahodí, se přičte k mezi, takže se nic tiše neztratí.
Průchod jde od těsnějšího, než je výchozí, až po extrémní oříznutí. Snížení limitu zlevňuje výpočet a meze se uvolňují.
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
Zvýšení limitu nad výchozí hodnotu tu nic nemění. Pro obvod této velikosti výchozí hodnota již ponechává každý člen, takže meze jsou tak těsné, jak je tato možnost dokáže udělat. To je typická situace. Zpřísnění možnosti přesnosti se vyplatí, až když její výchozí hodnota skutečně omezuje, a zjistíš to experimentem výše. Spusť jej znovu s přísnější hodnotou a podívej se, zda se meze pohnou.
Varování: Základní propagační rutina bere evolution_max_terms jako kapacitu k
předalokování, ne jako horní limit, proti kterému se kontroluje. Zadání něčeho nominálně neomezeného, například
numpy.iinfo(numpy.uint).max, žádá alokátor o terabajty paměti a ukončí proces.
Použij realistické číslo. Výchozí hodnota jednoho milionu je již štědrá.
atol_simplify
Po vytvoření komutátoru se členy s koeficientem pod atol_simplify zahodí. Stejně jako u
limitu vyvíjení se takto ztracená 1-norma přičte k mezi.
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
Nejtěsnější dostupnou mezí je spektrální norma komutátoru, která vyžaduje výpočet vlastních hodnot.
To se s rostoucí šířkou komutátoru stává nákladným, proto eigval_max_qubits nastavuje šířku,
nad kterou se pokus opustí ve prospěch trojúhelníkové nerovnosti. Tato mez,
přes koeficienty komutátoru, je platná, ale volnější.
Nastavení na nulu vynutí náhradní postup všude, což ukáže přínos řešiče vlastních hodnot v jediném čísle.
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
I při vypnutém řešiči vlastních hodnot zůstává průměr hluboko pod triviální 2.0. Podíl členů s mezí 0 se nemění: člen, který přesně komutuje s vyvinutou pozorovatelnou, má nulový komutátor, což se pozná dříve, než se vůbec uvažuje výpočet vlastních hodnot. U ostatních trojúhelníková mez u některých dosáhne 2.0 a tam se omezí, ale u mnoha je stále menší, takže si ponechají vypočítanou mez pod 2.0.
atol_eigenvalue
Když se spektrální norma počítá, je atol_eigenvalue tolerance konvergence
iterativního řešiče vlastních hodnot. Většina komutátorů se k tomuto řešiči ale nikdy nedostane:
-
Komutátor působící nejvýše na čtyři qubity se diagonalizuje přesně, přímo.
-
Širší komutátor se nejprve zredukuje na nejmenší počet qubitů, který jeho Pauliho členy potřebují, a pokud tato redukovaná podoba působí nejvýše na osm qubitů, diagonalizuje se přesně.
Pouze komutátor, který po této redukci stále potřebuje více než osm qubitů, jde k iterativnímu řešiči, což je jediné místo, kde se tato tolerance uplatní. Obvody velikosti použité v tomto průvodci se tam nikdy nedostanou, takže průchod touto možností by ukázal stejné meze pro každou hodnotu. Záleží na ní pro široké světelné kužele na velkých zařízeních.
Na rozdíl od ostatních možností přesnosti není uvolnění atol_eigenvalue v jednom směru bezpečné. Iterativní
řešič vlastních hodnot se k extrémní vlastní hodnotě blíží zevnitř spektra, takže odhad,
který plně nezkonvergoval, podhodnocuje spektrální normu, a tím i mez. Výchozí hodnota je z tohoto
důvodu záměrně těsná. Pokud řešič nezkonverguje, převezme to náhradní postup
s trojúhelníkovou nerovností, který je opět platný.
Rozsah
max_num_boxes
Někdy užitečným limitem není přesnost, ale hloubka. max_num_boxes přestane spouštět výpočty mezí
po daném počtu boxů a každé vrstvě za ním se místo toho přiřadí triviální mez
2.0.
Limit počítá zašuměné instrukce BoxOp (ty nesoucí anotaci InjectNoise), a ne
například hloubku v hradlech. Díky tomu zůstává svázán s uživatelsky viditelnou strukturou obvodu
a vyhýbá se vazbě na automaticky generované reference anotací, které se mohou mezi
běhy transpilace měnit.
To je přirozená volba pro obvod, jehož dvě poloviny se řeší dopřednými a zpětnými mezemi postupně: každou počítej jen tak hluboko, jak bude využita.
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
Provádění
timeout
timeout je časový rozpočet v sekundách (reálný čas). Když vyprší, už vypočítané meze se ponechají
a vše nevyřízené si ponechá triviální mez 2.0, takže výsledek je vždy použitelný, jen
volnější, čím dříve ho přerušíš.
Není to pevný termín: již běžící úlohy smějí doběhnout, takže volání může rozpočet překročit o dobu trvání jedné úlohy.
num_processes
Meze pro různé členy chyb jsou nezávislé, proto je num_processes rozděluje mezi pracovní
procesy. Samo o sobě to ovlivňuje jen dobu výpočtu, nikdy jeho výsledek.
Poznámka: timeout v kombinaci s num_processes způsobí, že výsledek závisí na časování: které úlohy
doběhnou před vypršením rozpočtu, není deterministické. Meze zůstávají platné, ale už nejsou
reprodukovatelné z běhu na běh.
Kde začít
Výchozí hodnoty jsou zvoleny tak, aby dávaly těsné meze u obvodů velikosti, kde je to cenově dostupné. Když je výpočet příliš pomalý, zkus použít možnosti zhruba v tomto pořadí:
-
num_processes: Zrychlení zdarma, bez ztráty přesnosti. -
max_num_boxes: Pokud předem víš, jak hluboko do obvodu ti jde. -
eigval_max_qubits: Přeskočí nejnákladnější krok pro nejširší komutátory, které jsou obvykle malou menšinou členů. -
evolution_max_termsaatol_simplify: Ty ovlivňují přesnost všude, takže je ladi nakonec a pomocí průchodu jako ty výše zjisti, jak moc se meze pohnou. -
timeout: Tvrdé zastavení spíše než pomůcka k ladění, pro případ, kdy výpočet musí skončit do termínu, bez ohledu na kvalitu.
Čím volnější meze, tím větší režii vzorkování compute_local_scales ohlásí pro
danou toleranci zkreslení. Tato režie je veličina, kterou stojí za to při ladění sledovat: říká ti,
kolik aproximace skutečně stojí.