Error detection na mababa ang overhead gamit ang mga spacetime code
Sa tutorial na ito, magpapatupad tayo ng workflow para mag-sample mula sa isang Clifford circuit at gagamitin ang package na qiskit-paulice para pataasin ang fidelity ng nasukat na distribusyon sa pamamagitan ng paglalapat ng spacetime Pauli checks sa buong circuit. Katulad ng coherent Pauli checks (CPC) ang spacetime Pauli checks dahil ang isang Clifford na "payload" circuit ay ina-entangle sa ilang ancillary qubit para "i-check" ang ilang invariant sa circuit. Ang pagsukat sa mga ancilla ay gumagawa ng syndrome na ginagamit para matukoy kung may logical error na nakita habang isinasagawa ang circuit. Ang pag-postselect lang ng mga sample na walang nakitang error ay maaaring magpabuti ng fidelity ng mga na-sample na distribusyon kapalit ng pagbaba ng postselection rate. Ang pangunahing pagkakaiba ng CPC at spacetime checks ay ang pagsukat ng mga CPC syndrome ay nangangailangan ng pagsukat ng mga operator na time-localized at mataas ang weight. Sa mga qubit topology na limitado ang connectivity, tulad ng heavy hex, ang pagpapatupad ng mga check na ito ay nangangailangan ng maraming SWAP gate, na madalas ginagawang masyadong malalim ang circuit para patakbuhin sa praktika. Ang pagpapatupad ng mga Pauli check bilang spacetime code ay nagpapahintulot na ipamahagi ang mga ito sa buong payload circuit, na nagbibigay-daan sa hardware-efficient na qubit encoding na nananatiling epektibo sa pagtukoy ng mga logical error habang isinasagawa ang circuit. Ang pangunahing papel ng package na qiskit-paulice ay maghanap at magpasok ng mga wastong spacetime Pauli check sa mga lokasyon ng circuit na parehong nagpapalaki ng bisa ng error detection at nagpapaliit ng qubit overhead.
Nagpapatupad ang tutorial na ito ng workflow para pataasin ang fidelity ng distribusyong na-sample mula sa isang quantum circuit gamit ang spacetime checks. Dahil gagamit tayo ng Clifford circuit, madali nating matatantya ang fidelity mula sa averaged expectation value ng mga stabilizer ng circuit. Susundin natin ang mga hakbang na ito:
-
Gumawa ng circuit: Mababaw na 1D brickwork na random Clifford circuit na ginagamit para i-visualize ang mga check.
-
Tukuyin ang mga qubit bilang target ng mga check: I-map ang circuit sa mga pisikal na qubit sa
ibm_bostonHeron QPU at tukuyin ang mga qubit na may katabing ancilla. -
I-transpile ang circuit sa backend: Pumili ng qubit layout at isalin ang mga gate ng circuit sa native gate set ng
ibm_boston. -
Gumawa ng noise model mula sa data ng backend: Gumawa ng magaspang na noise model batay sa benchmark data ng backend. Gagamitin ang noise data na ito para i-score ang bisa ng bawat karagdagang check.
-
I-visualize ang mga Pauli check: Magdagdag ng ilang Pauli check sa mababaw na circuit, para makita natin kung paano idinaragdag ang mga check.
-
Tukuyin ang mga stabilizer na susukatin: Dagdagan ang depth ng circuit para lumaki ang epekto ng gate noise. Mag-sample nang random ng subset ng mga stabilizer ng mas malalim na circuit.
-
Maghanap ng magagandang check para sa buong state: Maghanap ng isang set ng mga check para sa mga stabilizer ng state na inihahanda ng circuit. Wasto ang mga ganitong check anuman ang susukatin pagkatapos, kaya iisang checked circuit ang magagamit para sa bawat na-sample na stabilizer.
-
Mag-sample mula sa maingay na circuit: Sukatin ang bare at checked na mga circuit sa basis ng bawat na-sample na stabilizer gamit ang samplomatic, at i-sample ang mga ito gamit ang
Executorprimitive sa isang maingay na simulator na gumagamit ng parehong depolarizing noise na ginamit sa paghahanap ng mga check. -
I-postselect ang mga sample ng checked circuit na walang nakitang error: Alisin ang mga sample na bumagsak sa kahit isang check. Kalkulahin ang mga expectation value para sa bare na maingay na circuit at sa circuit na may mga check.
-
Ihambing ang mga fidelity ng orihinal at postselected na maingay na state: Ipakita na ang pag-postselect lang ng mga sample na walang nakitang error ay nagpapabuti sa tinatantyang fidelity ng maingay na state.
1. Gawin ang circuit
Una, gagawa tayo ng mababaw na Clifford circuit na ang mga entangling gate ay nakalatag sa brickwork na connectivity. Gagamitin natin ang maliit na circuit na ito para i-visualize kung paano idinaragdag ang mga check sa circuit.
# 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)

