Langkau ke kandungan utama

Meningkatkan Nilai Jangkaan: Penyerapan Hingar Terpropagat (PNA)

Dalam tutorial ini, kita akan belajar cara memanfaatkan alat terkini dalam ekosistem Qiskit untuk melaksanakan aliran kerja yang boleh disesuaikan sepenuhnya dengan pengurangan ralat. Kita akan memperkenalkan teknik PNA dan menggunakannya untuk mengurangkan ralat Gate. Kita juga akan menggunakan TREX untuk mengurangkan ralat pembacaan dan pemilihan pasca untuk mengurangkan ralat yang tidak ditangkap dalam model hingar yang dipelajari.

Garis Besar

  • Berikan gambaran ringkas tentang PNA
  • Cipta Circuit Trotter kuantum dan pemerhatian (observable). Transpail ke Backend dan masukkan ukuran pemilihan pasca.
  • Guna samplomatic untuk memutar lapisan Gate 2Q dan ukuran. Cari lapisan 2Q unik untuk mengurangkan kos pembelajaran hingar.
  • Guna NoiseLearnerV3 untuk mempelajari model ralat yang mempengaruhi Gate 2Q dan ukuran.
  • Guna qiskit-addon-pna untuk menjana pemerhatian yang mengurangkan hingar
  • Guna primitif qiskit-ibm-runtime.Executor untuk menjana sampel QPU mentah yang mencerminkan setiap shot bagi setiap rawak putar dan asas yang diukur
  • Guna qiskit-addon-utils untuk memproses data menjadi nilai jangkaan yang telah dikurangkan hingar.

Apakah penyerapan hingar terpropagat (PNA)?​

Teknik untuk mengurangkan ralat Gate dengan mempropagat pemerhatian melalui saluran hingar songsang yang mempengaruhi Gate 2-qubit, menghasilkan pemerhatian yang mengurangkan hingar. Gate 2Q dalam eksperimen yang ingin kita jalankan akan dipengaruhi oleh hingar yang ketara. Eksperimen beringar Jika kita mempelajari model hingar, kita boleh menerapkan songsangannya dan membatalkan hingar tersebut. Eksperimen dengan hingar dikurangkan Daripada melaksanakan saluran hingar songsang dengan mengambil sampelnya di QPU seperti dalam PEC, kita boleh melaksanakannya secara klasik dalam pemerhatian yang diukur menggunakan perambatan Pauli. Ini menghasilkan pemerhatian yang lebih kompleks yang, apabila diukur, memberi kesan mengurangkan hingar Gate yang dipelajari. Gambaran keseluruhan PNA

Jana Circuit Trotter yang dicerminkan dan pemerhatian​

Untuk eksperimen ini, kita akan mengkaji dinamik masa bagi model Ising tertendang 30-tapak pada rantai spin 1D. Hamiltonian yang dipertimbangkan ialah:

H=βˆ’Jβˆ‘βŸ¨i,j⟩ZiZj+hβˆ‘iXiH = -J\sum\limits_{\langle i,j \rangle} Z_iZ_j + h\sum\limits_iX_i,

di mana J>0J>0 menerangkan gandingan spin jiran terdekat, i<ji<j, dan medan melintang global, hh, ditetapkan kepada Ο€8\frac{\pi}{8}. Semakin jauh hh dari sudut Clifford (iaitu ΞΈ=nΟ€2,n∈Z\theta=n\frac{\pi}{2}, n \in \mathbb{Z}), semakin sukar untuk mempropagat penjana anti-hingar melalui Circuit.

Untuk pilihan pemerhatian, kita akan mempertimbangkan purata magnet tapak tunggal, 1Nβˆ‘i=1N⟨zi⟩\frac{1}{N} \sum_{i=1}^{N} \langle z_i \rangle, di mana NN ialah bilangan tapak.

# Added by doQumentation β€” required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-pna qiskit-addon-utils qiskit-ibm-runtime samplomatic
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Pauli, SparsePauliOp

num_qubits = 30
num_trotter_steps = 10
rx_angle = np.pi / 8

# Avg single-site magnetization
id_pauli = Pauli("I" * num_qubits)
observable = SparsePauliOp([id_pauli.dot(Pauli("Z"), [i]) for i in range(num_qubits)]) / num_qubits

