Lewati ke konten utama
Proyek ini masih dalam tahap beta. Menemukan bug? Buat isu GitHub. Punya pertanyaan atau ide? Tanya di Discussions — kami sangat menghargai masukan kamu!
Bagaimana kualitas terjemahan di halaman ini?

Postselection dengan pemeriksaan error non-Markovian

Panduan ini menjelaskan apa itu pemeriksaan error non-Markovian dan cara mengimplementasikannya di software dengan qiskit-mitigation. Kamu akan mulai dengan contoh kecil dua qubit untuk memvisualisasikan pemeriksaan tersebut dan membahas cara kerjanya. Setelah kamu membangun intuisi, kamu akan mencoba contoh 20 qubit tempat kamu mengimplementasikan pemeriksaan dan membandingkan berbagai strategi postselection.

Apa itu pemeriksaan error non-Markovian?​

Pemeriksaan error non-Markovian adalah uji berbasis pengukuran untuk mengetahui apakah keadaan sebuah qubit kemungkinan rusak oleh sumber error non-Markovian. Uji ini dilakukan dengan menempatkan gate bit-flip berdurasi diperpanjang (xslow atau serangkaian NN gate rx dengan sudut πN\frac{\pi}{N}) pada setiap qubit di awal circuit, diikuti XGate biasa lalu pengukuran untuk memastikan keadaan berhasil berbalik dari ∣0⟩↦∣1⟩↦∣0⟩|0\rangle \mapsto |1\rangle \mapsto |0\rangle. Uji ini juga dapat dilakukan di akhir circuit setelah setiap pengukuran terminal untuk memastikan device berhasil membalik keadaan terukur qubit dari ∣x⟩↦∣x⊕1⟩|x\rangle \mapsto |x\oplus1\rangle. Sampel yang terkait dengan pemeriksaan yang gagal dapat dianggap tidak andal, dan hanya memilih sampel yang lolos semua pemeriksaan lewat postselection dapat meningkatkan fidelity distribusi ber-noise.

Strategi postselection berdasarkan pemeriksaan error non-Markovian​

  • Postselection berbasis node: Pilih hanya sampel yang setiap qubit-nya lolos pemeriksaannya. Ini strategi yang paling ketat dan umumnya menyebabkan lebih banyak sampel dibuang.

  • Postselection berbasis edge: Untuk setiap edge pada coupling graph qubit, pastikan setidaknya satu qubit lolos pemeriksaan. Strategi ini lebih longgar; jika sebuah qubit gagal dalam pemeriksaan, sampel mungkin tetap dipertahankan jika semua tetangganya lolos pemeriksaan mereka.

Qubit spectator​

Kamu bisa menentukan qubit ancilla "spectator" yang bersebelahan dengan qubit yang dipakai untuk menjalankan circuit asli. Pemeriksaan error non-Markovian bisa ditempatkan pada spectator ini, memberikan cakupan lebih luas terhadap noise non-Markovian yang dirancang untuk dideteksi oleh pemeriksaan tersebut.

Bagian 1. Visualisasikan pemeriksaan error non-Markovian​

Sebelum mendemonstrasikan teknik ini pada QPU, kamu akan membuat circuit kecil dua qubit dan menambahkan pemeriksaan untuk memvisualisasikan cara kerjanya. Mulailah dengan keadaan Bell yang di-transpile ke QPU backend ibm_kingston.

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

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "qiskit_mitigation": "qiskit-mitigation", "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")
from qiskit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService(channel="ibm_quantum_platform")
backend = service.backend("ibm_kingston")

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
qc_isa = generate_preset_pass_manager(
optimization_level=0, backend=backend
).run(qc)
qc_isa.draw("mpl")

Output of the previous code cell

Tambahkan beberapa pemeriksaan error non-Markovian di awal circuit. Pemeriksaan hanya akan ditempatkan pada qubit yang diakhiri dengan pengukuran. Pemeriksaan ini memastikan device berhasil membalik keadaan awal qubit dari ∣0⟩↦∣1⟩↦∣0⟩|0\rangle \mapsto |1\rangle \mapsto |0\rangle, dan hasil pengukuran pemeriksaan ini dibaca ke register klasik baru bernama meas_pre.

from qiskit.transpiler import PassManager
from qiskit_mitigation.postselection.passes import (
AddPreCircuitNonMarkovianErrorChecks,
)

