伝播型ノイズ吸収(PNA)による期待値の改善
所要時間の目安:Heronプロセッサで10分(注:これはあくまで目安です。 (実行時間は状況によって異なる場合があります。)
学習成果
このチュートリアルを学習した後、ユーザーは以下の点を理解できるようになります:
- 伝播型ノイズ吸収(PNA)とは何か、そして、学習された逆ノイズチャネルを測定対象の観測量に吸収させることで、2量子ビットゲートのノイズをどのように低減するのか
- 「twirling」、基底の変更、およびノイズ注入を行うための回路レイヤーのボックス化と注釈付けの方法
samplomatic [NoiseLearnerV3( /docs/guides/directed-execution-model#noiselearnerv3 ) を用いてレイヤーノイズを学習し、それをノイズ低減観測量に伝播させる方法qiskit-addon-pna- の および
QuantumProgramクラスExecutorを使用してランダム化回路をサンプリングする方法qiskit-ibm-runtime、ならびにPNAとTREXおよびポストセレクションを組み合わせる方法
前提条件
このチュートリアルを進める前に、以下のトピックについてあらかじめ理解しておいていただくことをお勧めします:
- Qiskit のパターンワークフロー
- Estimatorプリミティブを用いた観測変数の期待値の計算
- 「推定器プリミティブとエラー軽減オプションの組み合わせ」 で取り上げられている、パウリ・トゥワーリングやTREXなどのエラー軽減手法
背景
このチュートリアルでは、Qiskitの高度なエラー軽減ツールを活用して、ノイズの多い量子実験における期待値の推定精度を向上させる方法について解説します。
「伝播ノイズ吸収(PNA)」とは何ですか?
伝播型ノイズ吸収とは、2量子ビットゲートに影響を及ぼす逆ノイズチャネルを通じて観測量を伝播させることで、ゲートエラーを軽減する手法であり、その結果、ノイズを軽減する観測量が生み出される。
量子エラー緩和を利用すれば、完全な耐故障性を必要とすることなく、ノイズの多い量子ハードウェアから有用な期待値を抽出することができる。 PNAは、回路の動作を変更するのではなく、ノイズの影響を観測量そのものに吸収することに特に重点を置いています。
量子回路内の各ノイズの混入したゲートは、理想的なゲートの後にノイズチャネルが続くものとしてモデル化できる。 PNAは、これらのノイズチャネルを学習または特徴付けし、その逆関数を定義する。 PNAは、逆演算をハードウェアの実行プロセスに組み込む(これは通常、実現不可能である)代わりに、逆ノイズチャネルを回路を通じて前方へ伝播させ、観測量に適用する。 このプロセスにより、観測量 が新しい演算子 に変換され、ノイズの混入した回路上で を測定した際の期待値は、ノイズのない理想的な回路上で を測定した際の期待値と同じになる。
これは次のように要約できます:
- 各ノイズ混入ゲート を、理想的な演算の後にノイズチャネル が続くものとしてモデル化する。
- ノイズ特性評価ツールを使用して、各 の値を測定または推定します。
- 逆ノイズマップ を定義し、パウリ転送法を用いて回路を順方向に伝播させる。
- これらの逆演算子を観測量に吸収することで、ノイズが低減された演算子 が得られる。
このノイズがパウリチャネル(あるいはより一般的には疎なパウリ・リンドブラッドチャネル)として記述される場合、この伝播はパウリ伝播を用いて効率的に行うことができる。 パウリ伝播は、計算複雑度を制御しつつ、逆ノイズチャネルがクリフォード演算および非クリフォード演算の層を通過する際にどのように変換されるかを近似するための枠組みを提供する。
緩和策を「観測可能な」領域に移すことで、PNAは、回路に物理的な補正操作を挿入した場合に生じうる大きなサンプリングオーバーヘッドを回避している。 その代わりに、元のノイズを含む回路が実行され、観測可能量は、その期待値によってノイズの影響が打ち消される新しい演算子 に変換される。
PNAのワークフロー
このプロセスは、以下の概念的な段階を通じて理解することができます。 最初の回路図は、標準的なノイズの多い実験を示しています。
ノイズモデルを学習できれば、その逆モデルを適用してノイズを除去することができます。
確率的誤差キャンセル(PEC)のようにQPU上でサンプリングして逆ノイズチャネルを実装するのではなく、パウリ伝播を用いて、測定された観測量に対して古典的にこれを適用する。 その結果得られる観測量により、測定時に学習されたゲートノイズが効果的に低減される。
Samplomatic と Executor を用いたモジュール型エラー緩和
PNAのアプローチは、Qiskitのモジュール型エラー軽減アーキテクチャに基づいています。 このアーキテクチャでは、ライブラ samplomatic リと、および Executor クラス( v0.47.0 で qiskit-ibm-runtimeQiskit Runtime に追加されたもの)を QuantumProgram 組み合わせて使用し、伝播型ノイズ吸収やパウリ・トゥワーリングといった手法を、異なる実験間で組み合わせ可能かつ再利用可能にしています。
Executor緩和ロジックを回路定義そのものに組み込むのではなく、緩和処理はアノテーションを通じて samplomatic 宣言的に表現され、ランダム化された回路の生成、実行、および後処理の方法を制御する を通じてプログラム的に管理されます。
このチュートリアルでは、 Qiskitのパターンを実装し、PNAが逆パウリノイズチャネルを伝播させ、それに応じて観測量を修正することで、ノイズの多いQPUにおける期待値の推定精度を向上させる仕組みを解説します。
ワークフローの概要
- ステップ1:量子問題への対応付け
- ミラー化されたトロッター化キック・アイジングモデルと、ターゲット観測量を作成する。
- ステップ2:ノイズの特性解析と増幅
- を使用して
samplomatic、回路内の固有の2量子ビット層および測定を特定し、注釈を付けます。 NoiseLearnerV3を使用して、各レイヤーに固有の影響を与えるノイズについて学びましょう。- 各アノテーションを
InjectNoise、それに対応する学習済みノイズモデルにマッピングする。 - を使用して
qiskit-addon-pna、逆ノイズチャネルを回路を通じて前方へ伝播させ、それらをターゲット観測量に吸収させる。
- を使用して
- ステップ3:量子実験の実施
ExecutorをQuantumProgram定義して、によるsamplexランダムサンプリングを指定し、を使用してバックエンド上で実験を実行します。
- ステップ4:結果を再構築し、分析する
- 緩和策(PNA、PNA+TREX、PNA+PS、PNA+PS+TREX)を比較し、緩和策を適用していない場合の結果との改善度合いを可視化する。
要件
このチュートリアルを始める前に、以下のものがインストールされていることを確認してください:
- Qiskit SDK v2.2 またはそれ以降で、 可視化機能をサポートしているもの
- Qiskit Runtime v0.47 またはそれ以降 (
pip install qiskit-ibm-runtime) - Samplomatic v0.13 以降 (
pip install samplomatic) - PNA Qiskit アドオン (
pip install qiskit-addon-pna) - Qiskit アドオン utils (
pip install qiskit-addon-utils)
セットアップ
from qiskit import QuantumCircuit
from qiskit.quantum_info import Pauli, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
QuantumProgram,
Executor,
NoiseLearnerV3,
)
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 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_pna import generate_noise_mitigating_observable
import samplomatic
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.annotations import InjectNoise
from samplomatic.utils import get_annotation, find_unique_box_instructions
import numpy as np
import matplotlib.pyplot as plt
# Selects a connected chain of low-error qubits on the target backend.
# The mirrored kicked-Ising circuit is a 1D chain, so we only need a line
# of connected physical qubits; the helper walks the backend's coupling map
# and grows a chain along the lowest-error two-qubit edges, so it works for
# any backend rather than relying on a hardcoded layout.
def find_qubit_chain(backend, length):
"""Find a connected chain of ``length`` physical qubits on ``backend``.
The chain is grown greedily along the lowest-error two-qubit edges, so it
favors better-performing qubits. Because the mirrored kicked-Ising circuit
is a 1D chain, a connected line is all we need.
"""
target = backend.target
# Identify the native two-qubit gate and build a per-edge error lookup.
two_qubit_gate = next(
name
for name in target.operation_names
if target[name]
and all(q is not None and len(q) == 2 for q in target[name])
)
edge_error = {
frozenset(qargs): (
1.0 if props is None or props.error is None else props.error
)
for qargs, props in target[two_qubit_gate].items()
}
graph = backend.coupling_map.graph.to_undirected(multigraph=False)
neighbors = {n: list(graph.neighbors(n)) for n in graph.node_indices()}
def first_chain_from(start):
path, visited = [start], {start}
def grow():
if len(path) == length:
return True
node = path[-1]
order = sorted(
neighbors[node],
key=lambda m: edge_error.get(frozenset((node, m)), 1.0),
)
for nxt in order:
if nxt not in visited:
visited.add(nxt)
path.append(nxt)
if grow():
return True
path.pop()
visited.remove(nxt)
return False
return path if grow() else None
def chain_cost(path):
return sum(
edge_error.get(frozenset((path[i], path[i + 1])), 1.0)
for i in range(len(path) - 1)
)
# Try low-degree qubits first (the natural ends of long chains) and keep
# the lowest-error chain found.
best_path, best_cost = None, float("inf")
for start in sorted(neighbors, key=lambda n: len(neighbors[n])):
chain = first_chain_from(start)
if chain is not None and (cost := chain_cost(chain)) < best_cost:
best_path, best_cost = chain, cost
if best_path is None:
raise ValueError(
f"Could not find a connected chain of {length} qubits "
f"on '{backend.name}'."
)
return best_path小規模シミュレータの例
PNAは、特定の量子プロセッサにおける物理的な 2量子ビットゲートのノイズを低減する。 このワークフローは、理想的なシミュレータには実質的に対応するものが存在しない2つのハードウェアサービスに依存しています:
NoiseLearnerV3トランスパイル回路の各固有の2量子ビット層に付随する、スパースなパウリ・リンドブラッドノイズチャネルを実験的に特徴づける。 ノイズのないシミュレータでは、学習すべきノイズが存在せず、伝播される逆チャネルは恒等チャネルとなる。- この
Executorプリミティブは、バックエンド上で生成されたsamplomatic、ねじれやランダム化が施された回路をサンプリングします。
原則として、合成ノイズモデルで代用することも可能です。 generate_noise_mitigating_observableたとえば、 Qiskit Aer を使って指示を添付 PauliLindbladError し、その結果として生成されたノイズの混じった回路を直接 に渡すことができます。 しかし、これは単に、自ら投入したノイズに対して従来の帳簿記録が正しいことを検証するに過ぎず、この手法の真の意義を曖昧にしてしまう。 そのため、シミュレータの例は省略し、ハードウェア上で直接PNAのワークフロー全体を実演します。Qiskitの各手順については、以下に詳しく説明します。
大規模なハードウェアの例
ここでは、 IBM Quantum® ハードウェア上で実行される30サイトのキック付きイジングモデルに対し、Qiskitパターンの4つのステップに従って、PNAワークフロー全体を実行します。
ステップ1:量子問題への対応付け
ミラーリングされたトロッター回路と観測量を生成する
この実験では、 1D のスピン鎖上の30サイトからなるキック付きアイジングモデルの時間的ダイナミクスについて検討する。 検討対象となるハミルトニアンは次のとおりである:
,
ここで、 は最近接スピン間の結合を表し、 であり、グローバル横磁場 は に設定される。 がクリフォード角(すなわち、 )から遠ざかるほど、回路内でのアンチノイズ発生器の伝播は困難になる。
観測量の選択については、単一サイトの平均磁化 を考慮する。ここで、 はサイトの数である。
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)
# Append the inverse circuit to complete the mirroring
circuit.compose(circuit.inverse(), inplace=True)
circuit.measure_active()
circuit.draw("mpl", fold=-1)Output:
ステップ 2:ハードウェア実行向けに問題を最適化する
次のステップは、実際の IBM Quantum ハードウェア上で実行できるよう、ミラーリングされたTrotter回路を準備することです。 QPU上で実行するには、単に抽象回路を構築するだけでは不十分であり、以下の条件を満たすように回路を最適化する必要があります:
-
バックエンドのネイティブなゲート設定と接続性を尊重します。 トランスパイレーションとは、論理回路を、ターゲットバックエンドがサポートするISA回路に変換するプロセスである。 これにより、すべてのゲートおよび量子ビット間の相互作用が物理的に実現可能であることが保証される。
-
回路層のレベルでノイズを特徴づけることができます。 PNAは、逆ノイズチャネルの学習と伝播に依存している。 これを効率的に行うために、トランスパイルされた回路を、それぞれ独立した2量子ビットの「ボックス化された」層に分割します。 これらのボックスにより、回路の各層を、それぞれ学習済みのノイズモデルに関連付けることができます。
-
PNAには、現実的なノイズモデルを組み込むことができます。 回路のボックス化が完了したら、この
NoiseLearnerV3サービスを利用して、各固有の2量子ビット層に影響を与えるパウリノイズチャネルを実験的に解明します。 これらの学習済みモデルは、Samplomaticの注釈を通じて回路に再び結び付けられます。
総じて、このステップは、理想化されたミラー回路と、学習済みのノイズモデルを備えたハードウェア実装可能な回路との間のギャップを埋めるものである。 この構成により、PNAは回路を通じて逆ノイズチャネルを伝播させ、それに応じて観測量調整を行うことができる。
バックエンドに接続し、ISA回路にトランスパイルする
まず、 IBM Quantum Compute Service を初期化し、バックエンドを選択します。 指示に従って認証情報を保存し、ご自身のアカウントで認証を行ってください。その後、自動的にその認証情報が読み込まれます QiskitRuntimeService() 。
# Initialize IBM Quantum Compute Service using your saved credentials
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=num_qubits
)
# 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}")Output:
Selected backend: ibm_fez
次に、バックエンド上で連結された量子ビットの連鎖を選択し、その連鎖上に回路をトランスパイルします。
「セットアップ」セクションで定義されたヘルパー find_qubit_chain を使用して、接続された物理量子ビットの num_qubits 1行を選択します。 initial_layout次に、で optimization_level=0 トランスパイルを行い、このチェーンをとして固定します。これにより、ミラー化された回路の2量子ビットゲート層構造が正確に維持されます。 この構造は、ボクシングおよびノイズ学習のステップの基盤となっているため、ここでは(ミラーゲートを無効にしてしまう)より高い最適化レベルは避ける必要があります。
# Find a connected, low-error chain of qubits on the chosen backend
layout = find_qubit_chain(backend, num_qubits)
# Transpile the circuit for the target backend, pinning the chain as the layout.
# optimization_level=0 preserves the mirrored two-qubit-gate layers that the
# boxing and noise-learning steps rely on.
pm = generate_preset_pass_manager(
backend=backend, optimization_level=0, initial_layout=layout
)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
isa_circuit.draw("mpl", fold=-1)Output:
2量子ビットゲート層と測定を回転させ、一意な層を見つけ出す
回路を囲み、固有の2量子ビット層を特定するために、を使用 samplomatic します。 ボックスとは、命令をまとめてグループ化する構造であり、これにより、特定の意図や注釈を、後で同じボックス内のすべてのゲートに一律に適用できるようになります。
generate_boxing_pass_managerここでは、単に2量子ビット層を特定するだけにとどまらない、というメソッドを呼び出します。 このシステムは、いくつかの重要な役割を果たしています:
- 回路内のすべての2量子ビット層をグループ化し、
- それらのレイヤーに および
ChangeBasisの注釈をTwirl適用し、 - 測定操作をそれぞれ独立した枠付きセクションにまとめ、そして
- 各2量子ビット層にアノテーションを
InjectNoise適用します。
これらの注釈は、回路全体を通じてノイズ、基底の変換、およびツイリングがどのように扱われるかを定義しています。 また、これらは、後の学習やノイズの低減に用いられる構造の基礎も築くものである。
主な設定オプションは以下の通りです:
enable_gates/enable_measures: True: すべての2量子ビットゲート層と終端測定をボックスで囲む。 ボックス内では、単一量子ビットゲートは左向きに配置されています。measure_annotations: all: 測定ボックスに およびChangeBasisの注釈を記載Twirlしてください。twirling_strategy: active: エンタングルメントゲートを含む各ボックス内のすべてのアクティブな量子ビットを回転させる。Twirl``inject_noise_targets: gates: エンタングルメントゲートを含む、注釈が付けられているすべてのボックスに注釈を追加InjectNoiseする。inject_noise_strategy: uniform_modification: 回路全体で、すべてのノイズ層を均等にスケーリングする。
# 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",
)
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)Output:
回路のサンプリング方法を定義するテンプレート回路と samplexを生成します。
Executorここでは、. から出力されたサンプルに対してポストセレクションを行うために必要な、スペクテーター測定およびポストセレクション測定も追加します。
# 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)Output:
次の方法を使ってノイズを学習します NoiseLearnerV3
エラー低減のためにPNAを適用する前に、まず回路内の各ユニークな2量子ビット層および測定層に作用するノイズの特性を明らかにする必要があります。 このため、本研究では、前述の各層について、ノイズモデルを実験的に学習するために、この NoiseLearnerV3 プログラムを使用する。 学習者は、各層に影響を与えるノイズチャネルを推定するベンチマーク形式の実験を実行し、学習されたモデルを含む結果オブジェクトを返します。
samplomaticまず、からを用いて find_unique_box_instructions 、回路内の固有の層を特定することから始めます。 これにより、異なるレイヤータイプごとにノイズの学習を1回のみ行うことが保証され、実験回数と総ショットコストを最小限に抑えることができます。 その結果として得られたレイヤーのリストが、ノイズ学習器に渡されます。
ノイズの学習方法を制御する重要なパラメータがいくつかあります:
num_randomizations: 学習構成ごとに使用されるランダム回路の数。shots_per_randomization: ランダム学習回路1回あたりの撮影枚数。layer_pair_depths: 学習実験で使用する回路の深さ(ペア数で測定)。post_selection: ゲートrxを使用してエッジベースのポストセレクションを有効にし、測定後のパルスを適用します。
# Noise learning parameters
num_randomizations_nl = 64
shots_per_randomization_nl = 128
# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. ``layer_pair_depths`` are measured in layer pairs (a layer + its
# inverse), so the deepest value is half the circuits's two-qubit-layer depth.
# Learning to this depth improves the quality of the mitigation.
depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2
# 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}
)
# Find the unique instructions (layers) from the boxed-up circuit
unique_2q_layers_and_meas = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)
# Configure and run the noise learner on the unique layers.
# Options can be passed directly as a dictionary.
noise_learner_options = {
"num_randomizations": num_randomizations_nl,
"shots_per_randomization": shots_per_randomization_nl,
"layer_pair_depths": layer_pair_depths,
"post_selection": {
"enable": True,
"strategy": "edge",
"x_pulse_type": "rx",
},
"environment": {"job_tags": ["TUT_PNA"]},
}
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()学習されたノイズ率を可視化する
ノイズモデルを学習した後、1量子ビット演算と2量子ビット演算の両方について、推定された誤り率の分布を調べることができます。 以下のコードは、学習されたノイズの結果からパウリ・リンドブラッド表現を抽出し、対応するノイズ率を収集します。
学習済みの各層について:
- ノイズモデルを、タプルの
(pstr, qubits, rate)疎なリストに変換する。ここで、pstrは与えられた量子ビットに作用するパウリ文字列であり、rateはそれに対応するエラー率である。 - 率を、1クビット(
len(pstr) == 1)項と2クビット(len(pstr) == 2)項に分けます。 - その後、料金一覧を並べ替え、その中央値を算出する。
1量子ビット(赤)および2量子ビット(青)のノイズ率分布を対数スケールでプロットし、その中央値を垂直線で示すことで、学習されたパウリ・リンドブラッド生成子の相対的な大きさを比較できるようにした。 1クビットおよび2クビットの発生率の順位は、デバイスや特性評価の対象となる特定の層によって異なります。今回の実験では、単一クビット( weight-1 )の発生源の方が、より高い中央値を示しています。
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()Output:
<matplotlib.legend.Legend at 0x125336120>
学習されたノイズと配電盤を関連付ける
各ユニークな2量子ビット層のノイズモデルを取得したら、それらをボックスで囲まれた回路内の対応 InjectNoise する注釈と関連付ける必要があります。
この InjectNoise ディレクティブは、シングルクビット・ドレッサーを用いて、制御可能かつ設定可能な方法で回路にノイズを注入するアノテーション samplomatic です。 これにより、異なるレイヤーにわたるモジュール式のノイズモデリングが可能になります。
各アノテーションには InjectNoise 、以下の内容が含まれます:
InjectNoise.ref- アノテーションを一意に識別するための識別子。 これは、オブジェクトがsamplex対応するノイズモデルを正しく割り当てるために使用されます。InjectNoise.modifier_ref(オプション) - 割り当てられたノイズモデルを乗数でスケーリングするための二次参照。
このステップでは、各 InjectNoise.ref とそれに対応する学習済みノイズモデル (PauliLindbladMap) との間の対応関係を構築します。この対応関係により、回路内の各エンタングルメントゲート層が適切なノイズモデルと結びつけられ、サンプリングおよびその後のノイズ低減ステップにおいて、ノイズの影響が正確に適用されるようになります。
# 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()
)学習されたアンチノイズを通じてオブザーバブルを伝播させる
前述の通り、これは2つのステップで行われます。 まず、アンチノイズ発生器を回路の末端まで伝播させます。 その後、その進化させた生成子を通じて観測可能量を伝播させます。 この処理は、回路内の各アンチノイズ発生器について繰り返される。 この実装では、特定の層にある各ジェネレータの出力が、回路の末端まで並列に伝播される。 さらに、 Python のマルチプロセッシング機能を用いて、アンチノイズの前方伝播と観測量の後方伝播の両方を並列に実行しています。 これにより、メモリ内での進化済みジェネレータの蓄積を防ぎ、計算リソースを最大限に活用することができます。
PNAを実行する際は、必ずノイズの混入した回路と観測対象を指定する必要があります。 ノイズの多い回路が、注釈付きの InjectNoise ボックス付き回路である場合は、前の手順で作成したマッピングを指定する必要があります。 qiskit-aerまた、から命令を含む PauliLindbladError 、ボックス化されていない回路を渡すこともできます。 その場合は、 refs_to_noise_models 指定する必要はありません。 主な入力情報に加え、以下の点についても考慮してください:
max_err_terms: 各アンチノイズ生成器において、前方伝播が行われる際に保持する項の数。 この値を大きくすると、一般的に精度は向上しますが、この挙動が単調増加することは保証されません。max_obs_terms: ノイズ低減観測量 において、進化したアンチノイズを通じてバックプロパゲーションが行われる際に保持される項の数。 値が大きくなるほど、一般に精度は向上しますが、その向上が単調に続くとは限りません。num_processes: このプロセスに割り当てるコア数。 ジェネレータは前方伝播され、観測可能変数に並列に適用されることを忘れないでください。search_step: バックプロパゲーションのステップでは、貪欲法を用いて、パウリ基底における2つの演算子を近似的に共役させる。search_stepこの処理は、...を増やすことで高速化できます。 詳細については、こちらpauli-propドキュメント をご覧ください。generate_noise_mitigating_observable``num_to_measure: この変数は の入力変数ではありませんが、 から実際に測定したい項の数を制御するために使用します。 ここでは、オブザーバブルに含まれる元の用語である上位30用語のみを測定します。 これらの項は、測定を行うことで学習されたゲートノイズを低減する効果が生じるよう、スケーリングが再調整されました。 から30項しか測定していないものの、これを大きく設定しておくと、上位項のスケーリング係数の精度が向上するため、多くの場合、依然として有用です。
# 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]Output:
Finished! 13740 / 13740 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), ".")Output:
[<matplotlib.lines.Line2D at 0x124b0a120>]
測定基底を標準形に変換する
次に、測定対象の観測量に含まれるすべてのパウリ項を完全に網羅できるような、測定用の最小限の基底の集合を求めます( クビットごとに可換であれば、複数の観測量を同時に測定することも可能です )。 Z 元の観測可能量(すべての単一Z パウリ演算子の和)に含まれる項のみを測定しているため、必要な基底は1つだけであり、それは「全基底」である。
Executorパウリの測定基底のセットを見つけることに加え、これらのパウリ項を、が想定する標準形に写像する必要があります。 標準的な量子ビットの順序付けに関する詳細については、 Samplomaticのドキュメントをご覧ください。
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
]ステップ3:量子実験の実施
サンプリング方法を QuantumProgram
Executor``QuantumProgramここで、によって実行されるすべての回路およびサンプリング設定の中核となるコンテナとして機能する を設定します。 このオブジェクトは、PNAで使用される測定結果を生成するために、ランダム化された回路インスタンスがどのように生成、バッチ処理、および実行されるかを定義します。
A には複数の項目 QuantumProgram を含めることができ、各項目はテンプレート回路と、ランダム化の適用方法を定義する対応 samplex するオブジェクトで構成されます。 この抽象化により、ランダムな回路生成からショットの収集・集約に至るまで、ワークフロー全体を単一のモジュール式プログラムとして処理することが可能 Executor になります。
この手順では、先ほど作成したテンプレート回路と samplex を使用して、PNA実験を実行する を作成します QuantumProgram 。 この構成には、以下の要素が含まれます:
template_circuit: 希望するすべてのランダム化(ツイリングによるランダム化、パラメータなど)を実現するために必要なすべてのゲートを含む回路。samplex: サンプリングの対象となる、回路のすべての可能なランダム化について確率分布を定義するオブジェクト。samplex_arguments: を完全に定義するために必要なバインディングは、samplexbasis_changes: ここでは、測定対象の観測量に含まれるすべてのパウリ項を網羅するように、測定に用いる基底の集合を指定します。noise_scales.ref: サンプルに余分なノイズが混入するのを防ぐため、各ノイズレイヤーのスケールを に0.0設定しました。pauli_lindblad_maps: が渡された場合にnoise_scales必須です。 これは、ノイズレイヤーを関連するノイズモデルにマッピングするだけです。
samplex_arguments``shape: で定義された暗黙の形状を拡張するための形状タプル。 この拡張によって導入される非自明な軸は、ランダム化を列挙する。
# 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. The samplex exposes one basis-change input per
# ChangeBasis-annotated box; here a single all-Z basis covers every term. We
# look up the basis-change interface name rather than hardcoding an index, since
# the name depends on the circuit's box structure.
bases_broadcastable = np.expand_dims(np.array(meas_bases_canonical), axis=1)
samplex_inputs |= {
spec.name: bases_broadcastable
for spec in samplex.inputs().get_specs(r"^basis_changes\.")
}
# 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_samplex_item(
circuit=template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations_exec,),
)以下の方法を使用して回路の動作を確認してください。 Executor
QuantumProgramこれで定義が完了したので、実験の実行は簡単です。 オブジェクトを Executor インスタンス化し、バックエンドを指定して、プログラムを実行するだけです。
# Execute (sample) the circuit
executor = Executor(backend)
job_exec = executor.run(program)
exec_results = job_exec.result()ステップ4:結果を再構築し、分析する
誤差を軽減した期待値を計算するには、次のように行います:
- 測定値に影響を与える、学習済みのノイズに基づいて、TREXのスケーリング係数を算出する
- 選択後のサンプルのみを保持するためのマスクを生成し、
- の
qiskit-addon-utils関数executor_expectation_valuesを使用して、すべてのデータを統合し、誤差を低減した期待値を算出する。
# 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,
)緩和策の比較:PNA、PNA+TREX、PNA+PS、PNA+PS+TREX
この Executor 結果を用いて、いくつかの緩和策のバリエーションについて期待値を計算し、可視化します。
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()Output:
この結果は、さまざまなエラー軽減手法を組み合わせることによる相乗効果を示している。 単純なPNAアプローチだけでも、期待値は理想的なベンチマーク値に近づいており、これは、逆ノイズチャネルを観測量に伝播させることで、2量子ビットゲートの誤差を効果的に補償できることを示している。
- TREXによる再重み付け(PNA+TREX)を追加することで、無作為化された回路におけるサンプリングの不均衡を補正し、推定精度がわずかに向上する。
- ポストセレクション(PNA+PS)は、残留誤差に起因すると考えられる一貫性のない測定結果をフィルタリングすることで、より顕著な性能向上をもたらします。
- 最後に、これら3つ(PNA+PS+TREX)を組み合わせることで、理想値にほぼ匹敵する最も正確な結果が得られ、これらの緩和策が互いに補完し合っていることが示された。
全体として、この比較から、PNAがノイズを考慮した期待値推定の堅牢な基盤として機能する一方で、TREXとポストセレクションは、さらなる精度向上のために相互に補完的な改良をもたらすことが明らかになった。
次のステップ
この作品に興味を持たれた方は、以下の資料もご参照ください: