Lumaktaw sa pangunahing nilalaman
Ang proyektong ito ay nasa beta. Nakakita ng bug? Mag-bukas ng GitHub issue. May tanong o ideya? Magtanong sa Discussions — gusto naming marinig ang iyong feedback!
Gaano kaganda ang kalidad ng pagsasalin sa pahinang ito?

Postselection na may non-Markovian error checks

Ipinapaliwanag ng gabay na ito kung ano ang mga non-Markovian error check at kung paano ipatupad ang mga ito sa software gamit ang qiskit-mitigation. Magsisimula ka sa isang maliit na dalawang-qubit na halimbawa para i-visualize ang mga check at talakayin kung paano gumagana ang mga ito. Pagkatapos mong magkaroon ng kaunting intuwisyon, susubukan mo ang 20-qubit na halimbawa kung saan magpapatupad ka ng mga check at maghahambing ng iba't ibang postselection strategy.

Ano ang mga non-Markovian error check?​

Ang mga non-Markovian error check ay mga pagsubok na nakabatay sa measurement kung malamang na nasira ang estado ng isang qubit ng isang non-Markovian na pinagmumulan ng error. Ginagawa ang pagsubok na ito sa paglalagay ng bit-flip gate na may pinalawig na tagal (xslow o isang serye ng NN rx gate na may anggulong πN\frac{\pi}{N}) sa bawat qubit sa simula ng circuit, na sinusundan ng karaniwang XGate at pagsukat para matiyak na matagumpay na na-flip ang estado mula ∣0⟩↦∣1⟩↦∣0⟩|0\rangle \mapsto |1\rangle \mapsto |0\rangle. Maaari ring isagawa ang pagsubok na ito sa dulo ng circuit pagkatapos ng bawat terminal measurement para matiyak na matagumpay na ma-flip ng device ang nasukat na estado ng qubit mula ∣x⟩↦∣x⊕1⟩|x\rangle \mapsto |x\oplus1\rangle. Ang mga sample na kaugnay ng mga pumalyang check ay maaaring ituring na hindi mapagkakatiwalaan, at ang pag-postselect lang ng mga sample na pumasa sa lahat ng check ay maaaring magpataas ng fidelity ng noisy na distribution.

Mga estratehiya sa pag-postselect batay sa mga non-Markovian error check​

  • Node-based postselection: I-postselect lang ang mga sample kung saan pumasa ang bawat qubit sa (mga) check nito. Ito ang pinakamahigpit na estratehiya at karaniwang nagreresulta sa mas maraming itinatapong sample.

  • Edge-based postselection: Para sa bawat edge sa coupling graph ng mga qubit, tiyaking kahit isang qubit ang pumasa sa (mga) check. Mas maluwag ang estratehiyang ito; kung pumalya ang isang qubit sa check, maaari pa ring panatilihin ang sample kung pumasa ang lahat ng kapitbahay nito sa kanilang mga check.

Mga spectator qubit​

Maaaring tumukoy ng mga "spectator" na ancilla qubit na katabi ng mga qubit na ginamit sa pagpapatakbo ng orihinal na circuit. Maaaring ilagay ang mga non-Markovian error check sa mga spectator na ito, na nagbibigay ng mas malawak na saklaw laban sa non-Markovian na noise na idinisenyo ang mga check na ito para matukoy.

Bahagi 1. I-visualize ang mga non-Markovian error check​

Bago ipakita ang teknik sa isang QPU, gagawa ka ng maliit na dalawang-qubit na circuit at magdaragdag ng mga check para i-visualize kung paano ito gumagana. Magsimula sa isang Bell state na na-transpile sa ibm_kingston backend QPU.

# 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

Magdagdag ng ilang non-Markovian error check sa simula ng circuit. Ilalagay lang ang mga check sa mga qubit na tinatapos ng isang measurement. Tinitiyak ng mga check na ito na matagumpay na ma-flip ng device ang paunang estado ng qubit mula ∣0⟩↦∣1⟩↦∣0⟩|0\rangle \mapsto |1\rangle \mapsto |0\rangle, at binabasa ang mga measurement ng mga check na ito sa isang bagong classical register na tinatawag na 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

Gayundin, maaari kang magdagdag ng mga check sa dulo ng circuit pagkatapos ng bawat terminal measurement. Tinitiyak ng mga check na ito na maaaring i-flip ng device ang estado ng qubit mula ∣x⟩↦∣x⊕1⟩|x\rangle \mapsto |x\oplus1\rangle, at binabasa ang mga measurement ng mga ito sa isang bagong classical register na tinatawag na 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