2. Tukuyin ang mga qubit bilang target ng mga check
Susunod, pipili tayo ng 1D qubit layout sa Heron r3 QPU, ibm_boston. Pipili tayo ng layout na zig-zag na dumadaan nang pahilis sa coupling graph at nagpapalaki ng bilang ng mga available na ancilla qubit. Para maghanap ng mga available na ancilla, gagamitin natin ang function na get_check_qubits, na nagbabalik ng mga listahan ng target/ancilla qubit pair. Ang isang Pauli check sa target_qubits[i] ay ipapatupad gamit ang ancilla_qubits[i].
Sa coupling graph sa ibaba, ang mga berdeng qubit ay ang mga payload qubit, at ang mga kahel na qubit ay ang mga ancilla na gagamitin natin para ipatupad ang mga check. Ang mga qubit na 68, 69, 89, 91, 111, 113, at 133 ay may katabing ancilla, kaya gagamitin natin ang mga qubit na iyon (na tumutugma sa mga virtual qubit na 0, 1, 3, 5, 7, 9, at 11) bilang mga target qubit para sa pagpapatupad ng mga check. Tandaan na ang qubit na 134 ay katabi ng qubit na 133 sa payload pero hindi naka-highlight bilang ancilla. Ito ay dahil ang 133 ay napili na (basta-basta) para magpatupad ng check kasama ang ancilla na 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]

3. I-transpile ang circuit sa backend
Dahil napili na ang backend, maaari na nating i-transpile ang ating virtual circuit sa isang ISA circuit. Dito, itinatakda lang natin ang qubit layout at isinasalin ang mga gate sa native gate set ng 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)

