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?

Detekce chyb s nízkou režií pomocí prostoročasových kódů

V tomto tutoriálu implementujeme postup pro vzorkování z Cliffordova obvodu a použijeme balíček qiskit-paulice ke zvýšení věrnosti naměřeného rozdělení pomocí prostoročasových Pauliho kontrol v celém obvodu. Prostoročasové Pauliho kontroly jsou podobné koherentním Pauliho kontrolám (CPC) v tom, že Cliffordův „datový“ obvod je provázán s některými pomocnými qubity, aby „kontroloval“ určité invarianty v obvodu. Měřením pomocných qubitů vzniká syndrom, který slouží k určení, zda byla během provádění obvodu detekována logická chyba. Postselekce pouze těch vzorků, u nichž nebyla detekována žádná chyba, může zlepšit věrnost vzorkovaných rozdělení za cenu snížení míry postselekce. Klíčový rozdíl mezi CPC a prostoročasovými kontrolami spočívá v tom, že měření syndromů CPC vyžaduje měření časově lokalizovaných operátorů s vysokou váhou. Na topologiích qubitů s omezenou konektivitou, jako je heavy hex, vyžaduje implementace těchto kontrol mnoho hradel SWAP, takže je obvod často příliš hluboký na to, aby se dal prakticky spustit. Implementace Pauliho kontrol jako prostoročasových kódů umožňuje jejich rozložení po celém datovém obvodu, což dovoluje hardwarově efektivní kódování qubitů, které zůstává účinné při detekci logických chyb během provádění obvodu. Hlavní úlohou balíčku qiskit-paulice je najít platné prostoročasové Pauliho kontroly a vložit je na místa v obvodu, která zároveň maximalizují účinnost detekce chyb a minimalizují režii qubitů.

Tento tutoriál implementuje postup pro zvýšení věrnosti rozdělení vzorkovaného z kvantového obvodu pomocí prostoročasových kontrol. Protože použijeme Cliffordův obvod, můžeme věrnost přímočaře odhadnout z průměrných očekávaných hodnot stabilizátorů obvodu. Budeme postupovat v těchto krocích:

  1. Vytvoření obvodu: Mělký 1D cihlový náhodný Cliffordův obvod použitý k vizualizaci kontrol.

  2. Určení qubitů jako cílů kontrol: Namapuj obvod na fyzické qubity na QPU Heron ibm_boston a urči qubity se sousedními pomocnými qubity.

  3. Transpilace obvodu pro backend: Vyber rozložení qubitů a převeď hradla obvodu do nativní sady hradel ibm_boston.

  4. Vytvoření modelu šumu z dat backendu: Vygeneruj přibližný model šumu na základě srovnávacích dat backendu. Tato data o šumu se použijí k ohodnocení účinnosti každé další kontroly.

  5. Vizualizace Pauliho kontrol: Přidej několik Pauliho kontrol do mělkého obvodu, abychom mohli vizualizovat, jak se kontroly přidávají.

  6. Určení stabilizátorů k měření: Zvyš hloubku obvodu, aby se zvýšil vliv šumu hradel. Náhodně vyber podmnožinu stabilizátorů hlubšího obvodu.

  7. Nalezení dobrých kontrol pro celý stav: Najdi jednu sadu kontrol pro stabilizátory stavu, který obvod připravuje. Takové kontroly jsou platné bez ohledu na to, co se měří poté, takže stejný kontrolovaný obvod slouží pro každý vzorkovaný stabilizátor.

  8. Vzorkování ze zašuměného obvodu: Změř holý a kontrolovaný obvod v bázi každého vzorkovaného stabilizátoru pomocí samplomatic a vzorkuj je primitivem Executor na zašuměném simulátoru, který používá stejný depolarizační šum, jaký byl použit k nalezení kontrol.

  9. Postselekce vzorků kontrolovaného obvodu bez detekovaných chyb: Odstraň vzorky, které selhaly alespoň v jedné kontrole. Vypočítej očekávané hodnoty pro holý zašuměný obvod a pro obvod obsahující kontroly.

  10. Porovnání věrností původního a postselektovaného zašuměného stavu: Ukaž, že postselekce pouze vzorků bez detekovaných chyb zlepšuje odhadnutou věrnost zašuměného stavu.

