Jak vypočítat vlastní meze
compute_forward_bounds a compute_backward_bounds jsou pohodlné obaly. Oba jsou postaveny ze
stejných tří částí, které veřejné API zpřístupňuje:
-
Normová funkce, která omezuje jeden vyvinutý člen Pauliho chyby
-
Světelný kužel, který určuje, k čemu se členy chyb vyvíjejí
-
compute_bounds, který prochází obvod a rozděluje práci po členech
Jejich složení vlastními silami znamená, že můžeš omezit něco, co obaly nepokrývají. Tento průvodce projde jeden případ, kde se to vyplatí: zrcadlový obvod, kde je obvyklá zpětná mez zbytečně volná.
Co obaly vlastně dělají
Oba obaly se liší ve třech výše uvedených místech:
compute_forward_bounds | compute_backward_bounds | |
|---|---|---|
| obvod | tak, jak je zadán | remove_measure(circuit).inverse() |
| normová funkce | time_evolved_norm_forward, vůči pozorovatelné | time_evolved_norm_backward, vůči počátečnímu stavu |
| světelný kužel | LightCone.initialize_from_pauli, zaset z pozorovatelné | LightCone.initialize_from_measurements, zaset z každého aktivního qubitu |
backwards | False | True |
Dopředná mez se ptá „Jak moc může tato chyba změnit pozorovatelnou, kterou se chystám měřit?“ a odpovídá spektrální normou komutátoru. Zpětná mez se ptá „Jak moc může tato chyba změnit stav, ze kterého jsem začal?“ a odpovídá nukleární normou, přičemž tento stav bere jako stav samých nul na každém aktivním qubitu. Jsou to různé otázky a každá je ta pravá pro chyby ve své vlastní polovině obvodu.
Případ zrcadlového obvodu
Zrcadlový obvod aplikuje nějakou unitární operaci a pak ji vrátí zpět: . Objevují se všude, kde je třeba přesně znát ideální výsledek (obecně Loschmidtovy echo, randomizované benchmarkování a ověřovací experimenty).
Zrcadlové obvody mají vlastnost, kterou obecná zpětná mez nedokáže využít. Protože druhá polovina je inverzí první, vyvíjení chyby zpět k počátečnímu stavu prochází stejná hradla jako vyvíjení vpřed k pozorovatelné. Obě otázky splývají, takže ostřejší mez dopředného typu je platná i pro zpětný směr.
# 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
# The circuit of the quickstart guide...
forward_half = trotter_ising_circuit(
num_qubits, num_steps=4, rx_angle=np.pi / 16, rzz_angle=-np.pi / 3
)
# ...followed by its inverse. The ideal result is the initial state.
circuit = forward_half.compose(forward_half.inverse())
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"mirror circuit: {num_qubits} qubits, depth {circuit.depth()}")
print(f"noisy layers: {len(noise_model_paulis)}")
mirror circuit: 10 qubits, depth 24
noisy layers: 4
Sestavení vlastní meze
Recept ponechává to, co compute_backward_bounds dělá s obvodem (odstraní měření,
invertuje jej a prochází jej s backwards=True) a nahrazuje ostatní dvě části: dopřednou
normovou funkci a světelný kužel zasetý z pozorovatelné pomocí LightCone.initialize_from_pauli,
přesně jako si jej zasévá compute_forward_bounds. Každá náhrada je jeden řádek.
from functools import partial
from qiskit_addon_slc.bounds import (
LightCone,
compute_bounds,
time_evolved_norm_forward,
)
from qiskit_addon_slc.utils import remove_measure
def compute_mirror_backward_bounds(
circuit,
noise_model_paulis,
/,
observable,
*,
evolution_max_terms=1_000_000,
**kwargs,
):
"""Bounds the backward direction of a mirror circuit with a forward-style bound.
Valid only for circuits whose second half inverts the first: the backward evolution then
traverses the same gates as the forward one, and reproduces the forward bounds.
"""
# As `compute_backward_bounds` does: strip measurements and invert, so that walking the circuit
# backwards from its end becomes walking the inverted circuit forwards.
circuit = remove_measure(circuit).inverse()
# The substitution: the forward norm function, bounding against the observable rather than
# against the initial state.
norm_fn = partial(
time_evolved_norm_forward,
observable=observable,
evolution_max_terms=evolution_max_terms,
)
# Seed the light cone from the observable, not from every active qubit.
light_cone = LightCone.initialize_from_pauli(circuit, observable)
return compute_bounds(
circuit,
noise_model_paulis,
light_cone,
norm_fn,
backwards=True,
**kwargs,
)
Kolik to přinese
Porovnání se standardní zpětnou mezí na stejném obvodu, shrnuté stejně jako v průvodci konfigurací: průměrná mez a její směrodatná odchylka, potom podíl členů s mezí 0, s vypočítanou mezí mezi a ponechaných na triviální 2.0.
from qiskit_addon_slc.bounds import (
compute_backward_bounds,
compute_forward_bounds,
)
forward_bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable
)
stock_backward = compute_backward_bounds(boxed_circuit, noise_model_paulis)
custom_backward = compute_mirror_backward_bounds(
boxed_circuit, noise_model_paulis, observable
)
def summarize(label, bounds):
"""Prints a one-line summary of a set of bounds."""
rates = np.concatenate([bound.rates for bound in bounds.values()])
# 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:18} 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%}"
)
summarize("forward", forward_bounds)
summarize("backward (stock)", stock_backward)
summarize("backward (custom)", custom_backward)
forward mean 0.4323 ± 0.679 │ at 0: 38% computed: 61% at 2.0: 1%
backward (stock) mean 1.7218 ± 0.592 │ at 0: 0% computed: 95% at 2.0: 5%
backward (custom) mean 0.4323 ± 0.679 │ at 0: 38% computed: 61% at 2.0: 1%
Standardní zpětná mez leží blízko triviální hodnoty 2.0, která nenese téměř žádnou informaci. Na zrcadlovém obvodu je každá chyba „blízko počátečního stavu“ ve smyslu, který tato mez měří, takže nedokáže rozlišit mezi členy. Vlastní mez je několikanásobně těsnější a mnoho členů omezuje hodnotou 0.
Pro ověření implementace lze obě porovnat box po boxu. U každého boxu vychází vlastní zpětná mez shodně s dopřednou mezí téhož boxu, až na chybu zaokrouhlení. Obě omezují stejnou chybu ve stejném místě obvodu a na zrcadlovém obvodu se hradla po tomto místě vyruší na inverzi hradel před ním, takže oba výpočty vyhodnocují tutéž veličinu z opačných konců:
matches = sum(
np.allclose(forward_bounds[box_id].rates, custom_backward[box_id].rates)
for box_id in forward_bounds
)
print(f"{matches} of {len(forward_bounds)} boxes match the forward bounds")
worst = max(
np.abs(forward_bounds[box_id].rates - custom_backward[box_id].rates).max()
for box_id in forward_bounds
)
print(f"largest disagreement across all terms: {worst:.2e}")
16 of 16 boxes match the forward bounds
largest disagreement across all terms: 6.00e-15
Poznámka: Tato rovnost je vlastností zrcadlové struktury, ne techniky. Na obvodu, který není sám sobě inverzní, stejná náhrada stále běží, ale už nepočítá platnou mez pro zpětný směr: odpovídá na dopřednou otázku o polovině obvodu, kde tato otázka neplatí. Použij to jen tehdy, když druhá polovina tvého obvodu invertuje první.
Zapojení do zbytku pracovního postupu
Výsledkem je běžný slovník Bounds, takže vše, co následuje, zůstává beze změny.
Na zrcadlovém obvodu také není důvod počítat kteroukoli mez pro celý obvod. Dopředné
meze jsou potřeba jen pro boxy , počítané od konce, a zpětné
meze jen pro boxy , počítané od začátku. max_num_boxes proto omezuje každou z
nich na polovinu zašuměných boxů a merge_bounds obě poloviny spojí.
Takto se meze používají v praxi, a znamená to, že porovnání box po boxu výše už není k dispozici: potřebuje obě meze pro každý box. Je to kontrola implementace pro tento průvodce, ne krok pracovního postupu.
from qiskit.quantum_info import PauliLindbladMap, QubitSparsePauliList
from qiskit_addon_slc.bounds import compute_local_scales, merge_bounds
# 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()
}
# Compute each bound for its own half of the mirror circuit only. `forward_bounds` has an entry
# for every noisy box, which are the boxes `max_num_boxes` counts.
half = len(forward_bounds) // 2
half_forward = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable, max_num_boxes=half
)
half_stock = compute_backward_bounds(
boxed_circuit, noise_model_paulis, max_num_boxes=half
)
half_custom = compute_mirror_backward_bounds(
boxed_circuit, noise_model_paulis, observable, max_num_boxes=half
)
print(
f"each bound computed for {half} of {len(forward_bounds)} noisy boxes\n"
)
print(f"{'bounds':34} {'overhead':>12} {'residual bias':>14}")
for label, bounds in (
(
"forward + stock backward",
merge_bounds(boxed_circuit, half_forward, half_stock, noise_rates),
),
(
"forward + custom backward",
merge_bounds(boxed_circuit, half_forward, half_custom, noise_rates),
),
("forward only, whole circuit", forward_bounds),
):
_, cost, bias = compute_local_scales(
boxed_circuit, bounds, noise_rates, bias_tolerance=0.05
)
print(f"{label:34} {cost:12.4f} {bias:14.4f}")
Optimal spacetime partitioning not implemented! Just partitioning list of noisy boxes.
Optimal spacetime partitioning not implemented! Just partitioning list of noisy boxes.
each bound computed for 8 of 16 noisy boxes
bounds overhead residual bias
forward + stock backward 29848.0583 0.0498
forward + custom backward 465.4028 0.0498
forward only, whole circuit 465.4028 0.0498
Se standardními zpětnými mezemi je režie vzorkování mnohem větší. merge_bounds používá
zpětné meze do jednoho bodu přepnutí v obvodu a odtud dále dopředné meze,
přičemž volí bod, který minimalizuje celkové zkreslení. Když je každá mez počítána pro jednu polovinu, tento bod
padne doprostřed obvodu, takže boxy jsou pokryty samotnými zpětnými mezemi. Standardní
zpětná mez je tam blízko triviální 2.0, což režii zvyšuje. Vlastní
mez je stejně těsná, jako by byla dopředná mez, takže režie odpovídá režii úplných
dopředných mezí.
Nabízí se zřejmá otázka, proč se obtěžovat, když úplné dopředné meze samy dávají stejný výsledek. Odpověď jsou náklady. Dopředná mez pro box na začátku obvodu vyvíjí jeho chybu skrz zbytek a poté celé , což je nejdelší a nejširší vyvíjení v obvodu. Vyvinutý operátor může růst exponenciálně v počtu non-Cliffordových hradel, kterými prochází, a výpočet spektrální normy komutátoru je exponenciálně dražší, jak se světelný kužel rozšiřuje. U přístupu půl na půl se žádná chyba nevyvíjí skrz více než polovinu obvodu.
Úspora proto roste s hloubkou obvodu. Co je důležitější, úplné dopředné
meze přestanou být vypočitatelné dlouho předtím, než polovičné. Jakmile vyvíjení raných
boxů překročí evolution_max_terms nebo eigval_max_qubits, jejich meze se zkrátí nebo přejdou
na trojúhelníkovou nerovnost a nakonec je jejich výpočet příliš drahý. Ať ten bod leží kdekoli,
přístup půl na půl potřebuje jen to, aby padl někam do první poloviny obvodu.
Dopředné meze nemusí sahat dál než do poloviny a na zrcadlovém obvodu zpětné
meze pro boxy vyvíjejí své chyby zpět na začátek skrz stejně velkou část
obvodu jako dopředné meze pro odpovídající boxy až na konec, takže jim dojdou síly
ve stejné hloubce.
Poznámka: Proč počítat zpětné meze pro boxy , když dopředné meze boxů vypadají, že by měly mít stejná čísla? Protože boxy nejsou svými zrcadlovými obrazy. Každý box vkládá svůj šum na jedné straně svých hradel a inverzní vrstvy jsou samostatné boxy s vlastními modely šumu, takže box, který zrcadlí box z , vidí jeho chyby v jiném místě obvodu. Proto se jejich meze shodují jen částečně a meze pro boxy je nutné počítat pro tyto boxy.
Psaní dalších vlastních mezí
Stejné tři náhrady pokrývají další případy:
-
Jiná norma.
time_evolved_norm_forwardpřijímácomm_norm_order; zadání čehokoli jiného než2přeskočí řešič vlastních hodnot a tuto normu spočítá přímo. -
Jiný světelný kužel.
LightCone.initialize_from_paulizaseje světelný kužel z libovolného Pauliho členu a konstruktorLightConebere qubity ke sledování a operace, které jej zasévají, takže mez lze spočítat vůči libovolnému operátoru, který tak umíš vyjádřit. -
Úplně jiná normová funkce.
compute_boundsvyžaduje jen volatelný objekt: který berePauliaRotationGatesa vracíCommutatorBounds. Cokoli splňující tento podpis funguje, včetně meze v uzavřeném tvaru specifické pro tvou rodinu obvodů.
Ať nahradíš cokoli, musíš dokázat, že výsledek je skutečná horní mez. Podhodnocení vlivu členu chyby nevede k viditelnému selhání. Vede ke střední hodnotě, která je tiše špatná, protože člen bude vypuštěn z modelu šumu použitého pro mitigaci.