4. Gumawa ng noise model mula sa data ng backend
Ngayong alam na natin kung paano ilalatag ang circuit sa QPU, maaari nating imodelo kung paano maaapektuhan ng gate at readout noise sa backend ang pagpapatakbo ng circuit. Ginagamit ang noise model para tukuyin ang pinakamainam na lugar sa circuit para maglagay ng mga Pauli check. Gusto nating maglagay ng mga check sa mga posisyong nakakakuha ng pinakamaraming error. Bagama't maaaring mapabuti ng mas tumpak na noise model ang kakayahan ng mga check sa pagtukoy, kadalasang hindi kailangang matuto ng noise model sa pamamagitan ng pag-sample sa QPU. Dito, ibinabatay natin ang isang uniform depolarizing gate noise model gamit lang ang benchmark data ng backend mula sa qiskit-ibm-runtime, at inaalis ang readout error na iniuulat din nito: ang mga check na makikita natin sa hakbang 7 ay ang ancilla lang nila ang binabasa, kaya hindi nakikita ng mga ito ang mga payload readout error at magpapababa lang ang mga iyon sa postselected na fidelity sa hakbang 10. Tingnan ang dokumentasyon ng qiskit_paulice.NoiseModel para sa karagdagang impormasyon sa pagtukoy ng gate at readout noise. Para sa mga user na gustong gumamit ng natutunang Pauli-Lindblad noise model, tingnan ang 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. I-visualize ang mga Pauli check
Magdagdag tayo ngayon ng ilang check sa circuit at tingnan kung ano ang itsura ng mga ito. Magagamit natin ang function na qiskit_paulice.add_pauli_checks para maghanap ng magandang set ng mga wastong check. Ibibigay natin ang Clifford payload circuit bilang input, kasama ang listahan ng mga payload qubit na paglalagyan ng mga check (mga target qubit) at ang noise model na ginawa natin sa nakaraang hakbang. Sa halimbawang ito, magdaragdag tayo ng mga check sa lahat ng 7 target qubit na may available na ancilla. Idinaragdag ang mga check sa mga qubit ayon sa pagkakasunod nila sa target_qubits, kaya madaling pag-isipan ang huling layout ng checked circuit: final_layout = layout + ancilla_qubits. Kung magpasya ang user na mas gusto niyang patakbuhin ang isa sa mga output circuit na mas kaunti (i) ang check, ang huling layout ay magiging final_layout = layout + ancilla_qubits[:i+1].
Ipinapakita ng visualization ng circuit na tama ang pagpapatupad ng mga check gamit ang tinukoy na mga target/ancilla pair.
Mga detalye tungkol sa qiskit_paulice.add_pauli_checks:
- Dapat Clifford circuit ang unang input. Dito, wala itong mga measurement at ipinapasa natin ang
stabilizers="all", kaya ang mga check ay hinahanap para sa mga stabilizer ng state na inihahanda ng circuit: wasto ang isang check kung ang spacetime Pauli product nito ay sumusukat ng ganoong stabilizer, ang syndrome nito ay ang ancilla bit lang nito, at nananatili itong wasto anuman ang basis na ginamit sa pagsukat ng payload pagkatapos. Ito ang kailangan ng fidelity estimation, dahil ang bawat na-sample na stabilizer ay sinusukat sa ibang basis. (Kapag inalis angstabilizers, hahanap ito ng mga check para sa mga terminal measurement ng circuit.) - Ang pangalawang positional argument ay listahan ng mga index ng target qubit. Ang bawat target qubit ay magbabahagi ng mga entangling gate sa isang ancilla qubit para bumuo ng check. Hahanapin at idaragdag ang mga check sa circuit ayon sa pagkakasunod ng mga ito sa argument na ito.
- Ang huling positional argument ay isang noise model na kumakatawan sa noise na inaasahang makakaapekto sa circuit habang isinasagawa. Ginagamit ang model na ito para suriin ang bisa ng isang check.
ancilla_qubits: Kung may layout ang input circuit, itinuturo ng kwarg na ito saadd_pauli_checkskung paano magtatalaga ng mga pisikal na qubit sa mga bagong idinagdag na ancilla. Dapat kasinghaba ngtarget_qubits(2nd positional arg) angancilla_qubits, at dapat gamitin angancilla_qubits[i]para ipatupad ang mga check satarget_qubits[i].cost: Ang cost function na ginagamit para suriin ang mga check. MaaaringgammaoLER(logical error rate), na pawang mga paraan para ilarawan kung gaano karami sa noise channel ang hindi natutukoy ng mga check. Sastabilizers="all", ang isang error ay itinuturing na logical kung binabago nito ang inihandang state, na eksaktong sinusukat ng fidelity.method: Ang method na ginagamit para maghanap ng mga wastong check. Ginagamit namin angwindowedna nagsa-sample nang random ng maraming spatial window subset ng support (mga wire) ng isang target qubit. Ang iba pang wastong value aygeneticatwindowed_genetic.- Ang output ng
add_pauli_checksay naglalaman ng mga circuit na may pataas na bilang ng mga check, mula sa circuit na walang check hanggang sa circuit na may mga check sa bawat target qubit (sa pag-aakalang may mga wastong check na natagpuan).
Tingnan ang Sections II-IV ng supplementary information para sa detalyadong impormasyon sa paghahanap ng magagandang spacetime Pauli check
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:

6. Tukuyin ang mga stabilizer na susukatin
Para subukan ang performance ng error detection, tatantyahin natin ang fidelity ng stabilizer state, , na ideal na inihahanda ng circuit, laban sa maingay na state, , na aktwal na inilalabas ng hardware. Ang projector sa purong stabilizer state, , ay tinutukoy bilang uniform na average sa elemento ng stabilizer group nito na :
Sa pagsasaksak nito sa fidelity equation, makikita natin na ang fidelity ng maingay na state, , ay katumbas ng average na expectation value ng lahat ng stabilizer, , kaugnay ng :
Para sa mas malalaking problema, imposible ang pagsukat (o kahit ang pag-enumerate) sa lahat ng stabilizer, kaya madalas na tinatantya ang fidelity sa pamamagitan ng pagsukat ng random na sample ng mga stabilizer. Ang pag-sample ng stabilizer na nang uniform at random mula sa ay nagbibigay ng unbiased estimator ng fidelity, ,
Para sa eksperimentong ito, gagawin nating mas malalim ang circuit para palakasin ang epekto ng gate noise, at magdaragdag tayo ng mga check sa bawat qubit na may katabing ancilla qubit. Bagama't may mas sopistikadong paraan para mag-sample ng mga stabilizer para tantyahin ang fidelity, sa halimbawang ito ay magsa-sample tayo ng stabilizer na may uniform na probability.
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. Maghanap ng magagandang check para sa buong state
Naghahanap tayo ng isang set ng mga check para sa malalim na circuit, eksaktong tulad ng sa hakbang 5. Dahil ang mga check ay ancilla lang nila ang binabasa, iisang checked circuit ang magagamit para sa bawat na-sample na stabilizer. Ang mga ibinalik na circuit ay ang mga check ancilla lang ang sinusukat, kaya idaragdag natin ang mga payload measurement sa susunod na hakbang.
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. Mag-sample mula sa maingay na circuit
Sina-sample natin ang bawat variant, mula sa bare payload hanggang sa circuit na may lahat ng 7 check, para sa bawat na-sample na stabilizer. Ang variant na walang check ay ang bare payload na may parehong mga measurement, kaya ito ang nagsisilbing maingay na reference. Sa halip na idugtong ang basis rotation ng bawat stabilizer sa kopya ng bawat variant, hinahayaan nating piliin ng samplomatic ang measurement basis sa oras ng pag-sample. Para sa bawat variant:
-
Sukatin ang payload at i-grupo ang mga terminal measurement sa isang box gamit ang boxing pass manager ng samplomatic. Ang box ay may dalang
ChangeBasisannotation, kaya angbuilday nagbabalik ng template circuit na ang mga basis-changing gate ay parameterized, kasama ang samplex na nagkakalkula ng mga parameter para sa hiniling na basis. -
Idagdag ang template at samplex sa isang
QuantumProgram. Ang samplex ay kumukuha ng isang measurement basis bawat qubit sa box, sa pataas na pisikal na pagkakasunod, na naka-encode bilang , , , . Ang pagpasa ng array ng mga basis na may leading axis na may haba na100ay nagpapasukat sa iisang item ng lahat ng100stabilizer: ang payload sa basis ng bawat stabilizer at ang mga ancilla sa . Ang qubit na nasa labas ng support ng isang stabilizer ay sinusukat din sa , at binabalewala ang bit nito kapag kinakalkula ang expectation value ng stabilizer.
Pinapatakbo ng Executor primitive mula sa qiskit-ibm-runtime ang buong program sa isang job. Kapag binigyan ng AerSimulator bilang kapalit ng QPU, tumatakbo ito nang lokal sa simulator na iyon, kaya binibigyan natin ito ng may parehong depolarizing gate noise na ginamit natin sa paghahanap ng mga check, at ginagamit ang stabilizer method dahil Clifford ang lahat ng circuit. Kailangan lang ang Qiskit Aer para buuin ang simulator na ito: kinukuha ng local mode ang seed mula sa mga opsyon ng Executor pero binabalewala ang kanilang noise_model, kaya dapat itakda ang noise sa mismong simulator. Ang pagpasa ng QPU backend sa halip ay magpapatakbo ng parehong program sa hardware. Kapag tapos na ang mga simulation, kakalkulahin natin ang postselection rate at mga expectation value para makakuha ng pakiramdam kung gaano kahusay gumana ang error detection bago i-visualize ang data.
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. I-postselect ang mga sample ng checked circuit
Sa package na ito, ang isang check ay ipinapatupad gamit ang mga entangling gate sa pagitan ng isang ancilla qubit at isang target qubit. Bawat ancilla ay nagsisimula sa , kaya ang ay nag-istabilize sa input state nito. Ang forward propagation ng mula sa simula ng ancilla sa buong checked circuit ay nagbibigay ng na pinarami ng isang stabilizer ng inihandang state, kaya pumapasa ang check kung ang ancilla bit ay . Itinatabi ang isang sample kung pumasa ang bawat check.
Sinusukat ng mga checked circuit ang mga ancilla papunta sa checks_c register, kaya itinatabi ang isang sample kung 0 ang bawat bit ng register na iyon. Sa pangkalahatan, ang method na get_postselection_method ng mga instance ng CheckedCircuit na output mula sa add_pauli_checks ay nagma-map ng bitstring ng isang shot sa syndrome ng bawat check nang sabay-sabay; para sa mga check na hinanap para sa mga stabilizer ng inihandang state, ang bawat syndrome ay ang ancilla bit lang nito, kaya binabasa natin nang direkta ang register.
Sa chart, makikita natin na ang pagdaragdag ng mas maraming check ay nagpapababa sa postselection rate. Bagama't ang layunin ng error detection ay mahuli ang pinakamaraming error hangga't maaari, habang bumababa ang postselection rate, maaaring mas maraming shot ang kailanganin para maabot ang isang target na accuracy. Kung masyadong bumaba ang postselection rate, maaaring maging hadlang ang bilang ng mga shot na kailangan para makakolekta ng isang logical sample. Sa halimbawang ito, tila nagko-converge ang postselection rate, na nagpapahiwatig na ang karagdagang mga check ay mas kaunti na ang naidaragdag na kakayahan sa pagtukoy. Bagama't karaniwang nagbibigay ng mas malaking kakayahan sa error detection ang pagdaragdag ng mas maraming Pauli check, may kapalit itong dagdag na depth ng circuit at dagdag na gastos sa pag-sample.
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()