1. Vytvoření obvodu​

Nejprve vytvoříme mělký Cliffordův obvod s provazujícími hradly uspořádanými do cihlové konektivity. Tento malý obvod použijeme k vizualizaci toho, jak se do obvodu přidávají kontroly.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "qiskit_paulice": "qiskit-paulice", "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

def random_clifford_circuit(
num_qubits: int, depth: int, rng: np.random.Generator
) -> QuantumCircuit:
"""Brickwork random Clifford on `num_qubits`, with `depth` CZ layers."""
qc = QuantumCircuit(num_qubits)
qc.h(range(num_qubits))
for d in range(depth):
for i in range(d % 2, num_qubits - 1, 2):
qc.cz(i, i + 1)
for q in range(num_qubits):
if rng.integers(0, 2):
qc.sx(q)
if rng.integers(0, 2):
qc.s(q)
if rng.integers(0, 2):
qc.sx(q)
return qc

num_qubits = 12
depth = 4
seed = 1764
rng = np.random.default_rng(seed)
np.random.seed(seed)
circuit = random_clifford_circuit(num_qubits, depth, rng)
circuit.draw("mpl", fold=-1, scale=0.6)

Output of the previous code cell

2. Určení qubitů jako cílů kontrol​

Dále zvolíme 1D rozložení qubitů na QPU Heron r3 ibm_boston. Zvolíme rozložení, které se diagonálně klikatí spojovacím grafem a maximalizuje počet dostupných pomocných qubitů. K nalezení dostupných pomocných qubitů použijeme funkci get_check_qubits, která vrací seznamy dvojic cílový/pomocný qubit. Pauliho kontrola na target_qubits[i] bude implementována pomocí ancilla_qubits[i].

Ve spojovacím grafu níže jsou zelené qubity datové qubity a oranžové qubity jsou pomocné qubity, které použijeme k implementaci kontrol. Qubity 68, 69, 89, 91, 111, 113 a 133 mají sousední pomocné qubity, takže tyto qubity (odpovídající virtuálním qubitům 0, 1, 3, 5, 7, 9 a 11) použijeme jako cílové qubity pro implementaci kontrol. Všimni si, že qubit 134 sousedí s qubitem 133 v datové části, ale není zvýrazněn jako pomocný qubit. Je to proto, že 133 už byl (libovolně) vybrán k implementaci kontroly s pomocným qubitem 132.

from qiskit.visualization import plot_coupling_map
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_paulice.layout import get_check_qubits

service = QiskitRuntimeService()
backend = service.backend("ibm_boston")

# Choose a layout and pair each available target qubit with a neighboring ancilla
layout = [68, 69, 78, 89, 90, 91, 98, 111, 112, 113, 119, 133]
target_qubits, ancilla_qubits = get_check_qubits(backend, layout)

print(f"Target qubits: {target_qubits}")
print(f"Ancilla qubits: {ancilla_qubits}")
plot_coupling_map(
num_qubits=backend.num_qubits,
qubit_coordinates=getattr(
backend.configuration(), "qubit_coordinates", None
),
coupling_map=backend.configuration().coupling_map,
figsize=(12, 12),
qubit_color=[
"#4CAF50"
if i in set(layout)
else "#FF9800"
if i in set(ancilla_qubits)
else "#DDDDDD"
for i in backend.coupling_map.graph.node_indices()
],
qubit_size=220,
line_width=2,
font_size=90,
)
Target qubits: [68, 69, 89, 91, 111, 113, 133]
Ancilla qubits: [67, 70, 88, 92, 110, 114, 132]

Output of the previous code cell

3. Transpilace obvodu pro backend​

Protože backend už byl vybrán, můžeme náš virtuální obvod transpilovat na obvod ISA. Zde jen nastavíme rozložení qubitů a převedeme hradla do nativní sady hradel ibm_boston.

from qiskit.transpiler import generate_preset_pass_manager

pm = generate_preset_pass_manager(
optimization_level=0, backend=backend, initial_layout=layout
)
circuit_isa = pm.run(circuit)
circuit_isa.draw("mpl", fold=-1, scale=0.6)

Output of the previous code cell

4. Vytvoření modelu šumu z dat backendu​

Teď, když víme, jak bude obvod na QPU rozložen, můžeme modelovat, jak šum hradel a šum čtení na backendu ovlivní provádění obvodu. Model šumu se používá k určení optimálních míst v obvodu pro umístění Pauliho kontrol. Chceme umístit kontroly na pozice, kde zachytí co největší množství chyb. Přesnější model šumu sice může zlepšit schopnost kontrol detekovat chyby, obvykle ale není nutné učit model šumu vzorkováním QPU. Zde odvozujeme model uniformního depolarizačního šumu hradel pouze ze srovnávacích dat backendu z qiskit-ibm-runtime a zahazujeme chybu čtení, kterou také hlásí: kontroly, které najdeme v kroku 7, čtou pouze svůj pomocný qubit, takže chyby čtení datových qubitů jsou pro ně neviditelné a pouze by snížily postselektovanou věrnost v kroku 10. Další informace o zadávání šumu hradel a čtení najdeš v dokumentaci qiskit_paulice.NoiseModel. Pokud chceš použít naučený model šumu Pauli-Lindblad, viz NoiseModel.from_pauli_lindblad_maps.

from dataclasses import replace

from qiskit_paulice.noise_models import NoiseModel

# Model the gate noise as a uniform depolarizing channel based on backend benchmark data
noise_model = replace(
NoiseModel.from_backend(backend, layout, uniform_gate_noise=True),
readout_noise=None,
)
print(noise_model)
NoiseModel(gate_noise=0.0015808382794757693, readout_noise=None, idling_noise=None)

5. Vizualizace Pauliho kontrol​

Nyní přidejme do obvodu nějaké kontroly a podívejme se, jak vypadají. Funkci qiskit_paulice.add_pauli_checks můžeme použít k nalezení dobré sady platných kontrol. Jako vstup zadáme Cliffordův datový obvod spolu se seznamem datových qubitů, na které se mají kontroly přidat (cílové qubity), a modelem šumu, který jsme vytvořili v předchozím kroku. V tomto příkladu přidáme kontroly na všech 7 cílových qubitů s dostupnými pomocnými qubity. Kontroly se přidávají na qubity v pořadí, v jakém se objevují v target_qubits, což usnadňuje úvahy o výsledném rozložení kontrolovaného obvodu: final_layout = layout + ancilla_qubits. Pokud se uživatel rozhodne spustit jeden z výstupních obvodů s menším počtem (i) kontrol, výsledné rozložení by bylo final_layout = layout + ancilla_qubits[:i+1].

Vizualizace obvodu ukazuje, že kontroly byly správně implementovány pomocí zadaných dvojic cílový/pomocný qubit.

