Pascapemilihan dengan semakan ralat bukan-Markovian
Panduan ini menerangkan apakah semakan ralat bukan-Markovian dan cara melaksanakannya dalam perisian dengan qiskit-mitigation. Anda akan bermula dengan contoh kecil dua qubit untuk memvisualisasikan semakan tersebut dan membincangkan cara ia berfungsi. Setelah anda membina sedikit gerak hati, anda akan mencuba contoh 20 qubit yang mana anda melaksanakan semakan dan membandingkan pelbagai strategi pascapemilihan.
Apakah semakan ralat bukan-Markovian?
Semakan ralat bukan-Markovian ialah ujian berasaskan pengukuran untuk menentukan sama ada keadaan sesuatu qubit berkemungkinan dirosakkan oleh punca ralat bukan-Markovian. Ujian ini dilakukan dengan meletakkan gate pembalik bit berdurasi dilanjutkan (xslow atau siri gate rx dengan sudut ) pada setiap qubit di permulaan litar, diikuti oleh XGate biasa dan pengukuran untuk memastikan keadaan berjaya dibalikkan daripada . Ujian ini juga boleh dilakukan di penghujung litar selepas setiap pengukuran terminal untuk memastikan peranti boleh membalikkan keadaan terukur qubit daripada dengan berjaya. Sampel yang dikaitkan dengan semakan yang gagal boleh dianggap tidak boleh dipercayai, dan memilih selepas-fakta hanya sampel yang lulus semua semakan boleh meningkatkan fidelity taburan berhingar.
Strategi untuk pascapemilihan berdasarkan semakan ralat bukan-Markovian
-
Pascapemilihan berasaskan nod: Pilih selepas-fakta hanya sampel yang setiap qubit lulus semakannya. Ini ialah strategi yang paling ketat dan lazimnya menyebabkan lebih banyak sampel dibuang.
-
Pascapemilihan berasaskan sisi: Bagi setiap sisi dalam graf gandingan qubit, pastikan sekurang-kurangnya satu qubit lulus semakan. Strategi ini lebih longgar; jika sesuatu qubit gagal dalam semakan, sampel masih boleh dikekalkan jika semua jirannya lulus semakan mereka.
Qubit penonton
Seseorang boleh menentukan qubit ancilla "penonton" yang bersebelahan dengan qubit yang digunakan untuk melaksanakan litar asal. Semakan ralat bukan-Markovian boleh diletakkan pada penonton ini, memberikan lebih banyak liputan terhadap hingar bukan-Markovian yang direka untuk dikesan oleh semakan ini.
Bahagian 1. Visualisasikan semakan ralat bukan-Markovian
Sebelum menunjukkan teknik ini pada QPU, anda akan mencipta litar kecil dua qubit dan menambahkan semakan untuk memvisualisasikan cara ia berfungsi. Mulakan dengan keadaan Bell yang ditranspilasi kepada 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")
Tambahkan beberapa semakan ralat bukan-Markovian pada permulaan litar. Semakan hanya akan diletakkan pada qubit yang ditamatkan dengan pengukuran. Semakan ini memastikan peranti boleh membalikkan keadaan awal qubit daripada , dan pengukuran semakan ini dibaca keluar dalam daftar klasik baharu yang dipanggil 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)
Begitu juga, anda boleh menambahkan semakan pada penghujung litar selepas setiap pengukuran terminal. Semakan ini memastikan peranti boleh membalikkan keadaan qubit daripada , dan pengukuran mereka dibaca keluar dalam daftar klasik baharu yang dipanggil 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)
Seperti yang dinyatakan dalam pengenalan, anda boleh menggunakan qubit ancilla yang bersebelahan dengan qubit data sebagai "penonton" untuk menyediakan liputan ralat yang lebih luas. Tambahkan semakan penonton pra-litar dan pasca-litar. Anda dapat melihat bahawa memandangkan qubit fizikal 2 pada ibm_kingston bersebelahan dengan qubit data 1, ia telah ditambahkan sebagai penonton, dan semakan pra- dan pasca-litar telah ditambahkan kepadanya. Pengukuran penonton telah ditambahkan pada daftar klasik baharu yang dipanggil spec_pre dan spec.
Litar seperti ini mengandungi semua semakan ralat bukan Markov yang mungkin, jadi pensampelan daripada litar ini memberi pengguna keupayaan untuk melakukan pascapemilihan berdasarkan sebarang gabungan semakan [pra/pasca]-litar dan strategi pascapemilihan berasaskan [nod/tepi]. Lihat Bahagian 2 panduan ini untuk melihat cara ia berfungsi pada perkakasan 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)

