シェーディッド・ライトコーンを用いた確率的誤り相殺
使用量の目安:Heronプロセッサーで10分(注:これはあくまで目安です。実際の実行時間は異なる場合があります。)
学習目標
-
確率的誤り相殺(PEC)とは何か、そしてそのサンプリングオーバーヘッドが、Circuitに作用する総ノイズに対してなぜ指数関数的に増大するのか
-
シェーディッド・ライトコーン(SLC)が、各ノイズ項の対象オブザーバブルへの寄与をどのように制限し、緩和予算を重要な箇所に使えるようにするか — そして、純粋に幾何学的なライトコーンによってすでに提供されているバイアスゼロのオーバーヘッド削減を超えて、シェーディングによって有限の残留バイアスとより低いサンプリングオーバーヘッドをトレードオフできる仕組み
-
NoiseLearnerV3を用いて層ノイズを学習し、samplomaticとExecutorプリミティブを通じて反ノイズを注入する方法 -
PECおよびPEC+SLCをTREXとポストセレクションと組み合わせて、ハードウェア上で期待値を推定する方法
前提条件
-
Qiskitパターンのワークフロー
-
オブザーバブルの期待値を計算するためのEstimatorプリミティブの使用
-
パウリツイリングやTREXなどのエラー緩和手法。Estimatorプリミティブとエラー緩和オプションの組み合わせで解説
背景
このチュートリアルでは、シェーディッド・ライトコーン(SLC)アドオンを使用してエラーを緩和する方法を示します。このアドオンは、確率的誤り相殺(PEC)技術の発展形であり、ユーザーは回路内の固有の層のノイズを学習した後、単一量子ビットゲートの適用と後処理技術によってノイズを打ち消します。他の手法と比較して、PECは緩和結果のバイアスに対してより堅牢な範囲を提供しますが、QPU時間に関してより高いオーバーヘッドが発生する傾向があります。PEC中、ノイズによる期待値の減衰を補償するために、平均結果はの係数で再スケーリングされます。ここで、は、回路内の層におけるエラーパウリの学習されたノイズ率です。この再スケーリングにより分散が倍になり、そのため、QPU上で必要な回路実行回数も倍になります。これをサンプリングコストまたはサンプリングオーバーヘッドと呼びます。は指数関数的に増大するため、PECは浅い回路や少数量子ビットの回路に限定されることがよくあります。PECの詳細については、ノイズの多い量子プロセッサー上でのスパース・パウリ・リンドブラッドモデルを用いた確率的誤り相殺を参照してください。
緩和する必要のない誤差を特定できれば、このサンプリングコストを指数関数的に削減できます。この方向への第一歩は、局所性を考慮したエラー緩和を実装することです。これは、迅速に計算可能な従来型の「ライトコーン」を使用して、回路全体にわたるオブザーバブルの誤差に対する感度を制限することでPECのオーバーヘッドを削減し、一部の問題に対してPECの実現可能性をより大規模な範囲に拡張します。このライトコーンの外側にある誤差は測定結果に影響を与えることができないため、誤差相殺の手順から除外できます。この除外により、追加のバイアスを導入することなく、サンプリングオーバーヘッドが(場合によっては大幅に)削減されます。特に、固定深度の回路の局所オブザーバブルを測定する場合、回路内の量子ビット数をスケールさせると、必要なサンプリングオーバーヘッドは最終的に頭打ちになります(量子回路の局所性とエラー緩和の図2bを参照)。
シェーディッド・ライトコーン(SLC)はさらに進んで、古典的なシミュレーションを使用して、回路全体にわたる誤差への感度をより厳密に制限します。これはQPU時間の一部をCPU時間と引き換えにするもので、バイアスを再正規化するために必要なサンプリングオーバーヘッドを削減します。ハードカットオフの代わりに、回路内の潜在的な誤差それぞれに、オブザーバブルの期待値がその誤差に対してどれだけ影響を受けやすいかを上限で表す段階的な「シェード」が割り当てられます。(以下で紹介する後方伝播境界では、この感度は回路の初期状態にも依存します — 典型的なワークフローおよびこのチュートリアルでは、全ゼロ状態です。)この洗練された特性評価により、分散を抑えたより効率的で的を絞ったPECの適用が可能になると同時に、ユーザーはオブザーバブル推定におけるバイアスを制御可能な形で調整できます。詳細については、古典的に高速化された量子エラー緩和のためのライトコーン・シェーディングを参照してください。
SLCアドオンのワークフローは、samplomaticライブラリを、qiskit-ibm-runtime v0.47.0でQiskit Runtimeに追加されたQuantumProgramクラスとExecutorクラスとともに活用しており、使いやすさを維持しながら、エラー抑制と緩和の実行設定をより柔軟に制御できるようにしています。詳細については、ディレクテッド実行モデルガイドを参照してください。
SLCエラー緩和ワークフローの概要
QPUのノイズをモデル化するために、デバイスの各量子ビットとエッジで局所的に生成された、1量子ビットおよび2量子ビットのパウリ誤差率を持つスパース・パウリ・リンドブラッドノイズモデルを使用します。この選択により、このチュートリアルで示すSLCエラー緩和ワークフローは次のようになります。
a. CPU — 1量子ビットおよび2量子ビットのパウリ誤差ごとの影響を制限
-
前方伝播(オブザーバブルへの影響を制限)。各誤差を回路の終端まで伝播させ、オブザーバブルとの交換子を計算します。
-
演算子項を進化の間に切り捨て、計算を扱いやすく保ちます。
-
量子速度限界に基づくオブザーバブルの緩い後方伝播により、これらの境界をさらに厳しくします。
-
-
後方伝播(初期状態への影響を制限)。各誤差を回路の始点まで伝播させ、初期状態との交換子を計算します。
b. QPU — ノイズ率を学習。NoiseLearnerV3を使用して、パウリ・リンドブラッドノイズモデルの率を推定します。
c. CPU — 緩和の優先順位付け
-
学習されたノイズ率で統合された境界を更新。以前に計算された前方境界と後方境界を組み合わせ、学習されたノイズ率で更新します。
-
計算された境界と学習された率を使用して、緩和すべきノイズ成分をランク付け。バイアスへの推定影響とそれを補正するために必要なコストに基づいて、各潜在的なノイズ誤差の優先順位を付けます。
d. QPU — 反ノイズを挿入して実行。Boxアノテーションを使用して指定された反ノイズ(逆ノイズ)で、対象の回路を実行します。
e. CPU — オブザーバブルを推定。非マルコフ性ノイズの影響を減らすために測定ベースのポストセレクションを適用しながら、期待値を計算します。
ノイズ学習の概要
ノイズ学習は、ノイズラーナーによって実行される、いくつかのエラー緩和手法における一般的なステップです。また、確率的誤差増幅チュートリアルにも登場します。NoiseLearnerV3では、学習対象のノイズ層をCircuitInstructionオブジェクトとして具体的に特定できるため、上記で説明した方法で各層に対する所望のSLCノイズ境界を計算できます。学習されたパウリ・リンドブラッドモデルは、PEC+SLCの優先順位付けに使用される係数を提供します。ゲートを層にまとめる方法は、generate_boxing_pass_manager関数とfind_unique_box_instructions関数の便利な機能を使用して決定でき、以下のステップ2で説明するように、SLCユーティリティ関数generate_noise_model_paulisに渡すことができます。
| パート 1 | パート 2 | パート 3 |
|---|---|---|
| 2量子ビット・ゲート層をパウリ・ツイリングする | 恒等演算子のペア層を繰り返しノイズを学習する | 忠実度(各ノイズ・チャンネルの誤差)を導出する |
![]() | ![]() |
後処理の概要
samplomatic と Executor フレームワークを使用して量子ハードウェア上で実行した後、ビット列の測定結果を目的のオブザーバブル値に変換します。このチュートリアルでは、ミラーリングされたイジング回路(以下のステップ1で構築)でのワークフローを実演します。この回路では、後半部分が前半部分を打ち消すため、測定するオブザーバブルの理想的な期待値は1になります。executor_expectation_values 関数を用いてオブザーバブル値を計算する際、ノイズの影響を低減するいくつかの後処理技術を適用します。これには、非マルコフ・ノイズの影響を受けたショットの除去、読み出しエラー緩和、および私たちのPEC実装の詳細を考慮することが含まれます。詳細は以下のステップ4で説明します。
必要条件
このチュートリアルを開始する前に、以下がインストールされていることを確認してください。
-
可視化サポートを含むQiskit SDK v2.2以降
-
Qiskit Runtime v0.47以降(
pip install qiskit-ibm-runtime) -
Shaded lightcones Qiskitアドオン v0.1以降(
pip install qiskit-addon-slc) -
Qiskitアドオン・ユーティリティ v0.3以降(
pip install qiskit-addon-utils) -
Samplomatic v0.13以降(
pip install samplomatic)
セットアップ
まず、このノートブックの実行に必要なパッケージと関数をインポートします。
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-slc qiskit-addon-utils qiskit-ibm-runtime samplomatic
from multiprocessing import set_start_method
# Setting this value prevents itertools.starmap deadlock on UNIX systems
set_start_method("spawn")
# Needed to prevent PySCF from parallelizing internally (SLC only)
%set_env OMP_NUM_THREADS=1
env: OMP_NUM_THREADS=1
import numpy as np
from matplotlib import pyplot as plt
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
QuantumProgram,
Executor,
NoiseLearnerV3,
)
import samplomatic
from samplomatic.utils import find_unique_box_instructions
from samplomatic.transpiler import generate_boxing_pass_manager
from qiskit_addon_utils.exp_vals.measurement_bases import (
get_measurement_bases,
)
from qiskit_addon_utils.exp_vals.expectation_values import (
executor_expectation_values,
)
from qiskit_addon_utils.noise_management import (
gamma_from_noisy_boxes,
trex_factors,
)
from qiskit_addon_utils.noise_management.post_selection import PostSelector
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
AddPostSelectionMeasures,
AddSpectatorMeasures,
)
from qiskit_addon_slc.bounds import (
compute_backward_bounds,
compute_forward_bounds,
compute_local_scales,
merge_bounds,
tighten_with_speed_limit,
)
from qiskit_addon_slc.utils import (
generate_noise_model_paulis,
map_modifier_ref_to_ref,
)
from qiskit_addon_slc.visualization import draw_shaded_lightcone
小規模シミュレーターの例
他の学習ベースのエラー緩和手法と同様に、shaded lightconesを用いたPECは特定の量子プロセッサーの物理的ノイズを緩和するため、理想的なシミュレーター上では意味のある類似物が存在しないハードウェア・サービスに依存します。
-
NoiseLearnerV3は、各ユニークな2量子ビット層上のスパース・パウリ・リンドブラッド・ノイズ・チャネルを実験的に特性評価します。ノイズのないシミュレーター上では、打ち消すべきノイズがありません。 -
Executorプリミティブは、samplomaticによって生成された、ツイルされ、アンチノイズが注入された回路をバックエンド上でサンプリングします。
shaded lightcone境界の計算は古典的ですが、学習されたハードウェア・ノイズ率(これが緩和予算とサンプリング・オーバーヘッドを決定します)に対してのみ意味を持ちます。これらの理由から、小規模シミュレーターの例はスキップし、ハードウェア上で直接、PEC+SLCの完全なワークフローを実演します。以下でQiskitパターンの各ステップを分解して説明します。
大規模ハードウェアの例
IBM Quantum® ハードウェア上で実行される20量子ビットのミラーリングされたイジング回路に対して、Qiskitパターンの4つのステップに従って、完全なPEC+SLCワークフローを実行します。
ステップ1: 問題のマッピング
私たちのデモンストレーションは次のように設定されています。
-
1次元イジング鎖ハミルトニアンの下での時間発展を行うことを選択します。1次元イジング鎖は、密な回路構造を与えるため、PEC実装を示すのに便利です。
-
オブザーバブル を測定することを選択します。これは1つの 成分と1つの 成分を含みます。これは、SLC境界が異なる種類のエラーをどのように異なって扱うかを示すのに便利です。
-
このオブザーバブルの理想的な期待値を厳密に1にしたいので、緩和された推定値の品質を判断しやすくなります。回路をミラーリングすることで、その目標にほぼ到達します。回路後半のすべてのゲートに対して、前半に逆ゲートが存在するため、理想的な回路は恒等操作として機能します。
-
最後に、理想的な期待値1を得るには、回路がオブザーバブルの+1固有状態で開始し(そしてミラーリングのおかげで理想的には終了も)する必要があります。オブザーバブルが非成分を含み、
Executorプリミティブが回路末尾での目的の測定基底を考慮するため、ミラー回路の先頭にprepare_basis関数を用いて対応する基底準備ゲートを追加します。get_measurement_bases関数を使用して、どのゲートが必要か、どこに配置するかを特定し、正準基底測定の準備に関するセクションで説明されているbox注釈規則から生じる量子ビット・インデックスの微妙な点を追跡します。
# Width of the Ising chain (a free parameter of this demonstration)
num_qubits = 20
# The observable to measure: one X component (on qubit 6) and one Z component
# (on qubit 13), as motivated in "Step 1: Map the problem" above
target_obs_sparse = [("XZ", [6, 13], 1.0)]
observable = SparsePauliOp.from_sparse_list(
target_obs_sparse, num_qubits=num_qubits
)
# Determine the measurement basis each qubit requires for this observable.
# The reverser is used during post-processing in Step 4.
bases_virt, reverser_virt = get_measurement_bases(observable)
# Free parameters of the demonstration circuit: the number of Trotter steps
# sets the circuit depth, and rx_angle is the transverse-field rotation angle
# applied in each step (pi/4 makes the dynamics non-Clifford)
num_trotter_steps = 10
rx_angle = np.pi / 4
def construct_ising_circuit(
num_qubits: int,
num_trotter_steps: int,
rx_angle: float,
) -> QuantumCircuit:
circuit = QuantumCircuit(num_qubits)
for _ 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)
return circuit
def prepare_basis(
circuit: QuantumCircuit, basis: list[int]
) -> QuantumCircuit:
# basis is a list of integer values from 0 to 3. These map to the basis measurement as:
# 0 = I; 1 = Z; 2 = X; 3 = Y
assert len(basis) == circuit.num_qubits
out_circ = circuit.copy_empty_like()
for qb, bas in enumerate(basis):
if bas == 2:
out_circ.h(qb)
elif bas == 3:
out_circ.rx(-np.pi / 2, qb)
out_circ.barrier()
out_circ.compose(circuit, inplace=True)
return out_circ
def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:
mirror_circ = circuit.copy_empty_like()
mirror_circ.compose(circuit.inverse(), inplace=True)
mirror_circ.barrier()
mirror_circ.compose(circuit, inplace=True)
mirror_circ.measure_active()
return mirror_circ
# Instantiate the mirrored circuit and prepend the basis-preparation gates
circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle)
mirrored_circuit = mirror_circuit(circuit)
mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])
mirrored_circuit.draw(
"mpl", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False
)