Podrobnosti o qiskit_paulice.add_pauli_checks:

  • Prvním vstupem by měl být Cliffordův obvod. Zde nemá žádná měření a předáváme stabilizers="all", takže se kontroly hledají pro stabilizátory stavu, který obvod připravuje: kontrola je platná, pokud její prostoročasový Pauliho součin měří takový stabilizátor, její syndrom je pouze její bit pomocného qubitu a zůstává platná bez ohledu na to, v jaké bázi se datová část následně měří. To je to, co odhad věrnosti potřebuje, protože každý vzorkovaný stabilizátor se měří v jiné bázi. (Vynechání stabilizers naopak hledá kontroly pro koncová měření obvodu.)
  • Druhým poziČním argumentem je seznam indexů cílových qubitů. Každý cílový qubit bude sdílet provazující hradla s pomocným qubitem, aby vytvořil kontrolu. Kontroly se budou hledat a přidávat do obvodu v pořadí, v jakém jsou uvedeny v tomto argumentu.
  • Posledním poziČním argumentem je model šumu představující šum, který se očekává při provádění obvodu. Tento model se používá k vyhodnocení účinnosti dané kontroly.
  • ancilla_qubits: Pokud má vstupní obvod rozložení, tento klíčový argument říká add_pauli_checks, jak přiřadit fyzické qubity nově přidaným pomocným qubitům. ancilla_qubits by měl mít stejnou délku jako target_qubits (2. poziční argument) a ancilla_qubits[i] by se měl použít k implementaci kontrol na target_qubits[i].
  • cost: Nákladová funkce použitá k vyhodnocení kontrol. Může být gamma nebo LER (míra logických chyb), což jsou všechno způsoby, jak popsat, jak velká část kanálu šumu zůstává kontrolami nedetekována. Při stabilizers="all" se chyba počítá jako logická, pokud mění připravený stav, což je přesně to, co věrnost měří.
  • method: Metoda použitá k nalezení platných kontrol. Používáme windowed, která náhodně vzorkuje mnoho prostorových podmnožin oken z nosiče (vodičů) cílového qubitu. Další platné hodnoty jsou genetic a windowed_genetic.
  • Výstup add_pauli_checks obsahuje obvody s rostoucím počtem kontrol, od obvodu bez kontrol až po obvod s kontrolami na každém cílovém qubitu (za předpokladu, že se najdou platné kontroly).

Podrobné informace o hledání dobrých prostoročasových Pauliho kontrol najdeš v oddílech II–IV doplňujících informací

import matplotlib.pyplot as plt
from qiskit_paulice import add_pauli_checks

# Find checks on circuit
checked = add_pauli_checks(
circuit_isa,
target_qubits,
noise_model,
ancilla_qubits=ancilla_qubits,
cost="gamma",
method="windowed",
seed=seed,
stabilizers="all",
)

print(f"Physical layout of payload+ancillas: {layout + ancilla_qubits}")
print("Checked circuit:")
checked[-1].circuit.draw("mpl", fold=-1, idle_wires=False)
Physical layout of payload+ancillas: [68, 69, 78, 89, 90, 91, 98, 111, 112, 113, 119, 133, 67, 70, 88, 92, 110, 114, 132]
Checked circuit:

Output of the previous code cell

6. Určení stabilizátorů k měření​

K otestování výkonu detekce chyb odhadneme věrnost stabilizátorového stavu, ∣ψ⟩=U∣0⟩⊗n|\psi\rangle = U|0\rangle^{\otimes n}, který obvod ideálně připravuje, vůči zašuměnému stavu, ρ\rho, který hardware skutečně vydává. Projektor na čistý stabilizátorový stav, ∣ψ⟩|\psi\rangle, je definován jako rovnoměrný průměr přes 2n2^n prvků jeho stabilizátorové grupy S\mathcal{S}:

∣ψ⟩⟨ψ∣=12n∑G∈SG|\psi\rangle\langle\psi| = \frac{1}{2^n}\sum_{G \in \mathcal{S}} G

Dosazením do rovnice věrnosti vidíme, že věrnost zašuměného stavu, ρ\rho, je rovna průměrné očekávané hodnotě všech stabilizátorů, G∈SG \in \mathcal{S}, vzhledem k ρ\rho:

F=Tr(ρ∣ψ⟩⟨ψ∣)=Tr(12n∑G∈SρG)=12n∑G∈STr(ρG)=12n∑G∈S⟨G⟩ρF = \mathrm{Tr}(\rho|\psi\rangle\langle\psi|) = \mathrm{Tr}({\frac{1}{2^n}\sum_{G \in \mathcal{S}} \rho G}) = \frac{1}{2^n} \sum_{G \in \mathcal{S}} \mathrm{Tr}(\rho G) = \frac{1}{2^n} \sum_{G \in \mathcal{S}} \langle G \rangle_\rho

