期待値の改善:伝播ノイズ吸収(PNA)
このチュートリアルでは、Qiskitエコシステムの最新ツールを活用して、完全にカスタマイズ可能なエラー軽減ワークフローを実装する方法を学びます。PNA技術を紹介し、それを使用してゲートエラーを軽減します。また、TREXを使用して読み出しエラーを軽減し、学習したノイズモデルでは捉えられないエラーを軽減するためにポスト選択を使用します。
概要
PNAの簡単な概要を説明します- Trotter化された量子Circuit とObservableを作成します。BackendにTranspileし、ポスト選択測定を含めます。
samplomaticを使用して2Q GateとMeasurementの層をTwirlし、ノイズ学習コストを削減するためにユニークな2Q層を見つけます。NoiseLearnerV3を使用して2Q GateとMeasurementに影響するエラーモデルを学習します。qiskit-addon-pnaを使用してノイズ軽減Observableを生成します。qiskit-ibm-runtime.Executorプリミティブを使用して、すべてのTwirlingランダム化および測定基底のすべてのショットを反映した生のQPUサンプルを生成します。qiskit-addon-utilsを使用してデータを後処理し、軽減された期待値を得ます。
伝播ノイズ吸収(PNA)とは何ですか?
Observableを2Qubit Gateに影響する逆ノイズチャンネルを通じて伝播させることで、ゲートエラーを軽減する技術であり、結果としてノイズ軽減Observableが得られます。
実行したい実験における2Q Gateは、大きなノイズの影響を受けます。
ノイズモデルを学習すれば、その逆を適用してノイズを除去できます。
PECのようにQPU上でサンプリングすることで逆ノイズチャンネルを実装するのではなく、パウリ伝播を使用して測定Observableにおいてそれを古典的に実装できます。これにより、測定時に学習されたゲートノイズを軽減する効果を持つ、より複雑なObservableが得られます。

ミラードTrotter Circuitと Observableの生成
この実験では、1Dスピン鎖上の30サイトキックドイジングモデルの時間ダイナミクスを研究します。考慮するハミルトニアンは次のとおりです:
,
ここで は最近接スピン()の結合を表し、全体的な横断場 は に設定されます。 がクリフォード角(すなわち )から離れるほど、反ノイズ生成子をCircuit全体に伝播させることが難しくなります。
Observableの選択として、平均単一サイト磁化 を考慮します。ここで はサイト数です。
# 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)

次に、ibm_kingston上でエラー率が低いQubitの連鎖を選択し、CircuitをBackendにTranspileします。
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.

2Qubit Gate層とMeasurementのTwirlおよびユニーク層の発見
ここでは、PassマネージャーがBoxにTwirlとInjectNoiseアノテーションを付与することを確認します。これにより、Circuitに影響するノイズを学習し、そのノイズを対応するCircuit層に関連付けることができます。
enable_gates/enable_measure: True: すべての2Q Gate層と終端Measurementをボックス化します。単一Qubit GateはBoxの中 で左側にドレッシングされます。measure_annotations: allMeasurementボックスにTwirlとChangeBasisアノテーションを含めます。twirling_strategy: active: もつれGateを含む各BoxのアクティブなすべてのQubitをTwirlします。inject_noise_targets: gates:InjectNoiseアノテーションは、もつれGateを含むすべてのTwirlアノテーション付きBoxに追加される必要があります。inject_noise_strategy: uniform_modification: すべてのノイズ層を同等にスケーリングします。
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)

テンプレートCircuitとSamplexの生成、Circuitのサンプリング方法の定義
ここでは、Executorから出力されたサンプルに対してポスト選択を実行するために必要なスペクテーターおよびポスト選択Measurementも追加します。
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)

ノイズを学習する
実験を実行する前に、Circuit内のエンタングリングゲートと測定に影響するノイズモデルを学習します。誤りを効果的に軽減するためには、正確なノイズモデルが必要です。実験を実行する直前にノイズを学習することで、ゲートの実行中に実際に影響するノイズをノイズモデルが忠実に記述できる可能性が最も高くなります。
ノイズを学習する前に、Circuit内のユニークな2-Qubitレイヤーを特定する必要があります。これにより、Circuit全体のノイズを学習するために必要なショット数を最小限に抑えることができます。samplomaticのfind_unique_box_instructionsを使用して、測定レイヤーを含むボックス化されたCircuitからユニークなレイヤーを取得します。これらが、ノイズ学習器に渡すレイヤーです。
レイヤーがわかったら、ノイズを学習できます。考慮するパラメーターはいくつかあります:
num_randomizations: 学習Circuit構成ごとに使用するランダムCircuitの数shots_per_randomization: ランダム学習Circuitごとに使用するショットの合計数layer_pair_depths: 学習実験で使用するCircuit深さ(ペア数で測定)post_selection: 測定後のパルスを実装するためにrxゲートを使用して、学習中にエッジベースのポスト選択を使用します
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>

Circuit のボックスを学習済みノイズに関連付ける
ここ では、各ボックスのInjectNoise参照IDと、そのボックス内のエンタングリングゲートに影響する学習済みノイズモデル(PauliLindbladMap)との間のマッピングを作成します。
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()