Deteksi error dengan overhead rendah menggunakan kode spacetime
Dalam tutorial ini, kita akan mengimplementasikan alur kerja untuk melakukan sampling dari circuit Clifford dan memakai package qiskit-paulice untuk meningkatkan fidelity distribusi hasil ukur dengan menerapkan spacetime Pauli checks di seluruh circuit. Spacetime Pauli checks mirip dengan coherent Pauli checks (CPC) karena circuit "payload" Clifford dientangle dengan beberapa qubit ancilla untuk "memeriksa" invarian tertentu dalam circuit. Pengukuran ancilla menghasilkan syndrome yang dipakai untuk menentukan apakah ada error logis yang terdeteksi selama eksekusi circuit. Dengan postselection hanya pada sampel yang tidak terdeteksi error, fidelity distribusi hasil sampling bisa meningkat dengan konsekuensi turunnya postselection rate. Perbedaan utama antara CPC dan spacetime checks adalah bahwa pengukuran syndrome CPC membutuhkan pengukuran operator berbobot tinggi yang terlokalisasi dalam waktu. Pada topologi qubit dengan konektivitas terbatas, seperti heavy hex, implementasi check ini membutuhkan banyak Gate SWAP, sehingga circuit sering menjadi terlalu dalam untuk dijalankan secara praktis. Mengimplementasikan Pauli checks sebagai kode spacetime memungkinkan check tersebut disebar ke seluruh circuit payload, sehingga menghasilkan encoding qubit yang efisien untuk hardware dan tetap efektif mendeteksi error logis selama eksekusi circuit. Peran utama package qiskit-paulice adalah menemukan dan menyisipkan spacetime Pauli checks yang valid ke lokasi-lokasi dalam circuit yang memaksimalkan efektivitas deteksi error sekaligus meminimalkan overhead qubit.
Tutorial ini mengimplementasikan alur kerja untuk meningkatkan fidelity distribusi hasil sampling dari quantum circuit menggunakan spacetime checks. Karena kita akan memakai circuit Clifford, kita bisa mengestimasi fidelity secara langsung dari rata-rata expectation value stabilizer circuit tersebut. Kita akan mengikuti langkah-langkah berikut:
-
Buat circuit: Circuit Clifford acak brickwork 1D yang dangkal, dipakai untuk memvisualisasikan check.
-
Identifikasi qubit sebagai target check: Petakan circuit ke qubit fisik pada QPU Heron
ibm_bostondan identifikasi qubit yang punya ancilla bertetangga. -
Transpile circuit ke Backend: Pilih layout qubit dan terjemahkan Gate circuit ke set Gate native
ibm_boston. -
Buat noise model dari data Backend: Hasilkan noise model kasar berdasarkan data benchmark Backend. Data noise ini akan dipakai untuk menilai efektivitas setiap check tambahan.
-
Visualisasikan Pauli checks: Tambahkan beberapa Pauli check ke circuit yang dangkal, supaya kita bisa melihat bagaimana check ditambahkan.
-
Tentukan stabilizer yang akan diukur: Perbesar kedalaman circuit untuk meningkatkan dampak noise Gate. Ambil sampel acak sebagian stabilizer dari circuit yang lebih dalam.
-
Temukan check yang bagus untuk seluruh state: Temukan satu set check untuk stabilizer dari state yang disiapkan circuit. Check seperti ini valid apa pun yang diukur setelahnya, sehingga circuit ber-check yang sama bisa dipakai untuk setiap stabilizer yang disampel.
-
Lakukan sampling dari circuit ber-noise: Ukur circuit polos dan circuit ber-check dalam basis setiap stabilizer yang disampel dengan samplomatic, lalu lakukan sampling dengan primitive
Executorpada simulator ber-noise yang memakai depolarizing noise yang sama dengan yang dipakai untuk menemukan check. -
Postselect sampel circuit ber-check yang tidak punya error terdeteksi: Buang sampel yang gagal setidaknya satu check. Hitung expectation value untuk circuit ber-noise polos dan circuit yang berisi check.
-
Bandingkan fidelity state ber-noise asli dan hasil postselection: Tunjukkan bahwa postselection hanya pada sampel tanpa error terdeteksi meningkatkan estimasi fidelity state ber-noise.
1. Buat circuit
Pertama, kita akan membuat circuit Clifford dangkal dengan Gate entangling yang ditata dalam konektivitas brickwork. Kita akan memakai circuit kecil ini untuk memvisualisasikan bagaimana check ditambahkan ke 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. Identifikasi qubit sebagai target check
Selanjutnya kita memilih layout qubit 1D pada QPU Heron r3, ibm_boston. Kita memilih layout yang zig-zag secara diagonal melalui coupling graph dan memaksimalkan jumlah qubit ancilla yang tersedia. Untuk menemukan ancilla yang tersedia, kita memakai fungsi get_check_qubits, yang mengembalikan daftar pasangan qubit target/ancilla. Pauli check pada target_qubits[i] akan diimplementasikan menggunakan ancilla_qubits[i].
Pada coupling graph di bawah, qubit hijau adalah qubit payload, dan qubit oranye adalah ancilla yang akan kita pakai untuk mengimplementasikan check. Qubit 68, 69, 89, 91, 111, 113, dan 133 punya ancilla bertetangga, jadi kita akan memakai qubit-qubit tersebut (yang berkorespondensi dengan qubit virtual 0, 1, 3, 5, 7, 9, dan 11) sebagai qubit target untuk mengimplementasikan check. Perhatikan bahwa qubit 134 bertetangga dengan qubit 133 di payload tetapi tidak ditandai sebagai ancilla. Hal ini karena 133 sudah (secara sembarang) dipilih untuk mengimplementasikan check dengan ancilla 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. Transpile circuit ke Backend
Karena Backend sudah dipilih, kita bisa langsung men-transpile circuit virtual kita menjadi circuit ISA. Di sini, kita cukup menetapkan layout qubit dan menerjemahkan Gate ke set Gate native 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. Buat noise model dari data Backend
Sekarang setelah kita tahu bagaimana circuit akan ditata di QPU, kita bisa memodelkan bagaimana noise Gate dan readout pada Backend akan memengaruhi eksekusi circuit. Noise model dipakai untuk menentukan tempat optimal di dalam circuit untuk menaruh Pauli check. Kita ingin menaruh check di posisi di mana check menangkap error paling banyak. Meskipun noise model yang lebih akurat bisa meningkatkan kemampuan deteksi check, biasanya tidak perlu mempelajari noise model dengan melakukan sampling pada QPU. Di sini kita menyimpulkan noise model depolarizing Gate yang seragam hanya dengan data benchmark Backend dari qiskit-ibm-runtime, dan membuang error readout yang juga dilaporkannya: check yang kita temukan di langkah 7 hanya membaca ancilla-nya, sehingga error readout payload tidak terlihat oleh check dan hanya akan menurunkan fidelity postselection di langkah 10. Lihat dokumentasi qiskit_paulice.NoiseModel untuk informasi lebih lanjut tentang cara menentukan noise Gate dan readout. Bagi pengguna yang ingin memakai noise model Pauli-Lindblad hasil pembelajaran, lihat 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. Visualisasikan Pauli checks
Sekarang mari kita tambahkan beberapa check ke circuit dan lihat tampilannya. Kita bisa memakai fungsi qiskit_paulice.add_pauli_checks untuk menemukan set check valid yang bagus. Kita memberikan circuit payload Clifford sebagai input, beserta daftar qubit payload tempat check akan ditambahkan (qubit target) dan noise model yang kita buat di langkah sebelumnya. Dalam contoh ini, kita akan menambahkan check ke semua 7 qubit target yang punya ancilla tersedia. Check ditambahkan ke qubit sesuai urutan kemunculannya di target_qubits, sehingga mudah untuk menalar layout akhir circuit ber-check: final_layout = layout + ancilla_qubits. Kalau pengguna memutuskan lebih suka menjalankan salah satu circuit output dengan lebih sedikit (i) check, layout akhirnya adalah final_layout = layout + ancilla_qubits[:i+1].
Visualisasi circuit menunjukkan bahwa check diimplementasikan dengan benar menggunakan pasangan target/ancilla yang ditentukan.
Detail tentang qiskit_paulice.add_pauli_checks:
- Input pertama harus berupa circuit Clifford. Di sini circuit tidak punya pengukuran dan kita memberikan
stabilizers="all", sehingga check ditemukan untuk stabilizer dari state yang disiapkan circuit: sebuah check valid jika hasil kali Pauli spacetime-nya mengukur stabilizer semacam itu, syndrome-nya hanyalah bit ancilla-nya, dan check tetap valid dalam basis apa pun payload diukur setelahnya. Inilah yang dibutuhkan estimasi fidelity, karena setiap stabilizer yang disampel diukur dalam basis yang berbeda. (Jikastabilizersdihilangkan, yang ditemukan adalah check untuk pengukuran terminal circuit.) - Argumen posisional kedua adalah daftar indeks qubit target. Setiap qubit target akan berbagi Gate entangling dengan satu qubit ancilla untuk membentuk sebuah check. Check akan ditemukan dan ditambahkan ke circuit sesuai urutan yang diberikan di argumen ini.
- Argumen posisional terakhir adalah noise model yang merepresentasikan noise yang diperkirakan memengaruhi circuit selama eksekusi. Model ini dipakai untuk mengevaluasi efektivitas suatu check.
ancilla_qubits: Jika circuit input punya layout, kwarg ini memberi tahuadd_pauli_checkscara menetapkan qubit fisik ke ancilla yang baru ditambahkan.ancilla_qubitsharus sama panjang dengantarget_qubits(argumen posisional ke-2), danancilla_qubits[i]dipakai untuk mengimplementasikan check padatarget_qubits[i].cost: Fungsi cost yang dipakai untuk mengevaluasi check. Bisa berupagammaatauLER(logical error rate), yang semuanya adalah cara menggambarkan seberapa banyak noise channel yang tidak terdeteksi oleh check. Denganstabilizers="all", sebuah error dihitung sebagai logis jika mengubah state yang disiapkan, yang persis diukur oleh fidelity.method: Metode yang dipakai untuk menemukan check valid. Kita memakaiwindowedyang secara acak mengambil banyak subset window spasial dari support (wire) sebuah qubit target. Nilai valid lainnya adalahgeneticdanwindowed_genetic.- Output
add_pauli_checksberisi circuit dengan jumlah check yang bertambah, dari circuit tanpa check hingga circuit dengan check pada setiap qubit target (dengan asumsi check valid ditemukan).
Lihat Bagian II-IV dari informasi suplementer untuk informasi detail tentang menemukan spacetime Pauli check yang bagus
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. Tentukan stabilizer yang akan diukur
Untuk menguji performa deteksi error, kita akan mengestimasi fidelity state stabilizer, , yang idealnya disiapkan circuit, terhadap state ber-noise, , yang sebenarnya dihasilkan hardware. Proyektor ke state stabilizer murni, , didefinisikan sebagai rata-rata seragam atas elemen grup stabilizer-nya :
Dengan memasukkan ini ke persamaan fidelity, kita melihat bahwa fidelity state ber-noise, , sama dengan rata-rata expectation value semua stabilizer, , terhadap :
Untuk masalah yang lebih besar, mengukur (bahkan sekadar mengenumerasi) semua stabilizer tidak layak dilakukan, sehingga fidelity sering diestimasi dengan mengukur sampel acak stabilizer. Mengambil sampel stabilizer secara seragam acak dari menghasilkan estimator fidelity yang tak bias, ,
Untuk eksperimen ini kita akan membuat circuit jauh lebih dalam untuk memperkuat efek noise Gate, dan kita akan menambahkan check ke setiap qubit yang punya qubit ancilla bertetangga. Meskipun ada cara yang lebih canggih untuk mengambil sampel stabilizer guna mengestimasi fidelity, dalam contoh ini kita akan mengambil sampel stabilizer dengan probabilitas seragam.
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. Temukan check yang bagus untuk seluruh state
Kita menemukan satu set check untuk circuit dalam persis seperti di langkah 5. Karena check hanya membaca ancilla-nya, circuit ber-check yang sama bisa dipakai untuk setiap stabilizer yang disampel. Circuit yang dikembalikan hanya mengukur ancilla check, jadi kita akan menambahkan pengukuran payload di langkah berikutnya.
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. Lakukan sampling dari circuit ber-noise
Kita melakukan sampling setiap varian, dari payload polos hingga circuit dengan semua 7 check, untuk setiap stabilizer yang disampel. Varian dengan nol check adalah payload polos dengan pengukuran yang sama, sehingga berfungsi sebagai referensi ber-noise. Alih-alih menambahkan rotasi basis setiap stabilizer ke salinan setiap varian, kita membiarkan samplomatic memilih basis pengukuran pada saat sampling. Untuk setiap varian kita:
-
Mengukur payload dan mengelompokkan pengukuran terminal ke dalam sebuah box dengan boxing pass manager milik samplomatic. Box tersebut membawa anotasi
ChangeBasis, sehinggabuildmengembalikan circuit template yang Gate pengubah basisnya diparameterisasi, beserta samplex yang menghitung parameter untuk basis yang diminta. -
Menambahkan template dan samplex ke sebuah
QuantumProgram. Samplex menerima satu basis pengukuran per qubit di dalam box, dalam urutan fisik menaik, dengan encoding , , , . Memberikan array basis dengan axis pertama sepanjang100membuat satu item mengukur semua100stabilizer: payload dalam basis tiap stabilizer dan ancilla dalam . Qubit di luar support sebuah stabilizer juga diukur dalam , dan bit-nya diabaikan saat expectation value stabilizer dihitung.
Primitive Executor dari qiskit-ibm-runtime menjalankan seluruh program dalam satu job. Jika diberi AerSimulator sebagai pengganti QPU, ia berjalan secara lokal pada simulator tersebut, jadi kita memberinya simulator dengan depolarizing noise Gate yang sama dengan yang kita pakai untuk menemukan check, dan memakai metode stabilizer karena semua circuit adalah Clifford. Qiskit Aer hanya dibutuhkan untuk membangun simulator ini: mode lokal mengambil seed dari opsi Executor tetapi mengabaikan noise_model mereka, jadi noise harus ditetapkan pada simulator itu sendiri. Memberikan Backend QPU sebagai gantinya akan menjalankan program yang sama di hardware. Setelah simulasi selesai, kita akan menghitung postselection rate dan expectation value untuk mendapat gambaran seberapa baik deteksi error bekerja sebelum memvisualisasikan datanya.
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. Postselect sampel circuit ber-check
Dalam package ini, sebuah check diimplementasikan menggunakan Gate entangling antara satu qubit ancilla dan satu qubit target. Setiap ancilla dimulai dalam , sehingga menstabilkan state inputnya. Merambatkan maju dari awal ancilla melalui seluruh circuit ber-check menghasilkan dikali sebuah stabilizer dari state yang disiapkan, sehingga check lolos jika bit ancilla bernilai . Sebuah sampel dipertahankan jika setiap check lolos.
Circuit ber-check mengukur ancilla ke register checks_c, sehingga sebuah sampel dipertahankan jika setiap bit register tersebut bernilai 0. Secara umum, method get_postselection_method dari instance CheckedCircuit yang dihasilkan add_pauli_checks memetakan bitstring sebuah shot ke syndrome setiap check sekaligus; untuk check yang ditemukan bagi stabilizer state yang disiapkan, setiap syndrome hanyalah bit ancilla-nya, jadi kita membaca register secara langsung.
Pada chart, kita melihat bahwa menambah lebih banyak check menyebabkan postselection rate turun. Meskipun tujuan deteksi error adalah menangkap error sebanyak mungkin, saat postselection rate turun, mungkin dibutuhkan lebih banyak shot untuk mencapai akurasi target tertentu. Jika postselection rate turun terlalu rendah, jumlah shot yang dibutuhkan untuk mengumpulkan satu sampel logis bisa menjadi terlalu besar. Dalam contoh ini, postselection rate tampak konvergen, yang menyiratkan bahwa check tambahan memberi kontribusi kemampuan deteksi tambahan yang makin kecil. Meskipun menambah lebih banyak Pauli check umumnya memberi kemampuan deteksi error yang lebih besar, hal itu datang dengan biaya kedalaman circuit yang meningkat dan biaya sampling yang bertambah.
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. Bandingkan fidelity state ber-noise asli dan hasil postselection
Kita melihat bahwa postselection hanya pada sampel tanpa error terdeteksi sangat memperbaiki expectation value untuk setiap stabilizer, dan dengan demikian memperbaiki estimasi fidelity state stabilizer. Meskipun nilai hasil postselection dihitung hanya memakai sampel yang lolos setiap check, expectation value-nya jauh lebih akurat dan varians sampelnya lebih rendah. Sekarang kita juga bisa melihat bahwa postselection rate di atas sebenarnya konvergen ke nilai yang mirip dengan fidelity circuit ber-noise. Postselection rate konvergen karena check menangkap sebagian besar error yang benar-benar terjadi selama eksekusi 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

Karena satu set check melayani setiap stabilizer, hanya ada satu kurva gamma untuk dilihat: nilai fungsi cost setelah setiap check di-commit. Untuk masalah lain pada set qubit fisik yang berbeda, dan dengan noise model yang tidak sempurna (ingat, noise model kita dalam tutorial ini memodelkan persis noise yang memengaruhi sampel), urutan penambahan check ke qubit target bisa berdampak lebih nyata pada bagaimana kurva konvergen.
Kurva gamma yang konvergen ke 1.0 ini menyiratkan bahwa check tambahan tidak akan bisa mendeteksi banyak error tambahan, karena hanya ada sangat sedikit error terpodel yang tidak tercakup oleh check. Ini memperkuat pengamatan kita sebelumnya bahwa konvergensi postselection rate disebabkan oleh tidak adanya error yang tidak tercakup di dalam 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()