U větších problémů je měření (nebo i vyjmenování) všech 2n2^n stabilizátorů neproveditelné, takže se věrnost často odhaduje měřením náhodného vzorku stabilizátorů. Rovnoměrně náhodný výběr MM stabilizátorů G1,…,GMG_1, \ldots, G_M z S\mathcal{S} dává nestranný odhad věrnosti, F^M\hat F_M,

F^M=1M∑i=1M⟨Gi⟩ρ\hat F_M = \frac{1}{M} \sum_{i=1}^M \langle G_i \rangle_\rho Pro tento experiment obvod výrazně prohloubíme, aby se zesílil vliv šumu hradel, a přidáme kontroly ke každému qubitu se sousedním pomocným qubitem. Ačkoli existují sofistikovanější způsoby vzorkování stabilizátorů pro odhad věrnosti, v tomto příkladu vybereme 100100 stabilizátorů s rovnoměrnou pravděpodobností.

from qiskit.quantum_info import Clifford, Pauli, PauliList

num_stabilizers = 100
num_shots = 1_000
num_checks = len(target_qubits)
depth = 72

circuit = random_clifford_circuit(num_qubits, depth, rng)
# Create all 2^N stabilizers
stabilizers = PauliList([Pauli("I" * num_qubits)])
for generator in (
Pauli(label) for label in Clifford(circuit).to_labels(mode="S")
):
stabilizers = stabilizers + stabilizers.compose(generator)

# Choose some random stabilizers from the set
keep = np.where(stabilizers.x.any(axis=1) | stabilizers.z.any(axis=1))[0]
chosen = np.random.default_rng(seed).choice(
keep, size=min(num_stabilizers, len(keep)), replace=False
)
stabilizers = stabilizers[chosen]
depth = circuit.depth(lambda x: x.operation.num_qubits == 2)
print(
f"Sampled {len(stabilizers)} stabilizers of {circuit.num_qubits}-qubit circuit with two-qubit depth of {depth}: {{{stabilizers[0]}, {stabilizers[1]}, ...}}"
)
Sampled 100 stabilizers of 12-qubit circuit with two-qubit depth of 72: {-YIIYZZZIZIYY, -XIXYIIZXYIZX, ...}

7. Nalezení dobrých kontrol pro celý stav​

Najdeme jednu sadu kontrol pro hluboký obvod přesně jako v kroku 5. Protože kontroly čtou pouze své pomocné qubity, stejný kontrolovaný obvod slouží pro každý vzorkovaný stabilizátor. Vrácené obvody měří pouze pomocné qubity kontrol, takže měření datových qubitů přidáme v dalším kroku.

import time

# Transpile the deep payload without measurements and find one set of checks for the whole state
payload_isa = pm.run(circuit)
t0 = time.time()
checked = add_pauli_checks(
payload_isa,
target_qubits,
noise_model,
ancilla_qubits=ancilla_qubits,
cost="gamma",
method="windowed",
seed=seed,
stabilizers="all",
)
print(
f"Added {len(checked[-1].check_qubits)} checks in {time.time() - t0:.0f}s."
)
print(
f"2-qubit depth increased from {payload_isa.depth(lambda x: len(x.qubits) == 2)} to "
f"{checked[-1].circuit.depth(lambda x: len(x.qubits) == 2)} when adding {num_checks} checks."
)
Added 7 checks in 3s.
2-qubit depth increased from 72 to 85 when adding 7 checks.

8. Vzorkování ze zašuměného obvodu​