qc_w_checks = PassManager([AddPreCircuitNonMarkovianErrorChecks()]).run(
qc_isa
)
qc_w_checks.draw("mpl", idle_wires=False)

Output of the previous code cell

Demikian pula, kamu bisa menambahkan pemeriksaan di akhir circuit setelah setiap pengukuran terminal. Pemeriksaan ini memastikan device bisa membalik keadaan qubit dari ∣x⟩↦∣x⊕1⟩|x\rangle \mapsto |x\oplus1\rangle, dan hasil pengukurannya dibaca ke register klasik baru bernama meas_ps.

from qiskit_mitigation.postselection.passes import (
AddPostCircuitNonMarkovianErrorChecks,
)

qc_w_checks = PassManager([AddPostCircuitNonMarkovianErrorChecks()]).run(
qc_w_checks
)
qc_w_checks.draw("mpl", idle_wires=False)

Output of the previous code cell

Seperti yang disebutkan di pendahuluan, kamu bisa memakai qubit ancilla yang bersebelahan dengan qubit data sebagai "spectator" untuk memberikan cakupan error yang lebih luas. Tambahkan pemeriksaan spectator sebelum dan sesudah circuit. Kamu bisa lihat bahwa karena qubit fisik 2 pada ibm_kingston bersebelahan dengan qubit data 1, qubit itu ditambahkan sebagai spectator, dan pemeriksaan pra-circuit serta pasca-circuit ditambahkan padanya. Pengukuran spectator ditambahkan ke register klasik baru bernama spec_pre dan spec.

Circuit seperti ini berisi semua kemungkinan pemeriksaan error non-Markovian, jadi sampling dari circuit ini memberi pengguna kemampuan untuk melakukan postselection berdasarkan kombinasi apa pun dari pemeriksaan [pra/pasca]-circuit dan strategi postselection berbasis [node/edge]. Lihat Bagian 2 panduan ini untuk melihat cara kerjanya di hardware kuantum.

from qiskit_mitigation.postselection.passes import (
AddSpectatorPostCircuitNonMarkovianErrorChecks,
AddSpectatorPreCircuitNonMarkovianErrorChecks,
)

qc_w_checks = PassManager(
[
AddSpectatorPreCircuitNonMarkovianErrorChecks(
coupling_map=backend.coupling_map
),
AddSpectatorPostCircuitNonMarkovianErrorChecks(
coupling_map=backend.coupling_map
),
]
).run(qc_w_checks)
qc_w_checks.draw("mpl", idle_wires=False, fold=-1)

Output of the previous code cell

Bagian 2: Demonstrasi pemeriksaan error non-Markovian pada QPU dan perbandingan strategi postselection​

Sekarang kamu sudah paham apa itu pemeriksaan error non-Markovian dan bagaimana cara mengimplementasikannya di perangkat lunak, kamu bisa menjalankan contoh yang lebih besar pada QPU dan melihat cara kerjanya. Pertama, siapkan sebuah circuit. Pilihan circuit-nya tidak terlalu penting; kamu hanya butuh circuit yang outputnya mudah diverifikasi. Di sini kita memilih circuit Ising cermin 1D dengan 20 qubit. Circuit ini tidak sepele untuk dieksekusi di hardware dan seharusnya terpengaruh oleh error non-Markovian, sehingga bisa dipakai untuk mendemonstrasikan efektivitas pemeriksaan error non-Markovian. Selain itu, struktur cermin memungkinkan kita membandingkan perhitungan nilai ekspektasi yang berderau dengan nilai eksak secara langsung.

Dalam eksperimen ini, kita menghitung nilai ekspektasi observabel. Pilihan observabel OO juga tidak penting, jadi kita pilih saja magnetisasi rata-rata per site tunggal O=1N∑i⟨Zi⟩O=\frac{1}{N}\sum_i\langle Z_i\rangle.

import numpy as np
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import CouplingMap

num_qubits = 20
initial_layout = initial_layout = [
124,
125,
126,
127,
128,
129,
130,
131,
132,
133,
134,
135,
139,
155,
154,
153,
152,
151,
150,
149,
][:num_qubits]
coupling_map = CouplingMap.from_line(num_qubits, bidirectional=False)

# Num Trotter steps and `rx` angle
num_steps = 6
theta_rx = np.pi / 6

