メインコンテンツへスキップ

トランスパイラーのステージ

Package versions

The code on this page was developed using the following requirements. We recommend using these versions or newer.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1

このページでは、Qiskit SDK に組み込まれたトランスパイルパイプラインのステージについて説明します。ステージは全部で六つあります。

  1. init
  2. layout
  3. routing
  4. translation
  5. optimization
  6. scheduling

generate_preset_pass_manager 関数は、これらのステージから構成されるプリセットのステージ付きパスマネージャーを生成します。各ステージを構成する具体的なパスは、generate_preset_pass_manager に渡す引数によって異なります。optimization_level は必須の位置引数であり、0、1、2、3 のいずれかの整数で指定します。値が大きいほど最適化が強力になりますが、計算コストも高くなります(トランスパイルのデフォルト設定と構成オプションを参照)。

回路をトランスパイルする推奨の方法は、プリセットのステージ付きパスマネージャーを作成し、そのパスマネージャーを回路に対して実行することです。詳細はパスマネージャーによるトランスパイルを参照してください。ただし、カスタマイズ性は低くなりますが、よりシンプルな代替手段として transpile 関数を使用する方法もあります。この関数は回路を直接引数として受け取ります。generate_preset_pass_manager と同様に、使用されるトランスパイラーパスは optimization_level などの引数によって決まります。実際、内部では transpile 関数が generate_preset_pass_manager を呼び出してプリセットのステージ付きパスマネージャーを生成し、それを回路に対して実行しています。

Init ステージ

最初のステージは、デフォルトではほとんど何も行いません。独自の初期最適化を追加したい場合に主に活用されます。また、多くのレイアウトおよびルーティングアルゴリズムは単一・二量子ビットゲートのみを対象として設計されているため、このステージでは三量子ビット以上のゲートを一・二量子ビットゲートに変換する処理も行われます。

このステージに独自の初期最適化を実装する方法については、プラグインおよびパスマネージャーのカスタマイズに関するセクションを参照してください。

Layout ステージ

次のステージは、回路を送信するバックエンドのレイアウト(接続構造)に関するものです。一般に、量子回路は抽象的な存在であり、その量子ビットは実際の計算に使用される物理的な量子ビットの「仮想」または「論理的」な表現です。ゲートのシーケンスを実行するには、「仮想」量子ビットと実際の量子デバイスの「物理」量子ビットとの一対一の対応付けが必要です。このマッピングは Layout オブジェクトとして保持され、バックエンドの命令セットアーキテクチャ(ISA)で定義される制約の一部です。

この画像は、ワイヤー表現の量子ビットが QPU 上の接続を表す図にマッピングされる様子を示しています。

マッピングの選択は、入力回路をデバイスのトポロジーにマッピングするために必要な SWAP 操作の数を最小化し、最もキャリブレーションの良い量子ビットを使用するうえで非常に重要です。このステージの重要性から、プリセットパスマネージャーは最適なレイアウトを見つけるためにいくつかの異なる手法を試みます。通常、これは二つのステップで構成されます。まず「完全な」レイアウト(SWAP 操作が不要なレイアウト)を見つけようとし、次に完全なレイアウトが見つからない場合に最適なレイアウトを探すヒューリスティックパスを実行します。最初のステップには、通常以下の二つの Pass が使用されます。

  • TrivialLayout: 各仮想量子ビットをデバイスの同じ番号の物理量子ビットに単純にマッピングします(例:[0,1,1,3] -> [0,1,1,3])。これは optimization_level=1 でのみ完全なレイアウト探索に使用される歴史的な動作です。失敗した場合は次に VF2Layout が試みられます。
  • VF2Layout: このステージをサブグラフ同型問題として扱い、VF2++ アルゴリズムで解く AnalysisPass です。複数のレイアウトが見つかった場合は、スコアリングヒューリスティックを実行して平均エラーが最も低いマッピングを選択します。

ヒューリスティックステージでは、デフォルトで以下の二つのパスが使用されます。

  • DenseLayout: 回路と同じ量子ビット数を持つデバイスのサブグラフのうち、接続性が最も高いものを探します(回路に IfElseOp などの制御フロー操作が含まれている場合、最適化レベル 1 で使用されます)。
  • SabreLayout: ランダムな初期レイアウトから出発し、SabreSwap アルゴリズムを繰り返し実行することでレイアウトを選択するパスです。VF2Layout パスで完全なレイアウトが見つからない場合に、最適化レベル 1、2、3 でのみ使用されます。このアルゴリズムの詳細については、論文 arXiv:1809.02573 を参照してください。

Routing ステージ

量子デバイス上で直接接続されていない量子ビット間の二量子ビットゲートを実行するには、回路に一つ以上の SWAP ゲートを挿入して、量子ビットの状態をデバイスのゲートマップ上で隣接するまで移動させる必要があります。各 SWAP ゲートは高コストでノイズの多い操作です。したがって、回路を所定のデバイスにマッピングするために必要な SWAP ゲートの最小数を求めることは、トランスパイルプロセスにおける重要なステップです。効率化のため、このステージはデフォルトで Layout ステージと並行して計算されますが、論理的には両者は独立しています。Layout ステージは使用するハードウェア量子ビットを選択し、Routing ステージは選択したレイアウトで回路を実行するために必要な SWAP ゲートを適切な数だけ挿入します。