Tulad ng nabanggit sa panimula, puwede kang gumamit ng mga ancilla qubit na katabi ng mga data qubit bilang "spectator" para magkaroon ng mas malawak na error coverage. Magdagdag ng mga spectator check bago at pagkatapos ng circuit. Makikita mo na dahil katabi ng data qubit 1 ang physical qubit 2 sa ibm_kingston, naidagdag ito bilang spectator, at nalagyan ito ng mga check bago at pagkatapos ng circuit. Naidagdag ang mga spectator measurement sa mga bagong classical register na tinatawag na spec_pre at spec.

Ang ganitong circuit ay naglalaman ng lahat ng posibleng non-Markovian error check, kaya ang pag-sample mula sa circuit na ito ay nagbibigay sa mga user ng kakayahang mag-postselect batay sa kahit anong kombinasyon ng mga [pre/post]-circuit check at [node/edge]-based na postselection strategy. Tingnan ang Part 2 ng gabay na ito para makita kung paano ito gumagana sa quantum hardware.

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

Part 2: Ipakita ang non-Markovian error checks sa isang QPU at ihambing ang mga postselection strategy​

Ngayong naiintindihan mo na kung ano ang non-Markovian error checks at kung paano ito ipinapatupad sa software, puwede ka nang magpatakbo ng mas malaking halimbawa sa isang QPU at makita kung paano ito gumagana. Una, maghanda ng circuit. Hindi gaanong mahalaga ang pagpili ng circuit; kailangan mo lang ng circuit na madaling i-verify ang output. Dito, pumili tayo ng 20-qubit, 1D mirrored Ising circuit. Hindi madaling i-execute ang circuit na ito sa hardware at dapat itong maapektuhan ng non-Markovian errors, kaya may pagkakataon tayong ipakita ang bisa ng non-Markovian error checks. Dahil din sa mirrored na istruktura, madali nating maihahambing ang mga noisy na kalkulasyon ng expectation value sa mga eksaktong halaga.

Sa eksperimentong ito, kinakalkula natin ang mga expectation value ng observable. Hindi rin mahalaga ang pagpili ng observable OO, kaya pinili lang natin ang average na single-site magnetization 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

Ngayon, magdaragdag tayo ng mga check bago at pagkatapos ng circuit sa bawat qubit, kasama ang pitong katabing spectator qubit. Kapag naidagdag mo na ang lahat ng posibleng check sa isang circuit, puwede kang magpasya kung aling postselection strategy ang gagamitin sa 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)]

Gamitin ang Executor primitive para mag-sample ng circuit na may mga check.

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

Alam ng PostSelector class ang istruktura ng creg ng mga check, kaya kaya nitong iugnay ang bawat bit sa mga sample sa kaukulang circuit measurement nito. Ang compute_mask method nito ay magagamit para gumawa ng mask sa isang sequence ng mga sample, ayon sa postselection strategy (node o edge) at sa uri ng non-Markovian error checks na titingnan (pre- o post-circuit). Para pagsamahin ang pre- at post-circuit strategy sa iisang mask, puwedeng kalkulahin nang hiwalay ang mga mask nila at pagkatapos ay pagsamahin gamit ang AND operation. Dito, ginagamit natin ang PostSelector para gumawa ng mga mask para sa lahat ng iba't ibang strategy at tingnan ang mga resulta gamit ang bawat posibleng permutation ng mga available na strategy.

Sa visualization sa ibaba, makikita natin kung paano naaapektuhan ng postselection strategy ang postselection rate, pati na ang kalidad ng sagot. Gaya ng inaasahan, ang mga strategy na gumagamit ng node-based postselection ang nagresulta sa pinakamababang postselection rate. Mas maluwag ang mga edge-based na strategy at karaniwang nagreresulta sa mas mataas na postselection rate. Dapat subukan ng mga user ang iba't ibang strategy sa kanilang problema para matukoy ang tamang balanse sa pagitan ng postselection rate at kalidad ng na-sample na distribution. Sa halimbawang ito, ang paggamit ng node-based postselection kasama ang pre- at post-circuit checks ang nagbigay ng pinakamahusay na sagot na may postselection rate na .028%. May ilang strategy na hindi nakahigit sa raw circuit. Makikita mo rin na ang pagdaragdag ng mga check nang hindi gumagamit ng alinman sa mga ito (ang bar na may label na "no selection" sa chart sa ibaba) ang pinakamasama ang performance.

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