ステップ2: 最適化
実行する回路、測定するオブザーバブル、およびノイズ学習パラメーターに関連する詳細を最適化します。出発点として、最もビジーでないHeronプロセッサーを選択し、フラクショナル・ゲートを有効にしてインスタンス化します。これらのフラクショナル・ゲートにより、私たちの事後選択フィルタリングの一部でより高い感度が得られます。
# Initialize IBM Quantum Compute Service using your saved credentials
service = QiskitRuntimeService()
# Select the least busy Heron device. This tutorial targets Heron processors:
# the usage estimate and the non-Markovian-noise post-selection discussed in
# Step 4 are specific to this processor family.
backend = service.least_busy(
operational=True,
simulator=False,
filters=lambda b: b.processor_type["family"] == "Heron",
)
# Re-fetch with fractional gates enabled (least_busy does not forward this)
# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.
backend = service.backend(backend.name, use_fractional_gates=True)
print(f"Selected backend: {backend.name}")
Selected backend: ibm_kingston
まず、回路を QPU上での実行に必要とされる ISA命令にトランスパイルします。実行に使用する物理量子ビットの選択は、結果の品質に大きな影響を与えます。なぜなら、コヒーレンス時間とゲート忠実度はデバイス全体で異なり、時間とともにドリフトするからです。慎重な実験のためには、簡易なベンチマーク実験(例えば、量子ビット選択のためのリアルタイム・ベンチマーキング チュートリアルを参照)に基づいて量子ビットのチェーンを手動選択することが有効な場合があります。このチュートリアルの目的のためには、バックエンドの結合マップとその最新のキャリブレーション・データに基づいてレイアウトを選択するQiskitトランスパイラーに依存します。
isa_pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = isa_pm.run(mirrored_circuit)
# The chain of physical qubits selected by the transpiler
layout = isa_circuit.layout.final_index_layout()
print(f"Selected layout: {layout}")
isa_observable = observable.apply_layout(
layout, num_qubits=isa_circuit.num_qubits
)
Selected layout: [71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 36, 21, 22, 23, 16, 3, 2]
wire_order = layout + [
q for q in range(isa_circuit.num_qubits) if q not in layout
]
isa_circuit.draw(
"mpl",
fold=-1,
scale=0.3,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)

