Přeskočit na hlavní obsah
Tento projekt je v betě. Našel jsi chybu? Otevřít issue na GitHubu. Máš otázku nebo nápad? Zeptej se v Discussions — rádi si poslechneme tvou zpětnou vazbu!
Jaká je kvalita překladu na této stránce?

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žnostVýchozíŘídíVliv na výsledek
Přesnost
evolution_max_terms1_000_000Kolik členů operátoru přežije vyvíjeníNižší: levnější, volnější meze
atol_simplify1e-8Které členy komutátoru se zahodí jako zanedbatelnéVyšší: levnější, volnější meze
eigval_max_qubits14Jak široký může být komutátor, aby se spočítala jeho spektrální normaNižší: levnější, volnější meze
atol_eigenvalue1e-10Kdy se iterativní řešič vlastních hodnot považuje za zkonvergovanýVyšší: levnější, méně přesné meze
Rozsah
max_num_boxesNonePro kolik boxů se počítají mezeBoxy za limitem si ponechají triviální mez 2.0
Provádění
timeoutNoneČasový rozpočet (reálný čas) pro celý výpočetZbývající práce si ponechá triviální mez 2.0
num_processes1Kolik 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, 2∑i∣ci∣2 \sum_i |c_i| přes koeficienty cic_i 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í:

  1. num_processes: Zrychlení zdarma, bez ztráty přesnosti.

  2. max_num_boxes: Pokud předem víš, jak hluboko do obvodu ti jde.

  3. eigval_max_qubits: Přeskočí nejnákladnější krok pro nejširší komutátory, které jsou obvykle malou menšinou členů.

  4. evolution_max_terms a atol_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.

  5. 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í.