# Implement Trotterized kicked-Ising model
circuit = QuantumCircuit(num_qubits)
for _step in range(num_trotter_steps):
circuit.rx(rx_angle, range(num_qubits))
for first_qubit in (1, 2):
for idx in range(first_qubit, num_qubits, 2):
# equivalent to Rzz(-pi/2):
circuit.sdg([idx - 1, idx])
circuit.cz(idx - 1, idx)
circuit.compose(circuit.inverse(), inplace=True)
circuit.measure_active()
circuit.draw("mpl", fold=-1)

Rajah Circuit kuantum

Seterusnya, kita akan memilih rantai Qubit pada ibm_kingston yang melaporkan kadar ralat rendah dan mentranspail Circuit ke Backend.

from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService

backend_name = "ibm_kingston"
service = QiskitRuntimeService()
backend = service.backend(backend_name, use_fractional_gates=True)

# Use a chain of low-noise qubits
layout = [
44,
45,
46,
47,
57,
67,
68,
69,
78,
89,
88,
87,
97,
107,
106,
105,
117,
125,
126,
127,
128,
129,
118,
109,
110,
111,
98,
91,
92,
93,
]

pm = generate_preset_pass_manager(backend=backend, initial_layout=layout, optimization_level=0)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
isa_circuit.draw("mpl", fold=-1)
qiskit_runtime_service._discover_account:WARNING:2025-11-10 14:30:57,148: Loading account with the given token. A saved account will not be used.

Rajah Circuit kuantum

Putar lapisan Gate 2-qubit dan ukuran serta cari lapisan unik​

Di sini kita memastikan pengurus laluan (pass manager) menganotasi kotak dengan anotasi Twirl dan InjectNoise, yang membolehkan kita mempelajari hingar yang akan mempengaruhi Circuit kita dan mengaitkan hingar tersebut dengan lapisan Circuit yang sepadan.

  • enable_gates/enable_measure: True: Kotakkan semua lapisan Gate 2q dan ukuran terminal. Gate qubit tunggal akan dipakaikan di dalam kotak.
  • measure_annotations: all Masukkan anotasi Twirl dan ChangeBasis pada kotak ukuran
  • twirling_strategy: active: Pusing semua Qubit aktif dalam setiap kotak yang mengandungi Gate penjalin
  • inject_noise_targets: gates: Anotasi InjectNoise perlu ditambah pada semua kotak beranotasi Twirl yang mengandungi Gate penjalin
  • inject_noise_strategy: uniform_modification: Semua lapisan hingar perlu diskala secara setara.
from samplomatic.transpiler import generate_boxing_pass_manager

# Box up circuit with Twirl and InjectNoise annotations
pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
twirling_strategy="active",
inject_noise_targets="gates",
inject_noise_strategy="uniform_modification",
remove_barriers=True,
)
boxed_circuit = pm.run(isa_circuit)
draw_circ = QuantumCircuit(boxed_circuit.num_qubits)
draw_circ.append(boxed_circuit.data[0], qargs=boxed_circuit.data[0].qubits)
draw_circ.append(boxed_circuit.data[1], qargs=boxed_circuit.data[1].qubits)
draw_circ.draw("mpl", fold=-1, scale=0.3, idle_wires=False)

Rajah Circuit kuantum

Jana Circuit templat dan samplex, tentukan cara Circuit akan disampling​

Di sini kita juga menambah ukuran spektator dan pemilihan pasca, yang diperlukan untuk melakukan pemilihan pasca pada sampel output dari Executor.

import samplomatic
from qiskit.transpiler import PassManager
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
AddPostSelectionMeasures,
AddSpectatorMeasures,
)

# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)

# Add post-selection instructions to the template circuit
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
template_circuit = post_selection_pm.run(template_circuit)
draw_circ = template_circuit.copy_empty_like()
draw_circ.data = template_circuit.data[:324]
draw_circ.draw("mpl", fold=-1, scale=0.3, idle_wires=False)

Rajah Circuit kuantum

Pelajari hingar​

Sebelum kita menjalankan eksperimen, kita mempelajari model hingar yang mempengaruhi Gate penjalin dan ukuran dalam Circuit. Mempunyai model hingar yang tepat adalah perlu untuk mengurangkan ralat dengan berkesan. Mempelajari hingar sejurus sebelum melaksanakan eksperimen memberi peluang terbaik agar model hingar menggambarkan hingar sebenar yang mempengaruhi Gate semasa pelaksanaan.

