エラー軽減オプションをEstimatorプリミティブと組み合わせる
推定所要時間:Heron r2 プロセッサで 7 分(注:これはあくまで目安です。 (実行時間は異なる場合があります。)
学習成果
このチュートリアルを進める前に、以下の内容についてあらかじめ理解しておいていただくことをお勧めします:
- 本ガイドで解説されている、動的デカップリング、測定誤差の低減、ゲート・トゥワーリング、およびゼロノイズ外挿法の基礎。
前提条件
このチュートリアルを終えた後、ユーザーは以下の点を理解できるようになります:
- 前述のエラー軽減手法が、ハードウェア上でどのように選択的に実装されるか。
- ハードウェアノイズを低減する能力の点で、両者はどのように比較されるか。
背景
このチュートリアルでは、 IBM Quantum 推定器プリミティブで利用可能なエラー抑制およびエラー軽減のオプションについて解説します。 このチュートリアルでは、以下の各メソッドを個別に実装する方法について説明します:
- ダイナミック・デカップリング
- 測定誤差の軽減
- ゲート回転
- ゼロノイズ外挿(ZNE)
なお、これらの手法を個別に実装する代わりに、 レジリエンスレベル (値は0、1、2をとる resilience_level )を用いて実装することも可能です:
- 0:緩和策は実施されていない。
- 1:測定誤差の低減が実施されています。
- 2:ゲートの回転、測定誤差の低減、およびZNEが実装されています。
このチュートリアルでは、回路と観測可能変数を構築し、エラー軽減設定のさまざまな組み合わせを用いて、Estimatorプリミティブを使用してジョブを提出します。 次に、結果をグラフ化して、さまざまな設定による影響を確認します。 このチュートリアルの大部分では、視覚化を容易にするために10キュービットの回路を使用していますが、最後にはワークフローを50キュービットに拡張します。
要件
このチュートリアルを始める前に、以下のものがインストールされていることを確認してください:
- Qiskit SDK v2.1 またはそれ以降、 可視化サポート付き
- Qiskit Runtime v0.40 またはそれ以降 (
pip install qiskit-ibm-runtime)
セットアップ
import matplotlib.pyplot as plt
import numpy as np
from qiskit.circuit.library import efficient_su2, unitary_overlap
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Batch, EstimatorV2 as Estimator小規模シミュレータの例
シミュレータでは実行時エラーの緩和機能がサポートされていないため、この手順は省略します。
ハードウェアの例
ステップ1:古典的な入力を量子問題にマッピングする
このウォークスルーでは、古典的な問題が既に量子化されていることを前提としています。 まず、回路と観測可能な量を構築して測定することから始めます。 ここで使用される技術は様々な種類の回路に適用可能ですが、簡便化のため、このチュートリアルではQiskit回路ライブラリに含まれる回路 efficient_su2 を使用します。
efficient_su2 これは、量子ビット間の接続性が限られている量子ハードウェア上でも効率的に実行できるように設計されたパラメータ化量子回路であり、最適化や化学といった応用分野の問題を解決するのに十分な表現力を備えています。 これは、パラメータ化された単一量子ビットゲートの層と、2量子ビットゲートの固定パターンを含む層を交互に重ね、所定の回数繰り返すことで構築される。 2量子ビットゲートのパターンは、ユーザーが指定することができます。 ここでは、2量子ビットゲートを可能な限り高密度に配置することで回路の深さを最小限に抑えられるため、組み込みの pairwise パターンを使用できます。 このパターンは、線形量子ビット接続のみを用いて実行可能です。
n_qubits = 10
reps = 1
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
circuit.decompose().draw("mpl", scale=0.7)Output:
観測量として、最後の量子ビットに作用するパウリ演算子 、すなわち を採用しましょう。 なお、最後の量子ビットがこの文字列の最初の要素に対応しているのは、Qiskitがリトルエンディアン表記を採用しているためです。
# Z on the last qubit (index -1) with coefficient 1.0
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)ここまで来たら、回路を動作させて観測量を測定することができます。 しかし、量子デバイスの出力を正解、つまり回路がエラーなく実行された場合の観測量の理論値と比較することも必要です。 小規模な量子回路の場合、この値は古典コンピュータ上で回路をシミュレーションすることで計算できますが、より大規模な実用レベルの回路では、これは不可能です。 この問題は、「ミラー回路」という手法(「コンピュート・アンコンピュート」とも呼ばれる)を用いて回避することができ、量子デバイスの性能評価を行う際に有用です。
ミラー回路
ミラー回路技術では、回路の各ゲートを逆順に反転させることで形成される逆回路と回路を連結する。 結果として得られる回路は恒等演算子を実装しており、これは些細なことでシミュレートできる。 元の回路の構造はミラー回路でも維持されるため、ミラー回路を実行しても、量子デバイスが元の回路でどのように動作するかを知ることができる。
次のコードセルは、回路にランダムなパラメータを割り当て、その後、unitary_overlap class を使用してミラー回路を構築します。 回路をミラーリングする前に、 バリア命令を追加してください。これにより、トランスパイラがバリアの両側にある回路の2つの部分を結合して、ゲートが1つも存在しないトランスパイル済み回路が生成されるのを防ぐことができます。
# Generate random parameters
rng = np.random.default_rng(1234)
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
# Assign the parameters to the circuit
assigned_circuit = circuit.assign_parameters(params)
# Add a barrier to prevent circuit optimization of mirrored operators
assigned_circuit.barrier()
# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)
mirror_circuit.decompose().draw("mpl", scale=0.7)Output:
ステップ2:量子ハードウェア実行に向けた問題の最適化
ハードウェア上で動作させる前に、回路を最適化する必要があります。 このプロセスにはいくつかのステップがある:
- 回路の仮想量子ビットをハードウェア上の物理量子ビットにマッピングする量子ビットレイアウトを選ぶ。
- 接続されていない量子ビット間の相互作用をルーティングするために、必要に応じてスワップゲートを挿入する。
- 回路内のゲートを、ハードウェア上で直接実行できる命令セット・アーキテクチャ(ISA) 命令に変換します。
- 回路の最適化を行い、回路の深さとゲート数を最小化する。
Qiskitに組み込まれたトランスパイラは、これらの手順をすべて自動的に実行できます。 この例ではハードウェア効率の良い回路を使用しているため、トランスパイラは相互作用のルーティングにスワップゲートを挿入する必要がない量子ビット配置を選択できるはずです。
回路を最適化する前に、使用するハードウェアデバイスを選択する必要があります。 次のコードセルは、127キュービット以上を持ち、かつ最も使用率が低いデバイスを要求します。
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)print(backend)Output:
<IBMBackend('ibm_fez')>
パスマネージャーを作成し、そのパスマネージャーを回路に対して実行することで、回路を選択したバックエンドにトランスパイルすることができます。 パスマネージャーを作成する簡単な方法は、関数 generate_preset_pass_manager を使用することです。 パスマネージャーを使用したトランスパイルの詳細については、「パスマネージャーを使用したトランスパイル」を参照してください。
pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=1234
)
isa_circuit = pass_manager.run(mirror_circuit)
isa_circuit.draw("mpl", idle_wires=False, scale=0.7, fold=-1)Output:
トランスパイルされた回路には、現在、ISA命令のみが含まれています。 すべてのゲートは、 ゲート、 回転、 およびCZゲートに分解されています。
トランスパイルプロセスは、回路の仮想量子ビットをハードウェア上の物理量子ビットにマッピングした。 量子ビットのレイアウトに関する情報は、トランスパイル回路の layout 。 observableも仮想量子ビットで定義されているので、このレイアウトをobservableに適用する必要があります。 apply_layoutSparsePauliOp メソッドで行うことができます。
isa_observable = observable.apply_layout(isa_circuit.layout)
print("Original observable:")
print(observable)
print()
print("Observable with layout applied:")
print(isa_observable)Output:
Original observable:
SparsePauliOp(['ZIIIIIIIII'],
coeffs=[1.+0.j])
Observable with layout applied:
SparsePauliOp(['IIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])
ステップ3: Qiskit primitivesを使用して実行する
これで、Estimatorプリミティブを使用して回路を実行する準備が整いました。
ここでは、5つのジョブを個別に実行します。最初はエラーの抑制や軽減を行わず、その後、 IBM Quantum Compute Service で利用可能なさまざまなエラー抑制および軽減オプションを順次有効にしていきます。 各オプションの詳細については、以下のページを参照してください:
これらのジョブは互いに独立して実行できるため、 バッチモードを使用することで、Quantum Compute に実行タイミングの最適化を任せることができます。
pub = (isa_circuit, isa_observable)
jobs = []
with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_SS"
] # add tag for this small scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0
# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)
# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)
# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)
# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)
# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)ステップ4:後処理を行い、結果を希望の古典形式で返す
最後に、データを分析することができる。 ここでは、ジョブ結果を取得し、そこから測定された期待値を抽出し、1標準偏差のエラーバーを含む値をプロットします。
# Retrieve the job results
results = [job.result() for job in jobs]
# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]
# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)
# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")
plt.show()Output:
この小さなスケールでは、ほとんどのエラー軽減技術の効果を見ることは難しいが、ゼロノイズ外挿は顕著な改善をもたらす。 しかし、ZNEの結果はエラーバーも大きいため、この改善はタダではないことに注意されたい。
大規模なハードウェアの例
実験を開発する際には、視覚化やシミュレーションを容易にするため、小さな回路から始めることが有用である。 10量子ビット回路でワークフローを開発・テストした今、これを50量子ビットまでスケールアップできます。 以下のコードセルは、このチュートリアルの全手順を繰り返しますが、今回は50量子ビット回路に適用します。
n_qubits = 50
reps = 1
# Construct circuit and observable
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)
# Assign parameters to circuit
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
assigned_circuit = circuit.assign_parameters(params)
assigned_circuit.barrier()
# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)
# Transpile circuit and observable
isa_circuit = pass_manager.run(mirror_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
# Run jobs
pub = (isa_circuit, isa_observable)
jobs = []
with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_LS"
] # add tag for this large scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0
# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)
# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)
# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)
# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)
# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)
# Retrieve the job results
results = [job.result() for job in jobs]
# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]
# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)
# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")
plt.show()Output:
50qubitの結果を先ほどの10qubitの結果と比較すると、以下のことに気づくかもしれない(結果は実行によって異なるかもしれない):
- すべての実験で、結果は理想値により近づき、すべての誤差棒は小さくなっている。
- 動的デカップリングを追加したことで、緩和策を講じなかった場合と比べてパフォーマンスが低下した可能性がある。 回路の配置が非常に密集しているため、これは驚くことではない。 動的デカップリングは、回路内に大きなギャップが存在し、その間クビットにゲートが適用されずに待機している場合に、特に有用である。 こうしたギャップが存在しない場合、動的デカップリングは効果を発揮せず、むしろ動的デカップリングパルス自体の誤差によって、パフォーマンスが低下してしまう可能性があります。 10キュービットの回路では、この効果を観測するには規模が小さすぎたのかもしれない。
- ゼロノイズ外挿を用いると、結果は理想値に非常に近くなります。 これはZNEの威力を示しています。
次のステップ
この内容に興味を持たれた方は、本チュートリアルでは触れられなかった、その他のエラー軽減およびエラー抑制の手法に関する以下の資料もご参照ください:
- 確率的誤差相殺(PEC)
- 行列を使用しない測定誤差の低減( M3 ) (「 M3 を使用したSamplerプリミティブの読み出し誤差低減 」チュートリアルも参照)
- 陰影付きの光錐
- 増幅型吸音(PNA)