回路をボックス化する
実装を容易にするため、回路命令を注釈付きボックスに配置する generate_boxing_pass_manager トランスパイル・パスを利用します。これらのボックスは、PECの場合、アンチノイズを回路のどこに注入すべきかを明確に示します。設定の詳細については、Samplomaticのドキュメントを参照してください。
SLCワークフローでは、プロセスの後半で inject_noise_strategy="individual_modification" を使用する必要があることに注意してください。これにより、回路内の BoxOp 命令の InjectNoise 注釈を一意に特定できます。(InjectNoise 注釈のないボックスはこの方法では特定できません。)
SLCバウンド計算では現在、InjectNoiseアノテーションがsite="after"で配置されていることを前提としています(qiskit-addon-slc#33を参照)。この設定のデフォルト値が最近のバージョンのsamplomaticで変更されたため(samplomatic#372を参照)、以下で明示的にinject_noise_site="after"を設定します。
find_unique_box_instructions 関数は、提供されたボックス化された回路を反復処理し、ノイズ学習とノイズ注入の目的で、ユニークな2量子ビット(2Q)層または測定を持つものを特定します。
# Box circuit with Twirl and InjectNoise annotations
boxes_pm = generate_boxing_pass_manager(
twirling_strategy="active",
inject_noise_strategy="individual_modification",
inject_noise_site="after",
inject_noise_targets="gates",
measure_annotations="all",
)
boxed_circuit = boxes_pm.run(isa_circuit)
# Find the unique instructions (layers) from boxed circuit
unique_2q_instructions = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)
boxed_circuit.draw(
"mpl",
fold=-1,
scale=0.3,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)