しかし、最適な SWAP マッピングを求めることは困難です。実際、これは NP 困難問題であり、ごく小さな量子デバイスや入力回路を除いて計算コストが非常に高くなります。これを回避するため、Qiskit では SabreSwap という確率的なヒューリスティックアルゴリズムを使用して、最適ではないかもしれませんが良質な SWAP マッピングを計算します。確率的な手法を使用するため、生成される回路は実行ごとに同一になるとは限りません。実際、同じ回路を繰り返し実行すると、出力の回路深さとゲート数が分布として現れます。そのため、多くのユーザーはルーティング関数(または StagedPassManager 全体)を何度も実行し、出力の分布から最も深さの小さい回路を選択することを選びます。

例として、15 量子ビットの GHZ 回路を、「悪い」(非接続な)initial_layout を使用して 100 回実行してみましょう。

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib qiskit qiskit-ibm-runtime
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit_ibm_runtime.fake_provider import FakeAuckland, FakeWashingtonV2
from qiskit.transpiler import generate_preset_pass_manager

backend = FakeAuckland()

ghz = QuantumCircuit(15)
ghz.h(0)
ghz.cx(0, range(1, 15))

depths = []
for seed in range(100):
pass_manager = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
layout_method="trivial", # Fixed layout mapped in circuit order
seed_transpiler=seed, # For reproducible results
)
depths.append(pass_manager.run(ghz).depth())

plt.figure(figsize=(8, 6))
plt.hist(depths, align="left", color="#AC557C")
plt.xlabel("Depth", fontsize=14)
plt.ylabel("Counts", fontsize=14)
Text(0, 0.5, 'Counts')

Output of the previous code cell

この幅広い分布は、SWAP マッパーが最適なマッピングを計算することがいかに難しいかを示しています。より深く理解するために、実行される回路とハードウェア上で選ばれた量子ビットの両方を確認してみましょう。

ghz.draw("mpl", idle_wires=False)

Output of the previous code cell

from qiskit.visualization import plot_circuit_layout

# Plot the hardware graph and indicate which hardware qubits were chosen to run the circuit
transpiled_circ = pass_manager.run(ghz)
plot_circuit_layout(transpiled_circ, backend)

Output of the previous code cell

ご覧のとおり、この回路は接続グラフ上で非常に遠い位置にある量子ビット 0 と 14 の間で二量子ビットゲートを実行する必要があります。そのため、この回路を実行するには SabreSwap パスですべての二量子ビットゲートを実行できるよう SWAP ゲートを挿入する必要があります。

なお、SabreSwap アルゴリズムは前のステージで使用した SabreLayout メソッドとは異なるものであることにも注意してください。デフォルトでは、SabreLayout はレイアウトとルーティングの両方を実行し、変換後の回路を返します。これはパスの API リファレンスページに記載されているいくつかの技術的な理由によるものです。

Translation ステージ

量子回路を記述する際は、任意の量子ゲート(ユニタリ操作)を自由に使用できます。また、量子ビットの測定やリセット命令などのゲート以外の操作も利用できます。ただし、ほとんどの量子デバイスがネイティブにサポートするのは、ごく少数の量子ゲートおよびゲート以外の操作に限られています。これらのネイティブゲートはターゲットの ISA の定義の一部であり、このプリセット PassManagers のステージは、回路に指定されたゲートを指定されたバックエンドのネイティブ基底ゲートに変換(アンロール)します。これは回路をバックエンドで実行するための重要なステップですが、通常は深さとゲート数の増加を招きます。

特に重要な二つの特殊なケースを取り上げて、このステージが何を行うかを説明します。

  1. SWAP ゲートがターゲットバックエンドのネイティブゲートでない場合、三つの CNOT ゲートが必要です。
print("native gates:" + str(sorted(backend.operation_names)))
qc = QuantumCircuit(2)
qc.swap(0, 1)
qc.decompose().draw("mpl")
native gates:['cx', 'delay', 'for_loop', 'id', 'if_else', 'measure', 'reset', 'rz', 'switch_case', 'sx', 'x']

Output of the previous code cell

SWAP は三つの CNOT ゲートの積であるため、ノイズの多い量子デバイスでは高コストな操作です。しかし、多くのデバイスが持つ限られたゲート接続性に回路を埋め込むためには、通常こうした操作が必要になります。したがって、回路内の SWAP ゲートの数を最小化することがトランスパイルプロセスの主要な目標の一つです。

  1. Toffoli ゲート、すなわち controlled-controlled-not ゲート(ccx)は三量子ビットゲートです。基底ゲートセットに単一・二量子ビットゲートしか含まれていない場合、このゲートを分解する必要があります。ただし、そのコストはかなり大きくなります。
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.decompose().draw("mpl")