Pro každý vzorkovaný stabilizátor vzorkujeme každou variantu, od holého datového obvodu po obvod se všemi 7 kontrolami. Varianta s nulou kontrol je holý datový obvod se stejnými měřeními, takže slouží jako zašuměná reference. Místo připojování bázové rotace každého stabilizátoru ke kopii každé varianty necháme samplomatic zvolit měřicí bázi v okamžiku vzorkování. Pro každou variantu:

  • Změř datovou část a seskup koncová měření do boxu pomocí boxovacího pass manageru samplomaticu. Box nese anotaci ChangeBasis, takže build vrací šablonový obvod, jehož hradla měnící bázi jsou parametrizovaná, spolu se samplexem, který vypočítá parametry pro požadovanou bázi.

  • Přidej šablonu a samplex do QuantumProgram. Samplex bere jednu měřicí bázi na qubit v boxu, ve vzestupném fyzickém pořadí, zakódovanou jako I=0I=0, Z=1Z=1, X=2X=2, Y=3Y=3. Předáním pole bází s vedoucí osou délky 100 jedna položka změří všech 100 stabilizátorů: datovou část v bázi každého stabilizátoru a pomocné qubity v ZZ. Qubit mimo nosič stabilizátoru se také měří v ZZ a jeho bit se při výpočtu očekávané hodnoty stabilizátoru ignoruje.

Primitivum Executor z qiskit-ibm-runtime spustí celý program v jedné úloze. Pokud mu místo QPU předáš AerSimulator, poběží lokálně na tomto simulátoru, takže mu předáme simulátor se stejným depolarizačním šumem hradel, jaký jsme použili k nalezení kontrol, a použijeme stabilizátorovou metodu, protože všechny obvody jsou Cliffordovy. Qiskit Aer je potřeba pouze k sestavení tohoto simulátoru: lokální režim přebírá seed z voleb Executor, ale jejich noise_model ignoruje, takže šum musí být nastaven na samotném simulátoru. Předání backendu QPU by místo toho spustilo stejný program na hardwaru. Po dokončení simulací vypočítáme míru postselekce a očekávané hodnoty, abychom získali představu o tom, jak dobře detekce chyb fungovala, než data vizualizujeme.

from qiskit import ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel as AerNoiseModel
from qiskit_aer.noise import depolarizing_error
from qiskit_ibm_runtime import Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from samplomatic.builders import build
from samplomatic.transpiler import generate_boxing_pass_manager

# Group the terminal measurements into a box whose measurement basis is chosen at sampling time
boxing_pm = generate_boxing_pass_manager(
enable_gates=False, measure_annotations="change_basis"
)

# bases[i, q]: basis in which physical qubit q is measured for stabilizer i (I=0, Z=1, X=2, Y=3)
bases = np.ones((len(stabilizers), backend.num_qubits), dtype=np.uint8)
bases[:, layout] = stabilizers.z + 2 * stabilizers.x

program = QuantumProgram(shots=num_shots)
for variant in checked:
circ = variant.circuit.copy()
circ.add_register(meas := ClassicalRegister(num_qubits, "meas"))
circ.measure(layout, meas)
template, samplex = build(boxing_pm.run(circ))
(basis_input,) = samplex.inputs().get_specs()
measured_qubits = sorted(layout + list(variant.check_qubits))
program.append_samplex_item(
template,
samplex=samplex,
samplex_arguments={basis_input.name: bases[:, measured_qubits]},
shape=(len(stabilizers),),
)

# Simulate the program with the same gate noise we used to find the checks
aer_nm = AerNoiseModel()
aer_nm.add_all_qubit_quantum_error(
depolarizing_error(noise_model.gate_noise, 2), ["cz"]
)
noisy_sim = AerSimulator(method="stabilizer", noise_model=aer_nm)

# outs[k][register]: bits of the variant with k checks, shape (stabilizers, shots, bits)
t0 = time.time()
outs = (
Executor(noisy_sim, options={"simulator": {"seed_simulator": seed}})
.run(program)
.result()
)
print(
f"Sampled {len(checked)} circuits for {len(stabilizers)} stabilizers in {time.time() - t0:.0f}s."
)
Sampled 8 circuits for 100 stabilizers in 77s.

9. Postselekce vzorků kontrolovaného obvodu​

V tomto balíčku je kontrola implementována pomocí provazujících hradel mezi jedním pomocným qubitem a jedním cílovým qubitem. Každý pomocný qubit začíná v ∣0⟩|0\rangle, takže ZancZ_\text{anc} stabilizuje jeho vstupní stav. Dopředná propagace ZancZ_\text{anc} od začátku pomocného qubitu celým kontrolovaným obvodem dává ZancZ_\text{anc} krát stabilizátor připraveného stavu, takže kontrola projde, pokud je bit pomocného qubitu 00. Vzorek se ponechá, pokud projdou všechny kontroly.