# Average single-site magnetization
observable = (
SparsePauliOp(
[
"I" * iq + "Z" + "I" * (num_qubits - iq - 1)
for iq in range(num_qubits)
]
)
/ num_qubits
)

# Create the Trotter circuit
circuit = QuantumCircuit(num_qubits)
edges = CouplingMap.from_line(num_qubits, bidirectional=False).get_edges()
edges_a = [e for e in edges if e[0] % 2 == 0]
edges_b = [e for e in edges if e[0] % 2 == 1]
for _ in range(num_steps):
circuit.rx(theta_rx, [i for i in range(num_qubits)])
for edge in edges_a:
circuit.cz(edge[0], edge[1])
for edge in edges_b:
circuit.cz(edge[0], edge[1])
circuit = circuit.compose(circuit.inverse())
circuit.measure_all()

# Transpile circuit to backend
pm = generate_preset_pass_manager(
optimization_level=0, backend=backend, initial_layout=initial_layout
)
isa_circuit = pm.run(circuit)
print(
f"two-qubit depth: {isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)}"
)
print(f"total depth: {isa_circuit.depth()}")
isa_circuit.draw("mpl", fold=-1, idle_wires=False)
two-qubit depth: 24
total depth: 85

Output of the previous code cell

Sekarang kita tambahkan pemeriksaan pra- dan pasca-circuit pada setiap qubit, termasuk tujuh qubit spectator yang bersebelahan. Saat kamu menambahkan semua pemeriksaan yang mungkin ke sebuah circuit, kamu bisa memutuskan strategi postselection mana yang dipakai saat postprocessing.

# Use all non-Markovian error checks so we can compare all strategies in post-processing.
combined = PassManager(
[
AddPreCircuitNonMarkovianErrorChecks(x_pulse_type="rx"),
AddSpectatorPreCircuitNonMarkovianErrorChecks(
backend.coupling_map, x_pulse_type="rx"
),
AddPostCircuitNonMarkovianErrorChecks(x_pulse_type="rx"),
AddSpectatorPostCircuitNonMarkovianErrorChecks(
backend.coupling_map, x_pulse_type="rx"
),
]
).run(isa_circuit)
combined_isa = generate_preset_pass_manager(
optimization_level=0, backend=backend
).run(combined)

print("cregs:", [(c.name, c.size) for c in combined_isa.cregs])
cregs: [('meas', 20), ('meas_pre', 20), ('spec_pre', 7), ('meas_ps', 20), ('spec', 7), ('spec_ps', 7)]

Gunakan primitif Executor untuk men-sample circuit yang berisi pemeriksaan tersebut.

from qiskit_ibm_runtime import Executor
from samplomatic.quantum_program import QuantumProgram

# Raw and combined-checks circuits in a single 100k-shot program so they share calibration.
program = QuantumProgram(shots=100_000)
program.append_circuit_item(isa_circuit) # raw circuit, no checks
program.append_circuit_item(
combined_isa
) # combined pre + post non-Markovian error checks
job = Executor(backend).run(program)
print(f"job id: {job.job_id()}")
print(f"status: {job.status()}")
job id: d9badrm6hjac73ffe31g
status: QUEUED

Kelas PostSelector tahu struktur creg dari pemeriksaan, sehingga ia bisa mengaitkan setiap bit dalam sampel dengan pengukuran circuit yang terkait. Metode compute_mask-nya bisa dipakai untuk membuat mask atas sekumpulan sampel, dengan strategi postselection (node atau edge) dan jenis pemeriksaan error non-Markovian yang ditinjau (pra- atau pasca-circuit). Untuk menggabungkan strategi pra- dan pasca-circuit menjadi satu mask, mask masing-masing bisa dihitung secara terpisah lalu digabungkan dengan operasi AND. Di sini kita memakai PostSelector untuk membuat mask bagi semua strategi dan melihat hasilnya dengan setiap permutasi strategi yang tersedia.

Pada visualisasi di bawah, kita melihat bagaimana strategi postselection memengaruhi tingkat postselection serta kualitas jawaban. Seperti yang diharapkan, strategi yang melibatkan postselection berbasis node menghasilkan tingkat postselection terendah. Strategi berbasis edge lebih longgar dan cenderung menghasilkan tingkat postselection yang lebih tinggi. Pengguna sebaiknya mencoba berbagai strategi pada masalah mereka untuk menentukan keseimbangan yang tepat antara tingkat postselection dan kualitas distribusi hasil sampling. Pada contoh ini, memakai postselection berbasis node dengan pemeriksaan pra- dan pasca-circuit memberi jawaban terbaik dengan tingkat postselection .028%. Beberapa strategi tidak mengungguli circuit mentah. Kamu juga bisa melihat bahwa menambahkan pemeriksaan tetapi tidak memakainya sama sekali (batang berlabel "no selection" pada grafik di bawah) memberi hasil terburuk.

import matplotlib.pyplot as plt
from qiskit_mitigation.postselection import PostSelector

selector = PostSelector.from_circuit(combined_isa, backend.coupling_map)

exec_results = job.result()
# The Executor returns numpy arrays keyed by classical-register name, so each result item is
# already the {creg_name: ndarray} mapping that compute_mask expects.
raw_bits = exec_results[0]["meas"] # raw circuit, no checks
bits = exec_results[1] # combined pre+post circuit

meas_bits = bits["meas"]
total = meas_bits[..., 0].size

def magnetization(b, mask=None):
"""Average single-site magnetization over the (optionally masked) measurement bits."""
kept = b if mask is None else b[mask]
return (1 - 2 * kept.astype(int)).mean() # bit 0 -> +1, bit 1 -> -1

rows = []

def report(label, mask=None, b=meas_bits):
"""Record and print the magnetization and kept fraction for one selection strategy."""
kept = b[..., 0].size if mask is None else int(mask.sum())
ev = magnetization(b, mask)
rows.append((label, ev, kept / total))
print(f"{label:<24}: <m> = {ev:+.4f} (kept {kept:>6}/{total})")

# Compare all possible postselection strategies
report("raw circuit", b=raw_bits)
report("no selection")
for s in ("node", "edge"):
report(f"pre-only ({s})", selector.compute_mask(bits, s, mode="pre"))
for s in ("node", "edge"):
report(f"post-only ({s})", selector.compute_mask(bits, s, mode="post"))
for pre_s in ("node", "edge"):
for post_s in ("node", "edge"):
keep = selector.compute_mask(
bits, pre_s, mode="pre"
) & selector.compute_mask(bits, post_s, mode="post")
report(f"pre={pre_s} + post={post_s}", keep)

labels = [r[0] for r in rows]
evs = [r[1] for r in rows]
fracs = [r[2] for r in rows]
x = range(len(labels))

fig, ax1 = plt.subplots(figsize=(11, 5))

# Bars: expectation value per selection strategy.
bar_colors = ["firebrick"] + ["steelblue"] * (len(evs) - 1)
ax1.bar(x, evs, color=bar_colors, alpha=0.85)
ax1.axhline(1.0, color="green", ls=":", lw=1.5, label="exact <m>")
ax1.axhline(rows[0][1], color="firebrick", ls="--", lw=1, label="raw <m>")
ax1.set_ylabel("<m> (avg single-site magnetization)", color="steelblue")
ax1.tick_params(axis="y", labelcolor="steelblue")
ax1.set_xticks(list(x))
ax1.set_xticklabels(labels, rotation=45, ha="right")
ax1.legend(loc="upper right")

# Line: fraction of shots retained (right axis).
ax2 = ax1.twinx()
ax2.plot(x, fracs, "o-", color="darkorange")
ax2.set_ylabel("kept fraction", color="darkorange")
ax2.tick_params(axis="y", labelcolor="darkorange")
ax2.set_ylim(0, 1.05)

ax1.set_title("Selection strategy: expectation value vs shots retained")
fig.tight_layout()
plt.show()
raw circuit : <m> = +0.5791 (kept 100000/100000)
no selection : <m> = +0.5211 (kept 100000/100000)
pre-only (node) : <m> = +0.6026 (kept 1357/100000)
pre-only (edge) : <m> = +0.5637 (kept 13029/100000)
post-only (node) : <m> = +0.5682 (kept 2254/100000)
post-only (edge) : <m> = +0.5265 (kept 27514/100000)
pre=node + post=node : <m> = +0.6393 (kept 28/100000)
pre=node + post=edge : <m> = +0.5799 (kept 442/100000)
pre=edge + post=node : <m> = +0.6006 (kept 317/100000)
pre=edge + post=edge : <m> = +0.5521 (kept 3836/100000)

Output of the previous code cell