Output of the previous code cell

量子回路内の Toffoli ゲート一つにつき、ハードウェアは最大で六つの CNOT ゲートといくつかの単一量子ビットゲートを実行する可能性があります。この例は、複数の Toffoli ゲートを使用するアルゴリズムは深さの大きな回路になり、ノイズの影響を大きく受けることを示しています。

Optimization ステージ

このステージは、量子回路をターゲットデバイスの基底ゲートセットに分解することを中心としており、Layout ステージと Routing ステージによる深さの増加に対抗する必要があります。幸い、ゲートを結合または除去することで回路を最適化する手法が数多く存在します。場合によっては、これらの手法が非常に効果的であり、レイアウトおよびルーティング後でも出力回路の深さが入力より小さくなることがあります。一方、最適化の余地が少なくノイズの多いデバイスでの実行が困難なケースもあります。このステージで各最適化レベルの違いが現れ始めます。

さらに、このステージでは回路内のすべての命令がターゲットバックエンドで利用可能な基底ゲートで構成されているかどうかを確認する最終チェックも実行されます。

以下に示す GHZ 状態の例は、最適化レベルの設定が回路深さとゲート数に与える影響を示しています。

備考

トランスパイルの出力は確率的な SWAP マッパーによって異なります。したがって、以下の数値はコードを実行するたびに変わる可能性があります。

15 量子ビットの GHZ 状態

以下のコードは 15 量子ビットの GHZ 状態を構築し、トランスパイルの optimization_level ごとに、結果として得られる回路の深さ、ゲート数、マルチ量子ビットゲート数を比較しています。

ghz = QuantumCircuit(15)
ghz.h(0)
ghz.cx(0, range(1, 15))

depths = []
gate_counts = []
multiqubit_gate_counts = []
levels = [str(x) for x in range(4)]
for level in range(4):
pass_manager = generate_preset_pass_manager(
optimization_level=level,
backend=backend,
seed_transpiler=1234,
)
circ = pass_manager.run(ghz)
depths.append(circ.depth())
gate_counts.append(sum(circ.count_ops().values()))
multiqubit_gate_counts.append(circ.count_ops()["cx"])

fig, (ax1, ax2) = plt.subplots(2, 1)
ax1.bar(levels, depths, label="Depth")
ax1.set_xlabel("Optimization Level")
ax1.set_ylabel("Depth")
ax1.set_title("Output Circuit Depth")
ax2.bar(levels, gate_counts, label="Number of Circuit Operations")
ax2.bar(levels, multiqubit_gate_counts, label="Number of CX gates")
ax2.set_xlabel("Optimization Level")
ax2.set_ylabel("Number of gates")
ax2.legend()
ax2.set_title("Number of output circuit gates")
fig.tight_layout()
plt.show()

Output of the previous code cell

Scheduling ステージ

この最後のステージは、明示的に呼び出された場合にのみ実行されます(Init ステージと同様)。デフォルトでは実行されません(ただし、generate_preset_pass_manager を呼び出す際に scheduling_method 引数を設定することでメソッドを指定できます)。Scheduling ステージは通常、回路がターゲット基底ゲートに変換され、デバイスにマッピングされ、最適化された後に使用されます。これらのパスは、回路内のアイドル時間すべてを考慮することに焦点を当てています。大まかに言えば、スケジューリングパスはゲート実行間のアイドル時間を考慮するための delay 命令を明示的に挿入し、回路がバックエンド上でどれくらいの時間実行されるかを検査するものと考えることができます。

以下に例を示します。

ghz = QuantumCircuit(5)
ghz.h(0)
ghz.cx(0, range(1, 5))

# Use fake backend
backend = FakeWashingtonV2()

# Run with optimization level 3 and 'asap' scheduling pass
pass_manager = generate_preset_pass_manager(
optimization_level=3,
backend=backend,
scheduling_method="asap",
seed_transpiler=1234,
)

circ = pass_manager.run(ghz)
circ.draw(output="mpl", idle_wires=False)

Output of the previous code cell

遅延命令を持つ回路

トランスパイラーは各量子ビットのアイドル時間を考慮するために Delay 命令を挿入しました。回路のタイミングをより詳しく把握するために、timeline.draw() 関数で表示することもできます。

同じ回路の timeline.draw() ビュー

回路のスケジューリングは、分析と制約マッピング、そしてパディングパスという二つの部分から成り立っています。最初の部分では、スケジューリング分析パスを実行します(デフォルトでは ALAPSchedulingAnalysis)。このパスは回路を解析し、回路内の各命令の開始時刻をスケジュールに記録します。回路の初期スケジュールが得られたら、ターゲットバックエンドのタイミング制約を考慮するために追加のパスを実行できます。最後に、PadDelayPadDynamicalDecoupling などのパディングパスを実行します。

次のステップ

Recommendations