Bahagian 2: Tunjukkan semakan ralat bukan Markov pada QPU dan bandingkan strategi pascapemilihan
Setelah anda memahami apa itu semakan ralat bukan Markov dan cara ia dilaksanakan dalam perisian, anda boleh menjalankan contoh yang lebih besar pada QPU dan melihat cara ia berfungsi. Pertama, sediakan satu litar. Pilihan litar tidak begitu penting; anda hanya memerlukan litar yang outputnya mudah disahkan. Di sini kami memilih litar Ising cermin 1D dengan 20 qubit. Litar ini tidak mudah dilaksanakan pada perkakasan dan sepatutnya terjejas oleh ralat bukan Markov, yang memberi peluang untuk menunjukkan keberkesanan semakan ralat bukan Markov. Selain itu, struktur cermin membolehkan kita membandingkan pengiraan nilai jangkaan yang bising dengan nilai tepat secara mudah.
Dalam eksperimen ini, kita mengira nilai jangkaan observabel. Pilihan observabel juga tidak penting, jadi kita hanya memilih purata magnetisasi tapak tunggal .
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
Sekarang kita menambahkan semakan pra- dan pasca-litar pada setiap qubit, termasuk tujuh qubit penonton yang bersebelahan. Apabila anda menambahkan semua semakan yang mungkin pada litar, anda boleh memutuskan strategi pascapemilihan yang hendak digunakan semasa pascapemprosesan.
# 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 mensampel litar yang mengandungi semakan 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 mengetahui struktur creg bagi semakan tersebut, jadi ia dapat mengaitkan setiap bit dalam sampel dengan pengukuran litar yang berkaitan. Kaedah compute_mask miliknya boleh digunakan untuk mencipta topeng ke atas satu jujukan sampel, berdasarkan strategi pascapemilihan (nod atau tepi) dan jenis semakan ralat bukan Markov yang hendak dilihat (pra- atau pasca-litar). Untuk menggabungkan strategi pra- dan pasca-litar menjadi satu topeng, topeng mereka boleh dikira secara berasingan dan kemudian digabungkan dengan operasi AND. Di sini kita menggunakan PostSelector untuk mencipta topeng bagi pelbagai strategi dan melihat keputusan menggunakan setiap permutasi strategi yang tersedia.
Dalam visualisasi di bawah, kita melihat bagaimana strategi pascapemilihan mempengaruhi kadar pascapemilihan, serta kualiti jawapan. Seperti dijangka, strategi yang melibatkan pascapemilihan berasaskan nod menghasilkan kadar pascapemilihan yang paling rendah. Strategi berasaskan tepi lebih longgar dan cenderung menghasilkan kadar pascapemilihan yang lebih tinggi. Pengguna harus mencuba pelbagai strategi pada masalah mereka untuk menentukan keseimbangan yang tepat antara kadar pascapemilihan dan kualiti taburan yang disampel. Dalam contoh ini, penggunaan pascapemilihan berasaskan nod dengan semakan pra- dan pasca-litar menghasilkan jawapan terbaik dengan kadar pascapemilihan .028%. Sesetengah strategi tidak mengatasi litar mentah. Anda juga dapat melihat bahawa menambahkan semakan tetapi tidak menggunakan sebarang daripadanya (bar berlabel "no selection" dalam carta di bawah) memberikan prestasi yang paling teruk.
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)