10. Ihambing ang mga fidelity ng orihinal at postselected na maingay na state
Nakikita natin na ang pag-postselect lang ng mga sample na walang nakitang error ay lubos na nagpapabuti sa expectation value ng bawat stabilizer, at kaya nagpapabuti sa tinatantyang fidelity ng stabilizer state. Bagama't kinalkula ang mga postselected na value gamit lang ang mga sample na pumasa sa bawat check, mas tumpak ang mga expectation value at mas mababa ang sampled variance. Makikita rin natin ngayon na ang postselection rate sa itaas ay nagko-converge pala sa value na katulad ng fidelity ng maingay na circuit. Nagko-converge ang postselection rate dahil nahuhuli ng mga check ang karamihan sa mga error na aktwal na nangyayari habang isinasagawa ang circuit.
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

Dahil iisang set ng mga check ang nagsisilbi sa bawat stabilizer, iisang gamma curve lang ang titingnan: ang value ng cost function pagkatapos ma-commit ang bawat check. Para sa ibang problema sa ibang set ng mga pisikal na qubit, at may hindi perpektong noise model (tandaan, ang noise model natin sa tutorial na ito ay eksaktong nagmodelo ng noise na nakaaapekto sa mga sample), ang pagkakasunod ng pagdaragdag ng mga check sa mga target qubit ay maaaring magkaroon ng mas malaking epekto sa kung paano nagko-converge ang curve.
Ang gamma curve na ito na nagko-converge sa 1.0 ay nagpapahiwatig na hindi na makakatukoy ang karagdagang mga check ng malaking karagdagang error, dahil napakakaunti na ng modeled na error na hindi sakop ng mga check. Pinagtitibay nito ang naunang obserbasyon natin na ang convergence sa postselection rate ay dahil sa kakulangan ng hindi sakop na error sa circuit.
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()