正準基底測定を準備する
量子ビットの順序を追跡するために特別な注意を払う必要があることに注意してください。以下では、回路のボックス化とユニークな命令の特定の方法に起因して、Executorに提供する際に量子ビットの順序を適切に更新する手段として、canonical_qubits の概念を導入します。詳細については、量子ビット順序規則 のドキュメントを参照してください。
# Determine the canonical qubits order
meas_box = boxed_circuit.data[-1]
canonical_qubits = [
idx
for idx, qubit in enumerate(boxed_circuit.qubits)
if qubit in meas_box.qubits
]
# map canonical qubit to physical (isa) qubit
c_2_p = {c: p for c, p in enumerate(canonical_qubits)}
# map physical (isa) qubit to virtual qubit (index in original circuit)
p_2_v = {p: v for v, p in enumerate(layout)}
# compute map between virtual and canonical qubit indices.
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}
assert len(c_2_v) == num_qubits
bases_canon = [
np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)
for base_i in bases_virt
]
ライトコーン・シェーディング、ノイズ学習、アンチノイズ注入のワークフロー
このチュートリアルでは、ノイズ学習が完了する前にSLCバウンド計算を実行するため、緩和対象のCircuitは学習されたノイズモデルにできるだけ時間的に近いタイミングで実行されます。原理的には、このワークフローはさらに並列化できます。ノイズ学習ジョブを実行しながら、並行してノイズバウンドを推定することが可能です。任意の量子Circuitに対して、ノイズバウンド計算は弱い指数関数的な依存性でスケールする可能性があるため、バウンド計算を並列化する(例えば多数のCPUコアにわたって)ことで、与えられた計算時間予算に対してより厳密なバウンドが得られ、さらにQPUの実行とバウンド計算自体も並列化することで、最も効率的なワークフローを実現できます。
学習対象のノイズ・モデルのパウリを予測する
ハードウェア上でノイズ学習を実行する前に、まず私たちのノイズ・モデルに含まれるパウリ誤差項を列挙します。各ユニークな層のノイズを、各アクティブな量子ビット上の重み1のパウリ(、、または)ごとに1つの誤差率、および各接続されたアクティブな量子ビット・ペアごとの重み2のパウリごとに1つの誤差率を持つスパース・パウリ・リンドブラッド・モデルとしてモデル化することを思い出してください。generate_noise_model_paulis 関数は、提供された回路のユニークなボックス化された各層を通過し、回路の量子ビット接続性を考慮に入れて、まさにこのパウリ項のリストを生成します。
言い換えると、このステップは NoiseLearnerV3 が後で学習された率で埋めることになるノイズ・モデルの構造を予測します。この構造を事前に知っておくことで、ノイズ学習が実行される前であっても、あらゆる潜在的な誤差に対する前方境界と後方境界を計算できます。
noise_model_paulis = generate_noise_model_paulis(
unique_2q_instructions, backend.coupling_map, boxed_circuit
)
noise_model_rates = {ref: None for ref in noise_model_paulis}
a. 前方境界を計算する
compute_forward_bounds 関数は、各層のゲートと上記で生成されたパウリ項との交換関係を、前方伝播誤差が目的のオブザーバブル にどのように影響するかという観点から評価します。パウリ項と交換するゲートについては、何も行いません。クリフォード・ゲートについては、回路の先頭に向かって押し出されます。非クリフォード・ゲートについては、目的のオブザーバブルへの影響を近似し、後で(すべての境界がマージされた後に)ノイズ・キャンセルの優先順位付けに使用します。この境界は、まずL2ノルム(すなわち、関連するパウリ項の係数の二乗和の平方根)を適用することで達成されます。関与する量子ビット項が多すぎる場合は、三角不等式を用いたより緩い境界に戻します。
境界計算パラメーターを設定する
以下の設定は、古典的な境界計算を制御します。
-
slc_atol: 計算の絶対許容誤差です。この閾値未満の係数を持つ演算子項は無視できるものとみなされ、破棄されます。 -
slc_eigval_max_qubits: より厳密な固有値ベース(L2ノルム)の境界を評価する量子ビットの最大数です。これより多くの量子ビットに支持される演算子は、上記の緩い三角不等式境界にフォールバックします。 -
slc_evolution_max_terms: 演算子を回路に沿って伝播させる際に保持されるパウリ項の最大数です。最小の項を切り捨てることで、計算を扱いやすく保ちますが、境界は若干緩くなります。 -
slc_num_processes: 境界計算を並列化するCPUプロセスの数です。 -
slc_timeout: 境界計算に割り当てられた時間予算(秒単位)です。この予算内に到達しなかった層には、自明な境界である2が割り当てられます。
このチュートリアルでは、タイムアウトは意図的に到達するように設計されています。ノートブックが素早く実行されるようにslc_timeoutを意図的に小さく保ち、タイムアウトに達しても安全であることを示しています。計算されるシェード(shade)はすべての段階で上限であるため、タイムアウトに達しても、到達しなかったレイヤーのバウンドが緩く(トリビアルに)なるだけで、エラーの影響を過小評価することは決してありません。これは以下の可視化において、最も左側のレイヤーがトリビアルなバウンドで完全に埋められている様子として表示されます。タイムアウト(またはプロセス数)を増やすと、バウンドが厳密になり、サンプリングのオーバーヘッドがさらに削減されます。
slc_atol = 1e-8
slc_eigval_max_qubits = 18
slc_evolution_max_terms = 1000
slc_num_processes = 8
slc_timeout = 60
forward_bounds = compute_forward_bounds(
boxed_circuit,
noise_model_paulis,
isa_observable,
evolution_max_terms=slc_evolution_max_terms,
eigval_max_qubits=slc_eigval_max_qubits,
atol=slc_atol,
num_processes=slc_num_processes,
timeout=slc_timeout,
)
Bounds computation timed out.
手動検査のためにSLCを可視化する
測定とパウリ項が局所誤差とどのように相互作用するかを調べることで、shaded境界の挙動を解釈できます。これらのパターンは、このキックされたイジング・ハミルトニアン時間発展問題に特有のものであり、論文Lightcone Shading for Classically Accelerated Quantum Error Mitigationにも登場します。いくつかの特徴的な点を紹介します。
-
オブザーバブル内の2つの非恒等パウリから生じる2つのコーンを明確に区別できます。
-
量子ビット6上のX測定が、最も右の層のXエラーと交換することがわかります。
-
量子ビット13上のZパウリが、最も右の層のZエラーと交換することがわかります。
-
上記で指定したタイムアウトに達すると、左側に残る層はすべて自明な境界2で埋められます。
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
forward_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)



