Use postselection in workloads
Halaman ini belum diterjemahkan. Anda sedang melihat versi asal dalam bahasa Inggeris.
Package versions
The code on this page was developed using the following requirements. We recommend using these versions or newer.
qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1
qiskit-addon-utils~=0.3.1
When optimizing a workload's error mitigation strategy, it is often useful to filter out measurements known to have been contaminated by non-Markovian (correlated) noise processes. One such method for doing so involves appending a circuit with a post-processing step that measures active and adjacent "spectator" qubits, applies a slow rotation to each qubit, and then measures them again. In instances where the two measurements do not confirm a flipped qubit as expected, the shot is discarded by applying a mask to the results.
The Qiskit addon utilities package provides a set of transpiler passes and a postselection function to apply the mask. This page provides guidance on how to incorporate postselection into your quantum workloads by using a four-qubit GHZ state as an example.
Create workloadβ
Start by preparing the circuit to execute and transpile against a backend that supports fractional gates.
# Added by doQumentation β required packages for this notebook
!pip install -q qiskit qiskit-addon-utils qiskit-ibm-runtime
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
circuit = QuantumCircuit(4)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.measure_all()
service = QiskitRuntimeService()
backend = service.least_busy(use_fractional_gates=True)
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
transpiled_circuit = pm.run(circuit)
transpiled_circuit.draw("mpl")
Add postselection transpiler passesβ
Next, create a preset pass manager that includes the AddPostSelectionMeasures and AddSpectatorMeasures passes from the qiskit-addon-utils package. This will append the circuit with a sequence of small angle RX rotations (effectively producing a long X gate) along with a second set of measurements.
from qiskit.transpiler import PassManager
from qiskit_addon_utils.noise_management.post_selection import PostSelector
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
AddPostSelectionMeasures,
AddSpectatorMeasures,
)
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map, add_barrier=True),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
template_circuit_ps = post_selection_pm.run(transpiled_circuit)
template_circuit_ps.draw("mpl", fold=-1, idle_wires=False)
Execute quantum programβ
Next, prepare a QuantumProgram object containing the circuit to be executed.
from qiskit_ibm_runtime import QuantumProgram, Executor
shots = 4000
program = QuantumProgram(shots=shots)
program.append_circuit_item(template_circuit_ps)
# Initialize the Executor job and run
executor = Executor(backend)
executor_job = executor.run(program)
print(f"Job ID: {executor_job.job_id()}")
Job ID: d82dumugbeec73alm5g0
Now you can interpret the results. The executor result is a dictionary with several keys.
executor_result = executor_job.result()[0]
executor_result.keys()
dict_keys(['meas', 'spec', 'meas_ps', 'spec_ps'])
These keys correspond to the active and spectator qubits before the rx instructions (meas and spec) and after the rx instructions (meas_ps and spec_ps). Each of these is an array of arrays based on the number of shots and qubits. In this case, the shape is (1000, 4).
Create postselection maskβ
From these measurements, you can create a mask using the PostSelector class from qiskit-addon-utils. This mask is a boolean array where each shot is marked as either True or False based on one of two postselection strategies. The first strategy, node, uses qubit information to decide whether a measurement shot should be discarded β and the second, edge, uses nearest-neighbor connectivity information to make this decision.
post_selector = PostSelector.from_circuit(
circuit=template_circuit_ps, coupling_map=backend.coupling_map
)
mask_node = post_selector.compute_mask(executor_result, strategy="node")
mask_edge = post_selector.compute_mask(executor_result, strategy="edge")
Both the node and the edge strategies often discard different shots. You can choose to select any of them. This notebook takes a bitwise AND, which is a conservative strategy that retains a shot only if it is passed by both node and edge strategies.
mask = mask_node & mask_edge
print(f"The combined mask: {mask}")
count_retained = 0
for m in mask:
count_retained += m
print(
f"Percentage of the shots retained is after post selection {100 * count_retained / shots}"
)
The combined mask: [ True True True ... True True True]
Percentage of the shots retained is after post selection 75.225
Compare the probability distribution with and without postselection. The following snippet computes the probability distribution before and after postselection, as well as the distance between the measured and ideal distributions.
counts = {}
counts_ps = {}
for idx, measurement in enumerate(executor_result["meas"]):
bitstring = ""
for bit in measurement:
bitstring += str(int(bit))
if bitstring in counts:
counts[bitstring] += 1
else:
counts[bitstring] = 1
# Compute count data for postselected shots based on the mask
if mask[idx]:
bitstring = ""
for bit in measurement:
bitstring += str(int(bit))
if bitstring in counts_ps:
counts_ps[bitstring] += 1
else:
counts_ps[bitstring] = 1
for key, val in counts.items():
counts[key] = val / shots
for key, val in counts_ps.items():
counts_ps[key] = float(val / count_retained)
To demonstrate how the postselection changed your results, compute the distance between the ideal probability distribution and the measured ones.
import itertools
from qiskit.visualization import plot_histogram
bitstrings = ["".join(i) for i in itertools.product("01", repeat=4)]
counts_ideal = {}
for bitstring in bitstrings:
counts_ideal[bitstring] = 0.0
counts_ideal["1111"] = 0.5
counts_ideal["0000"] = 0.5
prob_distance = 0.0
prob_distance_ps = 0.0
for bitstring in counts_ideal.keys():
dist = 0.0
dist_ps = 0.0
if bitstring in counts:
dist = abs(counts[bitstring] - counts_ideal[bitstring])
if bitstring in counts_ps:
dist_ps = abs(counts_ps[bitstring] - counts_ideal[bitstring])
prob_distance += dist
prob_distance_ps += dist_ps
print(
f"Distance from ideal distribution before postselection: {1-prob_distance*0.5}"
)
print(
f"Distance from ideal distribution before after-selection: {1-prob_distance_ps*0.5}"
)
plot_histogram([counts, counts_ps], legend=["Normal", "Post selected"])
Distance from ideal distribution before postselection: 0.9015
Distance from ideal distribution before after-selection: 0.9416749750747756
While postselection can significantly improve result quality by filtering out outcome measurements that were affected by non-Markovian noise, it is not a complete solution to error mitigation on its own. Postselection reduces the impact of certain errors by discarding invalid measurement results, but this comes at the cost of increased sampling overhead and does not address all error mechanisms present in near-term quantum hardware. As a result, it is likely insufficient to rely solely on postselection for more complex or deeper circuits. Instead, postselection is most effective when used as part of a broader error mitigation strategy β complementing techniques such as measurement error mitigation, noise-aware circuit compilation, or probabilistic error cancellation β to improve the reliability of quantum workloads while balancing accuracy and resource cost.
Next stepsβ
- Understand how to incorporate noise learning into a quantum workload.
- Read through other available error mitigation and suppression techniques.
- Learn how to use spacetime codes for a low-overhead approach to error detection