Kontrolované obvody měří pomocné qubity do registru checks_c, takže vzorek se ponechá, pokud je každý bit tohoto registru 0. Obecně metoda get_postselection_method instancí CheckedCircuit vrácených z add_pauli_checks mapuje bitový řetězec jednoho shotu na syndrom každé kontroly najednou; u kontrol nalezených pro stabilizátory připraveného stavu je každý syndrom jen bit jeho pomocného qubitu, takže registr čteme přímo.

V grafu vidíme, že přidáváním dalších kontrol míra postselekce klesá. Ačkoli cílem detekce chyb je zachytit co nejvíce chyb, s klesající mírou postselekce může být k dosažení cílové přesnosti potřeba více shotů. Pokud míra postselekce klesne příliš nízko, počet shotů potřebných ke sběru jednoho logického vzorku se může stát neúnosným. V tomto příkladu se zdá, že míra postselekce konverguje, což naznačuje, že další kontroly přispívají čím dál menší dodatečnou schopností detekce. Přestože přidání dalších Pauliho kontrol obecně poskytuje větší schopnost detekce chyb, přichází za cenu zvýšené hloubky obvodu a zvýšených nákladů na vzorkování.

def keep_mask(out):
"""Per-shot True/False: every ancilla read 0, so every Pauli check passed."""
if "checks_c" not in out: # the bare payload has no checks
return np.ones(out["meas"].shape[:-1], dtype=bool)
return ~out["checks_c"].any(axis=-1)

# masks[k][i]: kept shots of stabilizer i measured on the variant with k checks
masks = [keep_mask(out) for out in outs]
psrs_arr = np.array([mask.mean(axis=-1) for mask in masks]).T
ks = np.arange(psrs_arr.shape[1])

fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(ks, psrs_arr.T, color="darkorange", alpha=0.15, linewidth=1)
ax.plot(
ks,
np.median(psrs_arr, axis=0),
color="black",
linewidth=1,
linestyle="--",
label="median",
)
ax.set(
xlabel="Checks committed",
ylabel="Post-selection rate",
ylim=(0, 1.05),
title=f"Per-stabilizer post-selection rate ({len(psrs_arr)} stabilizers)",
)
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Output of the previous code cell

10. Porovnání věrností původního a postselektovaného zašuměného stavu​

Vidíme, že postselekce pouze vzorků bez detekovaných chyb výrazně zlepšuje očekávanou hodnotu každého stabilizátoru, a tím zlepšuje odhadnutou věrnost stabilizátorového stavu. Přestože postselektované hodnoty byly vypočteny pouze ze vzorků, které prošly každou kontrolou, očekávané hodnoty jsou mnohem přesnější a vzorkovaný rozptyl je nižší. Nyní také vidíme, že míra postselekce výše ve skutečnosti konvergovala k hodnotě podobné věrnosti zašuměného obvodu. Míra postselekce konvergovala, protože kontroly zachytávaly drtivou většinu chyb skutečně vznikajících během provádění obvodu.

def expectation_values(out, mask):
"""EV of each stabilizer from the shots of ``out`` kept by ``mask``.

Bit q of ``meas`` is virtual qubit q, so a stabilizer's eigenvalue in a shot is the parity of
the bits in its support, times its sign.
"""
support = stabilizers.x | stabilizers.z
parity = (out["meas"] & support[:, None, :]).sum(axis=-1) % 2
signs = np.where(stabilizers.phase == 2, -1.0, 1.0)
eigenvalues = np.where(parity, -1.0, 1.0) * signs[:, None]
kept = mask.sum(axis=-1)
return np.where(
kept > 0,
(eigenvalues * mask).sum(axis=-1) / np.maximum(kept, 1),
np.nan,
)