b. 量子スピード限界を用いて前方境界を厳密化する
次に、tighten_with_speed_limit 関数を使用して境界を厳密化します。オブザーバブルのライトコーンの幾何学的広がりは、上記で計算された前方境界にすでに完全に反映されています。この関数が追加するのは、回路を通じて情報が流れる速度の限界(ライトコーンは2量子ビット・ゲートの層ごとに最大で1量子ビットしか広がらない)を利用して、事前に計算された境界同士を互いに厳密化する自己無矛盾パスです。情報は両方向に流れます。
-
交換子境界から逆伝播されたオブザーバブルの境界へ: オブザーバブルがある量子ビット上で 誤差と交換する場合、その量子ビット上で大きな または 成分を持つことはできません。
-
逆伝播されたオブザーバブルの境界から交換子境界へ: オブザーバブルがある量子ビット上で と 成分がほとんどない場合、そこではほぼ と交換しなければなりません。
forward_bounds_tighter = tighten_with_speed_limit(
forward_bounds, boxed_circuit, noise_model_paulis, isa_observable
)
手動検査のためにSLCを可視化する
原理的には、この厳密化により、計算された境界とタイムアウト後の層に割り当てられた自明な境界との間のより滑らかな遷移が提供されます。この例では、タイムアウトに達する頃にはライトコーンがすでに回路の端まで広がっているため、その効果はほとんど見えません。
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
forward_bounds_tighter,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)