Sebelum kita mempelajari hingar, kita perlu mencari lapisan 2-qubit unik dalam Circuit kita, supaya kita boleh meminimumkan bilangan shot yang diperlukan untuk mempelajari hingar bagi keseluruhan Circuit. Kita menggunakan find_unique_box_instructions dari samplomatic untuk mendapatkan lapisan unik dari Circuit berkotak, termasuk lapisan ukuran. Lapisan-lapisan inilah yang kita hantar ke pemelajar hingar.

Setelah kita mengetahui lapisan-lapisan tersebut, kita boleh mempelajari hingar. Terdapat beberapa parameter yang perlu dipertimbangkan:

  • num_randomizations: Bilangan Circuit rawak yang digunakan per konfigurasi Circuit pembelajaran
  • shots_per_randomization: Jumlah shot yang digunakan per Circuit pembelajaran rawak
  • layer_pair_depths: Kedalaman Circuit (diukur dalam bilangan pasangan) yang digunakan dalam eksperimen pembelajaran.
  • post_selection: Kita akan menggunakan pemilihan pasca berasaskan tepi semasa pembelajaran menggunakan Gate rx untuk melaksanakan denyutan pasca-ukuran
from qiskit_ibm_runtime.noise_learner_v3.noise_learner_v3 import NoiseLearnerV3
from qiskit_ibm_runtime.options import NoiseLearnerV3Options
from samplomatic.utils import find_unique_box_instructions

# Load noise learner data from a shared job
load_saved_nl_result = True

# Noise learning parameters
num_randomizations_nl = 64
shots_per_randomization_nl = 128
strategy = "edge"
enable_postsel = True
x_pulse_type = "rx"

# Find the unique instructions (layers) from boxed-up circuit
unique_2q_layers_and_meas = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)

noise_learner_params = {
"num_randomizations": num_randomizations_nl,
"shots_per_randomization": shots_per_randomization_nl,
"layer_pair_depths": [1, 2, 4, 8, 12, 16, 24, 32, 40, 48],
"post_selection": {
"enable": enable_postsel,
"strategy": strategy,
"x_pulse_type": x_pulse_type,
},
"experimental": {},
}
# set the options
noise_learner_options = NoiseLearnerV3Options(**noise_learner_params)

# run the noise learner job
noise_learner = NoiseLearnerV3(backend, noise_learner_options)
noise_learner_job = noise_learner.run(unique_2q_layers_and_meas)
noise_learner_result = noise_learner_job.result()

nl_metadata = noise_learner_params | {"layout": layout}
import matplotlib.pyplot as plt