evs_noisy = expectation_values(outs[0], masks[0])
evs_post = expectation_values(outs[-1], masks[-1])
print(
f"ideal fidelity: 1.0\n"
f"noisy fidelity: {np.nanmean(evs_noisy):.4f}\n"
f"postselected fidelity: {np.nanmean(evs_post):.4f}\n"
f"mean post-sel rate: {masks[-1].mean():.3f}\n"
)

evs_ideal = np.ones(len(stabilizers))
idx = np.arange(len(stabilizers))

def cum_mean_sem(ys):
valid = ~np.isnan(ys)
s = np.cumsum(np.where(valid, ys, 0.0))
s2 = np.cumsum(np.where(valid, ys**2, 0.0))
n = np.maximum(np.cumsum(valid).astype(float), 1)
mean = s / n
sem = np.sqrt(np.maximum(s2 / n - mean**2, 0) / n)
return np.where(np.cumsum(valid) > 0, mean, np.nan), sem

def strip(ax, ys, color, label):
m, s = np.nanmean(ys), np.nanstd(ys)
ax.axhspan(
m - s, m + s, color=color, alpha=0.15, label=f"{label} mean ± std"
)
ax.axhline(m, color=color, linewidth=1, linestyle="--")

fig, ax = plt.subplots(figsize=(8, 4))
ax.axhline(np.nanmean(evs_ideal), color="black", linewidth=1.5, label="ideal")
strip(ax, evs_noisy, "red", "noisy")
strip(ax, evs_post, "green", "post-sel")
ax.scatter(
idx, evs_noisy, color="red", s=22, alpha=0.7, zorder=3, label="noisy <G>"
)
ax.scatter(
idx,
evs_post,
color="green",
s=22,
alpha=0.7,
zorder=3,
label="post-sel <G>",
)
ax.set(
xlabel="stabilizer index",
ylabel=r"$\langle G \rangle$",
ylim=(-0.1, 1.1),
title="Per-stabilizer EVs",
)
ax.legend(loc="lower left", ncol=2)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

M = np.arange(1, len(stabilizers) + 1)
fig, ax = plt.subplots(figsize=(8, 4))
for ys, color, label in [
(evs_ideal, "black", "ideal"),
(evs_noisy, "red", "noisy"),
(evs_post, "green", "post-sel"),
]:
cm, sem = cum_mean_sem(ys)
ax.plot(M, cm, color=color, linewidth=1.5, label=label)
ax.fill_between(M, cm - sem, cm + sem, color=color, alpha=0.15)
ax.set(
xlabel="number of stabilizers averaged",
ylabel="running fidelity estimate",
title="Fidelity convergence vs. number of stabilizers",
)
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
ideal fidelity: 1.0
noisy fidelity: 0.5582
postselected fidelity: 0.9747
mean post-sel rate: 0.535

Output of the previous code cell

Output of the previous code cell

Protože jedna sada kontrol slouží každému stabilizátoru, existuje jediná křivka gamma, na kterou se můžeme podívat: hodnota nákladové funkce po každé přidané kontrole. U jiných problémů na jiné sadě fyzických qubitů a s nedokonalým modelem šumu (pamatuj, že náš model šumu v tomto tutoriálu přesně modeloval šum ovlivňující vzorky) může mít pořadí, v jakém se kontroly přidávají na cílové qubity, výraznější vliv na to, jak křivka konverguje.

Konvergence této křivky gamma k 1.0 naznačuje, že další kontroly nebudou schopny detekovat mnoho dalších chyb, protože existuje jen velmi málo modelované chyby, kterou kontroly nepokrývají. To posiluje naše dřívější pozorování, že konvergence míry postselekce je způsobena nedostatkem nepokryté chyby v obvodu.

costs = [variant.cost for variant in checked]

fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(range(len(costs)), costs, color="steelblue", marker="o")
ax.set(
xlabel="Checks committed",
ylabel="Gamma",
yscale="log",
title="Gamma of the noise left uncovered by the checks",
)
ax.grid(True, alpha=0.3, which="both")
plt.tight_layout()
plt.show()

Output of the previous code cell