c. 後方境界を計算する
ノイズ予測のこの部分は、特定の層での誤差が入力状態 にどのように影響するかを評価します。compute_backward_bounds 関数は、まず回路を反転して測定ゲートを削除し、その後、前方境界計算で行ったのと同様の解析を進めます。
backward_bounds = compute_backward_bounds(
boxed_circuit,
noise_model_paulis,
evolution_max_terms=slc_evolution_max_terms,
num_processes=slc_num_processes,
timeout=slc_timeout,
)
手動検査のためにSLCを可視化する
後方境界の計算から、初期状態の構造がエラー伝播の初期挙動をどのように支配するかがわかります。
-
Zエラーが最初に |0⟩ 初期状態と交換する様子が明確にわかります。
-
Xバシスの+1固有状態を初期化する量子ビット6のみで、Zエラーが交換に失敗し、一方Xエラーは交換することがわかります。
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
backward_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)



学習されたノイズ率なしでマージされた境界をプレビューする
merged_bounds 関数は、後方境界から前方境界への切り替えが目的のオブザーバブルに対する推定総バイアスを最小化する回路上の点を決定します。このバイアスは、その点より前のすべてのノイズ位置に対する後方境界の寄与の合計に、それより後のすべてのノイズ位置に対する前方境界の寄与を加えたものとして計算されます。現在、これはすべての量子ビットに対して一様に行われます。
後方境界から前方境界に切り替える最適な点は、学習されたノイズ率に依存します。この段階では、まだいかなる率も学習していません。noise_model_rates はすべての層で None に初期化されており、merge_bounds はこれを一様なノイズ率として扱います。したがって、ここで示されているマージされた境界はプレビューにすぎません。ステップ3で実際に学習された率を用いてこれらを再計算します。
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
noise_model_rates,
)
Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
手動検査のためにSLCを可視化する
後方境界と厳密化された前方境界をマージした後、組み合わされたSLCの挙動が明らかになります。
-
上記の関数は、後方境界から厳密化された前方境界への切り替えが行われる分割点を教えてくれます。
-
以下に示すように、SLCには部分的な後方境界と部分的な厳密化された前方境界が含まれることがわかります。
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
merged_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)