hw_rates_1q = []
hw_rates_2q = []
for nlr in noise_learner_result[:2]:
plm_list = nlr.to_pauli_lindblad_map().to_sparse_list()
hw_rates_1q += [rate for (pstr, qubits, rate) in plm_list if len(pstr) == 1]
hw_rates_2q += [rate for (pstr, qubits, rate) in plm_list if len(pstr) == 2]
hw_rates_1q = sorted(hw_rates_1q)
hw_rates_2q = sorted(hw_rates_2q)
median_1q = hw_rates_1q[len(hw_rates_1q) // 2]
median_2q = hw_rates_2q[len(hw_rates_2q) // 2]
fig, ax = plt.subplots(1, 1, figsize=(14, 5))
ax.scatter(
(hw_rates_1q),
[(i) / (len(hw_rates_1q) - 1) for i in range(len(hw_rates_1q))],
color="red",
label="1q rates",
)
ax.set_xscale("log")
ax.set_ylim(0, 1.1)
ax.vlines(median_1q, 0, 1, color="red")
ax.text(median_1q * 1.1, 0.1, f"{median_1q:.2e}")
ax.scatter(
(hw_rates_2q),
[(i) / (len(hw_rates_2q) - 1) for i in range(len(hw_rates_2q))],
color="blue",
label="2q rates",
)
ax.set_xscale("log")
ax.set_ylim(0, 1.1)
ax.vlines(median_2q, 0, 1, color="blue")
ax.text(median_2q * 1.1, 0.2, f"{median_2q:.2e}")
ax.set_title("Learned noise rates")
ax.set_xlabel("Noise rate")
ax.set_yticks([])
plt.legend()
<matplotlib.legend.Legend at 0x321dd63f0>

Output plot

Kaitkan kotak litar dengan hingar yang dipelajari​

Di sini, kita buat pemetaan antara ID rujukan InjectNoise bagi setiap kotak kepada model hingar yang dipelajari (PauliLindbladMap) yang mempengaruhi get-get belitan dalam kotak tersebut.

from samplomatic.annotations import InjectNoise
from samplomatic.utils import get_annotation

# map inject noise refs to pauli lindblad maps
refs_to_noise_models = {}
for instruction, result in zip(unique_2q_layers_and_meas, noise_learner_result, strict=False):
if inject_noise_annot := get_annotation(instruction.operation, InjectNoise):
refs_to_noise_models[inject_noise_annot.ref] = result.to_pauli_lindblad_map()

Sebarkan observable melalui anti-hingar yang dipelajari untuk mendapatkan observable pengurang hingar​

Seperti yang dibincangkan di atas, ini dilakukan dalam dua langkah. Pertama, kita sebarkan penjana anti-hingar ke penghujung litar. Selepas itu, kita sebarkan observable melalui penjana yang telah berkembang itu. Proses ini diulang bagi setiap penjana anti-hingar dalam litar. Dalam pelaksanaan ini, setiap penjana dalam lapisan tertentu disebarkan ke penghujung litar secara selari. Selain itu, pemprosesan berbilang Python digunakan untuk melakukan penyebaran ke hadapan anti-hingar serta penyebaran ke belakang observable secara selari. Ini mencegah penumpukan penjana yang telah berkembang dalam ingatan dan juga memaksimumkan sumber pengiraan.

Apabila menjalankan PNA, kamu perlu sentiasa menyediakan litar berderau dan observable. Jika litar berderau kamu ialah litar berkotak dengan anotasi InjectNoise, kamu perlu menyediakan pemetaan yang kita buat dalam langkah di atas. Seseorang juga boleh menghantar litar tanpa kotak yang mengandungi arahan PauliLindbladError daripada qiskit-aer. Dalam kes itu, refs_to_noise_models tidak perlu disediakan. Selain input utama, pengguna perlu mempertimbangkan:

  • max_err_terms: Bilangan sebutan yang dikekalkan dalam setiap penjana anti-hingar semasa ia disebarkan ke hadapan. Membenarkan nilai ini lebih besar secara amnya meningkatkan ketepatan, tetapi kelakuan ini tidak dijamin bersifat monoton.
  • max_obs_terms: Bilangan sebutan yang dikekalkan dalam observable pengurang hingar, O~\tilde{O}, semasa ia disebarkan ke belakang melalui anti-hingar yang telah berkembang. Nilai yang lebih besar secara amnya meningkatkan ketepatan, tetapi tidak dijamin berlaku secara monoton.
  • num_processes: Bilangan teras yang diperuntukkan untuk proses ini. Ingat, penjana disebarkan ke hadapan dan digunakan pada observable secara selari.
  • search_step: Langkah penyebaran ke belakang menggunakan kaedah tamak untuk mengkonjugat secara anggaran dua pengoperasi dalam asas Pauli. Kaedah ini boleh dipercepatkan dengan meningkatkan search_step. Lihat dokumentasi pauli-prop untuk maklumat lanjut.
  • num_to_measure: Walaupun pemboleh ubah ini bukan input kepada generate_noise_mitigating_observable, kita gunakannya untuk mengawal berapa banyak sebutan daripada O~\tilde{O} yang sebenarnya kita mahu ukur. Di sini kita hanya akan mengukur 30 sebutan teratas, iaitu sebutan asal dalam observable kita. Sebutan-sebutan tersebut kini telah diskalakan semula supaya mengukurnya mempunyai kesan mengurangkan hingar get yang dipelajari. Walaupun kita hanya mengukur 30 sebutan daripada O~\tilde{O}, seringkali masih berguna untuk membiarkannya berkembang besar, kerana itu meningkatkan ketepatan faktor penskalaan sebutan-sebutan terdepan.
from qiskit_addon_pna import generate_noise_mitigating_observable

# PNA parameters
num_processes = 8
max_err_terms = 10_000
max_obs_terms = 10_000
num_to_measure = num_qubits

obs_tilde_isa = generate_noise_mitigating_observable(
boxed_circuit,
isa_observable,
refs_to_noise_models,
max_err_terms=max_err_terms,
max_obs_terms=max_obs_terms,
num_processes=num_processes,
print_progress=True,
search_step=8,
)
p_2_v = {p: v for v, p in enumerate(layout)}
obs_tilde_virtual = SparsePauliOp.from_sparse_list(
[
(pstr, [p_2_v[p] for p in p_qubits], coeff)
for (pstr, p_qubits, coeff) in obs_tilde_isa.to_sparse_list()
],
num_qubits=num_qubits,
)
obs_tilde_virtual = obs_tilde_virtual[np.argsort(np.abs(obs_tilde_virtual.coeffs))[::-1]][
:num_to_measure
]
Finished! 13560 / 13560 generators propagated.
obs_tilde_isa = obs_tilde_isa[np.argsort(np.abs(obs_tilde_isa.coeffs))][::-1]
plt.xscale("log")
plt.yscale("log")
plt.title(r"$\tilde{O}$ coeff magnitudes")
plt.ylabel("Magnitude")
plt.xlabel("Pauli term index")
plt.plot(np.abs(obs_tilde_isa.coeffs), ".")
[<matplotlib.lines.Line2D at 0x16b69e840>]

Plot output

Ubah asas pengukuran ke bentuk kanonik​

Seterusnya, kita akan mencari set asas minimum untuk diukur supaya kita dapat meliputi sepenuhnya setiap sebutan Pauli dalam observable yang diukur (banyak observable boleh diukur serentak jika ia komut secara qubit-wise). Memandangkan kita hanya mengukur sebutan dalam observable asal kita, iaitu jumlah semua Pauli tunggal-Z, hanya satu asas diperlukan -- asas semua-Z.

Selain mencari set asas pengukuran Pauli, kita perlu memetakan sebutan-sebutan Pauli ini ke bentuk kanonik yang dijangkakan oleh primitif Executor. Untuk maklumat lanjut tentang pengaturan Qubit kanonik, layari dokumentasi samplomatic.

from qiskit_addon_utils.exp_vals.measurement_bases import get_measurement_bases

meas_box = boxed_circuit.data[-1]
canonical_qubits = [
idx for idx, qubit in enumerate(boxed_circuit.qubits) if qubit in meas_box.qubits
]
c_2_p = {c: p for c, p in enumerate(canonical_qubits)} # canonical -> physical
p_2_v = {p: v for v, p in enumerate(layout)} # physical -> virtual
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()} # canonical -> virtual
meas_bases, bases_reverser = get_measurement_bases(obs_tilde_virtual)
meas_bases_canonical = [
np.array([base[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8) for base in meas_bases
]

Tentukan cara pensampelan dalam QuantumProgram​

QuantumProgram adalah tempat kita menentukan cara untuk mensampel eksperimen:

  • template_circuit: Litar yang mengandungi semua get yang diperlukan untuk melaksanakan semua rawak yang dikehendaki (daripada rawak twirling, parameter, dll.).
  • samplex: Objek yang mendefinisikan taburan kebarangkalian ke atas semua kemungkinan rawak litar untuk disampel.
  • samplex_arguments: Pengikatan yang diperlukan untuk menentukan samplex sepenuhnya
    • basis_changes: Di sinilah kita menentukan set asas untuk diukur yang akan meliputi semua sebutan Pauli dalam observable yang diukur.
    • noise_scales.ref: Kita tetapkan skala setiap lapisan hingar kepada 0.0 untuk mencegah hingar tambahan daripada disuntik ke dalam sampel kita
    • pauli_lindblad_maps: Diperlukan jika noise_scales dihantar. Ini hanya memetakan lapisan hingar kepada model hingar yang berkaitan.
  • shape: Tupel bentuk untuk melanjutkan bentuk implisit yang ditakrifkan oleh samplex_arguments. Paksi bukan trivial yang diperkenalkan oleh pelanjutan ini menghitung rawak.
from qiskit_ibm_runtime import QuantumProgram

# Control the # of shots during execution
shots_per_randomization_exec = 64
num_randomizations_exec = 6144

# Zero out the noise to prevent noise from being injected during execution.
# We only added InjectNoise annotations so PNA could associate the noise
# to layers in the circuit
samplex_inputs = {f"noise_scales.{ref}": 0.0 for ref in refs_to_noise_models}
samplex_inputs |= {"pauli_lindblad_maps": refs_to_noise_models}

# Specify the bases to measure
bases_broadcastable = np.expand_dims(np.array(meas_bases_canonical), axis=1)
samplex_inputs |= {"basis_changes": {"basis0": bases_broadcastable}}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = samplex.inputs().make_broadcastable().bind(**samplex_inputs)

# Instantiate the QuantumProgram with the specified parameters
program = QuantumProgram(shots=shots_per_randomization_exec)
program.append(
circuit=template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations_exec),
)

Sampel litar menggunakan prototaip primitif Executor​

Setelah kita mendefinisikan QuantumProgram, melaksanakan eksperimen adalah mudah. Kita hanya perlu membuat instantiasi objek Executor, menyediakan Backend, dan menjalankan program.

from qiskit_ibm_runtime import Executor

# Execute (sample) the circuit
executor = Executor(backend)
job_exec = executor.run(program)
exec_results = job_exec.result()

Proses pasca sampel untuk mengira nilai jangkaan termitigasi ralat​

Untuk mengira nilai jangkaan termitigasi ralat, kita akan:

  • Mengira faktor penskalaan TREX berdasarkan hingar yang dipelajari yang mempengaruhi pengukuran
  • Menjana topeng untuk mengekalkan hanya sampel terpilih pasca
  • Menggunakan fungsi executor_expectation_values daripada qiskit-addon-utils untuk menggabungkan semua data menjadi nilai jangkaan termitigasi ralat.
from qiskit_addon_utils.exp_vals.expectation_values import executor_expectation_values
from qiskit_addon_utils.noise_management import trex_factors
from qiskit_addon_utils.noise_management.post_selection import PostSelector

# Computing the TREX factors
measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_rescale_factors = trex_factors(measurement_noise_map, bases_reverser)

# Post-select the results
post_selector = PostSelector.from_circuit(
circuit=template_circuit, coupling_map=backend.coupling_map
)

# Compute the ps mask for filtering results
mask = post_selector.compute_mask(exec_results[0], strategy="edge")

# Compute expvals using post selected results
results = executor_expectation_values(
exec_results[0]["meas"],
bases_reverser,
meas_basis_axis=0,
avg_axis=1,
measurement_flips=exec_results[0]["measurement_flips.meas"],
pauli_signs=exec_results[0].get("pauli_signs", None),
postselect_mask=mask,
rescale_factors=trex_rescale_factors,
)
bases_reverser_unmit = {Pauli("Z" * num_qubits): [observable]}
args = [
(bases_reverser_unmit, None, None),
(bases_reverser, None, None),
(bases_reverser, None, trex_rescale_factors),
(bases_reverser, mask, None),
(bases_reverser, mask, trex_rescale_factors),
]

evs = []
for reverser, postsel_mask, factors in args:
# Compute expvals using post selected results
res_ps = executor_expectation_values(
exec_results[0]["meas"],
reverser,
meas_basis_axis=0,
avg_axis=1,
measurement_flips=exec_results[0]["measurement_flips.meas"],
pauli_signs=exec_results[0].get("pauli_signs", None),
postselect_mask=postsel_mask,
rescale_factors=factors,
)
res_ps = np.array(res_ps)
evs.append(res_ps[:, 0][0])

experiments = ["PNA", "PNA+TREX", "PNA+PS", "PNA+PS+TREX"]
colors = ["#d9d9d9", "#b0b0b0", "#7f7f7f", "#4c4c4c"]
plt.bar(experiments, evs[1:], color=colors)
plt.axhline(y=1, color="green", linestyle="--", linewidth=2, label="Ideal")
plt.axhline(y=evs[0], color="red", linestyle="--", linewidth=2, label="Unmitigated")
plt.ylabel("Expectation value", fontsize=14)

plt.title(r"30q Mirrored Ising, 10 Trotter steps, $\theta_{rx}=\frac{\pi}{8}$", fontsize=14)
plt.legend(loc="upper left", bbox_to_anchor=(1.05, 1), borderaxespad=0.0)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Plot output