ステップ3: 実行
このセクションでは、実際の量子デバイスを使用するワークフローの部分を開始します。この学習ベースのエラー緩和手法には、2つのステップがあります。
-
NoiseLearnerV3を使用してノイズを学習する。 -
samplomaticとExecutorフレームワークを用いてエラー緩和回路を実行する。
量子回路からの境界付き誤差を用いて、関連するノイズ率を学習し、エラー予算の優先順位を決定し、サンプリング・オーバーヘッドを決定し、QPU上で実行します。
a. ノイズ率を学習する
ノイズ学習器は、スパースなパウリ・リンドブラッド・ノイズ・モデルに基づいて、対象となる1つまたは複数の回路のゲートに影響を与えるノイズ過程を特性評価します。run() メソッドは、ノイズ学習器の設定で指定されたオプションを使用して、提供されたユニークな2量子ビット層に対するノイズ学習ジョブを起動します。これらのオプションは、パウリ・ツイリング戦略、ランダム化とショットの数、学習深度、および事後選択を制御します。
また、学習深度も意図的に選択します。samplomatic を用いた学習ベースの緩和における実用的な知見は、最も深い学習深度を緩和したい回路の深さに一致させることが非常に有益だということです。NLv3の layer_pair_depths は層ペア(層とその逆)で測定されるため、最も深い値を回路の2量子ビット層の深さの半分に設定します。
post_selection_enabled = True
# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. NLv3 ``layer_pair_depths`` are measured in layer pairs (a layer
# plus its inverse), so the deepest value is half the circuit's two-qubit-layer
# depth. Learning to this depth markedly improves the quality of the mitigation.
#
# We measure the two-qubit-layer depth on the pre-boxed ISA circuit: after
# boxing, every two-qubit gate is hidden inside a full-width ``BoxOp``, so a
# ``num_qubits == 2`` filter on ``boxed_circuit`` matches nothing (and
# ``QuantumCircuit.depth`` does not recurse into boxes).
depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2 # dividing by 2 since we want pairs
# Use a fixed schedule of learning depths, but drop any that exceed the circuit's
# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never
# learn deeper than the circuit being mitigated.
candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]
layer_pair_depths = sorted(
{d for d in candidate_depths if d < max_layer_pair_depth}
| {max_layer_pair_depth}
)
noise_learner_options = {
"num_randomizations": 64,
"shots_per_randomization": 128,
"layer_pair_depths": layer_pair_depths,
"post_selection": {
"enable": post_selection_enabled,
"strategy": "edge",
"x_pulse_type": "rx",
},
"environment": {"job_tags": ["TUT_SLC"]},
}
noise_learner = NoiseLearnerV3(backend, noise_learner_options)
noise_learner_job = noise_learner.run(unique_2q_instructions)
noise_learner_result = noise_learner_job.result()
if post_selection_enabled:
print(
"Minimum fraction of shots kept for noise learning experiments: ",
end="",
)
print(
f"{min([min(d.values()) for d in [nlr.metadata['post_selection']['fraction_kept'] for nlr in noise_learner_result[:2]]]):.2f}"
)
Minimum fraction of shots kept for noise learning experiments: 0.71
# Get a dict mapping each InjectNoise.ref to its learned PauliLindbladMap
refs_2_plm = noise_learner_result.to_dict(
unique_2q_instructions, require_refs=False
)
b.i. 実際に学習されたノイズ率でマージされた境界を更新する
特定のノイズ・モデルが学習されたので、学習されたノイズ率を予測されたノイズ境界に適用し、どの境界がバイアスの最小化に最も影響を与えるかの最終的な判定を得ることができます。
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
refs_2_plm,
)
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
b.ii. ハードウェア実行のための local_scales を計算する
compute_local_scales は、回路内の可能性のある各ノイズ誤差を調べ、その誤差が最終測定にどれだけバイアスを与える可能性があるか、およびそれを補正するのにどれだけコストがかかるかを推定します。次に、緩和する価値がどれだけあるかによって誤差をランク付けし、許容されるサンプリング・コスト予算内(または目的の精度を達成する範囲内)に収まりながら、バイアスをできるだけ減らすサブセットを選択します。結果は、どの誤差が積極的に緩和され、どの誤差が緩和されないまま残されるかを示すスケーリング係数のセット(local_scales)、予測される総サンプリング・コスト・オーバーヘッド(sampling_costs)、および残存バイアス(residual_bias_bound)です。
目的の残存バイアスを制御する能力は、PECのSLC実装の重要な機能です。元の実装では、サンプリング・オーバーヘッドは常にゼロ・バイアスを目標としていましたが、私たちは予想される残存バイアスとのトレードオフで必要なサンプリング・オーバーヘッドを調整できます。これにより、ユーザーは固定されたサンプリング予算内に留まることができ、これはワークフローを最初にプロトタイピングする際に特に有用です。
id_map = map_modifier_ref_to_ref(boxed_circuit)
summed_rates = 0.0
for box_id, noise_id in id_map.items():
learned_plm = refs_2_plm[noise_id]
summed_rates += np.sum(learned_plm.rates)
# print(f"{box_id}:\tgamma = {np.exp(2 * summed_rates):1.6e}\tsampling cost = {np.exp(4 * summed_rates):1.6e}")
total_gamma = np.exp(2 * summed_rates)
print(
f"Full PEC gamma={total_gamma}, sampling cost (gamma^2) = {total_gamma**2}"
)
Full PEC gamma=12.750876317157042, sampling cost (gamma^2) = 162.58484685543633
biases = []
costs = []
for bias in [0.0] + np.arange(0.001, 0.102, 0.01).tolist():
_, cost_, bias_ = compute_local_scales(
boxed_circuit,
merged_bounds,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=bias,
)
biases.append(bias_)
costs.append(cost_)
サンプリング・オーバーヘッドと残存バイアスをトレードオフする
xticks = np.arange(0, 11)
fig, ax = plt.subplots()
ax.scatter(
[0], [total_gamma**2], marker="D", c="tab:orange", label="full PEC"
)
ax.plot(
100 * np.array(biases),
np.array(costs),
"o-",
c="tab:blue",
label="PEC+SLC",
)
ax.set_yscale("log")
ax.set_xticks(xticks, [f"{x:.1f}" for x in xticks])
ax.set_xlabel("Remaining bias [%]")
ax.set_ylabel(r"Sampling overhead, $\gamma^2$")
ax.grid()
ax.legend()
fig.suptitle("PEC sampling overhead reduction due to SLC")
Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')
chosen_bias_thres = 0.1
local_scales, sampling_cost, residual_bias_bound = compute_local_scales(
boxed_circuit,
merged_bounds,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=chosen_bias_thres,
)
print(
f"PEC+SLC sampling cost (gamma^2) = {sampling_cost} "
f"w/ remaining bias = {100 * residual_bias_bound:.1f}%"
)
PEC+SLC sampling cost (gamma^2) = 48.13646509622307 w/ remaining bias = 10.0%
c. アンチノイズを用いて対象の回路を実行する
c.i. samplex を用いてテンプレート回路を準備する
samplex はSamplomaticの build メソッドの出力であり、template_circuit に対するランダム化されたパラメーターを生成するために必要なすべての情報をエンコードします。これらは次に QuantumProgram オブジェクトを設定するために使用され、それが Executor プリミティブを用いてQPU上で実行されます。各 QuantumProgram には複数の項目を含めることができ、それぞれを template と samplex のペアと考えることができます。
詳細については、Hello samplomatic チュートリアルを参照してください。
# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)
# Set up postselection if it's been enabled
if post_selection_enabled:
# Set up post selection PM (to add PS instructions)
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
final_template_circuit = post_selection_pm.run(template_circuit)
else:
final_template_circuit = template_circuit
c.ii. QuantumProgram を設定する
num_randomizations = 4096
shots_per_randomization = 64
chunk_size = 256
# Set up QuantumProgram
program = QuantumProgram(shots=shots_per_randomization, noise_maps=refs_2_plm)
# no EM
# Collect up a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(0) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
# plain PEC
# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
# PEC+SLC
# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
samplex_inputs |= {"local_scales": local_scales}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
c.iii. Executor プリミティブでプログラムを実行する
executor = Executor(backend)
job_exec = executor.run(program)
results_exec = job_exec.result()
ステップ4: 後処理
executor_expectation_values を用いて目的の最終期待値を計算する際、可能な限り最高品質の結果を得るために、いくつかの後処理技術を実装します。まず、読み出しプロセス中に発生するあらゆる誤差を考慮するツイルド読み出しエラー消去(TREX)を適用します。次に、事後選択法を用いて、Heronバックエンド上の非マルコフ・ノイズによる誤差を修正します。この方法では、アクティブな量子ビットとスペクテーター量子ビットを測定し、次に各量子ビットに緩やかな回転を適用し、再度測定します。2回の測定が期待通りに反転した量子ビットを確認できない場合、PostSelector から mask を適用してこれらのショットを破棄します。マスク計算内では、単一量子ビット・ノードまたは隣接するスペクテーター・エッジに基づいてフィルタリングする特定の戦略を設定でき、これはフィルタリングされるショット数と結果の品質の両方に影響を与える可能性があります。
measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_scale_factors = trex_factors(measurement_noise_map, reverser_virt)
post_selection_strategy = "node"
def post_process_conv(datum, steps=16, gamma=None, ps=False, trex=False):
meas = datum["meas"]
flips = datum["measurement_flips.meas"]
signs = datum.get("pauli_signs", None)
meas_basis_axis = None
avg_axis = 0
mask = None
if ps and post_selection_enabled:
# Post-select the results
post_selector = PostSelector.from_circuit(
circuit=final_template_circuit, coupling_map=backend.coupling_map
)
# Compute the ps mask for filtering results
mask = post_selector.compute_mask(
datum, strategy=post_selection_strategy
)
# Compute fraction of shots kept from post selection
total_num_shots = num_randomizations * shots_per_randomization
ps_ratio = np.sum(mask) * 100 / total_num_shots / len(bases_canon)
print(
f"With {post_selection_strategy}-based post selection ({ps_ratio:.1f}% of shots kept):"
)
results = []
for i in range(steps, num_randomizations + 1, steps):
# Compute mitigated expvals w/out post-selection
res = executor_expectation_values(
meas[:i],
reverser_virt,
meas_basis_axis,
avg_axis=avg_axis,
measurement_flips=flips[:i],
pauli_signs=signs[:i] if signs is not None else None,
postselect_mask=mask[:i] if mask is not None else None,
rescale_factors=trex_scale_factors if trex else None,
gamma_factor=gamma,
)
results.append(res[0])
return results
gamma_pec = gamma_from_noisy_boxes(refs_2_plm, id_map)
gamma_slc = gamma_from_noisy_boxes(refs_2_plm, id_map, local_scales)
steps = 16
results = {}
for label, result_idx, gamma, use_ps, use_trex in [
("PEC", 1, gamma_pec, True, True),
("PEC+SLC", 2, gamma_slc, True, True),
("Unmitigated", 0, None, False, False),
]:
res = post_process_conv(
results_exec[result_idx],
steps=steps,
gamma=gamma,
ps=use_ps,
trex=use_trex,
)
results[label] = res
With node-based post selection (24.1% of shots kept):
With node-based post selection (24.2% of shots kept):
実験結果の検証から、異なるアプローチ(PEC、SLCと組み合わせたPEC、緩和なしのベースライン)の挙動を直接比較できます。強調すべき具体的な詳細をいくつか挙げます。
-
緩和なしの結果は、10%バイアス帯域から大きく外れ(0.35付近)、ランダム化の数の影響を受けません。
-
このデバイスでは、完全なPECは のサンプリング・オーバーヘッドを要します。初期の大きな振動の後、素のPEC推定値は信号を回復し、正確な値をわずかに超えた1.1付近に落ち着きます。10%バイアス帯域はPEC+SLC推定器にのみ適用されることに注意してください。素のPECは原理的には偏りがないため、この残存偏差はバイアス保証の対象ではなく、統計的な揺らぎと学習されたノイズ・モデルの不完全さを反映しており、この問題サイズでは妥当なものです。
-
SLCはオーバーヘッドをさらに約3.4倍削減します(、残存バイアス境界は約10%)。PEC+SLCも少数のランダム化では強く変動し、初期には非物理的な領域にまで達することもありますが、その推定値は着実に正確な値に収束し、帯域内の1.0付近に落ち着きます。これは、サンプリング・コストの約3分の1で、素のPECと同等またはそれ以上の精度を達成し、ライトコーン・シェーディングの利点を示しています。
-
両方の緩和された推定値の誤差バーは、ランダム化が蓄積するにつれて縮小します。ランダム化数を固定すると、PEC+SLC推定器の方が統計分散が小さく、これはその小さい と一致します。
学習されたノイズ率(したがってオーバーヘッドと緩和された推定値)は、実行時のバックエンドとそのキャリブレーションに依存するため、このノートブックを再実行すると定量的に異なる値が得られる可能性があることに注意してください。
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
ax.axhline(1.0, color="black", label="Exact")
ax.fill_between(
[-50, 4100], -10, 0, color="grey", alpha=0.25, label="Unphysical"
)
ax.fill_between([-50, 4100], 1, 10, color="grey", alpha=0.25)
ax.fill_between(
[-50, 4100], 0.9, 1.1, color="red", alpha=0.25, label="10% bias"
)
for label, res in results.items():
ax.errorbar(
list(range(steps, num_randomizations + 1, steps)),
[r[0] for r in res],
yerr=[r[1] for r in res],
alpha=0.75,
marker="o",
linestyle="",
markerfacecolor="none",
label=label,
)
ax.set_ylabel(r"$\langle X_{6}Z_{13}\rangle$")
ax.set_xlabel("# randomizations")
ax.grid()
ax.legend(ncols=2)
ax.set_ylim([-0.1, 2.0])
ax.set_xlim([-50, 4100])
(-50.0, 4100.0)

次のステップ
この内容に興味を持たれた方は、以下の資料にも興味を持たれるかもしれません。

